Move the comment "Get the Canonical Frame Address (CFA)" to the top of the sequence of statements that actually get the CFA. Reword the comment "Find the Return Address (RA)" to "Get ...", as the statements actually get the RA. Add a respective comment to the statements that get the FP. This will be useful once future commits extend the logic to get the RA and FP. While at it align the comment on the "stack going in wrong direction" check to the following one on the "address is word aligned" check. Signed-off-by: Jens Remus --- kernel/unwind/user.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index d053295b1f7e..f81c36ab2861 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -38,6 +38,7 @@ static int unwind_user_next_common(struct unwind_user_state *state, return 0; } + /* Get the Canonical Frame Address (CFA) */ if (frame->use_fp) { if (state->fp < state->sp) return -EINVAL; @@ -45,11 +46,9 @@ static int unwind_user_next_common(struct unwind_user_state *state, } else { cfa = state->sp; } - - /* Get the Canonical Frame Address (CFA) */ cfa += frame->cfa_off; - /* stack going in wrong direction? */ + /* Make sure that stack is not going in wrong direction */ if (cfa <= state->sp) return -EINVAL; @@ -57,10 +56,11 @@ static int unwind_user_next_common(struct unwind_user_state *state, if (cfa & (state->ws - 1)) return -EINVAL; - /* Find the Return Address (RA) */ + /* Get the Return Address (RA) */ if (get_user_word(&ra, cfa, frame->ra_off, state->ws)) return -EINVAL; + /* Get the Frame Pointer (FP) */ if (frame->fp_off && get_user_word(&fp, cfa, frame->fp_off, state->ws)) return -EINVAL; -- 2.51.0 This simplifies the code. unwind_user_next_fp() does not need to return -EINVAL if config option HAVE_UNWIND_USER_FP is disabled, as unwind_user_start() will then not select this unwind method and unwind_user_next() will therefore not call it. Provide (1) a dummy definition of ARCH_INIT_USER_FP_FRAME, if the unwind user method HAVE_UNWIND_USER_FP is not enabled, (2) a common fallback definition of unwind_user_at_function_start() which returns false, and (3) a common dummy definition of ARCH_INIT_USER_FP_ENTRY_FRAME. Note that enabling the config option HAVE_UNWIND_USER_FP without defining ARCH_INIT_USER_FP_FRAME triggers a compile error, which is helpful when implementing support for this unwind user method in an architecture. Enabling the config option when providing an arch- specific unwind_user_at_function_start() definition makes it necessary to also provide an arch-specific ARCH_INIT_USER_FP_ENTRY_FRAME definition. Signed-off-by: Jens Remus --- arch/x86/include/asm/unwind_user.h | 1 + include/linux/unwind_user.h | 18 ++++++++++++++++-- kernel/unwind/user.c | 4 ---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index f9a1c460150d..a528eee80dd6 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -37,6 +37,7 @@ static inline bool unwind_user_at_function_start(struct pt_regs *regs) { return is_uprobe_at_func_entry(regs); } +#define unwind_user_at_function_start unwind_user_at_function_start #endif /* CONFIG_HAVE_UNWIND_USER_FP */ diff --git a/include/linux/unwind_user.h b/include/linux/unwind_user.h index 7f7282516bf5..64618618febd 100644 --- a/include/linux/unwind_user.h +++ b/include/linux/unwind_user.h @@ -5,8 +5,22 @@ #include #include -#ifndef ARCH_INIT_USER_FP_FRAME - #define ARCH_INIT_USER_FP_FRAME +#ifndef CONFIG_HAVE_UNWIND_USER_FP + +#define ARCH_INIT_USER_FP_FRAME(ws) + +#endif + +#ifndef ARCH_INIT_USER_FP_ENTRY_FRAME +#define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) +#endif + +#ifndef unwind_user_at_function_start +static inline bool unwind_user_at_function_start(struct pt_regs *regs) +{ + return false; +} +#define unwind_user_at_function_start unwind_user_at_function_start #endif int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries); diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index f81c36ab2861..fdb1001e3750 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -74,7 +74,6 @@ static int unwind_user_next_common(struct unwind_user_state *state, static int unwind_user_next_fp(struct unwind_user_state *state) { -#ifdef CONFIG_HAVE_UNWIND_USER_FP struct pt_regs *regs = task_pt_regs(current); if (state->topmost && unwind_user_at_function_start(regs)) { @@ -88,9 +87,6 @@ static int unwind_user_next_fp(struct unwind_user_state *state) ARCH_INIT_USER_FP_FRAME(state->ws) }; return unwind_user_next_common(state, &fp_frame); -#else - return -EINVAL; -#endif } static int unwind_user_next_sframe(struct unwind_user_state *state) -- 2.51.0 The unwind user framework in general requires an architecture-specific implementation of unwind_user_word_size() to be present for any unwind method, whether that is fp or a future other method, such as potentially sframe. Guard unwind_user_word_size() by the availability of the UNWIND_USER framework instead of the specific HAVE_UNWIND_USER_FP method. This facilitates to selectively disable HAVE_UNWIND_USER_FP on x86 (e.g. for test purposes) once a new unwind method is added to unwind user. Signed-off-by: Jens Remus --- arch/x86/include/asm/unwind_user.h | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index a528eee80dd6..4d699e4954ed 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -2,11 +2,27 @@ #ifndef _ASM_X86_UNWIND_USER_H #define _ASM_X86_UNWIND_USER_H -#ifdef CONFIG_HAVE_UNWIND_USER_FP +#ifdef CONFIG_UNWIND_USER #include #include +static inline int unwind_user_word_size(struct pt_regs *regs) +{ + /* We can't unwind VM86 stacks */ + if (regs->flags & X86_VM_MASK) + return 0; +#ifdef CONFIG_X86_64 + if (!user_64bit_mode(regs)) + return sizeof(int); +#endif + return sizeof(long); +} + +#endif /* CONFIG_UNWIND_USER */ + +#ifdef CONFIG_HAVE_UNWIND_USER_FP + #define ARCH_INIT_USER_FP_FRAME(ws) \ .cfa_off = 2*(ws), \ .ra_off = -1*(ws), \ @@ -21,18 +37,6 @@ .use_fp = false, \ .outermost = false, -static inline int unwind_user_word_size(struct pt_regs *regs) -{ - /* We can't unwind VM86 stacks */ - if (regs->flags & X86_VM_MASK) - return 0; -#ifdef CONFIG_X86_64 - if (!user_64bit_mode(regs)) - return sizeof(int); -#endif - return sizeof(long); -} - static inline bool unwind_user_at_function_start(struct pt_regs *regs) { return is_uprobe_at_func_entry(regs); -- 2.51.0 Get rid of superfluous ifdef and return explicit word size depending on 32-bit or 64-bit mode. Suggested-by: Linus Torvalds Signed-off-by: Jens Remus --- arch/x86/include/asm/unwind_user.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index 4d699e4954ed..2dfb5ef11e36 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -12,11 +12,7 @@ static inline int unwind_user_word_size(struct pt_regs *regs) /* We can't unwind VM86 stacks */ if (regs->flags & X86_VM_MASK) return 0; -#ifdef CONFIG_X86_64 - if (!user_64bit_mode(regs)) - return sizeof(int); -#endif - return sizeof(long); + return user_64bit_mode(regs) ? 8 : 4; } #endif /* CONFIG_UNWIND_USER */ -- 2.51.0 Align to x86 and add a compile-time check that asm/dwarf.h is only included in pure assembly files. Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v2: - Adjust to upstream change of __ASSEMBLY__ to __ASSEMBLER__. arch/s390/include/asm/dwarf.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/include/asm/dwarf.h b/arch/s390/include/asm/dwarf.h index e3ad6798d0cd..df9f467910f7 100644 --- a/arch/s390/include/asm/dwarf.h +++ b/arch/s390/include/asm/dwarf.h @@ -2,7 +2,9 @@ #ifndef _ASM_S390_DWARF_H #define _ASM_S390_DWARF_H -#ifdef __ASSEMBLER__ +#ifndef __ASSEMBLER__ +#warning "asm/dwarf.h should be only included in pure assembly files" +#endif #define CFI_STARTPROC .cfi_startproc #define CFI_ENDPROC .cfi_endproc @@ -33,6 +35,4 @@ .cfi_sections .eh_frame, .debug_frame #endif -#endif /* __ASSEMBLER__ */ - #endif /* _ASM_S390_DWARF_H */ -- 2.51.0 This replicates Josh's x86 commit TODO ("x86/asm: Avoid emitting DWARF CFI for non-VDSO") for s390. It also aligns asm/dwarf.h to x86 asm/dwarf2.h. It was decided years ago that .cfi_* annotations aren't maintainable in the kernel. For the kernel proper, ensure the CFI_* macros don't do anything. On the other hand the vDSO library *does* use them, so user space can unwind through it. Make sure these macros only work for vDSO. They aren't actually being used outside of vDSO anyway, so there's no functional change. Signed-off-by: Jens Remus --- Notes (jremus): Link to latest x86 patch: https://lore.kernel.org/all/20250425024022.477374378@goodmis.org/ arch/s390/include/asm/dwarf.h | 45 ++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/arch/s390/include/asm/dwarf.h b/arch/s390/include/asm/dwarf.h index df9f467910f7..6bcf37256feb 100644 --- a/arch/s390/include/asm/dwarf.h +++ b/arch/s390/include/asm/dwarf.h @@ -6,6 +6,18 @@ #warning "asm/dwarf.h should be only included in pure assembly files" #endif +.macro nocfi args:vararg +.endm + +#ifdef BUILD_VDSO + + /* + * For the vDSO, emit both runtime unwind information and debug + * symbols for the .dbg file. + */ + + .cfi_sections .eh_frame, .debug_frame + #define CFI_STARTPROC .cfi_startproc #define CFI_ENDPROC .cfi_endproc #define CFI_DEF_CFA_OFFSET .cfi_def_cfa_offset @@ -16,23 +28,24 @@ #ifdef CONFIG_AS_CFI_VAL_OFFSET #define CFI_VAL_OFFSET .cfi_val_offset #else -#define CFI_VAL_OFFSET # +#define CFI_VAL_OFFSET nocfi #endif -#ifndef BUILD_VDSO - /* - * Emit CFI data in .debug_frame sections and not in .eh_frame - * sections. The .eh_frame CFI is used for runtime unwind - * information that is not being used. Hence, vmlinux.lds.S - * can discard the .eh_frame sections. - */ - .cfi_sections .debug_frame -#else - /* - * For vDSO, emit CFI data in both, .eh_frame and .debug_frame - * sections. - */ - .cfi_sections .eh_frame, .debug_frame -#endif +#else /* !BUILD_VDSO */ + +/* + * On s390, these macros aren't used outside vDSO. As well they shouldn't be: + * they're fragile and very difficult to maintain. + */ + +#define CFI_STARTPROC nocfi +#define CFI_ENDPROC nocfi +#define CFI_DEF_CFA_OFFSET nocfi +#define CFI_ADJUST_CFA_OFFSET nocfi +#define CFI_RESTORE nocfi +#define CFI_REL_OFFSET nocfi +#define CFI_VAL_OFFSET nocfi + +#endif /* !BUILD_VDSO */ #endif /* _ASM_S390_DWARF_H */ -- 2.51.0 Keep all function symbols in the vDSO .symtab for stack trace purposes. This enables a stack tracer, such as perf, to lookup these function symbols in addition to those already exported in vDSO .dynsym. Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v2: - Use objcopy flag "-g" instead of "-S" with the cumbersome filter "-w -K "__arch_*" -K "__cvdso_*" -K "__s390_vdso_*" to keep the function symbols, as Josh did in "x86/vdso: Enable sframe generation in VDSO": https://lore.kernel.org/all/20250425024023.173709192@goodmis.org/ - Reword commit message. Note that unlike Josh I did not squash this into the subsequent patch "s390/vdso: Enable SFrame generation in vDSO", as this change is unrelated to enabling the use of SFrame. perf report/script do also benefit from this change when using perf record --call-graph dwarf. Note that this change does not cause the vDSO build-id to change. perf record may therefore not dump an updated copy of the vDSO to ~/.debug/[vdso]//vdso, so that perf report/script may use a stale copy without .symtab. Resolve by deleting ~/.debug/. arch/s390/kernel/vdso64/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kernel/vdso64/Makefile b/arch/s390/kernel/vdso64/Makefile index d8f0df742809..8e78dc3ba025 100644 --- a/arch/s390/kernel/vdso64/Makefile +++ b/arch/s390/kernel/vdso64/Makefile @@ -53,7 +53,7 @@ $(obj)/vdso64.so.dbg: $(obj)/vdso64.lds $(obj-vdso64) $(obj-cvdso64) FORCE $(call if_changed,vdso_and_check) # strip rule for the .so file -$(obj)/%.so: OBJCOPYFLAGS := -S +$(obj)/%.so: OBJCOPYFLAGS := -g $(obj)/%.so: $(obj)/%.so.dbg FORCE $(call if_changed,objcopy) -- 2.51.0 This replicates Josh's x86 patch "x86/vdso: Enable sframe generation in VDSO" [1] for s390. Test whether the assembler supports generating SFrame stack trace information. Note that it is insufficient to test whether the assembler supports option --gsframe, as GNU assembler supports that regardless of whether it is actually capable of generating SFrame stack trace information for the architecture. If so enable SFrame stack trace information generation in the vDSO library so kernel and user space can unwind through it. [1]: x86/vdso: Enable sframe generation in VDSO, https://lore.kernel.org/all/20250425024023.173709192@goodmis.org/ Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v2: - Introduce config option AS_SFRAME instead of requiring Josh's x86 patch as pre-requisite. - Reword commit message. Link to Josh's latest x86 patch: https://lore.kernel.org/all/20250425024023.173709192@goodmis.org/ arch/Kconfig | 3 +++ arch/s390/include/asm/dwarf.h | 4 ++++ arch/s390/kernel/vdso64/Makefile | 7 ++++++- arch/s390/kernel/vdso64/vdso64.lds.S | 9 +++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/arch/Kconfig b/arch/Kconfig index 06c4f909398c..7fa89d70b244 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -482,6 +482,9 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH It uses the same command line parameters, and sysctl interface, as the generic hardlockup detectors. +config AS_SFRAME + def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc) + config UNWIND_USER bool diff --git a/arch/s390/include/asm/dwarf.h b/arch/s390/include/asm/dwarf.h index 6bcf37256feb..2f148b15fd7d 100644 --- a/arch/s390/include/asm/dwarf.h +++ b/arch/s390/include/asm/dwarf.h @@ -16,7 +16,11 @@ * symbols for the .dbg file. */ +#ifdef CONFIG_AS_SFRAME + .cfi_sections .eh_frame, .debug_frame, .sframe +#else .cfi_sections .eh_frame, .debug_frame +#endif #define CFI_STARTPROC .cfi_startproc #define CFI_ENDPROC .cfi_endproc diff --git a/arch/s390/kernel/vdso64/Makefile b/arch/s390/kernel/vdso64/Makefile index 8e78dc3ba025..f597f3b863d7 100644 --- a/arch/s390/kernel/vdso64/Makefile +++ b/arch/s390/kernel/vdso64/Makefile @@ -20,7 +20,11 @@ targets := $(obj-vdso64) $(obj-cvdso64) vdso64.so vdso64.so.dbg obj-vdso64 := $(addprefix $(obj)/, $(obj-vdso64)) obj-cvdso64 := $(addprefix $(obj)/, $(obj-cvdso64)) -KBUILD_AFLAGS += -DBUILD_VDSO +ifeq ($(CONFIG_AS_SFRAME),y) + SFRAME_CFLAGS := -Wa,--gsframe +endif + +KBUILD_AFLAGS += -DBUILD_VDSO $(SFRAME_CFLAGS) KBUILD_CFLAGS += -DBUILD_VDSO -DDISABLE_BRANCH_PROFILING KBUILD_AFLAGS_64 := $(filter-out -m64,$(KBUILD_AFLAGS)) @@ -32,6 +36,7 @@ KBUILD_CFLAGS_64 := $(filter-out -mno-pic-data-is-text-relative,$(KBUILD_CFLAGS_ KBUILD_CFLAGS_64 := $(filter-out -munaligned-symbols,$(KBUILD_CFLAGS_64)) KBUILD_CFLAGS_64 := $(filter-out -fno-asynchronous-unwind-tables,$(KBUILD_CFLAGS_64)) KBUILD_CFLAGS_64 += -m64 -fPIC -fno-common -fno-builtin -fasynchronous-unwind-tables +KBUILD_CFLAGS_64 += $(SFRAME_CFLAGS) ldflags-y := -shared -soname=linux-vdso64.so.1 \ --hash-style=both --build-id=sha1 -T diff --git a/arch/s390/kernel/vdso64/vdso64.lds.S b/arch/s390/kernel/vdso64/vdso64.lds.S index e4f6551ae898..0205d84369ca 100644 --- a/arch/s390/kernel/vdso64/vdso64.lds.S +++ b/arch/s390/kernel/vdso64/vdso64.lds.S @@ -50,6 +50,11 @@ SECTIONS .eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr .eh_frame : { KEEP (*(.eh_frame)) } :text + +#ifdef CONFIG_AS_SFRAME + .sframe : { *(.sframe) } :text :sframe +#endif + .gcc_except_table : { *(.gcc_except_table .gcc_except_table.*) } .rela.dyn ALIGN(8) : { *(.rela.dyn) } @@ -114,6 +119,7 @@ SECTIONS * Very old versions of ld do not recognize this name token; use the constant. */ #define PT_GNU_EH_FRAME 0x6474e550 +#define PT_GNU_SFRAME 0x6474e554 /* * We must supply the ELF program headers explicitly to get just one @@ -125,6 +131,9 @@ PHDRS dynamic PT_DYNAMIC FLAGS(4); /* PF_R */ note PT_NOTE FLAGS(4); /* PF_R */ eh_frame_hdr PT_GNU_EH_FRAME; +#ifdef CONFIG_AS_SFRAME + sframe PT_GNU_SFRAME; +#endif } /* -- 2.51.0 Most architectures define their CFA as the value of the stack pointer (SP) at the call site in the previous frame, as suggested by the DWARF standard. Therefore the SP at call site can be unwound using an implicitly assumed value offset from CFA rule with an offset of zero: .cfi_val_offset , 0 As a result the SP at call site computes as follows: SP = CFA Enable unwinding of user space for architectures, such as s390, which define their CFA as the value of the SP at the call site in the previous frame with an offset. Do so by enabling architectures to override the default SP value offset from CFA of zero with an architecture-specific one: .cfi_val_offset , offset So that the SP at call site computes as follows: SP = CFA + offset Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v2: - Reword commit message. (Josh) - Use term "sp_off" instead of "sp_val_off". (Josh) - Move definition, initialization, and setting of sp_off field to happen right after the cfa_off field. - Use SFRAME_SP_OFFSET macro instead of sframe_sp_off() function, which can be overridden by an architecture, such as s390. - Drop lengthy sframe_sp_[val_]off() comment. arch/x86/include/asm/unwind_user.h | 2 ++ include/asm-generic/Kbuild | 1 + include/asm-generic/unwind_user_sframe.h | 12 ++++++++++++ include/linux/unwind_user_types.h | 1 + kernel/unwind/sframe.c | 2 ++ kernel/unwind/user.c | 11 ++++++----- 6 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 include/asm-generic/unwind_user_sframe.h diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index 2dfb5ef11e36..d70ffd7bbdb7 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -21,6 +21,7 @@ static inline int unwind_user_word_size(struct pt_regs *regs) #define ARCH_INIT_USER_FP_FRAME(ws) \ .cfa_off = 2*(ws), \ + .sp_off = 0, \ .ra_off = -1*(ws), \ .fp_off = -2*(ws), \ .use_fp = true, \ @@ -28,6 +29,7 @@ static inline int unwind_user_word_size(struct pt_regs *regs) #define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) \ .cfa_off = 1*(ws), \ + .sp_off = 0, \ .ra_off = -1*(ws), \ .fp_off = 0, \ .use_fp = false, \ diff --git a/include/asm-generic/Kbuild b/include/asm-generic/Kbuild index 295c94a3ccc1..b1d448ef4a50 100644 --- a/include/asm-generic/Kbuild +++ b/include/asm-generic/Kbuild @@ -60,6 +60,7 @@ mandatory-y += topology.h mandatory-y += trace_clock.h mandatory-y += uaccess.h mandatory-y += unwind_user.h +mandatory-y += unwind_user_sframe.h mandatory-y += vermagic.h mandatory-y += vga.h mandatory-y += video.h diff --git a/include/asm-generic/unwind_user_sframe.h b/include/asm-generic/unwind_user_sframe.h new file mode 100644 index 000000000000..8c9ac47bc8bd --- /dev/null +++ b/include/asm-generic/unwind_user_sframe.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_GENERIC_UNWIND_USER_SFRAME_H +#define _ASM_GENERIC_UNWIND_USER_SFRAME_H + +#include + +#ifndef SFRAME_SP_OFFSET +/* Most archs/ABIs define CFA as SP at call site, so that SP = CFA + 0. */ +#define SFRAME_SP_OFFSET 0 +#endif + +#endif /* _ASM_GENERIC_UNWIND_USER_SFRAME_H */ diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h index 616cc5ee4586..4656aa08a7db 100644 --- a/include/linux/unwind_user_types.h +++ b/include/linux/unwind_user_types.h @@ -29,6 +29,7 @@ struct unwind_stacktrace { struct unwind_user_frame { s32 cfa_off; + s32 sp_off; s32 ra_off; s32 fp_off; bool use_fp; diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c index 6465e7a315bc..7952b041dd23 100644 --- a/kernel/unwind/sframe.c +++ b/kernel/unwind/sframe.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "sframe.h" @@ -307,6 +308,7 @@ static __always_inline int __find_fre(struct sframe_section *sec, fre = prev_fre; frame->cfa_off = fre->cfa_off; + frame->sp_off = SFRAME_SP_OFFSET; frame->ra_off = fre->ra_off; frame->fp_off = fre->fp_off; frame->use_fp = SFRAME_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP; diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index fdb1001e3750..6c75a7411871 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -30,7 +30,7 @@ get_user_word(unsigned long *word, unsigned long base, int off, unsigned int ws) static int unwind_user_next_common(struct unwind_user_state *state, const struct unwind_user_frame *frame) { - unsigned long cfa, fp, ra; + unsigned long cfa, sp, fp, ra; /* Stop unwinding when reaching an outermost frame. */ if (frame->outermost) { @@ -48,12 +48,13 @@ static int unwind_user_next_common(struct unwind_user_state *state, } cfa += frame->cfa_off; + /* Get the Stack Pointer (SP) */ + sp = cfa + frame->sp_off; /* Make sure that stack is not going in wrong direction */ - if (cfa <= state->sp) + if (sp <= state->sp) return -EINVAL; - /* Make sure that the address is word aligned */ - if (cfa & (state->ws - 1)) + if (sp & (state->ws - 1)) return -EINVAL; /* Get the Return Address (RA) */ @@ -65,7 +66,7 @@ static int unwind_user_next_common(struct unwind_user_state *state, return -EINVAL; state->ip = ra; - state->sp = cfa; + state->sp = sp; if (frame->fp_off) state->fp = fp; state->topmost = false; -- 2.51.0 Not all architectures have the return address (RA) in user space saved on the stack on function entry, such as x86-64 does due to its CALL instruction pushing the RA onto the stack. Architectures/ABIs, such as s390, also do not necessarily enforce to save the RA in user space on the stack in the function prologue or even at all, for instance in leaf functions. Treat a RA offset from CFA of zero as indication that the RA is not saved (on the stack). For the topmost frame treat it as indication that the RA is in the link/RA register, such as on arm64 and s390, and obtain it from there. For non-topmost frames treat it as error, as the RA must be saved. Additionally allow the SP to be unchanged in the topmost frame, for architectures where SP at function entry == SP at call site, such as arm64 and s390. Note that treating a RA offset from CFA of zero as indication that the RA is not saved on the stack additionally allows for architectures, such as s390, where the frame pointer (FP) may be saved without the RA being saved as well. Provided that such architectures represent this in SFrame by encoding the "missing" RA offset using a padding RA offset with a value of zero. Signed-off-by: Jens Remus --- Notes (jremus): Changes in v2: - Reword commit subject and message. - Rename config option USER_RA_REG to UNWIND_USER_RA_REG and reword help text to mention both link and return address register. (Josh) - Move dummy user_return_address() from linux/ptrace.h to linux/unwind_user.h, rename to unwind_user_get_ra_reg(), return -EINVAL, and guard by !CONFIG_HAVE_UNWIND_USER_RA_REG. (Josh) - Do not check for !IS_ENABLED(CONFIG_HAVE_USER_RA_REG), as the dummy implementation of user_return_address() returns -EINVAL. - Drop config option USER_RA_REG / UNWIND_USER_RA_REG, as it is of no value any longer. - Drop topmost checks from unwind user sframe, as they are already done by unwind user. (Josh) include/linux/unwind_user.h | 9 +++++++++ kernel/unwind/sframe.c | 6 ++---- kernel/unwind/user.c | 17 +++++++++++++---- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/include/linux/unwind_user.h b/include/linux/unwind_user.h index 64618618febd..bc2edae39955 100644 --- a/include/linux/unwind_user.h +++ b/include/linux/unwind_user.h @@ -23,6 +23,15 @@ static inline bool unwind_user_at_function_start(struct pt_regs *regs) #define unwind_user_at_function_start unwind_user_at_function_start #endif +#ifndef unwind_user_get_ra_reg +static inline int unwind_user_get_ra_reg(unsigned long *val) +{ + WARN_ON_ONCE(1); + return -EINVAL; +} +#define unwind_user_get_ra_reg unwind_user_get_ra_reg +#endif + int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries); #endif /* _LINUX_UNWIND_USER_H */ diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c index 7952b041dd23..38b3577f5253 100644 --- a/kernel/unwind/sframe.c +++ b/kernel/unwind/sframe.c @@ -228,10 +228,8 @@ static __always_inline int __read_fre(struct sframe_section *sec, offset_count--; ra_off = sec->ra_off; - if (!ra_off) { - if (!offset_count--) - return -EFAULT; - + if (!ra_off && offset_count) { + offset_count--; UNSAFE_GET_USER_INC(ra_off, cur, offset_size, Efault); } diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index 6c75a7411871..58e1549cd9f4 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -50,16 +50,25 @@ static int unwind_user_next_common(struct unwind_user_state *state, /* Get the Stack Pointer (SP) */ sp = cfa + frame->sp_off; - /* Make sure that stack is not going in wrong direction */ - if (sp <= state->sp) + /* + * Make sure that stack is not going in wrong direction. Allow SP + * to be unchanged for the topmost frame, by subtracting topmost, + * which is either 0 or 1. + */ + if (sp <= state->sp - state->topmost) return -EINVAL; /* Make sure that the address is word aligned */ if (sp & (state->ws - 1)) return -EINVAL; /* Get the Return Address (RA) */ - if (get_user_word(&ra, cfa, frame->ra_off, state->ws)) - return -EINVAL; + if (frame->ra_off) { + if (get_user_word(&ra, cfa, frame->ra_off, state->ws)) + return -EINVAL; + } else { + if (!state->topmost || unwind_user_get_ra_reg(&ra)) + return -EINVAL; + } /* Get the Frame Pointer (FP) */ if (frame->fp_off && get_user_word(&fp, cfa, frame->fp_off, state->ws)) -- 2.51.0 Enable unwinding of user space for architectures, such as s390, that save the return address (RA) and/or frame pointer (FP) in other registers. This is only valid in the topmost frame, for instance when in a leaf function. Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v3: - Rename UNWIND_USER_LOC_NONE to UNWIND_USER_LOC_RETAIN to better disambiguate from new UNWIND_USER_LOC_UNKNOWN to be introduced for for back chain unwinding on s390. Other naming options: IDENTITY, KEEP, PRESERVE, SAME, UNCHANGED. Changes in RFC v2: - Reword HAVE_UNWIND_USER_LOC_REG help text. - Rename struct unwind_user_reginfo field frame_off to offset. (Josh) - Move dummy unwind_user_get_reg() from asm-generic/unwind_user.h to linux/unwind_user.h, drop its function comment, warn once, return -EINVAL, and guard by !HAVE_UNWIND_USER_LOC_REG. (Josh) - Rename generic_sframe_set_frame_reginfo() to sframe_init_reginfo() and drop its function comment. (Josh) - Do not check FP/RA offset for zero for UNWIND_USER_LOC_STACK. (Josh) - Do not check for !IS_ENABLED(CONFIG_HAVE_UNWIND_USER_LOC_REG), as the dummy implementation of unwind_user_get_reg() returns -EINVAL. - Drop config option HAVE_UNWIND_USER_LOC_REG, as it is no longer of any value. - Keep checking for topmost for UNWIND_USER_LOC_REG. (Jens) - Explicitly preserve FP if UNWIND_USER_LOC_NONE and drop later test for frame->fp.loc != UNWIND_USER_LOC_NONE. (Josh) Would it make sense to rename UNWIND_USER_LOC_NONE to one of the following to clarify its meaning for the unwinder? - UNWIND_USER_LOC_UNCHANGED - UNWIND_USER_LOC_RETAIN - UNWIND_USER_LOC_PRESERVED - UNWIND_USER_LOC_IDENTITY arch/x86/include/asm/unwind_user.h | 21 +++++++++++--- include/asm-generic/unwind_user_sframe.h | 15 ++++++++++ include/linux/unwind_user.h | 9 ++++++ include/linux/unwind_user_types.h | 18 ++++++++++-- kernel/unwind/sframe.c | 4 +-- kernel/unwind/user.c | 37 +++++++++++++++++++----- 6 files changed, 89 insertions(+), 15 deletions(-) diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index d70ffd7bbdb7..2480d86a405e 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -22,16 +22,27 @@ static inline int unwind_user_word_size(struct pt_regs *regs) #define ARCH_INIT_USER_FP_FRAME(ws) \ .cfa_off = 2*(ws), \ .sp_off = 0, \ - .ra_off = -1*(ws), \ - .fp_off = -2*(ws), \ + .ra = { \ + .loc = UNWIND_USER_LOC_STACK,\ + .offset = -1*(ws), \ + }, \ + .fp = { \ + .loc = UNWIND_USER_LOC_STACK,\ + .offset = -2*(ws), \ + }, \ .use_fp = true, \ .outermost = false, #define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) \ .cfa_off = 1*(ws), \ .sp_off = 0, \ - .ra_off = -1*(ws), \ - .fp_off = 0, \ + .ra = { \ + .loc = UNWIND_USER_LOC_STACK,\ + .offset = -1*(ws), \ + }, \ + .fp = { \ + .loc = UNWIND_USER_LOC_RETAIN,\ + }, \ .use_fp = false, \ .outermost = false, @@ -43,4 +54,6 @@ static inline bool unwind_user_at_function_start(struct pt_regs *regs) #endif /* CONFIG_HAVE_UNWIND_USER_FP */ +#include + #endif /* _ASM_X86_UNWIND_USER_H */ diff --git a/include/asm-generic/unwind_user_sframe.h b/include/asm-generic/unwind_user_sframe.h index 8c9ac47bc8bd..fd71d6b1916b 100644 --- a/include/asm-generic/unwind_user_sframe.h +++ b/include/asm-generic/unwind_user_sframe.h @@ -2,6 +2,7 @@ #ifndef _ASM_GENERIC_UNWIND_USER_SFRAME_H #define _ASM_GENERIC_UNWIND_USER_SFRAME_H +#include #include #ifndef SFRAME_SP_OFFSET @@ -9,4 +10,18 @@ #define SFRAME_SP_OFFSET 0 #endif +#ifndef sframe_init_reginfo +static inline void +sframe_init_reginfo(struct unwind_user_reginfo *reginfo, s32 offset) +{ + if (offset) { + reginfo->loc = UNWIND_USER_LOC_STACK; + reginfo->offset = offset; + } else { + reginfo->loc = UNWIND_USER_LOC_RETAIN; + } +} +#define sframe_init_reginfo sframe_init_reginfo +#endif + #endif /* _ASM_GENERIC_UNWIND_USER_SFRAME_H */ diff --git a/include/linux/unwind_user.h b/include/linux/unwind_user.h index bc2edae39955..61fd5c05d0f0 100644 --- a/include/linux/unwind_user.h +++ b/include/linux/unwind_user.h @@ -32,6 +32,15 @@ static inline int unwind_user_get_ra_reg(unsigned long *val) #define unwind_user_get_ra_reg unwind_user_get_ra_reg #endif +#ifndef unwind_user_get_reg +static inline int unwind_user_get_reg(unsigned long *val, int regnum) +{ + WARN_ON_ONCE(1); + return -EINVAL; +} +#define unwind_user_get_reg unwind_user_get_reg +#endif + int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries); #endif /* _LINUX_UNWIND_USER_H */ diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h index 4656aa08a7db..4f78999a0750 100644 --- a/include/linux/unwind_user_types.h +++ b/include/linux/unwind_user_types.h @@ -27,11 +27,25 @@ struct unwind_stacktrace { unsigned long *entries; }; +enum unwind_user_loc { + UNWIND_USER_LOC_RETAIN, + UNWIND_USER_LOC_STACK, + UNWIND_USER_LOC_REG, +}; + +struct unwind_user_reginfo { + enum unwind_user_loc loc; + union { + s32 offset; + int regnum; + }; +}; + struct unwind_user_frame { s32 cfa_off; s32 sp_off; - s32 ra_off; - s32 fp_off; + struct unwind_user_reginfo ra; + struct unwind_user_reginfo fp; bool use_fp; bool outermost; }; diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c index 38b3577f5253..45cd7380ac38 100644 --- a/kernel/unwind/sframe.c +++ b/kernel/unwind/sframe.c @@ -307,8 +307,8 @@ static __always_inline int __find_fre(struct sframe_section *sec, frame->cfa_off = fre->cfa_off; frame->sp_off = SFRAME_SP_OFFSET; - frame->ra_off = fre->ra_off; - frame->fp_off = fre->fp_off; + sframe_init_reginfo(&frame->ra, fre->ra_off); + sframe_init_reginfo(&frame->fp, fre->fp_off); frame->use_fp = SFRAME_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP; frame->outermost = fre->ra_undefined; diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index 58e1549cd9f4..45f82ed28fcb 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -62,22 +62,45 @@ static int unwind_user_next_common(struct unwind_user_state *state, return -EINVAL; /* Get the Return Address (RA) */ - if (frame->ra_off) { - if (get_user_word(&ra, cfa, frame->ra_off, state->ws)) - return -EINVAL; - } else { + switch (frame->ra.loc) { + case UNWIND_USER_LOC_RETAIN: if (!state->topmost || unwind_user_get_ra_reg(&ra)) return -EINVAL; + break; + case UNWIND_USER_LOC_STACK: + if (get_user_word(&ra, cfa, frame->ra.offset, state->ws)) + return -EINVAL; + break; + case UNWIND_USER_LOC_REG: + if (!state->topmost || unwind_user_get_reg(&ra, frame->ra.regnum)) + return -EINVAL; + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; } /* Get the Frame Pointer (FP) */ - if (frame->fp_off && get_user_word(&fp, cfa, frame->fp_off, state->ws)) + switch (frame->fp.loc) { + case UNWIND_USER_LOC_RETAIN: + fp = state->fp; + break; + case UNWIND_USER_LOC_STACK: + if (get_user_word(&fp, cfa, frame->fp.offset, state->ws)) + return -EINVAL; + break; + case UNWIND_USER_LOC_REG: + if (!state->topmost || unwind_user_get_reg(&fp, frame->fp.regnum)) + return -EINVAL; + break; + default: + WARN_ON_ONCE(1); return -EINVAL; + } state->ip = ra; state->sp = sp; - if (frame->fp_off) - state->fp = fp; + state->fp = fp; state->topmost = false; return 0; } -- 2.51.0 Enable architectures, such as s390, which store SFrame CFA offset values encoded, to e.g. make (better) use of unsigned 8-bit SFrame offsets. Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v2: - Rename generic_sframe_cfa_offset_decode() to sframe_cfa_offset_decode(). (Josh) include/asm-generic/unwind_user_sframe.h | 8 ++++++++ kernel/unwind/sframe.c | 1 + 2 files changed, 9 insertions(+) diff --git a/include/asm-generic/unwind_user_sframe.h b/include/asm-generic/unwind_user_sframe.h index fd71d6b1916b..ec68a77551f0 100644 --- a/include/asm-generic/unwind_user_sframe.h +++ b/include/asm-generic/unwind_user_sframe.h @@ -10,6 +10,14 @@ #define SFRAME_SP_OFFSET 0 #endif +#ifndef sframe_cfa_offset_decode +static inline s32 sframe_cfa_offset_decode(s32 offset) +{ + return offset; +} +#define sframe_cfa_offset_decode sframe_cfa_offset_decode +#endif + #ifndef sframe_init_reginfo static inline void sframe_init_reginfo(struct unwind_user_reginfo *reginfo, s32 offset) diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c index 45cd7380ac38..92f770fc21f6 100644 --- a/kernel/unwind/sframe.c +++ b/kernel/unwind/sframe.c @@ -226,6 +226,7 @@ static __always_inline int __read_fre(struct sframe_section *sec, UNSAFE_GET_USER_INC(cfa_off, cur, offset_size, Efault); offset_count--; + cfa_off = sframe_cfa_offset_decode(cfa_off); ra_off = sec->ra_off; if (!ra_off && offset_count) { -- 2.51.0 On s390 64-bit the s390x ELF ABI [1] designates register 11 as the "preferred" frame pointer (FP) register in user space. While at it convert instruction_pointer() and user_stack_pointer() from macros to inline functions, to align their definition with x86 and arm64. Use const qualifier on struct pt_regs pointers to prevent compiler warnings: arch/s390/kernel/stacktrace.c: In function ‘arch_stack_walk_user_common’: arch/s390/kernel/stacktrace.c:114:34: warning: passing argument 1 of ‘instruction_pointer’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] ... arch/s390/kernel/stacktrace.c:117:48: warning: passing argument 1 of ‘user_stack_pointer’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] ... [1]: s390x ELF ABI, https://github.com/IBM/s390x-abi/releases Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v2: - Separate provide frame_pointer() into this new commit. arch/s390/include/asm/ptrace.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/arch/s390/include/asm/ptrace.h b/arch/s390/include/asm/ptrace.h index dfa770b15fad..455c119167fc 100644 --- a/arch/s390/include/asm/ptrace.h +++ b/arch/s390/include/asm/ptrace.h @@ -212,8 +212,6 @@ void update_cr_regs(struct task_struct *task); #define arch_has_block_step() (1) #define user_mode(regs) (((regs)->psw.mask & PSW_MASK_PSTATE) != 0) -#define instruction_pointer(regs) ((regs)->psw.addr) -#define user_stack_pointer(regs)((regs)->gprs[15]) #define profile_pc(regs) instruction_pointer(regs) static inline long regs_return_value(struct pt_regs *regs) @@ -235,6 +233,22 @@ static __always_inline unsigned long kernel_stack_pointer(struct pt_regs *regs) return regs->gprs[15]; } +static __always_inline unsigned long instruction_pointer(const struct pt_regs *regs) +{ + return regs->psw.addr; +} + +static __always_inline unsigned long frame_pointer(const struct pt_regs *regs) +{ + /* Return ABI-designated "preferred" frame-pointer register value. */ + return regs->gprs[11]; +} + +static __always_inline unsigned long user_stack_pointer(const struct pt_regs *regs) +{ + return regs->gprs[15]; +} + static __always_inline unsigned long regs_get_register(struct pt_regs *regs, unsigned int offset) { if (offset >= NUM_GPRS) -- 2.51.0 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 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 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 +#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 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 + +#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 +#include + +#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 + +#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 #include #include -#include #include #include "sframe.h" #include "sframe_debug.h" +#include 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 Add support for an unwind user method to specify that the FP/RA location is unknown. For the frame pointer (FP) set the FP value to zero, so that subsequent unwind next frame that rely on FP fail. For the return address (RA) treat as error. This enables to implement support for unwinding of user space using back chain on s390 with a subsequent commit, which can only unwind SP and RA, but not FP. Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v3: - New patch. Prerequirement to implement unwind user fp using back chain on s390. include/linux/unwind_user_types.h | 1 + kernel/unwind/user.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h index 4f78999a0750..f44035b98f7c 100644 --- a/include/linux/unwind_user_types.h +++ b/include/linux/unwind_user_types.h @@ -28,6 +28,7 @@ struct unwind_stacktrace { }; enum unwind_user_loc { + UNWIND_USER_LOC_UNKNOWN, UNWIND_USER_LOC_RETAIN, UNWIND_USER_LOC_STACK, UNWIND_USER_LOC_REG, diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index 45f82ed28fcb..7d06bdbc7f0d 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -93,6 +93,10 @@ static int unwind_user_next_common(struct unwind_user_state *state, if (!state->topmost || unwind_user_get_reg(&fp, frame->fp.regnum)) return -EINVAL; break; + case UNWIND_USER_LOC_UNKNOWN: + /* FP cannot be unwound. Not an error. Set to zero. */ + fp = 0; + break; default: WARN_ON_ONCE(1); return -EINVAL; -- 2.51.0 This enables more sophisticated initialization of the FP frame, for instance to implement support for unwinding of user space using back chain on s390 with a subsequent commit. Signed-off-by: Jens Remus --- Notes (jremus): Changes in RFC v3: - New patch. Prerequirement to implement unwind user fp using back chain on s390. arch/x86/include/asm/unwind_user.h | 20 +++++++++++++++++--- include/linux/unwind_user.h | 20 +++++++------------- kernel/unwind/user.c | 16 ++++------------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index 2480d86a405e..ca581edecb9d 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -46,11 +46,25 @@ static inline int unwind_user_word_size(struct pt_regs *regs) .use_fp = false, \ .outermost = false, -static inline bool unwind_user_at_function_start(struct pt_regs *regs) +static inline int unwind_user_fp_get_frame(struct unwind_user_state *state, + struct unwind_user_frame *frame) { - return is_uprobe_at_func_entry(regs); + struct pt_regs *regs = task_pt_regs(current); + + if (state->topmost && is_uprobe_at_func_entry(regs)) { + const struct unwind_user_frame fp_entry_frame = { + ARCH_INIT_USER_FP_ENTRY_FRAME(state->ws) + }; + *frame = fp_entry_frame; + } else { + const struct unwind_user_frame fp_frame = { + ARCH_INIT_USER_FP_FRAME(state->ws) + }; + *frame = fp_frame; + } + return 0; } -#define unwind_user_at_function_start unwind_user_at_function_start +#define unwind_user_fp_get_frame unwind_user_fp_get_frame #endif /* CONFIG_HAVE_UNWIND_USER_FP */ diff --git a/include/linux/unwind_user.h b/include/linux/unwind_user.h index 61fd5c05d0f0..4adab1a612a6 100644 --- a/include/linux/unwind_user.h +++ b/include/linux/unwind_user.h @@ -7,21 +7,15 @@ #ifndef CONFIG_HAVE_UNWIND_USER_FP -#define ARCH_INIT_USER_FP_FRAME(ws) - -#endif - -#ifndef ARCH_INIT_USER_FP_ENTRY_FRAME -#define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) -#endif - -#ifndef unwind_user_at_function_start -static inline bool unwind_user_at_function_start(struct pt_regs *regs) +static inline int unwind_user_fp_get_frame(struct unwind_user_state *state, + struct unwind_user_frame *frame) { - return false; + WARN_ON_ONCE(1); + return -EINVAL; } -#define unwind_user_at_function_start unwind_user_at_function_start -#endif +#define unwind_user_fp_get_frame unwind_user_fp_get_frame + +#endif /* CONFIG_HAVE_UNWIND_USER_FP */ #ifndef unwind_user_get_ra_reg static inline int unwind_user_get_ra_reg(unsigned long *val) diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index 7d06bdbc7f0d..6877242ceae3 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -111,19 +111,11 @@ static int unwind_user_next_common(struct unwind_user_state *state, static int unwind_user_next_fp(struct unwind_user_state *state) { - struct pt_regs *regs = task_pt_regs(current); - - if (state->topmost && unwind_user_at_function_start(regs)) { - const struct unwind_user_frame fp_entry_frame = { - ARCH_INIT_USER_FP_ENTRY_FRAME(state->ws) - }; - return unwind_user_next_common(state, &fp_entry_frame); - } + struct unwind_user_frame frame; - const struct unwind_user_frame fp_frame = { - ARCH_INIT_USER_FP_FRAME(state->ws) - }; - return unwind_user_next_common(state, &fp_frame); + if (unwind_user_fp_get_frame(state, &frame)) + return -ENOENT; + return unwind_user_next_common(state, &frame); } static int unwind_user_next_sframe(struct unwind_user_state *state) -- 2.51.0 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 --- 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 +#include #include +#include #include +#include +#include #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 #endif /* _ASM_S390_UNWIND_USER_H */ -- 2.51.0