mirror of
https://github.com/tbsdtv/linux_media.git
synced 2025-07-24 05:01:03 +02:00
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: Jeff Xie <xiehuan09@gmail.com> Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
This commit is contained in:
@@ -643,6 +643,9 @@ config ARCH_MMAP_RND_BITS_MIN
|
|||||||
config ARCH_MMAP_RND_BITS_MAX
|
config ARCH_MMAP_RND_BITS_MAX
|
||||||
default 18
|
default 18
|
||||||
|
|
||||||
|
config ARCH_SUPPORTS_UPROBES
|
||||||
|
def_bool y
|
||||||
|
|
||||||
menu "Power management options"
|
menu "Power management options"
|
||||||
|
|
||||||
config ARCH_SUSPEND_POSSIBLE
|
config ARCH_SUSPEND_POSSIBLE
|
||||||
|
36
arch/loongarch/include/asm/uprobes.h
Normal file
36
arch/loongarch/include/asm/uprobes.h
Normal file
@@ -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 */
|
@@ -56,6 +56,7 @@ obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
|
|||||||
|
|
||||||
obj-$(CONFIG_KPROBES) += kprobes.o
|
obj-$(CONFIG_KPROBES) += kprobes.o
|
||||||
obj-$(CONFIG_RETHOOK) += rethook.o rethook_trampoline.o
|
obj-$(CONFIG_RETHOOK) += rethook.o rethook_trampoline.o
|
||||||
|
obj-$(CONFIG_UPROBES) += uprobes.o
|
||||||
|
|
||||||
obj-$(CONFIG_JUMP_LABEL) += jump_label.o
|
obj-$(CONFIG_JUMP_LABEL) += jump_label.o
|
||||||
|
|
||||||
|
@@ -47,6 +47,7 @@
|
|||||||
#include <asm/tlb.h>
|
#include <asm/tlb.h>
|
||||||
#include <asm/types.h>
|
#include <asm/types.h>
|
||||||
#include <asm/unwind.h>
|
#include <asm/unwind.h>
|
||||||
|
#include <asm/uprobes.h>
|
||||||
|
|
||||||
#include "access-helper.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)
|
if (regs->csr_prmd & CSR_PRMD_PIE)
|
||||||
local_irq_enable();
|
local_irq_enable();
|
||||||
|
|
||||||
current->thread.trap_nr = read_csr_excode();
|
|
||||||
if (__get_inst(&opcode, (u32 *)era, user))
|
if (__get_inst(&opcode, (u32 *)era, user))
|
||||||
goto out_sigsegv;
|
goto out_sigsegv;
|
||||||
|
|
||||||
@@ -711,18 +711,17 @@ asmlinkage void noinstr do_bp(struct pt_regs *regs)
|
|||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
case BRK_UPROBE_BP:
|
case BRK_UPROBE_BP:
|
||||||
if (notify_die(DIE_UPROBE, "Uprobe", regs, bcode,
|
if (uprobe_breakpoint_handler(regs))
|
||||||
current->thread.trap_nr, SIGTRAP) == NOTIFY_STOP)
|
|
||||||
goto out;
|
goto out;
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
case BRK_UPROBE_XOLBP:
|
case BRK_UPROBE_XOLBP:
|
||||||
if (notify_die(DIE_UPROBE_XOL, "Uprobe_XOL", regs, bcode,
|
if (uprobe_singlestep_handler(regs))
|
||||||
current->thread.trap_nr, SIGTRAP) == NOTIFY_STOP)
|
|
||||||
goto out;
|
goto out;
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
current->thread.trap_nr = read_csr_excode();
|
||||||
if (notify_die(DIE_TRAP, "Break", regs, bcode,
|
if (notify_die(DIE_TRAP, "Break", regs, bcode,
|
||||||
current->thread.trap_nr, SIGTRAP) == NOTIFY_STOP)
|
current->thread.trap_nr, SIGTRAP) == NOTIFY_STOP)
|
||||||
goto out;
|
goto out;
|
||||||
|
153
arch/loongarch/kernel/uprobes.c
Normal file
153
arch/loongarch/kernel/uprobes.c
Normal file
@@ -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);
|
||||||
|
}
|
Reference in New Issue
Block a user