/* * 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 * * Portions Copyright 2006-2008 John Birrell jb@freebsd.org * * $FreeBSD$ * */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fbt.h" MALLOC_DEFINE(M_FBT, "fbt", "Function Boundary Tracing"); dtrace_provider_id_t fbt_id; fbt_probe_t **fbt_probetab; int fbt_probetab_mask; static d_open_t fbt_open; static int fbt_unload(void); static void fbt_getargdesc(void *, dtrace_id_t, void *, dtrace_argdesc_t *); static void fbt_provide_module(void *, modctl_t *); static void fbt_destroy(void *, dtrace_id_t, void *); static void fbt_enable(void *, dtrace_id_t, void *); static void fbt_disable(void *, dtrace_id_t, void *); static void fbt_load(void *); static void fbt_suspend(void *, dtrace_id_t, void *); static void fbt_resume(void *, dtrace_id_t, void *); static struct cdevsw fbt_cdevsw = { .d_version = D_VERSION, .d_open = fbt_open, .d_name = "fbt", }; static dtrace_pattr_t fbt_attr = { { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON }, { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN }, { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_ISA }, { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON }, { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_ISA }, }; static dtrace_pops_t fbt_pops = { .dtps_provide = NULL, .dtps_provide_module = fbt_provide_module, .dtps_enable = fbt_enable, .dtps_disable = fbt_disable, .dtps_suspend = fbt_suspend, .dtps_resume = fbt_resume, .dtps_getargdesc = fbt_getargdesc, .dtps_getargval = NULL, .dtps_usermode = NULL, .dtps_destroy = fbt_destroy }; static struct cdev *fbt_cdev; static int fbt_probetab_size; static int fbt_verbose = 0; int fbt_excluded(const char *name) { if (strncmp(name, "dtrace_", 7) == 0 && strncmp(name, "dtrace_safe_", 12) != 0) { /* * Anything beginning with "dtrace_" may be called * from probe context unless it explicitly indicates * that it won't be called from probe context by * using the prefix "dtrace_safe_". */ return (1); } /* * Omit instrumentation of functions that are probably in DDB. It * makes it too hard to debug broken FBT. * * NB: kdb_enter() can be excluded, but its call to printf() can't be. * This is generally OK since we're not yet in debugging context. */ if (strncmp(name, "db_", 3) == 0 || strncmp(name, "kdb_", 4) == 0) return (1); /* * Lock owner methods may be called from probe context. */ if (strcmp(name, "owner_mtx") == 0 || strcmp(name, "owner_rm") == 0 || strcmp(name, "owner_rw") == 0 || strcmp(name, "owner_sx") == 0) return (1); /* * Stack unwinders may be called from probe context on some * platforms. */ #if defined(__aarch64__) || defined(__riscv) if (strcmp(name, "unwind_frame") == 0) return (1); #endif /* * When DTrace is built into the kernel we need to exclude * the FBT functions from instrumentation. */ #ifndef _KLD_MODULE if (strncmp(name, "fbt_", 4) == 0) return (1); #endif return (0); } static void fbt_doubletrap(void) { fbt_probe_t *fbt; int i; for (i = 0; i < fbt_probetab_size; i++) { fbt = fbt_probetab[i]; for (; fbt != NULL; fbt = fbt->fbtp_probenext) fbt_patch_tracepoint(fbt, fbt->fbtp_savedval); } } static void fbt_provide_module(void *arg, modctl_t *lf) { char modname[MAXPATHLEN]; int i; size_t len; strlcpy(modname, lf->filename, sizeof(modname)); len = strlen(modname); if (len > 3 && strcmp(modname + len - 3, ".ko") == 0) modname[len - 3] = '\0'; /* * Employees of dtrace and their families are ineligible. Void * where prohibited. */ if (strcmp(modname, "dtrace") == 0) return; /* * To register with DTrace, a module must list 'dtrace' as a * dependency in order for the kernel linker to resolve * symbols like dtrace_register(). All modules with such a * dependency are ineligible for FBT tracing. */ for (i = 0; i < lf->ndeps; i++) if (strncmp(lf->deps[i]->filename, "dtrace", 6) == 0) return; if (lf->fbt_nentries) { /* * This module has some FBT entries allocated; we're afraid * to screw with it. */ return; } /* * List the functions in the module and the symbol values. */ (void) linker_file_function_listall(lf, fbt_provide_module_function, modname); } static void fbt_destroy_one(fbt_probe_t *fbt) { fbt_probe_t *hash, *hashprev, *next; int ndx; ndx = FBT_ADDR2NDX(fbt->fbtp_patchpoint); for (hash = fbt_probetab[ndx], hashprev = NULL; hash != NULL; hashprev = hash, hash = hash->fbtp_hashnext) { if (hash == fbt) { if ((next = fbt->fbtp_tracenext) != NULL) next->fbtp_hashnext = hash->fbtp_hashnext; else next = hash->fbtp_hashnext; if (hashprev != NULL) hashprev->fbtp_hashnext = next; else fbt_probetab[ndx] = next; goto free; } else if (hash->fbtp_patchpoint == fbt->fbtp_patchpoint) { for (next = hash; next->fbtp_tracenext != NULL; next = next->fbtp_tracenext) { if (fbt == next->fbtp_tracenext) { next->fbtp_tracenext = fbt->fbtp_tracenext; goto free; } } } } panic("probe %p not found in hash table", fbt); free: free(fbt, M_FBT); } static void fbt_destroy(void *arg, dtrace_id_t id, void *parg) { fbt_probe_t *fbt = parg, *next; modctl_t *ctl; do { ctl = fbt->fbtp_ctl; ctl->fbt_nentries--; next = fbt->fbtp_probenext; fbt_destroy_one(fbt); fbt = next; } while (fbt != NULL); } static void fbt_enable(void *arg, dtrace_id_t id, void *parg) { fbt_probe_t *fbt = parg; modctl_t *ctl = fbt->fbtp_ctl; ctl->nenabled++; /* * 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->loadcnt != fbt->fbtp_loadcnt) { if (fbt_verbose) { printf("fbt is failing for probe %s " "(module %s reloaded)", fbt->fbtp_name, ctl->filename); } return; } for (; fbt != NULL; fbt = fbt->fbtp_probenext) { fbt_patch_tracepoint(fbt, fbt->fbtp_patchval); fbt->fbtp_enabled++; } } static void fbt_disable(void *arg, dtrace_id_t id, void *parg) { fbt_probe_t *fbt = parg, *hash; modctl_t *ctl = fbt->fbtp_ctl; ASSERT(ctl->nenabled > 0); ctl->nenabled--; if ((ctl->loadcnt != fbt->fbtp_loadcnt)) return; for (; fbt != NULL; fbt = fbt->fbtp_probenext) { fbt->fbtp_enabled--; for (hash = fbt_probetab[FBT_ADDR2NDX(fbt->fbtp_patchpoint)]; hash != NULL; hash = hash->fbtp_hashnext) { if (hash->fbtp_patchpoint == fbt->fbtp_patchpoint) { for (; hash != NULL; hash = hash->fbtp_tracenext) if (hash->fbtp_enabled > 0) break; break; } } if (hash == NULL) fbt_patch_tracepoint(fbt, fbt->fbtp_savedval); } } static void fbt_suspend(void *arg, dtrace_id_t id, void *parg) { fbt_probe_t *fbt = parg; modctl_t *ctl = fbt->fbtp_ctl; ASSERT(ctl->nenabled > 0); if ((ctl->loadcnt != fbt->fbtp_loadcnt)) return; for (; fbt != NULL; fbt = fbt->fbtp_probenext) fbt_patch_tracepoint(fbt, fbt->fbtp_savedval); } static void fbt_resume(void *arg, dtrace_id_t id, void *parg) { fbt_probe_t *fbt = parg; modctl_t *ctl = fbt->fbtp_ctl; ASSERT(ctl->nenabled > 0); if ((ctl->loadcnt != fbt->fbtp_loadcnt)) return; for (; fbt != NULL; fbt = fbt->fbtp_probenext) fbt_patch_tracepoint(fbt, fbt->fbtp_patchval); } static int fbt_ctfoff_init(modctl_t *lf, linker_ctf_t *lc) { const Elf_Sym *symp = lc->symtab; const ctf_header_t *hp = (const ctf_header_t *) lc->ctftab; const uint8_t *ctfdata = lc->ctftab + sizeof(ctf_header_t); int i; uint32_t *ctfoff; uint32_t objtoff = hp->cth_objtoff; uint32_t funcoff = hp->cth_funcoff; ushort_t info; ushort_t vlen; /* Sanity check. */ if (hp->cth_magic != CTF_MAGIC) { printf("Bad magic value in CTF data of '%s'\n",lf->pathname); return (EINVAL); } if (lc->symtab == NULL) { printf("No symbol table in '%s'\n",lf->pathname); return (EINVAL); } ctfoff = malloc(sizeof(uint32_t) * lc->nsym, M_LINKER, M_WAITOK); *lc->ctfoffp = ctfoff; for (i = 0; i < lc->nsym; i++, ctfoff++, symp++) { if (symp->st_name == 0 || symp->st_shndx == SHN_UNDEF) { *ctfoff = 0xffffffff; continue; } switch (ELF_ST_TYPE(symp->st_info)) { case STT_OBJECT: if (objtoff >= hp->cth_funcoff || (symp->st_shndx == SHN_ABS && symp->st_value == 0)) { *ctfoff = 0xffffffff; break; } *ctfoff = objtoff; objtoff += sizeof (ushort_t); break; case STT_FUNC: if (funcoff >= hp->cth_typeoff) { *ctfoff = 0xffffffff; break; } *ctfoff = funcoff; info = *((const ushort_t *)(ctfdata + funcoff)); vlen = CTF_INFO_VLEN(info); /* * If we encounter a zero pad at the end, just skip it. * Otherwise skip over the function and its return type * (+2) and the argument list (vlen). */ if (CTF_INFO_KIND(info) == CTF_K_UNKNOWN && vlen == 0) funcoff += sizeof (ushort_t); /* skip pad */ else funcoff += sizeof (ushort_t) * (vlen + 2); break; default: *ctfoff = 0xffffffff; break; } } return (0); } static ssize_t fbt_get_ctt_size(uint8_t version, const ctf_type_t *tp, ssize_t *sizep, ssize_t *incrementp) { ssize_t size, increment; if (tp->ctt_size == CTF_LSIZE_SENT) { size = CTF_TYPE_LSIZE(tp); increment = sizeof (ctf_type_t); } else { size = tp->ctt_size; increment = sizeof (ctf_stype_t); } if (sizep) *sizep = size; if (incrementp) *incrementp = increment; return (size); } static int fbt_typoff_init(linker_ctf_t *lc) { const ctf_header_t *hp = (const ctf_header_t *) lc->ctftab; const ctf_type_t *tbuf; const ctf_type_t *tend; const ctf_type_t *tp; const uint8_t *ctfdata = lc->ctftab + sizeof(ctf_header_t); int ctf_typemax = 0; uint32_t *xp; ulong_t pop[CTF_K_MAX + 1] = { 0 }; uint8_t version; /* Sanity check. */ if (hp->cth_magic != CTF_MAGIC) return (EINVAL); version = hp->cth_version; tbuf = (const ctf_type_t *) (ctfdata + hp->cth_typeoff); tend = (const ctf_type_t *) (ctfdata + hp->cth_stroff); /* * We make two passes through the entire type section. In this first * pass, we count the number of each type and the total number of types. */ for (tp = tbuf; tp < tend; ctf_typemax++) { ushort_t kind = CTF_INFO_KIND(tp->ctt_info); ulong_t vlen = CTF_INFO_VLEN(tp->ctt_info); ssize_t size, increment; size_t vbytes; (void) fbt_get_ctt_size(version, tp, &size, &increment); switch (kind) { case CTF_K_INTEGER: case CTF_K_FLOAT: vbytes = sizeof (uint_t); break; case CTF_K_ARRAY: vbytes = sizeof (ctf_array_t); break; case CTF_K_FUNCTION: vbytes = sizeof (ushort_t) * (vlen + (vlen & 1)); break; case CTF_K_STRUCT: case CTF_K_UNION: if (size < CTF_LSTRUCT_THRESH) vbytes = sizeof (ctf_member_t) * vlen; else vbytes = sizeof (ctf_lmember_t) * vlen; break; case CTF_K_ENUM: vbytes = sizeof (ctf_enum_t) * vlen; break; case CTF_K_FORWARD: /* * For forward declarations, ctt_type is the CTF_K_* * kind for the tag, so bump that population count too. * If ctt_type is unknown, treat the tag as a struct. */ if (tp->ctt_type == CTF_K_UNKNOWN || tp->ctt_type >= CTF_K_MAX) pop[CTF_K_STRUCT]++; else pop[tp->ctt_type]++; /*FALLTHRU*/ case CTF_K_UNKNOWN: vbytes = 0; break; case CTF_K_POINTER: case CTF_K_TYPEDEF: case CTF_K_VOLATILE: case CTF_K_CONST: case CTF_K_RESTRICT: vbytes = 0; break; default: printf("%s(%d): detected invalid CTF kind -- %u\n", __func__, __LINE__, kind); return (EIO); } tp = (ctf_type_t *)((uintptr_t)tp + increment + vbytes); pop[kind]++; } /* account for a sentinel value below */ ctf_typemax++; *lc->typlenp = ctf_typemax; xp = malloc(sizeof(uint32_t) * ctf_typemax, M_LINKER, M_ZERO | M_WAITOK); *lc->typoffp = xp; /* type id 0 is used as a sentinel value */ *xp++ = 0; /* * In the second pass, fill in the type offset. */ for (tp = tbuf; tp < tend; xp++) { ushort_t kind = CTF_INFO_KIND(tp->ctt_info); ulong_t vlen = CTF_INFO_VLEN(tp->ctt_info); ssize_t size, increment; size_t vbytes; (void) fbt_get_ctt_size(version, tp, &size, &increment); switch (kind) { case CTF_K_INTEGER: case CTF_K_FLOAT: vbytes = sizeof (uint_t); break; case CTF_K_ARRAY: vbytes = sizeof (ctf_array_t); break; case CTF_K_FUNCTION: vbytes = sizeof (ushort_t) * (vlen + (vlen & 1)); break; case CTF_K_STRUCT: case CTF_K_UNION: if (size < CTF_LSTRUCT_THRESH) vbytes = sizeof (ctf_member_t) * vlen; else vbytes = sizeof (ctf_lmember_t) * vlen; break; case CTF_K_ENUM: vbytes = sizeof (ctf_enum_t) * vlen; break; case CTF_K_FORWARD: case CTF_K_UNKNOWN: vbytes = 0; break; case CTF_K_POINTER: case CTF_K_TYPEDEF: case CTF_K_VOLATILE: case CTF_K_CONST: case CTF_K_RESTRICT: vbytes = 0; break; default: printf("%s(%d): detected invalid CTF kind -- %u\n", __func__, __LINE__, kind); return (EIO); } *xp = (uint32_t)((uintptr_t) tp - (uintptr_t) ctfdata); tp = (ctf_type_t *)((uintptr_t)tp + increment + vbytes); } return (0); } /* * CTF Declaration Stack * * In order to implement ctf_type_name(), we must convert a type graph back * into a C type declaration. Unfortunately, a type graph represents a storage * class ordering of the type whereas a type declaration must obey the C rules * for operator precedence, and the two orderings are frequently in conflict. * For example, consider these CTF type graphs and their C declarations: * * CTF_K_POINTER -> CTF_K_FUNCTION -> CTF_K_INTEGER : int (*)() * CTF_K_POINTER -> CTF_K_ARRAY -> CTF_K_INTEGER : int (*)[] * * In each case, parentheses are used to raise operator * to higher lexical * precedence, so the string form of the C declaration cannot be constructed by * walking the type graph links and forming the string from left to right. * * The functions in this file build a set of stacks from the type graph nodes * corresponding to the C operator precedence levels in the appropriate order. * The code in ctf_type_name() can then iterate over the levels and nodes in * lexical precedence order and construct the final C declaration string. */ typedef struct ctf_list { struct ctf_list *l_prev; /* previous pointer or tail pointer */ struct ctf_list *l_next; /* next pointer or head pointer */ } ctf_list_t; #define ctf_list_prev(elem) ((void *)(((ctf_list_t *)(elem))->l_prev)) #define ctf_list_next(elem) ((void *)(((ctf_list_t *)(elem))->l_next)) typedef enum { CTF_PREC_BASE, CTF_PREC_POINTER, CTF_PREC_ARRAY, CTF_PREC_FUNCTION, CTF_PREC_MAX } ctf_decl_prec_t; typedef struct ctf_decl_node { ctf_list_t cd_list; /* linked list pointers */ ctf_id_t cd_type; /* type identifier */ uint_t cd_kind; /* type kind */ uint_t cd_n; /* type dimension if array */ } ctf_decl_node_t; typedef struct ctf_decl { ctf_list_t cd_nodes[CTF_PREC_MAX]; /* declaration node stacks */ int cd_order[CTF_PREC_MAX]; /* storage order of decls */ ctf_decl_prec_t cd_qualp; /* qualifier precision */ ctf_decl_prec_t cd_ordp; /* ordered precision */ char *cd_buf; /* buffer for output */ char *cd_ptr; /* buffer location */ char *cd_end; /* buffer limit */ size_t cd_len; /* buffer space required */ int cd_err; /* saved error value */ } ctf_decl_t; /* * Simple doubly-linked list append routine. This implementation assumes that * each list element contains an embedded ctf_list_t as the first member. * An additional ctf_list_t is used to store the head (l_next) and tail * (l_prev) pointers. The current head and tail list elements have their * previous and next pointers set to NULL, respectively. */ static void ctf_list_append(ctf_list_t *lp, void *new) { ctf_list_t *p = lp->l_prev; /* p = tail list element */ ctf_list_t *q = new; /* q = new list element */ lp->l_prev = q; q->l_prev = p; q->l_next = NULL; if (p != NULL) p->l_next = q; else lp->l_next = q; } /* * Prepend the specified existing element to the given ctf_list_t. The * existing pointer should be pointing at a struct with embedded ctf_list_t. */ static void ctf_list_prepend(ctf_list_t *lp, void *new) { ctf_list_t *p = new; /* p = new list element */ ctf_list_t *q = lp->l_next; /* q = head list element */ lp->l_next = p; p->l_prev = NULL; p->l_next = q; if (q != NULL) q->l_prev = p; else lp->l_prev = p; } static void ctf_decl_init(ctf_decl_t *cd, char *buf, size_t len) { int i; bzero(cd, sizeof (ctf_decl_t)); for (i = CTF_PREC_BASE; i < CTF_PREC_MAX; i++) cd->cd_order[i] = CTF_PREC_BASE - 1; cd->cd_qualp = CTF_PREC_BASE; cd->cd_ordp = CTF_PREC_BASE; cd->cd_buf = buf; cd->cd_ptr = buf; cd->cd_end = buf + len; } static void ctf_decl_fini(ctf_decl_t *cd) { ctf_decl_node_t *cdp, *ndp; int i; for (i = CTF_PREC_BASE; i < CTF_PREC_MAX; i++) { for (cdp = ctf_list_next(&cd->cd_nodes[i]); cdp != NULL; cdp = ndp) { ndp = ctf_list_next(cdp); free(cdp, M_FBT); } } } static const ctf_type_t * ctf_lookup_by_id(linker_ctf_t *lc, ctf_id_t type) { const ctf_type_t *tp; uint32_t offset; uint32_t *typoff = *lc->typoffp; if (type >= *lc->typlenp) { printf("%s(%d): type %d exceeds max %ld\n",__func__,__LINE__,(int) type,*lc->typlenp); return(NULL); } /* Check if the type isn't cross-referenced. */ if ((offset = typoff[type]) == 0) { printf("%s(%d): type %d isn't cross referenced\n",__func__,__LINE__, (int) type); return(NULL); } tp = (const ctf_type_t *)(lc->ctftab + offset + sizeof(ctf_header_t)); return (tp); } static void fbt_array_info(linker_ctf_t *lc, ctf_id_t type, ctf_arinfo_t *arp) { const ctf_header_t *hp = (const ctf_header_t *) lc->ctftab; const ctf_type_t *tp; const ctf_array_t *ap; ssize_t increment; bzero(arp, sizeof(*arp)); if ((tp = ctf_lookup_by_id(lc, type)) == NULL) return; if (CTF_INFO_KIND(tp->ctt_info) != CTF_K_ARRAY) return; (void) fbt_get_ctt_size(hp->cth_version, tp, NULL, &increment); ap = (const ctf_array_t *)((uintptr_t)tp + increment); arp->ctr_contents = ap->cta_contents; arp->ctr_index = ap->cta_index; arp->ctr_nelems = ap->cta_nelems; } static const char * ctf_strptr(linker_ctf_t *lc, int name) { const ctf_header_t *hp = (const ctf_header_t *) lc->ctftab; const char *strp = ""; if (name < 0 || name >= hp->cth_strlen) return(strp); strp = (const char *)(lc->ctftab + hp->cth_stroff + name + sizeof(ctf_header_t)); return (strp); } static void ctf_decl_push(ctf_decl_t *cd, linker_ctf_t *lc, ctf_id_t type) { ctf_decl_node_t *cdp; ctf_decl_prec_t prec; uint_t kind, n = 1; int is_qual = 0; const ctf_type_t *tp; ctf_arinfo_t ar; if ((tp = ctf_lookup_by_id(lc, type)) == NULL) { cd->cd_err = ENOENT; return; } switch (kind = CTF_INFO_KIND(tp->ctt_info)) { case CTF_K_ARRAY: fbt_array_info(lc, type, &ar); ctf_decl_push(cd, lc, ar.ctr_contents); n = ar.ctr_nelems; prec = CTF_PREC_ARRAY; break; case CTF_K_TYPEDEF: if (ctf_strptr(lc, tp->ctt_name)[0] == '\0') { ctf_decl_push(cd, lc, tp->ctt_type); return; } prec = CTF_PREC_BASE; break; case CTF_K_FUNCTION: ctf_decl_push(cd, lc, tp->ctt_type); prec = CTF_PREC_FUNCTION; break; case CTF_K_POINTER: ctf_decl_push(cd, lc, tp->ctt_type); prec = CTF_PREC_POINTER; break; case CTF_K_VOLATILE: case CTF_K_CONST: case CTF_K_RESTRICT: ctf_decl_push(cd, lc, tp->ctt_type); prec = cd->cd_qualp; is_qual++; break; default: prec = CTF_PREC_BASE; } cdp = malloc(sizeof(*cdp), M_FBT, M_WAITOK); cdp->cd_type = type; cdp->cd_kind = kind; cdp->cd_n = n; if (ctf_list_next(&cd->cd_nodes[prec]) == NULL) cd->cd_order[prec] = cd->cd_ordp++; /* * Reset cd_qualp to the highest precedence level that we've seen so * far that can be qualified (CTF_PREC_BASE or CTF_PREC_POINTER). */ if (prec > cd->cd_qualp && prec < CTF_PREC_ARRAY) cd->cd_qualp = prec; /* * C array declarators are ordered inside out so prepend them. Also by * convention qualifiers of base types precede the type specifier (e.g. * const int vs. int const) even though the two forms are equivalent. */ if (kind == CTF_K_ARRAY || (is_qual && prec == CTF_PREC_BASE)) ctf_list_prepend(&cd->cd_nodes[prec], cdp); else ctf_list_append(&cd->cd_nodes[prec], cdp); } static void ctf_decl_sprintf(ctf_decl_t *cd, const char *format, ...) { size_t len = (size_t)(cd->cd_end - cd->cd_ptr); va_list ap; size_t n; va_start(ap, format); n = vsnprintf(cd->cd_ptr, len, format, ap); va_end(ap); cd->cd_ptr += MIN(n, len); cd->cd_len += n; } static ssize_t fbt_type_name(linker_ctf_t *lc, ctf_id_t type, char *buf, size_t len) { ctf_decl_t cd; ctf_decl_node_t *cdp; ctf_decl_prec_t prec, lp, rp; int ptr, arr; uint_t k; if (lc == NULL && type == CTF_ERR) return (-1); /* simplify caller code by permitting CTF_ERR */ ctf_decl_init(&cd, buf, len); ctf_decl_push(&cd, lc, type); if (cd.cd_err != 0) { ctf_decl_fini(&cd); return (-1); } /* * If the type graph's order conflicts with lexical precedence order * for pointers or arrays, then we need to surround the declarations at * the corresponding lexical precedence with parentheses. This can * result in either a parenthesized pointer (*) as in int (*)() or * int (*)[], or in a parenthesized pointer and array as in int (*[])(). */ ptr = cd.cd_order[CTF_PREC_POINTER] > CTF_PREC_POINTER; arr = cd.cd_order[CTF_PREC_ARRAY] > CTF_PREC_ARRAY; rp = arr ? CTF_PREC_ARRAY : ptr ? CTF_PREC_POINTER : -1; lp = ptr ? CTF_PREC_POINTER : arr ? CTF_PREC_ARRAY : -1; k = CTF_K_POINTER; /* avoid leading whitespace (see below) */ for (prec = CTF_PREC_BASE; prec < CTF_PREC_MAX; prec++) { for (cdp = ctf_list_next(&cd.cd_nodes[prec]); cdp != NULL; cdp = ctf_list_next(cdp)) { const ctf_type_t *tp = ctf_lookup_by_id(lc, cdp->cd_type); const char *name = ctf_strptr(lc, tp->ctt_name); if (k != CTF_K_POINTER && k != CTF_K_ARRAY) ctf_decl_sprintf(&cd, " "); if (lp == prec) { ctf_decl_sprintf(&cd, "("); lp = -1; } switch (cdp->cd_kind) { case CTF_K_INTEGER: case CTF_K_FLOAT: case CTF_K_TYPEDEF: ctf_decl_sprintf(&cd, "%s", name); break; case CTF_K_POINTER: ctf_decl_sprintf(&cd, "*"); break; case CTF_K_ARRAY: ctf_decl_sprintf(&cd, "[%u]", cdp->cd_n); break; case CTF_K_FUNCTION: ctf_decl_sprintf(&cd, "()"); break; case CTF_K_STRUCT: case CTF_K_FORWARD: ctf_decl_sprintf(&cd, "struct %s", name); break; case CTF_K_UNION: ctf_decl_sprintf(&cd, "union %s", name); break; case CTF_K_ENUM: ctf_decl_sprintf(&cd, "enum %s", name); break; case CTF_K_VOLATILE: ctf_decl_sprintf(&cd, "volatile"); break; case CTF_K_CONST: ctf_decl_sprintf(&cd, "const"); break; case CTF_K_RESTRICT: ctf_decl_sprintf(&cd, "restrict"); break; } k = cdp->cd_kind; } if (rp == prec) ctf_decl_sprintf(&cd, ")"); } ctf_decl_fini(&cd); return (cd.cd_len); } static void fbt_getargdesc(void *arg __unused, dtrace_id_t id __unused, void *parg, dtrace_argdesc_t *desc) { const ushort_t *dp; fbt_probe_t *fbt = parg; linker_ctf_t lc; modctl_t *ctl = fbt->fbtp_ctl; int ndx = desc->dtargd_ndx; int symindx = fbt->fbtp_symindx; uint32_t *ctfoff; uint32_t offset; ushort_t info, kind, n; if (fbt->fbtp_roffset != 0 && desc->dtargd_ndx == 0) { (void) strcpy(desc->dtargd_native, "int"); return; } desc->dtargd_ndx = DTRACE_ARGNONE; /* Get a pointer to the CTF data and it's length. */ if (linker_ctf_get(ctl, &lc) != 0) /* No CTF data? Something wrong? *shrug* */ return; /* Check if this module hasn't been initialised yet. */ if (*lc.ctfoffp == NULL) { /* * Initialise the CTF object and function symindx to * byte offset array. */ if (fbt_ctfoff_init(ctl, &lc) != 0) return; /* Initialise the CTF type to byte offset array. */ if (fbt_typoff_init(&lc) != 0) return; } ctfoff = *lc.ctfoffp; if (ctfoff == NULL || *lc.typoffp == NULL) return; /* Check if the symbol index is out of range. */ if (symindx >= lc.nsym) return; /* Check if the symbol isn't cross-referenced. */ if ((offset = ctfoff[symindx]) == 0xffffffff) return; dp = (const ushort_t *)(lc.ctftab + offset + sizeof(ctf_header_t)); info = *dp++; kind = CTF_INFO_KIND(info); n = CTF_INFO_VLEN(info); if (kind == CTF_K_UNKNOWN && n == 0) { printf("%s(%d): Unknown function!\n",__func__,__LINE__); return; } if (kind != CTF_K_FUNCTION) { printf("%s(%d): Expected a function!\n",__func__,__LINE__); return; } if (fbt->fbtp_roffset != 0) { /* Only return type is available for args[1] in return probe. */ if (ndx > 1) return; ASSERT(ndx == 1); } else { /* Check if the requested argument doesn't exist. */ if (ndx >= n) return; /* Skip the return type and arguments up to the one requested. */ dp += ndx + 1; } if (fbt_type_name(&lc, *dp, desc->dtargd_native, sizeof(desc->dtargd_native)) > 0) desc->dtargd_ndx = ndx; return; } static int fbt_linker_file_cb(linker_file_t lf, void *arg) { fbt_provide_module(arg, lf); return (0); } static void fbt_load(void *dummy) { /* Create the /dev/dtrace/fbt entry. */ fbt_cdev = make_dev(&fbt_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "dtrace/fbt"); /* Default the probe table size if not specified. */ if (fbt_probetab_size == 0) fbt_probetab_size = FBT_PROBETAB_SIZE; /* Choose the hash mask for the probe table. */ fbt_probetab_mask = fbt_probetab_size - 1; /* Allocate memory for the probe table. */ fbt_probetab = malloc(fbt_probetab_size * sizeof (fbt_probe_t *), M_FBT, M_WAITOK | M_ZERO); dtrace_doubletrap_func = fbt_doubletrap; dtrace_invop_add(fbt_invop); if (dtrace_register("fbt", &fbt_attr, DTRACE_PRIV_USER, NULL, &fbt_pops, NULL, &fbt_id) != 0) return; /* Create probes for the kernel and already-loaded modules. */ linker_file_foreach(fbt_linker_file_cb, NULL); } static int fbt_unload() { int error = 0; /* De-register the invalid opcode handler. */ dtrace_invop_remove(fbt_invop); dtrace_doubletrap_func = NULL; /* De-register this DTrace provider. */ if ((error = dtrace_unregister(fbt_id)) != 0) return (error); /* Free the probe table. */ free(fbt_probetab, M_FBT); fbt_probetab = NULL; fbt_probetab_mask = 0; destroy_dev(fbt_cdev); return (error); } static int fbt_modevent(module_t mod __unused, int type, void *data __unused) { int error = 0; switch (type) { case MOD_LOAD: break; case MOD_UNLOAD: break; case MOD_SHUTDOWN: break; default: error = EOPNOTSUPP; break; } return (error); } static int fbt_open(struct cdev *dev __unused, int oflags __unused, int devtype __unused, struct thread *td __unused) { return (0); } SYSINIT(fbt_load, SI_SUB_DTRACE_PROVIDER, SI_ORDER_ANY, fbt_load, NULL); SYSUNINIT(fbt_unload, SI_SUB_DTRACE_PROVIDER, SI_ORDER_ANY, fbt_unload, NULL); DEV_MODULE(fbt, fbt_modevent, NULL); MODULE_VERSION(fbt, 1); MODULE_DEPEND(fbt, dtrace, 1, 1, 1); MODULE_DEPEND(fbt, opensolaris, 1, 1, 1);