154 lines
3.4 KiB
C
154 lines
3.4 KiB
C
// 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);
|
|
}
|