[RFC 13/15] riscv: probes: sort out endian-ness

Ben Dooks
2024-12-20T15:58:30Z

Updated {k,u}probe code to deal with big endian mode where
the instruction stream is always in little endian.

Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
---
arch/riscv/kernel/probes/decode-insn.c | 2 +-
arch/riscv/kernel/probes/decode-insn.h | 5 +++++
arch/riscv/kernel/probes/kprobes.c | 30 ++++++++++++++++----------
arch/riscv/kernel/probes/uprobes.c | 10 ++++-----
4 files changed, 30 insertions(+), 17 deletions(-)

diff --git a/arch/riscv/kernel/probes/decode-insn.c b/arch/riscv/kernel/probes/decode-insn.c
index 65d9590bfb9f..5f30c10f7d8d 100644
--- a/arch/riscv/kernel/probes/decode-insn.c
+++ b/arch/riscv/kernel/probes/decode-insn.c
@@ -16,7 +16,7 @@
enum probe_insn __kprobes
riscv_probe_decode_insn(probe_opcode_t *addr, struct arch_probe_insn *api)
{
- probe_opcode_t insn = *addr;
+ probe_opcode_t insn = le32_to_cpu(*addr);

/*
* Reject instructions list:
diff --git a/arch/riscv/kernel/probes/decode-insn.h b/arch/riscv/kernel/probes/decode-insn.h
index 42269a7d676d..0515deb204b5 100644
--- a/arch/riscv/kernel/probes/decode-insn.h
+++ b/arch/riscv/kernel/probes/decode-insn.h
@@ -15,4 +15,9 @@ enum probe_insn {
enum probe_insn __kprobes
riscv_probe_decode_insn(probe_opcode_t *addr, struct arch_probe_insn *asi);

+static inline int read_insn_length(void *ptr)
+{
+ return GET_INSN_LENGTH(le16_to_cpu(*(__le16 *)ptr));
+}
+
#endif /* _RISCV_KERNEL_KPROBES_DECODE_INSN_H */
diff --git a/arch/riscv/kernel/probes/kprobes.c b/arch/riscv/kernel/probes/kprobes.c
index 474a65213657..aba684938284 100644
--- a/arch/riscv/kernel/probes/kprobes.c
+++ b/arch/riscv/kernel/probes/kprobes.c
@@ -24,13 +24,13 @@ post_kprobe_handler(struct kprobe *, struct kprobe_ctlblk *, struct pt_regs *);

static void __kprobes arch_prepare_ss_slot(struct kprobe *p)
{
- size_t len = GET_INSN_LENGTH(p->opcode);
- u32 insn = __BUG_INSN_32;
+ size_t len = read_insn_length(&p->opcode);
+ u32 insn = cpu_to_le32(__BUG_INSN_32);

p->ainsn.api.restore = (unsigned long)p->addr + len;

patch_text_nosync(p->ainsn.api.insn, &p->opcode, len);
- patch_text_nosync(p->ainsn.api.insn + len, &insn, GET_INSN_LENGTH(insn));
+ patch_text_nosync((void *)p->ainsn.api.insn + len, &insn, GET_INSN_LENGTH(__BUG_INSN_32));
}

static void __kprobes arch_prepare_simulate(struct kprobe *p)
@@ -58,7 +58,7 @@ static bool __kprobes arch_check_kprobe(struct kprobe *p)
if (tmp == addr)
return true;

- tmp += GET_INSN_LENGTH(*(u16 *)tmp);
+ tmp += read_insn_length((u16 *)tmp);
}

return false;
@@ -75,9 +75,9 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
return -EILSEQ;

/* copy instruction */
- p->opcode = (kprobe_opcode_t)(*insn++);
- if (GET_INSN_LENGTH(p->opcode) == 4)
- p->opcode |= (kprobe_opcode_t)(*insn) << 16;
+ *((u16 *)&p->opcode) = (*insn++);
+ if (read_insn_length(&p->opcode) == 4)
+ p->opcode = (kprobe_opcode_t)(*(u32 *)p->addr);

/* decode instruction */
switch (riscv_probe_decode_insn(p->addr, &p->ainsn.api)) {
@@ -107,16 +107,24 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
/* install breakpoint in text */
void __kprobes arch_arm_kprobe(struct kprobe *p)
{
- size_t len = GET_INSN_LENGTH(p->opcode);
- u32 insn = len == 4 ? __BUG_INSN_32 : __BUG_INSN_16;
+ size_t len = read_insn_length(&p->opcode);
+ u32 insn;
+
+ if (len == 4)
+ insn = cpu_to_le32(__BUG_INSN_32);
+ else {
+ insn = cpu_to_le16(__BUG_INSN_16);
+ insn |= cpu_to_le16(__BUG_INSN_16) << 16;
+ }

+ pr_info("%s: patching %px (%d bytes)\n", __func__, p->addr, (int)len);
patch_text(p->addr, &insn, len);
}

/* remove breakpoint from text */
void __kprobes arch_disarm_kprobe(struct kprobe *p)
{
- size_t len = GET_INSN_LENGTH(p->opcode);
+ size_t len = read_insn_length(&p->opcode);

patch_text(p->addr, &p->opcode, len);
}
@@ -336,7 +344,7 @@ kprobe_single_step_handler(struct pt_regs *regs)
struct kprobe *cur = kprobe_running();

if (cur && (kcb->kprobe_status & (KPROBE_HIT_SS | KPROBE_REENTER)) &&
- ((unsigned long)&cur->ainsn.api.insn[0] + GET_INSN_LENGTH(cur->opcode) == addr)) {
+ ((unsigned long)&cur->ainsn.api.insn[0] + read_insn_length(&cur->opcode) == addr)) {
kprobes_restore_local_irqflag(kcb, regs);
post_kprobe_handler(cur, kcb, regs);
return true;
diff --git a/arch/riscv/kernel/probes/uprobes.c b/arch/riscv/kernel/probes/uprobes.c
index 4b3dc8beaf77..e31c1dd337d5 100644
--- a/arch/riscv/kernel/probes/uprobes.c
+++ b/arch/riscv/kernel/probes/uprobes.c
@@ -12,9 +12,9 @@
bool is_swbp_insn(uprobe_opcode_t *insn)
{
#ifdef CONFIG_RISCV_ISA_C
- return (*insn & 0xffff) == UPROBE_SWBP_INSN;
+ return (*(u16 *)insn) == cpu_to_le16(UPROBE_SWBP_INSN);
#else
- return *insn == UPROBE_SWBP_INSN;
+ return *insn == cpu_to_le32(UPROBE_SWBP_INSN);
#endif
}

@@ -35,7 +35,7 @@ int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm,

opcode = *(probe_opcode_t *)(&auprobe->insn[0]);

- auprobe->insn_size = GET_INSN_LENGTH(opcode);
+ auprobe->insn_size = GET_INSN_LENGTH(le32_to_cpu(opcode));

switch (riscv_probe_decode_insn(&opcode, &auprobe->api)) {
case INSN_REJECTED:
@@ -172,8 +172,8 @@ void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,

/* Add ebreak behind opcode to simulate singlestep */
if (vaddr) {
- dst += GET_INSN_LENGTH(*(probe_opcode_t *)src);
- *(uprobe_opcode_t *)dst = __BUG_INSN_32;
+ dst += read_insn_length(src);
+ *(uprobe_opcode_t *)dst = cpu_to_le32(__BUG_INSN_32);
}

kunmap_atomic(kaddr);