From nobody Sun Feb 8 19:44:03 2026 Received: from galois.linutronix.de (Galois.linutronix.de [193.142.43.55]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C138415748D; Mon, 5 Aug 2024 11:56:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=193.142.43.55 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1722858971; cv=none; b=i0OIBAajtbJj/xGhSwMWhTBqV5dErBOjrzUSg/VSpCkqU5MTRj3NmISV5i5LLM2vpw8+ZmLDV9Xyjpd/T1dtjkJ2rDuQAp5QYK5VniL+G7o8hdIH8vJzzMCpvzRMFVaDMNu30XO8pzo3T64IAbYKUStmVE6Uk8BR7enAsFSm2Ak= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1722858971; c=relaxed/simple; bh=aNDukDa3+8oEvHD0DT5vOYw+yUstVKmNrpaqyIgISd8=; h=Date:From:To:Subject:Cc:In-Reply-To:References:MIME-Version: Message-ID:Content-Type; b=b0K1aHwdNVK8uROklaY2xblAZMzfuqSWpczwoFsVeYKkNXm81lT1kND2hWx8wECBumdVsYSFc3puTdjIBxXBMz5ax/3pjIcdMOA4OyiQBTbOpt1g0KarkpMZ7WRWFdeMAvzBESX+VBsZ3j1inV6o5QovfNi0xCyLQotm6Aklmn0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de; spf=pass smtp.mailfrom=linutronix.de; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=GbwO1jgA; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=EGfjB/tU; arc=none smtp.client-ip=193.142.43.55 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linutronix.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="GbwO1jgA"; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="EGfjB/tU" Date: Mon, 05 Aug 2024 11:56:06 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1722858966; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=cLEJKX7M6M0Kf1dvPMdmS8pEgJYNJvLk0KWZSuhjeWs=; b=GbwO1jgAv4ywkrmg6Nr0iN1j6+pqBlP4nR8ZA8LC7FlIXGGwNYZFX1r+y/Se995/DRgBOH Znm5i+Jsz1Yolwaufl5YI128m6XJ9b/dAptP1PrvWElP9cQKlsdtsOEw5fBT/wHzTxhXw7 ZqsyQQIBtcWsGhwbP7a5gZH7lIuSemZXI2YD8CRfKwZv6pEehhhFAUwsq5YnPXFAzOMGd8 By4cX9hFqrMP35Gnx6Qsi39QwrhN4Fs6R4DDmrILM18SiWBVMr6L3mCUPv4b353Md+XfW4 y3/vqsmnyrRNvn0AaRR1JtAXY97InjX5XIPIvkTENVnp0gcfI26D2hZWfc8mRA== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1722858966; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=cLEJKX7M6M0Kf1dvPMdmS8pEgJYNJvLk0KWZSuhjeWs=; b=EGfjB/tUTBfVgV0QRTFfTxRZ8h3oggruoC0/+VyCkFRPEtv19VYsLr0L1JjwG3yQbDUAA8 8EvZuqD+9r1TEEDA== From: "tip-bot2 for Andrii Nakryiko" Sender: tip-bot2@linutronix.de Reply-to: linux-kernel@vger.kernel.org To: linux-tip-commits@vger.kernel.org Subject: [tip: perf/core] perf,x86: avoid missing caller address in stack traces captured in uprobe Cc: Andrii Nakryiko , "Peter Zijlstra (Intel)" , x86@kernel.org, linux-kernel@vger.kernel.org In-Reply-To: <20240729175223.23914-1-andrii@kernel.org> References: <20240729175223.23914-1-andrii@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-ID: <172285896642.2215.15269623510741010803.tip-bot2@tip-bot2> Robot-ID: Robot-Unsubscribe: Contact to get blacklisted from these emails Precedence: bulk Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The following commit has been merged into the perf/core branch of tip: Commit-ID: cfa7f3d2c526c224a6271cc78a4a27a0de06f4f0 Gitweb: https://git.kernel.org/tip/cfa7f3d2c526c224a6271cc78a4a27a0d= e06f4f0 Author: Andrii Nakryiko AuthorDate: Mon, 29 Jul 2024 10:52:23 -07:00 Committer: Peter Zijlstra CommitterDate: Fri, 02 Aug 2024 11:30:30 +02:00 perf,x86: avoid missing caller address in stack traces captured in uprobe 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 Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20240729175223.23914-1-andrii@kernel.org --- arch/x86/events/core.c | 63 ++++++++++++++++++++++++++++++++++++++++- include/linux/uprobes.h | 2 +- kernel/events/uprobes.c | 2 +- 3 files changed, 67 insertions(+) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 12f2a0c..e6d133d 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -41,6 +41,8 @@ #include #include #include +#include +#include =20 #include "perf_event.h" =20 @@ -2814,6 +2816,46 @@ static unsigned long get_segment_base(unsigned int s= egment) return get_desc_base(desc); } =20 +#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 =3D current->utask->auprobe; + if (!auprobe) + return false; + + /* push %rbp/%ebp */ + if (auprobe->insn[0] =3D=3D 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 =20 #include @@ -2825,6 +2867,7 @@ perf_callchain_user32(struct pt_regs *regs, struct pe= rf_callchain_entry_ctx *ent unsigned long ss_base, cs_base; struct stack_frame_ia32 frame; const struct stack_frame_ia32 __user *fp; + u32 ret_addr; =20 if (user_64bit_mode(regs)) return 0; @@ -2834,6 +2877,12 @@ perf_callchain_user32(struct pt_regs *regs, struct p= erf_callchain_entry_ctx *ent =20 fp =3D compat_ptr(ss_base + regs->bp); pagefault_disable(); + + /* see perf_callchain_user() below for why we do this */ + if (is_uprobe_at_func_entry(regs) && + !get_user(ret_addr, (const u32 __user *)regs->sp)) + perf_callchain_store(entry, ret_addr); + while (entry->nr < entry->max_stack) { if (!valid_user_frame(fp, sizeof(frame))) break; @@ -2862,6 +2911,7 @@ perf_callchain_user(struct perf_callchain_entry_ctx *= entry, struct pt_regs *regs { struct stack_frame frame; const struct stack_frame __user *fp; + unsigned long ret_addr; =20 if (perf_guest_state()) { /* TODO: We don't support guest os callchain now */ @@ -2885,6 +2935,19 @@ perf_callchain_user(struct perf_callchain_entry_ctx = *entry, struct pt_regs *regs return; =20 pagefault_disable(); + + /* + * If we are called from uprobe handler, and we are indeed at the very + * entry to user function (which is normally a `push %rbp` instruction, + * under assumption of application being compiled with frame pointers), + * we should read return address from *regs->sp before proceeding + * to follow frame pointers, otherwise we'll skip immediate caller + * as %rbp is not yet setup. + */ + if (is_uprobe_at_func_entry(regs) && + !get_user(ret_addr, (const unsigned long __user *)regs->sp)) + perf_callchain_store(entry, ret_addr); + while (entry->nr < entry->max_stack) { if (!valid_user_frame(fp, sizeof(frame))) break; diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index b503faf..a270a58 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -76,6 +76,8 @@ struct uprobe_task { struct uprobe *active_uprobe; unsigned long xol_vaddr; =20 + struct arch_uprobe *auprobe; + struct return_instance *return_instances; unsigned int depth; }; diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 73cc477..f69ecd3 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -2082,6 +2082,7 @@ static void handler_chain(struct uprobe *uprobe, stru= ct pt_regs *regs) bool need_prep =3D false; /* prepare return uprobe, when needed */ =20 down_read(&uprobe->register_rwsem); + current->utask->auprobe =3D &uprobe->arch; for (uc =3D uprobe->consumers; uc; uc =3D uc->next) { int rc =3D 0; =20 @@ -2096,6 +2097,7 @@ static void handler_chain(struct uprobe *uprobe, stru= ct pt_regs *regs) =20 remove &=3D rc; } + current->utask->auprobe =3D NULL; =20 if (need_prep && !remove) prepare_uretprobe(uprobe, regs); /* put bp at return */