…d stacks
Every hooked allocator also carries a uretprobe, so on entry the kernel
replaces its return address with the [uprobes] trampoline. When it calls
another hooked allocator (operator new -> malloc, realloc -> malloc in
glibc), the inner capture copies that trampoline into the stack bytes.
Offline DWARF unwinding then reads it as the caller's return address and
stops, so C++ allocations lost every frame above operator new.
Before hashing, walk the task's pending return_instances and write each
orig_ret_vaddr back into the slot the kernel hijacked. On x86_64 that slot
is ri->stack, the function's entry sp.
The kernel does the same for perf and bpf_get_stackid callchains since
v6.11 (commit 4a365eb8a6d9 "perf,uprobes: fix user stack traces in the
presence of pending uretprobes"), but not for raw bpf_probe_read_user
copies. It matches trampoline values in an already unwound callchain. Raw
stack bytes also hold stale trampoline words in reused memory, so matching
by value here would patch the wrong words.
arm64 is unchanged: it hijacks the link register, and the kernel does not
track where the callee spills it, so there is no exact slot to patch.
Hashing now runs after the copy, so the dedup hash identifies the real
call path rather than one that includes the trampoline.
TL;DR: Nested allocator probes (
operator new→malloc) copied the uretprobe trampoline into captured stacks, cutting C++ memory flamegraphs off atoperator new. Memtrack now writes the real return addresses back before hashing (x86_64 only).[uprobes]trampoline. When it calls another hooked allocator (operator new→malloc, glibcrealloc→malloc), the nested capture copies the trampoline, and offline DWARF unwinding stops there.capture_stack_innernow copies the stack, writes each pendingreturn_instance'sorig_ret_vaddrback into the slot the kernel hijacked (ri->stack - spon x86_64, only if it still holds the trampoline), then hashes.bpf_get_stackidcallchains since v6.11 (torvalds/linux@4a365eb8a6d9,fixup_uretprobe_trampoline_entries), but not for rawbpf_probe_read_usercopies. It matches trampoline values in an unwound callchain. Raw stack bytes also hold stale trampoline words, so this patches by slot, not by value.test_nested_allocator_stack_has_no_uretprobe_trampoline: the fixture's payload stacks must contain no trampoline word and must contain the return address into the fixture'sallocate. Without the fix, 16/16 payload stacks contain the trampoline.cpp_fractal_computation575 nodes / 134 roots / 307 hex frames → 137 / 1 / 0.cpp_parallel_fractal_computation12,576 / 1,088 / 5,974 → 159 / 2 / 3; the remaining 3 hex frames are thread-start frames, not trampolines.Review notes
uprobe_task/return_instance/xol_areafields via CO-RE.operator new.Fixes COD-3758