[RFC PATCH v3 11/17] unwind_user: Enable archs that save RA/FP in other registers
From: Jens Remus <hidden>
Date: 2025-12-08 17:16:39
Also in:
bpf, linux-s390, lkml
Subsystem:
generic include/asm header files, the rest, userspace stack unwinding, x86 architecture (32-bit and 64-bit), x86 stack unwinding · Maintainers:
Arnd Bergmann, Linus Torvalds, Josh Poimboeuf, Steven Rostedt, Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, Peter Zijlstra
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 <redacted>
---
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 <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..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 <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_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