redbpf 是啥?
一个Rust写的ebpf库,bcc写的难受了可以试试这个。
可以把一个Rust方法挂载给eBPF
代码
git clone https://github.com/foniod/redbpf.git
安装llvm
sudo apt-get update \&& sudo apt-get -y install \wget \build-essential \software-properties-common \lsb-release \libelf-dev \linux-headers-generic \pkg-config \&& wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 13 && rm -f ./llvm.sh
安装cargo-bpf
首先配置下 这个环境变量export LLVM_SYS_130_PREFIX=/usr/lib/llvm-13
这里 llvm 配置你自己的安装路径
cargo install cargo-bpf
遇到这个坑
No suitable version of LLVM was found system-wide or pointed to by LLVM_SYS_120_PREFIX
看了下切到中科大源后,安装 cargo-bpf 配置的还是 llvm-sys-120 ,对应llvm12。但是README中让咱装的llvm13。
直接从当前的源码编译安装就行了
cd cargo-bpfcargo install --path .
Helloworld
BPF程序分为内核端和用户端。内核执行咱们编译的BPF字节码采集追踪数据,然后和用户端通过Map共享数据。
我们先来实现下内核端:
内核端
新建项目
cargo bpf new bpf_project
新增一个bpf模块
cd bpf_projectcargo bpf add m1
生成了这样的目录结构
├── Cargo.lock├── Cargo.toml└── src├── lib.rs└── m1├── main.rs└── mod.rs
首先在mod.rs中定义我们要给用户端共享的数据结构
pub const PATHLEN: usize = 256;#[repr(C)]#[derive(Debug, Clone)]pub struct OpenPath {pub filename: [u8; PATHLEN],}impl Default for OpenPath {fn default() -> Self {OpenPath {filename: [0; PATHLEN],}}}
后面我们将在此结构体中保存所有调用open的文件名,
repr[c] 代表此结构体按照C标准对齐。
然后在main.rs中定义我们的BPF程序
#[map]static mut OPEN_PATHS: PerfMap<OpenPath> = PerfMap::with_max_entries(1024);#[kprobe]fn do_sys_open(regs: Registers) {let mut path = OpenPath::default();unsafe {// 从这个参数中取出文件名let filename = regs.parm2() as *const u8;if bpf_probe_read_user_str(path.filename.as_mut_ptr() as *mut _,path.filename.len() as u32,filename as *const _,) <= 0{bpf_trace_printk(b"error on bpf_probe_read_user_str\0");return;}OPEN_PATHS.insert(regs.ctx, &path);}}
[map] 宏定义了我们用来跟用户端共享的map。do_sys_open 代表此函数被附着 到do_sys_open 上。
bpf_probe_read_user_str 是内核提供的供BPF程序使用的辅助函数,用于复制字符串。
复制完成后插入map。
最后编译
cargo bpf build
不出意外,我们的BPF程序编译好后会在这里:
target/bpf/programs/m1/m1.elf
用户端
新建个普通项目,这次cargo new 就可以了。不再需要cargo bpf
cargo new bpf_user
cargo.toml里这样配置下:
[package]name = "bpf_user"version = "0.1.0"edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]bpf_project = { path="../bpf_project" }redbpf = { version = "2.3.0", features = ["load"] }tokio = { version = "1.0", features = ["rt", "signal", "time", "io-util", "net", "sync"] }tracing-subscriber = "0.2"tracing = "0.1"futures = "0.3"
bpf_project 这里配置咱们刚搞的bpf程序相对路径。
日志,tokio都安排上。
main.rs
use tracing_subscriber::FmtSubscriber;#[tokio::main(flavor = "current_thread")]async fn main() {let subscriber = FmtSubscriber::builder().with_max_level(Level::WARN).finish();tracing::subscriber::set_global_default(subscriber).unwrap();let mut loaded = Loader::load(probe_code()).expect("Error on Loader load");let probes = loaded.kprobe_mut("do_sys_open").expect("Error on Loaded::kprobe_mut");probes.attach_kprobe("do_sys_open", 0).expect("Error on Attach probe");probes.attach_kprobe("do_sys_openat2", 0).expect("Error on Load do_sys_openat2");while let Some((map_name, events)) = loaded.events.next().await {if map_name == "OPEN_PATHS" {for event in events {let open_path = unsafe { ptr::read(event.as_ptr() as *const OpenPath) };unsafe {let cfilename = CStr::from_ptr(open_path.filename.as_ptr() as *const _);println!("{}", cfilename.to_string_lossy());};}}}}fn probe_code() -> &'static [u8] {include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"),"../bpf_project/m1/target/bpf/programs/m1/m1.elf"))}
probe_code 配置编译好的elf的路径。附着系统调用搞个do_sys_open,do_sys_openat2 两个就可以了。最后我们从封装好的EVENT中取出内核端定义的MAP,从中读数据就可以了,此时将打印出其他调用open的进程传入的path参数。
编译用户端
cargo build
执行用户端需要root权限,所以:
sudo target/debug/bpf_user
