LoongArch: Add uprobes support
authorTiezhu Yang <yangtiezhu@loongson.cn>
Thu, 29 Jun 2023 12:58:44 +0000 (20:58 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Thu, 29 Jun 2023 12:58:44 +0000 (20:58 +0800)
Uprobes is the user-space counterpart to kprobes, this patch adds
uprobes support for LoongArch.

Here is a simple example with CONFIG_UPROBE_EVENTS=y:

  # cat test.c
  #include <stdio.h>

  int add(int a, int b)
  {
     return a + b;
  }

  int main()
  {
  return add(2, 7);
  }
  # gcc test.c -o /tmp/test
  # nm /tmp/test | grep add
  0000000120004194 T add
  # cd /sys/kernel/debug/tracing
  # echo > uprobe_events
  # echo "p:myuprobe /tmp/test:0x4194 %r4 %r5" > uprobe_events
  # echo "r:myuretprobe /tmp/test:0x4194 %r4" >> uprobe_events
  # echo 1 > events/uprobes/enable
  # echo 1 > tracing_on
  # /tmp/test
  # cat trace
  ...
  #           TASK-PID     CPU#  |||||  TIMESTAMP  FUNCTION
  #              | |         |   |||||     |         |
              test-1060    [001] DNZff  1015.770620: myuprobe: (0x120004194) arg1=0x2 arg2=0x7
              test-1060    [001] DNZff  1015.770930: myuretprobe: (0x1200041f0 <- 0x120004194) arg1=0x9

Tested-by: Jeff Xie <xiehuan09@gmail.com>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
arch/loongarch/Kconfig
arch/loongarch/include/asm/uprobes.h [new file with mode: 0644]
arch/loongarch/kernel/Makefile
arch/loongarch/kernel/traps.c
arch/loongarch/kernel/uprobes.c [new file with mode: 0644]

index b787f8f..94ca147 100644 (file)
@@ -643,6 +643,9 @@ config ARCH_MMAP_RND_BITS_MIN
 config ARCH_MMAP_RND_BITS_MAX
        default 18
 
+config ARCH_SUPPORTS_UPROBES
+       def_bool y
+
 menu "Power management options"
 
 config ARCH_SUSPEND_POSSIBLE
diff --git a/arch/loongarch/include/asm/uprobes.h b/arch/loongarch/include/asm/uprobes.h
new file mode 100644 (file)
index 0000000..c8f5998
--- /dev/null
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __ASM_LOONGARCH_UPROBES_H
+#define __ASM_LOONGARCH_UPROBES_H
+
+#include <asm/inst.h>
+
+typedef u32 uprobe_opcode_t;
+
+#define MAX_UINSN_BYTES                8
+#define UPROBE_XOL_SLOT_BYTES  MAX_UINSN_BYTES
+
+#define UPROBE_SWBP_INSN       larch_insn_gen_break(BRK_UPROBE_BP)
+#define UPROBE_SWBP_INSN_SIZE  LOONGARCH_INSN_SIZE
+
+#define UPROBE_XOLBP_INSN      larch_insn_gen_break(BRK_UPROBE_XOLBP)
+
+struct arch_uprobe {
+       unsigned long   resume_era;
+       u32     insn[2];
+       u32     ixol[2];
+       bool    simulate;
+};
+
+struct arch_uprobe_task {
+       unsigned long saved_trap_nr;
+};
+
+#ifdef CONFIG_UPROBES
+bool uprobe_breakpoint_handler(struct pt_regs *regs);
+bool uprobe_singlestep_handler(struct pt_regs *regs);
+#else /* !CONFIG_UPROBES */
+static inline bool uprobe_breakpoint_handler(struct pt_regs *regs) { return false; }
+static inline bool uprobe_singlestep_handler(struct pt_regs *regs) { return false; }
+#endif /* CONFIG_UPROBES */
+
+#endif /* __ASM_LOONGARCH_UPROBES_H */
index 1061c36..8e279f0 100644 (file)
@@ -56,6 +56,7 @@ obj-$(CONFIG_HAVE_HW_BREAKPOINT)      += hw_breakpoint.o
 
 obj-$(CONFIG_KPROBES)          += kprobes.o
 obj-$(CONFIG_RETHOOK)          += rethook.o rethook_trampoline.o
+obj-$(CONFIG_UPROBES)          += uprobes.o
 
 obj-$(CONFIG_JUMP_LABEL)       += jump_label.o
 
index e56df45..8fb5e7a 100644 (file)
@@ -47,6 +47,7 @@
 #include <asm/tlb.h>
 #include <asm/types.h>
 #include <asm/unwind.h>
+#include <asm/uprobes.h>
 
 #include "access-helper.h"
 
@@ -689,7 +690,6 @@ asmlinkage void noinstr do_bp(struct pt_regs *regs)
        if (regs->csr_prmd & CSR_PRMD_PIE)
                local_irq_enable();
 
-       current->thread.trap_nr = read_csr_excode();
        if (__get_inst(&opcode, (u32 *)era, user))
                goto out_sigsegv;
 
@@ -711,18 +711,17 @@ asmlinkage void noinstr do_bp(struct pt_regs *regs)
                else
                        break;
        case BRK_UPROBE_BP:
-               if (notify_die(DIE_UPROBE, "Uprobe", regs, bcode,
-                              current->thread.trap_nr, SIGTRAP) == NOTIFY_STOP)
+               if (uprobe_breakpoint_handler(regs))
                        goto out;
                else
                        break;
        case BRK_UPROBE_XOLBP:
-               if (notify_die(DIE_UPROBE_XOL, "Uprobe_XOL", regs, bcode,
-                              current->thread.trap_nr, SIGTRAP) == NOTIFY_STOP)
+               if (uprobe_singlestep_handler(regs))
                        goto out;
                else
                        break;
        default:
+               current->thread.trap_nr = read_csr_excode();
                if (notify_die(DIE_TRAP, "Break", regs, bcode,
                               current->thread.trap_nr, SIGTRAP) == NOTIFY_STOP)
                        goto out;
diff --git a/arch/loongarch/kernel/uprobes.c b/arch/loongarch/kernel/uprobes.c
new file mode 100644 (file)
index 0000000..87abc71
--- /dev/null
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/highmem.h>
+#include <linux/ptrace.h>
+#include <linux/sched.h>
+#include <linux/uprobes.h>
+#include <asm/cacheflush.h>
+
+#define UPROBE_TRAP_NR UINT_MAX
+
+int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe,
+                            struct mm_struct *mm, unsigned long addr)
+{
+       int idx;
+       union loongarch_instruction insn;
+
+       if (addr & 0x3)
+               return -EILSEQ;
+
+       for (idx = ARRAY_SIZE(auprobe->insn) - 1; idx >= 0; idx--) {
+               insn.word = auprobe->insn[idx];
+               if (insns_not_supported(insn))
+                       return -EINVAL;
+       }
+
+       if (insns_need_simulation(insn)) {
+               auprobe->ixol[0] = larch_insn_gen_nop();
+               auprobe->simulate = true;
+       } else {
+               auprobe->ixol[0] = auprobe->insn[0];
+               auprobe->simulate = false;
+       }
+
+       auprobe->ixol[1] = UPROBE_XOLBP_INSN;
+
+       return 0;
+}
+
+int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
+{
+       struct uprobe_task *utask = current->utask;
+
+       utask->autask.saved_trap_nr = current->thread.trap_nr;
+       current->thread.trap_nr = UPROBE_TRAP_NR;
+       instruction_pointer_set(regs, utask->xol_vaddr);
+       user_enable_single_step(current);
+
+       return 0;
+}
+
+int arch_uprobe_post_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
+{
+       struct uprobe_task *utask = current->utask;
+
+       WARN_ON_ONCE(current->thread.trap_nr != UPROBE_TRAP_NR);
+       current->thread.trap_nr = utask->autask.saved_trap_nr;
+
+       if (auprobe->simulate)
+               instruction_pointer_set(regs, auprobe->resume_era);
+       else
+               instruction_pointer_set(regs, utask->vaddr + LOONGARCH_INSN_SIZE);
+
+       user_disable_single_step(current);
+
+       return 0;
+}
+
+void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
+{
+       struct uprobe_task *utask = current->utask;
+
+       current->thread.trap_nr = utask->autask.saved_trap_nr;
+       instruction_pointer_set(regs, utask->vaddr);
+       user_disable_single_step(current);
+}
+
+bool arch_uprobe_xol_was_trapped(struct task_struct *t)
+{
+       if (t->thread.trap_nr != UPROBE_TRAP_NR)
+               return true;
+
+       return false;
+}
+
+bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs)
+{
+       union loongarch_instruction insn;
+
+       if (!auprobe->simulate)
+               return false;
+
+       insn.word = auprobe->insn[0];
+       arch_simulate_insn(insn, regs);
+       auprobe->resume_era = regs->csr_era;
+
+       return true;
+}
+
+unsigned long arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr,
+                                               struct pt_regs *regs)
+{
+       unsigned long ra = regs->regs[1];
+
+       regs->regs[1] = trampoline_vaddr;
+
+       return ra;
+}
+
+bool arch_uretprobe_is_alive(struct return_instance *ret,
+                            enum rp_check ctx, struct pt_regs *regs)
+{
+       if (ctx == RP_CHECK_CHAIN_CALL)
+               return regs->regs[3] <= ret->stack;
+       else
+               return regs->regs[3] < ret->stack;
+}
+
+int arch_uprobe_exception_notify(struct notifier_block *self,
+                                unsigned long val, void *data)
+{
+       return NOTIFY_DONE;
+}
+
+bool uprobe_breakpoint_handler(struct pt_regs *regs)
+{
+       if (uprobe_pre_sstep_notifier(regs))
+               return true;
+
+       return false;
+}
+
+bool uprobe_singlestep_handler(struct pt_regs *regs)
+{
+       if (uprobe_post_sstep_notifier(regs))
+               return true;
+
+       return false;
+}
+
+unsigned long uprobe_get_swbp_addr(struct pt_regs *regs)
+{
+       return instruction_pointer(regs);
+}
+
+void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
+                          void *src, unsigned long len)
+{
+       void *kaddr = kmap_local_page(page);
+       void *dst = kaddr + (vaddr & ~PAGE_MASK);
+
+       memcpy(dst, src, len);
+       flush_icache_range((unsigned long)dst, (unsigned long)dst + len);
+       kunmap_local(kaddr);
+}