The cancellable syscall wrappers end with a tail call to __syscall_cancel,
the wrapper frame is then elided, so when the syscall executes the wrapper
is no longer present on the stack. Tools that unwind from CFI alone, such
as valgrind, perf and sampling profilers, cannot observe it. On gdb, it
only recovers it from DWARF call site information, which reduced-debuginfo
libc builds usually omit.
The behaviour is target dependent: for a shared (PIC) the tail call is
emitted on aarch64, arc, loongarch and riscv. It is not emitted on i386,
x86_64, arm, s390x, sparc and alpha, where the seventh argument is passed
on the stack or fewer argument registers are available, nor on powerpc
and mips, where the TOC/GOT pointer must be restored after the call.
This is why the problem was originally reported as aarch64 specific while
x86_64 was unaffected.
Rather than only inhibiting the tail call [1] (which keeps the wrapper frame
but still leaves the __syscall_cancel and __internal_syscall_cancel
frames), move the cancellation logic back into the wrappers. In the
single-threaded case the syscall is now issued directly from the wrapper;
only the multi-threaded path still calls the out-of-line __syscall_cancel_arch.
This keeps the wrapper observable and removes the extra frames, mimicking
how cancellation was handled before 89b53077d2.
The result is a small libc.so .text increase (size, first column):
ABI master patched diff increase
aarch64 1635880 1647424 11544 0.71%
x86_64 1981081 1992257 11176 0.56%
powerpc64le 2364336 2376964 12628 0.53%
riscv64 1368386 1376704 8318 0.61%
loongarch64 1741385 1755601 14216 0.82%
The tst-backtrace5 was suppose to track this issue, but due wrong
loop variable check it does not take this in account. This patch also fixes
it.
Checked on aarch64-linux-gnu, x86_64-linux-gnu, i686-linux-gnu,
arm-linux-gnueabihf, and powerpc64le-linux-gnu.
[1] https://sourceware.org/pipermail/libc-alpha/2025-March/165395.html