Build Pure‑C eBPF Projects with Native libbpf – No libbpf‑bootstrap Needed
This article walks through constructing pure‑C eBPF programs using the native libbpf library, covering kernel‑libbpf version mapping, environment setup for RPM and DEB systems, detailed Makefile analysis, embedding BPF objects via hexdump, and extending the approach to USDT and Uprobe tracing without relying on libbpf‑bootstrap.
01 – Climbing Higher
In the previous article "eBPF Hands‑On Series II: Building a Pure‑C eBPF Project", we demonstrated how to build a pure‑C eBPF project without kernel source. The libbpf library, once tightly coupled with kernel sources, has now matured and can be used completely independently.
Appendix A lists the libbpf version required by each kernel source example. Most kernels expose the libbpf version via the libbpf.map file in tools/lib/bpf:
$ cat ./source/tools/lib/bpf/libbpf.map | grep -oE '^LIBBPF_([0-9.]+)' | sort -rV | head -n1 | cut -d'_' -f2Older kernels define the version directly in tools/lib/bpf/Makefile:
$ cat ./source/tools/lib/bpf/Makefile
BPF_VERSION = 0
BPF_PATCHLEVEL = 0
BPF_EXTRAVERSION = 202 – eBPF Programming Scheme Overview
To simplify eBPF development and lower the entry barrier for libbpf users, the libbpf‑bootstrap framework was created and has become the most popular solution. Some older projects still use a single bpf_load.c file without libbpf.
The mainstream C‑based eBPF schemes can be grouped into three generations (see the diagram below). Besides C, some frameworks also support Python, Go, or Rust for user‑space loading.
Although libbpf‑bootstrap and libbpf‑go are widely adopted, the native libbpf approach still offers unique advantages:
Deeper control and flexibility : Direct use of libbpf lets you interact with lower‑level APIs, customize loading and management of eBPF programs and maps, and handle complex scenarios.
Better learning and understanding : Skipping the abstraction layer forces you to understand the eBPF subsystem internals.
Finer‑grained dependency management : You can pin exact libbpf versions and features.
Improved compatibility with older kernels : Native libbpf works better on legacy distributions and kernels.
This article dives into the second‑generation native libbpf scheme and proposes an improvement.
03 – Preparing the eBPF Development Environment
Most mainstream Linux distributions use either RPM or DEB package managers. The required packages differ slightly.
3.1 RPM‑based environment
$ yum install git make # basic tools
$ yum install kernel-headers-$(uname -r) # kernel headers
$ yum install clang llvm elfutils-libelf-devel # compiler and libs
$ yum install bpftool-$(uname -r) # or simply
$ yum install bpftool3.2 DEB‑based environment
$ apt-get update
$ apt install git make # basic tools
$ apt install linux-libc-dev # kernel headers
$ apt install clang llvm libelf-dev # compiler and libs
$ apt install linux-tools-common linux-tools-$(uname -r) # or generic
$ apt install bpftool04 – Building a Native libbpf eBPF Project
The goal is to share an improved build flow for the second‑generation scheme. libbpf‑1.3.0 is used as the base version. The following commands clone the demo repository and set a working directory:
$ cd ~
$ git clone https://github.com/alibaba/sreworks-ext.git
$ NATIVE_LIBBPF=~/sreworks-ext/demos/native_libbpf_guide/4.1 Initial Build of a Native libbpf Project
We start with a simple trace of the execve syscall:
$ cd $NATIVE_LIBBPF
$ cd trace_execve_libbpf130
$ make
$ sudo ./trace_execve
trace_execve 15836221 5501 bash 1534 bash 0 /usr/bin/ls
trace_execve 15914126 5502 bash 1534 bash 0 /usr/bin/ps
$ make cleanThe project directory layout is:
Directory
Description
./
User‑space code and main Makefile
./progs
Kernel‑space BPF source
./include
Project‑specific headers
./helpers
Non‑libbpf helper files
./tools/lib/bpf/
Copied from libbpf‑1.3.0/src/
./tools/include/
Copied from libbpf‑1.3.0/include/
./tools/build/
Feature‑detection scripts
./tools/scripts/
Makefile helper scripts
Only the trace_execve.c and trace_execve.bpf.c files need to be modified for custom logic.
4.2 Makefile Overview
The project contains four Makefiles: ./Makefile – builds the user‑space binary. ./progs/Makefile – builds the kernel‑space BPF object. ./tools/lib/bpf/Makefile – builds libbpf.a. ./tools/build/feature/Makefile – runs feature probes.
Running make --debug=v,m SHELL="bash -x" reveals the exact build order and dependencies.
05 – Improving the Native libbpf Build
The original scheme requires distributing both the user binary and the .bpf.o file. To avoid this, libbpf‑bootstrap introduced a skeleton header ( *.skel.h) that embeds the BPF object. We achieve the same effect with hexdump:
$ hexdump -v -e '\x'1/1 "%02x" trace_execve.bpf.o > trace_execve.hexThe resulting trace_execve.skel.h includes the hex string, and the user program loads it via bpf_object__open_mem() instead of bpf_object__open_file(). This makes the binary self‑contained.
5.1 Traditional Scheme Shortcomings
Renaming trace_execve.bpf.o breaks execution because the loader expects the file at runtime. The skeleton approach eliminates this dependency.
5.2 Generating *.skel.h with hexdump
Replace the bpftool gen skeleton step with the hexdump command shown above, then include the generated header in the user program.
5.3 Deep Dive into the Improved Build
The main Makefile now contains five static pattern rules that compile the BPF object, convert it to hex, generate the skeleton header, and finally compile the user binary. The dependency graph ensures that intermediate files are reused across multiple targets (e.g., trace_execve and probe_execve).
5.4 From File to Memory Loading
Old code:
char filename[256] = "progs/trace_execve.bpf.o";
struct bpf_object *bpf_obj = bpf_object__open_file(filename, NULL);New code:
#include "trace_execve.skel.h"
struct bpf_object *bpf_obj = bpf_object__open_mem(obj_buf, obj_buf_sz, NULL);5.5 Differences Between Native libbpf and libbpf‑bootstrap
Native libbpf gives you direct control over loading, linking, and destroying BPF programs. It also allows you to choose specific attach functions (e.g., bpf_program__attach_tracepoint) for finer runtime control.
06 – USDT and Uprobe Projects with Native libbpf
Using the same hex‑embedding technique, we can build user‑space tracing projects.
6.1 User‑Space Simulation Program
$ cd $NATIVE_LIBBPF
$ cd mark_usdt_uprobe
$ make
$ sudo cp umark /usr/bin/
$ sudo umark >/dev/null 2>&1 &
$ make cleanThe umark.c program defines two functions ( func_uprobe1, func_uprobe2) and emits USDT probes via DTRACE_PROBE1/2.
6.2 Building the USDT Project
$ cd $NATIVE_LIBBPF
$ cd trace_user_libbpf130
$ make
$ sudo ./usdt_test
func_usdt1 2375442 4604 umark 1534 bash 0 10 17
func_usdt2 2375442 4604 umark 1534 bash 0 20 306.3 USDT Code Analysis
Attachment can be done programmatically:
bpf_program__attach_usdt(bpf_prog1, -1, "/usr/bin/umark", "groupa", "probe1", NULL);Or via the SEC macro in the BPF source:
SEC("usdt//usr/bin/umark:groupb:probe2")The macro BPF_USDT expands to a wrapper that extracts probe arguments using bpf_usdt_arg().
6.4 Uprobe Code Analysis
Programmatic attachment:
func_off1 = get_elf_func_offset("/usr/bin/umark", "func_uprobe1");
bpf_program__attach_uprobe(bpf_prog1, 0, -1, "/usr/bin/umark", func_off1);Or via SEC macro:
SEC("uprobe//usr/bin/umark:func_uprobe2")The macro BPF_KPROBE generates a wrapper that reads arguments from PT_REGS_PARM* registers.
07 – Technical Exchange
This is the third article in the eBPF hands‑on series, focusing on building pure‑C eBPF projects with native libbpf. The next article will dive deeper into the internal logic of eBPF programs.
Join the discussion groups for further questions and sharing:
SREWorks Intelligent Operations Engineering Group (DingTalk ID: 35853026)
Tracing & Diagnosis SIG Developers & Users Group (DingTalk ID: 33304007)
Appendix A – Kernel ↔ libbpf Version Mapping
Kernel Version
libbpf Version
Notes
kernel‑6.5
LIBBPF_1.3.0
kernel‑6.4
LIBBPF_1.2.0
kernel‑6.3
LIBBPF_1.2.0
kernel‑6.2
LIBBPF_1.1.0
kernel‑6.1
LIBBPF_1.0.0
kernel‑6.0
LIBBPF_1.0.0
kernel‑5.19
LIBBPF_1.0.0
usdt.bpf.h appears
kernel‑5.18
LIBBPF_0.8.0
kernel‑5.17
LIBBPF_0.7.0
kernel‑5.16
LIBBPF_0.6.0
kernel‑5.15
LIBBPF_0.5.0
kernel‑5.14
LIBBPF_0.5.0
kernel‑5.13
LIBBPF_0.4.0
kernel‑5.12
LIBBPF_0.3.0
kernel‑5.11
LIBBPF_0.3.0
kernel‑5.10
LIBBPF_0.2.0
kernel‑5.9
LIBBPF_0.1.0
kernel‑5.8
LIBBPF_0.0.9
kernel‑5.7
LIBBPF_0.0.8
kernel‑5.6
LIBBPF_0.0.7
kernel‑5.5
LIBBPF_0.0.6
kernel‑5.4
LIBBPF_0.0.5
kernel‑5.3
LIBBPF_0.0.4
kernel‑5.2
LIBBPF_0.0.3
kernel‑5.1
LIBBPF_0.0.2
kernel‑5.0
LIBBPF_0.0.1
kernel‑4.19
LIBBPF_0.0.1
kernel‑4.18
LIBBPF_0.0.1
kernel‑4.9
LIBBPF_0.0.1
/ END /
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Big Data AI Platform
The Alibaba Cloud Big Data AI Platform builds on Alibaba’s leading cloud infrastructure, big‑data and AI engineering capabilities, scenario algorithms, and extensive industry experience to offer enterprises and developers a one‑stop, cloud‑native big‑data and AI capability suite. It boosts AI development efficiency, enables large‑scale AI deployment across industries, and drives business value.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
