| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/highmem.h> | |
| #include <linux/ptrace.h> | |
| #include <linux/uprobes.h> | |
| #include <asm/insn.h> | |
| #include "decode-insn.h" | |
| #define UPROBE_TRAP_NR UINT_MAX | |
| bool is_swbp_insn(uprobe_opcode_t *insn) | |
| { | |
| #ifdef CONFIG_RISCV_ISA_C | |
| return (*insn & 0xffff) == UPROBE_SWBP_INSN; | |
| #else | |
| return *insn == UPROBE_SWBP_INSN; | |
| #endif | |
| } | |
| bool is_trap_insn(uprobe_opcode_t *insn) | |
| { | |
| return riscv_insn_is_ebreak(*insn) || riscv_insn_is_c_ebreak(*insn); | |
| } | |
| unsigned long uprobe_get_swbp_addr(struct pt_regs *regs) | |
| { | |
| return instruction_pointer(regs); | |
| } | |
| int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, | |
| unsigned long addr) | |
| { | |
| probe_opcode_t opcode; | |
| opcode = *(probe_opcode_t *)(&auprobe->insn[0]); | |
| auprobe->insn_size = GET_INSN_LENGTH(opcode); | |
| switch (riscv_probe_decode_insn(&opcode, &auprobe->api)) { | |
| case INSN_REJECTED: | |
| return -EINVAL; | |
| case INSN_GOOD_NO_SLOT: | |
| auprobe->simulate = true; | |
| break; | |
| case INSN_GOOD: | |
| auprobe->simulate = false; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) | |
| { | |
| struct uprobe_task *utask = current->utask; | |
| utask->autask.saved_cause = current->thread.bad_cause; | |
| current->thread.bad_cause = UPROBE_TRAP_NR; | |
| instruction_pointer_set(regs, utask->xol_vaddr); | |
| 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.bad_cause != UPROBE_TRAP_NR); | |
| current->thread.bad_cause = utask->autask.saved_cause; | |
| instruction_pointer_set(regs, utask->vaddr + auprobe->insn_size); | |
| return 0; | |
| } | |
| bool arch_uprobe_xol_was_trapped(struct task_struct *t) | |
| { | |
| if (t->thread.bad_cause != UPROBE_TRAP_NR) | |
| return true; | |
| return false; | |
| } | |
| bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs) | |
| { | |
| probe_opcode_t insn; | |
| unsigned long addr; | |
| if (!auprobe->simulate) | |
| return false; | |
| insn = *(probe_opcode_t *)(&auprobe->insn[0]); | |
| addr = instruction_pointer(regs); | |
| if (auprobe->api.handler) | |
| auprobe->api.handler(insn, addr, regs); | |
| return true; | |
| } | |
| void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) | |
| { | |
| struct uprobe_task *utask = current->utask; | |
| current->thread.bad_cause = utask->autask.saved_cause; | |
| /* | |
| * Task has received a fatal signal, so reset back to probed | |
| * address. | |
| */ | |
| instruction_pointer_set(regs, utask->vaddr); | |
| } | |
| 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->sp <= ret->stack; | |
| else | |
| return regs->sp < ret->stack; | |
| } | |
| unsigned long | |
| arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr, | |
| struct pt_regs *regs) | |
| { | |
| unsigned long ra; | |
| ra = regs->ra; | |
| regs->ra = trampoline_vaddr; | |
| return ra; | |
| } | |
| 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_single_step_handler(struct pt_regs *regs) | |
| { | |
| if (uprobe_post_sstep_notifier(regs)) | |
| return true; | |
| return false; | |
| } | |
| void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr, | |
| void *src, unsigned long len) | |
| { | |
| /* Initialize the slot */ | |
| void *kaddr = kmap_local_page(page); | |
| void *dst = kaddr + (vaddr & ~PAGE_MASK); | |
| unsigned long start = (unsigned long)dst; | |
| memcpy(dst, src, len); | |
| /* Add ebreak behind opcode to simulate singlestep */ | |
| if (vaddr) { | |
| dst += GET_INSN_LENGTH(*(probe_opcode_t *)src); | |
| *(uprobe_opcode_t *)dst = __BUG_INSN_32; | |
| } | |
| flush_icache_range(start, start + len); | |
| kunmap_local(kaddr); | |
| } |