Linux - Tracepoints vs Kprobes vs fentry
In the Linux kernel ecosystem, Tracepoints and Kprobes are the two primary mechanisms used to hook into kernel events for debugging, performance analysis, and monitoring (via tools like perf, ftrace, and eBPF).
Tracepoints (Static Instrumentation)
Tracepoints are "markers" explicitly placed in the kernel source code by developers. They are pre-defined locations (e.g., sched_switch, net_dev_xmit) that expose specific variables to user-space.
- Nature: Static. You can only use tracepoints where a kernel developer has already put one.
- Stability: High. Tracepoints are considered a stable API. Even if the underlying kernel functions are refactored, the tracepoint name and the data it provides usually remain the same.
- Overhead: Very Low. When disabled, a tracepoint is essentially a
NOP(no-operation) instruction. When enabled, they are highly optimized. - Data Access: They provide a structured, clean format for data. You don't need to know the internal memory layout of the kernel; the tracepoint gives you exactly what you need (e.g., PID, timestamps, filenames).
Kprobes (Dynamic Instrumentation)
Kprobes (Kernel Probes) allow you to dynamically break into any kernel routine. It works by replacing the instruction at a specific address with a breakpoint.
- Nature: Dynamic. You can probe almost any function or even a specific line of code/instruction address while the kernel is running.
- Stability: Low. Kprobes depend on the kernel’s internal symbols and function signatures. If the kernel is updated and a function is renamed, removed, or its arguments change, your probe will break.
- Overhead: Higher. Because Kprobes use a breakpoint trap (exception) mechanism, the CPU has to save registers, switch contexts, execute the probe, and then single-step the original instruction. (Note: Kretprobes—for return values—have even more overhead).
- Data Access: Raw. You have access to the CPU registers and memory. To get useful data, you often have to manually cast memory addresses to kernel structures, which requires knowledge of the kernel source code.
When to Use Which?
Use Tracepoints when:
- You are building a tool meant to work across different kernel versions (e.g., a monitoring agent).
- The event you want to track is a common one (scheduler events, syscalls, network interrupts, disk I/O).
- Performance is critical, and you cannot afford the overhead of context switching.
Use Kprobes when:
- The kernel developer did not provide a tracepoint for the specific logic you are investigating.
- You are debugging a "Heisenbug" or a specific driver issue where you need to inspect internal function variables.
- You are doing deep security research or root-cause analysis on a specific kernel build.
The Modern Context: BPF (eBPF)
In modern Linux (Kernel 4.x+), both Tracepoints and Kprobes serve as trigger sources for BPF programs.
SEC("tracepoint/syscalls/sys_enter_openat"): Your BPF program runs whenever that specific stable hook is hit.SEC("kprobe/do_sys_open"): Your BPF program runs whenever the internaldo_sys_openfunction is called.
The "Golden Rule" of Linux Tracing: Always check for a Tracepoint first. If it exists, use it. If it doesn't exist and you absolutely need the data, use a Kprobe.
fentry/fexit: BPF Trampoline programs
In the context of BPF, fentry (Function Entry) is technically neither a Tracepoint nor a traditional Kprobe. It is a newer, high-performance alternative to Kprobes that was introduced in Linux Kernel 5.5.
Is it a Kprobe? (No, but it acts like one)
Like a Kprobe, fentry allows you to hook into almost any kernel function.
- The Difference: Traditional Kprobes use a "breakpoint" (an
int3instruction) which triggers a CPU exception. This is slow because the CPU has to stop, save everything, jump to a handler, and then jump back. - How
fentryworks: It uses the same mechanism as ftrace. When the kernel is compiled, a small "nop" (no-operation) space is left at the start of every function.fentryreplaces that "nop" with a direct jump to a BPF Trampoline. There is no "break" or "exception," making it significantly faster than a Kprobe.
Why use fentry instead of a Kprobe?
If you are using a modern kernel (5.5+), you should almost always prefer fentry over kprobe for two main reasons:
A. Performance
Because it uses a direct trampoline instead of a breakpoint trap, the overhead of calling the BPF program is much lower.
B. BTF (BPF Type Format) — The "Superpower"
This is the biggest advantage.
- With Kprobes: You have to look at the kernel source code to see that the second argument of a function is a
struct file *, then you have to usePT_REGS_PARM2to get the register, and then usebpf_probe_readto get data. It's tedious and error-prone. - With
fentry: It is BTF-aware. It knows the function signature. You can access arguments just like you are writing C code:// bpftrace example using fentry fentry:vmlinux:do_sys_open { printf("Opening file: %s\n", str(args->filename)); }
Comparison Table
| Feature | Kprobe | Tracepoint | fentry (BPF) |
|---|---|---|---|
| Placement | Anywhere | Static locations | Start of functions |
| Overhead | High (Breakpoint) | Very Low | Very Low (Trampoline) |
| Arguments | Raw Registers | Structured | Automatic (via BTF) |
| Kernel Req. | Very old (2.6+) | Old | Modern (5.5+) |