Whistler: Live eBPF Programming from the Common Lisp REPL
Whistler是一个用Common Lisp编写的eBPF DSL和优化编译器,允许开发者直接在REPL中编写、编译和加载eBPF程序。
核心亮点
- 直接在REPL中开发:修改probe后重新eval即可立即看到结果,反馈循环是即时的
- 完整的优化编译器:生成高度优化的eBPF字节码,效果等同于或优于clang编译的C代码
- 无需工具链:直接生成ELF eBPF文件,不需要clang+llvm工具链
- 零C依赖的loader:whistler/loader是完全用Common Lisp编写的BPF用户空间加载器
- 跨语言支持:可以生成匹配的C、Go、Rust、Python结构体定义
技术细节
Whistler编译器在宏展开时运行。当SBCL编译with-bpf-session表单时,eBPF字节码已经是一个常量——作为字面字节数组嵌入在展开式中。运行时代码只是创建maps、patch FD重定位,并调用bpf(BPF_PROG_LOAD, ...)。
关键创新:whistler:defstruct为BPF和CL两边生成访问器。一次定义同时服务于内核和用户空间,无需手动解析字节偏移。
示例代码
(with-bpf-session ()
(bpf:map counter :type :hash :key-size 4 :value-size 8 :max-entries 1)
(bpf:prog trace (:type :kprobe
:section "kprobe/__x64_sys_execve"
:license "GPL")
(incf (getmap counter 0))
0)
(bpf:attach trace "__x64_sys_execve")
(loop (sleep 1)
(format t "execve count: ~d~%" (bpf:map-ref counter 0))))
为什么这很重要
传统的eBPF工作流程是:为BPF端写C,用clang编译,然后为用户空间端写Go/Rust/Python。两门语言、单独的构建步骤、多个进程。
使用Whistler 1.0,工作流程是:写Lisp。编译器、加载器和用户空间应用程序共享一个进程。