diff options
| -rw-r--r-- | arch/arm/Kconfig | 3 | ||||
| -rw-r--r-- | arch/arm/include/asm/ptrace.h | 6 | ||||
| -rw-r--r-- | arch/arm/include/asm/thread_info.h | 5 | ||||
| -rw-r--r-- | arch/arm/include/asm/uprobes.h | 45 | ||||
| -rw-r--r-- | arch/arm/kernel/Makefile | 1 | ||||
| -rw-r--r-- | arch/arm/kernel/signal.c | 4 | ||||
| -rw-r--r-- | arch/arm/kernel/uprobes-arm.c | 234 | ||||
| -rw-r--r-- | arch/arm/kernel/uprobes.c | 210 | ||||
| -rw-r--r-- | arch/arm/kernel/uprobes.h | 35 |
9 files changed, 542 insertions, 1 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index e25419817791..4d05bb93714a 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig | |||
| @@ -207,6 +207,9 @@ config ZONE_DMA | |||
| 207 | config NEED_DMA_MAP_STATE | 207 | config NEED_DMA_MAP_STATE |
| 208 | def_bool y | 208 | def_bool y |
| 209 | 209 | ||
| 210 | config ARCH_SUPPORTS_UPROBES | ||
| 211 | def_bool y | ||
| 212 | |||
| 210 | config ARCH_HAS_DMA_SET_COHERENT_MASK | 213 | config ARCH_HAS_DMA_SET_COHERENT_MASK |
| 211 | bool | 214 | bool |
| 212 | 215 | ||
diff --git a/arch/arm/include/asm/ptrace.h b/arch/arm/include/asm/ptrace.h index 04c99f36ff7f..ee688b0a13c3 100644 --- a/arch/arm/include/asm/ptrace.h +++ b/arch/arm/include/asm/ptrace.h | |||
| @@ -80,6 +80,12 @@ static inline long regs_return_value(struct pt_regs *regs) | |||
| 80 | 80 | ||
| 81 | #define instruction_pointer(regs) (regs)->ARM_pc | 81 | #define instruction_pointer(regs) (regs)->ARM_pc |
| 82 | 82 | ||
| 83 | static inline void instruction_pointer_set(struct pt_regs *regs, | ||
| 84 | unsigned long val) | ||
| 85 | { | ||
| 86 | instruction_pointer(regs) = val; | ||
| 87 | } | ||
| 88 | |||
| 83 | #ifdef CONFIG_SMP | 89 | #ifdef CONFIG_SMP |
| 84 | extern unsigned long profile_pc(struct pt_regs *regs); | 90 | extern unsigned long profile_pc(struct pt_regs *regs); |
| 85 | #else | 91 | #else |
diff --git a/arch/arm/include/asm/thread_info.h b/arch/arm/include/asm/thread_info.h index 71a06b293489..f989d7c22dc5 100644 --- a/arch/arm/include/asm/thread_info.h +++ b/arch/arm/include/asm/thread_info.h | |||
| @@ -153,6 +153,7 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *, | |||
| 153 | #define TIF_SIGPENDING 0 | 153 | #define TIF_SIGPENDING 0 |
| 154 | #define TIF_NEED_RESCHED 1 | 154 | #define TIF_NEED_RESCHED 1 |
| 155 | #define TIF_NOTIFY_RESUME 2 /* callback before returning to user */ | 155 | #define TIF_NOTIFY_RESUME 2 /* callback before returning to user */ |
| 156 | #define TIF_UPROBE 7 | ||
| 156 | #define TIF_SYSCALL_TRACE 8 | 157 | #define TIF_SYSCALL_TRACE 8 |
| 157 | #define TIF_SYSCALL_AUDIT 9 | 158 | #define TIF_SYSCALL_AUDIT 9 |
| 158 | #define TIF_SYSCALL_TRACEPOINT 10 | 159 | #define TIF_SYSCALL_TRACEPOINT 10 |
| @@ -165,6 +166,7 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *, | |||
| 165 | #define _TIF_SIGPENDING (1 << TIF_SIGPENDING) | 166 | #define _TIF_SIGPENDING (1 << TIF_SIGPENDING) |
| 166 | #define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED) | 167 | #define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED) |
| 167 | #define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME) | 168 | #define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME) |
| 169 | #define _TIF_UPROBE (1 << TIF_UPROBE) | ||
| 168 | #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) | 170 | #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) |
| 169 | #define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT) | 171 | #define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT) |
| 170 | #define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) | 172 | #define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) |
| @@ -178,7 +180,8 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *, | |||
| 178 | /* | 180 | /* |
| 179 | * Change these and you break ASM code in entry-common.S | 181 | * Change these and you break ASM code in entry-common.S |
| 180 | */ | 182 | */ |
| 181 | #define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME) | 183 | #define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \ |
| 184 | _TIF_NOTIFY_RESUME | _TIF_UPROBE) | ||
| 182 | 185 | ||
| 183 | #endif /* __KERNEL__ */ | 186 | #endif /* __KERNEL__ */ |
| 184 | #endif /* __ASM_ARM_THREAD_INFO_H */ | 187 | #endif /* __ASM_ARM_THREAD_INFO_H */ |
diff --git a/arch/arm/include/asm/uprobes.h b/arch/arm/include/asm/uprobes.h new file mode 100644 index 000000000000..9472c20b7d49 --- /dev/null +++ b/arch/arm/include/asm/uprobes.h | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) 2012 Rabin Vincent <rabin at rab.in> | ||
| 3 | * | ||
| 4 | * This program is free software; you can redistribute it and/or modify | ||
| 5 | * it under the terms of the GNU General Public License version 2 as | ||
| 6 | * published by the Free Software Foundation. | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _ASM_UPROBES_H | ||
| 10 | #define _ASM_UPROBES_H | ||
| 11 | |||
| 12 | #include <asm/probes.h> | ||
| 13 | #include <asm/opcodes.h> | ||
| 14 | |||
| 15 | typedef u32 uprobe_opcode_t; | ||
| 16 | |||
| 17 | #define MAX_UINSN_BYTES 4 | ||
| 18 | #define UPROBE_XOL_SLOT_BYTES 64 | ||
| 19 | |||
| 20 | #define UPROBE_SWBP_ARM_INSN 0xe7f001f9 | ||
| 21 | #define UPROBE_SS_ARM_INSN 0xe7f001fa | ||
| 22 | #define UPROBE_SWBP_INSN __opcode_to_mem_arm(UPROBE_SWBP_ARM_INSN) | ||
| 23 | #define UPROBE_SWBP_INSN_SIZE 4 | ||
| 24 | |||
| 25 | struct arch_uprobe_task { | ||
| 26 | u32 backup; | ||
| 27 | unsigned long saved_trap_no; | ||
| 28 | }; | ||
| 29 | |||
| 30 | struct arch_uprobe { | ||
| 31 | u8 insn[MAX_UINSN_BYTES]; | ||
| 32 | unsigned long ixol[2]; | ||
| 33 | uprobe_opcode_t bpinsn; | ||
| 34 | bool simulate; | ||
| 35 | u32 pcreg; | ||
| 36 | void (*prehandler)(struct arch_uprobe *auprobe, | ||
| 37 | struct arch_uprobe_task *autask, | ||
| 38 | struct pt_regs *regs); | ||
| 39 | void (*posthandler)(struct arch_uprobe *auprobe, | ||
| 40 | struct arch_uprobe_task *autask, | ||
| 41 | struct pt_regs *regs); | ||
| 42 | struct arch_probes_insn asi; | ||
| 43 | }; | ||
| 44 | |||
| 45 | #endif | ||
diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index bb739f28dd80..a766bcbaf8ad 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile | |||
| @@ -50,6 +50,7 @@ obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o insn.o | |||
| 50 | obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o insn.o | 50 | obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o insn.o |
| 51 | obj-$(CONFIG_JUMP_LABEL) += jump_label.o insn.o patch.o | 51 | obj-$(CONFIG_JUMP_LABEL) += jump_label.o insn.o patch.o |
| 52 | obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o | 52 | obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o |
| 53 | obj-$(CONFIG_UPROBES) += probes.o probes-arm.o uprobes.o uprobes-arm.o | ||
| 53 | obj-$(CONFIG_KPROBES) += probes.o kprobes.o kprobes-common.o patch.o | 54 | obj-$(CONFIG_KPROBES) += probes.o kprobes.o kprobes-common.o patch.o |
| 54 | ifdef CONFIG_THUMB2_KERNEL | 55 | ifdef CONFIG_THUMB2_KERNEL |
| 55 | obj-$(CONFIG_KPROBES) += kprobes-thumb.o probes-thumb.o | 56 | obj-$(CONFIG_KPROBES) += kprobes-thumb.o probes-thumb.o |
diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c index 04d63880037f..bd1983437205 100644 --- a/arch/arm/kernel/signal.c +++ b/arch/arm/kernel/signal.c | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include <linux/personality.h> | 13 | #include <linux/personality.h> |
| 14 | #include <linux/uaccess.h> | 14 | #include <linux/uaccess.h> |
| 15 | #include <linux/tracehook.h> | 15 | #include <linux/tracehook.h> |
| 16 | #include <linux/uprobes.h> | ||
| 16 | 17 | ||
| 17 | #include <asm/elf.h> | 18 | #include <asm/elf.h> |
| 18 | #include <asm/cacheflush.h> | 19 | #include <asm/cacheflush.h> |
| @@ -590,6 +591,9 @@ do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall) | |||
| 590 | return restart; | 591 | return restart; |
| 591 | } | 592 | } |
| 592 | syscall = 0; | 593 | syscall = 0; |
| 594 | } else if (thread_flags & _TIF_UPROBE) { | ||
| 595 | clear_thread_flag(TIF_UPROBE); | ||
| 596 | uprobe_notify_resume(regs); | ||
| 593 | } else { | 597 | } else { |
| 594 | clear_thread_flag(TIF_NOTIFY_RESUME); | 598 | clear_thread_flag(TIF_NOTIFY_RESUME); |
| 595 | tracehook_notify_resume(regs); | 599 | tracehook_notify_resume(regs); |
diff --git a/arch/arm/kernel/uprobes-arm.c b/arch/arm/kernel/uprobes-arm.c new file mode 100644 index 000000000000..d3b655ff17da --- /dev/null +++ b/arch/arm/kernel/uprobes-arm.c | |||
| @@ -0,0 +1,234 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) 2012 Rabin Vincent <rabin at rab.in> | ||
| 3 | * | ||
| 4 | * This program is free software; you can redistribute it and/or modify | ||
| 5 | * it under the terms of the GNU General Public License version 2 as | ||
| 6 | * published by the Free Software Foundation. | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/kernel.h> | ||
| 10 | #include <linux/types.h> | ||
| 11 | #include <linux/stddef.h> | ||
| 12 | #include <linux/wait.h> | ||
| 13 | #include <linux/uprobes.h> | ||
| 14 | #include <linux/module.h> | ||
| 15 | |||
| 16 | #include "probes.h" | ||
| 17 | #include "probes-arm.h" | ||
| 18 | #include "uprobes.h" | ||
| 19 | |||
| 20 | static int uprobes_substitute_pc(unsigned long *pinsn, u32 oregs) | ||
| 21 | { | ||
| 22 | probes_opcode_t insn = __mem_to_opcode_arm(*pinsn); | ||
| 23 | probes_opcode_t temp; | ||
| 24 | probes_opcode_t mask; | ||
| 25 | int freereg; | ||
| 26 | u32 free = 0xffff; | ||
| 27 | u32 regs; | ||
| 28 | |||
| 29 | for (regs = oregs; regs; regs >>= 4, insn >>= 4) { | ||
| 30 | if ((regs & 0xf) == REG_TYPE_NONE) | ||
| 31 | continue; | ||
| 32 | |||
| 33 | free &= ~(1 << (insn & 0xf)); | ||
| 34 | } | ||
| 35 | |||
| 36 | /* No PC, no problem */ | ||
| 37 | if (free & (1 << 15)) | ||
| 38 | return 15; | ||
| 39 | |||
| 40 | if (!free) | ||
| 41 | return -1; | ||
| 42 | |||
| 43 | /* | ||
| 44 | * fls instead of ffs ensures that for "ldrd r0, r1, [pc]" we would | ||
| 45 | * pick LR instead of R1. | ||
| 46 | */ | ||
| 47 | freereg = free = fls(free) - 1; | ||
| 48 | |||
| 49 | temp = __mem_to_opcode_arm(*pinsn); | ||
| 50 | insn = temp; | ||
| 51 | regs = oregs; | ||
| 52 | mask = 0xf; | ||
| 53 | |||
| 54 | for (; regs; regs >>= 4, mask <<= 4, free <<= 4, temp >>= 4) { | ||
| 55 | if ((regs & 0xf) == REG_TYPE_NONE) | ||
| 56 | continue; | ||
| 57 | |||
| 58 | if ((temp & 0xf) != 15) | ||
| 59 | continue; | ||
| 60 | |||
| 61 | insn &= ~mask; | ||
| 62 | insn |= free & mask; | ||
| 63 | } | ||
| 64 | |||
| 65 | *pinsn = __opcode_to_mem_arm(insn); | ||
| 66 | return freereg; | ||
| 67 | } | ||
| 68 | |||
| 69 | static void uprobe_set_pc(struct arch_uprobe *auprobe, | ||
| 70 | struct arch_uprobe_task *autask, | ||
| 71 | struct pt_regs *regs) | ||
| 72 | { | ||
| 73 | u32 pcreg = auprobe->pcreg; | ||
| 74 | |||
| 75 | autask->backup = regs->uregs[pcreg]; | ||
| 76 | regs->uregs[pcreg] = regs->ARM_pc + 8; | ||
| 77 | } | ||
| 78 | |||
| 79 | static void uprobe_unset_pc(struct arch_uprobe *auprobe, | ||
| 80 | struct arch_uprobe_task *autask, | ||
| 81 | struct pt_regs *regs) | ||
| 82 | { | ||
| 83 | /* PC will be taken care of by common code */ | ||
| 84 | regs->uregs[auprobe->pcreg] = autask->backup; | ||
| 85 | } | ||
| 86 | |||
| 87 | static void uprobe_aluwrite_pc(struct arch_uprobe *auprobe, | ||
| 88 | struct arch_uprobe_task *autask, | ||
| 89 | struct pt_regs *regs) | ||
| 90 | { | ||
| 91 | u32 pcreg = auprobe->pcreg; | ||
| 92 | |||
| 93 | alu_write_pc(regs->uregs[pcreg], regs); | ||
| 94 | regs->uregs[pcreg] = autask->backup; | ||
| 95 | } | ||
| 96 | |||
| 97 | static void uprobe_write_pc(struct arch_uprobe *auprobe, | ||
| 98 | struct arch_uprobe_task *autask, | ||
| 99 | struct pt_regs *regs) | ||
| 100 | { | ||
| 101 | u32 pcreg = auprobe->pcreg; | ||
| 102 | |||
| 103 | load_write_pc(regs->uregs[pcreg], regs); | ||
| 104 | regs->uregs[pcreg] = autask->backup; | ||
| 105 | } | ||
| 106 | |||
| 107 | enum probes_insn | ||
| 108 | decode_pc_ro(probes_opcode_t insn, struct arch_probes_insn *asi, | ||
| 109 | const struct decode_header *d) | ||
| 110 | { | ||
| 111 | struct arch_uprobe *auprobe = container_of(asi, struct arch_uprobe, | ||
| 112 | asi); | ||
| 113 | struct decode_emulate *decode = (struct decode_emulate *) d; | ||
| 114 | u32 regs = decode->header.type_regs.bits >> DECODE_TYPE_BITS; | ||
| 115 | int reg; | ||
| 116 | |||
| 117 | reg = uprobes_substitute_pc(&auprobe->ixol[0], regs); | ||
| 118 | if (reg == 15) | ||
| 119 | return INSN_GOOD; | ||
| 120 | |||
| 121 | if (reg == -1) | ||
| 122 | return INSN_REJECTED; | ||
| 123 | |||
| 124 | auprobe->pcreg = reg; | ||
| 125 | auprobe->prehandler = uprobe_set_pc; | ||
| 126 | auprobe->posthandler = uprobe_unset_pc; | ||
| 127 | |||
| 128 | return INSN_GOOD; | ||
| 129 | } | ||
| 130 | |||
| 131 | enum probes_insn | ||
| 132 | decode_wb_pc(probes_opcode_t insn, struct arch_probes_insn *asi, | ||
| 133 | const struct decode_header *d, bool alu) | ||
| 134 | { | ||
| 135 | struct arch_uprobe *auprobe = container_of(asi, struct arch_uprobe, | ||
| 136 | asi); | ||
| 137 | enum probes_insn ret = decode_pc_ro(insn, asi, d); | ||
| 138 | |||
| 139 | if (((insn >> 12) & 0xf) == 15) | ||
| 140 | auprobe->posthandler = alu ? uprobe_aluwrite_pc | ||
| 141 | : uprobe_write_pc; | ||
| 142 | |||
| 143 | return ret; | ||
| 144 | } | ||
| 145 | |||
| 146 | enum probes_insn | ||
| 147 | decode_rd12rn16rm0rs8_rwflags(probes_opcode_t insn, | ||
| 148 | struct arch_probes_insn *asi, | ||
| 149 | const struct decode_header *d) | ||
| 150 | { | ||
| 151 | return decode_wb_pc(insn, asi, d, true); | ||
| 152 | } | ||
| 153 | |||
| 154 | enum probes_insn | ||
| 155 | decode_ldr(probes_opcode_t insn, struct arch_probes_insn *asi, | ||
| 156 | const struct decode_header *d) | ||
| 157 | { | ||
| 158 | return decode_wb_pc(insn, asi, d, false); | ||
| 159 | } | ||
| 160 | |||
| 161 | enum probes_insn | ||
| 162 | uprobe_decode_ldmstm(probes_opcode_t insn, | ||
| 163 | struct arch_probes_insn *asi, | ||
| 164 | const struct decode_header *d) | ||
| 165 | { | ||
| 166 | struct arch_uprobe *auprobe = container_of(asi, struct arch_uprobe, | ||
| 167 | asi); | ||
| 168 | unsigned reglist = insn & 0xffff; | ||
| 169 | int rn = (insn >> 16) & 0xf; | ||
| 170 | int lbit = insn & (1 << 20); | ||
| 171 | unsigned used = reglist | (1 << rn); | ||
| 172 | |||
| 173 | if (rn == 15) | ||
| 174 | return INSN_REJECTED; | ||
| 175 | |||
| 176 | if (!(used & (1 << 15))) | ||
| 177 | return INSN_GOOD; | ||
| 178 | |||
| 179 | if (used & (1 << 14)) | ||
| 180 | return INSN_REJECTED; | ||
| 181 | |||
| 182 | /* Use LR instead of PC */ | ||
| 183 | insn ^= 0xc000; | ||
| 184 | |||
| 185 | auprobe->pcreg = 14; | ||
| 186 | auprobe->ixol[0] = __opcode_to_mem_arm(insn); | ||
| 187 | |||
| 188 | auprobe->prehandler = uprobe_set_pc; | ||
| 189 | if (lbit) | ||
| 190 | auprobe->posthandler = uprobe_write_pc; | ||
| 191 | else | ||
| 192 | auprobe->posthandler = uprobe_unset_pc; | ||
| 193 | |||
| 194 | return INSN_GOOD; | ||
| 195 | } | ||
| 196 | |||
| 197 | const union decode_action uprobes_probes_actions[] = { | ||
| 198 | [PROBES_EMULATE_NONE] = {.handler = probes_simulate_nop}, | ||
| 199 | [PROBES_SIMULATE_NOP] = {.handler = probes_simulate_nop}, | ||
| 200 | [PROBES_PRELOAD_IMM] = {.handler = probes_simulate_nop}, | ||
| 201 | [PROBES_PRELOAD_REG] = {.handler = probes_simulate_nop}, | ||
| 202 | [PROBES_BRANCH_IMM] = {.handler = simulate_blx1}, | ||
| 203 | [PROBES_MRS] = {.handler = simulate_mrs}, | ||
| 204 | [PROBES_BRANCH_REG] = {.handler = simulate_blx2bx}, | ||
| 205 | [PROBES_CLZ] = {.handler = probes_simulate_nop}, | ||
| 206 | [PROBES_SATURATING_ARITHMETIC] = {.handler = probes_simulate_nop}, | ||
| 207 | [PROBES_MUL1] = {.handler = probes_simulate_nop}, | ||
| 208 | [PROBES_MUL2] = {.handler = probes_simulate_nop}, | ||
| 209 | [PROBES_SWP] = {.handler = probes_simulate_nop}, | ||
| 210 | [PROBES_LDRSTRD] = {.decoder = decode_pc_ro}, | ||
| 211 | [PROBES_LOAD_EXTRA] = {.decoder = decode_pc_ro}, | ||
| 212 | [PROBES_LOAD] = {.decoder = decode_ldr}, | ||
| 213 | [PROBES_STORE_EXTRA] = {.decoder = decode_pc_ro}, | ||
| 214 | [PROBES_STORE] = {.decoder = decode_pc_ro}, | ||
| 215 | [PROBES_MOV_IP_SP] = {.handler = simulate_mov_ipsp}, | ||
| 216 | [PROBES_DATA_PROCESSING_REG] = { | ||
| 217 | .decoder = decode_rd12rn16rm0rs8_rwflags}, | ||
| 218 | [PROBES_DATA_PROCESSING_IMM] = { | ||
| 219 | .decoder = decode_rd12rn16rm0rs8_rwflags}, | ||
| 220 | [PROBES_MOV_HALFWORD] = {.handler = probes_simulate_nop}, | ||
| 221 | [PROBES_SEV] = {.handler = probes_simulate_nop}, | ||
| 222 | [PROBES_WFE] = {.handler = probes_simulate_nop}, | ||
| 223 | [PROBES_SATURATE] = {.handler = probes_simulate_nop}, | ||
| 224 | [PROBES_REV] = {.handler = probes_simulate_nop}, | ||
| 225 | [PROBES_MMI] = {.handler = probes_simulate_nop}, | ||
| 226 | [PROBES_PACK] = {.handler = probes_simulate_nop}, | ||
| 227 | [PROBES_EXTEND] = {.handler = probes_simulate_nop}, | ||
| 228 | [PROBES_EXTEND_ADD] = {.handler = probes_simulate_nop}, | ||
| 229 | [PROBES_MUL_ADD_LONG] = {.handler = probes_simulate_nop}, | ||
| 230 | [PROBES_MUL_ADD] = {.handler = probes_simulate_nop}, | ||
| 231 | [PROBES_BITFIELD] = {.handler = probes_simulate_nop}, | ||
| 232 | [PROBES_BRANCH] = {.handler = simulate_bbl}, | ||
| 233 | [PROBES_LDMSTM] = {.decoder = uprobe_decode_ldmstm} | ||
| 234 | }; | ||
diff --git a/arch/arm/kernel/uprobes.c b/arch/arm/kernel/uprobes.c new file mode 100644 index 000000000000..f9bacee973bf --- /dev/null +++ b/arch/arm/kernel/uprobes.c | |||
| @@ -0,0 +1,210 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) 2012 Rabin Vincent <rabin at rab.in> | ||
| 3 | * | ||
| 4 | * This program is free software; you can redistribute it and/or modify | ||
| 5 | * it under the terms of the GNU General Public License version 2 as | ||
| 6 | * published by the Free Software Foundation. | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/kernel.h> | ||
| 10 | #include <linux/stddef.h> | ||
| 11 | #include <linux/errno.h> | ||
| 12 | #include <linux/highmem.h> | ||
| 13 | #include <linux/sched.h> | ||
| 14 | #include <linux/uprobes.h> | ||
| 15 | #include <linux/notifier.h> | ||
| 16 | |||
| 17 | #include <asm/opcodes.h> | ||
| 18 | #include <asm/traps.h> | ||
| 19 | |||
| 20 | #include "probes.h" | ||
| 21 | #include "probes-arm.h" | ||
| 22 | #include "uprobes.h" | ||
| 23 | |||
| 24 | #define UPROBE_TRAP_NR UINT_MAX | ||
| 25 | |||
| 26 | bool is_swbp_insn(uprobe_opcode_t *insn) | ||
| 27 | { | ||
| 28 | return (__mem_to_opcode_arm(*insn) & 0x0fffffff) == | ||
| 29 | (UPROBE_SWBP_ARM_INSN & 0x0fffffff); | ||
| 30 | } | ||
| 31 | |||
| 32 | int set_swbp(struct arch_uprobe *auprobe, struct mm_struct *mm, | ||
| 33 | unsigned long vaddr) | ||
| 34 | { | ||
| 35 | return uprobe_write_opcode(mm, vaddr, | ||
| 36 | __opcode_to_mem_arm(auprobe->bpinsn)); | ||
| 37 | } | ||
| 38 | |||
| 39 | bool arch_uprobe_ignore(struct arch_uprobe *auprobe, struct pt_regs *regs) | ||
| 40 | { | ||
| 41 | if (!auprobe->asi.insn_check_cc(regs->ARM_cpsr)) { | ||
| 42 | regs->ARM_pc += 4; | ||
| 43 | return true; | ||
| 44 | } | ||
| 45 | |||
| 46 | return false; | ||
| 47 | } | ||
| 48 | |||
| 49 | bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs) | ||
| 50 | { | ||
| 51 | probes_opcode_t opcode; | ||
| 52 | |||
| 53 | if (!auprobe->simulate) | ||
| 54 | return false; | ||
| 55 | |||
| 56 | opcode = __mem_to_opcode_arm(*(unsigned int *) auprobe->insn); | ||
| 57 | |||
| 58 | auprobe->asi.insn_singlestep(opcode, &auprobe->asi, regs); | ||
| 59 | |||
| 60 | return true; | ||
| 61 | } | ||
| 62 | |||
| 63 | unsigned long | ||
| 64 | arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr, | ||
| 65 | struct pt_regs *regs) | ||
| 66 | { | ||
| 67 | unsigned long orig_ret_vaddr; | ||
| 68 | |||
| 69 | orig_ret_vaddr = regs->ARM_lr; | ||
| 70 | /* Replace the return addr with trampoline addr */ | ||
| 71 | regs->ARM_lr = trampoline_vaddr; | ||
| 72 | return orig_ret_vaddr; | ||
| 73 | } | ||
| 74 | |||
| 75 | int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, | ||
| 76 | unsigned long addr) | ||
| 77 | { | ||
| 78 | unsigned int insn; | ||
| 79 | unsigned int bpinsn; | ||
| 80 | enum probes_insn ret; | ||
| 81 | |||
| 82 | /* Thumb not yet support */ | ||
| 83 | if (addr & 0x3) | ||
| 84 | return -EINVAL; | ||
| 85 | |||
| 86 | insn = __mem_to_opcode_arm(*(unsigned int *)auprobe->insn); | ||
| 87 | auprobe->ixol[0] = __opcode_to_mem_arm(insn); | ||
| 88 | auprobe->ixol[1] = __opcode_to_mem_arm(UPROBE_SS_ARM_INSN); | ||
| 89 | |||
| 90 | ret = arm_probes_decode_insn(insn, &auprobe->asi, false, | ||
| 91 | uprobes_probes_actions); | ||
| 92 | switch (ret) { | ||
| 93 | case INSN_REJECTED: | ||
| 94 | return -EINVAL; | ||
| 95 | |||
| 96 | case INSN_GOOD_NO_SLOT: | ||
| 97 | auprobe->simulate = true; | ||
| 98 | break; | ||
| 99 | |||
| 100 | case INSN_GOOD: | ||
| 101 | default: | ||
| 102 | break; | ||
| 103 | } | ||
| 104 | |||
| 105 | bpinsn = UPROBE_SWBP_ARM_INSN & 0x0fffffff; | ||
| 106 | if (insn >= 0xe0000000) | ||
| 107 | bpinsn |= 0xe0000000; /* Unconditional instruction */ | ||
| 108 | else | ||
| 109 | bpinsn |= insn & 0xf0000000; /* Copy condition from insn */ | ||
| 110 | |||
| 111 | auprobe->bpinsn = bpinsn; | ||
| 112 | |||
| 113 | return 0; | ||
| 114 | } | ||
| 115 | |||
| 116 | int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) | ||
| 117 | { | ||
| 118 | struct uprobe_task *utask = current->utask; | ||
| 119 | |||
| 120 | if (auprobe->prehandler) | ||
| 121 | auprobe->prehandler(auprobe, &utask->autask, regs); | ||
| 122 | |||
| 123 | utask->autask.saved_trap_no = current->thread.trap_no; | ||
| 124 | current->thread.trap_no = UPROBE_TRAP_NR; | ||
| 125 | regs->ARM_pc = utask->xol_vaddr; | ||
| 126 | |||
| 127 | return 0; | ||
| 128 | } | ||
| 129 | |||
| 130 | int arch_uprobe_post_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) | ||
| 131 | { | ||
| 132 | struct uprobe_task *utask = current->utask; | ||
| 133 | |||
| 134 | WARN_ON_ONCE(current->thread.trap_no != UPROBE_TRAP_NR); | ||
| 135 | |||
| 136 | current->thread.trap_no = utask->autask.saved_trap_no; | ||
| 137 | regs->ARM_pc = utask->vaddr + 4; | ||
| 138 | |||
| 139 | if (auprobe->posthandler) | ||
| 140 | auprobe->posthandler(auprobe, &utask->autask, regs); | ||
| 141 | |||
| 142 | return 0; | ||
| 143 | } | ||
| 144 | |||
| 145 | bool arch_uprobe_xol_was_trapped(struct task_struct *t) | ||
| 146 | { | ||
| 147 | if (t->thread.trap_no != UPROBE_TRAP_NR) | ||
| 148 | return true; | ||
| 149 | |||
| 150 | return false; | ||
| 151 | } | ||
| 152 | |||
| 153 | void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) | ||
| 154 | { | ||
| 155 | struct uprobe_task *utask = current->utask; | ||
| 156 | |||
| 157 | current->thread.trap_no = utask->autask.saved_trap_no; | ||
| 158 | instruction_pointer_set(regs, utask->vaddr); | ||
| 159 | } | ||
| 160 | |||
| 161 | int arch_uprobe_exception_notify(struct notifier_block *self, | ||
| 162 | unsigned long val, void *data) | ||
| 163 | { | ||
| 164 | return NOTIFY_DONE; | ||
| 165 | } | ||
| 166 | |||
| 167 | static int uprobe_trap_handler(struct pt_regs *regs, unsigned int instr) | ||
| 168 | { | ||
| 169 | unsigned long flags; | ||
| 170 | |||
| 171 | local_irq_save(flags); | ||
| 172 | instr &= 0x0fffffff; | ||
| 173 | if (instr == (UPROBE_SWBP_ARM_INSN & 0x0fffffff)) | ||
| 174 | uprobe_pre_sstep_notifier(regs); | ||
| 175 | else if (instr == (UPROBE_SS_ARM_INSN & 0x0fffffff)) | ||
| 176 | uprobe_post_sstep_notifier(regs); | ||
| 177 | local_irq_restore(flags); | ||
| 178 | |||
| 179 | return 0; | ||
| 180 | } | ||
| 181 | |||
| 182 | unsigned long uprobe_get_swbp_addr(struct pt_regs *regs) | ||
| 183 | { | ||
| 184 | return instruction_pointer(regs); | ||
| 185 | } | ||
| 186 | |||
| 187 | static struct undef_hook uprobes_arm_break_hook = { | ||
| 188 | .instr_mask = 0x0fffffff, | ||
| 189 | .instr_val = (UPROBE_SWBP_ARM_INSN & 0x0fffffff), | ||
| 190 | .cpsr_mask = MODE_MASK, | ||
| 191 | .cpsr_val = USR_MODE, | ||
| 192 | .fn = uprobe_trap_handler, | ||
| 193 | }; | ||
| 194 | |||
| 195 | static struct undef_hook uprobes_arm_ss_hook = { | ||
| 196 | .instr_mask = 0x0fffffff, | ||
| 197 | .instr_val = (UPROBE_SS_ARM_INSN & 0x0fffffff), | ||
| 198 | .cpsr_mask = MODE_MASK, | ||
| 199 | .cpsr_val = USR_MODE, | ||
| 200 | .fn = uprobe_trap_handler, | ||
| 201 | }; | ||
| 202 | |||
| 203 | static int arch_uprobes_init(void) | ||
| 204 | { | ||
| 205 | register_undef_hook(&uprobes_arm_break_hook); | ||
| 206 | register_undef_hook(&uprobes_arm_ss_hook); | ||
| 207 | |||
| 208 | return 0; | ||
| 209 | } | ||
| 210 | device_initcall(arch_uprobes_init); | ||
diff --git a/arch/arm/kernel/uprobes.h b/arch/arm/kernel/uprobes.h new file mode 100644 index 000000000000..1d0c12dfbd03 --- /dev/null +++ b/arch/arm/kernel/uprobes.h | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) 2012 Rabin Vincent <rabin at rab.in> | ||
| 3 | * | ||
| 4 | * This program is free software; you can redistribute it and/or modify | ||
| 5 | * it under the terms of the GNU General Public License version 2 as | ||
| 6 | * published by the Free Software Foundation. | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef __ARM_KERNEL_UPROBES_H | ||
| 10 | #define __ARM_KERNEL_UPROBES_H | ||
| 11 | |||
| 12 | enum probes_insn uprobe_decode_ldmstm(probes_opcode_t insn, | ||
| 13 | struct arch_probes_insn *asi, | ||
| 14 | const struct decode_header *d); | ||
| 15 | |||
| 16 | enum probes_insn decode_ldr(probes_opcode_t insn, | ||
| 17 | struct arch_probes_insn *asi, | ||
| 18 | const struct decode_header *d); | ||
| 19 | |||
| 20 | enum probes_insn | ||
| 21 | decode_rd12rn16rm0rs8_rwflags(probes_opcode_t insn, | ||
| 22 | struct arch_probes_insn *asi, | ||
| 23 | const struct decode_header *d); | ||
| 24 | |||
| 25 | enum probes_insn | ||
| 26 | decode_wb_pc(probes_opcode_t insn, struct arch_probes_insn *asi, | ||
| 27 | const struct decode_header *d, bool alu); | ||
| 28 | |||
| 29 | enum probes_insn | ||
| 30 | decode_pc_ro(probes_opcode_t insn, struct arch_probes_insn *asi, | ||
| 31 | const struct decode_header *d); | ||
| 32 | |||
| 33 | extern const union decode_action uprobes_probes_actions[]; | ||
| 34 | |||
| 35 | #endif | ||
