diff options
-rw-r--r-- | arch/arm/kernel/kprobes.c | 122 | ||||
-rw-r--r-- | arch/arm/kernel/kprobes.h | 7 |
2 files changed, 115 insertions, 14 deletions
diff --git a/arch/arm/kernel/kprobes.c b/arch/arm/kernel/kprobes.c index 0df2d6d57c04..a9050bad4434 100644 --- a/arch/arm/kernel/kprobes.c +++ b/arch/arm/kernel/kprobes.c | |||
@@ -34,10 +34,10 @@ | |||
34 | min((unsigned long)MAX_STACK_SIZE, \ | 34 | min((unsigned long)MAX_STACK_SIZE, \ |
35 | (unsigned long)current_thread_info() + THREAD_START_SP - (addr)) | 35 | (unsigned long)current_thread_info() + THREAD_START_SP - (addr)) |
36 | 36 | ||
37 | #define flush_insns(addr, cnt) \ | 37 | #define flush_insns(addr, size) \ |
38 | flush_icache_range((unsigned long)(addr), \ | 38 | flush_icache_range((unsigned long)(addr), \ |
39 | (unsigned long)(addr) + \ | 39 | (unsigned long)(addr) + \ |
40 | sizeof(kprobe_opcode_t) * (cnt)) | 40 | (size)) |
41 | 41 | ||
42 | /* Used as a marker in ARM_pc to note when we're in a jprobe. */ | 42 | /* Used as a marker in ARM_pc to note when we're in a jprobe. */ |
43 | #define JPROBE_MAGIC_ADDR 0xffffffff | 43 | #define JPROBE_MAGIC_ADDR 0xffffffff |
@@ -86,7 +86,8 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) | |||
86 | return -ENOMEM; | 86 | return -ENOMEM; |
87 | for (is = 0; is < MAX_INSN_SIZE; ++is) | 87 | for (is = 0; is < MAX_INSN_SIZE; ++is) |
88 | p->ainsn.insn[is] = tmp_insn[is]; | 88 | p->ainsn.insn[is] = tmp_insn[is]; |
89 | flush_insns(p->ainsn.insn, MAX_INSN_SIZE); | 89 | flush_insns(p->ainsn.insn, |
90 | sizeof(p->ainsn.insn[0]) * MAX_INSN_SIZE); | ||
90 | break; | 91 | break; |
91 | 92 | ||
92 | case INSN_GOOD_NO_SLOT: /* instruction doesn't need insn slot */ | 93 | case INSN_GOOD_NO_SLOT: /* instruction doesn't need insn slot */ |
@@ -97,24 +98,82 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) | |||
97 | return 0; | 98 | return 0; |
98 | } | 99 | } |
99 | 100 | ||
101 | #ifdef CONFIG_THUMB2_KERNEL | ||
102 | |||
103 | /* | ||
104 | * For a 32-bit Thumb breakpoint spanning two memory words we need to take | ||
105 | * special precautions to insert the breakpoint atomically, especially on SMP | ||
106 | * systems. This is achieved by calling this arming function using stop_machine. | ||
107 | */ | ||
108 | static int __kprobes set_t32_breakpoint(void *addr) | ||
109 | { | ||
110 | ((u16 *)addr)[0] = KPROBE_THUMB32_BREAKPOINT_INSTRUCTION >> 16; | ||
111 | ((u16 *)addr)[1] = KPROBE_THUMB32_BREAKPOINT_INSTRUCTION & 0xffff; | ||
112 | flush_insns(addr, 2*sizeof(u16)); | ||
113 | return 0; | ||
114 | } | ||
115 | |||
100 | void __kprobes arch_arm_kprobe(struct kprobe *p) | 116 | void __kprobes arch_arm_kprobe(struct kprobe *p) |
101 | { | 117 | { |
102 | *p->addr = KPROBE_BREAKPOINT_INSTRUCTION; | 118 | uintptr_t addr = (uintptr_t)p->addr & ~1; /* Remove any Thumb flag */ |
103 | flush_insns(p->addr, 1); | 119 | |
120 | if (!is_wide_instruction(p->opcode)) { | ||
121 | *(u16 *)addr = KPROBE_THUMB16_BREAKPOINT_INSTRUCTION; | ||
122 | flush_insns(addr, sizeof(u16)); | ||
123 | } else if (addr & 2) { | ||
124 | /* A 32-bit instruction spanning two words needs special care */ | ||
125 | stop_machine(set_t32_breakpoint, (void *)addr, &cpu_online_map); | ||
126 | } else { | ||
127 | /* Word aligned 32-bit instruction can be written atomically */ | ||
128 | u32 bkp = KPROBE_THUMB32_BREAKPOINT_INSTRUCTION; | ||
129 | #ifndef __ARMEB__ /* Swap halfwords for little-endian */ | ||
130 | bkp = (bkp >> 16) | (bkp << 16); | ||
131 | #endif | ||
132 | *(u32 *)addr = bkp; | ||
133 | flush_insns(addr, sizeof(u32)); | ||
134 | } | ||
104 | } | 135 | } |
105 | 136 | ||
137 | #else /* !CONFIG_THUMB2_KERNEL */ | ||
138 | |||
139 | void __kprobes arch_arm_kprobe(struct kprobe *p) | ||
140 | { | ||
141 | *p->addr = KPROBE_ARM_BREAKPOINT_INSTRUCTION; | ||
142 | flush_insns(p->addr, sizeof(p->addr[0])); | ||
143 | } | ||
144 | |||
145 | #endif /* !CONFIG_THUMB2_KERNEL */ | ||
146 | |||
106 | /* | 147 | /* |
107 | * The actual disarming is done here on each CPU and synchronized using | 148 | * The actual disarming is done here on each CPU and synchronized using |
108 | * stop_machine. This synchronization is necessary on SMP to avoid removing | 149 | * stop_machine. This synchronization is necessary on SMP to avoid removing |
109 | * a probe between the moment the 'Undefined Instruction' exception is raised | 150 | * a probe between the moment the 'Undefined Instruction' exception is raised |
110 | * and the moment the exception handler reads the faulting instruction from | 151 | * and the moment the exception handler reads the faulting instruction from |
111 | * memory. | 152 | * memory. It is also needed to atomically set the two half-words of a 32-bit |
153 | * Thumb breakpoint. | ||
112 | */ | 154 | */ |
113 | int __kprobes __arch_disarm_kprobe(void *p) | 155 | int __kprobes __arch_disarm_kprobe(void *p) |
114 | { | 156 | { |
115 | struct kprobe *kp = p; | 157 | struct kprobe *kp = p; |
158 | #ifdef CONFIG_THUMB2_KERNEL | ||
159 | u16 *addr = (u16 *)((uintptr_t)kp->addr & ~1); | ||
160 | kprobe_opcode_t insn = kp->opcode; | ||
161 | unsigned int len; | ||
162 | |||
163 | if (is_wide_instruction(insn)) { | ||
164 | ((u16 *)addr)[0] = insn>>16; | ||
165 | ((u16 *)addr)[1] = insn; | ||
166 | len = 2*sizeof(u16); | ||
167 | } else { | ||
168 | ((u16 *)addr)[0] = insn; | ||
169 | len = sizeof(u16); | ||
170 | } | ||
171 | flush_insns(addr, len); | ||
172 | |||
173 | #else /* !CONFIG_THUMB2_KERNEL */ | ||
116 | *kp->addr = kp->opcode; | 174 | *kp->addr = kp->opcode; |
117 | flush_insns(kp->addr, 1); | 175 | flush_insns(kp->addr, sizeof(kp->addr[0])); |
176 | #endif | ||
118 | return 0; | 177 | return 0; |
119 | } | 178 | } |
120 | 179 | ||
@@ -167,11 +226,23 @@ void __kprobes kprobe_handler(struct pt_regs *regs) | |||
167 | { | 226 | { |
168 | struct kprobe *p, *cur; | 227 | struct kprobe *p, *cur; |
169 | struct kprobe_ctlblk *kcb; | 228 | struct kprobe_ctlblk *kcb; |
170 | kprobe_opcode_t *addr = (kprobe_opcode_t *)regs->ARM_pc; | ||
171 | 229 | ||
172 | kcb = get_kprobe_ctlblk(); | 230 | kcb = get_kprobe_ctlblk(); |
173 | cur = kprobe_running(); | 231 | cur = kprobe_running(); |
174 | p = get_kprobe(addr); | 232 | |
233 | #ifdef CONFIG_THUMB2_KERNEL | ||
234 | /* | ||
235 | * First look for a probe which was registered using an address with | ||
236 | * bit 0 set, this is the usual situation for pointers to Thumb code. | ||
237 | * If not found, fallback to looking for one with bit 0 clear. | ||
238 | */ | ||
239 | p = get_kprobe((kprobe_opcode_t *)(regs->ARM_pc | 1)); | ||
240 | if (!p) | ||
241 | p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc); | ||
242 | |||
243 | #else /* ! CONFIG_THUMB2_KERNEL */ | ||
244 | p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc); | ||
245 | #endif | ||
175 | 246 | ||
176 | if (p) { | 247 | if (p) { |
177 | if (cur) { | 248 | if (cur) { |
@@ -511,17 +582,44 @@ int __kprobes arch_trampoline_kprobe(struct kprobe *p) | |||
511 | return 0; | 582 | return 0; |
512 | } | 583 | } |
513 | 584 | ||
514 | static struct undef_hook kprobes_break_hook = { | 585 | #ifdef CONFIG_THUMB2_KERNEL |
586 | |||
587 | static struct undef_hook kprobes_thumb16_break_hook = { | ||
588 | .instr_mask = 0xffff, | ||
589 | .instr_val = KPROBE_THUMB16_BREAKPOINT_INSTRUCTION, | ||
590 | .cpsr_mask = MODE_MASK, | ||
591 | .cpsr_val = SVC_MODE, | ||
592 | .fn = kprobe_trap_handler, | ||
593 | }; | ||
594 | |||
595 | static struct undef_hook kprobes_thumb32_break_hook = { | ||
596 | .instr_mask = 0xffffffff, | ||
597 | .instr_val = KPROBE_THUMB32_BREAKPOINT_INSTRUCTION, | ||
598 | .cpsr_mask = MODE_MASK, | ||
599 | .cpsr_val = SVC_MODE, | ||
600 | .fn = kprobe_trap_handler, | ||
601 | }; | ||
602 | |||
603 | #else /* !CONFIG_THUMB2_KERNEL */ | ||
604 | |||
605 | static struct undef_hook kprobes_arm_break_hook = { | ||
515 | .instr_mask = 0xffffffff, | 606 | .instr_mask = 0xffffffff, |
516 | .instr_val = KPROBE_BREAKPOINT_INSTRUCTION, | 607 | .instr_val = KPROBE_ARM_BREAKPOINT_INSTRUCTION, |
517 | .cpsr_mask = MODE_MASK, | 608 | .cpsr_mask = MODE_MASK, |
518 | .cpsr_val = SVC_MODE, | 609 | .cpsr_val = SVC_MODE, |
519 | .fn = kprobe_trap_handler, | 610 | .fn = kprobe_trap_handler, |
520 | }; | 611 | }; |
521 | 612 | ||
613 | #endif /* !CONFIG_THUMB2_KERNEL */ | ||
614 | |||
522 | int __init arch_init_kprobes() | 615 | int __init arch_init_kprobes() |
523 | { | 616 | { |
524 | arm_kprobe_decode_init(); | 617 | arm_kprobe_decode_init(); |
525 | register_undef_hook(&kprobes_break_hook); | 618 | #ifdef CONFIG_THUMB2_KERNEL |
619 | register_undef_hook(&kprobes_thumb16_break_hook); | ||
620 | register_undef_hook(&kprobes_thumb32_break_hook); | ||
621 | #else | ||
622 | register_undef_hook(&kprobes_arm_break_hook); | ||
623 | #endif | ||
526 | return 0; | 624 | return 0; |
527 | } | 625 | } |
diff --git a/arch/arm/kernel/kprobes.h b/arch/arm/kernel/kprobes.h index 86abfabe83f2..a84b14d8cdc8 100644 --- a/arch/arm/kernel/kprobes.h +++ b/arch/arm/kernel/kprobes.h | |||
@@ -18,10 +18,13 @@ | |||
18 | #define _ARM_KERNEL_KPROBES_H | 18 | #define _ARM_KERNEL_KPROBES_H |
19 | 19 | ||
20 | /* | 20 | /* |
21 | * This undefined instruction must be unique and | 21 | * These undefined instructions must be unique and |
22 | * reserved solely for kprobes' use. | 22 | * reserved solely for kprobes' use. |
23 | */ | 23 | */ |
24 | #define KPROBE_BREAKPOINT_INSTRUCTION 0xe7f001f8 | 24 | #define KPROBE_ARM_BREAKPOINT_INSTRUCTION 0xe7f001f8 |
25 | #define KPROBE_THUMB16_BREAKPOINT_INSTRUCTION 0xde18 | ||
26 | #define KPROBE_THUMB32_BREAKPOINT_INSTRUCTION 0xf7f0a018 | ||
27 | |||
25 | 28 | ||
26 | enum kprobe_insn { | 29 | enum kprobe_insn { |
27 | INSN_REJECTED, | 30 | INSN_REJECTED, |