From 52268b67364947c201179384bad058a640434063 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 26 Feb 2022 00:06:27 +0900 Subject: [PATCH 1/2] Support global variables in BPF programs Signed-off-by: Junyeong Jeong --- redbpf-macros/src/lib.rs | 19 ++++++++++++++++++ redbpf/src/lib.rs | 42 +++++++++++++++++++++++++++++++++++++++ redbpf/src/load/loader.rs | 8 ++++++-- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index e0a33e72..4543e241 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -904,3 +904,22 @@ fn parse_format_string(fmt: &str) -> Vec { } res } + +/// Attribute macro for defining global variables that can be accessed by +/// userspace programs +/// +/// It is not necessary to call this attribute for all global variables. This +/// attribute is needed to be called only if global variables are accessed by +/// userspace programs. You can omit this attribute if the global variable is +/// used among only BPF programs. +#[proc_macro_attribute] +pub fn global(_attrs: TokenStream, item: TokenStream) -> TokenStream { + let item = parse_macro_input!(item as ItemStatic); + let section_name = format!("globals/{}", item.ident.to_string()); + quote! { + #[no_mangle] + #[link_section = #section_name] + #item + } + .into() +} diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 3e4afc8d..fb742029 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -1380,6 +1380,19 @@ impl Module { pub fn task_iter_mut(&mut self, name: &str) -> Option<&mut TaskIter> { self.task_iters_mut().find(|p| p.common.name == name) } + + /// Get [`GlobalVariable`](struct.GlobalVariable.html) + /// + /// Returned instance is corresponding to a global variable of which name + /// is `name` in BPF programs. + pub fn global(&self, name: &str) -> Result> { + let map = self.map(name).ok_or_else(|| { + error!("map not found: {}", name); + Error::Map + })?; + + GlobalVariable::new(map) + } } impl<'a> ModuleBuilder<'a> { @@ -1483,6 +1496,10 @@ impl<'a> ModuleBuilder<'a> { symval_to_map_builders.insert(sym.st_value, map_builder); } } + (hdr::SHT_PROGBITS, Some("globals"), Some(name)) => { + let map_builder = MapBuilder::with_section_data(name, &content)?; + map_builders.insert(shndx, map_builder); + } (hdr::SHT_PROGBITS, Some(kind @ "kprobe"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "kretprobe"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "uprobe"), Some(name)) @@ -2729,6 +2746,31 @@ impl Drop for TaskIter { } } +/// A userspace proxy for global variables of BPF programs +/// +/// Global variables in BPF programs are implemented by BPF array map. +/// `GlobalVariable` provides a simple wrapper around it. +pub struct GlobalVariable<'a, T: Clone> { + array: Array<'a, T>, +} + +impl<'a, T: Clone> GlobalVariable<'a, T> { + fn new(map: &Map) -> Result> { + let array = Array::::new(map)?; + Ok(GlobalVariable { array }) + } + + /// Load value from the global variable of BPF programs + pub fn load(&self) -> Option { + self.array.get(0) + } + + /// Store value to the global variable of BPF programs + pub fn store(&self, val: T) -> Result<()> { + self.array.set(0, val) + } +} + #[inline] fn add_relocation( rels: &mut Vec, diff --git a/redbpf/src/load/loader.rs b/redbpf/src/load/loader.rs index 4bd9d30a..23ea2a57 100644 --- a/redbpf/src/load/loader.rs +++ b/redbpf/src/load/loader.rs @@ -15,8 +15,8 @@ use std::path::Path; use crate::load::map_io::PerfMessageStream; use crate::{cpus, Program}; use crate::{ - Error, KProbe, Map, Module, PerfMap, SkLookup, SocketFilter, StreamParser, StreamVerdict, - TaskIter, UProbe, XDP, + Error, GlobalVariable, KProbe, Map, Module, PerfMap, SkLookup, SocketFilter, StreamParser, + StreamVerdict, TaskIter, UProbe, XDP, }; #[derive(Debug)] @@ -188,4 +188,8 @@ impl Loaded { pub fn task_iter_mut(&mut self, name: &str) -> Option<&mut TaskIter> { self.module.task_iter_mut(name) } + + pub fn global(&self, name: &str) -> Result, Error> { + self.module.global(name) + } } From ee42f2907c10c0a1a5d4ae0afebc26b722677c8b Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 26 Feb 2022 00:14:53 +0900 Subject: [PATCH 2/2] Add example code that makes use of global variables Signed-off-by: Junyeong Jeong --- examples/example-probes/Cargo.toml | 5 ++ .../example-probes/src/global_var/main.rs | 28 +++++++ examples/example-probes/src/global_var/mod.rs | 13 ++++ examples/example-probes/src/lib.rs | 1 + .../example-userspace/examples/global-var.rs | 73 +++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 examples/example-probes/src/global_var/main.rs create mode 100644 examples/example-probes/src/global_var/mod.rs create mode 100644 examples/example-userspace/examples/global-var.rs diff --git a/examples/example-probes/Cargo.toml b/examples/example-probes/Cargo.toml index eaf202dc..6701aaee 100644 --- a/examples/example-probes/Cargo.toml +++ b/examples/example-probes/Cargo.toml @@ -88,3 +88,8 @@ required-features = ["probes"] name = "bounded_loop" path = "src/bounded_loop/main.rs" required-features = ["probes"] + +[[bin]] +name = "global_var" +path = "src/global_var/main.rs" +required-features = ["probes"] diff --git a/examples/example-probes/src/global_var/main.rs b/examples/example-probes/src/global_var/main.rs new file mode 100644 index 00000000..c11ff9d2 --- /dev/null +++ b/examples/example-probes/src/global_var/main.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] +use core::sync::atomic::Ordering; + +use redbpf_probes::kprobe::prelude::*; + +use example_probes::global_var::{GLOBAL_VAR, GLOBAL_VAR_INCORRECT}; + +program!(0xFFFFFFFE, "GPL"); + +#[map] +static mut PERCPU_MAP: PerCpuArray = PerCpuArray::with_max_entries(1); + +#[kprobe] +fn incr_write_count(_regs: Registers) { + unsafe { + GLOBAL_VAR.fetch_add(1, Ordering::Relaxed); + } + + unsafe { + GLOBAL_VAR_INCORRECT += 1; + } + + unsafe { + let val = PERCPU_MAP.get_mut(0).unwrap(); + *val += 1; + } +} diff --git a/examples/example-probes/src/global_var/mod.rs b/examples/example-probes/src/global_var/mod.rs new file mode 100644 index 00000000..4070dd4f --- /dev/null +++ b/examples/example-probes/src/global_var/mod.rs @@ -0,0 +1,13 @@ +use core::sync::atomic::AtomicU64; + +use redbpf_macros::global; + +/// global variable is shared between multiple cores so proper synchronization +/// should be involved carefully. +#[global] +pub static GLOBAL_VAR: AtomicU64 = AtomicU64::new(0); + +/// global variable without any synchronization mechanism. This results in wrong +/// statistics. +#[global] +pub static mut GLOBAL_VAR_INCORRECT: u64 = 0; diff --git a/examples/example-probes/src/lib.rs b/examples/example-probes/src/lib.rs index 743880c8..e2685a9c 100644 --- a/examples/example-probes/src/lib.rs +++ b/examples/example-probes/src/lib.rs @@ -12,6 +12,7 @@ pub mod bindings; pub mod echo; +pub mod global_var; pub mod hashmaps; pub mod mallocstacks; pub mod p0f; diff --git a/examples/example-userspace/examples/global-var.rs b/examples/example-userspace/examples/global-var.rs new file mode 100644 index 00000000..7349e9db --- /dev/null +++ b/examples/example-userspace/examples/global-var.rs @@ -0,0 +1,73 @@ +/// This example shows that global variable without proper synchronization has +/// incorrect value. On the other hand, the values of per-cpu map and global +/// variable updated by synchronized method are the same. It may take some +/// time to observe the incorrect value if the write-load is light. +/// +/// Note that the values of per-cpu map and global variable differ from time to +/// time because of the timing of map-read. +use libc; +use std::process; +use std::time::Duration; +use tokio; +use tokio::select; +use tracing::{error, Level}; +use tracing_subscriber::FmtSubscriber; + +use redbpf::{load::Loader, Array, PerCpuArray}; + +use probes::global_var::{GLOBAL_VAR, GLOBAL_VAR_INCORRECT}; +#[repr(C)] +#[derive(Debug, Clone)] +struct Data { + var: u64, + var_wo_sync: u64, +} + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let subscriber = FmtSubscriber::builder() + .with_max_level(Level::TRACE) + .finish(); + tracing::subscriber::set_global_default(subscriber).unwrap(); + if unsafe { libc::geteuid() != 0 } { + error!("You must be root to use eBPF!"); + process::exit(1); + } + + let mut loaded = Loader::load(probe_code()).unwrap(); + loaded + .kprobe_mut("incr_write_count") + .expect("kprobe_mut error") + .attach_kprobe("ksys_write", 0) + .expect("error attach_kprobe"); + + let gvar = loaded + .global::("GLOBAL_VAR") + .expect("error on accessing gvar"); + let gvar_wo_sync = loaded + .global::("GLOBAL_VAR_INCORRECT") + .expect("error on accessing gvar-wo-sync "); + let percpu_map = + PerCpuArray::::new(loaded.map("PERCPU_MAP").expect("PERCPU_MAP not found")) + .expect("can not initialize PerCpuArray"); + loop { + let pcpu_val = percpu_map.get(0).expect("percpu value"); + println!( + "w/ sync, w/o sync, pcpu = {}, {}, {}", + gvar.load().unwrap(), + gvar_wo_sync.load().unwrap(), + pcpu_val.iter().sum::() + ); + select! { + _ = tokio::time::sleep(Duration::from_secs(1)) => {} + _ = tokio::signal::ctrl_c() => { break } + } + } +} + +fn probe_code() -> &'static [u8] { + include_bytes!(concat!( + env!("OUT_DIR"), + "/target/bpf/programs/global_var/global_var.elf" + )) +}