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