Commit ae25884a authored by Peter Zijlstra's avatar Peter Zijlstra
Browse files

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: default avatarJens Remus <jremus@linux.ibm.com>
Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251024145156.GM4068168@noisy.programming.kicks-ass.net
parent 49cf34c0
Loading
Loading
Loading
Loading
+0 −40
Original line number Diff line number Diff line
@@ -2845,46 +2845,6 @@ static unsigned long get_segment_base(unsigned int segment)
	return get_desc_base(desc);
}

#ifdef CONFIG_UPROBES
/*
 * Heuristic-based check if uprobe is installed at the function entry.
 *
 * Under assumption of user code being compiled with frame pointers,
 * `push %rbp/%ebp` is a good indicator that we indeed are.
 *
 * Similarly, `endbr64` (assuming 64-bit mode) is also a common pattern.
 * If we get this wrong, captured stack trace might have one extra bogus
 * entry, but the rest of stack trace will still be meaningful.
 */
static bool is_uprobe_at_func_entry(struct pt_regs *regs)
{
	struct arch_uprobe *auprobe;

	if (!current->utask)
		return false;

	auprobe = current->utask->auprobe;
	if (!auprobe)
		return false;

	/* push %rbp/%ebp */
	if (auprobe->insn[0] == 0x55)
		return true;

	/* endbr64 (64-bit only) */
	if (user_64bit_mode(regs) && is_endbr((u32 *)auprobe->insn))
		return true;

	return false;
}

#else
static bool is_uprobe_at_func_entry(struct pt_regs *regs)
{
	return false;
}
#endif /* CONFIG_UPROBES */

#ifdef CONFIG_IA32_EMULATION

#include <linux/compat.h>
+12 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
#define _ASM_X86_UNWIND_USER_H

#include <asm/ptrace.h>
#include <asm/uprobes.h>

#define ARCH_INIT_USER_FP_FRAME(ws)			\
	.cfa_off	=  2*(ws),			\
@@ -10,6 +11,12 @@
	.fp_off		= -2*(ws),			\
	.use_fp		= true,

#define ARCH_INIT_USER_FP_ENTRY_FRAME(ws)		\
	.cfa_off	=  1*(ws),			\
	.ra_off		= -1*(ws),			\
	.fp_off		= 0,				\
	.use_fp		= false,

static inline int unwind_user_word_size(struct pt_regs *regs)
{
	/* We can't unwind VM86 stacks */
@@ -22,4 +29,9 @@ static inline int unwind_user_word_size(struct pt_regs *regs)
	return sizeof(long);
}

static inline bool unwind_user_at_function_start(struct pt_regs *regs)
{
	return is_uprobe_at_func_entry(regs);
}

#endif /* _ASM_X86_UNWIND_USER_H */
+9 −0
Original line number Diff line number Diff line
@@ -62,4 +62,13 @@ struct arch_uprobe_task {
	unsigned int			saved_tf;
};

#ifdef CONFIG_UPROBES
extern bool is_uprobe_at_func_entry(struct pt_regs *regs);
#else
static bool is_uprobe_at_func_entry(struct pt_regs *regs)
{
	return false;
}
#endif /* CONFIG_UPROBES */

#endif	/* _ASM_UPROBES_H */
+32 −0
Original line number Diff line number Diff line
@@ -1791,3 +1791,35 @@ bool arch_uretprobe_is_alive(struct return_instance *ret, enum rp_check ctx,
	else
		return regs->sp <= ret->stack;
}

/*
 * Heuristic-based check if uprobe is installed at the function entry.
 *
 * Under assumption of user code being compiled with frame pointers,
 * `push %rbp/%ebp` is a good indicator that we indeed are.
 *
 * Similarly, `endbr64` (assuming 64-bit mode) is also a common pattern.
 * If we get this wrong, captured stack trace might have one extra bogus
 * entry, but the rest of stack trace will still be meaningful.
 */
bool is_uprobe_at_func_entry(struct pt_regs *regs)
{
	struct arch_uprobe *auprobe;

	if (!current->utask)
		return false;

	auprobe = current->utask->auprobe;
	if (!auprobe)
		return false;

	/* push %rbp/%ebp */
	if (auprobe->insn[0] == 0x55)
		return true;

	/* endbr64 (64-bit only) */
	if (user_64bit_mode(regs) && is_endbr((u32 *)auprobe->insn))
		return true;

	return false;
}
+1 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ struct unwind_user_state {
	unsigned int				ws;
	enum unwind_user_type			current_type;
	unsigned int				available_types;
	bool					topmost;
	bool					done;
};

Loading