/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright (c) 2012 Joyent, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include static int whatis_debug = 0; /* for bsearch; r is an array of {base, size}, e points into w->w_addrs */ static int find_range(const void *r, const void *e) { const uintptr_t *range = r; uintptr_t el = *(const uintptr_t *)e; if (el < range[0]) return (1); if ((el - range[0]) >= range[1]) return (-1); return (0); } /* for qsort; simple uintptr comparator */ static int uintptr_cmp(const void *l, const void *r) { uintptr_t lhs = *(const uintptr_t *)l; uintptr_t rhs = *(const uintptr_t *)r; if (lhs < rhs) return (-1); if (lhs > rhs) return (1); return (0); } static const uintptr_t * mdb_whatis_search(mdb_whatis_t *w, uintptr_t base, size_t size) { uintptr_t range[2]; range[0] = base; range[1] = size; return (bsearch(range, w->w_addrs, w->w_naddrs, sizeof (*w->w_addrs), find_range)); } /* * Returns non-zero if and only if there is at least one address of interest * in the range [base, base+size). */ int mdb_whatis_overlaps(mdb_whatis_t *w, uintptr_t base, size_t size) { const uintptr_t *f; uint_t offset, cur; if (whatis_debug && w->w_magic != WHATIS_MAGIC) { mdb_warn( "mdb_whatis_overlaps(): bogus mdb_whatis_t pointer\n"); return (0); } if (w->w_done || size == 0) return (0); if (base + size - 1 < base) { mdb_warn("mdb_whatis_overlaps(): [%p, %p+%p) overflows\n", base, base, size); return (0); } f = mdb_whatis_search(w, base, size); if (f == NULL) return (0); cur = offset = f - w->w_addrs; /* * We only return success if there's an address we'll actually * match in the range. We can quickly check for the ALL flag * or a non-found address at our match point. */ if ((w->w_flags & WHATIS_ALL) || !w->w_addrfound[cur]) return (1); /* Search backwards then forwards for a non-found address */ while (cur > 0) { cur--; if (w->w_addrs[cur] < base) break; if (!w->w_addrfound[cur]) return (1); } for (cur = offset + 1; cur < w->w_naddrs; cur++) { if ((w->w_addrs[cur] - base) >= size) break; if (!w->w_addrfound[cur]) return (1); } return (0); /* everything has already been seen */ } /* * Iteratively search our list of addresses for matches in [base, base+size). */ int mdb_whatis_match(mdb_whatis_t *w, uintptr_t base, size_t size, uintptr_t *out) { size_t offset; if (whatis_debug) { if (w->w_magic != WHATIS_MAGIC) { mdb_warn( "mdb_whatis_match(): bogus mdb_whatis_t pointer\n"); goto done; } } if (w->w_done || size == 0) goto done; if (base + size - 1 < base) { mdb_warn("mdb_whatis_match(): [%p, %p+%x) overflows\n", base, base, size); return (0); } if ((offset = w->w_match_next) != 0 && (base != w->w_match_base || size != w->w_match_size)) { mdb_warn("mdb_whatis_match(): new range [%p, %p+%p) " "while still searching [%p, %p+%p)\n", base, base, size, w->w_match_base, w->w_match_base, w->w_match_size); offset = 0; } if (offset == 0) { const uintptr_t *f = mdb_whatis_search(w, base, size); if (f == NULL) goto done; offset = (f - w->w_addrs); /* Walk backwards until we reach the first match */ while (offset > 0 && w->w_addrs[offset - 1] >= base) offset--; w->w_match_base = base; w->w_match_size = size; } for (; offset < w->w_naddrs && ((w->w_addrs[offset] - base) < size); offset++) { *out = w->w_addrs[offset]; w->w_match_next = offset + 1; if (w->w_addrfound[offset]) { /* if we're not seeing everything, skip it */ if (!(w->w_flags & WHATIS_ALL)) continue; return (1); } /* We haven't seen this address yet. */ w->w_found++; w->w_addrfound[offset] = 1; /* If we've found them all, we're done */ if (w->w_found == w->w_naddrs && !(w->w_flags & WHATIS_ALL)) w->w_done = 1; return (1); } done: w->w_match_next = 0; w->w_match_base = 0; w->w_match_size = 0; return (0); } /* * Report a pointer (addr) in an object beginning at (base) in standard * whatis-style. (format, ...) are mdb_printf() arguments, to be printed * after the address information. The caller is responsible for printing * a newline (either in format or after the call returns) */ /*ARGSUSED*/ void mdb_whatis_report_object(mdb_whatis_t *w, uintptr_t addr, uintptr_t base, const char *format, ...) { va_list alist; if (whatis_debug) { if (mdb_whatis_search(w, addr, 1) == NULL) mdb_warn("mdb_whatis_report_object(): addr " "%p is not a pointer of interest.\n", addr); } if (addr < base) mdb_warn("whatis: addr (%p) is less than base (%p)\n", addr, base); if (addr == base) mdb_printf("%p is ", addr); else mdb_printf("%p is %p+%p, ", addr, base, addr - base); if (format == NULL) return; va_start(alist, format); mdb_iob_vprintf(mdb.m_out, format, alist); va_end(alist); } /* * Report an address (addr), with symbolic information if available, in * standard whatis-style. (format, ...) are mdb_printf() arguments, to be * printed after the address information. The caller is responsible for * printing a newline (either in format or after the call returns) */ /*ARGSUSED*/ void mdb_whatis_report_address(mdb_whatis_t *w, uintptr_t addr, const char *format, ...) { GElf_Sym sym; va_list alist; if (whatis_debug) { if (mdb_whatis_search(w, addr, 1) == NULL) mdb_warn("mdb_whatis_report_adddress(): addr " "%p is not a pointer of interest.\n", addr); } mdb_printf("%p is ", addr); if (mdb_lookup_by_addr(addr, MDB_SYM_FUZZY, NULL, 0, &sym) != -1 && (addr - (uintptr_t)sym.st_value) < sym.st_size) { mdb_printf("%a, ", addr); } va_start(alist, format); mdb_iob_vprintf(mdb.m_out, format, alist); va_end(alist); } uint_t mdb_whatis_flags(mdb_whatis_t *w) { /* Mask out the internal-only flags */ return (w->w_flags & WHATIS_PUBLIC); } uint_t mdb_whatis_done(mdb_whatis_t *w) { return (w->w_done); } /* * Whatis callback list management */ typedef struct whatis_callback { uint64_t wcb_index; mdb_module_t *wcb_module; const char *wcb_modname; char *wcb_name; mdb_whatis_cb_f *wcb_func; void *wcb_arg; uint_t wcb_prio; uint_t wcb_flags; } whatis_callback_t; static whatis_callback_t builtin_whatis[] = { { 0, NULL, "mdb", "mappings", whatis_run_mappings, NULL, WHATIS_PRIO_MIN, WHATIS_REG_NO_ID } }; #define NBUILTINS (sizeof (builtin_whatis) / sizeof (*builtin_whatis)) static whatis_callback_t *whatis_cb_start[NBUILTINS]; static whatis_callback_t **whatis_cb = NULL; /* callback array */ static size_t whatis_cb_count; /* count of callbacks */ static size_t whatis_cb_size; /* size of whatis_cb array */ static uint64_t whatis_cb_index; /* global count */ #define WHATIS_CB_SIZE_MIN 8 /* initial allocation size */ static int whatis_cbcmp(const void *lhs, const void *rhs) { whatis_callback_t *l = *(whatis_callback_t * const *)lhs; whatis_callback_t *r = *(whatis_callback_t * const *)rhs; int ret; /* First, handle NULLs; we want them at the end */ if (l == NULL && r == NULL) return (0); if (l == NULL) return (1); if (r == NULL) return (-1); /* Next, compare priorities */ if (l->wcb_prio < r->wcb_prio) return (-1); if (l->wcb_prio > r->wcb_prio) return (1); /* then module name */ if ((ret = strcmp(l->wcb_modname, r->wcb_modname)) != 0) return (ret); /* and finally insertion order */ if (l->wcb_index < r->wcb_index) return (-1); if (l->wcb_index > r->wcb_index) return (1); mdb_warn("whatis_cbcmp(): can't happen: duplicate indices\n"); return (0); } static void whatis_init(void) { int idx; for (idx = 0; idx < NBUILTINS; idx++) { whatis_cb_start[idx] = &builtin_whatis[idx]; whatis_cb_start[idx]->wcb_index = idx; } whatis_cb_index = idx; whatis_cb = whatis_cb_start; whatis_cb_count = whatis_cb_size = NBUILTINS; qsort(whatis_cb, whatis_cb_count, sizeof (*whatis_cb), whatis_cbcmp); } void mdb_whatis_register(const char *name, mdb_whatis_cb_f *func, void *arg, uint_t prio, uint_t flags) { whatis_callback_t *wcp; if (mdb.m_lmod == NULL) { mdb_warn("mdb_whatis_register(): can only be called during " "module load\n"); return; } if (strbadid(name)) { mdb_warn("mdb_whatis_register(): whatis name '%s' contains " "illegal characters\n"); return; } if ((flags & ~(WHATIS_REG_NO_ID|WHATIS_REG_ID_ONLY)) != 0) { mdb_warn("mdb_whatis_register(): flags (%x) contain unknown " "flags\n", flags); return; } if ((flags & WHATIS_REG_NO_ID) && (flags & WHATIS_REG_ID_ONLY)) { mdb_warn("mdb_whatis_register(): flags (%x) contains both " "NO_ID and ID_ONLY.\n", flags); return; } if (prio > WHATIS_PRIO_MIN) prio = WHATIS_PRIO_MIN; if (whatis_cb == NULL) whatis_init(); wcp = mdb_zalloc(sizeof (*wcp), UM_SLEEP); wcp->wcb_index = whatis_cb_index++; wcp->wcb_prio = prio; wcp->wcb_module = mdb.m_lmod; wcp->wcb_modname = mdb.m_lmod->mod_name; wcp->wcb_name = strdup(name); wcp->wcb_func = func; wcp->wcb_arg = arg; wcp->wcb_flags = flags; /* * See if we need to grow the array; note that at initialization * time, whatis_cb_count is greater than whatis_cb_size; this clues * us in to the fact that the array doesn't need to be freed. */ if (whatis_cb_count == whatis_cb_size) { size_t nsize = MAX(2 * whatis_cb_size, WHATIS_CB_SIZE_MIN); size_t obytes = sizeof (*whatis_cb) * whatis_cb_size; size_t nbytes = sizeof (*whatis_cb) * nsize; whatis_callback_t **narray = mdb_zalloc(nbytes, UM_SLEEP); bcopy(whatis_cb, narray, obytes); if (whatis_cb != whatis_cb_start) mdb_free(whatis_cb, obytes); whatis_cb = narray; whatis_cb_size = nsize; } /* add it into the table and re-sort */ whatis_cb[whatis_cb_count++] = wcp; qsort(whatis_cb, whatis_cb_count, sizeof (*whatis_cb), whatis_cbcmp); } void mdb_whatis_unregister_module(mdb_module_t *mod) { int found = 0; int idx; if (mod == NULL) return; for (idx = 0; idx < whatis_cb_count; idx++) { whatis_callback_t *cur = whatis_cb[idx]; if (cur->wcb_module == mod) { found++; whatis_cb[idx] = NULL; strfree(cur->wcb_name); mdb_free(cur, sizeof (*cur)); } } /* If any were removed, compact the array */ if (found != 0) { qsort(whatis_cb, whatis_cb_count, sizeof (*whatis_cb), whatis_cbcmp); whatis_cb_count -= found; } } int cmd_whatis(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_whatis_t w; size_t idx; int ret; int keep = 0; int list = 0; if (flags & DCMD_PIPE_OUT) { mdb_warn("whatis: cannot be output into a pipe\n"); return (DCMD_ERR); } if (mdb.m_lmod != NULL) { mdb_warn("whatis: cannot be called during module load\n"); return (DCMD_ERR); } if (whatis_cb == NULL) whatis_init(); bzero(&w, sizeof (w)); w.w_magic = WHATIS_MAGIC; whatis_debug = 0; if (mdb_getopts(argc, argv, 'D', MDB_OPT_SETBITS, TRUE, &whatis_debug, /* hidden */ 'b', MDB_OPT_SETBITS, WHATIS_BUFCTL, &w.w_flags, /* hidden */ 'l', MDB_OPT_SETBITS, TRUE, &list, /* hidden */ 'a', MDB_OPT_SETBITS, WHATIS_ALL, &w.w_flags, 'i', MDB_OPT_SETBITS, WHATIS_IDSPACE, &w.w_flags, 'k', MDB_OPT_SETBITS, TRUE, &keep, 'q', MDB_OPT_SETBITS, WHATIS_QUIET, &w.w_flags, 'v', MDB_OPT_SETBITS, WHATIS_VERBOSE, &w.w_flags, NULL) != argc) return (DCMD_USAGE); if (list) { mdb_printf("%%-16s %-12s %4s %?s %?s %8s%", "NAME", "MODULE", "PRIO", "FUNC", "ARG", "FLAGS"); for (idx = 0; idx < whatis_cb_count; idx++) { whatis_callback_t *cur = whatis_cb[idx]; const char *curfl = (cur->wcb_flags & WHATIS_REG_NO_ID) ? "NO_ID" : (cur->wcb_flags & WHATIS_REG_ID_ONLY) ? "ID_ONLY" : "none"; mdb_printf("%-16s %-12s %4d %-?p %-?p %8s\n", cur->wcb_name, cur->wcb_modname, cur->wcb_prio, cur->wcb_func, cur->wcb_arg, curfl); } return (DCMD_OK); } if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); w.w_addrs = &addr; w.w_naddrs = 1; /* If our input is a pipe, try to slurp it all up. */ if (!keep && (flags & DCMD_PIPE)) { mdb_pipe_t p; mdb_get_pipe(&p); if (p.pipe_len != 0) { w.w_addrs = p.pipe_data; w.w_naddrs = p.pipe_len; /* sort the address list */ qsort(w.w_addrs, w.w_naddrs, sizeof (*w.w_addrs), uintptr_cmp); } } w.w_addrfound = mdb_zalloc(w.w_naddrs * sizeof (*w.w_addrfound), UM_SLEEP | UM_GC); if (whatis_debug) { mdb_printf("Searching for:\n"); for (idx = 0; idx < w.w_naddrs; idx++) mdb_printf(" %p", w.w_addrs[idx]); } ret = 0; /* call in to the registered handlers */ for (idx = 0; idx < whatis_cb_count; idx++) { whatis_callback_t *cur = whatis_cb[idx]; /* Honor the ident flags */ if (w.w_flags & WHATIS_IDSPACE) { if (cur->wcb_flags & WHATIS_REG_NO_ID) continue; } else { if (cur->wcb_flags & WHATIS_REG_ID_ONLY) continue; } if (w.w_flags & WHATIS_VERBOSE) mdb_printf("Searching %s`%s...\n", cur->wcb_modname, cur->wcb_name); if (cur->wcb_func(&w, cur->wcb_arg) != 0) ret = 1; /* reset the match state for the next callback */ w.w_match_next = 0; w.w_match_base = 0; w.w_match_size = 0; if (w.w_done) break; } /* Report any unexplained pointers */ for (idx = 0; idx < w.w_naddrs; idx++) { uintptr_t addr = w.w_addrs[idx]; if (w.w_addrfound[idx]) continue; mdb_whatis_report_object(&w, addr, addr, "unknown\n"); } return ((ret != 0) ? DCMD_ERR : DCMD_OK); } void whatis_help(void) { int idx; mdb_printf("%s\n", "Given a virtual address (with -i, an identifier), report where it came\n" "from.\n" "\n" "When fed from a pipeline, ::whatis will not maintain the order the input\n" "comes in; addresses will be reported as it finds them. (-k prevents this;\n" "the output will be in the same order as the input)\n"); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf("%s", " -a Report all information about each address/identifier. The default\n" " behavior is to report only the first (most specific) source for each\n" " address/identifier.\n" " -i addr is an identifier, not a virtual address.\n" " -k Do not re-order the input. (may be slower)\n" " -q Quiet; don't print multi-line reports. (stack traces, etc.)\n" " -v Verbose output; display information about the progress of the search\n"); if (mdb.m_lmod != NULL) return; (void) mdb_dec_indent(2); mdb_printf("\n%SOURCES%\n\n"); (void) mdb_inc_indent(2); mdb_printf("The following information sources will be used:\n\n"); (void) mdb_inc_indent(2); for (idx = 0; idx < whatis_cb_count; idx++) { whatis_callback_t *cur = whatis_cb[idx]; mdb_printf("%s`%s\n", cur->wcb_modname, cur->wcb_name); } (void) mdb_dec_indent(2); }