[RFC PATCH v1 05/25] unwind_user: Flexible CFA recovery rules
From: Jens Remus <hidden>
Date: 2026-08-18 14:50:27
Also in:
linux-s390, lkml
Subsystem:
the rest, userspace stack unwinding, x86 architecture (32-bit and 64-bit), x86 stack unwinding · Maintainers:
Linus Torvalds, Josh Poimboeuf, Steven Rostedt, Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, Peter Zijlstra
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 <redacted>
Signed-off-by: Jens Remus <redacted>
---
Notes (jremus):
Cherry-picked from "[PATCH v16 00/20] unwind_deferred: Implement sframe
handling" series:
https://lore.kernel.org/all/20260521142546.3908498-16-jremus@linux.ibm.com/ (local)
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