mirror of https://github.com/torvalds/linux.git
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
This commit is contained in:
parent
49cf34c081
commit
ae25884ad7
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue