| // 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); |
| } |