Skip to content

tw4452852/zbpf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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:

  • bpf.Map takes care BPF map's update and delete 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

How to use

Prerequisite

  • 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).

Build

  • 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.

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

For reference:

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.

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.

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!