diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2011-08-08 02:22:41 -0400 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2011-08-08 02:22:41 -0400 |
commit | 1a43f2012455a977397deffe35912fd3f3ce17b9 (patch) | |
tree | 5189f337df44e7a495fbd097cd476b0380babd8c /arch/arm/kernel/kprobes.c | |
parent | e1b96ada659431669efaf3defa997abf5db68130 (diff) | |
parent | 322a8b034003c0d46d39af85bf24fee27b902f48 (diff) |
Merge commit 'v3.1-rc1' into imx-fixes
Diffstat (limited to 'arch/arm/kernel/kprobes.c')
-rw-r--r-- | arch/arm/kernel/kprobes.c | 222 |
1 files changed, 201 insertions, 21 deletions
diff --git a/arch/arm/kernel/kprobes.c b/arch/arm/kernel/kprobes.c index 1656c87501c0..129c1163248b 100644 --- a/arch/arm/kernel/kprobes.c +++ b/arch/arm/kernel/kprobes.c | |||
@@ -28,14 +28,16 @@ | |||
28 | #include <asm/traps.h> | 28 | #include <asm/traps.h> |
29 | #include <asm/cacheflush.h> | 29 | #include <asm/cacheflush.h> |
30 | 30 | ||
31 | #include "kprobes.h" | ||
32 | |||
31 | #define MIN_STACK_SIZE(addr) \ | 33 | #define MIN_STACK_SIZE(addr) \ |
32 | min((unsigned long)MAX_STACK_SIZE, \ | 34 | min((unsigned long)MAX_STACK_SIZE, \ |
33 | (unsigned long)current_thread_info() + THREAD_START_SP - (addr)) | 35 | (unsigned long)current_thread_info() + THREAD_START_SP - (addr)) |
34 | 36 | ||
35 | #define flush_insns(addr, cnt) \ | 37 | #define flush_insns(addr, size) \ |
36 | flush_icache_range((unsigned long)(addr), \ | 38 | flush_icache_range((unsigned long)(addr), \ |
37 | (unsigned long)(addr) + \ | 39 | (unsigned long)(addr) + \ |
38 | sizeof(kprobe_opcode_t) * (cnt)) | 40 | (size)) |
39 | 41 | ||
40 | /* 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. */ |
41 | #define JPROBE_MAGIC_ADDR 0xffffffff | 43 | #define JPROBE_MAGIC_ADDR 0xffffffff |
@@ -49,16 +51,35 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) | |||
49 | kprobe_opcode_t insn; | 51 | kprobe_opcode_t insn; |
50 | kprobe_opcode_t tmp_insn[MAX_INSN_SIZE]; | 52 | kprobe_opcode_t tmp_insn[MAX_INSN_SIZE]; |
51 | unsigned long addr = (unsigned long)p->addr; | 53 | unsigned long addr = (unsigned long)p->addr; |
54 | bool thumb; | ||
55 | kprobe_decode_insn_t *decode_insn; | ||
52 | int is; | 56 | int is; |
53 | 57 | ||
54 | if (addr & 0x3 || in_exception_text(addr)) | 58 | if (in_exception_text(addr)) |
55 | return -EINVAL; | 59 | return -EINVAL; |
56 | 60 | ||
61 | #ifdef CONFIG_THUMB2_KERNEL | ||
62 | thumb = true; | ||
63 | addr &= ~1; /* Bit 0 would normally be set to indicate Thumb code */ | ||
64 | insn = ((u16 *)addr)[0]; | ||
65 | if (is_wide_instruction(insn)) { | ||
66 | insn <<= 16; | ||
67 | insn |= ((u16 *)addr)[1]; | ||
68 | decode_insn = thumb32_kprobe_decode_insn; | ||
69 | } else | ||
70 | decode_insn = thumb16_kprobe_decode_insn; | ||
71 | #else /* !CONFIG_THUMB2_KERNEL */ | ||
72 | thumb = false; | ||
73 | if (addr & 0x3) | ||
74 | return -EINVAL; | ||
57 | insn = *p->addr; | 75 | insn = *p->addr; |
76 | decode_insn = arm_kprobe_decode_insn; | ||
77 | #endif | ||
78 | |||
58 | p->opcode = insn; | 79 | p->opcode = insn; |
59 | p->ainsn.insn = tmp_insn; | 80 | p->ainsn.insn = tmp_insn; |
60 | 81 | ||
61 | switch (arm_kprobe_decode_insn(insn, &p->ainsn)) { | 82 | switch ((*decode_insn)(insn, &p->ainsn)) { |
62 | case INSN_REJECTED: /* not supported */ | 83 | case INSN_REJECTED: /* not supported */ |
63 | return -EINVAL; | 84 | return -EINVAL; |
64 | 85 | ||
@@ -68,7 +89,10 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) | |||
68 | return -ENOMEM; | 89 | return -ENOMEM; |
69 | for (is = 0; is < MAX_INSN_SIZE; ++is) | 90 | for (is = 0; is < MAX_INSN_SIZE; ++is) |
70 | p->ainsn.insn[is] = tmp_insn[is]; | 91 | p->ainsn.insn[is] = tmp_insn[is]; |
71 | flush_insns(p->ainsn.insn, MAX_INSN_SIZE); | 92 | flush_insns(p->ainsn.insn, |
93 | sizeof(p->ainsn.insn[0]) * MAX_INSN_SIZE); | ||
94 | p->ainsn.insn_fn = (kprobe_insn_fn_t *) | ||
95 | ((uintptr_t)p->ainsn.insn | thumb); | ||
72 | break; | 96 | break; |
73 | 97 | ||
74 | case INSN_GOOD_NO_SLOT: /* instruction doesn't need insn slot */ | 98 | case INSN_GOOD_NO_SLOT: /* instruction doesn't need insn slot */ |
@@ -79,24 +103,88 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) | |||
79 | return 0; | 103 | return 0; |
80 | } | 104 | } |
81 | 105 | ||
106 | #ifdef CONFIG_THUMB2_KERNEL | ||
107 | |||
108 | /* | ||
109 | * For a 32-bit Thumb breakpoint spanning two memory words we need to take | ||
110 | * special precautions to insert the breakpoint atomically, especially on SMP | ||
111 | * systems. This is achieved by calling this arming function using stop_machine. | ||
112 | */ | ||
113 | static int __kprobes set_t32_breakpoint(void *addr) | ||
114 | { | ||
115 | ((u16 *)addr)[0] = KPROBE_THUMB32_BREAKPOINT_INSTRUCTION >> 16; | ||
116 | ((u16 *)addr)[1] = KPROBE_THUMB32_BREAKPOINT_INSTRUCTION & 0xffff; | ||
117 | flush_insns(addr, 2*sizeof(u16)); | ||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | void __kprobes arch_arm_kprobe(struct kprobe *p) | ||
122 | { | ||
123 | uintptr_t addr = (uintptr_t)p->addr & ~1; /* Remove any Thumb flag */ | ||
124 | |||
125 | if (!is_wide_instruction(p->opcode)) { | ||
126 | *(u16 *)addr = KPROBE_THUMB16_BREAKPOINT_INSTRUCTION; | ||
127 | flush_insns(addr, sizeof(u16)); | ||
128 | } else if (addr & 2) { | ||
129 | /* A 32-bit instruction spanning two words needs special care */ | ||
130 | stop_machine(set_t32_breakpoint, (void *)addr, &cpu_online_map); | ||
131 | } else { | ||
132 | /* Word aligned 32-bit instruction can be written atomically */ | ||
133 | u32 bkp = KPROBE_THUMB32_BREAKPOINT_INSTRUCTION; | ||
134 | #ifndef __ARMEB__ /* Swap halfwords for little-endian */ | ||
135 | bkp = (bkp >> 16) | (bkp << 16); | ||
136 | #endif | ||
137 | *(u32 *)addr = bkp; | ||
138 | flush_insns(addr, sizeof(u32)); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | #else /* !CONFIG_THUMB2_KERNEL */ | ||
143 | |||
82 | void __kprobes arch_arm_kprobe(struct kprobe *p) | 144 | void __kprobes arch_arm_kprobe(struct kprobe *p) |
83 | { | 145 | { |
84 | *p->addr = KPROBE_BREAKPOINT_INSTRUCTION; | 146 | kprobe_opcode_t insn = p->opcode; |
85 | flush_insns(p->addr, 1); | 147 | kprobe_opcode_t brkp = KPROBE_ARM_BREAKPOINT_INSTRUCTION; |
148 | if (insn >= 0xe0000000) | ||
149 | brkp |= 0xe0000000; /* Unconditional instruction */ | ||
150 | else | ||
151 | brkp |= insn & 0xf0000000; /* Copy condition from insn */ | ||
152 | *p->addr = brkp; | ||
153 | flush_insns(p->addr, sizeof(p->addr[0])); | ||
86 | } | 154 | } |
87 | 155 | ||
156 | #endif /* !CONFIG_THUMB2_KERNEL */ | ||
157 | |||
88 | /* | 158 | /* |
89 | * The actual disarming is done here on each CPU and synchronized using | 159 | * The actual disarming is done here on each CPU and synchronized using |
90 | * stop_machine. This synchronization is necessary on SMP to avoid removing | 160 | * stop_machine. This synchronization is necessary on SMP to avoid removing |
91 | * a probe between the moment the 'Undefined Instruction' exception is raised | 161 | * a probe between the moment the 'Undefined Instruction' exception is raised |
92 | * and the moment the exception handler reads the faulting instruction from | 162 | * and the moment the exception handler reads the faulting instruction from |
93 | * memory. | 163 | * memory. It is also needed to atomically set the two half-words of a 32-bit |
164 | * Thumb breakpoint. | ||
94 | */ | 165 | */ |
95 | int __kprobes __arch_disarm_kprobe(void *p) | 166 | int __kprobes __arch_disarm_kprobe(void *p) |
96 | { | 167 | { |
97 | struct kprobe *kp = p; | 168 | struct kprobe *kp = p; |
169 | #ifdef CONFIG_THUMB2_KERNEL | ||
170 | u16 *addr = (u16 *)((uintptr_t)kp->addr & ~1); | ||
171 | kprobe_opcode_t insn = kp->opcode; | ||
172 | unsigned int len; | ||
173 | |||
174 | if (is_wide_instruction(insn)) { | ||
175 | ((u16 *)addr)[0] = insn>>16; | ||
176 | ((u16 *)addr)[1] = insn; | ||
177 | len = 2*sizeof(u16); | ||
178 | } else { | ||
179 | ((u16 *)addr)[0] = insn; | ||
180 | len = sizeof(u16); | ||
181 | } | ||
182 | flush_insns(addr, len); | ||
183 | |||
184 | #else /* !CONFIG_THUMB2_KERNEL */ | ||
98 | *kp->addr = kp->opcode; | 185 | *kp->addr = kp->opcode; |
99 | flush_insns(kp->addr, 1); | 186 | flush_insns(kp->addr, sizeof(kp->addr[0])); |
187 | #endif | ||
100 | return 0; | 188 | return 0; |
101 | } | 189 | } |
102 | 190 | ||
@@ -130,12 +218,24 @@ static void __kprobes set_current_kprobe(struct kprobe *p) | |||
130 | __get_cpu_var(current_kprobe) = p; | 218 | __get_cpu_var(current_kprobe) = p; |
131 | } | 219 | } |
132 | 220 | ||
133 | static void __kprobes singlestep(struct kprobe *p, struct pt_regs *regs, | 221 | static void __kprobes |
134 | struct kprobe_ctlblk *kcb) | 222 | singlestep_skip(struct kprobe *p, struct pt_regs *regs) |
135 | { | 223 | { |
224 | #ifdef CONFIG_THUMB2_KERNEL | ||
225 | regs->ARM_cpsr = it_advance(regs->ARM_cpsr); | ||
226 | if (is_wide_instruction(p->opcode)) | ||
227 | regs->ARM_pc += 4; | ||
228 | else | ||
229 | regs->ARM_pc += 2; | ||
230 | #else | ||
136 | regs->ARM_pc += 4; | 231 | regs->ARM_pc += 4; |
137 | if (p->ainsn.insn_check_cc(regs->ARM_cpsr)) | 232 | #endif |
138 | p->ainsn.insn_handler(p, regs); | 233 | } |
234 | |||
235 | static inline void __kprobes | ||
236 | singlestep(struct kprobe *p, struct pt_regs *regs, struct kprobe_ctlblk *kcb) | ||
237 | { | ||
238 | p->ainsn.insn_singlestep(p, regs); | ||
139 | } | 239 | } |
140 | 240 | ||
141 | /* | 241 | /* |
@@ -149,11 +249,23 @@ void __kprobes kprobe_handler(struct pt_regs *regs) | |||
149 | { | 249 | { |
150 | struct kprobe *p, *cur; | 250 | struct kprobe *p, *cur; |
151 | struct kprobe_ctlblk *kcb; | 251 | struct kprobe_ctlblk *kcb; |
152 | kprobe_opcode_t *addr = (kprobe_opcode_t *)regs->ARM_pc; | ||
153 | 252 | ||
154 | kcb = get_kprobe_ctlblk(); | 253 | kcb = get_kprobe_ctlblk(); |
155 | cur = kprobe_running(); | 254 | cur = kprobe_running(); |
156 | p = get_kprobe(addr); | 255 | |
256 | #ifdef CONFIG_THUMB2_KERNEL | ||
257 | /* | ||
258 | * First look for a probe which was registered using an address with | ||
259 | * bit 0 set, this is the usual situation for pointers to Thumb code. | ||
260 | * If not found, fallback to looking for one with bit 0 clear. | ||
261 | */ | ||
262 | p = get_kprobe((kprobe_opcode_t *)(regs->ARM_pc | 1)); | ||
263 | if (!p) | ||
264 | p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc); | ||
265 | |||
266 | #else /* ! CONFIG_THUMB2_KERNEL */ | ||
267 | p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc); | ||
268 | #endif | ||
157 | 269 | ||
158 | if (p) { | 270 | if (p) { |
159 | if (cur) { | 271 | if (cur) { |
@@ -173,7 +285,8 @@ void __kprobes kprobe_handler(struct pt_regs *regs) | |||
173 | /* impossible cases */ | 285 | /* impossible cases */ |
174 | BUG(); | 286 | BUG(); |
175 | } | 287 | } |
176 | } else { | 288 | } else if (p->ainsn.insn_check_cc(regs->ARM_cpsr)) { |
289 | /* Probe hit and conditional execution check ok. */ | ||
177 | set_current_kprobe(p); | 290 | set_current_kprobe(p); |
178 | kcb->kprobe_status = KPROBE_HIT_ACTIVE; | 291 | kcb->kprobe_status = KPROBE_HIT_ACTIVE; |
179 | 292 | ||
@@ -193,6 +306,13 @@ void __kprobes kprobe_handler(struct pt_regs *regs) | |||
193 | } | 306 | } |
194 | reset_current_kprobe(); | 307 | reset_current_kprobe(); |
195 | } | 308 | } |
309 | } else { | ||
310 | /* | ||
311 | * Probe hit but conditional execution check failed, | ||
312 | * so just skip the instruction and continue as if | ||
313 | * nothing had happened. | ||
314 | */ | ||
315 | singlestep_skip(p, regs); | ||
196 | } | 316 | } |
197 | } else if (cur) { | 317 | } else if (cur) { |
198 | /* We probably hit a jprobe. Call its break handler. */ | 318 | /* We probably hit a jprobe. Call its break handler. */ |
@@ -300,7 +420,11 @@ void __naked __kprobes kretprobe_trampoline(void) | |||
300 | "bl trampoline_handler \n\t" | 420 | "bl trampoline_handler \n\t" |
301 | "mov lr, r0 \n\t" | 421 | "mov lr, r0 \n\t" |
302 | "ldmia sp!, {r0 - r11} \n\t" | 422 | "ldmia sp!, {r0 - r11} \n\t" |
423 | #ifdef CONFIG_THUMB2_KERNEL | ||
424 | "bx lr \n\t" | ||
425 | #else | ||
303 | "mov pc, lr \n\t" | 426 | "mov pc, lr \n\t" |
427 | #endif | ||
304 | : : : "memory"); | 428 | : : : "memory"); |
305 | } | 429 | } |
306 | 430 | ||
@@ -378,11 +502,22 @@ int __kprobes setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs) | |||
378 | struct jprobe *jp = container_of(p, struct jprobe, kp); | 502 | struct jprobe *jp = container_of(p, struct jprobe, kp); |
379 | struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); | 503 | struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); |
380 | long sp_addr = regs->ARM_sp; | 504 | long sp_addr = regs->ARM_sp; |
505 | long cpsr; | ||
381 | 506 | ||
382 | kcb->jprobe_saved_regs = *regs; | 507 | kcb->jprobe_saved_regs = *regs; |
383 | memcpy(kcb->jprobes_stack, (void *)sp_addr, MIN_STACK_SIZE(sp_addr)); | 508 | memcpy(kcb->jprobes_stack, (void *)sp_addr, MIN_STACK_SIZE(sp_addr)); |
384 | regs->ARM_pc = (long)jp->entry; | 509 | regs->ARM_pc = (long)jp->entry; |
385 | regs->ARM_cpsr |= PSR_I_BIT; | 510 | |
511 | cpsr = regs->ARM_cpsr | PSR_I_BIT; | ||
512 | #ifdef CONFIG_THUMB2_KERNEL | ||
513 | /* Set correct Thumb state in cpsr */ | ||
514 | if (regs->ARM_pc & 1) | ||
515 | cpsr |= PSR_T_BIT; | ||
516 | else | ||
517 | cpsr &= ~PSR_T_BIT; | ||
518 | #endif | ||
519 | regs->ARM_cpsr = cpsr; | ||
520 | |||
386 | preempt_disable(); | 521 | preempt_disable(); |
387 | return 1; | 522 | return 1; |
388 | } | 523 | } |
@@ -404,7 +539,12 @@ void __kprobes jprobe_return(void) | |||
404 | * This is to prevent any simulated instruction from writing | 539 | * This is to prevent any simulated instruction from writing |
405 | * over the regs when they are accessing the stack. | 540 | * over the regs when they are accessing the stack. |
406 | */ | 541 | */ |
542 | #ifdef CONFIG_THUMB2_KERNEL | ||
543 | "sub r0, %0, %1 \n\t" | ||
544 | "mov sp, r0 \n\t" | ||
545 | #else | ||
407 | "sub sp, %0, %1 \n\t" | 546 | "sub sp, %0, %1 \n\t" |
547 | #endif | ||
408 | "ldr r0, ="__stringify(JPROBE_MAGIC_ADDR)"\n\t" | 548 | "ldr r0, ="__stringify(JPROBE_MAGIC_ADDR)"\n\t" |
409 | "str %0, [sp, %2] \n\t" | 549 | "str %0, [sp, %2] \n\t" |
410 | "str r0, [sp, %3] \n\t" | 550 | "str r0, [sp, %3] \n\t" |
@@ -415,15 +555,28 @@ void __kprobes jprobe_return(void) | |||
415 | * Return to the context saved by setjmp_pre_handler | 555 | * Return to the context saved by setjmp_pre_handler |
416 | * and restored by longjmp_break_handler. | 556 | * and restored by longjmp_break_handler. |
417 | */ | 557 | */ |
558 | #ifdef CONFIG_THUMB2_KERNEL | ||
559 | "ldr lr, [sp, %2] \n\t" /* lr = saved sp */ | ||
560 | "ldrd r0, r1, [sp, %5] \n\t" /* r0,r1 = saved lr,pc */ | ||
561 | "ldr r2, [sp, %4] \n\t" /* r2 = saved psr */ | ||
562 | "stmdb lr!, {r0, r1, r2} \n\t" /* push saved lr and */ | ||
563 | /* rfe context */ | ||
564 | "ldmia sp, {r0 - r12} \n\t" | ||
565 | "mov sp, lr \n\t" | ||
566 | "ldr lr, [sp], #4 \n\t" | ||
567 | "rfeia sp! \n\t" | ||
568 | #else | ||
418 | "ldr r0, [sp, %4] \n\t" | 569 | "ldr r0, [sp, %4] \n\t" |
419 | "msr cpsr_cxsf, r0 \n\t" | 570 | "msr cpsr_cxsf, r0 \n\t" |
420 | "ldmia sp, {r0 - pc} \n\t" | 571 | "ldmia sp, {r0 - pc} \n\t" |
572 | #endif | ||
421 | : | 573 | : |
422 | : "r" (kcb->jprobe_saved_regs.ARM_sp), | 574 | : "r" (kcb->jprobe_saved_regs.ARM_sp), |
423 | "I" (sizeof(struct pt_regs) * 2), | 575 | "I" (sizeof(struct pt_regs) * 2), |
424 | "J" (offsetof(struct pt_regs, ARM_sp)), | 576 | "J" (offsetof(struct pt_regs, ARM_sp)), |
425 | "J" (offsetof(struct pt_regs, ARM_pc)), | 577 | "J" (offsetof(struct pt_regs, ARM_pc)), |
426 | "J" (offsetof(struct pt_regs, ARM_cpsr)) | 578 | "J" (offsetof(struct pt_regs, ARM_cpsr)), |
579 | "J" (offsetof(struct pt_regs, ARM_lr)) | ||
427 | : "memory", "cc"); | 580 | : "memory", "cc"); |
428 | } | 581 | } |
429 | 582 | ||
@@ -460,17 +613,44 @@ int __kprobes arch_trampoline_kprobe(struct kprobe *p) | |||
460 | return 0; | 613 | return 0; |
461 | } | 614 | } |
462 | 615 | ||
463 | static struct undef_hook kprobes_break_hook = { | 616 | #ifdef CONFIG_THUMB2_KERNEL |
617 | |||
618 | static struct undef_hook kprobes_thumb16_break_hook = { | ||
619 | .instr_mask = 0xffff, | ||
620 | .instr_val = KPROBE_THUMB16_BREAKPOINT_INSTRUCTION, | ||
621 | .cpsr_mask = MODE_MASK, | ||
622 | .cpsr_val = SVC_MODE, | ||
623 | .fn = kprobe_trap_handler, | ||
624 | }; | ||
625 | |||
626 | static struct undef_hook kprobes_thumb32_break_hook = { | ||
464 | .instr_mask = 0xffffffff, | 627 | .instr_mask = 0xffffffff, |
465 | .instr_val = KPROBE_BREAKPOINT_INSTRUCTION, | 628 | .instr_val = KPROBE_THUMB32_BREAKPOINT_INSTRUCTION, |
466 | .cpsr_mask = MODE_MASK, | 629 | .cpsr_mask = MODE_MASK, |
467 | .cpsr_val = SVC_MODE, | 630 | .cpsr_val = SVC_MODE, |
468 | .fn = kprobe_trap_handler, | 631 | .fn = kprobe_trap_handler, |
469 | }; | 632 | }; |
470 | 633 | ||
634 | #else /* !CONFIG_THUMB2_KERNEL */ | ||
635 | |||
636 | static struct undef_hook kprobes_arm_break_hook = { | ||
637 | .instr_mask = 0x0fffffff, | ||
638 | .instr_val = KPROBE_ARM_BREAKPOINT_INSTRUCTION, | ||
639 | .cpsr_mask = MODE_MASK, | ||
640 | .cpsr_val = SVC_MODE, | ||
641 | .fn = kprobe_trap_handler, | ||
642 | }; | ||
643 | |||
644 | #endif /* !CONFIG_THUMB2_KERNEL */ | ||
645 | |||
471 | int __init arch_init_kprobes() | 646 | int __init arch_init_kprobes() |
472 | { | 647 | { |
473 | arm_kprobe_decode_init(); | 648 | arm_kprobe_decode_init(); |
474 | register_undef_hook(&kprobes_break_hook); | 649 | #ifdef CONFIG_THUMB2_KERNEL |
650 | register_undef_hook(&kprobes_thumb16_break_hook); | ||
651 | register_undef_hook(&kprobes_thumb32_break_hook); | ||
652 | #else | ||
653 | register_undef_hook(&kprobes_arm_break_hook); | ||
654 | #endif | ||
475 | return 0; | 655 | return 0; |
476 | } | 656 | } |