All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/2] riscv: kprobes: simulate nop and c.nop instructions
@ 2026-09-02  8:08 Xiaofeng Yuan
  2026-09-02  8:08 ` [PATCH v3 1/2] " Xiaofeng Yuan
  2026-09-02  8:08 ` [PATCH v3 2/2] riscv: kprobes: add nop and c.nop to the KUnit test Xiaofeng Yuan
  0 siblings, 2 replies; 3+ messages in thread
From: Xiaofeng Yuan @ 2026-09-02  8:08 UTC (permalink / raw)
  To: Nam Cao, Paul Walmsley, Palmer Dabbelt
  Cc: Albert Ou, linux-riscv, Xiaofeng Yuan

Per-hit XOL out-of-line execution of a probed nop costs an extra
exception round-trip (the slot's trailing breakpoint traps back into
the kernel), and for uprobes that round-trip is a full
kernel<->userspace one.  Since these instructions have no
architectural effect, simply advance the program counter inside the
breakpoint handler instead.  riscv_probe_decode_insn() is shared by
kprobes and uprobes, so both benefit.  It primarily matters for
uprobes on USDT sites: under the SystemTap SDT ABI, the recorded
probe location is by construction a plain nop or c.nop.

patch 1: simulate nop and c.nop in the breakpoint handler.
patch 2: extend the RISC-V kprobes KUnit test to cover both.

Measured on QEMU (RISC-V virt, emulated): per-hit cost of a uprobe
on a USDT-style nop site drops by roughly 80 percent; a kprobe on a
nop by roughly 40 percent.  (arm64 commit ac4ad5c09b34 measured ~2x
on real arm64 hardware for the same class of change.)

Changes since v2:
- patch 1: no code change; commit message reworked per Nam Cao's
  review:
  - dropped the kernel-text nop motivation (jump_label text sites
    cannot be kprobed; register_kprobe() rejects them via
    jump_label_text_reserved()), and the mcount claim (the function
    entry instruction is an auipc; the nop sits at +4 only).
  - corrected the attribution of the arm64 prior: ac4ad5c09b34 was
    motivated by uprobe/USDT benchmarks, not by a generic "single
    step is slow" claim.
  - described the per-hit saving precisely in XOL terms (one extra
    exception round-trip; for uprobes a full kernel<->userspace one).
  - added a USDT-form uprobe measurement (ftrace uprobe event,
    emulated QEMU), in the style of the arm64 series numbers.
- patch 2: unchanged.

Changes since v1:
- patch 1: add use-case justification and benchmark result to the
  commit message.
- patch 2: simplify test functions to load KPROBE_TEST_MAGIC
  directly (per review).

Verified by cross-compiling for RISC-V and running the kprobes KUnit
test in QEMU (ok 1 kprobes_riscv).

Xiaofeng Yuan (2):
  riscv: kprobes: simulate nop and c.nop instructions
  riscv: kprobes: add nop and c.nop to the KUnit test

 arch/riscv/include/asm/insn.h                 | 11 +++++++++
 arch/riscv/kernel/probes/decode-insn.c        |  6 +++++
 arch/riscv/kernel/probes/simulate-insn.c      | 14 +++++++++++
 arch/riscv/kernel/probes/simulate-insn.h      |  2 ++
 .../kernel/tests/kprobes/test-kprobes-asm.S   | 23 +++++++++++++++++++
 5 files changed, 56 insertions(+)

-- 
2.43.0


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v3 1/2] riscv: kprobes: simulate nop and c.nop instructions
  2026-09-02  8:08 [PATCH v3 0/2] riscv: kprobes: simulate nop and c.nop instructions Xiaofeng Yuan
@ 2026-09-02  8:08 ` Xiaofeng Yuan
  2026-09-02  8:08 ` [PATCH v3 2/2] riscv: kprobes: add nop and c.nop to the KUnit test Xiaofeng Yuan
  1 sibling, 0 replies; 3+ messages in thread
From: Xiaofeng Yuan @ 2026-09-02  8:08 UTC (permalink / raw)
  To: Nam Cao, Paul Walmsley, Palmer Dabbelt
  Cc: Albert Ou, linux-riscv, Xiaofeng Yuan

A kprobe or uprobe placed on a nop currently replays the instruction
out-of-line: the breakpoint trap runs the probe handler and redirects
execution to a copy of the instruction sitting in an XOL slot, and a
second breakpoint appended after the copy traps again to finish the
hit.  That costs an extra exception round-trip per hit; for uprobes
the slot runs in user mode, so the re-trap is a full
kernel<->userspace round-trip.

nop and c.nop have no architectural effect, so the replay can be
replaced by simply advancing the program counter when handling the
initial breakpoint, completing every hit within a single trap.
arm64 does the same in its probe-decode path, which is also shared
between kprobes and uprobes; see commit ac4ad5c09b34 ("arm64: insn:
Simulate nop instruction for better uprobe performance").

riscv_probe_decode_insn() is shared by kprobes and uprobes, so the
simulation applies to both.  It primarily matters for uprobes on
USDT probe sites: under the SystemTap SDT ABI (sys/sdt.h, parsed by
libbpf), the recorded probe location is by construction a plain nop
or c.nop, the same case that motivated the arm64 series.

Measured on QEMU (RISC-V virt, emulated): for a uprobe on a USDT
style nop site the per-hit cost drops by roughly 80 percent; for a
kprobe on a nop by roughly 40 percent.  On real arm64 hardware the
referenced commit measured ~2x.

Compile tested on RISC-V, and verified with the RISC-V kprobes
KUnit test which now covers nop and c.nop.

Signed-off-by: Xiaofeng Yuan <yuanxiaofeng@eswincomputing.com>
---
 arch/riscv/include/asm/insn.h            | 11 +++++++++++
 arch/riscv/kernel/probes/decode-insn.c   |  6 ++++++
 arch/riscv/kernel/probes/simulate-insn.c | 14 ++++++++++++++
 arch/riscv/kernel/probes/simulate-insn.h |  2 ++
 4 files changed, 33 insertions(+)

diff --git a/arch/riscv/include/asm/insn.h b/arch/riscv/include/asm/insn.h
index c3005573e8..5b3944a5fa 100644
--- a/arch/riscv/include/asm/insn.h
+++ b/arch/riscv/include/asm/insn.h
@@ -228,6 +228,15 @@
 #define RVG_MASK_EBREAK		0xffffffff
 #define RVG_MASK_SRET		0xffffffff
 
+/*
+ * NOP and C.NOP have no variable fields, so all bits must match.
+ * NOP is encoded as ADDI x0, x0, 0 (0x00000013), C.NOP as C.ADDI x0, 0 (0x0001).
+ */
+#define RVG_MATCH_NOP		0x00000013
+#define RVG_MASK_NOP		0xffffffff
+#define RVC_MATCH_C_NOP		0x0001
+#define RVC_MASK_C_NOP		0xffff
+
 #define __INSN_LENGTH_MASK	_UL(0x3)
 #define __INSN_LENGTH_GE_32	_UL(0x3)
 #define __INSN_OPCODE_MASK	_UL(0x7F)
@@ -262,6 +271,8 @@ __RISCV_INSN_FUNCS(c_ebreak, RVC_MASK_C_EBREAK, RVC_MATCH_C_EBREAK)
 __RISCV_INSN_FUNCS(ebreak, RVG_MASK_EBREAK, RVG_MATCH_EBREAK)
 __RISCV_INSN_FUNCS(sret, RVG_MASK_SRET, RVG_MATCH_SRET)
 __RISCV_INSN_FUNCS(fence, RVG_MASK_FENCE, RVG_MATCH_FENCE);
+__RISCV_INSN_FUNCS(nop, RVG_MASK_NOP, RVG_MATCH_NOP)
+__RISCV_INSN_FUNCS(c_nop, RVC_MASK_C_NOP, RVC_MATCH_C_NOP)
 
 /* special case to catch _any_ system instruction */
 static __always_inline bool riscv_insn_is_system(u32 code)
diff --git a/arch/riscv/kernel/probes/decode-insn.c b/arch/riscv/kernel/probes/decode-insn.c
index 65d9590bfb..d28408f0b7 100644
--- a/arch/riscv/kernel/probes/decode-insn.c
+++ b/arch/riscv/kernel/probes/decode-insn.c
@@ -44,5 +44,11 @@ riscv_probe_decode_insn(probe_opcode_t *addr, struct arch_probe_insn *api)
 	RISCV_INSN_SET_SIMULATE(auipc,		insn);
 	RISCV_INSN_SET_SIMULATE(branch,		insn);
 
+	/* Simulate NOP for better performance */
+	RISCV_INSN_SET_SIMULATE(nop,		insn);
+#ifdef CONFIG_RISCV_ISA_C
+	RISCV_INSN_SET_SIMULATE(c_nop,		insn);
+#endif
+
 	return INSN_GOOD;
 }
diff --git a/arch/riscv/kernel/probes/simulate-insn.c b/arch/riscv/kernel/probes/simulate-insn.c
index fa581590c1..3b22d3ad53 100644
--- a/arch/riscv/kernel/probes/simulate-insn.c
+++ b/arch/riscv/kernel/probes/simulate-insn.c
@@ -237,3 +237,17 @@ bool __kprobes simulate_c_beqz(u32 opcode, unsigned long addr, struct pt_regs *r
 {
 	return simulate_c_bnez_beqz(opcode, addr, regs, false);
 }
+
+bool __kprobes simulate_nop(u32 opcode, unsigned long addr, struct pt_regs *regs)
+{
+	instruction_pointer_set(regs, addr + 4);
+
+	return true;
+}
+
+bool __kprobes simulate_c_nop(u32 opcode, unsigned long addr, struct pt_regs *regs)
+{
+	instruction_pointer_set(regs, addr + 2);
+
+	return true;
+}
diff --git a/arch/riscv/kernel/probes/simulate-insn.h b/arch/riscv/kernel/probes/simulate-insn.h
index 44ebbc444d..7e0936613f 100644
--- a/arch/riscv/kernel/probes/simulate-insn.h
+++ b/arch/riscv/kernel/probes/simulate-insn.h
@@ -29,5 +29,7 @@ bool simulate_c_jr(u32 opcode, unsigned long addr, struct pt_regs *regs);
 bool simulate_c_jalr(u32 opcode, unsigned long addr, struct pt_regs *regs);
 bool simulate_c_bnez(u32 opcode, unsigned long addr, struct pt_regs *regs);
 bool simulate_c_beqz(u32 opcode, unsigned long addr, struct pt_regs *regs);
+bool simulate_nop(u32 opcode, unsigned long addr, struct pt_regs *regs);
+bool simulate_c_nop(u32 opcode, unsigned long addr, struct pt_regs *regs);
 
 #endif /* _RISCV_KERNEL_PROBES_SIMULATE_INSN_H */
-- 
2.43.0


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH v3 2/2] riscv: kprobes: add nop and c.nop to the KUnit test
  2026-09-02  8:08 [PATCH v3 0/2] riscv: kprobes: simulate nop and c.nop instructions Xiaofeng Yuan
  2026-09-02  8:08 ` [PATCH v3 1/2] " Xiaofeng Yuan
@ 2026-09-02  8:08 ` Xiaofeng Yuan
  1 sibling, 0 replies; 3+ messages in thread
From: Xiaofeng Yuan @ 2026-09-02  8:08 UTC (permalink / raw)
  To: Nam Cao, Paul Walmsley, Palmer Dabbelt
  Cc: Albert Ou, linux-riscv, Xiaofeng Yuan

Extend the RISC-V kprobes KUnit test with test_kprobes_nop and
test_kprobes_c_nop, covering both the 32-bit nop and the
compressed c.nop simulation paths.

Signed-off-by: Xiaofeng Yuan <yuanxiaofeng@eswincomputing.com>
---
 .../kernel/tests/kprobes/test-kprobes-asm.S   | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/arch/riscv/kernel/tests/kprobes/test-kprobes-asm.S b/arch/riscv/kernel/tests/kprobes/test-kprobes-asm.S
index f16deee9e0..06a9cb35c9 100644
--- a/arch/riscv/kernel/tests/kprobes/test-kprobes-asm.S
+++ b/arch/riscv/kernel/tests/kprobes/test-kprobes-asm.S
@@ -181,6 +181,25 @@ SYM_FUNC_END(test_kprobes_c_bnez)
 
 #endif /* CONFIG_RISCV_ISA_C */
 
+SYM_FUNC_START(test_kprobes_nop)
+	.option push
+	.option norvc
+test_kprobes_nop_addr:
+	nop
+	.option pop
+	li a0, KPROBE_TEST_MAGIC
+	ret
+SYM_FUNC_END(test_kprobes_nop)
+
+#ifdef CONFIG_RISCV_ISA_C
+SYM_FUNC_START(test_kprobes_c_nop)
+test_kprobes_c_nop_addr:
+	c.nop
+	li a0, KPROBE_TEST_MAGIC
+	ret
+SYM_FUNC_END(test_kprobes_c_nop)
+#endif /* CONFIG_RISCV_ISA_C */
+
 .section .rodata
 SYM_DATA_START(test_kprobes_addresses)
 	RISCV_PTR test_kprobes_add_addr1
@@ -197,7 +216,9 @@ SYM_DATA_START(test_kprobes_addresses)
 	RISCV_PTR test_kprobes_branch_addr6
 	RISCV_PTR test_kprobes_branch_addr7
 	RISCV_PTR test_kprobes_branch_addr8
+	RISCV_PTR test_kprobes_nop_addr
 #ifdef CONFIG_RISCV_ISA_C
+	RISCV_PTR test_kprobes_c_nop_addr
 	RISCV_PTR test_kprobes_branch_c_j_addr1
 	RISCV_PTR test_kprobes_branch_c_j_addr2
 	RISCV_PTR test_kprobes_c_jr_addr1
@@ -220,7 +241,9 @@ SYM_DATA_START(test_kprobes_functions)
 	RISCV_PTR test_kprobes_jalr
 	RISCV_PTR test_kprobes_auipc
 	RISCV_PTR test_kprobes_branch
+	RISCV_PTR test_kprobes_nop
 #ifdef CONFIG_RISCV_ISA_C
+	RISCV_PTR test_kprobes_c_nop
 	RISCV_PTR test_kprobes_c_j
 	RISCV_PTR test_kprobes_c_jr
 	RISCV_PTR test_kprobes_c_jalr
-- 
2.43.0


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-09-02  8:08 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02  8:08 [PATCH v3 0/2] riscv: kprobes: simulate nop and c.nop instructions Xiaofeng Yuan
2026-09-02  8:08 ` [PATCH v3 1/2] " Xiaofeng Yuan
2026-09-02  8:08 ` [PATCH v3 2/2] riscv: kprobes: add nop and c.nop to the KUnit test Xiaofeng Yuan

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.