From ec31526de1837b12b86147220b6b03fd98d0ea5f Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Mon, 28 Feb 2022 14:54:42 +0000 Subject: [PATCH] Format arguments in userspace This change moves away argument formatting from eBPF to the userspace. eBPF part of aya-log writes unformatted log message and all arguments to the perf buffer and the userspace part of aya-log is formatting the message after receiving all arguments. Aya-based project to test this change: https://github.com/vadorovsky/aya-log-example Fixes: #4 Signed-off-by: Michal Rostecki Signed-off-by: Tuetuopay Co-authored-by: Tuetuopay --- aya-log-common/src/lib.rs | 33 ++- aya-log-ebpf/src/lib.rs | 130 ----------- aya-log-ebpf/src/macros.rs | 205 ------------------ aya-log/Cargo.toml | 1 + aya-log/src/lib.rs | 107 ++++++++- {aya-log-ebpf => ebpf}/.cargo/config.toml | 0 ebpf/Cargo.toml | 2 + ebpf/aya-log-ebpf-macros/Cargo.toml | 12 + ebpf/aya-log-ebpf-macros/src/expand.rs | 185 ++++++++++++++++ ebpf/aya-log-ebpf-macros/src/lib.rs | 52 +++++ .../aya-log-ebpf}/Cargo.toml | 9 +- ebpf/aya-log-ebpf/src/lib.rs | 133 ++++++++++++ ebpf/rust-toolchain.toml | 2 + 13 files changed, 524 insertions(+), 347 deletions(-) delete mode 100644 aya-log-ebpf/src/lib.rs delete mode 100644 aya-log-ebpf/src/macros.rs rename {aya-log-ebpf => ebpf}/.cargo/config.toml (100%) create mode 100644 ebpf/Cargo.toml create mode 100644 ebpf/aya-log-ebpf-macros/Cargo.toml create mode 100644 ebpf/aya-log-ebpf-macros/src/expand.rs create mode 100644 ebpf/aya-log-ebpf-macros/src/lib.rs rename {aya-log-ebpf => ebpf/aya-log-ebpf}/Cargo.toml (66%) create mode 100644 ebpf/aya-log-ebpf/src/lib.rs create mode 100644 ebpf/rust-toolchain.toml diff --git a/aya-log-common/src/lib.rs b/aya-log-common/src/lib.rs index 837bdbbf..b30fafc7 100644 --- a/aya-log-common/src/lib.rs +++ b/aya-log-common/src/lib.rs @@ -2,7 +2,7 @@ pub const LOG_BUF_CAPACITY: usize = 1024; -pub const LOG_FIELDS: usize = 6; +pub const LOG_FIELDS: usize = 7; #[repr(usize)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] @@ -37,8 +37,37 @@ pub enum RecordField { Module, File, Line, + NumArgs, Log, } +#[repr(usize)] +#[derive(Copy, Clone, Debug)] +pub enum ArgType { + I8, + I16, + I32, + I64, + I128, + Isize, + + U8, + U16, + U32, + U64, + U128, + Usize, + + F32, + F64, + + Str, +} + #[cfg(feature = "userspace")] -unsafe impl aya::Pod for RecordField {} +mod userspace { + use super::*; + + unsafe impl aya::Pod for RecordField {} + unsafe impl aya::Pod for ArgType {} +} diff --git a/aya-log-ebpf/src/lib.rs b/aya-log-ebpf/src/lib.rs deleted file mode 100644 index 5d2dd3b3..00000000 --- a/aya-log-ebpf/src/lib.rs +++ /dev/null @@ -1,130 +0,0 @@ -#![no_std] - -pub extern crate ufmt; - -mod macros; - -use core::{cmp, mem, ptr}; - -use aya_bpf::{ - macros::map, - maps::{PerCpuArray, PerfEventByteArray}, -}; -pub use aya_log_common::Level; -use aya_log_common::RecordField; -pub use aya_log_common::LOG_BUF_CAPACITY; - -#[doc(hidden)] -#[repr(C)] -pub struct LogBuf { - pub buf: [u8; LOG_BUF_CAPACITY], -} - -#[doc(hidden)] -#[map] -pub static mut AYA_LOG_BUF: PerCpuArray = PerCpuArray::with_max_entries(1, 0); - -#[doc(hidden)] -#[map] -pub static mut AYA_LOGS: PerfEventByteArray = PerfEventByteArray::new(0); - -#[doc(hidden)] -pub struct LogBufWriter<'a> { - pos: usize, - data: &'a mut [u8], -} - -impl<'a> LogBufWriter<'a> { - pub fn new(data: &mut [u8]) -> LogBufWriter<'_> { - LogBufWriter { - pos: mem::size_of::() + mem::size_of::(), - data, - } - } - - pub fn finish(self) -> usize { - let mut buf = self.data; - unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, RecordField::Log) }; - buf = &mut buf[mem::size_of::()..]; - - let len = self.pos - mem::size_of::() - mem::size_of::(); - unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, len) }; - - self.pos - } -} - -impl<'a> ufmt::uWrite for LogBufWriter<'a> { - type Error = (); - - fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { - let bytes = s.as_bytes(); - let len = bytes.len(); - - // this is to make sure the verifier knows about the upper bound - if len > LOG_BUF_CAPACITY { - return Err(()); - } - - let available = self.data.len() - self.pos; - if available < len { - return Err(()); - } - - self.data[self.pos..self.pos + len].copy_from_slice(&bytes[..len]); - self.pos += len; - Ok(()) - } -} - -struct TagLenValue<'a> { - tag: RecordField, - value: &'a [u8], -} - -impl<'a> TagLenValue<'a> { - #[inline(always)] - pub(crate) fn new(tag: RecordField, value: &'a [u8]) -> TagLenValue<'a> { - TagLenValue { tag, value } - } - - pub(crate) fn try_write(&self, mut buf: &mut [u8]) -> Result { - let size = mem::size_of::() + mem::size_of::() + self.value.len(); - if buf.len() < size { - return Err(()); - } - - unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.tag) }; - buf = &mut buf[mem::size_of::()..]; - - unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.value.len()) }; - buf = &mut buf[mem::size_of::()..]; - - let len = cmp::min(buf.len(), self.value.len()); - buf[..len].copy_from_slice(&self.value[..len]); - Ok(size) - } -} - -#[doc(hidden)] -pub fn write_record_header( - buf: &mut [u8], - target: &str, - level: Level, - module: &str, - file: &str, - line: u32, -) -> Result { - let mut size = 0; - for attr in [ - TagLenValue::new(RecordField::Target, target.as_bytes()), - TagLenValue::new(RecordField::Level, &(level as usize).to_ne_bytes()), - TagLenValue::new(RecordField::Module, module.as_bytes()), - TagLenValue::new(RecordField::File, file.as_bytes()), - TagLenValue::new(RecordField::Line, &line.to_ne_bytes()), - ] { - size += attr.try_write(&mut buf[size..])?; - } - - Ok(size) -} diff --git a/aya-log-ebpf/src/macros.rs b/aya-log-ebpf/src/macros.rs deleted file mode 100644 index abc8a586..00000000 --- a/aya-log-ebpf/src/macros.rs +++ /dev/null @@ -1,205 +0,0 @@ -/// Logs a message at the error level. -/// -/// # Examples -/// -/// ```no_run -/// # let ctx = (); -/// # let err_code = -1; -/// use aya_log_ebpf::error; -/// -/// error!(&ctx, "Error redirecting packet: {}", err_code); -/// error!(&ctx, target: "ingress", "Error redirecting packet: {}", err_code); -/// ``` -#[macro_export] -macro_rules! error { - ($ctx:expr, target: $target:expr, $($arg:tt)+) => ( - $crate::log!($ctx, target: $target, $crate::Level::Error, $($arg)+) - ); - ($ctx:expr, $($arg:tt)+) => ( - $crate::log!($ctx, $crate::Level::Error, $($arg)+) - ) -} - -/// Logs a message at the warn level. -/// -/// # Examples -/// -/// ``` -/// use aya_log_ebpf::warn; -/// -/// # fn main() { -/// let warn_description = "Invalid Input"; -/// -/// warn!("Warning! {}!", warn_description); -/// warn!(target: "input_events", "App received warning: {}", warn_description); -/// # } -/// ``` -#[macro_export] -macro_rules! warn { - ($ctx:expr, target: $target:expr, $($arg:tt)+) => ( - $crate::log!($ctx, target: $target, $crate::Level::Warn, $($arg)+) - ); - ($ctx:expr, $($arg:tt)+) => ( - $crate::log!($ctx, $crate::Level::Warn, $($arg)+) - ) -} - -/// Logs a message at the info level. -/// -/// # Examples -/// -/// ```edition2018 -/// use log::info; -/// -/// # fn main() { -/// # struct Connection { port: u32, speed: u32 } -/// let conn_info = Connection { port: 40, speed: 3 }; -/// -/// info!("Connected to port {} at {} Mb/s", conn_info.port, conn_info.speed); -/// info!(target: "connection_events", "Successfull connection, port: {}, speed: {}", -/// conn_info.port, conn_info.speed); -/// # } -/// ``` -#[macro_export] -macro_rules! info { - ($ctx:expr, target: $target:expr, $($arg:tt)+) => ( - $crate::log!($ctx, target: $target, $crate::Level::Info, $($arg)+) - ); - ($ctx:expr, $($arg:tt)+) => ( - $crate::log!($ctx, $crate::Level::Info, $($arg)+) - ) -} - -/// Logs a message at the debug level. -/// -/// # Examples -/// -/// ```edition2018 -/// use log::debug; -/// -/// # fn main() { -/// # struct Position { x: i64, y: i64 } -/// let pos = Position { x: 3.234, y: -1223 }; -/// -/// debug!("New position: x: {}, y: {}", pos.x, pos.y); -/// debug!(target: "app_events", "New position: x: {}, y: {}", pos.x, pos.y); -/// # } -/// ``` -#[macro_export] -macro_rules! debug { - ($ctx:expr, target: $target:expr, $($arg:tt)+) => ( - $crate::log!($ctx, target: $target, $crate::Level::Debug, $($arg)+) - ); - ($ctx:expr, $($arg:tt)+) => ( - $crate::log!($ctx, $crate::Level::Debug, $($arg)+) - ) -} - -/// Logs a message at the trace level. -/// -/// # Examples -/// -/// ```edition2018 -/// use log::trace; -/// -/// # fn main() { -/// # struct Position { x: i64, y: i64 } -/// let pos = Position { x: 3234, y: -1223 }; -/// -/// trace!("Position is: x: {}, y: {}", pos.x, pos.y); -/// trace!(target: "app_events", "x is {} and y is {}", -/// if pos.x >= 0 { "positive" } else { "negative" }, -/// if pos.y >= 0 { "positive" } else { "negative" }); -/// # } -/// ``` -#[macro_export] -macro_rules! trace { - ($ctx:expr, target: $target:expr, $($arg:tt)+) => ( - $crate::log!($ctx, target: $target, $crate::Level::Trace, $($arg)+) - ); - ($ctx:expr, $($arg:tt)+) => ( - $crate::log!($ctx, $crate::Level::Trace, $($arg)+) - ) -} - -// /// Determines if a message logged at the specified level in that module will -// /// be logged. -// /// -// /// This can be used to avoid expensive computation of log message arguments if -// /// the message would be ignored anyway. -// /// -// /// # Examples -// /// -// /// ```edition2018 -// /// use log::Level::Debug; -// /// use log::{debug, log_enabled}; -// /// -// /// # fn foo() { -// /// if log_enabled!(Debug) { -// /// let data = expensive_call(); -// /// debug!("expensive debug data: {} {}", data.x, data.y); -// /// } -// /// if log_enabled!(target: "Global", Debug) { -// /// let data = expensive_call(); -// /// debug!(target: "Global", "expensive debug data: {} {}", data.x, data.y); -// /// } -// /// # } -// /// # struct Data { x: u32, y: u32 } -// /// # fn expensive_call() -> Data { Data { x: 0, y: 0 } } -// /// # fn main() {} -// /// ``` -// macro_rules! log_enabled { -// (target: $target:expr, $lvl:expr) => {{ -// let lvl = $lvl; -// lvl <= $crate::STATIC_MAX_LEVEL -// }}; -// ($lvl:expr) => { -// log_enabled!(target: __log_module_path!(), $lvl) -// }; -// } - -/// Log a message at the given level. -/// -/// This macro will generically log with the specified `Level` and `format!` -/// based argument list. -/// -/// # Examples -/// -/// ```edition2018 -/// use log::{log, Level}; -/// -/// # fn main() { -/// let data = (42, "Forty-two"); -/// let private_data = "private"; -/// -/// log!(Level::Error, "Received errors: {}, {}", data.0, data.1); -/// log!(target: "app_events", Level::Warn, "App warning: {}, {}, {}", -/// data.0, data.1, private_data); -/// # } -/// ``` -#[macro_export] -macro_rules! log { - ($ctx:expr, target: $target:expr, $lvl:expr, $($arg:tt)+) => ({ - if let Some(buf) = unsafe { $crate::AYA_LOG_BUF.get_mut(0) } { - if let Ok(header_len) = $crate::write_record_header(&mut buf.buf, module_path!(), $lvl, module_path!(), file!(), line!()) { - if let Ok(message_len) = $crate::write_record_message!(&mut buf.buf[header_len..], $($arg)+) { - let record_len = header_len + message_len; - if record_len <= $crate::LOG_BUF_CAPACITY { - let _ = unsafe { $crate::AYA_LOGS.output($ctx, &buf.buf[..record_len], 0) }; - } - }; - } - } - }); - ($ctx:expr, $lvl:expr, $($arg:tt)+) => ($crate::log!($ctx, target: __log_module_path!(), $lvl, $($arg)+)) -} - -#[doc(hidden)] -#[macro_export] -macro_rules! write_record_message { - ($buf:expr, $($arg:tt)+) => {{ - use aya_log_ebpf::ufmt; - let mut writer = $crate::LogBufWriter::new($buf); - aya_log_ebpf::ufmt::uwrite!(writer, $($arg)+).map(|_| writer.finish()) - }} -} diff --git a/aya-log/Cargo.toml b/aya-log/Cargo.toml index 47056232..8bdad41b 100644 --- a/aya-log/Cargo.toml +++ b/aya-log/Cargo.toml @@ -13,6 +13,7 @@ edition = "2018" [dependencies] aya = { version = "0.10.5", features=["async_tokio"] } aya-log-common = { version = "0.1", path = "../aya-log-common", features=["userspace"] } +dyn-fmt = "0.3.0" thiserror = "1" log = "0.4" bytes = "1.1" diff --git a/aya-log/src/lib.rs b/aya-log/src/lib.rs index 559a2692..dfee4db8 100644 --- a/aya-log/src/lib.rs +++ b/aya-log/src/lib.rs @@ -59,11 +59,12 @@ //! [Log]: https://docs.rs/log/0.4.14/log/trait.Log.html //! [log]: https://docs.rs/log //! -use std::{convert::TryInto, io, mem, ptr, sync::Arc}; +use std::{convert::TryInto, io, mem, ptr, str, sync::Arc}; -use aya_log_common::{RecordField, LOG_BUF_CAPACITY, LOG_FIELDS}; +use aya_log_common::{ArgType, RecordField, LOG_BUF_CAPACITY, LOG_FIELDS}; use bytes::BytesMut; -use log::{Level, Log, Record}; +use dyn_fmt::AsStrFormatExt; +use log::{error, Level, Log, Record}; use thiserror::Error; use aya::{ @@ -157,6 +158,7 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> { let mut file = None; let mut line = None; let mut log = None; + let mut num_args = None; for _ in 0..LOG_FIELDS { let (attr, rest) = unsafe { TagLenValue::<'_, RecordField>::try_read(buf)? }; @@ -177,6 +179,9 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> { RecordField::Line => { line = Some(u32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?)); } + RecordField::NumArgs => { + num_args = Some(usize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?)); + } RecordField::Log => { log = Some(std::str::from_utf8(attr.value).map_err(|_| ())?); } @@ -185,9 +190,103 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> { buf = rest; } + let log_msg = log.ok_or(())?; + let full_log_msg = match num_args { + Some(n) => { + let mut args: Vec = Vec::new(); + for _ in 0..n { + let (attr, rest) = unsafe { TagLenValue::<'_, ArgType>::try_read(buf)? }; + + match attr.tag { + ArgType::I8 => { + args.push( + i8::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::I16 => { + args.push( + i16::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::I32 => { + args.push( + i32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::I64 => { + args.push( + i64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::I128 => { + args.push( + i128::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::Isize => { + args.push( + isize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .to_string(), + ); + } + ArgType::U8 => { + args.push( + u8::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::U16 => { + args.push( + u16::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::U32 => { + args.push( + u32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::U64 => { + args.push( + u64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::U128 => { + args.push( + u128::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::Usize => { + args.push( + usize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .to_string(), + ); + } + ArgType::F32 => { + args.push( + f32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::F64 => { + args.push( + f64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), + ); + } + ArgType::Str => match str::from_utf8(attr.value) { + Ok(v) => args.push(v.to_string()), + Err(e) => error!("received invalid utf8 string: {}", e), + }, + } + + buf = rest; + } + + log_msg.format(&args) + } + None => log_msg.to_string(), + }; + logger.log( &Record::builder() - .args(format_args!("{}", log.ok_or(())?)) + .args(format_args!("{}", full_log_msg)) .target(target.ok_or(())?) .level(level) .module_path(module) diff --git a/aya-log-ebpf/.cargo/config.toml b/ebpf/.cargo/config.toml similarity index 100% rename from aya-log-ebpf/.cargo/config.toml rename to ebpf/.cargo/config.toml diff --git a/ebpf/Cargo.toml b/ebpf/Cargo.toml new file mode 100644 index 00000000..4a122eef --- /dev/null +++ b/ebpf/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["aya-log-ebpf", "aya-log-ebpf-macros"] diff --git a/ebpf/aya-log-ebpf-macros/Cargo.toml b/ebpf/aya-log-ebpf-macros/Cargo.toml new file mode 100644 index 00000000..8e0262b0 --- /dev/null +++ b/ebpf/aya-log-ebpf-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "aya-log-ebpf-macros" +version = "0.1.0" +edition = "2018" + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "1.0" + +[lib] +proc-macro = true \ No newline at end of file diff --git a/ebpf/aya-log-ebpf-macros/src/expand.rs b/ebpf/aya-log-ebpf-macros/src/expand.rs new file mode 100644 index 00000000..5ff93aac --- /dev/null +++ b/ebpf/aya-log-ebpf-macros/src/expand.rs @@ -0,0 +1,185 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Error, Expr, LitStr, Result, Token, +}; + +pub(crate) struct LogArgs { + pub(crate) ctx: Expr, + pub(crate) target: Option, + pub(crate) level: Option, + pub(crate) format_string: LitStr, + pub(crate) formatting_args: Option>, +} + +mod kw { + syn::custom_keyword!(target); +} + +impl Parse for LogArgs { + fn parse(input: ParseStream) -> Result { + let ctx: Expr = input.parse()?; + input.parse::()?; + + // Parse `target: &str`, which is an optional argument. + let target: Option = if input.peek(kw::target) { + input.parse::()?; + input.parse::()?; + let t: Expr = input.parse()?; + input.parse::()?; + Some(t) + } else { + None + }; + + // Check whether the next token is `format_string: &str` (which i + // always provided) or `level` (which is an optional expression). + // If `level` is provided, it comes before `format_string`. + let (level, format_string): (Option, LitStr) = if input.peek(LitStr) { + // Only `format_string` is provided. + (None, input.parse()?) + } else { + // Both `level` and `format_string` are provided. + let level: Expr = input.parse()?; + input.parse::()?; + let format_string: LitStr = input.parse()?; + (Some(level), format_string) + }; + + // Parse variadic arguments. + let formatting_args: Option> = if input.is_empty() { + None + } else { + input.parse::()?; + Some(Punctuated::parse_terminated(input)?) + }; + + Ok(Self { + ctx, + target, + level, + format_string, + formatting_args, + }) + } +} + +pub(crate) fn log(args: LogArgs, level: Option) -> Result { + let ctx = args.ctx; + let target = match args.target { + Some(t) => quote! { #t }, + None => quote! { module_path!() }, + }; + let lvl: TokenStream = if let Some(l) = level { + l + } else if let Some(l) = args.level { + quote! { #l } + } else { + return Err(Error::new( + args.format_string.span(), + "missing `level` argument: try passing an `aya_log_ebpf::Level` value", + )); + }; + let format_string = args.format_string; + + let (num_args, write_args) = match args.formatting_args { + Some(formatting_args) => { + let formatting_exprs = formatting_args.iter(); + let num_args = formatting_exprs.len(); + + let write_args = quote! {{ + use ::aya_log_ebpf::WriteToBuf; + Ok::<_, ()>(record_len) #( .and_then(|record_len| { + { #formatting_exprs }.write(&mut buf.buf[record_len..]).map(|len| record_len + len) + }) )* + }}; + + (num_args, write_args) + } + None => (0, quote! {}), + }; + + // The way of writing to the perf buffer is different depending on whether + // we have variadic arguments or not. + let write_to_perf_buffer = if num_args > 0 { + // Writing with variadic arguments. + quote! { + if let Ok(record_len) = #write_args { + unsafe { ::aya_log_ebpf::AYA_LOGS.output( + #ctx, + &buf.buf[..record_len], 0 + )} + } + } + } else { + // Writing with no variadic arguments. + quote! { + unsafe { ::aya_log_ebpf::AYA_LOGS.output( + #ctx, + &buf.buf[..record_len], 0 + )} + } + }; + + Ok(quote! { + { + if let Some(buf) = unsafe { ::aya_log_ebpf::AYA_LOG_BUF.get_mut(0) } { + if let Ok(header_len) = ::aya_log_ebpf::write_record_header( + &mut buf.buf, + #target, + #lvl, + module_path!(), + file!(), + line!(), + #num_args, + ) { + if let Ok(message_len) = ::aya_log_ebpf::write_record_message( + &mut buf.buf[header_len..], + #format_string, + ) { + let record_len = header_len + message_len; + + #write_to_perf_buffer + } + } + } + } + }) +} + +pub(crate) fn error(args: LogArgs) -> Result { + log( + args, + Some(quote! { ::aya_log_ebpf::macro_support::Level::Error }), + ) +} + +pub(crate) fn warn(args: LogArgs) -> Result { + log( + args, + Some(quote! { ::aya_log_ebpf::macro_support::Level::Warn }), + ) +} + +pub(crate) fn info(args: LogArgs) -> Result { + log( + args, + Some(quote! { ::aya_log_ebpf::macro_support::Level::Info }), + ) +} + +pub(crate) fn debug(args: LogArgs) -> Result { + log( + args, + Some(quote! { ::aya_log_ebpf::macro_support::Level::Debug }), + ) +} + +pub(crate) fn trace(args: LogArgs) -> Result { + log( + args, + Some(quote! { ::aya_log_ebpf::macro_support::Level::Trace }), + ) +} diff --git a/ebpf/aya-log-ebpf-macros/src/lib.rs b/ebpf/aya-log-ebpf-macros/src/lib.rs new file mode 100644 index 00000000..264d93fb --- /dev/null +++ b/ebpf/aya-log-ebpf-macros/src/lib.rs @@ -0,0 +1,52 @@ +use proc_macro::TokenStream; +use syn::parse_macro_input; + +mod expand; + +#[proc_macro] +pub fn log(args: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as expand::LogArgs); + expand::log(args, None) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +#[proc_macro] +pub fn error(args: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as expand::LogArgs); + expand::error(args) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +#[proc_macro] +pub fn warn(args: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as expand::LogArgs); + expand::warn(args) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +#[proc_macro] +pub fn info(args: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as expand::LogArgs); + expand::info(args) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +#[proc_macro] +pub fn debug(args: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as expand::LogArgs); + expand::debug(args) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +#[proc_macro] +pub fn trace(args: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as expand::LogArgs); + expand::trace(args) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/aya-log-ebpf/Cargo.toml b/ebpf/aya-log-ebpf/Cargo.toml similarity index 66% rename from aya-log-ebpf/Cargo.toml rename to ebpf/aya-log-ebpf/Cargo.toml index 5e00a175..9ee4e548 100644 --- a/aya-log-ebpf/Cargo.toml +++ b/ebpf/aya-log-ebpf/Cargo.toml @@ -5,8 +5,8 @@ edition = "2018" [dependencies] aya-bpf = { git = "https://github.com/aya-rs/aya", branch = "main" } -aya-log-common = { path = "../aya-log-common" } -ufmt = { version = "0.1", package = "aya-ufmt" } +aya-log-common = { path = "../../aya-log-common" } +aya-log-ebpf-macros = { path = "../aya-log-ebpf-macros" } [lib] path = "src/lib.rs" @@ -18,7 +18,4 @@ opt-level = 2 overflow-checks = false [profile.release] -panic = "abort" - -[workspace] -members = [] \ No newline at end of file +panic = "abort" \ No newline at end of file diff --git a/ebpf/aya-log-ebpf/src/lib.rs b/ebpf/aya-log-ebpf/src/lib.rs new file mode 100644 index 00000000..633fa52a --- /dev/null +++ b/ebpf/aya-log-ebpf/src/lib.rs @@ -0,0 +1,133 @@ +#![no_std] + +use core::{cmp, mem, ptr}; + +use aya_bpf::{ + macros::map, + maps::{PerCpuArray, PerfEventByteArray}, +}; +use aya_log_common::{ArgType, RecordField}; +pub use aya_log_common::{Level, LOG_BUF_CAPACITY}; +pub use aya_log_ebpf_macros::{debug, error, info, log, trace, warn}; + +#[doc(hidden)] +#[repr(C)] +pub struct LogBuf { + pub buf: [u8; LOG_BUF_CAPACITY], +} + +#[doc(hidden)] +#[map] +pub static mut AYA_LOG_BUF: PerCpuArray = PerCpuArray::with_max_entries(1, 0); + +#[doc(hidden)] +#[map] +pub static mut AYA_LOGS: PerfEventByteArray = PerfEventByteArray::new(0); + +struct TagLenValue<'a, T> { + tag: T, + value: &'a [u8], +} + +impl<'a, T> TagLenValue<'a, T> +where + T: Copy, +{ + #[inline(always)] + pub(crate) fn new(tag: T, value: &'a [u8]) -> TagLenValue<'a, T> { + TagLenValue { tag, value } + } + + pub(crate) fn write(&self, mut buf: &mut [u8]) -> Result { + let size = mem::size_of::() + mem::size_of::() + self.value.len(); + if buf.len() < size { + return Err(()); + } + + unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.tag) }; + buf = &mut buf[mem::size_of::()..]; + + unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.value.len()) }; + buf = &mut buf[mem::size_of::()..]; + + let len = cmp::min(buf.len(), self.value.len()); + buf[..len].copy_from_slice(&self.value[..len]); + Ok(size) + } +} + +pub trait WriteToBuf { + #[allow(clippy::result_unit_err)] + fn write(&self, buf: &mut [u8]) -> Result; +} + +macro_rules! impl_write_to_buf { + ($type:ident, $arg_type:expr) => { + impl WriteToBuf for $type { + fn write(&self, buf: &mut [u8]) -> Result { + TagLenValue::::new($arg_type, &self.to_ne_bytes()).write(buf) + } + } + }; +} + +impl_write_to_buf!(i8, ArgType::I8); +impl_write_to_buf!(i16, ArgType::I16); +impl_write_to_buf!(i32, ArgType::I32); +impl_write_to_buf!(i64, ArgType::I64); +impl_write_to_buf!(i128, ArgType::I128); +impl_write_to_buf!(isize, ArgType::Isize); + +impl_write_to_buf!(u8, ArgType::U8); +impl_write_to_buf!(u16, ArgType::U16); +impl_write_to_buf!(u32, ArgType::U32); +impl_write_to_buf!(u64, ArgType::U64); +impl_write_to_buf!(u128, ArgType::U128); +impl_write_to_buf!(usize, ArgType::Usize); + +impl_write_to_buf!(f32, ArgType::F32); +impl_write_to_buf!(f64, ArgType::F64); + +impl WriteToBuf for str { + fn write(&self, buf: &mut [u8]) -> Result { + TagLenValue::::new(ArgType::Str, self.as_bytes()).write(buf) + } +} + +#[allow(clippy::result_unit_err)] +#[doc(hidden)] +pub fn write_record_header( + buf: &mut [u8], + target: &str, + level: Level, + module: &str, + file: &str, + line: u32, + num_args: usize, +) -> Result { + let mut size = 0; + for attr in [ + TagLenValue::::new(RecordField::Target, target.as_bytes()), + TagLenValue::::new(RecordField::Level, &(level as usize).to_ne_bytes()), + TagLenValue::::new(RecordField::Module, module.as_bytes()), + TagLenValue::::new(RecordField::File, file.as_bytes()), + TagLenValue::::new(RecordField::Line, &line.to_ne_bytes()), + TagLenValue::::new(RecordField::NumArgs, &num_args.to_ne_bytes()), + ] { + size += attr.write(&mut buf[size..])?; + } + + Ok(size) +} + +#[allow(clippy::result_unit_err)] +#[doc(hidden)] +pub fn write_record_message(buf: &mut [u8], msg: &str) -> Result { + TagLenValue::::new(RecordField::Log, msg.as_bytes()).write(buf) +} + +#[doc(hidden)] +pub mod macro_support { + pub use aya_log_common::{Level, LOG_BUF_CAPACITY}; + pub use aya_log_ebpf_macros::log; +} diff --git a/ebpf/rust-toolchain.toml b/ebpf/rust-toolchain.toml new file mode 100644 index 00000000..5d56faf9 --- /dev/null +++ b/ebpf/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly"