To enable support for SFrame V3 flexible FDEs with a subsequent patch, add support for the following flexible Canonical Frame Address (CFA) recovery rules:
CFA = SP + offset CFA = *(SP + offset) CFA = FP + offset CFA = *(FP + offset) CFA = register + offset CFA = *(register + offset) Note that CFA recovery rules that use arbitrary register contents are only valid when in the topmost frame, as their contents are otherwise unknown. Reviewed-by: Indu Bhagat <[email protected]> Signed-off-by: Jens Remus <[email protected]> --- Notes (jremus): Cherry-picked from "[PATCH v16 00/20] unwind_deferred: Implement sframe handling" series: https://lore.kernel.org/all/[email protected]/ arch/x86/include/asm/unwind_user.h | 12 ++++++++---- include/linux/unwind_user_types.h | 22 ++++++++++++++++++++-- kernel/unwind/user.c | 24 ++++++++++++++++++++---- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index 9c3417be4283..f38f7c5ff1de 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -20,7 +20,10 @@ static inline int unwind_user_word_size(struct pt_regs *regs) #ifdef CONFIG_HAVE_UNWIND_USER_FP #define ARCH_INIT_USER_FP_FRAME(ws) \ - .cfa_off = 2*(ws), \ + .cfa = { \ + .rule = UNWIND_USER_CFA_RULE_FP_OFFSET,\ + .offset = 2*(ws), \ + }, \ .ra = { \ .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\ .offset = -1*(ws), \ @@ -29,11 +32,13 @@ static inline int unwind_user_word_size(struct pt_regs *regs) .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\ .offset = -2*(ws), \ }, \ - .use_fp = true, \ .outermost = false, #define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) \ - .cfa_off = 1*(ws), \ + .cfa = { \ + .rule = UNWIND_USER_CFA_RULE_SP_OFFSET,\ + .offset = 1*(ws), \ + }, \ .ra = { \ .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\ .offset = -1*(ws), \ @@ -41,7 +46,6 @@ static inline int unwind_user_word_size(struct pt_regs *regs) .fp = { \ .rule = UNWIND_USER_RULE_RETAIN,\ }, \ - .use_fp = false, \ .outermost = false, static inline bool unwind_user_at_function_start(struct pt_regs *regs) diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h index 1b5db76e886a..670ac860ae76 100644 --- a/include/linux/unwind_user_types.h +++ b/include/linux/unwind_user_types.h @@ -27,6 +27,25 @@ struct unwind_stacktrace { #define UNWIND_USER_RULE_DEREF BIT(31) +enum unwind_user_cfa_rule { + UNWIND_USER_CFA_RULE_SP_OFFSET, /* CFA = SP + offset */ + UNWIND_USER_CFA_RULE_FP_OFFSET, /* CFA = FP + offset */ + UNWIND_USER_CFA_RULE_REG_OFFSET, /* CFA = register + offset */ + /* DEREF variants */ + UNWIND_USER_CFA_RULE_SP_OFFSET_DEREF = /* CFA = *(SP + offset) */ + UNWIND_USER_CFA_RULE_SP_OFFSET | UNWIND_USER_RULE_DEREF, + UNWIND_USER_CFA_RULE_FP_OFFSET_DEREF = /* CFA = *(FP + offset) */ + UNWIND_USER_CFA_RULE_FP_OFFSET | UNWIND_USER_RULE_DEREF, + UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF = /* CFA = *(register + offset) */ + UNWIND_USER_CFA_RULE_REG_OFFSET | UNWIND_USER_RULE_DEREF, +}; + +struct unwind_user_cfa_rule_data { + enum unwind_user_cfa_rule rule; + s32 offset; + unsigned int regnum; +}; + enum unwind_user_rule { UNWIND_USER_RULE_RETAIN, /* entity = entity */ UNWIND_USER_RULE_CFA_OFFSET, /* entity = CFA + offset */ @@ -45,10 +64,9 @@ struct unwind_user_rule_data { }; struct unwind_user_frame { - s32 cfa_off; + struct unwind_user_cfa_rule_data cfa; struct unwind_user_rule_data ra; struct unwind_user_rule_data fp; - bool use_fp; bool outermost; }; diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index f5d68e86d72b..9df5040df9b3 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -52,14 +52,30 @@ static int unwind_user_next_common(struct unwind_user_state *state, } /* Get the Canonical Frame Address (CFA) */ - if (frame->use_fp) { + switch (frame->cfa.rule) { + case UNWIND_USER_CFA_RULE_SP_OFFSET: + case UNWIND_USER_CFA_RULE_SP_OFFSET_DEREF: + cfa = state->sp; + break; + case UNWIND_USER_CFA_RULE_FP_OFFSET: + case UNWIND_USER_CFA_RULE_FP_OFFSET_DEREF: if (state->fp < state->sp) return -EINVAL; cfa = state->fp; - } else { - cfa = state->sp; + break; + case UNWIND_USER_CFA_RULE_REG_OFFSET: + case UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF: + if (!state->topmost || unwind_user_get_reg(&cfa, frame->cfa.regnum)) + return -EINVAL; + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; } - cfa += frame->cfa_off; + cfa += frame->cfa.offset; + if (frame->cfa.rule & UNWIND_USER_RULE_DEREF && + get_user_word(&cfa, cfa, 0, state->ws)) + return -EINVAL; /* * Make sure that stack is not going in wrong direction. Allow SP -- 2.53.0
