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 plus 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 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. In SFrame V3 this is represented using flexible FDEs, where the CFA, FP, and RA recovery rules may specify a DWARF register number. - To make use of the signed 8-bit SFrame data word 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 data word / 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. Include 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 --- Notes (jremus): Changes in v4: - Fix __s390_get_dwarf_fpr() access of user FPR. (Heiko) - Use __s390_dwarf_fpr_to_fpr() helper instead of large switch/case block. (Heiko) - Use unsigned int for DWARF register numbers, as DWARF defines them as unsigned LEB128. - SFrame V3 support: - Remove now unused arch_sframe_init_reginfo(). - Remove now unused SFRAME_V2_S390X_OFFSET_IS_REGNUM() and SFRAME_V2_S390X_OFFSET_DECODE_REGNUM() macros. - Rename SFRAME_V2_*() macros to SFRAME_V3_*(). - Add architecture-specific defines SFRAME_REG_SP and SFRAME_REG_FP. 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 | 70 ++++++++++++++++++++++ arch/s390/include/asm/unwind_user_sframe.h | 21 +++++++ kernel/unwind/sframe.c | 2 +- kernel/unwind/sframe.h | 11 ++++ 5 files changed, 104 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 0e5fad5f06ca..063f0c857600 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -250,6 +250,7 @@ config S390 select HAVE_SOFTIRQ_ON_OWN_STACK select HAVE_STACKPROTECTOR if CC_HAS_STACKPROTECTOR_GLOBAL 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..941aa3f0f70f --- /dev/null +++ b/arch/s390/include/asm/unwind_user.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_S390_UNWIND_USER_H +#define _ASM_S390_UNWIND_USER_H + +#include +#include +#include + +#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 unsigned long __s390_dwarf_fpr_to_fpr(unsigned int regnum) +{ + unsigned int fpr; + + /* + * Convert from s390 DWARF floating-point register number (16..31) + * to floating-point register number (0..15): left rotate the least + * significant three bits and then return the least significant four + * bits. + */ + fpr = (regnum & 3) << 1; + fpr |= (regnum & 4) >> 2; + fpr |= (regnum & 8); + return fpr; +} + +static inline unsigned long __s390_get_dwarf_fpr(unsigned int regnum) +{ + struct fpu *fpu = ¤t->thread.ufpu; + + save_user_fpu_regs(); + return fpu->vxrs[__s390_dwarf_fpr_to_fpr(regnum)].high; +} + +static inline int arch_unwind_user_get_reg(unsigned long *val, + unsigned int regnum) +{ + if (regnum <= 15) { + /* DWARF register numbers 0..15 */ + struct pt_regs *regs = task_pt_regs(current); + *val = regs->gprs[regnum]; + return 0; + } else if (regnum <= 31) { + /* DWARF register numbers 16..31 */ + *val = __s390_get_dwarf_fpr(regnum); + return 0; + } + + return -EINVAL; +} +#define unwind_user_get_reg arch_unwind_user_get_reg + +#endif /* CONFIG_UNWIND_USER */ + +#include + +#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..91eea28c8a48 --- /dev/null +++ b/arch/s390/include/asm/unwind_user_sframe.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_S390_UNWIND_USER_SFRAME_H +#define _ASM_S390_UNWIND_USER_SFRAME_H + +#include +#include + +#define SFRAME_SP_OFFSET SFRAME_S390X_SP_VAL_OFFSET + +#define SFRAME_REG_SP 15 +#define SFRAME_REG_FP 11 /* "preferred" FP register */ + +static inline s32 arch_sframe_cfa_offset_decode(s32 offset) +{ + return SFRAME_V3_S390X_CFA_OFFSET_DECODE(offset); +} +#define sframe_cfa_offset_decode arch_sframe_cfa_offset_decode + +#include + +#endif /* _ASM_S390_UNWIND_USER_SFRAME_H */ diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c index 5ac502f16bad..21283e3bda42 100644 --- a/kernel/unwind/sframe.c +++ b/kernel/unwind/sframe.c @@ -12,11 +12,11 @@ #include #include #include -#include #include #include "sframe.h" #include "sframe_debug.h" +#include struct sframe_fde_internal { unsigned long func_addr; diff --git a/kernel/unwind/sframe.h b/kernel/unwind/sframe.h index 5b6112945b6c..8a5322e95403 100644 --- a/kernel/unwind/sframe.h +++ b/kernel/unwind/sframe.h @@ -19,6 +19,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) */ struct sframe_preamble { u16 magic; @@ -84,4 +85,14 @@ struct sframe_fda_v3 { #define SFRAME_V3_FLEX_FDE_CTLWORD_DEREF_P(data) (((data) >> 1) & 0x1) #define SFRAME_V3_FLEX_FDE_CTLWORD_REG_P(data) ((data) & 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_V3_S390X_CFA_OFFSET_DECODE(offset) \ + (((offset) * SFRAME_S390X_CFA_OFFSET_ALIGNMENT_FACTOR) \ + - SFRAME_S390X_CFA_OFFSET_ADJUSTMENT) + #endif /* _SFRAME_H */ -- 2.51.0