summaryrefslogtreecommitdiff
path: root/kernel/unwind
diff options
context:
space:
mode:
authorPeter Zijlstra <peterz@infradead.org>2025-10-24 12:31:10 +0200
committerPeter Zijlstra <peterz@infradead.org>2025-10-29 10:29:58 +0100
commitae25884ad749e7f6e0c3565513bdc8aa2554a425 (patch)
tree373450384ff724ce72856374d5464dd3dc7abe22 /kernel/unwind
parent49cf34c0815f93fb2ea3ab5cfbac1124bd9b45d0 (diff)
downloadlinux-next-ae25884ad749e7f6e0c3565513bdc8aa2554a425.tar.gz
linux-next-ae25884ad749e7f6e0c3565513bdc8aa2554a425.zip
unwind_user/x86: Teach FP unwind about start of function
When userspace is interrupted at the start of a function, before we get a chance to complete the frame, unwind will miss one caller. X86 has a uprobe specific fixup for this, add bits to the generic unwinder to support this. Suggested-by: Jens Remus <jremus@linux.ibm.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://patch.msgid.link/20251024145156.GM4068168@noisy.programming.kicks-ass.net
Diffstat (limited to 'kernel/unwind')
-rw-r--r--kernel/unwind/user.c39
1 files changed, 30 insertions, 9 deletions
diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
index 642871527a13..39e270789444 100644
--- a/kernel/unwind/user.c
+++ b/kernel/unwind/user.c
@@ -26,14 +26,12 @@ get_user_word(unsigned long *word, unsigned long base, int off, unsigned int ws)
return get_user(*word, addr);
}
-static int unwind_user_next_fp(struct unwind_user_state *state)
+static int unwind_user_next_common(struct unwind_user_state *state,
+ const struct unwind_user_frame *frame)
{
- const struct unwind_user_frame frame = {
- ARCH_INIT_USER_FP_FRAME(state->ws)
- };
unsigned long cfa, fp, ra;
- if (frame.use_fp) {
+ if (frame->use_fp) {
if (state->fp < state->sp)
return -EINVAL;
cfa = state->fp;
@@ -42,7 +40,7 @@ static int unwind_user_next_fp(struct unwind_user_state *state)
}
/* Get the Canonical Frame Address (CFA) */
- cfa += frame.cfa_off;
+ cfa += frame->cfa_off;
/* stack going in wrong direction? */
if (cfa <= state->sp)
@@ -53,19 +51,41 @@ static int unwind_user_next_fp(struct unwind_user_state *state)
return -EINVAL;
/* Find the Return Address (RA) */
- if (get_user_word(&ra, cfa, frame.ra_off, state->ws))
+ if (get_user_word(&ra, cfa, frame->ra_off, state->ws))
return -EINVAL;
- if (frame.fp_off && get_user_word(&fp, cfa, frame.fp_off, state->ws))
+ if (frame->fp_off && get_user_word(&fp, cfa, frame->fp_off, state->ws))
return -EINVAL;
state->ip = ra;
state->sp = cfa;
- if (frame.fp_off)
+ if (frame->fp_off)
state->fp = fp;
+ state->topmost = false;
return 0;
}
+static int unwind_user_next_fp(struct unwind_user_state *state)
+{
+#ifdef CONFIG_HAVE_UNWIND_USER_FP
+ struct pt_regs *regs = task_pt_regs(current);
+
+ if (state->topmost && unwind_user_at_function_start(regs)) {
+ const struct unwind_user_frame fp_entry_frame = {
+ ARCH_INIT_USER_FP_ENTRY_FRAME(state->ws)
+ };
+ return unwind_user_next_common(state, &fp_entry_frame);
+ }
+
+ const struct unwind_user_frame fp_frame = {
+ ARCH_INIT_USER_FP_FRAME(state->ws)
+ };
+ return unwind_user_next_common(state, &fp_frame);
+#else
+ return -EINVAL;
+#endif
+}
+
static int unwind_user_next(struct unwind_user_state *state)
{
unsigned long iter_mask = state->available_types;
@@ -118,6 +138,7 @@ static int unwind_user_start(struct unwind_user_state *state)
state->done = true;
return -EINVAL;
}
+ state->topmost = true;
return 0;
}