[RFC PATCH v3 17/17] s390/unwind_user/fp: Enable back chain unwinding of user space

Jens Remus posted 17 patches 1 week, 3 days ago
[RFC PATCH v3 17/17] s390/unwind_user/fp: Enable back chain unwinding of user space
Posted by Jens Remus 1 week, 3 days ago
Unwinding of user space using frame pointer (FP) is virtually impossible
on s390 for the following reasons:  The s390 64-bit (s390x) ELF ABI [1]
does only designate a "preferred" FP register and does not mandate fixed
FP and return address (RA) stack save slots.  Therefore neither the FP
register nor the FP/RA stack save slot offsets from CFA are known.
Compilers, such as GCC and Clang, do not necessarily setup a FP register
early in the function prologue, even not with compiler option
-fno-omit-frame-pointer.  Therefore the CFA offset from FP register is
not known.

This could be resolved by having compiler option -no-omit-frame-pointer
enforce all of the following:  Use the preferred FP register 11 as frame
pointer, use fixed FP/RA stack slot offsets from CFA (e.g. -72 for FP
and -48 for RA), and setup the FP register immediately after saving the
call saved registers.

Fortunately s390 provides an alternative to frame pointer:  back chain,
which can be enabled using s390-specific compiler option -mbackchain.
The back chain is very similar to a frame pointer on the stack.

Leverage the unwind user fp infrastructure to enable unwinding of user
space using back chain.  Enable HAVE_UNWIND_USER_FP and provide a s390-
specific implementation of unwind_user_fp_get_frame(), which uses the
back chain.

Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---

Notes (jremus):
    Changes in RFC v3:
    - New patch.  Implement unwind user fp using back chain on s390. Reuses
      logic from RFC v2 patch "unwind_user/backchain: Introduce back chain
      user space unwinding". (Josh)

 arch/s390/Kconfig                   |  1 +
 arch/s390/include/asm/unwind_user.h | 83 +++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+)

diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig
index 52d3f3b3e086..eb6a0fe895bc 100644
--- a/arch/s390/Kconfig
+++ b/arch/s390/Kconfig
@@ -246,6 +246,7 @@ config S390
 	select HAVE_SETUP_PER_CPU_AREA
 	select HAVE_SOFTIRQ_ON_OWN_STACK
 	select HAVE_SYSCALL_TRACEPOINTS
+	select HAVE_UNWIND_USER_FP
 	select HAVE_UNWIND_USER_SFRAME
 	select HAVE_VIRT_CPU_ACCOUNTING
 	select HAVE_VIRT_CPU_ACCOUNTING_IDLE
diff --git a/arch/s390/include/asm/unwind_user.h b/arch/s390/include/asm/unwind_user.h
index 3a95be1eb886..99cbb83dd248 100644
--- a/arch/s390/include/asm/unwind_user.h
+++ b/arch/s390/include/asm/unwind_user.h
@@ -3,8 +3,12 @@
 #define _ASM_S390_UNWIND_USER_H
 
 #include <linux/sched/task_stack.h>
+#include <linux/security.h>
 #include <linux/types.h>
+#include <asm/asm-offsets.h>
 #include <asm/fpu-insn.h>
+#include <asm/stacktrace.h>
+#include <linux/unwind_user_types.h>
 
 #ifdef CONFIG_UNWIND_USER
 
@@ -95,6 +99,85 @@ static inline int arch_unwind_user_get_reg(unsigned long *val, int regnum)
 
 #endif /* CONFIG_UNWIND_USER */
 
+#ifdef CONFIG_HAVE_UNWIND_USER_FP
+
+static inline bool ip_within_vdso(unsigned long ip)
+{
+	return in_range(ip, current->mm->context.vdso_base, vdso_text_size());
+}
+
+static inline int unwind_user_fp_get_frame(struct unwind_user_state *state,
+					   struct unwind_user_frame *frame)
+{
+	struct stack_frame_user __user *sf;
+	unsigned long __user *ra_addr;
+	unsigned long sp;
+
+	sf = (void __user *)state->sp;
+
+	/*
+	 * In topmost frame check whether IP in early prologue, RA and SP
+	 * registers saved, and no new stack frame allocated.
+	 */
+	if (state->topmost) {
+		unsigned long ra, ra_reg;
+
+		ra_addr = (unsigned long __user *)&sf->gprs[8];
+		if (__get_user(ra, ra_addr))
+			return -EINVAL;
+		if (__get_user(sp, (unsigned long __user *)&sf->gprs[9]))
+			return -EINVAL;
+		if (unwind_user_get_ra_reg(&ra_reg))
+			return -EINVAL;
+		if (ra == ra_reg && sp == state->sp)
+			goto done;
+	}
+
+	if (__get_user(sp, (unsigned long __user *)&sf->back_chain))
+		return -EINVAL;
+	if (!sp && ip_within_vdso(state->ip)) {
+		/*
+		 * Assume non-standard vDSO user wrapper stack frame.
+		 * See vDSO user wrapper code for details.
+		 */
+		struct stack_frame_vdso_wrapper *sf_vdso = (void __user *)sf;
+
+		ra_addr = (unsigned long __user *)&sf_vdso->return_address;
+		sf = (void __user *)((unsigned long)sf + STACK_FRAME_VDSO_OVERHEAD);
+		if (__get_user(sp, (unsigned long __user *)&sf->back_chain))
+			return -EINVAL;
+	} else if (!sp) {
+		/*
+		 * Assume outermost frame reached. unwind_user_next_common()
+		 * disregards all other fields in outermost frame.
+		 */
+		frame->outermost = false;
+		return 0;
+	} else {
+		/*
+		 * Assume IP past prologue and new stack frame allocated.
+		 * Follow back chain, which then equals the SP at entry.
+		 * Skips caller if wrong in topmost frame.
+		 */
+		sf = (void __user *)sp;
+		ra_addr = (unsigned long __user *)&sf->gprs[8];
+	}
+
+done:
+	frame->cfa_off = sp - state->sp + 160;
+	frame->sp_off = -160;
+	frame->fp.loc = UNWIND_USER_LOC_UNKNOWN;	/* Cannot unwind FP. */
+	frame->use_fp = false;
+	frame->ra.loc = UNWIND_USER_LOC_STACK;
+	frame->ra.offset = (unsigned long)ra_addr - (state->sp + frame->cfa_off);
+	frame->outermost = false;
+
+	return 0;
+}
+#define unwind_user_fp_get_frame unwind_user_fp_get_frame
+
+#endif /* CONFIG_HAVE_UNWIND_USER_FP */
+
 #include <asm-generic/unwind_user.h>
 
 #endif /* _ASM_S390_UNWIND_USER_H */
-- 
2.51.0
Re: [RFC PATCH v3 17/17] s390/unwind_user/fp: Enable back chain unwinding of user space
Posted by Jens Remus 1 week ago
On 12/8/2025 6:15 PM, Jens Remus wrote:

...

> Leverage the unwind user fp infrastructure to enable unwinding of user
> space using back chain.  Enable HAVE_UNWIND_USER_FP and provide a s390-
> specific implementation of unwind_user_fp_get_frame(), which uses the
> back chain.

> diff --git a/arch/s390/include/asm/unwind_user.h b/arch/s390/include/asm/unwind_user.h

> +static inline int unwind_user_fp_get_frame(struct unwind_user_state *state,
> +					   struct unwind_user_frame *frame)
> +{
> +	struct stack_frame_user __user *sf;
> +	unsigned long __user *ra_addr;
> +	unsigned long sp;
> +
> +	sf = (void __user *)state->sp;
> +
> +	/*
> +	 * In topmost frame check whether IP in early prologue, RA and SP
> +	 * registers saved, and no new stack frame allocated.
> +	 */
> +	if (state->topmost) {
> +		unsigned long ra, ra_reg;
> +
> +		ra_addr = (unsigned long __user *)&sf->gprs[8];
> +		if (__get_user(ra, ra_addr))
> +			return -EINVAL;
> +		if (__get_user(sp, (unsigned long __user *)&sf->gprs[9]))
> +			return -EINVAL;
> +		if (unwind_user_get_ra_reg(&ra_reg))
> +			return -EINVAL;
> +		if (ra == ra_reg && sp == state->sp)
> +			goto done;
> +	}

I realized that this additional heuristic is flawed:

The topmost function may be past prologue, have allocated a new stack
frame, and called a function.  The callee may have saved its RA and SP
registers in the current stack frame, so that after the return from
function call, the heuristic would erroneously assume that the topmost
function is in early prologue and use the callee's RA and SP.

Instead of erroneously skipping the caller it might erroneously insert
a callee as caller.  I'll remove it again in the next version.

> +
> +	if (__get_user(sp, (unsigned long __user *)&sf->back_chain))
> +		return -EINVAL;
> +	if (!sp && ip_within_vdso(state->ip)) {
> +		/*
> +		 * Assume non-standard vDSO user wrapper stack frame.
> +		 * See vDSO user wrapper code for details.
> +		 */
> +		struct stack_frame_vdso_wrapper *sf_vdso = (void __user *)sf;
> +
> +		ra_addr = (unsigned long __user *)&sf_vdso->return_address;
> +		sf = (void __user *)((unsigned long)sf + STACK_FRAME_VDSO_OVERHEAD);
> +		if (__get_user(sp, (unsigned long __user *)&sf->back_chain))
> +			return -EINVAL;
> +	} else if (!sp) {
> +		/*
> +		 * Assume outermost frame reached. unwind_user_next_common()
> +		 * disregards all other fields in outermost frame.
> +		 */
> +		frame->outermost = false;

		frame->outermost = true;

> +		return 0;
> +	} else {
> +		/*
> +		 * Assume IP past prologue and new stack frame allocated.
> +		 * Follow back chain, which then equals the SP at entry.
> +		 * Skips caller if wrong in topmost frame.
> +		 */
> +		sf = (void __user *)sp;
> +		ra_addr = (unsigned long __user *)&sf->gprs[8];
> +	}
> +
> +done:
> +	frame->cfa_off = sp - state->sp + 160;
> +	frame->sp_off = -160;
> +	frame->fp.loc = UNWIND_USER_LOC_UNKNOWN;	/* Cannot unwind FP. */
> +	frame->use_fp = false;
> +	frame->ra.loc = UNWIND_USER_LOC_STACK;
> +	frame->ra.offset = (unsigned long)ra_addr - (state->sp + frame->cfa_off);
> +	frame->outermost = false;
> +
> +	return 0;
> +}
> +#define unwind_user_fp_get_frame unwind_user_fp_get_frame
Regards,
Jens
-- 
Jens Remus
Linux on Z Development (D3303)
+49-7031-16-1128 Office
jremus@de.ibm.com

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/