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-bpf
cargo install --path .
Helloworld
BPF程序分为内核端和用户端。内核执行咱们编译的BPF字节码采集追踪数据,然后和用户端通过Map共享数据。
我们先来实现下内核端:
内核端
新建项目
cargo bpf new bpf_project
新增一个bpf模块
cd bpf_project
cargo 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