[Aya-0] Aya-Ebpf Basic


0. 基本概念

Ebpf是介于Linux Kernel(内核层)User Space(用户层)的程序,它可以实现在用户层编写代码来对内核层进行一定的观测、介入。具体的技术细节,这里不进行过多的阐释。总体架构可以参考以下两个网站,也是 aya 推荐的:

  1. https://ebpf.io/what-is-ebpf/
  2. https://docs.cilium.io/en/stable/reference-guides/bpf/index.html

1. 程序结构

Ebpf的程序一般由两部分组成,一个用户层的Loader,一个内核层执行的Program。在功能上,我觉得和Injector非常相似,主要的目的就是将自己的代码注入到另一个进程中进行执行。对于Ebpf来说,相当于系统提供了Api让我们注入Ebpf-Program到内核进程中执行。

Ebpf程序是事件驱动的开发模型,而他的Loader又是由用户层提供的,所以Ebpf程序是没有main函数作为入口的。在流程上,就需要在用户空间指定一个时机来触发事件,从而执行相应的Ebpf程序,类似一种回调的机制。

以下面一个缩略的 Kprobe Loader 为例,可以清晰的看出 Loader 的结构

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    ...
    // 获取 Ebpf 程序的"句柄"
    let program: &mut KProbe = ebpf.program_mut("k_openat").unwrap().try_into()?;
    // 把 Ebpf 程序注入到内核
    program.load()?;
    // 在 "do_sys_openat2" 这个 函数地址 + 0 offset 注册事件,当内核执行到这个位置的时候 `ebpf_program` 将会被调用。
    program.attach("do_sys_openat2", 0)?;
    ...
    Ok(())
}

这里的 program 虽然是程序的意思,不过实际上是函数级别的,在 Ebpf 中,一个函数就代表了一个 Ebpf 程序(Program),比如这里的 k_openat 就是一个 Ebpf 程序。使用#[kprobe] 标记的就是kprobe 类型的 Ebpf 程序,以此类推,并且不同类型的程序处理问题的能力各有不同。

#[kprobe]
pub fn k_openat(ctx: ProbeContext) -> u32 {
    match try_k_openat(ctx) {
        Ok(ret) => ret,
        Err(ret) => ret,
    }
}

fn try_k_openat(ctx: ProbeContext) -> Result<u32, u32> {
    info!(&ctx, "k_openat called");
    Ok(0)
}

2. 项目结构

这里则简单地说一下 aya 给出的 Template 结构,我觉得挺不错的。 首先总共划分为 3 个子模块,分别是:

  1. 和项目同名的 probe_testLoader 模块,这里主要实现用户空间的处理逻辑,对Tokio的支持也就在这。
  2. probe_test-ebpf 模块,则是真正的 ebpf 程序模块,注入到内核的代码在这里实现。
  3. probe_test-common 模块可以写一些LoaderProgram之间共享的一些数据结构、功能函数。
./probe_test
├── Cargo.lock
├── Cargo.toml
├── README.md
├── probe_test
│   ├── Cargo.toml
│   ├── build.rs
│   └── src
│       └── main.rs
├── probe_test-common
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── probe_test-ebpf
│   ├── Cargo.toml
│   ├── build.rs
│   └── src
│       ├── lib.rs
│       └── main.rs
├── rustfmt.toml
└── target
    ├── CACHEDIR.TAG
    └── aarch64-unknown-linux-musl

同时也能看到每一个子模块内,都有build.rs这个构建脚本来控制编译流程。同时在probe_test/src/main.rs内使用了 aya::contact_raw_bytes,将 Program 程序编译生成的 .o 文件内联在 Loader 程序之内。所以不同于其他 Ebpf 框架,Aya 编译的程序只需要将可执行文件复制到目标机器上就可以直接运行了,方便挺多。

3. Aya 的代码怎么看

aya 的文档和源码可以在 https://docs.rs/aya/0.13.1/aya/ 找到,不过这里是Aya 用户空间程序的文档。对于 pub fn k_openat(ctx: ProbeContext) 这里的ProbeContext ,此类的Ebpf侧代码则在 crate::aya-ebpf 中实现。他们其实是不同的 crates,看源码的时候需要稍微注意一下。

4. Tips

aya 的日志是自己的实现的一套机制,依赖于环境变量RUST_LOG=info|debug|...的值,在一开始,我以为是自己的代码挂钩失败了,没有回显。后面在 Aya 的 Aya Book 仓库的实例代码中看到 RUST_LOG=info cargo run 我才发现这么个问题,比较坑。