
Make the instruction interpreter call back to semantic action functions through a function pointer array provided by the invoker. The interpreter decodes the instructions into groups and uses the group number to index into the supplied array. kprobes and uprobes code will each supply their own array of functions. Signed-off-by: David A. Long <dave.long@linaro.org> Acked-by: Jon Medhurst <tixy@linaro.org>
158 lines
3.9 KiB
C
158 lines
3.9 KiB
C
/*
|
|
* arch/arm/kernel/kprobes-common.c
|
|
*
|
|
* Copyright (C) 2011 Jon Medhurst <tixy@yxit.co.uk>.
|
|
*
|
|
* Some contents moved here from arch/arm/include/asm/kprobes-arm.c which is
|
|
* Copyright (C) 2006, 2007 Motorola Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/kprobes.h>
|
|
|
|
#include "kprobes.h"
|
|
|
|
|
|
static void __kprobes simulate_ldm1stm1(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
kprobe_opcode_t insn = p->opcode;
|
|
int rn = (insn >> 16) & 0xf;
|
|
int lbit = insn & (1 << 20);
|
|
int wbit = insn & (1 << 21);
|
|
int ubit = insn & (1 << 23);
|
|
int pbit = insn & (1 << 24);
|
|
long *addr = (long *)regs->uregs[rn];
|
|
int reg_bit_vector;
|
|
int reg_count;
|
|
|
|
reg_count = 0;
|
|
reg_bit_vector = insn & 0xffff;
|
|
while (reg_bit_vector) {
|
|
reg_bit_vector &= (reg_bit_vector - 1);
|
|
++reg_count;
|
|
}
|
|
|
|
if (!ubit)
|
|
addr -= reg_count;
|
|
addr += (!pbit == !ubit);
|
|
|
|
reg_bit_vector = insn & 0xffff;
|
|
while (reg_bit_vector) {
|
|
int reg = __ffs(reg_bit_vector);
|
|
reg_bit_vector &= (reg_bit_vector - 1);
|
|
if (lbit)
|
|
regs->uregs[reg] = *addr++;
|
|
else
|
|
*addr++ = regs->uregs[reg];
|
|
}
|
|
|
|
if (wbit) {
|
|
if (!ubit)
|
|
addr -= reg_count;
|
|
addr -= (!pbit == !ubit);
|
|
regs->uregs[rn] = (long)addr;
|
|
}
|
|
}
|
|
|
|
static void __kprobes simulate_stm1_pc(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
regs->ARM_pc = (long)p->addr + str_pc_offset;
|
|
simulate_ldm1stm1(p, regs);
|
|
regs->ARM_pc = (long)p->addr + 4;
|
|
}
|
|
|
|
static void __kprobes simulate_ldm1_pc(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
simulate_ldm1stm1(p, regs);
|
|
load_write_pc(regs->ARM_pc, regs);
|
|
}
|
|
|
|
static void __kprobes
|
|
emulate_generic_r0_12_noflags(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
register void *rregs asm("r1") = regs;
|
|
register void *rfn asm("lr") = p->ainsn.insn_fn;
|
|
|
|
__asm__ __volatile__ (
|
|
"stmdb sp!, {%[regs], r11} \n\t"
|
|
"ldmia %[regs], {r0-r12} \n\t"
|
|
#if __LINUX_ARM_ARCH__ >= 6
|
|
"blx %[fn] \n\t"
|
|
#else
|
|
"str %[fn], [sp, #-4]! \n\t"
|
|
"adr lr, 1f \n\t"
|
|
"ldr pc, [sp], #4 \n\t"
|
|
"1: \n\t"
|
|
#endif
|
|
"ldr lr, [sp], #4 \n\t" /* lr = regs */
|
|
"stmia lr, {r0-r12} \n\t"
|
|
"ldr r11, [sp], #4 \n\t"
|
|
: [regs] "=r" (rregs), [fn] "=r" (rfn)
|
|
: "0" (rregs), "1" (rfn)
|
|
: "r0", "r2", "r3", "r4", "r5", "r6", "r7",
|
|
"r8", "r9", "r10", "r12", "memory", "cc"
|
|
);
|
|
}
|
|
|
|
static void __kprobes
|
|
emulate_generic_r2_14_noflags(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
emulate_generic_r0_12_noflags(p, (struct pt_regs *)(regs->uregs+2));
|
|
}
|
|
|
|
static void __kprobes
|
|
emulate_ldm_r3_15(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
emulate_generic_r0_12_noflags(p, (struct pt_regs *)(regs->uregs+3));
|
|
load_write_pc(regs->ARM_pc, regs);
|
|
}
|
|
|
|
enum kprobe_insn __kprobes
|
|
kprobe_decode_ldmstm(kprobe_opcode_t insn, struct arch_specific_insn *asi,
|
|
const struct decode_header *h)
|
|
{
|
|
kprobe_insn_handler_t *handler = 0;
|
|
unsigned reglist = insn & 0xffff;
|
|
int is_ldm = insn & 0x100000;
|
|
int rn = (insn >> 16) & 0xf;
|
|
|
|
if (rn <= 12 && (reglist & 0xe000) == 0) {
|
|
/* Instruction only uses registers in the range R0..R12 */
|
|
handler = emulate_generic_r0_12_noflags;
|
|
|
|
} else if (rn >= 2 && (reglist & 0x8003) == 0) {
|
|
/* Instruction only uses registers in the range R2..R14 */
|
|
rn -= 2;
|
|
reglist >>= 2;
|
|
handler = emulate_generic_r2_14_noflags;
|
|
|
|
} else if (rn >= 3 && (reglist & 0x0007) == 0) {
|
|
/* Instruction only uses registers in the range R3..R15 */
|
|
if (is_ldm && (reglist & 0x8000)) {
|
|
rn -= 3;
|
|
reglist >>= 3;
|
|
handler = emulate_ldm_r3_15;
|
|
}
|
|
}
|
|
|
|
if (handler) {
|
|
/* We can emulate the instruction in (possibly) modified form */
|
|
asi->insn[0] = (insn & 0xfff00000) | (rn << 16) | reglist;
|
|
asi->insn_handler = handler;
|
|
return INSN_GOOD;
|
|
}
|
|
|
|
/* Fallback to slower simulation... */
|
|
if (reglist & 0x8000)
|
|
handler = is_ldm ? simulate_ldm1_pc : simulate_stm1_pc;
|
|
else
|
|
handler = simulate_ldm1stm1;
|
|
asi->insn_handler = handler;
|
|
return INSN_GOOD_NO_SLOT;
|
|
}
|
|
|