[RFC PATCH v3 14/17] s390/unwind_user/sframe: Enable HAVE_UNWIND_USER_SFRAME

Jens Remus
2025-12-08T17:16:46Z

Add s390 support for unwinding of user space using SFrame. This
leverages the previous commits to address the following s390
particularities:

- The CFA is defined as the value of the stack pointer (SP) at call
site in the previous frame + 160. Therefore the SP unwinds as
SP = CFA - 160. Therefore use a SP value offset from CFA of -160.

- The return address (RA) is not saved on the stack at function entry.
It is also not saved in the function prologue, when in leaf functions.
Therefore the RA does not necessarily need to be unwound in the first
unwinding step for the topmost frame.

- The frame pointer (FP) and/or return address (RA) may be saved in
other registers when in leaf functions. GCC effectively uses
floating-point registers (FPR) for this purpose. Therefore DWARF
register numbers may be encoded in the SFrame FP/RA offsets.

- To make use of the signed 8-bit SFrame offset size and effectively
reduce the .sframe section size the SFrame CFA offset values are
encoded as (CFA - 160) / 8. This is because the lowest CFA offset
value on s390 is by definition +160 (= value at function entry),
which does not fit into a signed 8-bit SFrame offset. Therefore
the CFA offset values are stored adjusted by -160. Additionally
they are scaled by the s390-specific DWARF data scaling factor of 8.
The s390x ELF ABI [1] guarantees that the CFA offset values are
always aligned on an 8-byte boundary.

Add s390-specific SFrame format definitions. Note that SFRAME_ABI_*
(and thus SFRAME_ABI_S390_ENDIAN_BIG) is currently unused.

Include <asm/unwind_user_sframe.h> after "sframe.h" to make those
s390-specific definitions available to architecture-specific unwind
user sframe code, particularly the s390-specific one.

[1]: s390x ELF ABI, https://github.com/IBM/s390x-abi/releases

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

Notes (jremus):
Changes in RFC v3:
- Adjust to rename of UNWIND_USER_LOC_NONE to UNWIND_USER_LOC_RETAIN.
- Adjust s390-specific unwind_user_word_size() to changes in the
x86-specific (see patch 4).

Changes in RFC v2:
- Provide unwind_user_word_size() to satisfy new unwind user need. Note
that support for COMPAT has not been implemented as s390 support for
COMPAT is expected to be removed with v6.19:
https://lore.kernel.org/all/20251201102713.22472A5b-hca@linux.ibm.com/
- Adjust to changes in preceding patches in this series that enable
support in unwind user (sframe) for s390 particularities.

Alternatively the s390-specific definitions could also be added to the
s390-specific unwind user sframe header. The current implementation
follows Binutils approach to have all SFrame format definitions in one
central header file.

arch/s390/Kconfig | 1 +
arch/s390/include/asm/unwind_user.h | 100 +++++++++++++++++++++
arch/s390/include/asm/unwind_user_sframe.h | 33 +++++++
kernel/unwind/sframe.c | 2 +-
kernel/unwind/sframe.h | 14 +++
5 files changed, 149 insertions(+), 1 deletion(-)
create mode 100644 arch/s390/include/asm/unwind_user.h
create mode 100644 arch/s390/include/asm/unwind_user_sframe.h

diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig
index df22b10d9141..52d3f3b3e086 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_SFRAME
select HAVE_VIRT_CPU_ACCOUNTING
select HAVE_VIRT_CPU_ACCOUNTING_IDLE
select HOTPLUG_SMT
diff --git a/arch/s390/include/asm/unwind_user.h b/arch/s390/include/asm/unwind_user.h
new file mode 100644
index 000000000000..3a95be1eb886
--- /dev/null
+++ b/arch/s390/include/asm/unwind_user.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_S390_UNWIND_USER_H
+#define _ASM_S390_UNWIND_USER_H
+
+#include <linux/sched/task_stack.h>
+#include <linux/types.h>
+#include <asm/fpu-insn.h>
+
+#ifdef CONFIG_UNWIND_USER
+
+static inline int unwind_user_word_size(struct pt_regs *regs)
+{
+ return 8;
+}
+
+static inline int arch_unwind_user_get_ra_reg(unsigned long *val)
+{
+ struct pt_regs *regs = task_pt_regs(current);
+ *val = regs->gprs[14];
+ return 0;
+}
+#define unwind_user_get_ra_reg arch_unwind_user_get_ra_reg
+
+static inline int __s390_get_dwarf_fpr(unsigned long *val, int regnum)
+{
+ switch (regnum) {
+ case 16:
+ fpu_std(0, (freg_t *)val);
+ break;
+ case 17:
+ fpu_std(2, (freg_t *)val);
+ break;
+ case 18:
+ fpu_std(4, (freg_t *)val);
+ break;
+ case 19:
+ fpu_std(6, (freg_t *)val);
+ break;
+ case 20:
+ fpu_std(1, (freg_t *)val);
+ break;
+ case 21:
+ fpu_std(3, (freg_t *)val);
+ break;
+ case 22:
+ fpu_std(5, (freg_t *)val);
+ break;
+ case 23:
+ fpu_std(7, (freg_t *)val);
+ break;
+ case 24:
+ fpu_std(8, (freg_t *)val);
+ break;
+ case 25:
+ fpu_std(10, (freg_t *)val);
+ break;
+ case 26:
+ fpu_std(12, (freg_t *)val);
+ break;
+ case 27:
+ fpu_std(14, (freg_t *)val);
+ break;
+ case 28:
+ fpu_std(9, (freg_t *)val);
+ break;
+ case 29:
+ fpu_std(11, (freg_t *)val);
+ break;
+ case 30:
+ fpu_std(13, (freg_t *)val);
+ break;
+ case 31:
+ fpu_std(15, (freg_t *)val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static inline int arch_unwind_user_get_reg(unsigned long *val, int regnum)
+{
+ if (0 <= regnum && regnum <= 15) {
+ struct pt_regs *regs = task_pt_regs(current);
+ *val = regs->gprs[regnum];
+ return 0;
+ } else if (16 <= regnum && regnum <= 31) {
+ return __s390_get_dwarf_fpr(val, regnum);
+ }
+
+ return -EINVAL;
+}
+#define unwind_user_get_reg arch_unwind_user_get_reg
+
+#endif /* CONFIG_UNWIND_USER */
+
+#include <asm-generic/unwind_user.h>
+
+#endif /* _ASM_S390_UNWIND_USER_H */
diff --git a/arch/s390/include/asm/unwind_user_sframe.h b/arch/s390/include/asm/unwind_user_sframe.h
new file mode 100644
index 000000000000..af650596cb5d
--- /dev/null
+++ b/arch/s390/include/asm/unwind_user_sframe.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_S390_UNWIND_USER_SFRAME_H
+#define _ASM_S390_UNWIND_USER_SFRAME_H
+
+#include <linux/unwind_user.h>
+#include <linux/types.h>
+
+#define SFRAME_SP_OFFSET SFRAME_S390X_SP_VAL_OFFSET
+
+static inline s32 arch_sframe_cfa_offset_decode(s32 offset)
+{
+ return SFRAME_V2_S390X_CFA_OFFSET_DECODE(offset);
+}
+#define sframe_cfa_offset_decode arch_sframe_cfa_offset_decode
+
+static inline void
+arch_sframe_init_reginfo(struct unwind_user_reginfo *reginfo, s32 offset)
+{
+ if (SFRAME_V2_S390X_OFFSET_IS_REGNUM(offset)) {
+ reginfo->loc = UNWIND_USER_LOC_REG;
+ reginfo->regnum = SFRAME_V2_S390X_OFFSET_DECODE_REGNUM(offset);
+ } else if (offset) {
+ reginfo->loc = UNWIND_USER_LOC_STACK;
+ reginfo->offset = offset;
+ } else {
+ reginfo->loc = UNWIND_USER_LOC_RETAIN;
+ }
+}
+#define sframe_init_reginfo arch_sframe_init_reginfo
+
+#include <asm-generic/unwind_user_sframe.h>
+
+#endif /* _ASM_S390_UNWIND_USER_SFRAME_H */
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 92f770fc21f6..bd446d55b552 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -12,11 +12,11 @@
#include <linux/mm.h>
#include <linux/string_helpers.h>
#include <linux/sframe.h>
-#include <asm/unwind_user_sframe.h>
#include <linux/unwind_user_types.h>

#include "sframe.h"
#include "sframe_debug.h"
+#include <asm/unwind_user_sframe.h>

struct sframe_fde_internal {
unsigned long func_start_addr;
diff --git a/kernel/unwind/sframe.h b/kernel/unwind/sframe.h
index 69ce0d5b9694..c09f25fbaa2f 100644
--- a/kernel/unwind/sframe.h
+++ b/kernel/unwind/sframe.h
@@ -18,6 +18,7 @@
#define SFRAME_ABI_AARCH64_ENDIAN_BIG 1
#define SFRAME_ABI_AARCH64_ENDIAN_LITTLE 2
#define SFRAME_ABI_AMD64_ENDIAN_LITTLE 3
+#define SFRAME_ABI_S390X_ENDIAN_BIG 4 /* s390 64-bit (s390x) */

#define SFRAME_FDE_TYPE_PCINC 0
#define SFRAME_FDE_TYPE_PCMASK 1
@@ -69,4 +70,17 @@ struct sframe_fde {
#define SFRAME_FRE_OFFSET_SIZE(data) ((data >> 5) & 0x3)
#define SFRAME_FRE_MANGLED_RA_P(data) ((data >> 7) & 0x1)

+/* s390 64-bit (s390x) */
+
+#define SFRAME_S390X_SP_VAL_OFFSET (-160)
+
+#define SFRAME_S390X_CFA_OFFSET_ADJUSTMENT SFRAME_S390X_SP_VAL_OFFSET
+#define SFRAME_S390X_CFA_OFFSET_ALIGNMENT_FACTOR 8
+#define SFRAME_V2_S390X_CFA_OFFSET_DECODE(offset) \
+ (((offset) * SFRAME_S390X_CFA_OFFSET_ALIGNMENT_FACTOR) \
+ - SFRAME_S390X_CFA_OFFSET_ADJUSTMENT)
+
+#define SFRAME_V2_S390X_OFFSET_IS_REGNUM(offset) ((offset) & 1)
+#define SFRAME_V2_S390X_OFFSET_DECODE_REGNUM(offset) ((offset) >> 1)
+
#endif /* _SFRAME_H */
--
2.51.0