
Ebpf
- 340 installs
- 155 repo stars
- Updated June 27, 2026
- mohitmishra786/low-level-dev-skills
Author eBPF programs for kernel observability, network filtering, and low-overhead tracing integrated into Linux services and agents.
About
Explains building eBPF programs for Linux: writing kernel bytecode, managing maps, attaching kprobes/tracepoints, loading with libbpf, and integrating observability or policy hooks into APIs, CLIs, and agent tooling.
- BPF maps and programs
- Kernel hook attachment
- CO-RE and BTF loading
- Observability pipelines
- Safety and verifier constraints
Ebpf by the numbers
- 340 all-time installs (skills.sh)
- +21 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,180 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mohitmishra786/low-level-dev-skills --skill ebpfAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 340 |
|---|---|
| repo stars | ★ 155 |
| Last updated | June 27, 2026 |
| Repository | mohitmishra786/low-level-dev-skills ↗ |
What it does
Author eBPF programs for kernel observability, network filtering, and low-overhead tracing integrated into Linux services and agents.
Files
eBPF
Purpose
Guide agents through writing, loading, and debugging eBPF programs using libbpf, bpftrace, and bpftool. Covers map types, program types, verifier errors, XDP networking, and CO-RE portability.
Triggers
- "How do I write an eBPF program to trace system calls?"
- "My eBPF program fails with a verifier error"
- "How do I use bpftrace to trace kernel events?"
- "How do I share data between kernel eBPF and userspace?"
- "How do I write an XDP program for packet filtering?"
- "How do I make my eBPF program portable across kernel versions (CO-RE)?"
Workflow
1. Choose the right tool
Goal?
├── One-liner kernel tracing / scripting → bpftrace
├── Production eBPF program with userspace → libbpf (C) or aya (Rust)
├── Inspect loaded programs and maps → bpftool
└── High-performance packet processing → XDP + libbpf2. bpftrace — quick kernel tracing
# Trace all execve calls with comm and args
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s %s\n", comm, str(args->filename)); }'
# Count syscalls by process
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
# Latency histogram for read() syscall
bpftrace -e '
tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_read { @us = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); }'
# List available tracepoints
bpftrace -l 'tracepoint:syscalls:*'
bpftrace -l 'kprobe:tcp_*'3. libbpf skeleton — minimal C program
// counter.bpf.c — kernel-side
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32);
__type(value, u64);
__uint(max_entries, 1024);
} call_count SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_read")
int trace_read(struct trace_event_raw_sys_enter *ctx)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 *cnt = bpf_map_lookup_elem(&call_count, &pid);
if (cnt)
(*cnt)++;
else {
u64 one = 1;
bpf_map_update_elem(&call_count, &pid, &one, BPF_ANY);
}
return 0;
}
char LICENSE[] SEC("license") = "GPL";// counter.c — userspace loader
#include "counter.skel.h"
int main(void) {
struct counter_bpf *skel = counter_bpf__open_and_load();
counter_bpf__attach(skel);
// read map, print results
counter_bpf__destroy(skel);
}# Build with libbpf
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -I/usr/include/bpf \
-c counter.bpf.c -o counter.bpf.o
bpftool gen skeleton counter.bpf.o > counter.skel.h
gcc -o counter counter.c -lbpf -lelf -lz4. eBPF map types
| Map type | Key→Value | Use case |
|---|---|---|
BPF_MAP_TYPE_HASH | arbitrary→arbitrary | Per-PID counters, state |
BPF_MAP_TYPE_ARRAY | u32→fixed | Config, metrics indexed by CPU |
BPF_MAP_TYPE_PERCPU_HASH | key→per-CPU val | High-frequency counters without locks |
BPF_MAP_TYPE_RINGBUF | — | Efficient kernel→userspace events |
BPF_MAP_TYPE_PERF_EVENT_ARRAY | — | Legacy perf event output |
BPF_MAP_TYPE_LRU_HASH | key→val | Connection tracking, limited size |
BPF_MAP_TYPE_PROG_ARRAY | u32→prog | Tail calls, program chaining |
BPF_MAP_TYPE_XSKMAP | — | AF_XDP socket redirection |
Use BPF_MAP_TYPE_RINGBUF over PERF_EVENT_ARRAY for new code — lower overhead, variable-size records.
5. Verifier error triage
| Error message | Root cause | Fix |
|---|---|---|
invalid mem access 'scalar' | Dereferencing unbounded pointer | Check pointer with null test before use |
R0 !read_ok | Return without setting R0 | Ensure all paths set a return value |
jump out of range | Branch target beyond program end | Restructure conditionals |
back-edge detected | Backward jump (loop) | Use bpf_loop() helper (kernel ≥5.17) or bounded loop |
unreachable insn | Dead code after return | Remove dead branches |
invalid indirect read | Stack read of uninitialised bytes | Zero-init structs: struct foo x = {} |
misaligned stack access | Pointer arithmetic off alignment | Align reads to __u64 boundaries |
# Get detailed verifier log
bpftool prog load prog.bpf.o /sys/fs/bpf/prog type kprobe \
2>&1 | head -100
# Check loaded programs
bpftool prog list
bpftool prog dump xlated id 426. XDP programs
// xdp_drop_icmp.bpf.c
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
SEC("xdp")
int xdp_filter(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 (bpf_ntohs(eth->h_proto) != ETH_P_IP)
return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol == IPPROTO_ICMP)
return XDP_DROP;
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";# Attach XDP program to interface
ip link set dev eth0 xdp obj xdp_drop_icmp.bpf.o sec xdp
# Remove
ip link set dev eth0 xdp off
# Use native (driver) mode for best performance
ip link set dev eth0 xdp obj prog.bpf.o sec xdp mode nativeXDP return codes: XDP_PASS, XDP_DROP, XDP_TX (hairpin), XDP_REDIRECT.
7. CO-RE — compile once, run everywhere
CO-RE (Compile Once - Run Everywhere) uses BTF type info to relocate field accesses at load time.
// Use BTF-based field access (CO-RE aware)
#include <vmlinux.h> // generated from running kernel's BTF
#include <bpf/bpf_core_read.h>
SEC("kprobe/tcp_connect")
int trace_connect(struct pt_regs *ctx)
{
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
u16 dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
// BPF_CORE_READ relocates the field offset at load time
bpf_printk("connect to port %d\n", bpf_ntohs(dport));
return 0;
}# Generate vmlinux.h from running kernel
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
# Verify BTF is enabled
ls /sys/kernel/btf/vmlinuxFor the full map types reference, see references/ebpf-map-types.md.
Related skills
- Use
skills/observability/ebpf-rustfor Aya framework Rust eBPF programs - Use
skills/profilers/linux-perffor perf-based tracing without eBPF - Use
skills/runtimes/binary-hardeningfor seccomp-bpf syscall filtering - Use
skills/low-level-programming/linux-kernel-modulesfor kernel module development
eBPF Map Types Reference
Source: https://docs.kernel.org/bpf/maps.html
Table of Contents
1. Core Map Types 2. Map Operations 3. Ringbuf vs Perf Event Array 4. Map Pinning
Core Map Types
| Type | Max Key | Max Value | Notes |
|---|---|---|---|
BPF_MAP_TYPE_HASH | 512 bytes | 65536 bytes | General-purpose hash map |
BPF_MAP_TYPE_ARRAY | u32 | 65536 bytes | Fixed-size, pre-allocated, zero-initialized |
BPF_MAP_TYPE_PROG_ARRAY | u32 | 4 bytes (prog fd) | Tail call table |
BPF_MAP_TYPE_PERF_EVENT_ARRAY | u32 | 4 bytes | Perf ring buffer per CPU |
BPF_MAP_TYPE_PERCPU_HASH | 512 bytes | 65536 bytes | Per-CPU values, no locking needed |
BPF_MAP_TYPE_PERCPU_ARRAY | u32 | 65536 bytes | Per-CPU array |
BPF_MAP_TYPE_STACK_TRACE | u32 | stack IDs | Stack trace storage |
BPF_MAP_TYPE_CGROUP_ARRAY | u32 | 4 bytes | cgroup file descriptor |
BPF_MAP_TYPE_LRU_HASH | 512 bytes | 65536 bytes | LRU eviction (kernel ≥4.10) |
BPF_MAP_TYPE_LRU_PERCPU_HASH | 512 bytes | 65536 bytes | Per-CPU LRU hash |
BPF_MAP_TYPE_LPM_TRIE | variable | 65536 bytes | Longest prefix match (for IPs) |
BPF_MAP_TYPE_ARRAY_OF_MAPS | u32 | 4 bytes (map fd) | Inner maps (map-in-map) |
BPF_MAP_TYPE_HASH_OF_MAPS | 512 bytes | 4 bytes (map fd) | Inner maps (map-in-map) |
BPF_MAP_TYPE_DEVMAP | u32 | 4 bytes | XDP device redirect |
BPF_MAP_TYPE_SOCKMAP | u32 | 4 bytes | Socket redirect (BPF_SK_MSG) |
BPF_MAP_TYPE_CPUMAP | u32 | 4 bytes | XDP CPU redirect |
BPF_MAP_TYPE_XSKMAP | u32 | 4 bytes | AF_XDP socket redirect |
BPF_MAP_TYPE_SOCKHASH | variable | 4 bytes | Socket hash redirect |
BPF_MAP_TYPE_CGROUP_STORAGE | cgroup id | 65536 bytes | Per-cgroup storage |
BPF_MAP_TYPE_RINGBUF | — | — | Shared ring buffer (kernel ≥5.8) |
BPF_MAP_TYPE_INODE_STORAGE | inode ptr | 65536 bytes | Per-inode local storage |
BPF_MAP_TYPE_TASK_STORAGE | task ptr | 65536 bytes | Per-task local storage |
BPF_MAP_TYPE_BLOOM_FILTER | — | — | Probabilistic membership test |
Map Operations
From eBPF program (kernel side)
// Lookup
void *bpf_map_lookup_elem(void *map, const void *key);
// Update (flags: BPF_ANY, BPF_NOEXIST, BPF_EXIST)
int bpf_map_update_elem(void *map, const void *key, const void *value, u64 flags);
// Delete
int bpf_map_delete_elem(void *map, const void *key);
// Atomic add (ARRAY and PERCPU_ARRAY only)
// Use __sync_fetch_and_add() for atomic incrementFrom userspace (libbpf)
#include <bpf/libbpf.h>
struct bpf_map *map = bpf_object__find_map_by_name(obj, "my_map");
int map_fd = bpf_map__fd(map);
// Lookup
bpf_map_lookup_elem(map_fd, &key, &value);
// Update
bpf_map_update_elem(map_fd, &key, &value, BPF_ANY);
// Delete
bpf_map_delete_elem(map_fd, &key);
// Iterate all keys
void *prev_key = NULL;
while (bpf_map_get_next_key(map_fd, prev_key, &key) == 0) {
bpf_map_lookup_elem(map_fd, &key, &value);
prev_key = &key;
}Ringbuf vs Perf Event Array
| Feature | BPF_MAP_TYPE_RINGBUF | BPF_MAP_TYPE_PERF_EVENT_ARRAY |
|---|---|---|
| Kernel version | ≥5.8 | ≥4.3 |
| Memory sharing | Single shared buffer | Per-CPU buffers |
| Variable-size records | Yes | Yes (with padding) |
| Overhead | Lower | Higher |
| Ordering | Preserved across CPUs | Not preserved |
| API | bpf_ringbuf_reserve/submit | bpf_perf_event_output |
// Ringbuf usage (preferred)
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024); // 256KB
} events SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_read")
int trace(void *ctx)
{
struct event *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_ringbuf_submit(e, 0);
return 0;
}Map Pinning
Pin maps to the BPF filesystem to share between programs:
# Pin via bpftool
bpftool map pin id 42 /sys/fs/bpf/my_map
# Load pinned map in libbpf
int map_fd = bpf_obj_get("/sys/fs/bpf/my_map");
# Mount bpffs if needed
mount -t bpf bpf /sys/fs/bpfPin map in BPF C code (auto-pinned by libbpf skeleton):
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32);
__type(value, u64);
__uint(pinning, LIBBPF_PIN_BY_NAME); // pins to /sys/fs/bpf/<name>
} my_map SEC(".maps");