Writing eBPF in Zig. Thanks to Zig's comptime and BTF, we can equip eBPF with strong type system both at comptime and runtime!
Suppose you want to trace the kernel function path_listxattr, and here's its prototype:
static ssize_t path_listxattr(const char __user *pathname, char __user *list,
size_t size, unsigned int lookup_flags)
As you can see, it has 4 input parameters and return type is ssize_t
.
With ctx = bpf.Kprobe{.name = "path_listxattr"}.Ctx()
, you could retrieve
the input parameter with ctx.arg0()
, ctx.arg1()
, ctx.arg2()
and ctx.arg3()
respectively,
and return value with ctx.ret()
.
the type will be consistent with the above prototype. If you try to access a non-existing
parameter, e.g. ctx.arg4()
, you will get a compilation error.
This also applies to syscall
with bpf.Ksyscall
, tracepoint
with bpf.Tracepoint
and
fentry
with bpf.Fentry
.
When writing in C, you always have to check the error conditions
(the return value of the helper function, pointer validation, ...)
With zbpf
, you won't care about the these cases, we handle it under the hood for you,
just focus on the business logic.
The following are some examples:
bpf.Map
takes care BPF map'supdate
anddelete
error.bpf.PerfEventArray
handles event output failure.bpf.RingBuffer
also handles space reservation.bpf.Xdp
validates the pointer for you.
If some error happens, you could get all the information (file, line number, return value ...) you need to debug in the kernel trace buffer:
~> sudo bpftool prog tracelog
test-11717 [005] d..21 10990692.273976: bpf_trace_printk: error occur at src/bpf/map.zig:110 return -2
- Make sure the linux kernel is built with
CONFIG_DEBUG_INFO_BTF=y
. - The only runtime library is
libc
(this is not even necessary if you build with musl-libc).
- Download the lastest Zig.
- Clone this repostory.
- Build with
zig build -Dbpf=/path/to/your/bpf/prog.zig -Dmain=/path/to/your/main.zig
.
For cross-compiling, you could specify the target with -Dtarget=<target>
,
the list of all supported targets could be retrieved by zig targets
.
Moreover, you could specify the target kernel with -Dvmlinux=/path/to/vmlinux
to extract BTF from it, otherwise, current kernel's BTF will be used.
That's all! The generated binary is located at ./zig-out/bin/zbpf
,
feel free to run it on your target machine.
Here's the Documentations generated by Zig's AutoDoc for you reference.
trace
is a tool built on top of zbpf
framework to trace kernel functions and syscalls.
It's heavily inspired by retsnoop.
One improvement I made (which is also what I feel when using retsnoop) is that trace
support
show parameters according its type (thanks to the Zig type system).
This is very helpful when debugging linux kernel.
For more details, you could check the implementation: BPF side
and Host side.
You could specify the kernel functions you want to trace with: zig build trace -Dkprobe=<kernel_function_name> -Dkprobe=...
And for system calls: zig build trace -Dsyscall=<syscall_name> -Dsyscall=...
.
Moreover, if you also want to capture the function's arguments, append the argument specifier, something like this:
-Dkprobe=<kernel_function_name>:arg0,arg1...
, it also supports access to the deeper field if the argument is a pointer to a struct:
-Dkprobe=<kernel_function_name>:arg0.field1.field0
.
You could even control how the argument is shown by using all the supported specifier by Zig's std.fmt
, something like this:
-Dkprobe=<kernel_function_name>:arg0.field1.field0/x
will show arg0.field1.field0
in hexadecimal notation.
Capturing call stack is also supported, append keyword stack
, for example -Dkprobe=<kernel_function_name>:arg0,stack
.
And here's a quick demo:
For reference:
- Syscalls: https://tw4452852.github.io/zbpf/#vmlinux.kernel_syscalls
- Kernel functions: https://tw4452852.github.io/zbpf/#vmlinux.kernel_funcs
Want to use your local specific linux kernel? No problem, you could set up the documentation locally with:
zig build docs [-Dvmlinux=/path/your/vmlinux]
Then browse the generated page which is located at ./zig-out/docs/index.html
.
Search for vmlinux.kernel_syscalls
and vmlinux.kernel_funcs
for syscalls and kernel functions respectively.
For each supported feature, we have the corresponding unit test.
You could find them under samples/
(BPF side) and src/tests
(Host side).
Build it with zig build test -Dtest=<name>
and run it with sudo zig-out/bin/test
.
Name | BPF side | Host side |
---|---|---|
exit | source | source |
panic | source | source |
trace_printk | source | source |
array | source | source |
hash | source | source |
perf_event | source | source |
ringbuf | source | source |
tracepoint | source | source |
iterator | source | source |
fentry | source | source |
kprobe | source | source |
kmulprobe | source | source |
xdp ping | source | source |
kfunc | source | source |
stack_trace | source | source |
Have fun!