[PATCH] s390: add CFI directives for call_on_stack

From: Stephen Brennan

Date: Fri Oct 30 2026 - 19:17:49 EST


When debugging s390 on drgn, we experience two issues. First, when a
panic is triggered on CPU 0, drgn is unable to unwind past a stack
switching event[1,2]. Second, drgn is unable to unwind through an
interrupt exception frame.

It turns out that the reason for both is the generated call_on_stack()
assembly. It modifies %r15 (the stack pointer) without any CFI
directives describing this change. Thus, the compiler-generated CFI is
wrong, and drgn tries to unwind using rules based on the wrong SP value.

To resolve this, use %r10 (a callee-saved register) to store the CFA in
a temporary register. Then, emit a CFI directive describing the change.
So long as DWARF CFI is used to unwind all leaf functions, the value of
%r10 should be recoverable, and unwinders will be able to use it to
determine the CFA and continue to the new stack.

I've tested this only in a QEMU VM using drgn's vmtest framework.
With the patch, drgn is able to unwind through two stack changes: the
change to the IRQ stack, and the change to the CPU0 nodat_stack during a
panic on CPU 0.

[1]: https://github.com/osandov/drgn/blob/97b613c23bd4d100a61b3b06fb1b5097fcef9f61/vmtest/enter_kdump.py#L69-L74
[2]: https://github.com/osandov/drgn/blob/97b613c23bd4d100a61b3b06fb1b5097fcef9f61/tests/linux_kernel/vmcore/test_vmcore.py#L19-L25

Signed-off-by: Stephen Brennan <stephen.s.brennan@xxxxxxxxxx>
---
arch/s390/include/asm/stacktrace.h | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/arch/s390/include/asm/stacktrace.h b/arch/s390/include/asm/stacktrace.h
index ac3606c3babe4..ed9ae00fa339c 100644
--- a/arch/s390/include/asm/stacktrace.h
+++ b/arch/s390/include/asm/stacktrace.h
@@ -170,6 +170,12 @@ static __always_inline unsigned long get_stack_pointer(struct task_struct *task,
#define CALL_PARM_5(t, a, ...) t, CALL_PARM_4(__VA_ARGS__)
#define CALL_PARM_6(t, a, ...) t, CALL_PARM_5(__VA_ARGS__)

+#ifdef CONFIG_DEBUG_INFO
+#define CALL_ON_STACK_CFI(str) str
+#else
+#define CALL_ON_STACK_CFI(str)
+#endif
+
/*
* Use call_on_stack() to call a function switching to a specified
* stack. Proper sign and zero extension of function arguments is
@@ -177,6 +183,10 @@ static __always_inline unsigned long get_stack_pointer(struct task_struct *task,
*
* rc = call_on_stack(nr, stack, rettype, fn, t1, a1, t2, a2, ...)
*
+ * Keep the original CFA in callee-saved register 10 while running on the
+ * alternate stack so that DWARF register save rules still refer to the
+ * original stack.
+ *
* - nr specifies the number of function arguments of fn.
* - stack specifies the stack to be used.
* - fn is the function to be called.
@@ -189,6 +199,8 @@ static __always_inline unsigned long get_stack_pointer(struct task_struct *task,
({ \
rettype (*__fn)(CALL_PARM_##nr(__VA_ARGS__)) = fn; \
unsigned long frame = current_frame_address(); \
+ register unsigned long cfa asm("10") = \
+ frame + STACK_FRAME_OVERHEAD; \
unsigned long __stack = stack; \
unsigned long prev; \
CALL_LARGS_##nr(__VA_ARGS__); \
@@ -196,15 +208,19 @@ static __always_inline unsigned long get_stack_pointer(struct task_struct *task,
\
CALL_TYPECHECK_##nr(__VA_ARGS__); \
asm volatile( \
+ CALL_ON_STACK_CFI(".cfi_remember_state\n") \
" lgr %[_prev],15\n" \
+ CALL_ON_STACK_CFI(".cfi_def_cfa %[_cfa],0\n") \
" lg 15,%[_stack]\n" \
" stg %[_frame],%[_bc](15)\n" \
" brasl 14,%[_fn]\n" \
- " lgr 15,%[_prev]" \
+ " lgr 15,%[_prev]\n" \
+ CALL_ON_STACK_CFI(".cfi_restore_state\n") \
: [_prev] "=&d" (prev), CALL_FMT_##nr \
: [_stack] "R" (__stack), \
[_bc] "i" (offsetof(struct stack_frame, back_chain)), \
[_frame] "d" (frame), \
+ [_cfa] "d" (cfa), \
[_fn] "X" (__fn) : CALL_CLOBBER_##nr); \
(rettype)r2; \
})
--
2.52.0