* [PATCH 1/4] sparc64: kprobes: Single-step from an executable instruction slot
2026-10-09 18:03 [PATCH 0/4] sparc64: kprobes: Make kprobes work on sun4v Stian Halseth
@ 2026-10-09 18:03 ` Stian Halseth
2026-11-13 9:32 ` Masami Hiramatsu (Google)
2026-10-09 18:03 ` [PATCH 2/4] sparc64: kprobes: Fix up register and FP branches after single-step Stian Halseth
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Stian Halseth @ 2026-10-09 18:03 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Masami Hiramatsu, Naveen N Rao, sparclinux, linux-trace-kernel,
linux-kernel, Stian Halseth
kprobes single-steps the probed instruction from kprobe->ainsn.insn[],
which is part of the kprobe itself and so lives in the linear mapping.
The sun4v linear mapping is not executable, and every probe hit oopses:
sun4v_insn_access_exception: ADDR[fff800005b0cd66c] CTX[0000] TYPE[0006], going.
Allocate the slot with get_insn_slot(), as other architectures do. It
comes from executable memory in the module area.
Fixes: c4bce90ea2069 ("[SPARC64]: Deal with PTE layout differences in SUN4V.")
Closes: https://github.com/sparclinux/issues/issues/110
Signed-off-by: Stian Halseth <stian@itx.no>
---
arch/sparc/include/asm/kprobes.h | 7 +++++--
arch/sparc/kernel/kprobes.c | 12 ++++++++++++
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/arch/sparc/include/asm/kprobes.h b/arch/sparc/include/asm/kprobes.h
index aec742cd898f2..8cc9697846fa1 100644
--- a/arch/sparc/include/asm/kprobes.h
+++ b/arch/sparc/include/asm/kprobes.h
@@ -11,13 +11,16 @@
#include <linux/types.h>
#include <linux/percpu.h>
+#define __ARCH_WANT_KPROBES_INSN_SLOT
+
typedef u32 kprobe_opcode_t;
#define MAX_INSN_SIZE 2
#define kretprobe_blacklist_size 0
-#define arch_remove_kprobe(p) do {} while (0)
+struct kprobe;
+void arch_remove_kprobe(struct kprobe *p);
#define flush_insn_slot(p) \
do { flushi(&(p)->ainsn.insn[0]); \
@@ -29,7 +32,7 @@ void __kretprobe_trampoline(void);
/* Architecture specific copy of original instruction*/
struct arch_specific_insn {
/* copy of the original instruction */
- kprobe_opcode_t insn[MAX_INSN_SIZE];
+ kprobe_opcode_t *insn;
};
struct prev_kprobe {
diff --git a/arch/sparc/kernel/kprobes.c b/arch/sparc/kernel/kprobes.c
index 191bbaca99212..608efda8cb948 100644
--- a/arch/sparc/kernel/kprobes.c
+++ b/arch/sparc/kernel/kprobes.c
@@ -52,6 +52,10 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
if ((unsigned long) p->addr & 0x3UL)
return -EILSEQ;
+ p->ainsn.insn = get_insn_slot();
+ if (!p->ainsn.insn)
+ return -ENOMEM;
+
p->ainsn.insn[0] = *p->addr;
flushi(&p->ainsn.insn[0]);
@@ -62,6 +66,14 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
return 0;
}
+void __kprobes arch_remove_kprobe(struct kprobe *p)
+{
+ if (p->ainsn.insn) {
+ free_insn_slot(p->ainsn.insn, 0);
+ p->ainsn.insn = NULL;
+ }
+}
+
void __kprobes arch_arm_kprobe(struct kprobe *p)
{
*p->addr = BREAKPOINT_INSTRUCTION;
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH 1/4] sparc64: kprobes: Single-step from an executable instruction slot
2026-10-09 18:03 ` [PATCH 1/4] sparc64: kprobes: Single-step from an executable instruction slot Stian Halseth
@ 2026-11-13 9:32 ` Masami Hiramatsu (Google)
2026-11-13 11:09 ` Stian Halseth
0 siblings, 1 reply; 7+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-11-13 9:32 UTC (permalink / raw)
To: Stian Halseth
Cc: David S. Miller, Andreas Larsson, Naveen N Rao, sparclinux,
linux-trace-kernel, linux-kernel
Hi,
On Fri, 9 Oct 2026 20:03:32 +0200, Stian Halseth <stian@itx.no> wrote:
> diff --git a/arch/sparc/kernel/kprobes.c b/arch/sparc/kernel/kprobes.c
> index 191bbaca99212..608efda8cb948 100644
> --- a/arch/sparc/kernel/kprobes.c
> +++ b/arch/sparc/kernel/kprobes.c
> @@ -52,6 +52,10 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
> if ((unsigned long) p->addr & 0x3UL)
> return -EILSEQ;
>
> + p->ainsn.insn = get_insn_slot();
> + if (!p->ainsn.insn)
> + return -ENOMEM;
> +
> p->ainsn.insn[0] = *p->addr;
> flushi(&p->ainsn.insn[0]);
>
> @@ -62,6 +66,14 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
> return 0;
> }
>
> +void __kprobes arch_remove_kprobe(struct kprobe *p)
> +{
> + if (p->ainsn.insn) {
> + free_insn_slot(p->ainsn.insn, 0);
> + p->ainsn.insn = NULL;
> + }
With this change, the p->ainsn.insn becomes NULL here when kill_kprobe()
calls this function because (1) init.text is removed or (2) probed module
is unloaded.
If another module is loaded at the same region and another kprobe
is registered at the same address, register_aggr_kprobe() ->
init_aggr_kprobe() -> flush_insn_slot(NULL) is called. This can
cause a problem because it is defined as:
#define flush_insn_slot(p) \
do { flushi(&(p)->ainsn.insn[0]); \
flushi(&(p)->ainsn.insn[1]); \
flushi(&(p)->ainsn.insn[2]); \
} while (0)
Since the flush_insn_slot() is only needed if p->ainsn.insn is
embedded in the struct kprobe, as this changed it to insn_slot,
we don't need this macro anymore.
So you should update it as same as other arch like:
#define flush_insn_slot(p) do { } while (0)
Thanks,
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH 1/4] sparc64: kprobes: Single-step from an executable instruction slot
2026-11-13 9:32 ` Masami Hiramatsu (Google)
@ 2026-11-13 11:09 ` Stian Halseth
0 siblings, 0 replies; 7+ messages in thread
From: Stian Halseth @ 2026-11-13 11:09 UTC (permalink / raw)
To: Masami Hiramatsu (Google)
Cc: David S. Miller, Andreas Larsson, Naveen N Rao, sparclinux,
linux-trace-kernel, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1095 bytes --]
Hi Masami,
On Fri, 2026-11-13 at 18:32 +0900, Masami Hiramatsu (Google) wrote:
>
> With this change, the p->ainsn.insn becomes NULL here when kill_kprobe()
> calls this function because (1) init.text is removed or (2) probed module
> is unloaded.
>
> If another module is loaded at the same region and another kprobe
> is registered at the same address, register_aggr_kprobe() ->
> init_aggr_kprobe() -> flush_insn_slot(NULL) is called. This can
> cause a problem because it is defined as:
>
> #define flush_insn_slot(p) \
> do { flushi(&(p)->ainsn.insn[0]); \
> flushi(&(p)->ainsn.insn[1]); \
> flushi(&(p)->ainsn.insn[2]); \
> } while (0)
>
> Since the flush_insn_slot() is only needed if p->ainsn.insn is
> embedded in the struct kprobe, as this changed it to insn_slot,
> we don't need this macro anymore.
> So you should update it as same as other arch like:
>
> #define flush_insn_slot(p) do { } while (0)
Thanks for the review, I'll test and adopt that change in v2.
I'll check for any other feedback, then prepare the v2 later today.
/Stian
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/4] sparc64: kprobes: Fix up register and FP branches after single-step
2026-10-09 18:03 [PATCH 0/4] sparc64: kprobes: Make kprobes work on sun4v Stian Halseth
2026-10-09 18:03 ` [PATCH 1/4] sparc64: kprobes: Single-step from an executable instruction slot Stian Halseth
@ 2026-10-09 18:03 ` Stian Halseth
2026-10-09 18:03 ` [PATCH 3/4] sparc64: kprobes: Resume after a delay slot at the saved next PC Stian Halseth
2026-10-09 18:03 ` [PATCH 4/4] sparc64: kprobes: Handle annulled branches Stian Halseth
3 siblings, 0 replies; 7+ messages in thread
From: Stian Halseth @ 2026-10-09 18:03 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Masami Hiramatsu, Naveen N Rao, sparclinux, linux-trace-kernel,
linux-kernel, Stian Halseth
relbranch_fixup() moves the target of a PC-relative branch from the
single-step slot back to the probed instruction, but it only knows
call, Bicc and BPcc. A taken BPr (brz, brnz, ...) or FBPfcc/FBfcc is
taken for an absolute jump, and execution resumes at the target
relative to the slot:
Unable to handle kernel paging request at virtual address 000000000fffff98
TPC: <0xfffff98>
Treat them like the other PC-relative branches.
Link: https://github.com/sparclinux/issues/issues/110
Signed-off-by: Stian Halseth <stian@itx.no>
---
arch/sparc/kernel/kprobes.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/arch/sparc/kernel/kprobes.c b/arch/sparc/kernel/kprobes.c
index 608efda8cb948..38e6d9b51a207 100644
--- a/arch/sparc/kernel/kprobes.c
+++ b/arch/sparc/kernel/kprobes.c
@@ -219,12 +219,15 @@ static unsigned long __kprobes relbranch_fixup(u32 insn, struct kprobe *p,
if (regs->tnpc == regs->tpc + 0x4UL)
return real_pc + 0x8UL;
- /* The three cases are call, branch w/prediction,
- * and traditional branch.
+ /* The cases are call and the branches BPcc, Bicc, BPr, FBPfcc
+ * and FBfcc.
*/
if ((insn & 0xc0000000) == 0x40000000 ||
(insn & 0xc1c00000) == 0x00400000 ||
- (insn & 0xc1c00000) == 0x00800000) {
+ (insn & 0xc1c00000) == 0x00800000 ||
+ (insn & 0xc1c00000) == 0x00c00000 ||
+ (insn & 0xc1c00000) == 0x01400000 ||
+ (insn & 0xc1c00000) == 0x01800000) {
unsigned long ainsn_addr;
ainsn_addr = (unsigned long) &p->ainsn.insn[0];
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 3/4] sparc64: kprobes: Resume after a delay slot at the saved next PC
2026-10-09 18:03 [PATCH 0/4] sparc64: kprobes: Make kprobes work on sun4v Stian Halseth
2026-10-09 18:03 ` [PATCH 1/4] sparc64: kprobes: Single-step from an executable instruction slot Stian Halseth
2026-10-09 18:03 ` [PATCH 2/4] sparc64: kprobes: Fix up register and FP branches after single-step Stian Halseth
@ 2026-10-09 18:03 ` Stian Halseth
2026-10-09 18:03 ` [PATCH 4/4] sparc64: kprobes: Handle annulled branches Stian Halseth
3 siblings, 0 replies; 7+ messages in thread
From: Stian Halseth @ 2026-10-09 18:03 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Masami Hiramatsu, Naveen N Rao, sparclinux, linux-trace-kernel,
linux-kernel, Stian Halseth
After single-stepping an instruction that does not branch, execution
resumes at the saved next PC, followed by the probed address plus 8.
For an instruction in the delay slot of a taken branch the saved next
PC is the branch target, so after the target execution goes back to
the probed address plus 8 instead of continuing at the target plus 4.
Use the saved next PC plus 4, which equals the probed address plus 8
for any other instruction.
Link: https://github.com/sparclinux/issues/issues/110
Signed-off-by: Stian Halseth <stian@itx.no>
---
arch/sparc/kernel/kprobes.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/arch/sparc/kernel/kprobes.c b/arch/sparc/kernel/kprobes.c
index 38e6d9b51a207..327d83188a978 100644
--- a/arch/sparc/kernel/kprobes.c
+++ b/arch/sparc/kernel/kprobes.c
@@ -211,13 +211,14 @@ static int __kprobes kprobe_handler(struct pt_regs *regs)
*
*/
static unsigned long __kprobes relbranch_fixup(u32 insn, struct kprobe *p,
- struct pt_regs *regs)
+ struct pt_regs *regs,
+ struct kprobe_ctlblk *kcb)
{
unsigned long real_pc = (unsigned long) p->addr;
/* Branch not taken, no mods necessary. */
if (regs->tnpc == regs->tpc + 0x4UL)
- return real_pc + 0x8UL;
+ return kcb->kprobe_orig_tnpc + 0x4UL;
/* The cases are call and the branches BPcc, Bicc, BPr, FBPfcc
* and FBfcc.
@@ -294,7 +295,7 @@ static void __kprobes resume_execution(struct kprobe *p,
{
u32 insn = p->ainsn.insn[0];
- regs->tnpc = relbranch_fixup(insn, p, regs);
+ regs->tnpc = relbranch_fixup(insn, p, regs, kcb);
/* This assignment must occur after relbranch_fixup() */
regs->tpc = kcb->kprobe_orig_tnpc;
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 4/4] sparc64: kprobes: Handle annulled branches
2026-10-09 18:03 [PATCH 0/4] sparc64: kprobes: Make kprobes work on sun4v Stian Halseth
` (2 preceding siblings ...)
2026-10-09 18:03 ` [PATCH 3/4] sparc64: kprobes: Resume after a delay slot at the saved next PC Stian Halseth
@ 2026-10-09 18:03 ` Stian Halseth
3 siblings, 0 replies; 7+ messages in thread
From: Stian Halseth @ 2026-10-09 18:03 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Masami Hiramatsu, Naveen N Rao, sparclinux, linux-trace-kernel,
linux-kernel, Stian Halseth
The single-stepped copy of a branch has a break instruction in its
delay slot. An annulled branch that is not taken skips that slot and
runs past the copy, and a taken ba,a or cbcond branches away without
reaching it.
Put a second break instruction after the delay slot, and resume after
the original delay slot when it is hit. Refuse probes on ba,a, fba,a
and cbcond.
Link: https://github.com/sparclinux/issues/issues/110
Signed-off-by: Stian Halseth <stian@itx.no>
---
arch/sparc/include/asm/kprobes.h | 3 ++-
arch/sparc/kernel/kprobes.c | 43 ++++++++++++++++++++++++++++----
2 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/arch/sparc/include/asm/kprobes.h b/arch/sparc/include/asm/kprobes.h
index 8cc9697846fa1..2f0980da80d3b 100644
--- a/arch/sparc/include/asm/kprobes.h
+++ b/arch/sparc/include/asm/kprobes.h
@@ -15,7 +15,7 @@
typedef u32 kprobe_opcode_t;
-#define MAX_INSN_SIZE 2
+#define MAX_INSN_SIZE 3
#define kretprobe_blacklist_size 0
@@ -25,6 +25,7 @@ void arch_remove_kprobe(struct kprobe *p);
#define flush_insn_slot(p) \
do { flushi(&(p)->ainsn.insn[0]); \
flushi(&(p)->ainsn.insn[1]); \
+ flushi(&(p)->ainsn.insn[2]); \
} while (0)
void __kretprobe_trampoline(void);
diff --git a/arch/sparc/kernel/kprobes.c b/arch/sparc/kernel/kprobes.c
index 327d83188a978..dc1180d4c2338 100644
--- a/arch/sparc/kernel/kprobes.c
+++ b/arch/sparc/kernel/kprobes.c
@@ -21,7 +21,8 @@
*
* In the kprobe->ainsn.insn[] array we store the original
* instruction at index zero and a break instruction at
- * index one.
+ * index one, and another at index two for a branch that
+ * annuls its delay slot.
*
* When we hit a kprobe we:
* - Run the pre-handler
@@ -47,11 +48,34 @@ DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);
struct kretprobe_blackpoint kretprobe_blacklist[] = {{NULL, NULL}};
+/* cbcond has no delay slot and ba,a annuls it, so when taken the
+ * single-stepped copy branches away without reaching a break instruction.
+ */
+static bool __kprobes kprobe_insn_rejected(u32 insn)
+{
+ if (insn & 0xc0000000)
+ return false;
+
+ switch ((insn >> 22) & 0x7) {
+ case 1: /* BPcc */
+ case 2: /* Bicc */
+ case 5: /* FBPfcc */
+ case 6: /* FBfcc */
+ return (insn & 0x3e000000) == 0x30000000;
+ case 3: /* BPr, cbcond */
+ return insn & 0x10000000;
+ }
+ return false;
+}
+
int __kprobes arch_prepare_kprobe(struct kprobe *p)
{
if ((unsigned long) p->addr & 0x3UL)
return -EILSEQ;
+ if (kprobe_insn_rejected(*p->addr))
+ return -EINVAL;
+
p->ainsn.insn = get_insn_slot();
if (!p->ainsn.insn)
return -ENOMEM;
@@ -62,6 +86,9 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
p->ainsn.insn[1] = BREAKPOINT_INSTRUCTION_2;
flushi(&p->ainsn.insn[1]);
+ p->ainsn.insn[2] = BREAKPOINT_INSTRUCTION_2;
+ flushi(&p->ainsn.insn[2]);
+
p->opcode = *p->addr;
return 0;
}
@@ -295,12 +322,18 @@ static void __kprobes resume_execution(struct kprobe *p,
{
u32 insn = p->ainsn.insn[0];
- regs->tnpc = relbranch_fixup(insn, p, regs, kcb);
+ if (regs->tpc == (unsigned long) &p->ainsn.insn[2]) {
+ /* An untaken branch annulled its delay slot. */
+ regs->tpc = kcb->kprobe_orig_tnpc + 0x4UL;
+ regs->tnpc = kcb->kprobe_orig_tnpc + 0x8UL;
+ } else {
+ regs->tnpc = relbranch_fixup(insn, p, regs, kcb);
- /* This assignment must occur after relbranch_fixup() */
- regs->tpc = kcb->kprobe_orig_tnpc;
+ /* This assignment must occur after relbranch_fixup() */
+ regs->tpc = kcb->kprobe_orig_tnpc;
- retpc_fixup(regs, insn, (unsigned long) p->addr);
+ retpc_fixup(regs, insn, (unsigned long) p->addr);
+ }
regs->tstate = ((regs->tstate & ~TSTATE_PIL) |
kcb->kprobe_orig_tstate_pil);
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread