[kvm-unit-tests PATCH v2 2/4] x86: Better backtraces for leaf functions

Mathias Krause
2025-09-15T21:54:53Z

Leaf functions are problematic for backtraces as they lack the frame
pointer setup epilogue. If such a function causes a fault, the original
caller won't be part of the backtrace. That's problematic if, for
example, memcpy() is failing because it got passed a bad pointer. The
generated backtrace will look like this, providing no clue what the
issue may be:

STACK: @401b31 4001ad
0x0000000000401b31: memcpy at lib/string.c:136 (discriminator 3)
for (i = 0; i < n; ++i)
> a[i] = b[i];

0x00000000004001ac: gdt32_end at x86/cstart64.S:127
lea __environ(%rip), %rdx
> call main
mov %eax, %edi

By abusing profiling, we can force the compiler to emit a frame pointer
setup epilogue even for leaf functions, making the above backtrace
change like this:

STACK: @401c21 400512 4001ad
0x0000000000401c21: memcpy at lib/string.c:136 (discriminator 3)
for (i = 0; i < n; ++i)
> a[i] = b[i];

0x0000000000400511: main at x86/hypercall.c:91 (discriminator 24)

> memcpy((void *)~0xbadc0de, (void *)0xdeadbeef, 42);

0x00000000004001ac: gdt32_end at x86/cstart64.S:127
lea __environ(%rip), %rdx
> call main
mov %eax, %edi

Above backtrace includes the failing memcpy() call, making it much
easier to spot the bug.

Enable "fake profiling" if supported by the compiler to get better
backtraces. The runtime overhead should be negligible for the gained
debugability as the profiling call is actually a NOP.

Signed-off-by: Mathias Krause <minipli@grsecurity.net>
---
One may argure that the "ifneq ($(KEEP_FRAME_POINTER),) ... endif"
wrapping isn't needed, and that's true. However, it simplifies toggling
that variable, if there'll ever be a need for it.

x86/Makefile.common | 11 +++++++++++
1 file changed, 11 insertions(+)

diff --git a/x86/Makefile.common b/x86/Makefile.common
index 5663a65d3df4..be18a77a779e 100644
--- a/x86/Makefile.common
+++ b/x86/Makefile.common
@@ -43,6 +43,17 @@ COMMON_CFLAGS += -O1
# stack.o relies on frame pointers.
KEEP_FRAME_POINTER := y

+ifneq ($(KEEP_FRAME_POINTER),)
+# Fake profiling to force the compiler to emit a frame pointer setup also in
+# leaf function (-mno-omit-leaf-frame-pointer doesn't work, unfortunately).
+#
+# Note:
+# We need to defer the cc-option test until -fno-pic or -no-pie have been
+# added to CFLAGS as -mnop-mcount needs it. The lazy evaluation of CFLAGS
+# during compilation makes this do "The Right Thing."
+LATE_CFLAGS += $(call cc-option, -pg -mnop-mcount, "")
+endif
+
FLATLIBS = lib/libcflat.a

ifeq ($(CONFIG_EFI),y)
--
2.47.3