├── .cargo └── config.toml ├── .github ├── dependabot.yml └── workflows │ ├── build-bpf.yml │ ├── build.yml │ └── lint.yml ├── .gitignore ├── .vim └── coc-settings.json ├── .vscode └── settings.json ├── Cargo.toml ├── README.md ├── aya-log-common ├── Cargo.toml └── src │ └── lib.rs ├── aya-log ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── ebpf ├── .cargo │ └── config.toml ├── Cargo.toml ├── aya-log-ebpf-macros │ ├── Cargo.toml │ └── src │ │ ├── expand.rs │ │ └── lib.rs ├── aya-log-ebpf │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── example │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── rust-toolchain.toml └── rustfmt.toml ├── release.toml ├── rustfmt.toml └── xtask ├── Cargo.toml └── src ├── build_ebpf.rs └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.github/workflows/build-bpf.yml: -------------------------------------------------------------------------------- 1 | name: build-bpf 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-20.04 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: nightly 25 | components: rust-src 26 | override: true 27 | 28 | - uses: Swatinem/rust-cache@v1 29 | 30 | - name: Pre-requisites 31 | run: cargo install bpf-linker 32 | 33 | - name: Build 34 | run: | 35 | pushd ebpf 36 | cargo build --verbose 37 | popd 38 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-20.04 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: Swatinem/rust-cache@v1 21 | 22 | - name: Build 23 | run: cargo build --verbose 24 | 25 | - name: Run tests 26 | run: RUST_BACKTRACE=full cargo test --verbose 27 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-20.04 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | profile: minimal 25 | toolchain: nightly 26 | components: rustfmt, clippy, rust-src 27 | override: true 28 | 29 | - name: Check formatting 30 | run: | 31 | cargo fmt --all -- --check 32 | pushd ebpf 33 | cargo fmt --all -- --check 34 | popd 35 | 36 | - name: Run clippy 37 | run: | 38 | cargo clippy -- --deny warnings 39 | pushd ebpf 40 | cargo clippy -- --deny warnings 41 | popd 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/master/Rust.gitignore 2 | 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | debug/ 6 | target/ 7 | 8 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 9 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 10 | Cargo.lock 11 | 12 | # These are backup files generated by rustfmt 13 | **/*.rs.bk 14 | -------------------------------------------------------------------------------- /.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": ["Cargo.toml", "ebpf/Cargo.toml"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": ["Cargo.toml", "ebpf/Cargo.toml"] 3 | } 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["aya-log", "aya-log-common", "xtask"] 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This repo has been moved. It is now located at https://github.com/aya-rs/aya** 2 | 3 | # aya-log - a logging library for eBPF programs 4 | 5 | ## Overview 6 | 7 | `aya-log` is a logging library for eBPF programs written using [aya]. Think of 8 | it as the [log] crate for eBPF. 9 | 10 | ## Installation 11 | 12 | ### User space 13 | 14 | Add `aya-log` to `Cargo.toml`: 15 | 16 | ```toml 17 | [dependencies] 18 | aya-log = { git = "https://github.com/aya-rs/aya-log", branch = "main" } 19 | ``` 20 | 21 | ### eBPF side 22 | 23 | Add `aya-log-ebpf` to `Cargo.toml`: 24 | 25 | ```toml 26 | [dependencies] 27 | aya-log-ebpf = { git = "https://github.com/aya-rs/aya-log", branch = "main" } 28 | ``` 29 | 30 | ## Example 31 | 32 | Here's an example that uses `aya-log` in conjunction with the [simplelog] crate 33 | to log eBPF messages to the terminal. 34 | 35 | ### User space code 36 | 37 | ```rust 38 | use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; 39 | use aya_log::BpfLogger; 40 | 41 | TermLogger::init( 42 | LevelFilter::Debug, 43 | ConfigBuilder::new() 44 | .set_target_level(LevelFilter::Error) 45 | .set_location_level(LevelFilter::Error) 46 | .build(), 47 | TerminalMode::Mixed, 48 | ColorChoice::Auto, 49 | ) 50 | .unwrap(); 51 | 52 | // Will log using the default logger, which is TermLogger in this case 53 | BpfLogger::init(&mut bpf).unwrap(); 54 | ``` 55 | 56 | ### eBPF code 57 | 58 | ```rust 59 | use aya_log_ebpf::info; 60 | 61 | fn try_xdp_firewall(ctx: XdpContext) -> Result { 62 | if let Some(port) = tcp_dest_port(&ctx)? { 63 | if block_port(port) { 64 | info!(&ctx, "❌ blocked incoming connection on port: {}", port); 65 | return Ok(XDP_DROP); 66 | } 67 | } 68 | 69 | Ok(XDP_PASS) 70 | } 71 | ``` 72 | 73 | [aya]: https://github.com/aya-rs/aya 74 | [log]: https://docs.rs/log 75 | [simplelog]: https://docs.rs/simplelog 76 | -------------------------------------------------------------------------------- /aya-log-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aya-log-common" 3 | version = "0.1.11-dev.0" 4 | description = "A logging library for eBPF programs." 5 | keywords = ["ebpf", "bpf", "log", "logging"] 6 | license = "MIT OR Apache-2.0" 7 | authors = ["The Aya Contributors"] 8 | repository = "https://github.com/aya-rs/aya-log" 9 | documentation = "https://docs.rs/aya-log" 10 | edition = "2018" 11 | 12 | [features] 13 | default = [] 14 | userspace = [ "aya" ] 15 | 16 | [dependencies] 17 | aya = { version = "0.11.0", optional=true } 18 | 19 | [lib] 20 | path = "src/lib.rs" 21 | -------------------------------------------------------------------------------- /aya-log-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::{cmp, mem, ptr}; 4 | 5 | pub const LOG_BUF_CAPACITY: usize = 8192; 6 | 7 | pub const LOG_FIELDS: usize = 7; 8 | 9 | #[repr(usize)] 10 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] 11 | pub enum Level { 12 | /// The "error" level. 13 | /// 14 | /// Designates very serious errors. 15 | Error = 1, 16 | /// The "warn" level. 17 | /// 18 | /// Designates hazardous situations. 19 | Warn, 20 | /// The "info" level. 21 | /// 22 | /// Designates useful information. 23 | Info, 24 | /// The "debug" level. 25 | /// 26 | /// Designates lower priority information. 27 | Debug, 28 | /// The "trace" level. 29 | /// 30 | /// Designates very low priority, often extremely verbose, information. 31 | Trace, 32 | } 33 | 34 | #[repr(usize)] 35 | #[derive(Copy, Clone, Debug)] 36 | pub enum RecordField { 37 | Target = 1, 38 | Level, 39 | Module, 40 | File, 41 | Line, 42 | NumArgs, 43 | Log, 44 | } 45 | 46 | #[repr(usize)] 47 | #[derive(Copy, Clone, Debug)] 48 | pub enum ArgType { 49 | I8, 50 | I16, 51 | I32, 52 | I64, 53 | I128, 54 | Isize, 55 | 56 | U8, 57 | U16, 58 | U32, 59 | U64, 60 | U128, 61 | Usize, 62 | 63 | F32, 64 | F64, 65 | 66 | Str, 67 | } 68 | 69 | #[cfg(feature = "userspace")] 70 | mod userspace { 71 | use super::*; 72 | 73 | unsafe impl aya::Pod for RecordField {} 74 | unsafe impl aya::Pod for ArgType {} 75 | } 76 | 77 | struct TagLenValue<'a, T> { 78 | tag: T, 79 | value: &'a [u8], 80 | } 81 | 82 | impl<'a, T> TagLenValue<'a, T> 83 | where 84 | T: Copy, 85 | { 86 | #[inline(always)] 87 | pub(crate) fn new(tag: T, value: &'a [u8]) -> TagLenValue<'a, T> { 88 | TagLenValue { tag, value } 89 | } 90 | 91 | pub(crate) fn write(&self, mut buf: &mut [u8]) -> Result { 92 | let size = mem::size_of::() + mem::size_of::() + self.value.len(); 93 | let remaining = cmp::min(buf.len(), LOG_BUF_CAPACITY); 94 | // Check if the size doesn't exceed the buffer bounds. 95 | if size > remaining { 96 | return Err(()); 97 | } 98 | 99 | unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.tag) }; 100 | buf = &mut buf[mem::size_of::()..]; 101 | 102 | unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.value.len()) }; 103 | buf = &mut buf[mem::size_of::()..]; 104 | 105 | let len = cmp::min(buf.len(), self.value.len()); 106 | // The verifier isn't happy with `len` being unbounded, so compare it 107 | // with `LOG_BUF_CAPACITY`. 108 | if len > LOG_BUF_CAPACITY { 109 | return Err(()); 110 | } 111 | buf[..len].copy_from_slice(&self.value[..len]); 112 | Ok(size) 113 | } 114 | } 115 | 116 | pub trait WriteToBuf { 117 | #[allow(clippy::result_unit_err)] 118 | fn write(&self, buf: &mut [u8]) -> Result; 119 | } 120 | 121 | macro_rules! impl_write_to_buf { 122 | ($type:ident, $arg_type:expr) => { 123 | impl WriteToBuf for $type { 124 | fn write(&self, buf: &mut [u8]) -> Result { 125 | TagLenValue::::new($arg_type, &self.to_ne_bytes()).write(buf) 126 | } 127 | } 128 | }; 129 | } 130 | 131 | impl_write_to_buf!(i8, ArgType::I8); 132 | impl_write_to_buf!(i16, ArgType::I16); 133 | impl_write_to_buf!(i32, ArgType::I32); 134 | impl_write_to_buf!(i64, ArgType::I64); 135 | impl_write_to_buf!(i128, ArgType::I128); 136 | impl_write_to_buf!(isize, ArgType::Isize); 137 | 138 | impl_write_to_buf!(u8, ArgType::U8); 139 | impl_write_to_buf!(u16, ArgType::U16); 140 | impl_write_to_buf!(u32, ArgType::U32); 141 | impl_write_to_buf!(u64, ArgType::U64); 142 | impl_write_to_buf!(u128, ArgType::U128); 143 | impl_write_to_buf!(usize, ArgType::Usize); 144 | 145 | impl_write_to_buf!(f32, ArgType::F32); 146 | impl_write_to_buf!(f64, ArgType::F64); 147 | 148 | impl WriteToBuf for str { 149 | fn write(&self, buf: &mut [u8]) -> Result { 150 | TagLenValue::::new(ArgType::Str, self.as_bytes()).write(buf) 151 | } 152 | } 153 | 154 | #[allow(clippy::result_unit_err)] 155 | #[doc(hidden)] 156 | #[inline(always)] 157 | pub fn write_record_header( 158 | buf: &mut [u8], 159 | target: &str, 160 | level: Level, 161 | module: &str, 162 | file: &str, 163 | line: u32, 164 | num_args: usize, 165 | ) -> Result { 166 | let mut size = 0; 167 | for attr in [ 168 | TagLenValue::::new(RecordField::Target, target.as_bytes()), 169 | TagLenValue::::new(RecordField::Level, &(level as usize).to_ne_bytes()), 170 | TagLenValue::::new(RecordField::Module, module.as_bytes()), 171 | TagLenValue::::new(RecordField::File, file.as_bytes()), 172 | TagLenValue::::new(RecordField::Line, &line.to_ne_bytes()), 173 | TagLenValue::::new(RecordField::NumArgs, &num_args.to_ne_bytes()), 174 | ] { 175 | size += attr.write(&mut buf[size..])?; 176 | } 177 | 178 | Ok(size) 179 | } 180 | 181 | #[allow(clippy::result_unit_err)] 182 | #[doc(hidden)] 183 | pub fn write_record_message(buf: &mut [u8], msg: &str) -> Result { 184 | TagLenValue::::new(RecordField::Log, msg.as_bytes()).write(buf) 185 | } 186 | -------------------------------------------------------------------------------- /aya-log/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aya-log" 3 | version = "0.1.11-dev.0" 4 | description = "A logging library for eBPF programs." 5 | keywords = ["ebpf", "bpf", "log", "logging"] 6 | license = "MIT OR Apache-2.0" 7 | authors = ["The Aya Contributors"] 8 | repository = "https://github.com/aya-rs/aya-log" 9 | readme = "README.md" 10 | documentation = "https://docs.rs/aya-log" 11 | edition = "2018" 12 | 13 | [dependencies] 14 | aya = { version = "0.11.0", features=["async_tokio"] } 15 | aya-log-common = { version = "0.1.11-dev.0", path = "../aya-log-common", features=["userspace"] } 16 | dyn-fmt = "0.3.0" 17 | thiserror = "1" 18 | log = "0.4" 19 | bytes = "1.1" 20 | tokio = { version = "1.2.0" } 21 | 22 | [dev-dependencies] 23 | simplelog = "0.12" 24 | testing_logger = "0.1.1" 25 | 26 | [lib] 27 | path = "src/lib.rs" 28 | -------------------------------------------------------------------------------- /aya-log/README.md: -------------------------------------------------------------------------------- 1 | # aya-log - a logging library for eBPF programs 2 | 3 | ## Overview 4 | 5 | `aya-log` is a logging library for eBPF programs written using [aya]. Think of 6 | it as the [log] crate for eBPF. 7 | 8 | ## Installation 9 | 10 | ### User space 11 | 12 | Add `aya-log` to `Cargo.toml`: 13 | 14 | ```toml 15 | [dependencies] 16 | aya-log = { git = "https://github.com/aya-rs/aya-log", branch = "main" } 17 | ``` 18 | 19 | ### eBPF side 20 | 21 | Add `aya-log-ebpf` to `Cargo.toml`: 22 | 23 | ```toml 24 | [dependencies] 25 | aya-log-ebpf = { git = "https://github.com/aya-rs/aya-log", branch = "main" } 26 | ``` 27 | 28 | ## Example 29 | 30 | Here's an example that uses `aya-log` in conjunction with the [simplelog] crate 31 | to log eBPF messages to the terminal. 32 | 33 | ### User space code 34 | 35 | ```rust 36 | use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; 37 | use aya_log::BpfLogger; 38 | 39 | TermLogger::init( 40 | LevelFilter::Debug, 41 | ConfigBuilder::new() 42 | .set_target_level(LevelFilter::Error) 43 | .set_location_level(LevelFilter::Error) 44 | .build(), 45 | TerminalMode::Mixed, 46 | ColorChoice::Auto, 47 | ) 48 | .unwrap(); 49 | 50 | // Will log using the default logger, which is TermLogger in this case 51 | BpfLogger::init(&mut bpf).unwrap(); 52 | ``` 53 | 54 | ### eBPF code 55 | 56 | ```rust 57 | use aya_log_ebpf::info; 58 | 59 | fn try_xdp_firewall(ctx: XdpContext) -> Result { 60 | if let Some(port) = tcp_dest_port(&ctx)? { 61 | if block_port(port) { 62 | info!(&ctx, "❌ blocked incoming connection on port: {}", port); 63 | return Ok(XDP_DROP); 64 | } 65 | } 66 | 67 | Ok(XDP_PASS) 68 | } 69 | ``` 70 | 71 | [aya]: https://github.com/aya-rs/aya 72 | [log]: https://docs.rs/log 73 | [simplelog]: https://docs.rs/simplelog 74 | -------------------------------------------------------------------------------- /aya-log/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A logging framework for eBPF programs. 2 | //! 3 | //! This is the user space side of the [Aya] logging framework. For the eBPF 4 | //! side, see the `aya-log-ebpf` crate. 5 | //! 6 | //! `aya-log` provides the [BpfLogger] type, which reads log records created by 7 | //! `aya-log-ebpf` and logs them using the [log] crate. Any logger that 8 | //! implements the [Log] trait can be used with this crate. 9 | //! 10 | //! # Example: 11 | //! 12 | //! This example uses the [simplelog] crate to log messages to the terminal. 13 | //! 14 | //! ```no_run 15 | //! # let mut bpf = aya::Bpf::load(&[]).unwrap(); 16 | //! use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; 17 | //! use aya_log::BpfLogger; 18 | //! 19 | //! // initialize simplelog::TermLogger as the default logger 20 | //! TermLogger::init( 21 | //! LevelFilter::Debug, 22 | //! ConfigBuilder::new() 23 | //! .set_target_level(LevelFilter::Error) 24 | //! .set_location_level(LevelFilter::Error) 25 | //! .build(), 26 | //! TerminalMode::Mixed, 27 | //! ColorChoice::Auto, 28 | //! ) 29 | //! .unwrap(); 30 | //! 31 | //! // start reading aya-log records and log them using the default logger 32 | //! BpfLogger::init(&mut bpf).unwrap(); 33 | //! ``` 34 | //! 35 | //! With the following eBPF code: 36 | //! 37 | //! ```ignore 38 | //! # let ctx = (); 39 | //! use aya_log_ebpf::{debug, error, info, trace, warn}; 40 | //! 41 | //! error!(&ctx, "this is an error message 🚨"); 42 | //! warn!(&ctx, "this is a warning message ⚠️"); 43 | //! info!(&ctx, "this is an info message ℹ️"); 44 | //! debug!(&ctx, "this is a debug message ️🐝"); 45 | //! trace!(&ctx, "this is a trace message 🔍"); 46 | //! ``` 47 | //! Outputs: 48 | //! 49 | //! ```text 50 | //! 21:58:55 [ERROR] xxx: [src/main.rs:35] this is an error message 🚨 51 | //! 21:58:55 [WARN] xxx: [src/main.rs:36] this is a warning message ⚠️ 52 | //! 21:58:55 [INFO] xxx: [src/main.rs:37] this is an info message ℹ️ 53 | //! 21:58:55 [DEBUG] (7) xxx: [src/main.rs:38] this is a debug message ️🐝 54 | //! 21:58:55 [TRACE] (7) xxx: [src/main.rs:39] this is a trace message 🔍 55 | //! ``` 56 | //! 57 | //! [Aya]: https://docs.rs/aya 58 | //! [simplelog]: https://docs.rs/simplelog 59 | //! [Log]: https://docs.rs/log/0.4.14/log/trait.Log.html 60 | //! [log]: https://docs.rs/log 61 | //! 62 | use std::{convert::TryInto, io, mem, ptr, str, sync::Arc}; 63 | 64 | use aya_log_common::{ArgType, RecordField, LOG_BUF_CAPACITY, LOG_FIELDS}; 65 | use bytes::BytesMut; 66 | use dyn_fmt::AsStrFormatExt; 67 | use log::{error, Level, Log, Record}; 68 | use thiserror::Error; 69 | 70 | use aya::{ 71 | maps::{ 72 | perf::{AsyncPerfEventArray, PerfBufferError}, 73 | MapError, 74 | }, 75 | util::online_cpus, 76 | Bpf, Pod, 77 | }; 78 | 79 | /// Log messages generated by `aya_log_ebpf` using the [log] crate. 80 | /// 81 | /// For more details see the [module level documentation](crate). 82 | pub struct BpfLogger; 83 | 84 | impl BpfLogger { 85 | /// Starts reading log records created with `aya-log-ebpf` and logs them 86 | /// with the default logger. See [log::logger]. 87 | pub fn init(bpf: &mut Bpf) -> Result { 88 | BpfLogger::init_with_logger(bpf, DefaultLogger {}) 89 | } 90 | 91 | /// Starts reading log records created with `aya-log-ebpf` and logs them 92 | /// with the given logger. 93 | pub fn init_with_logger( 94 | bpf: &mut Bpf, 95 | logger: T, 96 | ) -> Result { 97 | let logger = Arc::new(logger); 98 | let mut logs: AsyncPerfEventArray<_> = bpf.map_mut("AYA_LOGS")?.try_into()?; 99 | 100 | for cpu_id in online_cpus().map_err(Error::InvalidOnlineCpu)? { 101 | let mut buf = logs.open(cpu_id, None)?; 102 | 103 | let log = logger.clone(); 104 | tokio::spawn(async move { 105 | let mut buffers = (0..10) 106 | .map(|_| BytesMut::with_capacity(LOG_BUF_CAPACITY)) 107 | .collect::>(); 108 | 109 | loop { 110 | let events = buf.read_events(&mut buffers).await.unwrap(); 111 | 112 | #[allow(clippy::needless_range_loop)] 113 | for i in 0..events.read { 114 | let buf = &mut buffers[i]; 115 | log_buf(buf, &*log).unwrap(); 116 | } 117 | } 118 | }); 119 | } 120 | 121 | Ok(BpfLogger {}) 122 | } 123 | } 124 | 125 | #[derive(Copy, Clone, Debug)] 126 | struct DefaultLogger; 127 | 128 | impl Log for DefaultLogger { 129 | fn enabled(&self, metadata: &log::Metadata) -> bool { 130 | log::logger().enabled(metadata) 131 | } 132 | 133 | fn log(&self, record: &Record) { 134 | log::logger().log(record) 135 | } 136 | 137 | fn flush(&self) { 138 | log::logger().flush() 139 | } 140 | } 141 | 142 | #[derive(Error, Debug)] 143 | pub enum Error { 144 | #[error("error opening log event array")] 145 | MapError(#[from] MapError), 146 | 147 | #[error("error opening log buffer")] 148 | PerfBufferError(#[from] PerfBufferError), 149 | 150 | #[error("invalid /sys/devices/system/cpu/online format")] 151 | InvalidOnlineCpu(#[source] io::Error), 152 | } 153 | 154 | fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> { 155 | let mut target = None; 156 | let mut level = Level::Trace; 157 | let mut module = None; 158 | let mut file = None; 159 | let mut line = None; 160 | let mut log = None; 161 | let mut num_args = None; 162 | 163 | for _ in 0..LOG_FIELDS { 164 | let (attr, rest) = unsafe { TagLenValue::<'_, RecordField>::try_read(buf)? }; 165 | 166 | match attr.tag { 167 | RecordField::Target => { 168 | target = Some(std::str::from_utf8(attr.value).map_err(|_| ())?); 169 | } 170 | RecordField::Level => { 171 | level = unsafe { ptr::read_unaligned(attr.value.as_ptr() as *const _) } 172 | } 173 | RecordField::Module => { 174 | module = Some(std::str::from_utf8(attr.value).map_err(|_| ())?); 175 | } 176 | RecordField::File => { 177 | file = Some(std::str::from_utf8(attr.value).map_err(|_| ())?); 178 | } 179 | RecordField::Line => { 180 | line = Some(u32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?)); 181 | } 182 | RecordField::NumArgs => { 183 | num_args = Some(usize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?)); 184 | } 185 | RecordField::Log => { 186 | log = Some(std::str::from_utf8(attr.value).map_err(|_| ())?); 187 | } 188 | } 189 | 190 | buf = rest; 191 | } 192 | 193 | let log_msg = log.ok_or(())?; 194 | let full_log_msg = match num_args { 195 | Some(n) => { 196 | let mut args: Vec = Vec::new(); 197 | for _ in 0..n { 198 | let (attr, rest) = unsafe { TagLenValue::<'_, ArgType>::try_read(buf)? }; 199 | 200 | match attr.tag { 201 | ArgType::I8 => { 202 | args.push( 203 | i8::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 204 | ); 205 | } 206 | ArgType::I16 => { 207 | args.push( 208 | i16::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 209 | ); 210 | } 211 | ArgType::I32 => { 212 | args.push( 213 | i32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 214 | ); 215 | } 216 | ArgType::I64 => { 217 | args.push( 218 | i64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 219 | ); 220 | } 221 | ArgType::I128 => { 222 | args.push( 223 | i128::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 224 | ); 225 | } 226 | ArgType::Isize => { 227 | args.push( 228 | isize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) 229 | .to_string(), 230 | ); 231 | } 232 | ArgType::U8 => { 233 | args.push( 234 | u8::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 235 | ); 236 | } 237 | ArgType::U16 => { 238 | args.push( 239 | u16::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 240 | ); 241 | } 242 | ArgType::U32 => { 243 | args.push( 244 | u32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 245 | ); 246 | } 247 | ArgType::U64 => { 248 | args.push( 249 | u64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 250 | ); 251 | } 252 | ArgType::U128 => { 253 | args.push( 254 | u128::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 255 | ); 256 | } 257 | ArgType::Usize => { 258 | args.push( 259 | usize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) 260 | .to_string(), 261 | ); 262 | } 263 | ArgType::F32 => { 264 | args.push( 265 | f32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 266 | ); 267 | } 268 | ArgType::F64 => { 269 | args.push( 270 | f64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), 271 | ); 272 | } 273 | ArgType::Str => match str::from_utf8(attr.value) { 274 | Ok(v) => args.push(v.to_string()), 275 | Err(e) => error!("received invalid utf8 string: {}", e), 276 | }, 277 | } 278 | 279 | buf = rest; 280 | } 281 | 282 | log_msg.format(&args) 283 | } 284 | None => log_msg.to_string(), 285 | }; 286 | 287 | logger.log( 288 | &Record::builder() 289 | .args(format_args!("{}", full_log_msg)) 290 | .target(target.ok_or(())?) 291 | .level(level) 292 | .module_path(module) 293 | .file(file) 294 | .line(line) 295 | .build(), 296 | ); 297 | logger.flush(); 298 | Ok(()) 299 | } 300 | 301 | struct TagLenValue<'a, T: Pod> { 302 | tag: T, 303 | value: &'a [u8], 304 | } 305 | 306 | impl<'a, T: Pod> TagLenValue<'a, T> { 307 | unsafe fn try_read(mut buf: &'a [u8]) -> Result<(TagLenValue<'a, T>, &'a [u8]), ()> { 308 | if buf.len() < mem::size_of::() + mem::size_of::() { 309 | return Err(()); 310 | } 311 | 312 | let tag = ptr::read_unaligned(buf.as_ptr() as *const T); 313 | buf = &buf[mem::size_of::()..]; 314 | 315 | let len = usize::from_ne_bytes(buf[..mem::size_of::()].try_into().unwrap()); 316 | buf = &buf[mem::size_of::()..]; 317 | 318 | if buf.len() < len { 319 | return Err(()); 320 | } 321 | 322 | Ok(( 323 | TagLenValue { 324 | tag, 325 | value: &buf[..len], 326 | }, 327 | &buf[len..], 328 | )) 329 | } 330 | } 331 | 332 | #[cfg(test)] 333 | mod test { 334 | use super::*; 335 | use aya_log_common::{write_record_header, write_record_message, WriteToBuf}; 336 | use log::logger; 337 | use testing_logger; 338 | 339 | fn new_log(msg: &str, args: usize) -> Result<(usize, Vec), ()> { 340 | let mut buf = vec![0; 8192]; 341 | let mut len = write_record_header( 342 | &mut buf, 343 | "test", 344 | aya_log_common::Level::Info, 345 | "test", 346 | "test.rs", 347 | 123, 348 | args, 349 | )?; 350 | len += write_record_message(&mut buf[len..], msg)?; 351 | Ok((len, buf)) 352 | } 353 | 354 | #[test] 355 | fn test_str() { 356 | testing_logger::setup(); 357 | let (_, input) = new_log("test", 0).unwrap(); 358 | let logger = logger(); 359 | let _ = log_buf(&input, logger); 360 | testing_logger::validate(|captured_logs| { 361 | assert_eq!(captured_logs.len(), 1); 362 | assert_eq!(captured_logs[0].body, "test"); 363 | assert_eq!(captured_logs[0].level, Level::Info); 364 | }); 365 | } 366 | 367 | #[test] 368 | fn test_str_with_args() { 369 | testing_logger::setup(); 370 | let (len, mut input) = new_log("hello {}", 1).unwrap(); 371 | let name = "test"; 372 | (*name).write(&mut input[len..]).unwrap(); 373 | let logger = logger(); 374 | let _ = log_buf(&input, logger); 375 | testing_logger::validate(|captured_logs| { 376 | assert_eq!(captured_logs.len(), 1); 377 | assert_eq!(captured_logs[0].body, "hello test"); 378 | assert_eq!(captured_logs[0].level, Level::Info); 379 | }); 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /ebpf/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target-dir = "../target" 3 | target = "bpfel-unknown-none" 4 | 5 | [unstable] 6 | build-std = ["core"] -------------------------------------------------------------------------------- /ebpf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["aya-log-ebpf", "aya-log-ebpf-macros", "example"] 3 | 4 | 5 | [profile.dev] 6 | panic = "abort" 7 | debug = 1 8 | opt-level = 2 9 | overflow-checks = false 10 | 11 | [profile.release] 12 | panic = "abort" 13 | -------------------------------------------------------------------------------- /ebpf/aya-log-ebpf-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aya-log-ebpf-macros" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | proc-macro2 = "1.0" 8 | quote = "1.0" 9 | syn = "1.0" 10 | 11 | [lib] 12 | proc-macro = true 13 | -------------------------------------------------------------------------------- /ebpf/aya-log-ebpf-macros/src/expand.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | punctuated::Punctuated, 6 | Error, Expr, LitStr, Result, Token, 7 | }; 8 | 9 | pub(crate) struct LogArgs { 10 | pub(crate) ctx: Expr, 11 | pub(crate) target: Option, 12 | pub(crate) level: Option, 13 | pub(crate) format_string: LitStr, 14 | pub(crate) formatting_args: Option>, 15 | } 16 | 17 | mod kw { 18 | syn::custom_keyword!(target); 19 | } 20 | 21 | impl Parse for LogArgs { 22 | fn parse(input: ParseStream) -> Result { 23 | let ctx: Expr = input.parse()?; 24 | input.parse::()?; 25 | 26 | // Parse `target: &str`, which is an optional argument. 27 | let target: Option = if input.peek(kw::target) { 28 | input.parse::()?; 29 | input.parse::()?; 30 | let t: Expr = input.parse()?; 31 | input.parse::()?; 32 | Some(t) 33 | } else { 34 | None 35 | }; 36 | 37 | // Check whether the next token is `format_string: &str` (which i 38 | // always provided) or `level` (which is an optional expression). 39 | // If `level` is provided, it comes before `format_string`. 40 | let (level, format_string): (Option, LitStr) = if input.peek(LitStr) { 41 | // Only `format_string` is provided. 42 | (None, input.parse()?) 43 | } else { 44 | // Both `level` and `format_string` are provided. 45 | let level: Expr = input.parse()?; 46 | input.parse::()?; 47 | let format_string: LitStr = input.parse()?; 48 | (Some(level), format_string) 49 | }; 50 | 51 | // Parse variadic arguments. 52 | let formatting_args: Option> = if input.is_empty() { 53 | None 54 | } else { 55 | input.parse::()?; 56 | Some(Punctuated::parse_terminated(input)?) 57 | }; 58 | 59 | Ok(Self { 60 | ctx, 61 | target, 62 | level, 63 | format_string, 64 | formatting_args, 65 | }) 66 | } 67 | } 68 | 69 | pub(crate) fn log(args: LogArgs, level: Option) -> Result { 70 | let ctx = args.ctx; 71 | let target = match args.target { 72 | Some(t) => quote! { #t }, 73 | None => quote! { module_path!() }, 74 | }; 75 | let lvl: TokenStream = if let Some(l) = level { 76 | l 77 | } else if let Some(l) = args.level { 78 | quote! { #l } 79 | } else { 80 | return Err(Error::new( 81 | args.format_string.span(), 82 | "missing `level` argument: try passing an `aya_log_ebpf::Level` value", 83 | )); 84 | }; 85 | let format_string = args.format_string; 86 | 87 | let (num_args, write_args) = match args.formatting_args { 88 | Some(formatting_args) => { 89 | let formatting_exprs = formatting_args.iter(); 90 | let num_args = formatting_exprs.len(); 91 | 92 | let write_args = quote! {{ 93 | use ::aya_log_ebpf::WriteToBuf; 94 | Ok::<_, ()>(record_len) #( .and_then(|record_len| { 95 | if record_len >= buf.buf.len() { 96 | return Err(()); 97 | } 98 | { #formatting_exprs }.write(&mut buf.buf[record_len..]).map(|len| record_len + len) 99 | }) )* 100 | }}; 101 | 102 | (num_args, write_args) 103 | } 104 | None => (0, quote! {}), 105 | }; 106 | 107 | // The way of writing to the perf buffer is different depending on whether 108 | // we have variadic arguments or not. 109 | let write_to_perf_buffer = if num_args > 0 { 110 | // Writing with variadic arguments. 111 | quote! { 112 | if let Ok(record_len) = #write_args { 113 | unsafe { ::aya_log_ebpf::AYA_LOGS.output( 114 | #ctx, 115 | &buf.buf[..record_len], 0 116 | )} 117 | } 118 | } 119 | } else { 120 | // Writing with no variadic arguments. 121 | quote! { 122 | unsafe { ::aya_log_ebpf::AYA_LOGS.output( 123 | #ctx, 124 | &buf.buf[..record_len], 0 125 | )} 126 | } 127 | }; 128 | 129 | Ok(quote! { 130 | { 131 | if let Some(buf_ptr) = unsafe { ::aya_log_ebpf::AYA_LOG_BUF.get_ptr_mut(0) } { 132 | let buf = unsafe { &mut *buf_ptr }; 133 | if let Ok(header_len) = ::aya_log_ebpf::write_record_header( 134 | &mut buf.buf, 135 | #target, 136 | #lvl, 137 | module_path!(), 138 | file!(), 139 | line!(), 140 | #num_args, 141 | ) { 142 | if let Ok(message_len) = ::aya_log_ebpf::write_record_message( 143 | &mut buf.buf[header_len..], 144 | #format_string, 145 | ) { 146 | let record_len = header_len + message_len; 147 | 148 | #write_to_perf_buffer 149 | } 150 | } 151 | } 152 | } 153 | }) 154 | } 155 | 156 | pub(crate) fn error(args: LogArgs) -> Result { 157 | log( 158 | args, 159 | Some(quote! { ::aya_log_ebpf::macro_support::Level::Error }), 160 | ) 161 | } 162 | 163 | pub(crate) fn warn(args: LogArgs) -> Result { 164 | log( 165 | args, 166 | Some(quote! { ::aya_log_ebpf::macro_support::Level::Warn }), 167 | ) 168 | } 169 | 170 | pub(crate) fn info(args: LogArgs) -> Result { 171 | log( 172 | args, 173 | Some(quote! { ::aya_log_ebpf::macro_support::Level::Info }), 174 | ) 175 | } 176 | 177 | pub(crate) fn debug(args: LogArgs) -> Result { 178 | log( 179 | args, 180 | Some(quote! { ::aya_log_ebpf::macro_support::Level::Debug }), 181 | ) 182 | } 183 | 184 | pub(crate) fn trace(args: LogArgs) -> Result { 185 | log( 186 | args, 187 | Some(quote! { ::aya_log_ebpf::macro_support::Level::Trace }), 188 | ) 189 | } 190 | -------------------------------------------------------------------------------- /ebpf/aya-log-ebpf-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::parse_macro_input; 3 | 4 | mod expand; 5 | 6 | #[proc_macro] 7 | pub fn log(args: TokenStream) -> TokenStream { 8 | let args = parse_macro_input!(args as expand::LogArgs); 9 | expand::log(args, None) 10 | .unwrap_or_else(|err| err.to_compile_error()) 11 | .into() 12 | } 13 | 14 | #[proc_macro] 15 | pub fn error(args: TokenStream) -> TokenStream { 16 | let args = parse_macro_input!(args as expand::LogArgs); 17 | expand::error(args) 18 | .unwrap_or_else(|err| err.to_compile_error()) 19 | .into() 20 | } 21 | 22 | #[proc_macro] 23 | pub fn warn(args: TokenStream) -> TokenStream { 24 | let args = parse_macro_input!(args as expand::LogArgs); 25 | expand::warn(args) 26 | .unwrap_or_else(|err| err.to_compile_error()) 27 | .into() 28 | } 29 | 30 | #[proc_macro] 31 | pub fn info(args: TokenStream) -> TokenStream { 32 | let args = parse_macro_input!(args as expand::LogArgs); 33 | expand::info(args) 34 | .unwrap_or_else(|err| err.to_compile_error()) 35 | .into() 36 | } 37 | 38 | #[proc_macro] 39 | pub fn debug(args: TokenStream) -> TokenStream { 40 | let args = parse_macro_input!(args as expand::LogArgs); 41 | expand::debug(args) 42 | .unwrap_or_else(|err| err.to_compile_error()) 43 | .into() 44 | } 45 | 46 | #[proc_macro] 47 | pub fn trace(args: TokenStream) -> TokenStream { 48 | let args = parse_macro_input!(args as expand::LogArgs); 49 | expand::trace(args) 50 | .unwrap_or_else(|err| err.to_compile_error()) 51 | .into() 52 | } 53 | -------------------------------------------------------------------------------- /ebpf/aya-log-ebpf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aya-log-ebpf" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | aya-bpf = { git = "https://github.com/aya-rs/aya", branch = "main" } 8 | aya-log-common = { path = "../../aya-log-common" } 9 | aya-log-ebpf-macros = { path = "../aya-log-ebpf-macros" } 10 | 11 | [lib] 12 | path = "src/lib.rs" 13 | -------------------------------------------------------------------------------- /ebpf/aya-log-ebpf/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | use aya_bpf::{ 3 | macros::map, 4 | maps::{PerCpuArray, PerfEventByteArray}, 5 | }; 6 | pub use aya_log_common::{ 7 | write_record_header, write_record_message, Level, WriteToBuf, LOG_BUF_CAPACITY, 8 | }; 9 | pub use aya_log_ebpf_macros::{debug, error, info, log, trace, warn}; 10 | 11 | #[doc(hidden)] 12 | #[repr(C)] 13 | pub struct LogBuf { 14 | pub buf: [u8; LOG_BUF_CAPACITY], 15 | } 16 | 17 | #[doc(hidden)] 18 | #[map] 19 | pub static mut AYA_LOG_BUF: PerCpuArray = PerCpuArray::with_max_entries(1, 0); 20 | 21 | #[doc(hidden)] 22 | #[map] 23 | pub static mut AYA_LOGS: PerfEventByteArray = PerfEventByteArray::new(0); 24 | 25 | #[doc(hidden)] 26 | pub mod macro_support { 27 | pub use aya_log_common::{Level, LOG_BUF_CAPACITY}; 28 | pub use aya_log_ebpf_macros::log; 29 | } 30 | -------------------------------------------------------------------------------- /ebpf/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [dependencies] 8 | aya-bpf = { git = "https://github.com/aya-rs/aya", branch = "main" } 9 | aya-log-ebpf = { path = "../aya-log-ebpf" } 10 | 11 | [[bin]] 12 | name = "example" 13 | path = "src/main.rs" 14 | -------------------------------------------------------------------------------- /ebpf/example/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use aya_bpf::{macros::tracepoint, programs::TracePointContext, BpfContext}; 5 | use aya_log_ebpf::{debug, error, info, trace, warn}; 6 | 7 | #[tracepoint] 8 | pub fn example(ctx: TracePointContext) -> u32 { 9 | error!(&ctx, "this is an error message 🚨"); 10 | warn!(&ctx, "this is a warning message ⚠️"); 11 | info!(&ctx, "this is an info message ℹ️"); 12 | debug!(&ctx, "this is a debug message ️🐝"); 13 | trace!(&ctx, "this is a trace message 🔍"); 14 | let pid = ctx.pid(); 15 | info!(&ctx, "a message with args PID: {}", pid); 16 | 0 17 | } 18 | 19 | #[panic_handler] 20 | fn panic(_info: &core::panic::PanicInfo) -> ! { 21 | unsafe { core::hint::unreachable_unchecked() } 22 | } 23 | -------------------------------------------------------------------------------- /ebpf/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /ebpf/rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | reorder_imports = true 3 | imports_granularity = "Crate" 4 | 5 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "aya-log, aya-log-common: release version {{version}}" 2 | post-release-commit-message = "aya-log, aya-log-common: start next development iteration {{next_version}}" 3 | consolidate-pushes = true 4 | consolidate-commits = true 5 | shared-version = true 6 | dev-version = true 7 | dev-version-ext = "dev.0" 8 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | reorder_imports = true 3 | imports_granularity = "Crate" 4 | 5 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | publish = false 4 | version = "0.1.0" 5 | edition = "2018" 6 | 7 | [dependencies] 8 | structopt = {version = "0.3", default-features = false } 9 | anyhow = "1" 10 | 11 | [package.metadata.release] 12 | release = false 13 | -------------------------------------------------------------------------------- /xtask/src/build_ebpf.rs: -------------------------------------------------------------------------------- 1 | use std::{path::PathBuf, process::Command}; 2 | 3 | use structopt::StructOpt; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | pub enum Architecture { 7 | BpfEl, 8 | BpfEb, 9 | } 10 | 11 | impl std::str::FromStr for Architecture { 12 | type Err = String; 13 | 14 | fn from_str(s: &str) -> Result { 15 | Ok(match s { 16 | "bpfel-unknown-none" => Architecture::BpfEl, 17 | "bpfeb-unknown-none" => Architecture::BpfEb, 18 | _ => return Err("invalid target".to_owned()), 19 | }) 20 | } 21 | } 22 | 23 | impl std::fmt::Display for Architecture { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | f.write_str(match self { 26 | Architecture::BpfEl => "bpfel-unknown-none", 27 | Architecture::BpfEb => "bpfeb-unknown-none", 28 | }) 29 | } 30 | } 31 | 32 | #[derive(StructOpt)] 33 | pub struct Options { 34 | #[structopt(default_value = "bpfel-unknown-none", long)] 35 | target: Architecture, 36 | #[structopt(long)] 37 | release: bool, 38 | } 39 | 40 | pub fn build(opts: Options) -> Result<(), anyhow::Error> { 41 | let dir = PathBuf::from("aya-log-ebpf"); 42 | let target = format!("--target={}", opts.target); 43 | let mut args = vec![ 44 | "+nightly", 45 | "build", 46 | "--verbose", 47 | target.as_str(), 48 | "-Z", 49 | "build-std=core", 50 | ]; 51 | if opts.release { 52 | args.push("--release") 53 | } 54 | let status = Command::new("cargo") 55 | .current_dir(&dir) 56 | .args(&args) 57 | .status() 58 | .expect("failed to build bpf examples"); 59 | assert!(status.success()); 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | mod build_ebpf; 2 | 3 | use std::process::exit; 4 | 5 | use structopt::StructOpt; 6 | #[derive(StructOpt)] 7 | pub struct Options { 8 | #[structopt(subcommand)] 9 | command: Command, 10 | } 11 | 12 | #[derive(StructOpt)] 13 | enum Command { 14 | BuildEbpf(build_ebpf::Options), 15 | } 16 | 17 | fn main() { 18 | let opts = Options::from_args(); 19 | 20 | use Command::*; 21 | let ret = match opts.command { 22 | BuildEbpf(opts) => build_ebpf::build(opts), 23 | }; 24 | 25 | if let Err(e) = ret { 26 | eprintln!("{:#}", e); 27 | exit(1); 28 | } 29 | } 30 | --------------------------------------------------------------------------------