eBPF on Linux — kprobe vs fentry: Hooking Internals & What Production Observability Misses
There are two common ways to attach an eBPF program to a kernel function: kprobe and fentry . Most tutorials treat them as interchangeable — pick one, attach, read your data. They are not interchangeable. They install into the kernel by different mechanisms, they hand you the function's arguments in different forms, and on a hot path they cost different amounts of CPU per call. Get the choice…
Two common methods exist to attach an eBPF program to a kernel function: kprobe and fentry. Although tutorials often treat them interchangeably, they differ in their mechanisms for installation, how they access function arguments, and their CPU cost on a hot path. Misusing the choice for a function that fires frequently can introduce measurable overhead.
This post examines the differences at the source level, with a video demonstrating the hook locations in machine code. SentinelEdge, an eBPF project, hooks the kernel with kprobe across 13 attach points, providing a useful case for discussing when to opt for fentry.
Kprobe attaches by patching the target instruction, typically a breakpoint (int3 on x86) that triggers into the kprobe machinery, running the user's program, and then resuming the displaced instruction. This method works almost anywhere and requires no special build modifications to the kernel. Kprobe provides a raw struct pt_regs *\—the raw register file at the moment of the trap—allowing users to extract arguments manually via PT_REGS_PARM1..N, which correspond to the architecture's calling-convention registers (rdi, rsi, rdx, rcx, … on x86-64).
However, no function prototype is available, and offsets for argument extraction remain constant even if the kernel's function signature changes over time, potentially leading to silent data corruption.
Fentry, on the other hand, attaches to a function's __fentry__ site— the call slot left by the compiler at the top of every traceable function for ftrace—through a generated BPF trampoline. This approach does not trap the execution, leading to a closer cost to a regular call. Fentry receives typed, named arguments from the kernel prototype, eliminating the risk of silent data corruption if the signature shifts between versions (e.g., between kernel updates).
Additionally, fentry can be optimized to jump directly, reducing overhead even further compared to kprobe. An important nuance is that on modern kernels, a kprobe placed at a function's entry can be promoted to use the same ftrace call site (KPROBES_ON_FTRACE) or jump-optimized, making the difference between kprobe and fentry less pronounced.
Nonetheless, fentry remains the safer default for hot paths due to its type safety and lower overhead.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.