From da13143c0571863311e1b1be0f08f145aea3dc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20H=C3=B6ner?= Date: Sun, 24 Jul 2022 03:16:29 +0200 Subject: [PATCH] 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. --- bpf/aya-bpf/src/helpers.rs | 145 ++++++++++++++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/bpf/aya-bpf/src/helpers.rs b/bpf/aya-bpf/src/helpers.rs index 0159bb94..58170e46 100644 --- a/bpf/aya-bpf/src/helpers.rs +++ b/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 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 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( + 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 _), + } +}