Browse Source

bpf: add `bpf_printk!` helper

The `bpf_printk!` macro is a helper providing a convenient way to invoke the
`bpf_trace_printk` and `bpf_trace_vprintk` BPF helpers. It is implemented as
a macro because it requires variadic arguments.
Joel Höner 2 years ago
parent
commit
da13143c05
1 changed files with 144 additions and 1 deletions
  1. 144 1
      bpf/aya-bpf/src/helpers.rs

+ 144 - 1
bpf/aya-bpf/src/helpers.rs

@@ -13,7 +13,7 @@ pub use aya_bpf_bindings::helpers as gen;
 #[doc(hidden)]
 pub use gen::*;
 
-use crate::cty::{c_long, c_void};
+use crate::cty::{c_long, c_void, c_char};
 
 /// Read bytes stored at `src` and store them as a `T`.
 ///
@@ -701,3 +701,146 @@ pub fn bpf_get_current_pid_tgid() -> u64 {
 pub fn bpf_get_current_uid_gid() -> u64 {
     unsafe { gen::bpf_get_current_uid_gid() }
 }
+
+/// Prints a debug message to the BPF debugging pipe.
+///
+/// The [format string syntax][fmt] is the same as that of the `printk` kernel
+/// function. It is passed in as a fixed-size byte array (`&[u8; N]`), so you
+/// will have to prefix your string literal with a `b`. A terminating zero byte
+/// is appended automatically.
+///
+/// The macro can read from arbitrary pointers, so it must be used in an `unsafe`
+/// scope in order to compile.
+///
+/// Invocations with less than 4 arguments call the `bpf_trace_printk` helper,
+/// otherwise the `bpf_trace_vprintk` function is called. The latter function
+/// requires a kernel version >= 5.16 whereas the former has been around since
+/// the dawn of eBPF. The return value of the BPF helper is also returned from
+/// this macro.
+///
+/// Messages can be read by executing the following command in a second terminal:
+///
+/// ```bash
+/// sudo cat /sys/kernel/debug/tracing/trace_pipe
+/// ```
+///
+/// If no messages are printed when calling [`bpf_printk!`] after executing the
+/// above command, it is possible that tracing must first be enabled by running:
+///
+/// ```bash
+/// echo 1 | sudo tee /sys/kernel/debug/tracing/tracing_on
+/// ```
+///
+/// # Example
+///
+/// ```no_run
+/// # use aya_bpf::helpers::bpf_printk;
+/// bpf_printk!(b"hi there! dec: %d, hex: 0x%08X", 42, 0x1234);
+/// ```
+///
+/// [fmt]: https://www.kernel.org/doc/html/latest/core-api/printk-formats.html#printk-specifiers
+#[macro_export]
+macro_rules! bpf_printk {
+    ($fmt:literal $(,)? $($arg:expr),* $(,)?) => {{
+        use $crate::helpers::PrintkArg;
+        // Arguments must be placed on the stack to pass the verifier.
+        let fmt = unsafe {
+            // `concat_bytes!` is still unstable, so we have to emulate it manually.
+            let mut fmt = ::core::mem::MaybeUninit::<[u8; $fmt.len() + 1]>::uninit();
+            ::core::ptr::copy_nonoverlapping($fmt.as_ptr(), fmt.as_mut_ptr() as _, 1);
+            (*fmt.as_mut_ptr()).as_mut_ptr().add($fmt.len()).write(0);
+            fmt.assume_init()
+        };
+        let data = [$(PrintkArg::from($arg)),*];
+        $crate::helpers::bpf_printk_impl(&fmt, &data)
+    }};
+}
+
+// Macros are always exported from the crate root. Also export it from `helpers`.
+#[doc(inline)]
+pub use bpf_printk as bpf_printk;
+
+/// Argument ready to be passed to `printk` BPF helper.
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+pub struct PrintkArg(u64);
+
+impl PrintkArg {
+    /// Manually construct a `printk` BPF helper argument.
+    #[inline]
+    pub fn from_raw(x: u64) -> Self {
+        Self(x)
+    }
+}
+
+macro_rules! impl_integer_promotion {
+    ($($ty:ty : via $via:ty),* $(,)?) => {$(
+        /// Create `printk` arguments from integer types.
+        impl From<$ty> for PrintkArg {
+            #[inline]
+            fn from(x: $ty) -> PrintkArg {
+                PrintkArg(x as $via as u64)
+            }
+        }
+    )*}
+}
+
+impl_integer_promotion!(
+  char:  via u64,
+  u8:    via u64,
+  u16:   via u64,
+  u32:   via u64,
+  u64:   via u64,
+  usize: via u64,
+  i8:    via i64,
+  i16:   via i64,
+  i32:   via i64,
+  i64:   via i64,
+  isize: via i64,
+);
+
+/// Construct `printk` BPF helper arguments from constant pointers.
+impl<T> From<*const T> for PrintkArg {
+    #[inline]
+    fn from(x: *const T) -> Self {
+        PrintkArg(x as usize as u64)
+    }
+}
+
+/// Construct `printk` BPF helper arguments from mutable pointers.
+impl<T> From<*mut T> for PrintkArg {
+    #[inline]
+    fn from(x: *mut T) -> Self {
+        PrintkArg(x as usize as u64)
+    }
+}
+
+/// Internal helper function for the [`bpf_printk!`] macro.
+///
+/// The BPF helpers require the length for both the format string as well as
+/// the argument array to be of constant size to pass the verifier. Const
+/// generics are used to represent this requirement in Rust.
+#[inline]
+#[doc(hidden)]
+pub unsafe fn bpf_printk_impl<const FMT_LEN: usize, const NUM_ARGS: usize>(
+    fmt: &[u8; FMT_LEN],
+    args: &[PrintkArg; NUM_ARGS],
+) -> i64 {
+    // This function can't be wrapped in `helpers.rs` because it has variadic
+    // arguments. We also can't turn the definitions in `helpers.rs` into
+    // `const`s because MIRI believes casting arbitrary integers to function
+    // pointers to be an error.
+    let printk: unsafe extern "C" fn(fmt: *const c_char, fmt_size: u32, ...) -> c_long =
+        mem::transmute(6usize);
+
+    let fmt_ptr = fmt.as_ptr() as *const c_char;
+    let fmt_size = fmt.len() as u32;
+
+    match NUM_ARGS {
+        0 => printk(fmt_ptr, fmt_size),
+        1 => printk(fmt_ptr, fmt_size, args[0]),
+        2 => printk(fmt_ptr, fmt_size, args[0], args[1]),
+        3 => printk(fmt_ptr, fmt_size, args[0], args[1], args[2]),
+        _ => gen::bpf_trace_vprintk(fmt_ptr, fmt_size, args.as_ptr() as _, (NUM_ARGS * 8) as _),
+    }
+}