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 <jremus@xxxxxxxxxxxxx> --- arch/Kconfig | 7 ++++ arch/x86/include/asm/unwind_user.h | 24 +++++++++--- include/asm-generic/unwind_user.h | 20 ++++++++++ include/asm-generic/unwind_user_sframe.h | 24 ++++++++++++ include/linux/unwind_user_types.h | 18 ++++++++- kernel/unwind/sframe.c | 4 +- kernel/unwind/user.c | 47 ++++++++++++++++++++---- 7 files changed, 126 insertions(+), 18 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index 367eaf7e62e0..9e28dffe42cb 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -455,6 +455,13 @@ config HAVE_USER_RA_REG help The arch passes the return address (RA) in user space in a register. +config HAVE_UNWIND_USER_LOC_REG + bool + help + The arch potentially saves the return address (RA) and/or frame + pointer (FP) register values in user space in other registers, when + in the topmost frame (e.g. in leaf function). + config SFRAME_VALIDATION bool "Enable .sframe section debugging" depends on HAVE_UNWIND_USER_SFRAME diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index c2881840adf4..925d208aa39d 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -5,18 +5,30 @@ #include <linux/unwind_user_types.h> #define ARCH_INIT_USER_FP_FRAME \ - .cfa_off = (s32)sizeof(long) * 2, \ - .ra_off = (s32)sizeof(long) * -1, \ - .fp_off = (s32)sizeof(long) * -2, \ + .cfa_off = (s32)sizeof(long) * 2, \ + .ra = { \ + .loc = UNWIND_USER_LOC_STACK, \ + .frame_off = (s32)sizeof(long) * -1, \ + }, \ + .fp = { \ + .loc = UNWIND_USER_LOC_STACK, \ + .frame_off = (s32)sizeof(long) * -2, \ + }, \ .sp_val_off = (s32)0, \ .use_fp = true, #ifdef CONFIG_IA32_EMULATION #define ARCH_INIT_USER_COMPAT_FP_FRAME \ - .cfa_off = (s32)sizeof(u32) * 2, \ - .ra_off = (s32)sizeof(u32) * -1, \ - .fp_off = (s32)sizeof(u32) * -2, \ + .cfa_off = (s32)sizeof(u32) * 2, \ + .ra = { \ + .loc = UNWIND_USER_LOC_STACK, \ + .frame_off = (s32)sizeof(u32) * -1, \ + }, \ + .fp = { \ + .loc = UNWIND_USER_LOC_STACK, \ + .frame_off = (s32)sizeof(u32) * -2, \ + }, \ .sp_val_off = (s32)0, \ .use_fp = true, diff --git a/include/asm-generic/unwind_user.h b/include/asm-generic/unwind_user.h index b8882b909944..3891b7cfe3b8 100644 --- a/include/asm-generic/unwind_user.h +++ b/include/asm-generic/unwind_user.h @@ -2,4 +2,24 @@ #ifndef _ASM_GENERIC_UNWIND_USER_H #define _ASM_GENERIC_UNWIND_USER_H +#include <asm/unwind_user_types.h> + +#ifndef unwind_user_get_reg + +/** + * generic_unwind_user_get_reg - Get register value. + * @val: Register value. + * @regnum: DWARF register number to obtain the value from. + * + * Returns zero if successful. Otherwise -EINVAL. + */ +static inline int generic_unwind_user_get_reg(unsigned long *val, int regnum) +{ + return -EINVAL; +} + +#define unwind_user_get_reg generic_unwind_user_get_reg + +#endif /* !unwind_user_get_reg */ + #endif /* _ASM_GENERIC_UNWIND_USER_H */ diff --git a/include/asm-generic/unwind_user_sframe.h b/include/asm-generic/unwind_user_sframe.h index 6c87a7f29861..8cef3e0857b6 100644 --- a/include/asm-generic/unwind_user_sframe.h +++ b/include/asm-generic/unwind_user_sframe.h @@ -2,8 +2,31 @@ #ifndef _ASM_GENERIC_UNWIND_USER_SFRAME_H #define _ASM_GENERIC_UNWIND_USER_SFRAME_H +#include <linux/unwind_user_types.h> #include <linux/types.h> +/** + * generic_sframe_set_frame_reginfo - Populate info to unwind FP/RA register + * from SFrame offset. + * @reginfo: Unwind info for FP/RA register. + * @offset: SFrame offset value. + * + * A non-zero offset value denotes a stack offset from CFA and indicates + * that the register is saved on the stack. A zero offset value indicates + * that the register is not saved. + */ +static inline void generic_sframe_set_frame_reginfo( + struct unwind_user_reginfo *reginfo, + s32 offset) +{ + if (offset) { + reginfo->loc = UNWIND_USER_LOC_STACK; + reginfo->frame_off = offset; + } else { + reginfo->loc = UNWIND_USER_LOC_NONE; + } +} + /** * generic_sframe_sp_val_off - Get generic SP value offset from CFA. * @@ -25,6 +48,7 @@ static inline s32 generic_sframe_sp_val_off(void) return 0; } +#define sframe_set_frame_reginfo generic_sframe_set_frame_reginfo #define sframe_sp_val_off generic_sframe_sp_val_off #endif /* _ASM_GENERIC_UNWIND_USER_SFRAME_H */ diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h index adef01698bb3..57fd16e314cf 100644 --- a/include/linux/unwind_user_types.h +++ b/include/linux/unwind_user_types.h @@ -21,10 +21,24 @@ struct unwind_stacktrace { unsigned long *entries; }; +enum unwind_user_loc { + UNWIND_USER_LOC_NONE, + UNWIND_USER_LOC_STACK, + UNWIND_USER_LOC_REG, +}; + +struct unwind_user_reginfo { + enum unwind_user_loc loc; + union { + s32 frame_off; + int regnum; + }; +}; + struct unwind_user_frame { s32 cfa_off; - s32 ra_off; - s32 fp_off; + struct unwind_user_reginfo ra; + struct unwind_user_reginfo fp; s32 sp_val_off; bool use_fp; }; diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c index 5bfaf06e6cd2..43ef3a8c4c26 100644 --- a/kernel/unwind/sframe.c +++ b/kernel/unwind/sframe.c @@ -314,8 +314,8 @@ static __always_inline int __find_fre(struct sframe_section *sec, } frame->cfa_off = fre->cfa_off; - frame->ra_off = fre->ra_off; - frame->fp_off = fre->fp_off; + sframe_set_frame_reginfo(&frame->ra, fre->ra_off); + sframe_set_frame_reginfo(&frame->fp, fre->fp_off); frame->use_fp = SFRAME_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP; frame->sp_val_off = sframe_sp_val_off(); diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index 03a6da36192f..ee00d39d2a8e 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -98,26 +98,57 @@ static int unwind_user_next(struct unwind_user_state *state) /* Get the Return Address (RA) */ - if (frame->ra_off) { + switch (frame->ra.loc) { + case UNWIND_USER_LOC_NONE: + if (!IS_ENABLED(CONFIG_HAVE_USER_RA_REG) || !topmost) + goto done; + ra = user_return_address(task_pt_regs(current)); + break; + case UNWIND_USER_LOC_STACK: + if (!frame->ra.frame_off) + goto done; /* Make sure that the address is word aligned */ shift = sizeof(long) == 4 || compat_fp_state(state) ? 2 : 3; - if ((cfa + frame->ra_off) & ((1 << shift) - 1)) + if ((cfa + frame->ra.frame_off) & ((1 << shift) - 1)) goto done; - if (unwind_get_user_long(ra, cfa + frame->ra_off, state)) + if (unwind_get_user_long(ra, cfa + frame->ra.frame_off, state)) goto done; - } else { - if (!IS_ENABLED(CONFIG_HAVE_USER_RA_REG) || !topmost) + break; + case UNWIND_USER_LOC_REG: + if (!IS_ENABLED(CONFIG_HAVE_UNWIND_USER_LOC_REG) || !topmost) goto done; - ra = user_return_address(task_pt_regs(current)); + if (unwind_user_get_reg(&ra, frame->ra.regnum)) + goto done; + break; + default: + WARN_ON_ONCE(1); + goto done; } /* Get the Frame Pointer (FP) */ - if (frame->fp_off && unwind_get_user_long(fp, cfa + frame->fp_off, state)) + switch (frame->fp.loc) { + case UNWIND_USER_LOC_NONE: + break; + case UNWIND_USER_LOC_STACK: + if (!frame->fp.frame_off) + goto done; + if (unwind_get_user_long(fp, cfa + frame->fp.frame_off, state)) + goto done; + break; + case UNWIND_USER_LOC_REG: + if (!IS_ENABLED(CONFIG_HAVE_UNWIND_USER_LOC_REG) || !topmost) + goto done; + if (unwind_user_get_reg(&fp, frame->fp.regnum)) + goto done; + break; + default: + WARN_ON_ONCE(1); goto done; + } state->ip = ra; state->sp = sp; - if (frame->fp_off) + if (frame->fp.loc != UNWIND_USER_LOC_NONE) state->fp = fp; arch_unwind_user_next(state); -- 2.48.1