#include <linux/kernel.h>
#include <linux/highmem.h>
#include <linux/uprobes.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/kdebug.h>
#include <asm/cacheflush.h>
#include "kernel.h"
unsigned long uprobe_get_swbp_addr(struct pt_regs *regs)
{
return instruction_pointer(regs);
}
static void copy_to_page(struct page *page, unsigned long vaddr,
const void *src, int len)
{
void *kaddr = kmap_atomic(page);
memcpy(kaddr + (vaddr & ~PAGE_MASK), src, len);
kunmap_atomic(kaddr);
}
void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
void *src, unsigned long len)
{
const u32 stp_insn = UPROBE_STP_INSN;
u32 insn = *(u32 *) src;
u32 op = (insn >> 30) & 0x3;
u32 op2 = (insn >> 22) & 0x7;
if (op == 0 &&
(op2 == 1 || op2 == 2 || op2 == 3 || op2 == 5 || op2 == 6) &&
(insn & ANNUL_BIT) == ANNUL_BIT)
insn &= ~ANNUL_BIT;
copy_to_page(page, vaddr, &insn, len);
copy_to_page(page, vaddr+len, &stp_insn, 4);
}
int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe,
struct mm_struct *mm, unsigned long addr)
{
return 0;
}
static unsigned long relbranch_fixup(u32 insn, struct uprobe_task *utask,
struct pt_regs *regs)
{
if (regs->tnpc == regs->tpc + 0x4UL)
return utask->autask.saved_tnpc + 0x4UL;
if ((insn & 0xc0000000) == 0x40000000 ||
(insn & 0xc1c00000) == 0x00400000 ||
(insn & 0xc1c00000) == 0x00800000) {
unsigned long real_pc = (unsigned long) utask->vaddr;
unsigned long ixol_addr = utask->xol_vaddr;
return (real_pc + (regs->tnpc - ixol_addr));
}
return regs->tnpc;
}
static int retpc_fixup(struct pt_regs *regs, u32 insn,
unsigned long real_pc)
{
unsigned long *slot = NULL;
int rc = 0;
if ((insn & 0xc0000000) == 0x40000000)
slot = ®s->u_regs[UREG_I7];
if ((insn & 0xc1f80000) == 0x81c00000) {
unsigned long rd = ((insn >> 25) & 0x1f);
if (rd <= 15) {
slot = ®s->u_regs[rd];
} else {
unsigned long fp = regs->u_regs[UREG_FP];
flushw_all();
rd -= 16;
if (test_thread_64bit_stack(fp)) {
unsigned long __user *uslot =
(unsigned long __user *) (fp + STACK_BIAS) + rd;
rc = __put_user(real_pc, uslot);
} else {
unsigned int __user *uslot = (unsigned int
__user *) fp + rd;
rc = __put_user((u32) real_pc, uslot);
}
}
}
if (slot != NULL)
*slot = real_pc;
return rc;
}
bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs)
{
if (auprobe->ixol == (1 << 24)) {
regs->tnpc += 4;
regs->tpc += 4;
return true;
}
return false;
}
int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
{
struct uprobe_task *utask = current->utask;
struct arch_uprobe_task *autask = ¤t->utask->autask;
autask->saved_tpc = regs->tpc;
autask->saved_tnpc = regs->tnpc;
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;
struct arch_uprobe_task *autask = &utask->autask;
u32 insn = auprobe->ixol;
int rc = 0;
if (utask->state == UTASK_SSTEP_ACK) {
regs->tnpc = relbranch_fixup(insn, utask, regs);
regs->tpc = autask->saved_tnpc;
rc = retpc_fixup(regs, insn, (unsigned long) utask->vaddr);
} else {
regs->tnpc = utask->vaddr+4;
regs->tpc = autask->saved_tnpc+4;
}
return rc;
}
asmlinkage void uprobe_trap(struct pt_regs *regs,
unsigned long trap_level)
{
BUG_ON(trap_level != 0x173 && trap_level != 0x174);
if (!user_mode(regs)) {
local_irq_enable();
bad_trap(regs, trap_level);
return;
}
if (notify_die((trap_level == 0x173) ? DIE_BPT : DIE_SSTEP,
(trap_level == 0x173) ? "bpt" : "sstep",
regs, 0, trap_level, SIGTRAP) != NOTIFY_STOP)
bad_trap(regs, trap_level);
}
int arch_uprobe_exception_notify(struct notifier_block *self,
unsigned long val, void *data)
{
int ret = NOTIFY_DONE;
struct die_args *args = (struct die_args *)data;
if (args->regs && !user_mode(args->regs))
return NOTIFY_DONE;
switch (val) {
case DIE_BPT:
if (uprobe_pre_sstep_notifier(args->regs))
ret = NOTIFY_STOP;
break;
case DIE_SSTEP:
if (uprobe_post_sstep_notifier(args->regs))
ret = NOTIFY_STOP;
default:
break;
}
return ret;
}
void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
{
struct uprobe_task *utask = current->utask;
instruction_pointer_set(regs, utask->vaddr);
}
bool arch_uprobe_xol_was_trapped(struct task_struct *t)
{
return false;
}
unsigned long
arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr,
struct pt_regs *regs)
{
unsigned long orig_ret_vaddr = regs->u_regs[UREG_I7];
regs->u_regs[UREG_I7] = trampoline_vaddr-8;
return orig_ret_vaddr + 8;
}