]>
Commit | Line | Data |
---|---|---|
2d21ac55 A |
1 | /* |
2 | * CDDL HEADER START | |
3 | * | |
4 | * The contents of this file are subject to the terms of the | |
b0d623f7 A |
5 | * Common Development and Distribution License (the "License"). |
6 | * You may not use this file except in compliance with the License. | |
2d21ac55 A |
7 | * |
8 | * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE | |
9 | * or http://www.opensolaris.org/os/licensing. | |
10 | * See the License for the specific language governing permissions | |
11 | * and limitations under the License. | |
12 | * | |
13 | * When distributing Covered Code, include this CDDL HEADER in each | |
14 | * file and include the License file at usr/src/OPENSOLARIS.LICENSE. | |
15 | * If applicable, add the following below this CDDL HEADER, with the | |
16 | * fields enclosed by brackets "[]" replaced with your own identifying | |
17 | * information: Portions Copyright [yyyy] [name of copyright owner] | |
18 | * | |
19 | * CDDL HEADER END | |
20 | */ | |
21 | /* | |
b0d623f7 | 22 | * Copyright 2007 Sun Microsystems, Inc. All rights reserved. |
2d21ac55 A |
23 | * Use is subject to license terms. |
24 | */ | |
25 | ||
b0d623f7 | 26 | /* #pragma ident "@(#)fbt.c 1.18 07/01/10 SMI" */ |
2d21ac55 A |
27 | |
28 | #ifdef KERNEL | |
29 | #ifndef _KERNEL | |
30 | #define _KERNEL /* Solaris vs. Darwin */ | |
31 | #endif | |
32 | #endif | |
33 | ||
34 | #include <mach-o/loader.h> | |
b0d623f7 | 35 | #include <libkern/kernel_mach_header.h> |
2d21ac55 A |
36 | |
37 | #include <sys/param.h> | |
38 | #include <sys/systm.h> | |
39 | #include <sys/errno.h> | |
40 | #include <sys/stat.h> | |
41 | #include <sys/ioctl.h> | |
42 | #include <sys/conf.h> | |
43 | #include <sys/fcntl.h> | |
44 | #include <miscfs/devfs/devfs.h> | |
45 | #include <pexpert/pexpert.h> | |
46 | ||
47 | #include <sys/dtrace.h> | |
48 | #include <sys/dtrace_impl.h> | |
49 | #include <sys/fbt.h> | |
50 | ||
51 | #include <sys/dtrace_glue.h> | |
52 | ||
53 | /* #include <machine/trap.h> */ | |
54 | struct savearea_t; /* Used anonymously */ | |
55 | typedef kern_return_t (*perfCallback)(int, struct savearea_t *, int, int); | |
56 | ||
57 | #if defined (__ppc__) || defined (__ppc64__) | |
58 | extern perfCallback tempDTraceTrapHook, tempDTraceIntHook; | |
59 | extern kern_return_t fbt_perfCallback(int, struct savearea_t *, int, int); | |
60 | extern kern_return_t fbt_perfIntCallback(int, struct savearea_t *, int, int); | |
61 | #else | |
62 | extern perfCallback tempDTraceTrapHook; | |
63 | extern kern_return_t fbt_perfCallback(int, struct savearea_t *, int, int); | |
64 | #endif | |
65 | ||
66 | #define FBT_ADDR2NDX(addr) ((((uintptr_t)(addr)) >> 4) & fbt_probetab_mask) | |
67 | #define FBT_PROBETAB_SIZE 0x8000 /* 32k entries -- 128K total */ | |
68 | ||
69 | static dev_info_t *fbt_devi; | |
70 | static int fbt_probetab_size; | |
71 | dtrace_provider_id_t fbt_id; | |
72 | fbt_probe_t **fbt_probetab; | |
73 | int fbt_probetab_mask; | |
74 | static int fbt_verbose = 0; | |
75 | ||
76 | void fbt_init( void ); | |
77 | ||
78 | /*ARGSUSED*/ | |
79 | static void | |
80 | fbt_destroy(void *arg, dtrace_id_t id, void *parg) | |
81 | { | |
82 | #pragma unused(arg,id) | |
83 | fbt_probe_t *fbt = parg, *next, *hash, *last; | |
84 | int ndx; | |
85 | ||
86 | do { | |
87 | /* | |
88 | * Now we need to remove this probe from the fbt_probetab. | |
89 | */ | |
90 | ndx = FBT_ADDR2NDX(fbt->fbtp_patchpoint); | |
91 | last = NULL; | |
92 | hash = fbt_probetab[ndx]; | |
93 | ||
94 | while (hash != fbt) { | |
95 | ASSERT(hash != NULL); | |
96 | last = hash; | |
97 | hash = hash->fbtp_hashnext; | |
98 | } | |
99 | ||
100 | if (last != NULL) { | |
101 | last->fbtp_hashnext = fbt->fbtp_hashnext; | |
102 | } else { | |
103 | fbt_probetab[ndx] = fbt->fbtp_hashnext; | |
104 | } | |
105 | ||
106 | next = fbt->fbtp_next; | |
107 | kmem_free(fbt, sizeof (fbt_probe_t)); | |
108 | ||
109 | fbt = next; | |
110 | } while (fbt != NULL); | |
111 | } | |
112 | ||
113 | /*ARGSUSED*/ | |
114 | static void | |
115 | fbt_enable(void *arg, dtrace_id_t id, void *parg) | |
116 | { | |
117 | #pragma unused(arg,id) | |
118 | fbt_probe_t *fbt = parg; | |
119 | struct modctl *ctl = fbt->fbtp_ctl; | |
120 | ||
121 | #if defined (__ppc__) || defined (__ppc64__) | |
122 | dtrace_casptr(&tempDTraceIntHook, NULL, fbt_perfIntCallback); | |
123 | if (tempDTraceIntHook != (perfCallback)fbt_perfIntCallback) { | |
124 | if (fbt_verbose) { | |
125 | cmn_err(CE_NOTE, "fbt_enable is failing for probe %s " | |
126 | "in module %s: tempDTraceIntHook already occupied.", | |
127 | fbt->fbtp_name, ctl->mod_modname); | |
128 | } | |
129 | return; | |
130 | } | |
131 | #endif | |
132 | ||
133 | dtrace_casptr(&tempDTraceTrapHook, NULL, fbt_perfCallback); | |
134 | if (tempDTraceTrapHook != (perfCallback)fbt_perfCallback) { | |
135 | if (fbt_verbose) { | |
136 | cmn_err(CE_NOTE, "fbt_enable is failing for probe %s " | |
137 | "in module %s: tempDTraceTrapHook already occupied.", | |
138 | fbt->fbtp_name, ctl->mod_modname); | |
139 | } | |
140 | return; | |
141 | } | |
142 | ||
143 | for (; fbt != NULL; fbt = fbt->fbtp_next) | |
144 | (void)ml_nofault_copy( (vm_offset_t)&fbt->fbtp_patchval, (vm_offset_t)fbt->fbtp_patchpoint, | |
145 | sizeof(fbt->fbtp_patchval)); | |
146 | ||
147 | dtrace_membar_consumer(); | |
148 | } | |
149 | ||
150 | /*ARGSUSED*/ | |
151 | static void | |
152 | fbt_disable(void *arg, dtrace_id_t id, void *parg) | |
153 | { | |
154 | #pragma unused(arg,id) | |
155 | fbt_probe_t *fbt = parg; | |
156 | ||
157 | for (; fbt != NULL; fbt = fbt->fbtp_next) | |
158 | (void)ml_nofault_copy( (vm_offset_t)&fbt->fbtp_savedval, (vm_offset_t)fbt->fbtp_patchpoint, | |
159 | sizeof(fbt->fbtp_savedval)); | |
160 | ||
161 | dtrace_membar_consumer(); | |
162 | } | |
163 | ||
164 | /*ARGSUSED*/ | |
165 | static void | |
166 | fbt_suspend(void *arg, dtrace_id_t id, void *parg) | |
167 | { | |
168 | #pragma unused(arg,id) | |
169 | fbt_probe_t *fbt = parg; | |
170 | ||
171 | for (; fbt != NULL; fbt = fbt->fbtp_next) | |
172 | (void)ml_nofault_copy( (vm_offset_t)&fbt->fbtp_savedval, (vm_offset_t)fbt->fbtp_patchpoint, | |
173 | sizeof(fbt->fbtp_savedval)); | |
174 | ||
175 | dtrace_membar_consumer(); | |
176 | } | |
177 | ||
178 | /*ARGSUSED*/ | |
179 | static void | |
180 | fbt_resume(void *arg, dtrace_id_t id, void *parg) | |
181 | { | |
182 | #pragma unused(arg,id) | |
183 | fbt_probe_t *fbt = parg; | |
184 | struct modctl *ctl = fbt->fbtp_ctl; | |
185 | ||
186 | #if defined (__ppc__) || defined (__ppc64__) | |
187 | dtrace_casptr(&tempDTraceIntHook, NULL, fbt_perfIntCallback); | |
188 | if (tempDTraceIntHook != (perfCallback)fbt_perfIntCallback) { | |
189 | if (fbt_verbose) { | |
190 | cmn_err(CE_NOTE, "fbt_enable is failing for probe %s " | |
191 | "in module %s: tempDTraceIntHook already occupied.", | |
192 | fbt->fbtp_name, ctl->mod_modname); | |
193 | } | |
194 | return; | |
195 | } | |
196 | #endif | |
197 | ||
198 | dtrace_casptr(&tempDTraceTrapHook, NULL, fbt_perfCallback); | |
199 | if (tempDTraceTrapHook != (perfCallback)fbt_perfCallback) { | |
200 | if (fbt_verbose) { | |
201 | cmn_err(CE_NOTE, "fbt_resume is failing for probe %s " | |
202 | "in module %s: tempDTraceTrapHook already occupied.", | |
203 | fbt->fbtp_name, ctl->mod_modname); | |
204 | } | |
205 | return; | |
206 | } | |
207 | ||
208 | for (; fbt != NULL; fbt = fbt->fbtp_next) | |
209 | (void)ml_nofault_copy( (vm_offset_t)&fbt->fbtp_patchval, (vm_offset_t)fbt->fbtp_patchpoint, | |
210 | sizeof(fbt->fbtp_patchval)); | |
211 | ||
212 | dtrace_membar_consumer(); | |
213 | } | |
214 | ||
215 | #if !defined(__APPLE__) | |
216 | /*ARGSUSED*/ | |
217 | static void | |
218 | fbt_getargdesc(void *arg, dtrace_id_t id, void *parg, dtrace_argdesc_t *desc) | |
219 | { | |
220 | fbt_probe_t *fbt = parg; | |
221 | struct modctl *ctl = fbt->fbtp_ctl; | |
222 | struct module *mp = ctl->mod_mp; | |
223 | ctf_file_t *fp = NULL, *pfp; | |
224 | ctf_funcinfo_t f; | |
225 | int error; | |
226 | ctf_id_t argv[32], type; | |
227 | int argc = sizeof (argv) / sizeof (ctf_id_t); | |
228 | const char *parent; | |
229 | ||
230 | if (!ctl->mod_loaded || (ctl->mod_loadcnt != fbt->fbtp_loadcnt)) | |
231 | goto err; | |
232 | ||
233 | if (fbt->fbtp_roffset != 0 && desc->dtargd_ndx == 0) { | |
234 | (void) strlcpy(desc->dtargd_native, "int", | |
235 | sizeof(desc->dtargd_native)); | |
236 | return; | |
237 | } | |
238 | ||
239 | if ((fp = ctf_modopen(mp, &error)) == NULL) { | |
240 | /* | |
241 | * We have no CTF information for this module -- and therefore | |
242 | * no args[] information. | |
243 | */ | |
244 | goto err; | |
245 | } | |
246 | ||
247 | /* | |
248 | * If we have a parent container, we must manually import it. | |
249 | */ | |
250 | if ((parent = ctf_parent_name(fp)) != NULL) { | |
b0d623f7 A |
251 | struct modctl *mp = &modules; |
252 | struct modctl *mod = NULL; | |
2d21ac55 A |
253 | |
254 | /* | |
255 | * We must iterate over all modules to find the module that | |
256 | * is our parent. | |
257 | */ | |
b0d623f7 A |
258 | do { |
259 | if (strcmp(mp->mod_modname, parent) == 0) { | |
260 | mod = mp; | |
2d21ac55 | 261 | break; |
b0d623f7 A |
262 | } |
263 | } while ((mp = mp->mod_next) != &modules); | |
2d21ac55 A |
264 | |
265 | if (mod == NULL) | |
266 | goto err; | |
267 | ||
b0d623f7 | 268 | if ((pfp = ctf_modopen(mod->mod_mp, &error)) == NULL) { |
2d21ac55 | 269 | goto err; |
b0d623f7 | 270 | } |
2d21ac55 A |
271 | |
272 | if (ctf_import(fp, pfp) != 0) { | |
273 | ctf_close(pfp); | |
274 | goto err; | |
275 | } | |
276 | ||
277 | ctf_close(pfp); | |
278 | } | |
279 | ||
280 | if (ctf_func_info(fp, fbt->fbtp_symndx, &f) == CTF_ERR) | |
281 | goto err; | |
282 | ||
283 | if (fbt->fbtp_roffset != 0) { | |
284 | if (desc->dtargd_ndx > 1) | |
285 | goto err; | |
286 | ||
287 | ASSERT(desc->dtargd_ndx == 1); | |
288 | type = f.ctc_return; | |
289 | } else { | |
290 | if (desc->dtargd_ndx + 1 > f.ctc_argc) | |
291 | goto err; | |
292 | ||
293 | if (ctf_func_args(fp, fbt->fbtp_symndx, argc, argv) == CTF_ERR) | |
294 | goto err; | |
295 | ||
296 | type = argv[desc->dtargd_ndx]; | |
297 | } | |
298 | ||
299 | if (ctf_type_name(fp, type, desc->dtargd_native, | |
300 | DTRACE_ARGTYPELEN) != NULL) { | |
301 | ctf_close(fp); | |
302 | return; | |
303 | } | |
304 | err: | |
305 | if (fp != NULL) | |
306 | ctf_close(fp); | |
307 | ||
308 | desc->dtargd_ndx = DTRACE_ARGNONE; | |
309 | } | |
310 | #endif /* __APPLE__ */ | |
311 | ||
312 | static dtrace_pattr_t fbt_attr = { | |
313 | { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_ISA }, | |
314 | { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN }, | |
315 | { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN }, | |
316 | { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_ISA }, | |
317 | { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_ISA }, | |
318 | }; | |
319 | ||
320 | static dtrace_pops_t fbt_pops = { | |
321 | NULL, | |
322 | fbt_provide_module, | |
323 | fbt_enable, | |
324 | fbt_disable, | |
325 | fbt_suspend, | |
326 | fbt_resume, | |
327 | #if !defined(__APPLE__) | |
328 | fbt_getargdesc, | |
329 | #else | |
b0d623f7 | 330 | NULL, /* FIXME: where to look for xnu? */ |
2d21ac55 A |
331 | #endif /* __APPLE__ */ |
332 | NULL, | |
333 | NULL, | |
334 | fbt_destroy | |
335 | }; | |
336 | ||
337 | static void | |
338 | fbt_cleanup(dev_info_t *devi) | |
339 | { | |
340 | dtrace_invop_remove(fbt_invop); | |
341 | ddi_remove_minor_node(devi, NULL); | |
342 | kmem_free(fbt_probetab, fbt_probetab_size * sizeof (fbt_probe_t *)); | |
343 | fbt_probetab = NULL; | |
344 | fbt_probetab_mask = 0; | |
345 | } | |
346 | ||
347 | static int | |
348 | fbt_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) | |
349 | { | |
350 | switch (cmd) { | |
351 | case DDI_ATTACH: | |
352 | break; | |
353 | case DDI_RESUME: | |
354 | return (DDI_SUCCESS); | |
355 | default: | |
356 | return (DDI_FAILURE); | |
357 | } | |
358 | ||
359 | if (fbt_probetab_size == 0) | |
360 | fbt_probetab_size = FBT_PROBETAB_SIZE; | |
361 | ||
362 | fbt_probetab_mask = fbt_probetab_size - 1; | |
363 | fbt_probetab = | |
364 | kmem_zalloc(fbt_probetab_size * sizeof (fbt_probe_t *), KM_SLEEP); | |
365 | ||
366 | dtrace_invop_add(fbt_invop); | |
367 | ||
b0d623f7 | 368 | #if !defined(__APPLE__) |
2d21ac55 A |
369 | if (ddi_create_minor_node(devi, "fbt", S_IFCHR, 0, |
370 | DDI_PSEUDO, NULL) == DDI_FAILURE || | |
371 | dtrace_register("fbt", &fbt_attr, DTRACE_PRIV_KERNEL, NULL, | |
372 | &fbt_pops, NULL, &fbt_id) != 0) { | |
373 | fbt_cleanup(devi); | |
374 | return (DDI_FAILURE); | |
375 | } | |
b0d623f7 A |
376 | #else |
377 | if (ddi_create_minor_node(devi, "fbt", S_IFCHR, 0, | |
378 | DDI_PSEUDO, 0) == DDI_FAILURE || | |
379 | dtrace_register("fbt", &fbt_attr, DTRACE_PRIV_KERNEL, NULL, | |
380 | &fbt_pops, NULL, &fbt_id) != 0) { | |
381 | fbt_cleanup(devi); | |
382 | return (DDI_FAILURE); | |
383 | } | |
384 | #endif /* __APPLE__ */ | |
2d21ac55 A |
385 | |
386 | ddi_report_dev(devi); | |
387 | fbt_devi = devi; | |
388 | ||
389 | return (DDI_SUCCESS); | |
390 | } | |
391 | ||
392 | static d_open_t _fbt_open; | |
393 | ||
394 | static int | |
395 | _fbt_open(dev_t dev, int flags, int devtype, struct proc *p) | |
396 | { | |
397 | #pragma unused(dev,flags,devtype,p) | |
398 | return 0; | |
399 | } | |
400 | ||
401 | #define FBT_MAJOR -24 /* let the kernel pick the device number */ | |
402 | ||
403 | /* | |
404 | * A struct describing which functions will get invoked for certain | |
405 | * actions. | |
406 | */ | |
407 | static struct cdevsw fbt_cdevsw = | |
408 | { | |
409 | _fbt_open, /* open */ | |
410 | eno_opcl, /* close */ | |
411 | eno_rdwrt, /* read */ | |
412 | eno_rdwrt, /* write */ | |
413 | eno_ioctl, /* ioctl */ | |
414 | (stop_fcn_t *)nulldev, /* stop */ | |
415 | (reset_fcn_t *)nulldev, /* reset */ | |
416 | NULL, /* tty's */ | |
417 | eno_select, /* select */ | |
418 | eno_mmap, /* mmap */ | |
419 | eno_strat, /* strategy */ | |
420 | eno_getc, /* getc */ | |
421 | eno_putc, /* putc */ | |
422 | 0 /* type */ | |
423 | }; | |
424 | ||
425 | static int gDisableFBT = 0; | |
426 | struct modctl g_fbt_kernctl; | |
427 | #undef kmem_alloc /* from its binding to dt_kmem_alloc glue */ | |
428 | #undef kmem_free /* from its binding to dt_kmem_free glue */ | |
429 | #include <vm/vm_kern.h> | |
430 | ||
431 | void | |
432 | fbt_init( void ) | |
433 | { | |
434 | ||
593a1d5f | 435 | PE_parse_boot_argn("DisableFBT", &gDisableFBT, sizeof (gDisableFBT)); |
2d21ac55 A |
436 | |
437 | if (0 == gDisableFBT) | |
438 | { | |
439 | int majdevno = cdevsw_add(FBT_MAJOR, &fbt_cdevsw); | |
b0d623f7 | 440 | unsigned long size = 0, header_size, round_size; |
2d21ac55 A |
441 | kern_return_t ret; |
442 | void *p, *q; | |
443 | ||
444 | if (majdevno < 0) { | |
445 | printf("fbt_init: failed to allocate a major number!\n"); | |
446 | return; | |
447 | } | |
448 | ||
449 | /* | |
450 | * Capture the kernel's mach_header in its entirety and the contents of | |
451 | * its LINKEDIT segment (and only that segment). This is sufficient to | |
452 | * build all the fbt probes lazily the first time a client looks to | |
b0d623f7 | 453 | * the fbt provider. Remeber these on the global struct modctl g_fbt_kernctl. |
2d21ac55 | 454 | */ |
b0d623f7 | 455 | header_size = sizeof(kernel_mach_header_t) + _mh_execute_header.sizeofcmds; |
2d21ac55 A |
456 | p = getsegdatafromheader(&_mh_execute_header, SEG_LINKEDIT, &size); |
457 | ||
b0d623f7 A |
458 | round_size = round_page(header_size + size); |
459 | /* "q" will accomodate copied kernel_mach_header_t, its load commands, and LINKEIT segment. */ | |
2d21ac55 A |
460 | ret = kmem_alloc_pageable(kernel_map, (vm_offset_t *)&q, round_size); |
461 | ||
462 | if (p && (ret == KERN_SUCCESS)) { | |
b0d623f7 | 463 | kernel_segment_command_t *sgp; |
2d21ac55 A |
464 | |
465 | bcopy( (void *)&_mh_execute_header, q, header_size); | |
466 | bcopy( p, (char *)q + header_size, size); | |
467 | ||
468 | sgp = getsegbynamefromheader(q, SEG_LINKEDIT); | |
469 | ||
470 | if (sgp) { | |
b0d623f7 | 471 | sgp->vmaddr = (uintptr_t)((char *)q + header_size); |
2d21ac55 A |
472 | g_fbt_kernctl.address = (vm_address_t)q; |
473 | g_fbt_kernctl.size = header_size + size; | |
474 | } else { | |
475 | kmem_free(kernel_map, (vm_offset_t)q, round_size); | |
476 | g_fbt_kernctl.address = (vm_address_t)NULL; | |
477 | g_fbt_kernctl.size = 0; | |
478 | } | |
479 | } else { | |
480 | if (ret == KERN_SUCCESS) | |
481 | kmem_free(kernel_map, (vm_offset_t)q, round_size); | |
482 | g_fbt_kernctl.address = (vm_address_t)NULL; | |
483 | g_fbt_kernctl.size = 0; | |
484 | } | |
485 | ||
486 | strncpy((char *)&(g_fbt_kernctl.mod_modname), "mach_kernel", KMOD_MAX_NAME); | |
b0d623f7 | 487 | ((char *)&(g_fbt_kernctl.mod_modname))[KMOD_MAX_NAME -1] = '\0'; |
2d21ac55 | 488 | |
b0d623f7 | 489 | fbt_attach( (dev_info_t *)(uintptr_t)majdevno, DDI_ATTACH ); |
2d21ac55 A |
490 | |
491 | gDisableFBT = 1; /* Ensure this initialization occurs just one time. */ | |
492 | } | |
493 | else | |
494 | printf("fbt_init: DisableFBT non-zero, no FBT probes will be provided.\n"); | |
495 | } | |
496 | #undef FBT_MAJOR |