redbpf 是啥?

一个Rust写的ebpf库,bcc写的难受了可以试试这个。
可以把一个Rust方法挂载给eBPF

代码

  1. git clone https://github.com/foniod/redbpf.git

安装llvm

  1. sudo apt-get update \
  2. && sudo apt-get -y install \
  3. wget \
  4. build-essential \
  5. software-properties-common \
  6. lsb-release \
  7. libelf-dev \
  8. linux-headers-generic \
  9. pkg-config \
  10. && 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 配置你自己的安装路径

  1. 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。

直接从当前的源码编译安装就行了

  1. cd cargo-bpf
  2. cargo install --path .

Helloworld

BPF程序分为内核端和用户端。内核执行咱们编译的BPF字节码采集追踪数据,然后和用户端通过Map共享数据。
我们先来实现下内核端:

内核端

新建项目
  1. cargo bpf new bpf_project

新增一个bpf模块

  1. cd bpf_project
  2. cargo bpf add m1

生成了这样的目录结构

  1. ├── Cargo.lock
  2. ├── Cargo.toml
  3. └── src
  4. ├── lib.rs
  5. └── m1
  6. ├── main.rs
  7. └── mod.rs

首先在mod.rs中定义我们要给用户端共享的数据结构

  1. pub const PATHLEN: usize = 256;
  2. #[repr(C)]
  3. #[derive(Debug, Clone)]
  4. pub struct OpenPath {
  5. pub filename: [u8; PATHLEN],
  6. }
  7. impl Default for OpenPath {
  8. fn default() -> Self {
  9. OpenPath {
  10. filename: [0; PATHLEN],
  11. }
  12. }
  13. }

后面我们将在此结构体中保存所有调用open的文件名,
repr[c] 代表此结构体按照C标准对齐。

然后在main.rs中定义我们的BPF程序

  1. #[map]
  2. static mut OPEN_PATHS: PerfMap<OpenPath> = PerfMap::with_max_entries(1024);
  3. #[kprobe]
  4. fn do_sys_open(regs: Registers) {
  5. let mut path = OpenPath::default();
  6. unsafe {
  7. // 从这个参数中取出文件名
  8. let filename = regs.parm2() as *const u8;
  9. if bpf_probe_read_user_str(
  10. path.filename.as_mut_ptr() as *mut _,
  11. path.filename.len() as u32,
  12. filename as *const _,
  13. ) <= 0
  14. {
  15. bpf_trace_printk(b"error on bpf_probe_read_user_str\0");
  16. return;
  17. }
  18. OPEN_PATHS.insert(regs.ctx, &path);
  19. }
  20. }

[map] 宏定义了我们用来跟用户端共享的map。do_sys_open 代表此函数被附着 到do_sys_open 上。

bpf_probe_read_user_str 是内核提供的供BPF程序使用的辅助函数,用于复制字符串。

复制完成后插入map。

最后编译

  1. cargo bpf build

不出意外,我们的BPF程序编译好后会在这里:

target/bpf/programs/m1/m1.elf

用户端

新建个普通项目,这次cargo new 就可以了。不再需要cargo bpf

  1. cargo new bpf_user

cargo.toml里这样配置下:

  1. [package]
  2. name = "bpf_user"
  3. version = "0.1.0"
  4. edition = "2021"
  5. # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
  6. [dependencies]
  7. bpf_project = { path="../bpf_project" }
  8. redbpf = { version = "2.3.0", features = ["load"] }
  9. tokio = { version = "1.0", features = ["rt", "signal", "time", "io-util", "net", "sync"] }
  10. tracing-subscriber = "0.2"
  11. tracing = "0.1"
  12. futures = "0.3"

bpf_project 这里配置咱们刚搞的bpf程序相对路径。

日志,tokio都安排上。

main.rs

  1. use tracing_subscriber::FmtSubscriber;
  2. #[tokio::main(flavor = "current_thread")]
  3. async fn main() {
  4. let subscriber = FmtSubscriber::builder()
  5. .with_max_level(Level::WARN)
  6. .finish();
  7. tracing::subscriber::set_global_default(subscriber).unwrap();
  8. let mut loaded = Loader::load(probe_code()).expect("Error on Loader load");
  9. let probes = loaded
  10. .kprobe_mut("do_sys_open")
  11. .expect("Error on Loaded::kprobe_mut");
  12. probes
  13. .attach_kprobe("do_sys_open", 0)
  14. .expect("Error on Attach probe");
  15. probes
  16. .attach_kprobe("do_sys_openat2", 0)
  17. .expect("Error on Load do_sys_openat2");
  18. while let Some((map_name, events)) = loaded.events.next().await {
  19. if map_name == "OPEN_PATHS" {
  20. for event in events {
  21. let open_path = unsafe { ptr::read(event.as_ptr() as *const OpenPath) };
  22. unsafe {
  23. let cfilename = CStr::from_ptr(open_path.filename.as_ptr() as *const _);
  24. println!("{}", cfilename.to_string_lossy());
  25. };
  26. }
  27. }
  28. }
  29. }
  30. fn probe_code() -> &'static [u8] {
  31. include_bytes!(concat!(
  32. env!("CARGO_MANIFEST_DIR"),
  33. "../bpf_project/m1/target/bpf/programs/m1/m1.elf"
  34. ))
  35. }

probe_code 配置编译好的elf的路径。附着系统调用搞个do_sys_open,do_sys_openat2 两个就可以了。最后我们从封装好的EVENT中取出内核端定义的MAP,从中读数据就可以了,此时将打印出其他调用open的进程传入的path参数。

编译用户端

cargo build

执行用户端需要root权限,所以:

sudo target/debug/bpf_user