[v3,4/6] LoongArch: Add uprobes support
Commit Message
Uprobes is the user-space counterpart to kprobes, this commit
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
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
arch/loongarch/Kconfig | 3 +
arch/loongarch/include/asm/uprobes.h | 36 +++++++++
arch/loongarch/kernel/Makefile | 1 +
arch/loongarch/kernel/traps.c | 9 +--
arch/loongarch/kernel/uprobes.c | 142 +++++++++++++++++++++++++++++++++++
5 files changed, 186 insertions(+), 5 deletions(-)
create mode 100644 arch/loongarch/include/asm/uprobes.h
create mode 100644 arch/loongarch/kernel/uprobes.c
Comments
Hi, Tiezhu,
On Wed, Apr 19, 2023 at 5:57 PM Tiezhu Yang <yangtiezhu@loongson.cn> wrote:
>
> Uprobes is the user-space counterpart to kprobes, this commit
> 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
>
> Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
> ---
> arch/loongarch/Kconfig | 3 +
> arch/loongarch/include/asm/uprobes.h | 36 +++++++++
> arch/loongarch/kernel/Makefile | 1 +
> arch/loongarch/kernel/traps.c | 9 +--
> arch/loongarch/kernel/uprobes.c | 142 +++++++++++++++++++++++++++++++++++
> 5 files changed, 186 insertions(+), 5 deletions(-)
> create mode 100644 arch/loongarch/include/asm/uprobes.h
> create mode 100644 arch/loongarch/kernel/uprobes.c
>
> diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
> index 7fd5125..ab66ad2 100644
> --- a/arch/loongarch/Kconfig
> +++ b/arch/loongarch/Kconfig
> @@ -574,6 +574,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
> index 0000000..59e2683
> --- /dev/null
> +++ b/arch/loongarch/include/asm/uprobes.h
> @@ -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
In old versions we define UPROBE_XOL_SLOT_BYTES to 128, why we use 8 now?
> +
> +#define UPROBE_XOLBP_INSN larch_insn_gen_break(BRK_UPROBE_XOLBP)
> +#define UPROBE_SWBP_INSN larch_insn_gen_break(BRK_UPROBE_BP)
> +#define UPROBE_SWBP_INSN_SIZE LOONGARCH_INSN_SIZE
> +
> +struct arch_uprobe {
> + union {
> + 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 */
> diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
> index 78d4e33..67b9c26 100644
> --- a/arch/loongarch/kernel/Makefile
> +++ b/arch/loongarch/kernel/Makefile
> @@ -53,5 +53,6 @@ obj-$(CONFIG_PERF_EVENTS) += perf_event.o perf_regs.o
> obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
>
> obj-$(CONFIG_KPROBES) += kprobes.o kprobes_trampoline.o
> +obj-$(CONFIG_UPROBES) += uprobes.o
>
> CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS)
> diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
> index de8ebe2..cfc2e0c 100644
> --- a/arch/loongarch/kernel/traps.c
> +++ b/arch/loongarch/kernel/traps.c
> @@ -45,6 +45,7 @@
> #include <asm/tlb.h>
> #include <asm/types.h>
> #include <asm/unwind.h>
> +#include <asm/uprobes.h>
>
> #include "access-helper.h"
>
> @@ -440,7 +441,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;
>
> @@ -462,18 +462,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))
As I know, XOL means "execute out of line", is it an alias of "single step"?
Huacai
> 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
> index 0000000..628c39d
> --- /dev/null
> +++ b/arch/loongarch/kernel/uprobes.c
> @@ -0,0 +1,142 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <linux/highmem.h>
> +#include <linux/ptrace.h>
> +#include <linux/uprobes.h>
> +#include <linux/sched.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)
> +{
> + union loongarch_instruction insn;
> +
> + if (addr & 0x3)
> + return -EILSEQ;
> +
> + insn.word = auprobe->insn[0];
> +
> + 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);
> +
> + 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;
> + instruction_pointer_set(regs, utask->vaddr + LOONGARCH_INSN_SIZE);
> +
> + 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);
> +}
> +
> +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);
> +
> + 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);
> +}
> --
> 2.1.0
>
On 04/23/2023 09:12 PM, Huacai Chen wrote:
> Hi, Tiezhu,
>
> On Wed, Apr 19, 2023 at 5:57 PM Tiezhu Yang <yangtiezhu@loongson.cn> wrote:
>>
>> Uprobes is the user-space counterpart to kprobes, this commit
>> adds uprobes support for LoongArch.
...
>> +#define MAX_UINSN_BYTES 8
>> +#define UPROBE_XOL_SLOT_BYTES MAX_UINSN_BYTES
> In old versions we define UPROBE_XOL_SLOT_BYTES to 128, why we use 8 now?
I assume "the old version" maybe comes from mips, I am not quite sure
the backgrounds. For LoongArch, there are 2 xol slots to save 2
instructions, each of them is 32 bit, 2*32bit=8bytes, so
UPROBE_XOL_SLOT_BYTES can be 8.
>> +
>> +#define UPROBE_XOLBP_INSN larch_insn_gen_break(BRK_UPROBE_XOLBP)
>> +#define UPROBE_SWBP_INSN larch_insn_gen_break(BRK_UPROBE_BP)
>> +#define UPROBE_SWBP_INSN_SIZE LOONGARCH_INSN_SIZE
...
>> 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))
> As I know, XOL means "execute out of line", is it an alias of "single step"?
I think so, the instruction filled in xol slots is to single step,
arm64, riscv, csky also uses "single step" for the related code,
powerpc, s390 uses "DIE_SSTEP".
Thanks,
Tiezhu
@@ -574,6 +574,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
new file mode 100644
@@ -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_XOLBP_INSN larch_insn_gen_break(BRK_UPROBE_XOLBP)
+#define UPROBE_SWBP_INSN larch_insn_gen_break(BRK_UPROBE_BP)
+#define UPROBE_SWBP_INSN_SIZE LOONGARCH_INSN_SIZE
+
+struct arch_uprobe {
+ union {
+ 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 */
@@ -53,5 +53,6 @@ obj-$(CONFIG_PERF_EVENTS) += perf_event.o perf_regs.o
obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
obj-$(CONFIG_KPROBES) += kprobes.o kprobes_trampoline.o
+obj-$(CONFIG_UPROBES) += uprobes.o
CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS)
@@ -45,6 +45,7 @@
#include <asm/tlb.h>
#include <asm/types.h>
#include <asm/unwind.h>
+#include <asm/uprobes.h>
#include "access-helper.h"
@@ -440,7 +441,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;
@@ -462,18 +462,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;
new file mode 100644
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/highmem.h>
+#include <linux/ptrace.h>
+#include <linux/uprobes.h>
+#include <linux/sched.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)
+{
+ union loongarch_instruction insn;
+
+ if (addr & 0x3)
+ return -EILSEQ;
+
+ insn.word = auprobe->insn[0];
+
+ 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);
+
+ 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;
+ instruction_pointer_set(regs, utask->vaddr + LOONGARCH_INSN_SIZE);
+
+ 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);
+}
+
+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);
+
+ 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);
+}