From c0433f52d5eb003a40bf537e9f6d4c3cf860d651 Mon Sep 17 00:00:00 2001 From: Niclas Hedam Date: Sun, 12 Jul 2026 17:39:51 +0200 Subject: [PATCH 1/6] Add Docker-based build and test environment Provide a reproducible container for building and running the example so it no longer depends on a specific host setup. Verified to build and pass the test end-to-end under Docker Desktop on both x86_64 and aarch64. Two fixes were needed to make this work portably: - src/Makefile: point clang's bpf target at the host multiarch include dir ($(uname -m)-linux-gnu) so headers like are found. Previously this relied on gcc-multilib, which is x86-only and unavailable on arm64. - test/test.sh: invoke the loader directly instead of via sudo. The script already re-execs as root, so the extra sudo only broke root environments without sudo installed, such as containers. Usage: docker build -t ebpf-kill-example . docker run --rm --privileged ebpf-kill-example The entrypoint mounts tracefs (requires --privileged) so the kill tracepoint is reachable at test time. --- .dockerignore | 1 + Dockerfile | 32 ++++++++++++++++++++++++++++++++ docker-entrypoint.sh | 10 ++++++++++ src/Makefile | 7 +++++++ test/test.sh | 5 ++++- 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6b8710a --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..70f39b0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Reproducible build/test environment for ebpf-kill-example. +# +# Build: docker build -t ebpf-kill-example . +# Test: docker run --rm --privileged ebpf-kill-example +# +# The eBPF program is loaded into the kernel and attached to a tracepoint at +# test time, so running the tests requires --privileged (or CAP_BPF + +# CAP_PERFMON + CAP_SYS_ADMIN) and a kernel that exposes the kill tracepoint. +FROM ubuntu:24.04 + +# Toolchain: clang/llvm emit the BPF bytecode, libelf + zlib are libbpf's +# link-time dependencies, and build-essential/make build libbpf and the loader. +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + llvm \ + libelf-dev \ + zlib1g-dev \ + make \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY . . + +# Build the vendored libbpf submodule and the example. +RUN make + +# The entrypoint mounts tracefs so the tracepoint is reachable at test time. +RUN chmod +x docker-entrypoint.sh +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["make", "test"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..c7cfe09 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# The eBPF loader attaches to a kernel tracepoint, which is exposed through +# tracefs. Containers do not mount tracefs by default, so mount it here (this +# requires --privileged). Failure is ignored so non-test uses of the image +# still work without extra privileges. +if [ ! -e /sys/kernel/tracing/events ]; then + mount -t tracefs nodev /sys/kernel/tracing 2>/dev/null || true +fi + +exec "$@" diff --git a/src/Makefile b/src/Makefile index 8cb281b..47d332f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,6 +7,12 @@ LDFLAGS ?= -L$(LIBBPF_DIR) LIBS = -lbpf -lelf +# clang's `bpf` target does not search the host's multiarch include directory, +# so kernel/libc headers such as are not found. Point it there +# explicitly. Using $(uname -m) keeps this portable across architectures +# (e.g. x86_64-linux-gnu, aarch64-linux-gnu). +ARCH_INCLUDES = -I/usr/include/$(shell uname -m)-linux-gnu + all: $(TARGET) kern.o .PHONY: clean @@ -24,6 +30,7 @@ kern.o: kern.c -target bpf \ -D __BPF_TRACING__ \ $(CFLAGS) \ + $(ARCH_INCLUDES) \ -Wall \ -Werror \ -O2 -emit-llvm -c -g kern.c diff --git a/test/test.sh b/test/test.sh index 00f5007..8739550 100755 --- a/test/test.sh +++ b/test/test.sh @@ -14,7 +14,10 @@ set -e [ "$UID" -eq 0 ] || exec sudo bash "$0" "$@" echo "-- Loading eBPF program." -sudo ./src/ebpf-kill-example > /tmp/ebpf-kill.log & +# Already running as root (line 14 re-execs via sudo when needed), so invoke +# the loader directly. This keeps the test working in environments without +# sudo installed, such as containers. +./src/ebpf-kill-example > /tmp/ebpf-kill.log & sleep 5 From 5fc861c0bc3b856c65c8948e8678ab3667f87986 Mon Sep 17 00:00:00 2001 From: Niclas Hedam Date: Sun, 12 Jul 2026 17:39:55 +0200 Subject: [PATCH 2/6] CI: update to supported runner and checkout action The ubuntu-20.04 runner was retired by GitHub (April 2025) so CI no longer runs, and actions/checkout@v2 relies on a deprecated Node runtime. Move to ubuntu-24.04 (GA; 26.04 is still preview) and actions/checkout@v4. --- .github/workflows/default.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index 6235d39..a7209c0 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -15,12 +15,12 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install dependencies run: make deps From ba4598c6ab125b6aee677561961f85c2f3049509 Mon Sep 17 00:00:00 2001 From: Niclas Hedam Date: Sun, 12 Jul 2026 17:39:57 +0200 Subject: [PATCH 3/6] Update libbpf submodule to v1.7.0 Bump the vendored libbpf from v1.2.0 to v1.7.0 (latest stable, Mar 2025). The loader and eBPF program build unchanged against the new API, and the test passes end-to-end (verified in Docker). Also add zlib1g-dev to 'make deps', which newer libbpf requires at build time. --- Makefile | 2 +- libbpf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0fb38bf..818dd15 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ deps: sudo apt update - sudo apt install -y build-essential git make gcc clang llvm libelf-dev gcc-multilib + sudo apt install -y build-essential git make gcc clang llvm libelf-dev zlib1g-dev gcc-multilib git submodule update --init libbpf: diff --git a/libbpf b/libbpf index fbd60db..f5dcbae 160000 --- a/libbpf +++ b/libbpf @@ -1 +1 @@ -Subproject commit fbd60dbff51c870f5e80a17c4f2fd639eb80af90 +Subproject commit f5dcbae736e5d7f83a35718e01be1a8e3010fa39 From edae41556ab23ba903f986cfb7743550acf600b0 Mon Sep 17 00:00:00 2001 From: Niclas Hedam Date: Sun, 12 Jul 2026 17:39:59 +0200 Subject: [PATCH 4/6] Makefile: emit BPF object in a single clang step Replace the legacy 'clang -emit-llvm | llc' two-step with a single 'clang -target bpf ... -c -o kern.o' invocation. This is the standard modern way to build BPF objects, avoids llc version-skew issues, and removes the intermediate kern.ll artifact. Verified building and passing tests in Docker. --- src/Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Makefile b/src/Makefile index 47d332f..83b37a9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -20,18 +20,16 @@ all: $(TARGET) kern.o clean: rm -f $(TARGET) rm -f kern.o - rm -f kern.ll $(TARGET): %: user.c Makefile gcc $(CFLAGS) $(LDFLAGS) -o $(TARGET) user.c -Wl,-rpath=$(LIBBPF_DIR) $(LIBS) kern.o: kern.c - clang -S \ + clang \ -target bpf \ -D __BPF_TRACING__ \ $(CFLAGS) \ $(ARCH_INCLUDES) \ -Wall \ -Werror \ - -O2 -emit-llvm -c -g kern.c - llc -march=bpf -filetype=obj -o kern.o kern.ll + -O2 -g -c kern.c -o kern.o From 6dd8eb5109f521b3d781cd8075a17ca876caecb1 Mon Sep 17 00:00:00 2001 From: Niclas Hedam Date: Sun, 12 Jul 2026 17:40:00 +0200 Subject: [PATCH 5/6] user.c: use snprintf to build the object-file path Replace sprintf with snprintf bounded by sizeof(path) so an unusually long argv[0] cannot overflow the PATH_MAX buffer. --- src/user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user.c b/src/user.c index 7915b03..0335aaf 100644 --- a/src/user.c +++ b/src/user.c @@ -26,7 +26,7 @@ int main(int argc, char **argv) { char path[PATH_MAX]; - sprintf(path, "%s/kern.o", dirname(argv[0])); + snprintf(path, sizeof(path), "%s/kern.o", dirname(argv[0])); struct bpf_object *obj; struct bpf_link *link = NULL; From 2f92e53edaf878bda3bc9e9fda7c51d87765d920 Mon Sep 17 00:00:00 2001 From: Niclas Hedam Date: Sun, 12 Jul 2026 17:40:03 +0200 Subject: [PATCH 6/6] README: document Docker build/test and updated deps Add a Docker section covering the reproducible build/test environment, note it as the easiest path on non-Linux hosts and on arm64 (where gcc-multilib is unavailable), and list zlib1g-dev among the dependencies. --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 1654e28..34399ed 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,14 @@ To run this example, the following software is required. - **Linux kernel v4.19+** - **LLVM 10+** - libelf-dev (Installed via *make deps*) +- zlib1g-dev (Installed via *make deps*) - gcc-multilib (Installed via *make deps*) +Alternatively, you can build and run everything with [Docker](#docker), which +needs only Docker installed — no local toolchain. This is the easiest way to +try the example on non-Linux hosts (e.g. macOS) or on architectures where +*gcc-multilib* is unavailable, such as arm64. + ## Installation To install ebpf-kill-example, first clone this repository. @@ -57,6 +63,29 @@ nhed@nhed-1:~/Development/ebpf-kill-example$ make test [ OK ] -- eBPF program ran as expected. ``` +## Docker + +A `Dockerfile` is provided for a reproducible build and test environment that +does not depend on your host setup. This is handy on non-Linux hosts or when +you would rather not install the toolchain locally. + +Build the image: + +``` +docker build -t ebpf-kill-example . +``` + +Build and run the test. The eBPF program is loaded into the kernel and attached +to a tracepoint, so `--privileged` is required (the container mounts tracefs so +the tracepoint is reachable): + +``` +docker run --rm --privileged ebpf-kill-example +``` + +On Docker Desktop (macOS/Windows) the container runs against the Docker VM's +Linux kernel, so a Linux host is not required. + ## Example ![Example](/img/example.png?raw=true)