X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/0c530ab8987f0ae6a1a3d9284f40182b88852816..2d21ac55c334faf3a56e5634905ed6987fc787d4:/bsd/dev/dtrace/sdt.c diff --git a/bsd/dev/dtrace/sdt.c b/bsd/dev/dtrace/sdt.c new file mode 100644 index 000000000..640bfae34 --- /dev/null +++ b/bsd/dev/dtrace/sdt.c @@ -0,0 +1,673 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* #pragma ident "@(#)sdt.c 1.6 06/03/24 SMI" */ + +#ifdef KERNEL +#ifndef _KERNEL +#define _KERNEL /* Solaris vs. Darwin */ +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +struct savearea_t; /* Used anonymously */ +typedef kern_return_t (*perfCallback)(int, struct savearea_t *, int, int); + +#if defined (__ppc__) || defined (__ppc64__) +extern perfCallback tempDTraceTrapHook, tempDTraceIntHook; +extern kern_return_t fbt_perfCallback(int, struct savearea_t *, int, int); +extern kern_return_t fbt_perfIntCallback(int, struct savearea_t *, int, int); + +#define SDT_PATCHVAL 0x7c810808 +#define SDT_AFRAMES 6 +#elif defined(__i386__) || defined(__x86_64__) +extern perfCallback tempDTraceTrapHook; +extern kern_return_t fbt_perfCallback(int, struct savearea_t *, int, int); + +#define SDT_PATCHVAL 0xf0 +#define SDT_AFRAMES 6 +#else +#error Unknown architecture +#endif + +#define SDT_PROBETAB_SIZE 0x1000 /* 4k entries -- 16K total */ + +static dev_info_t *sdt_devi; +static int sdt_verbose = 0; +sdt_probe_t **sdt_probetab; +int sdt_probetab_size; +int sdt_probetab_mask; + +/*ARGSUSED*/ +static void +__sdt_provide_module(void *arg, struct modctl *ctl) +{ +#pragma unused(arg) + struct module *mp = (struct module *)ctl->address; + char *modname = ctl->mod_modname; + sdt_probedesc_t *sdpd; + sdt_probe_t *sdp, *old; + sdt_provider_t *prov; + int len; + + /* + * One for all, and all for one: if we haven't yet registered all of + * our providers, we'll refuse to provide anything. + */ + for (prov = sdt_providers; prov->sdtp_name != NULL; prov++) { + if (prov->sdtp_id == DTRACE_PROVNONE) + return; + } + + if (mp->sdt_nprobes != 0 || (sdpd = mp->sdt_probes) == NULL) + return; + + for (sdpd = mp->sdt_probes; sdpd != NULL; sdpd = sdpd->sdpd_next) { + char *name = sdpd->sdpd_name, *func, *nname; + int i, j; + dtrace_id_t id; + + for (prov = sdt_providers; prov->sdtp_prefix != NULL; prov++) { + char *prefpart, *prefix = prov->sdtp_prefix; + + if ((prefpart = strstr(name, prefix))) { + name = prefpart + strlen(prefix); + break; + } + } + + nname = kmem_alloc(len = strlen(name) + 1, KM_SLEEP); + + for (i = 0, j = 0; name[j] != '\0'; i++) { + if (name[j] == '_' && name[j + 1] == '_') { + nname[i] = '-'; + j += 2; + } else { + nname[i] = name[j++]; + } + } + + nname[i] = '\0'; + + sdp = kmem_zalloc(sizeof (sdt_probe_t), KM_SLEEP); + sdp->sdp_loadcnt = ctl->mod_loadcnt; + sdp->sdp_ctl = ctl; + sdp->sdp_name = nname; + sdp->sdp_namelen = len; + sdp->sdp_provider = prov; + + func = sdpd->sdpd_func; + + if (func == NULL) + func = ""; + + /* + * We have our provider. Now create the probe. + */ + if ((id = dtrace_probe_lookup(prov->sdtp_id, modname, + func, nname)) != DTRACE_IDNONE) { + old = dtrace_probe_arg(prov->sdtp_id, id); + ASSERT(old != NULL); + + sdp->sdp_next = old->sdp_next; + sdp->sdp_id = id; + old->sdp_next = sdp; + } else { + sdp->sdp_id = dtrace_probe_create(prov->sdtp_id, + modname, func, nname, SDT_AFRAMES, sdp); + + mp->sdt_nprobes++; + } + + sdp->sdp_hashnext = + sdt_probetab[SDT_ADDR2NDX(sdpd->sdpd_offset)]; + sdt_probetab[SDT_ADDR2NDX(sdpd->sdpd_offset)] = sdp; + + sdp->sdp_patchval = SDT_PATCHVAL; + sdp->sdp_patchpoint = (sdt_instr_t *)sdpd->sdpd_offset; + sdp->sdp_savedval = *sdp->sdp_patchpoint; + } +} + +/*ARGSUSED*/ +static void +sdt_destroy(void *arg, dtrace_id_t id, void *parg) +{ +#pragma unused(arg,id) + sdt_probe_t *sdp = parg, *old, *last, *hash; + int ndx; +#if !defined(__APPLE__) + struct modctl *ctl = sdp->sdp_ctl; + + if (ctl != NULL && ctl->mod_loadcnt == sdp->sdp_loadcnt) { + if ((ctl->mod_loadcnt == sdp->sdp_loadcnt && + ctl->mod_loaded)) { + ((struct module *)(ctl->mod_mp))->sdt_nprobes--; + } + } +#endif /* __APPLE__ */ + + while (sdp != NULL) { + old = sdp; + + /* + * Now we need to remove this probe from the sdt_probetab. + */ + ndx = SDT_ADDR2NDX(sdp->sdp_patchpoint); + last = NULL; + hash = sdt_probetab[ndx]; + + while (hash != sdp) { + ASSERT(hash != NULL); + last = hash; + hash = hash->sdp_hashnext; + } + + if (last != NULL) { + last->sdp_hashnext = sdp->sdp_hashnext; + } else { + sdt_probetab[ndx] = sdp->sdp_hashnext; + } + + kmem_free(sdp->sdp_name, sdp->sdp_namelen); + sdp = sdp->sdp_next; + kmem_free(old, sizeof (sdt_probe_t)); + } +} + +/*ARGSUSED*/ +static void +sdt_enable(void *arg, dtrace_id_t id, void *parg) +{ +#pragma unused(arg,id) + sdt_probe_t *sdp = parg; + struct modctl *ctl = sdp->sdp_ctl; + +#if !defined(__APPLE__) + ctl->mod_nenabled++; + + /* + * If this module has disappeared since we discovered its probes, + * refuse to enable it. + */ + if (!ctl->mod_loaded) { + if (sdt_verbose) { + cmn_err(CE_NOTE, "sdt is failing for probe %s " + "(module %s unloaded)", + sdp->sdp_name, ctl->mod_modname); + } + goto err; + } + + /* + * Now check that our modctl has the expected load count. If it + * doesn't, this module must have been unloaded and reloaded -- and + * we're not going to touch it. + */ + if (ctl->mod_loadcnt != sdp->sdp_loadcnt) { + if (sdt_verbose) { + cmn_err(CE_NOTE, "sdt is failing for probe %s " + "(module %s reloaded)", + sdp->sdp_name, ctl->mod_modname); + } + goto err; + } +#endif /* __APPLE__ */ + +#if defined (__ppc__) || defined (__ppc64__) + dtrace_casptr(&tempDTraceIntHook, NULL, fbt_perfIntCallback); + if (tempDTraceIntHook != (perfCallback)fbt_perfIntCallback) { + if (sdt_verbose) { + cmn_err(CE_NOTE, "sdt_enable is failing for probe %s " + "in module %s: tempDTraceIntHook already occupied.", + sdp->sdp_name, ctl->mod_modname); + } + return; + } +#endif + + dtrace_casptr(&tempDTraceTrapHook, NULL, fbt_perfCallback); + if (tempDTraceTrapHook != (perfCallback)fbt_perfCallback) { + if (sdt_verbose) { + cmn_err(CE_NOTE, "sdt_enable is failing for probe %s " + "in module %s: tempDTraceTrapHook already occupied.", + sdp->sdp_name, ctl->mod_modname); + } + return; + } + + while (sdp != NULL) { + (void)ml_nofault_copy( (vm_offset_t)&sdp->sdp_patchval, (vm_offset_t)sdp->sdp_patchpoint, + sizeof(sdp->sdp_patchval)); + sdp = sdp->sdp_next; + } +err: + ; +} + +/*ARGSUSED*/ +static void +sdt_disable(void *arg, dtrace_id_t id, void *parg) +{ +#pragma unused(arg,id) + sdt_probe_t *sdp = parg; +#if !defined(__APPLE__) + struct modctl *ctl = sdp->sdp_ctl; + + ctl->mod_nenabled--; + + if (!ctl->mod_loaded || ctl->mod_loadcnt != sdp->sdp_loadcnt) + goto err; +#endif /* __APPLE__ */ + + while (sdp != NULL) { + (void)ml_nofault_copy( (vm_offset_t)&sdp->sdp_savedval, (vm_offset_t)sdp->sdp_patchpoint, + sizeof(sdp->sdp_savedval)); + sdp = sdp->sdp_next; + } + +err: + ; +} + +static dtrace_pops_t sdt_pops = { + NULL, + sdt_provide_module, + sdt_enable, + sdt_disable, + NULL, + NULL, + sdt_getargdesc, + NULL, + NULL, + sdt_destroy +}; + +/*ARGSUSED*/ +static int +sdt_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) +{ +#pragma unused(cmd) + sdt_provider_t *prov; + + if (ddi_create_minor_node(devi, "sdt", S_IFCHR, + 0, DDI_PSEUDO, 0) == DDI_FAILURE) { + cmn_err(CE_NOTE, "/dev/sdt couldn't create minor node"); + ddi_remove_minor_node(devi, NULL); + return (DDI_FAILURE); + } + + ddi_report_dev(devi); + sdt_devi = devi; + + if (sdt_probetab_size == 0) + sdt_probetab_size = SDT_PROBETAB_SIZE; + + sdt_probetab_mask = sdt_probetab_size - 1; + sdt_probetab = + kmem_zalloc(sdt_probetab_size * sizeof (sdt_probe_t *), KM_SLEEP); + dtrace_invop_add(sdt_invop); + + for (prov = sdt_providers; prov->sdtp_name != NULL; prov++) { + if (dtrace_register(prov->sdtp_name, prov->sdtp_attr, + DTRACE_PRIV_KERNEL, NULL, + &sdt_pops, prov, &prov->sdtp_id) != 0) { + cmn_err(CE_WARN, "failed to register sdt provider %s", + prov->sdtp_name); + } + } + + return (DDI_SUCCESS); +} + +#if !defined(__APPLE__) +/*ARGSUSED*/ +static int +sdt_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + sdt_provider_t *prov; + + switch (cmd) { + case DDI_DETACH: + break; + + case DDI_SUSPEND: + return (DDI_SUCCESS); + + default: + return (DDI_FAILURE); + } + + for (prov = sdt_providers; prov->sdtp_name != NULL; prov++) { + if (prov->sdtp_id != DTRACE_PROVNONE) { + if (dtrace_unregister(prov->sdtp_id) != 0) + return (DDI_FAILURE); + + prov->sdtp_id = DTRACE_PROVNONE; + } + } + + dtrace_invop_remove(sdt_invop); + kmem_free(sdt_probetab, sdt_probetab_size * sizeof (sdt_probe_t *)); + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static int +sdt_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) +{ + int error; + + switch (infocmd) { + case DDI_INFO_DEVT2DEVINFO: + *result = (void *)sdt_devi; + error = DDI_SUCCESS; + break; + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)0; + error = DDI_SUCCESS; + break; + default: + error = DDI_FAILURE; + } + return (error); +} + +/*ARGSUSED*/ +static int +sdt_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) +{ + return (0); +} + +static struct cb_ops sdt_cb_ops = { + sdt_open, /* open */ + nodev, /* close */ + nulldev, /* strategy */ + nulldev, /* print */ + nodev, /* dump */ + nodev, /* read */ + nodev, /* write */ + nodev, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + nochpoll, /* poll */ + ddi_prop_op, /* cb_prop_op */ + 0, /* streamtab */ + D_NEW | D_MP /* Driver compatibility flag */ +}; + +static struct dev_ops sdt_ops = { + DEVO_REV, /* devo_rev, */ + 0, /* refcnt */ + sdt_info, /* get_dev_info */ + nulldev, /* identify */ + nulldev, /* probe */ + sdt_attach, /* attach */ + sdt_detach, /* detach */ + nodev, /* reset */ + &sdt_cb_ops, /* driver operations */ + NULL, /* bus operations */ + nodev /* dev power */ +}; + +/* + * Module linkage information for the kernel. + */ +static struct modldrv modldrv = { + &mod_driverops, /* module type (this is a pseudo driver) */ + "Statically Defined Tracing", /* name of module */ + &sdt_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, + (void *)&modldrv, + NULL +}; + +int +_init(void) +{ + return (mod_install(&modlinkage)); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +int +_fini(void) +{ + return (mod_remove(&modlinkage)); +} +#else +d_open_t _sdt_open; + +int _sdt_open(dev_t dev, int flags, int devtype, struct proc *p) +{ +#pragma unused(dev,flags,devtype,p) + return 0; +} + +#define SDT_MAJOR -24 /* let the kernel pick the device number */ + +/* + * A struct describing which functions will get invoked for certain + * actions. + */ +static struct cdevsw sdt_cdevsw = +{ + _sdt_open, /* open */ + eno_opcl, /* close */ + eno_rdwrt, /* read */ + eno_rdwrt, /* write */ + eno_ioctl, /* ioctl */ + (stop_fcn_t *)nulldev, /* stop */ + (reset_fcn_t *)nulldev, /* reset */ + NULL, /* tty's */ + eno_select, /* select */ + eno_mmap, /* mmap */ + eno_strat, /* strategy */ + eno_getc, /* getc */ + eno_putc, /* putc */ + 0 /* type */ +}; + +static int gSDTInited = 0; +static struct modctl g_sdt_kernctl; +static struct module g_sdt_mach_module; + +#include +#include + +extern struct mach_header _mh_execute_header; /* the kernel's mach header */ + +void sdt_init( void ) +{ + if (0 == gSDTInited) + { + int majdevno = cdevsw_add(SDT_MAJOR, &sdt_cdevsw); + + if (majdevno < 0) { + printf("sdt_init: failed to allocate a major number!\n"); + gSDTInited = 0; + return; + } + + if (MH_MAGIC != _mh_execute_header.magic) { + g_sdt_kernctl.address = (vm_address_t)NULL; + g_sdt_kernctl.size = 0; + } else { + struct mach_header *mh; + struct load_command *cmd; + struct segment_command *orig_ts = NULL, *orig_le = NULL; + struct symtab_command *orig_st = NULL; + struct nlist *sym = NULL; + char *strings; + unsigned int i; + + g_sdt_mach_module.sdt_nprobes = 0; + g_sdt_mach_module.sdt_probes = NULL; + + g_sdt_kernctl.address = (vm_address_t)&g_sdt_mach_module; + g_sdt_kernctl.size = 0; + strncpy((char *)&(g_sdt_kernctl.mod_modname), "mach_kernel", KMOD_MAX_NAME); + + mh = &_mh_execute_header; + cmd = (struct load_command *) &mh[1]; + for (i = 0; i < mh->ncmds; i++) { + if (cmd->cmd == LC_SEGMENT) { + struct segment_command *orig_sg = (struct segment_command *) cmd; + + if (strcmp(SEG_TEXT, orig_sg->segname) == 0) + orig_ts = orig_sg; + else if (strcmp(SEG_LINKEDIT, orig_sg->segname) == 0) + orig_le = orig_sg; + else if (strcmp("", orig_sg->segname) == 0) + orig_ts = orig_sg; /* kexts have a single unnamed segment */ + } + else if (cmd->cmd == LC_SYMTAB) + orig_st = (struct symtab_command *) cmd; + + cmd = (struct load_command *) ((caddr_t) cmd + cmd->cmdsize); + } + + if ((orig_ts == NULL) || (orig_st == NULL) || (orig_le == NULL)) + return; + + sym = (struct nlist *)orig_le->vmaddr; + strings = ((char *)sym) + orig_st->nsyms * sizeof(struct nlist); + + for (i = 0; i < orig_st->nsyms; i++) { + uint8_t n_type = sym[i].n_type & (N_TYPE | N_EXT); + char *name = strings + sym[i].n_un.n_strx; + char *prev_name; + unsigned long best; + unsigned int j; + + /* Check that the symbol is a global and that it has a name. */ + if (((N_SECT | N_EXT) != n_type && (N_ABS | N_EXT) != n_type)) + continue; + + if (0 == sym[i].n_un.n_strx) /* iff a null, "", name. */ + continue; + + /* Lop off omnipresent leading underscore. */ + if (*name == '_') + name += 1; + + if (strstr(name, "_dtrace_probe$")) { + sdt_probedesc_t *sdpd = kmem_alloc(sizeof(sdt_probedesc_t), KM_SLEEP); + int len = strlen(name) + 1; + + sdpd->sdpd_name = kmem_alloc(len, KM_SLEEP); + strncpy(sdpd->sdpd_name, name, len); /* NUL termination is ensured. */ + + prev_name = ""; + best = 0; + for (j = 0; j < orig_st->nsyms; j++) { + uint8_t n_type = sym[j].n_type & (N_TYPE | N_EXT); + char *name = strings + sym[j].n_un.n_strx; + + if (((N_SECT | N_EXT) != n_type && (N_ABS | N_EXT) != n_type)) + continue; + + if (0 == sym[j].n_un.n_strx) /* iff a null, "", name. */ + continue; + + if (*name == '_') + name += 1; + if (strstr(name, "_dtrace_probe$")) + continue; + + if (*(unsigned long *)sym[i].n_value <= (unsigned long)sym[j].n_value) + continue; + + if ((unsigned long)sym[j].n_value > best) { + best = (unsigned long)sym[j].n_value; + prev_name = name; + } + } + + sdpd->sdpd_func = kmem_alloc((len = strlen(prev_name) + 1), KM_SLEEP); + strncpy(sdpd->sdpd_func, prev_name, len); /* NUL termination is ensured. */ + + sdpd->sdpd_offset = *(unsigned long *)sym[i].n_value; + + sdpd->sdpd_next = g_sdt_mach_module.sdt_probes; + g_sdt_mach_module.sdt_probes = sdpd; + } else { + prev_name = name; + } + } + } + + sdt_attach( (dev_info_t *)majdevno, DDI_ATTACH ); + + gSDTInited = 1; + } else + panic("sdt_init: called twice!\n"); +} +#undef SDT_MAJOR + +/*ARGSUSED*/ +void +sdt_provide_module(void *arg, struct modctl *ctl) +{ +#pragma unused(ctl) + __sdt_provide_module(arg, &g_sdt_kernctl); + + sdt_probedesc_t *sdpd = g_sdt_mach_module.sdt_probes; + while (sdpd) { + sdt_probedesc_t *this_sdpd = sdpd; + kmem_free((void *)sdpd->sdpd_name, strlen(sdpd->sdpd_name) + 1); + kmem_free((void *)sdpd->sdpd_func, strlen(sdpd->sdpd_func) + 1); + sdpd = sdpd->sdpd_next; + kmem_free((void *)this_sdpd, sizeof(sdt_probedesc_t)); + } + g_sdt_mach_module.sdt_probes = NULL; +} + +#endif /* __APPLE__ */