1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #![deny(missing_docs)]
6
7 use std::fs::File;
8 use std::fs::OpenOptions;
9 use std::io::Write;
10 use std::os::unix::io::AsRawFd;
11 use std::path::Path;
12
13 use base::error;
14 use base::RawDescriptor;
15 use sync::Mutex;
16
17 static TRACE_MARKER_FILE: Mutex<Option<File>> = Mutex::new(None);
18
19 #[macro_export]
20 /// This macro is used as a placeholder to let us iterate over the compile-time
21 /// allocated vector of categories when we statically initialize it.
22 /// This macro should only be used internally to this crate.
23 macro_rules! zero_internal {
24 ($x:ident) => {
25 0
26 };
27 }
28
29 #[macro_export]
30 /// This macro expands an expression with its value for easier printing.
31 /// `expand_fmt_internal!(my_var)` becomes `"(my_var: {:?})"`.
32 macro_rules! expand_fmt_internal {
33 ($x:expr) => {
34 std::concat!("(", std::stringify!($x), ": {:?})")
35 };
36 }
37
38 #[macro_export]
39 /// Macro used to handle fd permanency across jailed devices.
40 /// If we run crosvm without `--disable-sandbox`, we need to add the `trace_marker`
41 /// file descriptor to the list of file descriptors allowed to be accessed by the
42 /// sandboxed process. We call this macro to add the file descriptor to the list
43 /// of `keep_rds` that the process is allowed to access every time we jail.
44 macro_rules! push_descriptors {
45 ($fd_vec:expr) => {
46 $crate::push_descriptors_internal($fd_vec);
47 };
48 }
49
50 #[macro_export]
51 /// Prints a single non-scoped message without creating a trace context.
52 /// The tagged variant lets us enable or disable individual categories.
53 macro_rules! trace_simple_print {
54 ($category: ident, $($t:tt)+) => {{
55 if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {
56 $crate::trace_simple_print!($($t)*);
57 }
58 }};
59 ($($t:tt)*) => {{
60 $crate::trace_simple_print_internal(std::format!($($t)*));
61 }};
62 }
63
64 /// Platform-specific implementation of the `push_descriptors!` macro. If the
65 /// `trace_marker` file has been initialized properly, it adds its file descriptor
66 /// to the list of file descriptors that are allowed to be accessed when the process
67 /// is jailed in the sandbox.
68 ///
69 /// # Arguments
70 ///
71 /// * `keep_rds` - List of file descriptors that will be accessible after jailing
push_descriptors_internal(keep_rds: &mut Vec<RawDescriptor>)72 pub fn push_descriptors_internal(keep_rds: &mut Vec<RawDescriptor>) {
73 if let Some(file) = TRACE_MARKER_FILE.lock().as_ref() {
74 let fd = file.as_raw_fd();
75 if !keep_rds.contains(&fd) {
76 keep_rds.push(fd);
77 }
78 }
79 }
80
81 #[macro_export]
82 /// Macro used to set up the trace environment categories. It takes a variable
83 /// number of arguments in pairs of category, boolean value on whether or not the
84 /// tracing category is enabled at compile time.
85 ///
86 /// # Example usage
87 ///
88 /// ```ignore
89 /// setup_trace_marker!(
90 /// (Category1, true),
91 /// (Category2, false)
92 /// );
93 /// ```
94 ///
95 /// Categories that are enabled will have their events traced at runtime via
96 /// `trace_event_begin!()`, `trace_event_end!()`, or `trace_event!()` scoped tracing.
97 /// The categories that are marked as false will have their events skipped.
98 macro_rules! setup_trace_marker {
99 ($(($cat:ident, $enabled:literal)),+) => {
100 #[allow(non_camel_case_types, missing_docs)]
101 /// The tracing categories that the trace_marker backend supports.
102 pub enum TracedCategories {
103 $($cat,)+
104 /// Hacky way to get the count of how many tracing categories we have in total.
105 CATEGORY_COUNT,
106 }
107
108 /// Vector counting how many tracing event contexts are running for each category.
109 pub static CATEGORY_COUNTER: [std::sync::atomic::AtomicI64; TracedCategories::CATEGORY_COUNT as usize] = [
110 $(
111 // Note, we pass $cat to the zero_internal! macro here, which always just returns
112 // 0, because it's impossible to iterate over $cat unless $cat is used.
113 std::sync::atomic::AtomicI64::new(zero_internal!($cat)),
114 )+
115 ];
116
117 /// Vector used to test if a category is enabled or not for tracing.
118 pub static ENABLED_CATEGORIES: [std::sync::atomic::AtomicBool; TracedCategories::CATEGORY_COUNT as usize] = [
119 $(
120 std::sync::atomic::AtomicBool::new($enabled),
121 )+
122 ];
123
124 /// Sequential identifier for scoped trace events. This unique identifier is incremented
125 /// for each new `trace_event()` call.
126 pub static EVENT_COUNTER: std::sync::atomic::AtomicU64 =
127 std::sync::atomic::AtomicU64::new(0);
128 }
129 }
130
131 #[macro_export]
132 /// Returns a Trace object with a new name and index and traces its enter state, if the
133 /// given category identifier is enabled. Extra args can be provided for easier debugging.
134 /// Upon exiting its scope, it is automatically collected and its exit gets traced.
135 ///
136 /// If the category identifier is not enabled for this event, nothing happens and the trace
137 /// event is skipped.
138 ///
139 /// # Example usage
140 ///
141 /// ```ignore
142 /// {
143 /// let _trace = trace_event!(Category, "exec", param1, param2);
144 ///
145 /// // ... Rest of code ...
146 ///
147 /// // End of `_trace`'s lifetime so `trace_event_end` is called.
148 /// }
149 /// ```
150 ///
151 /// This will output in `trace_marker`:
152 ///
153 /// - `$uid: $category Enter: exec - (param1: ...)(param2: ...)`
154 ///
155 /// and when _trace runs out of scope, it will output:
156 ///
157 /// - `$uid: Exit: exec`
158 ///
159 /// where `$uid` will be the same unique value across those two events.
160 macro_rules! trace_event {
161 ($category:ident, $name:literal, $($arg:expr),+) => {{
162 if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {
163 $crate::trace_event_begin!($category);
164 let index = $crate::EVENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
165 $crate::trace_simple_print!($category,
166 "{} {} Enter: {} - {}",
167 index,
168 std::stringify!($category),
169 $name,
170 // Creates a formatted list for each argument and their
171 // values.
172 std::format_args!(std::concat!($($crate::expand_fmt_internal!($arg),)*), $($arg),*));
173 Some($crate::Trace::new(index, $name, std::stringify!($category), $crate::TracedCategories::$category as usize))
174 } else {
175 None
176 }
177 }};
178 ($category:ident, $name:expr) => {{
179 if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {
180 $crate::trace_event_begin!($category);
181 let index = $crate::EVENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
182 $crate::trace_simple_print!($category,
183 "{} {} Enter: {}", index, std::stringify!($category), $name);
184 Some($crate::Trace::new(index, $name, std::stringify!($category), $crate::TracedCategories::$category as usize))
185 } else {
186 None
187 }
188 }};
189 }
190
191 #[macro_export]
192 /// Begins a tracing event context in the given category, it increases the counter
193 /// of the currently traced events for that category by one.
194 ///
195 /// # Arguments
196 ///
197 /// * `category` - Identifier name of the category.
198 macro_rules! trace_event_begin {
199 ($category:ident) => {
200 $crate::CATEGORY_COUNTER[$crate::TracedCategories::$category as usize]
201 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
202 };
203 }
204
205 #[macro_export]
206 /// Ends a tracing event context in the given category, it decreases the counter
207 /// of the currently traced events for that category by one.
208 ///
209 /// # Arguments
210 ///
211 /// * `category` - Identifier name of the category.
212 macro_rules! trace_event_end {
213 ($category:ident) => {
214 if ($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize]
215 .load(std::sync::atomic::Ordering::Relaxed))
216 {
217 $crate::CATEGORY_COUNTER[$crate::TracedCategories::$category as usize]
218 .fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
219 }
220 };
221 ($category_id:expr) => {
222 if ($crate::ENABLED_CATEGORIES[$category_id as usize]
223 .load(std::sync::atomic::Ordering::Relaxed))
224 {
225 $crate::CATEGORY_COUNTER[$category_id as usize]
226 .fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
227 }
228 };
229 }
230
231 // List of categories that can be enabled.
232 // If a category is marked as disabled here, no events will be processed for it.
233 setup_trace_marker!(
234 (VirtioFs, true),
235 (VirtioNet, true),
236 (USB, true),
237 (gpu_display, true),
238 (VirtioBlk, true),
239 (VirtioScsi, true)
240 );
241
242 /// Platform-specific implementation of the `trace_simple_print!` macro. If tracing
243 /// is enabled on the system, it writes the given message to the `trace_marker` file.
244 ///
245 /// # Arguments
246 ///
247 /// * `message` - The message to be written
trace_simple_print_internal(message: String)248 pub fn trace_simple_print_internal(message: String) {
249 // In case tracing is not working or the trace marker file is None we can
250 // just ignore this. We don't need to handle the error here.
251 if let Some(file) = TRACE_MARKER_FILE.lock().as_mut() {
252 // We ignore the error here in case write!() fails, because the trace
253 // marker file would be normally closed by the system unless we are
254 // actively tracing the runtime. It is not an error.
255 let _ = write!(file, "{}", message);
256 };
257 }
258
259 /// Initializes the trace_marker backend. It attepts to open the `trace_marker`
260 /// file and keep a reference that can be shared throughout the lifetime of the
261 /// crosvm process.
262 ///
263 /// If tracefs is not available on the system or the file cannot be opened,
264 /// tracing will not work but the crosvm process will still continue execution
265 /// without tracing.
init()266 pub fn init() {
267 let mut trace_marker_file = TRACE_MARKER_FILE.lock();
268 if trace_marker_file.is_some() {
269 return;
270 }
271
272 let path = Path::new("/sys/kernel/tracing/trace_marker");
273 let file = match OpenOptions::new().read(false).write(true).open(path) {
274 Ok(f) => f,
275 Err(e) => {
276 error!(
277 "Failed to open {}: {}. Tracing will not work.",
278 path.display(),
279 e
280 );
281 return;
282 }
283 };
284
285 *trace_marker_file = Some(file);
286 }
287
288 /// A trace context obtained from a `trace_event!()` call.
289 pub struct Trace {
290 /// Unique identifier for the specific event.
291 identifier: u64,
292 /// Name of the trace event.
293 name: String,
294 /// Category name to which the event belongs.
295 category: String,
296 /// Category ID to which the event belongs.
297 category_id: usize,
298 }
299
300 impl Trace {
301 /// Returns a Trace object with the given name, id, and category
new(identifier: u64, name: &str, category: &str, category_id: usize) -> Self302 pub fn new(identifier: u64, name: &str, category: &str, category_id: usize) -> Self {
303 Trace {
304 identifier,
305 name: name.to_string(),
306 category: category.to_string(),
307 category_id,
308 }
309 }
310 }
311
312 impl Drop for Trace {
drop(&mut self)313 fn drop(&mut self) {
314 trace_simple_print!("{} {} Exit: {}", self.identifier, self.category, self.name);
315 trace_event_end!(self.category_id);
316 }
317 }
318