Operations 40 min read

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.

Alibaba Cloud Big Data AI Platform
Alibaba Cloud Big Data AI Platform
Alibaba Cloud Big Data AI Platform
Build Pure‑C eBPF Projects with Native libbpf – No libbpf‑bootstrap Needed

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'_' -f2

Older kernels define the version directly in tools/lib/bpf/Makefile:

$ cat ./source/tools/lib/bpf/Makefile
BPF_VERSION = 0
BPF_PATCHLEVEL = 0
BPF_EXTRAVERSION = 2

02 – 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 bpftool

3.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 bpftool

04 – 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 clean

The 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.hex

The 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 clean

The 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 30

6.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 /

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

eBPFLinux kernelC ProgramminglibbpfSystem Tracing
Alibaba Cloud Big Data AI Platform
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.