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 <[email protected]> --- Notes (jremus): 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 dbdbad0beaf9..61a9ae9b07ea 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -26,16 +26,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_NONE, \ + }, \ .use_fp = false, \ .outermost = false, @@ -47,4 +58,6 @@ static inline bool unwind_user_at_function_start(struct pt_regs *regs) #endif /* CONFIG_HAVE_UNWIND_USER_FP */ +#include <asm-generic/unwind_user.h> + #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..163961ca5252 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 <linux/unwind_user_types.h> #include <linux/types.h> #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_NONE; + } +} +#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..6efc12b6e831 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_NONE, + 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..122045cb411f 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_NONE: 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_NONE: + 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
