diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..08c94a23 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/build-bpf.yml b/.github/workflows/build-bpf.yml new file mode 100644 index 00000000..002e965d --- /dev/null +++ b/.github/workflows/build-bpf.yml @@ -0,0 +1,37 @@ +name: build-bpf + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: rust-src + override: true + + - uses: Swatinem/rust-cache@v1 + + - name: Pre-requisites + run: cargo install bpf-linker + + - name: Build + run: | + pushd ebpf + cargo build --verbose + popd diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..dd031044 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: build + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + + - name: Build + run: cargo build --verbose + + - name: Run tests + run: RUST_BACKTRACE=full cargo test --verbose diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..169b536e --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +name: lint + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +env: + CARGO_TERM_COLOR: always + +jobs: + lint: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + components: rustfmt, clippy, rust-src + override: true + + - name: Check formatting + run: | + cargo fmt --all -- --check + pushd ebpf + cargo fmt --all -- --check + popd + + - name: Run clippy + run: | + cargo clippy -- --deny warnings + pushd ebpf + cargo clippy -- --deny warnings + popd diff --git a/.vim/coc-settings.json b/.vim/coc-settings.json new file mode 100644 index 00000000..f8d723bb --- /dev/null +++ b/.vim/coc-settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.linkedProjects": ["Cargo.toml", "ebpf/Cargo.toml"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..f8d723bb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.linkedProjects": ["Cargo.toml", "ebpf/Cargo.toml"] +} diff --git a/aya-log-common/src/lib.rs b/aya-log-common/src/lib.rs index 5a0bd419..b91d07db 100644 --- a/aya-log-common/src/lib.rs +++ b/aya-log-common/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] +use core::{cmp, mem, ptr}; + pub const LOG_BUF_CAPACITY: usize = 8192; pub const LOG_FIELDS: usize = 7; @@ -71,3 +73,106 @@ mod userspace { unsafe impl aya::Pod for RecordField {} unsafe impl aya::Pod for ArgType {} } + +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)] +#[inline(always)] +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) +} diff --git a/aya-log/Cargo.toml b/aya-log/Cargo.toml index f5165f56..082de210 100644 --- a/aya-log/Cargo.toml +++ b/aya-log/Cargo.toml @@ -19,5 +19,9 @@ log = "0.4" bytes = "1.1" tokio = { version = "1.2.0" } +[dev-dependencies] +simplelog = "0.12" +testing_logger = "0.1.1" + [lib] path = "src/lib.rs" diff --git a/aya-log/src/lib.rs b/aya-log/src/lib.rs index dfee4db8..fb82e448 100644 --- a/aya-log/src/lib.rs +++ b/aya-log/src/lib.rs @@ -34,7 +34,7 @@ //! //! With the following eBPF code: //! -//! ```no_run +//! ```ignore //! # let ctx = (); //! use aya_log_ebpf::{debug, error, info, trace, warn}; //! @@ -328,3 +328,54 @@ impl<'a, T: Pod> TagLenValue<'a, T> { )) } } + +#[cfg(test)] +mod test { + use super::*; + use aya_log_common::{write_record_header, write_record_message, WriteToBuf}; + use log::logger; + use testing_logger; + + fn new_log(msg: &str, args: usize) -> Result<(usize, Vec), ()> { + let mut buf = vec![0; 8192]; + let mut len = write_record_header( + &mut buf, + "test", + aya_log_common::Level::Info, + "test", + "test.rs", + 123, + args, + )?; + len += write_record_message(&mut buf[len..], msg)?; + Ok((len, buf)) + } + + #[test] + fn test_str() { + testing_logger::setup(); + let (_, input) = new_log("test", 0).unwrap(); + let logger = logger(); + let _ = log_buf(&input, logger); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "test"); + assert_eq!(captured_logs[0].level, Level::Info); + }); + } + + #[test] + fn test_str_with_args() { + testing_logger::setup(); + let (len, mut input) = new_log("hello {}", 1).unwrap(); + let name = "test"; + (*name).write(&mut input[len..]).unwrap(); + let logger = logger(); + let _ = log_buf(&input, logger); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "hello test"); + assert_eq!(captured_logs[0].level, Level::Info); + }); + } +} diff --git a/ebpf/Cargo.toml b/ebpf/Cargo.toml index 4a122eef..d29c15e0 100644 --- a/ebpf/Cargo.toml +++ b/ebpf/Cargo.toml @@ -1,2 +1,12 @@ [workspace] -members = ["aya-log-ebpf", "aya-log-ebpf-macros"] +members = ["aya-log-ebpf", "aya-log-ebpf-macros", "example"] + + +[profile.dev] +panic = "abort" +debug = 1 +opt-level = 2 +overflow-checks = false + +[profile.release] +panic = "abort" diff --git a/ebpf/aya-log-ebpf-macros/Cargo.toml b/ebpf/aya-log-ebpf-macros/Cargo.toml index 8e0262b0..9247eb82 100644 --- a/ebpf/aya-log-ebpf-macros/Cargo.toml +++ b/ebpf/aya-log-ebpf-macros/Cargo.toml @@ -9,4 +9,4 @@ quote = "1.0" syn = "1.0" [lib] -proc-macro = true \ No newline at end of file +proc-macro = true diff --git a/ebpf/aya-log-ebpf/Cargo.toml b/ebpf/aya-log-ebpf/Cargo.toml index 9ee4e548..5b716747 100644 --- a/ebpf/aya-log-ebpf/Cargo.toml +++ b/ebpf/aya-log-ebpf/Cargo.toml @@ -10,12 +10,3 @@ aya-log-ebpf-macros = { path = "../aya-log-ebpf-macros" } [lib] path = "src/lib.rs" - -[profile.dev] -panic = "abort" -debug = 1 -opt-level = 2 -overflow-checks = false - -[profile.release] -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 index 33210970..6faa949d 100644 --- a/ebpf/aya-log-ebpf/src/lib.rs +++ b/ebpf/aya-log-ebpf/src/lib.rs @@ -1,13 +1,11 @@ #![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_common::{ + write_record_header, write_record_message, Level, WriteToBuf, LOG_BUF_CAPACITY, +}; pub use aya_log_ebpf_macros::{debug, error, info, log, trace, warn}; #[doc(hidden)] @@ -24,109 +22,6 @@ pub static mut AYA_LOG_BUF: PerCpuArray = PerCpuArray::with_max_entries( #[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)] -#[inline(always)] -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}; diff --git a/ebpf/example/Cargo.toml b/ebpf/example/Cargo.toml new file mode 100644 index 00000000..e78d7049 --- /dev/null +++ b/ebpf/example/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +aya-bpf = { git = "https://github.com/aya-rs/aya", branch = "main" } +aya-log-ebpf = { path = "../aya-log-ebpf" } + +[[bin]] +name = "example" +path = "src/main.rs" diff --git a/ebpf/example/src/main.rs b/ebpf/example/src/main.rs new file mode 100644 index 00000000..b43b5364 --- /dev/null +++ b/ebpf/example/src/main.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use aya_bpf::{macros::tracepoint, programs::TracePointContext, BpfContext}; +use aya_log_ebpf::{debug, error, info, trace, warn}; + +#[tracepoint] +pub fn example(ctx: TracePointContext) -> u32 { + error!(&ctx, "this is an error message 🚨"); + warn!(&ctx, "this is a warning message âš ī¸"); + info!(&ctx, "this is an info message â„šī¸"); + debug!(&ctx, "this is a debug message ī¸đŸ"); + trace!(&ctx, "this is a trace message 🔍"); + let pid = ctx.pid(); + info!(&ctx, "a message with args PID: {}", pid); + 0 +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/ebpf/rustfmt.toml b/ebpf/rustfmt.toml new file mode 100644 index 00000000..0c3bc0ee --- /dev/null +++ b/ebpf/rustfmt.toml @@ -0,0 +1,4 @@ +unstable_features = true +reorder_imports = true +imports_granularity = "Crate" + diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..0c3bc0ee --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +unstable_features = true +reorder_imports = true +imports_granularity = "Crate" + diff --git a/xtask/src/build_ebpf.rs b/xtask/src/build_ebpf.rs index 756801f3..517a8a1d 100644 --- a/xtask/src/build_ebpf.rs +++ b/xtask/src/build_ebpf.rs @@ -1,5 +1,4 @@ -use std::path::PathBuf; -use std::process::Command; +use std::{path::PathBuf, process::Command}; use structopt::StructOpt;