Skip to content

Commit

Permalink
LoongArch: Add uprobes support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Tiezhu Yang authored and Huacai Chen committed Jun 29, 2023
1 parent 6e32036 commit 19bc6cb
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 5 deletions.
3 changes: 3 additions & 0 deletions arch/loongarch/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions arch/loongarch/include/asm/uprobes.h
Original file line number Diff line number Diff line change
@@ -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 */
1 change: 1 addition & 0 deletions arch/loongarch/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 4 additions & 5 deletions arch/loongarch/kernel/traps.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include <asm/tlb.h>
#include <asm/types.h>
#include <asm/unwind.h>
#include <asm/uprobes.h>

#include "access-helper.h"

Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand Down
153 changes: 153 additions & 0 deletions arch/loongarch/kernel/uprobes.c
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit 19bc6cb

Please sign in to comment.