OSDN Git Service

riscv: Add uprobes supported
authorGuo Ren <guoren@linux.alibaba.com>
Thu, 17 Dec 2020 16:01:44 +0000 (16:01 +0000)
committerPalmer Dabbelt <palmerdabbelt@google.com>
Thu, 14 Jan 2021 23:09:08 +0000 (15:09 -0800)
This patch adds support for uprobes on riscv architecture.

Just like kprobe, it support single-step and simulate instructions.

Signed-off-by: Guo Ren <guoren@linux.alibaba.com>
Reviewed-by: Pekka Enberg <penberg@kernel.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Palmer Dabbelt <palmerdabbelt@google.com>
Signed-off-by: Palmer Dabbelt <palmerdabbelt@google.com>
arch/riscv/Kconfig
arch/riscv/include/asm/processor.h
arch/riscv/include/asm/thread_info.h
arch/riscv/include/asm/uprobes.h [new file with mode: 0644]
arch/riscv/kernel/probes/Makefile
arch/riscv/kernel/probes/uprobes.c [new file with mode: 0644]
arch/riscv/kernel/signal.c
arch/riscv/kernel/traps.c
arch/riscv/mm/fault.c

index 07a4806..7b5bb48 100644 (file)
@@ -160,6 +160,9 @@ config ARCH_SELECT_MEMORY_MODEL
 config ARCH_WANT_GENERAL_HUGETLB
        def_bool y
 
+config ARCH_SUPPORTS_UPROBES
+       def_bool y
+
 config SYS_SUPPORTS_HUGETLBFS
        depends on MMU
        def_bool y
index bdddcd5..3a24003 100644 (file)
@@ -34,6 +34,7 @@ struct thread_struct {
        unsigned long sp;       /* Kernel mode stack */
        unsigned long s[12];    /* s[0]: frame pointer */
        struct __riscv_d_ext_state fstate;
+       unsigned long bad_cause;
 };
 
 #define INIT_THREAD {                                  \
index 97bf5a1..0e549a3 100644 (file)
@@ -75,6 +75,7 @@ struct thread_info {
 #define TIF_SYSCALL_AUDIT      7       /* syscall auditing */
 #define TIF_SECCOMP            8       /* syscall secure computing */
 #define TIF_NOTIFY_SIGNAL      9       /* signal notifications exist */
+#define TIF_UPROBE             10      /* uprobe breakpoint or singlestep */
 
 #define _TIF_SYSCALL_TRACE     (1 << TIF_SYSCALL_TRACE)
 #define _TIF_NOTIFY_RESUME     (1 << TIF_NOTIFY_RESUME)
@@ -84,10 +85,11 @@ struct thread_info {
 #define _TIF_SYSCALL_AUDIT     (1 << TIF_SYSCALL_AUDIT)
 #define _TIF_SECCOMP           (1 << TIF_SECCOMP)
 #define _TIF_NOTIFY_SIGNAL     (1 << TIF_NOTIFY_SIGNAL)
+#define _TIF_UPROBE            (1 << TIF_UPROBE)
 
 #define _TIF_WORK_MASK \
        (_TIF_NOTIFY_RESUME | _TIF_SIGPENDING | _TIF_NEED_RESCHED | \
-        _TIF_NOTIFY_SIGNAL)
+        _TIF_NOTIFY_SIGNAL | _TIF_UPROBE)
 
 #define _TIF_SYSCALL_WORK \
        (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_TRACEPOINT | _TIF_SYSCALL_AUDIT | \
diff --git a/arch/riscv/include/asm/uprobes.h b/arch/riscv/include/asm/uprobes.h
new file mode 100644 (file)
index 0000000..f2183e0
--- /dev/null
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef _ASM_RISCV_UPROBES_H
+#define _ASM_RISCV_UPROBES_H
+
+#include <asm/probes.h>
+#include <asm/patch.h>
+#include <asm/bug.h>
+
+#define MAX_UINSN_BYTES                8
+
+#ifdef CONFIG_RISCV_ISA_C
+#define UPROBE_SWBP_INSN       __BUG_INSN_16
+#define UPROBE_SWBP_INSN_SIZE  2
+#else
+#define UPROBE_SWBP_INSN       __BUG_INSN_32
+#define UPROBE_SWBP_INSN_SIZE  4
+#endif
+#define UPROBE_XOL_SLOT_BYTES  MAX_UINSN_BYTES
+
+typedef u32 uprobe_opcode_t;
+
+struct arch_uprobe_task {
+       unsigned long   saved_cause;
+};
+
+struct arch_uprobe {
+       union {
+               u8 insn[MAX_UINSN_BYTES];
+               u8 ixol[MAX_UINSN_BYTES];
+       };
+       struct arch_probe_insn api;
+       unsigned long insn_size;
+       bool simulate;
+};
+
+bool uprobe_breakpoint_handler(struct pt_regs *regs);
+bool uprobe_single_step_handler(struct pt_regs *regs);
+
+#endif /* _ASM_RISCV_UPROBES_H */
index abbd131..7f0840d 100644 (file)
@@ -2,4 +2,5 @@
 obj-$(CONFIG_KPROBES)          += kprobes.o decode-insn.o simulate-insn.o
 obj-$(CONFIG_KPROBES)          += kprobes_trampoline.o
 obj-$(CONFIG_KPROBES_ON_FTRACE)        += ftrace.o
+obj-$(CONFIG_UPROBES)          += uprobes.o decode-insn.o simulate-insn.o
 CFLAGS_REMOVE_simulate-insn.o = $(CC_FLAGS_FTRACE)
diff --git a/arch/riscv/kernel/probes/uprobes.c b/arch/riscv/kernel/probes/uprobes.c
new file mode 100644 (file)
index 0000000..7a057b5
--- /dev/null
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/highmem.h>
+#include <linux/ptrace.h>
+#include <linux/uprobes.h>
+
+#include "decode-insn.h"
+
+#define UPROBE_TRAP_NR UINT_MAX
+
+bool is_swbp_insn(uprobe_opcode_t *insn)
+{
+#ifdef CONFIG_RISCV_ISA_C
+       return (*insn & 0xffff) == UPROBE_SWBP_INSN;
+#else
+       return *insn == UPROBE_SWBP_INSN;
+#endif
+}
+
+unsigned long uprobe_get_swbp_addr(struct pt_regs *regs)
+{
+       return instruction_pointer(regs);
+}
+
+int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm,
+                            unsigned long addr)
+{
+       probe_opcode_t opcode;
+
+       opcode = *(probe_opcode_t *)(&auprobe->insn[0]);
+
+       auprobe->insn_size = GET_INSN_LENGTH(opcode);
+
+       switch (riscv_probe_decode_insn(&opcode, &auprobe->api)) {
+       case INSN_REJECTED:
+               return -EINVAL;
+
+       case INSN_GOOD_NO_SLOT:
+               auprobe->simulate = true;
+               break;
+
+       case INSN_GOOD:
+               auprobe->simulate = false;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
+{
+       struct uprobe_task *utask = current->utask;
+
+       utask->autask.saved_cause = current->thread.bad_cause;
+       current->thread.bad_cause = UPROBE_TRAP_NR;
+
+       instruction_pointer_set(regs, utask->xol_vaddr);
+
+       regs->status &= ~SR_SPIE;
+
+       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.bad_cause != UPROBE_TRAP_NR);
+
+       instruction_pointer_set(regs, utask->vaddr + auprobe->insn_size);
+
+       regs->status |= SR_SPIE;
+
+       return 0;
+}
+
+bool arch_uprobe_xol_was_trapped(struct task_struct *t)
+{
+       if (t->thread.bad_cause != UPROBE_TRAP_NR)
+               return true;
+
+       return false;
+}
+
+bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs)
+{
+       probe_opcode_t insn;
+       unsigned long addr;
+
+       if (!auprobe->simulate)
+               return false;
+
+       insn = *(probe_opcode_t *)(&auprobe->insn[0]);
+       addr = instruction_pointer(regs);
+
+       if (auprobe->api.handler)
+               auprobe->api.handler(insn, addr, regs);
+
+       return true;
+}
+
+void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
+{
+       struct uprobe_task *utask = current->utask;
+
+       /*
+        * Task has received a fatal signal, so reset back to probbed
+        * address.
+        */
+       instruction_pointer_set(regs, utask->vaddr);
+
+       regs->status &= ~SR_SPIE;
+}
+
+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->sp <= ret->stack;
+       else
+               return regs->sp < ret->stack;
+}
+
+unsigned long
+arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr,
+                                 struct pt_regs *regs)
+{
+       unsigned long ra;
+
+       ra = regs->ra;
+
+       regs->ra = trampoline_vaddr;
+
+       return ra;
+}
+
+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_single_step_handler(struct pt_regs *regs)
+{
+       if (uprobe_post_sstep_notifier(regs))
+               return true;
+
+       return false;
+}
+
+void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
+                          void *src, unsigned long len)
+{
+       /* Initialize the slot */
+       void *kaddr = kmap_atomic(page);
+       void *dst = kaddr + (vaddr & ~PAGE_MASK);
+
+       memcpy(dst, src, len);
+
+       /* Add ebreak behind opcode to simulate singlestep */
+       if (vaddr) {
+               dst += GET_INSN_LENGTH(*(probe_opcode_t *)src);
+               *(uprobe_opcode_t *)dst = __BUG_INSN_32;
+       }
+
+       kunmap_atomic(kaddr);
+
+       /*
+        * We probably need flush_icache_user_page() but it needs vma.
+        * This should work on most of architectures by default. If
+        * architecture needs to do something different it can define
+        * its own version of the function.
+        */
+       flush_dcache_page(page);
+}
index 469aef8..65942b3 100644 (file)
@@ -309,6 +309,9 @@ static void do_signal(struct pt_regs *regs)
 asmlinkage __visible void do_notify_resume(struct pt_regs *regs,
                                           unsigned long thread_info_flags)
 {
+       if (thread_info_flags & _TIF_UPROBE)
+               uprobe_notify_resume(regs);
+
        /* Handle pending signal delivery */
        if (thread_info_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
                do_signal(regs);
index 19a788a..2bca2fa 100644 (file)
@@ -76,6 +76,8 @@ void do_trap(struct pt_regs *regs, int signo, int code, unsigned long addr)
 static void do_trap_error(struct pt_regs *regs, int signo, int code,
        unsigned long addr, const char *str)
 {
+       current->thread.bad_cause = regs->cause;
+
        if (user_mode(regs)) {
                do_trap(regs, signo, code, addr);
        } else {
@@ -153,6 +155,14 @@ asmlinkage __visible void do_trap_break(struct pt_regs *regs)
        if (kprobe_breakpoint_handler(regs))
                return;
 #endif
+#ifdef CONFIG_UPROBES
+       if (uprobe_single_step_handler(regs))
+               return;
+
+       if (uprobe_breakpoint_handler(regs))
+               return;
+#endif
+       current->thread.bad_cause = regs->cause;
 
        if (user_mode(regs))
                force_sig_fault(SIGTRAP, TRAP_BRKPT, (void __user *)regs->epc);
index 1da870f..8f17519 100644 (file)
@@ -240,6 +240,7 @@ asmlinkage void do_page_fault(struct pt_regs *regs)
         * in an atomic region, then we must not take the fault.
         */
        if (unlikely(faulthandler_disabled() || !mm)) {
+               tsk->thread.bad_cause = cause;
                no_context(regs, addr);
                return;
        }
@@ -262,16 +263,19 @@ retry:
        mmap_read_lock(mm);
        vma = find_vma(mm, addr);
        if (unlikely(!vma)) {
+               tsk->thread.bad_cause = cause;
                bad_area(regs, mm, code, addr);
                return;
        }
        if (likely(vma->vm_start <= addr))
                goto good_area;
        if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
+               tsk->thread.bad_cause = cause;
                bad_area(regs, mm, code, addr);
                return;
        }
        if (unlikely(expand_stack(vma, addr))) {
+               tsk->thread.bad_cause = cause;
                bad_area(regs, mm, code, addr);
                return;
        }
@@ -284,6 +288,7 @@ good_area:
        code = SEGV_ACCERR;
 
        if (unlikely(access_error(cause, vma))) {
+               tsk->thread.bad_cause = cause;
                bad_area(regs, mm, code, addr);
                return;
        }
@@ -317,6 +322,7 @@ good_area:
        mmap_read_unlock(mm);
 
        if (unlikely(fault & VM_FAULT_ERROR)) {
+               tsk->thread.bad_cause = cause;
                mm_fault_error(regs, addr, fault);
                return;
        }