[kvm-unit-tests PATCH v2 4/4] arm: Fix backtraces involving leaf functions

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

When backtracing starts in a leaf function, it'll cause the code to go
off the rials as the stack frame for leaf functions is incomplete -- it
lacks the return address, likely just causing recursive exception
handling by trying to follow an invalid pointer chain.

Unfortunately, -mno-omit-leaf-frame-pointer isn't supported for the ARM
target as an easy fix. Make use of -mapcs-frame instead to force the
generation of an APCS stack frame layout[1] that can be traversed
reliably.

As Clang doesn't support -mapcs-frame, make the stack walking code
handle this by using the (old) more compact standard format as a
fall-back.

Link: https://developer.arm.com/documentation/dui0041/c/ARM-Procedure-Call-Standard/APCS-definition/The-stack-backtrace-data-structure [1]
Signed-off-by: Mathias Krause <minipli@grsecurity.net>

---
I failed to build KUT with Clang for ARM for various reasons, the code
is clearly lacking Clang support for ARM, so I doubt this fall-back will
be needed / used anytime soon.

arm/Makefile.arm | 8 ++++++++
lib/arm/stack.c | 18 ++++++++++++++++--
2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/arm/Makefile.arm b/arm/Makefile.arm
index d6250b7fb686..7734e17fe583 100644
--- a/arm/Makefile.arm
+++ b/arm/Makefile.arm
@@ -39,4 +39,12 @@ tests =

include $(SRCDIR)/$(TEST_DIR)/Makefile.common

+ifneq ($(KEEP_FRAME_POINTER),)
+# Force the generation of an APCS stack frame layout to be able to reliably
+# walk the stack. Otherwise the compiler may omit saving LR on the stack for
+# leaf functions and, unfortunately, -mno-omit-leaf-frame-pointer isn't
+# supported on ARM :(
+LATE_CFLAGS += $(call cc-option, -mapcs-frame -DAPCS_FRAMES, "")
+endif
+
arch_clean: arm_clean
diff --git a/lib/arm/stack.c b/lib/arm/stack.c
index 66d18b47ea53..b2384d8eb4c1 100644
--- a/lib/arm/stack.c
+++ b/lib/arm/stack.c
@@ -8,6 +8,20 @@
#include <libcflat.h>
#include <stack.h>

+/*
+ * APCS stack frames are generated by code like this:
+ * | mov ip, sp
+ * | push {..., fp, ip, lr, pc}
+ * | sub fp, ip, #4
+ */
+#ifdef APCS_FRAMES
+# define FP_IDX -3
+# define LR_IDX -1
+#else
+# define FP_IDX -1
+# define LR_IDX 0
+#endif
+
int arch_backtrace_frame(const void *frame, const void **return_addrs,
int max_depth, bool current_frame)
{
@@ -27,10 +41,10 @@ int arch_backtrace_frame(const void *frame, const void **return_addrs,
for (depth = 0; depth < max_depth; depth++) {
if (!fp)
break;
- return_addrs[depth] = (void *)fp[0];
+ return_addrs[depth] = (void *)fp[LR_IDX];
if (return_addrs[depth] == 0)
break;
- fp = (unsigned long *)fp[-1];
+ fp = (unsigned long *)fp[FP_IDX];
}

walking = 0;
--
2.47.3