Jon Medhurst | 0ab4c02 | 2011-07-06 11:25:18 +0100 | [diff] [blame] | 1 | /* |
Wang Nan | fca08f3 | 2015-01-09 10:19:49 +0800 | [diff] [blame] | 2 | * arch/arm/probes/kprobes/actions-common.c |
Jon Medhurst | 0ab4c02 | 2011-07-06 11:25:18 +0100 | [diff] [blame] | 3 | * |
| 4 | * Copyright (C) 2011 Jon Medhurst <tixy@yxit.co.uk>. |
| 5 | * |
Jon Medhurst | 6c8df33 | 2011-07-07 10:21:40 +0100 | [diff] [blame] | 6 | * Some contents moved here from arch/arm/include/asm/kprobes-arm.c which is |
| 7 | * Copyright (C) 2006, 2007 Motorola Inc. |
| 8 | * |
Jon Medhurst | 0ab4c02 | 2011-07-06 11:25:18 +0100 | [diff] [blame] | 9 | * This program is free software; you can redistribute it and/or modify |
| 10 | * it under the terms of the GNU General Public License version 2 as |
| 11 | * published by the Free Software Foundation. |
| 12 | */ |
| 13 | |
| 14 | #include <linux/kernel.h> |
| 15 | #include <linux/kprobes.h> |
Ben Dooks | 888be25 | 2013-11-08 18:29:25 +0000 | [diff] [blame] | 16 | #include <asm/opcodes.h> |
Jon Medhurst | 0ab4c02 | 2011-07-06 11:25:18 +0100 | [diff] [blame] | 17 | |
Wang Nan | fca08f3 | 2015-01-09 10:19:49 +0800 | [diff] [blame] | 18 | #include "core.h" |
Jon Medhurst | 0ab4c02 | 2011-07-06 11:25:18 +0100 | [diff] [blame] | 19 | |
| 20 | |
David A. Long | f145d66 | 2014-03-05 21:17:23 -0500 | [diff] [blame] | 21 | static void __kprobes simulate_ldm1stm1(probes_opcode_t insn, |
David A. Long | b4cd605 | 2014-03-05 21:41:29 -0500 | [diff] [blame] | 22 | struct arch_probes_insn *asi, |
David A. Long | 7579f4b3 | 2014-03-07 11:19:32 -0500 | [diff] [blame] | 23 | struct pt_regs *regs) |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 24 | { |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 25 | int rn = (insn >> 16) & 0xf; |
| 26 | int lbit = insn & (1 << 20); |
| 27 | int wbit = insn & (1 << 21); |
| 28 | int ubit = insn & (1 << 23); |
| 29 | int pbit = insn & (1 << 24); |
| 30 | long *addr = (long *)regs->uregs[rn]; |
| 31 | int reg_bit_vector; |
| 32 | int reg_count; |
| 33 | |
| 34 | reg_count = 0; |
| 35 | reg_bit_vector = insn & 0xffff; |
| 36 | while (reg_bit_vector) { |
| 37 | reg_bit_vector &= (reg_bit_vector - 1); |
| 38 | ++reg_count; |
| 39 | } |
| 40 | |
| 41 | if (!ubit) |
| 42 | addr -= reg_count; |
| 43 | addr += (!pbit == !ubit); |
| 44 | |
| 45 | reg_bit_vector = insn & 0xffff; |
| 46 | while (reg_bit_vector) { |
| 47 | int reg = __ffs(reg_bit_vector); |
| 48 | reg_bit_vector &= (reg_bit_vector - 1); |
| 49 | if (lbit) |
| 50 | regs->uregs[reg] = *addr++; |
| 51 | else |
| 52 | *addr++ = regs->uregs[reg]; |
| 53 | } |
| 54 | |
| 55 | if (wbit) { |
| 56 | if (!ubit) |
| 57 | addr -= reg_count; |
| 58 | addr -= (!pbit == !ubit); |
| 59 | regs->uregs[rn] = (long)addr; |
| 60 | } |
| 61 | } |
| 62 | |
David A. Long | f145d66 | 2014-03-05 21:17:23 -0500 | [diff] [blame] | 63 | static void __kprobes simulate_stm1_pc(probes_opcode_t insn, |
David A. Long | b4cd605 | 2014-03-05 21:41:29 -0500 | [diff] [blame] | 64 | struct arch_probes_insn *asi, |
David A. Long | 7579f4b3 | 2014-03-07 11:19:32 -0500 | [diff] [blame] | 65 | struct pt_regs *regs) |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 66 | { |
David A. Long | 7579f4b3 | 2014-03-07 11:19:32 -0500 | [diff] [blame] | 67 | unsigned long addr = regs->ARM_pc - 4; |
| 68 | |
| 69 | regs->ARM_pc = (long)addr + str_pc_offset; |
| 70 | simulate_ldm1stm1(insn, asi, regs); |
| 71 | regs->ARM_pc = (long)addr + 4; |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 72 | } |
| 73 | |
David A. Long | f145d66 | 2014-03-05 21:17:23 -0500 | [diff] [blame] | 74 | static void __kprobes simulate_ldm1_pc(probes_opcode_t insn, |
David A. Long | b4cd605 | 2014-03-05 21:41:29 -0500 | [diff] [blame] | 75 | struct arch_probes_insn *asi, |
David A. Long | 7579f4b3 | 2014-03-07 11:19:32 -0500 | [diff] [blame] | 76 | struct pt_regs *regs) |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 77 | { |
David A. Long | 7579f4b3 | 2014-03-07 11:19:32 -0500 | [diff] [blame] | 78 | simulate_ldm1stm1(insn, asi, regs); |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 79 | load_write_pc(regs->ARM_pc, regs); |
| 80 | } |
| 81 | |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 82 | static void __kprobes |
David A. Long | f145d66 | 2014-03-05 21:17:23 -0500 | [diff] [blame] | 83 | emulate_generic_r0_12_noflags(probes_opcode_t insn, |
David A. Long | b4cd605 | 2014-03-05 21:41:29 -0500 | [diff] [blame] | 84 | struct arch_probes_insn *asi, struct pt_regs *regs) |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 85 | { |
| 86 | register void *rregs asm("r1") = regs; |
David A. Long | 7579f4b3 | 2014-03-07 11:19:32 -0500 | [diff] [blame] | 87 | register void *rfn asm("lr") = asi->insn_fn; |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 88 | |
| 89 | __asm__ __volatile__ ( |
| 90 | "stmdb sp!, {%[regs], r11} \n\t" |
| 91 | "ldmia %[regs], {r0-r12} \n\t" |
| 92 | #if __LINUX_ARM_ARCH__ >= 6 |
| 93 | "blx %[fn] \n\t" |
| 94 | #else |
| 95 | "str %[fn], [sp, #-4]! \n\t" |
| 96 | "adr lr, 1f \n\t" |
| 97 | "ldr pc, [sp], #4 \n\t" |
| 98 | "1: \n\t" |
| 99 | #endif |
| 100 | "ldr lr, [sp], #4 \n\t" /* lr = regs */ |
| 101 | "stmia lr, {r0-r12} \n\t" |
| 102 | "ldr r11, [sp], #4 \n\t" |
| 103 | : [regs] "=r" (rregs), [fn] "=r" (rfn) |
| 104 | : "0" (rregs), "1" (rfn) |
| 105 | : "r0", "r2", "r3", "r4", "r5", "r6", "r7", |
| 106 | "r8", "r9", "r10", "r12", "memory", "cc" |
| 107 | ); |
| 108 | } |
| 109 | |
| 110 | static void __kprobes |
David A. Long | f145d66 | 2014-03-05 21:17:23 -0500 | [diff] [blame] | 111 | emulate_generic_r2_14_noflags(probes_opcode_t insn, |
David A. Long | b4cd605 | 2014-03-05 21:41:29 -0500 | [diff] [blame] | 112 | struct arch_probes_insn *asi, struct pt_regs *regs) |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 113 | { |
David A. Long | 7579f4b3 | 2014-03-07 11:19:32 -0500 | [diff] [blame] | 114 | emulate_generic_r0_12_noflags(insn, asi, |
| 115 | (struct pt_regs *)(regs->uregs+2)); |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 116 | } |
| 117 | |
| 118 | static void __kprobes |
David A. Long | f145d66 | 2014-03-05 21:17:23 -0500 | [diff] [blame] | 119 | emulate_ldm_r3_15(probes_opcode_t insn, |
David A. Long | b4cd605 | 2014-03-05 21:41:29 -0500 | [diff] [blame] | 120 | struct arch_probes_insn *asi, struct pt_regs *regs) |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 121 | { |
David A. Long | 7579f4b3 | 2014-03-07 11:19:32 -0500 | [diff] [blame] | 122 | emulate_generic_r0_12_noflags(insn, asi, |
| 123 | (struct pt_regs *)(regs->uregs+3)); |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 124 | load_write_pc(regs->ARM_pc, regs); |
| 125 | } |
| 126 | |
David A. Long | 44a0a59 | 2014-03-05 21:23:42 -0500 | [diff] [blame] | 127 | enum probes_insn __kprobes |
David A. Long | b4cd605 | 2014-03-05 21:41:29 -0500 | [diff] [blame] | 128 | kprobe_decode_ldmstm(probes_opcode_t insn, struct arch_probes_insn *asi, |
David A. Long | 3e6cd39 | 2014-03-06 18:06:43 -0500 | [diff] [blame] | 129 | const struct decode_header *h) |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 130 | { |
David A. Long | 47e190f | 2014-03-06 18:12:07 -0500 | [diff] [blame] | 131 | probes_insn_handler_t *handler = 0; |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 132 | unsigned reglist = insn & 0xffff; |
| 133 | int is_ldm = insn & 0x100000; |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 134 | int rn = (insn >> 16) & 0xf; |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 135 | |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 136 | if (rn <= 12 && (reglist & 0xe000) == 0) { |
| 137 | /* Instruction only uses registers in the range R0..R12 */ |
| 138 | handler = emulate_generic_r0_12_noflags; |
| 139 | |
| 140 | } else if (rn >= 2 && (reglist & 0x8003) == 0) { |
| 141 | /* Instruction only uses registers in the range R2..R14 */ |
| 142 | rn -= 2; |
| 143 | reglist >>= 2; |
| 144 | handler = emulate_generic_r2_14_noflags; |
| 145 | |
| 146 | } else if (rn >= 3 && (reglist & 0x0007) == 0) { |
| 147 | /* Instruction only uses registers in the range R3..R15 */ |
| 148 | if (is_ldm && (reglist & 0x8000)) { |
| 149 | rn -= 3; |
| 150 | reglist >>= 3; |
| 151 | handler = emulate_ldm_r3_15; |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | if (handler) { |
| 156 | /* We can emulate the instruction in (possibly) modified form */ |
Ben Dooks | 888be25 | 2013-11-08 18:29:25 +0000 | [diff] [blame] | 157 | asi->insn[0] = __opcode_to_mem_arm((insn & 0xfff00000) | |
| 158 | (rn << 16) | reglist); |
Jon Medhurst | 3d4a997 | 2011-06-14 15:54:28 +0100 | [diff] [blame] | 159 | asi->insn_handler = handler; |
| 160 | return INSN_GOOD; |
| 161 | } |
| 162 | |
| 163 | /* Fallback to slower simulation... */ |
Jon Medhurst | 235a4ce | 2011-07-07 08:57:22 +0100 | [diff] [blame] | 164 | if (reglist & 0x8000) |
| 165 | handler = is_ldm ? simulate_ldm1_pc : simulate_stm1_pc; |
| 166 | else |
| 167 | handler = simulate_ldm1stm1; |
| 168 | asi->insn_handler = handler; |
| 169 | return INSN_GOOD_NO_SLOT; |
| 170 | } |
| 171 | |