Attach cookies and multi-attach (kprobe.multi / uprobe.multi)¶
Blog series: Part 5 — First steps with libbpf (kprobe fundamentals) · Part 12 — Write eBPF in pure Java (compiler plugin enabling multi-attach)
Javadoc: @KProbeMulti · @UProbeMulti
Source: KProbeMulti.java · UProbeMulti.java · BPFProgram.java
See also: Kprobes / Fentry · Uprobes · BPF Maps
Without attach cookies, a single BPF program attached to two different kernel
functions cannot determine at runtime which entry point fired — all context
pointers look the same. Attach cookies solve this: each attachment carries a
u64 that the BPF side reads via bpf_get_attach_cookie(ctx), giving
zero-cost disambiguation with no extra map lookups. Multi-attach compounds the
benefit: instead of N separate attachKProbe calls (N syscalls, N BPF links),
one attachKProbeMulti call wires the program to all N symbols atomically and
returns a single BPFLink handle.
Two related capabilities landed in BPFProgram:
- Attach cookies - a
u64value bound to each attachment. Retrievable from BPF-side code viaBPFJ.bpf_get_attach_cookie(ctx). Lets one BPF program disambiguate multiple attachments of itself. - Multi-attach - a single syscall attaches one BPF program to N kernel
symbols (
kprobe.multi, kernel >= 5.18) or N user-space functions (uprobe.multi, kernel >= 6.6). Each attachment gets its own cookie.
Cookie-only¶
prog.attachKProbe(handle, "__x64_sys_openat", false, 0xAAAAL);
prog.attachKProbe(handle, "__x64_sys_openat2", false, 0xBBBBL);
Inside the BPF program:
The long cookie parameter is optional - existing cookie-less overloads
still work (they pass cookie = 0L).
kprobe.multi¶
@BPFFunction(section = "kprobe.multi/all", autoAttach = false)
@KProbeMulti("*")
int onSyscall(Ptr<?> ctx) {
long cookie = BPFJ.bpf_get_attach_cookie(ctx);
Ptr<Long> cur = counts.bpf_get(cookie);
long next;
if (cur == null) {
next = 1;
} else {
next = cur.val() + 1;
}
counts.put(cookie, next);
return 0;
}
Feature gate: Features.hasAttachType(BPFAttachType.TRACE_KPROBE_MULTI)
must return true. On older kernels the call throws
BPFLoadError.UnsupportedKernel("attach_type TRACE_KPROBE_MULTI", "5.18")
before touching libbpf.
uprobe.multi¶
prog.attachUprobeMulti(
prog.getProgramByName("onMany"),
"/usr/lib/x86_64-linux-gnu/libc.so.6",
new String[]{"malloc", "free", "getenv"},
new long[]{1L, 2L, 3L},
false /* not retprobe */);
Feature gate: Features.hasAttachType(BPFAttachType.TRACE_UPROBE_MULTI)
(kernel >= 6.6).
Choosing between single-attach and multi-attach¶
| Single-attach | Multi-attach | |
|---|---|---|
| BPF link handles | One per symbol | One for all N symbols |
| Independent detach | Yes — detach any subset | No — all detach together |
| Syscall cost | N bpf(BPF_LINK_CREATE) calls |
1 bpf(BPF_LINK_CREATE) call |
Use single-attach when you need to detach individual symbols at different points in the program's lifetime. Use multi-attach when all N attachment points share the same lifecycle and you want the lower syscall overhead.
Auto-attach interaction¶
autoAttachPrograms() and attachAllUprobes(pid, path) deliberately SKIP
programs whose section starts with kprobe.multi/, kretprobe.multi/,
uprobe.multi/, or uretprobe.multi/. Multi-attach needs the symbol
array - you must call attachKProbeMulti / attachUprobeMulti explicitly.
Sample¶
bpf-samples/src/main/java/me/bechberger/ebpf/samples/KProbeMultiCounter.java
attaches one BPF program to 20 x86_64 syscall entry points via
attachKProbeMulti. Each symbol receives a cookie equal to its index in the
array. The BPF handler reads the cookie and increments a per-cookie counter in
a BPFHashMap. After 5 seconds the Java side reads the map, sorts by count,
and prints the top 10.
Run (kernel ≥ 5.18 required):
Expected output:
Each line shows the symbol name and the number of calls observed during the 5-second window.