From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 862F636F426; Sun, 2 Aug 2026 08:18:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658725; cv=none; b=jGtr5qaM4x9d2wBlQ2ec3oBRi3swCVTBIRGi5bOg1Ifb8gcoofLdF1hQGf0m2UJdbfbB/ActNyF2hS/Y58hfp5l99FavRVTFw5wvicw4Ob/Pi+7pYzw8/YvEUi/XjbKkmXSB0kk7ot+WkV6csKseaaJKUR5VkNQrfxmTNweOJMQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658725; c=relaxed/simple; bh=/CWafS+K6eB1m+Pvkmx+7fz1Yk7FD0UsrD+xkftt5sk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=etCUflZ7nYA0AnEiJXZ4G0iEobcE0ErTHtCLPHaBQIQTEnXRDDYFftyJRbM+5GQToD1AnRkopqZTA5HSZ1HTBgU6Ou+I9kRJWgsxx1pK9fpiEPRxrLxfJ/O5wQNohY198ZnhUxN82+NQ0spPNf9+JtCWxNTz6RrkPVs/fk7ZId8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=SJPdEZDu; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="SJPdEZDu" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A25D21F00AC4; Sun, 2 Aug 2026 08:18:39 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658723; bh=uIHhTXoUrUyGkqLMEuvwwMOZZdqckdZTqxA0AW3Sgdw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=SJPdEZDumyAWBojKIF6OYZnfI7XE9HBzQyXTxHamJiZFyUQ/a1E9YhzwtaiHvHQd8 ZN+H2Df0an4PjXwPREOhpqrxDnD4Pljv6L4cehO/3OJZqumSUya7rYwbOmJ+2IHubB fPeJG8EHHBlE0lCrI7b37x8VOn4gOTJqHKXzNfsSRFOE79LGKmSWx0Qm9HW8J2c9Oc MBcGEKnH8SOdqw5Ns5LXVyqpUi0Xh/JBaHn05A62aYP6EtM8u6ANt8fFXMrDUCDO4A wmxT1GWCYcqZnYFN5kaxf0ubhJsvp5hG37pEya09sqOOAKJvCDIaiPGLFUj5XMZZsr InQAsGHni/mCA== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 01/11] x86/hw_breakpoints: Make DR7 updates NMI safe Date: Sun, 2 Aug 2026 17:18:37 +0900 Message-ID: <178565871750.714490.5506415865016304088.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Jinchao Wang Hardware breakpoint installation and removal run with IRQs disabled, but an NMI can still enter the same code through KGDB. The interrupted operation and the NMI can consequently claim the same slot or overwrite each other's DR7 state. Claim and release per-CPU slots with cmpxchg. Update cpu_dr7 with single-instruction per-CPU operations, and preserve hardware-first disable and hardware-last enable ordering. Add a per-CPU sequence number so interrupted DR7 writers and restore paths detect an NMI update and retry from the latest shadow state. Link: https://lore.kernel.org/all/4ee0a2efc9e8387af83286b8495b7d490247e165.= 1785067572.git.wangjinchao600@gmail.com/ Signed-off-by: Jinchao Wang Signed-off-by: Masami Hiramatsu (Google) --- arch/x86/include/asm/debugreg.h | 36 ++++++++++---- arch/x86/kernel/cpu/mce/core.c | 20 +++++--- arch/x86/kernel/hw_breakpoint.c | 98 +++++++++++++++++++----------------= ---- arch/x86/kernel/nmi.c | 10 +++- arch/x86/kernel/traps.c | 10 +++- 5 files changed, 101 insertions(+), 73 deletions(-) diff --git a/arch/x86/include/asm/debugreg.h b/arch/x86/include/asm/debugre= g.h index a2c1f2d24b64..b1fe1c47978d 100644 --- a/arch/x86/include/asm/debugreg.h +++ b/arch/x86/include/asm/debugreg.h @@ -18,6 +18,7 @@ #define DR7_FIXED_1 0x00000400 =20 DECLARE_PER_CPU(unsigned long, cpu_dr7); +DECLARE_PER_CPU(unsigned int, cpu_dr7_seq); =20 #ifndef CONFIG_PARAVIRT_XXL /* @@ -125,18 +126,20 @@ static __always_inline bool hw_breakpoint_active(void) =20 extern void hw_breakpoint_restore(void); =20 -static __always_inline unsigned long local_db_save(void) +static __always_inline void local_db_save(unsigned long *dr7, + unsigned int *dr7_seq) { - unsigned long dr7; + *dr7 =3D 0; + *dr7_seq =3D this_cpu_read(cpu_dr7_seq); =20 if (static_cpu_has(X86_FEATURE_HYPERVISOR) && !hw_breakpoint_active()) - return 0; + return; =20 - get_debugreg(dr7, 7); + get_debugreg(*dr7, 7); =20 /* Architecturally set bit */ - dr7 &=3D ~DR7_FIXED_1; - if (dr7) + *dr7 &=3D ~DR7_FIXED_1; + if (*dr7) set_debugreg(DR7_FIXED_1, 7); =20 /* @@ -145,20 +148,33 @@ static __always_inline unsigned long local_db_save(vo= id) * be good. */ barrier(); - - return dr7; } =20 -static __always_inline void local_db_restore(unsigned long dr7) +static __always_inline void local_db_restore(unsigned long dr7, + unsigned int dr7_seq) { + unsigned int seq; + /* * Ensure the compiler doesn't raise this statement into * the critical section; enabling breakpoints early would * not be good. */ barrier(); - if (dr7) + + do { + seq =3D this_cpu_read(cpu_dr7_seq); + if (seq =3D=3D dr7_seq) { + if (!dr7) + return; + } else { + dr7 =3D this_cpu_read(cpu_dr7); + if (!dr7) + dr7 =3D DR7_FIXED_1; + } + set_debugreg(dr7, 7); + } while (unlikely(seq !=3D this_cpu_read(cpu_dr7_seq))); } =20 #ifdef CONFIG_CPU_SUP_AMD diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index 9bba1e2f03af..8dba9cd04bfa 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -2139,20 +2139,22 @@ static __always_inline void exc_machine_check_user(= struct pt_regs *regs) DEFINE_IDTENTRY_MCE(exc_machine_check) { unsigned long dr7; + unsigned int dr7_seq; =20 - dr7 =3D local_db_save(); + local_db_save(&dr7, &dr7_seq); exc_machine_check_kernel(regs); - local_db_restore(dr7); + local_db_restore(dr7, dr7_seq); } =20 /* The user mode variant. */ DEFINE_IDTENTRY_MCE_USER(exc_machine_check) { unsigned long dr7; + unsigned int dr7_seq; =20 - dr7 =3D local_db_save(); + local_db_save(&dr7, &dr7_seq); exc_machine_check_user(regs); - local_db_restore(dr7); + local_db_restore(dr7, dr7_seq); } =20 #ifdef CONFIG_X86_FRED @@ -2170,13 +2172,14 @@ DEFINE_IDTENTRY_MCE_USER(exc_machine_check) DEFINE_FREDENTRY_MCE(exc_machine_check) { unsigned long dr7; + unsigned int dr7_seq; =20 - dr7 =3D local_db_save(); + local_db_save(&dr7, &dr7_seq); if (user_mode(regs)) exc_machine_check_user(regs); else exc_machine_check_kernel(regs); - local_db_restore(dr7); + local_db_restore(dr7, dr7_seq); } #endif #else @@ -2184,13 +2187,14 @@ DEFINE_FREDENTRY_MCE(exc_machine_check) DEFINE_IDTENTRY_RAW(exc_machine_check) { unsigned long dr7; + unsigned int dr7_seq; =20 - dr7 =3D local_db_save(); + local_db_save(&dr7, &dr7_seq); if (user_mode(regs)) exc_machine_check_user(regs); else exc_machine_check_kernel(regs); - local_db_restore(dr7); + local_db_restore(dr7, dr7_seq); } #endif =20 diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoin= t.c index f846c15f21ca..9ef24b55737f 100644 --- a/arch/x86/kernel/hw_breakpoint.c +++ b/arch/x86/kernel/hw_breakpoint.c @@ -40,6 +40,9 @@ DEFINE_PER_CPU(unsigned long, cpu_dr7); EXPORT_PER_CPU_SYMBOL(cpu_dr7); =20 +/* Sequence number of the per-CPU DR7 state. */ +DEFINE_PER_CPU(unsigned int, cpu_dr7_seq); + /* Per cpu debug address registers values */ static DEFINE_PER_CPU(unsigned long, cpu_debugreg[HBP_NUM]); =20 @@ -97,38 +100,30 @@ int decode_dr7(unsigned long dr7, int bpnum, unsigned = *len, unsigned *type) int arch_install_hw_breakpoint(struct perf_event *bp) { struct arch_hw_breakpoint *info =3D counter_arch_bp(bp); - unsigned long *dr7; + unsigned int seq; int i; =20 lockdep_assert_irqs_disabled(); =20 for (i =3D 0; i < HBP_NUM; i++) { - struct perf_event **slot =3D this_cpu_ptr(&bp_per_reg[i]); - - if (!*slot) { - *slot =3D bp; + if (!this_cpu_cmpxchg(bp_per_reg[i], NULL, bp)) break; - } } =20 if (WARN_ONCE(i =3D=3D HBP_NUM, "Can't find any breakpoint slot")) return -EBUSY; =20 - set_debugreg(info->address, i); - __this_cpu_write(cpu_debugreg[i], info->address); - - dr7 =3D this_cpu_ptr(&cpu_dr7); - *dr7 |=3D encode_dr7(i, info->len, info->type); - - /* - * Ensure we first write cpu_dr7 before we set the DR7 register. - * This ensures an NMI never see cpu_dr7 0 when DR7 is not. - */ - barrier(); - - set_debugreg(*dr7, 7); - if (info->mask) - amd_set_dr_addr_mask(info->mask, i); + do { + seq =3D this_cpu_inc_return(cpu_dr7_seq); + this_cpu_write(cpu_debugreg[i], info->address); + barrier(); + set_debugreg(info->address, i); + if (info->mask) + amd_set_dr_addr_mask(info->mask, i); + this_cpu_or(cpu_dr7, encode_dr7(i, info->len, info->type)); + barrier(); + set_debugreg(this_cpu_read(cpu_dr7), 7); + } while (seq !=3D this_cpu_read(cpu_dr7_seq)); =20 return 0; } @@ -146,36 +141,33 @@ void arch_uninstall_hw_breakpoint(struct perf_event *= bp) { struct arch_hw_breakpoint *info =3D counter_arch_bp(bp); unsigned long dr7; + unsigned int seq; int i; =20 lockdep_assert_irqs_disabled(); =20 for (i =3D 0; i < HBP_NUM; i++) { - struct perf_event **slot =3D this_cpu_ptr(&bp_per_reg[i]); - - if (*slot =3D=3D bp) { - *slot =3D NULL; + if (this_cpu_read(bp_per_reg[i]) =3D=3D bp) break; - } } =20 if (WARN_ONCE(i =3D=3D HBP_NUM, "Can't find any breakpoint slot")) return; =20 - dr7 =3D this_cpu_read(cpu_dr7); - dr7 &=3D ~__encode_dr7(i, info->len, info->type); - - set_debugreg(dr7, 7); - if (info->mask) - amd_set_dr_addr_mask(0, i); - - /* - * Ensure the write to cpu_dr7 is after we've set the DR7 register. - * This ensures an NMI never see cpu_dr7 0 when DR7 is not. - */ - barrier(); - - this_cpu_write(cpu_dr7, dr7); + do { + seq =3D this_cpu_inc_return(cpu_dr7_seq); + dr7 =3D this_cpu_read(cpu_dr7); + dr7 &=3D ~__encode_dr7(i, info->len, info->type); + set_debugreg(dr7, 7); + if (info->mask) + amd_set_dr_addr_mask(0, i); + barrier(); + this_cpu_and(cpu_dr7, + ~__encode_dr7(i, info->len, info->type)); + } while (seq !=3D this_cpu_read(cpu_dr7_seq)); + + WARN_ONCE(this_cpu_cmpxchg(bp_per_reg[i], bp, NULL) !=3D bp, + "Can't release breakpoint slot"); } =20 static int arch_bp_generic_len(int x86_len) @@ -309,13 +301,14 @@ static inline bool within_cpu_entry(unsigned long add= r, unsigned long end) sizeof(struct tlb_state))) return true; =20 - /* - * When in guest (X86_FEATURE_HYPERVISOR), local_db_save() - * will read per-cpu cpu_dr7 before clear dr7 register. - */ + /* local_db_save() reads this state before clearing DR7. */ if (within_area(addr, end, (unsigned long)&per_cpu(cpu_dr7, cpu), sizeof(cpu_dr7))) return true; + if (within_area(addr, end, + (unsigned long)&per_cpu(cpu_dr7_seq, cpu), + sizeof(cpu_dr7_seq))) + return true; } =20 return false; @@ -483,12 +476,17 @@ void flush_ptrace_hw_breakpoint(struct task_struct *t= sk) =20 void hw_breakpoint_restore(void) { - set_debugreg(__this_cpu_read(cpu_debugreg[0]), 0); - set_debugreg(__this_cpu_read(cpu_debugreg[1]), 1); - set_debugreg(__this_cpu_read(cpu_debugreg[2]), 2); - set_debugreg(__this_cpu_read(cpu_debugreg[3]), 3); - set_debugreg(DR6_RESERVED, 6); - set_debugreg(__this_cpu_read(cpu_dr7), 7); + unsigned int seq; + + do { + seq =3D this_cpu_inc_return(cpu_dr7_seq); + set_debugreg(this_cpu_read(cpu_debugreg[0]), 0); + set_debugreg(this_cpu_read(cpu_debugreg[1]), 1); + set_debugreg(this_cpu_read(cpu_debugreg[2]), 2); + set_debugreg(this_cpu_read(cpu_debugreg[3]), 3); + set_debugreg(DR6_RESERVED, 6); + set_debugreg(this_cpu_read(cpu_dr7), 7); + } while (seq !=3D this_cpu_read(cpu_dr7_seq)); } EXPORT_SYMBOL_FOR_KVM(hw_breakpoint_restore); =20 diff --git a/arch/x86/kernel/nmi.c b/arch/x86/kernel/nmi.c index 3c9f60d6ca5a..f55a0cbd5927 100644 --- a/arch/x86/kernel/nmi.c +++ b/arch/x86/kernel/nmi.c @@ -532,10 +532,13 @@ enum nmi_states { static DEFINE_PER_CPU(enum nmi_states, nmi_state); static DEFINE_PER_CPU(unsigned long, nmi_cr2); static DEFINE_PER_CPU(unsigned long, nmi_dr7); +static DEFINE_PER_CPU(unsigned int, nmi_dr7_seq); =20 DEFINE_IDTENTRY_RAW(exc_nmi) { irqentry_state_t irq_state; + unsigned long dr7; + unsigned int dr7_seq; struct nmi_stats *nsp =3D this_cpu_ptr(&nmi_stats); =20 /* @@ -572,7 +575,9 @@ DEFINE_IDTENTRY_RAW(exc_nmi) */ sev_es_ist_enter(regs); =20 - this_cpu_write(nmi_dr7, local_db_save()); + local_db_save(&dr7, &dr7_seq); + this_cpu_write(nmi_dr7, dr7); + this_cpu_write(nmi_dr7_seq, dr7_seq); =20 irq_state =3D irqentry_nmi_enter(regs); =20 @@ -594,7 +599,8 @@ DEFINE_IDTENTRY_RAW(exc_nmi) =20 irqentry_nmi_exit(regs, irq_state); =20 - local_db_restore(this_cpu_read(nmi_dr7)); + local_db_restore(this_cpu_read(nmi_dr7), + this_cpu_read(nmi_dr7_seq)); =20 sev_es_ist_exit(); =20 diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index 30aa8369957e..018abe736285 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -1231,8 +1231,12 @@ static noinstr void exc_debug_kernel(struct pt_regs = *regs, unsigned long dr6) * it results in an endless recursion and stack overflow. Thus we stay * with the IDT approach, i.e., save DR7 and disable #DB. */ - unsigned long dr7 =3D local_db_save(); - irqentry_state_t irq_state =3D irqentry_nmi_enter(regs); + unsigned long dr7; + unsigned int dr7_seq; + irqentry_state_t irq_state; + + local_db_save(&dr7, &dr7_seq); + irq_state =3D irqentry_nmi_enter(regs); instrumentation_begin(); =20 /* @@ -1289,7 +1293,7 @@ static noinstr void exc_debug_kernel(struct pt_regs *= regs, unsigned long dr6) instrumentation_end(); irqentry_nmi_exit(regs, irq_state); =20 - local_db_restore(dr7); + local_db_restore(dr7, dr7_seq); } =20 static noinstr void exc_debug_user(struct pt_regs *regs, unsigned long dr6) From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 807FA37F8B3; Sun, 2 Aug 2026 08:18:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658735; cv=none; b=LDRSvk+VeBtsMB8r+XPfqEyxrRZL2fJ8tYSv6/iKnyOsUb7YxqaKrmr8WA65IHAuwH2hLfm4ABpzrZKClYDV2jlpHLqA9/l8v4ZIyv2ebiUbAK70WsUZdErtFhUcHzU3F4pUpCOZ5UYWtazdTr7+ummYAvEdP00Pih/qmtXWryU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658735; c=relaxed/simple; bh=jdVNS7I1bb509M6k14U2tNrt1dP7h6wl7s1Q4QVj4YI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=mg9xfmsMwnl5f8LeqEVqIKv8xPyiaq741FfkPZWgqbKbjnbryghiYqFoNtAavxvPrItemF/ORYv7DKr/pKOJPZ1dkMWDgPAoMLRnyUXLOg6mXiKt/TP8K3OQ5SWovir+gMmZJVH/pzSp8qxC4s280hc0eXLLBDo3j149UBMzsew= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=OPX9MXGW; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="OPX9MXGW" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A416B1F00AC4; Sun, 2 Aug 2026 08:18:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658734; bh=ZzhdsFUmY1PAP7mnpYfjuHIRqOYywJnS409/fS0ZObw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=OPX9MXGWUnEh5pQuixvCIT20YCSGYG9Yx2pE9SDhKGUL2PHF//bCcAr3+UfZPhw2Y v7xu5hg+bcbD1rOofWCGlYJj76PD9Lf81YhkYE17l39NBmQZNnAibf1eVkkZbrnxmN LfiJghPtkmdlhVSdh4AOPvJ7CGuoQnrISxGFUc48QrGFRfs9BZkwNjBYZEUCkjEK4h EIkzS0pQL8p2ebbqdxn7cp5ivSvUyHVFCKuyrZ+W060Ro9EpPO2cbkrRAy79pifpgM NAEDjGyMkPgF1OIPMziTL71hkV+sFW7rxLpYzVXzugsZrmRGuR55htWzbDuP99LRp4 u/AIRN6fVsc4w== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 02/11] x86/hw_breakpoints: Add arch_modify_local_hw_breakpoint_addr() API Date: Sun, 2 Aug 2026 17:18:48 +0900 Message-ID: <178565872848.714490.5743606430062347908.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Jinchao Wang Wprobe needs to move an active per-CPU watchpoint without releasing and reserving its hardware slot. Add arch_modify_local_hw_breakpoint_addr() to find the installed local slot and update only its address shadow and hardware debug address register. Publish the shadow first so hw_breakpoint_restore() observes the new address if an NMI interrupts the update. The caller must provide an installed local event and a valid address. Slot ownership, breakpoint type, length, mask and DR7 remain unchanged. Link: https://lore.kernel.org/all/59637b96946653393a7ad3c7de094094796b39c2.= 1785067572.git.wangjinchao600@gmail.com/ Signed-off-by: Jinchao Wang Signed-off-by: Masami Hiramatsu (Google) --- Changes in v11: - Return int error code instead of void. - Validate the new address using hw_breakpoint_arch_parse() before updating registers. - Increment cpu_dr7_seq in a sequence loop for NMI protection. - Return -ENOENT if the breakpoint slot is not found on the local CPU. --- arch/x86/include/asm/hw_breakpoint.h | 2 ++ arch/x86/kernel/hw_breakpoint.c | 33 ++++++++++++++++++++++++++++++= +++ 2 files changed, 35 insertions(+) diff --git a/arch/x86/include/asm/hw_breakpoint.h b/arch/x86/include/asm/hw= _breakpoint.h index 0bc931cd0698..f35ec491f6dd 100644 --- a/arch/x86/include/asm/hw_breakpoint.h +++ b/arch/x86/include/asm/hw_breakpoint.h @@ -59,6 +59,8 @@ extern int hw_breakpoint_exceptions_notify(struct notifie= r_block *unused, =20 =20 int arch_install_hw_breakpoint(struct perf_event *bp); +int arch_modify_local_hw_breakpoint_addr(struct perf_event *bp, + unsigned long addr); void arch_uninstall_hw_breakpoint(struct perf_event *bp); void hw_breakpoint_pmu_read(struct perf_event *bp); void hw_breakpoint_pmu_unthrottle(struct perf_event *bp); diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoin= t.c index 9ef24b55737f..c89f6e71c3a9 100644 --- a/arch/x86/kernel/hw_breakpoint.c +++ b/arch/x86/kernel/hw_breakpoint.c @@ -128,6 +128,39 @@ int arch_install_hw_breakpoint(struct perf_event *bp) return 0; } =20 +int arch_modify_local_hw_breakpoint_addr(struct perf_event *bp, + unsigned long addr) +{ + struct arch_hw_breakpoint hw; + struct perf_event_attr attr =3D bp->attr; + unsigned int seq; + int i, ret; + + lockdep_assert_irqs_disabled(); + + attr.bp_addr =3D addr; + ret =3D hw_breakpoint_arch_parse(bp, &attr, &hw); + if (ret) + return ret; + + for (i =3D 0; i < HBP_NUM; i++) { + if (this_cpu_read(bp_per_reg[i]) =3D=3D bp) + break; + } + + if (WARN_ONCE(i =3D=3D HBP_NUM, "Can't find any breakpoint slot")) + return -ENOENT; + + do { + seq =3D this_cpu_inc_return(cpu_dr7_seq); + this_cpu_write(cpu_debugreg[i], addr); + barrier(); + set_debugreg(addr, i); + } while (seq !=3D this_cpu_read(cpu_dr7_seq)); + + return 0; +} + /* * Uninstall the breakpoint contained in the given counter. * From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 8A8982F616B; Sun, 2 Aug 2026 08:19:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658746; cv=none; b=g0GxVP3c2FRm5npKT7ntMovapOLEAKNvYE9Se8UxUs9gZWGL3myJjMuoxCCq9jHWPtEihAXOdIidkf5zIEh2G00/bjlH+alJG2COLm1OMmy31QBc4jxpqgmhlC+XvfmiJbCxy/3jR7LFXEmNHHwFNC9ZA98sUWPTYE/C3H/0qI0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658746; c=relaxed/simple; bh=WLSMomGHYuU3/xj6iFrupqQ0v3LAL6oPKQv+ueIKDmc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=F5wNfYGRqkxBqSO9i5Ltgn0+Etq85OvXJtKIrkJ+PC9E7MnuMAZVBteeb1CqyiwpXQKj4wpBppXzn2FBteItXgUiQ+HuwrnyL4rFB7tnUJF+XNJ4BaRGRGyS3S5NjLJ43DmxDjU6wGJjpOSWXCuiRLqyYeZUKnpnwT4Wbb0Jx88= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Ju2oTGJh; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Ju2oTGJh" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A64CB1F00AC4; Sun, 2 Aug 2026 08:19:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658745; bh=efYS360pd6ZZxxCBl/VvK2sP6q43hqiJMFfCbWEkZZk=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Ju2oTGJhdnvUu8t0fuLMOyx5lSAxE4laeLLdVIgASrcsHvwiy5l4DPMvacgtgle3L V0J0x9KHWS/yyrQvryEGLkb/WcSkLPsDv1AhbS8E4eGDHjNCrS2K4bsZIDzj9f1+dw MDycSl3LpZcivqCFOdqSDT8hFkecSoIynjozfoxbj9kssd1QtI9gvaZfUul4XeF9qD gTj3I6nHqJt2e9MAF0MleW5IXeDb7bXbJiEI70i9MTgkd7cUOW7aaH3TwCONk+C62W 2fsSZBdQtd/H1bCRjBA7I1dxMbP8Am2+HE44UswuXiy3DM5yrVB2/mCAvh4SInUV7q 92aLJD/b3oJWw== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 03/11] HWBP: Add modify_local_hw_breakpoint_addr() API Date: Sun, 2 Aug 2026 17:18:59 +0900 Message-ID: <178565873951.714490.18157944268328517957.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add modify_local_hw_breakpoint_addr() to update only the watched address of an installed hardware breakpoint on the local CPU without releasing and reserving its hardware slot. This is available when the architecture selects CONFIG_HAVE_MODIFY_LOCAL_HW_BREAKPOINT_ADDR. The caller must provide an installed local event and a valid address, and update other CPUs separately. Link: https://lore.kernel.org/all/f9c49dfa49bdc57ba8c0574bc9981c1e581acf92.= 1785067572.git.wangjinchao600@gmail.com/ Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Jinchao Wang --- Changes in v11: - Check return value of arch_modify_local_hw_breakpoint_addr() and return error on failure. - Update bp->attr.bp_addr and counter_arch_bp(bp)->address only after arch modification succeeds. --- arch/Kconfig | 8 ++++++++ arch/x86/Kconfig | 1 + include/linux/hw_breakpoint.h | 6 ++++++ kernel/events/hw_breakpoint.c | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/arch/Kconfig b/arch/Kconfig index fa7507ac8e13..bea383408e32 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -457,6 +457,14 @@ config HAVE_MIXED_BREAKPOINTS_REGS Select this option if your arch implements breakpoints under the latter fashion. =20 +config HAVE_MODIFY_LOCAL_HW_BREAKPOINT_ADDR + bool + depends on HAVE_HW_BREAKPOINT + help + Select this if the architecture can modify the address of an + installed hardware breakpoint on the local CPU without releasing + and reserving its hardware slot. + config HAVE_USER_RETURN_NOTIFIER bool =20 diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index fd979e0be1f0..33908103d9a0 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -247,6 +247,7 @@ config X86 select HAVE_FUNCTION_TRACER select HAVE_GCC_PLUGINS select HAVE_HW_BREAKPOINT + select HAVE_MODIFY_LOCAL_HW_BREAKPOINT_ADDR select HAVE_IOREMAP_PROT select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64 select HAVE_IRQ_TIME_ACCOUNTING diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h index db199d653dd1..bf65c7fffd99 100644 --- a/include/linux/hw_breakpoint.h +++ b/include/linux/hw_breakpoint.h @@ -81,6 +81,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr, perf_overflow_handler_t triggered, void *context); =20 +int modify_local_hw_breakpoint_addr(struct perf_event *bp, + unsigned long addr); + extern int register_perf_hw_breakpoint(struct perf_event *bp); extern void unregister_hw_breakpoint(struct perf_event *bp); extern void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cp= u_events); @@ -124,6 +127,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *att= r, perf_overflow_handler_t triggered, void *context) { return NULL; } static inline int +modify_local_hw_breakpoint_addr(struct perf_event *bp, + unsigned long addr) { return -EOPNOTSUPP; } +static inline int register_perf_hw_breakpoint(struct perf_event *bp) { return -ENOSYS; } static inline void unregister_hw_breakpoint(struct perf_event *bp) { } static inline void diff --git a/kernel/events/hw_breakpoint.c b/kernel/events/hw_breakpoint.c index 789add0c185a..3c799c88160c 100644 --- a/kernel/events/hw_breakpoint.c +++ b/kernel/events/hw_breakpoint.c @@ -888,6 +888,41 @@ void unregister_wide_hw_breakpoint(struct perf_event *= __percpu *cpu_events) } EXPORT_SYMBOL_GPL(unregister_wide_hw_breakpoint); =20 +/** + * modify_local_hw_breakpoint_addr - update a local breakpoint address + * @bp: the hwbp perf event for this CPU + * @addr: the new address for @bp + * + * Update only the address of an installed breakpoint on the local CPU wit= hout + * releasing and reserving its hardware slot. The caller must update other= CPUs. + * Return 0, or -EOPNOTSUPP if the architecture does not support this oper= ation. + */ +#ifdef CONFIG_HAVE_MODIFY_LOCAL_HW_BREAKPOINT_ADDR +int modify_local_hw_breakpoint_addr(struct perf_event *bp, + unsigned long addr) +{ + int ret; + + lockdep_assert_irqs_disabled(); + + ret =3D arch_modify_local_hw_breakpoint_addr(bp, addr); + if (ret) + return ret; + + counter_arch_bp(bp)->address =3D addr; + bp->attr.bp_addr =3D addr; + + return 0; +} +#else +int modify_local_hw_breakpoint_addr(struct perf_event *bp, + unsigned long addr) +{ + return -EOPNOTSUPP; +} +#endif +EXPORT_SYMBOL_GPL(modify_local_hw_breakpoint_addr); + /** * hw_breakpoint_is_used - check if breakpoints are currently used * From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 D7226372060; Sun, 2 Aug 2026 08:19:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658761; cv=none; b=cJDJw4dRjZKnPi7M2azt1arTnHN3oIbuBetbcUY2z8jACI+lPl+ucBepJnWPKmwWEfrrJvHW3+p1w+pS8OC3cdd6lY7NRpPYu7ng/C0GdC6+6n9PAkYGJJ3nD8tPRpNVd0RwQMhe+bghZ1p0CzZXk1F+4xvoXTJTocj9npqnTYo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658761; c=relaxed/simple; bh=7JDpR+wLKrziCxK0Hz1uU6zOrsy10Cnqn3HXdF5pFbA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=AQ4XACELRM0ibNq0p+rxJHgK53WlRz3tdCfWmU90Q3bldnmC/LeNzZymaX/uAn4cIo702qN8a3CXHM8PZjknopLwQ+0rmeDS9Yrc62COe1damEvxgfiPFNse1QSV7krEHUCZWfxlDO42nnQijJOJr+U5Pu061cwzta3bi2bH5IA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=bdLjHwaA; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="bdLjHwaA" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CE0911F00AC4; Sun, 2 Aug 2026 08:19:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658757; bh=1/uMNxifNUalJzymy2KEb98TfZqXEB4YRTZ31kkU8qk=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=bdLjHwaAeMuixldqfwJWLqHVrdgNM1PKag3kyw2rggEWTo90/owqWcX27GOEPvOac 45CRaJsimMUkFJkG6QGgjOR+5iQtLZWcdq+exxzHdon0UZnO4s6n02YKpy3KqpiwHS iyi5EUFD3rSr2N7os22HTkvdd0Qy9j9LRncHC/ylsOaj5N6gXF60QjmiUmxdJV+814 JLb8E7lFuKE2lfdd7tM44vdONu+5fP/SUvx+0OuvtUYswKHmWfMkb3hMH1ijtLww99 16b9GZ+xCipAHCuQTHo7AA993bzNI08HZ1KeNr4Q7SH5+f6EkNjmI+I9krWsub9wqf YP86eBKObeGDg== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 04/11] tracing: wprobe: Add watchpoint probe event based on hardware breakpoint Date: Sun, 2 Aug 2026 17:19:10 +0900 Message-ID: <178565875046.714490.17724442147958346980.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add a new probe event for the hardware breakpoint called wprobe-event. This wprobe allows user to trace (watch) the memory access at the specified memory address. The new syntax is; w[:[GROUP/]EVENT] [r|w|rw]@[ADDR|SYM][:SIZE] [FETCH_ARGs] User also can use $addr to fetch the accessed address and $value to fetch the accessed memory value (shorthand for '+0($addr)'). No other variables are supported. For example, tracing updates of the jiffies; /sys/kernel/tracing # echo 'w:my_jiffies w@jiffies' >> dynamic_events /sys/kernel/tracing # cat dynamic_events w:wprobes/my_jiffies w@jiffies:4 /sys/kernel/tracing # echo 1 > events/wprobes/my_jiffies/enable /sys/kernel/tracing # head -n 20 trace | tail -n 5 # TASK-PID CPU# ||||| TIMESTAMP FUNCTION # | | | ||||| | | -0 [000] d.Z1. 206.547317: my_jiffies: (tick_do_upd= ate_jiffies64+0xbe/0x130) -0 [000] d.Z1. 206.548341: my_jiffies: (tick_do_upd= ate_jiffies64+0xbe/0x130) -0 [000] d.Z1. 206.549346: my_jiffies: (tick_do_upd= ate_jiffies64+0xbe/0x130) Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- Changes in v11: - Add WPROBE_NO_MAXACT error log to reject maxactive prefix on wprobe definitions. Changes in v10: - Use zalloc_flex() in alloc_trace_wprobe(). - Support matching command arguments in trace_wprobe_match(). Changes in v9: - Rebased on probes/for-next branch. - Add WPROBE_NO_SIBLING error log to explicitly reject sibling probes since event triggers identify the target wprobe by event name. - Use traceprobe_parse_event_name() to properly validate group/event names instead of using the raw command string directly. - Generate unique event name (w_0x) for anonymous address-based watchpoints to avoid naming collisions. - Call traceprobe_update_arg() in __register_trace_wprobe() to resolve @symbol fetch arguments, consistent with kprobe and fprobe. Changes in v8: - Include required header files. - Use READ_ONCE(tw->addr) in trace handler to safely check dynamically updated addresses. - Prohibit unsafe perf support by returning -EOPNOTSUPP in wprobe_register(). - Add rollback logic to unregister already-enabled sibling probes if registration fails mid-loop. - Resolve symbol offsets dynamically in trace_wprobe_show() using kallsyms_lookup_name(). - Fix memory leak of parse_address_spec()'s symbol output in __trace_wprobe_create(). - Print "rw" instead of "x" for read-write type breakpoints in trace_wprobe_show(). - Document the $value fetcharg in wprobetrace.rst. Changes in v7: - Include IS_ERR_PCPU fix. - use seq_print_ip_sym_offset(). - fix checkpatch warning on DEFINE_FREE() - Use bp->attr.bp_addr instead of tw->addr because it can be updated from= another CPU. --- Documentation/trace/index.rst | 1=20 Documentation/trace/wprobetrace.rst | 70 +++ include/linux/trace_events.h | 2=20 kernel/trace/Kconfig | 13 + kernel/trace/Makefile | 1=20 kernel/trace/trace.c | 9=20 kernel/trace/trace.h | 5=20 kernel/trace/trace_probe.c | 21 + kernel/trace/trace_probe.h | 10=20 kernel/trace/trace_wprobe.c | 746 +++++++++++++++++++++++++++++++= ++++ 10 files changed, 874 insertions(+), 4 deletions(-) create mode 100644 Documentation/trace/wprobetrace.rst create mode 100644 kernel/trace/trace_wprobe.c diff --git a/Documentation/trace/index.rst b/Documentation/trace/index.rst index 5d9bf4694d5d..2f04f32001ed 100644 --- a/Documentation/trace/index.rst +++ b/Documentation/trace/index.rst @@ -36,6 +36,7 @@ the Linux kernel. kprobes kprobetrace fprobetrace + wprobetrace eprobetrace fprobe ring-buffer-design diff --git a/Documentation/trace/wprobetrace.rst b/Documentation/trace/wpro= betrace.rst new file mode 100644 index 000000000000..eb4f10607530 --- /dev/null +++ b/Documentation/trace/wprobetrace.rst @@ -0,0 +1,70 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Watchpoint probe (wprobe) Event Tracing +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +.. Author: Masami Hiramatsu + +Overview +-------- + +Wprobe event is a dynamic event based on the hardware breakpoint, which is +similar to other probe events, but it is for watching data access. It allo= ws +you to trace which code accesses a specified data. + +As same as other dynamic events, wprobe events are defined via +`dynamic_events` interface file on tracefs. + +Synopsis of wprobe-events +------------------------- +:: + + w:[GRP/][EVENT] SPEC [FETCHARGS] : Probe on data a= ccess + + GRP : Group name for wprobe. If omitted, use "wprobes" for it. + EVENT : Event name for wprobe. If omitted, an event name is + generated based on the address or symbol. + SPEC : Breakpoint specification. + [r|w|rw]@[:LENGTH] + + r|w|rw : Access type, r for read, w for write, and rw for both. + Default is rw if omitted. + ADDRESS : Address to trace (hexadecimal). + SYMBOL : Symbol name to trace. + LENGTH : Length of the data to trace in bytes. (1, 2, 4, or 8) + + FETCHARGS : Arguments. Each probe can have up to 128 args. + $addr : Fetch the accessing address. + $value : Fetch the memory value at the accessing address (same a= s +0($addr)). + @ADDR : Fetch memory at ADDR (ADDR should be in kernel) + @SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbo= l) + +|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*1)(\= *2) + \IMM : Store an immediate value to the argument. + NAME=3DFETCHARG : Set NAME as the argument name of FETCHARG. + FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types + (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types + (x8/x16/x32/x64), "char", "string", "ustring", "symbol",= "symstr" + and bitfield are supported. + + (\*1) this is useful for fetching a field of data structures. + (\*2) "u" means user-space dereference. + +For the details of TYPE, see :ref:`kprobetrace documentation `. + +Usage examples +-------------- +Here is an example to add a wprobe event on a variable `jiffies`. +:: + + # echo 'w:my_jiffies w@jiffies' >> dynamic_events + # cat dynamic_events + w:wprobes/my_jiffies w@jiffies + # echo 1 > events/wprobes/enable + # cat trace | head + # TASK-PID CPU# ||||| TIMESTAMP FUNCTION + # | | | ||||| | | + -0 [000] d.Z1. 717.026259: my_jiffies: (tick_do_up= date_jiffies64+0xbe/0x130) + -0 [000] d.Z1. 717.026373: my_jiffies: (tick_do_up= date_jiffies64+0xbe/0x130) + +You can see the code which writes to `jiffies` is `tick_do_update_jiffies6= 4()`. diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 5cbd09c8be8d..43ffd9a76d88 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -337,6 +337,7 @@ enum { TRACE_EVENT_FL_UPROBE_BIT, TRACE_EVENT_FL_EPROBE_BIT, TRACE_EVENT_FL_FPROBE_BIT, + TRACE_EVENT_FL_WPROBE_BIT, TRACE_EVENT_FL_CUSTOM_BIT, TRACE_EVENT_FL_TEST_STR_BIT, }; @@ -367,6 +368,7 @@ enum { TRACE_EVENT_FL_UPROBE =3D (1 << TRACE_EVENT_FL_UPROBE_BIT), TRACE_EVENT_FL_EPROBE =3D (1 << TRACE_EVENT_FL_EPROBE_BIT), TRACE_EVENT_FL_FPROBE =3D (1 << TRACE_EVENT_FL_FPROBE_BIT), + TRACE_EVENT_FL_WPROBE =3D (1 << TRACE_EVENT_FL_WPROBE_BIT), TRACE_EVENT_FL_CUSTOM =3D (1 << TRACE_EVENT_FL_CUSTOM_BIT), TRACE_EVENT_FL_TEST_STR =3D (1 << TRACE_EVENT_FL_TEST_STR_BIT), }; diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index 0ab5916575a9..b58c2565024f 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -862,6 +862,19 @@ config EPROBE_EVENTS convert the type of an event field. For example, turn an address into a string. =20 +config WPROBE_EVENTS + bool "Enable wprobe-based dynamic events" + depends on TRACING + depends on HAVE_HW_BREAKPOINT + select PROBE_EVENTS + select DYNAMIC_EVENTS + help + This allows the user to add watchpoint tracing events based on + hardware breakpoints on the fly via the ftrace interface. + + Those events can be inserted wherever hardware breakpoints can be + set, and record accessed memory address and values. + config BPF_EVENTS depends on BPF_SYSCALL depends on (KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index f934ff586bd4..141c8323de20 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile @@ -126,6 +126,7 @@ obj-$(CONFIG_FTRACE_RECORD_RECURSION) +=3D trace_recurs= ion_record.o obj-$(CONFIG_FPROBE) +=3D fprobe.o obj-$(CONFIG_RETHOOK) +=3D rethook.o obj-$(CONFIG_FPROBE_EVENTS) +=3D trace_fprobe.o +obj-$(CONFIG_WPROBE_EVENTS) +=3D trace_wprobe.o =20 obj-$(CONFIG_TRACEPOINT_BENCHMARK) +=3D trace_benchmark.o obj-$(CONFIG_RV) +=3D rv/ diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 19cc07360005..4ebece96d8b7 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4294,8 +4294,12 @@ static const char readme_msg[] =3D " uprobe_events\t\t- Create/append/remove/show the userspace dynamic eve= nts\n" "\t\t\t Write into this file to define/undefine new trace events.\n" #endif +#ifdef CONFIG_WPROBE_EVENTS + " wprobe_events\t\t- Create/append/remove/show the hardware breakpoint d= ynamic events\n" + "\t\t\t Write into this file to define/undefine new trace events.\n" +#endif #if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS) || \ - defined(CONFIG_FPROBE_EVENTS) + defined(CONFIG_FPROBE_EVENTS) || defined(CONFIG_WPROBE_EVENTS) "\t accepts: event-definitions (one definition per line)\n" #if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS) "\t Format: p[:[/][]] []\n" @@ -4305,6 +4309,9 @@ static const char readme_msg[] =3D "\t f[:[/][]] [%return] []\n" "\t t[:[/][]] []\n" #endif +#ifdef CONFIG_WPROBE_EVENTS + "\t w[:[/][]] [r|w|rw]@[:]\n" +#endif #ifdef CONFIG_HIST_TRIGGERS "\t s:[synthetic/] []\n" #endif diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index bf77331f56a4..64851a8d021f 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -179,6 +179,11 @@ struct fexit_trace_entry_head { unsigned long ret_ip; }; =20 +struct wprobe_trace_entry_head { + struct trace_entry ent; + unsigned long ip; +}; + #define TRACE_BUF_SIZE 1024 =20 struct trace_array; diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 5552ce7af5a1..9f4cad18977a 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1402,6 +1402,23 @@ static int parse_probe_vars(char *orig_arg, const st= ruct fetch_type *t, return 0; } =20 + /* wprobe only support "$addr" and "$value" variable */ + if (ctx->flags & TPARG_FL_WPROBE) { + if (!strcmp(arg, "addr")) { + code->op =3D FETCH_OP_BADDR; + return 0; + } + if (!strcmp(arg, "value")) { + code->op =3D FETCH_OP_BADDR; + code++; + code->op =3D FETCH_OP_DEREF; + code->offset =3D 0; + *pcode =3D code; + return 0; + } + goto inval; + } + if (strcmp(arg, "comm") =3D=3D 0 || strcmp(arg, "COMM") =3D=3D 0) { code->op =3D FETCH_OP_COMM; return 0; @@ -1461,8 +1478,8 @@ static int parse_probe_arg_register(char *arg, struct= fetch_insn *code, { int ret; =20 - if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) { - /* eprobe and fprobe do not handle registers */ + if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE | TPARG_FL_WPROBE)) { + /* eprobe, fprobe and wprobe do not handle registers */ trace_probe_log_err(ctx->offset, BAD_VAR); return -EINVAL; } diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index fba1af092a9b..6543d4c2cda5 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -90,6 +90,7 @@ typedef int (*print_type_func_t)(struct trace_seq *, void= *, void *); FETCH_OP(STACK, param), /* Stack: .param =3D index */ \ FETCH_OP(STACKP, none), /* Stack pointer */ \ FETCH_OP(RETVAL, none), /* Return value */ \ + FETCH_OP(BADDR, none), /* Break address */ \ FETCH_OP(IMM, imm), /* Immediate: .immediate */ \ FETCH_OP(COMM, none), /* Current comm */ \ FETCH_OP(CURRENT, none), /* Current task_struct address */\ @@ -418,6 +419,7 @@ static inline int traceprobe_get_entry_data_size(struct= trace_probe *tp) #define TPARG_FL_USER BIT(4) #define TPARG_FL_FPROBE BIT(5) #define TPARG_FL_TPOINT BIT(6) +#define TPARG_FL_WPROBE BIT(7) #define TPARG_FL_LOC_MASK GENMASK(4, 0) =20 static inline bool tparg_is_function_entry(unsigned int flags) @@ -544,6 +546,10 @@ extern int traceprobe_define_arg_fields(struct trace_e= vent_call *event_call, C(ARG_TOO_LONG, "Argument expression is too long"), \ C(ARRAY_NO_CLOSE, "Array is not closed"), \ C(ARRAY_TOO_BIG, "Array number is too big"), \ + C(BAD_ACCESS_ADDR, "Invalid access memory address"), \ + C(BAD_ACCESS_FMT, "Access memory address requires @"), \ + C(BAD_ACCESS_LEN, "This memory access length is not supported"), \ + C(BAD_ACCESS_TYPE, "Bad memory access type"), \ C(BAD_ADDR_SUFFIX, "Invalid probed address suffix"), \ C(BAD_ARG_NAME, "Argument name must follow the same rules as C identifie= rs"), \ C(BAD_ARG_NUM, "Invalid argument number"), \ @@ -626,7 +632,9 @@ extern int traceprobe_define_arg_fields(struct trace_ev= ent_call *event_call, C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"), \ C(TYPECAST_REQ_FIELD, "Typecast requires a field access"), \ C(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses"), \ - C(USED_ARG_NAME, "This argument name is already used"), + C(USED_ARG_NAME, "This argument name is already used"), \ + C(WPROBE_NO_MAXACT, "Watchpoint probe does not support maxactive"), \ + C(WPROBE_NO_SIBLING, "Watchpoint probe does not support sibling probes"), =20 #undef C #define C(a, b) TP_ERR_##a diff --git a/kernel/trace/trace_wprobe.c b/kernel/trace/trace_wprobe.c new file mode 100644 index 000000000000..df5e434fffb4 --- /dev/null +++ b/kernel/trace/trace_wprobe.c @@ -0,0 +1,746 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Hardware-breakpoint-based tracing events + * + * Copyright (C) 2023, Masami Hiramatsu + */ +#define pr_fmt(fmt) "trace_wprobe: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "trace_dynevent.h" +#include "trace_probe.h" +#include "trace_probe_kernel.h" +#include "trace_probe_tmpl.h" +#include "trace_output.h" + +#define WPROBE_EVENT_SYSTEM "wprobes" + +static int trace_wprobe_create(const char *raw_command); +static int trace_wprobe_show(struct seq_file *m, struct dyn_event *ev); +static int trace_wprobe_release(struct dyn_event *ev); +static bool trace_wprobe_is_busy(struct dyn_event *ev); +static bool trace_wprobe_match(const char *system, const char *event, + int argc, const char **argv, struct dyn_event *ev); + +static struct dyn_event_operations trace_wprobe_ops =3D { + .create =3D trace_wprobe_create, + .show =3D trace_wprobe_show, + .is_busy =3D trace_wprobe_is_busy, + .free =3D trace_wprobe_release, + .match =3D trace_wprobe_match, +}; + +struct trace_wprobe { + struct dyn_event devent; + struct perf_event * __percpu *bp_event; + unsigned long addr; + int len; + int type; + const char *symbol; + struct trace_probe tp; +}; + +static bool is_trace_wprobe(struct dyn_event *ev) +{ + return ev->ops =3D=3D &trace_wprobe_ops; +} + +static struct trace_wprobe *to_trace_wprobe(struct dyn_event *ev) +{ + return container_of(ev, struct trace_wprobe, devent); +} + +#define for_each_trace_wprobe(pos, dpos) \ + for_each_dyn_event(dpos) \ + if (is_trace_wprobe(dpos) && (pos =3D to_trace_wprobe(dpos))) + +static bool trace_wprobe_is_busy(struct dyn_event *ev) +{ + struct trace_wprobe *tw =3D to_trace_wprobe(ev); + + return trace_probe_is_enabled(&tw->tp); +} + +static bool trace_wprobe_match(const char *system, const char *event, + int argc, const char **argv, struct dyn_event *ev) +{ + struct trace_wprobe *tw =3D to_trace_wprobe(ev); + + if (event[0] !=3D '\0' && strcmp(trace_probe_name(&tw->tp), event)) + return false; + + if (system && strcmp(trace_probe_group_name(&tw->tp), system)) + return false; + + return trace_probe_match_command_args(&tw->tp, argc, argv); +} + +/* + * Note that we don't verify the fetch_insn code, since it does not come + * from user space. + */ +static int +process_fetch_insn(struct fetch_insn *code, void *rec, void *edata, + void *dest, void *base) +{ + void *baddr =3D rec; + unsigned long val; + int ret; + +retry: + /* 1st stage: get value from context */ + switch (code->op) { + case FETCH_OP_BADDR: + val =3D (unsigned long)baddr; + break; + case FETCH_NOP_SYMBOL: /* Ignore a place holder */ + code++; + goto retry; + default: + ret =3D process_common_fetch_insn(code, &val); + if (ret < 0) + return ret; + } + code++; + + return process_fetch_insn_bottom(code, val, dest, base); +} +NOKPROBE_SYMBOL(process_fetch_insn) + +static void wprobe_trace_handler(struct trace_wprobe *tw, + unsigned long addr, + struct pt_regs *regs, + struct trace_event_file *trace_file) +{ + struct wprobe_trace_entry_head *entry; + struct trace_event_call *call =3D trace_probe_event_call(&tw->tp); + struct trace_event_buffer fbuffer; + int dsize; + + if (WARN_ON_ONCE(call !=3D trace_file->event_call)) + return; + + if (trace_trigger_soft_disabled(trace_file)) + return; + + if (READ_ONCE(tw->addr) !=3D addr) + return; + + dsize =3D __get_data_size(&tw->tp, (void *)addr, NULL); + + entry =3D trace_event_buffer_reserve(&fbuffer, trace_file, + sizeof(*entry) + tw->tp.size + dsize); + if (!entry) + return; + + entry->ip =3D instruction_pointer(regs); + store_trace_args(&entry[1], &tw->tp, (void *)addr, NULL, sizeof(*entry), = dsize); + + fbuffer.regs =3D regs; + trace_event_buffer_commit(&fbuffer); +} + +static void wprobe_perf_handler(struct perf_event *bp, + struct perf_sample_data *data, + struct pt_regs *regs) +{ + struct trace_wprobe *tw =3D bp->overflow_handler_context; + struct event_file_link *link; + unsigned long addr =3D bp->attr.bp_addr; + + trace_probe_for_each_link_rcu(link, &tw->tp) + wprobe_trace_handler(tw, addr, regs, link->file); +} + +static int __register_trace_wprobe(struct trace_wprobe *tw) +{ + struct perf_event_attr attr; + int i, ret; + + if (tw->bp_event) + return -EINVAL; + + for (i =3D 0; i < tw->tp.nr_args; i++) { + ret =3D traceprobe_update_arg(&tw->tp.args[i]); + if (ret) + return ret; + } + + hw_breakpoint_init(&attr); + attr.bp_addr =3D tw->addr; + attr.bp_len =3D tw->len; + attr.bp_type =3D tw->type; + + tw->bp_event =3D register_wide_hw_breakpoint(&attr, wprobe_perf_handler, = tw); + if (IS_ERR_PCPU(tw->bp_event)) { + int ret =3D PTR_ERR_PCPU(tw->bp_event); + + tw->bp_event =3D NULL; + return ret; + } + + return 0; +} + +static void __unregister_trace_wprobe(struct trace_wprobe *tw) +{ + if (tw->bp_event) { + unregister_wide_hw_breakpoint(tw->bp_event); + tw->bp_event =3D NULL; + } +} + +static void free_trace_wprobe(struct trace_wprobe *tw) +{ + if (tw) { + trace_probe_cleanup(&tw->tp); + kfree(tw->symbol); + kfree(tw); + } +} +DEFINE_FREE(free_trace_wprobe, struct trace_wprobe *, + if (!IS_ERR_OR_NULL(_T)) + free_trace_wprobe(_T)) + + +static struct trace_wprobe *alloc_trace_wprobe(const char *group, + const char *event, + const char *symbol, + unsigned long addr, + int len, int type, int nargs) +{ + struct trace_wprobe *tw __free(free_trace_wprobe) =3D NULL; + int ret; + + tw =3D kzalloc_flex(*tw, tp.args, nargs); + if (!tw) + return ERR_PTR(-ENOMEM); + + if (symbol) { + tw->symbol =3D kstrdup(symbol, GFP_KERNEL); + if (!tw->symbol) + return ERR_PTR(-ENOMEM); + } + tw->addr =3D addr; + tw->len =3D len; + tw->type =3D type; + + ret =3D trace_probe_init(&tw->tp, event, group, false, nargs); + if (ret < 0) + return ERR_PTR(ret); + + dyn_event_init(&tw->devent, &trace_wprobe_ops); + return_ptr(tw); +} + +static struct trace_wprobe *find_trace_wprobe(const char *event, + const char *group) +{ + struct dyn_event *pos; + struct trace_wprobe *tw; + + for_each_trace_wprobe(tw, pos) + if (strcmp(trace_probe_name(&tw->tp), event) =3D=3D 0 && + strcmp(trace_probe_group_name(&tw->tp), group) =3D=3D 0) + return tw; + return NULL; +} + +static enum print_line_t +print_wprobe_event(struct trace_iterator *iter, int flags, + struct trace_event *event) +{ + struct wprobe_trace_entry_head *field; + struct trace_seq *s =3D &iter->seq; + struct trace_probe *tp; + + field =3D (struct wprobe_trace_entry_head *)iter->ent; + tp =3D trace_probe_primary_from_call( + container_of(event, struct trace_event_call, event)); + if (WARN_ON_ONCE(!tp)) + goto out; + + trace_seq_printf(s, "%s: (", trace_probe_name(tp)); + + if (!seq_print_ip_sym_offset(s, field->ip, flags)) + goto out; + + trace_seq_putc(s, ')'); + + if (trace_probe_print_args(s, tp->args, tp->nr_args, + (u8 *)&field[1], field) < 0) + goto out; + + trace_seq_putc(s, '\n'); +out: + return trace_handle_return(s); +} + +static int wprobe_event_define_fields(struct trace_event_call *event_call) +{ + int ret; + struct wprobe_trace_entry_head field; + struct trace_probe *tp; + + tp =3D trace_probe_primary_from_call(event_call); + if (WARN_ON_ONCE(!tp)) + return -ENOENT; + + DEFINE_FIELD(unsigned long, ip, FIELD_STRING_IP, 0); + + return traceprobe_define_arg_fields(event_call, sizeof(field), tp); +} + +static struct trace_event_functions wprobe_funcs =3D { + .trace =3D print_wprobe_event +}; + +static struct trace_event_fields wprobe_fields_array[] =3D { + { .type =3D TRACE_FUNCTION_TYPE, + .define_fields =3D wprobe_event_define_fields }, + {} +}; + +static int wprobe_register(struct trace_event_call *event, + enum trace_reg type, void *data); + +static inline void init_trace_event_call(struct trace_wprobe *tw) +{ + struct trace_event_call *call =3D trace_probe_event_call(&tw->tp); + + call->event.funcs =3D &wprobe_funcs; + call->class->fields_array =3D wprobe_fields_array; + call->flags =3D TRACE_EVENT_FL_WPROBE; + call->class->reg =3D wprobe_register; +} + +static int register_wprobe_event(struct trace_wprobe *tw) +{ + init_trace_event_call(tw); + return trace_probe_register_event_call(&tw->tp); +} + +static int register_trace_wprobe_event(struct trace_wprobe *tw) +{ + struct trace_wprobe *old_tw; + int ret; + + guard(mutex)(&event_mutex); + + old_tw =3D find_trace_wprobe(trace_probe_name(&tw->tp), + trace_probe_group_name(&tw->tp)); + if (old_tw) { + /* + * Wprobe does not support sibling probes because the event + * trigger (set_wprobe/clear_wprobe) identifies the target + * wprobe by its event name. Having multiple wprobes sharing + * the same event name would make the target ambiguous. + */ + trace_probe_log_set_index(0); + trace_probe_log_err(0, WPROBE_NO_SIBLING); + return -EBUSY; + } + + ret =3D register_wprobe_event(tw); + if (ret) + return ret; + + dyn_event_add(&tw->devent, trace_probe_event_call(&tw->tp)); + return 0; +} +static int unregister_wprobe_event(struct trace_wprobe *tw) +{ + return trace_probe_unregister_event_call(&tw->tp); +} + +static int unregister_trace_wprobe(struct trace_wprobe *tw) +{ + if (trace_probe_has_sibling(&tw->tp)) + goto unreg; + + if (trace_probe_is_enabled(&tw->tp)) + return -EBUSY; + + if (trace_event_dyn_busy(trace_probe_event_call(&tw->tp))) + return -EBUSY; + + if (unregister_wprobe_event(tw)) + return -EBUSY; + +unreg: + __unregister_trace_wprobe(tw); + dyn_event_remove(&tw->devent); + trace_probe_unlink(&tw->tp); + + return 0; +} + +static int enable_trace_wprobe(struct trace_event_call *call, + struct trace_event_file *file) +{ + struct trace_probe *tp; + struct trace_wprobe *tw; + bool enabled; + int ret =3D 0; + + tp =3D trace_probe_primary_from_call(call); + if (WARN_ON_ONCE(!tp)) + return -ENODEV; + enabled =3D trace_probe_is_enabled(tp); + + if (file) { + ret =3D trace_probe_add_file(tp, file); + if (ret) + return ret; + } else { + trace_probe_set_flag(tp, TP_FLAG_PROFILE); + } + + if (!enabled) { + list_for_each_entry(tw, trace_probe_probe_list(tp), tp.list) { + ret =3D __register_trace_wprobe(tw); + if (ret < 0) { + struct trace_wprobe *tmp; + + list_for_each_entry(tmp, trace_probe_probe_list(tp), tp.list) { + if (tmp =3D=3D tw) + break; + __unregister_trace_wprobe(tmp); + } + if (file) + trace_probe_remove_file(tp, file); + else + trace_probe_clear_flag(tp, TP_FLAG_PROFILE); + return ret; + } + } + } + + return 0; +} + +static int disable_trace_wprobe(struct trace_event_call *call, + struct trace_event_file *file) +{ + struct trace_wprobe *tw; + struct trace_probe *tp; + + tp =3D trace_probe_primary_from_call(call); + if (WARN_ON_ONCE(!tp)) + return -ENODEV; + + if (file) { + if (!trace_probe_get_file_link(tp, file)) + return -ENOENT; + if (!trace_probe_has_single_file(tp)) + goto out; + trace_probe_clear_flag(tp, TP_FLAG_TRACE); + } else { + trace_probe_clear_flag(tp, TP_FLAG_PROFILE); + } + + if (!trace_probe_is_enabled(tp)) { + list_for_each_entry(tw, trace_probe_probe_list(tp), tp.list) { + __unregister_trace_wprobe(tw); + } + } + +out: + if (file) + trace_probe_remove_file(tp, file); + + return 0; +} + +static int wprobe_register(struct trace_event_call *event, + enum trace_reg type, void *data) +{ + struct trace_event_file *file =3D data; + + switch (type) { + case TRACE_REG_REGISTER: + return enable_trace_wprobe(event, file); + case TRACE_REG_UNREGISTER: + return disable_trace_wprobe(event, file); + +#ifdef CONFIG_PERF_EVENTS + case TRACE_REG_PERF_REGISTER: + case TRACE_REG_PERF_UNREGISTER: + case TRACE_REG_PERF_OPEN: + case TRACE_REG_PERF_CLOSE: + case TRACE_REG_PERF_ADD: + case TRACE_REG_PERF_DEL: + return -EOPNOTSUPP; +#endif + } + return 0; +} + +static int parse_address_spec(const char *spec, unsigned long *addr, int *= type, + int *len, char **symbol) +{ + char *_spec __free(kfree) =3D NULL; + int _len =3D HW_BREAKPOINT_LEN_4; + int _type =3D HW_BREAKPOINT_RW; + unsigned long _addr =3D 0; + char *at, *col; + + _spec =3D kstrdup(spec, GFP_KERNEL); + if (!_spec) + return -ENOMEM; + + at =3D strchr(_spec, '@'); + col =3D strchr(_spec, ':'); + + if (!at) { + trace_probe_log_err(0, BAD_ACCESS_FMT); + return -EINVAL; + } + + if (at !=3D _spec) { + *at =3D '\0'; + + if (strcmp(_spec, "r") =3D=3D 0) + _type =3D HW_BREAKPOINT_R; + else if (strcmp(_spec, "w") =3D=3D 0) + _type =3D HW_BREAKPOINT_W; + else if (strcmp(_spec, "rw") =3D=3D 0) + _type =3D HW_BREAKPOINT_RW; + else { + trace_probe_log_err(0, BAD_ACCESS_TYPE); + return -EINVAL; + } + } + + if (col) { + *col =3D '\0'; + if (kstrtoint(col + 1, 0, &_len)) { + trace_probe_log_err(col + 1 - _spec, BAD_ACCESS_LEN); + return -EINVAL; + } + + switch (_len) { + case 1: + _len =3D HW_BREAKPOINT_LEN_1; + break; + case 2: + _len =3D HW_BREAKPOINT_LEN_2; + break; + case 4: + _len =3D HW_BREAKPOINT_LEN_4; + break; + case 8: + _len =3D HW_BREAKPOINT_LEN_8; + break; + default: + trace_probe_log_err(col + 1 - _spec, BAD_ACCESS_LEN); + return -EINVAL; + } + } + + if (kstrtoul(at + 1, 0, &_addr) !=3D 0) { + char *off_str =3D strpbrk(at + 1, "+-"); + int offset =3D 0; + + if (off_str) { + if (kstrtoint(off_str, 0, &offset) !=3D 0) { + trace_probe_log_err(off_str - _spec, BAD_PROBE_ADDR); + return -EINVAL; + } + *off_str =3D '\0'; + } + _addr =3D kallsyms_lookup_name(at + 1); + if (!_addr) { + trace_probe_log_err(at + 1 - _spec, BAD_ACCESS_ADDR); + return -ENOENT; + } + _addr +=3D offset; + *symbol =3D kstrdup(at + 1, GFP_KERNEL); + if (!*symbol) + return -ENOMEM; + } + + *addr =3D _addr; + *type =3D _type; + *len =3D _len; + return 0; +} + +static int __trace_wprobe_create(int argc, const char *argv[]) +{ + /* + * Argument syntax: + * b[:[GRP/][EVENT]] SPEC + * + * SPEC: + * [r|w|rw]@[ADDR|SYMBOL[+OFFS]][:LEN] + */ + struct traceprobe_parse_context *ctx __free(traceprobe_parse_context) =3D= NULL; + struct trace_wprobe *tw __free(free_trace_wprobe) =3D NULL; + const char *event =3D NULL, *group =3D WPROBE_EVENT_SYSTEM; + const char *tplog __free(trace_probe_log_clear) =3D NULL; + char *symbol __free(kfree) =3D NULL; + char *gbuf __free(kfree) =3D NULL; + char *ebuf __free(kfree) =3D NULL; + unsigned long addr; + int len, type, i; + int ret =3D 0; + + if (argv[0][0] !=3D 'w') + return -ECANCELED; + + if (argc < 2) + return -EINVAL; + + tplog =3D trace_probe_log_init("wprobe", argc, argv); + + if (argv[0][1] !=3D '\0') { + if (argv[0][1] !=3D ':') { + trace_probe_log_set_index(0); + trace_probe_log_err(1, WPROBE_NO_MAXACT); + return -EINVAL; + } + event =3D &argv[0][2]; + } + + trace_probe_log_set_index(1); + ret =3D parse_address_spec(argv[1], &addr, &type, &len, &symbol); + if (ret < 0) + return ret; + + trace_probe_log_set_index(0); + if (event) { + gbuf =3D kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL); + if (!gbuf) + return -ENOMEM; + ret =3D traceprobe_parse_event_name(&event, &group, gbuf, + event - argv[0]); + if (ret) + return ret; + } + + if (!event) { + /* Make a new event name */ + ebuf =3D kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL); + if (!ebuf) + return -ENOMEM; + if (symbol) + snprintf(ebuf, MAX_EVENT_NAME_LEN, "%s", symbol); + else + snprintf(ebuf, MAX_EVENT_NAME_LEN, "w_0x%lx", addr); + sanitize_event_name(ebuf); + event =3D ebuf; + } + + argc -=3D 2; argv +=3D 2; + tw =3D alloc_trace_wprobe(group, event, symbol, addr, len, type, argc); + if (IS_ERR(tw)) + return PTR_ERR(tw); + + ctx =3D kzalloc_obj(*ctx); + if (!ctx) + return -ENOMEM; + + ctx->flags =3D TPARG_FL_KERNEL | TPARG_FL_WPROBE; + + /* parse arguments */ + for (i =3D 0; i < argc; i++) { + trace_probe_log_set_index(i + 2); + ctx->offset =3D 0; + ret =3D traceprobe_parse_probe_arg(&tw->tp, i, argv[i], ctx); + if (ret) + return ret; /* This can be -ENOMEM */ + } + + ret =3D traceprobe_set_print_fmt(&tw->tp, PROBE_PRINT_NORMAL); + if (ret < 0) + return ret; + + ret =3D register_trace_wprobe_event(tw); + if (!ret) + tw =3D NULL; /* To avoid free */ + + return ret; +} + +static int trace_wprobe_create(const char *raw_command) +{ + return trace_probe_create(raw_command, __trace_wprobe_create); +} + +static int trace_wprobe_release(struct dyn_event *ev) +{ + struct trace_wprobe *tw =3D to_trace_wprobe(ev); + int ret =3D unregister_trace_wprobe(tw); + + if (!ret) + free_trace_wprobe(tw); + return ret; +} + +static int trace_wprobe_show(struct seq_file *m, struct dyn_event *ev) +{ + struct trace_wprobe *tw =3D to_trace_wprobe(ev); + int i; + + seq_printf(m, "w:%s/%s", trace_probe_group_name(&tw->tp), + trace_probe_name(&tw->tp)); + + const char *type_str; + + if (tw->type =3D=3D HW_BREAKPOINT_R) + type_str =3D "r"; + else if (tw->type =3D=3D HW_BREAKPOINT_W) + type_str =3D "w"; + else + type_str =3D "rw"; + + int len; + + if (tw->len =3D=3D HW_BREAKPOINT_LEN_1) + len =3D 1; + else if (tw->len =3D=3D HW_BREAKPOINT_LEN_2) + len =3D 2; + else if (tw->len =3D=3D HW_BREAKPOINT_LEN_4) + len =3D 4; + else + len =3D 8; + + if (tw->symbol) { + unsigned long sym_addr =3D kallsyms_lookup_name(tw->symbol); + long offset =3D sym_addr ? (long)(tw->addr - sym_addr) : 0; + + if (offset) + seq_printf(m, " %s@%s%+ld:%d", type_str, tw->symbol, offset, len); + else + seq_printf(m, " %s@%s:%d", type_str, tw->symbol, len); + } else { + seq_printf(m, " %s@0x%lx:%d", type_str, tw->addr, len); + } + + for (i =3D 0; i < tw->tp.nr_args; i++) + seq_printf(m, " %s=3D%s", tw->tp.args[i].name, tw->tp.args[i].comm); + seq_putc(m, '\n'); + + return 0; +} + +static __init int init_wprobe_trace(void) +{ + return dyn_event_register(&trace_wprobe_ops); +} +fs_initcall(init_wprobe_trace); + From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 7521A3203B6; Sun, 2 Aug 2026 08:19:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658769; cv=none; b=ctW52vyv0lWQemkUjLnvcOPhFHMxAnHoV9UkgOtwwCzJuv050/qMQAq/imp2AVJW2NB9TyAG7dbdiEIKwYu+5003L8RVvpMuopnaTRoEdS46tA4rF/lBezSNhW6t7UfHquE7D9nszVtntcHjsU7DAn575ggucSAqerp6S17My6Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658769; c=relaxed/simple; bh=UQmY+k5YWaVCDo6SuSbSCaeodphPvgVFa1uiBOXOPKY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=qO/nP753mSa1l6OzgVcEwMvPSYsbWbZoFMGl4POAAM2/FwiCJ9x8dd7xt3/5SWWKfYB/ORx8LaCf9tkEAKQnRO0GKSaOUOWWba7VV/wNJt5A6UBL8B1GE6tm5AFF/y7TNiAA1lI8KA23d6dJLn2XRIWwfkp+h4FJD7MKxwpDP0g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ACvC+9n5; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ACvC+9n5" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B1F6F1F00AC4; Sun, 2 Aug 2026 08:19:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658768; bh=VYR+OU0Dn6REQfZ5dvoGwVChYT/u8V3bDeY73u8gbsw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=ACvC+9n5sk8DHF20N/JBIruSI2uqq/fFYt06ja7qDVNThy1iH99x545S/3tz3hHXM ImZ1mzdTaj54i0RmgElNjlXdX2NoWp1Gvih+/Pzw2hZmCbPZirpCnvbszswnIgpx8e o2NiMBc7yuN4S1OXbKXGoQF9cR0G7X6CL/gUPKju+aq9nMxCBysBGba0+SkkRNl46V 8ONettXzp8aKCZ9EhHkvcWeP6mxZkx1aLSU16QgTs38cO49Wt1Zgw7Y6q+Yu1JCvTD NPHfYDCAuhbg5KoBuJ1GD8UmVAEA6VfA1l5OKA9KAzuiq/EYuL+1EO2SWV9uloSfmA +9tHs8a5kqrrA== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 05/11] x86: hw_breakpoint: Add a kconfig to clarify when a breakpoint fires Date: Sun, 2 Aug 2026 17:19:22 +0900 Message-ID: <178565876265.714490.13777267086485733929.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add CONFIG_HAVE_POST_BREAKPOINT_HOOK which indicates the hw_breakpoint on that architecture fires after the target memory has been modified. This is currently x86 only behavior. Signed-off-by: Masami Hiramatsu (Google) --- Changes in v11: - Move select to alphabetically sorted place. --- arch/Kconfig | 10 ++++++++++ arch/x86/Kconfig | 1 + kernel/trace/Kconfig | 1 + 3 files changed, 12 insertions(+) diff --git a/arch/Kconfig b/arch/Kconfig index bea383408e32..a096952987a8 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -457,6 +457,16 @@ config HAVE_MIXED_BREAKPOINTS_REGS Select this option if your arch implements breakpoints under the latter fashion. =20 +config HAVE_POST_BREAKPOINT_HOOK + bool + depends on HAVE_HW_BREAKPOINT + help + Depending on the arch implementation of hardware breakpoints, + some of them provide breakpoint hook after the target memory + is modified. + Select this option if your arch implements breakpoints overflow + handler hooks after the target memory is modified. + config HAVE_MODIFY_LOCAL_HW_BREAKPOINT_ADDR bool depends on HAVE_HW_BREAKPOINT diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 33908103d9a0..533010ab29a9 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -287,6 +287,7 @@ config X86 select MMU_GATHER_RCU_TABLE_FREE select MMU_GATHER_MERGE_VMAS select HAVE_POSIX_CPU_TIMERS_TASK_WORK + select HAVE_POST_BREAKPOINT_HOOK select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RELIABLE_STACKTRACE if UNWINDER_ORC || STACK_VALIDATION select HAVE_FUNCTION_ARG_ACCESS_API diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index b58c2565024f..d9b6fa5c35d9 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -866,6 +866,7 @@ config WPROBE_EVENTS bool "Enable wprobe-based dynamic events" depends on TRACING depends on HAVE_HW_BREAKPOINT + depends on HAVE_POST_BREAKPOINT_HOOK select PROBE_EVENTS select DYNAMIC_EVENTS help From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 BE133382381; Sun, 2 Aug 2026 08:19:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658780; cv=none; b=ivXAm87jMBIDZQ3TwsyYiS6KKt+wq1Iord/9LdewDmQpJqJk2kH75m9xwRmD3elrpVTq4tX9JisS4n2zNI8+fJCo8vZZuryOAnIgydeif4zeFb5VVNNkUfZEhf+TW26pAHaHItJhLigGlMILuISvfg+yKBwJqt6+Omw/p481hcw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658780; c=relaxed/simple; bh=7WwAsjXAm72C3N+aWoYKuzBUFcwI27R/LA/82emnZ6M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Z63wx1awkAzhbkpgkEC4oxOojBqsWpKOPkHXKJR/jEhqbFS5tBx/d8/EYiuUScL2ArkuKdokl62L9HKsbg57ghoLl6cnYz/QU05gbDiBFRVA/zWPHo+r0eVZF/g3lm9Fc79pLFZGEHn3RS+v0D7IdalkxF7AijUA1toYrDlY3H8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=EH8FkjBd; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="EH8FkjBd" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B12A51F00AC4; Sun, 2 Aug 2026 08:19:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658779; bh=lcnl1Kiyu2/Ws5GS/yrGcGPFrteWWVQg5IT2n1yE1q0=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=EH8FkjBd+eXRJtATfwQUTcieP07M/6Q+u2mUF4MI5AxTmMoeX3JqxryA1LqL267wD a27Y3GDa1N6Yl24MkqPkyPgcfwILLJmRDv7L4/knc7lOY2GL+lqB6c4UdvbFrp0Qu7 x21EScyjA8wBZdUkyZelsSp3q1XLFye4529SV10FQyN3fidIDycxVkFEoy7XtQLzuj qnyFWggHdsJgoyUUIQJyupG48QaTuckzPu5MKYCrEaxnm1xTVHBBiqHRUXy/XiFuUG UYW2QMkMOZ+RktzQWoEUotlvwjMbO+I0WWhaH6AACnJy8/K+/leD9EbZQ9RmSjNgKU fgiakD7Q5bFoA== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 06/11] selftests: tracing: Add a basic testcase for wprobe Date: Sun, 2 Aug 2026 17:19:33 +0900 Message-ID: <178565877350.714490.11248347882853426879.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add 'add_remove_wprobe.tc' testcase for testing wprobe event that tests adding and removing operations of the wprobe event. Signed-off-by: Masami Hiramatsu (Google) --- Changes in v9: - Fix command check logic to prevent early exit under 'set -e' (errexit) when grep or test fails. - Simplify enable/disable status checks by removing cat pipes. Changes in v8: - Fixed silently test failure path. --- tools/testing/selftests/ftrace/config | 1=20 .../ftrace/test.d/dynevent/add_remove_wprobe.tc | 63 ++++++++++++++++= ++++ 2 files changed, 64 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/add_remo= ve_wprobe.tc diff --git a/tools/testing/selftests/ftrace/config b/tools/testing/selftest= s/ftrace/config index 544de0db5f58..d2f503722020 100644 --- a/tools/testing/selftests/ftrace/config +++ b/tools/testing/selftests/ftrace/config @@ -27,3 +27,4 @@ CONFIG_STACK_TRACER=3Dy CONFIG_TRACER_SNAPSHOT=3Dy CONFIG_UPROBES=3Dy CONFIG_UPROBE_EVENTS=3Dy +CONFIG_WPROBE_EVENTS=3Dy diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wpro= be.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc new file mode 100644 index 000000000000..647c37d5e4c8 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc @@ -0,0 +1,63 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Generic dynamic event - add/remove wprobe events +# requires: dynamic_events "w[:[/][]] [r|w|rw]@[:= ]":README + +echo 0 > events/enable +echo > dynamic_events + +# Use jiffies as a variable that is frequently written to. +TARGET=3Djiffies + +echo "w:my_wprobe w@$TARGET" >> dynamic_events + +if ! grep -q my_wprobe dynamic_events; then + echo "Failed to create wprobe event" + exit_fail +fi + +if [ ! -d events/wprobes/my_wprobe ]; then + echo "Failed to create wprobe event directory" + exit_fail +fi + +echo 1 > events/wprobes/my_wprobe/enable + +# Check if the event is enabled +if ! grep -q 1 events/wprobes/my_wprobe/enable; then + echo "Failed to enable wprobe event" + exit_fail +fi + +# Let some time pass to trigger the breakpoint +sleep 1 + +# Check if we got any trace output +if ! grep -q my_wprobe trace; then + echo "wprobe event was not triggered" + exit_fail +fi + +echo 0 > events/wprobes/my_wprobe/enable + +# Check if the event is disabled +if ! grep -q 0 events/wprobes/my_wprobe/enable; then + echo "Failed to disable wprobe event" + exit_fail +fi + +echo "-:my_wprobe" >> dynamic_events + +if grep -q my_wprobe dynamic_events; then + echo "Failed to remove wprobe event" + exit_fail +fi + +if [ -d events/wprobes/my_wprobe ]; then + echo "Failed to remove wprobe event directory" + exit_fail +fi + +clear_trace + +exit 0 From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 BB5F4372060; Sun, 2 Aug 2026 08:19:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658791; cv=none; b=KQEvLXRGNKS21vkz2uHIG6oa3uzrxCDrxeSSNFUF35DiB8/V4lFQqtnPk/V6SPtAYSXkSJVPc+wGtC8q3OuxMC6gP/QQNhIyJ6zAXwkM0ckR8xKxxOI0JpLjwbKTesMujeUvf0wJ9fanl/ZkRdAkVkCoX2DsRo/9pAR4UEmmHM0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658791; c=relaxed/simple; bh=gEgmSpStBMZorNeU4tZqjEjYmKuJkXWpgxUYlYZRTSk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=BebP7XGinPKlxLcPX3dR+o8kN/skDiLsax2fgqtPraTfmtNtC60PxPAgooamPPRPxQNNfKkcqJyh37p5KpSmHsLA/xDhRsJQT8YoRcDbPalIzIj+qxlRg6tx/jbHwSBToj9gOu+wJYD0Park0ae3DQ9PBnRmEvw4Mz4nBA1kuJE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=GBNYH10Z; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="GBNYH10Z" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CD5591F00ACA; Sun, 2 Aug 2026 08:19:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658790; bh=r5VSCYmjb1sSxE5B+t4GSEeK+I37cQkuG1xiJPIv+a0=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=GBNYH10ZMqRPJCMAlMJAv1Z2EVHm57x9Eg46Add14s5xrN5W044073bQPIxn0pUtv xw6DWFT+RXjDs/sRnzh/mt4/ORWt370dzUfDfT9WjIfFIDitfoWyv+oJu0OgdqrTWa ZuEK7cFe7280FHANQq/+EnQsJO9lmZYKyJ8x3qFpHasGiZWoUVvWHgX9r6F8udpRbU y+Em8CUc1xwjx2ZkAcGQLrciYgIbDgOOwIhE2hxZYuWpwzqHkAVGMJiSFGAfB45MSc RlOXXvvo+w2zE3GeBoni13DimXCPQBDNZDIAndA1PKjzUZNQo8ftXBxQPhM1nDWdJt HonYBl5JmUVtA== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 07/11] selftests: tracing: Add syntax testcase for wprobe Date: Sun, 2 Aug 2026 17:19:44 +0900 Message-ID: <178565878464.714490.14020174891147519416.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add "wprobe_syntax_errors.tc" testcase for testing syntax errors of the watch probe events. Signed-off-by: Masami Hiramatsu (Google) --- Changes in v11: - Add WPROBE_NO_MAXACT syntax error case. --- .../test.d/dynevent/wprobes_syntax_errors.tc | 21 ++++++++++++++++= ++++ 1 file changed, 21 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/wprobes_= syntax_errors.tc diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_= errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_e= rrors.tc new file mode 100644 index 000000000000..9180b71001ec --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.= tc @@ -0,0 +1,21 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Watch probe event parser error log check +# requires: dynamic_events "w[:[/][]] [r|w|rw]@[:= ]":README + +check_error() { # command-with-error-pos-by-^ + ftrace_errlog_check 'wprobe' "$1" 'dynamic_events' +} + +check_error 'w^10 w@jiffies' # WPROBE_NO_MAXACT +check_error 'w ^symbol' # BAD_ACCESS_FMT +check_error 'w ^a@symbol' # BAD_ACCESS_TYPE +check_error 'w w@^symbol' # BAD_ACCESS_ADDR +check_error 'w w@jiffies^+offset' # BAD_ACCESS_ADDR +check_error 'w w@jiffies:^100' # BAD_ACCESS_LEN +check_error 'w w@jiffies ^$arg1' # BAD_VAR +check_error 'w w@jiffies ^$retval' # BAD_VAR +check_error 'w w@jiffies ^$stack' # BAD_VAR +check_error 'w w@jiffies ^%ax' # BAD_VAR + +exit 0 From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 596B63203B6; Sun, 2 Aug 2026 08:20:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658804; cv=none; b=GCKPOeQR9drmkdZIc455GQw1XXauL6gB7tzTSHZlmT5mLr8Cnk4xTnmsErooc9SI/2P7sT6OvF/KdD8Dp4dgD+PWZTgAA+SCzitu/mpM4+pe+KDkQ+T7mk9NzSEuAkDPvKc6nCugZFbbH+K5ZGZbIY/jnVIvruYocFzD19Ru+6Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658804; c=relaxed/simple; bh=jTb4ZVxZrzgKa11Sj2ntETCQotRNfhsQ2VtYwi6iTNA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=dmDa+KZdolB3akFco/5w2LNnf1q/vfNUI8jkQCj3mjlMq3YNv6vxfEHdAliOqtVwoZLFQMfVXzMqQl1JiozrN3QaY0VIorvEF78ALcxN36sn/iGhda4SAjC9wDRIx7n/LYXv38Y5Cuo4UtwL5VzyVZpTDYhi2/8GIy4Fa+ClqmM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=JlijOR1d; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="JlijOR1d" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D917E1F00AC4; Sun, 2 Aug 2026 08:19:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658801; bh=RiKu/cWzEa8NvvHK1skoFJ/R6aS3SVhGnz/H83P1xRk=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=JlijOR1d5GtHvriQ1Y0R5wP7f7EMmjulJqIRvEIIWur2JL6mMkibr1lm4BIatbKtR xh1UQFZxioAuXkM1sXqD/n4LTo2P8i4JiXPbxF3X0Gq3wZo6tESukLE2wTjPwQYsaN 08jClfZ94PzoVYfBKYvXnomy1KHOAQitNbcWsfopz4MA3nlUlxPyOQubUfhyseHOQ8 rW+nPYaZi5sW3DwZP1yoqn+s8BgT2qTvbx2ZRlcjUfsg8lw1jxQxn+uumAtTEFIuau EW1CLRB82Kx6nXrIAc2vqi20kFcgUIZ+LVI1i1rFo+0Pr7+Y0lUd26FSvuMCwXXe15 ILRFZwN3rTxOA== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 08/11] tracing: wprobe: Add wprobe event trigger Date: Sun, 2 Aug 2026 17:19:55 +0900 Message-ID: <178565879572.714490.13763586507727190682.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add wprobe event trigger to set and clear the watch event dynamically. This allows us to set an watchpoint on a given local variables and a slab object instead of static objects. The trigger syntax is below: - set_wprobe:WPROBE:FIELD[+OFFSET][:COUNT] [if FILTER] - clear_wprobe:WPROBE[:FIELD[+OFFSET]][:COUNT] [if FILTER] set_wprobe sets the address pointed by FIELD[+offset] to the WPROBE event. The FIELD is the field name of trigger event. clear_wprobe clears the watch address of WPROBE event. If the FIELD option is specified, it clears only if the current watch address is same as the given FIELD[+OFFSET] value. COUNT is the max number of activating trigger. The set_wprobe trigger does not change the type and length, these must be set when creating a new wprobe. Also, the WPROBE event must be disabled when setting the new trigger and it will be busy afterwards. Recommended usage is to add a new wprobe at NULL address and keep disabled. Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- Changes in v11: - Use new modify_local_hw_breakpoint_addr() API. - Add tracepoint_synchronize_unregister() in wprobe_unregister_trigger() and parse error path. - Safely check tw->bp_event for NULL in trace_wprobe_update_local() to prevent race conditions. - Use event_trigger_data::private_data_free. - Add count option support. - Fix trigger address calculation. (cast rec to char *) - Add work_pending flag to avoid update conflict with other CPU. - Fix to check wprobe_data->clear flag in wprobe_trigger_find_same(). Changes in v9: - Make event_trigger_free() non-static to solve build dependency. - Sync irq_work and work inside __unregister_trace_wprobe() before clearing tw->bp_event to prevent concurrent NULL pointer dereference. - Introduce private_free destructor in struct event_trigger_data to safely release wprobe_trigger_data after tracepoint readers exit. - Fix filter memory leak in wprobe_trigger_cmd_parse() on the error handling path of trace_event_try_get_ref() failure. - Avoid overwriting tw->addr on clear_wprobe trigger registration to prevent active watchpoint corruption and hardware breakpoint leak. Changes in v8: - Redesign wprobe_trigger() to be safe in NMI/hardirq contexts by deferring register updates to a workqueue via irq_work. - Skip trigger execution and increment an atomic missed count (tw->missed) if the tracepoint runs in NMI context to prevent recursive spinlock deadlocks. (this is currently hidden counter) - Prohibit attaching wprobe triggers to kprobe_events by checking TRACE_EVENT_FL_KPROBE in wprobe_trigger_cmd_parse(). - Use call_rcu() in trigger deactivation path and add synchronize_rcu() in parse failure path to ensure safe RCU lifetime cleanup. - Acquire the target tracepoint's module reference via trace_event_try_get_ref() to prevent module refcount underflows. - Fix event_trigger_data memory leak by properly freeing the initial refcount in wprobe_trigger_cmd_parse() on success. - Call on_each_cpu() with wait=3Dtrue in wprobe_work_func() to prevent use-after-free during trigger unregistration. - Synchronize concurrent wprobe triggers on the same event by using a shared raw spinlock (tw->lock). - Drop the support of kprobe events (that should be done later). Changes in v7: - Use kzalloc_obj(). - Update sample code in document. Changes in v6: - Update according to the latest change of trigger ops. Changes in v5: - Following the suggestions, the documentation was revised to suit rst. Changes in v3: - Add FIELD option support for clear_wprobe and update document. - Fix to unregister/free event_trigger_data on file correctly. - Fix syntax comments. Changes in v2: - Getting local cpu perf_event from trace_wprobe directly. - Remove trace_wprobe_local_perf() because it is conditionally unused. - Make CONFIG_WPROBE_TRIGGERS a hidden config. --- Documentation/trace/wprobetrace.rst | 93 ++++++ include/linux/trace_events.h | 1=20 kernel/trace/Kconfig | 10 + kernel/trace/trace.h | 1=20 kernel/trace/trace_events_trigger.c | 2=20 kernel/trace/trace_wprobe.c | 505 +++++++++++++++++++++++++++++++= ++++ 6 files changed, 611 insertions(+), 1 deletion(-) diff --git a/Documentation/trace/wprobetrace.rst b/Documentation/trace/wpro= betrace.rst index eb4f10607530..a4c0f0e676fd 100644 --- a/Documentation/trace/wprobetrace.rst +++ b/Documentation/trace/wprobetrace.rst @@ -68,3 +68,96 @@ Here is an example to add a wprobe event on a variable `= jiffies`. -0 [000] d.Z1. 717.026373: my_jiffies: (tick_do_up= date_jiffies64+0xbe/0x130) =20 You can see the code which writes to `jiffies` is `tick_do_update_jiffies6= 4()`. + +Combination with trigger action +------------------------------- +The event trigger action can extend the utilization of this wprobe. + +- set_wprobe:WPEVENT:FIELD[+|-ADJUST] +- clear_wprobe:WPEVENT[:FIELD[+|-]ADJUST] + +Set these triggers to the target event, then the WPROBE event will be +setup to trace the memory access at FIELD[+|-ADJUST] address. +When clear_wprobe is hit, if FIELD is NOT specified, the WPEVENT is +forcibly cleared. If FIELD[[+|-]ADJUST] is set, it clears WPEVENT only +if its watching address is the same as the FIELD[[+|-]ADJUST] value. + +Notes: +The set_wprobe trigger does not change the type and length, these +must be set when creating a new wprobe. + +The WPROBE event must be disabled when setting the new trigger +and it will be busy afterwards. Recommended usage is to add a new +wprobe at NULL address and keep disabled. + +Wprobe triggers are not supported on kprobe_events, because kprobes +themselves can use software breakpoints which conflicts with wprobe +operation. + + +For example, trace the first 8 bytes of the dentry data structure passed +to do_truncate() until it is deleted by dentry_kill(). +(Note: all tracefs setup uses '>>' so that it does not kick do_truncate()) +:: + + # echo 'w:watch rw@0:8 address=3D$addr value=3D+0($addr)' >> dynamic_eve= nts + # echo 'f:truncate do_truncate dentry=3D$arg2' >> dynamic_events + # echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger + # echo 'f:dentry_kill dentry_kill dentry=3D$arg1' >> dynamic_events + # echo 'clear_wprobe:watch:dentry' >> events/fprobes/dentry_kill/trigger + # echo 1 >> events/fprobes/truncate/enable + # echo 1 >> events/fprobes/dentry_kill/enable + + # echo aaa > /tmp/hoge + # echo bbb > /tmp/hoge + # echo ccc > /tmp/hoge + # rm /tmp/hoge + +Then, the trace data will show:: + + # tracer: nop + # + # entries-in-buffer/entries-written: 32/32 #P:8 + # + # _-----=3D> irqs-off/BH-disabled + # / _----=3D> need-resched + # | / _---=3D> hardirq/softirq + # || / _--=3D> preempt-depth + # ||| / _-=3D> migrate-disable + # |||| / delay + # TASK-PID CPU# ||||| TIMESTAMP FUNCTION + # | | | ||||| | | + sh-107 [004] ...1. 9.990418: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff888004ad6618 + sh-107 [004] ...1. 9.990914: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff888004b3de78 + sh-107 [004] ...1. 9.993175: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff8880049ddd40 + sh-107 [004] ..... 9.995198: truncate: (do_truncate= +0x4/0x120) dentry=3D0xffff8880048083a8 + sh-107 [004] ...1. 9.995389: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff8880049db998 + sh-107 [004] ..Zff 9.997503: watch: (lookup_fast+0x= aa/0x150) address=3D0xffff8880048083a8 value=3D0x8200080 + sh-107 [004] ..Zff 9.997509: watch: (path_openat+0x= 211/0xda0) address=3D0xffff8880048083a8 value=3D0x8200080 + sh-107 [004] ..Zff 9.997514: watch: (path_openat+0x= a56/0xda0) address=3D0xffff8880048083a8 value=3D0x8200080 + sh-107 [004] ..Zff 9.997518: watch: (path_openat+0x= ae2/0xda0) address=3D0xffff8880048083a8 value=3D0x8200080 + sh-107 [004] ..... 9.997521: truncate: (do_truncate= +0x4/0x120) dentry=3D0xffff8880048083a8 + sh-107 [004] ...1. 9.997582: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff888004808270 + sh-107 [004] ...1. 9.999365: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff8880049db728 + sh-107 [004] ...1. 9.999388: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff888004b1c000 + rm-113 [005] ..Zff 10.000965: watch: (lookup_fast+0x= aa/0x150) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] ..Zff 10.000971: watch: (path_lookupat+= 0x97/0x1e0) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] ..Zff 10.000984: watch: (lookup_fast+0x= aa/0x150) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] ..Zff 10.000988: watch: (path_lookupat+= 0x97/0x1e0) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] ..Zff 10.001010: watch: (lookup_one_qst= r_excl+0x28/0x140) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] ..Zff 10.001014: watch: (lookup_one_qst= r_excl+0xd1/0x140) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] ..Zff 10.001018: watch: (may_delete_den= try+0x1c/0x200) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] ..Zff 10.001021: watch: (may_delete_den= try+0x195/0x200) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] ..Zff 10.001031: watch: (vfs_unlink+0x5= e/0x260) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] d.Z.. 10.001067: watch: (d_make_discard= able+0x1b/0x40) address=3D0xffff8880048083a8 value=3D0x8200080 + rm-113 [005] d.Z.. 10.001071: watch: (d_make_discard= able+0x29/0x40) address=3D0xffff8880048083a8 value=3D0x200080 + rm-113 [005] ...1. 10.001072: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff8880048083a8 + rm-113 [005] ...1. 10.001218: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff8880048083a8 + sh-107 [004] ...1. 10.001416: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff8880049db110 + sh-107 [004] ...1. 10.001444: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff8880049db248 + sh-107 [004] ...1. 10.001500: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff888004ad6618 + sh-107 [004] ...1. 10.002067: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff888004b41e78 + sh-107 [004] ...1. 10.904920: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff888004b41e78 + sh-107 [004] ...1. 10.905129: dentry_kill: (dentry_k= ill+0x0/0x2c0) dentry=3D0xffff888004ad6618 + +You can see the watch event is correctly configured on the dentry. diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 43ffd9a76d88..f81a5308c116 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -738,6 +738,7 @@ enum event_trigger_type { ETT_EVENT_HIST =3D (1 << 4), ETT_HIST_ENABLE =3D (1 << 5), ETT_EVENT_EPROBE =3D (1 << 6), + ETT_EVENT_WPROBE =3D (1 << 7), }; =20 extern int filter_match_preds(struct event_filter *filter, void *rec); diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index d9b6fa5c35d9..5fd8ed63c516 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -876,6 +876,16 @@ config WPROBE_EVENTS Those events can be inserted wherever hardware breakpoints can be set, and record accessed memory address and values. =20 +config WPROBE_TRIGGERS + depends on WPROBE_EVENTS + depends on HAVE_MODIFY_LOCAL_HW_BREAKPOINT_ADDR + bool + default y + help + This adds an event trigger which will set the wprobe on a specific + field of an event. This allows user to trace the memory access of + an address pointed by the event field. + config BPF_EVENTS depends on BPF_SYSCALL depends on (KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 64851a8d021f..a789a722bc8b 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -1983,6 +1983,7 @@ trigger_data_alloc(struct event_command *cmd_ops, cha= r *cmd, char *param, void *private_data); extern void trigger_data_free(struct event_trigger_data *data); extern int event_trigger_init(struct event_trigger_data *data); +extern void event_trigger_free(struct event_trigger_data *data); extern int trace_event_trigger_enable_disable(struct trace_event_file *fil= e, int trigger_enable); extern void update_cond_flag(struct trace_event_file *file); diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_event= s_trigger.c index ad83419cb420..fa409ebd73c2 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c @@ -589,7 +589,7 @@ int event_trigger_init(struct event_trigger_data *data) * Usually used directly as the @free method in event trigger * implementations. */ -static void +void event_trigger_free(struct event_trigger_data *data) { if (WARN_ON_ONCE(data->ref <=3D 0)) diff --git a/kernel/trace/trace_wprobe.c b/kernel/trace/trace_wprobe.c index df5e434fffb4..5a034dce0845 100644 --- a/kernel/trace/trace_wprobe.c +++ b/kernel/trace/trace_wprobe.c @@ -6,7 +6,9 @@ */ #define pr_fmt(fmt) "trace_wprobe: " fmt =20 +#include #include +#include #include #include #include @@ -15,11 +17,16 @@ #include #include #include +#include #include #include +#include +#include +#include =20 #include =20 +#include "trace.h" #include "trace_dynevent.h" #include "trace_probe.h" #include "trace_probe_kernel.h" @@ -50,6 +57,17 @@ struct trace_wprobe { int len; int type; const char *symbol; + raw_spinlock_t lock; + struct irq_work irq_work; + struct work_struct work; + atomic_t missed; + /* + * work_pending is set to 1 before irq_work_queue() and cleared to 0 + * after wprobe_work_func() finishes on_each_cpu(). This prevents a + * new trigger from overwriting tw->addr while the work is propagating + * the old address to per-CPU debug registers via IPI. + */ + atomic_t work_pending; struct trace_probe tp; }; =20 @@ -198,14 +216,61 @@ static int __register_trace_wprobe(struct trace_wprob= e *tw) static void __unregister_trace_wprobe(struct trace_wprobe *tw) { if (tw->bp_event) { + irq_work_sync(&tw->irq_work); + cancel_work_sync(&tw->work); unregister_wide_hw_breakpoint(tw->bp_event); tw->bp_event =3D NULL; } } =20 +static int trace_wprobe_update_local(struct trace_wprobe *tw, unsigned lon= g addr) +{ + struct perf_event **pevent, *bp; + + pevent =3D READ_ONCE(tw->bp_event); + if (!pevent) + return -EINVAL; + + bp =3D *this_cpu_ptr(pevent); + if (!bp) + return -EINVAL; + + return modify_local_hw_breakpoint_addr(bp, addr); +} + +static void wprobe_smp_update_func(void *info) +{ + struct trace_wprobe *tw =3D info; + unsigned long addr =3D READ_ONCE(tw->addr); + + trace_wprobe_update_local(tw, addr); +} + +static void wprobe_work_func(struct work_struct *work) +{ + struct trace_wprobe *tw =3D container_of(work, struct trace_wprobe, work); + + on_each_cpu(wprobe_smp_update_func, tw, true); + /* + * Clear work_pending after all CPUs have updated their local debug + * registers. A new trigger may now update tw->addr and queue a new + * irq_work. + */ + atomic_set(&tw->work_pending, 0); +} + +static void wprobe_irq_work_func(struct irq_work *irq_work) +{ + struct trace_wprobe *tw =3D container_of(irq_work, struct trace_wprobe, i= rq_work); + + schedule_work(&tw->work); +} + static void free_trace_wprobe(struct trace_wprobe *tw) { if (tw) { + irq_work_sync(&tw->irq_work); + cancel_work_sync(&tw->work); trace_probe_cleanup(&tw->tp); kfree(tw->symbol); kfree(tw); @@ -237,6 +302,11 @@ static struct trace_wprobe *alloc_trace_wprobe(const c= har *group, tw->addr =3D addr; tw->len =3D len; tw->type =3D type; + raw_spin_lock_init(&tw->lock); + init_irq_work(&tw->irq_work, wprobe_irq_work_func); + INIT_WORK(&tw->work, wprobe_work_func); + atomic_set(&tw->missed, 0); + atomic_set(&tw->work_pending, 0); =20 ret =3D trace_probe_init(&tw->tp, event, group, false, nargs); if (ret < 0) @@ -744,3 +814,438 @@ static __init int init_wprobe_trace(void) } fs_initcall(init_wprobe_trace); =20 +#ifdef CONFIG_WPROBE_TRIGGERS + +static int wprobe_trigger_global_enabled; + +#define SET_WPROBE_STR "set_wprobe" +#define CLEAR_WPROBE_STR "clear_wprobe" +#define WPROBE_DEFAULT_CLEAR_ADDRESS ((unsigned long)&wprobe_trigger_globa= l_enabled) + +struct wprobe_trigger_data { + struct rcu_head rcu; + struct trace_event_file *file; + struct trace_wprobe *tw; + int offset; + long adjust; + const char *field; + bool clear; + long count; +}; + +static void wprobe_trigger(struct event_trigger_data *data, + struct trace_buffer *buffer, void *rec, + struct ring_buffer_event *event) +{ + struct wprobe_trigger_data *wprobe_data =3D data->private_data; + struct trace_wprobe *tw =3D wprobe_data->tw; + unsigned long addr, flags; + bool changed =3D false; + + addr =3D *(unsigned long *)((char *)rec + wprobe_data->offset); + addr +=3D wprobe_data->adjust; + + if (in_nmi()) { + atomic_inc(&tw->missed); + return; + } + + raw_spin_lock_irqsave(&tw->lock, flags); + + /* count < 0 means endless, i.e. always clear or always set */ + if (wprobe_data->count > 0) + wprobe_data->count--; + else if (!wprobe_data->count) + goto out; + + if (!wprobe_data->clear) { + if (tw->addr =3D=3D WPROBE_DEFAULT_CLEAR_ADDRESS) { + /* Skip if a previous work is still propagating the address */ + if (atomic_read(&tw->work_pending)) { + atomic_inc(&tw->missed); + goto out; + } + tw->addr =3D addr; + changed =3D true; + clear_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &wprobe_data->file->flags); + } + } else { + if (tw->addr !=3D WPROBE_DEFAULT_CLEAR_ADDRESS) { + /* Skip if a previous work is still propagating the address */ + if (atomic_read(&tw->work_pending)) { + atomic_inc(&tw->missed); + goto out; + } + if (!wprobe_data->field || tw->addr =3D=3D addr) { + tw->addr =3D WPROBE_DEFAULT_CLEAR_ADDRESS; + changed =3D true; + set_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &wprobe_data->file->flags); + } + } + } + + if (changed) { + /* + * Mark the work as pending before queuing irq_work so that + * subsequent triggers skip updating tw->addr until the work + * has finished propagating the address to all CPUs. + */ + atomic_set(&tw->work_pending, 1); + irq_work_queue(&tw->irq_work); + } + +out: + raw_spin_unlock_irqrestore(&tw->lock, flags); +} + +static void free_wprobe_trigger_data(struct wprobe_trigger_data *wprobe_da= ta) +{ + if (wprobe_data) { + kfree(wprobe_data->field); + kfree(wprobe_data); + } +} +DEFINE_FREE(free_wprobe_trigger_data, struct wprobe_trigger_data *, free_w= probe_trigger_data(_T)); + +static void free_private_wprobe_trigger_data(struct event_trigger_data *da= ta) +{ + free_wprobe_trigger_data(data->private_data); +} + +static int wprobe_trigger_print(struct seq_file *m, + struct event_trigger_data *data) +{ + struct wprobe_trigger_data *wprobe_data =3D data->private_data; + + if (wprobe_data->clear) { + seq_printf(m, "%s:%s", CLEAR_WPROBE_STR, + trace_event_name(wprobe_data->file->event_call)); + if (wprobe_data->field) { + seq_printf(m, ":%s%+ld", + wprobe_data->field, wprobe_data->adjust); + } + } else + seq_printf(m, "%s:%s:%s%+ld", SET_WPROBE_STR, + trace_event_name(wprobe_data->file->event_call), + wprobe_data->field, wprobe_data->adjust); + + if (wprobe_data->count =3D=3D -1) + seq_puts(m, ":unlimited"); + else + seq_printf(m, ":count=3D%ld", wprobe_data->count); + + if (data->filter_str) + seq_printf(m, " if %s\n", data->filter_str); + else + seq_putc(m, '\n'); + + return 0; +} + +static struct wprobe_trigger_data * +wprobe_trigger_alloc(struct trace_wprobe *tw, struct trace_event_file *fil= e, + bool clear) +{ + struct wprobe_trigger_data *wprobe_data; + + wprobe_data =3D kzalloc_obj(*wprobe_data); + if (!wprobe_data) + return NULL; + + wprobe_data->tw =3D tw; + wprobe_data->clear =3D clear; + wprobe_data->file =3D file; + wprobe_data->count =3D -1; + + return wprobe_data; +} + +static void wprobe_trigger_free(struct event_trigger_data *data) +{ + struct wprobe_trigger_data *wprobe_data =3D data->private_data; + + if (WARN_ON_ONCE(data->ref <=3D 0)) + return; + + data->ref--; + if (!data->ref) { + /* Remove the SOFT_MODE flag */ + trace_event_enable_disable(wprobe_data->file, 0, 1); + trace_event_put_ref(wprobe_data->file->event_call); + trigger_data_free(data); + } +} + +static int wprobe_trigger_cmd_parse(struct event_command *cmd_ops, + struct trace_event_file *file, + char *glob, char *cmd, + char *param_and_filter) +{ + /* + * set_wprobe:EVENT:FIELD[+OFFS] + * clear_wprobe:EVENT[:FIELD[+OFFS]] + */ + struct wprobe_trigger_data *wprobe_data __free(free_wprobe_trigger_data) = =3D NULL; + struct event_trigger_data *trigger_data __free(kfree) =3D NULL; + char *event_str, *field_str, *count_str; + struct ftrace_event_field *field =3D NULL; + struct trace_event_file *wprobe_file; + struct trace_array *tr =3D file->tr; + struct trace_event_call *event; + bool remove, clear =3D false; + struct trace_wprobe *tw; + char *param, *filter; + int ret; + + remove =3D event_trigger_check_remove(glob); + + if (!strcmp(cmd, CLEAR_WPROBE_STR)) + clear =3D true; + + if (event_trigger_empty_param(param_and_filter)) + return -EINVAL; + + ret =3D event_trigger_separate_filter(param_and_filter, ¶m, &filter, = true); + if (ret) + return ret; + + if (file->event_call->flags & TRACE_EVENT_FL_KPROBE) + return -EOPNOTSUPP; + + event_str =3D strsep(¶m, ":"); + + /* Find target wprobe */ + tw =3D find_trace_wprobe(event_str, WPROBE_EVENT_SYSTEM); + if (!tw) + return -ENOENT; + /* The target wprobe must not be used (unless clear) */ + if (!remove && !clear && trace_probe_is_enabled(&tw->tp)) + return -EBUSY; + + wprobe_file =3D find_event_file(tr, WPROBE_EVENT_SYSTEM, event_str); + if (!wprobe_file) + return -EINVAL; + + wprobe_data =3D wprobe_trigger_alloc(tw, wprobe_file, clear); + if (!wprobe_data) + return -ENOMEM; + + /* clear_wprobe does not need field. */ + if (!clear) { + char *offs; + + /* Find target field, which must be equivarent to "void *" */ + field_str =3D strsep(¶m, ":"); + if (!field_str) + return -EINVAL; + + offs =3D strpbrk(field_str, "+-"); + if (offs) { + long val; + + if (kstrtol(offs, 0, &val) < 0) + return -EINVAL; + wprobe_data->adjust =3D val; + *offs =3D '\0'; + } + + event =3D file->event_call; + field =3D trace_find_event_field(event, field_str); + if (!field) + return -ENOENT; + + if (field->size !=3D sizeof(void *)) + return -ENOEXEC; + wprobe_data->offset =3D field->offset; + wprobe_data->field =3D kstrdup(field_str, GFP_KERNEL); + if (!wprobe_data->field) + return -ENOMEM; + } + + /* count is optional, "unlimited" by default */ + count_str =3D strsep(¶m, ":"); + if (count_str) { + long val; + + if (strcmp(count_str, "unlimited")) { + if (str_has_prefix(count_str, "count=3D")) + count_str +=3D 6; + if (kstrtol(count_str, 0, &val) < 0) + return -EINVAL; + wprobe_data->count =3D val; + } + } + + trigger_data =3D trigger_data_alloc(cmd_ops, cmd, param, wprobe_data); + if (!trigger_data) + return -ENOMEM; + + trigger_data->private_data_free =3D free_private_wprobe_trigger_data; + + /* Up the trigger_data count to make sure nothing frees it on failure */ + event_trigger_init(trigger_data); + + if (remove) { + event_trigger_unregister(cmd_ops, file, glob+1, trigger_data); + return 0; + } + + ret =3D event_trigger_parse_num(param, trigger_data); + if (ret) + return ret; + + ret =3D event_trigger_set_filter(cmd_ops, file, filter, trigger_data); + if (ret < 0) + return ret; + + /* Soft-enable (register) wprobe event on WPROBE_DEFAULT_CLEAR_ADDRESS */ + if (!trace_event_try_get_ref(wprobe_file->event_call)) { + event_trigger_reset_filter(cmd_ops, trigger_data); + return -ENODEV; + } + + if (!clear) + WRITE_ONCE(tw->addr, WPROBE_DEFAULT_CLEAR_ADDRESS); + ret =3D trace_event_enable_disable(wprobe_file, 1, 1); + if (ret < 0) { + trace_event_put_ref(wprobe_file->event_call); + event_trigger_reset_filter(cmd_ops, trigger_data); + return ret; + } + ret =3D event_trigger_register(cmd_ops, file, glob, trigger_data); + if (ret) { + event_trigger_reset_filter(cmd_ops, trigger_data); + trace_event_enable_disable(wprobe_file, 0, 1); + trace_event_put_ref(wprobe_file->event_call); + tracepoint_synchronize_unregister(); + return ret; + } + /* Make it NULL to avoid freeing trigger_data and wprobe_data by __free()= */ + wprobe_data =3D NULL; + event_trigger_free(trigger_data); + trigger_data =3D NULL; + + return 0; +} + +/* Return event_trigger_data if there is a trigger which points the same w= probe */ +static struct event_trigger_data * +wprobe_trigger_find_same(struct event_trigger_data *test, + struct trace_event_file *file) +{ + struct wprobe_trigger_data *test_wprobe_data =3D test->private_data; + struct wprobe_trigger_data *wprobe_data; + struct event_trigger_data *iter; + + list_for_each_entry(iter, &file->triggers, list) { + wprobe_data =3D iter->private_data; + if (!wprobe_data || + iter->cmd_ops->trigger_type !=3D + test->cmd_ops->trigger_type) + continue; + if (wprobe_data->tw =3D=3D test_wprobe_data->tw && + wprobe_data->clear =3D=3D test_wprobe_data->clear) + return iter; + } + return NULL; +} + +static int wprobe_register_trigger(char *glob, + struct event_trigger_data *data, + struct trace_event_file *file) +{ + int ret =3D 0; + + lockdep_assert_held(&event_mutex); + + /* The same wprobe is not accept on the same file (event) */ + if (wprobe_trigger_find_same(data, file)) + return -EEXIST; + + if (data->cmd_ops->init) { + ret =3D data->cmd_ops->init(data); + if (ret < 0) + return ret; + } + + list_add_rcu(&data->list, &file->triggers); + + update_cond_flag(file); + ret =3D trace_event_trigger_enable_disable(file, 1); + if (ret < 0) { + list_del_rcu(&data->list); + update_cond_flag(file); + } + return ret; +} + +static void wprobe_unregister_trigger(char *glob, + struct event_trigger_data *test, + struct trace_event_file *file) +{ + struct event_trigger_data *data; + + lockdep_assert_held(&event_mutex); + + data =3D wprobe_trigger_find_same(test, file); + if (!data) + return; + + list_del_rcu(&data->list); + trace_event_trigger_enable_disable(file, 0); + update_cond_flag(file); + tracepoint_synchronize_unregister(); + if (data->cmd_ops->free) + data->cmd_ops->free(data); +} + +static struct event_command trigger_wprobe_set_cmd =3D { + .name =3D SET_WPROBE_STR, + .trigger_type =3D ETT_EVENT_WPROBE, + /* This triggers after when the event is recorded. */ + .flags =3D EVENT_CMD_FL_NEEDS_REC, + .parse =3D wprobe_trigger_cmd_parse, + .reg =3D wprobe_register_trigger, + .unreg =3D wprobe_unregister_trigger, + .set_filter =3D set_trigger_filter, + .trigger =3D wprobe_trigger, + .count_func =3D event_trigger_count, + .print =3D wprobe_trigger_print, + .init =3D event_trigger_init, + .free =3D wprobe_trigger_free, +}; + +static struct event_command trigger_wprobe_clear_cmd =3D { + .name =3D CLEAR_WPROBE_STR, + .trigger_type =3D ETT_EVENT_WPROBE, + /* This triggers after when the event is recorded. */ + .flags =3D EVENT_CMD_FL_NEEDS_REC, + .parse =3D wprobe_trigger_cmd_parse, + .reg =3D wprobe_register_trigger, + .unreg =3D wprobe_unregister_trigger, + .set_filter =3D set_trigger_filter, + .trigger =3D wprobe_trigger, + .count_func =3D event_trigger_count, + .print =3D wprobe_trigger_print, + .init =3D event_trigger_init, + .free =3D wprobe_trigger_free, +}; + +static __init int init_trigger_wprobe_cmds(void) +{ + int ret; + + ret =3D register_event_command(&trigger_wprobe_set_cmd); + if (WARN_ON(ret < 0)) + return ret; + ret =3D register_event_command(&trigger_wprobe_clear_cmd); + if (WARN_ON(ret < 0)) + unregister_event_command(&trigger_wprobe_set_cmd); + + if (!ret) + wprobe_trigger_global_enabled =3D 1; + + return ret; +} +fs_initcall(init_trigger_wprobe_cmds); +#endif /* CONFIG_WPROBE_TRIGGERS */ From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 2ECAE3203B6; Sun, 2 Aug 2026 08:20:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658815; cv=none; b=RJJTNdL8/p/wut7SI8LFdgw7ZzUZHuer2ompzrAEOZ2Tc9Q+LHu4ETID3rn5Bmr8CTqDH/hWvK2wpIpjwZ0jFUE3JkXI00Hc1NHEy0jrJmerQf46AnkikczvVTCkd05xLJ4h1kntcjlXPLu+2rIhKlv4B33hUYZcPVXqqEqP2rg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658815; c=relaxed/simple; bh=ViOVyO8yDqguHkU+X02XP0Bs/msFq4/hK8uTCxWGmGY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=OOQzQ/lxx1+4FjeDnYQnKeFC1SPh8wguwAsxeww0El6Hzsr5UNBk16yyF5UVgpSBPXJVoc3nObyqiiRE7KbkgP/9Iq60WqMMRt79/jRMFVpAvDhbDnIxdK8/S/OrcrMsX4WtGkiV4NYlq7MEj2G2LcliDF6VCBDi94G/1f5MJTA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=fb2cAbBl; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="fb2cAbBl" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0F4B91F00AC4; Sun, 2 Aug 2026 08:20:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658812; bh=JJ9Ut0yjyuZ90u+R8cbjLYmw1iskToab/1RjRS9W24Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=fb2cAbBlXiRqIJed4RgHx2UlOh9J7Mp9TspFsomOUka0fCEQ9iJ8jJGFPFbHlfgND bYaMI5lnGDykAM9ZzQIBcYsRFHIbBDlm+V+a2kFvmvaw8cwueQ7LLG39ufO+fxy7Vm rrgiiJtzdxRDucwcNUer514YVs4889wpe/KbfID/z2nxfhRn7ZdAusbjLV3+hKXzEt VooMpndTQlLFDrmHr4W8me4PxWRK/0BAsMRdFRv9Rfg4gOKwQX55+8x77aLDJL4Heu 4jhj6MSt25OskhBk653SUWeefUZ0b+ZqUkAOuOch4VThMZpMf4FcOfgRMw1kAO/0pq pLvdgtd8B4d1g== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 09/11] selftests: ftrace: Add wprobe trigger testcase Date: Sun, 2 Aug 2026 17:20:07 +0900 Message-ID: <178565880692.714490.8439336315058403320.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add a testcase for checking wprobe trigger. This sets set_wprobe and clear_wprobe triggers on fprobe event and static trace event to monitor memory accesses within the trace-events-sample kernel module. Signed-off-by: Masami Hiramatsu (Google) --- Changes in v11: - Update testcase to use trace-events-sample kernel module instead of VFS file operations. --- tools/testing/selftests/ftrace/config | 1=20 .../ftrace/test.d/trigger/trigger-wprobe.tc | 85 ++++++++++++++++= ++++ 2 files changed, 86 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-w= probe.tc diff --git a/tools/testing/selftests/ftrace/config b/tools/testing/selftest= s/ftrace/config index d2f503722020..ecdee77f360f 100644 --- a/tools/testing/selftests/ftrace/config +++ b/tools/testing/selftests/ftrace/config @@ -28,3 +28,4 @@ CONFIG_TRACER_SNAPSHOT=3Dy CONFIG_UPROBES=3Dy CONFIG_UPROBE_EVENTS=3Dy CONFIG_WPROBE_EVENTS=3Dy +CONFIG_WPROBE_TRIGGERS=3Dy diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.t= c b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc new file mode 100644 index 000000000000..4071fe0c878b --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc @@ -0,0 +1,85 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: event trigger - test wprobe trigger +# requires: dynamic_events "w[:[/][]] [r|w|rw]@[:= ]":README events/sched/sched_process_fork/trigger + +rmmod trace-events-sample ||: +if ! modprobe trace-events-sample ; then + echo "No trace-events sample module - please make CONFIG_SAMPLE_TRACE_EV= ENTS=3Dm" + exit_unresolved +fi + +cleanup_wprobe_triggers() { + if [ -f events/fprobes/testevent/trigger ]; then + reset_trigger_file events/fprobes/testevent/trigger || true + fi + if [ -f events/sample-trace/foo_bar_with_fn/trigger ]; then + reset_trigger_file events/sample-trace/foo_bar_with_fn/trigger || true + fi + echo 0 > events/enable 2>/dev/null || true + echo > dynamic_events 2>/dev/null || true + sleep 1 + rmmod trace-events-sample 2>/dev/null || true + return 0 +} + +trap cleanup_wprobe_triggers EXIT + +echo 0 > tracing_on + +# we will skip this test if fprobe is not supported. +if ! grep -Fq "f[:[/][]] [%return] []" READ= ME; then + echo "UNRESOLVED: fprobe is not supported" + exit_unresolved +fi + +# we will skip this test if the target function does not exist. +if ! grep -wq "sample_timer_cb" /proc/kallsyms; then + echo "UNRESOLVED: sample_timer_cb not found" + exit_unresolved +fi + +:;: "Add a wprobe event used by trigger" ;: +echo 'w:watch rw@0:8 address=3D$addr value=3D$value' > dynamic_events + +:;: "Add events for triggering wprobe" ;: +echo 'f:fprobes/testevent sample_timer_cb timer=3Dt' >> dynamic_events + +:;: "Enable all events before setting triggers" ;: +echo 1 > tracing_on +echo 1 >> events/fprobes/testevent/enable +echo 1 >> events/sample-trace/foo_bar_with_fn/enable + +:;: "Set set_wprobe trigger on testevent" ;: +echo 'set_wprobe:watch:timer' >> events/fprobes/testevent/trigger +cat events/fprobes/testevent/trigger | grep ^set_wprobe + +# Wait for sample_timer_cb to fire and set_wprobe trigger to activate +sleep 2 + +:;: "Check set_wprobe trigger activated the watchpoint" ;: +cat trace | grep watch + +:;: "Set clear_wprobe trigger on foo_bar_with_fn" ;: +echo 'clear_wprobe:watch' >> events/sample-trace/foo_bar_with_fn/trigger +cat events/sample-trace/foo_bar_with_fn/trigger | grep ^clear_wprobe + +# Clear trace and wait to ensure no new watchpoint events are generated +clear_trace +sleep 1 + +:;: "Ensure clear_wprobe trigger deactivated the watchpoint" ;: +! grep -q watch trace + +:;: "Remove wprobe triggers" ;: +echo '!set_wprobe:watch:timer' >> events/fprobes/testevent/trigger +echo '!clear_wprobe:watch' >> events/sample-trace/foo_bar_with_fn/trigger +! grep ^set_wprobe events/fprobes/testevent/trigger +! grep ^clear_wprobe events/sample-trace/foo_bar_with_fn/trigger + +:;: "Disable events and remove dynamic events" ;: +echo 0 > events/enable +echo > dynamic_events +clear_trace + +exit 0 From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 1A0033203B6; Sun, 2 Aug 2026 08:20:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658825; cv=none; b=W88n7KwglCqSgEoI0mC5WNrQXtmyxo5yhexhV02+fNcOh9uD0pDSHzfT2PfXvWPW1w9fUGngSvnAu1FCXNead+mYueB526WXBSKuqNQqslRep3zRHozumxvGHZAVVRGqnlEaLaFmL3zKiNV3wntZ8Sdc4yEEQIExoTpOp7oMGfU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658825; c=relaxed/simple; bh=H08wUjjwkKNAfGXA/aTivlHCK3GXh8nFiYIyaLFsc4k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=l5KHSymvNl59UbsqmQkIsKyUjBltFJvRBTSX5YqXycSiU6g4owyEizuiBPowq2npO3yLA2sMEohBwh8ZQ5OO6+DVnbCjbT4kwJ23Ry6ng24+kVHwmh5JDmBgssIUmc566xYhbVFtA7Odv4lvisEIeHo1URyyiOHL6OBwwNfmYQs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=T7m12BSO; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="T7m12BSO" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 81C7D1F00AC4; Sun, 2 Aug 2026 08:20:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658823; bh=Tdrw/5yTYgh/qdWgDYAeuaoaNlZs2dP5Wt6z39VZXHM=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=T7m12BSO4cAKeZqwKM5EOJE+Gebv70LsvgGk8w+vbEf8Zx0RLb2hykOEimpVPciS1 H928jVpkHpZo9yBIHnUSpukS1Uslxf9hg167s+6o01sX1oIsZoL/6khLWJBj24CalU 1847w8YihP+oKEpLPrVyOukFOp57OOOpStli/6vBHCAgFNpLdLw1sxKJv8eWPb6cX0 ByPZ3J/2UHMVLi7XDYC9XZMM07dcU6m/74P2pyvgouLkvGXJXXa6pOvkVBufrtnrI4 73meTZVIYFVnFhLh/apRV8CSbVhjPSzt+oIjAHZsDmFuU686Dnmo+Tpxr0o+tju2GK Jr91cevnQKpFQ== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 10/11] tracing/wprobe: Support BTF typecast in fetchargs Date: Sun, 2 Aug 2026 17:20:18 +0900 Message-ID: <178565881815.714490.8235446972645905404.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Allow BTF typecast syntax (STRUCT)FETCHARG->MEMBER in wprobe event fetchargs. Previously, handle_typecast() rejected any probe context that was not a function entry/return or tracepoint event probe. Wprobe events use $addr (the accessed address) and $value (the value at that address). By enabling BTF typecast, users can now cast these to a concrete struct type and access its fields directly. For example: echo 'w:watch rw@0:8 dflag=3D(dentry)$addr->d_flags' >> dynamic_events With a set_wprobe trigger pointing the watchpoint at a dentry address, the resulting trace shows d_flags being accessed at that location. Note that $addr and $value are restricted to kernel-space memory, which is consistent with the existing TPARG_FL_KERNEL flag used when parsing wprobe fetchargs. Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- Changes in v11: - Update trigger-wprobe-btf-typecast.tc to use trace-events-sample kernel module. - Fix commit comment. Changes in v9: - Newly added. --- kernel/trace/trace_probe.c | 13 +++- kernel/trace/trace_probe.h | 5 + tools/testing/selftests/ftrace/config | 1=20 .../test.d/trigger/trigger-wprobe-btf-typecast.tc | 72 ++++++++++++++++= ++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-w= probe-btf-typecast.tc diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 9f4cad18977a..af8fb2a246b6 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -889,6 +889,16 @@ static int query_btf_struct(const char *sname, struct = traceprobe_parse_context * ctx->struct_btf =3D NULL; } =20 + if (ctx->btf) { + id =3D btf_find_by_name_kind(ctx->btf, sname, BTF_KIND_STRUCT); + if (id > 0) { + btf_get(ctx->btf); + ctx->struct_btf =3D ctx->btf; + ctx->last_struct =3D btf_type_by_id(ctx->struct_btf, id); + return 0; + } + } + id =3D bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf); if (id < 0) return id; @@ -964,7 +974,8 @@ static int handle_typecast(char *arg, struct traceprobe= _parse_context *ctx) =20 if (!(tparg_is_event_probe(ctx->flags) || tparg_is_function_entry(ctx->flags) || - tparg_is_function_return(ctx->flags))) { + tparg_is_function_return(ctx->flags) || + tparg_is_wprobe(ctx->flags))) { trace_probe_log_err(ctx->offset, NOSUP_BTFARG); return -EOPNOTSUPP; } diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 6543d4c2cda5..6e927bb0e8dc 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -437,6 +437,11 @@ static inline bool tparg_is_event_probe(unsigned int f= lags) return !!(flags & TPARG_FL_TEVENT); } =20 +static inline bool tparg_is_wprobe(unsigned int flags) +{ + return !!(flags & TPARG_FL_WPROBE); +} + /* Each typecast consumes nested level. So the max number of typecast is 8= . */ #define TRACEPROBE_MAX_NESTED_LEVEL 8 =20 diff --git a/tools/testing/selftests/ftrace/config b/tools/testing/selftest= s/ftrace/config index ecdee77f360f..f067874902ed 100644 --- a/tools/testing/selftests/ftrace/config +++ b/tools/testing/selftests/ftrace/config @@ -1,5 +1,6 @@ CONFIG_BPF_SYSCALL=3Dy CONFIG_DEBUG_INFO_BTF=3Dy +CONFIG_DEBUG_INFO_BTF_MODULES=3Dy CONFIG_DEBUG_INFO_DWARF4=3Dy CONFIG_EPROBE_EVENTS=3Dy CONFIG_FPROBE=3Dy diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-b= tf-typecast.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wpro= be-btf-typecast.tc new file mode 100644 index 000000000000..3f2bebb28837 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-type= cast.tc @@ -0,0 +1,72 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: event trigger - test wprobe trigger with BTF typecast fetch= args +# requires: dynamic_events "w[:[/][]] [r|w|rw]@[:= ]":README events/sched/sched_process_fork/trigger "[(structname[,field])][->field[->field|.field...]]":README + +rmmod trace-events-sample ||: +if ! modprobe trace-events-sample ; then + echo "No trace-events sample module - please make CONFIG_SAMPLE_TRACE_EV= ENTS=3Dm" + exit_unresolved +fi + +cleanup_wprobe_triggers() { + if [ -f events/fprobes/testevent/trigger ]; then + reset_trigger_file events/fprobes/testevent/trigger || true + fi + echo 0 > events/enable 2>/dev/null || true + echo > dynamic_events 2>/dev/null || true + sleep 1 + rmmod trace-events-sample 2>/dev/null || true + return 0 +} + +trap cleanup_wprobe_triggers EXIT + +echo 0 > tracing_on + +# we will skip this test if fprobe is not supported. +if ! grep -Fq "f[:[/][]] [%return] []" READ= ME; then + echo "UNRESOLVED: fprobe is not supported" + exit_unresolved +fi + +# we will skip this test if the target function does not exist. +if ! grep -wq "sample_timer_cb" /proc/kallsyms; then + echo "UNRESOLVED: sample_timer_cb not found" + exit_unresolved +fi + +:;: "Add a wprobe event with BTF typecast fetchargs" ;: +# (foo_timer_data,timer)$addr->counter reads counter from struct foo_timer= _data via BTF typecast +echo 'w:watch rw@0:8 address=3D$addr counter=3D(foo_timer_data,timer)$addr= ->counter' >> dynamic_events + +:;: "Check the wprobe event is registered with counter field" ;: +grep -q "counter" dynamic_events + +:;: "Add fprobe event for sample_timer_cb" ;: +echo 'f:fprobes/testevent sample_timer_cb timer=3Dt' >> dynamic_events + +:;: "Enable all events before setting triggers" ;: +echo 1 > tracing_on +echo 1 >> events/fprobes/testevent/enable + +:;: "Set set_wprobe trigger on testevent" ;: +echo 'set_wprobe:watch:timer' >> events/fprobes/testevent/trigger +cat events/fprobes/testevent/trigger | grep ^set_wprobe + +# Wait for sample_timer_cb to fire and set_wprobe trigger to activate +sleep 3 + +:;: "Check set_wprobe trigger activated the watchpoint" ;: +cat trace | grep watch + +:;: "Remove wprobe triggers" ;: +echo '!set_wprobe:watch:timer' >> events/fprobes/testevent/trigger +! grep ^set_wprobe events/fprobes/testevent/trigger + +:;: "Disable events and remove dynamic events" ;: +echo 0 > events/enable +echo > dynamic_events +clear_trace + +exit 0 From nobody Sat Oct 3 12:03:40 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 B959D1C28E; Sun, 2 Aug 2026 08:20:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658839; cv=none; b=hsUbLwv6mXV//dn7ZWc/klnI9DHziW5GdZZjIAiw9aoUpbNxAn++WvT0mqJfnaWh1+YZGG9M8GwdZWiDig/aLqefiQUxtbCTjSVJndKqtG8zapHTPDc8emP3eyxomeVPzIdgPgDU/+KhWY35rwaPp4WRnlsLZBEjd7kFrWXt2Fs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785658839; c=relaxed/simple; bh=+yaRAa3iVwMuPsAwlnfT1/m5O40XZLIBty6vrotlxA8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=fLfwy7+5cvEWC9MGDMmjIlMbpqHv40P4df3UBnsh5IAs92dmBro+ZpKsnrNWlxs/EdnYZFHPdXulhjZ+8fY5RVauA9HiJFggvOQvnQhX1uSC0XF1pKLDXcTzGilgM/cPRW/puWCUHPYWL3B2MaAsB7I8rI2P5hFEUEc/+Hc9D7s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=TJtCC8ra; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="TJtCC8ra" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 774051F00AC4; Sun, 2 Aug 2026 08:20:31 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1785658835; bh=+LqOBy2ngMM4xWHUH3tp7KNp5UNBH05WCZeBTt5E6SY=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=TJtCC8ra2dHoT/r8PYW3UjwmWnOG6UycEWPqFsMWp5V/0EB+/XJSy/1CwGU+yMIP5 1rbediqXE+jvtY4ILhBLzoQPjA/PPBj0LNPXjG1cUlLJ6gwGRv8B4scqF4EGm6UpsO jzRbA/6dvDRr/Xr2z446Ye269GTtbaFmUC3Eu1xUUkvrsMQYOP+bJq3Uv2LtmQgu7Z eZunYL/v165zUXAGT0g/lOdSLEt6bJsPXuGDjA+k//ELUpfkd6nQoIitsG+QUrZkGR AWFyXiyFGFl+mHlBdOcTRA69OjXflKFuYr4bF64p4cDKwtuWA3M0kCJNB5lK3eAgRk mew455E4bERVQ== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Jinchao Wang , Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [PATCH v11 11/11] tracing/wprobe: Support BTF typecast in wprobe trigger command Date: Sun, 2 Aug 2026 17:20:29 +0900 Message-ID: <178565882911.714490.6743463801101196758.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178565870538.714490.11309825813968306287.stgit@devnote2> References: <178565870538.714490.11309825813968306287.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Extend the set_wprobe trigger syntax to support automatic BTF-based offset calculation using the form: set_wprobe:WPEVENT:(TYPE[,ASGN])FIELD->MEMBER[.SUBMEMBER[...]] Previously, the ADJUST value in FIELD[+/-ADJUST] had to be a numeric literal, requiring the user to know the exact byte offset of the target struct member. With this change, if the FIELD portion starts with (STRUCTTYPE), the offset of MEMBER within STRUCTTYPE is automatically resolved via BTF. This allows symbolic, kernel-version-independent watchpoint placement at specific struct fields. For example, to watch when the d_inode pointer inside a dentry is modified (not just when the dentry itself is accessed): echo 'w:watch rw@0:8 address=3D$addr value=3D$value' >> dynamic_events echo 'f:truncate do_truncate dentry=3D$arg2' >> dynamic_events echo 'set_wprobe:watch:(dentry)dentry->d_inode' \ >> events/fprobes/truncate/trigger Here, "dentry" is a field in the fprobe event record. The BTF lookup resolves offsetof(struct dentry, d_inode) at set_wprobe parse time, so no manual numeric offset is needed. Signed-off-by: Masami Hiramatsu (Google) --- Changes in v11: - Fix BTF member offset calculation and bitfield check in get_offset_of_field(). - Update trigger-wprobe-btf-offset.tc to use trace-events-sample kernel module. Changes in v10: - Newly added. --- kernel/trace/trace_wprobe.c | 201 ++++++++++++++++= +--- .../test.d/trigger/trigger-wprobe-btf-offset.tc | 74 +++++++ 2 files changed, 245 insertions(+), 30 deletions(-) create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-w= probe-btf-offset.tc diff --git a/kernel/trace/trace_wprobe.c b/kernel/trace/trace_wprobe.c index 5a034dce0845..add7eae03917 100644 --- a/kernel/trace/trace_wprobe.c +++ b/kernel/trace/trace_wprobe.c @@ -27,6 +27,7 @@ #include =20 #include "trace.h" +#include "trace_btf.h" #include "trace_dynevent.h" #include "trace_probe.h" #include "trace_probe_kernel.h" @@ -935,9 +936,10 @@ static int wprobe_trigger_print(struct seq_file *m, seq_printf(m, ":count=3D%ld", wprobe_data->count); =20 if (data->filter_str) - seq_printf(m, " if %s\n", data->filter_str); - else - seq_putc(m, '\n'); + seq_printf(m, " if %s", data->filter_str); + + seq_printf(m, " # offset:%d adjust:%ld\n", + wprobe_data->offset, wprobe_data->adjust); =20 return 0; } @@ -976,6 +978,155 @@ static void wprobe_trigger_free(struct event_trigger_= data *data) } } =20 +#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS + +static int get_offset_of_field(struct btf *btf, const struct btf_type *typ= e, char *field_name) +{ + const struct btf_member *field; + int bitoffs =3D 0; + u32 anon_offs; + char *next; + + do { + next =3D strchr(field_name, '.'); + if (next) + *next++ =3D '\0'; + + field =3D btf_find_struct_member(btf, type, field_name, &anon_offs); + if (IS_ERR_OR_NULL(field)) + return -ENOENT; + + if (btf_type_kflag(type)) { + /* Reject bitfield member access */ + if (BTF_MEMBER_BITFIELD_SIZE(field->offset)) + return -EINVAL; + bitoffs +=3D anon_offs + BTF_MEMBER_BIT_OFFSET(field->offset); + } else { + bitoffs +=3D anon_offs + field->offset; + } + + field_name =3D next; + if (next) { + type =3D btf_type_skip_modifiers(btf, field->type, NULL); + if (!type) + return -ENOENT; + } + } while (next); + return bitoffs / BITS_PER_BYTE; +} + +/* btf_put(NULL) is acceptable. */ +DEFINE_FREE(btf_put, struct btf *, btf_put(_T)) + +/* parse typecast: (TYPE[,ASGN])EVENT_FIELD->FIELD[.SUBFIELD...] and set a= djust. */ +static int wprobe_trigger_typecast_parse(char **field_str_ptr, + struct wprobe_trigger_data *wprobe_data) +{ + struct btf *btf __free(btf_put) =3D NULL; + const struct btf_type *type; + char *assign_field; + char *event_field; + char *type_field; + char *type_name; + char *offs; + long val =3D 0; + int id; + int adjust; + + type_name =3D *field_str_ptr + 1; + event_field =3D strchr(type_name, ')'); + if (!event_field) + return -EINVAL; + *event_field++ =3D '\0'; + + /* Check the optional assign field. */ + assign_field =3D strchr(type_name, ','); + if (assign_field) + *assign_field++ =3D '\0'; + + /* Get the type field name. */ + type_field =3D strstr(event_field, "->"); + if (!type_field) + return -EINVAL; + *type_field =3D '\0'; + type_field +=3D 2; + + offs =3D strpbrk(type_field, "+-"); + if (offs) { + if (kstrtol(offs, 0, &val) < 0) + return -EINVAL; + *offs =3D '\0'; + } + + /* find type from BTF */ + id =3D bpf_find_btf_id(type_name, BTF_KIND_STRUCT, &btf); + if (id < 0) + return id; + + type =3D btf_type_by_id(btf, id); + if (!type) + return -EINVAL; + + adjust =3D get_offset_of_field(btf, type, type_field); + if (adjust < 0) + return adjust; + wprobe_data->adjust =3D adjust + val; + + if (assign_field) { + /* assign_field should be a struct field */ + adjust =3D get_offset_of_field(btf, type, assign_field); + if (adjust < 0) + return adjust; + wprobe_data->adjust -=3D adjust; + } + + *field_str_ptr =3D event_field; + return 0; +} +#else +static int wprobe_trigger_typecast_parse(char **field_str_ptr, + struct wprobe_trigger_data *wprobe_data) +{ + return -EOPNOTSUPP; +} +#endif /* CONFIG_PROBE_EVENTS_BTF_ARGS */ + +static int wprobe_trigger_field_parse(char *field_str, struct trace_event_= file *file, + struct wprobe_trigger_data *wprobe_data) +{ + struct ftrace_event_field *field; + char *offs; + + if (field_str[0] =3D=3D '(') { + int ret =3D wprobe_trigger_typecast_parse(&field_str, wprobe_data); + + if (ret < 0) + return ret; + } else { + offs =3D strpbrk(field_str, "+-"); + if (offs) { + long val; + + if (kstrtol(offs, 0, &val) < 0) + return -EINVAL; + wprobe_data->adjust =3D val; + *offs =3D '\0'; + } + } + + field =3D trace_find_event_field(file->event_call, field_str); + if (!field) + return -ENOENT; + if (field->size !=3D sizeof(void *)) + return -ENOEXEC; + wprobe_data->offset =3D field->offset; + wprobe_data->field =3D kstrdup(field_str, GFP_KERNEL); + if (!wprobe_data->field) + return -ENOMEM; + + return 0; +} + static int wprobe_trigger_cmd_parse(struct event_command *cmd_ops, struct trace_event_file *file, char *glob, char *cmd, @@ -987,11 +1138,9 @@ static int wprobe_trigger_cmd_parse(struct event_comm= and *cmd_ops, */ struct wprobe_trigger_data *wprobe_data __free(free_wprobe_trigger_data) = =3D NULL; struct event_trigger_data *trigger_data __free(kfree) =3D NULL; - char *event_str, *field_str, *count_str; - struct ftrace_event_field *field =3D NULL; + char *event_str, *count_str, *comment; struct trace_event_file *wprobe_file; struct trace_array *tr =3D file->tr; - struct trace_event_call *event; bool remove, clear =3D false; struct trace_wprobe *tw; char *param, *filter; @@ -1002,6 +1151,10 @@ static int wprobe_trigger_cmd_parse(struct event_com= mand *cmd_ops, if (!strcmp(cmd, CLEAR_WPROBE_STR)) clear =3D true; =20 + comment =3D strchr(param_and_filter, '#'); + if (comment) + *comment =3D '\0'; + if (event_trigger_empty_param(param_and_filter)) return -EINVAL; =20 @@ -1032,34 +1185,22 @@ static int wprobe_trigger_cmd_parse(struct event_co= mmand *cmd_ops, =20 /* clear_wprobe does not need field. */ if (!clear) { - char *offs; + char *field_str =3D strsep(¶m, ":"); =20 - /* Find target field, which must be equivarent to "void *" */ - field_str =3D strsep(¶m, ":"); if (!field_str) return -EINVAL; - - offs =3D strpbrk(field_str, "+-"); - if (offs) { - long val; - - if (kstrtol(offs, 0, &val) < 0) - return -EINVAL; - wprobe_data->adjust =3D val; - *offs =3D '\0'; + ret =3D wprobe_trigger_field_parse(field_str, file, wprobe_data); + if (ret < 0) + return ret; + } else if (param) { + char *orig_param =3D param; + char *field_str =3D strsep(¶m, ":"); + + ret =3D wprobe_trigger_field_parse(field_str, file, wprobe_data); + if (ret < 0) { + /* field_str was not a field, so it must be count_str */ + param =3D orig_param; } - - event =3D file->event_call; - field =3D trace_find_event_field(event, field_str); - if (!field) - return -ENOENT; - - if (field->size !=3D sizeof(void *)) - return -ENOEXEC; - wprobe_data->offset =3D field->offset; - wprobe_data->field =3D kstrdup(field_str, GFP_KERNEL); - if (!wprobe_data->field) - return -ENOMEM; } =20 /* count is optional, "unlimited" by default */ diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-b= tf-offset.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe= -btf-offset.tc new file mode 100644 index 000000000000..dda179a23282 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-offs= et.tc @@ -0,0 +1,74 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: event trigger - test set_wprobe trigger with BTF struct off= set +# requires: dynamic_events "w[:[/][]] [r|w|rw]@[:= ]":README events/sched/sched_process_fork/trigger "[(structname[,field])][->field[->field|.field...]]":README + +rmmod trace-events-sample ||: +if ! modprobe trace-events-sample ; then + echo "No trace-events sample module - please make CONFIG_SAMPLE_TRACE_EV= ENTS=3Dm" + exit_unresolved +fi + +cleanup_wprobe_triggers() { + if [ -f events/fprobes/testevent/trigger ]; then + reset_trigger_file events/fprobes/testevent/trigger || true + fi + echo 0 > events/enable 2>/dev/null || true + echo > dynamic_events 2>/dev/null || true + sleep 1 + rmmod trace-events-sample 2>/dev/null || true + return 0 +} + +trap cleanup_wprobe_triggers EXIT + +echo 0 > tracing_on + +# we will skip this test if fprobe is not supported. +if ! grep -Fq "f[:[/][]] [%return] []" READ= ME; then + echo "UNRESOLVED: fprobe is not supported" + exit_unresolved +fi + +# we will skip this test if the target function does not exist. +if ! grep -wq "sample_timer_cb" /proc/kallsyms; then + echo "UNRESOLVED: sample_timer_cb not found" + exit_unresolved +fi + +:;: "Add a wprobe event watching 8 bytes" ;: +echo 'w:watch rw@0:8 address=3D$addr value=3D$value' >> dynamic_events + +:;: "Add fprobe event for sample_timer_cb" ;: +# sample_timer_cb(struct timer_list *t) +# container_of(t, struct foo_timer_data, timer) +echo 'f:fprobes/testevent sample_timer_cb timer=3Dt' >> dynamic_events + +:;: "Enable all events before setting triggers" ;: +echo 1 > tracing_on +echo 1 >> events/fprobes/testevent/enable + +:;: "Set set_wprobe trigger using BTF struct offset resolution" ;: +# Syntax: set_wprobe:WPEVENT:(STRUCT,FIELD)EVENT_FIELD->MEMBER +# (foo_timer_data,timer) is the BTF struct type and field name +# timer->expires is the struct member whose offset is resolved automatical= ly via BTF +echo 'set_wprobe:watch:(foo_timer_data,timer)timer->timer.expires' >> even= ts/fprobes/testevent/trigger +cat events/fprobes/testevent/trigger | grep ^set_wprobe + +# Wait for sample_timer_cb to fire and set_wprobe trigger to activate +sleep 3 + +:;: "Check set_wprobe trigger activated the watchpoint" ;: +cat trace | grep watch + +:;: "Remove wprobe triggers" ;: +# Since we don't know actual offset of timer->expires in foo_timer_data, w= e use reset_trigger_file +reset_trigger_file events/fprobes/testevent/trigger +! grep ^set_wprobe events/fprobes/testevent/trigger + +:;: "Disable events and remove dynamic events" ;: +echo 0 > events/enable +echo > dynamic_events +clear_trace + +exit 0