This guide covers the fundamental concepts of eBPF (extended Berkeley Packet Filter) that you need to understand to develop tools with the ebee project.
eBPF (extended Berkeley Packet Filter) is a revolutionary technology that allows you to run sandboxed programs in the Linux kernel without changing kernel source code or loading kernel modules.
- Sandboxed Execution: eBPF programs run in a virtual machine within the kernel
- Event-Driven: Programs are triggered by kernel events (system calls, tracepoints, etc.)
- Type Safety: Programs are verified before execution to ensure kernel safety
- Zero-Copy: Efficient data transfer between kernel and userspace
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Userspace │ │ Kernel │ │ Hardware │
│ Application │ │ eBPF VM │ │ Events │
│ │ │ │ │ │
│ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │
│ │ Go │ │ │ │ eBPF │ │ │ │ Tracepoint│ │
│ │ App │◄─┼────┼─►│ Program │◄─┼────┼─►│ /Kprobe │ │
│ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │
│ │ │ │ │ │
│ ┌───────────┐ │ │ ┌───────────┐ │ │ │
│ │ Ring │ │ │ │ eBPF │ │ │ │
│ │ Buffer │◄─┼────┼─►│ Maps │ │ │ │
│ └───────────┘ │ │ └───────────┘ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Attach to predefined kernel tracepoints for monitoring specific events.
SEC("tracepoint/sched/sched_process_exec")
int trace_exec(struct trace_event_raw_sched_process_exec *ctx) {
// Program logic here
return 0;
}Attach to kernel function entry and exit points.
SEC("kprobe/do_execve")
int kprobe_execve(struct pt_regs *ctx) {
// Program logic here
return 0;
}Attach to kernel function return points.
SEC("kretprobe/do_execve")
int kretprobe_execve(struct pt_regs *ctx) {
// Program logic here
return 0;
}Maps are the primary mechanism for sharing data between eBPF programs and userspace applications.
Efficient zero-copy communication between kernel and userspace.
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} events SEC(".maps");Key-value storage for eBPF programs.
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32);
__type(value, u64);
__uint(max_entries, 1024);
} counters SEC(".maps");Fixed-size array storage.
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, u32);
__type(value, u64);
__uint(max_entries, 1);
} config SEC(".maps");eBPF programs can call predefined helper functions to interact with the kernel.
// Get current process ID and thread group ID
u64 pid_tgid = bpf_get_current_pid_tgid();
// Get current process name
bpf_get_current_comm(&comm, sizeof(comm));
// Read kernel memory safely
bpf_probe_read(&value, sizeof(value), (void *)ptr);
// Read string from kernel memory
bpf_probe_read_str(&str, sizeof(str), (void *)ptr);
// Get current task structure
struct task_struct *task = (struct task_struct *)bpf_get_current_task();eBPF programs are written in C and compiled to eBPF bytecode.
clang -O2 -target bpf -c program.c -o program.oPrograms are loaded into the kernel and verified.
objs := programObjects{}
if err := loadProgramObjects(&objs, nil); err != nil {
log.Fatalf("loading objects: %v", err)
}Programs are attached to kernel events.
link, err := link.Tracepoint("sched", "sched_process_exec", objs.TraceExec, nil)Programs execute when their attached events occur.
Programs are detached and resources are freed.
defer objs.Close()
defer link.Close()The eBPF verifier ensures programs are safe to run in the kernel.
- Bounds Checking: All memory accesses are bounds-checked
- Type Safety: Programs must use correct data types
- Loop Prevention: Programs cannot have unbounded loops
- Resource Limits: Programs have size and complexity limits
# Invalid memory access
R0 invalid mem access 'scalar'
# Unbounded loop
back-edge from insn 10 to 10
# Invalid helper function usage
invalid func unknown#0struct data_t {
u32 pid; // Process ID
u32 ppid; // Parent process ID
char comm[16]; // Command name
char argv[256]; // Arguments
};// Task structure (simplified)
struct task_struct {
u32 pid;
u32 tgid;
struct task_struct *real_parent;
char comm[16];
// ... many more fields
};Always use bpf_probe_read() and bpf_probe_read_str() for kernel memory access.
// ❌ Unsafe
data->pid = current->pid;
// ✅ Safe
bpf_probe_read(&data->pid, sizeof(data->pid), ¤t->pid);Check return values and handle errors gracefully.
struct data_t *data = bpf_ringbuf_reserve(&events, sizeof(struct data_t), 0);
if (!data) {
return 0; // Skip event if reservation fails
}Always clean up resources in userspace.
defer objs.Close()
defer link.Close()Use efficient data structures and minimize kernel/userspace communication.
cat /sys/kernel/debug/bpf/verifier_log# List loaded programs
sudo bpftool prog list
# Show program details
sudo bpftool prog show id <prog_id>
# Dump program bytecode
sudo bpftool prog dump xlated id <prog_id>dmesg | tail// Filter by PID
if (target_pid != 0 && current_pid != target_pid) {
return 0;
}// Collect process information
data->pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF;
bpf_get_current_comm(&data->comm, sizeof(data->comm));// Update counters
u64 *counter = bpf_map_lookup_elem(&counters, &key);
if (counter) {
(*counter)++;
}Now that you understand eBPF fundamentals:
- Explore the tool documentation to see these concepts in action
- Try modifying existing tools to understand the patterns
- Create your own eBPF tools following the established patterns
- Read the development setup guide to get started