2 * Copyright (c) 2007 Apple Inc. All rights reserved.
7 * The contents of this file are subject to the terms of the
8 * Common Development and Distribution License, Version 1.0 only
9 * (the "License"). You may not use this file except in compliance
12 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
13 * or http://www.opensolaris.org/os/licensing.
14 * See the License for the specific language governing permissions
15 * and limitations under the License.
17 * When distributing Covered Code, include this CDDL HEADER in each
18 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
19 * If applicable, add the following below this CDDL HEADER, with the
20 * fields enclosed by brackets "[]" replaced with your own identifying
21 * information: Portions Copyright [yyyy] [name of copyright owner]
26 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
27 * Use is subject to license terms.
30 /* #pragma ident "@(#)fbt.c 1.15 05/09/19 SMI" */
34 #define _KERNEL /* Solaris vs. Darwin */
38 #define MACH__POSIX_C_SOURCE_PRIVATE 1 /* pulls in suitable savearea from
39 * mach/ppc/thread_status.h */
40 #include <kern/thread.h>
41 #include <mach/thread_status.h>
42 #include <arm/proc_reg.h>
43 #include <arm/caches_internal.h>
44 #include <arm/thread.h>
46 #include <mach-o/loader.h>
47 #include <mach-o/nlist.h>
48 #include <libkern/kernel_mach_header.h>
50 #include <sys/param.h>
51 #include <sys/systm.h>
52 #include <sys/errno.h>
54 #include <sys/ioctl.h>
56 #include <sys/fcntl.h>
57 #include <miscfs/devfs/devfs.h>
59 #include <sys/dtrace.h>
60 #include <sys/dtrace_impl.h>
63 #include <sys/dtrace_glue.h>
65 #define DTRACE_INVOP_PUSH_LR 8
66 #define DTRACE_INVOP_BL 9
67 #define DTRACE_INVOP_POP_PC 10
69 #define DTRACE_INVOP_THUMB_NOP_SKIP 2
70 #define DTRACE_INVOP_POP_PC_SKIP 2
71 #define DTRACE_INVOP_THUMB_SET_R7_SKIP 2
72 #define DTRACE_INVOP_THUMB_MOV_SP_TO_R7_SKIP 2
74 #define FBT_IS_THUMB_PUSH_LR(x) (((x) & 0x0000ff00) == 0x0000b500)
75 #define FBT_IS_THUMB_POP_R7(x) (((x) & 0x0000ff80) == 0x0000bc80)
76 #define FBT_IS_THUMB32_POP_R7LR(x,y) (((x) == 0x0000e8bd) && (((y) & 0x00004080) == 0x00004080))
77 #define FBT_IS_THUMB_POP_PC(x) (((x) & 0x0000ff00) == 0x0000bd00)
78 #define FBT_IS_THUMB_SET_R7(x) (((x) & 0x0000ff00) == 0x0000af00)
79 #define FBT_IS_THUMB_MOV_SP_TO_R7(x) (((x) & 0x0000ffff) == 0x0000466f)
80 #define FBT_THUMB_SET_R7_OFFSET(x) (((x) & 0x000000ff) << 2)
81 #define FBT_IS_THUMB_LDR_PC(x) (((x) & 0x0000f800) == 0x00004800)
82 #define FBT_IS_THUMB32_LDR_PC(x,y) ((x) == 0x0000f8df) /* Only for positive offset PC relative loads */
83 #define FBT_THUMB_STACK_REGS(x) ((x) & 0x00FF)
84 #define FBT_IS_THUMB_BX_REG(x) (((x) & 0x0000ff87) == 0x00004700)
86 #define FBT_PATCHVAL 0xdefc
87 #define FBT_AFRAMES_ENTRY 8
88 #define FBT_AFRAMES_RETURN 6
90 #define FBT_ENTRY "entry"
91 #define FBT_RETURN "return"
92 #define FBT_ADDR2NDX(addr) ((((uintptr_t)(addr)) >> 4) & fbt_probetab_mask)
94 #define VFPSAVE_ALIGN_DTRACE 16 /* This value should come from VFPSAVE_ALIGN */
96 extern dtrace_provider_id_t fbt_id
;
97 extern fbt_probe_t
**fbt_probetab
;
98 extern int fbt_probetab_mask
;
100 kern_return_t
fbt_perfCallback(int, struct arm_saved_state
*, __unused
int, __unused
int);
102 extern int dtrace_arm_condition_true(int cond
, int cpsr
);
105 /* Calculate the address of the ldr. (From the ARM Architecture reference) */
106 /* Does not check to see if it's really a load instruction, caller must do that */
108 static uint32_t thumb_ldr_pc_address(uint32_t address
)
110 return (address
& 0xFFFFFFFC) + (*(uint16_t*) address
& 0xFF) * 4 + 4;
113 static uint32_t thumb32_ldr_pc_address(uint32_t address
)
115 return (address
& 0xFFFFFFFC) + (*(uint16_t*) (address
+2) & 0xFFF) + 4;
118 /* Extract the current ITSTATE from the CPSR */
119 static uint32_t get_itstate(uint32_t cpsr
)
122 ((cpsr
& 0x06000000) >> 25) |
123 ((cpsr
& 0x0000FC00) >> 8);
126 static void clear_itstate(uint32_t* cpsr
)
128 *cpsr
&= ~0x0600FC00;
132 fbt_invop(uintptr_t addr
, uintptr_t * stack
, uintptr_t rval
)
134 fbt_probe_t
*fbt
= fbt_probetab
[FBT_ADDR2NDX(addr
)];
136 for (; fbt
!= NULL
; fbt
= fbt
->fbtp_hashnext
) {
137 if ((uintptr_t) fbt
->fbtp_patchpoint
== addr
) {
138 if (0 == CPU
->cpu_dtrace_invop_underway
) {
139 CPU
->cpu_dtrace_invop_underway
= 1; /* Race not possible on
140 * this per-cpu state */
142 struct arm_saved_state
* regs
= (struct arm_saved_state
*) stack
;
143 uintptr_t stack4
= *((uintptr_t*) regs
->sp
);
145 if ((regs
->cpsr
& PSR_MODE_MASK
) == PSR_FIQ_MODE
) {
147 * We do not handle probes firing from FIQ context. We used to
148 * try to undo the patch and rerun the instruction, but
149 * most of the time we can't do that successfully anyway.
150 * Instead, we just panic now so we fail fast.
152 panic("dtrace: fbt: The probe at %08x was called from FIQ_MODE",(unsigned) addr
);
156 * If we are not outside an IT block, and are not executing the last instruction of an IT block,
157 * then that is an instrumentation error or a code gen error. Either way, we panic.
159 uint32_t itstate
= get_itstate(regs
->cpsr
);
160 if ((itstate
& 0x7) != 0) {
161 panic("dtrace: fbt: Instruction stream error: Middle of IT block at %08x",(unsigned) addr
);
164 if (fbt
->fbtp_roffset
== 0) {
166 We need the frames to set up the backtrace, but we won't have the frame pointers
167 until after the instruction is emulated. So here we calculate the address of the
168 frame pointer from the saved instruction and put it in the stack. Yes, we end up
169 repeating this work again when we emulate the instruction.
171 This assumes that the frame area is immediately after the saved reg storage!
173 uint32_t offset
= ((uint32_t) regs
) + sizeof(struct arm_saved_state
);
175 /* Match the stack alignment required for arm_vfpsaved_state */
176 offset
&= ~(VFPSAVE_ALIGN_DTRACE
- 1);
177 offset
+= VFPSAVE_ALIGN_DTRACE
+ sizeof(struct arm_vfpsaved_state
);
178 #endif /* __ARM_VFP__ */
179 if (FBT_IS_THUMB_SET_R7(fbt
->fbtp_savedval
))
180 *((uint32_t*) offset
) = regs
->sp
+ FBT_THUMB_SET_R7_OFFSET(fbt
->fbtp_savedval
);
182 *((uint32_t*) offset
) = regs
->sp
;
184 CPU
->cpu_dtrace_caller
= regs
->lr
;
185 dtrace_probe(fbt
->fbtp_id
, regs
->r
[0], regs
->r
[1], regs
->r
[2], regs
->r
[3], stack4
);
186 CPU
->cpu_dtrace_caller
= 0;
188 /* Check to see if we're in the middle of an IT block. */
191 * We've already checked previously to see how far we are in the IT block.
192 * Here we must be getting ready to execute the last instruction.
194 int condition_it
= (itstate
& 0xF0) >> 4;
196 if (dtrace_arm_condition_true(condition_it
, regs
->cpsr
) == 0) {
197 /* Condition wasn't true, so becomes a nop. */
198 clear_itstate(®s
->cpsr
);
199 CPU
->cpu_dtrace_invop_underway
= 0;
200 return DTRACE_INVOP_NOP
;
204 dtrace_probe(fbt
->fbtp_id
, fbt
->fbtp_roffset
, rval
, 0, 0, 0);
205 CPU
->cpu_dtrace_caller
= 0;
207 /* The dtrace script may access cpsr, so make sure to clear only after probe fired. */
208 clear_itstate(®s
->cpsr
);
210 CPU
->cpu_dtrace_invop_underway
= 0;
214 On other architectures, we return a DTRACE constant to let the callback function
215 know what was replaced. On the ARM, since the function prologue/epilogue machine code
216 can vary, we need the actual bytes of the instruction, so return the savedval instead.
218 return (fbt
->fbtp_savedval
);
225 #define IS_USER_TRAP(regs) (((regs)->cpsr & PSR_MODE_MASK) == PSR_USER_MODE)
226 #define T_INVALID_OPCODE EXC_BAD_INSTRUCTION
227 #define FBT_EXCEPTION_CODE T_INVALID_OPCODE
232 struct arm_saved_state
* regs
,
233 __unused
int unused1
,
234 __unused
int unused2
)
236 #pragma unused (unused1)
237 #pragma unused (unused2)
238 kern_return_t retval
= KERN_FAILURE
;
240 if (FBT_EXCEPTION_CODE
== trapno
&& !IS_USER_TRAP(regs
)) {
241 boolean_t oldlevel
= 0;
242 machine_inst_t emul
= 0;
244 oldlevel
= ml_set_interrupts_enabled(FALSE
);
247 "Ldtrace_invop_callsite_pre_label:\n"
249 ".private_extern _dtrace_invop_callsite_pre\n"
250 "_dtrace_invop_callsite_pre:\n"
251 " .long Ldtrace_invop_callsite_pre_label\n"
255 emul
= dtrace_invop(regs
->pc
, (uintptr_t*) regs
, regs
->r
[0]);
258 "Ldtrace_invop_callsite_post_label:\n"
260 ".private_extern _dtrace_invop_callsite_post\n"
261 "_dtrace_invop_callsite_post:\n"
262 " .long Ldtrace_invop_callsite_post_label\n"
267 * The following emulation code does not execute properly if we are in the middle of
268 * an IT block. IT blocks need to be handled in the dtrace_invop function. If we do
269 * manage to get here and we are inside an IT block, then we missed a case somewhere
270 * prior to this point.
272 uint32_t itstate
= get_itstate(regs
->cpsr
);
274 panic("dtrace: fbt: Not emulated: Middle of IT block at %08x",(unsigned) regs
->pc
);
277 if (emul
== DTRACE_INVOP_NOP
) {
278 regs
->pc
+= DTRACE_INVOP_THUMB_NOP_SKIP
;
279 retval
= KERN_SUCCESS
;
280 } else if (FBT_IS_THUMB_SET_R7(emul
)) {
281 regs
->r
[7] = regs
->sp
+ FBT_THUMB_SET_R7_OFFSET(emul
);
282 regs
->pc
+= DTRACE_INVOP_THUMB_SET_R7_SKIP
;
283 retval
= KERN_SUCCESS
;
284 } else if (FBT_IS_THUMB_MOV_SP_TO_R7(emul
)) {
285 regs
->r
[7] = regs
->sp
;
286 regs
->pc
+= DTRACE_INVOP_THUMB_MOV_SP_TO_R7_SKIP
;
287 retval
= KERN_SUCCESS
;
288 } else if (FBT_IS_THUMB_POP_PC(emul
)) {
289 uintptr_t* sp
= (uintptr_t*) regs
->sp
;
291 machine_inst_t mask
= 0x0001;
293 while (mask
& 0x00ff) {
295 /* Pop this register */
296 regs
->r
[regnum
] = *sp
++;
303 regs
->sp
= (uintptr_t) sp
;
305 regs
->cpsr
|= PSR_TF
;
307 regs
->cpsr
&= ~PSR_TF
;
310 retval
= KERN_SUCCESS
;
311 } else if (FBT_IS_THUMB_BX_REG(emul
)) {
312 regs
->pc
= regs
->r
[(emul
>> 3) & 0xF];
315 regs
->cpsr
|= PSR_TF
;
317 regs
->cpsr
&= ~PSR_TF
;
320 retval
= KERN_SUCCESS
;
321 } else if (emul
== FBT_PATCHVAL
) {
322 /* Means we encountered an error but handled it, try same inst again */
323 retval
= KERN_SUCCESS
;
325 retval
= KERN_FAILURE
;
328 ml_set_interrupts_enabled(oldlevel
);
335 fbt_provide_probe(struct modctl
*ctl
, const char *modname
, const char* symbolName
, machine_inst_t
* symbolStart
, machine_inst_t
*instrHigh
)
341 fbt_probe_t
*newfbt
, *retfbt
, *entryfbt
;
342 machine_inst_t
*instr
, *pushinstr
= NULL
, *limit
, theInstr
;
343 int foundPushLR
, savedRegs
;
346 * Guard against null symbols
348 if (!symbolStart
|| !instrHigh
|| instrHigh
< symbolStart
) {
349 kprintf("dtrace: %s has an invalid address\n", symbolName
);
354 * Assume the compiler doesn't schedule instructions in the prologue.
358 limit
= (machine_inst_t
*)instrHigh
;
359 for (j
= 0, instr
= symbolStart
, theInstr
= 0;
360 (j
< 8) && instr
< instrHigh
; j
++, instr
++)
363 if (FBT_IS_THUMB_PUSH_LR(theInstr
)) {
365 /* Keep track of what registers we pushed. Compare this against the pop later. */
366 savedRegs
= FBT_THUMB_STACK_REGS(theInstr
);
369 if (foundPushLR
&& (FBT_IS_THUMB_SET_R7(theInstr
) || FBT_IS_THUMB_MOV_SP_TO_R7(theInstr
)))
370 /* Guard against a random setting of r7 from sp, we make sure we found the push first */
372 if (FBT_IS_THUMB_BX_REG(theInstr
)) /* We've gone too far, bail. */
374 if (FBT_IS_THUMB_POP_PC(theInstr
)) /* We've gone too far, bail. */
377 /* Check for 4 byte thumb instruction */
378 if (dtrace_instr_size(theInstr
,1) == 4)
382 if (!(foundPushLR
&& (FBT_IS_THUMB_SET_R7(theInstr
) || FBT_IS_THUMB_MOV_SP_TO_R7(theInstr
)))) {
386 thisid
= dtrace_probe_lookup(fbt_id
, modname
, symbolName
, FBT_ENTRY
);
387 newfbt
= kmem_zalloc(sizeof(fbt_probe_t
), KM_SLEEP
);
388 newfbt
->fbtp_next
= NULL
;
389 strlcpy( (char *)&(newfbt
->fbtp_name
), symbolName
, MAX_FBTP_NAME_CHARS
);
393 * The dtrace_probe previously existed, so we have to hook
394 * the newfbt entry onto the end of the existing fbt's
396 * If we find an fbt entry that was previously patched to
397 * fire, (as indicated by the current patched value), then
398 * we want to enable this newfbt on the spot.
400 entryfbt
= dtrace_probe_arg (fbt_id
, thisid
);
401 ASSERT (entryfbt
!= NULL
);
402 for(; entryfbt
!= NULL
; entryfbt
= entryfbt
->fbtp_next
) {
403 if (entryfbt
->fbtp_currentval
== entryfbt
->fbtp_patchval
)
406 if (entryfbt
->fbtp_next
== NULL
) {
407 entryfbt
->fbtp_next
= newfbt
;
408 newfbt
->fbtp_id
= entryfbt
->fbtp_id
;
415 * The dtrace_probe did not previously exist, so we
416 * create it and hook in the newfbt. Since the probe is
417 * new, we obviously do not need to enable it on the spot.
419 newfbt
->fbtp_id
= dtrace_probe_create(fbt_id
, modname
, symbolName
, FBT_ENTRY
, FBT_AFRAMES_ENTRY
, newfbt
);
423 newfbt
->fbtp_patchpoint
= instr
;
424 newfbt
->fbtp_ctl
= ctl
;
425 newfbt
->fbtp_loadcnt
= ctl
->mod_loadcnt
;
426 newfbt
->fbtp_rval
= DTRACE_INVOP_PUSH_LR
;
427 newfbt
->fbtp_savedval
= theInstr
;
428 newfbt
->fbtp_patchval
= FBT_PATCHVAL
;
429 newfbt
->fbtp_currentval
= 0;
430 newfbt
->fbtp_hashnext
= fbt_probetab
[FBT_ADDR2NDX(instr
)];
431 fbt_probetab
[FBT_ADDR2NDX(instr
)] = newfbt
;
434 fbt_enable(NULL
, newfbt
->fbtp_id
, newfbt
);
437 * The fbt entry chain is in place, one entry point per symbol.
438 * The fbt return chain can have multiple return points per
440 * Here we find the end of the fbt return chain.
445 thisid
= dtrace_probe_lookup(fbt_id
, modname
, symbolName
, FBT_RETURN
);
448 /* The dtrace_probe previously existed, so we have to
449 * find the end of the existing fbt chain. If we find
450 * an fbt return that was previously patched to fire,
451 * (as indicated by the currrent patched value), then
452 * we want to enable any new fbts on the spot.
454 retfbt
= dtrace_probe_arg (fbt_id
, thisid
);
455 ASSERT(retfbt
!= NULL
);
456 for (; retfbt
!= NULL
; retfbt
= retfbt
->fbtp_next
) {
457 if (retfbt
->fbtp_currentval
== retfbt
->fbtp_patchval
)
459 if(retfbt
->fbtp_next
== NULL
)
469 * Go back to the start of the function, in case
470 * the compiler emitted pcrel data loads
471 * before R7 was adjusted.
473 instr
= pushinstr
+ 1;
479 * We (desperately) want to avoid erroneously instrumenting a
480 * jump table. To determine if we're looking at a true instruction
481 * or an inline jump table that happens to contain the same
482 * byte sequences, we resort to some heuristic sleeze: we
483 * treat this instruction as being contained within a pointer,
484 * and see if that pointer points to within the body of the
485 * function. If it does, we refuse to instrument it.
487 if (((uintptr_t)instr
& 0x3) == 0) {
488 machine_inst_t
*ptr
= *(machine_inst_t
**)(void *)instr
;
490 if (ptr
>= (machine_inst_t
*)symbolStart
&& ptr
< limit
) {
491 /* kprintf("dtrace: fbt: Found jump table in %s, at %08x\n",symbolName,(unsigned)instr); */
498 * OK, it's an instruction.
502 /* Walked onto the start of the next routine? If so, bail out from this function */
503 if (FBT_IS_THUMB_PUSH_LR(theInstr
)) {
505 kprintf("dtrace: fbt: No return probe for %s, walked to next routine at %08x\n",symbolName
,(unsigned)instr
);
509 /* The PC relative data should be stored after the end of the function. If
510 * we see a PC relative load, assume the address to load from is the new end
511 * of the function. */
512 if (FBT_IS_THUMB_LDR_PC(theInstr
)) {
513 uint32_t newlimit
= thumb_ldr_pc_address((uint32_t) instr
);
514 if (newlimit
< (uint32_t) limit
)
515 limit
= (machine_inst_t
*) newlimit
;
517 if ((instr
+1) < limit
&& FBT_IS_THUMB32_LDR_PC(*instr
,*(instr
+1))) {
518 uint32_t newlimit
= thumb32_ldr_pc_address((uint32_t) instr
);
519 if (newlimit
< (uint32_t) limit
)
520 limit
= (machine_inst_t
*) newlimit
;
523 /* Look for the 1. pop { ..., pc } or 2. pop { ..., r7 } ... bx reg or 3. ldmia.w sp!, { ..., r7, lr } ... bx reg */
524 if (!FBT_IS_THUMB_POP_PC(theInstr
) &&
525 !FBT_IS_THUMB_POP_R7(theInstr
) &&
526 !FBT_IS_THUMB32_POP_R7LR(theInstr
,*(instr
+1))) {
528 if (dtrace_instr_size(theInstr
,1) == 4)
533 if (FBT_IS_THUMB_POP_PC(theInstr
)) {
534 if (savedRegs
!= FBT_THUMB_STACK_REGS(theInstr
)) {
535 /* What we're popping doesn't match what we're pushing, assume that we've
536 * gone too far in the function. Bail.
538 kprintf("dtrace: fbt: No return probe for %s, popped regs don't match at %08x\n",symbolName
,(unsigned)instr
);
542 /* Scan ahead for the bx */
543 for (j
= 0; (j
< 4) && (instr
< limit
); j
++, instr
++) {
545 if (FBT_IS_THUMB_BX_REG(theInstr
))
547 if (dtrace_instr_size(theInstr
,1) == 4)
551 if (!FBT_IS_THUMB_BX_REG(theInstr
))
556 * pop { ..., pc}, bx reg -- We have a winner!
559 newfbt
= kmem_zalloc(sizeof(fbt_probe_t
), KM_SLEEP
);
560 newfbt
->fbtp_next
= NULL
;
561 strlcpy( (char *)&(newfbt
->fbtp_name
), symbolName
, MAX_FBTP_NAME_CHARS
);
563 if (retfbt
== NULL
) {
564 newfbt
->fbtp_id
= dtrace_probe_create(fbt_id
, modname
,
565 symbolName
, FBT_RETURN
, FBT_AFRAMES_RETURN
, newfbt
);
567 retfbt
->fbtp_next
= newfbt
;
568 newfbt
->fbtp_id
= retfbt
->fbtp_id
;
572 newfbt
->fbtp_patchpoint
= instr
;
573 newfbt
->fbtp_ctl
= ctl
;
574 newfbt
->fbtp_loadcnt
= ctl
->mod_loadcnt
;
576 ASSERT(FBT_IS_THUMB_POP_PC(theInstr
) || FBT_IS_THUMB_BX_REG(theInstr
));
577 newfbt
->fbtp_rval
= DTRACE_INVOP_POP_PC
;
578 newfbt
->fbtp_roffset
=
579 (uintptr_t) ((uint8_t*) instr
- (uint8_t *)symbolStart
);
580 newfbt
->fbtp_savedval
= theInstr
;
581 newfbt
->fbtp_patchval
= FBT_PATCHVAL
;
582 newfbt
->fbtp_currentval
= 0;
583 newfbt
->fbtp_hashnext
= fbt_probetab
[FBT_ADDR2NDX(instr
)];
584 fbt_probetab
[FBT_ADDR2NDX(instr
)] = newfbt
;
587 fbt_enable(NULL
, newfbt
->fbtp_id
, newfbt
);