xref: /freebsd-src/sys/cddl/dev/fbt/x86/fbt_isa.c (revision 82283cad12a417abfb1469d899b2d7cfb1d38f77)
1266b4a78SMark Johnston /*
2266b4a78SMark Johnston  * CDDL HEADER START
3266b4a78SMark Johnston  *
4266b4a78SMark Johnston  * The contents of this file are subject to the terms of the
5266b4a78SMark Johnston  * Common Development and Distribution License (the "License").
6266b4a78SMark Johnston  * You may not use this file except in compliance with the License.
7266b4a78SMark Johnston  *
8266b4a78SMark Johnston  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9266b4a78SMark Johnston  * or http://www.opensolaris.org/os/licensing.
10266b4a78SMark Johnston  * See the License for the specific language governing permissions
11266b4a78SMark Johnston  * and limitations under the License.
12266b4a78SMark Johnston  *
13266b4a78SMark Johnston  * When distributing Covered Code, include this CDDL HEADER in each
14266b4a78SMark Johnston  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15266b4a78SMark Johnston  * If applicable, add the following below this CDDL HEADER, with the
16266b4a78SMark Johnston  * fields enclosed by brackets "[]" replaced with your own identifying
17266b4a78SMark Johnston  * information: Portions Copyright [yyyy] [name of copyright owner]
18266b4a78SMark Johnston  *
19266b4a78SMark Johnston  * CDDL HEADER END
20266b4a78SMark Johnston  *
21266b4a78SMark Johnston  * Portions Copyright 2006-2008 John Birrell jb@freebsd.org
22266b4a78SMark Johnston  *
23266b4a78SMark Johnston  */
24266b4a78SMark Johnston 
25266b4a78SMark Johnston /*
26266b4a78SMark Johnston  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
27266b4a78SMark Johnston  * Use is subject to license terms.
28266b4a78SMark Johnston  */
29266b4a78SMark Johnston 
30266b4a78SMark Johnston #include <sys/param.h>
31266b4a78SMark Johnston 
32266b4a78SMark Johnston #include <sys/dtrace.h>
33266b4a78SMark Johnston 
341aa8a926SMark Johnston #include <machine/cpufunc.h>
357a79ce2eSMark Johnston #include <machine/md_var.h>
361aa8a926SMark Johnston 
37266b4a78SMark Johnston #include "fbt.h"
38266b4a78SMark Johnston 
39266b4a78SMark Johnston #define	FBT_PUSHL_EBP		0x55
40266b4a78SMark Johnston #define	FBT_MOVL_ESP_EBP0_V0	0x8b
41266b4a78SMark Johnston #define	FBT_MOVL_ESP_EBP1_V0	0xec
42266b4a78SMark Johnston #define	FBT_MOVL_ESP_EBP0_V1	0x89
43266b4a78SMark Johnston #define	FBT_MOVL_ESP_EBP1_V1	0xe5
44266b4a78SMark Johnston #define	FBT_REX_RSP_RBP		0x48
45266b4a78SMark Johnston 
46266b4a78SMark Johnston #define	FBT_POPL_EBP		0x5d
47266b4a78SMark Johnston #define	FBT_RET			0xc3
48266b4a78SMark Johnston #define	FBT_RET_IMM16		0xc2
49266b4a78SMark Johnston #define	FBT_LEAVE		0xc9
50266b4a78SMark Johnston 
51266b4a78SMark Johnston #ifdef __amd64__
52266b4a78SMark Johnston #define	FBT_PATCHVAL		0xcc
53266b4a78SMark Johnston #else
54266b4a78SMark Johnston #define	FBT_PATCHVAL		0xf0
55266b4a78SMark Johnston #endif
56266b4a78SMark Johnston 
57*82283cadSMark Johnston #define FBT_AFRAMES 3
5890951695SChuck Silvers 
59266b4a78SMark Johnston int
60a7aa3d4dSMark Johnston fbt_invop(uintptr_t addr, struct trapframe *frame, uintptr_t scratch __unused)
61266b4a78SMark Johnston {
626c280659SMark Johnston 	solaris_cpu_t *cpu;
636c280659SMark Johnston 	uintptr_t *stack;
64a7aa3d4dSMark Johnston 	uintptr_t arg0, arg1, arg2, arg3, arg4, rval;
656c280659SMark Johnston 	fbt_probe_t *fbt;
66c208cb99SMark Johnston 	int8_t fbtrval;
67266b4a78SMark Johnston 
686c280659SMark Johnston #ifdef __amd64__
696c280659SMark Johnston 	stack = (uintptr_t *)frame->tf_rsp;
70a7aa3d4dSMark Johnston 	rval = frame->tf_rax;
716c280659SMark Johnston #else
726c280659SMark Johnston 	/* Skip hardware-saved registers. */
736c280659SMark Johnston 	stack = (uintptr_t *)frame->tf_isp + 3;
74a7aa3d4dSMark Johnston 	rval = frame->tf_eax;
756c280659SMark Johnston #endif
766c280659SMark Johnston 
776c280659SMark Johnston 	cpu = &solaris_cpu[curcpu];
786c280659SMark Johnston 	fbt = fbt_probetab[FBT_ADDR2NDX(addr)];
79266b4a78SMark Johnston 	for (; fbt != NULL; fbt = fbt->fbtp_hashnext) {
80c208cb99SMark Johnston 		if ((uintptr_t)fbt->fbtp_patchpoint != addr)
81c208cb99SMark Johnston 			continue;
82c208cb99SMark Johnston 		fbtrval = fbt->fbtp_rval;
830e69c959SMark Johnston 
840e69c959SMark Johnston 		/*
850e69c959SMark Johnston 		 * Report the address of the breakpoint for the benefit
860e69c959SMark Johnston 		 * of consumers fetching register values with regs[].
870e69c959SMark Johnston 		 */
8860013d9cSMark Johnston #ifdef __i386__
8960013d9cSMark Johnston 		frame->tf_eip--;
9060013d9cSMark Johnston #else
910e69c959SMark Johnston 		frame->tf_rip--;
9260013d9cSMark Johnston #endif
93c208cb99SMark Johnston 		for (; fbt != NULL; fbt = fbt->fbtp_tracenext) {
94c208cb99SMark Johnston 			ASSERT(fbt->fbtp_rval == fbtrval);
95266b4a78SMark Johnston 			if (fbt->fbtp_roffset == 0) {
966c280659SMark Johnston #ifdef __amd64__
976c280659SMark Johnston 				/* fbt->fbtp_rval == DTRACE_INVOP_PUSHQ_RBP */
986c280659SMark Johnston 				DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
996c280659SMark Johnston 				cpu->cpu_dtrace_caller = stack[0];
1006c280659SMark Johnston 				DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT |
1016c280659SMark Johnston 				    CPU_DTRACE_BADADDR);
1026c280659SMark Johnston 
1036c280659SMark Johnston 				arg0 = frame->tf_rdi;
1046c280659SMark Johnston 				arg1 = frame->tf_rsi;
1056c280659SMark Johnston 				arg2 = frame->tf_rdx;
1066c280659SMark Johnston 				arg3 = frame->tf_rcx;
1076c280659SMark Johnston 				arg4 = frame->tf_r8;
1086c280659SMark Johnston #else
109266b4a78SMark Johnston 				int i = 0;
1106c280659SMark Johnston 
111266b4a78SMark Johnston 				/*
112266b4a78SMark Johnston 				 * When accessing the arguments on the stack,
113266b4a78SMark Johnston 				 * we must protect against accessing beyond
114266b4a78SMark Johnston 				 * the stack.  We can safely set NOFAULT here
115266b4a78SMark Johnston 				 * -- we know that interrupts are already
116266b4a78SMark Johnston 				 * disabled.
117266b4a78SMark Johnston 				 */
118266b4a78SMark Johnston 				DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
119266b4a78SMark Johnston 				cpu->cpu_dtrace_caller = stack[i++];
1206c280659SMark Johnston 				arg0 = stack[i++];
1216c280659SMark Johnston 				arg1 = stack[i++];
1226c280659SMark Johnston 				arg2 = stack[i++];
1236c280659SMark Johnston 				arg3 = stack[i++];
1246c280659SMark Johnston 				arg4 = stack[i++];
125266b4a78SMark Johnston 				DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT |
126266b4a78SMark Johnston 				    CPU_DTRACE_BADADDR);
1276c280659SMark Johnston #endif
128266b4a78SMark Johnston 
1296c280659SMark Johnston 				dtrace_probe(fbt->fbtp_id, arg0, arg1,
1306c280659SMark Johnston 				    arg2, arg3, arg4);
131266b4a78SMark Johnston 
132266b4a78SMark Johnston 				cpu->cpu_dtrace_caller = 0;
133266b4a78SMark Johnston 			} else {
134266b4a78SMark Johnston #ifdef __amd64__
135266b4a78SMark Johnston 				/*
136266b4a78SMark Johnston 				 * On amd64, we instrument the ret, not the
137266b4a78SMark Johnston 				 * leave.  We therefore need to set the caller
1386c280659SMark Johnston 				 * to ensure that the top frame of a stack()
139266b4a78SMark Johnston 				 * action is correct.
140266b4a78SMark Johnston 				 */
141266b4a78SMark Johnston 				DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
142266b4a78SMark Johnston 				cpu->cpu_dtrace_caller = stack[0];
143266b4a78SMark Johnston 				DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT |
144266b4a78SMark Johnston 				    CPU_DTRACE_BADADDR);
145266b4a78SMark Johnston #endif
146266b4a78SMark Johnston 
147266b4a78SMark Johnston 				dtrace_probe(fbt->fbtp_id, fbt->fbtp_roffset,
148266b4a78SMark Johnston 				    rval, 0, 0, 0);
149266b4a78SMark Johnston 				cpu->cpu_dtrace_caller = 0;
150266b4a78SMark Johnston 			}
151266b4a78SMark Johnston 		}
1520e69c959SMark Johnston 		/* Advance to the instruction following the breakpoint. */
15360013d9cSMark Johnston #ifdef __i386__
15460013d9cSMark Johnston 		frame->tf_eip++;
15560013d9cSMark Johnston #else
1560e69c959SMark Johnston 		frame->tf_rip++;
15760013d9cSMark Johnston #endif
158c208cb99SMark Johnston 		return (fbtrval);
159266b4a78SMark Johnston 	}
160266b4a78SMark Johnston 
161266b4a78SMark Johnston 	return (0);
162266b4a78SMark Johnston }
163266b4a78SMark Johnston 
164266b4a78SMark Johnston void
165266b4a78SMark Johnston fbt_patch_tracepoint(fbt_probe_t *fbt, fbt_patchval_t val)
166266b4a78SMark Johnston {
1671de56ac7SMark Johnston 	register_t intr;
1687a79ce2eSMark Johnston 	bool old_wp;
169266b4a78SMark Johnston 
1701de56ac7SMark Johnston 	intr = intr_disable();
1717a79ce2eSMark Johnston 	old_wp = disable_wp();
172266b4a78SMark Johnston 	*fbt->fbtp_patchpoint = val;
1737a79ce2eSMark Johnston 	restore_wp(old_wp);
1741de56ac7SMark Johnston 	intr_restore(intr);
175266b4a78SMark Johnston }
176266b4a78SMark Johnston 
177266b4a78SMark Johnston int
178266b4a78SMark Johnston fbt_provide_module_function(linker_file_t lf, int symindx,
179266b4a78SMark Johnston     linker_symval_t *symval, void *opaque)
180266b4a78SMark Johnston {
181266b4a78SMark Johnston 	char *modname = opaque;
182266b4a78SMark Johnston 	const char *name = symval->name;
183c208cb99SMark Johnston 	fbt_probe_t *fbt, *hash, *retfbt;
184266b4a78SMark Johnston 	int j;
185266b4a78SMark Johnston 	int size;
186266b4a78SMark Johnston 	uint8_t *instr, *limit;
187266b4a78SMark Johnston 
1888bb9b7f1SMark Johnston 	if (fbt_excluded(name))
189266b4a78SMark Johnston 		return (0);
1908bb9b7f1SMark Johnston 
1918bb9b7f1SMark Johnston 	/*
1928bb9b7f1SMark Johnston 	 * trap_check() is a wrapper for DTrace's fault handler, so we don't
1938bb9b7f1SMark Johnston 	 * want to be able to instrument it.
1948bb9b7f1SMark Johnston 	 */
1958bb9b7f1SMark Johnston 	if (strcmp(name, "trap_check") == 0)
1968bb9b7f1SMark Johnston 		return (0);
197266b4a78SMark Johnston 
198266b4a78SMark Johnston 	size = symval->size;
199266b4a78SMark Johnston 
200266b4a78SMark Johnston 	instr = (uint8_t *) symval->value;
201266b4a78SMark Johnston 	limit = (uint8_t *) symval->value + symval->size;
202266b4a78SMark Johnston 
203266b4a78SMark Johnston #ifdef __amd64__
204266b4a78SMark Johnston 	while (instr < limit) {
205266b4a78SMark Johnston 		if (*instr == FBT_PUSHL_EBP)
206266b4a78SMark Johnston 			break;
207266b4a78SMark Johnston 
208266b4a78SMark Johnston 		if ((size = dtrace_instr_size(instr)) <= 0)
209266b4a78SMark Johnston 			break;
210266b4a78SMark Johnston 
211266b4a78SMark Johnston 		instr += size;
212266b4a78SMark Johnston 	}
213266b4a78SMark Johnston 
214266b4a78SMark Johnston 	if (instr >= limit || *instr != FBT_PUSHL_EBP) {
215266b4a78SMark Johnston 		/*
216266b4a78SMark Johnston 		 * We either don't save the frame pointer in this
217266b4a78SMark Johnston 		 * function, or we ran into some disassembly
218266b4a78SMark Johnston 		 * screw-up.  Either way, we bail.
219266b4a78SMark Johnston 		 */
220266b4a78SMark Johnston 		return (0);
221266b4a78SMark Johnston 	}
222266b4a78SMark Johnston #else
223266b4a78SMark Johnston 	if (instr[0] != FBT_PUSHL_EBP)
224266b4a78SMark Johnston 		return (0);
225266b4a78SMark Johnston 
226266b4a78SMark Johnston 	if (!(instr[1] == FBT_MOVL_ESP_EBP0_V0 &&
227266b4a78SMark Johnston 	    instr[2] == FBT_MOVL_ESP_EBP1_V0) &&
228266b4a78SMark Johnston 	    !(instr[1] == FBT_MOVL_ESP_EBP0_V1 &&
229266b4a78SMark Johnston 	    instr[2] == FBT_MOVL_ESP_EBP1_V1))
230266b4a78SMark Johnston 		return (0);
231266b4a78SMark Johnston #endif
232266b4a78SMark Johnston 
233266b4a78SMark Johnston 	fbt = malloc(sizeof (fbt_probe_t), M_FBT, M_WAITOK | M_ZERO);
234266b4a78SMark Johnston 	fbt->fbtp_name = name;
235266b4a78SMark Johnston 	fbt->fbtp_id = dtrace_probe_create(fbt_id, modname,
23690951695SChuck Silvers 	    name, FBT_ENTRY, FBT_AFRAMES, fbt);
237266b4a78SMark Johnston 	fbt->fbtp_patchpoint = instr;
238266b4a78SMark Johnston 	fbt->fbtp_ctl = lf;
239266b4a78SMark Johnston 	fbt->fbtp_loadcnt = lf->loadcnt;
240266b4a78SMark Johnston 	fbt->fbtp_rval = DTRACE_INVOP_PUSHL_EBP;
241266b4a78SMark Johnston 	fbt->fbtp_savedval = *instr;
242266b4a78SMark Johnston 	fbt->fbtp_patchval = FBT_PATCHVAL;
243266b4a78SMark Johnston 	fbt->fbtp_symindx = symindx;
244266b4a78SMark Johnston 
245c208cb99SMark Johnston 	for (hash = fbt_probetab[FBT_ADDR2NDX(instr)]; hash != NULL;
246c208cb99SMark Johnston 	    hash = hash->fbtp_hashnext) {
247c208cb99SMark Johnston 		if (hash->fbtp_patchpoint == fbt->fbtp_patchpoint) {
248c208cb99SMark Johnston 			fbt->fbtp_tracenext = hash->fbtp_tracenext;
249c208cb99SMark Johnston 			hash->fbtp_tracenext = fbt;
250c208cb99SMark Johnston 			break;
251c208cb99SMark Johnston 		}
252c208cb99SMark Johnston 	}
253c208cb99SMark Johnston 	if (hash == NULL) {
254266b4a78SMark Johnston 		fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)];
255266b4a78SMark Johnston 		fbt_probetab[FBT_ADDR2NDX(instr)] = fbt;
256c208cb99SMark Johnston 	}
257266b4a78SMark Johnston 
258266b4a78SMark Johnston 	lf->fbt_nentries++;
259266b4a78SMark Johnston 
260266b4a78SMark Johnston 	retfbt = NULL;
261266b4a78SMark Johnston again:
262266b4a78SMark Johnston 	if (instr >= limit)
263266b4a78SMark Johnston 		return (0);
264266b4a78SMark Johnston 
265266b4a78SMark Johnston 	/*
266266b4a78SMark Johnston 	 * If this disassembly fails, then we've likely walked off into
267266b4a78SMark Johnston 	 * a jump table or some other unsuitable area.  Bail out of the
268266b4a78SMark Johnston 	 * disassembly now.
269266b4a78SMark Johnston 	 */
270266b4a78SMark Johnston 	if ((size = dtrace_instr_size(instr)) <= 0)
271266b4a78SMark Johnston 		return (0);
272266b4a78SMark Johnston 
273266b4a78SMark Johnston #ifdef __amd64__
274266b4a78SMark Johnston 	/*
275266b4a78SMark Johnston 	 * We only instrument "ret" on amd64 -- we don't yet instrument
276266b4a78SMark Johnston 	 * ret imm16, largely because the compiler doesn't seem to
277266b4a78SMark Johnston 	 * (yet) emit them in the kernel...
278266b4a78SMark Johnston 	 */
279266b4a78SMark Johnston 	if (*instr != FBT_RET) {
280266b4a78SMark Johnston 		instr += size;
281266b4a78SMark Johnston 		goto again;
282266b4a78SMark Johnston 	}
283266b4a78SMark Johnston #else
284266b4a78SMark Johnston 	if (!(size == 1 &&
285266b4a78SMark Johnston 	    (*instr == FBT_POPL_EBP || *instr == FBT_LEAVE) &&
286266b4a78SMark Johnston 	    (*(instr + 1) == FBT_RET ||
287266b4a78SMark Johnston 	    *(instr + 1) == FBT_RET_IMM16))) {
288266b4a78SMark Johnston 		instr += size;
289266b4a78SMark Johnston 		goto again;
290266b4a78SMark Johnston 	}
291266b4a78SMark Johnston #endif
292266b4a78SMark Johnston 
293266b4a78SMark Johnston 	/*
294266b4a78SMark Johnston 	 * We (desperately) want to avoid erroneously instrumenting a
295266b4a78SMark Johnston 	 * jump table, especially given that our markers are pretty
296266b4a78SMark Johnston 	 * short:  two bytes on x86, and just one byte on amd64.  To
297266b4a78SMark Johnston 	 * determine if we're looking at a true instruction sequence
298266b4a78SMark Johnston 	 * or an inline jump table that happens to contain the same
299266b4a78SMark Johnston 	 * byte sequences, we resort to some heuristic sleeze:  we
300266b4a78SMark Johnston 	 * treat this instruction as being contained within a pointer,
301266b4a78SMark Johnston 	 * and see if that pointer points to within the body of the
302266b4a78SMark Johnston 	 * function.  If it does, we refuse to instrument it.
303266b4a78SMark Johnston 	 */
304266b4a78SMark Johnston 	for (j = 0; j < sizeof (uintptr_t); j++) {
305266b4a78SMark Johnston 		caddr_t check = (caddr_t) instr - j;
306266b4a78SMark Johnston 		uint8_t *ptr;
307266b4a78SMark Johnston 
308266b4a78SMark Johnston 		if (check < symval->value)
309266b4a78SMark Johnston 			break;
310266b4a78SMark Johnston 
311266b4a78SMark Johnston 		if (check + sizeof (caddr_t) > (caddr_t)limit)
312266b4a78SMark Johnston 			continue;
313266b4a78SMark Johnston 
314266b4a78SMark Johnston 		ptr = *(uint8_t **)check;
315266b4a78SMark Johnston 
316266b4a78SMark Johnston 		if (ptr >= (uint8_t *) symval->value && ptr < limit) {
317266b4a78SMark Johnston 			instr += size;
318266b4a78SMark Johnston 			goto again;
319266b4a78SMark Johnston 		}
320266b4a78SMark Johnston 	}
321266b4a78SMark Johnston 
322266b4a78SMark Johnston 	/*
323266b4a78SMark Johnston 	 * We have a winner!
324266b4a78SMark Johnston 	 */
325266b4a78SMark Johnston 	fbt = malloc(sizeof (fbt_probe_t), M_FBT, M_WAITOK | M_ZERO);
326266b4a78SMark Johnston 	fbt->fbtp_name = name;
327266b4a78SMark Johnston 
328266b4a78SMark Johnston 	if (retfbt == NULL) {
329266b4a78SMark Johnston 		fbt->fbtp_id = dtrace_probe_create(fbt_id, modname,
33090951695SChuck Silvers 		    name, FBT_RETURN, FBT_AFRAMES, fbt);
331266b4a78SMark Johnston 	} else {
332c208cb99SMark Johnston 		retfbt->fbtp_probenext = fbt;
333266b4a78SMark Johnston 		fbt->fbtp_id = retfbt->fbtp_id;
334266b4a78SMark Johnston 	}
335266b4a78SMark Johnston 
336266b4a78SMark Johnston 	retfbt = fbt;
337266b4a78SMark Johnston 	fbt->fbtp_patchpoint = instr;
338266b4a78SMark Johnston 	fbt->fbtp_ctl = lf;
339266b4a78SMark Johnston 	fbt->fbtp_loadcnt = lf->loadcnt;
340266b4a78SMark Johnston 	fbt->fbtp_symindx = symindx;
341266b4a78SMark Johnston 
342266b4a78SMark Johnston #ifndef __amd64__
343266b4a78SMark Johnston 	if (*instr == FBT_POPL_EBP) {
344266b4a78SMark Johnston 		fbt->fbtp_rval = DTRACE_INVOP_POPL_EBP;
345266b4a78SMark Johnston 	} else {
346266b4a78SMark Johnston 		ASSERT(*instr == FBT_LEAVE);
347266b4a78SMark Johnston 		fbt->fbtp_rval = DTRACE_INVOP_LEAVE;
348266b4a78SMark Johnston 	}
349266b4a78SMark Johnston 	fbt->fbtp_roffset =
350266b4a78SMark Johnston 	    (uintptr_t)(instr - (uint8_t *) symval->value) + 1;
351266b4a78SMark Johnston 
352266b4a78SMark Johnston #else
353266b4a78SMark Johnston 	ASSERT(*instr == FBT_RET);
354266b4a78SMark Johnston 	fbt->fbtp_rval = DTRACE_INVOP_RET;
355266b4a78SMark Johnston 	fbt->fbtp_roffset =
356266b4a78SMark Johnston 	    (uintptr_t)(instr - (uint8_t *) symval->value);
357266b4a78SMark Johnston #endif
358266b4a78SMark Johnston 
359266b4a78SMark Johnston 	fbt->fbtp_savedval = *instr;
360266b4a78SMark Johnston 	fbt->fbtp_patchval = FBT_PATCHVAL;
361266b4a78SMark Johnston 	fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)];
362266b4a78SMark Johnston 	fbt_probetab[FBT_ADDR2NDX(instr)] = fbt;
363266b4a78SMark Johnston 
364266b4a78SMark Johnston 	lf->fbt_nentries++;
365266b4a78SMark Johnston 
366266b4a78SMark Johnston 	instr += size;
367266b4a78SMark Johnston 	goto again;
368266b4a78SMark Johnston }
369