In the previous article “Developing a Hello World level eBPF program from scratch using C”, we explained in detail how to develop an eBPF program (including its user state part) from scratch based on C and the libbpf library. That article was the basis for subsequent articles on eBPF program development, because until now the kernel state part of an eBPF program running in the kernel state had to be developed in C, no matter what language the user state part of the eBPF program was developed in. In this way, the competition between other programming languages is how to make the development of the user state part of eBPF programs easier, and Go is no exception.
The most active Go eBPF package used to develop the user state part of eBPF in the Go community would be the cilium project’s open source cilium/ebpf.
Isovalent, the company behind the > cilium project, is also one of the main drivers for the adoption of eBPF technology in the cloud-native space.
In this article we will talk about how to develop eBPF applications based on cilium/ebpf!
1. Exploring the cilium/ebpf project example
The cilium/ebpf project borrows the idea of libbpf-boostrap to build the user state part of the eBPF program by code generation with bpf program inline. In order to figure out how to develop eBPF programs based on cilium/ebpf, let’s first explore the sample code provided by the cilium/ebpf project.
Let’s first download and look at the structure of the ebpf example.
-
Download cilium/ebpf project
1 2 3 4 5 6 7 8
$ git clone https://github.com/cilium/ebpf.git Cloning into 'ebpf'... remote: Enumerating objects: 7054, done. remote: Counting objects: 100% (183/183), done. remote: Compressing objects: 100% (112/112), done. remote: Total 7054 (delta 91), reused 124 (delta 69), pack-reused 6871 Receiving objects: 100% (7054/7054), 10.91 MiB | 265.00 KiB/s, done. Resolving deltas: 100% (4871/4871), done.
-
Explore the ebpf project sample code structure
The ebpf example is in the examples directory. Let’s take a look at tracepoint_in_c as an example to see how it is organized.
Judging from experience,
tracepoint.c
here corresponds to the kernel state part of the ebpf program, whilemain.go
andbpf_bpfel.go/bpf_bpfeb.go
are the user state part of the ebpf program, and as forbpf_bpfeb.o/bpf_bpfel.o
should be some kind of intermediate target file.Check this intermediate file by
readingelf -a bpf_bpfeb.o
.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
$readelf -a bpf_bpfeb.o ELF Header: Magic: 7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, big endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Linux BPF Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 1968 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 64 (bytes) Number of section headers: 13 Section header string table index: 1 ... ...
We see that this is an elf file containing linux bpf bytecode (Machine: Linux BPF).
After reading the documentation on cilium/ebpf, I figured out the relationship between these files and present it to you in the following schematic.
The source code file of the ebpf program (e.g. tracepoint.c in the figure) is compiled (bpf2go calls clang) by bpf2go (a code generation tool provided by cilium/ebpf) into the ebpf bytecode files bpf_bpfeb.o (big end) and bpf_bpfel.o (small end). Then bpf2go will generate bpf_bpfeb.go or bpf_bpfel.go based on the ebpf bytecode file. The bytecode of the ebpf program will be embedded in these two go source files as binary data. Taking bpf_bpfel.go as an example, we can find the following in its code (using the go:embed feature).
main.go is the main program for the user state part of the ebpf program. Compiling main.go with either bpf_bpfeb.go or bpf_bpfel.go creates the ebpf program.
With an initial exploration of the cilium/ebpf project example, let’s build the ebpf sample code.
2. Build ebpf example code
cilium/ebpf provides a convenient build script, we just need to execute “make -C …” under ebpf/examples to build the sample code.
The make build process will start the build container based on the quay.io/cilium/ebpf-builder image.
If you are in China, you need to do a little modification to the Makefile content like below and add the GOPROXY environment variable, otherwise the go module can’t be pulled due to GFW.
1 2 3 4 5 6 7 8 9 10 11 12 13
$git diff ../Makefile diff --git a/Makefile b/Makefile index 3a1da88..d7b1712 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ container-all: ${CONTAINER_ENGINE} run --rm ${CONTAINER_RUN_ARGS} \ -v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \ --env CFLAGS="-fdebug-prefix-map=/ebpf=." \ + --env GOPROXY="https://goproxy.io" \ --env HOME="/tmp" \ "${IMAGE}:${VERSION}" \ $(MAKE) all
Executing the build after this will give us the results we are looking for without any problems.
|
|
Taking ebpf under uretprobe as an example, let’s run it.
Open a new terminal and execute vi .bashrc
in the user home directory. in the above execution window of the uretprobe program we can see the following output.
This indicates that the ebpf program under uretprobe is executing as expected.
3. Using cilium/ebpf to develop the user state part for the previous Hello World eBPF program
With an initial understanding of the cilium/ebpf sample program, let’s develop the user state part of the hello world ebpf program from the previous article “Developing a Hello World level eBPF program from scratch using C language”.
Review the C source code of that hello world ebpf program.
|
|
Once this ebpf program is loaded into the kernel, it will be called once whenever the execve system call is executed, and we will see the corresponding log output in /sys/kernel/debug/tracing/trace_pipe
.
3.1. Converting ebpf core state programs to Go code using bpf2go
Based on the “routine” we got when exploring the cilium/ebpf example program, the first thing we need to do is to convert helloworld.bpf.c into a Go code file, and the indispensable tool for this conversion process is the cilium/ebpf bpf2go tool, let’s install the tool first.
|
|
Next, we can directly use the bpf2go tool to convert helloworld.ebpf.c
to the corresponding go source file.
|
|
But there is a problem here, that is, the bpf2go command line is followed by a series of header files provided to the clang compiler with reference paths to the Makefile in the article “Developing a Hello World-level eBPF program from scratch using C. If we follow these header file paths, although the bpf2go conversion can work, we need to depend on and install the libbpf library, which is obviously not what we want.
cilium/ebpf provides a headers directory in examples that contains all the headers needed to develop the user state part of the ebpf program, and we use it as our header reference path. To build ebpf based on this headers directory, however, we need to modify the include
statement in helloworld.bpf.c
.
The original include statement.
The modified include statement.
|
|
Next we will perform the conversion with the bpf2go tool.
|
|
We see that bpf2go smoothly generates ebpf bytecode with the corresponding Go source files.
3.2. Building the user state part of the helloworld ebpf program
The following is the main.go source code of the user state part of the helloword ebpf program built by referring to the cilium/ebpf example.
|
|
We know that an ebpf program has several key components.
- ebpf program data
- map: used for data interaction between user state and kernel state
- attach point
According to the description in cilium/ebpf architecture, the ebpf package abstracts the first two parts into a single data structure, bpfObjects.
We see that the main function loads the ebpf program into the kernel via the generated loadBpfObjects function and populates the bpfObjects structure. Once the bpf program is loaded successfully, we can then use the fields in the bpfObjects structure to perform the rest of the operations, such as docking the bpf program to the target pendant node via a function in the link package (e.g., the link.Tracepoint function in the text). This way, bpf can be executed via callbacks after the corresponding event.
Compile and execute the helloworld example below.
After that, open a new terminal and execute sudo cat /sys/kernel/debug/tracing/trace_pipe
. When execve is called, we can see a log output like the following.
|
|
3.3. Using go generate to drive the conversion of bpf2go
In terms of code generation, the Go toolchain provides the go generate tool natively, and the cilium/ebpf examples also use go generate to drive bpf2go to convert bpf programs to Go source files. Let’s do a little transformation here as well.
First we add a go:generate instruction line to the main function of main.go.
|
|
This way when we explicitly execute the go generate statement, go generate will scan for that instruction statement and execute the command that follows. Several variables are used here, which are defined in the Makefile. Of course, if you don’t want to use the Makefile, you can replace the variables with the corresponding values. Here we use the Makefile, and here is the contents of the Makefile.
|
|
With this Makefile, we can execute the make command to convert bpf2go to bpf programs.
|
|
4. Summary
- In this article we have explained how to develop the user state part of ebpf based on the cilium/ebpf package.
- ebpf borrows the idea of libbpf to build the user state part of ebpf by generating code with data inline.
- ebpf provides the bpf2go tool to convert the C source code of bpf to the corresponding go source code.
- ebpf abstracts the bpf program into bpfObjects, completes the loading of the bpf program into the kernel by generating loadBpfObjects, and then implements the association of ebpf with kernel events using packages such as link provided by the ebpf library.
- There are many more ways to play with ebpf packages, and this one is just to lay the groundwork. In subsequent articles, we will do further study and explanation for various types of bpf programs.
- The code for this article can be downloaded at here.