// Copyright 2022 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34#![deny(missing_docs)]56use std::fs::File;7use std::fs::OpenOptions;8use std::io::Write;9use std::os::unix::io::AsRawFd;10use std::path::Path;1112use base::error;13use base::RawDescriptor;14use sync::Mutex;1516static TRACE_MARKER_FILE: Mutex<Option<File>> = Mutex::new(None);1718#[macro_export]19/// This macro is used as a placeholder to let us iterate over the compile-time20/// allocated vector of categories when we statically initialize it.21/// This macro should only be used internally to this crate.22macro_rules! zero_internal {23($x:ident) => {24025};26}2728#[macro_export]29/// This macro expands an expression with its value for easier printing.30/// `expand_fmt_internal!(my_var)` becomes `"(my_var: {:?})"`.31macro_rules! expand_fmt_internal {32($x:expr) => {33std::concat!("(", std::stringify!($x), ": {:?})")34};35}3637#[macro_export]38/// Macro used to handle fd permanency across jailed devices.39/// If we run crosvm without `--disable-sandbox`, we need to add the `trace_marker`40/// file descriptor to the list of file descriptors allowed to be accessed by the41/// sandboxed process. We call this macro to add the file descriptor to the list42/// of `keep_rds` that the process is allowed to access every time we jail.43macro_rules! push_descriptors {44($fd_vec:expr) => {45$crate::push_descriptors_internal($fd_vec);46};47}4849#[macro_export]50/// Prints a single non-scoped message without creating a trace context.51/// The tagged variant lets us enable or disable individual categories.52macro_rules! trace_simple_print {53($category: ident, $($t:tt)+) => {{54if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {55$crate::trace_simple_print!($($t)*);56}57}};58($($t:tt)*) => {{59$crate::trace_simple_print_internal(std::format!($($t)*));60}};61}6263/// Platform-specific implementation of the `push_descriptors!` macro. If the64/// `trace_marker` file has been initialized properly, it adds its file descriptor65/// to the list of file descriptors that are allowed to be accessed when the process66/// is jailed in the sandbox.67///68/// # Arguments69///70/// * `keep_rds` - List of file descriptors that will be accessible after jailing71pub fn push_descriptors_internal(keep_rds: &mut Vec<RawDescriptor>) {72if let Some(file) = TRACE_MARKER_FILE.lock().as_ref() {73let fd = file.as_raw_fd();74if !keep_rds.contains(&fd) {75keep_rds.push(fd);76}77}78}7980#[macro_export]81/// Macro used to set up the trace environment categories. It takes a variable82/// number of arguments in pairs of category, boolean value on whether or not the83/// tracing category is enabled at compile time.84///85/// # Example usage86///87/// ```ignore88/// setup_trace_marker!(89/// (Category1, true),90/// (Category2, false)91/// );92/// ```93///94/// Categories that are enabled will have their events traced at runtime via95/// `trace_event_begin!()`, `trace_event_end!()`, or `trace_event!()` scoped tracing.96/// The categories that are marked as false will have their events skipped.97macro_rules! setup_trace_marker {98($(($cat:ident, $enabled:literal)),+) => {99#[allow(non_camel_case_types, missing_docs)]100/// The tracing categories that the trace_marker backend supports.101pub enum TracedCategories {102$($cat,)+103/// Hacky way to get the count of how many tracing categories we have in total.104CATEGORY_COUNT,105}106107/// Vector counting how many tracing event contexts are running for each category.108pub static CATEGORY_COUNTER: [std::sync::atomic::AtomicI64; TracedCategories::CATEGORY_COUNT as usize] = [109$(110// Note, we pass $cat to the zero_internal! macro here, which always just returns111// 0, because it's impossible to iterate over $cat unless $cat is used.112std::sync::atomic::AtomicI64::new(zero_internal!($cat)),113)+114];115116/// Vector used to test if a category is enabled or not for tracing.117pub static ENABLED_CATEGORIES: [std::sync::atomic::AtomicBool; TracedCategories::CATEGORY_COUNT as usize] = [118$(119std::sync::atomic::AtomicBool::new($enabled),120)+121];122123/// Sequential identifier for scoped trace events. This unique identifier is incremented124/// for each new `trace_event()` call.125pub static EVENT_COUNTER: std::sync::atomic::AtomicU64 =126std::sync::atomic::AtomicU64::new(0);127}128}129130#[macro_export]131/// Returns a Trace object with a new name and index and traces its enter state, if the132/// given category identifier is enabled. Extra args can be provided for easier debugging.133/// Upon exiting its scope, it is automatically collected and its exit gets traced.134///135/// If the category identifier is not enabled for this event, nothing happens and the trace136/// event is skipped.137///138/// # Example usage139///140/// ```ignore141/// {142/// let _trace = trace_event!(Category, "exec", param1, param2);143///144/// // ... Rest of code ...145///146/// // End of `_trace`'s lifetime so `trace_event_end` is called.147/// }148/// ```149///150/// This will output in `trace_marker`:151///152/// - `$uid: $category Enter: exec - (param1: ...)(param2: ...)`153///154/// and when _trace runs out of scope, it will output:155///156/// - `$uid: Exit: exec`157///158/// where `$uid` will be the same unique value across those two events.159macro_rules! trace_event {160($category:ident, $name:literal, $($arg:expr),+) => {{161if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {162$crate::trace_event_begin!($category);163let index = $crate::EVENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);164$crate::trace_simple_print!($category,165"{} {} Enter: {} - {}",166index,167std::stringify!($category),168$name,169// Creates a formatted list for each argument and their170// values.171std::format_args!(std::concat!($($crate::expand_fmt_internal!($arg),)*), $($arg),*));172Some($crate::Trace::new(index, $name, std::stringify!($category), $crate::TracedCategories::$category as usize))173} else {174None175}176}};177($category:ident, $name:expr) => {{178if($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize].load(std::sync::atomic::Ordering::Relaxed)) {179$crate::trace_event_begin!($category);180let index = $crate::EVENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);181$crate::trace_simple_print!($category,182"{} {} Enter: {}", index, std::stringify!($category), $name);183Some($crate::Trace::new(index, $name, std::stringify!($category), $crate::TracedCategories::$category as usize))184} else {185None186}187}};188}189190#[macro_export]191/// Begins a tracing event context in the given category, it increases the counter192/// of the currently traced events for that category by one.193///194/// # Arguments195///196/// * `category` - Identifier name of the category.197macro_rules! trace_event_begin {198($category:ident) => {199$crate::CATEGORY_COUNTER[$crate::TracedCategories::$category as usize]200.fetch_add(1, std::sync::atomic::Ordering::SeqCst);201};202}203204#[macro_export]205/// Ends a tracing event context in the given category, it decreases the counter206/// of the currently traced events for that category by one.207///208/// # Arguments209///210/// * `category` - Identifier name of the category.211macro_rules! trace_event_end {212($category:ident) => {213if ($crate::ENABLED_CATEGORIES[$crate::TracedCategories::$category as usize]214.load(std::sync::atomic::Ordering::Relaxed))215{216$crate::CATEGORY_COUNTER[$crate::TracedCategories::$category as usize]217.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);218}219};220($category_id:expr) => {221if ($crate::ENABLED_CATEGORIES[$category_id as usize]222.load(std::sync::atomic::Ordering::Relaxed))223{224$crate::CATEGORY_COUNTER[$category_id as usize]225.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);226}227};228}229230// List of categories that can be enabled.231// If a category is marked as disabled here, no events will be processed for it.232setup_trace_marker!(233(VirtioFs, true),234(VirtioNet, true),235(USB, true),236(gpu_display, true),237(VirtioBlk, true),238(VirtioScsi, true)239);240241/// Platform-specific implementation of the `trace_simple_print!` macro. If tracing242/// is enabled on the system, it writes the given message to the `trace_marker` file.243///244/// # Arguments245///246/// * `message` - The message to be written247pub fn trace_simple_print_internal(message: String) {248// In case tracing is not working or the trace marker file is None we can249// just ignore this. We don't need to handle the error here.250if let Some(file) = TRACE_MARKER_FILE.lock().as_mut() {251// We ignore the error here in case write!() fails, because the trace252// marker file would be normally closed by the system unless we are253// actively tracing the runtime. It is not an error.254let _ = write!(file, "{message}");255};256}257258/// Initializes the trace_marker backend. It attepts to open the `trace_marker`259/// file and keep a reference that can be shared throughout the lifetime of the260/// crosvm process.261///262/// If tracefs is not available on the system or the file cannot be opened,263/// tracing will not work but the crosvm process will still continue execution264/// without tracing.265pub fn init() {266let mut trace_marker_file = TRACE_MARKER_FILE.lock();267if trace_marker_file.is_some() {268return;269}270271let path = Path::new("/sys/kernel/tracing/trace_marker");272let file = match OpenOptions::new().read(false).write(true).open(path) {273Ok(f) => f,274Err(e) => {275error!(276"Failed to open {}: {}. Tracing will not work.",277path.display(),278e279);280return;281}282};283284*trace_marker_file = Some(file);285}286287/// A trace context obtained from a `trace_event!()` call.288pub struct Trace {289/// Unique identifier for the specific event.290identifier: u64,291/// Name of the trace event.292name: String,293/// Category name to which the event belongs.294category: String,295/// Category ID to which the event belongs.296category_id: usize,297}298299impl Trace {300/// Returns a Trace object with the given name, id, and category301pub fn new(identifier: u64, name: &str, category: &str, category_id: usize) -> Self {302Trace {303identifier,304name: name.to_string(),305category: category.to_string(),306category_id,307}308}309}310311impl Drop for Trace {312fn drop(&mut self) {313trace_simple_print!("{} {} Exit: {}", self.identifier, self.category, self.name);314trace_event_end!(self.category_id);315}316}317318319