How does the Linux kernel physically attach an eBPF program at a kprobe compared with at a tracepoint, and what does that mechanical difference mean for what you can attach to?
answer
- one is patched in, one is compiled in
- kallsyms versus the tracefs events tree
- inlined functions have nothing to patch
- registers versus a named format file
- static key means nop when disabled
basics
~20 sA kprobe patches the live kernel, replacing the instruction at a symbol's address with a trap or a jump, so it can go almost anywhere. A tracepoint is static instrumentation compiled into the source and exists only where a kernel developer placed one.
solid answer
~50 sA kprobe is dynamic. The kernel takes the address of a symbol — typically one you found in `/proc/kallsyms` — saves the instruction there and replaces it with a breakpoint, or with a jump to a trampoline when it can be optimised, so control detours through the probe and back. That means you can probe almost any function the compiler actually emitted, but nothing guarantees the function will exist next release, and its arguments reach you as raw registers in `struct pt_regs`. A tracepoint is static: a kernel developer placed it in the source, it is compiled in behind a static key so it costs nothing when disabled, and it publishes a named argument layout you can list under `/sys/kernel/tracing/events/`. Both are bound to a program historically through `perf_event_open()` plus `ioctl(PERF_EVENT_IOC_SET_BPF)`, and through a `bpf_link` in modern libbpf.
code
bash · 6 lines# candidate for a kprobe: does the symbol exist in the running kernel?
sudo grep -w ' do_unlinkat$' /proc/kallsyms
# candidate for a tracepoint: does one exist, and what does it publish?
ls /sys/kernel/tracing/events/syscalls/sys_enter_openat/
cat /sys/kernel/tracing/events/syscalls/sys_enter_openat/formatgo deeper
Know the one-line distinction: a kprobe is added to a running kernel at a symbol's address, a tracepoint was compiled into the kernel by its developers. Say which one you would look for first.
Explain the mechanics: instruction patching with a breakpoint or optimised jump versus a static key at a compiled-in call site, and how arguments reach you as pt_regs versus named format fields. Mention the perf_event_open plus PERF_EVENT_IOC_SET_BPF attach path.
Show the operational judgement: prefer the tracepoint when one covers the path, and be able to diagnose a failed kprobe attach — inlined symbol, notrace or blacklisted function, ambiguous module symbol — from the error rather than by guessing.
Own the portability policy for hook selection across a fleet on mixed kernels: which attach points you are willing to depend on, what you do when only an internal symbol covers a path, and how you keep an agent from silently losing visibility after a kernel upgrade.
## Two ways to get code to run inside a kernel function The eBPF tracing hooks fall into two families that differ not in what they observe but in **how the kernel arranges for your program to run**. Understanding the mechanism is what lets you predict which attachments will fail, which will be stable across kernels, and how you get at the arguments. ## kprobes: patch the running kernel A kprobe is dynamic instrumentation. You give the kernel an address — usually by naming a symbol, optionally with an offset — and the kprobe machinery: 1. Copies away the original instruction at that address. 2. Replaces it with a breakpoint instruction (`int3` on x86-64). 3. When a CPU hits the breakpoint, the trap handler runs the registered probe (your eBPF program), then single-steps or emulates the saved instruction and resumes. Where it can, the kernel *optimises* the probe: instead of a trap it patches in a jump to a generated trampoline, which is considerably cheaper than taking an exception. Either way the mechanism is a live modification of kernel text. The consequences follow directly: - **Coverage is enormous.** Almost any function the compiler actually emitted is probeable. You enumerate candidates with `grep` over `/proc/kallsyms`. - **Some functions are off limits.** Functions marked `notrace` or `noinstr`, and anything on the kernel's kprobe blacklist, cannot be probed — probing them would recurse into the very machinery handling the probe. - **Inlined functions are not there.** If the compiler folded a static function into its callers, there is no symbol and no single instruction stream to patch, and the attach fails with a "no such file or directory" style error even though the function is right there in the source. - **Arguments arrive as registers.** A kprobe program's context is `struct pt_regs`, so libbpf's `PT_REGS_PARM1`/`BPF_KPROBE` macros decode arguments according to the architecture's calling convention. That decoding is only correct if you know the function's real signature, and the signature is an internal kernel detail with no stability promise. - **kretprobe is the return variant.** It works by hijacking the return address, which is why it needs a preallocated pool of in-flight instances. ## Tracepoints: static instrumentation the kernel ships with A tracepoint is a call site placed in the kernel source by a developer using the `TRACE_EVENT` macro family. It is compiled into the kernel image and guarded by a static key (a jump label), so when no one is listening the cost is a nop, and enabling it patches the branch rather than the function. Because it is declared, a tracepoint has metadata. Each one appears under `/sys/kernel/tracing/events/<subsystem>/<name>/` with a `format` file describing its fields and their offsets, and an `id` used to open it as a perf event. Your eBPF program receives a struct matching that layout, with named fields, rather than raw registers. The consequences, again directly: - **You cannot put one where you want it.** Tracepoints exist only where someone placed them. If the code path you care about has none, a kprobe is your only dynamic option. - **The interface is comparatively stable.** Tracepoint fields are a semi-public interface that kernel developers try not to break gratuitously; internal function signatures carry no such expectation. - **The syscall tracepoints are the workhorses.** `syscalls/sys_enter_*` and `syscalls/sys_exit_*` give a well-defined view of syscall entry and exit without probing internal functions at all. There is also a lower-overhead variant: `BPF_PROG_TYPE_RAW_TRACEPOINT`, attached with `BPF_RAW_TRACEPOINT_OPEN`, which hands your program the tracepoint's raw arguments without the perf-format processing in between. ## The attach path itself Historically both families go through the perf subsystem. The loader opens a perf event for the probe — `perf_event_open()` with a kprobe or tracepoint configuration — and then binds the loaded program to it with `ioctl(fd, PERF_EVENT_IOC_SET_BPF, prog_fd)`. Older tooling created kprobes by writing to `kprobe_events` in tracefs first. Modern libbpf prefers a `bpf_link`: `bpf_program__attach_kprobe()` and `bpf_program__attach_tracepoint()` create a link object whose lifetime governs the attachment. ```bash # is there a symbol to probe? sudo grep -w ' do_unlinkat$' /proc/kallsyms # is there a tracepoint, and what fields does it publish? ls /sys/kernel/tracing/events/syscalls/sys_enter_openat/ cat /sys/kernel/tracing/events/syscalls/sys_enter_openat/format ``` ## Choosing between them The rule of thumb an interviewer wants: **prefer a tracepoint when one exists at the place you care about**, because the attachment is cheaper, the fields are named, and it is less likely to evaporate on the next kernel. Reach for a kprobe when no tracepoint covers the path — which is most of the kernel — and accept that you have taken on a dependency on an internal symbol and its calling convention. When you do reach for a dynamic probe on a modern kernel with BTF available, there is a third option built on a different mechanism again, which is worth knowing before you settle on kprobes by default.
- You attach to a function that exists in the source, and the kprobe attach fails. What are the likely reasons?Three common ones. The compiler inlined it, so no symbol was emitted and there is nothing to patch — check `/proc/kallsyms`. The function is marked `notrace`/`noinstr` or sits on the kprobe blacklist, because probing it would recurse into probe handling. Or the symbol exists but is duplicated across modules, so the name alone is ambiguous and you need to qualify it.
- What does a raw tracepoint give you that an ordinary tracepoint program does not?`BPF_PROG_TYPE_RAW_TRACEPOINT`, attached with `BPF_RAW_TRACEPOINT_OPEN`, receives the tracepoint's arguments as they were passed, skipping the work of building the perf-format record. It is cheaper on hot tracepoints, at the cost of working with the raw argument list rather than the named fields in the `format` file.
- Why is a disabled tracepoint effectively free, when a disabled kprobe simply does not exist?A tracepoint is compiled in but guarded by a static key: while no one is attached the branch is patched to fall straight through, so the cost is a nop. Enabling it rewrites that branch. A kprobe has no compiled-in presence at all — until you attach, the kernel text is untouched, and attaching is what modifies it.
saying these in an interview costs you the question
- Says tracepoints can be added at any kernel function at runtime
- Thinks kprobes and tracepoints are two names for one mechanism
- Expects kprobe arguments to arrive as named fields
- Believes a kprobe on an inlined function still works
- Claims internal function signatures are a stable interface