033 Project 33: eBPF Tracepoint Basics
033 Build eBPF Tracepoint Basics
Capture process exec events from sys_enter_execve using eBPF: a minimal kernel program, userspace loader with cilium/ebpf, and a perf/ring buffer reader. Linux lab only; requires privileges.
kernel tracepoint -> eBPF program -> perf/ring buffer -> userspace Go reader
Problem statement
On a Linux VM/lab host:
- Load a BPF object that attaches to
syscalls/sys_enter_execve - Emit
{pid, comm}events to userspace - Print events until Ctrl+C
- Detach cleanly on shutdown
Acceptance criteria
- Documented toolchain install (clang, llvm, bpf2go or prebuilt)
- Userspace program loads, attaches, reads events
- Running
ls/echoproduces lines - SIGINT stops without kernel leftover panic (best-effort detach)
- Clear notes if hardware/CI cannot run BPF
Setup
# Debian/Ubuntu-ish lab
sudo apt-get install -y clang llvm libbpf-dev linux-headers-$(uname -r)
mkdir ebpf-trace && cd ebpf-trace
go mod init example.com/ebpf-trace
go get github.com/cilium/ebpf@latest
go install github.com/cilium/ebpf/cmd/bpf2go@latest
# go 1.27Kernel program exec.bpf.c (sketch)
//go:build ignore
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
char LICENSE[] SEC("license") = "Dual BSD/GPL";
struct event {
__u32 pid;
char comm[16];
};
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} events SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_execve")
int handle_exec(struct trace_event_raw_sys_enter *ctx) {
struct event e = {};
e.pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&e.comm, sizeof(e.comm));
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &e, sizeof(e));
return 0;
}Generate bindings (names vary by bpf2go version):
go generate ./...
# //go:generate bpf2go -cc clang -cflags "-O2 -g -Wall" bpf exec.bpf.c -- -I/usr/includeUserspace main.go
package main
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/perf"
"github.com/cilium/ebpf/rlimit"
)
type event struct {
Pid uint32
Comm [16]byte
}
func main() {
// Allow the current process to lock memory for eBPF maps.
if err := rlimit.RemoveMemlock(); err != nil {
log.Fatal(err)
}
objs := bpfObjects{}
if err := loadBpfObjects(&objs, nil); err != nil {
log.Fatalf("load: %v", err)
}
defer objs.Close()
tp, err := link.Tracepoint("syscalls", "sys_enter_execve", objs.HandleExec, nil)
if err != nil {
log.Fatalf("attach: %v", err)
}
defer tp.Close()
rd, err := perf.NewReader(objs.Events, os.Getpagesize())
if err != nil {
log.Fatal(err)
}
defer rd.Close()
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
go func() {
<-sig
_ = rd.Close()
}()
fmt.Println("listening for execve; Ctrl+C to stop")
for {
rec, err := rd.Read()
if err != nil {
break
}
if rec.LostSamples != 0 {
fmt.Fprintf(os.Stderr, "lost %d samples\n", rec.LostSamples)
}
var e event
if err := binary.Read(bytes.NewBuffer(rec.RawSample), binary.LittleEndian, &e); err != nil {
continue
}
fmt.Printf("pid=%d comm=%s\n", e.Pid, bytes.TrimRight(e.Comm[:], "\x00"))
}
}Note:
bpfObjects,loadBpfObjects, and field names come frombpf2gooutput. Adjust to your generated API (objs.HandleExecvsobjs.bpfPrograms.HandleExec).
Step-by-step build path
- Install kernel headers + clang.
- Write minimal
.bpf.cwith one tracepoint. bpf2gogenerate Go + embed.- Load, attach, read perf events.
- Add filtering (UID, comm prefix) next.
Run and verification
go generate ./...
go build -o ebpf-trace .
sudo ./ebpf-trace
# other terminal:
ls /tmp; echo hi
# expect comm lines for shell toolsPitfalls
| Pitfall | Fix |
|---|---|
| macOS/Windows host | use Linux VM |
| Missing CAP_BPF/root | sudo or capabilities |
| Header/vmlinux mismatch | generate vmlinux.h via bpftool |
| Lost samples | larger perf buffer; reduce event rate |
| License section missing | BPF verifier rejects |
Stretch goals
- Include filename from execve args (careful helper limits).
- Switch to ringbuf map.
- Filter by cgroup.
- Export Prometheus counters of execs/sec.
Learning goals
- Event-driven observability near the kernel
- Userspace loader lifecycle
- Safe iteration practices for low-level tooling