Home

Awesome

zbpf

Writing eBPF in Zig. Thanks to Zig's comptime and BTF, we can equip eBPF with strong type system both at comptime and runtime!

Notable advantages when writing eBPF program with zbpf

Different available methods based on the type of program's context

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.

No more tedious error handling

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:

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

How to use

Prerequisite

Build

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.

Tools/trace

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:

asciicast

Samples

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.

NameBPF sideHost side
exitsourcesource
panicsourcesource
trace_printksourcesource
arraysourcesource
hashsourcesource
perf_eventsourcesource
ringbufsourcesource
tracepointsourcesource
iteratorsourcesource
fentrysourcesource
kprobesourcesource
kmulprobesourcesource
xdp pingsourcesource
kfuncsourcesource
stack_tracesourcesource

Have fun!