eBPF basics including program types, verifier, maps, BCC vs libbpf toolchains, and loading programs
Scope: Core eBPF concepts, program types, verifier constraints, maps, toolchain selection, and program lifecycle Lines: ~320 Last Updated: 2025-10-25 Format Version: 1.0 (Atomic)
Activate this skill when:
Classic BPF: Packet filtering only, limited instruction set eBPF: Extended functionality, multiple program types, maps, helper functions
Evolution:
Architecture:
User Space Program
↓
bpf() syscall
↓
eBPF Verifier (safety check)
↓
JIT Compiler
↓
Kernel Execution (kprobe, XDP, tracepoint, etc.)
Common types:
// Kprobe: Trace kernel function entry
SEC("kprobe/sys_execve")
int trace_execve(struct pt_regs *ctx) {
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
bpf_printk("Process: %s\n", comm);
return 0;
}
// XDP: Fast packet processing
SEC("xdp")
int xdp_drop_icmp(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto == htons(ETH_P_IP)) {
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol == IPPROTO_ICMP)
return XDP_DROP; // Drop ICMP packets
}
return XDP_PASS;
}
// Tracepoint: Stable kernel ABI
SEC("tracepoint/syscalls/sys_enter_open")
int trace_open(struct trace_event_raw_sys_enter *ctx) {
char filename[256];
bpf_probe_read_user_str(&filename, sizeof(filename),
(void *)ctx->args[0]);
bpf_printk("Opening: %s\n", filename);
return 0;
}
Program type table:
Type | Hook Point | Use Case
--------------|-------------------------|---------------------------
kprobe | Kernel function entry | Tracing, monitoring
kretprobe | Kernel function exit | Return value inspection
tracepoint | Stable kernel events | Production tracing
XDP | Network driver RX | Fast packet filtering
TC | Traffic control | Packet modification
socket_filter | Socket-level filtering | Per-socket inspection
cgroup/skb | cgroup network events | Container networking
Safety guarantees:
// ❌ REJECTED: Unbounded loop
for (int i = 0; i < n; i++) { // n from packet - verifier rejects
// ...
}
// ✅ ACCEPTED: Bounded loop
#pragma unroll
for (int i = 0; i < 10; i++) { // Fixed bound
// ...
}
// ❌ REJECTED: Uninitialized read
int value;
bpf_printk("%d", value); // value not initialized
// ✅ ACCEPTED: Initialized first
int value = 0;
bpf_printk("%d", value);
// ❌ REJECTED: Unbounded pointer access
char *ptr = get_user_pointer();
char c = *ptr; // Verifier can't prove bounds
// ✅ ACCEPTED: Bounds checking
char *ptr = get_user_pointer();
char buf[256];
bpf_probe_read(&buf, sizeof(buf), ptr); // Safe helper
Map types:
// Define map in eBPF program
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, u32); // PID
__type(value, u64); // Timestamp
} start_times SEC(".maps");
// Write to map
SEC("kprobe/sys_enter_read")
int trace_read_entry(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 ts = bpf_ktime_get_ns();
bpf_map_update_elem(&start_times, &pid, &ts, BPF_ANY);
return 0;
}
// Read from map
SEC("kretprobe/sys_exit_read")
int trace_read_exit(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 *tsp = bpf_map_lookup_elem(&start_times, &pid);
if (tsp) {
u64 delta = bpf_ktime_get_ns() - *tsp;
bpf_printk("PID %d read took %llu ns\n", pid, delta);
bpf_map_delete_elem(&start_times, &pid);
}
return 0;
}
BCC (BPF Compiler Collection):
# Quick prototyping with Python
from bcc import BPF
program = """
#include <uapi/linux/ptrace.h>
int trace_open(struct pt_regs *ctx) {
bpf_trace_printk("open() called\\n");
return 0;
}
"""
b = BPF(text=program)
b.attach_kprobe(event="do_sys_open", fn_name="trace_open")
b.trace_print()
libbpf (CO-RE - Compile Once, Run Everywhere):
// Modern approach - portable binaries
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
SEC("kprobe/do_sys_open")
int BPF_KPROBE(trace_open, int dfd, const char __user *filename) {
char fname[256];
bpf_probe_read_user_str(&fname, sizeof(fname), filename);
bpf_printk("Opening: %s\n", fname);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
When to use:
Use case: Efficient event streaming to userspace
// eBPF program
struct event {
u32 pid;
char comm[16];
char filename[256];
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} events SEC(".maps");
SEC("kprobe/do_sys_open")
int trace_open(struct pt_regs *ctx) {
struct event *e;
e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
if (!e)
return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
bpf_probe_read_user_str(&e->filename, sizeof(e->filename),
(void *)PT_REGS_PARM2(ctx));
bpf_ringbuf_submit(e, 0);
return 0;
}
// Userspace consumer (C)
#include <bpf/libbpf.h>
int handle_event(void *ctx, void *data, size_t data_sz) {
struct event *e = data;
printf("PID %d (%s) opened: %s\n", e->pid, e->comm, e->filename);
return 0;
}
int main() {
struct ring_buffer *rb;
struct bpf_object *obj;
struct bpf_map *map;
obj = bpf_object__open_file("program.bpf.o", NULL);
bpf_object__load(obj);
map = bpf_object__find_map_by_name(obj, "events");
rb = ring_buffer__new(bpf_map__fd(map), handle_event, NULL, NULL);
while (1) {
ring_buffer__poll(rb, 100); // Poll every 100ms
}
return 0;
}
When to use: Dynamic filtering controlled from userspace
// Allowlist map
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32); // PID
__type(value, u8); // Dummy value
} allowlist SEC(".maps");
SEC("kprobe/sys_execve")
int trace_execve(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
// Only trace allowed PIDs
if (!bpf_map_lookup_elem(&allowlist, &pid))
return 0;
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
bpf_printk("Allowed PID %d: %s\n", pid, comm);
return 0;
}
# Userspace control (Python with BCC)
from bcc import BPF
b = BPF(src_file="filter.bpf.c")
allowlist = b["allowlist"]
# Add PIDs to allowlist
allowlist[c_uint(1234)] = c_ubyte(1)
allowlist[c_uint(5678)] = c_ubyte(1)
# Remove PID
del allowlist[c_uint(1234)]
Use case: High-performance counters
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 256);
__type(key, u32);
__type(value, u64);
} syscall_counts SEC(".maps");
SEC("tracepoint/raw_syscalls/sys_enter")
int count_syscalls(struct trace_event_raw_sys_enter *ctx) {
u32 syscall_id = ctx->id;
u64 *count;
count = bpf_map_lookup_elem(&syscall_counts, &syscall_id);
if (count)
__sync_fetch_and_add(count, 1); // Atomic increment
return 0;
}
// Userspace aggregation
int fd = bpf_map__fd(map);
__u32 key = 0;
__u64 values[num_cpus];
for (key = 0; key < 256; key++) {
if (bpf_map_lookup_elem(fd, &key, values) == 0) {
__u64 total = 0;
for (int i = 0; i < num_cpus; i++)
total += values[i];
if (total > 0)
printf("Syscall %d: %llu calls\n", key, total);
}
}
Use case: Debugging complex programs
# Load with verbose verifier output
bpftool prog load program.o /sys/fs/bpf/myprog type kprobe
# Common errors and fixes:
# Error: "back-edge from insn X to Y"
# Fix: Use #pragma unroll or reduce loop bound
# Error: "R1 invalid mem access"
# Fix: Add bounds checking before pointer dereference
# Error: "unreachable insn X"
# Fix: Remove dead code or ensure all paths are reachable
# Error: "program too large"
# Fix: Split into multiple programs or simplify logic
Common helpers:
// Get current process info
u32 pid = bpf_get_current_pid_tgid() >> 32;
u32 tid = bpf_get_current_pid_tgid() & 0xFFFFFFFF;
u64 uid = bpf_get_current_uid_gid() >> 32;
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
// Time
u64 ns = bpf_ktime_get_ns();
// Kernel memory (safe)
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
u32 ppid;
bpf_probe_read_kernel(&ppid, sizeof(ppid), &task->real_parent->tgid);
// User memory (safe)
char user_buf[256];
bpf_probe_read_user(&user_buf, sizeof(user_buf), user_ptr);
// Output
bpf_printk("Debug: %s %d\n", comm, pid);
// Tail calls (program chaining)
bpf_tail_call(ctx, &prog_array, index);
# Compile
clang -O2 -target bpf -c program.bpf.c -o program.bpf.o
# Load and attach
bpftool prog load program.bpf.o /sys/fs/bpf/myprog
bpftool prog attach pinned /sys/fs/bpf/myprog kprobe do_sys_open
# List programs
bpftool prog list
# Dump map
bpftool map dump name my_map
Operation | Function | Description
-------------------|-------------------------------------|------------------
Lookup | bpf_map_lookup_elem(map, &key) | Get value
Update | bpf_map_update_elem(map, &k, &v, 0)| Set value
Delete | bpf_map_delete_elem(map, &key) | Remove key
Get next key | bpf_map_get_next_key(map, &k, &nk) | Iterate keys
✅ DO: Keep programs simple and verifiable
✅ DO: Use bounded loops with #pragma unroll
✅ DO: Check all pointer bounds before access
✅ DO: Use libbpf for production deployments
✅ DO: Test with verifier verbose mode
❌ DON'T: Use unbounded loops or recursion
❌ DON'T: Access uninitialized variables
❌ DON'T: Exceed verifier complexity limits
// ❌ NEVER: Unbounded operations
int count = get_packet_size(); // From network
for (int i = 0; i < count; i++) { // REJECTED
// Process
}
// ✅ CORRECT: Bounded with max
int count = get_packet_size();
if (count > 100)
count = 100;
#pragma unroll
for (int i = 0; i < count; i++) { // ACCEPTED
// Process
}
❌ Unbounded loops: Verifier rejection ✅ Correct approach: Fixed bounds or explicit limits
// ❌ Don't: Forget bounds checking
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
__u16 proto = eth->h_proto; // REJECTED - no bounds check
// ✅ Correct: Always check bounds
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_DROP;
__u16 proto = eth->h_proto; // ACCEPTED
❌ No bounds checking: Verifier rejection, potential crashes ✅ Better: Check before every pointer access
ebpf-tracing-observability.md - Using eBPF for tracing and monitoringebpf-networking.md - XDP and TC for packet processingebpf-security-monitoring.md - Security use cases with eBPFlinux-kernel-internals.md - Understanding kernel hooksLast Updated: 2025-10-25 Format Version: 1.0 (Atomic)