A kprobe single-steps a copy of the probed instruction in
p->ainsn.insn[]. When the copy is a taken PC-relative branch, its target
is relative to the copy, and relbranch_fixup() moves it back to the
probed code. It only recognizes call, BPcc and Bicc, though. For a taken
BPr (brz, brnz, ...), FBfcc or FBPfcc it keeps the target as is, and the
kernel continues at the copy's address plus the branch displacement.

GCC often starts a function with a BPr on an argument. For example,
__se_sys_getcpu() begins with "brz,pn %o0". With a kprobe on it
("p:kprobes/kgetcpu __se_sys_getcpu" in kprobe_events), the first
getcpu(NULL, NULL, NULL) crashes the kernel in QEMU sun4u:

  init(1): Kernel illegal instruction [#1]
  TSTATE: 0000004411001603 TPC: fffff800048d63c0 TNPC: fffff8000492631c
  Kernel panic - not syncing: Fatal exception

Add the missing branch formats. BPr is matched with bit 28 clear. The
CBcond instructions of newer CPUs share its op2 value, but they have no
delay slot, so a taken one never reaches the single-step breakpoint and
this function.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: [email protected]
Assisted-by: LLM
Signed-off-by: Danish Khateeb <[email protected]>
---
 arch/sparc/kernel/kprobes.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/arch/sparc/kernel/kprobes.c b/arch/sparc/kernel/kprobes.c
index 191bbaca9921..9a26c57c8d33 100644
--- a/arch/sparc/kernel/kprobes.c
+++ b/arch/sparc/kernel/kprobes.c
@@ -207,12 +207,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 with a PC-relative
+        * displacement.
         */
-       if ((insn & 0xc0000000) == 0x40000000 ||
-           (insn & 0xc1c00000) == 0x00400000 ||
-           (insn & 0xc1c00000) == 0x00800000) {
+       if ((insn & 0xc0000000) == 0x40000000 ||        /* call */
+           (insn & 0xc1c00000) == 0x00400000 ||        /* BPcc */
+           (insn & 0xc1c00000) == 0x00800000 ||        /* Bicc */
+           (insn & 0xd1c00000) == 0x00c00000 ||        /* BPr */
+           (insn & 0xc1c00000) == 0x01400000 ||        /* FBPfcc */
+           (insn & 0xc1c00000) == 0x01800000) {        /* FBfcc */
                unsigned long ainsn_addr;
 
                ainsn_addr = (unsigned long) &p->ainsn.insn[0];
-- 
2.55.0


Reply via email to