Commit 19bc6cb6 authored by Tiezhu Yang's avatar Tiezhu Yang Committed by Huacai Chen
Browse files

LoongArch: Add uprobes support



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: default avatarJeff Xie <xiehuan09@gmail.com>
Signed-off-by: default avatarTiezhu Yang <yangtiezhu@loongson.cn>
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
parent 6e320363
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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
+36 −0
Original line number Diff line number Diff line
/* 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 −0
Original line number Diff line number Diff line
@@ -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

+4 −5
Original line number Diff line number Diff line
@@ -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;
+153 −0
Original line number Diff line number Diff line
// 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);
}