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 <vadorovsky@gmail.com>
Signed-off-by: Tuetuopay <tuetuopay@me.com>
Co-authored-by: Tuetuopay <tuetuopay@me.com>
pull/350/head
Michal Rostecki 3 years ago committed by Dave Tucker
parent 9b229d00e1
commit ca1fe7e05f

@ -2,7 +2,7 @@
pub const LOG_BUF_CAPACITY: usize = 1024; pub const LOG_BUF_CAPACITY: usize = 1024;
pub const LOG_FIELDS: usize = 6; pub const LOG_FIELDS: usize = 7;
#[repr(usize)] #[repr(usize)]
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
@ -37,8 +37,37 @@ pub enum RecordField {
Module, Module,
File, File,
Line, Line,
NumArgs,
Log, 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")] #[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 {}
}

@ -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<LogBuf> = 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::<RecordField>() + mem::size_of::<usize>(),
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::<RecordField>()..];
let len = self.pos - mem::size_of::<RecordField>() - mem::size_of::<usize>();
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<usize, ()> {
let size = mem::size_of::<RecordField>() + mem::size_of::<usize>() + 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::<RecordField>()..];
unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.value.len()) };
buf = &mut buf[mem::size_of::<usize>()..];
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<usize, ()> {
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)
}

@ -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())
}}
}

@ -13,6 +13,7 @@ edition = "2018"
[dependencies] [dependencies]
aya = { version = "0.10.5", features=["async_tokio"] } aya = { version = "0.10.5", features=["async_tokio"] }
aya-log-common = { version = "0.1", path = "../aya-log-common", features=["userspace"] } aya-log-common = { version = "0.1", path = "../aya-log-common", features=["userspace"] }
dyn-fmt = "0.3.0"
thiserror = "1" thiserror = "1"
log = "0.4" log = "0.4"
bytes = "1.1" bytes = "1.1"

@ -59,11 +59,12 @@
//! [Log]: https://docs.rs/log/0.4.14/log/trait.Log.html //! [Log]: https://docs.rs/log/0.4.14/log/trait.Log.html
//! [log]: https://docs.rs/log //! [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 bytes::BytesMut;
use log::{Level, Log, Record}; use dyn_fmt::AsStrFormatExt;
use log::{error, Level, Log, Record};
use thiserror::Error; use thiserror::Error;
use aya::{ use aya::{
@ -157,6 +158,7 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> {
let mut file = None; let mut file = None;
let mut line = None; let mut line = None;
let mut log = None; let mut log = None;
let mut num_args = None;
for _ in 0..LOG_FIELDS { for _ in 0..LOG_FIELDS {
let (attr, rest) = unsafe { TagLenValue::<'_, RecordField>::try_read(buf)? }; 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 => { RecordField::Line => {
line = Some(u32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?)); 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 => { RecordField::Log => {
log = Some(std::str::from_utf8(attr.value).map_err(|_| ())?); 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; buf = rest;
} }
let log_msg = log.ok_or(())?;
let full_log_msg = match num_args {
Some(n) => {
let mut args: Vec<String> = 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( logger.log(
&Record::builder() &Record::builder()
.args(format_args!("{}", log.ok_or(())?)) .args(format_args!("{}", full_log_msg))
.target(target.ok_or(())?) .target(target.ok_or(())?)
.level(level) .level(level)
.module_path(module) .module_path(module)

@ -0,0 +1,2 @@
[workspace]
members = ["aya-log-ebpf", "aya-log-ebpf-macros"]

@ -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

@ -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<Expr>,
pub(crate) level: Option<Expr>,
pub(crate) format_string: LitStr,
pub(crate) formatting_args: Option<Punctuated<Expr, Token![,]>>,
}
mod kw {
syn::custom_keyword!(target);
}
impl Parse for LogArgs {
fn parse(input: ParseStream) -> Result<Self> {
let ctx: Expr = input.parse()?;
input.parse::<Token![,]>()?;
// Parse `target: &str`, which is an optional argument.
let target: Option<Expr> = if input.peek(kw::target) {
input.parse::<kw::target>()?;
input.parse::<Token![:]>()?;
let t: Expr = input.parse()?;
input.parse::<Token![,]>()?;
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<Expr>, 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::<Token![,]>()?;
let format_string: LitStr = input.parse()?;
(Some(level), format_string)
};
// Parse variadic arguments.
let formatting_args: Option<Punctuated<Expr, Token![,]>> = if input.is_empty() {
None
} else {
input.parse::<Token![,]>()?;
Some(Punctuated::parse_terminated(input)?)
};
Ok(Self {
ctx,
target,
level,
format_string,
formatting_args,
})
}
}
pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStream> {
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<TokenStream> {
log(
args,
Some(quote! { ::aya_log_ebpf::macro_support::Level::Error }),
)
}
pub(crate) fn warn(args: LogArgs) -> Result<TokenStream> {
log(
args,
Some(quote! { ::aya_log_ebpf::macro_support::Level::Warn }),
)
}
pub(crate) fn info(args: LogArgs) -> Result<TokenStream> {
log(
args,
Some(quote! { ::aya_log_ebpf::macro_support::Level::Info }),
)
}
pub(crate) fn debug(args: LogArgs) -> Result<TokenStream> {
log(
args,
Some(quote! { ::aya_log_ebpf::macro_support::Level::Debug }),
)
}
pub(crate) fn trace(args: LogArgs) -> Result<TokenStream> {
log(
args,
Some(quote! { ::aya_log_ebpf::macro_support::Level::Trace }),
)
}

@ -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()
}

@ -5,8 +5,8 @@ edition = "2018"
[dependencies] [dependencies]
aya-bpf = { git = "https://github.com/aya-rs/aya", branch = "main" } aya-bpf = { git = "https://github.com/aya-rs/aya", branch = "main" }
aya-log-common = { path = "../aya-log-common" } aya-log-common = { path = "../../aya-log-common" }
ufmt = { version = "0.1", package = "aya-ufmt" } aya-log-ebpf-macros = { path = "../aya-log-ebpf-macros" }
[lib] [lib]
path = "src/lib.rs" path = "src/lib.rs"
@ -19,6 +19,3 @@ overflow-checks = false
[profile.release] [profile.release]
panic = "abort" panic = "abort"
[workspace]
members = []

@ -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<LogBuf> = 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<usize, ()> {
let size = mem::size_of::<T>() + mem::size_of::<usize>() + 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::<T>()..];
unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.value.len()) };
buf = &mut buf[mem::size_of::<usize>()..];
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<usize, ()>;
}
macro_rules! impl_write_to_buf {
($type:ident, $arg_type:expr) => {
impl WriteToBuf for $type {
fn write(&self, buf: &mut [u8]) -> Result<usize, ()> {
TagLenValue::<ArgType>::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<usize, ()> {
TagLenValue::<ArgType>::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<usize, ()> {
let mut size = 0;
for attr in [
TagLenValue::<RecordField>::new(RecordField::Target, target.as_bytes()),
TagLenValue::<RecordField>::new(RecordField::Level, &(level as usize).to_ne_bytes()),
TagLenValue::<RecordField>::new(RecordField::Module, module.as_bytes()),
TagLenValue::<RecordField>::new(RecordField::File, file.as_bytes()),
TagLenValue::<RecordField>::new(RecordField::Line, &line.to_ne_bytes()),
TagLenValue::<RecordField>::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<usize, ()> {
TagLenValue::<RecordField>::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;
}

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"
Loading…
Cancel
Save