root/arch/loongarch/kernel/uprobes.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);

        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;
        instruction_pointer_set(regs, utask->vaddr + LOONGARCH_INSN_SIZE);

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

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

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