Skip to content
This repository was archived by the owner on Oct 31, 2024. It is now read-only.

Commit adf290f

Browse files
anakryikogregkh
authored andcommitted
perf,x86: avoid missing caller address in stack traces captured in uprobe
[ Upstream commit cfa7f3d ] When tracing user functions with uprobe functionality, it's common to install the probe (e.g., a BPF program) at the first instruction of the function. This is often going to be `push %rbp` instruction in function preamble, which means that within that function frame pointer hasn't been established yet. This leads to consistently missing an actual caller of the traced function, because perf_callchain_user() only records current IP (capturing traced function) and then following frame pointer chain (which would be caller's frame, containing the address of caller's caller). So when we have target_1 -> target_2 -> target_3 call chain and we are tracing an entry to target_3, captured stack trace will report target_1 -> target_3 call chain, which is wrong and confusing. This patch proposes a x86-64-specific heuristic to detect `push %rbp` (`push %ebp` on 32-bit architecture) instruction being traced. Given entire kernel implementation of user space stack trace capturing works under assumption that user space code was compiled with frame pointer register (%rbp/%ebp) preservation, it seems pretty reasonable to use this instruction as a strong indicator that this is the entry to the function. In that case, return address is still pointed to by %rsp/%esp, so we fetch it and add to stack trace before proceeding to unwind the rest using frame pointer-based logic. We also check for `endbr64` (for 64-bit modes) as another common pattern for function entry, as suggested by Josh Poimboeuf. Even if we get this wrong sometimes for uprobes attached not at the function entry, it's OK because stack trace will still be overall meaningful, just with one extra bogus entry. If we don't detect this, we end up with guaranteed to be missing caller function entry in the stack trace, which is worse overall. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://lkml.kernel.org/r/20240729175223.23914-1-andrii@kernel.org Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent 4ee08b4 commit adf290f

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

arch/x86/events/core.c

+63
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
#include <asm/desc.h>
4242
#include <asm/ldt.h>
4343
#include <asm/unwind.h>
44+
#include <asm/uprobes.h>
45+
#include <asm/ibt.h>
4446

4547
#include "perf_event.h"
4648

@@ -2816,6 +2818,46 @@ static unsigned long get_segment_base(unsigned int segment)
28162818
return get_desc_base(desc);
28172819
}
28182820

2821+
#ifdef CONFIG_UPROBES
2822+
/*
2823+
* Heuristic-based check if uprobe is installed at the function entry.
2824+
*
2825+
* Under assumption of user code being compiled with frame pointers,
2826+
* `push %rbp/%ebp` is a good indicator that we indeed are.
2827+
*
2828+
* Similarly, `endbr64` (assuming 64-bit mode) is also a common pattern.
2829+
* If we get this wrong, captured stack trace might have one extra bogus
2830+
* entry, but the rest of stack trace will still be meaningful.
2831+
*/
2832+
static bool is_uprobe_at_func_entry(struct pt_regs *regs)
2833+
{
2834+
struct arch_uprobe *auprobe;
2835+
2836+
if (!current->utask)
2837+
return false;
2838+
2839+
auprobe = current->utask->auprobe;
2840+
if (!auprobe)
2841+
return false;
2842+
2843+
/* push %rbp/%ebp */
2844+
if (auprobe->insn[0] == 0x55)
2845+
return true;
2846+
2847+
/* endbr64 (64-bit only) */
2848+
if (user_64bit_mode(regs) && is_endbr(*(u32 *)auprobe->insn))
2849+
return true;
2850+
2851+
return false;
2852+
}
2853+
2854+
#else
2855+
static bool is_uprobe_at_func_entry(struct pt_regs *regs)
2856+
{
2857+
return false;
2858+
}
2859+
#endif /* CONFIG_UPROBES */
2860+
28192861
#ifdef CONFIG_IA32_EMULATION
28202862

28212863
#include <linux/compat.h>
@@ -2827,6 +2869,7 @@ perf_callchain_user32(struct pt_regs *regs, struct perf_callchain_entry_ctx *ent
28272869
unsigned long ss_base, cs_base;
28282870
struct stack_frame_ia32 frame;
28292871
const struct stack_frame_ia32 __user *fp;
2872+
u32 ret_addr;
28302873

28312874
if (user_64bit_mode(regs))
28322875
return 0;
@@ -2836,6 +2879,12 @@ perf_callchain_user32(struct pt_regs *regs, struct perf_callchain_entry_ctx *ent
28362879

28372880
fp = compat_ptr(ss_base + regs->bp);
28382881
pagefault_disable();
2882+
2883+
/* see perf_callchain_user() below for why we do this */
2884+
if (is_uprobe_at_func_entry(regs) &&
2885+
!get_user(ret_addr, (const u32 __user *)regs->sp))
2886+
perf_callchain_store(entry, ret_addr);
2887+
28392888
while (entry->nr < entry->max_stack) {
28402889
if (!valid_user_frame(fp, sizeof(frame)))
28412890
break;
@@ -2864,6 +2913,7 @@ perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs
28642913
{
28652914
struct stack_frame frame;
28662915
const struct stack_frame __user *fp;
2916+
unsigned long ret_addr;
28672917

28682918
if (perf_guest_state()) {
28692919
/* TODO: We don't support guest os callchain now */
@@ -2887,6 +2937,19 @@ perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs
28872937
return;
28882938

28892939
pagefault_disable();
2940+
2941+
/*
2942+
* If we are called from uprobe handler, and we are indeed at the very
2943+
* entry to user function (which is normally a `push %rbp` instruction,
2944+
* under assumption of application being compiled with frame pointers),
2945+
* we should read return address from *regs->sp before proceeding
2946+
* to follow frame pointers, otherwise we'll skip immediate caller
2947+
* as %rbp is not yet setup.
2948+
*/
2949+
if (is_uprobe_at_func_entry(regs) &&
2950+
!get_user(ret_addr, (const unsigned long __user *)regs->sp))
2951+
perf_callchain_store(entry, ret_addr);
2952+
28902953
while (entry->nr < entry->max_stack) {
28912954
if (!valid_user_frame(fp, sizeof(frame)))
28922955
break;

include/linux/uprobes.h

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ struct uprobe_task {
7676
struct uprobe *active_uprobe;
7777
unsigned long xol_vaddr;
7878

79+
struct arch_uprobe *auprobe;
80+
7981
struct return_instance *return_instances;
8082
unsigned int depth;
8183
};

kernel/events/uprobes.c

+2
Original file line numberDiff line numberDiff line change
@@ -2071,6 +2071,7 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
20712071
bool need_prep = false; /* prepare return uprobe, when needed */
20722072

20732073
down_read(&uprobe->register_rwsem);
2074+
current->utask->auprobe = &uprobe->arch;
20742075
for (uc = uprobe->consumers; uc; uc = uc->next) {
20752076
int rc = 0;
20762077

@@ -2085,6 +2086,7 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
20852086

20862087
remove &= rc;
20872088
}
2089+
current->utask->auprobe = NULL;
20882090

20892091
if (need_prep && !remove)
20902092
prepare_uretprobe(uprobe, regs); /* put bp at return */

0 commit comments

Comments
 (0)