/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2018 Joyent, Inc. */ /* * The main CPU-control loops, used to control masters and slaves. */ #include #include #include #include #include #include #define KAIF_SLAVE_CMD_SPIN 0 #define KAIF_SLAVE_CMD_SWITCH 1 #define KAIF_SLAVE_CMD_RESUME 2 #define KAIF_SLAVE_CMD_FLUSH 3 #define KAIF_SLAVE_CMD_REBOOT 4 #if defined(__sparc) #define KAIF_SLAVE_CMD_ACK 5 #endif /* * Used to synchronize attempts to set kaif_master_cpuid. kaif_master_cpuid may * be read without kaif_master_lock, and may be written by the current master * CPU. */ int kaif_master_cpuid = KAIF_MASTER_CPUID_UNSET; static uintptr_t kaif_master_lock = 0; /* * Used to ensure that all CPUs leave the debugger together. kaif_loop_lock must * be held to write kaif_looping, but need not be held to read it. */ static volatile uint_t kaif_looping; static uintptr_t kaif_loop_lock; static volatile int kaif_slave_cmd; static volatile int kaif_slave_tgt; /* target cpuid for CMD_SWITCH */ static void kaif_lock_enter(uintptr_t *lock) { while (cas(lock, 0, 1) != 0) continue; membar_producer(); } static void kaif_lock_exit(uintptr_t *lock) { *lock = 0; membar_producer(); } static void kaif_start_slaves(int cmd) { kaif_slave_cmd = cmd; kmdb_kdi_start_slaves(); } static int kaif_master_loop(kaif_cpusave_t *cpusave) { int notflushed, i; #if defined(__sparc) kaif_prom_rearm(); #endif kaif_trap_set_debugger(); /* * If we re-entered due to a ::switch, we need to tell the slave CPUs * to sleep again. */ kmdb_kdi_stop_slaves(cpusave->krs_cpu_id, 0); master_loop: switch (kmdb_dpi_reenter()) { case KMDB_DPI_CMD_SWITCH_CPU: /* * We assume that the target CPU is a valid slave. There's no * easy way to complain here, so we'll assume that the caller * has done the proper checking. */ if (kmdb_dpi_switch_target == cpusave->krs_cpu_id) break; kaif_slave_tgt = kaif_master_cpuid = kmdb_dpi_switch_target; cpusave->krs_cpu_state = KAIF_CPU_STATE_SLAVE; membar_producer(); /* * Switch back to the saved trap table before we switch CPUs -- * we need to make sure that only one CPU is on the debugger's * table at a time. */ kaif_trap_set_saved(cpusave); kaif_start_slaves(KAIF_SLAVE_CMD_SWITCH); /* The new master is now awake */ return (KAIF_CPU_CMD_SWITCH); case KMDB_DPI_CMD_RESUME_ALL: case KMDB_DPI_CMD_RESUME_UNLOAD: /* * Resume everyone, clean up for next entry. */ kaif_master_cpuid = KAIF_MASTER_CPUID_UNSET; membar_producer(); kaif_start_slaves(KAIF_SLAVE_CMD_RESUME); if (kmdb_dpi_work_required()) kmdb_dpi_wrintr_fire(); kaif_trap_set_saved(cpusave); return (KAIF_CPU_CMD_RESUME); case KMDB_DPI_CMD_RESUME_MASTER: /* * Single-CPU resume, which is performed on the debugger's * trap table (so no need to switch back). */ return (KAIF_CPU_CMD_RESUME_MASTER); case KMDB_DPI_CMD_FLUSH_CACHES: kaif_start_slaves(KAIF_SLAVE_CMD_FLUSH); /* * Wait for the other cpus to finish flushing their caches. */ do { notflushed = 0; for (i = 0; i < kaif_ncpusave; i++) { kaif_cpusave_t *save = &kaif_cpusave[i]; if (save->krs_cpu_state == KAIF_CPU_STATE_SLAVE && !save->krs_cpu_flushed) { notflushed++; break; } } } while (notflushed > 0); kaif_slave_cmd = KAIF_SLAVE_CMD_SPIN; break; #if defined(__i386) || defined(__amd64) case KMDB_DPI_CMD_REBOOT: /* * Reboot must be initiated by CPU 0. I could ask why, but I'm * afraid that I don't want to know the answer. */ if (cpusave->krs_cpu_id == 0) kmdb_kdi_reboot(); kaif_start_slaves(KAIF_SLAVE_CMD_REBOOT); /* * Spin forever, waiting for CPU 0 (apparently a slave) to * reboot the system. */ for (;;) continue; /*NOTREACHED*/ break; #endif } goto master_loop; } static int kaif_slave_loop(kaif_cpusave_t *cpusave) { int slavecmd, rv; #if defined(__sparc) /* * If the user elects to drop to OBP from the debugger, some OBP * implementations will cross-call the slaves. We have to turn * IE back on so we can receive the cross-calls. If we don't, * some OBP implementations will wait forever. */ interrupts_on(); #endif /* Wait for duty to call */ for (;;) { slavecmd = kaif_slave_cmd; if (slavecmd == KAIF_SLAVE_CMD_SWITCH && kaif_slave_tgt == cpusave->krs_cpu_id) { kaif_slave_cmd = KAIF_SLAVE_CMD_SPIN; cpusave->krs_cpu_state = KAIF_CPU_STATE_MASTER; rv = KAIF_CPU_CMD_SWITCH; break; } else if (slavecmd == KAIF_SLAVE_CMD_FLUSH) { kmdb_kdi_flush_caches(); cpusave->krs_cpu_flushed = 1; continue; #if defined(__i386) || defined(__amd64) } else if (slavecmd == KAIF_SLAVE_CMD_REBOOT && cpusave->krs_cpu_id == 0) { rv = 0; kmdb_kdi_reboot(); break; #endif } else if (slavecmd == KAIF_SLAVE_CMD_RESUME) { rv = KAIF_CPU_CMD_RESUME; break; #if defined(__sparc) } else if (slavecmd == KAIF_SLAVE_CMD_ACK) { cpusave->krs_cpu_acked = 1; } else if (cpusave->krs_cpu_acked && slavecmd == KAIF_SLAVE_CMD_SPIN) { cpusave->krs_cpu_acked = 0; #endif } kmdb_kdi_slave_wait(); } #if defined(__sparc) interrupts_off(); #endif return (rv); } static void kaif_select_master(kaif_cpusave_t *cpusave) { kaif_lock_enter(&kaif_master_lock); if (kaif_master_cpuid == KAIF_MASTER_CPUID_UNSET) { /* This is the master. */ kaif_master_cpuid = cpusave->krs_cpu_id; cpusave->krs_cpu_state = KAIF_CPU_STATE_MASTER; kaif_slave_cmd = KAIF_SLAVE_CMD_SPIN; membar_producer(); kmdb_kdi_stop_slaves(cpusave->krs_cpu_id, 1); } else { /* The master was already chosen - go be a slave */ cpusave->krs_cpu_state = KAIF_CPU_STATE_SLAVE; membar_producer(); } kaif_lock_exit(&kaif_master_lock); } int kaif_main_loop(kaif_cpusave_t *cpusave) { int cmd; if (kaif_master_cpuid == KAIF_MASTER_CPUID_UNSET) { /* * Special case: Unload requested before first debugger entry. * Don't stop the world, as there's nothing to clean up that * can't be handled by the running kernel. */ if (!kmdb_dpi_resume_requested && kmdb_kdi_get_unload_request()) { cpusave->krs_cpu_state = KAIF_CPU_STATE_NONE; return (KAIF_CPU_CMD_RESUME); } /* * We're a slave with no master, so just resume. This can * happen if, prior to this, two CPUs both raced through * kdi_cmnint() - for example, a breakpoint on a frequently * called function. The loser will be redirected to the slave * loop; note that the event itself is lost at this point. * * The winner will then cross-call that slave, but it won't * actually be received until the slave returns to the kernel * and enables interrupts. We'll then come back in via * kdi_slave_entry() and hit this path. */ if (cpusave->krs_cpu_state == KAIF_CPU_STATE_SLAVE) { cpusave->krs_cpu_state = KAIF_CPU_STATE_NONE; return (KAIF_CPU_CMD_RESUME); } kaif_select_master(cpusave); #ifdef __sparc if (kaif_master_cpuid == cpusave->krs_cpu_id) { /* * Everyone has arrived, so we can disarm the post-PROM * entry point. */ *kaif_promexitarmp = 0; membar_producer(); } #endif } else if (kaif_master_cpuid == cpusave->krs_cpu_id) { cpusave->krs_cpu_state = KAIF_CPU_STATE_MASTER; } else { cpusave->krs_cpu_state = KAIF_CPU_STATE_SLAVE; } cpusave->krs_cpu_flushed = 0; kaif_lock_enter(&kaif_loop_lock); kaif_looping++; kaif_lock_exit(&kaif_loop_lock); /* * We know who the master and slaves are, so now they can go off * to their respective loops. */ do { if (kaif_master_cpuid == cpusave->krs_cpu_id) cmd = kaif_master_loop(cpusave); else cmd = kaif_slave_loop(cpusave); } while (cmd == KAIF_CPU_CMD_SWITCH); kaif_lock_enter(&kaif_loop_lock); kaif_looping--; kaif_lock_exit(&kaif_loop_lock); cpusave->krs_cpu_state = KAIF_CPU_STATE_NONE; if (cmd == KAIF_CPU_CMD_RESUME) { /* * By this point, the master has directed the slaves to resume, * and everyone is making their way to this point. We're going * to block here until all CPUs leave the master and slave * loops. When all have arrived, we'll turn them all loose. * This barrier is required for two reasons: * * 1. There exists a race condition whereby a CPU could reenter * the debugger while another CPU is still in the slave loop * from this debugger entry. This usually happens when the * current master releases the slaves, and makes it back to * the world before the slaves notice the release. The * former master then triggers a debugger entry, and attempts * to stop the slaves for this entry before they've even * resumed from the last one. When the slaves arrive here, * they'll have re-disabled interrupts, and will thus ignore * cross-calls until they finish resuming. * * 2. At the time of this writing, there exists a SPARC bug that * causes an apparently unsolicited interrupt vector trap * from OBP to one of the slaves. This wouldn't normally be * a problem but for the fact that the cross-called CPU * encounters some sort of failure while in OBP. OBP * recovers by executing the debugger-hook word, which sends * the slave back into the debugger, triggering a debugger * fault. This problem seems to only happen during resume, * the result being that all CPUs save for the cross-called * one make it back into the world, while the cross-called * one is stuck at the debugger fault prompt. Leave the * world in that state too long, and you'll get a mondo * timeout panic. If we hold everyone here, we can give the * the user a chance to trigger a panic for further analysis. * To trigger the bug, "pool_unlock:b :c" and "while : ; do * psrset -p ; done". * * When the second item is fixed, the barrier can move into * kaif_select_master(), immediately prior to the setting of * kaif_master_cpuid. */ while (kaif_looping != 0) continue; } return (cmd); } #if defined(__sparc) static int slave_loop_barrier_failures = 0; /* for debug */ /* * There exist a race condition observed by some * platforms where the kmdb master cpu exits to OBP via * prom_enter_mon (e.g. "$q" command) and then later re-enter * kmdb (typing "go") while the slaves are still proceeding * from the OBP idle-loop back to the kmdb slave loop. The * problem arises when the master cpu now back in kmdb proceed * to re-enter OBP (e.g. doing a prom_read() from the kmdb main * loop) while the slaves are still trying to get out of (the * previous trip in) OBP into the safety of the kmdb slave loop. * This routine forces the slaves to explicitly acknowledge * that they are back in the slave loop. The master cpu can * call this routine to ensure that all slave cpus are back * in the slave loop before proceeding. */ void kaif_slave_loop_barrier(void) { extern void kdi_usecwait(clock_t); int i; int not_acked; int timeout_count = 0; kaif_start_slaves(KAIF_SLAVE_CMD_ACK); /* * Wait for slave cpus to explicitly acknowledge * that they are spinning in the slave loop. */ do { not_acked = 0; for (i = 0; i < kaif_ncpusave; i++) { kaif_cpusave_t *save = &kaif_cpusave[i]; if (save->krs_cpu_state == KAIF_CPU_STATE_SLAVE && !save->krs_cpu_acked) { not_acked++; break; } } if (not_acked == 0) break; /* * Play it safe and do a timeout delay. * We will do at most kaif_ncpusave delays before * bailing out of this barrier. */ kdi_usecwait(200); } while (++timeout_count < kaif_ncpusave); if (not_acked > 0) /* * we cannot establish a barrier with all * the slave cpus coming back from OBP * Record this fact for future debugging */ slave_loop_barrier_failures++; kaif_slave_cmd = KAIF_SLAVE_CMD_SPIN; } #endif /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KAIF_START_H #define _KAIF_START_H #include #include #ifdef __cplusplus extern "C" { #endif extern int kaif_main_loop(kaif_cpusave_t *); extern int kaif_master_cpuid; #define KAIF_MASTER_CPUID_UNSET -1 #define KAIF_CPU_CMD_RESUME 0 #define KAIF_CPU_CMD_RESUME_MASTER 1 #define KAIF_CPU_CMD_SWITCH 2 #ifdef __cplusplus } #endif #endif /* _KAIF_START_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KCTL_H #define _KCTL_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef enum { KCTL_ST_INACTIVE = 0, /* kmdb is inactive */ KCTL_ST_DSEG_ALLOCED, /* kmdb segment has been allocated */ KCTL_ST_INITIALIZED, /* kmdb_init has been called */ KCTL_ST_KCTL_PREACTIVATED, /* kctl preactivation completed */ KCTL_ST_MOD_NOTIFIERS, /* krtld module notifiers registered */ KCTL_ST_THREAD_STARTED, /* WR queue thread started */ KCTL_ST_DBG_ACTIVATED, /* kmdb activated */ KCTL_ST_ACTIVE, /* kernel is aware of kmdb activation */ KCTL_ST_DEACTIVATING /* debugger is being deactivated */ } kctl_state_t; typedef enum { KCTL_WR_ST_RUN, /* WR queue thread is running */ KCTL_WR_ST_STOP, /* WR queue thread is stopping */ KCTL_WR_ST_STOPPED /* WR queue thread has stopped */ } kctl_wr_state_t; typedef struct kctl { dev_info_t *kctl_drv_dip; /* Driver's device info structure */ size_t kctl_memgoalsz; /* Desired size of debugger memory */ caddr_t kctl_dseg; /* Debugger segment (Oz) address */ size_t kctl_dseg_size; /* Debugger segment (Oz) size */ caddr_t kctl_mrbase; /* Add'l Oz memory range base address */ size_t kctl_mrsize; /* Add'l Oz memory range size */ vnode_t kctl_vp; /* vnode used to allocate dbgr seg */ kctl_state_t kctl_state; /* State of debugger */ uint_t kctl_boot_loaded; /* Set if debugger loaded at boot */ struct bootops *kctl_boot_ops; /* Boot operations (during init only) */ const char *kctl_execname; /* Path of this module */ uint_t kctl_wr_avail; /* Work available on the WR queue */ ksema_t kctl_wr_avail_sem; /* For WR thr: Work avail on WR queue */ kthread_t *kctl_wr_thr; /* Thread that processes WR queue */ kctl_wr_state_t kctl_wr_state; /* State of WR queue thread */ kmutex_t kctl_lock; /* serializes (de)activation */ kcondvar_t kctl_wr_cv; /* WR queue thread completion */ kmutex_t kctl_wr_lock; /* WR queue thread completion */ uint_t kctl_flags; /* KMDB_F_* from kmdb.h */ #ifdef __sparc caddr_t kctl_tba; /* kmdb's native trap table */ #endif } kctl_t; extern kctl_t kctl; struct bootops; extern void kctl_dprintf(const char *, ...); extern void kctl_warn(const char *, ...); extern int kctl_preactivate_isadep(void); extern void kctl_activate_isadep(kdi_debugvec_t *); extern void kctl_depreactivate_isadep(void); extern void kctl_cleanup(void); extern void *kctl_boot_tmpinit(void); extern void kctl_boot_tmpfini(void *); extern void kctl_auxv_init(kmdb_auxv_t *, const char *, const char **, void *); extern void kctl_auxv_init_isadep(kmdb_auxv_t *, void *); extern void kctl_auxv_fini(kmdb_auxv_t *); extern void kctl_auxv_fini_isadep(kmdb_auxv_t *); #ifdef sun4v extern void kctl_auxv_set_promif(kmdb_auxv_t *); extern void kctl_switch_promif(void); #endif extern void kctl_wrintr(void); extern void kctl_wrintr_fire(void); extern void kctl_wr_thr_start(void); extern void kctl_wr_thr_stop(void); extern void kctl_wr_thr_join(void); extern int kctl_mod_decompress(struct modctl *); extern void kctl_mod_loaded(struct modctl *); extern void kctl_mod_changed(uint_t, struct modctl *); extern void kctl_mod_notify_reg(void); extern void kctl_mod_notify_unreg(void); extern void kctl_dmod_init(void); extern void kctl_dmod_fini(void); extern void kctl_dmod_sync(void); extern void kctl_dmod_autoload(const char *); extern void kctl_dmod_unload_all(void); extern void kctl_dmod_path_reset(void); extern int kctl_wr_process(void); extern void kctl_wr_unload(void); extern char *kctl_basename(char *); extern char *kctl_strdup(const char *); extern void kctl_strfree(char *); #if defined(__sparc) extern kthread_t *kctl_curthread_set(kthread_t *); #endif #ifdef __cplusplus } #endif #endif /* _KCTL_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include static uintptr_t kctl_lookup_by_name(char *modname, char *symname) { struct modctl *mctl; Sym *ksym; uintptr_t addr; if ((mctl = mod_hold_by_name(modname)) == NULL) return (0); if ((ksym = kobj_lookup_all(mctl->mod_mp, symname, 1)) == NULL) { mod_release_mod(mctl); return (0); } addr = ksym->st_value; mod_release_mod(mctl); return (addr); } static uintptr_t kctl_boot_lookup_by_name(char *modname, char *symname) { struct modctl *mctl; Sym *ksym; if ((mctl = kobj_boot_mod_lookup(modname)) == NULL) return (0); if ((ksym = kobj_lookup_all(mctl->mod_mp, symname, 1)) == NULL) return (0); return (ksym->st_value); } void kctl_auxv_init(kmdb_auxv_t *kav, const char *cfg, const char **argv, void *romp) { bzero(kav, sizeof (kmdb_auxv_t)); kav->kav_dseg = kctl.kctl_dseg; kav->kav_dseg_size = kctl.kctl_dseg_size; kav->kav_pagesize = PAGESIZE; kav->kav_ncpu = NCPU; kav->kav_kdi = &kobj_kdi; kav->kav_wrintr_fire = kctl_wrintr_fire; kav->kav_config = cfg; kav->kav_argv = argv; kav->kav_modpath = kobj_module_path; kctl_dprintf("kctl_auxv_init: modpath '%s'", kav->kav_modpath); if (kctl.kctl_boot_loaded) { kav->kav_lookup_by_name = kctl_boot_lookup_by_name; kav->kav_flags |= KMDB_AUXV_FL_NOUNLOAD; } else kav->kav_lookup_by_name = kctl_lookup_by_name; if (kctl.kctl_flags & KMDB_F_TRAP_NOSWITCH) kav->kav_flags |= KMDB_AUXV_FL_NOTRPSWTCH; kctl_auxv_init_isadep(kav, romp); /* can modify anything in kav */ } void kctl_auxv_fini(kmdb_auxv_t *kav) { kctl_auxv_fini_isadep(kav); } /* * 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. */ /* * Driver-side functions for loading and unloading dmods. */ #include #include #include #include #include #include #include #include #include #include #include #include struct modctl *kdi_dmods; /* * When a load is attempted, a check is first made of the modules on the * kctl_dmods list. If a module is found, the load will not proceed. * kctl_dmods_lock must be held while traversing kctl_dmods, and while adding * to and subtracting from it. */ static struct modctl kctl_dmods; static kmutex_t kctl_dmods_lock; static kmdb_wr_path_t *kctl_dmod_path; /* * Used to track outstanding driver-initiated load notifications. These * notifications have been allocated by driver, and thus must be freed by the * driver in the event of an emergency unload. If we don't free them free * them ourselves, they'll leak. Granted, the world is probably melting down * at that point, but there's no reason why we shouldn't tidy up the deck * chairs before we go. */ static kmdb_wr_load_t *kctl_dmod_loads; static kmutex_t kctl_dmod_loads_lock; static int kctl_find_module(char *modname, char *fullname, size_t fullnamelen) { intptr_t fd; int i; /* If they gave us an absolute path, we don't need to search */ if (modname[0] == '/') { if (strlen(modname) + 1 > fullnamelen) { cmn_err(CE_WARN, "Can't load dmod %s - name too long", modname); return (0); } if ((fd = kobj_open(modname)) == -1) return (0); kobj_close(fd); (void) strcpy(fullname, modname); return (1); } for (i = 0; kctl_dmod_path->dpth_path[i] != NULL; i++) { const char *path = kctl_dmod_path->dpth_path[i]; if (strlen(path) + 1 + strlen(modname) + 1 > fullnamelen) { kctl_dprintf("Can't load dmod from %s/%s - " "name too long", path, modname); continue; } (void) snprintf(fullname, fullnamelen, "%s/%s", path, modname); if ((fd = kobj_open(fullname)) == -1) continue; kobj_close(fd); kctl_dprintf("kobj_open %s found", fullname); /* Found it */ return (1); } /* No luck */ return (0); } static void kctl_dlr_free(kmdb_wr_load_t *dlr) { if (dlr->dlr_node.wn_flags & WNFLAGS_NOFREE) return; kctl_strfree(dlr->dlr_fname); kmem_free(dlr, sizeof (kmdb_wr_load_t)); } int kctl_dmod_load(kmdb_wr_load_t *dlr) { struct modctl *modp; char modpath[MAXPATHLEN]; const char *modname = kctl_basename(dlr->dlr_fname); int rc; mutex_enter(&kctl_dmods_lock); /* Have we already loaded this dmod? */ for (modp = kctl_dmods.mod_next; modp != &kctl_dmods; modp = modp->mod_next) { if (strcmp(modname, modp->mod_modname) == 0) { mutex_exit(&kctl_dmods_lock); dlr->dlr_errno = EEXIST; return (-1); } } /* * If we find something that looks like a dmod, create a modctl for it, * and add said modctl to our dmods list. This will allow us to drop * the dmods lock, while still preventing duplicate loads. If we aren't * able to actually load the dmod, we can always remove the modctl * later. */ if (!kctl_find_module(dlr->dlr_fname, modpath, sizeof (modpath))) { mutex_exit(&kctl_dmods_lock); dlr->dlr_errno = ENOENT; return (-1); } modp = kobj_zalloc(sizeof (struct modctl), KM_SLEEP); modp->mod_filename = kctl_strdup(modpath); modp->mod_modname = kctl_basename(modp->mod_filename); modp->mod_busy = 1; modp->mod_loadflags |= MOD_NOAUTOUNLOAD | MOD_NONOTIFY; modp->mod_next = &kctl_dmods; modp->mod_prev = kctl_dmods.mod_prev; modp->mod_prev->mod_next = modp; kctl_dmods.mod_prev = modp; mutex_exit(&kctl_dmods_lock); if (kctl.kctl_boot_ops == NULL) rc = kobj_load_module(modp, 0); else rc = kobj_load_primary_module(modp); if (rc != 0) { kctl_warn("failed to load dmod %s", modp->mod_modname); if (kctl.kctl_boot_ops == NULL) mod_release_requisites(modp); mutex_enter(&kctl_dmods_lock); modp->mod_next->mod_prev = modp->mod_prev; modp->mod_prev->mod_next = modp->mod_next; mutex_exit(&kctl_dmods_lock); kctl_strfree(modp->mod_filename); kobj_free(modp, sizeof (struct modctl)); dlr->dlr_errno = EMDB_NOMOD; return (-1); } /* * It worked! If the module has any CTF data, decompress it, and make a * note of the load. */ mutex_enter(&mod_lock); if ((rc = kctl_mod_decompress(modp)) != 0) { kctl_warn("failed to decompress CTF data for dmod %s: %s", modpath, ctf_errmsg(rc)); } mutex_exit(&mod_lock); kctl_dprintf("loaded dmod %s at %p", modpath, modp); modp->mod_ref = 1; modp->mod_loaded = 1; dlr->dlr_modctl = modp; return (0); } /* * Driver-initiated loads. Load the module and announce it to the debugger. */ void kctl_dmod_autoload(const char *fname) { kmdb_wr_load_t *dlr; dlr = kobj_zalloc(sizeof (kmdb_wr_load_t), KM_SLEEP); dlr->dlr_node.wn_task = WNTASK_DMOD_LOAD; dlr->dlr_fname = kctl_strdup(fname); /* * If we're loading at boot, the kmdb_wr_load_t will have been * "allocated" by krtld, and will thus not be under the control of * kmem. We need to ensure that we don't attempt to free it when * we get it back from the debugger. */ if (kctl.kctl_boot_ops != NULL) dlr->dlr_node.wn_flags |= WNFLAGS_NOFREE; if (kctl_dmod_load(dlr) < 0) { kctl_dlr_free(dlr); return; } /* * Add to the list of open driver-initiated loads. We need to track * these so we can free them (and thus avoid leaks) in the event that * the debugger needs to be blown away before it can return them. */ mutex_enter(&kctl_dmod_loads_lock); dlr->dlr_next = kctl_dmod_loads; if (kctl_dmod_loads != NULL) kctl_dmod_loads->dlr_prev = dlr; kctl_dmod_loads = dlr; mutex_exit(&kctl_dmod_loads_lock); kmdb_wr_debugger_notify(dlr); } void kctl_dmod_load_all(void) { /* * The standard list of modules isn't populated until the tail end of * kobj_init(). Prior to that point, the only available list is that of * primaries. We'll use that if the normal list isn't ready yet. */ if (modules.mod_mp == NULL) { /* modules hasn't been initialized yet -- use primaries */ struct modctl_list *ml; for (ml = kobj_linkmaps[KOBJ_LM_PRIMARY]; ml != NULL; ml = ml->modl_next) kctl_dmod_autoload(ml->modl_modp->mod_modname); } else { struct modctl *modp = &modules; do { if (modp->mod_mp != NULL) kctl_dmod_autoload(modp->mod_modname); } while ((modp = modp->mod_next) != &modules); } } void kctl_dmod_load_ack(kmdb_wr_load_t *dlr) { /* Remove from the list of open driver-initiated requests */ mutex_enter(&kctl_dmod_loads_lock); if (dlr->dlr_prev == NULL) kctl_dmod_loads = dlr->dlr_next; else dlr->dlr_prev->dlr_next = dlr->dlr_next; if (dlr->dlr_next != NULL) dlr->dlr_next->dlr_prev = dlr->dlr_prev; mutex_exit(&kctl_dmod_loads_lock); kctl_dlr_free(dlr); } static int kctl_dmod_unload_common(struct modctl *modp) { struct modctl *m; kctl_dprintf("unloading dmod %s", modp->mod_modname); mutex_enter(&kctl_dmods_lock); for (m = kctl_dmods.mod_next; m != &kctl_dmods; m = m->mod_next) { if (m == modp) break; } mutex_exit(&kctl_dmods_lock); if (m != modp) return (ENOENT); /* Found it */ modp->mod_ref = 0; modp->mod_loaded = 0; kobj_unload_module(modp); mod_release_requisites(modp); /* Remove it from our dmods list */ mutex_enter(&kctl_dmods_lock); modp->mod_next->mod_prev = modp->mod_prev; modp->mod_prev->mod_next = modp->mod_next; mutex_exit(&kctl_dmods_lock); kctl_strfree(modp->mod_filename); kmem_free(modp, sizeof (struct modctl)); return (0); } void kctl_dmod_unload(kmdb_wr_unload_t *dur) { int rc; if ((rc = kctl_dmod_unload_common(dur->dur_modctl)) != 0) { cmn_err(CE_WARN, "unexpected dmod unload failure: %d", rc); dur->dur_errno = rc; } } /* * This will be called during shutdown. The debugger has been stopped, we're * off the module notification list, and we've already processed everything in * the driver's work queue. We should have received (and processed) unload * requests for each of the dmods we've loaded. To be safe, however, we'll * double-check. * * If we're doing an emergency shutdown, there may be outstanding * driver-initiated messages that haven't been returned to us. The debugger is * dead, so it's not going to be returning them. We'll leak them unless we * find and free them ourselves. */ void kctl_dmod_unload_all(void) { kmdb_wr_load_t *dlr; struct modctl *modp; while ((modp = kctl_dmods.mod_next) != &kctl_dmods) (void) kctl_dmod_unload_common(modp); while ((dlr = kctl_dmod_loads) != NULL) { kctl_dmod_loads = dlr->dlr_next; kctl_dprintf("freed orphan load notification for %s", dlr->dlr_fname); kctl_dlr_free(dlr); } } kmdb_wr_path_t * kctl_dmod_path_set(kmdb_wr_path_t *pth) { kmdb_wr_path_t *opth; if (kctl.kctl_flags & KMDB_F_DRV_DEBUG) { if (pth != NULL) { int i; kctl_dprintf("changing dmod path to: %p", pth); for (i = 0; pth->dpth_path[i] != NULL; i++) kctl_dprintf(" %s", pth->dpth_path[i]); } else { kctl_dprintf("changing dmod path to NULL"); } } opth = kctl_dmod_path; kctl_dmod_path = pth; return (opth); } void kctl_dmod_path_reset(void) { kmdb_wr_path_t *pth; if ((pth = kctl_dmod_path_set(NULL)) != NULL) { WR_ACK(pth); kmdb_wr_debugger_notify(pth); } } void kctl_dmod_sync(void) { struct modctl *modp; /* * kobj_sync() has no visibility into our dmods, so we need to * explicitly tell krtld to export the portions of our dmods that were * allocated using boot scratch memory. */ for (modp = kctl_dmods.mod_next; modp != &kctl_dmods; modp = modp->mod_next) kobj_export_module(modp->mod_mp); } void kctl_dmod_init(void) { mutex_init(&kctl_dmod_loads_lock, NULL, MUTEX_DRIVER, NULL); mutex_init(&kctl_dmods_lock, NULL, MUTEX_DRIVER, NULL); bzero(&kctl_dmods, sizeof (struct modctl)); kctl_dmods.mod_next = kctl_dmods.mod_prev = &kctl_dmods; kdi_dmods = &kctl_dmods; } void kctl_dmod_fini(void) { mutex_destroy(&kctl_dmods_lock); mutex_destroy(&kctl_dmod_loads_lock); kdi_dmods = NULL; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include /* * The boot printing interfaces don't expose anything that'll allow us * to print multiple arguments at once. That is, they expose printf-like * interfaces, but these interfaces only support one format specifier per * invocation. The routines in this file allow the rest of the driver * to pretend that this limitation doesn't exist. */ #include static void kctl_vprintf(int code, const char *format, va_list ap) { if (kctl.kctl_boot_ops == NULL) { vcmn_err(code, format, ap); } else { char buf[128]; if (code == CE_WARN) BOP_PUTSARG(kctl.kctl_boot_ops, "WARNING: ", NULL); else if (code == CE_NOTE) BOP_PUTSARG(kctl.kctl_boot_ops, "NOTE: ", NULL); (void) vsnprintf(buf, sizeof (buf), format, ap); BOP_PUTSARG(kctl.kctl_boot_ops, "%s\n", buf); } } void kctl_warn(const char *format, ...) { va_list ap; va_start(ap, format); kctl_vprintf(CE_WARN, format, ap); va_end(ap); } void kctl_dprintf(const char *format, ...) { va_list ap; if (!(kctl.kctl_flags & KMDB_F_DRV_DEBUG)) return; va_start(ap, format); kctl_vprintf(CE_NOTE, format, ap); va_end(ap); } /* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include kctl_t kctl; #define KCTL_EXECNAME "/kernel/drv/kmdb" #if defined(_LP64) #define KCTL_MEM_GOALSZ (20 * 1024 * 1024) #else #define KCTL_MEM_GOALSZ (10 * 1024 * 1024) #endif /* * kmdb will call its own copies of the promif routines during * initialization. As these routines are intended to be used when the * world is stopped, they don't attempt to grab the PROM lock. Very * Bad Things could happen if kmdb called a prom routine while someone * else was calling the kernel's copy of another prom routine, so we * grab the PROM lock ourselves before we start initialization. */ #ifdef __sparc #define KCTL_PROM_LOCK promif_preprom() #define KCTL_PROM_UNLOCK promif_postprom() #else #define KCTL_PROM_LOCK #define KCTL_PROM_UNLOCK #endif static int kctl_init(void) { if (kobj_kdi.kdi_version != KDI_VERSION) { kctl_warn("kmdb/kernel version mismatch (expected %d, " "found %d)", KDI_VERSION, kobj_kdi.kdi_version); return (-1); } sema_init(&kctl.kctl_wr_avail_sem, 0, NULL, SEMA_DRIVER, NULL); mutex_init(&kctl.kctl_wr_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&kctl.kctl_wr_cv, NULL, CV_DRIVER, NULL); mutex_init(&kctl.kctl_lock, NULL, MUTEX_DRIVER, NULL); kctl.kctl_execname = KCTL_EXECNAME; /* XXX get from modctl? */ kctl.kctl_state = KCTL_ST_INACTIVE; kctl.kctl_dseg = kctl.kctl_mrbase = NULL; kctl.kctl_dseg_size = kctl.kctl_mrsize = 0; kctl_dmod_init(); return (0); } static void kctl_fini(void) { kctl_dmod_fini(); mutex_destroy(&kctl.kctl_lock); cv_destroy(&kctl.kctl_wr_cv); mutex_destroy(&kctl.kctl_wr_lock); sema_destroy(&kctl.kctl_wr_avail_sem); } static uint_t kctl_set_state(uint_t state) { uint_t ostate = kctl.kctl_state; /* forward progess only, please */ if (state > ostate) { kctl_dprintf("new kctl state: %d", state); kctl.kctl_state = state; } return (ostate); } static int kctl_boot_dseg_alloc(caddr_t dsegaddr, size_t dsegsz) { /* * The Intel boot memory allocator will cleverly map us onto a 4M * page if we request the whole 4M Intel segment at once. This * will break physical memory r/w, so we break the request into * chunks. The allocator isn't smart enough to combine requests, * so it'll give us a bunch of 4k pages. */ while (dsegsz >= 1024*1024) { size_t sz = MIN(dsegsz, 1024*1024); if (BOP_ALLOC(kctl.kctl_boot_ops, dsegaddr, sz, BO_NO_ALIGN) != dsegaddr) return (-1); dsegaddr += sz; dsegsz -= sz; } return (0); } static int kctl_dseg_alloc(caddr_t addr, size_t sz) { ASSERT(((uintptr_t)addr & PAGEOFFSET) == 0); /* make sure there isn't something there already (like kadb) */ if (hat_getpfnum(kas.a_hat, addr) != PFN_INVALID) return (EAGAIN); if (segkmem_xalloc(NULL, addr, sz, VM_NOSLEEP, 0, segkmem_page_create, NULL) == NULL) return (ENOMEM); return (0); } static void kctl_dseg_free(caddr_t addr, size_t sz) { ASSERT(((uintptr_t)addr & PAGEOFFSET) == 0); segkmem_free(NULL, addr, sz); } static void kctl_memavail(void) { size_t needed; caddr_t base; /* * We're now free to allocate the non-fixed portion of the debugger's * memory region. */ needed = P2ROUNDUP(kctl.kctl_memgoalsz <= kctl.kctl_dseg_size ? 0 : kctl.kctl_memgoalsz - kctl.kctl_dseg_size, PAGESIZE); if (needed == 0) return; if ((base = kmem_zalloc(needed, KM_NOSLEEP)) == NULL) { /* * If we're going to wedge the machine during debugger startup, * at least let them know why it's going to wedge. */ cmn_err(CE_WARN, "retrying of kmdb allocation of 0x%lx bytes", (ulong_t)needed); base = kmem_zalloc(needed, KM_SLEEP); } kdi_dvec->dv_memavail(base, needed); kctl.kctl_mrbase = base; kctl.kctl_mrsize = needed; } void kctl_cleanup(void) { uint_t state = kctl_set_state(KCTL_ST_DEACTIVATING); kctl_dprintf("cleaning up from state %d", state); ASSERT(kctl.kctl_boot_loaded == 0); switch (state) { case KCTL_ST_ACTIVE: boothowto &= ~RB_DEBUG; /* XXX there's a race here */ kdi_dvec = NULL; /*FALLTHROUGH*/ case KCTL_ST_DBG_ACTIVATED: KCTL_PROM_LOCK; kmdb_deactivate(); KCTL_PROM_UNLOCK; /*FALLTHROUGH*/ case KCTL_ST_THREAD_STARTED: if (curthread != kctl.kctl_wr_thr) { kctl_wr_thr_stop(); kctl_wr_thr_join(); } /*FALLTHROUGH*/ case KCTL_ST_MOD_NOTIFIERS: kctl_mod_notify_unreg(); /*FALLTHROUGH*/ case KCTL_ST_KCTL_PREACTIVATED: kctl_depreactivate_isadep(); /*FALLTHROUGH*/ case KCTL_ST_INITIALIZED: /* There's no kmdb_fini */ case KCTL_ST_DSEG_ALLOCED: kctl_dseg_free(kctl.kctl_dseg, kctl.kctl_dseg_size); if (kctl.kctl_mrbase != NULL) kmem_free(kctl.kctl_mrbase, kctl.kctl_mrsize); /*FALLTHROUGH*/ } kctl.kctl_state = KCTL_ST_INACTIVE; } static void kctl_startup_modules(void) { struct modctl *modp; /* * Normal module load and unload is now available. Prior to this point, * we could only load modules, and that only when the debugger was being * initialized. * * We'll need to prepare the modules we've already loaded (if any) for * the brave new world in which boot is unmapped. */ kctl_dmod_sync(); /* * Process any outstanding loads or unloads and prepare for automatic * module loading and unloading. */ (void) kctl_wr_process(); kctl_mod_notify_reg(); (void) kctl_set_state(KCTL_ST_MOD_NOTIFIERS); modp = &modules; do { kctl_mod_loaded(modp); } while ((modp = modp->mod_next) != &modules); } static void kctl_startup_thread(void) { /* * Create the worker thread, which will handle future requests from the * debugger. */ kctl_wr_thr_start(); (void) kctl_set_state(KCTL_ST_THREAD_STARTED); } static int kctl_startup_boot(void) { struct modctl_list *lp, **lpp; int rc; if (kctl_wr_process() < 0) { kctl_warn("kmdb: failed to load modules"); return (-1); } mutex_enter(&mod_lock); for (lpp = kobj_linkmaps; *lpp != NULL; lpp++) { for (lp = *lpp; lp != NULL; lp = lp->modl_next) { if ((rc = kctl_mod_decompress(lp->modl_modp)) != 0) { kctl_warn("kmdb: failed to decompress CTF data " "for %s: %s", lp->modl_modp->mod_modname, ctf_errmsg(rc)); } } } mutex_exit(&mod_lock); return (0); } static int kctl_startup_preactivate(void *romp, const char *cfg, const char **argv) { kmdb_auxv_t kav; int rc; kctl_auxv_init(&kav, cfg, argv, romp); KCTL_PROM_LOCK; rc = kmdb_init(kctl.kctl_execname, &kav); KCTL_PROM_UNLOCK; kctl_auxv_fini(&kav); if (rc < 0) return (EMDB_KNOLOAD); (void) kctl_set_state(KCTL_ST_INITIALIZED); if (kctl_preactivate_isadep() != 0) return (EIO); (void) kctl_set_state(KCTL_ST_KCTL_PREACTIVATED); return (0); } static int kctl_startup_activate(uint_t flags) { kdi_debugvec_t *dvec; KCTL_PROM_LOCK; kmdb_activate(&dvec, flags); KCTL_PROM_UNLOCK; (void) kctl_set_state(KCTL_ST_DBG_ACTIVATED); /* * fill in a few remaining debugvec entries. */ dvec->dv_kctl_modavail = kctl_startup_modules; dvec->dv_kctl_thravail = kctl_startup_thread; dvec->dv_kctl_memavail = kctl_memavail; kctl_activate_isadep(dvec); kdi_dvec = dvec; membar_producer(); boothowto |= RB_DEBUG; (void) kctl_set_state(KCTL_ST_ACTIVE); return (0); } static int kctl_state_check(uint_t state, uint_t ok_state) { if (state == ok_state) return (0); if (state == KCTL_ST_INACTIVE) return (EMDB_KINACTIVE); else if (kctl.kctl_state > KCTL_ST_INACTIVE && kctl.kctl_state < KCTL_ST_ACTIVE) return (EMDB_KACTIVATING); else if (kctl.kctl_state == KCTL_ST_ACTIVE) return (EMDB_KACTIVE); else if (kctl.kctl_state == KCTL_ST_DEACTIVATING) return (EMDB_KDEACTIVATING); else return (EINVAL); } int kctl_deactivate(void) { int rc; mutex_enter(&kctl.kctl_lock); if (kctl.kctl_boot_loaded) { rc = EMDB_KNOUNLOAD; goto deactivate_done; } if ((rc = kctl_state_check(kctl.kctl_state, KCTL_ST_ACTIVE)) != 0) goto deactivate_done; kmdb_kdi_set_unload_request(); kmdb_kdi_kmdb_enter(); /* * The debugger will pass the request to the work thread, which will * stop itself. */ kctl_wr_thr_join(); deactivate_done: mutex_exit(&kctl.kctl_lock); return (rc); } /* * Called from krtld, this indicates that the user loaded kmdb at boot. We * track activation states, but we don't attempt to clean up if activation * fails, because boot debugger load failures are fatal. * * Further complicating matters, various kernel routines, such as bcopy and * mutex_enter, assume the presence of some basic state. On SPARC, it's the * presence of a valid curthread pointer. On AMD64, it's a valid curcpu * pointer in GSBASE. We set up temporary versions of these before beginning * activation, and tear them down when we're done. */ int kctl_boot_activate(struct bootops *ops, void *romp, size_t memsz, const char **argv) { void *old; #ifdef __lint { /* * krtld does a name-based symbol lookup to find this routine. It then * casts the address it gets, calling the result. We want to make sure * that the call in krtld stays in sync with the prototype for this * function, so we define a type (kctl_boot_activate_f) that matches the * current prototype. The following assignment ensures that the type * still matches the declaration, with lint as the enforcer. */ kctl_boot_activate_f *kba = kctl_boot_activate; if (kba == NULL) /* Make lint think kba is actually used */ return (0); } #endif old = kctl_boot_tmpinit(); /* Set up temporary state */ ASSERT(ops != NULL); kctl.kctl_boot_ops = ops; /* must be set before kctl_init */ if (kctl_init() < 0) return (-1); kctl.kctl_boot_loaded = 1; kctl_dprintf("beginning kmdb initialization"); if (memsz == 0) memsz = KCTL_MEM_GOALSZ; kctl.kctl_dseg = kdi_segdebugbase; kctl.kctl_dseg_size = memsz > kdi_segdebugsize ? kdi_segdebugsize : memsz; kctl.kctl_memgoalsz = memsz; if (kctl_boot_dseg_alloc(kctl.kctl_dseg, kctl.kctl_dseg_size) < 0) { kctl_warn("kmdb: failed to allocate %lu-byte debugger area at " "%p", kctl.kctl_dseg_size, (void *)kctl.kctl_dseg); return (-1); } (void) kctl_set_state(KCTL_ST_DSEG_ALLOCED); if (kctl_startup_preactivate(romp, NULL, argv) != 0 || kctl_startup_activate(KMDB_ACT_F_BOOT)) { kctl_warn("kmdb: failed to activate"); return (-1); } if (kctl_startup_boot() < 0) return (-1); kctl_dprintf("finished with kmdb initialization"); kctl_boot_tmpfini(old); kctl.kctl_boot_ops = NULL; return (0); } int kctl_modload_activate(size_t memsz, const char *cfg, uint_t flags) { int rc; mutex_enter(&kctl.kctl_lock); if ((rc = kctl_state_check(kctl.kctl_state, KCTL_ST_INACTIVE)) != 0) { if ((flags & KMDB_F_AUTO_ENTRY) && rc == EMDB_KACTIVE) { kmdb_kdi_kmdb_enter(); rc = 0; } mutex_exit(&kctl.kctl_lock); return (rc); } kctl.kctl_flags = flags; if (memsz == 0) memsz = KCTL_MEM_GOALSZ; kctl.kctl_dseg = kdi_segdebugbase; kctl.kctl_dseg_size = memsz > kdi_segdebugsize ? kdi_segdebugsize : memsz; kctl.kctl_memgoalsz = memsz; if ((rc = kctl_dseg_alloc(kctl.kctl_dseg, kctl.kctl_dseg_size)) != 0) goto activate_fail; (void) kctl_set_state(KCTL_ST_DSEG_ALLOCED); if ((rc = kctl_startup_preactivate(NULL, cfg, NULL)) != 0) goto activate_fail; kctl_startup_modules(); kctl_startup_thread(); if ((rc = kctl_startup_activate(0)) != 0) goto activate_fail; kctl_memavail(); /* Must be after kdi_dvec is set */ if (kctl.kctl_flags & KMDB_F_AUTO_ENTRY) kmdb_kdi_kmdb_enter(); mutex_exit(&kctl.kctl_lock); return (0); activate_fail: kctl_cleanup(); mutex_exit(&kctl.kctl_lock); return (rc); } /* * This interface will be called when drv/kmdb loads. When we get the call, one * of two things will have happened: * * 1. The debugger was loaded at boot. We've progressed far enough into boot * as to allow drv/kmdb to be loaded as a non-primary. Invocation of this * interface is the signal to the debugger that it can start allowing things * like dmod loading and automatic CTF decompression - things which require * the system services that have now been started. * * 2. The debugger was loaded after boot. mdb opened /dev/kmdb, causing * drv/kmdb to load, followed by misc/kmdb. Nothing has been set up yet, * so we need to initialize. Activation will occur separately, so we don't * have to worry about that. */ int kctl_attach(dev_info_t *dip) { kctl.kctl_drv_dip = dip; return (0); } int kctl_detach(void) { return (kctl.kctl_state == KCTL_ST_INACTIVE ? 0 : EBUSY); } static struct modlmisc modlmisc = { &mod_miscops, KMDB_VERSION }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modlmisc, NULL }; /* * Invoked only when debugger is loaded via modload - not invoked when debugger * is loaded at boot. kctl_boot_activate needs to call anything (aside from * mod_install) this function does. */ int _init(void) { if (kctl_init() < 0) return (EINVAL); return (mod_install(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { kctl_fini(); return (mod_remove(&modlinkage)); } /* * 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. */ /* * Tracking of kernel module loads and unloads */ #include #include #include #include #include static kobj_notify_list_t kctl_mod_notifiers[] = { { kctl_mod_changed, KOBJ_NOTIFY_MODLOADED }, { kctl_mod_changed, KOBJ_NOTIFY_MODUNLOADING }, { NULL, 0 } }; int kctl_mod_decompress(struct modctl *modp) { ctf_file_t *fp; struct module *mp = modp->mod_mp; int rc; if ((kmdb_kdi_get_flags() & KMDB_KDI_FL_NOCTF) || mp->ctfdata == NULL) return (0); if ((fp = ctf_modopen(mp, &rc)) == NULL) return (rc); ctf_close(fp); return (0); } void kctl_mod_loaded(struct modctl *modp) { int rc; mutex_enter(&mod_lock); if (modp->mod_mp == NULL) { mutex_exit(&mod_lock); return; } if ((rc = kctl_mod_decompress(modp)) != 0) { cmn_err(CE_WARN, "failed to decompress CTF data for %s: %s\n", modp->mod_modname, ctf_errmsg(rc)); } mutex_exit(&mod_lock); if (!(kmdb_kdi_get_flags() & KMDB_KDI_FL_NOMODS)) kctl_dmod_autoload(modp->mod_modname); } /*ARGSUSED*/ void kctl_mod_changed(uint_t why, struct modctl *what) { if (why == KOBJ_NOTIFY_MODLOADED) kctl_mod_loaded(what); } /* * Tell krtld to notify kmdb when modules have been loaded and unloaded */ void kctl_mod_notify_reg(void) { kobj_notify_list_t *kn; for (kn = kctl_mod_notifiers; kn->kn_func != NULL; kn++) (void) kobj_notify_add(kn); } void kctl_mod_notify_unreg(void) { kobj_notify_list_t *kn; for (kn = kctl_mod_notifiers; kn->kn_func != NULL; kn++) (void) kobj_notify_remove(kn); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include char * kctl_basename(char *s) { char *p = strrchr(s, '/'); if (p == NULL) return (s); return (++p); } char * kctl_strdup(const char *s) { char *s1 = kobj_alloc(strlen(s) + 1, KM_SLEEP); (void) strcpy(s1, s); return (s1); } void kctl_strfree(char *s) { kobj_free(s, strlen(s) + 1); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * Implements the kernel side of the debugger/kernel work queue. */ #include #include #include #include #include #include #include #define KCTL_WR_PROCESS_NORMAL (void *)0 #define KCTL_WR_PROCESS_UNLOADING (void *)1 /* * Processes events from the debugger -> driver notification queue. Returns * 1 if the debugger should be awakened after the queue has been processed. */ static int kctl_wr_process_cb(kmdb_wr_t *wn, void *arg) { int unloading = (arg == KCTL_WR_PROCESS_UNLOADING); switch (WR_TASK(wn)) { case WNTASK_DMOD_LOAD: { /* * If this is an ack, then we're getting back a message from a * load we initiated. Free it. If it's not an ack, we process * the message (attempt to load the requested module) and send * an ack back to the debugger. */ kmdb_wr_load_t *dlr = (kmdb_wr_load_t *)wn; if (WR_ISACK(dlr)) { kctl_dprintf("received ack for dmod load of %s", dlr->dlr_fname); kctl_dmod_load_ack(dlr); return (0); } else kctl_dprintf("received dmod load request %s", dlr->dlr_fname); if (unloading) { /* * If the user didn't wait for all dmods to load before * they triggered the debugger unload, we may have some * dmod load requests on the queue in front of the * blizzard of dmod unload requests that the debugger * will generate as part of its unload. The debugger * won't have generated unloads for pending dmods, so * we can safely ignore the load requests. */ kctl_dprintf("skipping load of dmod %s due to " "in-process unload"); } else (void) kctl_dmod_load(dlr); /* dlr will have errno */ WR_ACK(dlr); kmdb_wr_debugger_notify(dlr); return (1); } case WNTASK_DMOD_LOAD_ALL: /* * We don't initiate all-module loads, so this can't be an * ack. We process the load-all, and send the message back * to the driver as an ack. */ ASSERT(!WR_ISACK(wn)); kctl_dprintf("received request to load all dmods"); (void) kctl_dmod_load_all(); WR_ACK(wn); kmdb_wr_debugger_notify(wn); return (1); case WNTASK_DMOD_UNLOAD: { /* * The driver received an unload request. We don't initiate * unloads, so this can't be an ack. We process the unload, * and send the message back to the driver as an ack. */ kmdb_wr_unload_t *dur = (kmdb_wr_unload_t *)wn; ASSERT(!WR_ISACK(dur)); ASSERT(kctl.kctl_boot_ops == NULL); kctl_dprintf("received dmod unload message %s", dur->dur_modname); kctl_dmod_unload(dur); WR_ACK(dur); kmdb_wr_debugger_notify(dur); return (1); } case WNTASK_DMOD_PATH_CHANGE: { /* * We don't initiate path changes, so this can't be an ack. * This request type differs from the others in that we only * return it (as an ack) when we're done with it. We're only * done with it when we receive another one, or when the * debugger is unloading. */ kmdb_wr_path_t *pth = (kmdb_wr_path_t *)wn; kmdb_wr_path_t *opth; ASSERT(!WR_ISACK(pth)); kctl_dprintf("received path change message"); if ((opth = kctl_dmod_path_set(pth)) != NULL) { /* We have an old path request to return */ WR_ACK(opth); kmdb_wr_debugger_notify(opth); /* * The debugger can process the returned path change * request at its leisure */ return (0); } /* Nothing to do */ return (0); } default: cmn_err(CE_WARN, "Received unknown work request %d from kmdb\n", wn->wn_task); /* Drop message */ return (0); } /*NOTREACHED*/ } int kctl_wr_process(void) { return (kmdb_wr_driver_process(kctl_wr_process_cb, KCTL_WR_PROCESS_NORMAL)); } /* * Catches the "work to do" soft interrupt, and passes the notification along * to the worker thread. */ /*ARGSUSED*/ void kctl_wrintr(void) { kctl.kctl_wr_avail = 0; sema_v(&kctl.kctl_wr_avail_sem); } /* * This routine is called by the debugger while the world is resuming. */ void kctl_wrintr_fire(void) { kctl.kctl_wr_avail = 1; kdi_softcall(kctl_wrintr); } /* * Given the possibility of asynchronous unload, the locking semantics are * somewhat tricky. See kctl_main.c */ /*ARGSUSED*/ static void kctl_wr_thread(void *arg) { callb_cpr_t cprinfo; kmutex_t cprlock; mutex_init(&cprlock, NULL, MUTEX_DEFAULT, NULL); CALLB_CPR_INIT(&cprinfo, &cprlock, callb_generic_cpr, "kmdb work"); for (;;) { /* * XXX what should I do here for panic? It'll spin unless I * can figure out a way to park it. Presumably I don't want to * let it exit. */ mutex_enter(&cprlock); CALLB_CPR_SAFE_BEGIN(&cprinfo); mutex_exit(&cprlock); sema_p(&kctl.kctl_wr_avail_sem); mutex_enter(&cprlock); CALLB_CPR_SAFE_END(&cprinfo, &cprlock); mutex_exit(&cprlock); kctl_dprintf("kctl worker thread - waking up"); if (kmdb_kdi_get_unload_request() || kctl.kctl_wr_state != KCTL_WR_ST_RUN) { /* * We've either got a debugger-initiated unload (if * unload_request returned true), or we're stopping due * to an error discovered by the driver (if * kctl_worker_run is no longer non-zero). Start * cleaning up. */ /* * The debugger has already deactivated itself, and will * have dumped a bunch of stuff on the queue. We need * to process it before exiting. */ (void) kmdb_wr_driver_process(kctl_wr_process_cb, KCTL_WR_PROCESS_UNLOADING); break; } /* * A non-zero return means we've passed messages back to the * debugger for processing, so we need to wake the debugger up. */ if (kctl_wr_process() > 0) kmdb_kdi_kmdb_enter(); } /* * NULL out the dmod search path, so we can send the current one back * to the debugger. XXX this should probably be somewhere else. */ kctl_dmod_path_reset(); /* * The debugger will send us unload notifications for each dmod that it * noticed. If, for example, the debugger is unloaded before the first * start, it won't have noticed any of the dmods we loaded. We'll need * to initiate the unloads ourselves. */ kctl_dmod_unload_all(); kctl.kctl_wr_state = KCTL_WR_ST_STOPPED; /* * Must be last, as it concludes by setting state to INACTIVE. The * kctl data structure must not be accessed by this thread after that * point. */ kctl_cleanup(); mutex_enter(&cprlock); CALLB_CPR_EXIT(&cprinfo); mutex_destroy(&cprlock); } void kctl_wr_thr_start(void) { kctl.kctl_wr_avail = 0; kctl.kctl_wr_state = KCTL_WR_ST_RUN; kctl.kctl_wr_thr = thread_create(NULL, 0, kctl_wr_thread, NULL, 0, &p0, TS_RUN, minclsyspri); } void kctl_wr_thr_stop(void) { ASSERT(kctl.kctl_wr_state == KCTL_WR_ST_RUN); kctl.kctl_wr_state = KCTL_WR_ST_STOP; sema_v(&kctl.kctl_wr_avail_sem); } void kctl_wr_thr_join(void) { thread_join(kctl.kctl_wr_thr->t_did); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KCTL_WR_H #define _KCTL_WR_H #include #ifdef __cplusplus extern "C" { #endif extern void kctl_dmod_load_ack(kmdb_wr_load_t *); extern int kctl_dmod_load(kmdb_wr_load_t *); extern void kctl_dmod_load_all(void); extern void kctl_dmod_unload(kmdb_wr_unload_t *); extern kmdb_wr_path_t *kctl_dmod_path_set(kmdb_wr_path_t *); #ifdef __cplusplus } #endif #endif /* _KCTL_WR_H */ /* * 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. */ #ifndef _KMDB_AUXV_H #define _KMDB_AUXV_H /* * The kmdb_auxv is the interface between the driver and the debugger portions * of kmdb. It is used for three purposes: * * 1) To pass system-specific configuration information to the debugger. This * information is used by the debugger to tailor itself to the running * system. * * 2) To pass debugger state information to the driver. * * 3) To configure the DPI. * * We use this somewhat torturous method to initialize and configure kmdb due to * the somewhat unique requirements of kmdb as a pseudo-standalone debugger. * The debugger portion of kmdb is compiled as a standalone, without any * external dependencies. As a result, it cannot communicate directly to the * outside world. Any such communications must be through functions passed to * it by the driver. The auxv provides a means by which these pointers and * other parameters may be passed to the debugger. */ #include #include #include #ifdef sun4v #include #endif #ifdef __cplusplus extern "C" { #endif typedef struct kmdb_auxv_nv { char kanv_name[25]; char kanv_val[50]; } kmdb_auxv_nv_t; #define KMDB_AUXV_FL_NOUNLOAD 0x1 /* don't allow debugger unload */ #define KMDB_AUXV_FL_NOTRPSWTCH 0x2 /* don't switch to kmdb's TBA/IDT */ typedef struct kmdb_auxv { caddr_t kav_dseg; /* Base of segdebug */ size_t kav_dseg_size; /* Size of segdebug */ size_t kav_pagesize; /* Base page size */ int kav_ncpu; /* Maximum number of CPUs */ kdi_t *kav_kdi; /* Ops vector for KDI */ void *kav_romp; /* Opaque PROM handle */ #ifdef __sparc int *kav_promexitarmp; /* PROM exit kmdb entry armer */ caddr_t kav_tba_active; /* Trap table to be used */ caddr_t kav_tba_obp; /* OBP's trap table */ #ifdef sun4v caddr_t kav_tba_kernel; /* Kernel's trap table */ #endif caddr_t kav_tba_native; /* kmdb's trap table */ size_t kav_tba_native_sz; /* kmdb's trap table size */ #endif #if defined(__i386) || defined(__amd64) kmdb_auxv_nv_t *kav_pcache; /* Copies of common props */ int kav_nprops; /* Size of prop cache */ #endif uintptr_t (*kav_lookup_by_name)(char *, char *); /* Live kernel only */ void (*kav_wrintr_fire)(void); /* Send softint to driver */ const char *kav_config; /* State string from MDB */ const char **kav_argv; /* Args from boot line */ uint_t kav_flags; /* KMDB_AUXV_FL_* */ const char *kav_modpath; /* kernel module_path */ #ifdef __sparc void (*kav_ktrap_install)(int, void (*)(void)); /* Add to krnl trptbl */ void (*kav_ktrap_restore)(void); /* Restore krnl trap hdlrs */ #ifdef sun4v uint_t kav_domaining; /* Domaining status */ caddr_t kav_promif_root; /* PROM shadow tree root */ ihandle_t kav_promif_in; /* PROM input dev instance */ ihandle_t kav_promif_out; /* PROM output dev instance */ phandle_t kav_promif_pin; /* PROM input dev package */ phandle_t kav_promif_pout; /* PROM output dev package */ pnode_t kav_promif_chosennode; /* PROM "/chosen" node */ pnode_t kav_promif_optionsnode; /* PROM "/options" node */ #endif #endif } kmdb_auxv_t; #ifdef __cplusplus } #endif #endif /* _KMDB_AUXV_H */ /* * 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. */ #include #include #include #include #include #include #include static const char _mdb_version[] = KMDB_VERSION; const char * mdb_conf_version(void) { return (_mdb_version); } const char * mdb_conf_isa(void) { #if defined(__sparc) #if defined(__sparcv9) return ("sparcv9"); #else /* __sparcv9 */ return ("sparc"); #endif /* __sparcv9 */ #elif defined(__amd64) return ("amd64"); #elif defined(__i386) return ("i386"); #else #error "unknown ISA" #endif } /* * These functions are needed for path evaluation, and must be run prior to * target initialization. The kmdb symbol resolution machinery hasn't been * initialized at this point, so we have to rely on the kernel to look up * utsname and platform for us. */ void mdb_conf_uname(struct utsname *utsp) { bzero(utsp, sizeof (struct utsname)); if (kmdb_dpi_get_state(NULL) == DPI_STATE_INIT) { struct utsname *utsaddr; /* * The kernel is running during DPI initialization, so we'll ask * it to do the lookup. Our own symbol resolution facilities * won't be available until after the debugger starts. */ if ((utsaddr = (struct utsname *)kmdb_kdi_lookup_by_name("unix", "utsname")) == NULL) { warn("'utsname' symbol is missing from kernel\n"); (void) strcpy(utsp->sysname, "unknown"); return; } bcopy(utsaddr, utsp, sizeof (struct utsname)); } else (void) mdb_tgt_uname(mdb.m_target, utsp); } const char * mdb_conf_platform(void) { if (kmdb_dpi_get_state(NULL) == DPI_STATE_INIT) { static char plat[SYS_NMLN]; caddr_t plataddr; /* * The kernel is running during DPI initialization, so we'll ask * it to do the lookup. Our own symbol resolution facilities * won't be available until after the debugger starts. */ if ((plataddr = (caddr_t)kmdb_kdi_lookup_by_name("unix", "platform")) == NULL) { warn("conf: 'platform' symbol is missing from " "kernel\n"); return ("unknown"); } (void) strncpy(plat, plataddr, sizeof (plat)); plat[sizeof (plat) - 1] = '\0'; return (plat); } else return (mdb_tgt_platform(mdb.m_target)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Debugger co-routine context support. kmdb co-routines are essentially the * same as the ones used by mdb, with the exception that we allocate the stack * for the co-routine from our heap. */ #include #include #include #include #include #include #include #include #include static void context_init(mdb_context_t *volatile c) { c->ctx_status = c->ctx_func(); ASSERT(c->ctx_resumes > 0); longjmp(c->ctx_pcb, 1); } mdb_context_t * mdb_context_create(int (*func)(void)) { mdb_context_t *c = mdb_zalloc(sizeof (mdb_context_t), UM_NOSLEEP); size_t pagesize = mdb.m_pagesize; if (c == NULL) return (NULL); c->ctx_func = func; c->ctx_stacksize = pagesize * 4; c->ctx_stack = mdb_alloc_align(c->ctx_stacksize, pagesize, UM_NOSLEEP); if (c->ctx_stack == NULL) { mdb_free(c, sizeof (mdb_context_t)); return (NULL); } kmdb_makecontext(&c->ctx_uc, (void (*)(void *))context_init, c, c->ctx_stack, c->ctx_stacksize); return (c); } void mdb_context_destroy(mdb_context_t *c) { mdb_free_align(c->ctx_stack, c->ctx_stacksize); mdb_free(c, sizeof (mdb_context_t)); } void mdb_context_switch(mdb_context_t *c) { if (setjmp(c->ctx_pcb) == 0 && kmdb_setcontext(&c->ctx_uc) == -1) fail("failed to change context to %p", (void *)c); else fail("unexpectedly returned from context %p", (void *)c); } jmp_buf * mdb_context_getpcb(mdb_context_t *c) { c->ctx_resumes++; return (&c->ctx_pcb); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_CONTEXT_IMPL_H #define _KMDB_CONTEXT_IMPL_H #include #include #include #ifdef __cplusplus extern "C" { #endif void kmdb_makecontext(ucontext_t *, void (*)(void *), void *, caddr_t, size_t); int kmdb_setcontext(ucontext_t *); #ifdef __cplusplus } #endif #endif /* _KMDB_CONTEXT_IMPL_H */ /* * 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. */ #include void mdb_create_builtin_tgts(void) { mdb_module_t *mp; if ((mp = mdb_module_load_builtin("kmdb_kvm")) != NULL) mp->mod_tgt_ctor = kmdb_kvm_create; } void mdb_create_loadable_disasms(void) { } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2015, Joyent, Inc. */ /* * libctf open/close interposition layer * * This interposition layer serves two purposes. First, it allows the * introduction of a kmdb-specific implementation of ctf_open(). The normal * ctf_open() call open(2)s a file, and reads the CTF data directly from it. * No such facility is available in kmdb, as kmdb does not run in userland. We * can, however, observe that kmdb uses this interface only to read CTF data * from dmods. dmods, as viewed by kmdb, do have CTF data, but they have it * in a form that is best accessed by ctf_bufopen(). This interposition layer * allows us to translate ctf_open() calls into ctf_bufopen() calls. * * The second purpose of the interposition layer is to reduce the work needed * to call mdb_ctf_bufopen. */ #include #include #include #include #include #include #include #include #include #include static kmdb_modctl_t * mdb_ctf_name2ctl(const char *pathname) { mdb_var_t *v; if ((v = mdb_nv_lookup(&mdb.m_dmodctl, strbasename(pathname))) == NULL) return (NULL); return ((kmdb_modctl_t *)MDB_NV_COOKIE(v)); } ctf_file_t * mdb_ctf_open(const char *pathname, int *errp) { struct module *mp; kmdb_modctl_t *kmc; ctf_file_t *ctfp; if ((kmc = mdb_ctf_name2ctl(pathname)) == NULL) { if (errp != NULL) *errp = ENOENT; return (NULL); } mp = kmc->kmc_modctl->mod_mp; if (mp->ctfdata == NULL) { if (errp != NULL) *errp = ECTF_NOCTFDATA; return (NULL); } if ((ctfp = mdb_ctf_bufopen(mp->ctfdata, mp->ctfsize, mp->symtbl, mp->symhdr, mp->strings, mp->strhdr, errp)) == NULL) return (NULL); mdb_dprintf(MDB_DBG_MODULE, "loaded %lu bytes of CTF data for %s\n", (ulong_t)mp->ctfsize, kmc->kmc_modname); return (ctfp); } void mdb_ctf_close(ctf_file_t *fp) { ctf_close(fp); } /*ARGSUSED*/ int mdb_ctf_write(const char *file, ctf_file_t *fp) { return (ENOTSUP); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2017 Jason King. */ #include #include #include /*ARGSUSED*/ mdb_demangler_t * mdb_dem_load(void) { (void) set_errno(ENOTSUP); return (NULL); } void mdb_dem_unload(mdb_demangler_t *dmp) { if (dmp != NULL) fail("attempted to unload demangler %p\n", (void *)dmp); } /*ARGSUSED*/ const char * mdb_dem_convert(mdb_demangler_t *dmp, const char *name) { return (name); } /*ARGSUSED*/ int cmd_demangle(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_warn("C++ symbol demangling not available\n"); return (DCMD_ERR); } /*ARGSUSED*/ int cmd_demflags(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_warn("C++ demangling facility is currently disabled\n"); return (DCMD_ERR); } /*ARGSUSED*/ int cmd_demstr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_warn("C++ symbol demangling not available\n"); return (DCMD_ERR); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include /* * kmdb libdl-style interface for the manipulation of dmods. */ static char *dl_errstr; static kmdb_modctl_t * dl_name2ctl(const char *pathname) { mdb_var_t *v; if ((v = mdb_nv_lookup(&mdb.m_dmodctl, strbasename(pathname))) == NULL) return (NULL); return ((kmdb_modctl_t *)MDB_NV_COOKIE(v)); } /*ARGSUSED*/ void * dlmopen(Lmid_t lmid, const char *pathname, int mode) { kmdb_modctl_t *kmc; if ((kmc = dl_name2ctl(pathname)) == NULL) { dl_errstr = "unregistered module"; return (NULL); } kmc->kmc_dlrefcnt++; dl_errstr = NULL; return (kmc); } int dlclose(void *dlp) { kmdb_modctl_t *kmc = dlp; dl_errstr = NULL; ASSERT(kmc->kmc_dlrefcnt > 0); if (--kmc->kmc_dlrefcnt > 0) return (0); return (0); } char * dlerror(void) { char *str = dl_errstr; dl_errstr = NULL; return (str); } static void * dl_findsym(kmdb_modctl_t *kmc, const char *name) { GElf_Sym sym; uint_t symid; if (mdb_gelf_symtab_lookup_by_name(kmc->kmc_symtab, name, &sym, &symid) < 0) return (NULL); return ((void *)(uintptr_t)sym.st_value); } /*ARGSUSED*/ void * dlsym(void *dlp, const char *name) { kmdb_modctl_t *kmc = dlp; mdb_var_t *v; void *addr; switch ((uintptr_t)dlp) { case (uintptr_t)RTLD_NEXT: mdb_nv_rewind(&mdb.m_dmodctl); while ((v = mdb_nv_advance(&mdb.m_dmodctl)) != NULL) { if ((addr = dl_findsym(MDB_NV_COOKIE(v), name)) != NULL) break; } break; case (uintptr_t)RTLD_DEFAULT: case (uintptr_t)RTLD_SELF: dl_errstr = "invalid handle"; return (NULL); default: addr = dl_findsym(kmc, name); } dl_errstr = (addr == NULL) ? "symbol not found" : NULL; return (addr); } #pragma weak _dladdr1 = dladdr1 /*ARGSUSED*/ int dladdr1(void *address, Dl_info *dlip, void **info, int flags) { /* * umem uses this for debugging information. We'll pretend to fail. */ return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * The DPI, or debugger/PROM interface, is used to isolate the debugger from the * means by which we use the PROM to control the machine. */ #include #include #include #include #include #include #include #include #include #include #include #include #include jmp_buf *kmdb_dpi_fault_pcb; jmp_buf kmdb_dpi_resume_pcb; jmp_buf kmdb_dpi_entry_pcb; static int kmdb_dpi_state; static int kmdb_dpi_state_why; uint_t kmdb_dpi_resume_requested; uint_t kmdb_dpi_switch_target = (uint_t)-1; /* Used by the style-specific resume interfaces to signal the driver */ void (*kmdb_dpi_wrintr_fire)(void); int kmdb_dpi_init(kmdb_auxv_t *kav) { kmdb_dpi_state = DPI_STATE_INIT; kmdb_dpi_resume_requested = 0; kmdb_dpi_wrintr_fire = kav->kav_wrintr_fire; mdb.m_dpi = &kmdb_dpi_ops; return (mdb.m_dpi->dpo_init(kav)); } /*ARGSUSED1*/ void kmdb_activate(kdi_debugvec_t **dvecp, uint_t flags) { mdb.m_dpi->dpo_debugger_activate(dvecp, flags); } void kmdb_deactivate(void) { mdb.m_dpi->dpo_debugger_deactivate(); } int kmdb_dpi_reenter(void) { int cmd; kmdb_kdi_system_claim(); if ((cmd = setjmp(kmdb_dpi_entry_pcb)) == 0) { /* Direct entry from the driver */ if (kmdb_dpi_resume_requested) longjmp(kmdb_dpi_resume_pcb, 1); kmdb_first_start(); fail("kmdb_first_start returned"); /*NOTREACHED*/ } mdb_dprintf(MDB_DBG_DPI, "returning to driver - cmd %d%s\n", cmd, (kmdb_dpi_work_required() ? " (work required)" : "")); kmdb_kdi_system_release(); membar_producer(); /* * The debugger wants us to do something - it returned a command * via the setjmp(). The driver will know what to do with the * command. */ return (cmd); } void kmdb_dpi_enter_mon(void) { mdb.m_dpi->dpo_enter_mon(); } void kmdb_dpi_modchg_register(void (*func)(struct modctl *, int)) { mdb.m_dpi->dpo_modchg_register(func); } void kmdb_dpi_modchg_cancel(void) { mdb.m_dpi->dpo_modchg_cancel(); } int kmdb_dpi_get_cpu_state(int cpuid) { return (mdb.m_dpi->dpo_get_cpu_state(cpuid)); } int kmdb_dpi_get_master_cpuid(void) { return (mdb.m_dpi->dpo_get_master_cpuid()); } const mdb_tgt_gregset_t * kmdb_dpi_get_gregs(int cpuid) { return (mdb.m_dpi->dpo_get_gregs(cpuid)); } jmp_buf * kmdb_dpi_set_fault_hdlr(jmp_buf *jb) { jmp_buf *oldpcb = kmdb_dpi_fault_pcb; kmdb_dpi_fault_pcb = jb; return (oldpcb); } void kmdb_dpi_restore_fault_hdlr(jmp_buf *jb) { (void) kmdb_dpi_set_fault_hdlr(jb); } /* * Used to tell the driver that it needs to do work after the resume. * * CAUTION: This routine may be called *after* mdb_destroy */ int kmdb_dpi_work_required(void) { return (kmdb_kdi_get_unload_request() || !kmdb_wr_driver_notify_isempty()); } void kmdb_dpi_resume_master(void) { kmdb_dpi_resume_common(KMDB_DPI_CMD_RESUME_MASTER); } void kmdb_dpi_resume(void) { kmdb_dpi_resume_common(KMDB_DPI_CMD_RESUME_ALL); } void kmdb_dpi_resume_unload(void) { kmdb_dpi_resume_common(KMDB_DPI_CMD_RESUME_UNLOAD); } int kmdb_dpi_switch_master(int tgt_cpuid) { if (kmdb_dpi_get_cpu_state(tgt_cpuid) < 0) return (-1); /* errno is set for us */ kmdb_dpi_switch_target = tgt_cpuid; kmdb_dpi_resume_common(KMDB_DPI_CMD_SWITCH_CPU); return (0); } void kmdb_dpi_flush_slave_caches(void) { kmdb_dpi_resume_common(KMDB_DPI_CMD_FLUSH_CACHES); } typedef struct work_results { mdb_nv_t res_loads; mdb_nv_t res_unloads; } work_results_t; static int kmdb_dbgnotify_cb(kmdb_wr_t *wn, void *arg) { work_results_t *res = arg; switch (WR_TASK(wn)) { case WNTASK_DMOD_LOAD: { /* * If this is an ack, the driver finished processing a load we * requested. We process it and free the message. If this * isn't an ack, then it's a driver-initiated load. We process * the message, and send it back as an ack so the driver can * free it. */ kmdb_wr_load_t *dlr = (kmdb_wr_load_t *)wn; mdb_dprintf(MDB_DBG_DPI, "received module load message\n"); if (kmdb_module_loaded(dlr) && res != NULL) { (void) mdb_nv_insert(&res->res_loads, strbasename(dlr->dlr_fname), NULL, 0, 0); } if (WR_ISACK(dlr)) { kmdb_module_load_ack(dlr); return (0); } /* Send it back as an ack */ mdb_dprintf(MDB_DBG_DPI, "Sending load request for %s back " "as an ack\n", dlr->dlr_fname); WR_ACK(wn); kmdb_wr_driver_notify(wn); return (0); } case WNTASK_DMOD_LOAD_ALL: /* * We initiated the load-all, so this must be an ack. The * individual module load messages will arrive separately - * there's no need to do anything further with this message. */ ASSERT(WR_ISACK(wn)); mdb_dprintf(MDB_DBG_DPI, "received module load all ack\n"); kmdb_module_load_all_ack(wn); return (0); case WNTASK_DMOD_UNLOAD: { /* * The debugger received an unload message. The driver isn't * supposed to initiate unloads, so we shouldn't see anything * but acks. We tell the dmod subsystem that the module has * been unloaded, and we free the message. */ kmdb_wr_unload_t *dur = (kmdb_wr_unload_t *)wn; ASSERT(WR_ISACK(dur)); mdb_dprintf(MDB_DBG_DPI, "received module unload ack\n"); if (kmdb_module_unloaded(dur) && res != NULL) { (void) mdb_nv_insert(&res->res_unloads, dur->dur_modname, NULL, 0, 0); } /* Done with message */ kmdb_module_unload_ack(dur); return (0); } case WNTASK_DMOD_PATH_CHANGE: { /* * The debugger received a path change message. The driver * can't initiate these, so it must be an acknowledgement. * There's no processing to be done, so just free the message. */ kmdb_wr_path_t *dpth = (kmdb_wr_path_t *)wn; ASSERT(WR_ISACK(dpth)); mdb_dprintf(MDB_DBG_DPI, "received path change ack\n"); kmdb_module_path_ack(dpth); return (0); } default: mdb_warn("Received unknown message type %d from driver\n", wn->wn_task); /* Ignore it */ return (0); } } static void print_modules(mdb_nv_t *mods) { mdb_var_t *v; mdb_nv_rewind(mods); while ((v = mdb_nv_advance(mods)) != NULL) mdb_printf(" %s", mdb_nv_get_name(v)); } void kmdb_dpi_process_work_queue(void) { work_results_t res; (void) mdb_nv_create(&res.res_loads, UM_SLEEP); (void) mdb_nv_create(&res.res_unloads, UM_SLEEP); mdb_dprintf(MDB_DBG_DPI, "processing work queue\n"); (void) kmdb_wr_debugger_process(kmdb_dbgnotify_cb, &res); if (mdb_nv_size(&res.res_loads)) { mdb_printf("Loaded modules: ["); print_modules(&res.res_loads); mdb_printf(" ]\n"); } if (mdb_nv_size(&res.res_unloads)) { mdb_printf("Unloaded modules: ["); print_modules(&res.res_unloads); mdb_printf(" ]\n"); } mdb_nv_destroy(&res.res_loads); mdb_nv_destroy(&res.res_unloads); } int kmdb_dpi_step(void) { return (mdb.m_dpi->dpo_step()); } uintptr_t kmdb_dpi_call(uintptr_t func, uint_t argc, const uintptr_t *argv) { return (mdb.m_dpi->dpo_call(func, argc, argv)); } int kmdb_dpi_brkpt_arm(uintptr_t addr, mdb_instr_t *instrp) { int rc; if ((rc = mdb.m_dpi->dpo_brkpt_arm(addr, instrp)) < 0) mdb_warn("failed to arm breakpoint at %a", addr); mdb_dprintf(MDB_DBG_DPI, "brkpt armed at %p %A\n", (void *)addr, addr); return (rc); } int kmdb_dpi_brkpt_disarm(uintptr_t addr, mdb_instr_t instrp) { int rc; if ((rc = mdb.m_dpi->dpo_brkpt_disarm(addr, instrp)) < 0) mdb_warn("failed to disarm breakpoint at %a", addr); mdb_dprintf(MDB_DBG_DPI, "brkpt disarmed at %p %A\n", (void *)addr, addr); return (rc); } int kmdb_dpi_wapt_validate(kmdb_wapt_t *wp) { if (mdb.m_dpi->dpo_wapt_validate(wp) < 0) return (-1); /* errno is set for us */ return (0); } int kmdb_dpi_wapt_reserve(kmdb_wapt_t *wp) { if (mdb.m_dpi->dpo_wapt_reserve(wp) < 0) return (-1); /* errno is set for us */ mdb_dprintf(MDB_DBG_DPI, "wapt reserve type %d at %p, priv %p\n", wp->wp_type, (void *)wp->wp_addr, wp->wp_priv); return (0); } void kmdb_dpi_wapt_release(kmdb_wapt_t *wp) { mdb.m_dpi->dpo_wapt_release(wp); } void kmdb_dpi_wapt_arm(kmdb_wapt_t *wp) { mdb.m_dpi->dpo_wapt_arm(wp); mdb_dprintf(MDB_DBG_DPI, "wapt armed at %p (type %d, priv %p)\n", (void *)wp->wp_addr, wp->wp_type, wp->wp_priv); } void kmdb_dpi_wapt_disarm(kmdb_wapt_t *wp) { mdb.m_dpi->dpo_wapt_disarm(wp); mdb_dprintf(MDB_DBG_DPI, "wapt disarmed at %p (type %d, priv %p)\n", (void *)wp->wp_addr, wp->wp_type, wp->wp_priv); } int kmdb_dpi_wapt_match(kmdb_wapt_t *wp) { return (mdb.m_dpi->dpo_wapt_match(wp)); } void kmdb_dpi_set_state(int state, int why) { if (kmdb_dpi_state != DPI_STATE_LOST) { mdb_dprintf(MDB_DBG_DPI, "dpi_set_state %d why %d\n", state, why); kmdb_dpi_state = state; kmdb_dpi_state_why = why; } } int kmdb_dpi_get_state(int *whyp) { if (whyp != NULL) *whyp = kmdb_dpi_state_why; return (kmdb_dpi_state); } void kmdb_dpi_dump_crumbs(uintptr_t addr, int cpuid) { mdb.m_dpi->dpo_dump_crumbs(addr, cpuid); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_DPI_H #define _KMDB_DPI_H /* * Retargetable Kmdb/PROM interface */ #include #include #ifdef __sparc #include #endif /* __sparc */ #include #include #include #include #include /* * The following directive tells the mapfile generator that only those * prototypes and declarations ending with a "Driver OK" comment should be * included in the mapfile. * * MAPFILE: export "Driver OK" */ #ifdef __cplusplus extern "C" { #endif #define DPI_MASTER_CPUID (-1) /* matches CPU ID for master */ #define DPI_ALLOW_FAULTS NULL #define DPI_STATE_INIT 1 /* debugger initializing */ #define DPI_STATE_STOPPED 2 /* User-requested stop (debug_enter) */ #define DPI_STATE_FAULTED 3 /* Breakpoint, watchpoint, etc. */ #define DPI_STATE_LOST 4 /* debugger fault */ #define DPI_STATE_WHY_BKPT 1 #define DPI_STATE_WHY_V_WAPT 2 #define DPI_STATE_WHY_P_WAPT 3 #define DPI_STATE_WHY_TRAP 4 #define DPI_WAPT_TYPE_PHYS 0x1 /* Physical address (SPARC only) */ #define DPI_WAPT_TYPE_VIRT 0x2 /* Virtual address */ #define DPI_WAPT_TYPE_IO 0x4 /* I/O space (Intel only) */ #define DPI_CPU_STATE_NONE 0 #define DPI_CPU_STATE_MASTER 1 #define DPI_CPU_STATE_SLAVE 2 typedef struct dpi_ops dpi_ops_t; typedef struct kmdb_wapt { uintptr_t wp_addr; /* Watchpoint base address */ size_t wp_size; /* Size of watched area, in bytes */ int wp_type; /* DPI_WAPT_TYPE_* */ uint_t wp_wflags; /* access modes */ void *wp_priv; /* DPI-private data */ } kmdb_wapt_t; extern int kmdb_dpi_init(kmdb_auxv_t *); extern void kmdb_dpi_enter_mon(void); extern void kmdb_dpi_modchg_register(void (*)(struct modctl *, int)); extern void kmdb_dpi_modchg_cancel(void); extern int kmdb_dpi_get_cpu_state(int); extern int kmdb_dpi_get_master_cpuid(void); extern const mdb_tgt_gregset_t *kmdb_dpi_get_gregs(int); extern int kmdb_dpi_get_register(const char *, kreg_t *); extern int kmdb_dpi_set_register(const char *, kreg_t); extern jmp_buf *kmdb_dpi_set_fault_hdlr(jmp_buf *); extern void kmdb_dpi_restore_fault_hdlr(jmp_buf *); extern int kmdb_dpi_brkpt_arm(uintptr_t, mdb_instr_t *); extern int kmdb_dpi_brkpt_disarm(uintptr_t, mdb_instr_t); extern int kmdb_dpi_wapt_validate(kmdb_wapt_t *); extern int kmdb_dpi_wapt_reserve(kmdb_wapt_t *); extern void kmdb_dpi_wapt_release(kmdb_wapt_t *); extern void kmdb_dpi_wapt_arm(kmdb_wapt_t *); extern void kmdb_dpi_wapt_disarm(kmdb_wapt_t *); extern int kmdb_dpi_wapt_match(kmdb_wapt_t *); extern void kmdb_dpi_set_state(int, int); extern int kmdb_dpi_get_state(int *); extern int kmdb_dpi_step(void); extern uintptr_t kmdb_dpi_call(uintptr_t, uint_t, const uintptr_t *); extern void kmdb_dpi_process_work_queue(void); extern int kmdb_dpi_work_required(void); /* Driver OK */ extern void kmdb_dpi_flush_slave_caches(void); extern void kmdb_dpi_dump_crumbs(uintptr_t, int); /* * Debugger/Kernel suspend */ extern jmp_buf kmdb_dpi_entry_pcb; extern uint_t kmdb_dpi_resume_requested; /* Driver OK */ extern uint_t kmdb_dpi_switch_target; /* Driver OK */ extern jmp_buf kmdb_dpi_resume_pcb; extern int kmdb_dpi_reenter(void); extern void kmdb_dpi_resume(void); extern void kmdb_dpi_resume_unload(void); extern int kmdb_dpi_switch_master(int); #ifdef __cplusplus } #endif #endif /* _KMDB_DPI_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Joyent, Inc. */ #ifndef _KMDB_DPI_IMPL_H #define _KMDB_DPI_IMPL_H #include #ifdef __sparc #include #endif /* __sparc */ #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif extern jmp_buf *kmdb_dpi_fault_pcb; /* * The routines used by the kmdb side of the DPI to access the saved state * of the current kernel instance, and to control that instance. A populated * version of this vector is provided by the DPI backend used to control the * machine. General use of the kmdb DPI is not via direct invocation of the * functions in this ops vector, but rather flows through the convenience * wrappers in kmdb_dpi.c. */ struct dpi_ops { int (*dpo_init)(kmdb_auxv_t *); void (*dpo_debugger_activate)(kdi_debugvec_t **, uint_t); void (*dpo_debugger_deactivate)(void); void (*dpo_enter_mon)(void); void (*dpo_modchg_register)(void (*)(struct modctl *, int)); void (*dpo_modchg_cancel)(void); int (*dpo_get_cpu_state)(int); int (*dpo_get_master_cpuid)(void); const mdb_tgt_gregset_t *(*dpo_get_gregs)(int); int (*dpo_get_register)(const char *, kreg_t *); int (*dpo_set_register)(const char *, kreg_t); #ifdef __sparc int (*dpo_get_rwin)(int, int, struct rwindow *); int (*dpo_get_nwin)(int); #endif int (*dpo_brkpt_arm)(uintptr_t, mdb_instr_t *); int (*dpo_brkpt_disarm)(uintptr_t, mdb_instr_t); int (*dpo_wapt_validate)(kmdb_wapt_t *); int (*dpo_wapt_reserve)(kmdb_wapt_t *); void (*dpo_wapt_release)(kmdb_wapt_t *); void (*dpo_wapt_arm)(kmdb_wapt_t *); void (*dpo_wapt_disarm)(kmdb_wapt_t *); int (*dpo_wapt_match)(kmdb_wapt_t *); int (*dpo_step)(void); uintptr_t (*dpo_call)(uintptr_t, uint_t, const uintptr_t *); void (*dpo_dump_crumbs)(uintptr_t, int); #ifdef __sparc void (*dpo_kernpanic)(int); #endif }; extern void (*kmdb_dpi_wrintr_fire)(void); extern dpi_ops_t kmdb_dpi_ops; extern void kmdb_dpi_resume_common(int); extern void kmdb_dpi_resume_master(void); /* Used by the debugger to tell the driver how to resume */ #define KMDB_DPI_CMD_RESUME_ALL 1 /* Resume all CPUs */ #define KMDB_DPI_CMD_RESUME_MASTER 2 /* Resume only master CPU */ #define KMDB_DPI_CMD_RESUME_UNLOAD 3 /* Resume for debugger unload */ #define KMDB_DPI_CMD_SWITCH_CPU 4 /* Switch to another CPU */ #define KMDB_DPI_CMD_FLUSH_CACHES 5 /* Flush slave caches */ #define KMDB_DPI_CMD_REBOOT 6 /* Reboot the machine */ #ifdef __cplusplus } #endif #endif /* _KMDB_DPI_IMPL_H */ /* * 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. * Copyright 2016 Joyent, Inc. */ /* * Handling of unintentional faults (i.e. bugs) in the debugger. */ #include #include #include #include #include #include #include #include #include void kmdb_fault(kreg_t tt, kreg_t pc, kreg_t sp, int cpuid) { int debug_self_confirm = 0; volatile int try; jmp_buf pcb, *old; char c; /* Make absolutely sure */ kmdb_kdi_system_claim(); try = 1; if (setjmp(pcb) != 0) { if (++try == 2) { mdb_iob_printf(mdb.m_err, "\n*** First stack trace attempt failed. " "Trying safe mode.\n\n"); kmdb_fault_display(tt, pc, sp, 1); } else { mdb_iob_printf(mdb.m_err, "\n*** Unable to print stack trace.\n"); } } else { old = kmdb_dpi_set_fault_hdlr(&pcb); mdb_iob_printf(mdb.m_err, "\n*** Debugger Fault (CPU %d)\n\n", cpuid); kmdb_fault_display(tt, pc, sp, 0); } kmdb_dpi_restore_fault_hdlr(old); if (mdb.m_term != NULL) { for (;;) { mdb_iob_printf(mdb.m_err, "\n%s: " #if defined(__sparc) #ifndef sun4v "(o)bp, " #endif /* sun4v */ "(p)anic" #else "reboo(t)" #endif ", or (d)ebug with self? ", mdb.m_pname); mdb_iob_flush(mdb.m_err); if (IOP_READ(mdb.m_term, &c, sizeof (c)) != sizeof (c)) goto fault_obp; mdb_iob_printf(mdb.m_err, "\n"); switch (c) { #ifdef __sparc case 'p': kmdb_dpi_kernpanic(cpuid); /*NOTREACHED*/ continue; #endif #ifndef sun4v case 'o': case 'O': #endif /* sun4v */ case 't': case 'T': kmdb_dpi_enter_mon(); continue; case 'd': case 'D': /* * Debug self - get confirmation, because they * can't go back to their running system if * they choose this one. */ if (debug_self_confirm == 0) { mdb_iob_printf(mdb.m_err, "NOTE: You will not be able to " "resume your system if you " "choose this option.\nPlease " "select 'd' again to confirm.\n"); debug_self_confirm = 1; continue; } kmdb_dpi_set_state(DPI_STATE_LOST, 0); return; } } } fault_obp: exit(1); /*NOTREACHED*/ } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_FAULT_H #define _KMDB_FAULT_H #include #ifdef __cplusplus extern "C" { #endif extern void kmdb_fault(kreg_t, kreg_t, kreg_t, int); extern void kmdb_fault_display(kreg_t, kreg_t, kreg_t, int); extern void kmdb_print_stack(void); #ifdef __cplusplus } #endif #endif /* _KMDB_FAULT_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kmdb version of the File Descriptor I/O Backend * * We don't have any files to open, so this is just a stub. */ #include #include #include #include #include /*ARGSUSED*/ mdb_io_t * mdb_fdio_create_path(const char *path[], const char *fname, int flags, mode_t mode) { (void) set_errno(ENOENT); return (NULL); } /* * 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. */ #ifndef _KMDB_IO_H #define _KMDB_IO_H /* * KMDB-specific I/O functions */ #ifdef __cplusplus extern "C" { #endif extern mdb_io_t *kmdb_promio_create(char *); extern char kmdb_getchar(void); #ifdef __cplusplus } #endif #endif /* _KMDB_IO_H */ /* * 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. */ #ifndef _KMDB_KCTL_H #define _KMDB_KCTL_H /* * Interfaces used by the driver and mdb to interact with kmdb, and vice versa */ #include #include #include #ifdef __cplusplus extern "C" { #endif /* * Interfaces used by mdb to control kmdb */ #define KMDB_IOC (0xDB << 16) #define KMDB_IOC_START (KMDB_IOC|1) #define KMDB_IOC_STOP (KMDB_IOC|2) #define KMDB_ACT_F_BOOT 0x1 /* activated during boot */ extern int kmdb_init(const char *, kmdb_auxv_t *); /* * This function should only be defined for sun4v. However the mdb build * uses a custom tool (hdr2map) to generate mapfile from header files but * this tool does not take care of preprocessor directives and functions * are included into the mapfile whatever the architecture is and even * if there is an #ifdef sun4v. So we always declare this function but it * has a fake definition for all architecture but sun4v. */ extern void kmdb_init_promif(char *, kmdb_auxv_t *); extern void kmdb_activate(kdi_debugvec_t **, uint_t); extern void kmdb_deactivate(void); #ifdef __cplusplus } #endif #endif /* _KMDB_KCTL_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * The KDI, or kernel/debugger interface, is used to allow the kernel and the * debugger to communicate. These communications take two forms: * * 1. kernel to debugger. Interfaces of this type are used by the kernel to * inform the debugger of changes in the state of the system that need to * be noted by the debugger. For example, the kernel uses one of these * interfaces to tell debugger that the set of currently-loaded modules * has changed. * * 2. debugger to kernel. Interfaces of this type are used by the debugger * to extract information from the kernel that would otherwise be difficult * to get, or to perform services that are specific to the machine being * used. An example of the former is the module iterator, which is needed * to allow symbol resolution, but which needs to resolve symbols prior * to the iteration. The latter class include machine-specific or * cpu-type-specific functions, such as the I-cache flusher. By directly * using the kernel versions of these functions, we avoid the need to * include multiple versions of each function - one per cpu and/or machine - * in kmdb. */ #include #include #include #include #include #include #include #include static int kdi_unload_request; typedef struct mod_interp_data { int (*mid_usercb)(struct modctl *, void *); void *mid_userarg; jmp_buf mid_pcb; jmp_buf *mid_oldpcb; } mod_interp_data_t; static kmdb_auxv_t *kdi_auxv; int kmdb_kdi_mods_changed(void) { return (mdb.m_kdi->kdi_mods_changed()); } static int kmdb_kdi_mod_interp(struct modctl *mp, void *arg) { mod_interp_data_t *mid = arg; int rc; kmdb_dpi_restore_fault_hdlr(mid->mid_oldpcb); rc = mid->mid_usercb(mp, mid->mid_userarg); mid->mid_oldpcb = kmdb_dpi_set_fault_hdlr(&mid->mid_pcb); return (rc); } /* * We need to protect ourselves against any problems that may occur while * executing the module iterator, currently located in krtld. If, for * example, one of the next pointers in the module list points to an invalid * address, we don't want kmdb to explode. As such, we protect ourselves * with the DPI fault-protection routines. We don't want our fault-protection * callback to protect the callback that the kmdb consumer provided, so we * provide our own interposition callback that removes our fault-protector * before invoking the user's callback. */ int kmdb_kdi_mod_iter(int (*cb)(struct modctl *, void *), void *arg) { mod_interp_data_t mid; int rc; if (setjmp(mid.mid_pcb) != 0) { /* We took a fault while iterating through the modules */ kmdb_dpi_restore_fault_hdlr(mid.mid_oldpcb); return (-1); } mid.mid_usercb = cb; mid.mid_userarg = arg; mid.mid_oldpcb = kmdb_dpi_set_fault_hdlr(&mid.mid_pcb); rc = mdb.m_kdi->kdi_mod_iter(kmdb_kdi_mod_interp, &mid); kmdb_dpi_restore_fault_hdlr(mid.mid_oldpcb); return (rc); } int kmdb_kdi_mod_isloaded(struct modctl *modp) { return (mdb.m_kdi->kdi_mod_isloaded(modp)); } int kmdb_kdi_mod_haschanged(struct modctl *mc1, struct module *mp1, struct modctl *mc2, struct module *mp2) { return (mdb.m_kdi->kdi_mod_haschanged(mc1, mp1, mc2, mp2)); } static ssize_t kdi_prw(void *buf, size_t nbytes, physaddr_t addr, int (*rw)(caddr_t, size_t, physaddr_t, size_t *)) { size_t sz; int rc; kmdb_dpi_flush_slave_caches(); if ((rc = rw(buf, nbytes, addr, &sz)) != 0) return (set_errno(rc)); return (sz); } ssize_t kmdb_kdi_pread(void *buf, size_t nbytes, physaddr_t addr) { return (kdi_prw(buf, nbytes, addr, mdb.m_kdi->kdi_pread)); } ssize_t kmdb_kdi_pwrite(void *buf, size_t nbytes, physaddr_t addr) { return (kdi_prw(buf, nbytes, addr, mdb.m_kdi->kdi_pwrite)); } void kmdb_kdi_flush_caches(void) { mdb.m_kdi->kdi_flush_caches(); } int kmdb_kdi_get_unload_request(void) { return (kdi_unload_request); } void kmdb_kdi_set_unload_request(void) { kdi_unload_request = 1; } int kmdb_kdi_get_flags(void) { uint_t flags = 0; if (mdb.m_flags & MDB_FL_NOCTF) flags |= KMDB_KDI_FL_NOCTF; if (mdb.m_flags & MDB_FL_NOMODS) flags |= KMDB_KDI_FL_NOMODS; return (flags); } size_t kmdb_kdi_range_is_nontoxic(uintptr_t va, size_t sz, int write) { return (mdb.m_kdi->kdi_range_is_nontoxic(va, sz, write)); } void kmdb_kdi_system_claim(void) { (void) kmdb_dpi_call((uintptr_t)mdb.m_kdi->kdi_system_claim, 0, NULL); kmdb_prom_debugger_entry(); } void kmdb_kdi_system_release(void) { kmdb_prom_debugger_exit(); if (mdb.m_kdi->kdi_system_release != NULL) { (void) kmdb_dpi_call((uintptr_t)mdb.m_kdi->kdi_system_release, 0, NULL); } } struct cons_polledio * kmdb_kdi_get_polled_io(void) { return (mdb.m_kdi->kdi_get_polled_io()); } void kmdb_kdi_kmdb_enter(void) { mdb.m_kdi->kdi_kmdb_enter(); } int kmdb_kdi_vtop(uintptr_t va, physaddr_t *pap) { jmp_buf pcb, *oldpcb; int rc = 0; if (setjmp(pcb) == 0) { int err; oldpcb = kmdb_dpi_set_fault_hdlr(&pcb); if ((err = mdb.m_kdi->kdi_vtop(va, pap)) != 0) rc = set_errno(err == ENOENT ? EMDB_NOMAP : err); } else { /* We faulted during the translation */ rc = set_errno(EMDB_NOMAP); } kmdb_dpi_restore_fault_hdlr(oldpcb); return (rc); } kdi_dtrace_state_t kmdb_kdi_dtrace_get_state(void) { return (mdb.m_kdi->kdi_dtrace_get_state()); } int kmdb_kdi_dtrace_set(int state) { int err; if ((err = mdb.m_kdi->kdi_dtrace_set(state)) != 0) return (set_errno(err)); return (0); } /* * This function is to be called only during kmdb initialization, as it * uses the running kernel for symbol translation facilities. */ uintptr_t kmdb_kdi_lookup_by_name(char *modname, char *symname) { ASSERT(kmdb_dpi_get_state(NULL) == DPI_STATE_INIT); return (kdi_auxv->kav_lookup_by_name(modname, symname)); } void kmdb_kdi_init(kdi_t *kdi, kmdb_auxv_t *kav) { mdb.m_kdi = kdi; mdb.m_pagesize = kav->kav_pagesize; kdi_unload_request = 0; kdi_auxv = kav; kmdb_kdi_init_isadep(kdi, kav); } void kmdb_kdi_end_init(void) { kdi_auxv = NULL; } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_KDI_H #define _KMDB_KDI_H #include #include #include #include #include #include #include /* * The following directive tells the mapfile generator that only those * prototypes and declarations ending with a "Driver OK" comment should be * included in the mapfile. * * MAPFILE: export "Driver OK" */ #ifdef __cplusplus extern "C" { #endif struct module; /* * KDI initialization */ extern void kmdb_kdi_init(kdi_t *, kmdb_auxv_t *); extern void kmdb_kdi_init_isadep(kdi_t *, kmdb_auxv_t *); extern void kmdb_kdi_end_init(void); /* * Debugger -> Kernel functions for use when the kernel is stopped */ extern int kmdb_kdi_mods_changed(void); extern int kmdb_kdi_mod_iter(int (*)(struct modctl *, void *), void *); extern int kmdb_kdi_mod_isloaded(struct modctl *); extern int kmdb_kdi_mod_haschanged(struct modctl *, struct module *, struct modctl *, struct module *); extern ssize_t kmdb_kdi_pread(void *, size_t, physaddr_t); extern ssize_t kmdb_kdi_pwrite(void *, size_t, physaddr_t); extern void kmdb_kdi_stop_slaves(int, int); extern void kmdb_kdi_start_slaves(void); extern void kmdb_kdi_slave_wait(void); extern void kmdb_kdi_kmdb_enter(void); /* Driver OK */ extern void kmdb_kdi_system_claim(void); extern void kmdb_kdi_system_release(void); extern size_t kmdb_kdi_range_is_nontoxic(uintptr_t, size_t, int); extern void kmdb_kdi_flush_caches(void); extern struct cons_polledio *kmdb_kdi_get_polled_io(void); extern int kmdb_kdi_vtop(uintptr_t, physaddr_t *); extern kdi_dtrace_state_t kmdb_kdi_dtrace_get_state(void); extern int kmdb_kdi_dtrace_set(int); /* * Driver -> Debugger notifications */ extern int kmdb_kdi_get_unload_request(void); /* Driver OK */ extern void kmdb_kdi_set_unload_request(void); /* Driver OK */ #define KMDB_KDI_FL_NOMODS 0x1 #define KMDB_KDI_FL_NOCTF 0x2 extern int kmdb_kdi_get_flags(void); /* Driver OK */ /* * Debugger -> Kernel functions for use only when the kernel is running */ extern uintptr_t kmdb_kdi_lookup_by_name(char *, char *); #ifdef __cplusplus } #endif #endif /* _KMDB_KDI_H */ /* * 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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013 by Delphix. All rights reserved. * * Copyright 2019 Joyent, Inc. * Copyright 2025 Oxide Computer Company */ #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 static const char KMT_RTLD_NAME[] = "krtld"; static const char KMT_MODULE[] = "mdb_ks"; static const char KMT_CTFPARENT[] = "genunix"; static mdb_list_t kmt_defbp_list; /* List of current deferred bp's */ static int kmt_defbp_lock; /* For list, running kernel holds */ static uint_t kmt_defbp_modchg_isload; /* Whether mod change is load/unload */ static struct modctl *kmt_defbp_modchg_modctl; /* modctl for defbp checking */ static uint_t kmt_defbp_num; /* Number of referenced def'd bp's */ static int kmt_defbp_bpspec; /* vespec for def'd bp activation bp */ static const mdb_se_ops_t kmt_brkpt_ops; static const mdb_se_ops_t kmt_wapt_ops; static void kmt_sync(mdb_tgt_t *); typedef struct kmt_symarg { mdb_tgt_sym_f *sym_cb; /* Caller's callback function */ void *sym_data; /* Callback function argument */ uint_t sym_type; /* Symbol type/binding filter */ mdb_syminfo_t sym_info; /* Symbol id and table id */ const char *sym_obj; /* Containing object */ } kmt_symarg_t; typedef struct kmt_maparg { mdb_tgt_t *map_target; /* Target used for mapping iter */ mdb_tgt_map_f *map_cb; /* Caller's callback function */ void *map_data; /* Callback function argument */ } kmt_maparg_t; /*ARGSUSED*/ int kmt_setflags(mdb_tgt_t *t, int flags) { /* * We only handle one flag (ALLOWIO), and we can't fail to set or clear * it, so we just blindly replace the t_flags version with the one * passed. */ t->t_flags = (t->t_flags & ~MDB_TGT_F_ALLOWIO) | (flags & MDB_TGT_F_ALLOWIO); return (0); } /*ARGSUSED*/ const char * kmt_name(mdb_tgt_t *t) { return ("kmdb_kvm"); } /*ARGSUSED*/ static const char * kmt_platform(mdb_tgt_t *t) { static char platform[SYS_NMLN]; if (kmdb_dpi_get_state(NULL) == DPI_STATE_INIT) return (mdb_conf_platform()); if (mdb_tgt_readsym(mdb.m_target, MDB_TGT_AS_VIRT, platform, sizeof (platform), "unix", "platform") != sizeof (platform)) { warn("'platform' symbol is missing from kernel\n"); return ("unknown"); } return (platform); } static int kmt_uname(mdb_tgt_t *t, struct utsname *utsp) { return (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, utsp, sizeof (struct utsname), MDB_TGT_OBJ_EXEC, "utsname")); } /*ARGSUSED*/ static int kmt_dmodel(mdb_tgt_t *t) { return (MDB_TGT_MODEL_NATIVE); } /*ARGSUSED*/ ssize_t kmt_rw(mdb_tgt_t *t, void *buf, size_t nbytes, uint64_t addr, ssize_t (*rw)(void *, size_t, uint64_t)) { /* * chunksz needs to be volatile because of the use of setjmp() in this * function. */ volatile size_t chunksz; size_t n, ndone; jmp_buf *oldpcb = NULL; jmp_buf pcb; ssize_t res; kmdb_prom_check_interrupt(); if (nbytes == 0) return (0); /* * Try to process the entire buffer, as requested. If we catch a fault, * try smaller chunks. This allows us to handle regions that cross * mapping boundaries. */ chunksz = nbytes; ndone = 0; if (setjmp(pcb) != 0) { if (chunksz == 1) { /* We failed with the smallest chunk - give up */ kmdb_dpi_restore_fault_hdlr(oldpcb); return (ndone > 0 ? ndone : -1); /* errno set for us */ } else if (chunksz > 4) chunksz = 4; else chunksz = 1; } oldpcb = kmdb_dpi_set_fault_hdlr(&pcb); while (nbytes > 0) { n = MIN(chunksz, nbytes); if ((res = rw(buf, n, addr)) != n) return (res < 0 ? res : ndone + res); addr += n; nbytes -= n; ndone += n; buf = ((caddr_t)buf + n); } kmdb_dpi_restore_fault_hdlr(oldpcb); return (ndone); } static void kmt_bcopy(const void *s1, void *s2, size_t n) { /* * We need to guarantee atomic accesses for certain sizes. bcopy won't * make that guarantee, so we need to do it ourselves. */ #ifdef _LP64 if (n == 8 && ((uintptr_t)s1 & 7) == 0 && ((uintptr_t)s2 & 7) == 0) *(uint64_t *)s2 = *(uint64_t *)s1; else #endif if (n == 4 && ((uintptr_t)s1 & 3) == 0 && ((uintptr_t)s2 & 3) == 0) *(uint32_t *)s2 = *(uint32_t *)s1; else if (n == 2 && ((uintptr_t)s1 & 1) == 0 && ((uintptr_t)s2 & 1) == 0) *(uint16_t *)s2 = *(uint16_t *)s1; else if (n == 1) *(uint8_t *)s2 = *(uint8_t *)s1; else bcopy(s1, s2, n); } static ssize_t kmt_reader(void *buf, size_t nbytes, uint64_t addr) { kmt_bcopy((void *)(uintptr_t)addr, buf, nbytes); return (nbytes); } ssize_t kmt_writer(void *buf, size_t nbytes, uint64_t addr) { kmt_bcopy(buf, (void *)(uintptr_t)addr, nbytes); return (nbytes); } /*ARGSUSED*/ static ssize_t kmt_read(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr) { /* * We don't want to allow reads of I/O-mapped memory. Multi-page reads * that cross into I/O-mapped memory should be restricted to the initial * non-I/O region. Reads that begin in I/O-mapped memory are failed * outright. */ if (!(t->t_flags & MDB_TGT_F_ALLOWIO) && (nbytes = kmdb_kdi_range_is_nontoxic(addr, nbytes, 0)) == 0) return (set_errno(EMDB_NOMAP)); return (kmt_rw(t, buf, nbytes, addr, kmt_reader)); } /*ARGSUSED*/ static ssize_t kmt_pread(mdb_tgt_t *t, void *buf, size_t nbytes, physaddr_t addr) { return (kmt_rw(t, buf, nbytes, addr, kmdb_kdi_pread)); } /*ARGSUSED*/ ssize_t kmt_pwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, physaddr_t addr) { return (kmt_rw(t, (void *)buf, nbytes, addr, kmdb_kdi_pwrite)); } static uintptr_t kmt_read_kas(mdb_tgt_t *t) { GElf_Sym sym; if (mdb_tgt_lookup_by_name(t, "unix", "kas", &sym, NULL) < 0) { warn("'kas' symbol is missing from kernel\n"); (void) set_errno(EMDB_NOSYM); return (0); } return ((uintptr_t)sym.st_value); } static int kmt_vtop(mdb_tgt_t *t, mdb_tgt_as_t as, uintptr_t va, physaddr_t *pap) { mdb_module_t *mod; struct as *asp; mdb_var_t *v; switch ((uintptr_t)as) { case (uintptr_t)MDB_TGT_AS_PHYS: case (uintptr_t)MDB_TGT_AS_FILE: case (uintptr_t)MDB_TGT_AS_IO: return (set_errno(EINVAL)); case (uintptr_t)MDB_TGT_AS_VIRT: case (uintptr_t)MDB_TGT_AS_VIRT_I: case (uintptr_t)MDB_TGT_AS_VIRT_S: if ((asp = (struct as *)kmt_read_kas(t)) == NULL) return (-1); /* errno is set for us */ break; default: asp = (struct as *)as; /* We don't support non-kas vtop */ if (asp != (struct as *)kmt_read_kas(t)) return (set_errno(EMDB_TGTNOTSUP)); } if (kmdb_prom_vtop(va, pap) == 0) return (0); if ((v = mdb_nv_lookup(&mdb.m_modules, "unix")) != NULL && (mod = mdb_nv_get_cookie(v)) != NULL) { int (*fptr)(uintptr_t, struct as *, physaddr_t *); fptr = (int (*)(uintptr_t, struct as *, physaddr_t *)) dlsym(mod->mod_hdl, "platform_vtop"); if ((fptr != NULL) && ((*fptr)(va, asp, pap) == 0)) return (0); } return (set_errno(EMDB_NOMAP)); } /*ARGSUSED*/ static int kmt_cpuregs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const mdb_tgt_gregset_t *gregs; intptr_t cpuid = DPI_MASTER_CPUID; int i; if (flags & DCMD_ADDRSPEC) { if (argc != 0) return (DCMD_USAGE); if ((cpuid = mdb_cpu2cpuid(addr)) < 0) { (void) set_errno(EMDB_NOMAP); mdb_warn("failed to find cpuid for cpu at %p", addr); return (DCMD_ERR); } } i = mdb_getopts(argc, argv, 'c', MDB_OPT_UINTPTR, &cpuid, NULL); argc -= i; argv += i; if (argc != 0) return (DCMD_USAGE); if ((gregs = kmdb_dpi_get_gregs(cpuid)) == NULL) { warn("failed to retrieve registers for cpu %d", (int)cpuid); return (DCMD_ERR); } kmt_printregs(gregs); return (DCMD_OK); } static int kmt_regs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); return (kmt_cpuregs(addr, flags, argc, argv)); } static int kmt_cpustack_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { intptr_t cpuid = DPI_MASTER_CPUID; uint_t verbose = 0; int i; if (flags & DCMD_ADDRSPEC) { if ((cpuid = mdb_cpu2cpuid(addr)) < 0) { (void) set_errno(EMDB_NOMAP); mdb_warn("failed to find cpuid for cpu at %p", addr); return (DCMD_ERR); } flags &= ~DCMD_ADDRSPEC; } i = mdb_getopts(argc, argv, 'c', MDB_OPT_UINTPTR, &cpuid, 'v', MDB_OPT_SETBITS, 1, &verbose, NULL); argc -= i; argv += i; return (kmt_cpustack(addr, flags, argc, argv, cpuid, verbose)); } /* * Lasciate ogne speranza, voi ch'intrate. */ static int kmt_call(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t *call_argv, rval; int parse_strings = 1; GElf_Sym sym; jmp_buf *oldpcb = NULL; jmp_buf pcb; int i; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_tgt_lookup_by_addr(mdb.m_target, addr, MDB_TGT_SYM_EXACT, NULL, 0, &sym, NULL) == 0 && GELF_ST_TYPE(sym.st_info) != STT_FUNC) { warn("%a is not a function\n", addr); return (DCMD_ERR); } if (argc > 1 && argv[0].a_type == MDB_TYPE_STRING && strcmp(argv[0].a_un.a_str, "-s") == 0) { parse_strings = 0; argc--; argv++; } call_argv = mdb_alloc(sizeof (uintptr_t) * argc, UM_SLEEP); for (i = 0; i < argc; i++) { switch (argv[i].a_type) { case MDB_TYPE_STRING: /* * mdb_strtoull doesn't return on error, so we have to * pre-check strings suspected to contain numbers. */ if (parse_strings && strisbasenum(argv[i].a_un.a_str)) { call_argv[i] = (uintptr_t)mdb_strtoull( argv[i].a_un.a_str); } else call_argv[i] = (uintptr_t)argv[i].a_un.a_str; break; case MDB_TYPE_IMMEDIATE: call_argv[i] = argv[i].a_un.a_val; break; default: mdb_free(call_argv, sizeof (uintptr_t) * argc); return (DCMD_USAGE); } } if (setjmp(pcb) != 0) { warn("call failed: caught a trap\n"); kmdb_dpi_restore_fault_hdlr(oldpcb); mdb_free(call_argv, sizeof (uintptr_t) * argc); return (DCMD_ERR); } oldpcb = kmdb_dpi_set_fault_hdlr(&pcb); rval = kmdb_dpi_call(addr, argc, call_argv); kmdb_dpi_restore_fault_hdlr(oldpcb); if (flags & DCMD_PIPE_OUT) { mdb_printf("%p\n", rval); } else { /* pretty-print the results */ mdb_printf("%p = %a(", rval, addr); for (i = 0; i < argc; i++) { if (i > 0) mdb_printf(", "); if (argv[i].a_type == MDB_TYPE_STRING) { /* I'm ashamed but amused */ char *quote = &("\""[parse_strings && strisbasenum(argv[i].a_un.a_str)]); mdb_printf("%s%s%s", quote, argv[i].a_un.a_str, quote); } else mdb_printf("%p", argv[i].a_un.a_val); } mdb_printf(");\n"); } mdb_free(call_argv, sizeof (uintptr_t) * argc); return (DCMD_OK); } /*ARGSUSED*/ int kmt_dump_crumbs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { intptr_t cpu = -1; if (flags & DCMD_ADDRSPEC) { if (argc != 0) return (DCMD_USAGE); } else { addr = 0; if (mdb_getopts(argc, argv, 'c', MDB_OPT_UINTPTR, &cpu, NULL) != argc) return (DCMD_USAGE); } kmdb_dpi_dump_crumbs(addr, cpu); return (DCMD_OK); } /*ARGSUSED*/ static int kmt_noducttape(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int a = 0; return (a/a); } static int kmt_dmod_status(char *msg, int state) { kmdb_modctl_t *kmc; mdb_var_t *v; int first = 1, n = 0; mdb_nv_rewind(&mdb.m_dmodctl); while ((v = mdb_nv_advance(&mdb.m_dmodctl)) != NULL) { kmc = MDB_NV_COOKIE(v); if (kmc->kmc_state != state) continue; n++; if (msg != NULL) { if (first) { mdb_printf(msg, NULL); first = 0; } mdb_printf(" %s", kmc->kmc_modname); } } if (!first && msg != NULL) mdb_printf("\n"); return (n); } /*ARGSUSED*/ static int kmt_status_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct utsname uts; char uuid[UUID_PRINTABLE_STRING_LENGTH]; kreg_t tt; if (mdb_tgt_readsym(mdb.m_target, MDB_TGT_AS_VIRT, &uts, sizeof (uts), "unix", "utsname") != sizeof (uts)) { warn("failed to read 'utsname' struct from kernel\n"); bzero(&uts, sizeof (uts)); (void) strcpy(uts.nodename, "unknown machine"); } mdb_printf("debugging live kernel (%d-bit) on %s\n", (int)(sizeof (void *) * NBBY), (*uts.nodename == '\0' ? "(not set)" : uts.nodename)); mdb_printf("operating system: %s %s (%s)\n", uts.release, uts.version, uts.machine); mdb_print_buildversion(); if (mdb_readsym(uuid, sizeof (uuid), "dump_osimage_uuid") == sizeof (uuid) && uuid[sizeof (uuid) - 1] == '\0') { mdb_printf("image uuid: %s\n", uuid[0] != '\0' ? uuid : "(not set)"); } mdb_printf("DTrace state: %s\n", (kmdb_kdi_dtrace_get_state() == KDI_DTSTATE_DTRACE_ACTIVE ? "active (debugger breakpoints cannot " "be armed)" : "inactive")); (void) kmdb_dpi_get_register("tt", &tt); mdb_printf("stopped on: %s\n", kmt_trapname(tt)); (void) kmt_dmod_status("pending dmod loads:", KMDB_MC_STATE_LOADING); (void) kmt_dmod_status("pending dmod unloads:", KMDB_MC_STATE_UNLOADING); return (DCMD_OK); } /*ARGSUSED*/ static int kmt_switch(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (kmdb_dpi_switch_master((int)addr) < 0) { warn("failed to switch to CPU %d", (int)addr); return (DCMD_ERR); } return (DCMD_OK); } static void kmt_stack_help(void) { mdb_printf( "Options:\n" " -n do not resolve addresses to names\n" " -s show the size of each stack frame to the left\n" " -t where CTF is present, show types for functions and " "arguments\n" " -v include frame pointer information (this is the default " "for %$C%)\n" "\n" "If the optional %cnt% is given, no more than %cnt% " "arguments are shown\nfor each stack frame.\n"); } static const mdb_dcmd_t kmt_dcmds[] = { { "$c", "?[-nstv] [cnt]", "print stack backtrace", kmt_stack, kmt_stack_help }, { "$C", "?[-nstv] [cnt]", "print stack backtrace", kmt_stackv, kmt_stack_help }, { "$r", NULL, "print general-purpose registers", kmt_regs }, { "$?", NULL, "print status and registers", kmt_regs }, { ":x", ":", "change the active CPU", kmt_switch }, { "call", ":[arg ...]", "call a kernel function", kmt_call }, { "cpustack", "?[-v] [-c cpuid] [cnt]", "print stack backtrace for a " "specific CPU", kmt_cpustack_dcmd }, { "cpuregs", "?[-c cpuid]", "print general-purpose registers for a " "specific CPU", kmt_cpuregs }, { "crumbs", NULL, NULL, kmt_dump_crumbs }, #if defined(__i386) || defined(__amd64) { "in", ":[-L len]", "read from I/O port", kmt_in_dcmd }, { "out", ":[-L len] val", "write to I/O port", kmt_out_dcmd }, { "rdmsr", ":", "read an MSR", kmt_rdmsr }, { "wrmsr", ": val", "write an MSR", kmt_wrmsr }, { "rdpcicfg", ": bus dev func", "read a register in PCI config space", kmt_rdpcicfg }, { "wrpcicfg", ": bus dev func val", "write a register in PCI config space", kmt_wrpcicfg }, #endif { "noducttape", NULL, NULL, kmt_noducttape }, { "regs", NULL, "print general-purpose registers", kmt_regs }, { "stack", "?[-nstv] [cnt]", "print stack backtrace", kmt_stack, kmt_stack_help }, { "stackregs", "?[-nstv] [cnt]", "print stack backtrace and registers", kmt_stackr, kmt_stack_help }, { "status", NULL, "print summary of current target", kmt_status_dcmd }, { "switch", ":", "change the active CPU", kmt_switch }, { NULL } }; static uintmax_t kmt_reg_disc_get(const mdb_var_t *v) { mdb_tgt_reg_t r = 0; (void) mdb_tgt_getareg(MDB_NV_COOKIE(v), 0, mdb_nv_get_name(v), &r); return (r); } static void kmt_reg_disc_set(mdb_var_t *v, uintmax_t r) { if (mdb_tgt_putareg(MDB_NV_COOKIE(v), 0, mdb_nv_get_name(v), r) == -1) warn("failed to modify %%%s register", mdb_nv_get_name(v)); } static const mdb_nv_disc_t kmt_reg_disc = { .disc_get = kmt_reg_disc_get, .disc_set = kmt_reg_disc_set }; /*ARGSUSED*/ static int kmt_getareg(mdb_tgt_t *t, mdb_tgt_tid_t tid, const char *rname, mdb_tgt_reg_t *rp) { kreg_t val; if (kmdb_dpi_get_register(rname, &val) < 0) return (set_errno(EMDB_BADREG)); *rp = val; return (0); } /*ARGSUSED*/ static int kmt_putareg(mdb_tgt_t *t, mdb_tgt_tid_t tid, const char *rname, mdb_tgt_reg_t r) { if (kmdb_dpi_set_register(rname, r) < 0) return (set_errno(EMDB_BADREG)); return (0); } static void kmt_mod_destroy(kmt_module_t *km) { if (km->km_name != NULL) strfree(km->km_name); if (km->km_symtab != NULL) mdb_gelf_symtab_destroy(km->km_symtab); if (km->km_ctfp != NULL) mdb_ctf_close(km->km_ctfp); } static kmt_module_t * kmt_mod_create(mdb_tgt_t *t, struct modctl *ctlp, char *name) { kmt_module_t *km = mdb_zalloc(sizeof (kmt_module_t), UM_SLEEP); struct module *mod; km->km_name = mdb_alloc(strlen(name) + 1, UM_SLEEP); (void) strcpy(km->km_name, name); bcopy(ctlp, &km->km_modctl, sizeof (struct modctl)); if (mdb_tgt_vread(t, &km->km_module, sizeof (struct module), (uintptr_t)km->km_modctl.mod_mp) != sizeof (struct module)) goto create_module_cleanup; mod = &km->km_module; if (mod->symhdr != NULL && mod->strhdr != NULL && mod->symtbl != NULL && mod->strings != NULL) { mdb_gelf_ehdr_to_gehdr(&mod->hdr, &km->km_ehdr); km->km_symtab = mdb_gelf_symtab_create_raw(&km->km_ehdr, mod->symhdr, mod->symtbl, mod->strhdr, mod->strings, MDB_TGT_SYMTAB); km->km_symtab_va = mod->symtbl; km->km_strtab_va = mod->strings; if (mdb_tgt_vread(t, &km->km_symtab_hdr, sizeof (Shdr), (uintptr_t)mod->symhdr) != sizeof (Shdr) || mdb_tgt_vread(t, &km->km_strtab_hdr, sizeof (Shdr), (uintptr_t)mod->strhdr) != sizeof (Shdr)) goto create_module_cleanup; } /* * We don't want everyone rooting around in the module structure, so we * make copies of the interesting members. */ km->km_text_va = (uintptr_t)mod->text; km->km_text_size = mod->text_size; km->km_data_va = (uintptr_t)mod->data; km->km_data_size = mod->data_size; km->km_bss_va = (uintptr_t)mod->bss; km->km_bss_size = mod->bss_size; km->km_ctf_va = mod->ctfdata; km->km_ctf_size = mod->ctfsize; if (mod->flags & KOBJ_PRIM) km->km_flags |= KM_F_PRIMARY; return (km); create_module_cleanup: warn("failed to read module %s\n", name); kmt_mod_destroy(km); return (NULL); } static void kmt_mod_remove(kmt_data_t *kmt, kmt_module_t *km) { mdb_var_t *v = mdb_nv_lookup(&kmt->kmt_modules, km->km_name); ASSERT(v != NULL); mdb_dprintf(MDB_DBG_KMOD, "removing module %s\n", km->km_name); mdb_list_delete(&kmt->kmt_modlist, km); mdb_nv_remove(&kmt->kmt_modules, v); kmt_mod_destroy(km); } static int kmt_modlist_update_cb(struct modctl *modp, void *arg) { mdb_tgt_t *t = arg; kmt_data_t *kmt = t->t_data; kmt_module_t *km; mdb_var_t *v; char name[MAXNAMELEN]; if (mdb_tgt_readstr(t, MDB_TGT_AS_VIRT, name, MAXNAMELEN, (uintptr_t)modp->mod_modname) <= 0) { warn("failed to read module name at %p", (void *)modp->mod_modname); } /* We only care about modules that are actually loaded */ if (!kmdb_kdi_mod_isloaded(modp)) return (0); /* * Skip the modules we already know about and that haven't * changed since last time we were here. */ if ((v = mdb_nv_lookup(&kmt->kmt_modules, name)) != NULL) { km = MDB_NV_COOKIE(v); if (kmdb_kdi_mod_haschanged(&km->km_modctl, &km->km_module, modp, modp->mod_mp)) { /* * The module has changed since last we saw it. For * safety, remove our old version, and treat it as a * new module. */ mdb_dprintf(MDB_DBG_KMOD, "stutter module %s\n", name); kmt_mod_remove(kmt, km); } else { km->km_seen = 1; return (0); } } mdb_dprintf(MDB_DBG_KMOD, "found new module %s\n", name); if ((km = kmt_mod_create(t, modp, name)) != NULL) { mdb_list_append(&kmt->kmt_modlist, km); (void) mdb_nv_insert(&kmt->kmt_modules, name, NULL, (uintptr_t)km, 0); km->km_seen = 1; } return (0); } static void kmt_modlist_update(mdb_tgt_t *t) { kmt_data_t *kmt = t->t_data; kmt_module_t *km, *kmn; if (kmdb_kdi_mod_iter(kmt_modlist_update_cb, t) < 0) { warn("failed to complete update of kernel module list\n"); return; } km = mdb_list_next(&kmt->kmt_modlist); while (km != NULL) { kmn = mdb_list_next(km); if (km->km_seen == 1) { /* Reset the mark for next time */ km->km_seen = 0; } else { /* * We didn't see it on the kernel's module list, so * remove it from our view of the world. */ kmt_mod_remove(kmt, km); } km = kmn; } } static void kmt_periodic(mdb_tgt_t *t) { (void) mdb_tgt_status(t, &t->t_status); } int kmt_lookup_by_addr(mdb_tgt_t *t, uintptr_t addr, uint_t flags, char *buf, size_t nbytes, GElf_Sym *symp, mdb_syminfo_t *sip) { kmt_data_t *kmt = t->t_data; kmt_module_t *km = mdb_list_next(&kmt->kmt_modlist); kmt_module_t *sym_km = NULL; kmt_module_t prmod; GElf_Sym sym; uint_t symid; const char *name; /* * We look through the private symbols (if any), then through the module * symbols. We can simplify the loop if we pretend the private symbols * come from a module. */ if (mdb.m_prsym != NULL) { bzero(&prmod, sizeof (kmt_module_t)); prmod.km_name = "<<>>"; prmod.km_symtab = mdb.m_prsym; prmod.km_list.ml_next = (mdb_list_t *)km; km = &prmod; } /* Symbol resolution isn't available during initialization */ if (kmdb_dpi_get_state(NULL) == DPI_STATE_INIT) return (set_errno(EMDB_NOSYM)); for (; km != NULL; km = mdb_list_next(km)) { if (km != &prmod && !kmt->kmt_symavail) continue; if (km->km_symtab == NULL) continue; if (mdb_gelf_symtab_lookup_by_addr(km->km_symtab, addr, flags, buf, nbytes, symp, &sip->sym_id) != 0 || symp->st_value == 0) continue; if (flags & MDB_TGT_SYM_EXACT) { sym_km = km; goto found; } /* * If this is the first match we've found, or if this symbol is * closer to the specified address than the last one we found, * use it. */ if (sym_km == NULL || mdb_gelf_sym_closer(symp, &sym, addr)) { sym_km = km; sym = *symp; symid = sip->sym_id; } } /* * kmdb dmods are normal kernel modules, loaded by krtld as such. To * avoid polluting modinfo, and to keep from confusing the module * subsystem (many dmods have the same names as real kernel modules), * kmdb keeps their modctls separate, and doesn't allow their loading * to be broadcast via the krtld module load/unload mechanism. As a * result, kmdb_kvm doesn't find out about them, and can't turn their * addresses into symbols. This can be most inconvenient during * debugger faults, as the dmod frames will show up without names. * We weren't able to turn the requested address into a symbol, so we'll * take a spin through the dmods, trying to match our address against * their symbols. */ if (sym_km == NULL) { return (kmdb_module_lookup_by_addr(addr, flags, buf, nbytes, symp, sip)); } *symp = sym; sip->sym_id = symid; found: /* * Once we've found something, copy the final name into the caller's * buffer and prefix it with the load object name if appropriate. */ name = mdb_gelf_sym_name(sym_km->km_symtab, symp); if (sym_km == &prmod) { if (buf != NULL) { (void) strncpy(buf, name, nbytes); buf[nbytes - 1] = '\0'; } sip->sym_table = MDB_TGT_PRVSYM; } else { if (buf != NULL) { if (sym_km->km_flags & KM_F_PRIMARY) { (void) strncpy(buf, name, nbytes); buf[nbytes - 1] = '\0'; } else { (void) mdb_snprintf(buf, nbytes, "%s`%s", sym_km->km_name, name); } } sip->sym_table = MDB_TGT_SYMTAB; } return (0); } static int kmt_lookup_by_name(mdb_tgt_t *t, const char *obj, const char *name, GElf_Sym *symp, mdb_syminfo_t *sip) { kmt_data_t *kmt = t->t_data; kmt_module_t *km; mdb_var_t *v; GElf_Sym sym; uint_t symid; int n; if (!kmt->kmt_symavail) return (set_errno(EMDB_NOSYM)); switch ((uintptr_t)obj) { case (uintptr_t)MDB_TGT_OBJ_EXEC: case (uintptr_t)MDB_TGT_OBJ_EVERY: km = mdb_list_next(&kmt->kmt_modlist); n = mdb_nv_size(&kmt->kmt_modules); break; case (uintptr_t)MDB_TGT_OBJ_RTLD: obj = kmt->kmt_rtld_name; /*FALLTHROUGH*/ default: /* * If this is a request for a dmod symbol, let kmdb_module * handle it. */ if (obj != NULL && strncmp(obj, "DMOD`", 5) == 0) { return (kmdb_module_lookup_by_name(obj + 5, name, symp, sip)); } if ((v = mdb_nv_lookup(&kmt->kmt_modules, obj)) == NULL) return (set_errno(EMDB_NOOBJ)); km = mdb_nv_get_cookie(v); n = 1; } /* * kmdb's kvm target is at a bit of a disadvantage compared to mdb's * kvm target when it comes to global symbol lookups. mdb has ksyms, * which hides pesky things like symbols that are undefined in unix, * but which are defined in genunix. We don't have such a facility - * we simply iterate through the modules, looking for a given symbol * in each. Unless we're careful, we'll return the undef in the * aforementioned case. */ for (; n > 0; n--, km = mdb_list_next(km)) { if (mdb_gelf_symtab_lookup_by_name(km->km_symtab, name, &sym, &symid) == 0 && sym.st_shndx != SHN_UNDEF) break; } if (n == 0) return (set_errno(EMDB_NOSYM)); bcopy(&sym, symp, sizeof (GElf_Sym)); sip->sym_id = symid; sip->sym_table = MDB_TGT_SYMTAB; return (0); } static int kmt_symtab_func(void *data, const GElf_Sym *sym, const char *name, uint_t id) { kmt_symarg_t *arg = data; if (mdb_tgt_sym_match(sym, arg->sym_type)) { arg->sym_info.sym_id = id; return (arg->sym_cb(arg->sym_data, sym, name, &arg->sym_info, arg->sym_obj)); } return (0); } static void kmt_symtab_iter(mdb_gelf_symtab_t *gst, uint_t type, const char *obj, mdb_tgt_sym_f *cb, void *p) { kmt_symarg_t arg; arg.sym_cb = cb; arg.sym_data = p; arg.sym_type = type; arg.sym_info.sym_table = gst->gst_tabid; arg.sym_obj = obj; mdb_gelf_symtab_iter(gst, kmt_symtab_func, &arg); } static int kmt_symbol_iter(mdb_tgt_t *t, const char *obj, uint_t which, uint_t type, mdb_tgt_sym_f *cb, void *data) { kmt_data_t *kmt = t->t_data; kmt_module_t *km; mdb_gelf_symtab_t *symtab = NULL; mdb_var_t *v; if (which == MDB_TGT_DYNSYM) return (set_errno(EMDB_TGTNOTSUP)); switch ((uintptr_t)obj) { case (uintptr_t)MDB_TGT_OBJ_EXEC: case (uintptr_t)MDB_TGT_OBJ_EVERY: mdb_nv_rewind(&kmt->kmt_modules); while ((v = mdb_nv_advance(&kmt->kmt_modules)) != NULL) { km = mdb_nv_get_cookie(v); if (km->km_symtab != NULL) { kmt_symtab_iter(km->km_symtab, type, km->km_name, cb, data); } } return (0); case (uintptr_t)MDB_TGT_OBJ_RTLD: obj = kmt->kmt_rtld_name; /*FALLTHROUGH*/ default: if (strncmp(obj, "DMOD`", 5) == 0) { return (kmdb_module_symbol_iter(obj + 5, type, cb, data)); } if ((v = mdb_nv_lookup(&kmt->kmt_modules, obj)) == NULL) return (set_errno(EMDB_NOOBJ)); km = mdb_nv_get_cookie(v); symtab = km->km_symtab; } if (symtab != NULL) kmt_symtab_iter(symtab, type, obj, cb, data); return (0); } static int kmt_mapping_walk(uintptr_t addr, const void *data, kmt_maparg_t *marg) { /* * This is a bit sketchy but avoids problematic compilation of this * target against the current VM implementation. Now that we have * vmem, we can make this less broken and more informative by changing * this code to invoke the vmem walker in the near future. */ const struct kmt_seg { caddr_t s_base; size_t s_size; } *segp = (const struct kmt_seg *)data; mdb_map_t map; GElf_Sym sym; mdb_syminfo_t info; map.map_base = (uintptr_t)segp->s_base; map.map_size = segp->s_size; map.map_flags = MDB_TGT_MAP_R | MDB_TGT_MAP_W | MDB_TGT_MAP_X; if (kmt_lookup_by_addr(marg->map_target, addr, MDB_TGT_SYM_EXACT, map.map_name, MDB_TGT_MAPSZ, &sym, &info) == -1) { (void) mdb_iob_snprintf(map.map_name, MDB_TGT_MAPSZ, "%lr", addr); } return (marg->map_cb(marg->map_data, &map, map.map_name)); } static int kmt_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { kmt_maparg_t m; uintptr_t kas; m.map_target = t; m.map_cb = func; m.map_data = private; if ((kas = kmt_read_kas(t)) == 0) return (-1); /* errno is set for us */ return (mdb_pwalk("seg", (mdb_walk_cb_t)kmt_mapping_walk, &m, kas)); } static const mdb_map_t * kmt_mod_to_map(kmt_module_t *km, mdb_map_t *map) { (void) strncpy(map->map_name, km->km_name, MDB_TGT_MAPSZ); map->map_name[MDB_TGT_MAPSZ - 1] = '\0'; map->map_base = km->km_text_va; map->map_size = km->km_text_size; map->map_flags = MDB_TGT_MAP_R | MDB_TGT_MAP_W | MDB_TGT_MAP_X; return (map); } static int kmt_object_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { kmt_data_t *kmt = t->t_data; kmt_module_t *km; mdb_map_t m; for (km = mdb_list_next(&kmt->kmt_modlist); km != NULL; km = mdb_list_next(km)) { if (func(private, kmt_mod_to_map(km, &m), km->km_name) == -1) break; } return (0); } static const mdb_map_t * kmt_addr_to_map(mdb_tgt_t *t, uintptr_t addr) { kmt_data_t *kmt = t->t_data; kmt_module_t *km; for (km = mdb_list_next(&kmt->kmt_modlist); km != NULL; km = mdb_list_next(km)) { if (addr - km->km_text_va < km->km_text_size || addr - km->km_data_va < km->km_data_size || addr - km->km_bss_va < km->km_bss_size) return (kmt_mod_to_map(km, &kmt->kmt_map)); } (void) set_errno(EMDB_NOMAP); return (NULL); } static kmt_module_t * kmt_module_by_name(kmt_data_t *kmt, const char *name) { kmt_module_t *km; for (km = mdb_list_next(&kmt->kmt_modlist); km != NULL; km = mdb_list_next(km)) { if (strcmp(name, km->km_name) == 0) return (km); } return (NULL); } static const mdb_map_t * kmt_name_to_map(mdb_tgt_t *t, const char *name) { kmt_data_t *kmt = t->t_data; kmt_module_t *km; mdb_map_t m; /* * If name is MDB_TGT_OBJ_EXEC, return the first module on the list, * which will be unix since we keep kmt_modlist in load order. */ if (name == MDB_TGT_OBJ_EXEC) { return (kmt_mod_to_map(mdb_list_next(&kmt->kmt_modlist), &m)); } if (name == MDB_TGT_OBJ_RTLD) name = kmt->kmt_rtld_name; if ((km = kmt_module_by_name(kmt, name)) != NULL) return (kmt_mod_to_map(km, &m)); (void) set_errno(EMDB_NOOBJ); return (NULL); } static ctf_file_t * kmt_load_ctfdata(mdb_tgt_t *t, kmt_module_t *km) { kmt_data_t *kmt = t->t_data; int err; if (km->km_ctfp != NULL) return (km->km_ctfp); if (km->km_ctf_va == NULL || km->km_symtab == NULL) { (void) set_errno(EMDB_NOCTF); return (NULL); } if ((km->km_ctfp = mdb_ctf_bufopen(km->km_ctf_va, km->km_ctf_size, km->km_symtab_va, &km->km_symtab_hdr, km->km_strtab_va, &km->km_strtab_hdr, &err)) == NULL) { (void) set_errno(ctf_to_errno(err)); return (NULL); } mdb_dprintf(MDB_DBG_KMOD, "loaded %lu bytes of CTF data for %s\n", (ulong_t)km->km_ctf_size, km->km_name); if (ctf_parent_name(km->km_ctfp) != NULL) { mdb_var_t *v; if ((v = mdb_nv_lookup(&kmt->kmt_modules, ctf_parent_name(km->km_ctfp))) != NULL) { kmt_module_t *pm = mdb_nv_get_cookie(v); if (pm->km_ctfp == NULL) (void) kmt_load_ctfdata(t, pm); if (pm->km_ctfp != NULL && ctf_import(km->km_ctfp, pm->km_ctfp) == CTF_ERR) { warn("failed to import parent types into " "%s: %s\n", km->km_name, ctf_errmsg(ctf_errno(km->km_ctfp))); } } else { warn("failed to load CTF data for %s - parent %s not " "loaded\n", km->km_name, ctf_parent_name(km->km_ctfp)); } } return (km->km_ctfp); } ctf_file_t * kmt_addr_to_ctf(mdb_tgt_t *t, uintptr_t addr) { kmt_data_t *kmt = t->t_data; kmt_module_t *km; for (km = mdb_list_next(&kmt->kmt_modlist); km != NULL; km = mdb_list_next(km)) { if (addr - km->km_text_va < km->km_text_size || addr - km->km_data_va < km->km_data_size || addr - km->km_bss_va < km->km_bss_size) return (kmt_load_ctfdata(t, km)); } return (kmdb_module_addr_to_ctf(addr)); } ctf_file_t * kmt_name_to_ctf(mdb_tgt_t *t, const char *name) { kmt_data_t *kt = t->t_data; kmt_module_t *km; if (name == MDB_TGT_OBJ_EXEC) { name = KMT_CTFPARENT; } else if (name == MDB_TGT_OBJ_RTLD) { name = kt->kmt_rtld_name; } else if (strncmp(name, "DMOD`", 5) == 0) { /* Request for CTF data for a DMOD symbol */ return (kmdb_module_name_to_ctf(name + 5)); } if ((km = kmt_module_by_name(kt, name)) != NULL) return (kmt_load_ctfdata(t, km)); (void) set_errno(EMDB_NOOBJ); return (NULL); } /*ARGSUSED*/ static int kmt_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { int state; bzero(tsp, sizeof (mdb_tgt_status_t)); switch ((state = kmdb_dpi_get_state(NULL))) { case DPI_STATE_INIT: tsp->st_state = MDB_TGT_RUNNING; tsp->st_pc = 0; break; case DPI_STATE_STOPPED: tsp->st_state = MDB_TGT_STOPPED; (void) kmdb_dpi_get_register("pc", &tsp->st_pc); break; case DPI_STATE_FAULTED: tsp->st_state = MDB_TGT_STOPPED; (void) kmdb_dpi_get_register("pc", &tsp->st_pc); tsp->st_flags |= MDB_TGT_ISTOP; break; case DPI_STATE_LOST: tsp->st_state = MDB_TGT_LOST; (void) kmdb_dpi_get_register("pc", &tsp->st_pc); break; } mdb_dprintf(MDB_DBG_KMOD, "kmt_status, dpi: %d tsp: %d, pc = %p %A\n", state, tsp->st_state, (void *)tsp->st_pc, tsp->st_pc); return (0); } /* * Invoked when kmt_defbp_enter_debugger is called, this routine activates and * deactivates deferred breakpoints in response to module load and unload * events. */ /*ARGSUSED*/ static void kmt_defbp_event(mdb_tgt_t *t, int vid, void *private) { if (kmt_defbp_modchg_isload) { if (!mdb_tgt_sespec_activate_all(t) && (mdb.m_flags & MDB_FL_BPTNOSYMSTOP)) { /* * We weren't able to activate the breakpoints. * If so requested, we'll return without calling * continue, thus throwing the user into the debugger. */ return; } } else { mdb_sespec_t *sep, *nsep; const mdb_map_t *map, *bpmap; mdb_map_t modmap; if ((map = kmt_addr_to_map(t, (uintptr_t)kmt_defbp_modchg_modctl->mod_text)) == NULL) { warn("module unload notification for unknown module %s", kmt_defbp_modchg_modctl->mod_modname); return; /* drop into the debugger */ } bcopy(map, &modmap, sizeof (mdb_map_t)); for (sep = mdb_list_next(&t->t_active); sep; sep = nsep) { nsep = mdb_list_next(sep); if (sep->se_ops == &kmt_brkpt_ops) { kmt_brkpt_t *kb = sep->se_data; if ((bpmap = kmt_addr_to_map(t, kb->kb_addr)) == NULL || (bpmap->map_base == modmap.map_base && bpmap->map_size == modmap.map_size)) { mdb_tgt_sespec_idle_one(t, sep, EMDB_NOMAP); } } } } (void) mdb_tgt_continue(t, NULL); } static void kmt_defbp_enter_debugger(void) { /* * The debugger places a breakpoint here. We can't have a simple * nop function here, because GCC knows much more than we do, and * will optimize away the call to it. */ (void) get_fp(); } /* * This routine is called while the kernel is running. It attempts to determine * whether any deferred breakpoints exist for the module being changed (loaded * or unloaded). If any such breakpoints exist, the debugger will be entered to * process them. */ static void kmt_defbp_modchg(struct modctl *mctl, int isload) { kmt_defbp_t *dbp; kmt_defbp_lock = 1; for (dbp = mdb_list_next(&kmt_defbp_list); dbp; dbp = mdb_list_next(dbp)) { if (!dbp->dbp_ref) continue; if (strcmp(mctl->mod_modname, dbp->dbp_objname) == 0) { /* * Activate the breakpoint */ kmt_defbp_modchg_isload = isload; kmt_defbp_modchg_modctl = mctl; kmt_defbp_enter_debugger(); break; } } kmt_defbp_lock = 0; } /*ARGSUSED*/ static int kmt_continue(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { int n; kmdb_dpi_resume(); /* * The order of the following two calls is important. If there are * load acks on the work queue, we'll initialize the dmods they * represent. This will involve a call to _mdb_init, which may very * well result in a symbol lookup. If we haven't resynced our view * of symbols with the current state of the world, this lookup could * end very badly. We therefore make sure to sync before processing * the work queue. */ kmt_sync(t); kmdb_dpi_process_work_queue(); if (kmdb_kdi_get_unload_request()) t->t_flags |= MDB_TGT_F_UNLOAD; (void) mdb_tgt_status(t, &t->t_status); if ((n = kmt_dmod_status(NULL, KMDB_MC_STATE_LOADING) + kmt_dmod_status(NULL, KMDB_MC_STATE_UNLOADING)) != 0) { mdb_warn("%d dmod load%c/unload%c pending\n", n, "s"[n == 1], "s"[n == 1]); } return (0); } /*ARGSUSED*/ static int kmt_step(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { int rc; if ((rc = kmdb_dpi_step()) == 0) (void) mdb_tgt_status(t, &t->t_status); return (rc); } static int kmt_defbp_activate(mdb_tgt_t *t) { kmdb_dpi_modchg_register(kmt_defbp_modchg); /* * The routines that add and arm breakpoints will check for the proper * DTrace state, but they'll just put this breakpoint on the idle list * if DTrace is active. It'll correctly move to the active list when * DTrace deactivates, but that's insufficient for our purposes -- we * need to do extra processing at that point. We won't get to do said * processing with with a normal idle->active transition, so we just * won't add it add it until we're sure that it'll stick. */ if (kmdb_kdi_dtrace_get_state() == KDI_DTSTATE_DTRACE_ACTIVE) return (set_errno(EMDB_DTACTIVE)); kmt_defbp_bpspec = mdb_tgt_add_vbrkpt(t, (uintptr_t)kmt_defbp_enter_debugger, MDB_TGT_SPEC_HIDDEN, kmt_defbp_event, NULL); return (0); } static void kmt_defbp_deactivate(mdb_tgt_t *t) { kmdb_dpi_modchg_cancel(); if (kmt_defbp_bpspec != 0) { if (t != NULL) (void) mdb_tgt_vespec_delete(t, kmt_defbp_bpspec); kmt_defbp_bpspec = 0; } } static kmt_defbp_t * kmt_defbp_create(mdb_tgt_t *t, const char *objname, const char *symname) { kmt_defbp_t *dbp = mdb_alloc(sizeof (kmt_defbp_t), UM_SLEEP); mdb_dprintf(MDB_DBG_KMOD, "defbp_create %s`%s\n", objname, symname); dbp->dbp_objname = strdup(objname); dbp->dbp_symname = strdup(symname); dbp->dbp_ref = 1; kmt_defbp_num++; if (kmt_defbp_num == 1 || kmt_defbp_bpspec == 0) { if (kmt_defbp_activate(t) < 0) warn("failed to activate deferred breakpoints"); } mdb_list_append(&kmt_defbp_list, dbp); return (dbp); } static void kmt_defbp_destroy(kmt_defbp_t *dbp) { mdb_dprintf(MDB_DBG_KMOD, "defbp_destroy %s`%s\n", dbp->dbp_objname, dbp->dbp_symname); mdb_list_delete(&kmt_defbp_list, dbp); strfree(dbp->dbp_objname); strfree(dbp->dbp_symname); mdb_free(dbp, sizeof (kmt_defbp_t)); } static void kmt_defbp_prune_common(int all) { kmt_defbp_t *dbp, *ndbp; /* We can't remove items from the list while the driver is using it. */ if (kmt_defbp_lock) return; for (dbp = mdb_list_next(&kmt_defbp_list); dbp != NULL; dbp = ndbp) { ndbp = mdb_list_next(dbp); if (!all && dbp->dbp_ref) continue; kmt_defbp_destroy(dbp); } } static void kmt_defbp_prune(void) { kmt_defbp_prune_common(0); } static void kmt_defbp_destroy_all(void) { kmt_defbp_prune_common(1); } static void kmt_defbp_delete(mdb_tgt_t *t, kmt_defbp_t *dbp) { dbp->dbp_ref = 0; ASSERT(kmt_defbp_num > 0); kmt_defbp_num--; if (kmt_defbp_num == 0) kmt_defbp_deactivate(t); kmt_defbp_prune(); } static int kmt_brkpt_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { mdb_tgt_status_t tsp; kmt_bparg_t *ka = args; kmt_brkpt_t *kb; GElf_Sym s; mdb_instr_t instr; (void) mdb_tgt_status(t, &tsp); if (tsp.st_state != MDB_TGT_RUNNING && tsp.st_state != MDB_TGT_STOPPED) return (set_errno(EMDB_NOPROC)); if (ka->ka_symbol != NULL) { if (mdb_tgt_lookup_by_scope(t, ka->ka_symbol, &s, NULL) == -1) { if (errno != EMDB_NOOBJ && !(errno == EMDB_NOSYM && !(mdb.m_flags & MDB_FL_BPTNOSYMSTOP))) { warn("breakpoint %s activation failed", ka->ka_symbol); } return (-1); /* errno is set for us */ } ka->ka_addr = (uintptr_t)s.st_value; } #ifdef __sparc if (ka->ka_addr & 3) return (set_errno(EMDB_BPALIGN)); #endif if (mdb_vread(&instr, sizeof (instr), ka->ka_addr) != sizeof (instr)) return (-1); /* errno is set for us */ if (kmdb_kdi_dtrace_get_state() == KDI_DTSTATE_DTRACE_ACTIVE) warn("breakpoint will not arm until DTrace is inactive\n"); kb = mdb_zalloc(sizeof (kmt_brkpt_t), UM_SLEEP); kb->kb_addr = ka->ka_addr; sep->se_data = kb; return (0); } /*ARGSUSED*/ static void kmt_brkpt_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { mdb_free(sep->se_data, sizeof (kmt_brkpt_t)); } /*ARGSUSED*/ static char * kmt_brkpt_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { uintptr_t addr = 0; if (vep != NULL) { kmt_bparg_t *ka = vep->ve_args; if (ka->ka_symbol != NULL) { (void) mdb_iob_snprintf(buf, nbytes, "stop at %s", ka->ka_symbol); } else { (void) mdb_iob_snprintf(buf, nbytes, "stop at %a", ka->ka_addr); addr = ka->ka_addr; } } else { addr = ((kmt_brkpt_t *)sep->se_data)->kb_addr; (void) mdb_iob_snprintf(buf, nbytes, "stop at %a", addr); } sp->spec_base = addr; sp->spec_size = sizeof (mdb_instr_t); return (buf); } static int kmt_brkpt_secmp(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { kmt_brkpt_t *kb = sep->se_data; kmt_bparg_t *ka = args; GElf_Sym sym; if (ka->ka_symbol != NULL) { return (mdb_tgt_lookup_by_scope(t, ka->ka_symbol, &sym, NULL) == 0 && sym.st_value == kb->kb_addr); } return (ka->ka_addr == kb->kb_addr); } /*ARGSUSED*/ static int kmt_brkpt_vecmp(mdb_tgt_t *t, mdb_vespec_t *vep, void *args) { kmt_bparg_t *ka1 = vep->ve_args; kmt_bparg_t *ka2 = args; if (ka1->ka_symbol != NULL && ka2->ka_symbol != NULL) return (strcmp(ka1->ka_symbol, ka2->ka_symbol) == 0); if (ka1->ka_symbol == NULL && ka2->ka_symbol == NULL) return (ka1->ka_addr == ka2->ka_addr); return (0); /* fail if one is symbolic, other is an explicit address */ } static int kmt_brkpt_arm(mdb_tgt_t *t, mdb_sespec_t *sep) { kmt_data_t *kmt = t->t_data; kmt_brkpt_t *kb = sep->se_data; int rv; if (kmdb_kdi_dtrace_get_state() == KDI_DTSTATE_DTRACE_ACTIVE) return (set_errno(EMDB_DTACTIVE)); if ((rv = kmdb_dpi_brkpt_arm(kb->kb_addr, &kb->kb_oinstr)) != 0) return (rv); if (kmt->kmt_narmedbpts++ == 0) (void) kmdb_kdi_dtrace_set(KDI_DTSET_KMDB_BPT_ACTIVATE); return (0); } static int kmt_brkpt_disarm(mdb_tgt_t *t, mdb_sespec_t *sep) { kmt_data_t *kmt = t->t_data; kmt_brkpt_t *kb = sep->se_data; int rv; ASSERT(kmdb_kdi_dtrace_get_state() == KDI_DTSTATE_KMDB_BPT_ACTIVE); if ((rv = kmdb_dpi_brkpt_disarm(kb->kb_addr, kb->kb_oinstr)) != 0) return (rv); if (--kmt->kmt_narmedbpts == 0) (void) kmdb_kdi_dtrace_set(KDI_DTSET_KMDB_BPT_DEACTIVATE); return (0); } /* * Determine whether the specified sespec is an armed watchpoint that overlaps * with the given breakpoint and has the given flags set. We use this to find * conflicts with breakpoints, below. */ static int kmt_wp_overlap(mdb_sespec_t *sep, kmt_brkpt_t *kb, int flags) { const kmdb_wapt_t *wp = sep->se_data; return (sep->se_state == MDB_TGT_SPEC_ARMED && sep->se_ops == &kmt_wapt_ops && (wp->wp_wflags & flags) && kb->kb_addr - wp->wp_addr < wp->wp_size); } /* * We step over breakpoints using our single-stepper. If a conflicting * watchpoint is present, we must temporarily remove it before stepping over the * breakpoint so we don't immediately re-trigger the watchpoint. We know the * watchpoint has already triggered on our trap instruction as part of fetching * it. Before we return, we must re-install any disabled watchpoints. */ static int kmt_brkpt_cont(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { kmt_brkpt_t *kb = sep->se_data; int status = -1; int error; for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (kmt_wp_overlap(sep, kb, MDB_TGT_WA_X)) (void) kmdb_dpi_wapt_disarm(sep->se_data); } if (kmdb_dpi_brkpt_disarm(kb->kb_addr, kb->kb_oinstr) == 0 && kmt_step(t, tsp) == 0) status = kmt_status(t, tsp); error = errno; /* save errno from disarm, step, or status */ for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (kmt_wp_overlap(sep, kb, MDB_TGT_WA_X)) kmdb_dpi_wapt_arm(sep->se_data); } (void) set_errno(error); return (status); } /*ARGSUSED*/ static int kmt_brkpt_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { kmt_brkpt_t *kb = sep->se_data; int state, why; kreg_t pc; state = kmdb_dpi_get_state(&why); (void) kmdb_dpi_get_register("pc", &pc); return (state == DPI_STATE_FAULTED && why == DPI_STATE_WHY_BKPT && pc == kb->kb_addr); } static const mdb_se_ops_t kmt_brkpt_ops = { .se_ctor = kmt_brkpt_ctor, .se_dtor = kmt_brkpt_dtor, .se_info = kmt_brkpt_info, .se_secmp = kmt_brkpt_secmp, .se_vecmp = kmt_brkpt_vecmp, .se_arm = kmt_brkpt_arm, .se_disarm = kmt_brkpt_disarm, .se_cont = kmt_brkpt_cont, .se_match = kmt_brkpt_match, }; static int kmt_wapt_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { mdb_tgt_status_t tsp; kmdb_wapt_t *vwp = args; kmdb_wapt_t *swp; (void) mdb_tgt_status(t, &tsp); if (tsp.st_state != MDB_TGT_RUNNING && tsp.st_state != MDB_TGT_STOPPED) return (set_errno(EMDB_NOPROC)); swp = mdb_alloc(sizeof (kmdb_wapt_t), UM_SLEEP); bcopy(vwp, swp, sizeof (kmdb_wapt_t)); if (kmdb_dpi_wapt_reserve(swp) < 0) { mdb_free(swp, sizeof (kmdb_wapt_t)); return (-1); /* errno is set for us */ } sep->se_data = swp; return (0); } /*ARGSUSED*/ static void kmt_wapt_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { kmdb_wapt_t *wp = sep->se_data; kmdb_dpi_wapt_release(wp); mdb_free(wp, sizeof (kmdb_wapt_t)); } /*ARGSUSED*/ static char * kmt_wapt_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { kmdb_wapt_t *wp = vep != NULL ? vep->ve_args : sep->se_data; const char *fmt; char desc[24]; ASSERT(wp->wp_wflags != 0); desc[0] = '\0'; switch (wp->wp_wflags) { case MDB_TGT_WA_R: (void) strcat(desc, "/read"); break; case MDB_TGT_WA_W: (void) strcat(desc, "/write"); break; case MDB_TGT_WA_X: (void) strcat(desc, "/exec"); break; default: if (wp->wp_wflags & MDB_TGT_WA_R) (void) strcat(desc, "/r"); if (wp->wp_wflags & MDB_TGT_WA_W) (void) strcat(desc, "/w"); if (wp->wp_wflags & MDB_TGT_WA_X) (void) strcat(desc, "/x"); } switch (wp->wp_type) { case DPI_WAPT_TYPE_PHYS: fmt = "stop on %s of phys [%p, %p)"; break; case DPI_WAPT_TYPE_VIRT: fmt = "stop on %s of [%la, %la)"; break; case DPI_WAPT_TYPE_IO: if (wp->wp_size == 1) fmt = "stop on %s of I/O port %p"; else fmt = "stop on %s of I/O port [%p, %p)"; break; default: fmt = "stop on %s of unknown [%p, %p]"; break; } (void) mdb_iob_snprintf(buf, nbytes, fmt, desc + 1, wp->wp_addr, wp->wp_addr + wp->wp_size); sp->spec_base = wp->wp_addr; sp->spec_size = wp->wp_size; return (buf); } /*ARGSUSED*/ static int kmt_wapt_secmp(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { kmdb_wapt_t *wp1 = sep->se_data; kmdb_wapt_t *wp2 = args; return (wp1->wp_addr == wp2->wp_addr && wp1->wp_size == wp2->wp_size && wp1->wp_wflags == wp2->wp_wflags); } /*ARGSUSED*/ static int kmt_wapt_vecmp(mdb_tgt_t *t, mdb_vespec_t *vep, void *args) { kmdb_wapt_t *wp1 = vep->ve_args; kmdb_wapt_t *wp2 = args; return (wp1->wp_addr == wp2->wp_addr && wp1->wp_size == wp2->wp_size && wp1->wp_wflags == wp2->wp_wflags); } /*ARGSUSED*/ static int kmt_wapt_arm(mdb_tgt_t *t, mdb_sespec_t *sep) { kmdb_dpi_wapt_arm(sep->se_data); return (0); } /*ARGSUSED*/ static int kmt_wapt_disarm(mdb_tgt_t *t, mdb_sespec_t *sep) { kmdb_dpi_wapt_disarm(sep->se_data); return (0); } /* * Determine whether the specified sespec is an armed breakpoint at the given * %pc. We use this to find conflicts with watchpoints below. */ static int kmt_bp_overlap(mdb_sespec_t *sep, uintptr_t pc) { kmt_brkpt_t *kb = sep->se_data; return (sep->se_state == MDB_TGT_SPEC_ARMED && sep->se_ops == &kmt_brkpt_ops && kb->kb_addr == pc); } /* * We step over watchpoints using our single-stepper. If a conflicting * breakpoint is present, we must temporarily disarm it before stepping over * the watchpoint so we do not immediately re-trigger the breakpoint. This is * similar to the case handled in kmt_brkpt_cont(), above. */ static int kmt_wapt_cont(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { mdb_sespec_t *bep = NULL; int status = -1; int error, why; /* * If we stopped for anything other than a watchpoint, check to see * if there's a breakpoint here. */ if (!(kmdb_dpi_get_state(&why) == DPI_STATE_FAULTED && (why == DPI_STATE_WHY_V_WAPT || why == DPI_STATE_WHY_P_WAPT))) { kreg_t pc; (void) kmdb_dpi_get_register("pc", &pc); for (bep = mdb_list_next(&t->t_active); bep != NULL; bep = mdb_list_next(bep)) { if (kmt_bp_overlap(bep, pc)) { (void) bep->se_ops->se_disarm(t, bep); bep->se_state = MDB_TGT_SPEC_ACTIVE; break; } } } kmdb_dpi_wapt_disarm(sep->se_data); if (kmt_step(t, tsp) == 0) status = kmt_status(t, tsp); error = errno; /* save errno from step or status */ if (bep != NULL) mdb_tgt_sespec_arm_one(t, bep); (void) set_errno(error); return (status); } /*ARGSUSED*/ static int kmt_wapt_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { return (kmdb_dpi_wapt_match(sep->se_data)); } static const mdb_se_ops_t kmt_wapt_ops = { .se_ctor = kmt_wapt_ctor, .se_dtor = kmt_wapt_dtor, .se_info = kmt_wapt_info, .se_secmp = kmt_wapt_secmp, .se_vecmp = kmt_wapt_vecmp, .se_arm = kmt_wapt_arm, .se_disarm = kmt_wapt_disarm, .se_cont = kmt_wapt_cont, .se_match = kmt_wapt_match, }; /*ARGSUSED*/ static int kmt_trap_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { sep->se_data = args; /* trap number */ return (0); } /*ARGSUSED*/ static char * kmt_trap_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { const char *name; int trapnum; if (vep != NULL) trapnum = (intptr_t)vep->ve_args; else trapnum = (intptr_t)sep->se_data; if (trapnum == KMT_TRAP_ALL) name = "any trap"; else if (trapnum == KMT_TRAP_NOTENUM) name = "miscellaneous trap"; else name = kmt_trapname(trapnum); (void) mdb_iob_snprintf(buf, nbytes, "single-step stop on %s", name); return (buf); } /*ARGSUSED2*/ static int kmt_trap_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { int spectt = (intptr_t)sep->se_data; kmt_data_t *kmt = t->t_data; kreg_t tt; (void) kmdb_dpi_get_register("tt", &tt); switch (spectt) { case KMT_TRAP_ALL: return (1); case KMT_TRAP_NOTENUM: return (tt > kmt->kmt_trapmax || !BT_TEST(kmt->kmt_trapmap, tt)); default: return (tt == spectt); } } static const mdb_se_ops_t kmt_trap_ops = { .se_ctor = kmt_trap_ctor, .se_dtor = no_se_dtor, .se_info = kmt_trap_info, .se_secmp = no_se_secmp, .se_vecmp = no_se_vecmp, .se_arm = no_se_arm, .se_disarm = no_se_disarm, .se_cont = no_se_cont, .se_match = kmt_trap_match, }; static void kmt_bparg_dtor(mdb_vespec_t *vep) { kmt_bparg_t *ka = vep->ve_args; if (ka->ka_symbol != NULL) strfree(ka->ka_symbol); if (ka->ka_defbp != NULL) kmt_defbp_delete(mdb.m_target, ka->ka_defbp); mdb_free(ka, sizeof (kmt_bparg_t)); } static int kmt_add_vbrkpt(mdb_tgt_t *t, uintptr_t addr, int spec_flags, mdb_tgt_se_f *func, void *data) { kmt_bparg_t *ka = mdb_alloc(sizeof (kmt_bparg_t), UM_SLEEP); ka->ka_addr = addr; ka->ka_symbol = NULL; ka->ka_defbp = NULL; return (mdb_tgt_vespec_insert(t, &kmt_brkpt_ops, spec_flags, func, data, ka, kmt_bparg_dtor)); } static int kmt_add_sbrkpt(mdb_tgt_t *t, const char *fullname, int spec_flags, mdb_tgt_se_f *func, void *data) { kmt_bparg_t *ka; kmt_defbp_t *dbp; GElf_Sym sym; char *tick, *objname, *symname; int serrno; if ((tick = strchr(fullname, '`')) == fullname) { (void) set_errno(EMDB_NOOBJ); return (0); } /* * Deferred breakpoints are always scoped. If we didn't find a tick, * there's no scope. We'll create a vbrkpt, but only if we can turn the * provided string into an address. */ if (tick == NULL) { uintptr_t addr; if (strisbasenum(fullname)) { addr = mdb_strtoull(fullname); /* a bare address */ } else if (mdb_tgt_lookup_by_name(t, MDB_TGT_OBJ_EVERY, fullname, &sym, NULL) < 0) { (void) set_errno(EMDB_NOSYM); return (0); } else { addr = (uintptr_t)sym.st_value; /* unscoped sym name */ } return (kmt_add_vbrkpt(t, addr, spec_flags, func, data)); } if (*(tick + 1) == '\0') { (void) set_errno(EMDB_NOSYM); return (0); } objname = strndup(fullname, tick - fullname); symname = tick + 1; if (mdb_tgt_lookup_by_name(t, objname, symname, NULL, NULL) < 0 && errno != EMDB_NOOBJ) { serrno = errno; strfree(objname); (void) set_errno(serrno); return (0); /* errno is set for us */ } dbp = kmt_defbp_create(t, objname, symname); strfree(objname); ka = mdb_alloc(sizeof (kmt_bparg_t), UM_SLEEP); ka->ka_symbol = strdup(fullname); ka->ka_addr = 0; ka->ka_defbp = dbp; return (mdb_tgt_vespec_insert(t, &kmt_brkpt_ops, spec_flags, func, data, ka, kmt_bparg_dtor)); } static int kmt_wparg_overlap(const kmdb_wapt_t *wp1, const kmdb_wapt_t *wp2) { /* Assume the watchpoint spaces don't overlap */ if (wp1->wp_type != wp2->wp_type) return (0); if (wp2->wp_addr + wp2->wp_size <= wp1->wp_addr) return (0); /* no range overlap */ if (wp1->wp_addr + wp1->wp_size <= wp2->wp_addr) return (0); /* no range overlap */ return (wp1->wp_addr != wp2->wp_addr || wp1->wp_size != wp2->wp_size || wp1->wp_wflags != wp2->wp_wflags); } static void kmt_wparg_dtor(mdb_vespec_t *vep) { mdb_free(vep->ve_args, sizeof (kmdb_wapt_t)); } static int kmt_add_wapt_common(mdb_tgt_t *t, uintptr_t addr, size_t len, uint_t wflags, int spec_flags, mdb_tgt_se_f *func, void *data, int type) { kmdb_wapt_t *wp = mdb_alloc(sizeof (kmdb_wapt_t), UM_SLEEP); mdb_sespec_t *sep; wp->wp_addr = addr; wp->wp_size = len; wp->wp_type = type; wp->wp_wflags = wflags; if (kmdb_dpi_wapt_validate(wp) < 0) return (0); /* errno is set for us */ for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (sep->se_ops == &kmt_wapt_ops && mdb_list_next(&sep->se_velist) != NULL && kmt_wparg_overlap(wp, sep->se_data)) goto wapt_dup; } for (sep = mdb_list_next(&t->t_idle); sep; sep = mdb_list_next(sep)) { if (sep->se_ops == &kmt_wapt_ops && kmt_wparg_overlap(wp, ((mdb_vespec_t *)mdb_list_next(&sep->se_velist))->ve_args)) goto wapt_dup; } return (mdb_tgt_vespec_insert(t, &kmt_wapt_ops, spec_flags, func, data, wp, kmt_wparg_dtor)); wapt_dup: mdb_free(wp, sizeof (kmdb_wapt_t)); (void) set_errno(EMDB_WPDUP); return (0); } static int kmt_add_pwapt(mdb_tgt_t *t, physaddr_t addr, size_t len, uint_t wflags, int spec_flags, mdb_tgt_se_f *func, void *data) { return (kmt_add_wapt_common(t, (uintptr_t)addr, len, wflags, spec_flags, func, data, DPI_WAPT_TYPE_PHYS)); } static int kmt_add_vwapt(mdb_tgt_t *t, uintptr_t addr, size_t len, uint_t wflags, int spec_flags, mdb_tgt_se_f *func, void *data) { return (kmt_add_wapt_common(t, addr, len, wflags, spec_flags, func, data, DPI_WAPT_TYPE_VIRT)); } static int kmt_add_iowapt(mdb_tgt_t *t, uintptr_t addr, size_t len, uint_t wflags, int spec_flags, mdb_tgt_se_f *func, void *data) { return (kmt_add_wapt_common(t, addr, len, wflags, spec_flags, func, data, DPI_WAPT_TYPE_IO)); } static int kmt_add_trap(mdb_tgt_t *t, int trapnum, int spec_flags, mdb_tgt_se_f *func, void *data) { kmt_data_t *kmt = t->t_data; if (trapnum != KMT_TRAP_ALL && trapnum != KMT_TRAP_NOTENUM) { if (trapnum < 0 || trapnum > kmt->kmt_trapmax) { (void) set_errno(EMDB_BADFLTNUM); return (0); } BT_SET(kmt->kmt_trapmap, trapnum); } return (mdb_tgt_vespec_insert(t, &kmt_trap_ops, spec_flags, func, data, (void *)(uintptr_t)trapnum, no_ve_dtor)); } /*ARGSUSED*/ static uintmax_t kmt_cpuid_disc_get(const mdb_var_t *v) { return (kmdb_dpi_get_master_cpuid()); } static const mdb_nv_disc_t kmt_cpuid_disc = { .disc_get = kmt_cpuid_disc_get }; /* * This routine executes while the kernel is running. */ void kmt_activate(mdb_tgt_t *t) { kmt_data_t *kmt = t->t_data; mdb_prop_postmortem = FALSE; mdb_prop_kernel = TRUE; (void) mdb_tgt_register_dcmds(t, &kmt_dcmds[0], MDB_MOD_FORCE); mdb_tgt_register_regvars(t, kmt->kmt_rds, &kmt_reg_disc, 0); /* * Force load of the MDB krtld module, in case it's been rolled into * unix. */ (void) mdb_module_load(KMT_RTLD_NAME, MDB_MOD_SILENT | MDB_MOD_DEFER); } static void kmt_destroy(mdb_tgt_t *t) { kmt_data_t *kmt = t->t_data; kmt_module_t *km, *pkm; mdb_nv_destroy(&kmt->kmt_modules); for (km = mdb_list_prev(&kmt->kmt_modlist); km != NULL; km = pkm) { pkm = mdb_list_prev(km); mdb_free(km, sizeof (kmt_module_t)); } if (!kmt_defbp_lock) kmt_defbp_destroy_all(); if (kmt->kmt_trapmap != NULL) mdb_free(kmt->kmt_trapmap, BT_SIZEOFMAP(kmt->kmt_trapmax)); mdb_free(kmt, sizeof (kmt_data_t)); } static const mdb_tgt_ops_t kmt_ops = { .t_setflags = kmt_setflags, .t_setcontext = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_activate = kmt_activate, .t_deactivate = (void (*)())(uintptr_t)mdb_tgt_nop, .t_periodic = kmt_periodic, .t_destroy = kmt_destroy, .t_name = kmt_name, .t_isa = (const char *(*)())mdb_conf_isa, .t_platform = kmt_platform, .t_uname = kmt_uname, .t_dmodel = kmt_dmodel, .t_aread = (ssize_t (*)())mdb_tgt_notsup, .t_awrite = (ssize_t (*)())mdb_tgt_notsup, .t_vread = kmt_read, .t_vwrite = kmt_write, .t_pread = kmt_pread, .t_pwrite = kmt_pwrite, .t_fread = kmt_read, .t_fwrite = kmt_write, .t_ioread = kmt_ioread, .t_iowrite = kmt_iowrite, .t_vtop = kmt_vtop, .t_lookup_by_name = kmt_lookup_by_name, .t_lookup_by_addr = kmt_lookup_by_addr, .t_symbol_iter = kmt_symbol_iter, .t_mapping_iter = kmt_mapping_iter, .t_object_iter = kmt_object_iter, .t_addr_to_map = kmt_addr_to_map, .t_name_to_map = kmt_name_to_map, .t_addr_to_ctf = kmt_addr_to_ctf, .t_name_to_ctf = kmt_name_to_ctf, .t_status = kmt_status, .t_run = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_step = kmt_step, .t_step_out = kmt_step_out, .t_next = kmt_next, .t_cont = kmt_continue, .t_signal = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_add_vbrkpt = kmt_add_vbrkpt, .t_add_sbrkpt = kmt_add_sbrkpt, .t_add_pwapt = kmt_add_pwapt, .t_add_vwapt = kmt_add_vwapt, .t_add_iowapt = kmt_add_iowapt, .t_add_sysenter = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sysexit = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_signal = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_fault = kmt_add_trap, .t_getareg = kmt_getareg, .t_putareg = kmt_putareg, .t_stack_iter = (int (*)())(uintptr_t)mdb_tgt_nop, /* XXX */ .t_auxv = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_thread_name = (int (*)())(uintptr_t)mdb_tgt_notsup, }; /* * Called immediately upon resumption of the system after a step or continue. * Allows us to synchronize kmt's view of the world with reality. */ /*ARGSUSED*/ static void kmt_sync(mdb_tgt_t *t) { kmt_data_t *kmt = t->t_data; int symavail; mdb_dprintf(MDB_DBG_KMOD, "synchronizing with kernel\n"); symavail = kmt->kmt_symavail; kmt->kmt_symavail = FALSE; /* * Resync our view of the world if the modules have changed, or if we * didn't have any symbols coming into this function. The latter will * only happen on startup. */ if (kmdb_kdi_mods_changed() || !symavail) kmt_modlist_update(t); /* * It would be nice if we could run this less frequently, perhaps * after a dvec-initiated trigger. */ kmdb_module_sync(); kmt->kmt_symavail = TRUE; mdb_dprintf(MDB_DBG_KMOD, "synchronization complete\n"); kmt_defbp_prune(); if (kmt_defbp_num > 0 && kmt_defbp_bpspec == 0 && kmdb_kdi_dtrace_get_state() != KDI_DTSTATE_DTRACE_ACTIVE) { /* * Deferred breakpoints were created while DTrace was active, * and consequently the deferred breakpoint enabling mechanism * wasn't activated. Activate it now, and then try to activate * the deferred breakpoints. We do this so that we can catch * the ones which may apply to modules that have been loaded * while they were waiting for DTrace to deactivate. */ (void) kmt_defbp_activate(t); (void) mdb_tgt_sespec_activate_all(t); } (void) mdb_tgt_status(t, &t->t_status); } /* * This routine executes while the kernel is running. */ /*ARGSUSED*/ int kmdb_kvm_create(mdb_tgt_t *t, int argc, const char *argv[]) { kmt_data_t *kmt; if (argc != 0) return (set_errno(EINVAL)); kmt = mdb_zalloc(sizeof (kmt_data_t), UM_SLEEP); t->t_data = kmt; t->t_ops = &kmt_ops; t->t_flags |= MDB_TGT_F_RDWR; /* kmdb is always r/w */ (void) mdb_nv_insert(&mdb.m_nv, "cpuid", &kmt_cpuid_disc, 0, MDB_NV_PERSIST | MDB_NV_RDONLY); (void) mdb_nv_create(&kmt->kmt_modules, UM_SLEEP); kmt_init_isadep(t); kmt->kmt_symavail = FALSE; bzero(&kmt_defbp_list, sizeof (mdb_list_t)); return (0); } /* * This routine is called once, when kmdb first has control of the world. */ void kmdb_kvm_startup(void) { kmt_data_t *kmt = mdb.m_target->t_data; mdb_dprintf(MDB_DBG_KMOD, "kmdb_kvm startup\n"); kmt_sync(mdb.m_target); (void) mdb_module_load_builtin(KMT_MODULE); kmt_startup_isadep(mdb.m_target); /* * This is here because we need to write the deferred breakpoint * breakpoint when the debugger starts. Our normal r/o write routines * don't work when the kernel is running, so we have to do it during * startup. */ (void) mdb_tgt_sespec_activate_all(mdb.m_target); kmt->kmt_rtld_name = KMT_RTLD_NAME; if (kmt_module_by_name(kmt, KMT_RTLD_NAME) == NULL) kmt->kmt_rtld_name = "unix"; } /* * This routine is called after kmdb has loaded its initial set of modules. */ void kmdb_kvm_poststartup(void) { mdb_dprintf(MDB_DBG_KMOD, "kmdb_kvm post-startup\n"); (void) mdb_dis_select(kmt_def_dismode()); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_KVM_H #define _KMDB_KVM_H /* * External, non-constructor interfaces for kmdb_kvm. */ #include #include #include #include #ifdef __cplusplus extern "C" { #endif extern void kmdb_kvm_startup(void); extern void kmdb_kvm_poststartup(void); extern void kmdb_kvm_defbp_process(uint_t, struct modctl *); #ifdef __cplusplus } #endif #endif /* _KMDB_KVM_H */ /* * 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. */ #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 #include #include #include #include #ifdef __sparc #define KMDB_STACK_SIZE (384 * 1024) #else #define KMDB_STACK_SIZE (192 * 1024) #endif caddr_t kmdb_main_stack; size_t kmdb_main_stack_size; #define KMDB_DEF_IPATH "internal" #if defined(_LP64) #define KMDB_DEF_LPATH \ "%r/platform/%p/kernel/kmdb/%i:" \ "%r/platform/%m/kernel/kmdb/%i:" \ "%r/kernel/kmdb/%i" #else #define KMDB_DEF_LPATH \ "%r/platform/%m/kernel/kmdb:" \ "%r/kernel/kmdb" #endif #define MDB_DEF_PROMPT "[%<_cpuid>]> " #define KMDB_DEF_TERM_TYPE "vt100" /* * Similar to the panic_* variables in the kernel, we keep some relevant * information stored in a set of global _mdb_abort_* variables; in the * event that the debugger dumps core, these will aid core dump analysis. */ const char *volatile _mdb_abort_str; /* reason for failure */ /* * The kernel supplies a space-delimited list of directories * (/platform/sun4u/kernel /kernel /usr/kernel ...) which we must transform into * a debugger-friendly, colon-delimited module search path. We add the kmdb * module directory to each component and change the delimiter. */ static char * kmdb_modpath2lpath(const char *modpath) { #ifdef _LP64 static const char suffix[] = "/kmdb/%i:"; #else static const char suffix[] = "/kmdb:"; #endif const char *c; char *lpath, *lpend, *nlpath; size_t lpsz, lpres; if (strchr(modpath, ':') != NULL) { warn("invalid kernel module path\n"); return (NULL); } lpres = lpsz = strlen(modpath) + MAXPATHLEN; lpend = lpath = mdb_zalloc(lpsz, UM_SLEEP); while (isspace(*modpath)) modpath++; for (; *modpath != '\0'; modpath = c) { size_t sz; for (c = modpath; !isspace(*c) && *c != '\0'; c++) continue; sz = (c - modpath) + sizeof (suffix) - 1; if (sz >= lpres) continue; (void) strncpy(lpend, modpath, c - modpath); (void) strcpy(lpend + (c - modpath), suffix); lpend += sz; lpres -= sz; while (isspace(*c)) c++; } if (lpend != lpath) lpend[-1] = '\0'; /* eat trailing colon */ nlpath = strdup(lpath); mdb_free(lpath, lpsz); return (nlpath); } /* * called while the kernel is running */ int kmdb_init(const char *execname, kmdb_auxv_t *kav) { mdb_io_t *in_io, *out_io, *err_io, *null_io; mdb_tgt_ctor_f *tgt_ctor = kmdb_kvm_create; mdb_tgt_t *tgt; int i; /* * The beginnings of debugger initialization are a bit of a dance, due * to interlocking dependencies between kmdb_prom_init, * mdb_umem_startup, and mdb_create. In particular, allocator * initialization can't begin until prom_init() is called, * kmdb_prom_init can't finish until the allocator is ready and * mdb_create has been called. We therefore split kmdb_prom_init into * two pieces, and call thembefore and after umem initialization and * mdb_create. */ kmdb_prom_init_begin("kmdb", kav); mdb_umem_startup(kav->kav_dseg, kav->kav_dseg_size, kav->kav_pagesize); mdb_create(execname, "kmdb"); kmdb_prom_init_finish(kav); mdb.m_dseg = kav->kav_dseg; mdb.m_dsegsz = kav->kav_dseg_size; out_io = kmdb_promio_create("stdout"); mdb.m_out = mdb_iob_create(out_io, MDB_IOB_WRONLY); err_io = kmdb_promio_create("stderr"); mdb.m_err = mdb_iob_create(err_io, MDB_IOB_WRONLY); mdb_iob_clrflags(mdb.m_err, MDB_IOB_AUTOWRAP); null_io = mdb_nullio_create(); mdb.m_null = mdb_iob_create(null_io, MDB_IOB_WRONLY); in_io = kmdb_promio_create("stdin"); mdb.m_term = NULL; if (kav->kav_config != NULL) mdb_set_config(kav->kav_config); if (kav->kav_argv != NULL) { for (i = 0; kav->kav_argv[i] != NULL; i++) { if (!mdb_set_options(kav->kav_argv[i], TRUE)) return (-1); } } if (kav->kav_flags & KMDB_AUXV_FL_NOUNLOAD) mdb.m_flags |= MDB_FL_NOUNLOAD; mdb.m_in = mdb_iob_create(in_io, MDB_IOB_RDONLY); mdb_iob_setflags(mdb.m_in, MDB_IOB_TTYLIKE); mdb_lex_reset(); kmdb_kdi_init(kav->kav_kdi, kav); if (kmdb_dpi_init(kav) < 0) { warn("Couldn't initialize kernel/PROM interface\n"); return (-1); } /* * Path evaluation part 1: Create the initial module path to allow * the target constructor to load a support module. We base kmdb's * module path off the kernel's module path unless the user has * explicitly supplied one. */ mdb_set_ipath(KMDB_DEF_IPATH); if (strlen(mdb.m_lpathstr) > 0) { mdb_set_lpath(mdb.m_lpathstr); } else { char *lpath; if (kav->kav_modpath != NULL && *kav->kav_modpath != '\0' && (lpath = kmdb_modpath2lpath(kav->kav_modpath)) != NULL) { mdb_set_lpath(lpath); strfree(lpath); } else { mdb_set_lpath(KMDB_DEF_LPATH); } } if (mdb_get_prompt() == NULL) (void) mdb_set_prompt(MDB_DEF_PROMPT); tgt = mdb_tgt_create(tgt_ctor, mdb.m_tgtflags, 0, NULL); if (tgt == NULL) { warn("failed to initialize target"); return (-1); } mdb_tgt_activate(tgt); mdb_create_loadable_disasms(); /* * Path evaluation part 2: Re-evaluate the path now that the target * is ready (and thus we have access to the real platform string). */ mdb_set_ipath(mdb.m_ipathstr); mdb_set_lpath(mdb.m_lpathstr); if (!(mdb.m_flags & MDB_FL_NOMODS)) mdb_module_load_all(MDB_MOD_DEFER); /* Allocate the main debugger stack */ kmdb_main_stack = mdb_alloc(KMDB_STACK_SIZE, UM_SLEEP); kmdb_main_stack_size = KMDB_STACK_SIZE; kmdb_kdi_end_init(); return (0); } #ifdef sun4v void kmdb_init_promif(char *pgmname, kmdb_auxv_t *kav) { kmdb_prom_init_promif(pgmname, kav); } #else /*ARGSUSED*/ void kmdb_init_promif(char *pgmname, kmdb_auxv_t *kav) { /* * Fake function for non sun4v. See comments in kmdb_ctl.h */ ASSERT(0); } #endif /* * First-time kmdb startup. Run when kmdb has control of the machine for the * first time. */ static void kmdb_startup(void) { mdb_io_t *inio, *outio; if (mdb.m_termtype == NULL) { /* * The terminal type wasn't specified, so we guess. If we're * on console, we'll get a terminal type from the PROM. If not, * we'll use the default. */ const char *ttype; if ((ttype = kmdb_prom_term_type()) == NULL) { ttype = KMDB_DEF_TERM_TYPE; warn("unable to determine terminal type: " "assuming `%s'\n", ttype); } mdb.m_flags |= MDB_FL_TERMGUESS; mdb.m_termtype = strdup(ttype); } else if (mdb.m_flags & MDB_FL_TERMGUESS) { /* * The terminal type wasn't specified by the user, but a guess * was made using either $TERM or a property from the SMF. A * terminal type from the PROM code overrides the guess, so * we'll use that if we can. */ char *promttype; if ((promttype = kmdb_prom_term_type()) != NULL) { strfree(mdb.m_termtype); mdb.m_termtype = strdup(promttype); } } inio = kmdb_promio_create("stdin"); outio = kmdb_promio_create("stdout"); if ((mdb.m_term = mdb_termio_create(mdb.m_termtype, inio, outio)) == NULL && strcmp(mdb.m_termtype, KMDB_DEF_TERM_TYPE) != 0) { warn("failed to set terminal type to `%s', using `" KMDB_DEF_TERM_TYPE "'\n", mdb.m_termtype); strfree(mdb.m_termtype); mdb.m_termtype = strdup(KMDB_DEF_TERM_TYPE); if ((mdb.m_term = mdb_termio_create(mdb.m_termtype, inio, outio)) == NULL) { fail("failed to set terminal type to `" KMDB_DEF_TERM_TYPE "'\n"); } } mdb_iob_destroy(mdb.m_in); mdb.m_in = mdb_iob_create(mdb.m_term, MDB_IOB_RDONLY); mdb_iob_setpager(mdb.m_out, mdb.m_term); mdb_iob_setflags(mdb.m_out, MDB_IOB_PGENABLE); kmdb_kvm_startup(); /* * kmdb_init() and kctl_activate() may have been talking to each other, * and may have left some messages for us. The driver -> debugger * queue is normally processed during the resume path, so we have to * do it manually here if we want it to be run for first startup. */ kmdb_dpi_process_work_queue(); kmdb_kvm_poststartup(); } void kmdb_main(void) { int status; kmdb_dpi_set_state(DPI_STATE_STOPPED, 0); mdb_printf("\nWelcome to kmdb\n"); kmdb_startup(); /* * Debugger termination is a bit tricky. For compatibility with kadb, * neither an EOF on stdin nor a normal ::quit will cause the debugger * to unload. In both cases, they get a trip to OBP, after which the * debugger returns. * * The only supported way to cause the debugger to unload is to specify * the unload flag to ::quit, or to have the driver request it. The * driver request is the primary exit mechanism - the ::quit flag is * provided for convenience. * * Both forms of "exit" (unqualified ::quit that won't cause an unload, * and a driver request that will) are signalled by an MDB_ERR_QUIT. In * the latter case, however, the KDI will have the unload request. */ for (;;) { status = mdb_run(); if (status == MDB_ERR_QUIT && kmdb_kdi_get_unload_request()) { break; } else if (status == MDB_ERR_QUIT || status == 0) { kmdb_dpi_enter_mon(); } else if (status == MDB_ERR_OUTPUT) { /* * If a write failed on stdout, give up. A more * informative error message will already have been * printed by mdb_run(). */ if (mdb_iob_getflags(mdb.m_out) & MDB_IOB_ERR) fail("write to stdout failed, exiting\n"); } else if (status != MDB_ERR_ABORT) { fail("debugger exited abnormally (status = %s)\n", mdb_err2str(status)); } } mdb_destroy(); kmdb_dpi_resume_unload(); /*NOTREACHED*/ } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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. */ /* * Routines for manipulating the kmdb-specific aspects of dmods. */ #include #include #include #include #include #include typedef struct kmod_symarg { mdb_tgt_sym_f *sym_cb; /* Caller's callback function */ void *sym_data; /* Callback function argument */ uint_t sym_type; /* Symbol type/binding filter */ mdb_syminfo_t sym_info; /* Symbol id and table id */ const char *sym_obj; /* Containing object */ } kmod_symarg_t; void kmdb_module_path_set(const char **path, size_t pathlen) { kmdb_wr_path_t *wr; wr = mdb_zalloc(sizeof (kmdb_wr_path_t), UM_SLEEP); wr->dpth_node.wn_task = WNTASK_DMOD_PATH_CHANGE; wr->dpth_path = mdb_path_dup(path, pathlen, &wr->dpth_pathlen); kmdb_wr_driver_notify(wr); } void kmdb_module_path_ack(kmdb_wr_path_t *dpth) { if (dpth->dpth_path != NULL) mdb_path_free(dpth->dpth_path, dpth->dpth_pathlen); mdb_free(dpth, sizeof (kmdb_wr_path_t)); } static kmdb_modctl_t * kmdb_module_lookup_loaded(const char *name) { kmdb_modctl_t *kmc; mdb_var_t *v; if ((v = mdb_nv_lookup(&mdb.m_dmodctl, name)) == NULL) return (NULL); kmc = MDB_NV_COOKIE(v); if (kmc->kmc_state != KMDB_MC_STATE_LOADED) return (NULL); return (kmc); } /* * Given an address, try to match it up with a dmod symbol. */ int kmdb_module_lookup_by_addr(uintptr_t addr, uint_t flags, char *buf, size_t nbytes, GElf_Sym *symp, mdb_syminfo_t *sip) { kmdb_modctl_t *sym_kmc = NULL; GElf_Sym sym; uint_t symid; mdb_var_t *v; const char *name; mdb_nv_rewind(&mdb.m_dmodctl); while ((v = mdb_nv_advance(&mdb.m_dmodctl)) != NULL) { kmdb_modctl_t *kmc = MDB_NV_COOKIE(v); if (kmc->kmc_state != KMDB_MC_STATE_LOADED) continue; if (mdb_gelf_symtab_lookup_by_addr(kmc->kmc_symtab, addr, flags, buf, nbytes, symp, &sip->sym_id) != 0 || symp->st_value == 0) continue; if (flags & MDB_TGT_SYM_EXACT) { sym_kmc = kmc; goto found; } /* * If this is the first match we've found, or if this symbol is * closer to the specified address than the last one we found, * use it. */ if (sym_kmc == NULL || mdb_gelf_sym_closer(symp, &sym, addr)) { sym_kmc = kmc; sym = *symp; symid = sip->sym_id; } } if (sym_kmc == NULL) return (set_errno(EMDB_NOSYMADDR)); *symp = sym; sip->sym_id = symid; found: /* * Once we've found something, copy the final name into the caller's * buffer, prefixed with a marker identifying this as a dmod symbol. */ if (buf != NULL) { name = mdb_gelf_sym_name(sym_kmc->kmc_symtab, symp); (void) mdb_snprintf(buf, nbytes, "DMOD`%s`%s", sym_kmc->kmc_modname, name); } sip->sym_table = MDB_TGT_SYMTAB; return (0); } /* * Locate a given dmod symbol */ int kmdb_module_lookup_by_name(const char *obj, const char *name, GElf_Sym *symp, mdb_syminfo_t *sip) { kmdb_modctl_t *kmc; if ((kmc = kmdb_module_lookup_loaded(obj)) == NULL) return (set_errno(EMDB_NOSYMADDR)); if (mdb_gelf_symtab_lookup_by_name(kmc->kmc_symtab, name, symp, &sip->sym_id) == 0) { sip->sym_table = MDB_TGT_SYMTAB; return (0); } return (set_errno(EMDB_NOSYM)); } ctf_file_t * kmdb_module_addr_to_ctf(uintptr_t addr) { mdb_var_t *v; mdb_nv_rewind(&mdb.m_dmodctl); while ((v = mdb_nv_advance(&mdb.m_dmodctl)) != NULL) { kmdb_modctl_t *kmc = MDB_NV_COOKIE(v); struct module *mp; if (kmc->kmc_state != KMDB_MC_STATE_LOADED) continue; mp = kmc->kmc_modctl->mod_mp; if (addr - (uintptr_t)mp->text < mp->text_size || addr - (uintptr_t)mp->data < mp->data_size || addr - mp->bss < mp->bss_size) { ctf_file_t *ctfp = kmc->kmc_mod->mod_ctfp; if (ctfp == NULL) { (void) set_errno(EMDB_NOCTF); return (NULL); } return (ctfp); } } (void) set_errno(EMDB_NOMAP); return (NULL); } ctf_file_t * kmdb_module_name_to_ctf(const char *obj) { kmdb_modctl_t *kmc; ctf_file_t *ctfp; if ((kmc = kmdb_module_lookup_loaded(obj)) == NULL) { (void) set_errno(EMDB_NOOBJ); return (NULL); } if ((ctfp = kmc->kmc_mod->mod_ctfp) == NULL) { (void) set_errno(EMDB_NOCTF); return (NULL); } return (ctfp); } static int kmdb_module_symtab_func(void *data, const GElf_Sym *sym, const char *name, uint_t id) { kmod_symarg_t *arg = data; if (mdb_tgt_sym_match(sym, arg->sym_type)) { arg->sym_info.sym_id = id; return (arg->sym_cb(arg->sym_data, sym, name, &arg->sym_info, arg->sym_obj)); } return (0); } int kmdb_module_symbol_iter(const char *obj, uint_t type, mdb_tgt_sym_f *cb, void *p) { kmdb_modctl_t *kmc; kmod_symarg_t arg; mdb_var_t *v; if ((v = mdb_nv_lookup(&mdb.m_dmodctl, obj)) == NULL) return (set_errno(EMDB_NOMOD)); kmc = MDB_NV_COOKIE(v); if (kmc->kmc_state != KMDB_MC_STATE_LOADED) return (set_errno(EMDB_NOMOD)); arg.sym_cb = cb; arg.sym_data = p; arg.sym_type = type; arg.sym_info.sym_table = kmc->kmc_symtab->gst_tabid; arg.sym_obj = obj; mdb_gelf_symtab_iter(kmc->kmc_symtab, kmdb_module_symtab_func, &arg); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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. */ #ifndef _KMDB_MODULE_H #define _KMDB_MODULE_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define KMDB_MC_STATE_LOADING 1 #define KMDB_MC_STATE_LOADED 2 #define KMDB_MC_STATE_UNLOADING 3 #define KMDB_MC_FL_NOUNLOAD 0x1 /* * The mdb_module_t describes the runtime attributes of dmods - things that * matter after the dmod has been loaded. kmdb needs to track information about * modules before they've been loaded, and while they're in the process of * being unloaded. As such, a kmdb_modctl_t is created for each module when the * load is requested, and isn't destroyed until the module has completed * unloading. * * This description reflects the sequence of events that occur during the * successful loading and unloading of a dmod. * * 1. Debugger requests a dmod load. * * A kmdb_modctl_t is allocated. kmc_state is set to KMDB_MC_STATE_LOADING. * * 2. The driver reports the successful loading of the dmod. * * kmc_state is set to KMDB_MC_STATE_LOADED, and an mdb_module_t is created * by mdb_module_create. * * 3. Debugger requests a dmod unload. * * The mdb_module_t is destroyed, and kmc_state is set to * KMDB_MC_STATE_UNLOADING. * * 4. The driver reports the successful unloading of the dmod. * * The kmdb_modctl_t is destroyed. */ typedef struct kmdb_modctl { mdb_module_t *kmc_mod; /* common dmod state */ struct modctl *kmc_modctl; /* kernel's modctl for this dmod */ int kmc_exported; /* KOBJ_EXPORTED set when last seen? */ char *kmc_modname; /* name of this dmod */ ushort_t kmc_loadmode; /* MDB_MOD_* from load request */ ushort_t kmc_flags; /* KMDB_MC_FL_* (above) */ int kmc_dlrefcnt; /* Counts dlopens/dlcloses */ int kmc_state; /* KMDB_MC_STATE_* (above) */ mdb_gelf_symtab_t *kmc_symtab; /* This dmod's symbol table */ GElf_Ehdr kmc_ehdr; /* Copy of ehdr in gelf format */ } kmdb_modctl_t; extern boolean_t kmdb_module_loaded(kmdb_wr_load_t *); extern void kmdb_module_load_ack(kmdb_wr_load_t *); extern void kmdb_module_load_all_ack(kmdb_wr_t *); extern boolean_t kmdb_module_unloaded(kmdb_wr_unload_t *); extern void kmdb_module_unload_ack(kmdb_wr_unload_t *); extern void kmdb_module_path_set(const char **, size_t); extern void kmdb_module_path_ack(kmdb_wr_path_t *); extern int kmdb_module_lookup_by_addr(uintptr_t, uint_t, char *, size_t, GElf_Sym *, mdb_syminfo_t *); extern int kmdb_module_lookup_by_name(const char *, const char *, GElf_Sym *, mdb_syminfo_t *); extern ctf_file_t *kmdb_module_addr_to_ctf(uintptr_t); extern ctf_file_t *kmdb_module_name_to_ctf(const char *); extern int kmdb_module_symbol_iter(const char *, uint_t, mdb_tgt_sym_f *, void *); extern void kmdb_module_sync(void); #ifdef __cplusplus } #endif #endif /* _KMDB_MODULE_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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. * Copyright 2025 Edgecast Cloud LLC. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void kmdb_module_request_unload(kmdb_modctl_t *, const char *, int); static void kmc_free(kmdb_modctl_t *kmc) { if (kmc->kmc_modname != NULL) strfree(kmc->kmc_modname); mdb_free(kmc, sizeof (kmdb_modctl_t)); } /* * Sends a request to the driver to load the module. */ int mdb_module_load(const char *fname, int mode) { const char *modname = strbasename(fname); kmdb_wr_load_t *dlr; kmdb_modctl_t *kmc = NULL; const char *wformat = NULL; mdb_var_t *v; if (!mdb_module_validate_name(modname, &wformat)) goto module_load_err; if ((v = mdb_nv_lookup(&mdb.m_dmodctl, modname)) != NULL) { kmc = MDB_NV_COOKIE(v); if (kmc->kmc_state == KMDB_MC_STATE_LOADING) wformat = "module %s is already being loaded\n"; else wformat = "module %s is being unloaded\n"; goto module_load_err; } kmc = mdb_zalloc(sizeof (kmdb_modctl_t), UM_SLEEP); kmc->kmc_loadmode = mode; kmc->kmc_modname = strdup(modname); kmc->kmc_state = KMDB_MC_STATE_LOADING; if (mdb_nv_insert(&mdb.m_dmodctl, modname, NULL, (uintptr_t)kmc, 0) == NULL) { wformat = "module %s can't be registered for load\n"; kmc_free(kmc); goto module_load_err; } dlr = mdb_zalloc(sizeof (kmdb_wr_load_t), UM_SLEEP); dlr->dlr_node.wn_task = WNTASK_DMOD_LOAD; dlr->dlr_fname = strdup(fname); kmdb_wr_driver_notify(dlr); if (!(mode & MDB_MOD_DEFER) && mdb_tgt_continue(mdb.m_target, NULL) == 0) return (0); if (!(mode & MDB_MOD_SILENT)) mdb_printf("%s load pending (:c to complete)\n", modname); return (0); module_load_err: if (!(mode & MDB_MOD_SILENT)) warn(wformat, modname); return (-1); } /* * Module load post processing. Either clean up from error or * update modctl state. */ boolean_t kmdb_module_loaded(kmdb_wr_load_t *dlr) { struct modctl *modp = dlr->dlr_modctl; const char *modname = strbasename(dlr->dlr_fname); struct module *mp; kmdb_modctl_t *kmc = NULL; mdb_var_t *v; v = mdb_nv_lookup(&mdb.m_dmodctl, modname); if (dlr->dlr_errno != 0) { /* * We're somewhat limited in the diagnostics that we can * provide in the event of a failed load. In most load-failure * cases, the driver can only send up a generic errno. We use * EMDB_ENOMOD to signal generic errors, and supply our own * message. This twists the meaning of EMDB_NOMOD somewhat, but * it's better than defining a new one. */ if (dlr->dlr_errno == EMDB_NOMOD) { mdb_warn("%s does not appear to be a kmdb dmod\n", modname); } else { (void) set_errno(dlr->dlr_errno); mdb_warn("dmod %s failed to load", modname); } if (v != NULL) mdb_nv_remove(&mdb.m_dmodctl, v); return (B_FALSE); } if ((mp = modp->mod_mp) == NULL || mp->symhdr == NULL || mp->strhdr == NULL || mp->symtbl == NULL || mp->strings == NULL) { mdb_warn("dmod %s did not load properly\n"); return (B_FALSE); } if (v == NULL) { kmc = mdb_zalloc(sizeof (kmdb_modctl_t), UM_SLEEP); kmc->kmc_loadmode = MDB_MOD_LOCAL; kmc->kmc_modname = strdup(modname); kmc->kmc_state = KMDB_MC_STATE_LOADING; (void) mdb_nv_insert(&mdb.m_dmodctl, modname, NULL, (uintptr_t)kmc, 0); } else { kmc = MDB_NV_COOKIE(v); ASSERT(kmc->kmc_symtab == NULL); } kmc->kmc_modctl = modp; kmc->kmc_exported = (mp->flags & KOBJ_EXPORTED) != 0; mdb_gelf_ehdr_to_gehdr(&mp->hdr, &kmc->kmc_ehdr); kmc->kmc_symtab = mdb_gelf_symtab_create_raw(&kmc->kmc_ehdr, mp->symhdr, mp->symtbl, mp->strhdr, mp->strings, MDB_TGT_SYMTAB); if (mp->flags & KOBJ_PRIM) kmc->kmc_flags |= KMDB_MC_FL_NOUNLOAD; if (mdb_module_create(modname, modp->mod_filename, kmc->kmc_loadmode, &kmc->kmc_mod) < 0) { if (kmc->kmc_symtab != NULL) mdb_gelf_symtab_destroy(kmc->kmc_symtab); kmdb_module_request_unload(kmc, kmc->kmc_modname, MDB_MOD_DEFER); return (B_FALSE); } kmc->kmc_state = KMDB_MC_STATE_LOADED; return (B_TRUE); } void kmdb_module_load_ack(kmdb_wr_load_t *dlr) { strfree(dlr->dlr_fname); mdb_free(dlr, sizeof (kmdb_wr_load_t)); } void mdb_module_load_all(int mode) { kmdb_wr_t *wn; ASSERT(mode & MDB_MOD_DEFER); wn = mdb_zalloc(sizeof (kmdb_wr_t), UM_SLEEP); wn->wn_task = WNTASK_DMOD_LOAD_ALL; kmdb_wr_driver_notify(wn); } void kmdb_module_load_all_ack(kmdb_wr_t *wn) { mdb_free(wn, sizeof (kmdb_wr_t)); } static void kmdb_module_request_unload(kmdb_modctl_t *kmc, const char *modname, int mode) { kmdb_wr_unload_t *dur = mdb_zalloc(sizeof (kmdb_wr_unload_t), UM_SLEEP); dur->dur_node.wn_task = WNTASK_DMOD_UNLOAD; dur->dur_modname = strdup(modname); dur->dur_modctl = kmc->kmc_modctl; kmdb_wr_driver_notify(dur); kmc->kmc_state = KMDB_MC_STATE_UNLOADING; if (!(mode & MDB_MOD_DEFER) && mdb_tgt_continue(mdb.m_target, NULL) == 0) return; if (!(mode & MDB_MOD_SILENT)) mdb_printf("%s unload pending (:c to complete)\n", modname); } /*ARGSUSED*/ int mdb_module_unload(const char *name, int mode) { kmdb_modctl_t *kmc = NULL; const char *basename; mdb_var_t *v; /* * We may have been called with the name from the module itself * if the caller is iterating through the module list, so we need * to make a copy of the name. If we don't, we can't use it after * the call to unload_common(), which frees the module. */ name = strdup(name); basename = strbasename(name); /* * Make sure the module is in the proper state for unloading. Modules * may only be unloaded if they have properly completed loading. */ if ((v = mdb_nv_lookup(&mdb.m_dmodctl, basename)) != NULL) { kmc = MDB_NV_COOKIE(v); switch (kmc->kmc_state) { case KMDB_MC_STATE_LOADING: warn("%s is in the process of loading\n", basename); return (set_errno(EMDB_NOMOD)); case KMDB_MC_STATE_UNLOADING: warn("%s is already being unloaded\n", basename); return (set_errno(EMDB_NOMOD)); default: ASSERT(kmc->kmc_state == KMDB_MC_STATE_LOADED); } if (kmc->kmc_flags & KMDB_MC_FL_NOUNLOAD) return (set_errno(EMDB_KMODNOUNLOAD)); } if (mdb_module_unload_common(name) < 0) { if (!(mode & MDB_MOD_SILENT)) { mdb_dprintf(MDB_DBG_MODULE, "unload of %s failed\n", name); } return (-1); /* errno is set for us */ } /* * Any modules legitimately not listed in dmodctl (builtins, for * example) will be handled by mdb_module_unload_common. If any of * them get here, we've got a problem. */ if (v == NULL) { warn("unload of unregistered module %s\n", basename); return (set_errno(EMDB_NOMOD)); } ASSERT(kmc->kmc_dlrefcnt == 0); mdb_gelf_symtab_destroy(kmc->kmc_symtab); kmdb_module_request_unload(kmc, basename, mode); return (0); } boolean_t kmdb_module_unloaded(kmdb_wr_unload_t *dur) { mdb_var_t *v; if ((v = mdb_nv_lookup(&mdb.m_dmodctl, dur->dur_modname)) == NULL) { mdb_warn("unload for unrequested module %s\n", dur->dur_modname); return (B_FALSE); } if (dur->dur_errno != 0) { mdb_warn("dmod %s failed to unload", dur->dur_modname); return (B_FALSE); } kmc_free(MDB_NV_COOKIE(v)); mdb_nv_remove(&mdb.m_dmodctl, v); return (B_TRUE); } void kmdb_module_unload_ack(kmdb_wr_unload_t *dur) { if (dur->dur_modname != NULL) strfree(dur->dur_modname); mdb_free(dur, sizeof (kmdb_wr_unload_t)); } /* * Called by the kmdb_kvm target upon debugger reentry, this routine checks * to see if the loaded dmods have changed. Of particular interest is the * exportation of dmod symbol tables, which will happen during the boot * process for dmods that were loaded prior to kernel startup. If this * has occurred, we'll need to reconstruct our view of the symbol tables for * the affected dmods, since the old symbol tables lived in bootmem * and have been moved during the kobj_export_module(). * * Also, any ctf_file_t we might have opened is now invalid, since it * has internal pointers to the old data as well. */ void kmdb_module_sync(void) { mdb_var_t *v; mdb_nv_rewind(&mdb.m_dmodctl); while ((v = mdb_nv_advance(&mdb.m_dmodctl)) != NULL) { kmdb_modctl_t *kmc = MDB_NV_COOKIE(v); struct module *mp; if (kmc->kmc_state != KMDB_MC_STATE_LOADED) continue; mp = kmc->kmc_modctl->mod_mp; if ((mp->flags & (KOBJ_PRIM | KOBJ_EXPORTED)) && !kmc->kmc_exported) { /* * The exporting process moves the symtab from boot * scratch memory to vmem. */ if (kmc->kmc_symtab != NULL) mdb_gelf_symtab_destroy(kmc->kmc_symtab); kmc->kmc_symtab = mdb_gelf_symtab_create_raw( &kmc->kmc_ehdr, mp->symhdr, mp->symtbl, mp->strhdr, mp->strings, MDB_TGT_SYMTAB); if (kmc->kmc_mod->mod_ctfp != NULL) { ctf_close(kmc->kmc_mod->mod_ctfp); kmc->kmc_mod->mod_ctfp = mdb_ctf_open(kmc->kmc_modname, NULL); } kmc->kmc_exported = TRUE; } } } /* * 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. */ #include #include #include #ifdef sun4v #include #endif #include #include #include #include #include #include #include #include #include #include #include #define KMDB_PROM_DEF_CONS_MODE "9600,n,1,-,-" #define KMDB_PROM_READBUF_SIZE 1024 static char kmdb_prom_readbuf[KMDB_PROM_READBUF_SIZE]; static int kmdb_prom_readbuf_head; static int kmdb_prom_readbuf_tail; static int kmdb_prom_getchar(int wait) { struct cons_polledio *pio = mdb.m_pio; uintptr_t ischar; uintptr_t getchar; uintptr_t arg; if (pio == NULL || pio->cons_polledio_getchar == NULL) { int c; while ((c = prom_mayget()) == -1) { if (!wait) return (-1); } return (c); } ischar = (uintptr_t)pio->cons_polledio_ischar; getchar = (uintptr_t)pio->cons_polledio_getchar; arg = (uintptr_t)pio->cons_polledio_argument; if (!wait && ischar != 0 && !kmdb_dpi_call(ischar, 1, &arg)) return (-1); return ((int)kmdb_dpi_call(getchar, 1, &arg)); } static ssize_t kmdb_prom_polled_write(caddr_t buf, size_t len) { uintptr_t args[2]; int i; args[0] = (uintptr_t)mdb.m_pio->cons_polledio_argument; for (i = 0; i < len; i++) { args[1] = *buf++; (void) kmdb_dpi_call( (uintptr_t)mdb.m_pio->cons_polledio_putchar, 2, args); } return (len); } static ssize_t kmdb_prom_reader(caddr_t buf, size_t len, int wait) { int nread = 0; int c; while (nread < len) { if ((c = kmdb_prom_getchar(wait)) == -1) break; *buf++ = (char)c; nread++; wait = 0; } return (nread); } static ssize_t kmdb_prom_writer(caddr_t buf, size_t len) { if (mdb.m_pio != NULL && mdb.m_pio->cons_polledio_putchar != NULL) return (kmdb_prom_polled_write(buf, len)); return (kmdb_prom_obp_writer(buf, len)); } /* * Due to the nature of kmdb, we don't have signals. This prevents us from * receiving asynchronous notification when the user would like to abort active * dcmds. Whereas mdb can simply declare a SIGINT handler, we must * occasionally poll the input stream, looking for pending ^C characters. To * give the illusion of asynchronous interrupt delivery, this polling is * triggered from several commonly-used functions, such as kmdb_prom_write and * the *read and *write target ops. When an interrupt check is triggered, we * read through pending input, looking for interrupt characters. If we find * one, we deliver an interrupt immediately. * * In a read context, we can deliver the interrupt character directly back to * the termio handler rather than raising an interrupt. * * OBP doesn't have an "unget" facility. Any character read for interrupt * checking is gone forever, unless we save it. Loss of these characters * would prevent us from supporting typeahead. We like typeahead, so we're * going to save characters gathered during interrupt checking. As with * ungetc(3c), however, we can only store a finite number of characters in * our typeahead buffer. Characters read beyond that will be silently dropped * after they undergo interrupt processing. * * The typeahead facility is implemented as a ring buffer, stored in * kmdb_prom_readbuf. */ static size_t kmdb_prom_drain_readbuf(void *buf, size_t len) { size_t n, tailread; /* * If head > tail, life is easy - we can simply read as much as we need * in one gulp. */ if (kmdb_prom_readbuf_head > kmdb_prom_readbuf_tail) { n = MIN(kmdb_prom_readbuf_head - kmdb_prom_readbuf_tail, len); bcopy(kmdb_prom_readbuf + kmdb_prom_readbuf_tail, buf, n); kmdb_prom_readbuf_tail += n; return (n); } else if (kmdb_prom_readbuf_head == kmdb_prom_readbuf_tail) { return (0); } /* * The consumable slots wrap around zero (there are slots from tail to * zero, and from zero to head). We have to read them in two parts. */ n = MIN(KMDB_PROM_READBUF_SIZE - kmdb_prom_readbuf_tail, len); bcopy(kmdb_prom_readbuf + kmdb_prom_readbuf_tail, buf, n); kmdb_prom_readbuf_tail = (kmdb_prom_readbuf_tail + n) % KMDB_PROM_READBUF_SIZE; if (n == len) { /* * We filled the passed buffer from the first part, so there's * no need to read the second. */ return (n); } else { tailread = n; } n = MIN(kmdb_prom_readbuf_head, len - tailread); buf = (void *)((uintptr_t)buf + tailread); bcopy(kmdb_prom_readbuf, buf, n); kmdb_prom_readbuf_tail = (kmdb_prom_readbuf_tail + n) % KMDB_PROM_READBUF_SIZE; return (tailread + n); } static void check_int(char *buf, size_t len) { int i; for (i = 0; i < len; i++) { if (buf[i] == CTRL('c')) { kmdb_prom_readbuf_tail = kmdb_prom_readbuf_head; if (mdb.m_intr == 0) longjmp(mdb.m_frame->f_pcb, MDB_ERR_SIGINT); else mdb.m_pend++; } } } /* * Attempt to refill the ring buffer from the input stream. This called from * two contexts: * * Direct read: read the input into our buffer until input is exhausted, or the * buffer is full. * * Interrupt check: called 'asynchronously' from the normal read routines; read * the input into our buffer until it is exhausted, discarding input if the * buffer is full. In this case we look ahead for any interrupt characters, * delivering an interrupt directly if we find one. */ static void kmdb_prom_fill_readbuf(int check_for_int, int wait) { int oldhead, left, n; /* * Calculate the number of slots left before we wrap around to the * beginning again. */ left = KMDB_PROM_READBUF_SIZE - kmdb_prom_readbuf_head; if (kmdb_prom_readbuf_tail == 0) left--; if (kmdb_prom_readbuf_head == kmdb_prom_readbuf_tail || (kmdb_prom_readbuf_head > kmdb_prom_readbuf_tail && left > 0)) { /* * head > tail, so we have to read in two parts - the slots * from head until we wrap back around to zero, and the ones * from zero to tail. We handle the first part here, and let * the common code handle the second. */ if ((n = kmdb_prom_reader(kmdb_prom_readbuf + kmdb_prom_readbuf_head, left, wait)) <= 0) return; oldhead = kmdb_prom_readbuf_head; kmdb_prom_readbuf_head = (kmdb_prom_readbuf_head + n) % KMDB_PROM_READBUF_SIZE; if (check_for_int) check_int(kmdb_prom_readbuf + oldhead, n); if (n != left) return; } left = kmdb_prom_readbuf_tail - kmdb_prom_readbuf_head - 1; if (left > 0) { if ((n = kmdb_prom_reader(kmdb_prom_readbuf + kmdb_prom_readbuf_head, left, wait)) <= 0) return; oldhead = kmdb_prom_readbuf_head; kmdb_prom_readbuf_head = (kmdb_prom_readbuf_head + n) % KMDB_PROM_READBUF_SIZE; if (check_for_int) check_int(kmdb_prom_readbuf + oldhead, n); if (n != left) return; } if (check_for_int) { char c; while (kmdb_prom_reader(&c, 1, 0) == 1) check_int(&c, 1); } } void kmdb_prom_check_interrupt(void) { kmdb_prom_fill_readbuf(1, 0); } /* * OBP reads are always non-blocking. If there are characters available, * we'll return as many as we can. If nothing is available, we'll spin * until one shows up. */ ssize_t kmdb_prom_read(void *buf, size_t len, struct termios *tio) { size_t totread = 0; size_t thisread; char *c = (char *)buf; int wait = 1; for (;;) { kmdb_prom_fill_readbuf(0, wait); thisread = kmdb_prom_drain_readbuf(c, len); len -= thisread; totread += thisread; c += thisread; /* wait until something shows up */ if (totread == 0) continue; wait = 0; /* * We're done if we've exhausted available input or if we've * filled the provided buffer. */ if (len == 0 || thisread == 0) break; } if (tio->c_iflag & ICRNL) { char *cbuf = buf; int i; for (i = 0; i < totread; i++) { if (cbuf[i] == '\r') cbuf[i] = '\n'; } } if (tio->c_lflag & ECHO) (void) kmdb_prom_write(buf, totread, tio); return (totread); } /*ARGSUSED*/ ssize_t kmdb_prom_write(const void *bufp, size_t len, struct termios *tio) { caddr_t buf = (caddr_t)bufp; size_t left = len; char *nl = "\r\n"; char *c; kmdb_prom_check_interrupt(); if (!(tio->c_oflag & ONLCR)) return (kmdb_prom_writer(buf, left)); /* translate every \n into \r\n */ while ((c = strnchr(buf, '\n', left)) != NULL) { if (c != buf) { size_t sz = (size_t)(c - buf); (void) kmdb_prom_writer(buf, sz); left -= sz; } buf = c + 1; left--; (void) kmdb_prom_writer(nl, 2); } if (*buf != '\0') (void) kmdb_prom_writer(buf, left); return (len); } static char * kmdb_get_ttyio_mode(kmdb_auxv_t *kav, char *devname) { char *modepname, *modepval; modepname = mdb_alloc(strlen(devname) + 5 + 1, UM_SLEEP); (void) strcpy(modepname, devname); (void) strcat(modepname, "-mode"); modepval = kmdb_prom_get_ddi_prop(kav, modepname); strfree(modepname); return (modepval); } static int termios_setispeed(struct termios *tip, speed_t s) { if (s > (2 * CBAUD + 1)) return (-1); if ((s << IBSHIFT) > CIBAUD) { tip->c_cflag |= CIBAUDEXT; s -= ((CIBAUD >> IBSHIFT) + 1); } else tip->c_cflag &= ~CIBAUDEXT; tip->c_cflag = (tip->c_cflag & ~CIBAUD) | ((s << IBSHIFT) & CIBAUD); return (0); } static int termios_setospeed(struct termios *tip, speed_t s) { if (s > (2 * CBAUD + 1)) return (-1); if (s > CBAUD) { tip->c_cflag |= CBAUDEXT; s -= (CBAUD + 1); } else tip->c_cflag &= ~CBAUDEXT; tip->c_cflag = (tip->c_cflag & ~CBAUD) | (s & CBAUD); return (0); } static int kmdb_parse_mode(const char *mode, struct termios *tip, int in) { static const uint_t baudmap[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 76800, 115200, 153600, 230400, 307200, 460800, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000 }; static const uint_t bitsmap[] = { CS6, CS6, CS7, CS8 }; char *m = strdup(mode); char *w; int rc = -1; speed_t speed; int baud, i; /* * termios supports different baud rates and flow control types for * input and output, but it requires character width, parity, and stop * bits to be equal in input and output. obp allows them to be * different, but we're going to (silently) assume that nobody will use * it that way. */ /* baud rate - see baudmap above */ if ((w = strtok(m, ",")) == NULL) goto parse_mode_bail; baud = strtol(w, NULL, 10); speed = 0; for (i = 0; i < sizeof (baudmap) / sizeof (baudmap[0]); i++) { if (baudmap[i] == baud) { speed = i; break; } } if (speed == 0) goto parse_mode_bail; if (in == 1) (void) termios_setispeed(tip, speed); else (void) termios_setospeed(tip, speed); /* character width (bits) - 5, 6, 7, or 8 */ if ((w = strtok(NULL, ",")) == NULL || strlen(w) != 1 || *w < '5' || *w > '8') goto parse_mode_bail; tip->c_cflag = (tip->c_cflag & ~CSIZE) | bitsmap[*w - '5']; /* parity - `n' (none), `e' (even), or `o' (odd) */ if ((w = strtok(NULL, ",")) == NULL || strlen(w) != 1 || strchr("neo", *w) == NULL) goto parse_mode_bail; tip->c_cflag = (tip->c_cflag & ~(PARENB|PARODD)); switch (*w) { case 'n': /* nothing */ break; case 'e': tip->c_cflag |= PARENB; break; case 'o': tip->c_cflag |= PARENB|PARODD; break; } /* * stop bits - 1, or 2. obp can, in theory, support 1.5 bits, * but we can't. how many angels can dance on half of a bit? */ if ((w = strtok(NULL, ",")) == NULL || strlen(w) != 1 || *w < '1' || *w > '2') goto parse_mode_bail; if (*w == '1') tip->c_cflag &= ~CSTOPB; else tip->c_cflag |= CSTOPB; /* flow control - `-' (none), `h' (h/w), or `s' (s/w - XON/XOFF) */ if ((w = strtok(NULL, ",")) == NULL || strlen(w) != 1 || strchr("-hs", *w) == NULL) goto parse_mode_bail; tip->c_cflag &= ~(CRTSXOFF|CRTSCTS); tip->c_iflag &= ~(IXON|IXANY|IXOFF); switch (*w) { case 'h': tip->c_cflag |= (in == 1 ? CRTSXOFF : CRTSCTS); break; case 's': tip->c_iflag |= (in == 1 ? IXOFF : IXON); break; } rc = 0; parse_mode_bail: strfree(m); return (rc); } #ifdef __sparc #define ATTACHED_TERM_TYPE "sun" #else #define ATTACHED_TERM_TYPE "sun-color" #endif static void kmdb_prom_term_init(kmdb_auxv_t *kav, kmdb_promif_t *pif) { const char ccs[NCCS] = { 0x03, 0x1c, 0x08, 0x15, 0x04, 0x00, 0x00, 0x00, 0x11, 0x13, 0x1a, 0x19, 0x12, 0x0f, 0x17, 0x16 }; char *conin = NULL, *conout = NULL; if (kmdb_prom_stdout_is_framebuffer(kav)) { struct winsize *wsz = &pif->pif_wsz; /* Set default dimensions. */ wsz->ws_row = KMDB_PIF_WINSIZE_ROWS; wsz->ws_col = KMDB_PIF_WINSIZE_COLS; kmdb_prom_get_tem_size(kav, &wsz->ws_row, &wsz->ws_col); pif->pif_oterm = ATTACHED_TERM_TYPE; } bzero(&pif->pif_tios, sizeof (struct termios)); /* output device characteristics */ if ((conout = kmdb_prom_get_ddi_prop(kav, "output-device")) == NULL || strcmp(conout, "screen") == 0) { (void) kmdb_parse_mode(KMDB_PROM_DEF_CONS_MODE, &pif->pif_tios, 0); } else if (*conout == '/') { /* * We're not going to be able to get characteristics for a * device that's specified as a path, so don't even try. * Conveniently, this allows us to avoid chattering on * Serengetis. */ (void) kmdb_parse_mode(KMDB_PROM_DEF_CONS_MODE, &pif->pif_tios, 0); } else { char *mode = kmdb_get_ttyio_mode(kav, conout); #ifdef __sparc /* * Some platforms (Starfire) define a value of `ttya' for * output-device, but neglect to provide a specific property * with the characteristics. We'll provide a default value. */ if (mode == NULL && strcmp(conout, "ttya") == 0) { (void) kmdb_parse_mode(KMDB_PROM_DEF_CONS_MODE, &pif->pif_tios, 0); } else #endif { if (mode == NULL || kmdb_parse_mode(mode, &pif->pif_tios, 0) < 0) { /* * Either we couldn't retrieve the * characteristics for this console, or they * weren't parseable. The console hasn't been * set up yet, so we can't warn. We'll have to * silently fall back to the default * characteristics. */ (void) kmdb_parse_mode(KMDB_PROM_DEF_CONS_MODE, &pif->pif_tios, 0); } } if (mode != NULL) kmdb_prom_free_ddi_prop(mode); } /* input device characteristics */ if ((conin = kmdb_prom_get_ddi_prop(kav, "input-device")) == NULL || strcmp(conin, "keyboard") == 0) { (void) kmdb_parse_mode(KMDB_PROM_DEF_CONS_MODE, &pif->pif_tios, 1); } else if (*conin == '/') { /* See similar case in output-device above */ (void) kmdb_parse_mode(KMDB_PROM_DEF_CONS_MODE, &pif->pif_tios, 1); } else { char *mode = kmdb_get_ttyio_mode(kav, conin); #ifdef __sparc /* * Some platforms (Starfire) define a value of `ttya' for * input-device, but neglect to provide a specific property * with the characteristics. We'll provide a default value. */ if (mode == NULL && strcmp(conin, "ttya") == 0) { (void) kmdb_parse_mode(KMDB_PROM_DEF_CONS_MODE, &pif->pif_tios, 1); } else #endif { if (mode == NULL || kmdb_parse_mode(mode, &pif->pif_tios, 1) < 0) { /* * Either we couldn't retrieve the * characteristics for this console, or they * weren't parseable. The console hasn't been * set up yet, so we can't warn. We'll have to * silently fall back to the default * characteristics. */ (void) kmdb_parse_mode(KMDB_PROM_DEF_CONS_MODE, &pif->pif_tios, 1); } } if (mode != NULL) kmdb_prom_free_ddi_prop(mode); } /* various characteristics of the prom read/write interface */ pif->pif_tios.c_iflag |= ICRNL; pif->pif_tios.c_lflag |= ECHO; bcopy(ccs, &pif->pif_tios.c_cc, sizeof (ccs)); if (conin != NULL) kmdb_prom_free_ddi_prop(conin); if (conout != NULL) kmdb_prom_free_ddi_prop(conout); } char * kmdb_prom_term_type(void) { return (mdb.m_promif->pif_oterm); } int kmdb_prom_term_ctl(int req, void *arg) { switch (req) { case TCGETS: { struct termios *ti = arg; bcopy(&mdb.m_promif->pif_tios, ti, sizeof (struct termios)); return (0); } case TIOCGWINSZ: /* * When kmdb is used over a serial console, we have no idea how * large the terminal window is. When we're invoked on a local * console, however, we do, and need to share that information * with the debugger in order to contradict potentially * incorrect sizing information retrieved from the terminfo * database. One specific case where this happens is with the * Intel console, which is 80x25. The terminfo entry for * sun-color -- the default terminal type for local Intel * consoles -- was cloned from sun, which has a height of 34 * rows. */ if (mdb.m_promif->pif_oterm != NULL) { struct winsize *wsz = arg; wsz->ws_row = mdb.m_promif->pif_wsz.ws_row; wsz->ws_col = mdb.m_promif->pif_wsz.ws_col; wsz->ws_xpixel = wsz->ws_ypixel = 0; return (0); } return (set_errno(ENOTSUP)); default: return (set_errno(EINVAL)); } } int kmdb_prom_vtop(uintptr_t virt, physaddr_t *pap) { physaddr_t pa; int rc = kmdb_kdi_vtop(virt, &pa); #ifdef __sparc if (rc < 0 && errno == EAGAIN) rc = kmdb_prom_translate_virt(virt, &pa); #endif if (rc == 0 && pap != NULL) *pap = pa; return (rc); } void kmdb_prom_debugger_entry(void) { /* * While kmdb_prom_debugger_entry and kmdb_prom_debugger_exit are not * guaranteed to be called an identical number of times (an intentional * debugger fault will cause an additional entry call without a matching * exit call), we must ensure that the polled I/O entry and exit calls * match. */ if (mdb.m_pio == NULL) { mdb.m_pio = kmdb_kdi_get_polled_io(); if (mdb.m_pio != NULL && mdb.m_pio->cons_polledio_enter != NULL) { (void) kmdb_dpi_call( (uintptr_t)mdb.m_pio->cons_polledio_enter, 1, (uintptr_t *)&mdb.m_pio->cons_polledio_argument); } } } void kmdb_prom_debugger_exit(void) { if (mdb.m_pio != NULL && mdb.m_pio->cons_polledio_exit != NULL) { (void) kmdb_dpi_call((uintptr_t)mdb.m_pio->cons_polledio_exit, 1, (uintptr_t *)&mdb.m_pio->cons_polledio_argument); } mdb.m_pio = NULL; } /* * The prom_* files use ASSERT, which is #defined as assfail(). We need to * redirect that to our assert function. This is also used by the various STAND * libraries. */ int kmdb_prom_assfail(const char *assertion, const char *file, int line) { (void) mdb_dassert(assertion, file, line); /*NOTREACHED*/ return (0); } /* * Begin the initialization of the debugger/PROM interface. Initialization is * performed in two steps due to interlocking dependencies between promif and * both the memory allocator and mdb_create. The first phase is performed * before either of the others have been initialized, and thus must neither * attempt to allocate memory nor access/write to `mdb'. */ void kmdb_prom_init_begin(char *pgmname, kmdb_auxv_t *kav) { #ifdef sun4v if (kav->kav_domaining) kmdb_prom_init_promif(pgmname, kav); else prom_init(pgmname, kav->kav_romp); #else prom_init(pgmname, kav->kav_romp); #endif /* Initialize the interrupt ring buffer */ kmdb_prom_readbuf_head = kmdb_prom_readbuf_tail; #if defined(__i386) || defined(__amd64) kmdb_sysp = kav->kav_romp; #endif } #ifdef sun4v void kmdb_prom_init_promif(char *pgmname, kmdb_auxv_t *kav) { ASSERT(kav->kav_domaining); cif_init(pgmname, kav->kav_promif_root, kav->kav_promif_in, kav->kav_promif_out, kav->kav_promif_pin, kav->kav_promif_pout, kav->kav_promif_chosennode, kav->kav_promif_optionsnode); } #endif /* * Conclude the initialization of the debugger/PROM interface. Memory * allocation and the global `mdb' object are now available. */ void kmdb_prom_init_finish(kmdb_auxv_t *kav) { mdb.m_promif = mdb_zalloc(sizeof (kmdb_promif_t), UM_SLEEP); kmdb_prom_term_init(kav, mdb.m_promif); } /* * 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. */ #ifndef _KMDB_PROMIF_H #define _KMDB_PROMIF_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif extern void kmdb_prom_init_begin(char *, kmdb_auxv_t *); extern void kmdb_prom_init_finish(kmdb_auxv_t *); #ifdef sun4v extern void kmdb_prom_init_promif(char *, kmdb_auxv_t *); #endif extern ssize_t kmdb_prom_read(void *, size_t, struct termios *); extern ssize_t kmdb_prom_write(const void *, size_t, struct termios *); extern ihandle_t kmdb_prom_get_handle(char *); extern void kmdb_prom_check_interrupt(void); extern int kmdb_prom_vtop(uintptr_t, physaddr_t *); extern char *kmdb_prom_term_type(void); extern int kmdb_prom_term_ctl(int, void *); extern void kmdb_prom_debugger_entry(void); extern void kmdb_prom_debugger_exit(void); #ifdef __cplusplus } #endif #endif /* _KMDB_PROMIF_H */ /* * 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. */ #ifndef _KMDB_PROMIF_IMPL_H #define _KMDB_PROMIF_IMPL_H #include #include #include #ifdef __cplusplus extern "C" { #endif #if defined(__i386) || defined(__amd64) struct boot_syscalls; extern struct boot_syscalls *kmdb_sysp; #endif /* * We don't have an easy way of asking for the window size, so we're going to * hard-code the current values. */ #ifdef __sparc #define KMDB_PIF_WINSIZE_ROWS 34 #else #define KMDB_PIF_WINSIZE_ROWS 25 #endif #define KMDB_PIF_WINSIZE_COLS 80 typedef struct kmdb_promif { char *pif_oterm; /* term type for local console (NULL if rem) */ struct termios pif_tios; /* derived settings for console */ struct winsize pif_wsz; /* winsize for local console */ } kmdb_promif_t; extern void kmdb_prom_init_finish_isadep(kmdb_auxv_t *); extern char *kmdb_prom_get_ddi_prop(kmdb_auxv_t *, char *); extern void kmdb_prom_free_ddi_prop(char *); extern ssize_t kmdb_prom_obp_writer(caddr_t, size_t); extern int kmdb_prom_stdout_is_framebuffer(kmdb_auxv_t *); extern void kmdb_prom_get_tem_size(kmdb_auxv_t *, ushort_t *, ushort_t *); #ifdef __cplusplus } #endif #endif /* _KMDB_PROMIF_IMPL_H */ /* * 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. */ /* * PROM I/O backend */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define PIO_FL_TIO_READ 0x001 typedef struct pio_data { char pio_name[MAXPATHLEN]; ihandle_t pio_fd; uint_t pio_flags; struct termios pio_ti; } pio_data_t; static pid_t pio_pgrp; static ssize_t pio_read(mdb_io_t *io, void *buf, size_t nbytes) { pio_data_t *pdp = io->io_data; if (io->io_next == NULL) return (kmdb_prom_read(buf, nbytes, &pdp->pio_ti)); return (IOP_READ(io->io_next, buf, nbytes)); } static ssize_t pio_write(mdb_io_t *io, const void *buf, size_t nbytes) { pio_data_t *pdp = io->io_data; if (io->io_next == NULL) return (kmdb_prom_write(buf, nbytes, &pdp->pio_ti)); return (IOP_WRITE(io->io_next, buf, nbytes)); } static off64_t pio_seek(mdb_io_t *io, off64_t offset, int whence) { if (io->io_next == NULL) return (set_errno(ENOTSUP)); return (IOP_SEEK(io->io_next, offset, whence)); } static int pio_ctl(mdb_io_t *io, int req, void *arg) { pio_data_t *pdp = io->io_data; if (io->io_next != NULL) return (IOP_CTL(io->io_next, req, arg)); switch (req) { case TIOCGWINSZ: return (kmdb_prom_term_ctl(TIOCGWINSZ, arg)); case TCGETS: { struct termios *ti = arg; if (!(pdp->pio_flags & PIO_FL_TIO_READ)) { (void) kmdb_prom_term_ctl(TCGETS, &pdp->pio_ti); pdp->pio_flags |= PIO_FL_TIO_READ; } bcopy(&pdp->pio_ti, ti, sizeof (struct termios)); mdb_dprintf(MDB_DBG_CMDBUF, "pio_ctl: gets: i: 0%o o: 0%o c: " "0%o l: 0%o\n", ti->c_iflag, ti->c_oflag, ti->c_cflag, ti->c_lflag); return (0); } case TCSETSW: { struct termios *ti = arg; mdb_dprintf(MDB_DBG_CMDBUF, "pio_ctl: setsw: i: 0%o o: 0%o c: " "0%o l: 0%o\n", ti->c_iflag, ti->c_oflag, ti->c_cflag, ti->c_lflag); bcopy(ti, &pdp->pio_ti, sizeof (struct termios)); return (0); } case TIOCSPGRP: pio_pgrp = *(pid_t *)arg; mdb_dprintf(MDB_DBG_CMDBUF, "pio_ctl: spgrp: %ld\n", (long)pio_pgrp); return (0); case TIOCGPGRP: mdb_dprintf(MDB_DBG_CMDBUF, "pio_ctl: gpgrp: %ld\n", (long)pio_pgrp); *(pid_t *)arg = pio_pgrp; return (0); case MDB_IOC_CTTY: mdb_dprintf(MDB_DBG_CMDBUF, "pio_ctl: ignoring MDB_IOC_CTTY\n"); return (0); case MDB_IOC_GETFD: return (set_errno(ENOTSUP)); default: warn("Unknown ioctl %d\n", req); return (set_errno(EINVAL)); } } void pio_close(mdb_io_t *io) { pio_data_t *pdp = io->io_data; mdb_free(pdp, sizeof (pio_data_t)); } static const char * pio_name(mdb_io_t *io) { pio_data_t *pdp = io->io_data; if (io->io_next == NULL) return (pdp->pio_name); return (IOP_NAME(io->io_next)); } static const mdb_io_ops_t promio_ops = { .io_read = pio_read, .io_write = pio_write, .io_seek = pio_seek, .io_ctl = pio_ctl, .io_close = pio_close, .io_name = pio_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume, }; mdb_io_t * kmdb_promio_create(char *name) { mdb_io_t *io; pio_data_t *pdp; ihandle_t hdl = kmdb_prom_get_handle(name); if (hdl == -1) return (NULL); io = mdb_zalloc(sizeof (mdb_io_t), UM_SLEEP); pdp = mdb_zalloc(sizeof (pio_data_t), UM_SLEEP); (void) strlcpy(pdp->pio_name, name, MAXPATHLEN); pdp->pio_fd = hdl; #ifdef __sparc pdp->pio_ti.c_oflag |= ONLCR; pdp->pio_ti.c_iflag |= ICRNL; #endif pdp->pio_ti.c_lflag |= ECHO; io->io_data = pdp; io->io_ops = &promio_ops; return (io); } char kmdb_getchar(void) { char c; while (IOP_READ(mdb.m_term, &c, 1) != 1) continue; if (isprint(c) && c != '\n') mdb_iob_printf(mdb.m_out, "%c", c); mdb_iob_printf(mdb.m_out, "\n"); return (c); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include /*ARGSUSED*/ void mdb_shell_exec(char *cmd) { yyperror("shell escape facility not available in kmdb\n"); } /*ARGSUSED*/ void mdb_shell_pipe(char *cmd) { yyperror("shell pipe facility not available in kmdb\n"); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_START_H #define _KMDB_START_H /* * Used for initial initialization of kmdb */ #include #ifdef __cplusplus extern "C" { #endif extern caddr_t kmdb_main_stack; extern size_t kmdb_main_stack_size; extern void kmdb_first_start(void); #ifdef __cplusplus } #endif #endif /* _KMDB_START_H */ /* * 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. */ /* * Stubs for basic system services otherwise unavailable to the debugger. */ #include #include #include #include #include #include #include #include #include #include #include /*ARGSUSED*/ char * getenv(const char *name) { /* There aren't any environment variables here */ return (NULL); } char * strerror(int errnum) { static char errnostr[16]; (void) mdb_snprintf(errnostr, sizeof (errnostr), "Error %d", errnum); return (errnostr); } pid_t getpid(void) { return (1); } /* * We're trying to isolate ourselves from the rest of the world as much as * possible, so we can't rely on the time in the kernel proper. For now, we * just bump a counter whenever time is requested, thus guaranteeing that * things with timestamps can be compared according to order of occurrance. */ hrtime_t gethrtime(void) { static hrtime_t kmdb_timestamp; return (++kmdb_timestamp); } /* * Signal handling */ /*ARGSUSED*/ int sigemptyset(sigset_t *set) { return (0); } /*ARGSUSED*/ int sigaddset(sigset_t *set, int signo) { return (0); } /*ARGSUSED*/ int sigfillset(sigset_t *set) { return (0); } /*ARGSUSED*/ int sigprocmask(int how, const sigset_t *set, sigset_t *oset) { return (0); } /*ARGSUSED*/ int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) { return (0); } /*ARGSUSED*/ int kill(pid_t pid, int sig) { if (sig == SIGABRT) { mdb_printf("Debugger aborted\n"); exit(1); } return (0); } /*ARGSUSED*/ int proc_str2flt(const char *buf, int *ptr) { return (-1); } /*ARGSUSED*/ int proc_str2sig(const char *buf, int *ptr) { return (-1); } /*ARGSUSED*/ int proc_str2sys(const char *buf, int *ptr) { return (-1); } /*ARGSUSED*/ void exit(int status) { #ifdef __sparc extern void kmdb_prom_exit_to_mon(void) __NORETURN; kmdb_prom_exit_to_mon(); #else extern void kmdb_dpi_reboot(void) __NORETURN; static int recurse = 0; if (!recurse) { recurse = 1; mdb_iob_printf(mdb.m_out, "Press any key to reboot\n"); mdb_iob_flush(mdb.m_out); mdb_iob_clearlines(mdb.m_out); (void) kmdb_getchar(); } kmdb_dpi_reboot(); #endif } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 */ /* BEGIN PROLOGUE */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kmdb_terminfo_skel.c is the skeleton used to generate * kmdb_terminfo.c, which contains the kmdb-specific version * of terminfo. */ #include #include #include #include #include #include typedef enum { TIO_ATTR_REQSTR, TIO_ATTR_STR, TIO_ATTR_BOOL, TIO_ATTR_INT } termio_attr_type_t; typedef struct { const char *ta_name; termio_attr_type_t ta_type; const void *ta_data; } termio_attr_t; typedef struct { const char *td_name; const termio_attr_t *td_data; } termio_desc_t; /* END PROLOGUE */ /* * tigen will insert the following definitions here: * * _attrs (one per terminal type passed to tigen) * termio_db */ /* BEGIN EPILOGUE */ static const termio_desc_t *tdp; /*ARGSUSED*/ int setupterm(char *name, int fd, int *err) { for (tdp = termio_db; tdp->td_name != NULL; tdp++) { if (strcmp(tdp->td_name, name) == 0) return (OK); } *err = 0; return (ERR); } int restartterm(char *name, int fd, int *err) { const termio_desc_t *otdp = tdp; int status; if ((status = setupterm(name, fd, err)) != OK) tdp = otdp; /* restore old terminal settings */ return (status); } const char * tigetstr(const char *name) { const termio_attr_t *tap; for (tap = tdp->td_data; tap->ta_name != NULL; tap++) { if (strcmp(tap->ta_name, name) == 0) { if (tap->ta_type == TIO_ATTR_REQSTR || tap->ta_type == TIO_ATTR_STR) return (tap->ta_data); else return ((char *)-1); } } return (NULL); } int tigetflag(const char *name) { const termio_attr_t *tap; for (tap = tdp->td_data; tap->ta_name != NULL; tap++) { if (strcmp(tap->ta_name, name) == 0) { if (tap->ta_type == TIO_ATTR_BOOL) return ((uintptr_t)tap->ta_data); else return (-1); } } return (0); } int tigetnum(const char *name) { const termio_attr_t *tap; for (tap = tdp->td_data; tap->ta_name != NULL; tap++) { if (strcmp(tap->ta_name, name) == 0) { if (tap->ta_type == TIO_ATTR_INT) return ((uintptr_t)tap->ta_data); else return (-2); } } return (-1); } /* END EPILOGUE */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #define UMEM_STANDALONE #include /* * The standalone umem requires that kmdb provide some error-handling * services. These are them. */ /*ARGSUSED*/ int __umem_assert_failed(const char *assertion, const char *file, int line) { #ifdef DEBUG (void) mdb_dassert(assertion, file, line); /*NOTREACHED*/ #endif return (0); } void umem_panic(const char *format, ...) { va_list alist; va_start(alist, format); vfail(format, alist); va_end(alist); } void umem_err_recoverable(const char *format, ...) { va_list alist; va_start(alist, format); vwarn(format, alist); va_end(alist); } int umem_vsnprintf(char *s, size_t n, const char *format, va_list ap) { return (mdb_iob_vsnprintf(s, n, format, ap)); } int umem_snprintf(char *s, size_t n, const char *format, ...) { va_list ap; int rc; va_start(ap, format); rc = umem_vsnprintf(s, n, format, ap); va_end(ap); return (rc); } /* These aren't atomic, but we're not MT, so it doesn't matter */ uint32_t umem_atomic_add_32_nv(uint32_t *target, int32_t delta) { return (*target = *target + delta); } void umem_atomic_add_64(uint64_t *target, int64_t delta) { *target = *target + delta; } uint64_t umem_atomic_swap_64(volatile uint64_t *t, uint64_t v) { uint64_t old = *t; *t = v; return (old); } /* * Standalone umem must be manually initialized */ void mdb_umem_startup(caddr_t base, size_t len, size_t pgsize) { umem_startup(base, len, pgsize, base, base + len); } /* * The kernel will tell us when there's more memory available for us to use. * This is most common on amd64, which boots with only 4G of VA available, and * later expands to the full 64-bit address space. */ int mdb_umem_add(caddr_t base, size_t len) { return (umem_add(base, len)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_UMEMGLUE_H #define _KMDB_UMEMGLUE_H /* * kmdb interface to libumem */ #ifdef __cplusplus extern "C" { #endif extern void mdb_umem_startup(caddr_t, size_t, size_t); extern int mdb_umem_add(caddr_t, size_t); #ifdef __cplusplus } #endif #endif /* _KMDB_UMEMGLUE_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * The communication mechanism for requesting that the driver perform work on * behalf of the debugger. Messages are passed and processed in FIFO order, * with no provision for high priority messages. High priority messages, such * as debugger termination requests, should be passed using a different * mechanism. * * Two FIFO queues are used for communication - one from the debugger to the * driver, known as the driver_notify queue, and one from the driver to the * debugger, known as the debugger_notify queue. Messages are added to one * queue, processed by the party on the other end, and are sent back as * acknowledgements on the other queue. All messages must be acknowledged, in * part because the party who sent the message is the only one who can free it. * * Debugger-initiated work requests are usually triggered by dcmds such as * ::load. In the case of a ::load, the debugger adds a load request to the * driver_notify queue. The driver removes the request from the queue and * processes it. When processing is complete, the message is turned into an * acknowledgement, and completion status is added. The message is then added * to the debugger_notify queue. Upon receipt, the debugger removes the * message from the queue, notes the completion status, and frees it. * * The driver can itself initiate unsolicited work, such as the automatic * loading of a dmod in response to a krtld module load notification. In this * case, the driver loads the module and creates a work-completion message. * This completion is identical to the one sent in the solicited load case * above, with the exception of the acknowledgement bit, which isn't be set. * When the debugger receives the completion message, it notes the completion * status, and sends the message back to the driver via the driver_notify queue, * this time with the acknowledgement bit set. */ #include #include #include #include #include /* * Called by the driver to pass a message to the debugger. The debugger could * start running at any time. Nodes are added to the queue in FIFO order, but * with links pointing in reverse order. */ void kmdb_wr_debugger_notify(void *arg) { kmdb_wr_t *new = arg; kmdb_wr_t *curtail; new->wn_next = new->wn_prev = NULL; membar_producer(); do { if ((curtail = mdb.m_dbgwrtail) == NULL) { /* * The queue is empty, because tail will only be NULL if * head is NULL too. We're the only one who can add * to the queue, so we can blindly add our node. The * debugger can't look at tail until head is non-NULL, * so we set tail first. */ mdb.m_dbgwrtail = new; membar_producer(); mdb.m_dbgwrhead = new; membar_producer(); break; } /* * Point the new node at the current tail. Attempt to set tail * to point to our new node, but only as long as tail is what * we think it is. */ new->wn_prev = curtail; membar_producer(); } while (cas((uintptr_t *)&mdb.m_dbgwrtail, (uintptr_t)curtail, (uintptr_t)new) != (uintptr_t)curtail); } /* * Called by the debugger to receive messages from the driver. The driver * has added the nodes in FIFO order, but has only set the prev pointers. We * have to correct that before processing the nodes. This routine will not * be preempted. */ int kmdb_wr_debugger_process(int (*cb)(kmdb_wr_t *, void *), void *arg) { kmdb_wr_t *wn, *wnn; int i; if (mdb.m_dbgwrhead == NULL) return (0); /* The queue is empty, so there's nothing to do */ /* Re-establish the next links so we can traverse in FIFO order */ mdb.m_dbgwrtail->wn_next = NULL; for (wn = mdb.m_dbgwrtail; wn->wn_prev != NULL; wn = wn->wn_prev) wn->wn_prev->wn_next = wn; /* We don't own wn after we've invoked the callback */ wn = mdb.m_dbgwrhead; i = 0; do { wnn = wn->wn_next; i += cb(wn, arg); } while ((wn = wnn) != NULL); mdb.m_dbgwrhead = mdb.m_dbgwrtail = NULL; return (i); } /* * Called by the debugger to check queue status. */ int kmdb_wr_debugger_notify_isempty(void) { return (mdb.m_dbgwrhead == NULL); } /* * Called by the debugger to pass a message to the driver. This routine will * not be preempted. */ void kmdb_wr_driver_notify(void *arg) { kmdb_wr_t *new = arg; /* * We restrict ourselves to manipulating the rear of the queue. We * don't look at the head unless the tail is NULL. */ if (mdb.m_drvwrtail == NULL) { new->wn_next = new->wn_prev = NULL; mdb.m_drvwrhead = mdb.m_drvwrtail = new; } else { mdb.m_drvwrtail->wn_next = new; new->wn_prev = mdb.m_drvwrtail; new->wn_next = NULL; mdb.m_drvwrtail = new; } } /* * Called by the driver to receive messages from the debugger. The debugger * could start running at any time. * * NOTE: This routine may run *after* mdb_destroy(), and may *NOT* use any MDB * services. */ int kmdb_wr_driver_process(int (*cb)(kmdb_wr_t *, void *), void *arg) { kmdb_wr_t *worklist, *wn, *wnn; int rc, rv, i; if ((worklist = mdb.m_drvwrhead) == NULL) { return (0); /* The queue is empty, so there's nothing to do */ } mdb.m_drvwrhead = NULL; /* The debugger uses tail, so enqueues still work */ membar_producer(); mdb.m_drvwrtail = NULL; membar_producer(); /* * The current set of messages has been removed from the queue, so * we can process them at our leisure. */ wn = worklist; rc = i = 0; do { wnn = wn->wn_next; if ((rv = cb(wn, arg)) < 0) rc = -1; else i += rv; } while ((wn = wnn) != NULL); return (rc == 0 ? i : -1); } /* * Called by the debugger to check queue status */ int kmdb_wr_driver_notify_isempty(void) { return (mdb.m_drvwrhead == NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_WR_H #define _KMDB_WR_H #include #ifdef __cplusplus extern "C" { #endif typedef struct kmdb_wr kmdb_wr_t; extern void kmdb_wr_debugger_notify(void *); extern int kmdb_wr_debugger_process(int (*)(kmdb_wr_t *, void *), void *); extern int kmdb_wr_debugger_notify_isempty(void); extern void kmdb_wr_driver_notify(void *); extern int kmdb_wr_driver_process(int (*)(kmdb_wr_t *, void *), void *); extern int kmdb_wr_driver_notify_isempty(void); #ifdef __cplusplus } #endif #endif /* _KMDB_WR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _KMDB_WR_IMPL_H #define _KMDB_WR_IMPL_H #include #ifdef __cplusplus extern "C" { #endif #define WNTASK_DMOD_LOAD 0x0001 /* Load a specific dmod */ #define WNTASK_DMOD_LOAD_ALL 0x0002 /* Load all dmods for kmods */ #define WNTASK_DMOD_UNLOAD 0x0004 /* Unload a specific dmod */ #define WNTASK_DMOD_PATH_CHANGE 0x0008 /* Change dmod search path */ #define WNFLAGS_NOFREE 0x0001 /* Don't free this wr on ack */ #define WNTASK_ACK 0x8000 /* Acknowledgement of req */ #define WR_ISACK(wr) ((((kmdb_wr_t *)(wr))->wn_task) & WNTASK_ACK) #define WR_ACK(wr) (((kmdb_wr_t *)(wr))->wn_task) |= WNTASK_ACK #define WR_TASK(wr) ((((kmdb_wr_t *)(wr))->wn_task) & ~WNTASK_ACK) struct kmdb_wr { struct kmdb_wr *wn_next; /* List of work requests */ struct kmdb_wr *wn_prev; /* List of work requests */ ushort_t wn_task; /* Task to be performed */ ushort_t wn_flags; /* Flags for this request */ uint_t wn_errno; /* Status for completed reqs */ }; /* * Debugger-initiated loads: Debugger creates, passes to driver, driver loads * the module, returns the request as an ack. Driver-initiated loads: driver * creates, loads module, passes to debugger as announcement, debugger returns * as an ack. */ typedef struct kmdb_wr_load { kmdb_wr_t dlr_node; /* Supplied by requestor */ char *dlr_fname; /* Filled in by driver upon successful completion */ struct modctl *dlr_modctl; /* * Used by the driver to track outstanding driver-initiated * notifications for leak prevention. */ struct kmdb_wr_load *dlr_next; struct kmdb_wr_load *dlr_prev; } kmdb_wr_load_t; #define dlr_errno dlr_node.wn_errno /* * The debugger creates a request for a module to be unloaded, and passes it * to the driver. The driver unloads the module, and returns the message to * the debugger as an ack. */ typedef struct kmdb_wr_unload { kmdb_wr_t dur_node; /* Supplied by requestor */ char *dur_modname; struct modctl *dur_modctl; } kmdb_wr_unload_t; #define dur_errno dur_node.wn_errno /* * The debugger creates a new path-change "request" when the dmod search path * changes, and sends it to the driver. The driver hangs onto the request * until either the path changes again or the debugger is unloaded. Either way, * the state change request is passed back at that time as an ack. */ typedef struct kmdb_wr_path { kmdb_wr_t dpth_node; /* Supplied by requestor */ const char **dpth_path; size_t dpth_pathlen; } kmdb_wr_path_t; #define dpth_errno dpth_node.wn_errno #ifdef __cplusplus } #endif #endif /* _KMDB_WR_IMPL_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Joyent, Inc. * Copyright 2025 Oxide Computer Company */ #ifndef _KVM_H #define _KVM_H /* * The kmdb target */ #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define KM_F_PRIMARY 1 #define KMT_TRAP_NOTENUM -1 /* Glob for unnamed traps */ #define KMT_TRAP_ALL -2 /* Glob for all traps */ typedef struct kmt_module { mdb_list_t km_list; /* List forward/back pointers */ char *km_name; /* Module name */ char km_seen; GElf_Ehdr km_ehdr; mdb_gelf_symtab_t *km_symtab; Shdr km_symtab_hdr; Shdr km_strtab_hdr; const void *km_symtab_va; const void *km_strtab_va; uintptr_t km_text_va; size_t km_text_size; uintptr_t km_data_va; size_t km_data_size; uintptr_t km_bss_va; size_t km_bss_size; const void *km_ctf_va; size_t km_ctf_size; ctf_file_t *km_ctfp; struct modctl km_modctl; struct module km_module; int km_flags; } kmt_module_t; typedef struct kmt_data { const mdb_tgt_regdesc_t *kmt_rds; /* Register description table */ mdb_nv_t kmt_modules; /* Hash table of modules */ mdb_list_t kmt_modlist; /* List of mods in load order */ const char *kmt_rtld_name; /* Module containing krtld */ caddr_t kmt_writemap; /* Used to map PAs for writes */ size_t kmt_writemapsz; /* Size of same */ mdb_map_t kmt_map; /* Persistant map for callers */ ulong_t *kmt_trapmap; size_t kmt_trapmax; int kmt_symavail; /* Symbol resolution allowed */ uint_t kmt_narmedbpts; /* Number of armed brkpts */ #if defined(__i386) || defined(__amd64) struct { GElf_Sym _kmt_cmnint; GElf_Sym _kmt_cmntrap; GElf_Sym _kmt_sysenter; GElf_Sym _kmt_brand_sysenter; #if defined(__amd64) GElf_Sym _kmt_syscall; GElf_Sym _kmt_brand_syscall; #endif } kmt_intrsyms; #endif } kmt_data_t; #if defined(__i386) || defined(__amd64) #define kmt_cmnint kmt_intrsyms._kmt_cmnint #define kmt_cmntrap kmt_intrsyms._kmt_cmntrap #endif typedef struct kmt_defbp { mdb_list_t dbp_bplist; char *dbp_objname; char *dbp_symname; int dbp_ref; } kmt_defbp_t; typedef struct kmt_brkpt { uintptr_t kb_addr; /* Breakpoint address */ mdb_instr_t kb_oinstr; /* Replaced instruction */ } kmt_brkpt_t; typedef struct kmt_bparg { uintptr_t ka_addr; /* Explicit address */ char *ka_symbol; /* Symbolic name */ kmt_defbp_t *ka_defbp; } kmt_bparg_t; extern void kmt_printregs(const mdb_tgt_gregset_t *gregs); extern const char *kmt_def_dismode(void); extern void kmt_init_isadep(mdb_tgt_t *); extern void kmt_startup_isadep(mdb_tgt_t *); extern ssize_t kmt_write(mdb_tgt_t *, const void *, size_t, uintptr_t); extern ssize_t kmt_pwrite(mdb_tgt_t *, const void *, size_t, physaddr_t); extern ssize_t kmt_rw(mdb_tgt_t *, void *, size_t, uint64_t, ssize_t (*)(void *, size_t, uint64_t)); extern ssize_t kmt_writer(void *, size_t, uint64_t); extern ssize_t kmt_ioread(mdb_tgt_t *, void *, size_t, uintptr_t); extern ssize_t kmt_iowrite(mdb_tgt_t *, const void *, size_t, uintptr_t); extern int kmt_step_out(mdb_tgt_t *, uintptr_t *); extern int kmt_next(mdb_tgt_t *, uintptr_t *); extern int kmt_stack(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmt_stackv(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmt_stackr(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmt_cpustack(uintptr_t, uint_t, int, const mdb_arg_t *, int, uint_t); extern const char *kmt_trapname(int); #ifdef __cplusplus } #endif #endif /* _KVM_H */ /* * 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 */ /* BEGIN PROLOGUE */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Garrett D'Amore */ /* * This file is used to limit the symbols that are to be exported from the * debugger. This ensures that dmods follow the module API. * * There is a special rule for generating the mapfile. If the symbol * is not a function then the symbol, in the mapfile, must have the * the token "variable" as the third symbol on the line - see the * entries for __ctype and errno below. */ $mapfile_version 2 SYMBOL_SCOPE { global: /* END PROLOGUE */ /* BEGIN EPILOGUE */ /* * Secret additions to the module API */ /* There should be only one - ours */ errno; /* variable */ isprint; isalnum; isalpha; isgraph; iscntrl; isdigit; isxdigit; isupper; islower; ispunct; isspace; strlcpy; mdb_tgt_aread; mdb_dis_create; mdb_dis_destroy; local: *; }; /* END EPILOGUE */ Copyright (c) 1980 Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors. 4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PORTIONS OF MDB COMMAND FUNCTIONALITY /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 1980 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ /* * This localtime is a modified version of offtime from libc, which does not * bother to figure out the time zone from the kernel, from environment * variables, or from Unix files. */ #include #include #include #include static int mon_lengths[2][MONSPERYEAR] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static int year_lengths[2] = { DAYSPERNYEAR, DAYSPERLYEAR }; struct tm * localtime(const time_t *clock) { struct tm *tmp; long days; long rem; int y; int yleap; int *ip; static struct tm tm; tmp = &tm; days = *clock / SECSPERDAY; rem = *clock % SECSPERDAY; while (rem < 0) { rem += SECSPERDAY; --days; } while (rem >= SECSPERDAY) { rem -= SECSPERDAY; ++days; } tmp->tm_hour = (int)(rem / SECSPERHOUR); rem = rem % SECSPERHOUR; tmp->tm_min = (int)(rem / SECSPERMIN); tmp->tm_sec = (int)(rem % SECSPERMIN); tmp->tm_wday = (int)((EPOCH_WDAY + days) % DAYSPERWEEK); if (tmp->tm_wday < 0) tmp->tm_wday += DAYSPERWEEK; y = EPOCH_YEAR; if (days >= 0) { for (;;) { yleap = isleap(y); if (days < (long)year_lengths[yleap]) break; if (++y > 9999) { errno = EOVERFLOW; return (NULL); } days = days - (long)year_lengths[yleap]; } } else { do { if (--y < 0) { errno = EOVERFLOW; return (NULL); } yleap = isleap(y); days = days + (long)year_lengths[yleap]; } while (days < 0); } tmp->tm_year = y - TM_YEAR_BASE; tmp->tm_yday = (int)days; ip = mon_lengths[yleap]; for (tmp->tm_mon = 0; days >= (long)ip[tmp->tm_mon]; ++(tmp->tm_mon)) days = days - (long)ip[tmp->tm_mon]; tmp->tm_mday = (int)(days + 1); tmp->tm_isdst = 0; return (tmp); } /* * So is ctime... */ /* * This routine converts time as follows. * The epoch is 0000 Jan 1 1970 GMT. * The argument time is in seconds since then. * The localtime(t) entry returns a pointer to an array * containing * seconds (0-59) * minutes (0-59) * hours (0-23) * day of month (1-31) * month (0-11) * year-1970 * weekday (0-6, Sun is 0) * day of the year * daylight savings flag * * The routine corrects for daylight saving * time and will work in any time zone provided * "timezone" is adjusted to the difference between * Greenwich and local standard time (measured in seconds). * In places like Michigan "daylight" must * be initialized to 0 to prevent the conversion * to daylight time. * There is a table which accounts for the peculiarities * undergone by daylight time in 1974-1975. * * The routine does not work * in Saudi Arabia which runs on Solar time. * * asctime(tvec) * where tvec is produced by localtime * returns a ptr to a character string * that has the ascii time in the form * Thu Jan 01 00:00:00 1970\n\0 * 01234567890123456789012345 * 0 1 2 * * ctime(t) just calls localtime, then asctime. * * tzset() looks for an environment variable named * TZ. * If the variable is present, it will set the external * variables "timezone", "altzone", "daylight", and "tzname" * appropriately. It is called by localtime, and * may also be called explicitly by the user. */ #define dysize(A) (((A)%4)? 365: 366) #define CBUFSIZ 26 static char *ct_numb(); /* * POSIX.1c standard version of the function asctime_r. * User gets it via static asctime_r from the header file. */ char * __posix_asctime_r(const struct tm *t, char *cbuf) { const char *Date = "Day Mon 00 00:00:00 1900\n"; const char *Day = "SunMonTueWedThuFriSat"; const char *Month = "JanFebMarAprMayJunJulAugSepOctNovDec"; const char *ncp; const int *tp; char *cp; if (t == NULL) return (NULL); cp = cbuf; for (ncp = Date; *cp++ = *ncp++; /* */) ; ncp = Day + (3*t->tm_wday); cp = cbuf; *cp++ = *ncp++; *cp++ = *ncp++; *cp++ = *ncp++; cp++; tp = &t->tm_mon; ncp = Month + ((*tp) * 3); *cp++ = *ncp++; *cp++ = *ncp++; *cp++ = *ncp++; cp = ct_numb(cp, *--tp); cp = ct_numb(cp, *--tp+100); cp = ct_numb(cp, *--tp+100); cp = ct_numb(cp, *--tp+100); if (t->tm_year > 9999) { errno = EOVERFLOW; return (NULL); } else { uint_t hun = 19 + (t->tm_year / 100); cp[1] = (hun / 10) + '0'; cp[2] = (hun % 10) + '0'; } cp += 2; cp = ct_numb(cp, t->tm_year+100); return (cbuf); } /* * POSIX.1c Draft-6 version of the function asctime_r. * It was implemented by Solaris 2.3. */ char * asctime_r(const struct tm *t, char *cbuf, int buflen) { if (buflen < CBUFSIZ) { errno = ERANGE; return (NULL); } return (__posix_asctime_r(t, cbuf)); } char * ctime(const time_t *t) { return (asctime(localtime(t))); } char * asctime(const struct tm *t) { static char cbuf[CBUFSIZ]; return (asctime_r(t, cbuf, CBUFSIZ)); } static char * ct_numb(char *cp, int n) { cp++; if (n >= 10) *cp++ = (n/10)%10 + '0'; else *cp++ = ' '; /* Pad with blanks */ *cp++ = n%10 + '0'; return (cp); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2014 Garrett D'Amore */ /* * ASCII versions of ctype character classification functions. This avoids * pulling in the entire locale framework that is in libc. */ int isdigit(int c) { return ((c >= '0' && c <= '9') ? 1 : 0); } int isupper(int c) { return ((c >= 'A' && c <= 'Z') ? 1 : 0); } int islower(int c) { return ((c >= 'a' && c <= 'z') ? 1 : 0); } int isspace(int c) { return (((c == ' ') || (c == '\t') || (c == '\r') || (c == '\n') || (c == '\v') || (c == '\f')) ? 1 : 0); } int isxdigit(int c) { return ((isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) ? 1 : 0); } int isalpha(int c) { return ((isupper(c) || islower(c)) ? 1 : 0); } int isalnum(int c) { return ((isalpha(c) || isdigit(c)) ? 1 : 0); } int ispunct(int c) { return (((c >= '!') && (c <= '/')) || ((c >= ':') && (c <= '@')) || ((c >= '[') && (c <= '`')) || ((c >= '{') && (c <= '~'))); } int iscntrl(int c) { return ((c < 0x20) || (c == 0x7f)); } int isprint(int c) { /* * Almost the inverse of iscntrl, but be careful that c > 0x7f * returns false for everything. */ return ((c >= ' ') && (c <= '~')); } int isgraph(int c) { /* isgraph is like is print, but excludes */ return ((c >= '!') && (c <= '~')); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include int errno; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #define EOF (-1) int opterr = 1, optind = 1, optopt = 0; char *optarg = NULL; int _sp = 1; extern void warn(const char *, ...); void getopt_reset(void) { opterr = 1; optind = 1; optopt = 0; optarg = NULL; _sp = 1; } int getopt(int argc, char *const *argv, const char *opts) { char c; char *cp; if (_sp == 1) { if (optind >= argc || argv[optind][0] != '-' || argv[optind] == NULL || argv[optind][1] == '\0') return (EOF); else if (strcmp(argv[optind], "--") == 0) { optind++; return (EOF); } } optopt = c = (unsigned char)argv[optind][_sp]; if (c == ':' || (cp = strchr(opts, c)) == NULL) { if (opts[0] != ':') warn("%s: illegal option -- %c\n", argv[0], c); if (argv[optind][++_sp] == '\0') { optind++; _sp = 1; } return ('?'); } if (*(cp + 1) == ':') { if (argv[optind][_sp+1] != '\0') optarg = &argv[optind++][_sp+1]; else if (++optind >= argc) { if (opts[0] != ':') { warn("%s: option requires an argument" " -- %c\n", argv[0], c); } _sp = 1; optarg = NULL; return (opts[0] == ':' ? ':' : '?'); } else optarg = argv[optind++]; _sp = 1; } else { if (argv[optind][++_sp] == '\0') { _sp = 1; optind++; } optarg = NULL; } return (c); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYS_SALIB_H #define _SYS_SALIB_H #include #include #include #ifdef __cplusplus extern "C" { #endif extern char *asctime(const struct tm *); extern char *ctime(const time_t *); extern int bcmp(const void *, const void *, size_t); extern void bcopy(const void *, void *, size_t); extern void *bsearch(const void *, const void *, size_t, size_t, int (*)(const void *, const void *)); extern void bzero(void *, size_t); extern int getopt(int, char *const [], const char *); extern void getopt_reset(void); extern void *memchr(const void *, int, size_t); extern int memcmp(const void *, const void *, size_t); extern void *memcpy(void *, const void *, size_t); extern void *memccpy(void *, const void *, int, size_t); extern void *memmove(void *, const void *, size_t); extern void *memset(void *, int, size_t); extern void qsort(void *, size_t, size_t, int (*)(const void *, const void *)); extern long strtol(const char *, char **, int); extern unsigned long strtoul(const char *, char **, int); extern char *strcat(char *, const char *); extern char *strchr(const char *, int); extern int strcmp(const char *, const char *); extern char *strcpy(char *, const char *); extern int strcasecmp(const char *, const char *); extern int strncasecmp(const char *, const char *, size_t); extern size_t strlcpy(char *, const char *, size_t); extern size_t strlen(const char *); extern char *strncat(char *, const char *, size_t); extern size_t strlcat(char *, const char *, size_t); extern int strncmp(const char *, const char *, size_t); extern char *strncpy(char *, const char *, size_t); extern char *strrchr(const char *, int); extern char *strstr(const char *, const char *); extern size_t strspn(const char *, const char *); extern char *strpbrk(const char *, const char *); extern char *strtok(char *, const char *); #ifdef __cplusplus } #endif #endif /* _SYS_SALIB_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include /*ARGSUSED*/ void * ctf_zopen(int *errp) { /* The kernel will have decompressed the buffer for us */ return (ctf_set_open_errno(errp, ECTF_ZMISSING)); } /*ARGSUSED*/ const void * ctf_sect_mmap(ctf_sect_t *sp, int fd) { return (MAP_FAILED); /* we don't support this in kmdb */ } /*ARGSUSED*/ void ctf_sect_munmap(const ctf_sect_t *sp) { /* we don't support this in kmdb */ } /*ARGSUSED*/ ctf_file_t * ctf_fdopen(int fd, int *errp) { return (ctf_set_open_errno(errp, ENOTSUP)); } /*ARGSUSED*/ ctf_file_t * ctf_fdcreate_int(int fd, int *errp, ctf_sect_t *ctfp) { return (ctf_set_open_errno(errp, ENOTSUP)); } /*ARGSUSED*/ ctf_file_t * ctf_open(const char *filename, int *errp) { return (ctf_set_open_errno(errp, ENOTSUP)); } int ctf_version(int version) { ASSERT(version > 0 && version <= CTF_VERSION); if (version > 0) _libctf_version = MIN(CTF_VERSION, version); return (_libctf_version); } void * ctf_data_alloc(size_t size) { void *buf = mdb_alloc(size, UM_NOSLEEP); if (buf == NULL) return (MAP_FAILED); return (buf); } void ctf_data_free(void *buf, size_t size) { mdb_free(buf, size); } /*ARGSUSED*/ void ctf_data_protect(void *buf, size_t size) { /* Not supported in kmdb */ } void * ctf_alloc(size_t size) { return (mdb_alloc(size, UM_NOSLEEP)); } void ctf_free(void *buf, size_t size) { mdb_free(buf, size); } /*ARGSUSED*/ const char * ctf_strerror(int err) { return (NULL); /* Not supported in kmdb */ } /*PRINTFLIKE1*/ void ctf_dprintf(const char *format, ...) { va_list alist; va_start(alist, format); mdb_dvprintf(MDB_DBG_CTF, format, alist); va_end(alist); } /*ARGSUSED*/ int z_uncompress(void *dst, size_t *dstlen, const void *src, size_t srclen) { return (Z_ERRNO); } /*ARGSUSED*/ const char * z_strerror(int err) { return ("zlib unsupported in kmdb"); } int ctf_vsnprintf(char *buf, size_t nbytes, const char *format, va_list alist) { return ((int)mdb_iob_vsnprintf(buf, nbytes, format, alist)); } # # Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. # # 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 (c) 2012, Joyent, Inc. # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_SCOPE { global: ctf_add_array; ctf_add_float; ctf_add_integer; ctf_add_member; ctf_add_pointer; ctf_add_struct; ctf_add_type; ctf_add_typedef; ctf_add_union; ctf_array_info; ctf_bufopen; ctf_close; ctf_create; ctf_delete_type; ctf_discard; ctf_enum_iter; ctf_enum_name; ctf_enum_value; ctf_errmsg; ctf_errno; ctf_fdopen; ctf_func_args; ctf_func_info; ctf_getmodel; ctf_getspecific; ctf_import; ctf_label_info; ctf_label_iter; ctf_label_topmost; ctf_lookup_by_name; ctf_lookup_by_symbol; ctf_member_info; ctf_member_iter; ctf_open; ctf_parent_name; ctf_setmodel; ctf_setspecific; ctf_type_align; ctf_type_cmp; ctf_type_encoding; ctf_type_iter; ctf_type_lname; ctf_type_kind; ctf_type_name; ctf_type_pointer; ctf_type_reference; ctf_type_resolve; ctf_type_size; ctf_type_visit; ctf_update; ctf_version; local: *; }; # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 (c) 1997-1999 by Sun Microsystems, Inc. # All rights reserved. # #ident "%Z%%M% %I% %E% SMI" include ../../../Makefile.cmd HDRS = mdb_modapi.h ROOTHDRDIR = $(ROOT)/usr/include/sys ROOTHDRS = $(HDRS:%=$(ROOTHDRDIR)/%) $(ROOTHDRS) : FILEMODE= 0644 $(ROOTHDRDIR)/%.h: %.h $(INS.file) .KEEP_STATE: .SUFFIXES: install_h: $(ROOTHDRS) /* * 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 2021 Joyent, Inc. */ /* * Modular Debugger (MDB) * * Refer to the white paper "A Modular Debugger for Solaris" for information * on the design, features, and goals of MDB. See /shared/sac/PSARC/1999/169 * for copies of the paper and related documentation. * * This file provides the basic construction and destruction of the debugger's * global state, as well as the main execution loop, mdb_run(). MDB maintains * a stack of execution frames (mdb_frame_t's) that keep track of its current * state, including a stack of input and output buffers, walk and memory * garbage collect lists, and a list of commands (mdb_cmd_t's). As the * parser consumes input, it fills in a list of commands to execute, and then * invokes mdb_call(), below. A command consists of a dcmd, telling us * what function to execute, and a list of arguments and other invocation- * specific data. Each frame may have more than one command, kept on a list, * when multiple commands are separated by | operators. New frames may be * stacked on old ones by nested calls to mdb_run: this occurs when, for * example, in the middle of processing one input source (such as a file * or the terminal), we invoke a dcmd that in turn calls mdb_eval(). mdb_eval * will construct a new frame whose input source is the string passed to * the eval function, and then execute this frame to completion. */ #include #include #define _MDB_PRIVATE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _KMDB #include #endif /* * Macro for testing if a dcmd's return status (x) indicates that we should * abort the current loop or pipeline. */ #define DCMD_ABORTED(x) ((x) == DCMD_USAGE || (x) == DCMD_ABORT) extern const mdb_dcmd_t mdb_dcmd_builtins[]; extern const mdb_walker_t mdb_walker_builtins[]; extern mdb_dis_ctor_f *const mdb_dis_builtins[]; /* * Variable discipline for toggling MDB_FL_PSYM based on the value of the * undocumented '_' variable. Once adb(1) has been removed from the system, * we should just remove this functionality and always disable PSYM for macros. */ static uintmax_t psym_disc_get(const mdb_var_t *v) { int i = (mdb.m_flags & MDB_FL_PSYM) ? 1 : 0; int j = (MDB_NV_VALUE(v) != 0) ? 1 : 0; if ((i ^ j) == 0) MDB_NV_VALUE((mdb_var_t *)v) = j ^ 1; return (MDB_NV_VALUE(v)); } static void psym_disc_set(mdb_var_t *v, uintmax_t value) { if (value == 0) mdb.m_flags |= MDB_FL_PSYM; else mdb.m_flags &= ~MDB_FL_PSYM; MDB_NV_VALUE(v) = value; } /* * Variable discipline for making <1 (most recent offset) behave properly. */ static uintmax_t roff_disc_get(const mdb_var_t *v) { return (MDB_NV_VALUE(v)); } static void roff_disc_set(mdb_var_t *v, uintmax_t value) { mdb_nv_set_value(mdb.m_proffset, MDB_NV_VALUE(v)); MDB_NV_VALUE(v) = value; } /* * Variable discipline for exporting the representative thread. */ static uintmax_t thr_disc_get(const mdb_var_t *v) { mdb_tgt_status_t s; if (mdb.m_target != NULL && mdb_tgt_status(mdb.m_target, &s) == 0) return (s.st_tid); return (MDB_NV_VALUE(v)); } const char ** mdb_path_alloc(const char *s, size_t *newlen) { char *format = mdb_alloc(strlen(s) * 2 + 1, UM_NOSLEEP); const char **path; char *p, *q; struct utsname uts; size_t len; int i; mdb_arg_t arg_i, arg_m, arg_p, arg_r, arg_t, arg_R, arg_V; mdb_argvec_t argv; static const char *empty_path[] = { NULL }; if (format == NULL) goto nomem; while (*s == ':') s++; /* strip leading delimiters */ if (*s == '\0') { *newlen = 0; return (empty_path); } (void) strcpy(format, s); mdb_argvec_create(&argv); /* * %i embedded in path string expands to ISA. */ arg_i.a_type = MDB_TYPE_STRING; if (mdb.m_target != NULL) arg_i.a_un.a_str = mdb_tgt_isa(mdb.m_target); else arg_i.a_un.a_str = mdb_conf_isa(); /* * %p embedded in path string expands to the platform name. */ arg_p.a_type = MDB_TYPE_STRING; if (mdb.m_target != NULL) arg_p.a_un.a_str = mdb_tgt_platform(mdb.m_target); else arg_p.a_un.a_str = mdb_conf_platform(); /* * %r embedded in path string expands to root directory, or * to the empty string if root is "/" (to avoid // in paths). */ arg_r.a_type = MDB_TYPE_STRING; arg_r.a_un.a_str = strcmp(mdb.m_root, "/") ? mdb.m_root : ""; /* * %t embedded in path string expands to the target name, defaulting to * kvm; this is so we can find mdb_kb, which is used during bootstrap. */ arg_t.a_type = MDB_TYPE_STRING; arg_t.a_un.a_str = mdb.m_target ? mdb_tgt_name(mdb.m_target) : "kvm"; /* * %R and %V expand to uname -r (release) and uname -v (version). */ if (mdb.m_target == NULL || mdb_tgt_uname(mdb.m_target, &uts) < 0) mdb_conf_uname(&uts); arg_m.a_type = MDB_TYPE_STRING; arg_m.a_un.a_str = uts.machine; arg_R.a_type = MDB_TYPE_STRING; arg_R.a_un.a_str = uts.release; arg_V.a_type = MDB_TYPE_STRING; if (mdb.m_flags & MDB_FL_LATEST) arg_V.a_un.a_str = "latest"; else arg_V.a_un.a_str = uts.version; /* * In order to expand the buffer, we examine the format string for * our % tokens and construct an argvec, replacing each % token * with %s along the way. If we encounter an unknown token, we * shift over the remaining format buffer and stick in %%. */ for (q = format; (q = strchr(q, '%')) != NULL; q++) { switch (q[1]) { case 'i': mdb_argvec_append(&argv, &arg_i); *++q = 's'; break; case 'm': mdb_argvec_append(&argv, &arg_m); *++q = 's'; break; case 'p': mdb_argvec_append(&argv, &arg_p); *++q = 's'; break; case 'r': mdb_argvec_append(&argv, &arg_r); *++q = 's'; break; case 't': mdb_argvec_append(&argv, &arg_t); *++q = 's'; break; case 'R': mdb_argvec_append(&argv, &arg_R); *++q = 's'; break; case 'V': mdb_argvec_append(&argv, &arg_V); *++q = 's'; break; default: bcopy(q + 1, q + 2, strlen(q)); *++q = '%'; } } /* * We're now ready to use our printf engine to format the final string. * Take one lap with a NULL buffer to determine how long the final * string will be, allocate it, and format it. */ len = mdb_iob_asnprintf(NULL, 0, format, argv.a_data); if ((p = mdb_alloc(len + 1, UM_NOSLEEP)) != NULL) (void) mdb_iob_asnprintf(p, len + 1, format, argv.a_data); else goto nomem; mdb_argvec_zero(&argv); mdb_argvec_destroy(&argv); mdb_free(format, strlen(s) * 2 + 1); format = NULL; /* * Compress the string to exclude any leading delimiters. */ for (q = p; *q == ':'; q++) continue; if (q != p) bcopy(q, p, strlen(q) + 1); /* * Count up the number of delimited elements. A sequence of * consecutive delimiters is only counted once. */ for (i = 1, q = p; (q = strchr(q, ':')) != NULL; i++) { while (*q == ':') q++; } if ((path = mdb_alloc(sizeof (char *) * (i + 1), UM_NOSLEEP)) == NULL) { mdb_free(p, len + 1); goto nomem; } for (i = 0, q = strtok(p, ":"); q != NULL; q = strtok(NULL, ":")) path[i++] = q; path[i] = NULL; *newlen = len + 1; return (path); nomem: warn("failed to allocate memory for path"); if (format != NULL) mdb_free(format, strlen(s) * 2 + 1); *newlen = 0; return (empty_path); } const char ** mdb_path_dup(const char *path[], size_t pathlen, size_t *npathlenp) { char **npath; int i, j; for (i = 0; path[i] != NULL; i++) continue; /* count the path elements */ npath = mdb_zalloc(sizeof (char *) * (i + 1), UM_SLEEP); if (pathlen > 0) { npath[0] = mdb_alloc(pathlen, UM_SLEEP); bcopy(path[0], npath[0], pathlen); } for (j = 1; j < i; j++) npath[j] = npath[0] + (path[j] - path[0]); npath[i] = NULL; *npathlenp = pathlen; return ((const char **)npath); } void mdb_path_free(const char *path[], size_t pathlen) { int i; for (i = 0; path[i] != NULL; i++) continue; /* count the path elements */ if (i > 0) { mdb_free((void *)path[0], pathlen); mdb_free(path, sizeof (char *) * (i + 1)); } } /* * Convert path string "s" to canonical form, expanding any %o tokens that are * found within the path. The old path string is specified by "path", a buffer * of size MAXPATHLEN which is then overwritten with the new path string. */ static const char * path_canon(char *path, const char *s) { char *p = path; char *q = p + MAXPATHLEN - 1; char old[MAXPATHLEN]; char c; (void) strcpy(old, p); *q = '\0'; while (p < q && (c = *s++) != '\0') { if (c == '%') { if ((c = *s++) == 'o') { (void) strncpy(p, old, (size_t)(q - p)); p += strlen(p); } else { *p++ = '%'; if (p < q && c != '\0') *p++ = c; else break; } } else *p++ = c; } *p = '\0'; return (path); } void mdb_set_ipath(const char *path) { if (mdb.m_ipath != NULL) mdb_path_free(mdb.m_ipath, mdb.m_ipathlen); path = path_canon(mdb.m_ipathstr, path); mdb.m_ipath = mdb_path_alloc(path, &mdb.m_ipathlen); } void mdb_set_lpath(const char *path) { if (mdb.m_lpath != NULL) mdb_path_free(mdb.m_lpath, mdb.m_lpathlen); path = path_canon(mdb.m_lpathstr, path); mdb.m_lpath = mdb_path_alloc(path, &mdb.m_lpathlen); #ifdef _KMDB kmdb_module_path_set(mdb.m_lpath, mdb.m_lpathlen); #endif } static void prompt_update(void) { (void) mdb_snprintf(mdb.m_prompt, sizeof (mdb.m_prompt), mdb.m_promptraw); mdb.m_promptlen = strlen(mdb.m_prompt); } const char * mdb_get_prompt(void) { if (mdb.m_promptlen == 0) return (NULL); else return (mdb.m_prompt); } int mdb_set_prompt(const char *p) { size_t len = strlen(p); if (len > MDB_PROMPTLEN) { warn("prompt may not exceed %d characters\n", MDB_PROMPTLEN); return (0); } (void) strcpy(mdb.m_promptraw, p); prompt_update(); return (1); } static mdb_frame_t frame0; void mdb_create(const char *execname, const char *arg0) { static const mdb_nv_disc_t psym_disc = { .disc_set = psym_disc_set, .disc_get = psym_disc_get }; static const mdb_nv_disc_t roff_disc = { .disc_set = roff_disc_set, .disc_get = roff_disc_get }; static const mdb_nv_disc_t thr_disc = { .disc_get = thr_disc_get }; static char rootdir[MAXPATHLEN]; const mdb_dcmd_t *dcp; const mdb_walker_t *wcp; int i; bzero(&mdb, sizeof (mdb_t)); mdb.m_flags = MDB_FL_PSYM | MDB_FL_PAGER | MDB_FL_BPTNOSYMSTOP | MDB_FL_READBACK; mdb.m_radix = MDB_DEF_RADIX; mdb.m_nargs = MDB_DEF_NARGS; mdb.m_histlen = MDB_DEF_HISTLEN; mdb.m_armemlim = MDB_DEF_ARRMEM; mdb.m_arstrlim = MDB_DEF_ARRSTR; mdb.m_pname = strbasename(arg0); if (strcmp(mdb.m_pname, "adb") == 0) { mdb.m_flags |= MDB_FL_NOMODS | MDB_FL_ADB | MDB_FL_REPLAST; mdb.m_flags &= ~MDB_FL_PAGER; } mdb.m_ipathstr = mdb_zalloc(MAXPATHLEN, UM_SLEEP); mdb.m_lpathstr = mdb_zalloc(MAXPATHLEN, UM_SLEEP); (void) strncpy(rootdir, execname, sizeof (rootdir)); rootdir[sizeof (rootdir) - 1] = '\0'; (void) strdirname(rootdir); if (strcmp(strbasename(rootdir), "sparcv9") == 0 || strcmp(strbasename(rootdir), "sparcv7") == 0 || strcmp(strbasename(rootdir), "amd64") == 0 || strcmp(strbasename(rootdir), "i86") == 0) (void) strdirname(rootdir); if (strcmp(strbasename(rootdir), "bin") == 0) { (void) strdirname(rootdir); if (strcmp(strbasename(rootdir), "usr") == 0) (void) strdirname(rootdir); } else (void) strcpy(rootdir, "/"); mdb.m_root = rootdir; mdb.m_rminfo.mi_dvers = MDB_API_VERSION; mdb.m_rminfo.mi_dcmds = mdb_dcmd_builtins; mdb.m_rminfo.mi_walkers = mdb_walker_builtins; (void) mdb_nv_create(&mdb.m_rmod.mod_walkers, UM_SLEEP); (void) mdb_nv_create(&mdb.m_rmod.mod_dcmds, UM_SLEEP); mdb.m_rmod.mod_name = mdb.m_pname; mdb.m_rmod.mod_info = &mdb.m_rminfo; (void) mdb_nv_create(&mdb.m_disasms, UM_SLEEP); (void) mdb_nv_create(&mdb.m_modules, UM_SLEEP); (void) mdb_nv_create(&mdb.m_dcmds, UM_SLEEP); (void) mdb_nv_create(&mdb.m_walkers, UM_SLEEP); (void) mdb_nv_create(&mdb.m_nv, UM_SLEEP); mdb.m_dot = mdb_nv_insert(&mdb.m_nv, ".", NULL, 0, MDB_NV_PERSIST); mdb.m_rvalue = mdb_nv_insert(&mdb.m_nv, "0", NULL, 0, MDB_NV_PERSIST); mdb.m_roffset = mdb_nv_insert(&mdb.m_nv, "1", &roff_disc, 0, MDB_NV_PERSIST); mdb.m_proffset = mdb_nv_insert(&mdb.m_nv, "2", NULL, 0, MDB_NV_PERSIST); mdb.m_rcount = mdb_nv_insert(&mdb.m_nv, "9", NULL, 0, MDB_NV_PERSIST); (void) mdb_nv_insert(&mdb.m_nv, "b", NULL, 0, MDB_NV_PERSIST); (void) mdb_nv_insert(&mdb.m_nv, "d", NULL, 0, MDB_NV_PERSIST); (void) mdb_nv_insert(&mdb.m_nv, "e", NULL, 0, MDB_NV_PERSIST); (void) mdb_nv_insert(&mdb.m_nv, "m", NULL, 0, MDB_NV_PERSIST); (void) mdb_nv_insert(&mdb.m_nv, "t", NULL, 0, MDB_NV_PERSIST); (void) mdb_nv_insert(&mdb.m_nv, "_", &psym_disc, 0, MDB_NV_PERSIST); (void) mdb_nv_insert(&mdb.m_nv, "hits", NULL, 0, MDB_NV_PERSIST); (void) mdb_nv_insert(&mdb.m_nv, "thread", &thr_disc, 0, MDB_NV_PERSIST | MDB_NV_RDONLY); mdb.m_prsym = mdb_gelf_symtab_create_mutable(); (void) mdb_nv_insert(&mdb.m_modules, mdb.m_pname, NULL, (uintptr_t)&mdb.m_rmod, MDB_NV_RDONLY); for (dcp = &mdb_dcmd_builtins[0]; dcp->dc_name != NULL; dcp++) (void) mdb_module_add_dcmd(&mdb.m_rmod, dcp, 0); for (wcp = &mdb_walker_builtins[0]; wcp->walk_name != NULL; wcp++) (void) mdb_module_add_walker(&mdb.m_rmod, wcp, 0); for (i = 0; mdb_dis_builtins[i] != NULL; i++) (void) mdb_dis_create(mdb_dis_builtins[i]); mdb_macalias_create(); mdb_create_builtin_tgts(); (void) mdb_callb_add(NULL, MDB_CALLB_PROMPT, (mdb_callb_f)prompt_update, NULL); /* * The call to ctf_create that this does can in fact fail, but that's * okay. All of the ctf functions that might use the synthetic types * make sure that this is safe. */ (void) mdb_ctf_synthetics_init(); #ifdef _KMDB (void) mdb_nv_create(&mdb.m_dmodctl, UM_SLEEP); #endif mdb_lex_state_create(&frame0); mdb_list_append(&mdb.m_flist, &frame0); mdb.m_frame = &frame0; } void mdb_destroy(void) { const mdb_dcmd_t *dcp; mdb_var_t *v; int unload_mode = MDB_MOD_SILENT; #ifdef _KMDB unload_mode |= MDB_MOD_DEFER; #endif mdb_intr_disable(); mdb_ctf_synthetics_fini(); mdb_macalias_destroy(); /* * Some targets use modules during ->t_destroy, so do it first. */ if (mdb.m_target != NULL) (void) mdb_tgt_destroy(mdb.m_target); /* * Unload modules _before_ destroying the disassemblers since a * module that installs a disassembler should try to clean up after * itself. */ mdb_module_unload_all(unload_mode); mdb_nv_rewind(&mdb.m_disasms); while ((v = mdb_nv_advance(&mdb.m_disasms)) != NULL) mdb_dis_destroy(mdb_nv_get_cookie(v)); mdb_callb_remove_all(); if (mdb.m_defdisasm != NULL) strfree(mdb.m_defdisasm); if (mdb.m_prsym != NULL) mdb_gelf_symtab_destroy(mdb.m_prsym); for (dcp = &mdb_dcmd_builtins[0]; dcp->dc_name != NULL; dcp++) (void) mdb_module_remove_dcmd(&mdb.m_rmod, dcp->dc_name); mdb_nv_destroy(&mdb.m_nv); mdb_nv_destroy(&mdb.m_walkers); mdb_nv_destroy(&mdb.m_dcmds); mdb_nv_destroy(&mdb.m_modules); mdb_nv_destroy(&mdb.m_disasms); mdb_free(mdb.m_ipathstr, MAXPATHLEN); mdb_free(mdb.m_lpathstr, MAXPATHLEN); if (mdb.m_ipath != NULL) mdb_path_free(mdb.m_ipath, mdb.m_ipathlen); if (mdb.m_lpath != NULL) mdb_path_free(mdb.m_lpath, mdb.m_lpathlen); if (mdb.m_in != NULL) mdb_iob_destroy(mdb.m_in); mdb_iob_destroy(mdb.m_out); mdb.m_out = NULL; mdb_iob_destroy(mdb.m_err); mdb.m_err = NULL; if (mdb.m_log != NULL) mdb_io_rele(mdb.m_log); mdb_lex_state_destroy(&frame0); } /* * The real main loop of the debugger: create a new execution frame on the * debugger stack, and while we have input available, call into the parser. */ int mdb_run(void) { volatile int err; mdb_frame_t f; mdb_intr_disable(); mdb_frame_push(&f); /* * This is a fresh mdb context, so ignore any pipe command we may have * inherited from the previous frame. */ f.f_pcmd = NULL; if ((err = setjmp(f.f_pcb)) != 0) { int pop = (mdb.m_in != NULL && (mdb_iob_isapipe(mdb.m_in) || mdb_iob_isastr(mdb.m_in))); int fromcmd = (f.f_cp != NULL); mdb_dprintf(MDB_DBG_DSTK, "frame <%u> caught event %s\n", f.f_id, mdb_err2str(err)); /* * If a syntax error or other failure has occurred, pop all * input buffers pushed by commands executed in this frame. */ while (mdb_iob_stack_size(&f.f_istk) != 0) { if (mdb.m_in != NULL) mdb_iob_destroy(mdb.m_in); mdb.m_in = mdb_iob_stack_pop(&f.f_istk); yylineno = mdb_iob_lineno(mdb.m_in); } /* * Reset standard output and the current frame to a known, * clean state, so we can continue execution. */ mdb_iob_margin(mdb.m_out, MDB_IOB_DEFMARGIN); mdb_iob_clrflags(mdb.m_out, MDB_IOB_INDENT); mdb_iob_discard(mdb.m_out); mdb_frame_reset(&f); /* * If there was an error writing to output, display a warning * message if this is the topmost frame. */ if (err == MDB_ERR_OUTPUT && mdb.m_depth == 1 && errno != EPIPE) mdb_warn("write failed"); /* * If an interrupt or quit signal is reported, we may have been * in the middle of typing or processing the command line: * print a newline and discard everything in the parser's iob. * Note that we do this after m_out has been reset, otherwise * we could trigger a pipe context switch or cause a write * to a broken pipe (in the case of a shell command) when * writing the newline. */ if (err == MDB_ERR_SIGINT || err == MDB_ERR_QUIT) { mdb_iob_nl(mdb.m_out); yydiscard(); } /* * If we quit or abort using the output pager, reset the * line count on standard output back to zero. */ if (err == MDB_ERR_PAGER || MDB_ERR_IS_FATAL(err)) mdb_iob_clearlines(mdb.m_out); /* * If the user requested the debugger quit or abort back to * the top, or if standard input is a pipe or mdb_eval("..."), * then propagate the error up the debugger stack. */ if (MDB_ERR_IS_FATAL(err) || pop != 0 || (err == MDB_ERR_PAGER && mdb.m_fmark != &f) || (err == MDB_ERR_NOMEM && !fromcmd)) { mdb_frame_pop(&f, err); return (err); } /* * If we've returned here from a context where signals were * blocked (e.g. a signal handler), we can now unblock them. */ if (err == MDB_ERR_SIGINT) (void) mdb_signal_unblock(SIGINT); } else mdb_intr_enable(); for (;;) { while (mdb.m_in != NULL && (mdb_iob_getflags(mdb.m_in) & (MDB_IOB_ERR | MDB_IOB_EOF)) == 0) { if (mdb.m_depth == 1 && mdb_iob_stack_size(&f.f_istk) == 0) { mdb_iob_clearlines(mdb.m_out); mdb_tgt_periodic(mdb.m_target); } (void) yyparse(); } if (mdb.m_in != NULL) { if (mdb_iob_err(mdb.m_in)) { warn("error reading input stream %s\n", mdb_iob_name(mdb.m_in)); } mdb_iob_destroy(mdb.m_in); mdb.m_in = NULL; } if (mdb_iob_stack_size(&f.f_istk) == 0) break; /* return when we're out of input */ mdb.m_in = mdb_iob_stack_pop(&f.f_istk); yylineno = mdb_iob_lineno(mdb.m_in); } mdb_frame_pop(&f, 0); /* * The value of '.' is a per-frame attribute, to preserve it properly * when switching frames. But in the case of calling mdb_run() * explicitly (such as through mdb_eval), we want to propagate the value * of '.' to the parent. */ mdb_nv_set_value(mdb.m_dot, f.f_dot); return (0); } /* * The read-side of the pipe executes this service routine. We simply call * mdb_run to create a new frame on the execution stack and run the MDB parser, * and then propagate any error code back to the previous frame. */ static int runsvc(void) { int err = mdb_run(); if (err != 0) { mdb_dprintf(MDB_DBG_DSTK, "forwarding error %s from pipeline\n", mdb_err2str(err)); longjmp(mdb.m_frame->f_pcb, err); } return (err); } /* * Read-side pipe service routine: if we longjmp here, just return to the read * routine because now we have more data to consume. Otherwise: * (1) if ctx_data is non-NULL, longjmp to the write-side to produce more data; * (2) if wriob is NULL, there is no writer but this is the first read, so we * can just execute mdb_run() to completion on the current stack; * (3) if (1) and (2) are false, then there is a writer and this is the first * read, so create a co-routine context to execute mdb_run(). */ /*ARGSUSED*/ static void rdsvc(mdb_iob_t *rdiob, mdb_iob_t *wriob, mdb_iob_ctx_t *ctx) { if (setjmp(ctx->ctx_rpcb) == 0) { /* * Save the current standard input into the pipe context, and * reset m_in to point to the pipe. We will restore it on * the way back in wrsvc() below. */ ctx->ctx_iob = mdb.m_in; mdb.m_in = rdiob; ctx->ctx_rptr = mdb.m_frame; if (ctx->ctx_wptr != NULL) mdb_frame_switch(ctx->ctx_wptr); if (ctx->ctx_data != NULL) longjmp(ctx->ctx_wpcb, 1); else if (wriob == NULL) (void) runsvc(); else if ((ctx->ctx_data = mdb_context_create(runsvc)) != NULL) mdb_context_switch(ctx->ctx_data); else mdb_warn("failed to create pipe context"); } } /* * Write-side pipe service routine: if we longjmp here, just return to the * write routine because now we have free space in the pipe buffer for writing; * otherwise longjmp to the read-side to consume data and create space for us. */ /*ARGSUSED*/ static void wrsvc(mdb_iob_t *rdiob, mdb_iob_t *wriob, mdb_iob_ctx_t *ctx) { if (setjmp(ctx->ctx_wpcb) == 0) { ctx->ctx_wptr = mdb.m_frame; if (ctx->ctx_rptr != NULL) mdb_frame_switch(ctx->ctx_rptr); mdb.m_in = ctx->ctx_iob; longjmp(ctx->ctx_rpcb, 1); } } /* * Call the current frame's mdb command. This entry point is used by the * MDB parser to actually execute a command once it has successfully parsed * a line of input. The command is waiting for us in the current frame. * We loop through each command on the list, executing its dcmd with the * appropriate argument. If the command has a successor, we know it had * a | operator after it, and so we need to create a pipe and replace * stdout with the pipe's output buffer. */ int mdb_call(uintmax_t addr, uintmax_t count, uint_t flags) { mdb_frame_t *fp = mdb.m_frame; mdb_cmd_t *cp, *ncp; mdb_iob_t *iobs[2]; int status, err = 0; jmp_buf pcb; if (mdb_iob_isapipe(mdb.m_in)) yyerror("syntax error"); mdb_intr_disable(); fp->f_cp = mdb_list_next(&fp->f_cmds); if (flags & DCMD_LOOP) flags |= DCMD_LOOPFIRST; /* set LOOPFIRST if this is a loop */ for (cp = mdb_list_next(&fp->f_cmds); cp; cp = mdb_list_next(cp)) { if (mdb_list_next(cp) != NULL) { mdb_iob_pipe(iobs, rdsvc, wrsvc); mdb_iob_stack_push(&fp->f_istk, mdb.m_in, yylineno); mdb.m_in = iobs[MDB_IOB_RDIOB]; mdb_iob_stack_push(&fp->f_ostk, mdb.m_out, 0); mdb.m_out = iobs[MDB_IOB_WRIOB]; ncp = mdb_list_next(cp); mdb_vcb_inherit(cp, ncp); bcopy(fp->f_pcb, pcb, sizeof (jmp_buf)); ASSERT(fp->f_pcmd == NULL); fp->f_pcmd = ncp; mdb_frame_set_pipe(fp); if ((err = setjmp(fp->f_pcb)) == 0) { status = mdb_call_idcmd(cp->c_dcmd, addr, count, flags | DCMD_PIPE_OUT, &cp->c_argv, &cp->c_addrv, cp->c_vcbs); mdb.m_lastret = status; ASSERT(mdb.m_in == iobs[MDB_IOB_RDIOB]); ASSERT(mdb.m_out == iobs[MDB_IOB_WRIOB]); } else { mdb_dprintf(MDB_DBG_DSTK, "frame <%u> caught " "error %s from pipeline\n", fp->f_id, mdb_err2str(err)); } if (err != 0 || DCMD_ABORTED(status)) { mdb_iob_setflags(mdb.m_in, MDB_IOB_ERR); mdb_iob_setflags(mdb.m_out, MDB_IOB_ERR); } else { mdb_iob_flush(mdb.m_out); (void) mdb_iob_ctl(mdb.m_out, I_FLUSH, (void *)FLUSHW); } mdb_frame_clear_pipe(fp); mdb_iob_destroy(mdb.m_out); mdb.m_out = mdb_iob_stack_pop(&fp->f_ostk); if (mdb.m_in != NULL) mdb_iob_destroy(mdb.m_in); mdb.m_in = mdb_iob_stack_pop(&fp->f_istk); yylineno = mdb_iob_lineno(mdb.m_in); fp->f_pcmd = NULL; bcopy(pcb, fp->f_pcb, sizeof (jmp_buf)); if (MDB_ERR_IS_FATAL(err)) longjmp(fp->f_pcb, err); if (err != 0 || DCMD_ABORTED(status) || mdb_addrvec_length(&ncp->c_addrv) == 0) break; addr = mdb_nv_get_value(mdb.m_dot); count = 1; flags = 0; } else { mdb_intr_enable(); mdb.m_lastret = mdb_call_idcmd(cp->c_dcmd, addr, count, flags, &cp->c_argv, &cp->c_addrv, cp->c_vcbs); mdb_intr_disable(); } fp->f_cp = mdb_list_next(cp); mdb_cmd_reset(cp); } /* * If our last-command list is non-empty, destroy it. Then copy the * current frame's cmd list to the m_lastc list and reset the frame. */ while ((cp = mdb_list_next(&mdb.m_lastc)) != NULL) { mdb_list_delete(&mdb.m_lastc, cp); mdb_cmd_destroy(cp); } mdb_list_move(&fp->f_cmds, &mdb.m_lastc); mdb_frame_reset(fp); mdb_intr_enable(); return (err == 0); } uintmax_t mdb_dot_incr(const char *op) { uintmax_t odot, ndot; odot = mdb_nv_get_value(mdb.m_dot); ndot = odot + mdb.m_incr; if ((odot ^ ndot) & 0x8000000000000000ull) yyerror("'%s' would cause '.' to overflow\n", op); return (ndot); } uintmax_t mdb_dot_decr(const char *op) { uintmax_t odot, ndot; odot = mdb_nv_get_value(mdb.m_dot); ndot = odot - mdb.m_incr; if (ndot > odot) yyerror("'%s' would cause '.' to underflow\n", op); return (ndot); } mdb_iwalker_t * mdb_walker_lookup(const char *s) { const char *p = strchr(s, '`'); mdb_var_t *v; if (p != NULL) { size_t nbytes = MIN((size_t)(p - s), MDB_NV_NAMELEN - 1); char mname[MDB_NV_NAMELEN]; mdb_module_t *mod; (void) strncpy(mname, s, nbytes); mname[nbytes] = '\0'; if ((v = mdb_nv_lookup(&mdb.m_modules, mname)) == NULL) { (void) set_errno(EMDB_NOMOD); return (NULL); } mod = mdb_nv_get_cookie(v); if ((v = mdb_nv_lookup(&mod->mod_walkers, ++p)) != NULL) return (mdb_nv_get_cookie(v)); } else if ((v = mdb_nv_lookup(&mdb.m_walkers, s)) != NULL) return (mdb_nv_get_cookie(mdb_nv_get_cookie(v))); (void) set_errno(EMDB_NOWALK); return (NULL); } mdb_idcmd_t * mdb_dcmd_lookup(const char *s) { const char *p = strchr(s, '`'); mdb_var_t *v; if (p != NULL) { size_t nbytes = MIN((size_t)(p - s), MDB_NV_NAMELEN - 1); char mname[MDB_NV_NAMELEN]; mdb_module_t *mod; (void) strncpy(mname, s, nbytes); mname[nbytes] = '\0'; if ((v = mdb_nv_lookup(&mdb.m_modules, mname)) == NULL) { (void) set_errno(EMDB_NOMOD); return (NULL); } mod = mdb_nv_get_cookie(v); if ((v = mdb_nv_lookup(&mod->mod_dcmds, ++p)) != NULL) return (mdb_nv_get_cookie(v)); } else if ((v = mdb_nv_lookup(&mdb.m_dcmds, s)) != NULL) return (mdb_nv_get_cookie(mdb_nv_get_cookie(v))); (void) set_errno(EMDB_NODCMD); return (NULL); } void mdb_dcmd_usage(const mdb_idcmd_t *idcp, mdb_iob_t *iob) { const char *prefix = "", *usage = ""; char name0 = idcp->idc_name[0]; if (idcp->idc_usage != NULL) { if (idcp->idc_usage[0] == ':') { if (name0 != ':' && name0 != '$') prefix = "address::"; else prefix = "address"; usage = &idcp->idc_usage[1]; } else if (idcp->idc_usage[0] == '?') { if (name0 != ':' && name0 != '$') prefix = "[address]::"; else prefix = "[address]"; usage = &idcp->idc_usage[1]; } else usage = idcp->idc_usage; } mdb_iob_printf(iob, "Usage: %s%s %s\n", prefix, idcp->idc_name, usage); if (idcp->idc_help != NULL) { mdb_iob_printf(iob, "%s: try '::help %s' for more " "information\n", mdb.m_pname, idcp->idc_name); } } static mdb_idcmd_t * dcmd_ndef(const mdb_idcmd_t *idcp) { mdb_var_t *v = mdb_nv_get_ndef(idcp->idc_var); if (v != NULL) return (mdb_nv_get_cookie(mdb_nv_get_cookie(v))); return (NULL); } static int dcmd_invoke(mdb_idcmd_t *idcp, uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, const mdb_vcb_t *vcbs) { int status; mdb_dprintf(MDB_DBG_DCMD, "dcmd %s`%s dot = %lr incr = %llr\n", idcp->idc_modp->mod_name, idcp->idc_name, addr, mdb.m_incr); if ((status = idcp->idc_funcp(addr, flags, argc, argv)) == DCMD_USAGE) { mdb_dcmd_usage(idcp, mdb.m_err); goto done; } while (status == DCMD_NEXT && (idcp = dcmd_ndef(idcp)) != NULL) status = idcp->idc_funcp(addr, flags, argc, argv); if (status == DCMD_USAGE) mdb_dcmd_usage(idcp, mdb.m_err); if (status == DCMD_NEXT) status = DCMD_OK; done: /* * If standard output is a pipe and there are vcbs active, we need to * flush standard out and the write-side of the pipe. The reasons for * this are explained in more detail in mdb_vcb.c. */ if ((flags & DCMD_PIPE_OUT) && (vcbs != NULL)) { mdb_iob_flush(mdb.m_out); (void) mdb_iob_ctl(mdb.m_out, I_FLUSH, (void *)FLUSHW); } return (status); } void mdb_call_tab(mdb_idcmd_t *idcp, mdb_tab_cookie_t *mcp, uint_t flags, uintmax_t argc, mdb_arg_t *argv) { if (idcp->idc_tabp == NULL) return; (void) idcp->idc_tabp(mcp, flags, argc, argv); } /* * Call an internal dcmd directly: this code is used by module API functions * that need to execute dcmds, and by mdb_call() above. */ int mdb_call_idcmd(mdb_idcmd_t *idcp, uintmax_t addr, uintmax_t count, uint_t flags, mdb_argvec_t *avp, mdb_addrvec_t *adp, mdb_vcb_t *vcbs) { int is_exec = (strcmp(idcp->idc_name, "$<") == 0); mdb_arg_t *argv; int argc; uintmax_t i; int status; /* * Update the values of dot and the most recent address and count * to the values of our input parameters. */ mdb_nv_set_value(mdb.m_dot, addr); mdb.m_raddr = addr; mdb.m_dcount = count; /* * Here the adb(1) man page lies: '9' is only set to count * when the command is $<, not when it's $<<. */ if (is_exec) mdb_nv_set_value(mdb.m_rcount, count); /* * We can now return if the repeat count is zero. */ if (count == 0) return (DCMD_OK); /* * To guard against bad dcmds, we avoid passing the actual argv that * we will use to free argument strings directly to the dcmd. Instead, * we pass a copy that will be garbage collected automatically. */ argc = avp->a_nelems; argv = mdb_alloc(sizeof (mdb_arg_t) * argc, UM_SLEEP | UM_GC); bcopy(avp->a_data, argv, sizeof (mdb_arg_t) * argc); if (mdb_addrvec_length(adp) != 0) { flags |= DCMD_PIPE | DCMD_LOOP | DCMD_LOOPFIRST | DCMD_ADDRSPEC; addr = mdb_addrvec_shift(adp); mdb_nv_set_value(mdb.m_dot, addr); mdb_vcb_propagate(vcbs); count = 1; } status = dcmd_invoke(idcp, addr, flags, argc, argv, vcbs); if (DCMD_ABORTED(status)) goto done; /* * If the command is $< and we're not receiving input from a pipe, we * ignore the repeat count and just return since the macro file is now * pushed on to the input stack. */ if (is_exec && mdb_addrvec_length(adp) == 0) goto done; /* * If we're going to loop, we've already executed the dcmd once, * so clear the LOOPFIRST flag before proceeding. */ if (flags & DCMD_LOOP) flags &= ~DCMD_LOOPFIRST; for (i = 1; i < count; i++) { addr = mdb_dot_incr(","); mdb_nv_set_value(mdb.m_dot, addr); status = dcmd_invoke(idcp, addr, flags, argc, argv, vcbs); if (DCMD_ABORTED(status)) goto done; } while (mdb_addrvec_length(adp) != 0) { addr = mdb_addrvec_shift(adp); mdb_nv_set_value(mdb.m_dot, addr); mdb_vcb_propagate(vcbs); status = dcmd_invoke(idcp, addr, flags, argc, argv, vcbs); if (DCMD_ABORTED(status)) goto done; } done: mdb_iob_nlflush(mdb.m_out); return (status); } void mdb_intr_enable(void) { ASSERT(mdb.m_intr >= 1); if (mdb.m_intr == 1 && mdb.m_pend != 0) { (void) mdb_signal_block(SIGINT); mdb.m_intr = mdb.m_pend = 0; mdb_dprintf(MDB_DBG_DSTK, "delivering pending INT\n"); longjmp(mdb.m_frame->f_pcb, MDB_ERR_SIGINT); } else mdb.m_intr--; } void mdb_intr_disable(void) { mdb.m_intr++; ASSERT(mdb.m_intr >= 1); } /* * Create an encoded string representing the internal user-modifiable * configuration of the debugger and return a pointer to it. The string can be * used to initialize another instance of the debugger with the same * configuration as this one. */ char * mdb_get_config(void) { size_t r, n = 0; char *s = NULL; while ((r = mdb_snprintf(s, n, "%x;%x;%x;%x;%x;%x;%lx;%x;%x;%s;%s;%s;%s;%s", mdb.m_tgtflags, mdb.m_flags, mdb.m_debug, mdb.m_radix, mdb.m_nargs, mdb.m_histlen, (ulong_t)mdb.m_symdist, mdb.m_execmode, mdb.m_forkmode, mdb.m_root, mdb.m_termtype, mdb.m_ipathstr, mdb.m_lpathstr, mdb.m_prompt)) > n) { mdb_free(s, n); n = r + 1; s = mdb_alloc(r + 1, UM_SLEEP); } return (s); } /* * Decode a configuration string created with mdb_get_config() and reset the * appropriate parts of the global mdb_t accordingly. */ void mdb_set_config(const char *s) { const char *p; size_t len; if ((p = strchr(s, ';')) != NULL) { mdb.m_tgtflags = strntoul(s, (size_t)(p - s), 16); s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_flags = strntoul(s, (size_t)(p - s), 16); mdb.m_flags &= ~(MDB_FL_LOG | MDB_FL_LATEST); s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_debug = strntoul(s, (size_t)(p - s), 16); s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_radix = (int)strntoul(s, (size_t)(p - s), 16); if (mdb.m_radix < 2 || mdb.m_radix > 16) mdb.m_radix = MDB_DEF_RADIX; s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_nargs = (int)strntoul(s, (size_t)(p - s), 16); mdb.m_nargs = MAX(mdb.m_nargs, 0); s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_histlen = (int)strntoul(s, (size_t)(p - s), 16); mdb.m_histlen = MAX(mdb.m_histlen, 1); s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_symdist = strntoul(s, (size_t)(p - s), 16); s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_execmode = (uchar_t)strntoul(s, (size_t)(p - s), 16); if (mdb.m_execmode > MDB_EM_FOLLOW) mdb.m_execmode = MDB_EM_ASK; s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_forkmode = (uchar_t)strntoul(s, (size_t)(p - s), 16); if (mdb.m_forkmode > MDB_FM_CHILD) mdb.m_forkmode = MDB_FM_ASK; s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_root = strndup(s, (size_t)(p - s)); s = p + 1; } if ((p = strchr(s, ';')) != NULL) { mdb.m_termtype = strndup(s, (size_t)(p - s)); s = p + 1; } if ((p = strchr(s, ';')) != NULL) { size_t len = MIN(sizeof (mdb.m_ipathstr) - 1, p - s); (void) strncpy(mdb.m_ipathstr, s, len); mdb.m_ipathstr[len] = '\0'; s = p + 1; } if ((p = strchr(s, ';')) != NULL) { size_t len = MIN(sizeof (mdb.m_lpathstr) - 1, p - s); (void) strncpy(mdb.m_lpathstr, s, len); mdb.m_lpathstr[len] = '\0'; s = p + 1; } p = s + strlen(s); len = MIN(MDB_PROMPTLEN, (size_t)(p - s)); (void) strncpy(mdb.m_prompt, s, len); mdb.m_prompt[len] = '\0'; mdb.m_promptlen = len; } mdb_module_t * mdb_get_module(void) { if (mdb.m_lmod) return (mdb.m_lmod); if (mdb.m_frame == NULL) return (NULL); if (mdb.m_frame->f_wcbs && mdb.m_frame->f_wcbs->w_walker && mdb.m_frame->f_wcbs->w_walker->iwlk_modp && !mdb.m_frame->f_cbactive) return (mdb.m_frame->f_wcbs->w_walker->iwlk_modp); if (mdb.m_frame->f_cp && mdb.m_frame->f_cp->c_dcmd) return (mdb.m_frame->f_cp->c_dcmd->idc_modp); return (NULL); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright (c) 2017 Joyent, Inc. All rights reserved. */ #ifndef _MDB_H #define _MDB_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _KMDB #include #endif #ifdef __cplusplus extern "C" { #endif #define MDB_ERR_PARSE 1 /* Error occurred in lexer or parser */ #define MDB_ERR_NOMEM 2 /* Failed to allocate needed memory */ #define MDB_ERR_PAGER 3 /* User quit current command from pager */ #define MDB_ERR_SIGINT 4 /* User interrupt: abort current command */ #define MDB_ERR_QUIT 5 /* User request: quit debugger */ #define MDB_ERR_ASSERT 6 /* Assertion failure: abort current command */ #define MDB_ERR_API 7 /* API function error: abort current command */ #define MDB_ERR_ABORT 8 /* User abort or resume: abort to top level */ #define MDB_ERR_OUTPUT 9 /* Write to m_out failed: abort to top level */ #define MDB_ERR_IS_FATAL(err) \ ((err) == MDB_ERR_QUIT || (err) == MDB_ERR_ABORT || \ (err) == MDB_ERR_OUTPUT) #define MDB_DEF_RADIX 16 /* Default output radix */ #define MDB_DEF_NARGS 6 /* Default # of arguments in stack trace */ #define MDB_DEF_HISTLEN 128 /* Default length of command history */ #define MDB_DEF_SYMDIST 0x8000 /* Default symbol distance for addresses */ #define MDB_DEF_ARRMEM 32 /* Default number of array members to print */ #define MDB_DEF_ARRSTR 1024 /* Default number of array chars to print */ #define MDB_ARR_NOLIMIT -1UL /* No limit on number of array elements */ #define MDB_FL_PSYM 0x000001 /* Print dot as symbol + offset */ #define MDB_FL_LOG 0x000002 /* Logging is enabled */ #define MDB_FL_NOMODS 0x000004 /* Skip automatic mdb module loading */ #define MDB_FL_USECUP 0x000008 /* Use term cup init sequences */ #define MDB_FL_ADB 0x000010 /* Enable stricter adb(1) compat */ #define MDB_FL_SHOWLMID 0x000020 /* Show link map id with symbol names */ #define MDB_FL_IGNEOF 0x000040 /* Ignore EOF as a synonym for ::quit */ #define MDB_FL_REPLAST 0x000080 /* Naked newline repeats prev command */ #define MDB_FL_PAGER 0x000100 /* Enable pager by default */ #define MDB_FL_LATEST 0x000200 /* Replace verstring with "latest" */ #define MDB_FL_VCREATE 0x000400 /* Victim process created by debugger */ #define MDB_FL_JOBCTL 0x000800 /* Victim process jobctl on same tty */ #define MDB_FL_DEMANGLE 0x001000 /* Demangle symbols as part of %a */ #define MDB_FL_EXEC 0x002000 /* Debugger exec'd by a prev instance */ #define MDB_FL_NOCTF 0x004000 /* Skip automatic CTF data loading */ #define MDB_FL_BPTNOSYMSTOP 0x008000 /* Stop on def bkpts for unk symbols */ #define MDB_FL_TERMGUESS 0x010000 /* m_termtype derived from userland */ #define MDB_FL_READBACK 0x020000 /* Read value back after write */ #ifdef _KMDB #define MDB_FL_NOUNLOAD 0x040000 /* Don't allow debugger unload */ #endif #define MDB_FL_LMRAW 0x080000 /* Show unres link map object names */ #define MDB_FL_AUTOWRAP 0x100000 /* Autowrap lines at term width */ #define MDB_FL_VOLATILE 0x0001 /* Mask of all volatile flags to save/restore */ #define MDB_EM_ASK 0 /* Ask what to do on an exec */ #define MDB_EM_STOP 1 /* Stop after an exec */ #define MDB_EM_FOLLOW 2 /* Follow an exec */ #define MDB_FM_ASK 0 /* Ask what to do on a fork */ #define MDB_FM_PARENT 1 /* Follow parent process on a fork */ #define MDB_FM_CHILD 2 /* Follow child process on a fork */ #define MDB_PROMPTLEN 35 /* Maximum prompt length */ struct kmdb_promif; typedef struct mdb { uint_t m_tgtflags; /* Target open flags (see mdb_target.h) */ uint_t m_flags; /* Miscellaneous flags (see above) */ uint_t m_debug; /* Debugging flags (see mdb_debug.h) */ int m_radix; /* Default radix for output formatting */ int m_nargs; /* Default number of arguments in stack trace */ int m_histlen; /* Length of command history */ size_t m_symdist; /* Distance from sym for addr match (0=smart) */ const char *m_pname; /* Program basename from argv[0] */ char m_promptraw[MDB_PROMPTLEN + 1]; /* Un-expanded prompt */ char m_prompt[MDB_PROMPTLEN + 1]; /* Prompt for interactive mode */ size_t m_promptlen; /* Length of prompt in bytes */ const char *m_shell; /* Shell for ! commands and pipelines */ char *m_root; /* Root for path construction */ char *m_ipathstr; /* Path string for include path */ char *m_lpathstr; /* Path string for library path */ const char **m_ipath; /* Path for $< and $<< macro files */ size_t m_ipathlen; /* Length of underlying ipath buffer */ const char **m_lpath; /* Path for :: loadable modules */ size_t m_lpathlen; /* Length of underlying lpath buffer */ mdb_modinfo_t m_rminfo; /* Root debugger module information */ mdb_module_t m_rmod; /* Root debugger module (builtins) */ mdb_module_t *m_mhead; /* Head of module list (in load order) */ mdb_module_t *m_mtail; /* Tail of module list (in load order) */ mdb_list_t m_tgtlist; /* List of active target backends */ mdb_tgt_t *m_target; /* Current debugger target backend */ mdb_nv_t m_disasms; /* Hash of available disassemblers */ mdb_disasm_t *m_disasm; /* Current disassembler backend */ char *m_defdisasm; /* Deferred diassembler selection */ mdb_nv_t m_modules; /* Name/value hash for loadable modules */ mdb_nv_t m_dcmds; /* Name/value hash for extended commands */ mdb_nv_t m_walkers; /* Name/value hash for walk operations */ mdb_nv_t m_nv; /* Name/value hash for named variables */ mdb_var_t *m_dot; /* Variable reference for '.' */ uintmax_t m_incr; /* Current increment */ uintmax_t m_raddr; /* Most recent address specified to a dcmd */ uintmax_t m_dcount; /* Most recent count specified to a dcmd */ mdb_var_t *m_rvalue; /* Most recent value printed */ mdb_var_t *m_roffset; /* Most recent offset from an instruction */ mdb_var_t *m_proffset; /* Previous value of m_roffset */ mdb_var_t *m_rcount; /* Most recent count on $< dcmd */ mdb_iob_t *m_in; /* Input stream */ mdb_iob_t *m_out; /* Output stream */ mdb_iob_t *m_err; /* Error stream */ mdb_iob_t *m_null; /* Null stream */ char *m_termtype; /* Interactive mode terminal type */ mdb_io_t *m_term; /* Terminal for interactive mode */ mdb_io_t *m_log; /* Log file i/o backend (NULL if not logging) */ mdb_module_t *m_lmod; /* Pointer to loading module, if in load */ mdb_list_t m_lastc; /* Last executed command list */ mdb_gelf_symtab_t *m_prsym; /* Private symbol table */ mdb_demangler_t *m_demangler; /* Demangler (see ) */ mdb_list_t m_flist; /* Stack of execution frames */ struct mdb_frame *volatile m_frame; /* Current stack frame */ struct mdb_frame *volatile m_fmark; /* Stack marker for pager */ uint_t m_fid; /* Next frame identifier number to assign */ uint_t m_depth; /* Depth of m_frame stack */ volatile uint_t m_intr; /* Don't allow SIGINT if set */ volatile uint_t m_pend; /* Pending SIGINT count */ pid_t m_pgid; /* Debugger process group id */ uint_t m_rdvers; /* Librtld_db version number */ uint_t m_ctfvers; /* Libctf version number */ ulong_t m_armemlim; /* Limit on number of array members to print */ ulong_t m_arstrlim; /* Limit on number of array chars to print */ uchar_t m_execmode; /* Follow exec behavior */ uchar_t m_forkmode; /* Follow fork behavior */ char **m_env; /* Current environment */ mdb_list_t m_cblist; /* List of callbacks */ mdb_nv_t m_macaliases; /* Name/value hash of ADB macro aliases */ ctf_file_t *m_synth; /* Container for synthetic types */ int m_lastret; /* Result of running the last command */ #ifdef _KMDB struct dpi_ops *m_dpi; /* DPI ops vector */ struct kdi *m_kdi; /* KDI ops vector */ size_t m_pagesize; /* Base page size for this machine */ caddr_t m_dseg; /* Debugger segment address */ size_t m_dsegsz; /* Debugger segment size */ mdb_nv_t m_dmodctl; /* dmod name -> kmdb_modctl hash */ kmdb_wr_t *m_drvwrhead; /* Driver work request queue */ kmdb_wr_t *m_drvwrtail; /* Driver work request queue */ kmdb_wr_t *m_dbgwrhead; /* Debugger request queue */ kmdb_wr_t *m_dbgwrtail; /* Debugger request queue */ struct cons_polledio *m_pio; /* Polled I/O struct from kernel */ struct kmdb_promif *m_promif; /* Debugger/PROM interface state */ #endif } mdb_t; #ifdef _MDB_PRIVATE mdb_t mdb; #else extern mdb_t mdb; #endif #ifdef _MDB #define MDB_CONFIG_ENV_VAR "_MDB_CONFIG" extern void mdb_create(const char *, const char *); extern void mdb_destroy(void); extern int mdb_call_idcmd(mdb_idcmd_t *, uintmax_t, uintmax_t, uint_t, mdb_argvec_t *, mdb_addrvec_t *, mdb_vcb_t *); extern void mdb_call_tab(mdb_idcmd_t *, mdb_tab_cookie_t *, uint_t, uintmax_t, mdb_arg_t *); extern int mdb_call(uintmax_t, uintmax_t, uint_t); extern int mdb_run(void); extern const char *mdb_get_prompt(void); extern int mdb_set_prompt(const char *); extern void mdb_set_ipath(const char *); extern void mdb_set_lpath(const char *); extern const char **mdb_path_alloc(const char *, size_t *); extern const char **mdb_path_dup(const char *[], size_t, size_t *); extern void mdb_path_free(const char *[], size_t); extern uintmax_t mdb_dot_incr(const char *); extern uintmax_t mdb_dot_decr(const char *); extern mdb_iwalker_t *mdb_walker_lookup(const char *); extern mdb_idcmd_t *mdb_dcmd_lookup(const char *); extern void mdb_dcmd_usage(const mdb_idcmd_t *, mdb_iob_t *); extern void mdb_pservice_init(void); extern void mdb_intr_enable(void); extern void mdb_intr_disable(void); extern char *mdb_get_config(void); extern void mdb_set_config(const char *); extern mdb_module_t *mdb_get_module(void); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #define AD_INIT 16 /* initial size of addrvec array */ #define AD_GROW 2 /* array growth multiplier */ void mdb_addrvec_create(mdb_addrvec_t *adp) { bzero(adp, sizeof (mdb_addrvec_t)); } void mdb_addrvec_destroy(mdb_addrvec_t *adp) { mdb_free(adp->ad_data, sizeof (uintptr_t) * adp->ad_size); bzero(adp, sizeof (mdb_addrvec_t)); } void mdb_addrvec_unshift(mdb_addrvec_t *adp, uintptr_t value) { if (adp->ad_nelems >= adp->ad_size) { size_t size = adp->ad_size ? adp->ad_size * AD_GROW : AD_INIT; void *data = mdb_alloc(sizeof (uintptr_t) * size, UM_SLEEP); bcopy(adp->ad_data, data, sizeof (uintptr_t) * adp->ad_size); mdb_free(adp->ad_data, sizeof (uintptr_t) * adp->ad_size); adp->ad_data = data; adp->ad_size = size; } adp->ad_data[adp->ad_nelems++] = value; } uintptr_t mdb_addrvec_shift(mdb_addrvec_t *adp) { if (adp->ad_ndx < adp->ad_nelems) return (adp->ad_data[adp->ad_ndx++]); return ((uintptr_t)-1L); } size_t mdb_addrvec_length(mdb_addrvec_t *adp) { if (adp != NULL) { ASSERT(adp->ad_nelems >= adp->ad_ndx); return (adp->ad_nelems - adp->ad_ndx); } return (0); /* convenience for callers */ } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_ADDRVEC_H #define _MDB_ADDRVEC_H #include #ifdef __cplusplus extern "C" { #endif typedef struct mdb_addrvec { uintptr_t *ad_data; /* Array of addresses */ size_t ad_nelems; /* Number of valid elements */ size_t ad_size; /* Array size */ size_t ad_ndx; /* Array index */ } mdb_addrvec_t; #ifdef _MDB extern void mdb_addrvec_create(mdb_addrvec_t *); extern void mdb_addrvec_destroy(mdb_addrvec_t *); extern uintptr_t mdb_addrvec_shift(mdb_addrvec_t *); extern void mdb_addrvec_unshift(mdb_addrvec_t *, uintptr_t); extern size_t mdb_addrvec_length(mdb_addrvec_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_ADDRVEC_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #define AV_DEFSZ 16 /* Initial size of argument vector */ #define AV_GROW 2 /* Multiplier for growing argument vector */ void mdb_argvec_create(mdb_argvec_t *vec) { vec->a_data = NULL; vec->a_nelems = 0; vec->a_size = 0; } void mdb_argvec_destroy(mdb_argvec_t *vec) { if (vec->a_data != NULL) { mdb_argvec_reset(vec); mdb_free(vec->a_data, sizeof (mdb_arg_t) * vec->a_size); } } void mdb_argvec_append(mdb_argvec_t *vec, const mdb_arg_t *arg) { if (vec->a_nelems >= vec->a_size) { size_t size = vec->a_size ? vec->a_size * AV_GROW : AV_DEFSZ; void *data = mdb_alloc(sizeof (mdb_arg_t) * size, UM_NOSLEEP); if (data == NULL) { warn("failed to grow argument vector"); longjmp(mdb.m_frame->f_pcb, MDB_ERR_NOMEM); } bcopy(vec->a_data, data, sizeof (mdb_arg_t) * vec->a_size); mdb_free(vec->a_data, sizeof (mdb_arg_t) * vec->a_size); vec->a_data = data; vec->a_size = size; } bcopy(arg, &vec->a_data[vec->a_nelems++], sizeof (mdb_arg_t)); } void mdb_argvec_reset(mdb_argvec_t *vec) { size_t nelems = vec->a_nelems; mdb_arg_t *arg; for (arg = vec->a_data; nelems != 0; nelems--, arg++) { if (arg->a_type == MDB_TYPE_STRING && arg->a_un.a_str != NULL) strfree((char *)arg->a_un.a_str); } vec->a_nelems = 0; } void mdb_argvec_zero(mdb_argvec_t *vec) { #ifdef DEBUG size_t i; for (i = 0; i < vec->a_size; i++) { vec->a_data[i].a_type = UMEM_UNINITIALIZED_PATTERN; vec->a_data[i].a_un.a_val = ((u_longlong_t)UMEM_UNINITIALIZED_PATTERN << 32) | ((u_longlong_t)UMEM_UNINITIALIZED_PATTERN); } #endif vec->a_nelems = 0; } void mdb_argvec_copy(mdb_argvec_t *dst, const mdb_argvec_t *src) { if (src->a_nelems > dst->a_size) { mdb_arg_t *data = mdb_alloc(sizeof (mdb_arg_t) * src->a_nelems, UM_NOSLEEP); if (data == NULL) { warn("failed to grow argument vector"); longjmp(mdb.m_frame->f_pcb, MDB_ERR_NOMEM); } if (dst->a_data != NULL) mdb_free(dst->a_data, sizeof (mdb_arg_t) * dst->a_size); dst->a_data = data; dst->a_size = src->a_nelems; } bcopy(src->a_data, dst->a_data, sizeof (mdb_arg_t) * src->a_nelems); dst->a_nelems = src->a_nelems; } static int argvec_process_subopt(const mdb_opt_t *opt, const mdb_arg_t *arg) { mdb_subopt_t *sop; const char *start; const char *next; char error[32]; size_t len; uint_t value = 0; uint_t i; start = arg->a_un.a_str; for (i = 0; ; i++) { next = strchr(start, ','); if (next == NULL) len = strlen(start); else len = next - start; /* * Record the index of the subopt if a match if found. */ for (sop = opt->opt_subopts; sop->sop_flag; sop++) { if (strlen(sop->sop_str) == len && strncmp(sop->sop_str, start, len) == 0) { value |= sop->sop_flag; sop->sop_index = i; goto found; } } (void) mdb_snprintf(error, len + 1, "%s", start); warn("invalid option for -%c: \"%s\"\n", opt->opt_char, error); return (-1); found: if (next == NULL) break; start = next + 1; } *((uint_t *)opt->opt_valp) = value; return (0); } static int argvec_process_opt(const mdb_opt_t *opt, const mdb_arg_t *arg) { uint64_t ui64; uintptr_t uip; switch (opt->opt_type) { case MDB_OPT_SETBITS: *((uint_t *)opt->opt_valp) |= opt->opt_bits; break; case MDB_OPT_CLRBITS: *((uint_t *)opt->opt_valp) &= ~opt->opt_bits; break; case MDB_OPT_STR: if (arg->a_type != MDB_TYPE_STRING) { warn("string argument required for -%c\n", opt->opt_char); return (-1); } *((const char **)opt->opt_valp) = arg->a_un.a_str; break; case MDB_OPT_UINTPTR_SET: *opt->opt_flag = TRUE; /* FALLTHROUGH */ case MDB_OPT_UINTPTR: uip = (uintptr_t)mdb_argtoull(arg); *((uintptr_t *)opt->opt_valp) = uip; break; case MDB_OPT_UINT64: ui64 = (uint64_t)mdb_argtoull(arg); *((uint64_t *)opt->opt_valp) = ui64; break; case MDB_OPT_SUBOPTS: if (arg->a_type != MDB_TYPE_STRING) { warn("string argument required for -%c\n", opt->opt_char); return (-1); } return (argvec_process_subopt(opt, arg)); default: warn("internal: bad opt=%p type=%hx\n", (void *)opt, opt->opt_type); return (-1); } return (0); } static const mdb_opt_t * argvec_findopt(const mdb_opt_t *opts, char c) { const mdb_opt_t *optp; for (optp = opts; optp->opt_char != 0; optp++) { if (optp->opt_char == c) return (optp); } return (NULL); } static int argvec_getopts(const mdb_opt_t *opts, const mdb_arg_t *argv, int argc) { const mdb_opt_t *optp; const mdb_arg_t *argp; mdb_arg_t arg; const char *p; int i; int nargs; /* Number of arguments consumed in an iteration */ for (i = 0; i < argc; i++, argv++) { /* * Each option must begin with a string argument whose first * character is '-' and has additional characters afterward. */ if (argv->a_type != MDB_TYPE_STRING || argv->a_un.a_str[0] != '-' || argv->a_un.a_str[1] == '\0') return (i); /* * The special prefix '--' ends option processing. */ if (strncmp(argv->a_un.a_str, "--", 2) == 0) return (i); for (p = &argv->a_un.a_str[1]; *p != '\0'; p++) { /* * Locate an option struct whose opt_char field * matches the current option letter. */ if ((optp = argvec_findopt(opts, *p)) == NULL) { warn("illegal option -- %c\n", *p); return (i); } /* * Require an argument for strings, immediate * values, subopt-lists and callback functions * which require arguments. */ if (optp->opt_type == MDB_OPT_STR || optp->opt_type == MDB_OPT_UINTPTR || optp->opt_type == MDB_OPT_UINTPTR_SET || optp->opt_type == MDB_OPT_SUBOPTS || optp->opt_type == MDB_OPT_UINT64) { /* * More text after the option letter: * forge a string argument from remainder. */ if (p[1] != '\0') { arg.a_type = MDB_TYPE_STRING; arg.a_un.a_str = ++p; argp = &arg; p += strlen(p) - 1; nargs = 0; /* * Otherwise use the next argv element as * the argument if there is one. */ } else if (++i == argc) { warn("option requires an " "argument -- %c\n", *p); return (i - 1); } else { argp = ++argv; nargs = 1; } } else { argp = NULL; nargs = 0; } /* * Perform type-specific handling for this option. */ if (argvec_process_opt(optp, argp) == -1) return (i - nargs); } } return (i); } int mdb_getopts(int argc, const mdb_arg_t *argv, ...) { /* * For simplicity just declare enough options on the stack to handle * a-z and A-Z and an extra terminator. */ mdb_opt_t opts[53], *op = &opts[0]; va_list alist; int c, i = 0; mdb_subopt_t *sop; va_start(alist, argv); for (i = 0; i < (sizeof (opts) / sizeof (opts[0]) - 1); i++, op++) { if ((c = va_arg(alist, int)) == 0) break; /* end of options */ op->opt_char = (char)c; op->opt_type = va_arg(alist, uint_t); if (op->opt_type == MDB_OPT_SETBITS || op->opt_type == MDB_OPT_CLRBITS) { op->opt_bits = va_arg(alist, uint_t); } else if (op->opt_type == MDB_OPT_UINTPTR_SET) { op->opt_flag = va_arg(alist, boolean_t *); } else if (op->opt_type == MDB_OPT_SUBOPTS) { op->opt_subopts = va_arg(alist, mdb_subopt_t *); for (sop = op->opt_subopts; sop->sop_flag; sop++) sop->sop_index = -1; } op->opt_valp = va_arg(alist, void *); } bzero(&opts[i], sizeof (mdb_opt_t)); va_end(alist); return (argvec_getopts(opts, argv, argc)); } u_longlong_t mdb_argtoull(const mdb_arg_t *arg) { switch (arg->a_type) { case MDB_TYPE_STRING: return (mdb_strtoull(arg->a_un.a_str)); case MDB_TYPE_IMMEDIATE: return (arg->a_un.a_val); case MDB_TYPE_CHAR: return (arg->a_un.a_char); } /* NOTREACHED */ return (0); } /* * The old adb breakpoint and watchpoint routines did not accept any arguments; * all characters after the verb were concatenated to form the string callback. * This utility function concatenates all arguments in argv[] into a single * string to simplify the implementation of these legacy routines. */ char * mdb_argv_to_str(int argc, const mdb_arg_t *argv) { char *s = NULL; size_t n = 0; int i; for (i = 0; i < argc; i++) { if (argv[i].a_type == MDB_TYPE_STRING) n += strlen(argv[i].a_un.a_str); } if (n != 0) { s = mdb_zalloc(n + argc, UM_SLEEP); for (i = 0; i < argc - 1; i++, argv++) { (void) strcat(s, argv->a_un.a_str); (void) strcat(s, " "); } (void) strcat(s, argv->a_un.a_str); } return (s); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_ARGVEC_H #define _MDB_ARGVEC_H #include #ifdef __cplusplus extern "C" { #endif struct mdb_arg; typedef struct mdb_argvec { struct mdb_arg *a_data; /* Array of arguments */ size_t a_nelems; /* Number of valid elements */ size_t a_size; /* Array size */ } mdb_argvec_t; /* see mdb_modapi.h for 1-6 */ #define MDB_OPT_SUBOPTS 7 /* Option requires a mdb_subopt_t */ /* list and a value argument */ typedef struct mdb_subopt { uint_t sop_flag; /* Option flag */ const char *sop_str; /* Sub-option name */ int sop_index; /* Index of subopt in argument */ } mdb_subopt_t; typedef struct mdb_opt { char opt_char; /* Option name */ void *opt_valp; /* Value storage pointer */ uint_t opt_bits; /* Bits to set or clear for booleans */ boolean_t *opt_flag; /* pointer to flag (uintptr_set) */ mdb_subopt_t *opt_subopts; /* List of mdb_subopt_t */ uint_t opt_type; /* Option type (see above) */ } mdb_opt_t; #ifdef _MDB #ifdef _BIG_ENDIAN #ifdef _LP64 #define MDB_INIT_CHAR(x) ((const char *)((uintptr_t)(uchar_t)(x) << 56)) #else /* _LP64 */ #define MDB_INIT_CHAR(x) ((const char *)((uintptr_t)(uchar_t)(x) << 24)) #endif /* _LP64 */ #else /* _BIG_ENDIAN */ #define MDB_INIT_CHAR(x) ((const char *)(uchar_t)(x)) #endif /* _BIG_ENDIAN */ #define MDB_INIT_STRING(x) ((const char *)(x)) extern void mdb_argvec_create(mdb_argvec_t *); extern void mdb_argvec_destroy(mdb_argvec_t *); extern void mdb_argvec_append(mdb_argvec_t *, const struct mdb_arg *); extern void mdb_argvec_reset(mdb_argvec_t *); extern void mdb_argvec_zero(mdb_argvec_t *); extern void mdb_argvec_copy(mdb_argvec_t *, const mdb_argvec_t *); extern char *mdb_argv_to_str(int, const struct mdb_arg *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_ARGVEC_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Callback facility designed to allow interested parties (dmods, targets, or * even the core debugger framework) to register for notification when certain * "interesting" events occur. */ #include #include #include #include #include mdb_callb_t * mdb_callb_add(mdb_module_t *m, int class, mdb_callb_f fp, void *arg) { mdb_callb_t *new = mdb_zalloc(sizeof (mdb_callb_t), UM_SLEEP); ASSERT(class == MDB_CALLB_STCHG || class == MDB_CALLB_PROMPT); new->cb_mod = m; new->cb_class = class; new->cb_func = fp; new->cb_arg = arg; if (m == NULL) { mdb_list_prepend(&mdb.m_cblist, new); } else { mdb_list_insert(&mdb.m_cblist, m->mod_cb, new); if (m->mod_cb == NULL) m->mod_cb = new; } return (new); } void mdb_callb_remove(mdb_callb_t *cb) { if (cb->cb_mod != NULL) { mdb_callb_t *next = mdb_list_next(cb); mdb_module_t *mod = cb->cb_mod; if (mod->mod_cb == cb) { if (next == NULL || next->cb_mod != mod) mod->mod_cb = NULL; else mod->mod_cb = next; } } mdb_list_delete(&mdb.m_cblist, cb); mdb_free(cb, sizeof (mdb_callb_t)); } void mdb_callb_remove_by_mod(mdb_module_t *m) { while (m->mod_cb != NULL) mdb_callb_remove(m->mod_cb); } void mdb_callb_remove_all(void) { mdb_callb_t *cb; while ((cb = mdb_list_next(&mdb.m_cblist)) != NULL) mdb_callb_remove(cb); } void mdb_callb_fire(int class) { mdb_callb_t *cb, *next; ASSERT(class == MDB_CALLB_STCHG || class == MDB_CALLB_PROMPT); mdb_dprintf(MDB_DBG_CALLB, "invoking %s callbacks\n", (class == MDB_CALLB_STCHG ? "state change" : "prompt")); for (cb = mdb_list_next(&mdb.m_cblist); cb != NULL; cb = next) { next = mdb_list_next(cb); if (cb->cb_class == class) cb->cb_func(cb->cb_arg); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_CALLB_H #define _MDB_CALLB_H #include #include #ifdef __cplusplus extern "C" { #endif /* * Callback facility designed to allow interested parties (dmods, targets, or * even the core debugger framework) to register for notification when certain * "interesting" events occur. */ /* * Callback classes: * (MDB_CALLBACK_* definitions in the module API need to be in sync with these) */ #define MDB_CALLB_STCHG 1 /* System execution state change */ #define MDB_CALLB_PROMPT 2 /* Before printing the prompt */ typedef void (*mdb_callb_f)(void *); typedef struct mdb_callb { mdb_list_t cb_list; /* List of callbacks */ mdb_module_t *cb_mod; /* Requesting module (if any) */ int cb_class; /* When to notify */ mdb_callb_f cb_func; /* Function to invoke */ void *cb_arg; /* Argument for cb_func */ } mdb_callb_t; extern mdb_callb_t *mdb_callb_add(mdb_module_t *, int, mdb_callb_f, void *); extern void mdb_callb_remove(mdb_callb_t *); extern void mdb_callb_remove_by_mod(mdb_module_t *); extern void mdb_callb_remove_all(void); extern void mdb_callb_fire(int); #ifdef __cplusplus } #endif #endif /* _MDB_CALLB_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * The MDB command buffer is a simple structure that keeps track of the * command history list, and provides operations to manipulate the current * buffer according to the various emacs editing options. The terminal * code uses this code to keep track of the actual contents of the command * line, and then uses this content to perform redraw operations. */ #include #include #include #include #include #include #include #define CMDBUF_LINELEN BUFSIZ /* Length of each buffer line */ #define CMDBUF_TABLEN 8 /* Length of a tab in spaces */ static void cmdbuf_shiftr(mdb_cmdbuf_t *cmd, size_t nbytes) { bcopy(&cmd->cmd_buf[cmd->cmd_bufidx], &cmd->cmd_buf[cmd->cmd_bufidx + nbytes], cmd->cmd_buflen - cmd->cmd_bufidx); } static void mdb_cmdbuf_allocchunk(mdb_cmdbuf_t *cmd) { int i; char **newhistory; ssize_t newhalloc = cmd->cmd_halloc + MDB_DEF_HISTLEN; if (newhalloc > cmd->cmd_histlen) newhalloc = cmd->cmd_histlen; newhistory = mdb_alloc(newhalloc * sizeof (char *), UM_SLEEP); bcopy(cmd->cmd_history, newhistory, cmd->cmd_halloc * sizeof (char *)); mdb_free(cmd->cmd_history, cmd->cmd_halloc * sizeof (char *)); for (i = cmd->cmd_halloc; i < newhalloc; i++) newhistory[i] = mdb_alloc(CMDBUF_LINELEN, UM_SLEEP); cmd->cmd_history = newhistory; cmd->cmd_halloc = newhalloc; } void mdb_cmdbuf_create(mdb_cmdbuf_t *cmd) { size_t i; cmd->cmd_halloc = MDB_DEF_HISTLEN < mdb.m_histlen ? MDB_DEF_HISTLEN : mdb.m_histlen; cmd->cmd_history = mdb_alloc(cmd->cmd_halloc * sizeof (char *), UM_SLEEP); cmd->cmd_linebuf = mdb_alloc(CMDBUF_LINELEN, UM_SLEEP); for (i = 0; i < cmd->cmd_halloc; i++) cmd->cmd_history[i] = mdb_alloc(CMDBUF_LINELEN, UM_SLEEP); cmd->cmd_buf = cmd->cmd_history[0]; cmd->cmd_linelen = CMDBUF_LINELEN; cmd->cmd_histlen = mdb.m_histlen; cmd->cmd_buflen = 0; cmd->cmd_bufidx = 0; cmd->cmd_hold = 0; cmd->cmd_hnew = 0; cmd->cmd_hcur = 0; cmd->cmd_hlen = 0; } void mdb_cmdbuf_destroy(mdb_cmdbuf_t *cmd) { size_t i; for (i = 0; i < cmd->cmd_halloc; i++) mdb_free(cmd->cmd_history[i], CMDBUF_LINELEN); mdb_free(cmd->cmd_linebuf, CMDBUF_LINELEN); mdb_free(cmd->cmd_history, cmd->cmd_halloc * sizeof (char *)); } int mdb_cmdbuf_caninsert(mdb_cmdbuf_t *cmd, size_t nbytes) { return (cmd->cmd_buflen + nbytes < cmd->cmd_linelen); } int mdb_cmdbuf_atstart(mdb_cmdbuf_t *cmd) { return (cmd->cmd_bufidx == 0); } int mdb_cmdbuf_atend(mdb_cmdbuf_t *cmd) { return (cmd->cmd_bufidx == cmd->cmd_buflen); } int mdb_cmdbuf_insert(mdb_cmdbuf_t *cmd, int c) { if (c == '\t') { if (cmd->cmd_buflen + CMDBUF_TABLEN < cmd->cmd_linelen) { int i; if (cmd->cmd_buflen != cmd->cmd_bufidx) cmdbuf_shiftr(cmd, CMDBUF_TABLEN); for (i = 0; i < CMDBUF_TABLEN; i++) cmd->cmd_buf[cmd->cmd_bufidx++] = ' '; cmd->cmd_buflen += CMDBUF_TABLEN; return (0); } return (-1); } if (c < ' ' || c > '~') return (-1); if (cmd->cmd_buflen < cmd->cmd_linelen) { if (cmd->cmd_buflen != cmd->cmd_bufidx) cmdbuf_shiftr(cmd, 1); cmd->cmd_buf[cmd->cmd_bufidx++] = (char)c; cmd->cmd_buflen++; return (0); } return (-1); } const char * mdb_cmdbuf_accept(mdb_cmdbuf_t *cmd) { if (cmd->cmd_bufidx < cmd->cmd_linelen) { int is_repeating = 0; cmd->cmd_buf[cmd->cmd_buflen++] = '\0'; (void) strcpy(cmd->cmd_linebuf, cmd->cmd_buf); if (cmd->cmd_hold != cmd->cmd_hnew) { int lastidx = cmd->cmd_hnew == 0 ? cmd->cmd_halloc - 1 : cmd->cmd_hnew - 1; is_repeating = strcmp(cmd->cmd_buf, cmd->cmd_history[lastidx]) == 0; } /* * Don't bother inserting empty or repeating buffers into the * history ring. */ if (cmd->cmd_buflen > 1 && !is_repeating) { cmd->cmd_hnew = (cmd->cmd_hnew + 1) % cmd->cmd_histlen; if (cmd->cmd_hnew >= cmd->cmd_halloc) mdb_cmdbuf_allocchunk(cmd); cmd->cmd_buf = cmd->cmd_history[cmd->cmd_hnew]; cmd->cmd_hcur = cmd->cmd_hnew; if (cmd->cmd_hlen + 1 == cmd->cmd_histlen) cmd->cmd_hold = (cmd->cmd_hold + 1) % cmd->cmd_histlen; else cmd->cmd_hlen++; } else if (is_repeating) { cmd->cmd_hcur = cmd->cmd_hnew; } cmd->cmd_bufidx = 0; cmd->cmd_buflen = 0; return ((const char *)cmd->cmd_linebuf); } return (NULL); } /*ARGSUSED*/ int mdb_cmdbuf_backspace(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_bufidx > 0) { if (cmd->cmd_buflen != cmd->cmd_bufidx) { bcopy(&cmd->cmd_buf[cmd->cmd_bufidx], &cmd->cmd_buf[cmd->cmd_bufidx - 1], cmd->cmd_buflen - cmd->cmd_bufidx); } cmd->cmd_bufidx--; cmd->cmd_buflen--; return (0); } return (-1); } /*ARGSUSED*/ int mdb_cmdbuf_delchar(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_bufidx < cmd->cmd_buflen) { if (cmd->cmd_bufidx < --cmd->cmd_buflen) { bcopy(&cmd->cmd_buf[cmd->cmd_bufidx + 1], &cmd->cmd_buf[cmd->cmd_bufidx], cmd->cmd_buflen - cmd->cmd_bufidx); } return (0); } return (-1); } /*ARGSUSED*/ int mdb_cmdbuf_fwdchar(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_bufidx < cmd->cmd_buflen) { cmd->cmd_bufidx++; return (0); } return (-1); } /*ARGSUSED*/ int mdb_cmdbuf_backchar(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_bufidx > 0) { cmd->cmd_bufidx--; return (0); } return (-1); } int mdb_cmdbuf_transpose(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_bufidx > 0 && cmd->cmd_buflen > 1) { c = cmd->cmd_buf[cmd->cmd_bufidx - 1]; if (cmd->cmd_bufidx == cmd->cmd_buflen) { cmd->cmd_buf[cmd->cmd_bufidx - 1] = cmd->cmd_buf[cmd->cmd_bufidx - 2]; cmd->cmd_buf[cmd->cmd_bufidx - 2] = (char)c; } else { cmd->cmd_buf[cmd->cmd_bufidx - 1] = cmd->cmd_buf[cmd->cmd_bufidx]; cmd->cmd_buf[cmd->cmd_bufidx++] = (char)c; } return (0); } return (-1); } /*ARGSUSED*/ int mdb_cmdbuf_home(mdb_cmdbuf_t *cmd, int c) { cmd->cmd_bufidx = 0; return (0); } /*ARGSUSED*/ int mdb_cmdbuf_end(mdb_cmdbuf_t *cmd, int c) { cmd->cmd_bufidx = cmd->cmd_buflen; return (0); } static size_t fwdword_index(mdb_cmdbuf_t *cmd) { size_t i = cmd->cmd_bufidx + 1; ASSERT(cmd->cmd_bufidx < cmd->cmd_buflen); while (i < cmd->cmd_buflen && isspace(cmd->cmd_buf[i])) i++; while (i < cmd->cmd_buflen && !isspace(cmd->cmd_buf[i]) && !isalnum(cmd->cmd_buf[i]) && cmd->cmd_buf[i] != '_') i++; while (i < cmd->cmd_buflen && (isalnum(cmd->cmd_buf[i]) || cmd->cmd_buf[i] == '_')) i++; return (i); } /*ARGSUSED*/ int mdb_cmdbuf_fwdword(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_bufidx == cmd->cmd_buflen) return (-1); cmd->cmd_bufidx = fwdword_index(cmd); return (0); } /*ARGSUSED*/ int mdb_cmdbuf_killfwdword(mdb_cmdbuf_t *cmd, int c) { size_t i; if (cmd->cmd_bufidx == cmd->cmd_buflen) return (-1); i = fwdword_index(cmd); bcopy(&cmd->cmd_buf[i], &cmd->cmd_buf[cmd->cmd_bufidx], cmd->cmd_buflen - i); cmd->cmd_buflen -= i - cmd->cmd_bufidx; return (0); } static size_t backword_index(mdb_cmdbuf_t *cmd) { size_t i = cmd->cmd_bufidx - 1; ASSERT(cmd->cmd_bufidx != 0); while (i != 0 && isspace(cmd->cmd_buf[i])) i--; while (i != 0 && !isspace(cmd->cmd_buf[i]) && !isalnum(cmd->cmd_buf[i]) && cmd->cmd_buf[i] != '_') i--; while (i != 0 && (isalnum(cmd->cmd_buf[i]) || cmd->cmd_buf[i] == '_')) i--; if (i != 0) i++; return (i); } /*ARGSUSED*/ int mdb_cmdbuf_backword(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_bufidx == 0) return (-1); cmd->cmd_bufidx = backword_index(cmd); return (0); } /*ARGSUSED*/ int mdb_cmdbuf_killbackword(mdb_cmdbuf_t *cmd, int c) { size_t i; if (cmd->cmd_bufidx == 0) return (-1); i = backword_index(cmd); bcopy(&cmd->cmd_buf[cmd->cmd_bufidx], &cmd->cmd_buf[i], cmd->cmd_buflen - cmd->cmd_bufidx); cmd->cmd_buflen -= cmd->cmd_bufidx - i; cmd->cmd_bufidx = i; return (0); } /*ARGSUSED*/ int mdb_cmdbuf_kill(mdb_cmdbuf_t *cmd, int c) { cmd->cmd_buflen = cmd->cmd_bufidx; return (0); } /*ARGSUSED*/ int mdb_cmdbuf_reset(mdb_cmdbuf_t *cmd, int c) { cmd->cmd_buflen = 0; cmd->cmd_bufidx = 0; return (0); } /*ARGSUSED*/ int mdb_cmdbuf_prevhist(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_hcur != cmd->cmd_hold) { if (cmd->cmd_hcur-- == cmd->cmd_hnew) { cmd->cmd_buf[cmd->cmd_buflen] = 0; (void) strcpy(cmd->cmd_linebuf, cmd->cmd_buf); } if (cmd->cmd_hcur < 0) cmd->cmd_hcur = cmd->cmd_halloc - 1; (void) strcpy(cmd->cmd_buf, cmd->cmd_history[cmd->cmd_hcur]); cmd->cmd_bufidx = strlen(cmd->cmd_buf); cmd->cmd_buflen = cmd->cmd_bufidx; return (0); } return (-1); } /*ARGSUSED*/ int mdb_cmdbuf_nexthist(mdb_cmdbuf_t *cmd, int c) { if (cmd->cmd_hcur != cmd->cmd_hnew) { cmd->cmd_hcur = (cmd->cmd_hcur + 1) % cmd->cmd_halloc; if (cmd->cmd_hcur == cmd->cmd_hnew) { (void) strcpy(cmd->cmd_buf, cmd->cmd_linebuf); } else { (void) strcpy(cmd->cmd_buf, cmd->cmd_history[cmd->cmd_hcur]); } cmd->cmd_bufidx = strlen(cmd->cmd_buf); cmd->cmd_buflen = cmd->cmd_bufidx; return (0); } return (-1); } /*ARGSUSED*/ int mdb_cmdbuf_findhist(mdb_cmdbuf_t *cmd, int c) { ssize_t i, n; if (cmd->cmd_buflen != 0) { cmd->cmd_hcur = cmd->cmd_hnew; cmd->cmd_buf[cmd->cmd_buflen] = 0; (void) strcpy(cmd->cmd_linebuf, cmd->cmd_buf); } for (i = cmd->cmd_hcur, n = 0; n < cmd->cmd_hlen; n++) { if (--i < 0) i = cmd->cmd_halloc - 1; if (strstr(cmd->cmd_history[i], cmd->cmd_linebuf) != NULL) { (void) strcpy(cmd->cmd_buf, cmd->cmd_history[i]); cmd->cmd_bufidx = strlen(cmd->cmd_buf); cmd->cmd_buflen = cmd->cmd_bufidx; cmd->cmd_hcur = i; return (0); } } cmd->cmd_hcur = cmd->cmd_hnew; cmd->cmd_bufidx = 0; cmd->cmd_buflen = 0; return (-1); } /* * 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. */ #ifndef _MDB_CMDBUF_H #define _MDB_CMDBUF_H #include #ifdef __cplusplus extern "C" { #endif typedef struct mdb_cmdbuf { char **cmd_history; /* Circular array of history buffers */ char *cmd_linebuf; /* Temporary history for current buffer */ char *cmd_buf; /* Current line buffer */ size_t cmd_linelen; /* Maximum line length */ size_t cmd_histlen; /* Maximum history entries */ size_t cmd_halloc; /* Number of allocated history entries */ size_t cmd_buflen; /* Number of bytes in current line buffer */ size_t cmd_bufidx; /* Byte position in current line buffer */ ssize_t cmd_hold; /* Oldest history entry index */ ssize_t cmd_hnew; /* Newest history entry index */ ssize_t cmd_hcur; /* Current history entry index */ ssize_t cmd_hlen; /* Number of valid history buffers */ } mdb_cmdbuf_t; #ifdef _MDB extern void mdb_cmdbuf_create(mdb_cmdbuf_t *); extern void mdb_cmdbuf_destroy(mdb_cmdbuf_t *); extern const char *mdb_cmdbuf_accept(mdb_cmdbuf_t *); extern int mdb_cmdbuf_caninsert(mdb_cmdbuf_t *, size_t); extern int mdb_cmdbuf_atstart(mdb_cmdbuf_t *); extern int mdb_cmdbuf_atend(mdb_cmdbuf_t *); extern int mdb_cmdbuf_insert(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_backspace(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_delchar(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_fwdchar(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_backchar(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_transpose(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_home(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_end(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_fwdword(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_backword(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_killfwdword(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_killbackword(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_kill(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_reset(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_prevhist(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_nexthist(mdb_cmdbuf_t *, int); extern int mdb_cmdbuf_findhist(mdb_cmdbuf_t *, int); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_CMDBUF_H */ /* * 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 2021 Joyent, Inc. * Copyright (c) 2013 Josef 'Jeff' Sipek * Copyright (c) 2015, 2017 by Delphix. All rights reserved. * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. * Copyright 2025 Oxide Computer Company * Copyright 2025 Edgecast Cloud LLC */ #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 #include #include #include #include #include #include #ifdef _KMDB #include #endif #include #ifdef __sparc #define SETHI_MASK 0xc1c00000 #define SETHI_VALUE 0x01000000 #define IS_SETHI(machcode) (((machcode) & SETHI_MASK) == SETHI_VALUE) #define OP(machcode) ((machcode) >> 30) #define OP3(machcode) (((machcode) >> 19) & 0x3f) #define RD(machcode) (((machcode) >> 25) & 0x1f) #define RS1(machcode) (((machcode) >> 14) & 0x1f) #define I(machcode) (((machcode) >> 13) & 0x01) #define IMM13(machcode) ((machcode) & 0x1fff) #define IMM22(machcode) ((machcode) & 0x3fffff) #define OP_ARITH_MEM_MASK 0x2 #define OP_ARITH 0x2 #define OP_MEM 0x3 #define OP3_CC_MASK 0x10 #define OP3_COMPLEX_MASK 0x20 #define OP3_ADD 0x00 #define OP3_OR 0x02 #define OP3_XOR 0x03 #ifndef R_O7 #define R_O7 0xf #endif #endif /* __sparc */ static mdb_tgt_addr_t write_uint8(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t ull, uint_t rdback) { uint8_t o, n = (uint8_t)ull; if (rdback && mdb_tgt_aread(mdb.m_target, as, &o, sizeof (o), addr) == -1) return (addr); if (mdb_tgt_awrite(mdb.m_target, as, &n, sizeof (n), addr) == -1) return (addr); if (rdback) { if (mdb_tgt_aread(mdb.m_target, as, &n, sizeof (n), addr) == -1) return (addr); mdb_iob_printf(mdb.m_out, "%-#*lla%16T%-#8x=%8T0x%x\n", mdb_iob_getmargin(mdb.m_out), addr, o, n); } return (addr + sizeof (n)); } static mdb_tgt_addr_t write_uint16(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t ull, uint_t rdback) { uint16_t o, n = (uint16_t)ull; if (rdback && mdb_tgt_aread(mdb.m_target, as, &o, sizeof (o), addr) == -1) return (addr); if (mdb_tgt_awrite(mdb.m_target, as, &n, sizeof (n), addr) == -1) return (addr); if (rdback) { if (mdb_tgt_aread(mdb.m_target, as, &n, sizeof (n), addr) == -1) return (addr); mdb_iob_printf(mdb.m_out, "%-#*lla%16T%-#8hx=%8T0x%hx\n", mdb_iob_getmargin(mdb.m_out), addr, o, n); } return (addr + sizeof (n)); } static mdb_tgt_addr_t write_uint32(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t ull, uint_t rdback) { uint32_t o, n = (uint32_t)ull; if (rdback && mdb_tgt_aread(mdb.m_target, as, &o, sizeof (o), addr) == -1) return (addr); if (mdb_tgt_awrite(mdb.m_target, as, &n, sizeof (n), addr) == -1) return (addr); if (rdback) { if (mdb_tgt_aread(mdb.m_target, as, &n, sizeof (n), addr) == -1) return (addr); mdb_iob_printf(mdb.m_out, "%-#*lla%16T%-#16x=%8T0x%x\n", mdb_iob_getmargin(mdb.m_out), addr, o, n); } return (addr + sizeof (n)); } static mdb_tgt_addr_t write_uint64(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t n, uint_t rdback) { uint64_t o; if (rdback && mdb_tgt_aread(mdb.m_target, as, &o, sizeof (o), addr) == -1) return (addr); if (mdb_tgt_awrite(mdb.m_target, as, &n, sizeof (n), addr) == -1) return (addr); if (rdback) { if (mdb_tgt_aread(mdb.m_target, as, &n, sizeof (n), addr) == -1) return (addr); mdb_iob_printf(mdb.m_out, "%-#*lla%16T%-#24llx=%8T0x%llx\n", mdb_iob_getmargin(mdb.m_out), addr, o, n); } return (addr + sizeof (n)); } /* * Writes to objects of size 1, 2, 4, or 8 bytes. The function * doesn't care if the object is a number or not (e.g. it could * be a byte array, or a struct) as long as the size of the write * is one of the aforementioned ones. */ static mdb_tgt_addr_t write_var_uint(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t val, size_t size, uint_t rdback) { if (size < sizeof (uint64_t)) { uint64_t max_num = 1ULL << (size * NBBY); if (val >= max_num) { uint64_t write_len = 0; /* count bytes needed for val */ while (val != 0) { write_len++; val >>= NBBY; } mdb_warn("value too big for the length of the write: " "supplied %llu bytes but maximum is %llu bytes\n", (u_longlong_t)write_len, (u_longlong_t)size); return (addr); } } switch (size) { case 1: return (write_uint8(as, addr, val, rdback)); case 2: return (write_uint16(as, addr, val, rdback)); case 4: return (write_uint32(as, addr, val, rdback)); case 8: return (write_uint64(as, addr, val, rdback)); default: mdb_warn("writes of size %u are not supported\n ", size); return (addr); } } static mdb_tgt_addr_t write_ctf_uint(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t n, uint_t rdback) { mdb_ctf_id_t mid; size_t size; ssize_t type_size; int kind; if (mdb_ctf_lookup_by_addr(addr, &mid) != 0) { mdb_warn("no CTF data found at this address\n"); return (addr); } kind = mdb_ctf_type_kind(mid); if (kind == CTF_ERR) { mdb_warn("CTF data found but type kind could not be read"); return (addr); } if (kind == CTF_K_TYPEDEF) { mdb_ctf_id_t temp_id; if (mdb_ctf_type_resolve(mid, &temp_id) != 0) { mdb_warn("failed to resolve type"); return (addr); } kind = mdb_ctf_type_kind(temp_id); } if (kind != CTF_K_INTEGER && kind != CTF_K_POINTER && kind != CTF_K_ENUM) { mdb_warn("CTF type should be integer, pointer, or enum\n"); return (addr); } type_size = mdb_ctf_type_size(mid); if (type_size < 0) { mdb_warn("CTF data found but size could not be read"); return (addr); } size = type_size; return (write_var_uint(as, addr, n, size, rdback)); } static int write_arglist(mdb_tgt_as_t as, mdb_tgt_addr_t addr, int argc, const mdb_arg_t *argv) { mdb_tgt_addr_t (*write_value)(mdb_tgt_as_t, mdb_tgt_addr_t, uint64_t, uint_t); mdb_tgt_addr_t naddr; uintmax_t value; int rdback = mdb.m_flags & MDB_FL_READBACK; size_t i; if (argc == 1) { mdb_warn("expected value to write following %c\n", argv->a_un.a_char); return (DCMD_ERR); } switch (argv->a_un.a_char) { case 'v': write_value = write_uint8; break; case 'w': write_value = write_uint16; break; case 'z': write_value = write_ctf_uint; break; case 'W': write_value = write_uint32; break; case 'Z': write_value = write_uint64; break; default: write_value = NULL; break; } for (argv++, i = 1; i < argc; i++, argv++) { if (argv->a_type == MDB_TYPE_CHAR) { mdb_warn("expected immediate value instead of '%c'\n", argv->a_un.a_char); return (DCMD_ERR); } if (argv->a_type == MDB_TYPE_STRING) { if (mdb_eval(argv->a_un.a_str) == -1) { mdb_warn("failed to write \"%s\"", argv->a_un.a_str); return (DCMD_ERR); } value = mdb_nv_get_value(mdb.m_dot); } else value = argv->a_un.a_val; mdb_nv_set_value(mdb.m_dot, addr); if ((naddr = write_value(as, addr, value, rdback)) == addr) { mdb_warn("failed to write %llr at address 0x%llx", value, addr); mdb.m_incr = 0; return (DCMD_ERR); } mdb.m_incr = naddr - addr; addr = naddr; } return (DCMD_OK); } static mdb_tgt_addr_t match_uint16(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t v64, uint64_t m64) { uint16_t x, val = (uint16_t)v64, mask = (uint16_t)m64; for (; mdb_tgt_aread(mdb.m_target, as, &x, sizeof (x), addr) == sizeof (x); addr += sizeof (x)) { if ((x & mask) == val) { mdb_iob_printf(mdb.m_out, "%lla\n", addr); break; } } return (addr); } static mdb_tgt_addr_t match_uint32(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t v64, uint64_t m64) { uint32_t x, val = (uint32_t)v64, mask = (uint32_t)m64; for (; mdb_tgt_aread(mdb.m_target, as, &x, sizeof (x), addr) == sizeof (x); addr += sizeof (x)) { if ((x & mask) == val) { mdb_iob_printf(mdb.m_out, "%lla\n", addr); break; } } return (addr); } static mdb_tgt_addr_t match_uint64(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint64_t val, uint64_t mask) { uint64_t x; for (; mdb_tgt_aread(mdb.m_target, as, &x, sizeof (x), addr) == sizeof (x); addr += sizeof (x)) { if ((x & mask) == val) { mdb_iob_printf(mdb.m_out, "%lla\n", addr); break; } } return (addr); } static int match_arglist(mdb_tgt_as_t as, uint_t flags, mdb_tgt_addr_t addr, int argc, const mdb_arg_t *argv) { mdb_tgt_addr_t (*match_value)(mdb_tgt_as_t, mdb_tgt_addr_t, uint64_t, uint64_t); uint64_t args[2] = { 0, -1ULL }; /* [ value, mask ] */ size_t i; if (argc < 2) { mdb_warn("expected value following %c\n", argv->a_un.a_char); return (DCMD_ERR); } if (argc > 3) { mdb_warn("only value and mask may follow %c\n", argv->a_un.a_char); return (DCMD_ERR); } switch (argv->a_un.a_char) { case 'l': match_value = match_uint16; break; case 'L': match_value = match_uint32; break; case 'M': match_value = match_uint64; break; default: mdb_warn("unknown match value %c\n", argv->a_un.a_char); return (DCMD_ERR); } for (argv++, i = 1; i < argc; i++, argv++) { if (argv->a_type == MDB_TYPE_CHAR) { mdb_warn("expected immediate value instead of '%c'\n", argv->a_un.a_char); return (DCMD_ERR); } if (argv->a_type == MDB_TYPE_STRING) { if (mdb_eval(argv->a_un.a_str) == -1) { mdb_warn("failed to evaluate \"%s\"", argv->a_un.a_str); return (DCMD_ERR); } args[i - 1] = mdb_nv_get_value(mdb.m_dot); } else args[i - 1] = argv->a_un.a_val; } addr = match_value(as, addr, args[0], args[1]); mdb_nv_set_value(mdb.m_dot, addr); /* * In adb(1), the match operators ignore any repeat count that has * been applied to them. We emulate this undocumented property * by returning DCMD_ABORT if our input is not a pipeline. */ return ((flags & DCMD_PIPE) ? DCMD_OK : DCMD_ABORT); } static int argncmp(int argc, const mdb_arg_t *argv, const char *s) { for (; *s != '\0'; s++, argc--, argv++) { if (argc == 0 || argv->a_type != MDB_TYPE_CHAR) return (FALSE); if (argv->a_un.a_char != *s) return (FALSE); } return (TRUE); } static int print_arglist(mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char buf[MDB_TGT_SYM_NAMLEN]; mdb_tgt_addr_t oaddr = addr; mdb_tgt_addr_t naddr; GElf_Sym sym; size_t i, n; if (DCMD_HDRSPEC(flags) && (flags & DCMD_PIPE_OUT) == 0) { const char *fmt; int is_dis; /* * This is nasty, but necessary for precise adb compatibility. * Detect disassembly format by looking for "ai" or "ia": */ if (argncmp(argc, argv, "ai")) { fmt = "%-#*lla\n"; is_dis = TRUE; } else if (argncmp(argc, argv, "ia")) { fmt = "%-#*lla"; is_dis = TRUE; } else { fmt = "%-#*lla%16T"; is_dis = FALSE; } /* * If symbolic decoding is on, disassembly is off, and the * address exactly matches a symbol, print the symbol name: */ if ((mdb.m_flags & MDB_FL_PSYM) && !is_dis && (as == MDB_TGT_AS_VIRT || as == MDB_TGT_AS_FILE) && mdb_tgt_lookup_by_addr(mdb.m_target, (uintptr_t)addr, MDB_TGT_SYM_EXACT, buf, sizeof (buf), &sym, NULL) == 0) mdb_iob_printf(mdb.m_out, "%s:\n", buf); /* * If this is a virtual address, cast it so that it reflects * only the valid component of the address. */ if (as == MDB_TGT_AS_VIRT) addr = (uintptr_t)addr; mdb_iob_printf(mdb.m_out, fmt, (uint_t)mdb_iob_getmargin(mdb.m_out), addr); } if (argc == 0) { /* * Yes, for you trivia buffs: if you use a format verb and give * no format string, you get: X^"= "i ... note that in adb the * the '=' verb once had 'z' as its default, but then 'z' was * deleted (it was once an alias for 'i') and so =\n now calls * scanform("z") and produces a 'bad modifier' message. */ static const mdb_arg_t def_argv[] = { { MDB_TYPE_CHAR, MDB_INIT_CHAR('X') }, { MDB_TYPE_CHAR, MDB_INIT_CHAR('^') }, { MDB_TYPE_STRING, MDB_INIT_STRING("= ") }, { MDB_TYPE_CHAR, MDB_INIT_CHAR('i') } }; argc = sizeof (def_argv) / sizeof (mdb_arg_t); argv = def_argv; } mdb_iob_setflags(mdb.m_out, MDB_IOB_INDENT); for (i = 0, n = 1; i < argc; i++, argv++) { switch (argv->a_type) { case MDB_TYPE_CHAR: naddr = mdb_fmt_print(mdb.m_target, as, addr, n, argv->a_un.a_char); mdb.m_incr = naddr - addr; addr = naddr; n = 1; break; case MDB_TYPE_IMMEDIATE: n = argv->a_un.a_val; break; case MDB_TYPE_STRING: mdb_iob_puts(mdb.m_out, argv->a_un.a_str); n = 1; break; } } mdb.m_incr = addr - oaddr; mdb_iob_clrflags(mdb.m_out, MDB_IOB_INDENT); return (DCMD_OK); } static int print_common(mdb_tgt_as_t as, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_addr_t addr = mdb_nv_get_value(mdb.m_dot); if (argc != 0 && argv->a_type == MDB_TYPE_CHAR) { if (strchr("vwzWZ", argv->a_un.a_char)) return (write_arglist(as, addr, argc, argv)); if (strchr("lLM", argv->a_un.a_char)) return (match_arglist(as, flags, addr, argc, argv)); } return (print_arglist(as, addr, flags, argc, argv)); } /*ARGSUSED*/ static int cmd_print_core(uintptr_t x, uint_t flags, int argc, const mdb_arg_t *argv) { return (print_common(MDB_TGT_AS_VIRT, flags, argc, argv)); } #ifndef _KMDB /*ARGSUSED*/ static int cmd_print_object(uintptr_t x, uint_t flags, int argc, const mdb_arg_t *argv) { return (print_common(MDB_TGT_AS_FILE, flags, argc, argv)); } #endif /*ARGSUSED*/ static int cmd_print_phys(uintptr_t x, uint_t flags, int argc, const mdb_arg_t *argv) { return (print_common(MDB_TGT_AS_PHYS, flags, argc, argv)); } /*ARGSUSED*/ static int cmd_print_value(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintmax_t ndot, dot = mdb_get_dot(); const char *tgt_argv[1]; mdb_tgt_t *t; size_t i, n; if (argc == 0) { mdb_warn("expected one or more format characters " "following '='\n"); return (DCMD_ERR); } tgt_argv[0] = (const char *)˙ t = mdb_tgt_create(mdb_value_tgt_create, 0, 1, tgt_argv); mdb_iob_setflags(mdb.m_out, MDB_IOB_INDENT); for (i = 0, n = 1; i < argc; i++, argv++) { switch (argv->a_type) { case MDB_TYPE_CHAR: ndot = mdb_fmt_print(t, MDB_TGT_AS_VIRT, dot, n, argv->a_un.a_char); if (argv->a_un.a_char == '+' || argv->a_un.a_char == '-') dot = ndot; n = 1; break; case MDB_TYPE_IMMEDIATE: n = argv->a_un.a_val; break; case MDB_TYPE_STRING: mdb_iob_puts(mdb.m_out, argv->a_un.a_str); n = 1; break; } } mdb_iob_clrflags(mdb.m_out, MDB_IOB_INDENT); mdb_nv_set_value(mdb.m_dot, dot); mdb.m_incr = 0; mdb_tgt_destroy(t); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_assign_variable(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintmax_t dot = mdb_nv_get_value(mdb.m_dot); const char *p; mdb_var_t *v; if (argc == 2) { if (argv->a_type != MDB_TYPE_CHAR) { mdb_warn("improper arguments following '>' operator\n"); return (DCMD_ERR); } switch (argv->a_un.a_char) { case 'c': addr = *((uchar_t *)&addr); break; case 's': addr = *((ushort_t *)&addr); break; case 'i': addr = *((uint_t *)&addr); break; case 'l': addr = *((ulong_t *)&addr); break; default: mdb_warn("%c is not a valid // modifier\n", argv->a_un.a_char); return (DCMD_ERR); } dot = addr; argv++; argc--; } if (argc != 1 || argv->a_type != MDB_TYPE_STRING) { mdb_warn("expected single variable name following '>'\n"); return (DCMD_ERR); } if (strlen(argv->a_un.a_str) >= (size_t)MDB_NV_NAMELEN) { mdb_warn("variable names may not exceed %d characters\n", MDB_NV_NAMELEN - 1); return (DCMD_ERR); } if ((p = strbadid(argv->a_un.a_str)) != NULL) { mdb_warn("'%c' may not be used in a variable name\n", *p); return (DCMD_ERR); } if ((v = mdb_nv_lookup(&mdb.m_nv, argv->a_un.a_str)) == NULL) (void) mdb_nv_insert(&mdb.m_nv, argv->a_un.a_str, NULL, dot, 0); else mdb_nv_set_value(v, dot); mdb.m_incr = 0; return (DCMD_OK); } static int print_soutype(const char *sou, uintptr_t addr, uint_t flags) { static const char *prefixes[] = { "struct ", "union " }; size_t namesz = 7 + strlen(sou) + 1; char *name = mdb_alloc(namesz, UM_SLEEP | UM_GC); mdb_ctf_id_t id; int i; for (i = 0; i < 2; i++) { (void) mdb_snprintf(name, namesz, "%s%s", prefixes[i], sou); if (mdb_ctf_lookup_by_name(name, &id) == 0) { mdb_arg_t v; int rv; v.a_type = MDB_TYPE_STRING; v.a_un.a_str = name; rv = mdb_call_dcmd("print", addr, flags, 1, &v); return (rv); } } return (DCMD_ERR); } static int print_type(const char *name, uintptr_t addr, uint_t flags) { mdb_ctf_id_t id; char *sname; size_t snamesz; int rv; if (!(flags & DCMD_ADDRSPEC)) { addr = mdb_get_dot(); flags |= DCMD_ADDRSPEC; } if ((rv = print_soutype(name, addr, flags)) != DCMD_ERR) return (rv); snamesz = strlen(name) + 3; sname = mdb_zalloc(snamesz, UM_SLEEP | UM_GC); (void) mdb_snprintf(sname, snamesz, "%s_t", name); if (mdb_ctf_lookup_by_name(sname, &id) == 0) { mdb_arg_t v; int rv; v.a_type = MDB_TYPE_STRING; v.a_un.a_str = sname; rv = mdb_call_dcmd("print", addr, flags, 1, &v); return (rv); } sname[snamesz - 2] = 's'; rv = print_soutype(sname, addr, flags); return (rv); } static int exec_alias(const char *fname, uintptr_t addr, uint_t flags) { const char *alias; int rv; if ((alias = mdb_macalias_lookup(fname)) == NULL) return (DCMD_ERR); if (flags & DCMD_ADDRSPEC) { size_t sz = sizeof (uintptr_t) * 2 + strlen(alias) + 1; char *addralias = mdb_alloc(sz, UM_SLEEP | UM_GC); (void) mdb_snprintf(addralias, sz, "%p%s", addr, alias); rv = mdb_eval(addralias); } else { rv = mdb_eval(alias); } return (rv == -1 ? DCMD_ABORT : DCMD_OK); } /*ARGSUSED*/ static int cmd_src_file(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *fname; mdb_io_t *fio; int rv; if (argc != 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); fname = argv->a_un.a_str; if (flags & DCMD_PIPE_OUT) { mdb_warn("macro files cannot be used as input to a pipeline\n"); return (DCMD_ABORT); } if ((fio = mdb_fdio_create_path(mdb.m_ipath, fname, O_RDONLY, 0)) != NULL) { mdb_frame_t *fp = mdb.m_frame; int err; mdb_iob_stack_push(&fp->f_istk, mdb.m_in, yylineno); mdb.m_in = mdb_iob_create(fio, MDB_IOB_RDONLY); err = mdb_run(); ASSERT(fp == mdb.m_frame); mdb.m_in = mdb_iob_stack_pop(&fp->f_istk); yylineno = mdb_iob_lineno(mdb.m_in); if (err == MDB_ERR_PAGER && mdb.m_fmark != fp) longjmp(fp->f_pcb, err); if (err == MDB_ERR_QUIT || err == MDB_ERR_ABORT || err == MDB_ERR_SIGINT || err == MDB_ERR_OUTPUT) longjmp(fp->f_pcb, err); return (DCMD_OK); } if ((rv = exec_alias(fname, addr, flags)) != DCMD_ERR || (rv = print_type(fname, addr, flags)) != DCMD_ERR) return (rv); mdb_warn("failed to open %s (see ::help '$<')\n", fname); return (DCMD_ABORT); } static int cmd_exec_file(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *fname; mdb_io_t *fio; int rv; /* * The syntax [expr[,count]]$< with no trailing macro file name is * magic in that if count is zero, this command won't be called and * the expression is thus a no-op. If count is non-zero, we get * invoked with argc == 0, and this means abort the current macro. * If our debugger stack depth is greater than one, we may be using * $< from within a previous $<<, so in that case we set m_in to * NULL to force this entire frame to be popped. */ if (argc == 0) { if (mdb_iob_stack_size(&mdb.m_frame->f_istk) != 0) { mdb_iob_destroy(mdb.m_in); mdb.m_in = mdb_iob_stack_pop(&mdb.m_frame->f_istk); } else if (mdb.m_depth > 1) { mdb_iob_destroy(mdb.m_in); mdb.m_in = NULL; } else mdb_warn("input stack is empty\n"); return (DCMD_OK); } if ((flags & (DCMD_PIPE | DCMD_PIPE_OUT)) || mdb.m_depth == 1) return (cmd_src_file(addr, flags, argc, argv)); if (argc != 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); fname = argv->a_un.a_str; if ((fio = mdb_fdio_create_path(mdb.m_ipath, fname, O_RDONLY, 0)) != NULL) { mdb_iob_destroy(mdb.m_in); mdb.m_in = mdb_iob_create(fio, MDB_IOB_RDONLY); return (DCMD_OK); } if ((rv = exec_alias(fname, addr, flags)) != DCMD_ERR || (rv = print_type(fname, addr, flags)) != DCMD_ERR) return (rv); mdb_warn("failed to open %s (see ::help '$<')\n", fname); return (DCMD_ABORT); } #ifndef _KMDB /*ARGSUSED*/ static int cmd_cat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int status = DCMD_OK; char buf[BUFSIZ]; mdb_iob_t *iob; mdb_io_t *fio; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); for (; argc-- != 0; argv++) { if (argv->a_type != MDB_TYPE_STRING) { mdb_warn("expected string argument\n"); status = DCMD_ERR; continue; } if ((fio = mdb_fdio_create_path(NULL, argv->a_un.a_str, O_RDONLY, 0)) == NULL) { mdb_warn("failed to open %s", argv->a_un.a_str); status = DCMD_ERR; continue; } iob = mdb_iob_create(fio, MDB_IOB_RDONLY); while (!(mdb_iob_getflags(iob) & (MDB_IOB_EOF | MDB_IOB_ERR))) { ssize_t len = mdb_iob_read(iob, buf, sizeof (buf)); if (len > 0) { if (mdb_iob_write(mdb.m_out, buf, len) < 0) { if (errno != EPIPE) mdb_warn("write failed"); status = DCMD_ERR; break; } } } if (mdb_iob_err(iob)) mdb_warn("error while reading %s", mdb_iob_name(iob)); mdb_iob_destroy(iob); } return (status); } #endif /*ARGSUSED*/ static int cmd_grep(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (mdb_eval(argv->a_un.a_str) == -1) return (DCMD_ABORT); if (mdb_get_dot() != 0) mdb_printf("%lr\n", addr); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_map(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (mdb_eval(argv->a_un.a_str) == -1) return (DCMD_ABORT); mdb_printf("%llr\n", mdb_get_dot()); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_notsup(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_warn("command is not supported by current target\n"); return (DCMD_ERR); } /*ARGSUSED*/ static int cmd_quit(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { #ifdef _KMDB uint_t opt_u = FALSE; if (mdb_getopts(argc, argv, 'u', MDB_OPT_SETBITS, TRUE, &opt_u, NULL) != argc) return (DCMD_USAGE); if (opt_u) { if (mdb.m_flags & MDB_FL_NOUNLOAD) { warn("%s\n", mdb_strerror(EMDB_KNOUNLOAD)); return (DCMD_ERR); } kmdb_kdi_set_unload_request(); } #endif longjmp(mdb.m_frame->f_pcb, MDB_ERR_QUIT); /*NOTREACHED*/ return (DCMD_ERR); } #ifdef _KMDB static void quit_help(void) { mdb_printf( "-u unload the debugger (if not loaded at boot)\n"); } #endif /*ARGSUSED*/ static int cmd_vars(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_nz = FALSE, opt_tag = FALSE, opt_prt = FALSE; mdb_var_t *v; if (mdb_getopts(argc, argv, 'n', MDB_OPT_SETBITS, TRUE, &opt_nz, 'p', MDB_OPT_SETBITS, TRUE, &opt_prt, 't', MDB_OPT_SETBITS, TRUE, &opt_tag, NULL) != argc) return (DCMD_USAGE); mdb_nv_rewind(&mdb.m_nv); while ((v = mdb_nv_advance(&mdb.m_nv)) != NULL) { if ((opt_tag == FALSE || (v->v_flags & MDB_NV_TAGGED)) && (opt_nz == FALSE || mdb_nv_get_value(v) != 0)) { if (opt_prt) { mdb_printf("%#llr>%s\n", mdb_nv_get_value(v), mdb_nv_get_name(v)); } else { mdb_printf("%s = %llr\n", mdb_nv_get_name(v), mdb_nv_get_value(v)); } } } return (DCMD_OK); } /*ARGSUSED*/ static int cmd_nzvars(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintmax_t value; mdb_var_t *v; if (argc != 0) return (DCMD_USAGE); mdb_nv_rewind(&mdb.m_nv); while ((v = mdb_nv_advance(&mdb.m_nv)) != NULL) { if ((value = mdb_nv_get_value(v)) != 0) mdb_printf("%s = %llr\n", mdb_nv_get_name(v), value); } return (DCMD_OK); } /*ARGSUSED*/ static int cmd_radix(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); if (flags & DCMD_ADDRSPEC) { if (addr < 2 || addr > 16) { mdb_warn("expected radix from 2 to 16\n"); return (DCMD_ERR); } mdb.m_radix = (int)addr; } mdb_iob_printf(mdb.m_out, "radix = %d base ten\n", mdb.m_radix); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_symdist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); if (flags & DCMD_ADDRSPEC) mdb.m_symdist = addr; mdb_printf("symbol matching distance = %lr (%s)\n", mdb.m_symdist, mdb.m_symdist ? "absolute mode" : "smart mode"); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_pgwidth(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); if (flags & DCMD_ADDRSPEC) mdb_iob_resize(mdb.m_out, mdb.m_out->iob_rows, addr); mdb_printf("output page width = %lu\n", mdb.m_out->iob_cols); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_reopen(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); if (mdb_tgt_setflags(mdb.m_target, MDB_TGT_F_RDWR) == -1) { mdb_warn("failed to re-open target for writing"); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ static int print_xdata(void *ignored, const char *name, const char *desc, size_t nbytes) { mdb_printf("%-24s - %s (%lu bytes)\n", name, desc, (ulong_t)nbytes); return (0); } /*ARGSUSED*/ static int cmd_xdata(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0 || (flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); (void) mdb_tgt_xdata_iter(mdb.m_target, print_xdata, NULL); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_unset(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_var_t *v; size_t i; for (i = 0; i < argc; i++) { if (argv[i].a_type != MDB_TYPE_STRING) { mdb_warn("bad option: arg %lu is not a string\n", (ulong_t)i + 1); return (DCMD_USAGE); } } for (i = 0; i < argc; i++, argv++) { if ((v = mdb_nv_lookup(&mdb.m_nv, argv->a_un.a_str)) == NULL) mdb_warn("variable '%s' not defined\n", argv->a_un.a_str); else mdb_nv_remove(&mdb.m_nv, v); } return (DCMD_OK); } #ifndef _KMDB /*ARGSUSED*/ static int cmd_log(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_e = FALSE, opt_d = FALSE; const char *filename = NULL; int i; i = mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &opt_d, 'e', MDB_OPT_SETBITS, TRUE, &opt_e, NULL); if ((i != argc && i != argc - 1) || (opt_d && opt_e) || (i != argc && argv[i].a_type != MDB_TYPE_STRING) || (i != argc && opt_d == TRUE) || (flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb.m_depth != 1) { mdb_warn("log may not be manipulated in this context\n"); return (DCMD_ABORT); } if (i != argc) filename = argv[i].a_un.a_str; /* * If no arguments were specified, print the log file name (if any) * and report whether the log is enabled or disabled. */ if (argc == 0) { if (mdb.m_log) { mdb_printf("%s: logging to \"%s\" is currently %s\n", mdb.m_pname, IOP_NAME(mdb.m_log), mdb.m_flags & MDB_FL_LOG ? "enabled" : "disabled"); } else mdb_printf("%s: no log is active\n", mdb.m_pname); return (DCMD_OK); } /* * If the -d option was specified, pop the log i/o object off the * i/o stack of stdin, stdout, and stderr. */ if (opt_d) { if (mdb.m_flags & MDB_FL_LOG) { (void) mdb_iob_pop_io(mdb.m_in); (void) mdb_iob_pop_io(mdb.m_out); (void) mdb_iob_pop_io(mdb.m_err); mdb.m_flags &= ~MDB_FL_LOG; } else mdb_warn("logging is already disabled\n"); return (DCMD_OK); } /* * The -e option is the default: (re-)enable logging by pushing * the log i/o object on to stdin, stdout, and stderr. If we have * a previous log file, we need to pop it and close it. If we have * no new log file, push the previous one back on. */ if (filename != NULL) { if (mdb.m_log != NULL) { if (mdb.m_flags & MDB_FL_LOG) { (void) mdb_iob_pop_io(mdb.m_in); (void) mdb_iob_pop_io(mdb.m_out); (void) mdb_iob_pop_io(mdb.m_err); mdb.m_flags &= ~MDB_FL_LOG; } mdb_io_rele(mdb.m_log); } mdb.m_log = mdb_fdio_create_path(NULL, filename, O_CREAT | O_APPEND | O_WRONLY, 0666); if (mdb.m_log == NULL) { mdb_warn("failed to open %s", filename); return (DCMD_ERR); } } if (mdb.m_log != NULL) { mdb_iob_push_io(mdb.m_in, mdb_logio_create(mdb.m_log)); mdb_iob_push_io(mdb.m_out, mdb_logio_create(mdb.m_log)); mdb_iob_push_io(mdb.m_err, mdb_logio_create(mdb.m_log)); mdb_printf("%s: logging to \"%s\"\n", mdb.m_pname, filename); mdb.m_log = mdb_io_hold(mdb.m_log); mdb.m_flags |= MDB_FL_LOG; return (DCMD_OK); } mdb_warn("no log file has been selected\n"); return (DCMD_ERR); } static int cmd_old_log(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc == 0) { mdb_arg_t arg = { MDB_TYPE_STRING, MDB_INIT_STRING("-d") }; return (cmd_log(addr, flags, 1, &arg)); } return (cmd_log(addr, flags, argc, argv)); } #endif /*ARGSUSED*/ static int cmd_load(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i, mode = MDB_MOD_LOCAL; i = mdb_getopts(argc, argv, #ifdef _KMDB 'd', MDB_OPT_SETBITS, MDB_MOD_DEFER, &mode, #endif 'f', MDB_OPT_SETBITS, MDB_MOD_FORCE, &mode, 'g', MDB_OPT_SETBITS, MDB_MOD_GLOBAL, &mode, 's', MDB_OPT_SETBITS, MDB_MOD_SILENT, &mode, NULL); argc -= i; argv += i; if ((flags & DCMD_ADDRSPEC) || argc != 1 || argv->a_type != MDB_TYPE_STRING || strchr("+-", argv->a_un.a_str[0]) != NULL) return (DCMD_USAGE); if (mdb_module_load(argv->a_un.a_str, mode) < 0) return (DCMD_ERR); return (DCMD_OK); } static void load_help(void) { mdb_printf( #ifdef _KMDB "-d defer load until next continue\n" #endif "-s load module silently\n"); } /*ARGSUSED*/ static int cmd_unload(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int mode = 0; int i; i = mdb_getopts(argc, argv, #ifdef _KMDB 'd', MDB_OPT_SETBITS, MDB_MOD_DEFER, &mode, #endif NULL); argc -= i; argv += i; if (argc != 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (mdb_module_unload(argv->a_un.a_str, mode) == -1) { mdb_warn("failed to unload %s", argv->a_un.a_str); return (DCMD_ERR); } return (DCMD_OK); } #ifdef _KMDB static void unload_help(void) { mdb_printf( "-d defer unload until next continue\n"); } #endif static int cmd_dbmode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc > 1 || (argc != 0 && (flags & DCMD_ADDRSPEC))) return (DCMD_USAGE); if (argc != 0) { if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if ((addr = mdb_dstr2mode(argv->a_un.a_str)) != MDB_DBG_HELP) mdb_dmode(addr); } else if (flags & DCMD_ADDRSPEC) mdb_dmode(addr); mdb_printf("debugging mode = 0x%04x\n", mdb.m_debug); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_version(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { #ifdef DEBUG mdb_printf("\r%s (DEBUG)\n", mdb_conf_version()); #else mdb_printf("\r%s\n", mdb_conf_version()); #endif return (DCMD_OK); } /*ARGSUSED*/ static int cmd_algol(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (mdb.m_flags & MDB_FL_ADB) mdb_printf("No algol 68 here\n"); else mdb_printf("No adb here\n"); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_obey(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (mdb.m_flags & MDB_FL_ADB) mdb_printf("CHAPTER 1\n"); else mdb_printf("No Language H here\n"); return (DCMD_OK); } /*ARGSUSED*/ static int print_global(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { uintptr_t value; if (mdb_tgt_vread((mdb_tgt_t *)data, &value, sizeof (value), (uintptr_t)sym->st_value) == sizeof (value)) mdb_printf("%s(%llr):\t%lr\n", name, sym->st_value, value); else mdb_printf("%s(%llr):\t?\n", name, sym->st_value); return (0); } /*ARGSUSED*/ static int cmd_globals(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); (void) mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY, MDB_TGT_SYMTAB, MDB_TGT_BIND_GLOBAL | MDB_TGT_TYPE_OBJECT | MDB_TGT_TYPE_FUNC, print_global, mdb.m_target); return (0); } /*ARGSUSED*/ static int cmd_eval(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (mdb_eval(argv->a_un.a_str) == -1) return (DCMD_ABORT); return (DCMD_OK); } /*ARGSUSED*/ static int print_file(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { int i = *((int *)data); mdb_printf("%d\t%s\n", i++, name); *((int *)data) = i; return (0); } /*ARGSUSED*/ static int cmd_files(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i = 1; const char *obj = MDB_TGT_OBJ_EVERY; if ((flags & DCMD_ADDRSPEC) || argc > 1) return (DCMD_USAGE); if (argc == 1) { if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); obj = argv->a_un.a_str; } (void) mdb_tgt_symbol_iter(mdb.m_target, obj, MDB_TGT_SYMTAB, MDB_TGT_BIND_ANY | MDB_TGT_TYPE_FILE, print_file, &i); return (DCMD_OK); } static const char * map_name(const mdb_map_t *map, const char *name) { if (map->map_flags & MDB_TGT_MAP_HEAP) return ("[ heap ]"); if (name != NULL && name[0] != 0) return (name); if (map->map_flags & MDB_TGT_MAP_SHMEM) return ("[ shmem ]"); if (map->map_flags & MDB_TGT_MAP_STACK) return ("[ stack ]"); if (map->map_flags & MDB_TGT_MAP_ANON) return ("[ anon ]"); if (map->map_name[0] == '\0') return ("[ unknown ]"); return (map->map_name); } /*ARGSUSED*/ static int print_map(void *ignored, const mdb_map_t *map, const char *name) { name = map_name(map, name); mdb_printf("%?p %?p %?lx %s\n", map->map_base, map->map_base + map->map_size, map->map_size, name); return (0); } static int cmd_mappings(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const mdb_map_t *m; if (argc > 1 || (argc != 0 && (flags & DCMD_ADDRSPEC))) return (DCMD_USAGE); mdb_printf("%%?s %?s %?s %s%\n", "BASE", "LIMIT", "SIZE", "NAME"); if (flags & DCMD_ADDRSPEC) { if ((m = mdb_tgt_addr_to_map(mdb.m_target, addr)) == NULL) mdb_warn("failed to obtain mapping"); else (void) print_map(NULL, m, NULL); } else if (argc != 0) { if (argv->a_type == MDB_TYPE_STRING) m = mdb_tgt_name_to_map(mdb.m_target, argv->a_un.a_str); else m = mdb_tgt_addr_to_map(mdb.m_target, argv->a_un.a_val); if (m == NULL) mdb_warn("failed to obtain mapping"); else (void) print_map(NULL, m, NULL); } else if (mdb_tgt_mapping_iter(mdb.m_target, print_map, NULL) == -1) mdb_warn("failed to iterate over mappings"); return (DCMD_OK); } static int whatis_map_callback(void *wp, const mdb_map_t *map, const char *name) { mdb_whatis_t *w = wp; uintptr_t cur; name = map_name(map, name); while (mdb_whatis_match(w, map->map_base, map->map_size, &cur)) mdb_whatis_report_address(w, cur, "in %s [%p,%p)\n", name, map->map_base, map->map_base + map->map_size); return (0); } /*ARGSUSED*/ int whatis_run_mappings(mdb_whatis_t *w, void *ignored) { (void) mdb_tgt_mapping_iter(mdb.m_target, whatis_map_callback, w); return (0); } /*ARGSUSED*/ static int objects_printversion(void *ignored, const mdb_map_t *map, const char *name) { ctf_file_t *ctfp; const char *version; ctfp = mdb_tgt_name_to_ctf(mdb.m_target, name); if (ctfp == NULL || (version = ctf_label_topmost(ctfp)) == NULL) version = "Unknown"; mdb_printf("%-28s %s\n", name, version); return (0); } /*ARGSUSED*/ static int cmd_objects(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_v = FALSE; mdb_tgt_map_f *cb; if ((flags & DCMD_ADDRSPEC) || mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (opt_v) { cb = objects_printversion; mdb_printf("%%-28s %s%\n", "NAME", "VERSION"); } else { cb = print_map; mdb_printf("%%?s %?s %?s %s%\n", "BASE", "LIMIT", "SIZE", "NAME"); } if (mdb_tgt_object_iter(mdb.m_target, cb, NULL) == -1) { mdb_warn("failed to iterate over objects"); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ static int showrev_addversion(void *vers_nv, const mdb_map_t *ignored, const char *object) { ctf_file_t *ctfp; const char *version = NULL; char *objname; objname = mdb_alloc(strlen(object) + 1, UM_SLEEP | UM_GC); (void) strcpy(objname, object); if ((ctfp = mdb_tgt_name_to_ctf(mdb.m_target, objname)) != NULL) version = ctf_label_topmost(ctfp); /* * Not all objects have CTF and label data, so set version to "Unknown". */ if (version == NULL) version = "Unknown"; (void) mdb_nv_insert(vers_nv, version, NULL, (uintptr_t)objname, MDB_NV_OVERLOAD); return (0); } static int showrev_ispatch(const char *s) { if (s == NULL) return (0); if (*s == 'T') s++; /* skip T for T-patch */ for (; *s != '\0'; s++) { if ((*s < '0' || *s > '9') && *s != '-') return (0); } return (1); } /*ARGSUSED*/ static int showrev_printobject(mdb_var_t *v, void *ignored) { mdb_printf("%s ", MDB_NV_COOKIE(v)); return (0); } static int showrev_printversion(mdb_var_t *v, void *showall) { const char *version = mdb_nv_get_name(v); int patch; patch = showrev_ispatch(version); if (patch || (uintptr_t)showall) { mdb_printf("%s: %s Objects: ", (patch ? "Patch" : "Version"), version); (void) mdb_inc_indent(2); mdb_nv_defn_iter(v, showrev_printobject, NULL); (void) mdb_dec_indent(2); mdb_printf("\n"); } return (0); } /* * Display version information for each object in the system. * Print information about patches only, unless showall is TRUE. */ static int showrev_objectversions(int showall) { mdb_nv_t vers_nv; (void) mdb_nv_create(&vers_nv, UM_SLEEP | UM_GC); if (mdb_tgt_object_iter(mdb.m_target, showrev_addversion, &vers_nv) == -1) { mdb_warn("failed to iterate over objects"); return (DCMD_ERR); } mdb_nv_sort_iter(&vers_nv, showrev_printversion, (void *)(uintptr_t)showall, UM_SLEEP | UM_GC); return (DCMD_OK); } /* * Display information similar to what showrev(8) displays when invoked * with no arguments. */ static int showrev_sysinfo(void) { const char *s; int rc; struct utsname u; if ((rc = mdb_tgt_uname(mdb.m_target, &u)) != -1) { mdb_printf("Hostname: %s\n", u.nodename); mdb_printf("Release: %s\n", u.release); mdb_printf("Kernel architecture: %s\n", u.machine); } /* * Match the order of the showrev(8) output and put "Application * architecture" before "Kernel version" */ if ((s = mdb_tgt_isa(mdb.m_target)) != NULL) mdb_printf("Application architecture: %s\n", s); if (rc != -1) mdb_printf("Kernel version: %s %s %s %s\n", u.sysname, u.release, u.machine, u.version); if ((s = mdb_tgt_platform(mdb.m_target)) != NULL) mdb_printf("Platform: %s\n", s); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_showrev(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_p = FALSE, opt_v = FALSE; if ((flags & DCMD_ADDRSPEC) || mdb_getopts(argc, argv, 'p', MDB_OPT_SETBITS, TRUE, &opt_p, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (opt_p || opt_v) return (showrev_objectversions(opt_v)); else return (showrev_sysinfo()); } #ifdef __sparc static void findsym_output(uintptr_t *symlist, uintptr_t value, uintptr_t location) { uintptr_t *symbolp; for (symbolp = symlist; *symbolp; symbolp++) if (value == *symbolp) mdb_printf("found %a at %a\n", value, location); } /*ARGSUSED*/ static int findsym_cb(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { uint32_t *text; int len; int i; int j; uint8_t rd; uintptr_t value; int32_t imm13; uint8_t op; uint8_t op3; uintptr_t *symlist = data; size_t size = sym->st_size; /* * if the size of the symbol is 0, then this symbol must be for an * alternate entry point or just some global label. We will, * therefore, get back to the text that follows this symbol in * some other symbol */ if (size == 0) return (0); if (sym->st_shndx == SHN_UNDEF) return (0); text = alloca(size); if (mdb_vread(text, size, sym->st_value) == -1) { mdb_warn("failed to read text for %s", name); return (0); } len = size / 4; for (i = 0; i < len; i++) { if (!IS_SETHI(text[i])) continue; rd = RD(text[i]); value = IMM22(text[i]) << 10; /* * see if we already have a match with just the sethi */ findsym_output(symlist, value, sym->st_value + i * 4); /* * search from the sethi on until we hit a relevant instr */ for (j = i + 1; j < len; j++) { if ((op = OP(text[j])) & OP_ARITH_MEM_MASK) { op3 = OP3(text[j]); if (RS1(text[j]) != rd) goto instr_end; /* * This is a simple tool; we only deal * with operations which take immediates */ if (I(text[j]) == 0) goto instr_end; /* * sign extend the immediate value */ imm13 = IMM13(text[j]); imm13 <<= 19; imm13 >>= 19; if (op == OP_ARITH) { /* arithmetic operations */ if (op3 & OP3_COMPLEX_MASK) goto instr_end; switch (op3 & ~OP3_CC_MASK) { case OP3_OR: value |= imm13; break; case OP3_ADD: value += imm13; break; case OP3_XOR: value ^= imm13; break; default: goto instr_end; } } else { /* loads and stores */ /* op3 == OP_MEM */ value += imm13; } findsym_output(symlist, value, sym->st_value + j * 4); instr_end: /* * if we're clobbering rd, break */ if (RD(text[j]) == rd) break; } else if (IS_SETHI(text[j])) { if (RD(text[j]) == rd) break; } else if (OP(text[j]) == 1) { /* * see if a call clobbers an %o or %g */ if (rd <= R_O7) break; } } } return (0); } static int cmd_findsym(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t *symlist; uint_t optg = FALSE; uint_t type; int len, i; i = mdb_getopts(argc, argv, 'g', MDB_OPT_SETBITS, TRUE, &optg, NULL); argc -= i; argv += i; len = argc + ((flags & DCMD_ADDRSPEC) ? 1 : 0) + 1; if (len <= 1) return (DCMD_USAGE); /* * Set up a NULL-terminated symbol list, and then iterate over the * symbol table, scanning each function for references to these symbols. */ symlist = mdb_alloc(len * sizeof (uintptr_t), UM_SLEEP | UM_GC); len = 0; for (i = 0; i < argc; i++, argv++) { const char *str = argv->a_un.a_str; uintptr_t value; GElf_Sym sym; if (argv->a_type == MDB_TYPE_STRING) { if (strchr("+-", str[0]) != NULL) return (DCMD_USAGE); else if (str[0] >= '0' && str[0] <= '9') value = mdb_strtoull(str); else if (mdb_lookup_by_name(str, &sym) != 0) { mdb_warn("symbol '%s' not found", str); return (DCMD_USAGE); } else value = sym.st_value; } else value = argv[i].a_un.a_val; if (value != (uintptr_t)NULL) symlist[len++] = value; } if (flags & DCMD_ADDRSPEC) symlist[len++] = addr; symlist[len] = (uintptr_t)NULL; if (optg) type = MDB_TGT_BIND_GLOBAL | MDB_TGT_TYPE_FUNC; else type = MDB_TGT_BIND_ANY | MDB_TGT_TYPE_FUNC; if (mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY, MDB_TGT_SYMTAB, type, findsym_cb, symlist) == -1) { mdb_warn("failed to iterate over symbol table"); return (DCMD_ERR); } return (DCMD_OK); } #endif /* __sparc */ static int dis_str2addr(const char *s, uintptr_t *addr) { GElf_Sym sym; if (s[0] >= '0' && s[0] <= '9') { *addr = (uintptr_t)mdb_strtoull(s); return (0); } if (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EVERY, s, &sym, NULL) == -1) { mdb_warn("symbol '%s' not found\n", s); return (-1); } *addr = (uintptr_t)sym.st_value; return (0); } static int cmd_dis(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *tgt = mdb.m_target; mdb_disasm_t *dis = mdb.m_disasm; uintptr_t oaddr, naddr; mdb_tgt_as_t as; mdb_tgt_status_t st; char buf[BUFSIZ]; GElf_Sym sym; int i; uint_t opt_f = FALSE; /* File-mode off by default */ uint_t opt_p = FALSE; /* Physical mode off by default */ uint_t opt_w = FALSE; /* Window mode off by default */ uint_t opt_a = FALSE; /* Raw-address mode off by default */ uint_t opt_b = FALSE; /* Address & symbols off by default */ uintptr_t n = -1UL; /* Length of window in instructions */ uintptr_t eaddr = 0; /* Ending address; 0 if limited by n */ i = mdb_getopts(argc, argv, 'f', MDB_OPT_SETBITS, TRUE, &opt_f, 'p', MDB_OPT_SETBITS, TRUE, &opt_p, 'w', MDB_OPT_SETBITS, TRUE, &opt_w, 'a', MDB_OPT_SETBITS, TRUE, &opt_a, 'b', MDB_OPT_SETBITS, TRUE, &opt_b, 'n', MDB_OPT_UINTPTR, &n, NULL); /* * Disgusting argument post-processing ... basically the idea is to get * the target address into addr, which we do by using the specified * expression value, looking up a string as a symbol name, or by * using the address specified as dot. */ if (i != argc) { if (argc != 0 && (argc - i) == 1) { if (argv[i].a_type == MDB_TYPE_STRING) { if (argv[i].a_un.a_str[0] == '-') return (DCMD_USAGE); if (dis_str2addr(argv[i].a_un.a_str, &addr)) return (DCMD_ERR); } else addr = argv[i].a_un.a_val; } else return (DCMD_USAGE); } /* * If we're not in window mode yet, and some type of arguments were * specified, see if the address corresponds nicely to a function. * If not, turn on window mode; otherwise disassemble the function. */ if (opt_w == FALSE && (argc != i || (flags & DCMD_ADDRSPEC))) { if (mdb_tgt_lookup_by_addr(tgt, addr, MDB_TGT_SYM_EXACT, buf, sizeof (buf), &sym, NULL) == 0 && GELF_ST_TYPE(sym.st_info) == STT_FUNC) { /* * If the symbol has a size then set our end address to * be the end of the function symbol we just located. */ if (sym.st_size != 0) eaddr = addr + (uintptr_t)sym.st_size; } else opt_w = TRUE; } /* * Window-mode doesn't make sense in a loop. */ if (flags & DCMD_LOOP) opt_w = FALSE; /* * If -n was explicit, limit output to n instructions; * otherwise set n to some reasonable default */ if (n != -1UL) eaddr = 0; else n = 10; /* * If the state is IDLE (i.e. no address space), turn on -f. */ if (mdb_tgt_status(tgt, &st) == 0 && st.st_state == MDB_TGT_IDLE) { if (opt_p) { mdb_warn("cannot use -p (physical address mode) when " "operating on a file\n"); return (DCMD_ERR); } opt_f = TRUE; } if (opt_f && opt_p) { mdb_warn("-f (file mode) and -p (physical address mode) " "cannot be used together\n"); return (DCMD_ERR); } if (opt_f) as = MDB_TGT_AS_FILE; else if (opt_p) as = MDB_TGT_AS_PHYS; else as = MDB_TGT_AS_VIRT_I; if (opt_w == FALSE) { n++; while ((eaddr == 0 && n-- != 0) || (addr < eaddr)) { naddr = mdb_dis_ins2str(dis, tgt, as, buf, sizeof (buf), addr); if (naddr == addr) return (DCMD_ERR); if (opt_a) mdb_printf("%-#32p%8T%s\n", addr, buf); else if (opt_b) mdb_printf("%-#?p %-#32a%8T%s\n", addr, addr, buf); else mdb_printf("%-#32a%8T%s\n", addr, buf); addr = naddr; } } else { #ifdef __sparc if (addr & 0x3) { mdb_warn("address is not properly aligned\n"); return (DCMD_ERR); } #endif for (oaddr = mdb_dis_previns(dis, tgt, as, addr, n); oaddr < addr; oaddr = naddr) { naddr = mdb_dis_ins2str(dis, tgt, as, buf, sizeof (buf), oaddr); if (naddr == oaddr) return (DCMD_ERR); if (opt_a) mdb_printf("%-#32p%8T%s\n", oaddr, buf); else if (opt_b) mdb_printf("%-#?p %-#32a%8T%s\n", oaddr, oaddr, buf); else mdb_printf("%-#32a%8T%s\n", oaddr, buf); } if ((naddr = mdb_dis_ins2str(dis, tgt, as, buf, sizeof (buf), addr)) == addr) return (DCMD_ERR); mdb_printf("%"); mdb_flush(); if (opt_a) mdb_printf("%-#32p%8T%s%", addr, buf); else if (opt_b) mdb_printf("%-#?p %-#32a%8T%s", addr, addr, buf); else mdb_printf("%-#32a%8T%s%", addr, buf); mdb_printf("%\n"); for (addr = naddr; n-- != 0; addr = naddr) { naddr = mdb_dis_ins2str(dis, tgt, as, buf, sizeof (buf), addr); if (naddr == addr) return (DCMD_ERR); if (opt_a) mdb_printf("%-#32p%8T%s\n", addr, buf); else if (opt_b) mdb_printf("%-#?p %-#32a%8T%s\n", addr, addr, buf); else mdb_printf("%-#32a%8T%s\n", addr, buf); } } mdb_set_dot(addr); return (DCMD_OK); } static void dis_help(void) { static const char dis_desc[] = "Disassembles instructions starting at the final argument or the current\n" "value of dot. If the address is the start of a function, the entire\n" "function is disassembled, or else a window of instructions before and after\n" "the disassembled address are displayed.\n" "\n"; static const char dis_opts[] = " -a Print instruction addresses as numeric values instead of \n" " symbolic values.\n" " -b Print instruction addresses as both numeric and symbolic " "values.\n" " -f Read instructions from the target's object file instead of the \n" " target's virtual address space.\n" " -p Read instructions from the target's physical address space\n" " rather than its virtual address space.\n" " -n instr Display 'instr' instructions before and after the given " "address.\n" " -w Force window behavior, even at the start of a function.\n" "\n"; static const char dis_examples[] = " ::dis\n" " clock::dis\n" " ::dis gethrtime\n" " set_freemem+0x16::dis -n 4\n" "\n"; mdb_printf("%s", dis_desc); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf("%s", dis_opts); (void) mdb_dec_indent(2); mdb_printf("%EXAMPLES%\n"); (void) mdb_inc_indent(2); (void) mdb_printf("%s", dis_examples); } /*ARGSUSED*/ static int walk_step(uintptr_t addr, const void *data, void *private) { mdb_printf("%#lr\n", addr); return (WALK_NEXT); } static int cmd_walk(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int status; if (argc < 1 || argc > 2 || argv[0].a_type != MDB_TYPE_STRING || argv[argc - 1].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (argc > 1) { const char *name = argv[1].a_un.a_str; mdb_var_t *v = mdb_nv_lookup(&mdb.m_nv, name); const char *p; if (v != NULL && (v->v_flags & MDB_NV_RDONLY) != 0) { mdb_warn("variable %s is read-only\n", name); return (DCMD_ABORT); } if (v == NULL && (p = strbadid(name)) != NULL) { mdb_warn("'%c' may not be used in a variable " "name\n", *p); return (DCMD_ABORT); } if (v == NULL && (v = mdb_nv_insert(&mdb.m_nv, name, NULL, 0, 0)) == NULL) return (DCMD_ERR); /* * If there already exists a vcb for this variable, we may be * calling ::walk in a loop. We only create a vcb for this * variable on the first invocation. */ if (mdb_vcb_find(v, mdb.m_frame) == NULL) mdb_vcb_insert(mdb_vcb_create(v), mdb.m_frame); } if (flags & DCMD_ADDRSPEC) status = mdb_pwalk(argv->a_un.a_str, walk_step, NULL, addr); else status = mdb_walk(argv->a_un.a_str, walk_step, NULL); if (status == -1) { mdb_warn("failed to perform walk"); return (DCMD_ERR); } return (DCMD_OK); } static int cmd_walk_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc > 1) return (1); if (argc == 1) { ASSERT(argv[0].a_type == MDB_TYPE_STRING); return (mdb_tab_complete_walker(mcp, argv[0].a_un.a_str)); } if (argc == 0 && flags & DCMD_TAB_SPACE) return (mdb_tab_complete_walker(mcp, NULL)); return (1); } static ssize_t mdb_partial_xread(void *buf, size_t nbytes, uintptr_t addr, void *arg) { ssize_t (*fp)(mdb_tgt_t *, const void *, size_t, uintptr_t) = (ssize_t (*)(mdb_tgt_t *, const void *, size_t, uintptr_t))arg; return (fp(mdb.m_target, buf, nbytes, addr)); } /* ARGSUSED3 */ static ssize_t mdb_partial_pread(void *buf, size_t nbytes, physaddr_t addr, void *arg) { return (mdb_tgt_pread(mdb.m_target, buf, nbytes, addr)); } static int cmd_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t dflags = MDB_DUMP_ALIGN | MDB_DUMP_NEWDOT | MDB_DUMP_ASCII | MDB_DUMP_HEADER; uint_t phys = FALSE; uint_t file = FALSE; uintptr_t group = 4; uintptr_t length = 0; uintptr_t width = 1; mdb_tgt_status_t st; int error; if (mdb_getopts(argc, argv, 'e', MDB_OPT_SETBITS, MDB_DUMP_ENDIAN, &dflags, 'f', MDB_OPT_SETBITS, TRUE, &file, 'g', MDB_OPT_UINTPTR, &group, 'l', MDB_OPT_UINTPTR, &length, 'p', MDB_OPT_SETBITS, TRUE, &phys, 'q', MDB_OPT_CLRBITS, MDB_DUMP_ASCII, &dflags, 'r', MDB_OPT_SETBITS, MDB_DUMP_RELATIVE, &dflags, 's', MDB_OPT_SETBITS, MDB_DUMP_SQUISH, &dflags, 't', MDB_OPT_SETBITS, MDB_DUMP_TRIM, &dflags, 'u', MDB_OPT_CLRBITS, MDB_DUMP_ALIGN, &dflags, 'v', MDB_OPT_SETBITS, MDB_DUMP_PEDANT, &dflags, 'w', MDB_OPT_UINTPTR, &width, NULL) != argc) return (DCMD_USAGE); if ((phys && file) || (width == 0) || (width > 0x10) || (group == 0) || (group > 0x100) || (mdb.m_dcount > 1 && length > 0)) return (DCMD_USAGE); if (length == 0) length = mdb.m_dcount; /* * If neither -f nor -p were specified and the state is IDLE (i.e. no * address space), turn on -p. This is so we can read large files. */ if (phys == FALSE && file == FALSE && mdb_tgt_status(mdb.m_target, &st) == 0 && st.st_state == MDB_TGT_IDLE) phys = TRUE; dflags |= MDB_DUMP_GROUP(group) | MDB_DUMP_WIDTH(width); if (phys) error = mdb_dump64(mdb_get_dot(), length, dflags, mdb_partial_pread, NULL); else if (file) error = mdb_dumpptr(addr, length, dflags, mdb_partial_xread, (void *)mdb_tgt_fread); else error = mdb_dumpptr(addr, length, dflags, mdb_partial_xread, (void *)mdb_tgt_vread); return (((flags & DCMD_LOOP) || (error == -1)) ? DCMD_ABORT : DCMD_OK); } /*ARGSUSED*/ static int cmd_echo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); for (; argc-- != 0; argv++) { if (argv->a_type == MDB_TYPE_STRING) mdb_printf("%s ", argv->a_un.a_str); else mdb_printf("%llr ", argv->a_un.a_val); } mdb_printf("\n"); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_head(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint64_t cnt = 10; const char *c; mdb_pipe_t p; if (!(flags & DCMD_PIPE)) return (DCMD_USAGE); if (argc == 1 || argc == 2) { const char *num; if (argc == 1) { if (argv[0].a_type != MDB_TYPE_STRING || *argv[0].a_un.a_str != '-') return (DCMD_USAGE); num = argv[0].a_un.a_str + 1; } else { if (argv[0].a_type != MDB_TYPE_STRING || strcmp(argv[0].a_un.a_str, "-n") != 0) return (DCMD_USAGE); num = argv[1].a_un.a_str; } for (cnt = 0, c = num; *c != '\0' && isdigit(*c); c++) cnt = cnt * 10 + (*c - '0'); if (*c != '\0') return (DCMD_USAGE); } else if (argc != 0) { return (DCMD_USAGE); } mdb_get_pipe(&p); if (p.pipe_data == NULL) return (DCMD_OK); p.pipe_len = MIN(p.pipe_len, cnt); if (flags & DCMD_PIPE_OUT) { mdb_set_pipe(&p); } else { while (p.pipe_len-- > 0) mdb_printf("%lx\n", *p.pipe_data++); } return (DCMD_OK); } static void head_help(void) { mdb_printf( "-n num\n or\n" "-num pass only the first `num' elements in the pipe.\n" "\n%Note:% `num' is a decimal number.\n"); } static int cmd_typeset(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int add_tag = 0, del_tag = 0; const char *p; mdb_var_t *v; if (argc == 0) return (cmd_vars(addr, flags, argc, argv)); if (argv->a_type == MDB_TYPE_STRING && (argv->a_un.a_str[0] == '-' || argv->a_un.a_str[0] == '+')) { if (argv->a_un.a_str[1] != 't') return (DCMD_USAGE); if (argv->a_un.a_str[0] == '-') add_tag++; else del_tag++; argc--; argv++; } if (!(flags & DCMD_ADDRSPEC)) addr = 0; /* set variables to zero unless explicit addr given */ for (; argc-- != 0; argv++) { if (argv->a_type != MDB_TYPE_STRING) continue; if (argv->a_un.a_str[0] == '-' || argv->a_un.a_str[0] == '+') { mdb_warn("ignored bad option -- %s\n", argv->a_un.a_str); continue; } if ((p = strbadid(argv->a_un.a_str)) != NULL) { mdb_warn("'%c' may not be used in a variable " "name\n", *p); return (DCMD_ERR); } if ((v = mdb_nv_lookup(&mdb.m_nv, argv->a_un.a_str)) == NULL) { v = mdb_nv_insert(&mdb.m_nv, argv->a_un.a_str, NULL, addr, 0); } else if (flags & DCMD_ADDRSPEC) mdb_nv_set_value(v, addr); if (v != NULL) { if (add_tag) v->v_flags |= MDB_NV_TAGGED; if (del_tag) v->v_flags &= ~MDB_NV_TAGGED; } } return (DCMD_OK); } #ifndef _KMDB /*ARGSUSED*/ static int cmd_context(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_tgt_setcontext(mdb.m_target, (void *)addr) == 0) return (DCMD_OK); return (DCMD_ERR); } #endif /*ARGSUSED*/ static int cmd_prompt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *p = ""; if (argc != 0) { if (argc > 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); p = argv->a_un.a_str; } (void) mdb_set_prompt(p); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_term(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_printf("%s\n", mdb.m_termtype); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_vtop(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { physaddr_t pa; mdb_tgt_as_t as = MDB_TGT_AS_VIRT; if (mdb_getopts(argc, argv, 'a', MDB_OPT_UINTPTR, (uintptr_t *)&as, NULL) != argc) return (DCMD_USAGE); if (mdb_tgt_vtop(mdb.m_target, as, addr, &pa) == -1) { mdb_warn("failed to get physical mapping"); return (DCMD_ERR); } if (flags & DCMD_PIPE_OUT) mdb_printf("%llr\n", pa); else mdb_printf("virtual %lr mapped to physical %llr\n", addr, pa); return (DCMD_OK); } #define EVENTS_OPT_A 0x1 /* ::events -a (show all events) */ #define EVENTS_OPT_V 0x2 /* ::events -v (verbose display) */ static const char * event_action(const mdb_tgt_spec_desc_t *sp) { if (!(sp->spec_flags & MDB_TGT_SPEC_HIDDEN) && sp->spec_data != NULL) return (sp->spec_data); return ("-"); } static void print_evsep(void) { static const char dash20[] = "--------------------"; mdb_printf("----- - -- -- -- %s%s --%s\n", dash20, dash20, dash20); } /*ARGSUSED*/ static int print_event(mdb_tgt_t *t, void *private, int vid, void *data) { uint_t opts = (uint_t)(uintptr_t)private; mdb_tgt_spec_desc_t sp; char s1[41], s2[22]; const char *s2str; int visible; (void) mdb_tgt_vespec_info(t, vid, &sp, s1, sizeof (s1)); visible = !(sp.spec_flags & (MDB_TGT_SPEC_HIDDEN|MDB_TGT_SPEC_DELETED)); if ((opts & EVENTS_OPT_A) || visible) { int encoding = (!(sp.spec_flags & MDB_TGT_SPEC_DISABLED)) | (!(sp.spec_flags & MDB_TGT_SPEC_MATCHED) << 1); char ldelim = "<<(["[encoding]; char rdelim = ">>)]"[encoding]; char state = "0-+*!"[sp.spec_state]; char tflag = "T "[!(sp.spec_flags & MDB_TGT_SPEC_STICKY)]; char aflag = "d "[!(sp.spec_flags & MDB_TGT_SPEC_AUTODIS)]; if (sp.spec_flags & MDB_TGT_SPEC_TEMPORARY) tflag = 't'; /* TEMP takes precedence over STICKY */ if (sp.spec_flags & MDB_TGT_SPEC_AUTODEL) aflag = 'D'; /* AUTODEL takes precedence over AUTODIS */ if (sp.spec_flags & MDB_TGT_SPEC_AUTOSTOP) aflag = 's'; /* AUTOSTOP takes precedence over both */ if (opts & EVENTS_OPT_V) { if (sp.spec_state == MDB_TGT_SPEC_IDLE || sp.spec_state == MDB_TGT_SPEC_ERROR) s2str = mdb_strerror(sp.spec_errno); else s2str = "-"; } else s2str = event_action(&sp); if (mdb_snprintf(s2, sizeof (s2), "%s", s2str) >= sizeof (s2)) (void) strabbr(s2, sizeof (s2)); if (vid > -10 && vid < 10) mdb_printf("%c%2d %c", ldelim, vid, rdelim); else mdb_printf("%c%3d%c", ldelim, vid, rdelim); mdb_printf(" %c %c%c %2u %2u %-40s %-21s\n", state, tflag, aflag, sp.spec_hits, sp.spec_limit, s1, s2); if (opts & EVENTS_OPT_V) { mdb_printf("%-17s%s\n", "", event_action(&sp)); print_evsep(); } } return (0); } /*ARGSUSED*/ static int cmd_events(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts = 0; if ((flags & DCMD_ADDRSPEC) || mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, EVENTS_OPT_A, &opts, 'v', MDB_OPT_SETBITS, EVENTS_OPT_V, &opts, NULL) != argc) return (DCMD_USAGE); if (opts & EVENTS_OPT_V) { mdb_printf(" ID S TA HT LM %-40s %-21s\n%-17s%s\n", "Description", "Status", "", "Action"); } else { mdb_printf(" ID S TA HT LM %-40s %-21s\n", "Description", "Action"); } print_evsep(); return (mdb_tgt_vespec_iter(mdb.m_target, print_event, (void *)(uintptr_t)opts)); } static int tgt_status(const mdb_tgt_status_t *tsp) { const char *format; char buf[BUFSIZ]; if (tsp->st_flags & MDB_TGT_BUSY) return (DCMD_OK); if (tsp->st_pc != 0) { if (mdb_dis_ins2str(mdb.m_disasm, mdb.m_target, MDB_TGT_AS_VIRT_I, buf, sizeof (buf), tsp->st_pc) != tsp->st_pc) format = "target stopped at:\n%-#16a%8T%s\n"; else format = "target stopped at %a:\n"; mdb_warn(format, tsp->st_pc, buf); } switch (tsp->st_state) { case MDB_TGT_IDLE: mdb_warn("target is idle\n"); break; case MDB_TGT_RUNNING: if (tsp->st_flags & MDB_TGT_DSTOP) mdb_warn("target is running, stop directive pending\n"); else mdb_warn("target is running\n"); break; case MDB_TGT_STOPPED: if (tsp->st_pc == 0) mdb_warn("target is stopped\n"); break; case MDB_TGT_UNDEAD: mdb_warn("target has terminated\n"); break; case MDB_TGT_DEAD: mdb_warn("target is a core dump\n"); break; case MDB_TGT_LOST: mdb_warn("target is no longer under debugger control\n"); break; } mdb_set_dot(tsp->st_pc); return (DCMD_OK); } /* * mdb continue/step commands take an optional signal argument, but the * corresponding kmdb versions don't. */ #ifdef _KMDB #define CONT_MAXARGS 0 /* no optional SIG argument */ #else #define CONT_MAXARGS 1 #endif /*ARGSUSED*/ static int cmd_cont_common(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, int (*t_cont)(mdb_tgt_t *, mdb_tgt_status_t *), const char *name) { mdb_tgt_t *t = mdb.m_target; mdb_tgt_status_t st; int sig = 0; if ((flags & DCMD_ADDRSPEC) || argc > CONT_MAXARGS) return (DCMD_USAGE); if (argc > 0) { if (argv->a_type == MDB_TYPE_STRING) { if (proc_str2sig(argv->a_un.a_str, &sig) == -1) { mdb_warn("invalid signal name -- %s\n", argv->a_un.a_str); return (DCMD_USAGE); } } else sig = (int)(intmax_t)argv->a_un.a_val; } (void) mdb_tgt_status(t, &st); if (st.st_state == MDB_TGT_IDLE && mdb_tgt_run(t, 0, NULL) == -1) { if (errno != EMDB_TGT) mdb_warn("failed to create new target"); return (DCMD_ERR); } if (sig != 0 && mdb_tgt_signal(t, sig) == -1) { mdb_warn("failed to post signal %d", sig); return (DCMD_ERR); } if (st.st_state == MDB_TGT_IDLE && t_cont == &mdb_tgt_step) { (void) mdb_tgt_status(t, &st); return (tgt_status(&st)); } if (t_cont(t, &st) == -1) { if (errno != EMDB_TGT) mdb_warn("failed to %s target", name); return (DCMD_ERR); } return (tgt_status(&st)); } static int cmd_step(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int (*func)(mdb_tgt_t *, mdb_tgt_status_t *) = &mdb_tgt_step; const char *name = "single-step"; if (argc > 0 && argv->a_type == MDB_TYPE_STRING) { if (strcmp(argv->a_un.a_str, "out") == 0) { func = &mdb_tgt_step_out; name = "step (out)"; argv++; argc--; } else if (strcmp(argv->a_un.a_str, "over") == 0) { func = &mdb_tgt_next; name = "step (over)"; argv++; argc--; } } return (cmd_cont_common(addr, flags, argc, argv, func, name)); } static int cmd_step_out(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (cmd_cont_common(addr, flags, argc, argv, &mdb_tgt_step_out, "step (out)")); } static int cmd_next(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (cmd_cont_common(addr, flags, argc, argv, &mdb_tgt_next, "step (over)")); } static int cmd_cont(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (cmd_cont_common(addr, flags, argc, argv, &mdb_tgt_continue, "continue")); } #ifndef _KMDB /*ARGSUSED*/ static int cmd_run(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); if (mdb_tgt_run(mdb.m_target, argc, argv) == -1) { if (errno != EMDB_TGT) mdb_warn("failed to create new target"); return (DCMD_ERR); } return (cmd_cont(0, 0, 0, NULL)); } #endif /* * To simplify the implementation of :d, :z, and ::delete, we use the sp * parameter to store the criteria for what to delete. If spec_base is set, * we delete vespecs with a matching address. If spec_id is set, we delete * vespecs with a matching id. Otherwise, we delete all vespecs. We bump * sp->spec_size so the caller can tell how many vespecs were deleted. */ static int ve_delete(mdb_tgt_t *t, mdb_tgt_spec_desc_t *sp, int vid, void *data) { mdb_tgt_spec_desc_t spec; int status = -1; if (vid < 0) return (0); /* skip over target implementation events */ if (sp->spec_base != 0) { (void) mdb_tgt_vespec_info(t, vid, &spec, NULL, 0); if (sp->spec_base - spec.spec_base < spec.spec_size) status = mdb_tgt_vespec_delete(t, vid); } else if (sp->spec_id == 0) { (void) mdb_tgt_vespec_info(t, vid, &spec, NULL, 0); if (!(spec.spec_flags & MDB_TGT_SPEC_STICKY)) status = mdb_tgt_vespec_delete(t, vid); } else if (sp->spec_id == vid) status = mdb_tgt_vespec_delete(t, vid); if (status == 0) { if (data != NULL) strfree(data); sp->spec_size++; } return (0); } static int ve_delete_spec(mdb_tgt_spec_desc_t *sp) { (void) mdb_tgt_vespec_iter(mdb.m_target, (mdb_tgt_vespec_f *)ve_delete, sp); if (sp->spec_size == 0) { if (sp->spec_id != 0 || sp->spec_base != 0) mdb_warn("no traced events matched description\n"); } return (DCMD_OK); } /*ARGSUSED*/ static int cmd_zapall(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_spec_desc_t spec; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); bzero(&spec, sizeof (spec)); return (ve_delete_spec(&spec)); } static int cmd_delete(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_spec_desc_t spec; if (((flags & DCMD_ADDRSPEC) && argc > 0) || argc > 1) return (DCMD_USAGE); bzero(&spec, sizeof (spec)); if (flags & DCMD_ADDRSPEC) spec.spec_base = addr; else if (argc == 0) spec.spec_base = mdb_get_dot(); else if (argv->a_type == MDB_TYPE_STRING && strcmp(argv->a_un.a_str, "all") != 0) spec.spec_id = (int)(intmax_t)mdb_strtonum(argv->a_un.a_str, 10); else if (argv->a_type == MDB_TYPE_IMMEDIATE) spec.spec_id = (int)(intmax_t)argv->a_un.a_val; return (ve_delete_spec(&spec)); } static int cmd_write(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_as_t as; int rdback = mdb.m_flags & MDB_FL_READBACK; mdb_tgt_addr_t naddr; size_t forced_size = 0; boolean_t opt_p, opt_o, opt_l; uint64_t val = 0; int i; opt_p = opt_o = opt_l = B_FALSE; i = mdb_getopts(argc, argv, 'p', MDB_OPT_SETBITS, B_TRUE, &opt_p, 'o', MDB_OPT_SETBITS, B_TRUE, &opt_o, 'l', MDB_OPT_UINTPTR_SET, &opt_l, (uintptr_t *)&forced_size, NULL); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (opt_p && opt_o) { mdb_warn("-o and -p are incompatible\n"); return (DCMD_USAGE); } argc -= i; argv += i; if (argc == 0) return (DCMD_USAGE); switch (argv[0].a_type) { case MDB_TYPE_STRING: val = mdb_strtoull(argv[0].a_un.a_str); break; case MDB_TYPE_IMMEDIATE: val = argv[0].a_un.a_val; break; default: return (DCMD_USAGE); } if (opt_p) as = MDB_TGT_AS_PHYS; else if (opt_o) as = MDB_TGT_AS_FILE; else as = MDB_TGT_AS_VIRT; if (opt_l) naddr = write_var_uint(as, addr, val, forced_size, rdback); else naddr = write_ctf_uint(as, addr, val, rdback); if (addr == naddr) { mdb_warn("failed to write %llr at address %#llx", val, addr); return (DCMD_ERR); } return (DCMD_OK); } void write_help(void) { mdb_printf( "-l length force a write with the specified length in bytes\n" "-o write data to the object file location specified\n" "-p write data to the physical address specified\n" "\n" "Attempts to write the given value to the address provided.\n" "If -l is not specified, the address must be the position of a\n" "symbol that is either of integer, pointer, or enum type. The\n" "type and the size of the symbol are inferred by the CTF found\n" "in the provided address. The length of the write is guaranteed\n" "to be the inferred size of the symbol.\n" "\n" "If no CTF data exists, or the address provided is not a symbol\n" "of integer or pointer type, then the write fails. At that point\n" "the user can force the write by using the '-l' option and\n" "specifying its length.\n" "\n" "Note that forced writes with a length that are bigger than\n" "the size of the biggest data pointer supported are not allowed." "\n"); } static void srcexec_file_help(void) { mdb_printf( "The library of macros delivered with previous versions of Solaris have been\n" "superseded by the dcmds and walkers provided by MDB. See ::help for\n" "commands that can be used to list the available dcmds and walkers.\n" "\n" "Aliases have been created for several of the more popular macros. To see\n" "the list of aliased macros, as well as their native MDB equivalents,\n" "type $M.\n"); #ifdef _KMDB mdb_printf( "When invoked, the $< and $<< dcmds will consult the macro alias list. If an\n" "alias cannot be found, an attempt will be made to locate a data type whose\n" "name corresponds to the requested macro. If such a type can be found, it\n" "will be displayed using the ::print dcmd.\n"); #else mdb_printf( "When invoked, the $< and $<< dcmds will first attempt to locate a macro with\n" "the indicated name. If no macro can be found, and if no alias exists for\n" "this macro, an attempt will be made to locate a data type whose name\n" "corresponds to the requested macro. If such a type can be found, it will be\n" "displayed using the ::print dcmd.\n"); #endif } static void events_help(void) { mdb_printf("Options:\n" "-a show all events, including internal debugger events\n" "-v show verbose display, including inactivity reason\n" "\nOutput Columns:\n" "ID decimal event specifier id number:\n" " [ ] event tracing is enabled\n" " ( ) event tracing is disabled\n" " < > target is currently stopped on this type of event\n\n" "S event specifier state:\n" " - event specifier is idle (not applicable yet)\n" " + event specifier is active\n" " * event specifier is armed (target program running)\n" " ! error occurred while attempting to arm event\n\n" "TA event specifier flags:\n" " t event specifier is temporary (delete at next stop)\n" " T event specifier is sticky (::delete all has no effect)\n" " d event specifier will be disabled when HT = LM\n" " D event specifier will be deleted when HT = LM\n" " s target will automatically stop when HT = LM\n\n" "HT hit count (number of times event has occurred)\n" "LM hit limit (limit for autostop, disable, delete)\n"); } static void dump_help(void) { mdb_printf( "-e adjust for endianness\n" " (assumes 4-byte words; use -g to change word size)\n" #ifdef _KMDB "-f no effect\n" #else "-f dump from object file\n" #endif "-g n display bytes in groups of n\n" " (default is 4; n must be a power of 2, divide line width)\n" "-l n display n bytes\n" " (default is 1; rounded up to multiple of line width)\n" "-p dump from physical memory\n" "-q don't print ASCII\n" "-r use relative numbering (automatically sets -u)\n" "-s elide repeated lines\n" "-t only read from and display contents of specified addresses\n" " (default is to read and print entire lines)\n" "-u un-align output\n" " (default is to align output at paragraph boundary)\n" "-w n display n 16-byte paragraphs per line\n" " (default is 1, maximum is 16)\n"); } static void bitx_help(void) { mdb_printf( "Extract bits from an integer or, with the optional third\n" "argument, set the value in the provided bit range and show\n" "the result.\n\n" "Bit positions are inclusive and specified with the high bit\n" "first.\n"); } static int cmd_bitx(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint64_t val = addr; uint8_t high, low; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (argc != 2 && argc != 3) return (DCMD_USAGE); high = (uint8_t)mdb_argtoull(&argv[0]); low = (uint8_t)mdb_argtoull(&argv[1]); if (high > 63 || low > 63) { mdb_warn("bit positions must be in the range [0,%r]\n", 63); return (DCMD_ERR); } if (high < low) { mdb_warn("high bit must not be less than the low bit\n"); return (DCMD_ERR); } uint64_t mask = (1ULL << (high - low + 1)) - 1ULL; if (argc == 3) { uint64_t nval = (uint64_t)mdb_argtoull(&argv[2]); if ((~mask & nval) != 0) { mdb_warn( "value (%lr) too large for bit range [%r:%r]\n", nval, high, low); return (DCMD_ERR); } val &= ~(mask << low); val |= nval << low; mdb_nv_set_value(mdb.m_dot, val); } else { val = ((val >> low) & mask); } mdb_printf("%lr\n", val); return (DCMD_OK); } /* "DESCRIPTION" section text for ::bcmp */ static void bcmp_help(void) { mdb_printf( "-n Number of bytes to compare\n" "\n" "Both addr2 and -n arguments are required.\n" "\n" "Output is silent if both memory areas are identical. If they\n" "are not identical, the offset of the first differing byte prints\n" "on stdout or a pipe and dot is set to (addr + the offset).\n"); } /* Actual implementation of ::bcmp */ static int cmd_bcmp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t other_addr = 0; uint64_t length = 0; uintptr_t high_addr, low_addr; if (argc < 1 || argc > 3 || !(flags & DCMD_ADDRSPEC) || argv[0].a_type != MDB_TYPE_STRING || argv[0].a_un.a_str[0] == '-') { return (DCMD_USAGE); } if (dis_str2addr(argv[0].a_un.a_str, &other_addr) == -1) return (DCMD_ERR); argv++; argc--; if (mdb_getopts(argc, argv, 'n', MDB_OPT_UINT64, &length, NULL) != argc) { return (DCMD_USAGE); } /* Cap the length to (2 ^ 31 - 1) bytes for now. */ if (length > INT_MAX || length == 0) { mdb_warn("Lengths not [1..0t%d]/[1..0x%x] are unsupported.\n", INT_MAX, INT_MAX); return (DCMD_ERR); } if (addr > other_addr) { high_addr = addr; low_addr = other_addr; } else { high_addr = other_addr; low_addr = addr; } if (high_addr - low_addr < length) { mdb_warn("Segments are overlapping for length %r.\n", length); return (DCMD_ERR); } /* Check for bounds at the higher address, which means wrapping? */ if (high_addr + length < high_addr) { mdb_warn("High-address segment would wrap around\n"); return (DCMD_ERR); } /* * Okay, we can finally start the comparison. Be deliberate, we can * make this faster later. * * For now, read them in 1KiB at a time. */ uintptr_t offset = 0; #define BCMP_CHUNK_SIZE 1024 uint8_t *chunk = mdb_alloc(BCMP_CHUNK_SIZE, UM_SLEEP | UM_GC); uint8_t *other_chunk = mdb_alloc(BCMP_CHUNK_SIZE, UM_SLEEP | UM_GC); while (offset < length) { size_t readsize = (length - offset > BCMP_CHUNK_SIZE) ? BCMP_CHUNK_SIZE : length - offset; size_t chunk_offset = 0; if (mdb_vread(chunk, readsize, addr + offset) == -1) { mdb_warn("Read failure 0x%p, length %d\n", addr + offset, readsize); return (DCMD_ERR); } if (mdb_vread(other_chunk, readsize, other_addr + offset) == -1) { mdb_warn("Read failure 0x%p, length %d\n", other_addr + offset, readsize); return (DCMD_ERR); } /* Like I said, we can make this faster later. */ while (chunk_offset < readsize) { if (chunk[chunk_offset] != other_chunk[chunk_offset]) break; chunk_offset++; } offset += chunk_offset; if (chunk_offset < readsize) { mdb_printf("%lr\n", offset); /* Set the dot so someone can utter "/B" immediately. */ mdb_set_dot(addr + offset); break; /* out of while loop, to return DCMD_OK... */ } } #undef BCMP_CHUNK_SIZE return (DCMD_OK); } /* * Table of built-in dcmds associated with the root 'mdb' module. Future * expansion of this program should be done here, or through the external * loadable module interface. */ const mdb_dcmd_t mdb_dcmd_builtins[] = { /* * dcmds common to both mdb and kmdb */ { ">", "variable-name", "assign variable", cmd_assign_variable }, { "/", "fmt-list", "format data from virtual as", cmd_print_core }, { "\\", "fmt-list", "format data from physical as", cmd_print_phys }, { "@", "fmt-list", "format data from physical as", cmd_print_phys }, { "=", "fmt-list", "format immediate value", cmd_print_value }, { "$<", "macro-name", "replace input with macro", cmd_exec_file, srcexec_file_help }, { "$<<", "macro-name", "source macro", cmd_src_file, srcexec_file_help}, { "$%", NULL, NULL, cmd_quit }, { "$?", NULL, "print status and registers", cmd_notsup }, { "$a", NULL, NULL, cmd_algol }, { "$b", "[-av]", "list traced software events", cmd_events, events_help }, { "$c", "?[cnt]", "print stack backtrace", cmd_notsup }, { "$C", "?[cnt]", "print stack backtrace", cmd_notsup }, { "$d", NULL, "get/set default output radix", cmd_radix }, { "$D", "?[mode,...]", NULL, cmd_dbmode }, { "$e", NULL, "print listing of global symbols", cmd_globals }, { "$f", NULL, "print listing of source files", cmd_files }, { "$m", "?[name]", "print address space mappings", cmd_mappings }, { "$M", NULL, "list macro aliases", cmd_macalias_list }, { "$P", "[prompt]", "set debugger prompt string", cmd_prompt }, { "$q", NULL, "quit debugger", cmd_quit }, { "$Q", NULL, "quit debugger", cmd_quit }, { "$r", NULL, "print general-purpose registers", cmd_notsup }, { "$s", NULL, "get/set symbol matching distance", cmd_symdist }, { "$v", NULL, "print non-zero variables", cmd_nzvars }, { "$V", "[mode]", "get/set disassembly mode", cmd_dismode }, { "$w", NULL, "get/set output page width", cmd_pgwidth }, { "$W", NULL, "re-open target in write mode", cmd_reopen }, { ":a", ":[cmd...]", "set read access watchpoint", cmd_oldwpr }, { ":b", ":[cmd...]", "breakpoint at the specified address", cmd_oldbp }, { ":d", "?[id|all]", "delete traced software events", cmd_delete }, { ":p", ":[cmd...]", "set execute access watchpoint", cmd_oldwpx }, { ":S", NULL, NULL, cmd_step }, { ":w", ":[cmd...]", "set write access watchpoint", cmd_oldwpw }, { ":z", NULL, "delete all traced software events", cmd_zapall }, { "array", ":[type count] [variable]", "print each array element's " "address", cmd_array }, { "bitx", ": [new value]", "extract bits from, or set bits in, a value", cmd_bitx, bitx_help }, { "bcmp", ": <-n count>", "compare two disjoint sections of memory", cmd_bcmp, bcmp_help}, { "bp", "?[+/-dDestT] [-c cmd] [-n count] sym ...", "breakpoint at the " "specified addresses or symbols", cmd_bp, bp_help }, { "dcmds", "[[-n] pattern]", "list available debugger commands", cmd_dcmds, cmd_dcmds_help }, { "delete", "?[id|all]", "delete traced software events", cmd_delete }, { "dis", "?[-abfpw] [-n cnt] [addr]", "disassemble near addr", cmd_dis, dis_help }, { "disasms", NULL, "list available disassemblers", cmd_disasms }, { "dismode", "[mode]", "get/set disassembly mode", cmd_dismode }, { "dmods", "[-l] [mod]", "list loaded debugger modules", cmd_dmods }, { "dump", "?[-eqrstu] [-f|-p] [-g bytes] [-l bytes] [-w paragraphs]", "dump memory from specified address", cmd_dump, dump_help }, { "echo", "args ...", "echo arguments", cmd_echo }, { "enum", "?[-ex] enum [name]", "print an enumeration", cmd_enum, enum_help }, { "eval", "command", "evaluate the specified command", cmd_eval }, { "events", "[-av]", "list traced software events", cmd_events, events_help }, { "evset", "?[+/-dDestT] [-c cmd] [-n count] id ...", "set software event specifier attributes", cmd_evset, evset_help }, { "files", "[object]", "print listing of source files", cmd_files }, #ifdef __sparc { "findsym", "?[-g] [symbol|addr ...]", "search for symbol references " "in all known functions", cmd_findsym, NULL }, #endif { "formats", NULL, "list format specifiers", cmd_formats }, { "grep", "?expr", "print dot if expression is true", cmd_grep }, { "head", "-num|-n num", "limit number of elements in pipe", cmd_head, head_help }, { "help", "[cmd]", "list commands/command help", cmd_help, NULL, cmd_help_tab }, { "linkerset", "[name]", "display linkersets", cmd_linkerset, linkerset_help, cmd_linkerset_tab }, { "list", "?type member [variable]", "walk list using member as link pointer", cmd_list, NULL, mdb_tab_complete_mt }, { "map", "?expr", "print dot after evaluating expression", cmd_map }, { "mappings", "?[name]", "print address space mappings", cmd_mappings }, { "nm", "?[-DPdghnopuvx] [-f format] [-t types] [object]", "print symbols", cmd_nm, nm_help }, { "nmadd", ":[-fo] [-e end] [-s size] name", "add name to private symbol table", cmd_nmadd, nmadd_help }, { "nmdel", "name", "remove name from private symbol table", cmd_nmdel }, { "obey", NULL, NULL, cmd_obey }, { "objects", "[-v]", "print load objects information", cmd_objects }, { "offsetof", "type member", "print the offset of a given struct " "or union member", cmd_offsetof, NULL, mdb_tab_complete_mt }, { "print", "?[-aCdhiLptx] [-c lim] [-l lim] [type] [member|offset ...]", "print the contents of a data structure", cmd_print, print_help, cmd_print_tab }, { "printf", "?format type member ...", "print and format the " "member(s) of a data structure", cmd_printf, printf_help, cmd_printf_tab }, { "regs", NULL, "print general purpose registers", cmd_notsup }, { "set", "[-wF] [+/-o opt] [-s dist] [-I path] [-L path] [-P prompt]", "get/set debugger properties", cmd_set }, { "showrev", "[-pv]", "print version information", cmd_showrev }, { "sizeof", "type", "print the size of a type", cmd_sizeof, NULL, cmd_sizeof_tab }, { "stack", "?[cnt]", "print stack backtrace", cmd_notsup }, { "stackregs", "?", "print stack backtrace and registers", cmd_notsup }, { "status", NULL, "print summary of current target", cmd_notsup }, { "term", NULL, "display current terminal type", cmd_term }, { "typeset", "[+/-t] var ...", "set variable attributes", cmd_typeset }, { "typedef", "[-c model | -d | -l | -r file | -w file ] [type] [name]", "create synthetic types", cmd_typedef, cmd_typedef_help }, { "typelist", NULL, "list known types", cmd_typelist }, { "unset", "[name ...]", "unset variables", cmd_unset }, { "vars", "[-npt]", "print listing of variables", cmd_vars }, { "version", NULL, "print debugger version string", cmd_version }, { "vtop", ":[-a as]", "print physical mapping of virtual address", cmd_vtop }, { "walk", "?name [variable]", "walk data structure", cmd_walk, NULL, cmd_walk_tab }, { "walkers", "[[-n] pattern]", "list available walkers", cmd_walkers, cmd_walkers_help }, { "whatis", ":[-aikqv]", "given an address, return information", cmd_whatis, whatis_help }, { "whence", "[-v] name ...", "show source of walk or dcmd", cmd_which }, { "which", "[-v] name ...", "show source of walk or dcmd", cmd_which }, { "write", "?[-op] [-l len] value", "write value to the provided memory location", cmd_write, write_help }, { "xdata", NULL, "print list of external data buffers", cmd_xdata }, #ifdef _KMDB /* * dcmds specific to kmdb, or which have kmdb-specific arguments */ { "?", "fmt-list", "format data from virtual as", cmd_print_core }, { ":c", NULL, "continue target execution", cmd_cont }, { ":e", NULL, "step target over next instruction", cmd_next }, { ":s", NULL, "single-step target to next instruction", cmd_step }, { ":u", NULL, "step target out of current function", cmd_step_out }, { "cont", NULL, "continue target execution", cmd_cont }, { "load", "[-sd] module", "load debugger module", cmd_load, load_help }, { "next", NULL, "step target over next instruction", cmd_next }, { "quit", "[-u]", "quit debugger", cmd_quit, quit_help }, { "step", "[ over | out ]", "single-step target to next instruction", cmd_step }, { "unload", "[-d] module", "unload debugger module", cmd_unload, unload_help }, { "wp", ":[+/-dDelstT] [-rwx] [-pi] [-c cmd] [-n count] [-L size]", "set a watchpoint at the specified address", cmd_wp, wp_help }, #else /* * dcmds specific to mdb, or which have mdb-specific arguments */ { "?", "fmt-list", "format data from object file", cmd_print_object }, { "$>", "[file]", "log session to a file", cmd_old_log }, { "$g", "?", "get/set demangling options", cmd_demflags }, { "$G", NULL, "enable/disable demangling support", cmd_demangle }, { "$i", NULL, "print signals that are ignored", cmd_notsup }, { "$l", NULL, "print the representative thread's lwp id", cmd_notsup }, { "$p", ":", "change debugger target context", cmd_context }, { "$x", NULL, "print floating point registers", cmd_notsup }, { "$X", NULL, "print floating point registers", cmd_notsup }, { "$y", NULL, "print floating point registers", cmd_notsup }, { "$Y", NULL, "print floating point registers", cmd_notsup }, { ":A", "?[core|pid]", "attach to process or core file", cmd_notsup }, { ":c", "[SIG]", "continue target execution", cmd_cont }, { ":e", "[SIG]", "step target over next instruction", cmd_next }, { ":i", ":", "ignore signal (delete all matching events)", cmd_notsup }, { ":k", NULL, "forcibly kill and release target", cmd_notsup }, { ":t", "?[+/-dDestT] [-c cmd] [-n count] SIG ...", "stop on delivery " "of the specified signals", cmd_sigbp, sigbp_help }, { ":r", "[ args ... ]", "run a new target process", cmd_run }, { ":R", NULL, "release the previously attached process", cmd_notsup }, { ":s", "[SIG]", "single-step target to next instruction", cmd_step }, { ":u", "[SIG]", "step target out of current function", cmd_step_out }, { "attach", "?[core|pid]", "attach to process or core file", cmd_notsup }, { "cat", "[file ...]", "concatenate and display files", cmd_cat }, { "cont", "[SIG]", "continue target execution", cmd_cont }, { "context", ":", "change debugger target context", cmd_context }, { "dem", "name ...", "demangle C++ symbol names", cmd_demstr }, { "fltbp", "?[+/-dDestT] [-c cmd] [-n count] fault ...", "stop on machine fault", cmd_fltbp, fltbp_help }, { "fpregs", NULL, "print floating point registers", cmd_notsup }, { "kill", NULL, "forcibly kill and release target", cmd_notsup }, { "load", "[-s] module", "load debugger module", cmd_load, load_help }, { "log", "[-d | [-e] file]", "log session to a file", cmd_log }, { "next", "[SIG]", "step target over next instruction", cmd_next }, { "quit", NULL, "quit debugger", cmd_quit }, { "release", NULL, "release the previously attached process", cmd_notsup }, { "run", "[ args ... ]", "run a new target process", cmd_run }, { "sigbp", "?[+/-dDestT] [-c cmd] [-n count] SIG ...", "stop on " "delivery of the specified signals", cmd_sigbp, sigbp_help }, { "step", "[ over | out ] [SIG]", "single-step target to next instruction", cmd_step }, { "sysbp", "?[+/-dDestT] [-io] [-c cmd] [-n count] syscall ...", "stop on entry or exit from system call", cmd_sysbp, sysbp_help }, { "unload", "module", "unload debugger module", cmd_unload }, { "wp", ":[+/-dDelstT] [-rwx] [-c cmd] [-n count] [-L size]", "set a watchpoint at the specified address", cmd_wp, wp_help }, #endif { NULL } }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include static const char _mdb_version[] = "mdb 1.1"; const char * mdb_conf_version(void) { return (_mdb_version); } const char * mdb_conf_platform(void) { static char platbuf[MAXNAMELEN]; if (sysinfo(SI_PLATFORM, platbuf, MAXNAMELEN) != -1) return (platbuf); return ("unknown"); } const char * mdb_conf_isa(void) { #if defined(__sparc) #if defined(__sparcv9) return ("sparcv9"); #else /* __sparcv9 */ return ("sparc"); #endif /* __sparcv9 */ #elif defined(__amd64) return ("amd64"); #elif defined(__i386) return ("i386"); #else #error "unknown ISA" #endif } void mdb_conf_uname(struct utsname *utsp) { bzero(utsp, sizeof (struct utsname)); (void) uname(utsp); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1997-1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_CONF_H #define _MDB_CONF_H #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern const char *mdb_conf_version(void); extern const char *mdb_conf_platform(void); extern const char *mdb_conf_isa(void); extern void mdb_conf_uname(struct utsname *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_CONF_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Debugger co-routine context support: In order to implement the context- * switching necessary for MDB pipes, we need the ability to establish a * co-routine context that has a separate stack. We use this stack to execute * the MDB parser, and then switch back and forth between this code and the * dcmd which is producing output to be consumed. We implement a context by * mapping a few pages of anonymous memory, and then using setcontext(2) to * switch to this stack and begin execution of a new function. */ #include #include #include #include #include #include #include #include #include #include #include static void context_init(mdb_context_t *volatile c) { c->ctx_status = c->ctx_func(); ASSERT(c->ctx_resumes > 0); longjmp(c->ctx_pcb, 1); } mdb_context_t * mdb_context_create(int (*func)(void)) { mdb_context_t *c = mdb_zalloc(sizeof (mdb_context_t), UM_NOSLEEP); size_t pagesize = sysconf(_SC_PAGESIZE); int prot = sysconf(_SC_STACK_PROT); static int zfd = -1; if (c == NULL) return (NULL); if (prot == -1) prot = PROT_READ | PROT_WRITE | PROT_EXEC; c->ctx_func = func; c->ctx_stacksize = pagesize * 4; c->ctx_stack = mmap(NULL, c->ctx_stacksize, prot, MAP_PRIVATE | MAP_ANON, -1, 0); /* * If the mmap failed with EBADFD, this kernel doesn't have MAP_ANON * support; fall back to opening /dev/zero, caching the fd, and using * that to mmap chunks of anonymous memory. */ if (c->ctx_stack == MAP_FAILED && errno == EBADF) { if (zfd == -1 && (zfd = open("/dev/zero", O_RDWR)) >= 0) (void) fcntl(zfd, F_SETFD, FD_CLOEXEC); if (zfd >= 0) { c->ctx_stack = mmap(NULL, c->ctx_stacksize, prot, MAP_PRIVATE, zfd, 0); } } c->ctx_uc.uc_flags = UC_ALL; if (c->ctx_stack == MAP_FAILED || getcontext(&c->ctx_uc) != 0) { mdb_free(c, sizeof (mdb_context_t)); return (NULL); } c->ctx_uc.uc_stack.ss_sp = c->ctx_stack; c->ctx_uc.uc_stack.ss_size = c->ctx_stacksize; c->ctx_uc.uc_stack.ss_flags = 0; c->ctx_uc.uc_link = NULL; makecontext(&c->ctx_uc, context_init, 1, c); return (c); } void mdb_context_destroy(mdb_context_t *c) { if (munmap(c->ctx_stack, c->ctx_stacksize) == -1) fail("failed to unmap stack %p", c->ctx_stack); mdb_free(c, sizeof (mdb_context_t)); } void mdb_context_switch(mdb_context_t *c) { if (setjmp(c->ctx_pcb) == 0 && setcontext(&c->ctx_uc) == -1) fail("failed to change context to %p", (void *)c); else fail("unexpectedly returned from context %p", (void *)c); } jmp_buf * mdb_context_getpcb(mdb_context_t *c) { c->ctx_resumes++; return (&c->ctx_pcb); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_CONTEXT_H #define _MDB_CONTEXT_H #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB /* * We hide the details of the context from the rest of MDB using the opaque * mdb_context tag. This will facilitate later porting activities. */ typedef struct mdb_context mdb_context_t; extern mdb_context_t *mdb_context_create(int (*)(void)); extern void mdb_context_destroy(mdb_context_t *); extern void mdb_context_switch(mdb_context_t *); extern jmp_buf *mdb_context_getpcb(mdb_context_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_CONTEXT_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_CONTEXT_IMPL_H #define _MDB_CONTEXT_IMPL_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif struct mdb_context { int (*ctx_func)(void); /* pointer to start function */ int ctx_status; /* return status of ctx_func */ int ctx_resumes; /* count of context resume calls */ size_t ctx_stacksize; /* size of stack in bytes */ void *ctx_stack; /* stack base address */ ucontext_t ctx_uc; /* user context structure */ jmp_buf ctx_pcb; /* control block for resume */ }; #ifdef __cplusplus } #endif #endif /* _MDB_CONTEXT_IMPL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2018 Joyent, Inc. */ #include #include #include #include #include #include void mdb_create_builtin_tgts(void) { mdb_module_t *mp; if ((mp = mdb_module_load_builtin("mdb_kvm")) != NULL) mp->mod_tgt_ctor = mdb_kvm_tgt_create; if ((mp = mdb_module_load_builtin("mdb_proc")) != NULL) mp->mod_tgt_ctor = mdb_proc_tgt_create; if ((mp = mdb_module_load_builtin("mdb_kproc")) != NULL) mp->mod_tgt_ctor = mdb_kproc_tgt_create; if ((mp = mdb_module_load_builtin("mdb_raw")) != NULL) mp->mod_tgt_ctor = mdb_rawfile_tgt_create; #ifdef __amd64 if ((mp = mdb_module_load_builtin("mdb_bhyve")) != NULL) mp->mod_tgt_ctor = mdb_bhyve_tgt_create; #endif } void mdb_create_loadable_disasms(void) { DIR *dir; struct dirent *dp; char buf[PATH_MAX], *p, *q; size_t len; #ifdef _LP64 len = mdb_snprintf(buf, sizeof (buf), "%s/usr/lib/mdb/disasm/%s", mdb.m_root, mdb_conf_isa()); #else len = mdb_snprintf(buf, sizeof (buf), "%s/usr/lib/mdb/disasm", mdb.m_root); #endif p = &buf[len]; if ((dir = opendir(buf)) == NULL) return; while ((dp = readdir(dir)) != NULL) { if (dp->d_name[0] == '.') continue; /* skip "." and ".." */ if ((q = strrchr(dp->d_name, '.')) == NULL || strcmp(q, ".so") != 0) continue; (void) mdb_snprintf(p, sizeof (buf) - len, "/%s", dp->d_name); (void) mdb_module_load(buf, MDB_MOD_SILENT); } (void) closedir(dir); } /* * 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) 2013, 2016 by Delphix. All rights reserved. * Copyright (c) 2018, Joyent, Inc. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include typedef struct tnarg { mdb_tgt_t *tn_tgt; /* target to use for lookup */ const char *tn_name; /* query string to lookup */ ctf_file_t *tn_fp; /* CTF container from match */ ctf_id_t tn_id; /* CTF type ID from match */ } tnarg_t; typedef struct type_iter { mdb_ctf_type_f *ti_cb; void *ti_arg; ctf_file_t *ti_fp; } type_iter_t; typedef struct member_iter { mdb_ctf_member_f *mi_cb; void *mi_arg; ctf_file_t *mi_fp; uint32_t mi_flags; ulong_t mi_off; } member_iter_t; typedef struct type_visit { mdb_ctf_visit_f *tv_cb; void *tv_arg; ctf_file_t *tv_fp; ulong_t tv_base_offset; /* used when recursing from type_cb() */ int tv_base_depth; /* used when recursing from type_cb() */ int tv_min_depth; } type_visit_t; typedef struct mbr_info { const char *mbr_member; ulong_t *mbr_offp; mdb_ctf_id_t *mbr_typep; } mbr_info_t; typedef struct synth_intrinsic { const char *syn_name; ctf_encoding_t syn_enc; uint_t syn_kind; } synth_intrinsic_t; typedef struct synth_typedef { const char *syt_src; const char *syt_targ; } synth_typedef_t; /* * As part of our support for synthetic types via ::typedef, we define a core * set of types. */ static const synth_intrinsic_t synth_builtins32[] = { { "void", { CTF_INT_SIGNED, 0, 0 }, CTF_K_INTEGER }, { "signed", { CTF_INT_SIGNED, 0, 32 }, CTF_K_INTEGER }, { "unsigned", { 0, 0, 32 }, CTF_K_INTEGER }, { "char", { CTF_INT_SIGNED | CTF_INT_CHAR, 0, 8 }, CTF_K_INTEGER }, { "short", { CTF_INT_SIGNED, 0, 16 }, CTF_K_INTEGER }, { "int", { CTF_INT_SIGNED, 0, 32 }, CTF_K_INTEGER }, { "long", { CTF_INT_SIGNED, 0, 32 }, CTF_K_INTEGER }, { "long long", { CTF_INT_SIGNED, 0, 64 }, CTF_K_INTEGER }, { "signed char", { CTF_INT_SIGNED | CTF_INT_CHAR, 0, 8 }, CTF_K_INTEGER }, { "signed short", { CTF_INT_SIGNED, 0, 16 }, CTF_K_INTEGER }, { "signed int", { CTF_INT_SIGNED, 0, 32 }, CTF_K_INTEGER }, { "signed long", { CTF_INT_SIGNED, 0, 32 }, CTF_K_INTEGER }, { "signed long long", { CTF_INT_SIGNED, 0, 64 }, CTF_K_INTEGER }, { "unsigned char", { CTF_INT_CHAR, 0, 8 }, CTF_K_INTEGER }, { "unsigned short", { 0, 0, 16 }, CTF_K_INTEGER }, { "unsigned int", { 0, 0, 32 }, CTF_K_INTEGER }, { "unsigned long", { 0, 0, 32 }, CTF_K_INTEGER }, { "unsigned long long", { 0, 0, 64 }, CTF_K_INTEGER }, { "_Bool", { CTF_INT_BOOL, 0, 8 }, CTF_K_INTEGER }, { "float", { CTF_FP_SINGLE, 0, 32 }, CTF_K_FLOAT }, { "double", { CTF_FP_DOUBLE, 0, 64 }, CTF_K_FLOAT }, { "long double", { CTF_FP_LDOUBLE, 0, 128 }, CTF_K_FLOAT }, { "float imaginary", { CTF_FP_IMAGRY, 0, 32 }, CTF_K_FLOAT }, { "double imaginary", { CTF_FP_DIMAGRY, 0, 64 }, CTF_K_FLOAT }, { "long double imaginary", { CTF_FP_LDIMAGRY, 0, 128 }, CTF_K_FLOAT }, { "float complex", { CTF_FP_CPLX, 0, 64 }, CTF_K_FLOAT }, { "double complex", { CTF_FP_DCPLX, 0, 128 }, CTF_K_FLOAT }, { "long double complex", { CTF_FP_LDCPLX, 0, 256 }, CTF_K_FLOAT }, { NULL, { 0, 0, 0}, 0 } }; static const synth_intrinsic_t synth_builtins64[] = { { "void", { CTF_INT_SIGNED, 0, 0 }, CTF_K_INTEGER }, { "signed", { CTF_INT_SIGNED, 0, 32 }, CTF_K_INTEGER }, { "unsigned", { 0, 0, 32 }, CTF_K_INTEGER }, { "char", { CTF_INT_SIGNED | CTF_INT_CHAR, 0, 8 }, CTF_K_INTEGER }, { "short", { CTF_INT_SIGNED, 0, 16 }, CTF_K_INTEGER }, { "int", { CTF_INT_SIGNED, 0, 32 }, CTF_K_INTEGER }, { "long", { CTF_INT_SIGNED, 0, 64 }, CTF_K_INTEGER }, { "long long", { CTF_INT_SIGNED, 0, 64 }, CTF_K_INTEGER }, { "signed char", { CTF_INT_SIGNED | CTF_INT_CHAR, 0, 8 }, CTF_K_INTEGER }, { "signed short", { CTF_INT_SIGNED, 0, 16 }, CTF_K_INTEGER }, { "signed int", { CTF_INT_SIGNED, 0, 32 }, CTF_K_INTEGER }, { "signed long", { CTF_INT_SIGNED, 0, 64 }, CTF_K_INTEGER }, { "signed long long", { CTF_INT_SIGNED, 0, 64 }, CTF_K_INTEGER }, { "unsigned char", { CTF_INT_CHAR, 0, 8 }, CTF_K_INTEGER }, { "unsigned short", { 0, 0, 16 }, CTF_K_INTEGER }, { "unsigned int", { 0, 0, 32 }, CTF_K_INTEGER }, { "unsigned long", { 0, 0, 64 }, CTF_K_INTEGER }, { "unsigned long long", { 0, 0, 64 }, CTF_K_INTEGER }, { "_Bool", { CTF_INT_BOOL, 0, 8 }, CTF_K_INTEGER }, { "float", { CTF_FP_SINGLE, 0, 32 }, CTF_K_FLOAT }, { "double", { CTF_FP_DOUBLE, 0, 64 }, CTF_K_FLOAT }, { "long double", { CTF_FP_LDOUBLE, 0, 128 }, CTF_K_FLOAT }, { "float imaginary", { CTF_FP_IMAGRY, 0, 32 }, CTF_K_FLOAT }, { "double imaginary", { CTF_FP_DIMAGRY, 0, 64 }, CTF_K_FLOAT }, { "long double imaginary", { CTF_FP_LDIMAGRY, 0, 128 }, CTF_K_FLOAT }, { "float complex", { CTF_FP_CPLX, 0, 64 }, CTF_K_FLOAT }, { "double complex", { CTF_FP_DCPLX, 0, 128 }, CTF_K_FLOAT }, { "long double complex", { CTF_FP_LDCPLX, 0, 256 }, CTF_K_FLOAT }, { NULL, { 0, 0, 0 }, 0 } }; static const synth_typedef_t synth_typedefs32[] = { { "char", "int8_t" }, { "short", "int16_t" }, { "int", "int32_t" }, { "long long", "int64_t" }, { "int", "intptr_t" }, { "unsigned char", "uint8_t" }, { "unsigned short", "uint16_t" }, { "unsigned", "uint32_t" }, { "unsigned long long", "uint64_t" }, { "unsigned char", "uchar_t" }, { "unsigned short", "ushort_t" }, { "unsigned", "uint_t" }, { "unsigned long", "ulong_t" }, { "unsigned long long", "u_longlong_t" }, { "int", "ptrdiff_t" }, { "unsigned", "uintptr_t" }, { NULL, NULL } }; static const synth_typedef_t synth_typedefs64[] = { { "char", "int8_t" }, { "short", "int16_t" }, { "int", "int32_t" }, { "long", "int64_t" }, { "long", "intptr_t" }, { "unsigned char", "uint8_t" }, { "unsigned short", "uint16_t" }, { "unsigned", "uint32_t" }, { "unsigned long", "uint64_t" }, { "unsigned char", "uchar_t" }, { "unsigned short", "ushort_t" }, { "unsigned", "uint_t" }, { "unsigned long", "ulong_t" }, { "unsigned long long", "u_longlong_t" }, { "long", "ptrdiff_t" }, { "unsigned long", "uintptr_t" }, { NULL, NULL } }; static void set_ctf_id(mdb_ctf_id_t *p, ctf_file_t *fp, ctf_id_t id) { mdb_ctf_impl_t *mcip = (mdb_ctf_impl_t *)p; mcip->mci_fp = fp; mcip->mci_id = id; } /* * Callback function for mdb_tgt_object_iter used from name_to_type, below, * to search the CTF namespace of each object file for a particular name. */ /*ARGSUSED*/ static int obj_lookup(void *data, const mdb_map_t *mp, const char *name) { tnarg_t *tnp = data; ctf_file_t *fp; ctf_id_t id; if ((fp = mdb_tgt_name_to_ctf(tnp->tn_tgt, name)) != NULL && (id = ctf_lookup_by_name(fp, tnp->tn_name)) != CTF_ERR) { tnp->tn_fp = fp; tnp->tn_id = id; /* * We may have found a forward declaration. If we did, we'll * note the ID and file pointer, but we'll keep searching in * an attempt to find the real thing. If we found something * real (i.e. not a forward), we stop the iteration. */ return (ctf_type_kind(fp, id) == CTF_K_FORWARD ? 0 : -1); } return (0); } /* * Convert a string type name with an optional leading object specifier into * the corresponding CTF file container and type ID. If an error occurs, we * print an appropriate message and return NULL. */ static ctf_file_t * name_to_type(mdb_tgt_t *t, const char *cname, ctf_id_t *idp) { const char *object = MDB_TGT_OBJ_EXEC; ctf_file_t *fp = NULL; ctf_id_t id = CTF_ERR; tnarg_t arg; char *p, *s; char buf[MDB_SYM_NAMLEN]; char *name = &buf[0]; (void) mdb_snprintf(buf, sizeof (buf), "%s", cname); if ((p = strrsplit(name, '`')) != NULL) { /* * We need to shuffle things around a little to support * type names of the form "struct module`name". */ if ((s = strsplit(name, ' ')) != NULL) { bcopy(cname + (s - name), name, (p - s) - 1); name[(p - s) - 1] = '\0'; bcopy(cname, name + (p - s), s - name); p = name + (p - s); } if (*name != '\0') object = name; name = p; } /* * Attempt to look up the name in the primary object file. If this * fails and the name was unscoped, search all remaining object files. * Finally, search the synthetic types. */ if (((fp = mdb_tgt_name_to_ctf(t, object)) == NULL || (id = ctf_lookup_by_name(fp, name)) == CTF_ERR || ctf_type_kind(fp, id) == CTF_K_FORWARD) && object == MDB_TGT_OBJ_EXEC) { arg.tn_tgt = t; arg.tn_name = name; arg.tn_fp = NULL; arg.tn_id = CTF_ERR; (void) mdb_tgt_object_iter(t, obj_lookup, &arg); if (arg.tn_id != CTF_ERR) { fp = arg.tn_fp; id = arg.tn_id; } else if (mdb.m_synth != NULL) { if ((id = ctf_lookup_by_name(mdb.m_synth, name)) != CTF_ERR) fp = mdb.m_synth; } } if (fp == NULL) return (NULL); /* errno is set for us */ if (id == CTF_ERR) { (void) set_errno(ctf_to_errno(ctf_errno(fp))); return (NULL); } *idp = id; return (fp); } /* * Check to see if there is ctf data in the given object. This is useful * so that we don't enter some loop where every call to lookup fails. */ int mdb_ctf_enabled_by_object(const char *object) { mdb_tgt_t *t = mdb.m_target; return (mdb_tgt_name_to_ctf(t, object) != NULL); } int mdb_ctf_lookup_by_name(const char *name, mdb_ctf_id_t *p) { ctf_file_t *fp = NULL; mdb_ctf_impl_t *mcip = (mdb_ctf_impl_t *)p; mdb_tgt_t *t = mdb.m_target; if (mcip == NULL) return (set_errno(EINVAL)); if ((fp = name_to_type(t, name, &mcip->mci_id)) == NULL) { mdb_ctf_type_invalidate(p); return (-1); /* errno is set for us */ } mcip->mci_fp = fp; return (0); } int mdb_ctf_lookup_by_symbol(const GElf_Sym *symp, const mdb_syminfo_t *sip, mdb_ctf_id_t *p) { ctf_file_t *fp = NULL; mdb_ctf_impl_t *mcip = (mdb_ctf_impl_t *)p; mdb_tgt_t *t = mdb.m_target; if (mcip == NULL) return (set_errno(EINVAL)); if (symp == NULL || sip == NULL) { mdb_ctf_type_invalidate(p); return (set_errno(EINVAL)); } if ((fp = mdb_tgt_addr_to_ctf(t, symp->st_value)) == NULL) { mdb_ctf_type_invalidate(p); return (-1); /* errno is set for us */ } if ((mcip->mci_id = ctf_lookup_by_symbol(fp, sip->sym_id)) == CTF_ERR) { mdb_ctf_type_invalidate(p); return (set_errno(ctf_to_errno(ctf_errno(fp)))); } mcip->mci_fp = fp; return (0); } int mdb_ctf_lookup_by_addr(uintptr_t addr, mdb_ctf_id_t *p) { GElf_Sym sym; mdb_syminfo_t si; char name[MDB_SYM_NAMLEN]; const mdb_map_t *mp; mdb_tgt_t *t = mdb.m_target; const char *obj, *c; if (p == NULL) return (set_errno(EINVAL)); if (mdb_tgt_lookup_by_addr(t, addr, MDB_TGT_SYM_EXACT, name, sizeof (name), NULL, NULL) == -1) { mdb_ctf_type_invalidate(p); return (-1); /* errno is set for us */ } if ((c = strrsplit(name, '`')) != NULL) { obj = name; } else { if ((mp = mdb_tgt_addr_to_map(t, addr)) == NULL) { mdb_ctf_type_invalidate(p); return (-1); /* errno is set for us */ } obj = mp->map_name; c = name; } if (mdb_tgt_lookup_by_name(t, obj, c, &sym, &si) == -1) { mdb_ctf_type_invalidate(p); return (-1); /* errno is set for us */ } return (mdb_ctf_lookup_by_symbol(&sym, &si, p)); } int mdb_ctf_module_lookup(const char *name, mdb_ctf_id_t *p) { ctf_file_t *fp; ctf_id_t id; mdb_module_t *mod; if ((mod = mdb_get_module()) == NULL) return (set_errno(EMDB_CTX)); if ((fp = mod->mod_ctfp) == NULL) return (set_errno(EMDB_NOCTF)); if ((id = ctf_lookup_by_name(fp, name)) == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(fp)))); set_ctf_id(p, fp, id); return (0); } /*ARGSUSED*/ int mdb_ctf_func_info(const GElf_Sym *symp, const mdb_syminfo_t *sip, mdb_ctf_funcinfo_t *mfp) { ctf_file_t *fp = NULL; ctf_funcinfo_t f; mdb_tgt_t *t = mdb.m_target; char name[MDB_SYM_NAMLEN]; const mdb_map_t *mp; mdb_syminfo_t si; int err; if (symp == NULL || mfp == NULL) return (set_errno(EINVAL)); /* * In case the input symbol came from a merged or private symbol table, * re-lookup the address as a symbol, and then perform a fully scoped * lookup of that symbol name to get the mdb_syminfo_t for its CTF. */ if ((fp = mdb_tgt_addr_to_ctf(t, symp->st_value)) == NULL || (mp = mdb_tgt_addr_to_map(t, symp->st_value)) == NULL || mdb_tgt_lookup_by_addr(t, symp->st_value, MDB_TGT_SYM_FUZZY, name, sizeof (name), NULL, NULL) != 0) return (-1); /* errno is set for us */ if (strchr(name, '`') != NULL) err = mdb_tgt_lookup_by_scope(t, name, NULL, &si); else err = mdb_tgt_lookup_by_name(t, mp->map_name, name, NULL, &si); if (err != 0) return (-1); /* errno is set for us */ if (ctf_func_info(fp, si.sym_id, &f) == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(fp)))); set_ctf_id(&mfp->mtf_return, fp, f.ctc_return); mfp->mtf_argc = f.ctc_argc; mfp->mtf_flags = f.ctc_flags; mfp->mtf_symidx = si.sym_id; return (0); } int mdb_ctf_func_args(const mdb_ctf_funcinfo_t *funcp, uint_t len, mdb_ctf_id_t *argv) { ctf_file_t *fp; ctf_id_t cargv[32]; int i; if (len > (sizeof (cargv) / sizeof (cargv[0]))) return (set_errno(EINVAL)); if (funcp == NULL || argv == NULL) return (set_errno(EINVAL)); fp = mdb_ctf_type_file(funcp->mtf_return); if (ctf_func_args(fp, funcp->mtf_symidx, len, cargv) == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(fp)))); for (i = MIN(len, funcp->mtf_argc) - 1; i >= 0; i--) { set_ctf_id(&argv[i], fp, cargv[i]); } return (0); } void mdb_ctf_type_invalidate(mdb_ctf_id_t *idp) { set_ctf_id(idp, NULL, CTF_ERR); } int mdb_ctf_type_valid(mdb_ctf_id_t id) { return (((mdb_ctf_impl_t *)&id)->mci_id != CTF_ERR); } int mdb_ctf_type_cmp(mdb_ctf_id_t aid, mdb_ctf_id_t bid) { mdb_ctf_impl_t *aidp = (mdb_ctf_impl_t *)&aid; mdb_ctf_impl_t *bidp = (mdb_ctf_impl_t *)&bid; return (ctf_type_cmp(aidp->mci_fp, aidp->mci_id, bidp->mci_fp, bidp->mci_id)); } int mdb_ctf_type_resolve(mdb_ctf_id_t mid, mdb_ctf_id_t *outp) { ctf_id_t id; mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)∣ if ((id = ctf_type_resolve(idp->mci_fp, idp->mci_id)) == CTF_ERR) { if (outp) mdb_ctf_type_invalidate(outp); return (set_errno(ctf_to_errno(ctf_errno(idp->mci_fp)))); } if (ctf_type_kind(idp->mci_fp, id) == CTF_K_FORWARD) { char name[MDB_SYM_NAMLEN]; mdb_ctf_id_t lookup_id; if (ctf_type_name(idp->mci_fp, id, name, sizeof (name)) != NULL && mdb_ctf_lookup_by_name(name, &lookup_id) == 0 && outp != NULL) { *outp = lookup_id; return (0); } } if (outp != NULL) set_ctf_id(outp, idp->mci_fp, id); return (0); } char * mdb_ctf_type_name(mdb_ctf_id_t id, char *buf, size_t len) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; char *ret; if (!mdb_ctf_type_valid(id)) { (void) set_errno(EINVAL); return (NULL); } ret = ctf_type_name(idp->mci_fp, idp->mci_id, buf, len); if (ret == NULL) (void) set_errno(ctf_to_errno(ctf_errno(idp->mci_fp))); return (ret); } ssize_t mdb_ctf_type_lname(mdb_ctf_id_t id, char *buf, size_t len) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; char *ret; if (!mdb_ctf_type_valid(id)) return (set_errno(EINVAL)); return (ctf_type_lname(idp->mci_fp, idp->mci_id, buf, len)); } ssize_t mdb_ctf_type_size(mdb_ctf_id_t id) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; ssize_t ret; /* resolve the type in case there's a forward declaration */ if ((ret = mdb_ctf_type_resolve(id, &id)) != 0) return (ret); if ((ret = ctf_type_size(idp->mci_fp, idp->mci_id)) == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(idp->mci_fp)))); return (ret); } int mdb_ctf_type_kind(mdb_ctf_id_t id) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; int ret; if ((ret = ctf_type_kind(idp->mci_fp, idp->mci_id)) == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(idp->mci_fp)))); return (ret); } int mdb_ctf_type_reference(mdb_ctf_id_t mid, mdb_ctf_id_t *outp) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)∣ ctf_id_t id; if ((id = ctf_type_reference(idp->mci_fp, idp->mci_id)) == CTF_ERR) { if (outp) mdb_ctf_type_invalidate(outp); return (set_errno(ctf_to_errno(ctf_errno(idp->mci_fp)))); } if (outp != NULL) set_ctf_id(outp, idp->mci_fp, id); return (0); } int mdb_ctf_type_encoding(mdb_ctf_id_t id, ctf_encoding_t *ep) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; if (ctf_type_encoding(idp->mci_fp, idp->mci_id, ep) == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(idp->mci_fp)))); return (0); } /* * callback proxy for mdb_ctf_type_visit */ static int type_cb(const char *name, ctf_id_t type, ulong_t off, int depth, void *arg) { type_visit_t *tvp = arg; mdb_ctf_id_t id; mdb_ctf_id_t base; mdb_ctf_impl_t *basep = (mdb_ctf_impl_t *)&base; int ret; if (depth < tvp->tv_min_depth) return (0); off += tvp->tv_base_offset; depth += tvp->tv_base_depth; set_ctf_id(&id, tvp->tv_fp, type); (void) mdb_ctf_type_resolve(id, &base); if ((ret = tvp->tv_cb(name, id, base, off, depth, tvp->tv_arg)) != 0) return (ret); /* * If the type resolves to a type in a different file, we must have * followed a forward declaration. We need to recurse into the * new type. */ if (basep->mci_fp != tvp->tv_fp && mdb_ctf_type_valid(base)) { type_visit_t tv; tv.tv_cb = tvp->tv_cb; tv.tv_arg = tvp->tv_arg; tv.tv_fp = basep->mci_fp; tv.tv_base_offset = off; tv.tv_base_depth = depth; tv.tv_min_depth = 1; /* depth = 0 has already been done */ ret = ctf_type_visit(basep->mci_fp, basep->mci_id, type_cb, &tv); } return (ret); } int mdb_ctf_type_visit(mdb_ctf_id_t id, mdb_ctf_visit_f *func, void *arg) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; type_visit_t tv; int ret; tv.tv_cb = func; tv.tv_arg = arg; tv.tv_fp = idp->mci_fp; tv.tv_base_offset = 0; tv.tv_base_depth = 0; tv.tv_min_depth = 0; ret = ctf_type_visit(idp->mci_fp, idp->mci_id, type_cb, &tv); if (ret == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(idp->mci_fp)))); return (ret); } int mdb_ctf_array_info(mdb_ctf_id_t id, mdb_ctf_arinfo_t *arp) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; ctf_arinfo_t car; if (ctf_array_info(idp->mci_fp, idp->mci_id, &car) == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(idp->mci_fp)))); set_ctf_id(&arp->mta_contents, idp->mci_fp, car.ctr_contents); set_ctf_id(&arp->mta_index, idp->mci_fp, car.ctr_index); arp->mta_nelems = car.ctr_nelems; return (0); } const char * mdb_ctf_enum_name(mdb_ctf_id_t id, int value) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; const char *ret; /* resolve the type in case there's a forward declaration */ if (mdb_ctf_type_resolve(id, &id) != 0) return (NULL); if ((ret = ctf_enum_name(idp->mci_fp, idp->mci_id, value)) == NULL) (void) set_errno(ctf_to_errno(ctf_errno(idp->mci_fp))); return (ret); } /* * callback proxy for mdb_ctf_member_iter */ static int member_iter_cb(const char *name, ctf_id_t type, ulong_t off, void *data) { int ret, kind; member_iter_t *mip = data; mdb_ctf_id_t id; set_ctf_id(&id, mip->mi_fp, type); ret = mip->mi_cb(name, id, mip->mi_off + off, mip->mi_arg); if (ret != 0) { return (ret); } /* * If we have an anonymous struct or union and we've been asked to * process it, recurse through it. We need to modify the offset that * we're at as we progress through this so that way the caller sees this * at the correct memory location. */ kind = ctf_type_kind(mip->mi_fp, type); if ((kind == CTF_K_STRUCT || kind == CTF_K_UNION) && *name == '\0' && (mip->mi_flags & MDB_CTF_F_ITER_ANON) != 0) { mip->mi_off += off; ret = ctf_member_iter(mip->mi_fp, type, member_iter_cb, mip); mip->mi_off -= off; } return (ret); } int mdb_ctf_member_iter(mdb_ctf_id_t id, mdb_ctf_member_f *cb, void *data, uint32_t flags) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; member_iter_t mi; int ret; if ((flags & ~MDB_CTF_F_ITER_ANON) != 0) { return (set_errno(EINVAL)); } /* resolve the type in case there's a forward declaration */ if ((ret = mdb_ctf_type_resolve(id, &id)) != 0) return (ret); mi.mi_cb = cb; mi.mi_arg = data; mi.mi_fp = idp->mci_fp; mi.mi_flags = flags; mi.mi_off = 0; ret = ctf_member_iter(idp->mci_fp, idp->mci_id, member_iter_cb, &mi); if (ret == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(idp->mci_fp)))); return (ret); } int mdb_ctf_enum_iter(mdb_ctf_id_t id, mdb_ctf_enum_f *cb, void *data) { mdb_ctf_impl_t *idp = (mdb_ctf_impl_t *)&id; int ret; /* resolve the type in case there's a forward declaration */ if ((ret = mdb_ctf_type_resolve(id, &id)) != 0) return (ret); return (ctf_enum_iter(idp->mci_fp, idp->mci_id, cb, data)); } /* * callback proxy for mdb_ctf_type_iter */ /* ARGSUSED */ static int type_iter_cb(ctf_id_t type, boolean_t root, void *data) { type_iter_t *tip = data; mdb_ctf_id_t id; set_ctf_id(&id, tip->ti_fp, type); return (tip->ti_cb(id, tip->ti_arg)); } int mdb_ctf_type_iter(const char *object, mdb_ctf_type_f *cb, void *data) { ctf_file_t *fp; mdb_tgt_t *t = mdb.m_target; int ret; type_iter_t ti; if (object == MDB_CTF_SYNTHETIC_ITER) fp = mdb.m_synth; else fp = mdb_tgt_name_to_ctf(t, object); if (fp == NULL) return (-1); ti.ti_cb = cb; ti.ti_arg = data; ti.ti_fp = fp; if ((ret = ctf_type_iter(fp, B_FALSE, type_iter_cb, &ti)) == CTF_ERR) return (set_errno(ctf_to_errno(ctf_errno(fp)))); return (ret); } /* utility functions */ ctf_id_t mdb_ctf_type_id(mdb_ctf_id_t id) { return (((mdb_ctf_impl_t *)&id)->mci_id); } ctf_file_t * mdb_ctf_type_file(mdb_ctf_id_t id) { return (((mdb_ctf_impl_t *)&id)->mci_fp); } static int member_info_cb(const char *name, mdb_ctf_id_t id, ulong_t off, void *data) { mbr_info_t *mbrp = data; if (strcmp(name, mbrp->mbr_member) == 0) { if (mbrp->mbr_offp != NULL) *(mbrp->mbr_offp) = off; if (mbrp->mbr_typep != NULL) *(mbrp->mbr_typep) = id; return (1); } return (0); } int mdb_ctf_member_info(mdb_ctf_id_t id, const char *member, ulong_t *offp, mdb_ctf_id_t *typep) { mbr_info_t mbr; int rc; mbr.mbr_member = member; mbr.mbr_offp = offp; mbr.mbr_typep = typep; rc = mdb_ctf_member_iter(id, member_info_cb, &mbr, MDB_CTF_F_ITER_ANON); /* couldn't get member list */ if (rc == -1) return (-1); /* errno is set for us */ /* not a member */ if (rc == 0) return (set_errno(EMDB_CTFNOMEMB)); return (0); } /* * Returns offset in _bits_ in *retp. */ int mdb_ctf_offsetof(mdb_ctf_id_t id, const char *member, ulong_t *retp) { return (mdb_ctf_member_info(id, member, retp, NULL)); } /* * Returns offset in _bytes_, or -1 on failure. */ int mdb_ctf_offsetof_by_name(const char *type, const char *member) { mdb_ctf_id_t id; ulong_t off; if (mdb_ctf_lookup_by_name(type, &id) == -1) { mdb_warn("couldn't find type %s", type); return (-1); } if (mdb_ctf_offsetof(id, member, &off) == -1) { mdb_warn("couldn't find member %s of type %s", member, type); return (-1); } if (off % 8 != 0) { mdb_warn("member %s of type %s is an unsupported bitfield\n", member, type); return (-1); } off /= 8; return (off); } ssize_t mdb_ctf_sizeof_by_name(const char *type) { mdb_ctf_id_t id; ssize_t size; if (mdb_ctf_lookup_by_name(type, &id) == -1) { mdb_warn("couldn't find type %s", type); return (-1); } if ((size = mdb_ctf_type_size(id)) == -1) { mdb_warn("couldn't determine type size of %s", type); return (-1); } return (size); } /*ARGSUSED*/ static int num_members_cb(const char *name, mdb_ctf_id_t id, ulong_t off, void *data) { int *count = data; *count = *count + 1; return (0); } int mdb_ctf_num_members(mdb_ctf_id_t id) { int count = 0; if (mdb_ctf_member_iter(id, num_members_cb, &count, 0) != 0) return (-1); /* errno is set for us */ return (count); } typedef struct mbr_contains { char **mbc_bufp; size_t *mbc_lenp; ulong_t *mbc_offp; mdb_ctf_id_t *mbc_idp; ssize_t mbc_total; } mbr_contains_t; static int offset_to_name_cb(const char *name, mdb_ctf_id_t id, ulong_t off, void *data) { mbr_contains_t *mbc = data; ulong_t size; ctf_encoding_t e; size_t n; if (*mbc->mbc_offp < off) return (0); if (mdb_ctf_type_encoding(id, &e) == -1) size = mdb_ctf_type_size(id) * NBBY; else size = e.cte_bits; if (off + size <= *mbc->mbc_offp) return (0); if (*name == '\0' && (mdb_ctf_type_kind(id) == CTF_K_STRUCT || mdb_ctf_type_kind(id) == CTF_K_UNION)) { n = mdb_snprintf(*mbc->mbc_bufp, *mbc->mbc_lenp, ""); } else { n = mdb_snprintf(*mbc->mbc_bufp, *mbc->mbc_lenp, "%s", name); } mbc->mbc_total += n; if (n > *mbc->mbc_lenp) n = *mbc->mbc_lenp; *mbc->mbc_lenp -= n; *mbc->mbc_bufp += n; *mbc->mbc_offp -= off; *mbc->mbc_idp = id; return (1); } ssize_t mdb_ctf_offset_to_name(mdb_ctf_id_t id, ulong_t off, char *buf, size_t len, int dot, mdb_ctf_id_t *midp, ulong_t *moffp) { size_t size; size_t n; mbr_contains_t mbc; if (!mdb_ctf_type_valid(id)) return (set_errno(EINVAL)); /* * Quick sanity check to make sure the given offset is within * this scope of this type. */ if (mdb_ctf_type_size(id) * NBBY <= off) return (set_errno(EINVAL)); mbc.mbc_bufp = &buf; mbc.mbc_lenp = &len; mbc.mbc_offp = &off; mbc.mbc_idp = &id; mbc.mbc_total = 0; *buf = '\0'; for (;;) { /* * Check for an exact match. */ if (off == 0) break; (void) mdb_ctf_type_resolve(id, &id); /* * Find the member that contains this offset. */ switch (mdb_ctf_type_kind(id)) { case CTF_K_ARRAY: { mdb_ctf_arinfo_t ar; uint_t index; (void) mdb_ctf_array_info(id, &ar); size = mdb_ctf_type_size(ar.mta_contents) * NBBY; index = off / size; id = ar.mta_contents; off %= size; n = mdb_snprintf(buf, len, "[%u]", index); mbc.mbc_total += n; if (n > len) n = len; buf += n; len -= n; break; } case CTF_K_STRUCT: { int ret; /* * Find the member that contains this offset * and continue. */ if (dot) { mbc.mbc_total++; if (len != 0) { *buf++ = '.'; *buf = '\0'; len--; } } ret = mdb_ctf_member_iter(id, offset_to_name_cb, &mbc, 0); if (ret == -1) return (-1); /* errno is set for us */ /* * If we did not find a member containing this offset * (due to holes in the structure), return EINVAL. */ if (ret == 0) return (set_errno(EINVAL)); break; } case CTF_K_UNION: /* * Treat unions like atomic entities since we can't * do more than guess which member of the union * might be the intended one. */ goto done; case CTF_K_INTEGER: case CTF_K_FLOAT: case CTF_K_POINTER: case CTF_K_ENUM: goto done; default: return (set_errno(EINVAL)); } dot = 1; } done: if (midp != NULL) *midp = id; if (moffp != NULL) *moffp = off; return (mbc.mbc_total); } static void mdb_ctf_warn(uint_t flags, const char *format, ...) { va_list alist; if (flags & MDB_CTF_VREAD_QUIET) return; va_start(alist, format); vwarn(format, alist); va_end(alist); } /* * Check if two types are structurally the same rather than logically * the same. That is to say that two types are equal if they have the * same logical structure rather than having the same ids in CTF-land. */ static int type_equals(mdb_ctf_id_t, mdb_ctf_id_t); static int type_equals_cb(const char *name, mdb_ctf_id_t amem, ulong_t aoff, void *data) { mdb_ctf_id_t b = *(mdb_ctf_id_t *)data; ulong_t boff; mdb_ctf_id_t bmem; /* * Look up the corresponding member in the other composite type. */ if (mdb_ctf_member_info(b, name, &boff, &bmem) != 0) return (1); /* * We don't allow members to be shuffled around. */ if (aoff != boff) return (1); return (type_equals(amem, bmem) ? 0 : 1); } static int type_equals(mdb_ctf_id_t a, mdb_ctf_id_t b) { size_t asz, bsz; int akind, bkind; mdb_ctf_arinfo_t aar, bar; /* * Resolve both types down to their fundamental types, and make * sure their sizes and kinds match. */ if (mdb_ctf_type_resolve(a, &a) != 0 || mdb_ctf_type_resolve(b, &b) != 0 || (asz = mdb_ctf_type_size(a)) == -1UL || (bsz = mdb_ctf_type_size(b)) == -1UL || (akind = mdb_ctf_type_kind(a)) == -1 || (bkind = mdb_ctf_type_kind(b)) == -1 || asz != bsz || akind != bkind) { return (0); } switch (akind) { case CTF_K_INTEGER: case CTF_K_FLOAT: case CTF_K_POINTER: /* * For pointers we could be a little stricter and require * both pointers to reference types which look vaguely * similar (for example, we could insist that the two types * have the same name). However, all we really care about * here is that the structure of the two types are the same, * and, in that regard, one pointer is as good as another. */ return (1); case CTF_K_UNION: case CTF_K_STRUCT: /* * The test for the number of members is only strictly * necessary for unions since we'll find other problems with * structs. However, the extra check will do no harm. */ return (mdb_ctf_num_members(a) == mdb_ctf_num_members(b) && mdb_ctf_member_iter(a, type_equals_cb, &b, 0) == 0); case CTF_K_ARRAY: return (mdb_ctf_array_info(a, &aar) == 0 && mdb_ctf_array_info(b, &bar) == 0 && aar.mta_nelems == bar.mta_nelems && type_equals(aar.mta_index, bar.mta_index) && type_equals(aar.mta_contents, bar.mta_contents)); } return (0); } typedef struct member { char *m_modbuf; char *m_tgtbuf; const char *m_tgtname; mdb_ctf_id_t m_tgtid; uint_t m_flags; } member_t; static int vread_helper(mdb_ctf_id_t, char *, mdb_ctf_id_t, char *, const char *, uint_t); static int member_cb(const char *name, mdb_ctf_id_t modmid, ulong_t modoff, void *data) { member_t *mp = data; char *modbuf = mp->m_modbuf; mdb_ctf_id_t tgtmid; char *tgtbuf = mp->m_tgtbuf; ulong_t tgtoff; char tgtname[128]; (void) mdb_snprintf(tgtname, sizeof (tgtname), "member %s of type %s", name, mp->m_tgtname); if (mdb_ctf_member_info(mp->m_tgtid, name, &tgtoff, &tgtmid) != 0) { if (mp->m_flags & MDB_CTF_VREAD_IGNORE_ABSENT) return (0); mdb_ctf_warn(mp->m_flags, "could not find %s\n", tgtname); return (set_errno(EMDB_CTFNOMEMB)); } return (vread_helper(modmid, modbuf + modoff / NBBY, tgtmid, tgtbuf + tgtoff / NBBY, tgtname, mp->m_flags)); } typedef struct enum_value { int *ev_modbuf; const char *ev_name; } enum_value_t; static int enum_cb(const char *name, int value, void *data) { enum_value_t *ev = data; if (strcmp(name, ev->ev_name) == 0) { *ev->ev_modbuf = value; return (1); } return (0); } static int vread_helper(mdb_ctf_id_t modid, char *modbuf, mdb_ctf_id_t tgtid, char *tgtbuf, const char *tgtname, uint_t flags) { size_t modsz, tgtsz; int modkind, tgtkind, mod_members; member_t mbr; enum_value_t ev; int ret; mdb_ctf_arinfo_t tar, mar; int i; char typename[128]; char mdbtypename[128]; ctf_encoding_t tgt_encoding, mod_encoding; boolean_t signed_int = B_FALSE; if (mdb_ctf_type_name(tgtid, typename, sizeof (typename)) == NULL) { (void) mdb_snprintf(typename, sizeof (typename), "#%ul", mdb_ctf_type_id(tgtid)); } if (mdb_ctf_type_name(modid, mdbtypename, sizeof (mdbtypename)) == NULL) { (void) mdb_snprintf(mdbtypename, sizeof (mdbtypename), "#%ul", mdb_ctf_type_id(modid)); } if (tgtname == NULL) tgtname = ""; /* * Resolve the types to their canonical form. */ (void) mdb_ctf_type_resolve(modid, &modid); (void) mdb_ctf_type_resolve(tgtid, &tgtid); if ((modkind = mdb_ctf_type_kind(modid)) == -1) { mdb_ctf_warn(flags, "couldn't determine type kind of mdb module type %s\n", mdbtypename); return (-1); /* errno is set for us */ } if ((tgtkind = mdb_ctf_type_kind(tgtid)) == -1) { mdb_ctf_warn(flags, "couldn't determine type kind of %s\n", typename); return (-1); /* errno is set for us */ } if ((modsz = mdb_ctf_type_size(modid)) == -1UL) { mdb_ctf_warn(flags, "couldn't determine type size of " "mdb module type %s\n", mdbtypename); return (-1); /* errno is set for us */ } if ((tgtsz = mdb_ctf_type_size(tgtid)) == -1UL) { mdb_ctf_warn(flags, "couldn't determine size of %s (%s)\n", typename, tgtname); return (-1); /* errno is set for us */ } if (tgtkind == CTF_K_POINTER && modkind == CTF_K_INTEGER && strcmp(mdbtypename, "uintptr_t") == 0) { /* allow them to convert a pointer to a uintptr_t */ ASSERT(modsz == tgtsz); } else if (tgtkind != modkind) { mdb_ctf_warn(flags, "unexpected kind for type %s (%s)\n", typename, tgtname); return (set_errno(EMDB_INCOMPAT)); } switch (tgtkind) { case CTF_K_INTEGER: case CTF_K_FLOAT: /* * Must determine if the target and module types have the same * encoding before we can copy them. */ if (mdb_ctf_type_encoding(tgtid, &tgt_encoding) != 0) { mdb_ctf_warn(flags, "couldn't determine encoding of type %s (%s)\n", typename, tgtname); return (-1); /* errno is set for us */ } if (mdb_ctf_type_encoding(modid, &mod_encoding) != 0) { mdb_ctf_warn(flags, "couldn't determine encoding of " "mdb module type %s\n", mdbtypename); return (-1); /* errno is set for us */ } if (modkind == CTF_K_INTEGER) { if ((tgt_encoding.cte_format & CTF_INT_SIGNED) != (mod_encoding.cte_format & CTF_INT_SIGNED)) { mdb_ctf_warn(flags, "signedness mismatch between type " "%s (%s) and mdb module type %s\n", typename, tgtname, mdbtypename); return (set_errno(EMDB_INCOMPAT)); } signed_int = ((tgt_encoding.cte_format & CTF_INT_SIGNED) != 0); } else if (tgt_encoding.cte_format != mod_encoding.cte_format) { mdb_ctf_warn(flags, "encoding mismatch (%#x != %#x) between type " "%s (%s) and mdb module type %s\n", tgt_encoding.cte_format, mod_encoding.cte_format, typename, tgtname, mdbtypename); return (set_errno(EMDB_INCOMPAT)); } /* FALLTHROUGH */ case CTF_K_POINTER: /* * If the sizes don't match we need to be tricky to make * sure that the caller gets the correct data. */ if (modsz < tgtsz) { mdb_ctf_warn(flags, "size of type %s (%s) is too " "large for mdb module type %s\n", typename, tgtname, mdbtypename); return (set_errno(EMDB_INCOMPAT)); } else if (modsz > tgtsz) { /* BEGIN CSTYLED */ /* * Fill modbuf with 1's for sign extension if target * buf is a signed integer and its value is negative. * * S = sign bit (in most-significant byte) * * BIG ENDIAN DATA * +--------+--------+--------+--------+ * |S | | | | * +--------+--------+--------+--------+ * 0 1 ... sz-1 sz * * LITTLE ENDIAN DATA * +--------+--------+--------+--------+ * | | | |S | * +--------+--------+--------+--------+ * 0 1 ... sz-1 sz */ /* END CSTYLED */ #ifdef _BIG_ENDIAN if (signed_int && (tgtbuf[0] & 0x80) != 0) #else if (signed_int && (tgtbuf[tgtsz - 1] & 0x80) != 0) #endif (void) memset(modbuf, 0xFF, modsz); else bzero(modbuf, modsz); #ifdef _BIG_ENDIAN bcopy(tgtbuf, modbuf + modsz - tgtsz, tgtsz); #else bcopy(tgtbuf, modbuf, tgtsz); #endif } else { bcopy(tgtbuf, modbuf, modsz); } return (0); case CTF_K_ENUM: if (modsz != tgtsz || modsz != sizeof (int)) { mdb_ctf_warn(flags, "unexpected size of type %s (%s)\n", typename, tgtname); return (set_errno(EMDB_INCOMPAT)); } /* * Default to the same value as in the target. */ bcopy(tgtbuf, modbuf, sizeof (int)); /* LINTED */ i = *(int *)tgtbuf; /* LINTED */ ev.ev_modbuf = (int *)modbuf; ev.ev_name = mdb_ctf_enum_name(tgtid, i); if (ev.ev_name == NULL) { mdb_ctf_warn(flags, "unexpected value %u of enum type %s (%s)\n", i, typename, tgtname); return (set_errno(EMDB_INCOMPAT)); } ret = mdb_ctf_enum_iter(modid, enum_cb, &ev); if (ret == 0) { /* value not found */ mdb_ctf_warn(flags, "unexpected value %s (%u) of enum type %s (%s)\n", ev.ev_name, i, typename, tgtname); return (set_errno(EMDB_INCOMPAT)); } else if (ret == 1) { /* value found */ return (0); } else if (ret == -1) { mdb_ctf_warn(flags, "could not iterate enum %s (%s)\n", typename, tgtname); } return (ret); case CTF_K_STRUCT: mbr.m_modbuf = modbuf; mbr.m_tgtbuf = tgtbuf; mbr.m_tgtid = tgtid; mbr.m_flags = flags; mbr.m_tgtname = typename; return (mdb_ctf_member_iter(modid, member_cb, &mbr, 0)); case CTF_K_UNION: mbr.m_modbuf = modbuf; mbr.m_tgtbuf = tgtbuf; mbr.m_tgtid = tgtid; mbr.m_flags = flags; mbr.m_tgtname = typename; /* * Not all target union members need to be present in the * mdb type. If there is only a single union member in the * mdb type, its actual type does not need to match with * its target's type. On the other hand, if more than one * union members are specified in the mdb type, their types * must match with the types of their relevant union members * of the target union. */ mod_members = mdb_ctf_num_members(modid); if (mod_members == 1) { return (mdb_ctf_member_iter(modid, member_cb, &mbr, 0)); } else if (mod_members > 1) { if (mdb_ctf_member_iter(modid, type_equals_cb, &tgtid, 0)) { mdb_ctf_warn(flags, "inexact match for union %s (%s)\n", typename, tgtname); return (set_errno(EMDB_INCOMPAT)); } /* * From the check above we know that the members * which are present in the mdb type are equal to * the types in the target. Thus, the member_cb * callback below will not move anything around and * it is equivalent to: * * bcopy(tgtbuf, modbuf, MAX(module member's sizes)) */ return (mdb_ctf_member_iter(modid, member_cb, &mbr, 0)); } else { /* * We either got 0 or -1. In any case that number * should be returned right away. For the error * case of -1, errno has been set for us. */ return (mod_members); } case CTF_K_ARRAY: if (mdb_ctf_array_info(tgtid, &tar) != 0) { mdb_ctf_warn(flags, "couldn't get array info for %s (%s)\n", typename, tgtname); return (-1); /* errno is set for us */ } if (mdb_ctf_array_info(modid, &mar) != 0) { mdb_ctf_warn(flags, "couldn't get array info for mdb module type %s\n", mdbtypename); return (-1); /* errno is set for us */ } if (tar.mta_nelems != mar.mta_nelems) { mdb_ctf_warn(flags, "unexpected array size (%u) for type %s (%s)\n", tar.mta_nelems, typename, tgtname); return (set_errno(EMDB_INCOMPAT)); } if ((modsz = mdb_ctf_type_size(mar.mta_contents)) == -1UL) { mdb_ctf_warn(flags, "couldn't determine type size of " "mdb module type %s\n", mdbtypename); return (-1); /* errno is set for us */ } if ((tgtsz = mdb_ctf_type_size(tar.mta_contents)) == -1UL) { mdb_ctf_warn(flags, "couldn't determine size of %s (%s)\n", typename, tgtname); return (-1); /* errno is set for us */ } for (i = 0; i < tar.mta_nelems; i++) { ret = vread_helper(mar.mta_contents, modbuf + i * modsz, tar.mta_contents, tgtbuf + i * tgtsz, tgtname, flags); if (ret != 0) return (ret); } return (0); } mdb_ctf_warn(flags, "unsupported kind %d for type %s (%s)\n", modkind, typename, tgtname); return (set_errno(EMDB_INCOMPAT)); } /* * Like mdb_vread(), mdb_ctf_vread() is used to read from the target's * virtual address space. However, mdb_ctf_vread() can be used to safely * read a complex type (e.g. a struct) from the target, even if MDB was compiled * against a different definition of that type (e.g. when debugging a crash * dump from an older release). * * Callers can achieve this by defining their own type which corresponds to the * type in the target, but contains only the members that the caller requires. * Using the CTF type information embedded in the target, mdb_ctf_vread will * find the required members in the target and fill in the caller's structure. * The members are located by name, and their types are verified to be * compatible. * * By convention, the caller will declare a type with the name "mdb_", * where is the name of the type in the target (e.g. mdb_zio_t). This * type will contain the members that the caller is interested in. For example: * * typedef struct mdb_zio { * enum zio_type io_type; * uintptr_t io_waiter; * struct { * struct { * uintptr_t list_next; * } list_head; * } io_parent_list; * int io_error; * } mdb_zio_t; * * mdb_zio_t zio; * error = mdb_ctf_vread(&zio, "zio_t", "mdb_zio_t", zio_target_addr, 0); * * If a given MDB module has different dcmds or walkers that need to read * different members from the same struct, then different "mdb_" types * should be declared for each caller. By convention, these types should * be named "mdb__", e.g. mdb_findstack_kthread_t * for ::findstack. If the MDB module is compiled from several source files, * one must be especially careful to not define different types with the * same name in different source files, because the compiler can not detect * this error. * * Enums will also be translated by name, so the mdb module will receive * the enum value it expects even if the target has renumbered the enum. * Warning: it will therefore only work with enums are only used to store * legitimate enum values (not several values or-ed together). * * Flags values: * * MDB_CTF_VREAD_QUIET: keep quiet about failures * MDB_CTF_VREAD_IGNORE_ABSENT: ignore any member that couldn't be found in the * target struct; be careful not to use an uninitialized result. */ int mdb_ctf_vread(void *modbuf, const char *target_typename, const char *mdb_typename, uintptr_t addr, uint_t flags) { ctf_file_t *mfp; ctf_id_t mid; void *tgtbuf; size_t size; mdb_ctf_id_t tgtid; mdb_ctf_id_t modid; mdb_module_t *mod; int ret; if ((mod = mdb_get_module()) == NULL) { mdb_ctf_warn(flags, "could not get MDB module"); return (set_errno(EMDB_NOCTF)); } if ((mfp = mod->mod_ctfp) == NULL) { mdb_ctf_warn(flags, "no ctf data found for mdb module %s\n", mod->mod_name); return (set_errno(EMDB_NOCTF)); } if ((mid = ctf_lookup_by_name(mfp, mdb_typename)) == CTF_ERR) { mdb_ctf_warn(flags, "couldn't find ctf data for " "type %s in mdb module %s\n", mdb_typename, mod->mod_name); return (set_errno(ctf_to_errno(ctf_errno(mfp)))); } set_ctf_id(&modid, mfp, mid); if (mdb_ctf_lookup_by_name(target_typename, &tgtid) != 0) { mdb_ctf_warn(flags, "couldn't find type %s in target's ctf data\n", target_typename); return (set_errno(EMDB_NOCTF)); } /* * Read the data out of the target's address space. */ if ((size = mdb_ctf_type_size(tgtid)) == -1UL) { mdb_ctf_warn(flags, "couldn't determine size of type %s\n", target_typename); return (-1); /* errno is set for us */ } tgtbuf = mdb_alloc(size, UM_SLEEP); if (mdb_vread(tgtbuf, size, addr) < 0) { mdb_ctf_warn(flags, "couldn't read %s from %p\n", target_typename, addr); mdb_free(tgtbuf, size); return (-1); /* errno is set for us */ } ret = vread_helper(modid, modbuf, tgtid, tgtbuf, NULL, flags); mdb_free(tgtbuf, size); return (ret); } /* * Note: mdb_ctf_readsym() doesn't take separate parameters for the name * of the target's type vs the mdb module's type. Use with complicated * types (e.g. structs) may result in unnecessary failure if a member of * the struct has been changed in the target, but is not actually needed * by the mdb module. Use mdb_lookup_by_name() + mdb_ctf_vread() to * avoid this problem. */ int mdb_ctf_readsym(void *buf, const char *typename, const char *name, uint_t flags) { GElf_Sym sym; if (mdb_lookup_by_obj(MDB_TGT_OBJ_EVERY, name, &sym) != 0) { mdb_ctf_warn(flags, "couldn't find symbol %s\n", name); return (-1); /* errno is set for us */ } return (mdb_ctf_vread(buf, typename, typename, sym.st_value, flags)); } ctf_file_t * mdb_ctf_bufopen(const void *ctf_va, size_t ctf_size, const void *sym_va, Shdr *symhdr, const void *str_va, Shdr *strhdr, int *errp) { ctf_sect_t ctdata, symtab, strtab; ctdata.cts_name = ".SUNW_ctf"; ctdata.cts_type = SHT_PROGBITS; ctdata.cts_flags = 0; ctdata.cts_data = ctf_va; ctdata.cts_size = ctf_size; ctdata.cts_entsize = 1; ctdata.cts_offset = 0; symtab.cts_name = ".symtab"; symtab.cts_type = symhdr->sh_type; symtab.cts_flags = symhdr->sh_flags; symtab.cts_data = sym_va; symtab.cts_size = symhdr->sh_size; symtab.cts_entsize = symhdr->sh_entsize; symtab.cts_offset = symhdr->sh_offset; strtab.cts_name = ".strtab"; strtab.cts_type = strhdr->sh_type; strtab.cts_flags = strhdr->sh_flags; strtab.cts_data = str_va; strtab.cts_size = strhdr->sh_size; strtab.cts_entsize = strhdr->sh_entsize; strtab.cts_offset = strhdr->sh_offset; return (ctf_bufopen(&ctdata, &symtab, &strtab, errp)); } int mdb_ctf_synthetics_init(void) { int err; if ((mdb.m_synth = ctf_create(&err)) == NULL) return (set_errno(ctf_to_errno(err))); return (0); } void mdb_ctf_synthetics_fini(void) { if (mdb.m_synth == NULL) return; ctf_close(mdb.m_synth); mdb.m_synth = NULL; } int mdb_ctf_synthetics_create_base(int kind) { const synth_intrinsic_t *synp; const synth_typedef_t *sytp; int err; ctf_id_t id; ctf_file_t *cp = mdb.m_synth; if (mdb.m_synth == NULL) { mdb_printf("synthetic types disabled: ctf create failed\n"); return (1); } switch (kind) { case SYNTHETIC_ILP32: synp = synth_builtins32; sytp = synth_typedefs32; break; case SYNTHETIC_LP64: synp = synth_builtins64; sytp = synth_typedefs64; break; default: mdb_dprintf(MDB_DBG_CTF, "invalid type of intrinsic: %d\n", kind); return (1); } err = 0; for (; synp->syn_name != NULL; synp++) { if (synp->syn_kind == CTF_K_INTEGER) { err = ctf_add_integer(cp, CTF_ADD_ROOT, synp->syn_name, &synp->syn_enc); } else { err = ctf_add_float(cp, CTF_ADD_ROOT, synp->syn_name, &synp->syn_enc); } if (err == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "couldn't add synthetic " "type: %s\n", synp->syn_name); goto discard; } } if (ctf_update(cp) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types\n"); goto discard; } for (; sytp->syt_src != NULL; sytp++) { id = ctf_lookup_by_name(cp, sytp->syt_src); if (id == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "cailed to lookup %s: %s\n", sytp->syt_src, ctf_errmsg(ctf_errno(cp))); goto discard; } if (ctf_add_typedef(cp, CTF_ADD_ROOT, sytp->syt_targ, id) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "couldn't add typedef %s " "%s: %s\n", sytp->syt_targ, sytp->syt_src, ctf_errmsg(ctf_errno(cp))); goto discard; } } if (ctf_update(cp) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types\n"); goto discard; } return (0); discard: err = set_errno(ctf_to_errno(ctf_errno(cp))); (void) ctf_discard(cp); return (err); } int mdb_ctf_synthetics_reset(void) { mdb_ctf_synthetics_fini(); return (mdb_ctf_synthetics_init()); } int mdb_ctf_add_typedef(const char *name, const mdb_ctf_id_t *p, mdb_ctf_id_t *new) { ctf_id_t rid; mdb_ctf_id_t tid; mdb_ctf_impl_t *mcip = (mdb_ctf_impl_t *)p; if (mdb.m_synth == NULL) { mdb_printf("synthetic types disabled: ctf create failed\n"); return (1); } if (mdb_ctf_lookup_by_name(name, &tid) == 0) { mdb_dprintf(MDB_DBG_CTF, "failed to add type %s: a type " "with that name already exists\n", name); return (set_errno(EEXIST)); } rid = ctf_add_type(mdb.m_synth, mcip->mci_fp, mcip->mci_id); if (rid == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add reference type: %s\n", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } rid = ctf_add_typedef(mdb.m_synth, CTF_ADD_ROOT, name, rid); if (rid == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add typedef: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (new != NULL) set_ctf_id(new, mdb.m_synth, rid); return (0); } int mdb_ctf_add_struct(const char *name, mdb_ctf_id_t *rid) { mdb_ctf_id_t tid; ctf_id_t id; if (mdb.m_synth == NULL) { mdb_printf("synthetic types disabled: ctf create failed\n"); return (1); } if (name != NULL && mdb_ctf_lookup_by_name(name, &tid) == 0) { mdb_dprintf(MDB_DBG_CTF, "failed to add type %s: a type " "with that name already exists\n", name); return (set_errno(EEXIST)); } if ((id = ctf_add_struct(mdb.m_synth, CTF_ADD_ROOT, name)) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add struct: %s\n", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (rid != NULL) set_ctf_id(rid, mdb.m_synth, id); return (0); } int mdb_ctf_add_union(const char *name, mdb_ctf_id_t *rid) { mdb_ctf_id_t tid; ctf_id_t id; if (mdb.m_synth == NULL) { mdb_printf("synthetic types disabled: ctf create failed\n"); return (1); } if (name != NULL && mdb_ctf_lookup_by_name(name, &tid) == 0) { mdb_dprintf(MDB_DBG_CTF, "failed to add type %s: a type " "with that name already exists\n", name); return (set_errno(EEXIST)); } if ((id = ctf_add_union(mdb.m_synth, CTF_ADD_ROOT, name)) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add union: %s\n", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (rid != NULL) set_ctf_id(rid, mdb.m_synth, id); return (0); } int mdb_ctf_add_member(const mdb_ctf_id_t *p, const char *name, const mdb_ctf_id_t *mtype, mdb_ctf_id_t *rid) { ctf_id_t id, mtid; mdb_ctf_impl_t *mcip = (mdb_ctf_impl_t *)p; mdb_ctf_impl_t *mcim = (mdb_ctf_impl_t *)mtype; if (mdb.m_synth == NULL) { mdb_printf("synthetic types disabled: ctf create failed\n"); return (DCMD_ERR); } if (mcip->mci_fp != mdb.m_synth) { mdb_dprintf(MDB_DBG_CTF, "requested to add member to a type " "that wasn't created from a synthetic\n"); return (set_errno(EINVAL)); } mtid = ctf_add_type(mdb.m_synth, mcim->mci_fp, mcim->mci_id); if (mtid == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add member type: %s\n", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } id = ctf_add_member(mdb.m_synth, mcip->mci_id, name, mtid, ULONG_MAX); if (id == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add member %s: %s\n", name, ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (rid != NULL) set_ctf_id(rid, mdb.m_synth, id); return (0); } int mdb_ctf_add_array(const mdb_ctf_arinfo_t *marp, mdb_ctf_id_t *rid) { mdb_ctf_impl_t *mcip; ctf_arinfo_t car; ctf_id_t id; if (mdb.m_synth == NULL) { mdb_printf("synthetic types disabled: ctf create failed\n"); return (1); } car.ctr_nelems = marp->mta_nelems; mcip = (mdb_ctf_impl_t *)&marp->mta_contents; id = ctf_add_type(mdb.m_synth, mcip->mci_fp, mcip->mci_id); if (id == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add member type: %s\n", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } car.ctr_contents = id; mcip = (mdb_ctf_impl_t *)&marp->mta_index; id = ctf_add_type(mdb.m_synth, mcip->mci_fp, mcip->mci_id); if (id == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add member type: %s\n", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } car.ctr_index = id; if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } id = ctf_add_array(mdb.m_synth, CTF_ADD_ROOT, &car); if (id == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (rid != NULL) set_ctf_id(rid, mdb.m_synth, id); return (0); } int mdb_ctf_add_pointer(const mdb_ctf_id_t *p, mdb_ctf_id_t *rid) { ctf_id_t id; mdb_ctf_impl_t *mcip = (mdb_ctf_impl_t *)p; if (mdb.m_synth == NULL) { mdb_printf("synthetic types disabled: ctf create failed\n"); return (1); } id = ctf_add_type(mdb.m_synth, mcip->mci_fp, mcip->mci_id); if (id == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add pointer type: %s\n", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } id = ctf_add_pointer(mdb.m_synth, CTF_ADD_ROOT, NULL, id); if (id == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add pointer: %s\n", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (rid != NULL) set_ctf_id(rid, mdb.m_synth, id); return (0); } int mdb_ctf_type_delete(const mdb_ctf_id_t *id) { int ret; mdb_ctf_impl_t *mcip = (mdb_ctf_impl_t *)id; if (mcip->mci_fp != mdb.m_synth) { mdb_warn("bad ctf_file_t, expected synth container\n"); return (1); } ret = ctf_delete_type(mcip->mci_fp, mcip->mci_id); if (ret != 0) { mdb_dprintf(MDB_DBG_CTF, "failed to delete synthetic type: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } if (ctf_update(mdb.m_synth) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types: %s", ctf_errmsg(ctf_errno(mdb.m_synth))); return (set_errno(ctf_to_errno(ctf_errno(mdb.m_synth)))); } return (0); } /* ARGSUSED */ static int mdb_ctf_synthetics_file_cb(mdb_ctf_id_t id, void *arg) { ctf_file_t *targ = arg; mdb_ctf_impl_t *mcip = (mdb_ctf_impl_t *)&id; if (ctf_add_type(targ, mcip->mci_fp, mcip->mci_id) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to add type %d: %s\n", mcip->mci_id, ctf_errmsg(ctf_errno(mcip->mci_fp))); return (set_errno(ctf_to_errno(ctf_errno(mcip->mci_fp)))); } return (0); } int mdb_ctf_synthetics_from_file(const char *file) { ctf_file_t *fp, *syn = mdb.m_synth; int ret; type_iter_t ti; if (syn == NULL) { mdb_warn("synthetic types disabled: ctf create failed\n"); return (1); } if ((fp = mdb_ctf_open(file, &ret)) == NULL) { mdb_warn("failed to parse ctf data in %s: %s\n", file, ctf_errmsg(ret)); return (1); } ret = DCMD_OK; ti.ti_fp = fp; ti.ti_arg = syn; ti.ti_cb = mdb_ctf_synthetics_file_cb; if (ctf_type_iter(fp, B_FALSE, type_iter_cb, &ti) == CTF_ERR) { ret = set_errno(ctf_to_errno(ctf_errno(fp))); mdb_warn("failed to add types"); goto cleanup; } if (ctf_update(syn) == CTF_ERR) { mdb_dprintf(MDB_DBG_CTF, "failed to update synthetic types\n"); ret = set_errno(ctf_to_errno(ctf_errno(fp))); } cleanup: ctf_close(fp); if (ret != 0) (void) ctf_discard(syn); return (ret); } int mdb_ctf_synthetics_to_file(const char *file) { int err; ctf_file_t *fp = mdb.m_synth; if (fp == NULL) { mdb_warn("synthetic types are disabled, not writing " "anything\n"); return (DCMD_ERR); } err = mdb_ctf_write(file, fp); if (err != 0) { if (err == CTF_ERR) (void) set_errno(ctf_to_errno(ctf_errno(fp))); else (void) set_errno(err); err = DCMD_ERR; } else { err = DCMD_OK; } return (err); } static int cmd_typelist_type(mdb_ctf_id_t id, void *arg) { char buf[1024]; if (mdb_ctf_type_name(id, buf, sizeof (buf)) != NULL) { mdb_printf("%s\n", buf); } return (0); } static int cmd_typelist_module(void *data, const mdb_map_t *mp, const char *name) { (void) mdb_ctf_type_iter(name, cmd_typelist_type, data); return (0); } int cmd_typelist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags & DCMD_ADDRSPEC) != 0) { return (DCMD_USAGE); } (void) mdb_tgt_object_iter(mdb.m_target, cmd_typelist_module, NULL); (void) mdb_ctf_type_iter(MDB_CTF_SYNTHETIC_ITER, cmd_typelist_type, NULL); return (DCMD_OK); } /* * 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) 2013, 2015 by Delphix. All rights reserved. * Copyright 2018 Joyent, Inc. * Copyright 2025 Oxide Computer Company */ #ifndef _MDB_CTF_H #define _MDB_CTF_H #include #include #ifdef _MDB #include #include #endif /* * The following directive tells the mapfile generator that prototypes and * declarations ending with an "Internal" comment should be excluded from the * mapfile. * * MAPFILE: exclude "Internal" */ #ifdef __cplusplus extern "C" { #endif typedef struct mdb_ctf_id { void *_opaque[2]; } mdb_ctf_id_t; typedef struct mdb_ctf_funcinfo { mdb_ctf_id_t mtf_return; /* function return type */ uint_t mtf_argc; /* number of arguments */ uint_t mtf_flags; /* function attributes (see libctf.h) */ uint_t mtf_symidx; /* for ctf_func_args */ } mdb_ctf_funcinfo_t; typedef struct mdb_ctf_arinfo { mdb_ctf_id_t mta_contents; /* type of array conents */ mdb_ctf_id_t mta_index; /* type of array index */ uint_t mta_nelems; /* number of elements */ } mdb_ctf_arinfo_t; typedef int mdb_ctf_visit_f(const char *, mdb_ctf_id_t, mdb_ctf_id_t, ulong_t, int, void *); typedef int mdb_ctf_member_f(const char *, mdb_ctf_id_t, ulong_t, void *); typedef int mdb_ctf_enum_f(const char *, int, void *); typedef int mdb_ctf_type_f(mdb_ctf_id_t, void *); extern int mdb_ctf_enabled_by_object(const char *); extern int mdb_ctf_lookup_by_name(const char *, mdb_ctf_id_t *); extern int mdb_ctf_lookup_by_addr(uintptr_t, mdb_ctf_id_t *); extern int mdb_ctf_module_lookup(const char *, mdb_ctf_id_t *); extern int mdb_ctf_func_info(const GElf_Sym *, const mdb_syminfo_t *, mdb_ctf_funcinfo_t *); extern int mdb_ctf_func_args(const mdb_ctf_funcinfo_t *, uint_t, mdb_ctf_id_t *); extern void mdb_ctf_type_invalidate(mdb_ctf_id_t *); extern int mdb_ctf_type_valid(mdb_ctf_id_t); extern int mdb_ctf_type_cmp(mdb_ctf_id_t, mdb_ctf_id_t); extern int mdb_ctf_type_resolve(mdb_ctf_id_t, mdb_ctf_id_t *); extern char *mdb_ctf_type_name(mdb_ctf_id_t, char *, size_t); extern ssize_t mdb_ctf_type_lname(mdb_ctf_id_t, char *, size_t); extern ssize_t mdb_ctf_type_size(mdb_ctf_id_t); extern int mdb_ctf_type_kind(mdb_ctf_id_t); extern int mdb_ctf_type_reference(const mdb_ctf_id_t, mdb_ctf_id_t *); extern int mdb_ctf_type_encoding(mdb_ctf_id_t, ctf_encoding_t *); extern int mdb_ctf_type_visit(mdb_ctf_id_t, mdb_ctf_visit_f *, void *); extern int mdb_ctf_array_info(mdb_ctf_id_t, mdb_ctf_arinfo_t *); extern const char *mdb_ctf_enum_name(mdb_ctf_id_t, int); extern int mdb_ctf_member_iter(mdb_ctf_id_t, mdb_ctf_member_f *, void *, uint32_t); extern int mdb_ctf_enum_iter(mdb_ctf_id_t, mdb_ctf_enum_f *, void *); extern int mdb_ctf_type_iter(const char *, mdb_ctf_type_f *, void *); extern int mdb_ctf_type_delete(const mdb_ctf_id_t *); /* * Flags to mdb_ctf_member_iter to change iteration behavior. * * MDB_CTF_F_ITER_ANON This controls whether or not type member * iteration will descend into anonymous structs * or unions recursively. This is used for cases * where one is trying to reason about member * references (ala ::print) in a similar way to a C * compiler. */ #define MDB_CTF_F_ITER_ANON (1 << 0) /* * Special values for mdb_ctf_type_iter. */ #define MDB_CTF_SYNTHETIC_ITER (const char *)(-1L) #define SYNTHETIC_ILP32 1 #define SYNTHETIC_LP64 2 extern int mdb_ctf_synthetics_create_base(int); extern int mdb_ctf_synthetics_reset(void); /* * Synthetic creation routines */ extern int mdb_ctf_add_typedef(const char *, const mdb_ctf_id_t *, mdb_ctf_id_t *); extern int mdb_ctf_add_struct(const char *, mdb_ctf_id_t *); extern int mdb_ctf_add_union(const char *, mdb_ctf_id_t *); extern int mdb_ctf_add_member(const mdb_ctf_id_t *, const char *, const mdb_ctf_id_t *, mdb_ctf_id_t *); extern int mdb_ctf_add_array(const mdb_ctf_arinfo_t *, mdb_ctf_id_t *); extern int mdb_ctf_add_pointer(const mdb_ctf_id_t *, mdb_ctf_id_t *); /* utility stuff */ extern ctf_id_t mdb_ctf_type_id(mdb_ctf_id_t); extern ctf_file_t *mdb_ctf_type_file(mdb_ctf_id_t); extern int mdb_ctf_member_info(mdb_ctf_id_t, const char *, ulong_t *, mdb_ctf_id_t *); extern int mdb_ctf_offsetof(mdb_ctf_id_t, const char *, ulong_t *); extern int mdb_ctf_num_members(mdb_ctf_id_t); extern int mdb_ctf_offsetof_by_name(const char *, const char *); extern ssize_t mdb_ctf_sizeof_by_name(const char *); extern ssize_t mdb_ctf_offset_to_name(mdb_ctf_id_t, ulong_t, char *, size_t, int, mdb_ctf_id_t *, ulong_t *); #define MDB_CTF_VREAD_QUIET 0x100 #define MDB_CTF_VREAD_IGNORE_ABSENT 0x200 extern int mdb_ctf_vread(void *, const char *, const char *, uintptr_t, uint_t); extern int mdb_ctf_readsym(void *, const char *, const char *, uint_t); #ifdef _MDB extern ctf_file_t *mdb_ctf_open(const char *, int *); /* Internal */ extern ctf_file_t *mdb_ctf_bufopen(const void *, size_t, /* Internal */ const void *, Shdr *, const void *, Shdr *, int *); extern int mdb_ctf_write(const char *, ctf_file_t *fp); /* Internal */ extern void mdb_ctf_close(ctf_file_t *fp); /* Internal */ extern int mdb_ctf_synthetics_init(void); /* Internal */ extern void mdb_ctf_synthetics_fini(void); /* Internal */ extern int mdb_ctf_synthetics_from_file(const char *); /* Internal */ extern int mdb_ctf_synthetics_to_file(const char *); /* Internal */ extern int cmd_typelist(uintptr_t, uint_t, int, /* Internal */ const mdb_arg_t *); #endif #ifdef __cplusplus } #endif #endif /* _MDB_CTF_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_CTF_IMPL_H #define _MDB_CTF_IMPL_H #ifdef __cplusplus extern "C" { #endif typedef struct mdb_ctf_impl { ctf_file_t *mci_fp; ctf_id_t mci_id; } mdb_ctf_impl_t; extern int mdb_ctf_lookup_by_symbol(const GElf_Sym *, const mdb_syminfo_t *, mdb_ctf_id_t *); #ifdef __cplusplus } #endif #endif /* _MDB_CTF_IMPL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2015, Joyent, Inc. */ /* * libctf open/close interposition layer * * The mdb flavor of the interposition layer serves only to make ctf_bufopen * calls easier. The kmdb flavor (the real reason for the layer) has more * intelligence behind mdb_ctf_open() than does this one. */ #include #include #include #include #include #include #include ctf_file_t * mdb_ctf_open(const char *filename, int *errp) { return (ctf_open(filename, errp)); } void mdb_ctf_close(ctf_file_t *fp) { ctf_close(fp); } int mdb_ctf_write(const char *filename, ctf_file_t *fp) { int fd, ret; if ((fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) return (errno); if (ctf_write(fp, fd) == CTF_ERR) { (void) close(fd); return (CTF_ERR); } ret = close(fd); if (ret != 0) ret = errno; return (ret); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include typedef struct dbg_mode { const char *m_name; const char *m_desc; uint_t m_bits; } dbg_mode_t; static const dbg_mode_t dbg_modetab[] = { { "cmdbuf", "debug command editing buffer", MDB_DBG_CMDBUF }, #ifdef YYDEBUG { "parser", "debug parser internals", MDB_DBG_PARSER }, #endif { "help", "display this listing", MDB_DBG_HELP }, { "module", "debug module processing", MDB_DBG_MODULE }, { "dcmd", "debug dcmd processing", MDB_DBG_DCMD }, { "elf", "debug ELF file processing", MDB_DBG_ELF }, { "mach", "debug machine-dependent code", MDB_DBG_MACH }, { "shell", "debug shell escapes", MDB_DBG_SHELL }, { "kmod", "debug kernel module processing", MDB_DBG_KMOD }, { "walk", "debug walk callback processing", MDB_DBG_WALK }, { "umem", "debug memory management", MDB_DBG_UMEM }, { "dstk", "debug execution stack", MDB_DBG_DSTK }, { "tgt", "debug target backends", MDB_DBG_TGT }, { "psvc", "debug proc_service clients", MDB_DBG_PSVC }, { "proc", "debug libproc internals", MDB_DBG_PROC }, { "ctf", "debug libctf internals", MDB_DBG_CTF }, { "dpi", "debugger/PROM interface (kmdb only)", MDB_DBG_DPI }, { "kdi", "kernel/debugger interface (kmdb only)", MDB_DBG_KDI }, { "callb", "debug callback invocations", MDB_DBG_CALLB }, { "all", "set all debug modes", (uint_t)~MDB_DBG_HELP }, { "none", "unset all debug modes", 0 }, { NULL, 0 } }; static const char dbg_prefix[] = "mdb DEBUG: "; /*PRINTFLIKE2*/ void mdb_dprintf(uint_t mode, const char *format, ...) { if ((mdb.m_debug & mode) == mode && mdb.m_err != NULL) { va_list alist; mdb_iob_puts(mdb.m_err, dbg_prefix); va_start(alist, format); mdb_iob_vprintf(mdb.m_err, format, alist); va_end(alist); } } void mdb_dvprintf(uint_t mode, const char *format, va_list alist) { if ((mdb.m_debug & mode) == mode && format != NULL && *format != '\0' && mdb.m_err != NULL) { mdb_iob_puts(mdb.m_err, dbg_prefix); mdb_iob_vprintf(mdb.m_err, format, alist); if (format[strlen(format) - 1] != '\n') mdb_iob_nl(mdb.m_err); } } uint_t mdb_dstr2mode(const char *s) { const dbg_mode_t *mp; const char *name; char dstr[256]; uint_t bits = 0; if (s == NULL) return (0); (void) strncpy(dstr, s, sizeof (dstr)); dstr[sizeof (dstr) - 1] = '\0'; for (name = strtok(dstr, ","); name; name = strtok(NULL, ",")) { for (mp = dbg_modetab; mp->m_name != NULL; mp++) { if (strcmp(name, mp->m_name) == 0) { if (mp->m_bits != 0) bits |= mp->m_bits; else bits = 0; break; } } if (mp->m_name == NULL) warn("unknown debug option \"%s\"\n", name); } if (bits & MDB_DBG_HELP) { warn("Debugging tokens:\n"); for (mp = dbg_modetab; mp->m_name != NULL; mp++) warn("\t%s: %s\n", mp->m_name, mp->m_desc); } return (bits); } void mdb_dmode(uint_t bits) { int *libproc_debugp, *libctf_debugp; void (*rd_logp)(const int); if ((libproc_debugp = dlsym(RTLD_SELF, "_libproc_debug")) != NULL) *libproc_debugp = (bits & MDB_DBG_PROC) != 0; if ((libctf_debugp = dlsym(RTLD_SELF, "_libctf_debug")) != NULL) *libctf_debugp = (bits & MDB_DBG_CTF) != 0; if ((rd_logp = (void (*)())dlsym(RTLD_SELF, "rd_log")) != NULL) rd_logp((bits & MDB_DBG_PSVC) != 0); mdb_lex_debug(bits & MDB_DBG_PARSER); mdb.m_debug = bits; } int mdb_dassert(const char *expr, const char *file, int line) { fail("\"%s\", line %d: assertion failed: %s\n", file, line, expr); /*NOTREACHED*/ return (0); } /* * Function to convert mdb longjmp codes (see ) into a string for * debugging routines. */ const char * mdb_err2str(int err) { static const char *const errtab[] = { "0", "PARSE", "NOMEM", "PAGER", "SIGINT", "QUIT", "ASSERT", "API", "ABORT", "OUTPUT" }; static char buf[32]; if (err >= 0 && err < sizeof (errtab) / sizeof (errtab[0])) return (errtab[err]); (void) mdb_iob_snprintf(buf, sizeof (buf), "ERR#%d", err); return (buf); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_DEBUG_H #define _MDB_DEBUG_H #ifdef __cplusplus extern "C" { #endif #include #include #define MDB_DBG_CMDBUF 0x00000001 #define MDB_DBG_PARSER 0x00000002 #define MDB_DBG_HELP 0x00000004 #define MDB_DBG_MODULE 0x00000008 #define MDB_DBG_DCMD 0x00000010 #define MDB_DBG_ELF 0x00000020 #define MDB_DBG_MACH 0x00000040 #define MDB_DBG_SHELL 0x00000080 #define MDB_DBG_KMOD 0x00000100 #define MDB_DBG_WALK 0x00000200 #define MDB_DBG_UMEM 0x00000400 #define MDB_DBG_DSTK 0x00000800 #define MDB_DBG_TGT 0x00001000 #define MDB_DBG_PSVC 0x00002000 #define MDB_DBG_PROC 0x00004000 #define MDB_DBG_CTF 0x00008000 #define MDB_DBG_DPI 0x00010000 #define MDB_DBG_KDI 0x00020000 #define MDB_DBG_CALLB 0x00040000 #ifdef _MDB extern void mdb_dprintf(uint_t, const char *, ...); extern void mdb_dvprintf(uint_t, const char *, va_list); extern uint_t mdb_dstr2mode(const char *); extern void mdb_dmode(uint_t); extern const char *mdb_err2str(int); extern int mdb_dassert(const char *, const char *, int); #ifdef DEBUG #define ASSERT(x) ((void)((x) || mdb_dassert(#x, __FILE__, __LINE__))) #else #define ASSERT(x) #endif #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_DEBUG_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2001-2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Jason King */ /* * Copyright (c) 2019, Joyent, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include static void * mdb_dem_alloc(size_t len) { return (mdb_alloc(len, UM_SLEEP)); } static void mdb_dem_free(void *p, size_t len) { mdb_free(p, len); } static sysdem_ops_t mdb_dem_demops = { .alloc = mdb_dem_alloc, .free = mdb_dem_free }; mdb_demangler_t * mdb_dem_load(void) { mdb_demangler_t *dmp; dmp = mdb_alloc(sizeof (mdb_demangler_t), UM_SLEEP); dmp->dm_len = 0; dmp->dm_buf = NULL; dmp->dm_flags = MDB_DM_SCOPE; dmp->dm_lang = SYSDEM_LANG_AUTO; return (dmp); } void mdb_dem_unload(mdb_demangler_t *dmp) { mdb_free(dmp->dm_buf, dmp->dm_len); mdb_free(dmp, sizeof (mdb_demangler_t)); } static const char * mdb_dem_filter(mdb_demangler_t *dmp, const char *name) { static const char s_pref[] = "static "; static const char c_suff[] = " const"; static const char v_suff[] = " volatile"; /* * We process dm_dem, which skips the prefix in dm_buf (if any) */ size_t len = strlen(dmp->dm_dem); char *end = dmp->dm_dem + len; size_t resid; /* * If static, const, and volatile qualifiers should not be displayed, * rip all of them out of dmp->dm_dem. */ if (!(dmp->dm_flags & MDB_DM_QUAL)) { if (strncmp(dmp->dm_dem, s_pref, sizeof (s_pref) - 1) == 0) { bcopy(dmp->dm_dem + sizeof (s_pref) - 1, dmp->dm_dem, len - (sizeof (s_pref) - 1) + 1); end -= sizeof (s_pref) - 1; len -= sizeof (s_pref) - 1; } for (;;) { if (len > sizeof (c_suff) - 1 && strcmp(end - (sizeof (c_suff) - 1), c_suff) == 0) { end -= sizeof (c_suff) - 1; len -= sizeof (c_suff) - 1; *end = '\0'; continue; } if (len > sizeof (v_suff) - 1 && strcmp(end - (sizeof (v_suff) - 1), v_suff) == 0) { end -= sizeof (v_suff) - 1; len -= sizeof (v_suff) - 1; *end = '\0'; continue; } break; } } /* * If function arguments should not be displayed, remove everything * between the outermost set of parentheses in dmp->dm_dem. */ if (!(dmp->dm_flags & MDB_DM_FUNCARG)) { char *lp = strchr(dmp->dm_dem, '('); char *rp = strrchr(dmp->dm_dem, ')'); if (lp != NULL && rp != NULL) bcopy(rp + 1, lp, strlen(rp) + 1); } /* * If function scope specifiers should not be displayed, remove text * from the leftmost space to the rightmost colon prior to any paren. */ if (!(dmp->dm_flags & MDB_DM_SCOPE)) { char *c, *s, *lp = strchr(dmp->dm_dem, '('); if (lp != NULL) *lp = '\0'; c = strrchr(dmp->dm_dem, ':'); s = strchr(dmp->dm_dem, ' '); if (lp != NULL) *lp = '('; if (c != NULL) { if (s == NULL || s > c) bcopy(c + 1, dmp->dm_dem, strlen(c + 1) + 1); else bcopy(c + 1, s + 1, strlen(c + 1) + 1); } } len = strlen(dmp->dm_dem); /* recompute length of buffer */ /* * Compute bytes remaining */ resid = (dmp->dm_buf + dmp->dm_len) - (dmp->dm_dem + len); /* * If we want to append the mangled name as well and there is enough * space for "[]\0" and at least one character, append "["+name+"]". */ if ((dmp->dm_flags & MDB_DM_MANGLED) && resid > 3) { char *p = dmp->dm_dem + len; *p++ = '['; (void) strncpy(p, name, resid - 3); p[resid - 3] = '\0'; p += strlen(p); (void) strcpy(p, "]"); } /* * We return the whole string */ return (dmp->dm_buf); } /* * Take a name: (the foo`bar` is optional) * foo`bar`__mangled_ * and put: * foo`bar`demangled * into dmp->dm_buf. Point dmp->dm_dem to the beginning of the * demangled section of the result. */ static int mdb_dem_process(mdb_demangler_t *dmp, const char *name) { char *res = NULL; size_t reslen = 0; char *prefix = strrchr(name, '`'); size_t prefixlen = 0; if (prefix) { prefix++; /* the ` is part of the prefix */ prefixlen = prefix - name; } res = sysdemangle(name + prefixlen, dmp->dm_lang, &mdb_dem_demops); if (res == NULL) { /* * EINVAL indicates the name is not a properly mangled name * (or perhaps is truncated so it cannot be correctly * demangled) while ENOTSUP means sysdemangle could not * determine which language was used to mangle the name when * SYSDEM_LANG_AUTO is used (the name might not be mangled, * the name could be truncated enough to prevent determination * of the name, etc). * * Both are allowed/expected failure modes, so in both cases * do not emit a warning -- let the caller display the * original name. */ if (errno != EINVAL && errno != ENOTSUP) mdb_warn("Error while demangling"); return (-1); } reslen = (res != NULL) ? strlen(res) : 0; reslen += prefixlen; reslen += 1; if (reslen > dmp->dm_len) { mdb_free(dmp->dm_buf, dmp->dm_len); dmp->dm_buf = mdb_zalloc(reslen, UM_SLEEP); if (dmp->dm_buf == NULL) { dmp->dm_len = 0; mdb_warn("Unable to allocate memory for demangling"); return (-1); } dmp->dm_len = reslen; } if (prefixlen > 0) (void) strlcpy(dmp->dm_buf, name, prefixlen + 1); else *dmp->dm_buf = '\0'; if (res != NULL) { (void) strlcat(dmp->dm_buf, res, dmp->dm_len); mdb_dem_free(res, strlen(res) + 1); } /* * Save the position of the demangled string for mdb_dem_filter() */ dmp->dm_dem = dmp->dm_buf + prefixlen; return (0); } /* used by mdb_io.c:iob_addr2str */ const char * mdb_dem_convert(mdb_demangler_t *dmp, const char *name) { if (mdb_dem_process(dmp, name) != 0 || strcmp(dmp->dm_buf, name) == 0) return (name); return (mdb_dem_filter(dmp, name)); } /*ARGSUSED*/ int cmd_demangle(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_demangler_t *dmp = mdb.m_demangler; if (argc > 0) return (DCMD_USAGE); if (dmp != NULL && !(mdb.m_flags & MDB_FL_DEMANGLE)) { mdb_printf("C++ symbol demangling enabled\n"); mdb.m_flags |= MDB_FL_DEMANGLE; } else if (dmp == NULL) { if ((mdb.m_demangler = mdb_dem_load()) != NULL) { mdb_printf("C++ symbol demangling enabled\n"); mdb.m_flags |= MDB_FL_DEMANGLE; } else { mdb_warn("no memory to load C++ demangler"); mdb.m_flags &= ~MDB_FL_DEMANGLE; } } else { mdb_dem_unload(mdb.m_demangler); mdb.m_flags &= ~MDB_FL_DEMANGLE; mdb.m_demangler = NULL; mdb_printf("C++ symbol demangling disabled\n"); } return (DCMD_OK); } /*ARGSUSED*/ int cmd_demflags(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { static const char *const dm_desc[] = { "static/const/volatile member func qualifiers displayed", "scope resolution specifiers displayed", "function arguments displayed", "mangled name displayed" }; mdb_demangler_t *dmp = mdb.m_demangler; int i; if (argc > 0) return (DCMD_USAGE); if (dmp == NULL || !(mdb.m_flags & MDB_FL_DEMANGLE)) { mdb_warn("C++ demangling facility is currently disabled\n"); return (DCMD_ERR); } if (flags & DCMD_ADDRSPEC) dmp->dm_flags = ((uint_t)addr & MDB_DM_ALL); for (i = 0; i < sizeof (dm_desc) / sizeof (dm_desc[0]); i++) { mdb_printf("0x%x\t%s\t%s\n", 1 << i, (dmp->dm_flags & (1 << i)) ? "on" : "off", dm_desc[i]); } return (DCMD_OK); } /*ARGSUSED*/ int cmd_demstr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags & DCMD_ADDRSPEC) || argc == 0) return (DCMD_USAGE); if (mdb.m_demangler == NULL && (mdb.m_demangler = mdb_dem_load()) == NULL) { mdb_warn("failed to load demangler"); return (DCMD_ERR); } for (; argc != 0; argc--, argv++) { mdb_printf("%s == %s\n", argv->a_un.a_str, mdb_dem_convert(mdb.m_demangler, argv->a_un.a_str)); } return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2001-2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Jason King. */ #ifndef _MDB_DEMANGLE_H #define _MDB_DEMANGLE_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB #include #include #include #include typedef struct mdb_demangler { sysdem_lang_t dm_lang; /* language to demangle */ char *dm_buf; /* demangling buffer */ size_t dm_len; /* size of dm_buf in bytes */ char *dm_dem; /* start of demangled string (in buf) */ uint_t dm_flags; /* convert flags (see below) */ } mdb_demangler_t; #define MDB_DM_QUAL 0x1 /* show static/const/volatile */ #define MDB_DM_SCOPE 0x2 /* show function scope specifiers */ #define MDB_DM_FUNCARG 0x4 /* show function arguments */ #define MDB_DM_MANGLED 0x8 /* show mangled name */ #define MDB_DM_ALL 0xf /* mask of all valid flags */ extern mdb_demangler_t *mdb_dem_load(void); extern void mdb_dem_unload(mdb_demangler_t *); extern const char *mdb_dem_convert(mdb_demangler_t *, const char *); extern int cmd_demangle(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_demflags(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_demstr(uintptr_t, uint_t, int, const mdb_arg_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_DEMANGLE_H */ /* * 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 2011 Joyent, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include int mdb_dis_select(const char *name) { mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, name); if (v != NULL) { mdb.m_disasm = mdb_nv_get_cookie(v); return (0); } if (mdb.m_target == NULL) { if (mdb.m_defdisasm != NULL) strfree(mdb.m_defdisasm); mdb.m_defdisasm = strdup(name); return (0); } return (set_errno(EMDB_NODIS)); } mdb_disasm_t * mdb_dis_create(mdb_dis_ctor_f *ctor) { mdb_disasm_t *dp = mdb_zalloc(sizeof (mdb_disasm_t), UM_SLEEP); if ((dp->dis_module = mdb.m_lmod) == NULL) dp->dis_module = &mdb.m_rmod; if (ctor(dp) == 0) { mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, dp->dis_name); if (v != NULL) { dp->dis_ops->dis_destroy(dp); mdb_free(dp, sizeof (mdb_disasm_t)); (void) set_errno(EMDB_DISEXISTS); return (NULL); } (void) mdb_nv_insert(&mdb.m_disasms, dp->dis_name, NULL, (uintptr_t)dp, MDB_NV_RDONLY | MDB_NV_SILENT); if (mdb.m_disasm == NULL) { mdb.m_disasm = dp; } else if (mdb.m_defdisasm != NULL && strcmp(mdb.m_defdisasm, dp->dis_name) == 0) { mdb.m_disasm = dp; strfree(mdb.m_defdisasm); mdb.m_defdisasm = NULL; } return (dp); } mdb_free(dp, sizeof (mdb_disasm_t)); return (NULL); } void mdb_dis_destroy(mdb_disasm_t *dp) { mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, dp->dis_name); ASSERT(v != NULL); mdb_nv_remove(&mdb.m_disasms, v); dp->dis_ops->dis_destroy(dp); mdb_free(dp, sizeof (mdb_disasm_t)); if (mdb.m_disasm == dp) (void) mdb_dis_select("default"); } mdb_tgt_addr_t mdb_dis_ins2str(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, char *buf, size_t len, mdb_tgt_addr_t addr) { return (dp->dis_ops->dis_ins2str(dp, t, as, buf, len, addr)); } mdb_tgt_addr_t mdb_dis_previns(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint_t n) { return (dp->dis_ops->dis_previns(dp, t, as, addr, n)); } mdb_tgt_addr_t mdb_dis_nextins(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr) { return (dp->dis_ops->dis_nextins(dp, t, as, addr)); } /*ARGSUSED*/ int cmd_dismode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags & DCMD_ADDRSPEC) || argc > 1) return (DCMD_USAGE); if (argc != 0) { const char *name; if (argv->a_type == MDB_TYPE_STRING) name = argv->a_un.a_str; else name = numtostr(argv->a_un.a_val, 10, NTOS_UNSIGNED); if (mdb_dis_select(name) == -1) { warn("failed to set disassembly mode"); return (DCMD_ERR); } } mdb_printf("disassembly mode is %s (%s)\n", mdb.m_disasm->dis_name, mdb.m_disasm->dis_desc); return (DCMD_OK); } /*ARGSUSED*/ static int print_dis(mdb_var_t *v, void *ignore) { mdb_disasm_t *dp = mdb_nv_get_cookie(v); mdb_printf("%-24s - %s\n", dp->dis_name, dp->dis_desc); return (0); } /*ARGSUSED*/ int cmd_disasms(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); mdb_nv_sort_iter(&mdb.m_disasms, print_dis, NULL, UM_SLEEP | UM_GC); return (DCMD_OK); } /* * Generic libdisasm disassembler interfaces. */ #define DISBUFSZ 64 /* * Internal structure used by the read and lookup routines. */ typedef struct dis_buf { mdb_tgt_t *db_tgt; mdb_tgt_as_t db_as; mdb_tgt_addr_t db_addr; mdb_tgt_addr_t db_nextaddr; uchar_t db_buf[DISBUFSZ]; ssize_t db_bufsize; boolean_t db_readerr; } dis_buf_t; /* * Disassembler support routine for lookup up an address. Rely on mdb's "%a" * qualifier to convert the address to a symbol. */ /*ARGSUSED*/ static int libdisasm_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start, size_t *len) { char c; GElf_Sym sym; if (buf != NULL) { #ifdef __sparc uint32_t instr[3]; uint32_t dtrace_id; /* * On SPARC, DTrace FBT trampoline entries have a sethi/or pair * that indicates the dtrace probe id; this may appear as the * first two instructions or one instruction into the * trampoline. */ if (mdb_vread(instr, sizeof (instr), (uintptr_t)addr) == sizeof (instr)) { if ((instr[0] & 0xfffc0000) == 0x11000000 && (instr[1] & 0xffffe000) == 0x90122000) { dtrace_id = (instr[0] << 10) | (instr[1] & 0x1fff); (void) mdb_snprintf(buf, sizeof (buf), "dt=%#x", dtrace_id); goto out; } else if ((instr[1] & 0xfffc0000) == 0x11000000 && (instr[2] & 0xffffe000) == 0x90122000) { dtrace_id = (instr[1] << 10) | (instr[2] & 0x1fff); (void) mdb_snprintf(buf, sizeof (buf), "dt=%#x", dtrace_id); goto out; } } #endif (void) mdb_snprintf(buf, buflen, "%a", (uintptr_t)addr); } #ifdef __sparc out: #endif if (mdb_lookup_by_addr(addr, MDB_SYM_FUZZY, &c, 1, &sym) < 0) return (-1); if (start != NULL) *start = sym.st_value; if (len != NULL) *len = sym.st_size; return (0); } /* * Disassembler support routine for reading from the target. Rather than having * to read one byte at a time, we read from the address space in chunks. If the * current address doesn't lie within our buffer range, we read in the chunk * starting from the given address. */ static int libdisasm_read(void *data, uint64_t pc, void *buf, size_t buflen) { dis_buf_t *db = data; size_t offset; size_t len; if (pc - db->db_addr >= db->db_bufsize) { if (mdb_tgt_aread(db->db_tgt, db->db_as, db->db_buf, sizeof (db->db_buf), pc) != -1) { db->db_bufsize = sizeof (db->db_buf); } else if (mdb_tgt_aread(db->db_tgt, db->db_as, db->db_buf, buflen, pc) != -1) { db->db_bufsize = buflen; } else { if (!db->db_readerr) mdb_warn("failed to read instruction at %#lr", (uintptr_t)pc); db->db_readerr = B_TRUE; return (-1); } db->db_addr = pc; } offset = pc - db->db_addr; len = MIN(buflen, db->db_bufsize - offset); (void) memcpy(buf, (char *)db->db_buf + offset, len); db->db_nextaddr = pc + len; return (len); } static mdb_tgt_addr_t libdisasm_ins2str(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, char *buf, size_t len, mdb_tgt_addr_t pc) { dis_handle_t *dhp = dp->dis_data; dis_buf_t db = { 0 }; const char *p; /* * Set the libdisasm data to point to our buffer. This will be * passed as the first argument to the lookup and read functions. */ db.db_tgt = t; db.db_as = as; dis_set_data(dhp, &db); if ((p = mdb_tgt_name(t)) != NULL && strcmp(p, "proc") == 0) { /* check for ELF ET_REL type; turn on NOIMMSYM if so */ GElf_Ehdr leh; if (mdb_tgt_getxdata(t, "ehdr", &leh, sizeof (leh)) != -1 && leh.e_type == ET_REL) { dis_flags_set(dhp, DIS_NOIMMSYM); } else { dis_flags_clear(dhp, DIS_NOIMMSYM); } } /* * Attempt to disassemble the instruction. If this fails because of an * unknown opcode, drive on anyway. If it fails because we couldn't * read from the target, bail out immediately. */ if (dis_disassemble(dhp, pc, buf, len) != 0) (void) mdb_snprintf(buf, len, "***ERROR--unknown op code***"); if (db.db_readerr) return (pc); /* * Return the updated location */ return (db.db_nextaddr); } static mdb_tgt_addr_t libdisasm_previns(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t pc, uint_t n) { dis_handle_t *dhp = dp->dis_data; dis_buf_t db = { 0 }; /* * Set the libdisasm data to point to our buffer. This will be * passed as the first argument to the lookup and read functions. * We set 'readerr' to B_TRUE to turn off the mdb_warn() in * libdisasm_read, because the code works by probing backwards until a * valid address is found. */ db.db_tgt = t; db.db_as = as; db.db_readerr = B_TRUE; dis_set_data(dhp, &db); return (dis_previnstr(dhp, pc, n)); } /*ARGSUSED*/ static mdb_tgt_addr_t libdisasm_nextins(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t pc) { mdb_tgt_addr_t npc; char c; if ((npc = libdisasm_ins2str(dp, t, as, &c, 1, pc)) == pc) return (pc); /* * Probe the address to make sure we can read something from it - we * want the address we return to actually contain something. */ if (mdb_tgt_aread(t, as, &c, 1, npc) != 1) return (pc); return (npc); } static void libdisasm_destroy(mdb_disasm_t *dp) { dis_handle_t *dhp = dp->dis_data; dis_handle_destroy(dhp); } static const mdb_dis_ops_t libdisasm_ops = { .dis_destroy = libdisasm_destroy, .dis_ins2str = libdisasm_ins2str, .dis_previns = libdisasm_previns, .dis_nextins = libdisasm_nextins }; /* * Generic function for creating a libdisasm-backed disassembler. Creates an * MDB disassembler with the given name backed by libdis with the given flags. */ static int libdisasm_create(mdb_disasm_t *dp, const char *name, const char *desc, int flags) { if ((dp->dis_data = dis_handle_create(flags, NULL, libdisasm_lookup, libdisasm_read)) == NULL) return (-1); dp->dis_name = name; dp->dis_ops = &libdisasm_ops; dp->dis_desc = desc; return (0); } #if defined(__i386) || defined(__amd64) static int ia16_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "ia16", "Intel 16-bit disassembler", DIS_X86_SIZE16)); } static int ia32_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "ia32", "Intel 32-bit disassembler", DIS_X86_SIZE32)); } #endif #if defined(__amd64) static int amd64_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "amd64", "AMD64 and IA32e 64-bit disassembler", DIS_X86_SIZE64)); } #endif #if defined(__sparc) static int sparc1_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "1", "SPARC-v8 disassembler", DIS_SPARC_V8)); } static int sparc2_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "2", "SPARC-v9 disassembler", DIS_SPARC_V9)); } static int sparc4_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "4", "UltraSPARC1-v9 disassembler", DIS_SPARC_V9 | DIS_SPARC_V9_SGI)); } static int sparcv8_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "v8", "SPARC-v8 disassembler", DIS_SPARC_V8)); } static int sparcv9_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "v9", "SPARC-v9 disassembler", DIS_SPARC_V9)); } static int sparcv9plus_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "v9plus", "UltraSPARC1-v9 disassembler", DIS_SPARC_V9 | DIS_SPARC_V9_SGI)); } #endif /*ARGSUSED*/ static void defdis_destroy(mdb_disasm_t *dp) { /* Nothing to do here */ } /*ARGSUSED*/ static mdb_tgt_addr_t defdis_ins2str(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, char *buf, size_t len, mdb_tgt_addr_t addr) { return (addr); } /*ARGSUSED*/ static mdb_tgt_addr_t defdis_previns(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, uint_t n) { return (addr); } /*ARGSUSED*/ static mdb_tgt_addr_t defdis_nextins(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr) { return (addr); } static const mdb_dis_ops_t defdis_ops = { .dis_destroy = defdis_destroy, .dis_ins2str = defdis_ins2str, .dis_previns = defdis_previns, .dis_nextins = defdis_nextins, }; static int defdis_create(mdb_disasm_t *dp) { dp->dis_name = "default"; dp->dis_desc = "default no-op disassembler"; dp->dis_ops = &defdis_ops; return (0); } mdb_dis_ctor_f *const mdb_dis_builtins[] = { defdis_create, #if defined(__amd64) ia16_create, ia32_create, amd64_create, #elif defined(__i386) ia16_create, ia32_create, #elif defined(__sparc) sparc1_create, sparc2_create, sparc4_create, sparcv8_create, sparcv9_create, sparcv9plus_create, #endif NULL }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_DISASM_H #define _MDB_DISASM_H #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB /* * Forward declaration of the disassembler structure: the internals are defined * in mdb_disasm_impl.h and is opaque with respect to callers of this interface. */ struct mdb_disasm; typedef struct mdb_disasm mdb_disasm_t; /* * Disassemblers are created by calling mdb_dis_create() with a disassembler * constructor function. A constructed disassembler can be selected (made * the current disassembler) by invoking mdb_dis_select(). */ typedef int mdb_dis_ctor_f(mdb_disasm_t *); extern int mdb_dis_select(const char *); extern mdb_disasm_t *mdb_dis_create(mdb_dis_ctor_f *); extern void mdb_dis_destroy(mdb_disasm_t *); /* * Disassembler operations - instruction-to-string and backstep. */ extern mdb_tgt_addr_t mdb_dis_ins2str(mdb_disasm_t *, mdb_tgt_t *, mdb_tgt_as_t, char *, size_t, mdb_tgt_addr_t); extern mdb_tgt_addr_t mdb_dis_previns(mdb_disasm_t *, mdb_tgt_t *, mdb_tgt_as_t, mdb_tgt_addr_t, uint_t); extern mdb_tgt_addr_t mdb_dis_nextins(mdb_disasm_t *, mdb_tgt_t *, mdb_tgt_as_t, mdb_tgt_addr_t); /* * Builtin dcmds for selecting and listing disassemblers: */ extern int cmd_dismode(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_disasms(uintptr_t, uint_t, int, const mdb_arg_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_DISASM_H */ /* * 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. */ #ifndef _MDB_DISASM_IMPL_H #define _MDB_DISASM_IMPL_H /* * Disassembler Implementation * * Each disassembler provides a string name (for selection with $V or -V), * a brief description, and the set of operations defined in mdb_dis_ops_t. * Currently the interface defined here is very primitive, but we hope to * greatly enhance it in the future if we have a two-pass disassembler. */ #include #include #ifdef __cplusplus extern "C" { #endif typedef struct mdb_dis_ops { void (*dis_destroy)(mdb_disasm_t *); mdb_tgt_addr_t (*dis_ins2str)(mdb_disasm_t *, mdb_tgt_t *, mdb_tgt_as_t, char *, size_t, mdb_tgt_addr_t); mdb_tgt_addr_t (*dis_previns)(mdb_disasm_t *, mdb_tgt_t *, mdb_tgt_as_t, mdb_tgt_addr_t, uint_t); mdb_tgt_addr_t (*dis_nextins)(mdb_disasm_t *, mdb_tgt_t *, mdb_tgt_as_t, mdb_tgt_addr_t); } mdb_dis_ops_t; struct mdb_disasm { const char *dis_name; /* Disassembler name */ const char *dis_desc; /* Brief description */ mdb_module_t *dis_module; /* Backpointer to containing module */ const mdb_dis_ops_t *dis_ops; /* Pointer to ops vector */ void *dis_data; /* Private storage */ }; #ifdef __cplusplus } #endif #endif /* _MDB_DISASM_IMPL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #define DUMP_PARAGRAPH 16 #define DUMP_WIDTH(x) (DUMP_PARAGRAPH * ((((x) >> 16) & 0xf) + 1)) #define DUMP_GROUP(x) ((((x) >> 20) & 0xff) + 1) #define DUMP_MAXWIDTH DUMP_WIDTH(MDB_DUMP_WIDTH(0x10)) /* * This is the implementation of mdb's generic hexdump facility (though * not named such in case we decide to add support for other radices). * While it is possible to call mdb_dump_internal directly, it is * recommended that you use mdb_dumpptr or mdb_dump64 instead. */ /* * Output the header for the dump. pad is the width of the address * field, and offset is the index of the byte that we want highlighted. * If the output isn't MDB_DUMP_ALIGNed, we use offset to adjust the * labels to reflect the true least significant address nibble. */ static void mdb_dump_header(int flags, int pad, int offset) { int nalign = !(flags & MDB_DUMP_ALIGN); int group = DUMP_GROUP(flags); int width = DUMP_WIDTH(flags); int i; mdb_printf("%*s ", pad, ""); for (i = 0; i < width; i++) { if (!(i % group)) mdb_printf((group == 1 && i && !(i % 8)) ? " " : " "); if (i == offset && !nalign) mdb_printf("\\/"); else mdb_printf("%2x", (i + (nalign * offset)) & 0xf); } if (flags & MDB_DUMP_ASCII) { mdb_printf(" "); for (i = 0; i < width; i++) { if (i == offset && !nalign) mdb_printf("v"); else mdb_printf("%x", (i + (nalign * offset)) & 0xf); } } mdb_printf("\n"); } /* * Output a line of data. pad is as defined above. A non-zero lmargin * and/or rmargin indicate a set of bytes that shouldn't be printed. */ static void mdb_dump_data(uint64_t addr, uchar_t *buf, int flags, int pad, int lmargin, int rmargin) { uchar_t abuf[DUMP_MAXWIDTH + 1]; int group = DUMP_GROUP(flags); int width = DUMP_WIDTH(flags); int i; #ifdef _LITTLE_ENDIAN int flip = FALSE; if (flags & MDB_DUMP_ENDIAN) flip = TRUE; #endif mdb_printf("%0*llx: ", pad, addr); for (i = 0; i < width; i++) { if (!(i % group)) mdb_printf((group == 1 && i && !(i % 8)) ? " " : " "); if (i < lmargin || (width - i) <= rmargin) { mdb_printf(" "); #ifdef _LITTLE_ENDIAN } else if (flip) { int j = group * ((i / group) + 1) - (i % group) - 1; mdb_printf("%02x", buf[j]); #endif } else { mdb_printf("%02x", buf[i]); } } if (flags & MDB_DUMP_ASCII) { for (i = 0; i < width; i++) if (i < lmargin || (width - i) <= rmargin) abuf[i] = ' '; else if (buf[i] < ' ' || buf[i] > '~') abuf[i] = '.'; else abuf[i] = buf[i]; abuf[width] = '\0'; mdb_printf(" %s", abuf); } mdb_printf("\n"); } /* * Given an address and a length, compute the number of characters * needed to display addresses within that range. */ static int mdb_dump_pad(uint64_t addr, uint64_t len, int flags, int bytes) { uint64_t x; int bits; if (flags & MDB_DUMP_PEDANT) { /* * Assume full width pointers */ bits = NBBY * bytes; } else { /* * Vary width based on address and length, but first * check to see if the address is relevant. */ if (len > 1 || (addr && len == 1)) len--; if (flags & MDB_DUMP_RELATIVE) x = len; else x = len + addr; bits = 0; while (x) { bits++; x >>= 1; } } return ((bits + 3) / 4); } /* * The main dump routine, called by mdb_dump64 and (indirectly) by * mdb_dumpptr. Arguments: * addr - the address to start dumping at * len - the amount of data to dump * flags - to tune operation (see mdb_modapi.h) * func - callback function used to obtain data * arg - argument to pass to callback function * bytes - size of pointer type */ int mdb_dump_internal(uint64_t addr, uint64_t len, int flags, mdb_dump64_cb_t func, void *arg, int bytes) { uchar_t buffers[2][DUMP_MAXWIDTH]; uchar_t *buf, *pbuf; uint64_t i; ssize_t j; uint64_t addrmax; uint64_t offset; /* bytes between first position and addr */ uint64_t reqlen = len; /* requested length */ int l, r; /* left and right margins */ int pskip; /* previous line was skipped */ int pvalid; /* previous line was valid (we may skip) */ int bread, bwanted; /* used to handle partial reads */ int pad, n; int group, width; int err = 0; addrmax = (1LL << (bytes * NBBY - 1)) - 1 + (1LL << (bytes * NBBY - 1)); /* * Ensure that len doesn't wrap around the end of addressable * memory. Note that because we take an address and a length, * it isn't possible to dump from 0 to UINT64_MAX if * MDB_DUMP_TRIM is set. */ if (len && (len - 1 > addrmax - addr)) { len = addrmax - addr; if (addr || (addrmax < UINT64_MAX)) len++; } /* * If a) the grouping isn't a power of two, or * b) the display width is not evenly divisible by the grouping * we ignore the specified grouping (and default to 4). */ group = DUMP_GROUP(flags); width = DUMP_WIDTH(flags); if (((group - 1) & group) || (width % group)) { group = 4; flags = (flags & 0xfffff) | MDB_DUMP_GROUP(group); } /* * If we are reordering bytes to adjust for endianness, turn * off text output, headers, and alignment to cut down on the * number of special cases (and confusing output). For * correctness, we will continue to observe MDB_DUMP_TRIM, but * will truncate output if the specified length isn't a * multiple of the grouping. */ if (flags & MDB_DUMP_ENDIAN) { flags &= ~(MDB_DUMP_ALIGN | MDB_DUMP_HEADER | MDB_DUMP_ASCII); if (flags & MDB_DUMP_TRIM) len -= len % group; } /* * If we are interested in seeing the data indexed relative to * the starting location, paragraph alignment is irrelevant. * The left margin will always be 0. */ if (flags & MDB_DUMP_RELATIVE) { flags &= ~MDB_DUMP_ALIGN; l = 0; } else { l = addr % DUMP_PARAGRAPH; } /* * Compute the width of our addresses, and adjust our starting * point based on the address and the state of the alignment * flag. */ pad = mdb_dump_pad(addr, len, flags, bytes); if (flags & MDB_DUMP_ALIGN) { len += l; addr -= l; offset = l; } else { offset = 0; } /* * Display the header (if appropriate), using the left margin * to determine what our column header offset should be. */ if (flags & MDB_DUMP_HEADER) mdb_dump_header(flags, pad, l); /* * If we aren't trimming and aligning the output, the left * margin is now irrelevant and should be zeroed. */ if (!(flags & MDB_DUMP_TRIM) || !(flags & MDB_DUMP_ALIGN)) l = 0; /* * We haven't skipped the previous line, it isn't valid to skip * the current line, and we use buffer 0 first. lint doesn't * realize that this implies pbuf won't be accessed until after * it is set, so we explicitly initialize that here, too. */ pskip = pvalid = FALSE; pbuf = NULL; n = 0; r = 0; for (i = 0; i < len && r == 0; i += width) { /* * Select the current buffer. */ buf = buffers[n]; /* * We have a right margin only if we are on the last * line and either (1) MDB_DUMP_TRIM is set or (2) our * untrimmed output would require reading past the end * of addressable memory. In either case, we clear * pvalid since we don't want to skip the last line. */ if ((uint64_t)width >= len - i) { pvalid = FALSE; if (flags & MDB_DUMP_TRIM) r = width - (len - i); if ((uint64_t)width - 1 > addrmax - (addr + i)) { int nr = width - (addrmax - (addr + i)) - 1; r = MAX(r, nr); } } /* * Read data into the current buffer, obeying the left * and right margins. * * We handle read(2)-style partial results by * repeatedly calling the callback until we fill the * buffer, we get a 0 (end of file), or we get a -1 * (error). We take care to never read the same data * twice, though. * * mdb(1)-style partial results (i.e. EMDB_PARTIAL) are * treated like any other error. If more exotic * handling is desired, the caller is free to wrap * their callback with an auxiliary function. See * mdb_dumpptr and mdb_dump64 for examples of this. */ bread = l; bwanted = width - r; while (bread < bwanted) { j = func(buf + bread, bwanted - bread, addr + i + bread, arg); if (j <= 0) { if (i + bread < offset) { l++; j = 1; } else { r += bwanted - bread; pvalid = FALSE; if (j == -1) err = errno; if (bread == l) { i += width; goto out; } break; } } bread += j; } /* * If we are eliminating repeated lines, AND it is * valid to eliminate this line, AND the current line * is the same as the previous line, don't print the * current line. If we didn't skip the previous line, * print an asterisk and set the previous-line-skipped * flag. * * Otherwise, print the line and clear the * previous-line-skipped flag. */ if ((flags & MDB_DUMP_SQUISH) && pvalid && (memcmp(buf, pbuf, width) == 0)) { if (!pskip) { mdb_printf("*\n"); pskip = TRUE; } } else { if (flags & MDB_DUMP_RELATIVE) mdb_dump_data(i, buf, flags, pad, l, r); else mdb_dump_data(addr + i, buf, flags, pad, l, r); pskip = FALSE; } /* * If we have a non-zero left margin then we don't have * a full buffer of data and we shouldn't try to skip * the next line. It doesn't matter if the right * margin is non-zero since we'll fall out of the loop. */ if (!l) pvalid = TRUE; /* * Swap buffers, and zero the left margin. */ n = (n + 1) % 2; pbuf = buf; l = 0; } out: /* * If we successfully dumped everything, update . to be the * address following that of the last byte requested. */ if (i - r - offset >= reqlen) { if (flags & MDB_DUMP_NEWDOT) mdb_set_dot(addr + offset + reqlen); } else if (err) { errno = err; mdb_warn("failed to read data at %#llx", addr + i - r); return (-1); } return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2001-2002 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_DUMP_H #define _MDB_DUMP_H #include #ifdef __cplusplus extern "C" { #endif extern int mdb_dump_internal(uint64_t, uint64_t, int, mdb_dump64_cb_t, void *, int); #ifdef __cplusplus } #endif #endif /* _MDB_DUMP_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include static const char *const _mdb_errlist[] = { "unknown symbol name", /* EMDB_NOSYM */ "unknown object file name", /* EMDB_NOOBJ */ "no mapping for address", /* EMDB_NOMAP */ "unknown dcmd name", /* EMDB_NODCMD */ "unknown walk name", /* EMDB_NOWALK */ "dcmd name already in use", /* EMDB_DCMDEXISTS */ "walk name already in use", /* EMDB_WALKEXISTS */ "no support for platform", /* EMDB_NOPLAT */ "no process active", /* EMDB_NOPROC */ "specified name is too long", /* EMDB_NAME2BIG */ "specified name contains illegal characters", /* EMDB_NAMEBAD */ "failed to allocate needed memory", /* EMDB_ALLOC */ "specified module is not loaded", /* EMDB_NOMOD */ "cannot unload built-in module", /* EMDB_BUILTINMOD */ "no walk is currently active", /* EMDB_NOWCB */ "invalid walk state argument", /* EMDB_BADWCB */ "walker does not accept starting address", /* EMDB_NOWALKLOC */ "walker requires starting address", /* EMDB_NOWALKGLOB */ "failed to initialize walk", /* EMDB_WALKINIT */ "walker cannot be layered on itself", /* EMDB_WALKLOOP */ "i/o stream is read-only", /* EMDB_IORO */ "i/o stream is write-only", /* EMDB_IOWO */ "no symbol corresponds to address", /* EMDB_NOSYMADDR */ "unknown disassembler name", /* EMDB_NODIS */ "disassembler name already in use", /* EMDB_DISEXISTS */ "no such software event specifier", /* EMDB_NOSESPEC */ "no such xdata available", /* EMDB_NOXD */ "xdata name already in use", /* EMDB_XDEXISTS */ "operation not supported by target", /* EMDB_TGTNOTSUP */ "target is not open for writing", /* EMDB_TGTRDONLY */ "invalid register name", /* EMDB_BADREG */ "no register set available for thread", /* EMDB_NOREGS */ "stack address is not properly aligned", /* EMDB_STKALIGN */ "no executable file is open", /* EMDB_NOEXEC */ "failed to evaluate command", /* EMDB_EVAL */ "command cancelled by user", /* EMDB_CANCEL */ "only %lu of %lu bytes could be read", /* EMDB_PARTIAL */ "dcmd failed", /* EMDB_DCFAIL */ "improper dcmd usage", /* EMDB_DCUSAGE */ "target error", /* EMDB_TGT */ "invalid system call number", /* EMDB_BADSYSNUM */ "invalid signal number", /* EMDB_BADSIGNUM */ "invalid fault number", /* EMDB_BADFLTNUM */ "target is currently executing", /* EMDB_TGTBUSY */ "target has completed execution", /* EMDB_TGTZOMB */ "target is a core file", /* EMDB_TGTCORE */ "debugger lost control of target", /* EMDB_TGTLOST */ "libthread_db call failed unexpectedly", /* EMDB_TDB */ "failed to dlopen library", /* EMDB_RTLD */ "librtld_db call failed unexpectedly", /* EMDB_RTLD_DB */ "runtime linker data not available", /* EMDB_NORTLD */ "invalid thread identifier", /* EMDB_NOTHREAD */ "event specifier disabled", /* EMDB_SPECDIS */ "unknown link map id", /* EMDB_NOLMID */ "failed to determine return address", /* EMDB_NORETADDR */ "watchpoint size exceeds address space limit", /* EMDB_WPRANGE */ "conflict with existing watchpoint", /* EMDB_WPDUP */ "address not aligned on an instruction boundary", /* EMDB_BPALIGN */ "library is missing demangler entry point", /* EMDB_NODEM */ "cannot read past current end of file", /* EMDB_EOF */ "no symbolic debug information available for module", /* EMDB_NOCTF */ "libctf call failed unexpectedly", /* EMDB_CTF */ "thread local storage has not yet been allocated", /* EMDB_TLS */ "object does not support thread local storage", /* EMDB_NOTLS */ "no such member of structure or union", /* EMDB_CTFNOMEMB */ "inappropriate context for action", /* EMDB_CTX */ "module incompatible with target", /* EMDB_INCOMPAT */ "operation not supported by target on this platform", /* EMDB_TGTHWNOTSUP */ "kmdb is not loaded", /* EMDB_KINACTIVE */ "kmdb is loading", /* EMDB_KACTIVATING */ "kmdb is already loaded", /* EMDB_KACTIVE */ "kmdb is unloading", /* EMDB_KDEACTIVATING */ "kmdb could not be loaded", /* EMDB_KNOLOAD */ "boot-loaded kmdb cannot be unloaded", /* EMDB_KNOUNLOAD */ "too many enabled watchpoints for this machine", /* EMDB_WPTOOMANY */ "DTrace is active", /* EMDB_DTACTIVE */ "boot-loaded module cannot be unloaded", /* EMDB_KMODNOUNLOAD */ "stack frame pointer is invalid", /* EMDB_STKFRAME */ "unexpected short write" /* EMDB_SHORTWRITE */ }; static const int _mdb_nerr = sizeof (_mdb_errlist) / sizeof (_mdb_errlist[0]); static size_t errno_rbytes; /* EMDB_PARTIAL actual bytes read */ static size_t errno_nbytes; /* EMDB_PARTIAL total bytes requested */ static int errno_libctf; /* EMDB_CTF underlying error code */ #ifndef _KMDB static int errno_rtld_db; /* EMDB_RTLD_DB underlying error code */ #endif const char * mdb_strerror(int err) { static char buf[256]; const char *str; if (err >= EMDB_BASE && (err - EMDB_BASE) < _mdb_nerr) str = _mdb_errlist[err - EMDB_BASE]; else str = strerror(err); switch (err) { case EMDB_PARTIAL: (void) mdb_iob_snprintf(buf, sizeof (buf), str, errno_rbytes, errno_nbytes); str = buf; break; #ifndef _KMDB case EMDB_RTLD_DB: if (rd_errstr(errno_rtld_db) != NULL) str = rd_errstr(errno_rtld_db); break; #endif case EMDB_CTF: if (ctf_errmsg(errno_libctf) != NULL) str = ctf_errmsg(errno_libctf); break; } return (str ? str : "unknown error"); } void vwarn(const char *format, va_list alist) { int err = errno; mdb_iob_printf(mdb.m_err, "%s: ", mdb.m_pname); mdb_iob_vprintf(mdb.m_err, format, alist); if (strchr(format, '\n') == NULL) mdb_iob_printf(mdb.m_err, ": %s\n", mdb_strerror(err)); } void vdie(const char *format, va_list alist) { vwarn(format, alist); mdb_destroy(); exit(1); } void vfail(const char *format, va_list alist) { extern const char *volatile _mdb_abort_str; static char buf[256]; static int nfail; if (_mdb_abort_str == NULL) { _mdb_abort_str = buf; /* Do this first so we don't recurse */ (void) mdb_iob_vsnprintf(buf, sizeof (buf), format, alist); nfail = 1; } /* * We'll try to print failure messages twice. Any more than that, * and we're probably hitting an assertion or some other problem in * the printing routines, and will recurse until we run out of stack. */ if (nfail++ < 3) { mdb_iob_printf(mdb.m_err, "%s ABORT: ", mdb.m_pname); mdb_iob_vprintf(mdb.m_err, format, alist); mdb_iob_flush(mdb.m_err); (void) mdb_signal_blockall(); (void) mdb_signal_raise(SIGABRT); (void) mdb_signal_unblock(SIGABRT); } exit(1); } /*PRINTFLIKE1*/ void warn(const char *format, ...) { va_list alist; va_start(alist, format); vwarn(format, alist); va_end(alist); } /*PRINTFLIKE1*/ void die(const char *format, ...) { va_list alist; va_start(alist, format); vdie(format, alist); va_end(alist); } /*PRINTFLIKE1*/ void fail(const char *format, ...) { va_list alist; va_start(alist, format); vfail(format, alist); va_end(alist); } int set_errbytes(size_t rbytes, size_t nbytes) { errno_rbytes = rbytes; errno_nbytes = nbytes; errno = EMDB_PARTIAL; return (-1); } int set_errno(int err) { errno = err; return (-1); } int ctf_to_errno(int err) { errno_libctf = err; return (EMDB_CTF); } #ifndef _KMDB /* * The libthread_db interface is a superfund site and provides no strerror * equivalent for us to call: we try to provide some sensible handling for its * garbage bin of error return codes here. First of all, we don't bother * interpreting all of the possibilities, since many of them aren't even used * in the implementation anymore. We try to map thread_db errors we may see * to UNIX errnos or mdb errnos as appropriate. */ int tdb_to_errno(int err) { switch (err) { case TD_OK: case TD_PARTIALREG: return (0); case TD_NOCAPAB: return (ENOTSUP); case TD_BADPH: case TD_BADTH: case TD_BADSH: case TD_BADTA: case TD_BADKEY: case TD_NOEVENT: return (EINVAL); case TD_NOFPREGS: case TD_NOXREGS: return (EMDB_NOREGS); case TD_NOTHR: return (EMDB_NOTHREAD); case TD_MALLOC: return (EMDB_ALLOC); case TD_TLSDEFER: return (EMDB_TLS); case TD_NOTLS: return (EMDB_NOTLS); case TD_DBERR: case TD_ERR: default: return (EMDB_TDB); } } int rdb_to_errno(int err) { errno_rtld_db = err; return (EMDB_RTLD_DB); } #endif /* _KMDB */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_ERR_H #define _MDB_ERR_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern const char *mdb_strerror(int); extern void vwarn(const char *, va_list); extern void vdie(const char *, va_list); extern void vfail(const char *, va_list); extern void warn(const char *, ...); extern void die(const char *, ...); extern void fail(const char *, ...); extern int set_errbytes(size_t, size_t); extern int set_errno(int); extern int ctf_to_errno(int); #ifndef _KMDB extern int tdb_to_errno(int); extern int rdb_to_errno(int); #endif #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_ERR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_ERRNO_H #define _MDB_ERRNO_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB #define EMDB_BASE 1000 /* Base value for mdb errnos */ enum { EMDB_NOSYM = EMDB_BASE, /* Symbol not found */ EMDB_NOOBJ, /* Object file not found */ EMDB_NOMAP, /* No mapping for address */ EMDB_NODCMD, /* Dcmd not found */ EMDB_NOWALK, /* Walk not found */ EMDB_DCMDEXISTS, /* Dcmd already exists */ EMDB_WALKEXISTS, /* Walk already exists */ EMDB_NOPLAT, /* No platform support */ EMDB_NOPROC, /* No process created yet */ EMDB_NAME2BIG, /* Name is too long */ EMDB_NAMEBAD, /* Name is invalid */ EMDB_ALLOC, /* Failed to allocate memory */ EMDB_NOMOD, /* Module not found */ EMDB_BUILTINMOD, /* Cannot unload builtin mod */ EMDB_NOWCB, /* No walk is active */ EMDB_BADWCB, /* Invalid walk state */ EMDB_NOWALKLOC, /* Walker doesn't accept addr */ EMDB_NOWALKGLOB, /* Walker requires addr */ EMDB_WALKINIT, /* Walker init failed */ EMDB_WALKLOOP, /* Walker layering loop */ EMDB_IORO, /* I/O stream is read-only */ EMDB_IOWO, /* I/O stream is write-only */ EMDB_NOSYMADDR, /* No symbol for address */ EMDB_NODIS, /* Disassembler not found */ EMDB_DISEXISTS, /* Disassembler exists */ EMDB_NOSESPEC, /* No software event spec */ EMDB_NOXD, /* No such xdata */ EMDB_XDEXISTS, /* Xdata name already exists */ EMDB_TGTNOTSUP, /* Op not supported by tgt */ EMDB_TGTRDONLY, /* Tgt not open for writing */ EMDB_BADREG, /* Invalid register name */ EMDB_NOREGS, /* No registers for thread */ EMDB_STKALIGN, /* Bad stack pointer align */ EMDB_NOEXEC, /* No executable file open */ EMDB_EVAL, /* Failed to mdb_eval() */ EMDB_CANCEL, /* Command cancelled by user */ EMDB_PARTIAL, /* Partial read occurred */ EMDB_DCFAIL, /* Dcmd failed */ EMDB_DCUSAGE, /* Dcmd usage error */ EMDB_TGT, /* Internal target error */ EMDB_BADSYSNUM, /* Invalid system call code */ EMDB_BADSIGNUM, /* Invalid signal number */ EMDB_BADFLTNUM, /* Invalid fault number */ EMDB_TGTBUSY, /* Target is busy executing */ EMDB_TGTZOMB, /* Target is a zombie */ EMDB_TGTCORE, /* Target is a core file */ EMDB_TGTLOST, /* Target is lost to mdb */ EMDB_TDB, /* libthread_db error */ EMDB_RTLD, /* libdl error */ EMDB_RTLD_DB, /* librtld_db error */ EMDB_NORTLD, /* no librtld_db */ EMDB_NOTHREAD, /* Invalid thread identifier */ EMDB_SPECDIS, /* Event specifier disabled */ EMDB_NOLMID, /* Link map not found */ EMDB_NORETADDR, /* No return address found */ EMDB_WPRANGE, /* Watchpoint size overflow */ EMDB_WPDUP, /* Watchpoint duplicate */ EMDB_BPALIGN, /* Breakpoint alignment err */ EMDB_NODEM, /* Bad demangler library */ EMDB_EOF, /* Read failed at EOF */ EMDB_NOCTF, /* No CTF data for module */ EMDB_CTF, /* libctf error */ EMDB_TLS, /* TLS not allocated */ EMDB_NOTLS, /* TLS not supported in obj */ EMDB_CTFNOMEMB, /* No CTF member of type */ EMDB_CTX, /* Action in invalid context */ EMDB_INCOMPAT, /* Mod incompat. w/ target */ EMDB_TGTHWNOTSUP, /* Not sup by tgt on this h/w */ EMDB_KINACTIVE, /* kmdb is not loaded */ EMDB_KACTIVATING, /* kmdb is loading */ EMDB_KACTIVE, /* kmdb is already loaded */ EMDB_KDEACTIVATING, /* kmdb is unloading */ EMDB_KNOLOAD, /* kmdb could not be loaded */ EMDB_KNOUNLOAD, /* kmdb cannot be unloaded */ EMDB_WPTOOMANY, /* Too many watchpoints */ EMDB_DTACTIVE, /* DTrace is active */ EMDB_KMODNOUNLOAD, /* module can't be unloaded */ EMDB_STKFRAME, /* Bad stack frame pointer */ EMDB_SHORTWRITE /* unexpected short write */ }; #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_ERRNO_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include /*ARGSUSED*/ void cmd_event(mdb_tgt_t *t, int vid, void *s) { if (s != NULL && mdb_eval(s) == -1) mdb_warn("failed to eval [ %d ] command \"%s\"", vid, s); } int cmd_evset(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t setb = 0, clrb = 0; const char *opt_c = NULL; uint_t opt_F = FALSE; uintptr_t opt_n = 0; int *idv = mdb_zalloc(sizeof (int) * (argc + 1), UM_SLEEP | UM_GC); int idc = 0; int status = DCMD_OK; const char *p; void *data; int argi; if (flags & DCMD_ADDRSPEC) idv[idc++] = (int)(intptr_t)addr; /* * Perform an initial pass through argv: we accumulate integer ids into * idv, and compute a group of bits to set and a group to clear. */ while (argc != 0 && (argi = mdb_getopts(argc, argv, 'c', MDB_OPT_STR, &opt_c, 'd', MDB_OPT_SETBITS, MDB_TGT_SPEC_AUTODIS, &setb, 'D', MDB_OPT_SETBITS, MDB_TGT_SPEC_AUTODEL, &setb, 'e', MDB_OPT_SETBITS, MDB_TGT_SPEC_DISABLED, &clrb, 'F', MDB_OPT_SETBITS, TRUE, &opt_F, 'n', MDB_OPT_UINTPTR, &opt_n, 's', MDB_OPT_SETBITS, MDB_TGT_SPEC_AUTOSTOP, &setb, 't', MDB_OPT_SETBITS, MDB_TGT_SPEC_TEMPORARY, &setb, 'T', MDB_OPT_SETBITS, MDB_TGT_SPEC_STICKY, &setb, NULL)) != argc) { argv += argi; /* advance past elements processed by getopts */ argc -= argi; /* decrement argc by number of args processed */ if (argv->a_type == MDB_TYPE_STRING) { if (argv->a_un.a_str[0] == '+') { for (p = argv->a_un.a_str + 1; *p != '\0'; ) { switch (*p++) { case 'd': clrb |= MDB_TGT_SPEC_AUTODIS; break; case 'D': clrb |= MDB_TGT_SPEC_AUTODEL; break; case 'e': setb |= MDB_TGT_SPEC_DISABLED; break; case 's': clrb |= MDB_TGT_SPEC_AUTOSTOP; break; case 't': clrb |= MDB_TGT_SPEC_TEMPORARY; break; case 'T': clrb |= MDB_TGT_SPEC_STICKY; break; default: mdb_warn("illegal option -- " "+%c\n", p[-1]); return (DCMD_USAGE); } } } else if (argv->a_un.a_str[0] != '-') { idv[idc++] = (int)(intmax_t) mdb_strtonum(argv->a_un.a_str, 10); } else return (DCMD_USAGE); } else idv[idc++] = (int)(intmax_t)argv->a_un.a_val; argc--; argv++; } if (idc == 0) { mdb_warn("expected one or more event IDs to be specified\n"); return (DCMD_USAGE); } /* * If -n was not specified, then -d means "disable now" instead of * meaning "set auto-disable after n hits". */ if (opt_n == 0 && (setb & MDB_TGT_SPEC_AUTODIS)) setb = (setb & ~MDB_TGT_SPEC_AUTODIS) | MDB_TGT_SPEC_DISABLED; while (idc-- != 0) { mdb_tgt_spec_desc_t sp; int id = *idv++; bzero(&sp, sizeof (mdb_tgt_spec_desc_t)); (void) mdb_tgt_vespec_info(mdb.m_target, id, &sp, NULL, 0); data = sp.spec_data; if (opt_F == FALSE && (sp.spec_flags & MDB_TGT_SPEC_HIDDEN)) { mdb_warn("cannot modify event %d: internal " "debugger event\n", id); status = DCMD_ERR; continue; } sp.spec_flags |= setb; sp.spec_flags &= ~clrb; if (opt_c && !(sp.spec_flags & MDB_TGT_SPEC_HIDDEN)) { if (opt_c[0] != '\0') sp.spec_data = strdup(opt_c); else sp.spec_data = NULL; } if (opt_n) sp.spec_limit = opt_n; if (mdb_tgt_vespec_modify(mdb.m_target, id, sp.spec_flags, sp.spec_limit, sp.spec_data) == -1) { mdb_warn("failed to modify event %d", id); data = sp.spec_data; status = DCMD_ERR; } if (opt_c && data && !(sp.spec_flags & MDB_TGT_SPEC_HIDDEN)) strfree(data); } return (status); } /* * Utility routine for performing the stock argument processing that is common * among the dcmds that create event specifiers. We parse out the standard set * of event property options from the command-line, and return a copy of the * argument list to the caller that consists solely of the remaining non-option * arguments. If a parsing error occurs, NULL is returned. */ static const mdb_arg_t * ev_getopts(uintmax_t addr, uint_t flags, int argc, const mdb_arg_t *argv, uint_t *evflags, char **opt_c, uint_t *opt_i, uint_t *opt_l, uint64_t *opt_L, uintptr_t *opt_n, uint_t *opt_o, uint_t *opt_p, uint_t *rwx) { uint_t setb = 0, clrb = 0; const char *p; int argi; mdb_arg_t *av; int ac = 0; /* keep lint happy */ *opt_p = FALSE; av = mdb_alloc(sizeof (mdb_arg_t) * (argc + 2), UM_SLEEP | UM_GC); /* * If an address was specified, take it as an additional immediate * value argument by adding it to the argument list. */ if (flags & DCMD_ADDRSPEC) { av[ac].a_type = MDB_TYPE_IMMEDIATE; av[ac++].a_un.a_val = addr; } /* * Now call mdb_getopts repeatedly to parse the argument list. We need * to handle '+[a-z]' processing manually, and we also manually copy * each non-option argument into the av[] array as we encounter them. */ while (argc != 0 && (argi = mdb_getopts(argc, argv, 'c', MDB_OPT_STR, opt_c, 'd', MDB_OPT_SETBITS, MDB_TGT_SPEC_AUTODIS, &setb, 'D', MDB_OPT_SETBITS, MDB_TGT_SPEC_AUTODEL, &setb, 'e', MDB_OPT_SETBITS, MDB_TGT_SPEC_DISABLED, &clrb, 'i', MDB_OPT_SETBITS, TRUE, opt_i, 'n', MDB_OPT_UINTPTR, opt_n, 'o', MDB_OPT_SETBITS, TRUE, opt_o, #ifdef _KMDB 'p', MDB_OPT_SETBITS, TRUE, opt_p, #endif 'r', MDB_OPT_SETBITS, MDB_TGT_WA_R, rwx, 's', MDB_OPT_SETBITS, MDB_TGT_SPEC_AUTOSTOP, &setb, 'l', MDB_OPT_SETBITS, TRUE, opt_l, 'L', MDB_OPT_UINT64, opt_L, 't', MDB_OPT_SETBITS, MDB_TGT_SPEC_TEMPORARY, &setb, 'T', MDB_OPT_SETBITS, MDB_TGT_SPEC_STICKY, &setb, 'w', MDB_OPT_SETBITS, MDB_TGT_WA_W, rwx, 'x', MDB_OPT_SETBITS, MDB_TGT_WA_X, rwx, NULL)) != argc) { argv += argi; /* advance past elements processed by getopts */ argc -= argi; /* decrement argc by number of args processed */ if (argv->a_type == MDB_TYPE_STRING) { if (argv->a_un.a_str[0] == '+') { for (p = argv->a_un.a_str + 1; *p != '\0'; ) { switch (*p++) { case 'd': clrb |= MDB_TGT_SPEC_AUTODIS; break; case 'D': clrb |= MDB_TGT_SPEC_AUTODEL; break; case 'e': setb |= MDB_TGT_SPEC_DISABLED; break; case 's': clrb |= MDB_TGT_SPEC_AUTOSTOP; break; case 't': clrb |= MDB_TGT_SPEC_TEMPORARY; break; case 'T': clrb |= MDB_TGT_SPEC_STICKY; break; default: mdb_warn("illegal option -- " "+%c\n", p[-1]); return (NULL); } } } else if (argv->a_un.a_str[0] != '-') { av[ac++] = *argv; } else return (NULL); } else av[ac++] = *argv; argc--; argv++; } /* * If no arguments were found on the command-line, return NULL to * indicate that the caller should return DCMD_USAGE. */ if (ac == 0) return (NULL); /* * If -n was not specified, then -d means "disable now" instead of * meaning "set auto-disable after n hits". */ if (opt_n == 0 && (setb & MDB_TGT_SPEC_AUTODIS)) setb = (setb & ~MDB_TGT_SPEC_AUTODIS) | MDB_TGT_SPEC_DISABLED; /* * Return the final set of flags, and terminate the argument array * with a NULL string argument. */ *evflags = setb & ~clrb; av[ac].a_type = MDB_TYPE_STRING; av[ac].a_un.a_str = NULL; return (av); } /* * Utility function for modifying the spec_data and spec_limit properties of an * event specifier. We use this for handling the -c and -n options below. */ static void ev_setopts(mdb_tgt_t *t, int id, const char *opt_c, uintptr_t opt_n) { mdb_tgt_spec_desc_t sp; (void) mdb_tgt_vespec_info(t, id, &sp, NULL, 0); if (opt_c != NULL) sp.spec_data = strdup(opt_c); if (opt_n != 0) sp.spec_limit = opt_n; if (mdb_tgt_vespec_modify(t, id, sp.spec_flags, sp.spec_limit, sp.spec_data) == -1) { mdb_warn("failed to modify event %d", id); if (opt_c != NULL) strfree(sp.spec_data); } } int cmd_bp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char *opt_c = NULL; uint_t opt_i = FALSE; uint_t opt_l = FALSE; uint64_t opt_L = 0; uintptr_t opt_n = 0; uint_t opt_o = FALSE; uint_t opt_p = FALSE; uint_t opt_rwx = 0; int status = DCMD_OK; int id; if ((argv = ev_getopts(addr, flags, argc, argv, &flags, &opt_c, &opt_i, &opt_l, &opt_L, &opt_n, &opt_o, &opt_p, &opt_rwx)) == NULL || opt_i || opt_o || opt_rwx != 0 || opt_l || opt_L != 0 || opt_p) return (DCMD_USAGE); while (argv->a_type != MDB_TYPE_STRING || argv->a_un.a_str != NULL) { if (argv->a_type == MDB_TYPE_STRING) { id = mdb_tgt_add_sbrkpt(mdb.m_target, argv->a_un.a_str, flags, cmd_event, NULL); } else { id = mdb_tgt_add_vbrkpt(mdb.m_target, argv->a_un.a_val, flags, cmd_event, NULL); } if (id == 0) { mdb_warn("failed to add breakpoint at %s", argv->a_type == MDB_TYPE_STRING ? argv->a_un.a_str : numtostr(argv->a_un.a_val, mdb.m_radix, NTOS_UNSIGNED | NTOS_SHOWBASE)); status = DCMD_ERR; } else if (opt_c || opt_n) ev_setopts(mdb.m_target, id, opt_c, opt_n); argv++; } return (status); } int cmd_sigbp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char *opt_c = NULL; uint_t opt_i = FALSE; uint_t opt_l = FALSE; uint64_t opt_L = 0; uintptr_t opt_n = 0; uint_t opt_o = FALSE; uint_t opt_p = FALSE; uint_t opt_rwx = 0; int status = DCMD_OK; int id, sig; if ((argv = ev_getopts(addr, flags, argc, argv, &flags, &opt_c, &opt_i, &opt_l, &opt_L, &opt_n, &opt_o, &opt_p, &opt_rwx)) == NULL || opt_i || opt_l || opt_L != 0 || opt_o || opt_p || opt_rwx != 0) return (DCMD_USAGE); while (argv->a_type != MDB_TYPE_STRING || argv->a_un.a_str != NULL) { if (argv->a_type == MDB_TYPE_STRING) { if (proc_str2sig(argv->a_un.a_str, &sig) == -1) { mdb_warn("invalid signal name -- %s\n", argv->a_un.a_str); status = DCMD_ERR; argv++; continue; } } else sig = (int)(intmax_t)argv->a_un.a_val; if ((id = mdb_tgt_add_signal(mdb.m_target, sig, flags, cmd_event, NULL)) == 0) { mdb_warn("failed to trace signal %d", sig); status = DCMD_ERR; } else if (opt_c || opt_n) ev_setopts(mdb.m_target, id, opt_c, opt_n); argv++; } return (status); } int cmd_sysbp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char *opt_c = NULL; uint_t opt_i = FALSE; uint_t opt_l = FALSE; uint64_t opt_L = 0; uintptr_t opt_n = 0; uint_t opt_o = FALSE; uint_t opt_p = FALSE; uint_t opt_rwx = 0; int status = DCMD_OK; int id, sysnum; if ((argv = ev_getopts(addr, flags, argc, argv, &flags, &opt_c, &opt_i, &opt_l, &opt_L, &opt_n, &opt_o, &opt_p, &opt_rwx)) == NULL || (opt_i && opt_o) || opt_l || opt_L != 0 || opt_p || opt_rwx != 0) return (DCMD_USAGE); while (argv->a_type != MDB_TYPE_STRING || argv->a_un.a_str != NULL) { if (argv->a_type == MDB_TYPE_STRING) { if (proc_str2sys(argv->a_un.a_str, &sysnum) == -1) { mdb_warn("invalid system call name -- %s\n", argv->a_un.a_str); status = DCMD_ERR; argv++; continue; } } else sysnum = (int)(intmax_t)argv->a_un.a_val; if (opt_o) { id = mdb_tgt_add_sysexit(mdb.m_target, sysnum, flags, cmd_event, NULL); } else { id = mdb_tgt_add_sysenter(mdb.m_target, sysnum, flags, cmd_event, NULL); } if (id == 0) { mdb_warn("failed to trace system call %d", sysnum); status = DCMD_ERR; } else if (opt_c || opt_n) ev_setopts(mdb.m_target, id, opt_c, opt_n); argv++; } return (status); } int cmd_fltbp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char *opt_c = NULL; uint_t opt_i = FALSE; uint_t opt_l = FALSE; uint64_t opt_L = 0; uintptr_t opt_n = 0; uint_t opt_o = FALSE; uint_t opt_p = FALSE; uint_t opt_rwx = 0; int status = DCMD_OK; int id, fltnum; if ((argv = ev_getopts(addr, flags, argc, argv, &flags, &opt_c, &opt_i, &opt_l, &opt_L, &opt_n, &opt_o, &opt_p, &opt_rwx)) == NULL || opt_i || opt_l || opt_L != 0 || opt_o || opt_p || opt_rwx != 0) return (DCMD_USAGE); while (argv->a_type != MDB_TYPE_STRING || argv->a_un.a_str != NULL) { if (argv->a_type == MDB_TYPE_STRING) { if (proc_str2flt(argv->a_un.a_str, &fltnum) == -1) { mdb_warn("invalid fault name -- %s\n", argv->a_un.a_str); status = DCMD_ERR; argv++; continue; } } else fltnum = (int)(intmax_t)argv->a_un.a_val; id = mdb_tgt_add_fault(mdb.m_target, fltnum, flags, cmd_event, NULL); if (id == 0) { mdb_warn("failed to trace fault %d", fltnum); status = DCMD_ERR; } else if (opt_c || opt_n) ev_setopts(mdb.m_target, id, opt_c, opt_n); argv++; } return (status); } /*ARGSUSED*/ int cmd_wp(uintptr_t x, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_addr_t addr = mdb_get_dot(); char *opt_c = NULL; uint_t opt_i = FALSE; uint_t opt_l = FALSE; uint64_t opt_L = 0; uintptr_t opt_n = 0; uint_t opt_o = FALSE; uint_t opt_p = FALSE; uint_t opt_rwx = 0; int id; char buf[MDB_SYM_NAMLEN]; GElf_Sym gsym; int size; if ((argv = ev_getopts(addr, flags, argc, argv, &flags, &opt_c, &opt_i, &opt_l, &opt_L, &opt_n, &opt_o, &opt_p, &opt_rwx)) == NULL || opt_o || (opt_p && opt_i)) return (DCMD_USAGE); #ifndef _KMDB if (opt_i) return (DCMD_USAGE); #endif if (argv->a_type != MDB_TYPE_IMMEDIATE) return (DCMD_USAGE); if (opt_rwx == 0) { mdb_warn("at least one of -r, -w, or -x must be specified\n"); return (DCMD_USAGE); } if ((opt_l) + (opt_L > 0) + (mdb.m_dcount != 1) > 1) { mdb_warn("only one of -l, -L, or command count can be " "specified\n"); return (DCMD_ABORT); } if (opt_l) { if (mdb_lookup_by_addr(addr, MDB_SYM_EXACT, buf, sizeof (buf), &gsym) == -1) { mdb_warn("failed to lookup symbol at %p", addr); return (DCMD_ERR); } if (gsym.st_size == 0) { mdb_warn("cannot set watchpoint: symbol '%s' has zero " "size\n", buf); return (DCMD_ERR); } size = gsym.st_size; } else if (opt_L != 0) { size = opt_L; } else size = mdb.m_dcount; if (opt_p) { id = mdb_tgt_add_pwapt(mdb.m_target, addr, size, opt_rwx, flags, cmd_event, NULL); } else if (opt_i) { id = mdb_tgt_add_iowapt(mdb.m_target, addr, size, opt_rwx, flags, cmd_event, NULL); } else { id = mdb_tgt_add_vwapt(mdb.m_target, addr, size, opt_rwx, flags, cmd_event, NULL); } if (id == 0) { mdb_warn("failed to set watchpoint at %p", addr); return ((opt_l || opt_L) ? DCMD_ERR : DCMD_ABORT); } if (opt_c || opt_n) ev_setopts(mdb.m_target, id, opt_c, opt_n); /* * We use m_dcount as an argument; don't loop. We ignore this * restriction with the -l and -L options, since we read the size from * the symbol and don't rely on the count. */ return ((opt_l || opt_L) ? DCMD_OK : DCMD_ABORT); } /*ARGSUSED*/ int cmd_oldbp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char *s = mdb_argv_to_str(argc, argv); if (mdb_tgt_add_vbrkpt(mdb.m_target, addr, 0, cmd_event, s) == 0) { mdb_warn("failed to add breakpoint"); if (s != NULL) strfree(s); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ static int oldwp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, uint_t rwx) { char *s = mdb_argv_to_str(argc, argv); if (mdb_tgt_add_vwapt(mdb.m_target, addr, mdb.m_dcount, rwx, 0, cmd_event, s) == 0) { mdb_warn("failed to add watchpoint"); if (s != NULL) strfree(s); return (DCMD_ABORT); } return (DCMD_ABORT); /* we use m_dcount as an argument; don't loop */ } int cmd_oldwpr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (oldwp(addr, flags, argc, argv, MDB_TGT_WA_R)); } int cmd_oldwpw(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (oldwp(addr, flags, argc, argv, MDB_TGT_WA_W)); } int cmd_oldwpx(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (oldwp(addr, flags, argc, argv, MDB_TGT_WA_X)); } static const char _evset_help[] = "+/-d disable specifier when hit count reaches limit (+d to unset);\n" " if -n is not present with -d, specifier is disabled immediately\n\n" "+/-D delete specifier when hit count reaches limit (+D to unset);\n" "+/-e enable specifier (+e or -d to disable)\n" "+/-s stop target when hit count reaches limit (+s to unset)\n" "+/-t delete specifier the next time the target stops (+t to unset)\n" "+/-T sticky bit: ::delete all will not remove specifier (+T to unset)\n\n" "-c cmd execute \"cmd\" each time the corresponding event occurs\n" "-n count set limit for -D, -d, or -s to \"count\" (default 1)\n\n"; void bp_help(void) { mdb_printf(_evset_help); mdb_printf("addr set breakpoint at specified virtual address\n"); mdb_printf("sym set deferred breakpoint at specified symbol\n"); } void evset_help(void) { mdb_printf(_evset_help); mdb_printf("addr/id set properties of specified event ids\n"); } void fltbp_help(void) { mdb_printf(_evset_help); mdb_printf("flt fault name (see ) or number\n"); } void sigbp_help(void) { mdb_printf(_evset_help); mdb_printf("SIG signal name (see signal(3HEAD)) or number\n"); } void sysbp_help(void) { mdb_printf(_evset_help); mdb_printf("-i trace system call on entry into kernel (default)\n" "-o trace system call on exit from kernel\n\n" "syscall system call name (see ) or number\n"); } void wp_help(void) { mdb_printf(_evset_help); mdb_printf( #ifdef _KMDB "-p treat addr as a physical address\n" "-i treat addr as an I/O port address\n" #endif "-l use size of addr's type for watched region\n" "-L size set size of watched region (default 1)\n" "-r trace read access to watched region\n" "-w trace write access to watched region\n" "-x trace execute access to watched region\n\n" "addr address for base of watched region\n" "repeat size of watched region (equivalent to -L)\n"); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_EVSET_H #define _MDB_EVSET_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern void cmd_event(mdb_tgt_t *, int, void *); extern int cmd_evset(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_bp(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_sigbp(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_sysbp(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_fltbp(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_wp(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_oldbp(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_oldwpr(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_oldwpw(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_oldwpx(uintptr_t, uint_t, int, const mdb_arg_t *); extern void bp_help(void); extern void evset_help(void); extern void fltbp_help(void); extern void sigbp_help(void); extern void sysbp_help(void); extern void wp_help(void); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_EVSET_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * File Descriptor I/O Backend * * Simple backend to pass though io_ops to the corresponding system calls on * an underlying fd. We provide functions to create fdio objects using file * descriptors, explicit file names, and path lookups. We save the complete * filename so that mdb_iob_name can be used to report the complete filename * of an open macro file in syntax error messages. */ #include #include #include #include #include #include #include #include #include #include #include typedef struct fd_data { char fd_name[MAXPATHLEN]; /* Save filename for error messages */ int fd_fd; /* File descriptor */ } fd_data_t; static ssize_t fdio_read(mdb_io_t *io, void *buf, size_t nbytes) { fd_data_t *fdp = io->io_data; if (io->io_next == NULL) return (read(fdp->fd_fd, buf, nbytes)); return (IOP_READ(io->io_next, buf, nbytes)); } static ssize_t fdio_write(mdb_io_t *io, const void *buf, size_t nbytes) { fd_data_t *fdp = io->io_data; if (io->io_next == NULL) return (write(fdp->fd_fd, buf, nbytes)); return (IOP_WRITE(io->io_next, buf, nbytes)); } static off64_t fdio_seek(mdb_io_t *io, off64_t offset, int whence) { fd_data_t *fdp = io->io_data; if (io->io_next == NULL) return (lseek64(fdp->fd_fd, offset, whence)); return (IOP_SEEK(io->io_next, offset, whence)); } static int fdio_ctl(mdb_io_t *io, int req, void *arg) { fd_data_t *fdp = io->io_data; if (io->io_next != NULL) return (IOP_CTL(io->io_next, req, arg)); if (req == MDB_IOC_GETFD) return (fdp->fd_fd); else return (ioctl(fdp->fd_fd, req, arg)); } static void fdio_close(mdb_io_t *io) { fd_data_t *fdp = io->io_data; (void) close(fdp->fd_fd); mdb_free(fdp, sizeof (fd_data_t)); } static const char * fdio_name(mdb_io_t *io) { fd_data_t *fdp = io->io_data; if (io->io_next == NULL) return (fdp->fd_name); return (IOP_NAME(io->io_next)); } mdb_io_t * mdb_fdio_create_path(const char *path[], const char *fname, int flags, mode_t mode) { int fd; char buf[MAXPATHLEN]; if (path != NULL && strchr(fname, '/') == NULL) { int i; for (fd = -1, i = 0; path[i] != NULL; i++) { (void) mdb_iob_snprintf(buf, MAXPATHLEN, "%s/%s", path[i], fname); if (access(buf, F_OK) == 0) { fd = open64(buf, flags, mode); fname = buf; break; } } if (fd == -1) (void) set_errno(ENOENT); } else fd = open64(fname, flags, mode); if (fd >= 0) return (mdb_fdio_create_named(fd, fname)); return (NULL); } static const mdb_io_ops_t fdio_file_ops = { .io_read = fdio_read, .io_write = fdio_write, .io_seek = fdio_seek, .io_ctl = fdio_ctl, .io_close = fdio_close, .io_name = fdio_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume }; /* * Read media logical block size. On error, return DEV_BSIZE. */ static uint_t fdio_bdev_info(int fd) { struct dk_minfo disk_info; if ((ioctl(fd, DKIOCGMEDIAINFO, (caddr_t)&disk_info)) == -1) return (DEV_BSIZE); return (disk_info.dki_lbsize); } /* * In order to read from a block-oriented device, we pick up the seek pointer, * read each containing block, and then copy the desired range of bytes back * into the caller's buffer. At the end of the transfer we reset the seek * pointer to where the caller thinks it should be. */ static ssize_t fdio_bdev_read(mdb_io_t *io, void *buf, size_t nbytes) { fd_data_t *fdp = io->io_data; ssize_t resid = nbytes; size_t blksize; uchar_t *blk; off64_t off; if (io->io_next != NULL) return (IOP_READ(io->io_next, buf, nbytes)); if ((off = lseek64(fdp->fd_fd, 0, SEEK_CUR)) == -1) return (-1); /* errno is set for us */ blksize = fdio_bdev_info(fdp->fd_fd); blk = mdb_zalloc(blksize, UM_SLEEP | UM_GC); while (resid != 0) { off64_t devoff = off & ~(blksize - 1); size_t blkoff = off & (blksize - 1); size_t len = MIN(resid, blksize - blkoff); if (pread64(fdp->fd_fd, blk, blksize, devoff) != blksize) break; /* errno is set for us, unless EOF */ bcopy(&blk[blkoff], buf, len); resid -= len; off += len; buf = (char *)buf + len; } if (resid == nbytes && nbytes != 0) return (set_errno(EMDB_EOF)); (void) lseek64(fdp->fd_fd, off, SEEK_SET); return (nbytes - resid); } /* * To perform a write to a block-oriented device, we use the same basic * algorithm as fdio_bdev_read(), above. In the inner loop, we read an * entire block, modify it using the data from the caller's buffer, and * then write the entire block back to the device. */ static ssize_t fdio_bdev_write(mdb_io_t *io, const void *buf, size_t nbytes) { fd_data_t *fdp = io->io_data; ssize_t resid = nbytes; size_t blksize; uchar_t *blk; off64_t off; if (io->io_next != NULL) return (IOP_WRITE(io->io_next, buf, nbytes)); if ((off = lseek64(fdp->fd_fd, 0, SEEK_CUR)) == -1) return (-1); /* errno is set for us */ blksize = fdio_bdev_info(fdp->fd_fd); blk = mdb_zalloc(blksize, UM_SLEEP | UM_GC); while (resid != 0) { off64_t devoff = off & ~(blksize - 1); size_t blkoff = off & (blksize - 1); size_t len = MIN(resid, blksize - blkoff); if (pread64(fdp->fd_fd, blk, blksize, devoff) != blksize) break; /* errno is set for us, unless EOF */ bcopy(buf, &blk[blkoff], len); if (pwrite64(fdp->fd_fd, blk, blksize, devoff) != blksize) break; /* errno is set for us, unless EOF */ resid -= len; off += len; buf = (char *)buf + len; } if (resid == nbytes && nbytes != 0) return (set_errno(EMDB_EOF)); (void) lseek64(fdp->fd_fd, off, SEEK_SET); return (nbytes - resid); } static const mdb_io_ops_t fdio_bdev_ops = { .io_read = fdio_bdev_read, .io_write = fdio_bdev_write, .io_seek = fdio_seek, .io_ctl = fdio_ctl, .io_close = fdio_close, .io_name = fdio_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume, }; mdb_io_t * mdb_fdio_create(int fd) { mdb_io_t *io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP); fd_data_t *fdp = mdb_alloc(sizeof (fd_data_t), UM_SLEEP); struct dk_cinfo info; struct stat64 st; switch (fd) { case STDIN_FILENO: (void) strcpy(fdp->fd_name, "(stdin)"); break; case STDOUT_FILENO: (void) strcpy(fdp->fd_name, "(stdout)"); break; case STDERR_FILENO: (void) strcpy(fdp->fd_name, "(stderr)"); break; default: (void) mdb_iob_snprintf(fdp->fd_name, MAXPATHLEN, "fd %d", fd); } fdp->fd_fd = fd; /* * We determine if something is a raw block-oriented disk device by * testing to see if it is a character device that supports DKIOCINFO. * If we are operating on a disk in raw mode, we must do our own * block-oriented i/o; otherwise we can just use read() and write(). */ if (fstat64(fd, &st) == 0 && S_ISCHR(st.st_mode) && ioctl(fd, DKIOCINFO, &info) == 0) io->io_ops = &fdio_bdev_ops; else io->io_ops = &fdio_file_ops; io->io_data = fdp; io->io_next = NULL; io->io_refcnt = 0; return (io); } mdb_io_t * mdb_fdio_create_named(int fd, const char *name) { mdb_io_t *io = mdb_fdio_create(fd); fd_data_t *fdp = io->io_data; (void) strncpy(fdp->fd_name, name, MAXPATHLEN); fdp->fd_name[MAXPATHLEN - 1] = '\0'; return (io); } int mdb_fdio_fileno(mdb_io_t *io) { fd_data_t *fdp = io->io_data; return (fdp->fd_fd); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2020 Joyent, Inc. * Copyright (c) 2017 by Delphix. All rights reserved. */ /* * Format String Decoder * * This file provides the core engine for converting strings of format * characters into formatted output. The various format dcmds invoke the * mdb_fmt_print() function below with a target, address space identifier, * address, count, and format character, and it reads the required data from * the target and prints the formatted output to stdout. Since nearly two * thirds of the format characters can be expressed as simple printf format * strings, we implement the engine using the lookup table below. Each entry * provides either a pointer to a printf format string or a pointer to a * function to perform special processing. For the printf case, the * corresponding data size in bytes is also supplied. The printf processing * code handles 1, 2, 4, and 8-byte reads into an unsigned integer container * of the given size, and then simply calls mdb_iob_printf with the integer * and format string. This handles all printf cases, except when unsigned * promotion of an integer type in the varargs list does not perform the * conversion we require to get the proper result. With the current set of * format characters, this case only occurs twice: we need a 4-byte float * to get promoted to 8-byte double for the 'f' format so it can be * correctly formatted by %f, and we need a 1-byte int8_t to get promoted * with sign extension to a 4-byte int32_t for the 'v' format so it can be * correctly formatted by %d. We provide explicit functions to handle these * cases, as well as to handle special format characters such as 'i', etc. * We also provide a cmd_formats() dcmd function below which prints a table * of the output formats and their sizes. Format characters that provide * custom functions provide their help description string explicitly. All * the printf formats have their help strings generated automatically by * our printf "unparser" mdb_iob_format2str(). */ #include #include #include #include #include #include #include #define FUNCP(p) ((void *)(p)) /* Cast to f_ptr type */ #define SZ_NONE ((size_t)-1L) /* Format does not change dot */ typedef mdb_tgt_addr_t mdb_fmt_func_f(mdb_tgt_t *, mdb_tgt_as_t, mdb_tgt_addr_t, size_t); /* * There are several 'special' characters that are handled outside of * mdb_fmt_print(). These are characters that write (vwWZ) and characters that * match (lLM). We include them here so that ::formats can display an * appropriate message, but they are handled specially by write_arglist() and * match_arglist() in mdb_cmds.c. */ #define FMT_NONE 0x0 /* Format character is not supported */ #define FMT_FUNC 0x1 /* f_ptr is a mdb_fmt_func_f to call */ #define FMT_PRINTF 0x2 /* f_ptr is a const char * format string */ #define FMT_MATCH 0x4 /* Match command (not supported here) */ #define FMT_WRITE 0x8 /* Command writes to address space */ #define FMT_NOAUTOWRAP 0x10 /* Autowrap should not be autoenabled */ #define FMT_TYPE(x) ((x) & 0x7) /* Excludes modifying flags */ typedef struct mdb_fmt_desc { int f_type; /* Type of format (see above) */ void *f_ptr; /* Data pointer (see above) */ const char *f_help; /* Additional help string */ size_t f_size; /* Size of type in bytes, or SZ_NONE */ boolean_t f_float; /* Is this a floating point type */ } mdb_fmt_desc_t; static const char help_plus[] = "increment dot by the count"; static const char help_minus[] = "decrement dot by the count"; static const char help_escchr[] = "character using C character notation"; static const char help_swapint[] = "swap bytes and shorts"; static const char help_dotinstr[] = "address and disassembled instruction"; static const char help_instr[] = "disassembled instruction"; static const char help_escstr[] = "string using C string notation"; static const char help_time32[] = "decoded time32_t"; static const char help_carat[] = "decrement dot by increment * count"; static const char help_dot[] = "dot as symbol+offset"; #ifndef _KMDB static const char help_f[] = "float"; #endif static const char help_swapshort[] = "swap bytes"; static const char help_nl[] = "newline"; static const char help_ws[] = "whitespace"; static const char help_rawstr[] = "raw string"; static const char help_tab[] = "horizontal tab"; static const char help_sdbyte[] = "decimal signed int"; static const char help_time64[] = "decoded time64_t"; static const char help_binary[] = "binary unsigned long long"; static const char help_hex64[] = "hexadecimal long long"; static const char help_match32[] = "int"; static const char help_match64[] = "long long"; static const char help_match16[] = "short"; static const char help_uintptr[] = "hexadecimal uintptr_t"; static const char help_ctf[] = "whose size is inferred by CTF info"; static const char help_jazzed[] = "jazzed-up binary unsigned long long"; /*ARGSUSED*/ static mdb_tgt_addr_t fmt_dot(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT; char buf[24]; mdb_iob_clrflags(mdb.m_out, oflags); if (mdb.m_flags & MDB_FL_PSYM) { while (cnt-- != 0) mdb_iob_printf(mdb.m_out, "%-#16lla%16T", addr); } else { (void) mdb_iob_snprintf(buf, sizeof (buf), "%#llx:", (u_longlong_t)addr); while (cnt-- != 0) mdb_iob_printf(mdb.m_out, "%-16s%16T", buf); } mdb_iob_setflags(mdb.m_out, oflags); mdb_nv_set_value(mdb.m_rvalue, addr); return (addr); } #ifndef _KMDB static mdb_tgt_addr_t fmt_float(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { float f; /* * We need to handle float as a special case because we need it to be * promoted to a double by virtue of appearing as a parameter, and all * our generic format handling below is based on integer types. */ while (cnt-- != 0) { if (mdb_tgt_aread(t, as, &f, sizeof (f), addr) != sizeof (f)) { warn("failed to read data from target"); break; } mdb_iob_printf(mdb.m_out, "%e", f); addr += sizeof (f); } return (addr); } #endif /*ARGSUSED*/ static mdb_tgt_addr_t fmt_plus(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { return (addr + cnt); } /*ARGSUSED*/ static mdb_tgt_addr_t fmt_minus(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { return (addr - cnt); } /*ARGSUSED*/ static mdb_tgt_addr_t fmt_carat(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { return (addr - (mdb.m_incr * cnt)); } /*ARGSUSED*/ static mdb_tgt_addr_t fmt_nl(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { while (cnt-- != 0) mdb_iob_nl(mdb.m_out); return (addr); } /*ARGSUSED*/ static mdb_tgt_addr_t fmt_ws(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { mdb_iob_ws(mdb.m_out, cnt); return (addr); } /*ARGSUSED*/ static mdb_tgt_addr_t fmt_tab(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { size_t ts = mdb_iob_gettabstop(mdb.m_out); mdb_iob_tabstop(mdb.m_out, cnt); mdb_iob_tab(mdb.m_out); mdb_iob_tabstop(mdb.m_out, ts); return (addr); } static mdb_tgt_addr_t fmt_rawstr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT; char buf[BUFSIZ]; ssize_t nbytes; mdb_iob_clrflags(mdb.m_out, oflags); for (; cnt-- != 0; addr++) { do { nbytes = mdb_tgt_readstr(t, as, buf, BUFSIZ, addr); if (nbytes > 0) { mdb_iob_puts(mdb.m_out, buf); addr += MIN(nbytes, BUFSIZ - 1); } else if (nbytes < 0) { warn("failed to read data from target"); goto out; } } while (nbytes == BUFSIZ); if (cnt != 0) mdb_iob_puts(mdb.m_out, "\\0"); } out: mdb_iob_setflags(mdb.m_out, oflags); return (addr); } static mdb_tgt_addr_t fmt_escstr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT; char buf[BUFSIZ]; ssize_t nbytes; char *s; mdb_iob_clrflags(mdb.m_out, oflags); for (; cnt-- != 0; addr++) { do { nbytes = mdb_tgt_readstr(t, as, buf, BUFSIZ, addr); if (nbytes > 0) { s = strchr2esc(buf, strlen(buf)); mdb_iob_puts(mdb.m_out, s); strfree(s); addr += MIN(nbytes, BUFSIZ - 1); } else if (nbytes < 0) { warn("failed to read data from target"); goto out; } } while (nbytes == BUFSIZ); if (cnt != 0) mdb_iob_puts(mdb.m_out, "\\0"); } out: mdb_iob_setflags(mdb.m_out, oflags); return (addr); } static mdb_tgt_addr_t fmt_escchr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { char *(*convert)(const char *, size_t); ssize_t nbytes; char *buf, *s; if (mdb.m_flags & MDB_FL_ADB) convert = &strchr2adb; else convert = &strchr2esc; buf = mdb_alloc(cnt + 1, UM_SLEEP); buf[cnt] = 0; if ((nbytes = mdb_tgt_aread(t, as, buf, cnt, addr)) > 0) { s = convert(buf, nbytes); mdb_iob_puts(mdb.m_out, s); strfree(s); addr += nbytes; } mdb_free(buf, cnt + 1); return (addr); } static mdb_tgt_addr_t fmt_swapshort(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { ushort_t x; while (cnt-- != 0) { if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) { x = (x << 8) | (x >> 8); mdb_iob_printf(mdb.m_out, "%-8x", x); mdb_nv_set_value(mdb.m_rvalue, x); addr += sizeof (x); } else { warn("failed to read data from target"); break; } } return (addr); } static mdb_tgt_addr_t fmt_swapint(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { uint_t x; while (cnt-- != 0) { if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) { x = ((x << 24) | ((x << 8) & 0xff0000) | ((x >> 8) & 0xff00) | ((x >> 24) & 0xff)); mdb_iob_printf(mdb.m_out, "%-16x", x); mdb_nv_set_value(mdb.m_rvalue, x); addr += sizeof (x); } else { warn("failed to read data from target"); break; } } return (addr); } static mdb_tgt_addr_t fmt_time32(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { int32_t x; while (cnt-- != 0) { if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) { mdb_iob_printf(mdb.m_out, "%-24Y", (time_t)x); mdb_nv_set_value(mdb.m_rvalue, x); addr += sizeof (x); } else { warn("failed to read data from target"); break; } } return (addr); } static mdb_tgt_addr_t fmt_time64(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { int64_t x; while (cnt-- != 0) { if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) { if ((time_t)x == x) mdb_iob_printf(mdb.m_out, "%-24Y", (time_t)x); else mdb_iob_printf(mdb.m_out, "%-24llR", x); mdb_nv_set_value(mdb.m_rvalue, x); addr += sizeof (x); } else { warn("failed to read data from target"); break; } } return (addr); } static mdb_tgt_addr_t fmt_sdbyte(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { int8_t x; while (cnt-- != 0) { if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) { mdb_iob_printf(mdb.m_out, "%-8d", (int32_t)x); mdb_nv_set_value(mdb.m_rvalue, (uint8_t)x); addr += sizeof (x); } else { warn("failed to read data from target"); break; } } return (addr); } static mdb_tgt_addr_t fmt_instr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { char buf[BUFSIZ]; uintptr_t naddr; if (as == MDB_TGT_AS_VIRT) as = MDB_TGT_AS_VIRT_I; while (cnt-- != 0) { naddr = mdb_dis_ins2str(mdb.m_disasm, t, as, buf, sizeof (buf), addr); if (naddr == addr) return (addr); /* If we didn't move, we failed */ mdb_iob_printf(mdb.m_out, "%s\n", buf); addr = naddr; } return (addr); } static mdb_tgt_addr_t fmt_dotinstr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT; char buf[BUFSIZ]; uintptr_t naddr; uint32_t i; if (as == MDB_TGT_AS_VIRT) as = MDB_TGT_AS_VIRT_I; for (mdb_iob_clrflags(mdb.m_out, oflags); cnt-- != 0; addr = naddr) { if (mdb_tgt_aread(t, as, &i, sizeof (i), addr) != sizeof (i)) { warn("failed to read data from target"); break; /* Fail if we can't read instruction */ } naddr = mdb_dis_ins2str(mdb.m_disasm, t, as, buf, sizeof (buf), addr); if (naddr == addr) break; /* Fail if we didn't advance */ mdb_iob_printf(mdb.m_out, "%lx %x: %s\n", (long)addr, i, buf); } mdb_iob_setflags(mdb.m_out, oflags); return (addr); } static mdb_tgt_addr_t fmt_binary(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { uint64_t x; const char *fmts[] = { "%-64s", "%-65s" }; const uint64_t mask = 0x8000000000000000ull; while (cnt-- != 0) { if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) { mdb_iob_printf(mdb.m_out, fmts[(x & mask) != 0], numtostr(x, 2, NTOS_UNSIGNED)); mdb_nv_set_value(mdb.m_rvalue, x); addr += sizeof (x); } else { warn("failed to read data from target"); break; } } return (addr); } static mdb_tgt_addr_t fmt_jazzed(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { uint64_t x; char buf[256]; while (cnt-- != 0) { boolean_t header = B_TRUE; if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) != sizeof (x)) { warn("failed to read data from target"); break; } mdb_nv_set_value(mdb.m_rvalue, x); addr += sizeof (x); mdb_iob_printf(mdb.m_out, "%s\n", numtostr(x, 2, NTOS_UNSIGNED)); while (x != 0) { int b = 63, forearm; int i = 0, highbit; /* * Find the high bit... */ while (!(x & (1ULL << b))) b--; highbit = b; /* * ...and iterate over the remaining bits, putting * the upper arm in our buffer for any set bit (and * a space otherwise). */ while (x & ((1ULL << b) - 1)) { buf[i++] = x & (1ULL << b) ? '|' : ' '; b--; } /* * If this is the header line, print the upper arm * for the lowest set bit and continue... */ if (header) { header = B_FALSE; buf[i] = '\0'; mdb_iob_printf(mdb.m_out, "%s|\n", buf); continue; } /* * ...otherwise, put the elbow and forearm into our * buffer, and print it. */ buf[i++] = '+'; for (forearm = b; forearm > -2; forearm--) buf[i++] = '-'; buf[i] = '\0'; mdb_iob_printf(mdb.m_out, "%s bit %d %smask 0x%0*llx\n", buf, b, b < 10 && highbit >= 10 ? " " : "", (highbit / 4) + 1, 1ULL << b); /* * Finally, clear the lowest set bit and continue. */ x &= ~(1ULL << b); } } return (addr); } static mdb_tgt_addr_t fmt_hex64(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt) { const char *fmts[] = { "%-16llx", "%-17llx" }; const uint64_t mask = 0xf000000000000000ull; uint64_t x; while (cnt-- != 0) { if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) { mdb_iob_printf(mdb.m_out, fmts[(x & mask) != 0], x); mdb_nv_set_value(mdb.m_rvalue, x); addr += sizeof (x); } else { warn("failed to read data from target"); break; } } return (addr); } static const mdb_fmt_desc_t fmttab[] = { { FMT_NONE, NULL, NULL, 0 }, /* 0 = NUL */ { FMT_NONE, NULL, NULL, 0 }, /* 1 = SOH */ { FMT_NONE, NULL, NULL, 0 }, /* 2 = STX */ { FMT_NONE, NULL, NULL, 0 }, /* 3 = ETX */ { FMT_NONE, NULL, NULL, 0 }, /* 4 = EOT */ { FMT_NONE, NULL, NULL, 0 }, /* 5 = ENQ */ { FMT_NONE, NULL, NULL, 0 }, /* 6 = ACK */ { FMT_NONE, NULL, NULL, 0 }, /* 7 = BEL */ { FMT_NONE, NULL, NULL, 0 }, /* 8 = BS */ { FMT_NONE, NULL, NULL, 0 }, /* 9 = \t */ { FMT_NONE, NULL, NULL, 0 }, /* 10 = \n */ { FMT_NONE, NULL, NULL, 0 }, /* 11 = VT */ { FMT_NONE, NULL, NULL, 0 }, /* 12 = FF */ { FMT_NONE, NULL, NULL, 0 }, /* 13 = \r */ { FMT_NONE, NULL, NULL, 0 }, /* 14 = SO */ { FMT_NONE, NULL, NULL, 0 }, /* 15 = SI */ { FMT_NONE, NULL, NULL, 0 }, /* 16 = DLE */ { FMT_NONE, NULL, NULL, 0 }, /* 17 = DC1 */ { FMT_NONE, NULL, NULL, 0 }, /* 18 = DC2 */ { FMT_NONE, NULL, NULL, 0 }, /* 19 = DC3 */ { FMT_NONE, NULL, NULL, 0 }, /* 20 = DC4 */ { FMT_NONE, NULL, NULL, 0 }, /* 21 = NAK */ { FMT_NONE, NULL, NULL, 0 }, /* 22 = EYC */ { FMT_NONE, NULL, NULL, 0 }, /* 23 = ETB */ { FMT_NONE, NULL, NULL, 0 }, /* 24 = CAN */ { FMT_NONE, NULL, NULL, 0 }, /* 25 = EM */ { FMT_NONE, NULL, NULL, 0 }, /* 26 = SUB */ { FMT_NONE, NULL, NULL, 0 }, /* 27 = ESC */ { FMT_NONE, NULL, NULL, 0 }, /* 28 = FS */ { FMT_NONE, NULL, NULL, 0 }, /* 29 = GS */ { FMT_NONE, NULL, NULL, 0 }, /* 30 = RS */ { FMT_NONE, NULL, NULL, 0 }, /* 31 = US */ { FMT_NONE, NULL, NULL, 0 }, /* 32 = SPACE */ { FMT_NONE, NULL, NULL, 0 }, /* 33 = ! */ { FMT_NONE, NULL, NULL, 0 }, /* 34 = " */ { FMT_NONE, NULL, NULL, 0 }, /* 35 = # */ { FMT_NONE, NULL, NULL, 0 }, /* 36 = $ */ { FMT_NONE, NULL, NULL, 0 }, /* 37 = % */ { FMT_NONE, NULL, NULL, 0 }, /* 38 = & */ { FMT_NONE, NULL, NULL, 0 }, /* 39 = ' */ { FMT_NONE, NULL, NULL, 0 }, /* 40 = ( */ { FMT_NONE, NULL, NULL, 0 }, /* 41 = ) */ { FMT_NONE, NULL, NULL, 0 }, /* 42 = * */ { FMT_FUNC, FUNCP(fmt_plus), help_plus, 0 }, /* 43 = + */ { FMT_NONE, NULL, NULL, 0 }, /* 44 = , */ { FMT_FUNC, FUNCP(fmt_minus), help_minus, 0 }, /* 45 = - */ { FMT_NONE, NULL, NULL, 0 }, /* 46 = . */ { FMT_NONE, NULL, NULL, 0 }, /* 47 = / */ { FMT_NONE, NULL, NULL, 0 }, /* 48 = 0 */ { FMT_NONE, NULL, NULL, 0 }, /* 49 = 1 */ { FMT_NONE, NULL, NULL, 0 }, /* 50 = 2 */ { FMT_NONE, NULL, NULL, 0 }, /* 51 = 3 */ { FMT_NONE, NULL, NULL, 0 }, /* 52 = 4 */ { FMT_NONE, NULL, NULL, 0 }, /* 53 = 5 */ { FMT_NONE, NULL, NULL, 0 }, /* 54 = 6 */ { FMT_NONE, NULL, NULL, 0 }, /* 55 = 7 */ { FMT_NONE, NULL, NULL, 0 }, /* 56 = 8 */ { FMT_NONE, NULL, NULL, 0 }, /* 57 = 9 */ { FMT_NONE, NULL, NULL, 0 }, /* 58 = : */ { FMT_NONE, NULL, NULL, 0 }, /* 59 = ; */ { FMT_NONE, NULL, NULL, 0 }, /* 60 = < */ { FMT_NONE, NULL, NULL, 0 }, /* 61 = = */ { FMT_NONE, NULL, NULL, 0 }, /* 62 = > */ { FMT_NONE, NULL, NULL, 0 }, /* 63 = ? */ { FMT_NONE, NULL, NULL, 0 }, /* 64 = @ */ { FMT_NONE, NULL, NULL, 0 }, /* 65 = A */ { FMT_PRINTF, "%-8x", NULL, 1 }, /* 66 = B */ { FMT_FUNC, FUNCP(fmt_escchr), help_escchr, 1 }, /* 67 = C */ { FMT_PRINTF, "%-16d", NULL, 4 }, /* 68 = D */ { FMT_PRINTF, "%-21llu", NULL, 8 }, /* 69 = E */ #ifdef _KMDB { FMT_NONE, NULL, NULL, 0 }, /* 70 = F */ #else { FMT_PRINTF, "%g", NULL, sizeof (double), B_TRUE }, /* 70 = F */ #endif { FMT_PRINTF, "%-23llo", NULL, 8 }, /* 71 = G */ { FMT_FUNC, FUNCP(fmt_swapint), help_swapint, 4 }, /* 72 = H */ { FMT_FUNC, FUNCP(fmt_dotinstr), help_dotinstr, 0 }, /* 73 = I */ { FMT_FUNC, FUNCP(fmt_hex64), help_hex64, 8 }, /* 74 = J */ #ifdef _LP64 { FMT_FUNC, FUNCP(fmt_hex64), help_uintptr, 8 }, /* 75 = K (J) */ #else { FMT_PRINTF, "%-16x", help_uintptr, 4 }, /* 75 = K (X) */ #endif { FMT_MATCH, NULL, help_match32, 4 }, /* 76 = L */ { FMT_MATCH, NULL, help_match64, 8 }, /* 77 = M */ { FMT_FUNC, FUNCP(fmt_nl), help_nl, SZ_NONE }, /* 78 = N */ { FMT_PRINTF, "%-#16o", NULL, 4 }, /* 79 = O */ { FMT_PRINTF, "%-19a", NULL, sizeof (uintptr_t) }, /* 80 = P */ { FMT_PRINTF, "%-#16q", NULL, 4 }, /* 81 = Q */ { FMT_FUNC, FUNCP(fmt_binary), help_binary, 8 }, /* 82 = R */ { FMT_FUNC, FUNCP(fmt_escstr), help_escstr, 0 }, /* 83 = S */ { FMT_FUNC, FUNCP(fmt_tab), help_tab, SZ_NONE }, /* 84 = T */ { FMT_PRINTF, "%-16u", NULL, 4 }, /* 85 = U */ { FMT_PRINTF, "%-8u", NULL, 1 }, /* 86 = V */ { FMT_PRINTF|FMT_WRITE, "%-16r", NULL, 4 }, /* 87 = W */ { FMT_PRINTF, "%-16x", NULL, 4 }, /* 88 = X */ { FMT_FUNC, FUNCP(fmt_time32), help_time32, 4 }, /* 89 = Y */ { FMT_FUNC|FMT_WRITE, FUNCP(fmt_hex64), help_hex64, 8 }, /* 90 = Z */ { FMT_NONE, NULL, NULL, 0 }, /* 91 = [ */ { FMT_NONE, NULL, NULL, 0 }, /* 92 = \ */ { FMT_NONE, NULL, NULL, 0 }, /* 93 = ] */ { FMT_FUNC, FUNCP(fmt_carat), help_carat, 0 }, /* 94 = ^ */ { FMT_NONE, NULL, NULL, 0 }, /* 95 = _ */ { FMT_NONE, NULL, NULL, 0 }, /* 96 = ` */ { FMT_FUNC, FUNCP(fmt_dot), help_dot, SZ_NONE }, /* 97 = a */ { FMT_PRINTF, "%-#8o", NULL, 1 }, /* 98 = b */ { FMT_PRINTF, "%c", NULL, 1 }, /* 99 = c */ { FMT_PRINTF, "%-8hd", NULL, 2 }, /* 100 = d */ { FMT_PRINTF, "%-21lld", NULL, 8 }, /* 101 = e */ #ifdef _KMDB { FMT_NONE, NULL, NULL, 0 }, /* 102 = f */ #else { FMT_FUNC, FUNCP(fmt_float), help_f, sizeof (float), B_TRUE }, /* 102 = f */ #endif { FMT_PRINTF, "%-24llq", NULL, 8 }, /* 103 = g */ { FMT_FUNC, FUNCP(fmt_swapshort), help_swapshort, 2 }, /* 104 = h */ { FMT_FUNC, FUNCP(fmt_instr), help_instr, 0 }, /* 105 = i */ { FMT_FUNC|FMT_NOAUTOWRAP, FUNCP(fmt_jazzed), help_jazzed, 8 }, /* 106 = j */ { FMT_NONE, NULL, NULL, 0 }, /* 107 = k */ { FMT_MATCH, NULL, help_match16, 2 }, /* 108 = l */ { FMT_NONE, NULL, NULL, 0 }, /* 109 = m */ { FMT_FUNC, FUNCP(fmt_nl), help_nl, SZ_NONE }, /* 110 = n */ { FMT_PRINTF, "%-#8ho", NULL, 2 }, /* 111 = o */ { FMT_PRINTF, "%-19a", NULL, sizeof (uintptr_t) }, /* 112 = p */ { FMT_PRINTF, "%-#8hq", NULL, 2 }, /* 113 = q */ { FMT_FUNC, FUNCP(fmt_ws), help_ws, SZ_NONE }, /* 114 = r */ { FMT_FUNC, FUNCP(fmt_rawstr), help_rawstr, 0 }, /* 115 = s */ { FMT_FUNC, FUNCP(fmt_tab), help_tab, SZ_NONE }, /* 116 = t */ { FMT_PRINTF, "%-8hu", NULL, 2 }, /* 117 = u */ { FMT_FUNC|FMT_WRITE, FUNCP(fmt_sdbyte), help_sdbyte, 1 }, /* 118 = v */ { FMT_PRINTF|FMT_WRITE, "%-8hr", NULL, 2 }, /* 119 = w */ { FMT_PRINTF, "%-8hx", NULL, 2 }, /* 120 = x */ { FMT_FUNC, FUNCP(fmt_time64), help_time64, 8 }, /* 121 = y */ { FMT_WRITE, NULL, help_ctf, 0 }, /* 122 = z */ }; mdb_tgt_addr_t mdb_fmt_print(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt, char fmt) { const mdb_fmt_desc_t *fp = &fmttab[fmt]; mdb_fmt_func_f *funcp; uintmax_t rvalue; void *buf; uint_t oflags = mdb.m_flags; union { uint64_t i8; uint32_t i4; uint16_t i2; uint8_t i1; double d; } u; if (fmt < 0 || fmt > (sizeof (fmttab) / sizeof (fmttab[0]))) { warn("invalid format character -- '%c'\n", fmt); return (addr); } if (!(fp->f_type & FMT_NOAUTOWRAP)) { /* * Unless a format has explicitly opted out, we force autowrap * for the duration of mdb_fmt_print(). */ mdb_iob_set_autowrap(mdb.m_out); } switch (FMT_TYPE(fp->f_type)) { case FMT_FUNC: funcp = (mdb_fmt_func_f *)fp->f_ptr; addr = funcp(t, as, addr, cnt); break; case FMT_PRINTF: switch (fp->f_size) { case 1: buf = &u.i1; break; case 2: buf = &u.i2; break; case 4: buf = &u.i4; break; case 8: buf = &u.i8; break; default: fail("format %c is defined using illegal size\n", fmt); } if (fp->f_float == B_TRUE) { if (fp->f_size != 8) { fail("format %c is using illegal fp size\n", fmt); } buf = &u.d; } while (cnt-- != 0) { if (mdb_tgt_aread(t, as, buf, fp->f_size, addr) != fp->f_size) { warn("failed to read data from target"); return (addr); } switch (fp->f_size) { case 1: mdb_iob_printf(mdb.m_out, fp->f_ptr, u.i1); rvalue = u.i1; break; case 2: mdb_iob_printf(mdb.m_out, fp->f_ptr, u.i2); rvalue = u.i2; break; case 4: mdb_iob_printf(mdb.m_out, fp->f_ptr, u.i4); rvalue = u.i4; break; case 8: if (fp->f_float) { mdb_iob_printf(mdb.m_out, fp->f_ptr, u.d); } else { mdb_iob_printf(mdb.m_out, fp->f_ptr, u.i8); } rvalue = u.i8; break; default: rvalue = 0; break; } mdb_nv_set_value(mdb.m_rvalue, rvalue); addr += fp->f_size; } break; default: warn("invalid format character -- '%c'\n", fmt); } mdb.m_flags = oflags; return (addr); } /*ARGSUSED*/ int cmd_formats(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const mdb_fmt_desc_t *fp = &fmttab[0]; int i; const char *write; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); for (i = 0; i < (sizeof (fmttab) / sizeof (fmttab[0])); i++, fp++) { if (fp->f_type == FMT_NONE) continue; write = (fp->f_type & FMT_WRITE) ? "write " : ""; if (fp->f_type & FMT_FUNC) mdb_printf("%c - %s%s", i, write, fp->f_help); else if (fp->f_type & FMT_MATCH) mdb_printf("%c - match %s", i, fp->f_help); else if (fp->f_help != NULL) mdb_printf("%c - %s%s", i, write, fp->f_help); else mdb_printf("%c - %s%s", i, write, mdb_iob_format2str(fp->f_ptr)); switch (fp->f_size) { case SZ_NONE: mdb_printf("\n"); break; case 0: mdb_printf(" (variable size)\n"); break; case 1: mdb_printf(" (1 byte)\n"); break; default: mdb_printf(" (%lu bytes)\n", fp->f_size); } } return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1997-1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_FMT_H #define _MDB_FMT_H #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB mdb_tgt_addr_t mdb_fmt_print(mdb_tgt_t *, mdb_tgt_as_t, mdb_tgt_addr_t, size_t, char); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_FMT_H */ /* * 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. */ /* * Utility routines to manage debugger frames and commands. A debugger frame * is used by each invocation of mdb_run() (the main parsing loop) to manage * its state. Refer to the comments in mdb.c for more information on frames. * Each frame has a list of commands (that is, a dcmd, argument list, and * optional address list) that represent a pipeline after it has been parsed. */ #include #include #include #include #include #include #include mdb_cmd_t * mdb_cmd_create(mdb_idcmd_t *idcp, mdb_argvec_t *argv) { mdb_cmd_t *cp = mdb_zalloc(sizeof (mdb_cmd_t), UM_NOSLEEP); if (cp == NULL) { warn("failed to allocate memory for command"); longjmp(mdb.m_frame->f_pcb, MDB_ERR_NOMEM); } mdb_list_append(&mdb.m_frame->f_cmds, cp); mdb_argvec_copy(&cp->c_argv, argv); mdb_argvec_zero(argv); cp->c_dcmd = idcp; return (cp); } void mdb_cmd_destroy(mdb_cmd_t *cp) { mdb_addrvec_destroy(&cp->c_addrv); mdb_argvec_destroy(&cp->c_argv); mdb_vcb_purge(cp->c_vcbs); mdb_free(cp, sizeof (mdb_cmd_t)); } void mdb_cmd_reset(mdb_cmd_t *cp) { mdb_addrvec_destroy(&cp->c_addrv); mdb_vcb_purge(cp->c_vcbs); cp->c_vcbs = NULL; } void mdb_frame_reset(mdb_frame_t *fp) { mdb_cmd_t *cp; while ((cp = mdb_list_next(&fp->f_cmds)) != NULL) { mdb_list_delete(&fp->f_cmds, cp); mdb_cmd_destroy(cp); } fp->f_cp = NULL; fp->f_pcmd = NULL; while (mdb_iob_stack_size(&fp->f_ostk) != 0) { mdb_iob_destroy(mdb.m_out); mdb.m_out = mdb_iob_stack_pop(&fp->f_ostk); } mdb_wcb_purge(&fp->f_wcbs); mdb_recycle(&fp->f_mblks); } void mdb_frame_push(mdb_frame_t *fp) { mdb_intr_disable(); if (mdb.m_fmark == NULL) mdb.m_fmark = fp; mdb_lex_state_save(mdb.m_frame->f_lstate); bzero(fp, sizeof (mdb_frame_t)); mdb_lex_state_create(fp); mdb_list_append(&mdb.m_flist, fp); fp->f_flags = mdb.m_flags & MDB_FL_VOLATILE; fp->f_pcmd = mdb.m_frame->f_pcmd; fp->f_id = mdb.m_fid++; mdb.m_frame->f_dot = mdb_nv_get_value(mdb.m_dot); mdb.m_frame = fp; mdb.m_depth++; mdb_dprintf(MDB_DBG_DSTK, "push frame <%u> mark=%p in=%s out=%s\n", fp->f_id, (void *)mdb.m_fmark, mdb_iob_name(mdb.m_in), mdb_iob_name(mdb.m_out)); mdb_intr_enable(); } void mdb_frame_pop(mdb_frame_t *fp, int err) { mdb_intr_disable(); ASSERT(mdb_iob_stack_size(&fp->f_istk) == 0); ASSERT(mdb_iob_stack_size(&fp->f_ostk) == 0); ASSERT(mdb_list_next(&fp->f_cmds) == NULL); ASSERT(fp->f_mblks == NULL); ASSERT(fp->f_wcbs == NULL); mdb_dprintf(MDB_DBG_DSTK, "pop frame <%u> status=%s\n", fp->f_id, mdb_err2str(err)); if (mdb.m_frame == fp) { mdb.m_flags &= ~MDB_FL_VOLATILE; mdb.m_flags |= fp->f_flags; mdb_frame_switch(mdb_list_prev(fp)); } if (mdb.m_fmark == fp) mdb.m_fmark = NULL; mdb_lex_state_destroy(fp); mdb_list_delete(&mdb.m_flist, fp); ASSERT(mdb.m_depth != 0); mdb.m_depth--; mdb_intr_enable(); } void mdb_frame_switch(mdb_frame_t *frame) { mdb_lex_state_save(mdb.m_frame->f_lstate); mdb.m_frame->f_dot = mdb_nv_get_value(mdb.m_dot); mdb.m_frame = frame; mdb_lex_state_restore(mdb.m_frame->f_lstate); mdb_dprintf(MDB_DBG_DSTK, "switch to frame <%u>\n", mdb.m_frame->f_id); mdb_nv_set_value(mdb.m_dot, frame->f_dot); } void mdb_frame_set_pipe(mdb_frame_t *frame) { frame->pipe = TRUE; } void mdb_frame_clear_pipe(mdb_frame_t *frame) { frame->pipe = FALSE; } mdb_frame_t * mdb_frame_pipe(void) { mdb_frame_t *frame = mdb_list_prev(mdb.m_frame); while (frame && frame->pipe == FALSE) frame = mdb_list_prev(frame); return (frame); } /* * 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. */ #ifndef _MDB_FRAME_H #define _MDB_FRAME_H #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct mdb_cmd { mdb_list_t c_list; /* List forward/back pointers */ mdb_idcmd_t *c_dcmd; /* Dcmd to invoke */ mdb_argvec_t c_argv; /* Arguments for this command */ mdb_addrvec_t c_addrv; /* Addresses for this command */ mdb_vcb_t *c_vcbs; /* Variable control block list */ } mdb_cmd_t; typedef struct mdb_frame { mdb_list_t f_list; /* Frame stack forward/back pointers */ mdb_list_t f_cmds; /* List of commands to execute */ mdb_wcb_t *f_wcbs; /* Walk control blocks for GC */ mdb_mblk_t *f_mblks; /* Memory blocks for GC */ mdb_cmd_t *f_pcmd; /* Next cmd in pipe (if pipe active) */ mdb_cmd_t *f_cp; /* Pointer to executing command */ mdb_iob_stack_t f_istk; /* Stack of input i/o buffers */ mdb_iob_stack_t f_ostk; /* Stack of output i/o buffers */ jmp_buf f_pcb; /* Control block for longjmp */ uint_t f_flags; /* Volatile flags to save/restore */ uint_t f_id; /* ID for debugging purposes */ mdb_argvec_t f_argvec; /* Command arguments */ int f_oldstate; /* Last lex state */ struct mdb_lex_state *f_lstate; /* Current lex state */ uintmax_t f_dot; /* Value of '.' */ mdb_bool_t pipe; /* frame has pipe context */ uint_t f_cbactive; /* true iff a callback is active */ } mdb_frame_t; #ifdef _MDB extern mdb_cmd_t *mdb_cmd_create(mdb_idcmd_t *, mdb_argvec_t *); extern void mdb_cmd_destroy(mdb_cmd_t *); extern void mdb_cmd_reset(mdb_cmd_t *); extern void mdb_frame_reset(mdb_frame_t *); extern void mdb_frame_push(mdb_frame_t *); extern void mdb_frame_pop(mdb_frame_t *, int); extern void mdb_frame_switch(mdb_frame_t *); extern void mdb_frame_set_pipe(mdb_frame_t *); extern void mdb_frame_clear_pipe(mdb_frame_t *); extern mdb_frame_t *mdb_frame_pipe(void); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_FRAME_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2013 by Delphix. All rights reserved. */ #ifndef _MDB_GCORE_H #define _MDB_GCORE_H /* * The kernel has its own definition of exit which has a different signature * than the user space definition. This seems to be the standard way to deal * with this. */ #define exit kern_exit #include #include #include #include #undef exit /* mdb versions of kernel structures used for ctf read calls */ typedef struct mdb_proc { uintptr_t p_as; uintptr_t p_brkbase; size_t p_brksize; uintptr_t p_usrstack; size_t p_stksize; user_t p_user; uintptr_t p_agenttp; uintptr_t p_tlist; uintptr_t p_zone; uintptr_t p_ldt; kcondvar_t p_holdlwps; int p_lwpcnt; uintptr_t p_lwpdir; uint_t p_lwpdir_sz; uintptr_t p_cred; uint_t p_flag; int p_zombcnt; uintptr_t p_pidp; pid_t p_ppid; uintptr_t p_pgidp; uintptr_t p_sessp; uintptr_t p_task; uintptr_t p_pool; model_t p_model; char p_wcode; ushort_t p_ldtlimit; uintptr_t p_exec; uint_t p_proc_flag; ushort_t p_pidflag; k_sigset_t p_ignore; k_sigset_t p_siginfo; k_sigset_t p_sig; k_sigset_t p_sigmask; k_fltset_t p_fltmask; int p_wdata; } mdb_proc_t; typedef struct mdb_kthread { ushort_t t_proc_flag; uint_t t_state; lwpchan_t t_lwpchan; ushort_t t_whystop; uint8_t t_dtrace_stop; uintptr_t t_forw; uintptr_t t_lwp; id_t t_tid; short t_sysnum; pri_t t_pri; time_t t_start; id_t t_cid; uintptr_t t_cpu; int t_bind_pset; short t_bind_cpu; uintptr_t t_lpl; ushort_t t_schedflag; ushort_t t_whatstop; k_sigset_t t_sig; uintptr_t t_schedctl; k_sigset_t t_hold; hrtime_t t_stoptime; } mdb_kthread_t; typedef struct mdb_seg { uintptr_t s_base; size_t s_size; uintptr_t s_ops; uintptr_t s_data; uintptr_t s_as; } mdb_seg_t; typedef struct mdb_as { uintptr_t a_proc; } mdb_as_t; typedef struct mdb_segvn_data { uintptr_t vp; uint64_t offset; uint16_t flags; uint8_t pageprot; uint8_t prot; uintptr_t amp; struct vpage *vpage; uint64_t anon_index; uint8_t type; } mdb_segvn_data_t; typedef struct mdb_vnode { enum vtype v_type; uintptr_t v_data; uintptr_t v_op; uintptr_t v_path; } mdb_vnode_t; typedef struct mdb_znode { uint64_t z_size; } mdb_znode_t; typedef struct mdb_tmpnode { vattr_t tn_attr; } mdb_tmpnode_t; typedef struct mdb_vnodeops { uintptr_t vnop_name; } mdb_vnodeops_t; typedef struct mdb_shm_data { uintptr_t shm_sptseg; } mdb_shm_data_t; typedef struct mdb_watched_page { uintptr_t wp_vaddr; uint8_t wp_oprot; } mdb_watched_page_t; typedef struct mdb_pid { pid_t pid_id; } mdb_pid_t; typedef struct mdb_sess { uintptr_t s_sidp; } mdb_sess_t; typedef struct mdb_task { taskid_t tk_tkid; uintptr_t tk_proj; } mdb_task_t; typedef struct mdb_kproject { projid_t kpj_id; } mdb_kproject_t; typedef struct mdb_zone { zoneid_t zone_id; uintptr_t zone_name; } mdb_zone_t; typedef struct mdb_sc_shared { char sc_sigblock; } mdb_sc_shared_t; typedef struct mdb_klwp { uintptr_t lwp_regs; struct pcb lwp_pcb; uchar_t lwp_asleep; uchar_t lwp_cursig; uintptr_t lwp_curinfo; k_siginfo_t lwp_siginfo; stack_t lwp_sigaltstack; uintptr_t lwp_oldcontext; short lwp_badpriv; uintptr_t lwp_ustack; char lwp_eosys; } mdb_klwp_t; typedef struct mdb_cpu { processorid_t cpu_id; } mdb_cpu_t; typedef struct mdb_lpl { lgrp_id_t lpl_lgrpid; } mdb_lpl_t; typedef struct mdb_sigqueue { k_siginfo_t sq_info; } mdb_sigqueue_t; typedef struct mdb_pool { poolid_t pool_id; } mdb_pool_t; typedef struct mdb_amp { uintptr_t ahp; } mdb_amp_t; typedef struct mdb_anon_hdr { pgcnt_t size; uintptr_t array_chunk; int flags; } mdb_anon_hdr_t; typedef struct mdb_anon { uintptr_t an_vp; anoff_t an_off; } mdb_anon_t; /* Used to construct a linked list of prmap_ts */ typedef struct prmap_node { struct prmap_node *next; prmap_t m; } prmap_node_t; /* Fields common to psinfo_t and pstatus_t */ typedef struct pcommon { int pc_nlwp; int pc_nzomb; pid_t pc_pid; pid_t pc_ppid; pid_t pc_pgid; pid_t pc_sid; taskid_t pc_taskid; projid_t pc_projid; zoneid_t pc_zoneid; char pc_dmodel; } pcommon_t; /* AVL walk callback structures */ typedef struct read_maps_cbarg { mdb_proc_t *p; uintptr_t brkseg; uintptr_t stkseg; prmap_node_t *map_head; prmap_node_t *map_tail; int map_len; } read_maps_cbarg_t; typedef struct as_segat_cbarg { uintptr_t addr; uintptr_t res; } as_segat_cbarg_t; typedef struct getwatchprot_cbarg { uintptr_t wp_vaddr; mdb_watched_page_t wp; boolean_t found; } getwatchprot_cbarg_t; struct gcore_segops; typedef struct gcore_seg { mdb_seg_t *gs_seg; void *gs_data; struct gcore_segops *gs_ops; } gcore_seg_t; /* * These are the ISA-dependent functions that need to be * implemented for ::gcore. */ extern uintptr_t gcore_prgetstackbase(mdb_proc_t *); extern int gcore_prfetchinstr(mdb_klwp_t *, ulong_t *); extern int gcore_prisstep(mdb_klwp_t *); extern void gcore_getgregs(mdb_klwp_t *, gregset_t); extern int gcore_prgetrvals(mdb_klwp_t *, long *, long *); #endif /* _MDB_GCORE_H */ /* * 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. */ #include #include #include #include #include #include #include #include #include #include #define GST_GROW 2 /* Mutable symbol table growth multiplier */ #define GST_DEFSZ 16 /* Mutable symbol table initial size */ #define GST_NVFLG (MDB_NV_EXTNAME | MDB_NV_SILENT) static const char *gelf_strtab; /* Active string table for qsort callbacks */ static mdb_gelf_file_t * gelf_sect_init(mdb_gelf_file_t *gf) { mdb_gelf_sect_t *gsp, *shstr = &gf->gf_sects[gf->gf_shstrndx]; size_t i; GElf_Half npbit = 0; GElf_Shdr *shp; GElf_Phdr *gpp; if (gf->gf_mode == GF_PROGRAM) gf->gf_shnum = 0; /* Simplifies other code paths */ if (gf->gf_shnum == 0) return (gf); /* If no section headers we're done here */ if (IOP_SEEK(gf->gf_io, shstr->gs_shdr.sh_offset, SEEK_SET) == -1) { warn("failed to seek %s to shdr strings", IOP_NAME(gf->gf_io)); return (NULL); } shstr->gs_data = mdb_zalloc(shstr->gs_shdr.sh_size + 1, UM_SLEEP); if (IOP_READ(gf->gf_io, shstr->gs_data, shstr->gs_shdr.sh_size) != shstr->gs_shdr.sh_size) { warn("failed to read %s shdr strings", IOP_NAME(gf->gf_io)); mdb_free(shstr->gs_data, shstr->gs_shdr.sh_size); return (NULL); } for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) { shp = &gsp->gs_shdr; gsp->gs_name = (const char *)shstr->gs_data + shp->sh_name; if (shp->sh_name >= shstr->gs_shdr.sh_size) { warn("section name for %s:[%u] is corrupt: %u\n", IOP_NAME(gf->gf_io), i, shp->sh_name); gsp->gs_name = shstr->gs_data; /* empty string */ } if (shp->sh_type == SHT_PROGBITS && (shp->sh_flags & SHF_ALLOC)) npbit++; /* Keep count for ET_REL code below */ } /* * If the file is of type ET_REL, we would still like to provide file * i/o using the mdb_gelf_rw() function defined below. To simplify * things, we forge up a sequence of Phdrs based on Shdrs which have * been marked SHF_ALLOC and are of type SHT_PROGBITS. We convert * relevant Shdr fields to their Phdr equivalents, and then set the * p_vaddr (virtual base address) to the section's file offset. * This allows us to relocate a given symbol by simply incrementing * its st_value by the file offset of the section corresponding to * its st_shndx, and then perform i/o to read or write the symbol's * value in the object file. */ if (gf->gf_ehdr.e_type == ET_REL && npbit != 0) { gf->gf_phdrs = mdb_zalloc(sizeof (GElf_Phdr) * npbit, UM_SLEEP); gf->gf_phnum = npbit; gf->gf_npload = npbit; gpp = gf->gf_phdrs; gsp = gf->gf_sects; for (i = 0; i < gf->gf_shnum; i++, gsp++) { shp = &gsp->gs_shdr; if ((shp->sh_type == SHT_PROGBITS) && (shp->sh_flags & SHF_ALLOC)) { gpp->p_type = PT_LOAD; gpp->p_flags = PF_R; if (shp->sh_flags & SHF_EXECINSTR) gpp->p_flags |= PF_X; if (shp->sh_flags & SHF_WRITE) gpp->p_flags |= PF_W; gpp->p_offset = shp->sh_offset; gpp->p_vaddr = shp->sh_offset; gpp->p_filesz = shp->sh_size; gpp->p_memsz = shp->sh_size; gpp->p_align = shp->sh_addralign; gpp++; } } } return (gf); } void * mdb_gelf_sect_load(mdb_gelf_file_t *gf, mdb_gelf_sect_t *gsp) { ssize_t nbytes; if (gsp->gs_data != NULL) return (gsp->gs_data); mdb_dprintf(MDB_DBG_ELF, "loading %s:%s (%lu bytes)\n", IOP_NAME(gf->gf_io), gsp->gs_name, (ulong_t)gsp->gs_shdr.sh_size); gsp->gs_data = mdb_alloc(gsp->gs_shdr.sh_size, UM_SLEEP); if (IOP_SEEK(gf->gf_io, gsp->gs_shdr.sh_offset, SEEK_SET) == -1) { warn("failed to seek to start of %s:%s", IOP_NAME(gf->gf_io), gsp->gs_name); goto err; } nbytes = IOP_READ(gf->gf_io, gsp->gs_data, gsp->gs_shdr.sh_size); if (nbytes < 0) { warn("failed to read %s:%s", IOP_NAME(gf->gf_io), gsp->gs_name); goto err; } if (nbytes < gsp->gs_shdr.sh_size) { mdb_dprintf(MDB_DBG_ELF, "only %ld of %llu bytes of %s:%s " "could be read\n", (long)nbytes, (u_longlong_t) gsp->gs_shdr.sh_size, IOP_NAME(gf->gf_io), gsp->gs_name); bzero((char *)gsp->gs_data + nbytes, (size_t)gsp->gs_shdr.sh_size - nbytes); } return (gsp->gs_data); err: mdb_free(gsp->gs_data, sizeof (gsp->gs_shdr.sh_size)); gsp->gs_data = NULL; return (NULL); } void mdb_gelf_ehdr_to_gehdr(Ehdr *src, GElf_Ehdr *dst) { bcopy(src->e_ident, dst->e_ident, sizeof (dst->e_ident)); dst->e_type = src->e_type; dst->e_machine = src->e_machine; dst->e_version = src->e_version; dst->e_entry = src->e_entry; dst->e_phoff = src->e_phoff; dst->e_shoff = src->e_shoff; dst->e_flags = src->e_flags; dst->e_ehsize = src->e_ehsize; dst->e_phentsize = src->e_phentsize; dst->e_phnum = src->e_phnum; dst->e_shentsize = src->e_shentsize; dst->e_shnum = src->e_shnum; dst->e_shstrndx = src->e_shstrndx; } static GElf_Shdr * gelf32_to_shdr(const Elf32_Shdr *src, GElf_Shdr *dst) { if (src != NULL) { dst->sh_name = src->sh_name; dst->sh_type = src->sh_type; dst->sh_flags = src->sh_flags; dst->sh_addr = src->sh_addr; dst->sh_offset = src->sh_offset; dst->sh_size = src->sh_size; dst->sh_link = src->sh_link; dst->sh_info = src->sh_info; dst->sh_addralign = src->sh_addralign; dst->sh_entsize = src->sh_entsize; return (dst); } return (NULL); } static GElf_Shdr * gelf64_to_shdr(const Elf64_Shdr *src, GElf_Shdr *dst) { if (src != NULL) { bcopy(src, dst, sizeof (Elf64_Shdr)); return (dst); } return (NULL); } static mdb_gelf_file_t * gelf_shdrs_init(mdb_gelf_file_t *gf, size_t shdr_size, GElf_Shdr *(*elf2gelf)(const void *, GElf_Shdr *)) { caddr_t shdrs, shp; size_t i; mdb_gelf_sect_t *gsp; size_t nbytes; mdb_dprintf(MDB_DBG_ELF, "loading %s section headers (%u entries)\n", IOP_NAME(gf->gf_io), gf->gf_shnum); if (gf->gf_shnum == 0) return (gf); if (IOP_SEEK(gf->gf_io, (off64_t)gf->gf_ehdr.e_shoff, SEEK_SET) == -1) { warn("failed to seek %s to shdrs", IOP_NAME(gf->gf_io)); return (NULL); } nbytes = shdr_size * gf->gf_shnum; shdrs = mdb_alloc(nbytes, UM_SLEEP); if (IOP_READ(gf->gf_io, shdrs, nbytes) != nbytes) { warn("failed to read %s section headers", IOP_NAME(gf->gf_io)); mdb_free(shdrs, nbytes); return (NULL); } gf->gf_sects = mdb_zalloc(sizeof (mdb_gelf_sect_t) * gf->gf_shnum, UM_SLEEP); shp = shdrs; gsp = gf->gf_sects; for (i = 0; i < gf->gf_shnum; i++, shp += shdr_size, gsp++) (void) elf2gelf(shp, &gsp->gs_shdr); mdb_free(shdrs, nbytes); return (gf); } static GElf_Phdr * gelf32_to_phdr(const Elf32_Phdr *src, GElf_Phdr *dst) { if (src != NULL) { dst->p_type = src->p_type; dst->p_offset = src->p_offset; dst->p_vaddr = src->p_vaddr; dst->p_paddr = src->p_paddr; dst->p_filesz = src->p_filesz; dst->p_memsz = src->p_memsz; dst->p_flags = src->p_flags; dst->p_align = src->p_align; return (dst); } return (NULL); } static GElf_Phdr * gelf64_to_phdr(const Elf64_Phdr *src, GElf_Phdr *dst) { if (src != NULL) { bcopy(src, dst, sizeof (Elf64_Phdr)); return (dst); } return (NULL); } static int gelf_phdr_compare(const void *lp, const void *rp) { GElf_Phdr *lhs = (GElf_Phdr *)lp; GElf_Phdr *rhs = (GElf_Phdr *)rp; /* * If both p_type fields are PT_LOAD, we want to sort by vaddr. * Exception is that p_vaddr == 0 means ignore this (put at end). */ if (lhs->p_type == PT_LOAD && rhs->p_type == PT_LOAD) { if (lhs->p_vaddr != rhs->p_vaddr) { if (lhs->p_vaddr == 0) return (1); /* lhs is "greater" */ if (rhs->p_vaddr == 0) return (-1); /* rhs is "greater" */ return (lhs->p_vaddr > rhs->p_vaddr ? 1 : -1); } return (0); } /* * If the p_type fields don't match, we need to make sure that PT_LOAD * entries are considered "less" (i.e. move towards the beginning * of the array we are sorting) */ if (lhs->p_type != rhs->p_type) { if (lhs->p_type == PT_LOAD) return (-1); /* rhs is "greater" */ if (rhs->p_type == PT_LOAD) return (1); /* lhs is "greater" */ return (lhs->p_type > rhs->p_type ? 1 : -1); } /* * If the p_type is the same but neither is PT_LOAD, then * just sort by file offset (doesn't really matter) */ if (lhs->p_offset != rhs->p_offset) return (lhs->p_offset > rhs->p_offset ? 1 : -1); return (0); } static mdb_gelf_file_t * gelf_phdrs_init(mdb_gelf_file_t *gf, size_t phdr_size, GElf_Phdr *(*elf2gelf)(const void *, GElf_Phdr *)) { caddr_t phdrs, php; size_t i; GElf_Phdr *gpp; size_t nbytes; mdb_dprintf(MDB_DBG_ELF, "loading %s program headers (%lu entries)\n", IOP_NAME(gf->gf_io), gf->gf_phnum); if (gf->gf_phnum == 0) return (gf); if (IOP_SEEK(gf->gf_io, (off64_t)gf->gf_ehdr.e_phoff, SEEK_SET) == -1) { warn("failed to seek %s to phdrs", IOP_NAME(gf->gf_io)); return (NULL); } nbytes = phdr_size * gf->gf_phnum; phdrs = mdb_alloc(nbytes, UM_SLEEP); if (IOP_READ(gf->gf_io, phdrs, nbytes) != nbytes) { warn("failed to read %s program headers", IOP_NAME(gf->gf_io)); mdb_free(phdrs, nbytes); return (NULL); } gf->gf_phdrs = mdb_zalloc(sizeof (GElf_Phdr) * gf->gf_phnum, UM_SLEEP); php = phdrs; gpp = gf->gf_phdrs; /* * Iterate through the list of phdrs locating those that are of type * PT_LOAD; increment gf_npload so we know how many are loadable. */ for (i = 0; i < gf->gf_phnum; i++, php += phdr_size, gpp++) { (void) elf2gelf(php, gpp); if (gpp->p_type != PT_LOAD) continue; mdb_dprintf(MDB_DBG_ELF, "PT_LOAD va=0x%llx flags=0x%x " "memsz=%llu filesz=%llu off=%llu\n", (u_longlong_t) gpp->p_vaddr, gpp->p_flags, (u_longlong_t)gpp->p_memsz, (u_longlong_t)gpp->p_filesz, (u_longlong_t)gpp->p_offset); gf->gf_npload++; } /* * Now we sort the phdrs array using a comparison routine which * arranges for the PT_LOAD phdrs with non-zero virtual addresses * to come first sorted by virtual address. This means that we * can access the complete phdr table by examining the array * gf->gf_phdrs[0 .. gf->gf_phnum - 1], and we can access a sorted * array of valid PT_LOAD pdhrs by examining the array * gf->gf_phdrs[0 .. gf->gf_npload - 1]. */ qsort(gf->gf_phdrs, gf->gf_phnum, sizeof (GElf_Phdr), gelf_phdr_compare); /* * Locate the PT_DYNAMIC Phdr if one is present; we save this * Phdr pointer in gf->gf_dynp for future use. */ for (gpp = gf->gf_phdrs, i = 0; i < gf->gf_phnum; i++, gpp++) { if (gpp->p_type == PT_DYNAMIC) { mdb_dprintf(MDB_DBG_ELF, "PT_DYNAMIC " "filesize = %lluULL off=%lluULL\n", (u_longlong_t)gpp->p_filesz, (u_longlong_t)gpp->p_offset); gf->gf_dynp = gpp; break; } } mdb_free(phdrs, nbytes); return (gf); } static GElf_Dyn * gelf32_to_dyn(const Elf32_Dyn *src, GElf_Dyn *dst) { if (src != NULL) { dst->d_tag = (GElf_Xword)(Elf32_Word)src->d_tag; dst->d_un.d_ptr = src->d_un.d_ptr; return (dst); } return (NULL); } static GElf_Dyn * gelf64_to_dyn(const Elf64_Dyn *src, GElf_Dyn *dst) { if (src != NULL) { bcopy(src, dst, sizeof (Elf64_Dyn)); return (dst); } return (NULL); } static GElf_Xword gelf_dyn_lookup(mdb_gelf_file_t *gf, GElf_Xword tag) { size_t i; for (i = 0; i < gf->gf_ndyns; i++) { if (gf->gf_dyns[i].d_tag == tag) return (gf->gf_dyns[i].d_un.d_val); } return ((GElf_Xword)-1L); } void mdb_gelf_dyns_set(mdb_gelf_file_t *gf, void *dyns, size_t dyns_sz) { size_t ndyns, i, dyn_size; caddr_t dp; GElf_Dyn *gdp; if (gf->gf_dyns != NULL) { /* Free the existing dyn entries */ free(gf->gf_dyns); gf->gf_dyns = NULL; gf->gf_ndyns = 0; } if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) dyn_size = sizeof (Elf32_Dyn); else dyn_size = sizeof (Elf64_Dyn); ndyns = dyns_sz / dyn_size; gf->gf_dyns = mdb_zalloc(sizeof (GElf_Dyn) * ndyns, UM_SLEEP); gf->gf_ndyns = ndyns; dp = dyns; gdp = gf->gf_dyns; if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) { for (i = 0; i < ndyns; i++, dp += dyn_size, gdp++) { /* LINTED - alignment */ (void) gelf32_to_dyn((Elf32_Dyn *)dp, gdp); } } else { for (i = 0; i < ndyns; i++, dp += dyn_size, gdp++) { /* LINTED - alignment */ (void) gelf64_to_dyn((Elf64_Dyn *)dp, gdp); } } } static GElf_Dyn * gelf_dyns_init(mdb_gelf_file_t *gf, size_t dyn_size, GElf_Dyn *(*elf2gelf)(const void *, GElf_Dyn *)) { size_t nbytes, ndyns, i; caddr_t dyns, dp; GElf_Dyn *gdp; off64_t dyn_addr; if (gf->gf_dyns != NULL) return (gf->gf_dyns); /* Already loaded */ if (gf->gf_dynp == NULL) return (NULL); /* No PT_DYNAMIC entry was found */ nbytes = gf->gf_dynp->p_filesz; ndyns = nbytes / dyn_size; /* * If this is an executable in PROGRAM view, then p_vaddr is an * absolute address; we need to subtract the virtual base address of * the mapping. In FILE view, dyn_addr is just the file offset. */ if (gf->gf_mode == GF_PROGRAM) { if (gf->gf_ehdr.e_type == ET_EXEC && gf->gf_npload != 0) dyn_addr = gf->gf_dynp->p_vaddr - gf->gf_phdrs->p_vaddr; else dyn_addr = gf->gf_dynp->p_vaddr; } else { mdb_gelf_sect_t *gsp = gf->gf_sects; for (i = 0; i < gf->gf_shnum; i++, gsp++) { if (gsp->gs_shdr.sh_type == SHT_DYNAMIC) { dyn_addr = gsp->gs_shdr.sh_offset; break; } } if (i == gf->gf_shnum) return (NULL); /* No SHT_DYNAMIC entry was found */ } mdb_dprintf(MDB_DBG_ELF, "loading _DYNAMIC[] (%lu entries) " "from offset %llx\n", (ulong_t)ndyns, (longlong_t)dyn_addr); if (IOP_SEEK(gf->gf_io, dyn_addr, SEEK_SET) == -1) { warn("failed to seek %s to _DYNAMIC", IOP_NAME(gf->gf_io)); return (NULL); } dyns = mdb_alloc(nbytes, UM_SLEEP); if (IOP_READ(gf->gf_io, dyns, nbytes) != nbytes) { warn("failed to read %s:_DYNAMIC", IOP_NAME(gf->gf_io)); mdb_free(dyns, nbytes); return (NULL); } gf->gf_dyns = mdb_zalloc(sizeof (GElf_Dyn) * ndyns, UM_SLEEP); gf->gf_ndyns = ndyns; dp = dyns; gdp = gf->gf_dyns; for (i = 0; i < ndyns; i++, dp += dyn_size, gdp++) (void) elf2gelf(dp, gdp); mdb_free(dyns, nbytes); return (gf->gf_dyns); } static mdb_gelf_file_t * gelf32_init(mdb_gelf_file_t *gf, mdb_io_t *io, const Elf32_Ehdr *ehdr) { /* * Convert the Elf32_Ehdr to a GElf_Ehdr */ bcopy(ehdr->e_ident, gf->gf_ehdr.e_ident, EI_NIDENT); gf->gf_ehdr.e_type = ehdr->e_type; gf->gf_ehdr.e_machine = ehdr->e_machine; gf->gf_ehdr.e_version = ehdr->e_version; gf->gf_ehdr.e_entry = ehdr->e_entry; gf->gf_ehdr.e_phoff = ehdr->e_phoff; gf->gf_ehdr.e_shoff = ehdr->e_shoff; gf->gf_ehdr.e_flags = ehdr->e_flags; gf->gf_ehdr.e_ehsize = ehdr->e_ehsize; gf->gf_ehdr.e_phentsize = ehdr->e_phentsize; gf->gf_ehdr.e_phnum = ehdr->e_phnum; gf->gf_ehdr.e_shentsize = ehdr->e_shentsize; gf->gf_ehdr.e_shnum = ehdr->e_shnum; gf->gf_ehdr.e_shstrndx = ehdr->e_shstrndx; gf->gf_shnum = gf->gf_ehdr.e_shnum; gf->gf_shstrndx = gf->gf_ehdr.e_shstrndx; gf->gf_phnum = gf->gf_ehdr.e_phnum; if ((gf->gf_shnum == 0 && ehdr->e_shoff != 0) || gf->gf_shstrndx == SHN_XINDEX || gf->gf_phnum == PN_XNUM) { Elf32_Shdr shdr0; if (ehdr->e_shoff == 0) return (NULL); if (IOP_SEEK(io, (off64_t)ehdr->e_shoff, SEEK_SET) == -1) { warn("failed to seek %s", IOP_NAME(io)); return (NULL); } if (IOP_READ(io, &shdr0, sizeof (shdr0)) != sizeof (shdr0)) { warn("failed to read extended ELF header from %s", IOP_NAME(io)); return (NULL); } if (gf->gf_shnum == 0) gf->gf_shnum = shdr0.sh_size; if (gf->gf_shstrndx == SHN_XINDEX) gf->gf_shstrndx = shdr0.sh_link; if (gf->gf_phnum == PN_XNUM) gf->gf_phnum = shdr0.sh_info; } /* * Initialize the section and program headers. We skip initializing * the section headers if this is a program image because they are * not loadable and thus we can't get at them. */ if (gf->gf_mode == GF_FILE && gelf_shdrs_init(gf, sizeof (Elf32_Shdr), (GElf_Shdr *(*)(const void *, GElf_Shdr *))gelf32_to_shdr) == NULL) return (NULL); if (gelf_phdrs_init(gf, sizeof (Elf32_Phdr), (GElf_Phdr *(*)(const void *, GElf_Phdr *))gelf32_to_phdr) == NULL) return (NULL); (void) gelf_dyns_init(gf, sizeof (Elf32_Dyn), (GElf_Dyn *(*)(const void *, GElf_Dyn *))gelf32_to_dyn); return (gf); } static mdb_gelf_file_t * gelf64_init(mdb_gelf_file_t *gf, mdb_io_t *io, Elf64_Ehdr *ehdr) { /* * Save a copy of the ELF file header */ bcopy(ehdr, &gf->gf_ehdr, sizeof (Elf64_Ehdr)); gf->gf_shnum = gf->gf_ehdr.e_shnum; gf->gf_shstrndx = gf->gf_ehdr.e_shstrndx; gf->gf_phnum = gf->gf_ehdr.e_phnum; if ((gf->gf_shnum == 0 && ehdr->e_shoff != 0) || gf->gf_shstrndx == SHN_XINDEX || gf->gf_phnum == PN_XNUM) { Elf64_Shdr shdr0; if (ehdr->e_shoff == 0) return (NULL); if (IOP_SEEK(io, (off64_t)ehdr->e_shoff, SEEK_SET) == -1) { warn("failed to seek %s", IOP_NAME(io)); return (NULL); } if (IOP_READ(io, &shdr0, sizeof (shdr0)) != sizeof (shdr0)) { warn("failed to read extended ELF header from %s", IOP_NAME(io)); return (NULL); } if (gf->gf_shnum == 0) gf->gf_shnum = shdr0.sh_size; if (gf->gf_shstrndx == SHN_XINDEX) gf->gf_shstrndx = shdr0.sh_link; if (gf->gf_phnum == PN_XNUM) gf->gf_phnum = shdr0.sh_info; } /* * Initialize the section and program headers. We skip initializing * the section headers if this is a program image because they are * not loadable and thus we can't get at them. */ if (gf->gf_mode == GF_FILE && gelf_shdrs_init(gf, sizeof (Elf64_Shdr), (GElf_Shdr *(*)(const void *, GElf_Shdr *))gelf64_to_shdr) == NULL) return (NULL); if (gelf_phdrs_init(gf, sizeof (Elf64_Phdr), (GElf_Phdr *(*)(const void *, GElf_Phdr *))gelf64_to_phdr) == NULL) return (NULL); (void) gelf_dyns_init(gf, sizeof (Elf64_Dyn), (GElf_Dyn *(*)(const void *, GElf_Dyn *))gelf64_to_dyn); return (gf); } int mdb_gelf_check(mdb_io_t *io, Elf32_Ehdr *ehp, GElf_Half etype) { #ifdef _BIG_ENDIAN uchar_t order = ELFDATA2MSB; #else uchar_t order = ELFDATA2LSB; #endif ssize_t nbytes; (void) IOP_SEEK(io, (off64_t)0L, SEEK_SET); nbytes = IOP_READ(io, ehp, sizeof (Elf32_Ehdr)); if (nbytes == -1) { if (etype != ET_NONE) warn("failed to read ELF header from %s", IOP_NAME(io)); return (-1); } if (nbytes != sizeof (Elf32_Ehdr) || bcmp(&ehp->e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0) { if (etype != ET_NONE) warn("%s is not an ELF file\n", IOP_NAME(io)); return (-1); } if (ehp->e_ident[EI_DATA] != order) { warn("ELF file %s has different endianness from debugger\n", IOP_NAME(io)); return (-1); } if (ehp->e_version != EV_CURRENT) { warn("ELF file %s uses different ELF version (%lu) than " "debugger (%u)\n", IOP_NAME(io), (ulong_t)ehp->e_version, EV_CURRENT); return (-1); } if (etype != ET_NONE && ehp->e_type != etype) { warn("ELF file %s is not of the expected type\n", IOP_NAME(io)); return (-1); } return (0); } mdb_gelf_file_t * mdb_gelf_create(mdb_io_t *io, GElf_Half etype, int mode) { union { Elf32_Ehdr h32; Elf64_Ehdr h64; } ehdr; mdb_gelf_file_t *gf = mdb_zalloc(sizeof (mdb_gelf_file_t), UM_SLEEP); ASSERT(mode == GF_FILE || mode == GF_PROGRAM); gf->gf_mode = mode; /* * Assign the i/o backend now, but don't hold it until we're sure * we're going to succeed; otherwise the caller will be responsible * for mdb_io_destroy()ing it. */ gf->gf_io = io; if (mdb_gelf_check(io, &ehdr.h32, etype) == -1) goto err; switch (ehdr.h32.e_ident[EI_CLASS]) { case ELFCLASS32: gf = gelf32_init(gf, io, &ehdr.h32); break; case ELFCLASS64: if (IOP_SEEK(io, (off64_t)0L, SEEK_SET) == -1) { warn("failed to seek %s", IOP_NAME(io)); goto err; } if (IOP_READ(io, &ehdr.h64, sizeof (ehdr.h64)) != sizeof (ehdr.h64)) { warn("failed to read ELF header from %s", IOP_NAME(io)); goto err; } gf = gelf64_init(gf, io, &ehdr.h64); break; default: warn("%s is an unsupported ELF class: %u\n", IOP_NAME(io), ehdr.h32.e_ident[EI_CLASS]); goto err; } if (gf != NULL && gelf_sect_init(gf) != NULL) { gf->gf_io = mdb_io_hold(io); return (gf); } err: if (gf != NULL) { if (gf->gf_sects != NULL) { mdb_free(gf->gf_sects, gf->gf_shnum * sizeof (mdb_gelf_sect_t)); } mdb_free(gf, sizeof (mdb_gelf_file_t)); } return (NULL); } void mdb_gelf_destroy(mdb_gelf_file_t *gf) { mdb_gelf_sect_t *gsp; size_t i; for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) { if (gsp->gs_data != NULL) mdb_free(gsp->gs_data, gsp->gs_shdr.sh_size); } mdb_free(gf->gf_sects, gf->gf_shnum * sizeof (mdb_gelf_sect_t)); mdb_free(gf->gf_phdrs, gf->gf_phnum * sizeof (GElf_Phdr)); mdb_io_rele(gf->gf_io); mdb_free(gf, sizeof (mdb_gelf_file_t)); } /* * Sort comparison function for 32-bit symbol address-to-name lookups. We sort * symbols by value. If values are equal, we prefer the symbol that is * non-zero sized, typed, not weak, or lexically first, in that order. */ static int gelf32_sym_compare(const void *lp, const void *rp) { Elf32_Sym *lhs = *((Elf32_Sym **)lp); Elf32_Sym *rhs = *((Elf32_Sym **)rp); if (lhs->st_value != rhs->st_value) return (lhs->st_value > rhs->st_value ? 1 : -1); if ((lhs->st_size == 0) != (rhs->st_size == 0)) return (lhs->st_size == 0 ? 1 : -1); if ((ELF32_ST_TYPE(lhs->st_info) == STT_NOTYPE) != (ELF32_ST_TYPE(rhs->st_info) == STT_NOTYPE)) return (ELF32_ST_TYPE(lhs->st_info) == STT_NOTYPE ? 1 : -1); if ((ELF32_ST_BIND(lhs->st_info) == STB_WEAK) != (ELF32_ST_BIND(rhs->st_info) == STB_WEAK)) return (ELF32_ST_BIND(lhs->st_info) == STB_WEAK ? 1 : -1); return (strcmp(gelf_strtab + lhs->st_name, gelf_strtab + rhs->st_name)); } /* * Sort comparison function for 64-bit symbol address-to-name lookups. We sort * symbols by value. If values are equal, we prefer the symbol that is * non-zero sized, typed, not weak, or lexically first, in that order. */ static int gelf64_sym_compare(const void *lp, const void *rp) { Elf64_Sym *lhs = *((Elf64_Sym **)lp); Elf64_Sym *rhs = *((Elf64_Sym **)rp); if (lhs->st_value != rhs->st_value) return (lhs->st_value > rhs->st_value ? 1 : -1); if ((lhs->st_size == 0) != (rhs->st_size == 0)) return (lhs->st_size == 0 ? 1 : -1); if ((ELF64_ST_TYPE(lhs->st_info) == STT_NOTYPE) != (ELF64_ST_TYPE(rhs->st_info) == STT_NOTYPE)) return (ELF64_ST_TYPE(lhs->st_info) == STT_NOTYPE ? 1 : -1); if ((ELF64_ST_BIND(lhs->st_info) == STB_WEAK) != (ELF64_ST_BIND(rhs->st_info) == STB_WEAK)) return (ELF64_ST_BIND(lhs->st_info) == STB_WEAK ? 1 : -1); return (strcmp(gelf_strtab + lhs->st_name, gelf_strtab + rhs->st_name)); } static void gelf32_symtab_sort(mdb_gelf_symtab_t *gst) { Elf32_Sym **sympp = (Elf32_Sym **)gst->gst_asmap; mdb_var_t *v; mdb_nv_rewind(&gst->gst_nv); while ((v = mdb_nv_advance(&gst->gst_nv)) != NULL) { Elf32_Sym *sym = MDB_NV_COOKIE(v); if (sym->st_value != 0 && (ELF32_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size)) *sympp++ = sym; } gst->gst_aslen = (size_t)(sympp - (Elf32_Sym **)gst->gst_asmap); ASSERT(gst->gst_aslen <= gst->gst_asrsv); gelf_strtab = gst->gst_ssect ? gst->gst_ssect->gs_data : NULL; qsort(gst->gst_asmap, gst->gst_aslen, sizeof (Elf32_Sym *), gelf32_sym_compare); gelf_strtab = NULL; } static void gelf32_symtab_init(mdb_gelf_symtab_t *gst) { #if STT_NUM != (STT_TLS + 1) #error "STT_NUM has grown. update gelf32_symtab_init()" #endif const char *base = (const char *)gst->gst_ssect->gs_data; Elf32_Sym *sym = gst->gst_dsect->gs_data; mdb_nv_t *nv = &gst->gst_nv; Elf32_Word ss_size = gst->gst_ssect->gs_shdr.sh_size; size_t asrsv = 0; GElf_Word i, n; if (gst->gst_dsect->gs_shdr.sh_entsize != sizeof (Elf32_Sym)) { warn("%s sh_entsize %llu != sizeof (Elf32_Sym); " "using %u instead\n", gst->gst_dsect->gs_name, (u_longlong_t)gst->gst_dsect->gs_shdr.sh_entsize, (uint_t)sizeof (Elf32_Sym)); gst->gst_dsect->gs_shdr.sh_entsize = sizeof (Elf32_Sym); } n = gst->gst_dsect->gs_shdr.sh_size / gst->gst_dsect->gs_shdr.sh_entsize; for (i = 0; i < n; i++, sym++) { const char *name = base + sym->st_name; uchar_t type = ELF32_ST_TYPE(sym->st_info); if (type >= STT_NUM || type == STT_SECTION) continue; /* skip sections and unknown types */ if (sym->st_name >= ss_size || name[0] < '!' || name[0] > '~') { if (sym->st_name >= ss_size || name[0] != '\0') { warn("ignoring %s symbol [%u]: invalid name\n", gst->gst_dsect->gs_name, i); sym->st_name = 0; } continue; /* skip corrupt or empty names */ } (void) mdb_nv_insert(nv, name, NULL, (uintptr_t)sym, GST_NVFLG); if (sym->st_value != 0 && (ELF32_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size)) asrsv++; /* reserve space in the address map */ } if (gst->gst_ehdr->e_type == ET_REL && gst->gst_file != NULL) { GElf_Word smax = gst->gst_file->gf_shnum; mdb_gelf_sect_t *gsp; for (sym = gst->gst_dsect->gs_data, i = 0; i < n; i++, sym++) { if (sym->st_shndx > SHN_UNDEF && sym->st_shndx < smax) { gsp = &gst->gst_file->gf_sects[sym->st_shndx]; sym->st_value += gsp->gs_shdr.sh_offset; if (ELF32_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size != 0) asrsv++; /* reserve space in asmap */ } } } gst->gst_asmap = mdb_alloc(sizeof (Elf32_Sym *) * asrsv, UM_SLEEP); gst->gst_asrsv = asrsv; gelf32_symtab_sort(gst); } static void gelf64_symtab_sort(mdb_gelf_symtab_t *gst) { Elf64_Sym **sympp = (Elf64_Sym **)gst->gst_asmap; mdb_var_t *v; mdb_nv_rewind(&gst->gst_nv); while ((v = mdb_nv_advance(&gst->gst_nv)) != NULL) { Elf64_Sym *sym = MDB_NV_COOKIE(v); if (sym->st_value != 0 && (ELF64_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size)) *sympp++ = sym; } gst->gst_aslen = (size_t)(sympp - (Elf64_Sym **)gst->gst_asmap); ASSERT(gst->gst_aslen <= gst->gst_asrsv); gelf_strtab = gst->gst_ssect ? gst->gst_ssect->gs_data : NULL; qsort(gst->gst_asmap, gst->gst_aslen, sizeof (Elf64_Sym *), gelf64_sym_compare); gelf_strtab = NULL; } static void gelf64_symtab_init(mdb_gelf_symtab_t *gst) { #if STT_NUM != (STT_TLS + 1) #error "STT_NUM has grown. update gelf64_symtab_init()" #endif const char *base = (const char *)gst->gst_ssect->gs_data; Elf64_Sym *sym = gst->gst_dsect->gs_data; mdb_nv_t *nv = &gst->gst_nv; Elf64_Xword ss_size = gst->gst_ssect->gs_shdr.sh_size; size_t asrsv = 0; GElf_Word i, n; if (gst->gst_dsect->gs_shdr.sh_entsize != sizeof (Elf64_Sym)) { warn("%s sh_entsize %llu != sizeof (Elf64_Sym); " "using %u instead\n", gst->gst_dsect->gs_name, (u_longlong_t)gst->gst_dsect->gs_shdr.sh_entsize, (uint_t)sizeof (Elf64_Sym)); gst->gst_dsect->gs_shdr.sh_entsize = sizeof (Elf64_Sym); } n = gst->gst_dsect->gs_shdr.sh_size / gst->gst_dsect->gs_shdr.sh_entsize; for (i = 0; i < n; i++, sym++) { const char *name = base + sym->st_name; uchar_t type = ELF64_ST_TYPE(sym->st_info); if (type >= STT_NUM || type == STT_SECTION) continue; /* skip sections and unknown types */ if (sym->st_name >= ss_size || name[0] < '!' || name[0] > '~') { if (sym->st_name >= ss_size || name[0] != '\0') { warn("ignoring %s symbol [%u]: invalid name\n", gst->gst_dsect->gs_name, i); sym->st_name = 0; } continue; /* skip corrupt or empty names */ } (void) mdb_nv_insert(nv, name, NULL, (uintptr_t)sym, GST_NVFLG); if (sym->st_value != 0 && (ELF64_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size)) asrsv++; /* reserve space in the address map */ } if (gst->gst_ehdr->e_type == ET_REL && gst->gst_file != NULL) { GElf_Word smax = gst->gst_file->gf_shnum; mdb_gelf_sect_t *gsp; for (sym = gst->gst_dsect->gs_data, i = 0; i < n; i++, sym++) { if (sym->st_shndx > SHN_UNDEF && sym->st_shndx < smax) { gsp = &gst->gst_file->gf_sects[sym->st_shndx]; sym->st_value += gsp->gs_shdr.sh_offset; if (ELF64_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size != 0) asrsv++; /* reserve space in asmap */ } } } gst->gst_asmap = mdb_alloc(sizeof (Elf64_Sym *) * asrsv, UM_SLEEP); gst->gst_asrsv = asrsv; gelf64_symtab_sort(gst); } mdb_gelf_symtab_t * mdb_gelf_symtab_create_file(mdb_gelf_file_t *gf, GElf_Word elftype, uint_t tabid) { mdb_gelf_sect_t *gsp; const char *dsname = NULL; const char *ssname; size_t i; GElf_Word link; /* * Examine the sh_link field in the the Elf header to get the name * of the corresponding strings section */ for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) { if (gsp->gs_shdr.sh_type == elftype) { dsname = gsp->gs_name; link = gsp->gs_shdr.sh_link; break; } } if (dsname == NULL) return (NULL); if (link > gf->gf_shnum) { /* * Invalid link number due to corrupt elf file. */ warn("link number %ud larger than number of sections %d\n", link, gf->gf_shnum); return (NULL); } ssname = (gf->gf_sects + link)->gs_name; return (mdb_gelf_symtab_create_file_by_name(gf, dsname, ssname, tabid)); } mdb_gelf_symtab_t * mdb_gelf_symtab_create_file_by_name(mdb_gelf_file_t *gf, const char *dsname, const char *ssname, uint_t tabid) { mdb_gelf_symtab_t *gst; mdb_gelf_sect_t *gsp; size_t i; gst = mdb_alloc(sizeof (mdb_gelf_symtab_t), UM_SLEEP); (void) mdb_nv_create(&gst->gst_nv, UM_SLEEP); gst->gst_asmap = NULL; gst->gst_aslen = 0; gst->gst_asrsv = 0; gst->gst_ehdr = &gf->gf_ehdr; gst->gst_file = gf; gst->gst_dsect = NULL; gst->gst_ssect = NULL; gst->gst_id = 0; gst->gst_tabid = tabid; for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) { if (strcmp(gsp->gs_name, dsname) == 0) { gst->gst_dsect = gsp; break; } } for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) { if (strcmp(gsp->gs_name, ssname) == 0) { gst->gst_ssect = gsp; break; } } if (gst->gst_dsect == NULL || gst->gst_ssect == NULL) goto err; /* Failed to locate data or string section */ if (mdb_gelf_sect_load(gf, gst->gst_dsect) == NULL) goto err; /* Failed to load data section */ if (mdb_gelf_sect_load(gf, gst->gst_ssect) == NULL) goto err; /* Failed to load string section */ if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) gelf32_symtab_init(gst); else gelf64_symtab_init(gst); return (gst); err: mdb_nv_destroy(&gst->gst_nv); mdb_free(gst, sizeof (mdb_gelf_symtab_t)); return (NULL); } mdb_gelf_symtab_t * mdb_gelf_symtab_create_raw(const GElf_Ehdr *ehdr, const void *dshdr, void *ddata, const void *sshdr, void *sdata, uint_t tabid) { mdb_gelf_symtab_t *gst; gst = mdb_alloc(sizeof (mdb_gelf_symtab_t), UM_SLEEP); (void) mdb_nv_create(&gst->gst_nv, UM_SLEEP); gst->gst_asmap = NULL; gst->gst_aslen = 0; gst->gst_asrsv = 0; gst->gst_ehdr = ehdr; gst->gst_file = NULL; /* Flag for raw symtab */ gst->gst_id = 0; gst->gst_tabid = tabid; gst->gst_dsect = mdb_zalloc(sizeof (mdb_gelf_sect_t), UM_SLEEP); gst->gst_dsect->gs_name = ".symtab"; gst->gst_dsect->gs_data = ddata; gst->gst_ssect = mdb_zalloc(sizeof (mdb_gelf_sect_t), UM_SLEEP); gst->gst_ssect->gs_name = ".strtab"; gst->gst_ssect->gs_data = sdata; if (ehdr->e_ident[EI_CLASS] == ELFCLASS32) { (void) gelf32_to_shdr(dshdr, &gst->gst_dsect->gs_shdr); (void) gelf32_to_shdr(sshdr, &gst->gst_ssect->gs_shdr); gelf32_symtab_init(gst); } else { (void) gelf64_to_shdr(dshdr, &gst->gst_dsect->gs_shdr); (void) gelf64_to_shdr(sshdr, &gst->gst_ssect->gs_shdr); gelf64_symtab_init(gst); } return (gst); } mdb_gelf_symtab_t * mdb_gelf_symtab_create_dynamic(mdb_gelf_file_t *gf, uint_t tabid) { GElf_Addr dt_symtab, dt_strtab, dt_hash; GElf_Xword dt_syment, dt_strsz; mdb_gelf_symtab_t *gst; uint_t hash_h[2]; off64_t base = 0; ASSERT(gf->gf_mode == GF_PROGRAM); /* * Read in and cache the array of GElf_Dyn structures from the * PT_DYNAMIC phdr. Abort if this is not possible. */ if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) { (void) gelf_dyns_init(gf, sizeof (Elf32_Dyn), (GElf_Dyn *(*)(const void *, GElf_Dyn *))gelf32_to_dyn); } else { (void) gelf_dyns_init(gf, sizeof (Elf64_Dyn), (GElf_Dyn *(*)(const void *, GElf_Dyn *))gelf64_to_dyn); } /* * Pre-fetch all the DT_* entries we will need for creating the * dynamic symbol table; abort if any are missing. */ if ((dt_hash = gelf_dyn_lookup(gf, DT_HASH)) == -1L) { warn("failed to get DT_HASH for %s\n", IOP_NAME(gf->gf_io)); return (NULL); } if ((dt_symtab = gelf_dyn_lookup(gf, DT_SYMTAB)) == -1L) { warn("failed to get DT_SYMTAB for %s\n", IOP_NAME(gf->gf_io)); return (NULL); } if ((dt_syment = gelf_dyn_lookup(gf, DT_SYMENT)) == -1L) { warn("failed to get DT_SYMENT for %s\n", IOP_NAME(gf->gf_io)); return (NULL); } if ((dt_strtab = gelf_dyn_lookup(gf, DT_STRTAB)) == -1L) { warn("failed to get DT_STRTAB for %s\n", IOP_NAME(gf->gf_io)); return (NULL); } if ((dt_strsz = gelf_dyn_lookup(gf, DT_STRSZ)) == -1L) { warn("failed to get DT_STRSZ for %s\n", IOP_NAME(gf->gf_io)); return (NULL); } /* * If this is an executable, then DT_HASH is an absolute address; * we need to subtract the virtual base address of the mapping. */ if (gf->gf_ehdr.e_type == ET_EXEC && gf->gf_npload != 0) base = (off64_t)gf->gf_phdrs->p_vaddr; /* * Read in the header for the DT_HASH: this consists of nbucket * and nchain values (nchain is the number of hashed symbols). */ if (IOP_SEEK(gf->gf_io, (off64_t)dt_hash - base, SEEK_SET) == -1) { warn("failed to seek ELF file to start of DT_HASH"); return (NULL); } if (IOP_READ(gf->gf_io, hash_h, sizeof (hash_h)) != sizeof (hash_h)) { warn("failed to read DT_HASH header"); return (NULL); } gst = mdb_zalloc(sizeof (mdb_gelf_symtab_t), UM_SLEEP); (void) mdb_nv_create(&gst->gst_nv, UM_SLEEP); gst->gst_asmap = NULL; gst->gst_aslen = 0; gst->gst_asrsv = 0; gst->gst_ehdr = &gf->gf_ehdr; gst->gst_file = gf; gst->gst_id = 0; gst->gst_tabid = tabid; gst->gst_dsect = mdb_zalloc(sizeof (mdb_gelf_sect_t), UM_SLEEP); gst->gst_dsect->gs_name = ".dynsym"; gst->gst_dsect->gs_shdr.sh_offset = dt_symtab - (GElf_Addr)base; gst->gst_dsect->gs_shdr.sh_size = hash_h[1] * dt_syment; gst->gst_dsect->gs_shdr.sh_entsize = dt_syment; gst->gst_ssect = mdb_zalloc(sizeof (mdb_gelf_sect_t), UM_SLEEP); gst->gst_ssect->gs_name = ".dynstr"; gst->gst_ssect->gs_shdr.sh_offset = dt_strtab - (GElf_Addr)base; gst->gst_ssect->gs_shdr.sh_size = dt_strsz; gst->gst_ssect->gs_shdr.sh_entsize = 0; if (mdb_gelf_sect_load(gf, gst->gst_dsect) == NULL) goto err; if (mdb_gelf_sect_load(gf, gst->gst_ssect) == NULL) goto err; if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) gelf32_symtab_init(gst); else gelf64_symtab_init(gst); return (gst); err: mdb_gelf_symtab_destroy(gst); return (NULL); } mdb_gelf_symtab_t * mdb_gelf_symtab_create_mutable(void) { mdb_gelf_symtab_t *gst; static GElf_Ehdr ehdr; gst = mdb_zalloc(sizeof (mdb_gelf_symtab_t), UM_SLEEP); (void) mdb_nv_create(&gst->gst_nv, UM_SLEEP); gst->gst_ehdr = &ehdr; if (ehdr.e_version == 0) { #ifdef _LP64 uchar_t class = ELFCLASS64; #else uchar_t class = ELFCLASS32; #endif #ifdef _BIG_ENDIAN uchar_t data = ELFDATA2MSB; #else uchar_t data = ELFDATA2LSB; #endif /* * Since all mutable symbol tables will use a native Ehdr, * we can just have a single static copy which they all * point to and we only need initialize once. */ ehdr.e_ident[EI_MAG0] = ELFMAG0; ehdr.e_ident[EI_MAG1] = ELFMAG1; ehdr.e_ident[EI_MAG2] = ELFMAG2; ehdr.e_ident[EI_MAG3] = ELFMAG3; ehdr.e_ident[EI_CLASS] = class; ehdr.e_ident[EI_DATA] = data; ehdr.e_ident[EI_VERSION] = EV_CURRENT; ehdr.e_type = ET_NONE; ehdr.e_version = EV_CURRENT; } return (gst); } void mdb_gelf_symtab_destroy(mdb_gelf_symtab_t *gst) { if (gst->gst_file == NULL) { if (gst->gst_dsect == NULL && gst->gst_ssect == NULL) { mdb_var_t *v; mdb_nv_rewind(&gst->gst_nv); while ((v = mdb_nv_advance(&gst->gst_nv)) != NULL) { char *name = (char *)mdb_nv_get_name(v); mdb_gelf_dsym_t *dsp = mdb_nv_get_cookie(v); mdb_free(name, strlen(name) + 1); mdb_free(dsp, sizeof (mdb_gelf_dsym_t)); } } else { mdb_free(gst->gst_dsect, sizeof (mdb_gelf_sect_t)); mdb_free(gst->gst_ssect, sizeof (mdb_gelf_sect_t)); } } else if (gst->gst_file->gf_mode == GF_PROGRAM) { mdb_gelf_sect_t *dsect = gst->gst_dsect; mdb_gelf_sect_t *ssect = gst->gst_ssect; if (dsect->gs_data != NULL) mdb_free(dsect->gs_data, dsect->gs_shdr.sh_size); if (ssect->gs_data != NULL) mdb_free(ssect->gs_data, ssect->gs_shdr.sh_size); mdb_free(gst->gst_dsect, sizeof (mdb_gelf_sect_t)); mdb_free(gst->gst_ssect, sizeof (mdb_gelf_sect_t)); } mdb_nv_destroy(&gst->gst_nv); mdb_free(gst->gst_asmap, gst->gst_asrsv * sizeof (void *)); mdb_free(gst, sizeof (mdb_gelf_symtab_t)); } size_t mdb_gelf_symtab_size(mdb_gelf_symtab_t *gst) { return (mdb_nv_size(&gst->gst_nv)); } static GElf_Sym * gelf32_to_sym(const Elf32_Sym *src, GElf_Sym *dst) { if (src != NULL) { dst->st_name = src->st_name; dst->st_info = src->st_info; dst->st_other = src->st_other; dst->st_shndx = src->st_shndx; dst->st_value = src->st_value; dst->st_size = src->st_size; return (dst); } return (NULL); } static GElf_Sym * gelf64_to_sym(const Elf64_Sym *src, GElf_Sym *dst) { if (src != NULL) { bcopy(src, dst, sizeof (GElf_Sym)); return (dst); } return (NULL); } /*ARGSUSED*/ static GElf_Sym * gelf64_nocopy(const Elf64_Sym *src, GElf_Sym *dst) { return ((GElf_Sym *)src); } static const void * gelf32_sym_search(const Elf32_Sym **asmap, size_t aslen, uintptr_t addr) { ulong_t i, mid, lo = 0, hi = aslen - 1; const Elf32_Sym *symp; Elf32_Addr v; size_t size; if (aslen == 0) return (NULL); while (hi - lo > 1) { mid = (lo + hi) / 2; if (addr >= asmap[mid]->st_value) lo = mid; else hi = mid; } i = addr < asmap[hi]->st_value ? lo : hi; symp = asmap[i]; v = symp->st_value; /* * If the previous entry has the same value, improve our choice. The * order of equal-valued symbols is determined by gelf32_sym_compare(). */ while (i-- != 0 && asmap[i]->st_value == v) symp = asmap[i]; /* * If an absolute symbol distance was specified, use that; otherwise * use the ELF symbol size, or 1 byte if the ELF size is zero. */ if (mdb.m_symdist == 0) size = MAX(symp->st_size, 1); else size = mdb.m_symdist; if (addr - symp->st_value < size) return (symp); return (NULL); } static const void * gelf64_sym_search(const Elf64_Sym **asmap, size_t aslen, uintptr_t addr) { ulong_t i, mid, lo = 0, hi = aslen - 1; const Elf64_Sym *symp; Elf64_Addr v; size_t size; if (aslen == 0) return (NULL); while (hi - lo > 1) { mid = (lo + hi) / 2; if (addr >= asmap[mid]->st_value) lo = mid; else hi = mid; } i = addr < asmap[hi]->st_value ? lo : hi; symp = asmap[i]; v = symp->st_value; /* * If the previous entry has the same value, improve our choice. The * order of equal-valued symbols is determined by gelf64_sym_compare(). */ while (i-- != 0 && asmap[i]->st_value == v) symp = asmap[i]; /* * If an absolute symbol distance was specified, use that; otherwise * use the ELF symbol size, or 1 byte if the ELF size is zero. */ if (mdb.m_symdist == 0) size = MAX(symp->st_size, 1); else size = mdb.m_symdist; if (addr - symp->st_value < size) return (symp); return (NULL); } const char * mdb_gelf_sym_name(mdb_gelf_symtab_t *gst, const GElf_Sym *sym) { const mdb_gelf_dsym_t *dsp; if (gst->gst_ssect != NULL) return ((const char *)gst->gst_ssect->gs_data + sym->st_name); if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) dsp = gelf32_sym_search(gst->gst_asmap, gst->gst_aslen, sym->st_value); else dsp = gelf64_sym_search(gst->gst_asmap, gst->gst_aslen, sym->st_value); if (dsp != NULL) return (mdb_nv_get_name(dsp->ds_var)); return (NULL); } int mdb_gelf_sym_closer(const GElf_Sym *s1, const GElf_Sym *s2, uintptr_t addr) { uintptr_t v1 = (uintptr_t)s1->st_value; uintptr_t v2 = (uintptr_t)s2->st_value; uintptr_t d1 = v1 > addr ? v1 - addr : addr - v1; uintptr_t d2 = v2 > addr ? v2 - addr : addr - v2; return (d1 < d2); } int mdb_gelf_symtab_lookup_by_addr(mdb_gelf_symtab_t *gst, uintptr_t addr, uint_t flags, char *buf, size_t nbytes, GElf_Sym *sym, uint_t *idp) { union { const mdb_gelf_dsym_t *dsp; const Elf32_Sym *s32; const Elf64_Sym *s64; caddr_t sp; } u; const char *name; if (gst == NULL) return (set_errno(EMDB_NOSYMADDR)); if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) { u.s32 = gelf32_sym_search(gst->gst_asmap, gst->gst_aslen, addr); if (gelf32_to_sym(u.s32, sym) == NULL) return (set_errno(EMDB_NOSYMADDR)); } else { u.s64 = gelf64_sym_search(gst->gst_asmap, gst->gst_aslen, addr); if (gelf64_to_sym(u.s64, sym) == NULL) return (set_errno(EMDB_NOSYMADDR)); } if ((flags & GST_EXACT) && (sym->st_value != addr)) return (set_errno(EMDB_NOSYMADDR)); if (gst->gst_ssect != NULL) { name = (const char *)gst->gst_ssect->gs_data + sym->st_name; if (idp != NULL) { *idp = (u.sp - (caddr_t)gst->gst_dsect->gs_data) / gst->gst_dsect->gs_shdr.sh_entsize; } } else { name = mdb_nv_get_name(u.dsp->ds_var); if (idp != NULL) *idp = u.dsp->ds_id; } if (nbytes > 0) { (void) strncpy(buf, name, nbytes - 1); buf[nbytes - 1] = '\0'; } return (0); } int mdb_gelf_symtab_lookup_by_name(mdb_gelf_symtab_t *gst, const char *name, GElf_Sym *sym, uint_t *idp) { mdb_var_t *v; if (gst != NULL && (v = mdb_nv_lookup(&gst->gst_nv, name)) != NULL) { if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) (void) gelf32_to_sym(mdb_nv_get_cookie(v), sym); else (void) gelf64_to_sym(mdb_nv_get_cookie(v), sym); if (idp != NULL) { if (gst->gst_file == NULL && gst->gst_dsect == NULL) { mdb_gelf_dsym_t *dsp = mdb_nv_get_cookie(v); *idp = dsp->ds_id; } else { *idp = ((uintptr_t)mdb_nv_get_cookie(v) - (uintptr_t)gst->gst_dsect->gs_data) / gst->gst_dsect->gs_shdr.sh_entsize; } } return (0); } return (set_errno(EMDB_NOSYM)); } int mdb_gelf_symtab_lookup_by_file(mdb_gelf_symtab_t *gst, const char *file, const char *name, GElf_Sym *sym, uint_t *idp) { GElf_Sym *(*s2gelf)(const void *, GElf_Sym *); size_t sym_size; caddr_t sp, ep; mdb_var_t *v; if (gst == NULL) return (set_errno(EMDB_NOSYM)); if ((v = mdb_nv_lookup(&gst->gst_nv, file)) == NULL) return (set_errno(EMDB_NOOBJ)); if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) { s2gelf = (GElf_Sym *(*)(const void *, GElf_Sym *))gelf32_to_sym; sym_size = sizeof (Elf32_Sym); } else { s2gelf = (GElf_Sym *(*)(const void *, GElf_Sym *))gelf64_to_sym; sym_size = sizeof (Elf64_Sym); } (void) s2gelf(mdb_nv_get_cookie(v), sym); if (GELF_ST_TYPE(sym->st_info) != STT_FILE) return (set_errno(EMDB_NOOBJ)); ep = (caddr_t)gst->gst_dsect->gs_data + gst->gst_dsect->gs_shdr.sh_size; sp = (caddr_t)mdb_nv_get_cookie(v); /* * We assume that symbol lookups scoped by source file name are only * relevant for userland debugging and are a relatively rare request, * and so we use a simple but inefficient linear search with copying. */ for (sp += sym_size; sp < ep; sp += sym_size) { (void) s2gelf(sp, sym); /* Convert native symbol to GElf */ if (GELF_ST_TYPE(sym->st_info) == STT_SECTION || GELF_ST_TYPE(sym->st_info) == STT_FILE || GELF_ST_BIND(sym->st_info) != STB_LOCAL) break; /* End of this file's locals */ if (strcmp(mdb_gelf_sym_name(gst, sym), name) == 0) { if (idp != NULL) { *idp = (sp - (caddr_t) gst->gst_dsect->gs_data) / sym_size; } return (0); } } return (set_errno(EMDB_NOSYM)); } void mdb_gelf_symtab_iter(mdb_gelf_symtab_t *gst, int (*func)(void *, const GElf_Sym *, const char *, uint_t), void *private) { GElf_Sym *(*s2gelf)(const void *, GElf_Sym *); GElf_Sym sym, *symp; size_t sym_size; if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) { s2gelf = (GElf_Sym *(*)(const void *, GElf_Sym *))gelf32_to_sym; sym_size = sizeof (Elf32_Sym); } else { s2gelf = (GElf_Sym *(*)(const void *, GElf_Sym *))gelf64_nocopy; sym_size = sizeof (Elf64_Sym); } /* * If this is a mutable symbol table, we iterate over the hash table * of symbol names; otherwise we go iterate over the data buffer. For * non-mutable tables, this means that ::nm will show all symbols, * including those with duplicate names (not present in gst_nv). */ if (gst->gst_file == NULL && gst->gst_dsect == NULL) { mdb_gelf_dsym_t *dsp; mdb_var_t *v; mdb_nv_rewind(&gst->gst_nv); while ((v = mdb_nv_advance(&gst->gst_nv)) != NULL) { dsp = mdb_nv_get_cookie(v); symp = s2gelf(dsp, &sym); if (func(private, symp, mdb_nv_get_name(v), dsp->ds_id) == -1) break; } } else { const char *sbase = gst->gst_ssect->gs_data; caddr_t sp = gst->gst_dsect->gs_data; caddr_t ep = sp + gst->gst_dsect->gs_shdr.sh_size; uint_t i; for (i = 0; sp < ep; sp += sym_size, i++) { symp = s2gelf(sp, &sym); if (func(private, symp, sbase + symp->st_name, i) == -1) break; } } } static void gelf_sym_to_32(const GElf_Sym *src, Elf32_Sym *dst) { dst->st_name = src->st_name; dst->st_info = src->st_info; dst->st_other = src->st_other; dst->st_shndx = src->st_shndx; dst->st_value = (Elf32_Addr)src->st_value; dst->st_size = (Elf32_Word)src->st_size; } static void gelf_sym_to_64(const GElf_Sym *src, Elf64_Sym *dst) { bcopy(src, dst, sizeof (Elf64_Sym)); } void mdb_gelf_symtab_insert(mdb_gelf_symtab_t *gst, const char *name, const GElf_Sym *symp) { mdb_gelf_dsym_t *dsp; mdb_var_t *v; ASSERT(gst->gst_file == NULL && gst->gst_dsect == NULL); v = mdb_nv_lookup(&gst->gst_nv, name); if (v == NULL) { char *s = mdb_alloc(strlen(name) + 1, UM_SLEEP); (void) strcpy(s, name); dsp = mdb_alloc(sizeof (mdb_gelf_dsym_t), UM_SLEEP); dsp->ds_id = gst->gst_id++; dsp->ds_var = mdb_nv_insert(&gst->gst_nv, s, NULL, (uintptr_t)dsp, GST_NVFLG); gst->gst_aslen++; ASSERT(gst->gst_aslen == mdb_nv_size(&gst->gst_nv)); if (gst->gst_aslen > gst->gst_asrsv) { mdb_free(gst->gst_asmap, sizeof (void *) * gst->gst_asrsv); gst->gst_asrsv = gst->gst_asrsv != 0 ? gst->gst_asrsv * GST_GROW : GST_DEFSZ; gst->gst_asmap = mdb_alloc(sizeof (void *) * gst->gst_asrsv, UM_SLEEP); } } else dsp = mdb_nv_get_cookie(v); mdb_dprintf(MDB_DBG_ELF, "added symbol (\"%s\", %llx)\n", name, (u_longlong_t)symp->st_value); bcopy(symp, &dsp->ds_sym, sizeof (GElf_Sym)); dsp->ds_sym.st_name = (uintptr_t)mdb_nv_get_name(dsp->ds_var); if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) { gelf_sym_to_32(symp, &dsp->ds_u.ds_s32); gelf32_symtab_sort(gst); } else { gelf_sym_to_64(symp, &dsp->ds_u.ds_s64); gelf64_symtab_sort(gst); } } void mdb_gelf_symtab_delete(mdb_gelf_symtab_t *gst, const char *name, GElf_Sym *symp) { mdb_var_t *v; ASSERT(gst->gst_file == NULL && gst->gst_dsect == NULL); v = mdb_nv_lookup(&gst->gst_nv, name); if (v != NULL) { char *name = (char *)mdb_nv_get_name(v); mdb_gelf_dsym_t *dsp = mdb_nv_get_cookie(v); if (symp != NULL) bcopy(&dsp->ds_sym, symp, sizeof (GElf_Sym)); mdb_dprintf(MDB_DBG_ELF, "removed symbol (\"%s\", %llx)\n", name, (u_longlong_t)dsp->ds_sym.st_value); mdb_nv_remove(&gst->gst_nv, v); gst->gst_aslen--; ASSERT(gst->gst_aslen == mdb_nv_size(&gst->gst_nv)); mdb_free(name, strlen(name) + 1); mdb_free(dsp, sizeof (mdb_gelf_dsym_t)); if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) gelf32_symtab_sort(gst); else gelf64_symtab_sort(gst); } } static const GElf_Phdr * gelf_phdr_lookup(mdb_gelf_file_t *gf, uintptr_t addr) { const GElf_Phdr *gpp = gf->gf_phdrs; size_t i; for (i = 0; i < gf->gf_npload; i++, gpp++) { if (addr >= gpp->p_vaddr && addr < gpp->p_vaddr + gpp->p_memsz) return (gpp); } return (NULL); } ssize_t mdb_gelf_rw(mdb_gelf_file_t *gf, void *buf, size_t nbytes, uintptr_t addr, ssize_t (*prw)(mdb_io_t *, void *, size_t), mdb_gelf_rw_t rw) { ssize_t resid = nbytes; while (resid != 0) { const GElf_Phdr *php = gelf_phdr_lookup(gf, addr); uintptr_t mapoff; ssize_t memlen, filelen, len = 0; off64_t off; if (php == NULL) break; /* No mapping for this address */ mapoff = addr - php->p_vaddr; memlen = MIN(resid, php->p_memsz - mapoff); filelen = MIN(resid, php->p_filesz - mapoff); off = (off64_t)php->p_offset + mapoff; if (filelen > 0 && (IOP_SEEK(gf->gf_io, off, SEEK_SET) != off || (len = prw(gf->gf_io, buf, filelen)) <= 0)) break; if (rw == GIO_READ && len == filelen && filelen < memlen) { bzero((char *)buf + len, memlen - filelen); len += memlen - filelen; } resid -= len; addr += len; buf = (char *)buf + len; } if (resid == nbytes && nbytes != 0) return (set_errno(EMDB_NOMAP)); return (nbytes - resid); } mdb_gelf_sect_t * mdb_gelf_sect_by_name(mdb_gelf_file_t *gf, const char *name) { size_t i; for (i = 0; i < gf->gf_shnum; i++) { if (strcmp(gf->gf_sects[i].gs_name, name) == 0) return (&gf->gf_sects[i]); } return (NULL); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_GELF_H #define _MDB_GELF_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB #define GST_FUZZY 0 /* lookup_by_addr matches closest sym */ #define GST_EXACT 1 /* lookup_by_addr must be exact */ #define GF_FILE 0 /* Open as ELF file image */ #define GF_PROGRAM 1 /* Open as ELF program image */ typedef struct mdb_gelf_sect { GElf_Shdr gs_shdr; /* ELF section header */ const char *gs_name; /* Section name */ void *gs_data; /* Section data */ } mdb_gelf_sect_t; typedef struct mdb_gelf_file { GElf_Ehdr gf_ehdr; /* ELF file header */ GElf_Phdr *gf_phdrs; /* Array of program headers */ size_t gf_npload; /* Number of sorted PT_LOAD phdrs */ GElf_Phdr *gf_dynp; /* Pointer to PT_DYNAMIC phdr */ GElf_Dyn *gf_dyns; /* Array of dynamic entries */ size_t gf_ndyns; /* Number of dynamic entries */ size_t gf_shnum; /* Number of section headers */ size_t gf_shstrndx; /* Index of section string table */ size_t gf_phnum; /* Number of program headers */ mdb_gelf_sect_t *gf_sects; /* Array of section structs */ mdb_io_t *gf_io; /* I/o backend for ELF file */ int gf_mode; /* Mode flag (see above) */ } mdb_gelf_file_t; typedef struct mdb_gelf_symtab { mdb_nv_t gst_nv; /* Name/value hash for name lookups */ void *gst_asmap; /* Sorted array of symbol pointers */ size_t gst_aslen; /* Number of entries in gst_asmap */ size_t gst_asrsv; /* Actual reserved size of gst_asmap */ const GElf_Ehdr *gst_ehdr; /* Associated ELF file ehdr */ mdb_gelf_file_t *gst_file; /* Associated ELF file */ mdb_gelf_sect_t *gst_dsect; /* Associated ELF data section */ mdb_gelf_sect_t *gst_ssect; /* Associated ELF string section */ uint_t gst_id; /* Next symbol ID to use if mutable */ uint_t gst_tabid; /* ID for symbol table */ } mdb_gelf_symtab_t; typedef struct mdb_gelf_dsym { union { Elf32_Sym ds_s32; /* 32-bit native symbol data */ Elf64_Sym ds_s64; /* 64-bit native symbol data */ } ds_u; GElf_Sym ds_sym; /* Generic ELF symbol data */ mdb_var_t *ds_var; /* Backpointer to nv element */ uint_t ds_id; /* Symbol id number */ } mdb_gelf_dsym_t; extern int mdb_gelf_check(mdb_io_t *, Elf32_Ehdr *, GElf_Half); extern mdb_gelf_file_t *mdb_gelf_create(mdb_io_t *, GElf_Half, int); extern void mdb_gelf_destroy(mdb_gelf_file_t *); extern void mdb_gelf_dyns_set(mdb_gelf_file_t *, void *, size_t); extern void mdb_gelf_ehdr_to_gehdr(Ehdr *, GElf_Ehdr *); typedef enum { GIO_READ, GIO_WRITE } mdb_gelf_rw_t; extern ssize_t mdb_gelf_rw(mdb_gelf_file_t *, void *, size_t, uintptr_t, ssize_t (*)(mdb_io_t *, void *, size_t), mdb_gelf_rw_t); extern mdb_gelf_symtab_t *mdb_gelf_symtab_create_file(mdb_gelf_file_t *, GElf_Word, uint_t); extern mdb_gelf_symtab_t *mdb_gelf_symtab_create_file_by_name(mdb_gelf_file_t *, const char *, const char *, uint_t); extern mdb_gelf_symtab_t *mdb_gelf_symtab_create_raw(const GElf_Ehdr *, const void *, void *, const void *, void *, uint_t); extern mdb_gelf_symtab_t *mdb_gelf_symtab_create_dynamic(mdb_gelf_file_t *, uint_t); extern mdb_gelf_symtab_t *mdb_gelf_symtab_create_mutable(void); extern void mdb_gelf_symtab_destroy(mdb_gelf_symtab_t *); extern size_t mdb_gelf_symtab_size(mdb_gelf_symtab_t *); extern const char *mdb_gelf_sym_name(mdb_gelf_symtab_t *, const GElf_Sym *); extern int mdb_gelf_sym_closer(const GElf_Sym *, const GElf_Sym *, uintptr_t); extern int mdb_gelf_symtab_lookup_by_addr(mdb_gelf_symtab_t *, uintptr_t, uint_t, char *, size_t, GElf_Sym *, uint_t *); extern int mdb_gelf_symtab_lookup_by_name(mdb_gelf_symtab_t *, const char *, GElf_Sym *, uint_t *); extern int mdb_gelf_symtab_lookup_by_file(mdb_gelf_symtab_t *, const char *, const char *, GElf_Sym *, uint_t *); extern void mdb_gelf_symtab_iter(mdb_gelf_symtab_t *, int (*)(void *, const GElf_Sym *, const char *, uint_t), void *); extern void mdb_gelf_symtab_insert(mdb_gelf_symtab_t *, const char *, const GElf_Sym *); extern void mdb_gelf_symtab_delete(mdb_gelf_symtab_t *, const char *, GElf_Sym *); extern mdb_gelf_sect_t *mdb_gelf_sect_by_name(mdb_gelf_file_t *, const char *); extern void *mdb_gelf_sect_load(mdb_gelf_file_t *, mdb_gelf_sect_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_GELF_H */ %{ /* * 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. */ /* * Copyright (c) 2019, Joyent, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include /* * Utility routines to fetch values from the target's virtual address space * and object file, respectively. These are called from the handlers for * the * /.../ and % /.../ code below. */ extern int yylex(void); static void vfetch(void *buf, size_t nbytes, uintptr_t addr) { if (mdb_tgt_vread(mdb.m_target, buf, nbytes, addr) != nbytes) yyperror("failed to read from address %p", addr); } static void ffetch(void *buf, size_t nbytes, uintptr_t addr) { if (mdb_tgt_fread(mdb.m_target, buf, nbytes, addr) != nbytes) yyperror("failed to read from address %p", addr); } /* * Because we define YYMAXDEPTH as zero below, we have to provide a YYEXPAND() * function to expand our yys and yyv variables. For simplicity, we currently * define these structures statically; a more complex solution can be defined if * it is ever needed. If we return 'val', yacc assumes resize has failed. */ static int yyexpand(int val) { return (val ? val : YYMAXDEPTH); } #define YYEXPAND yyexpand /* * This will cause the rest of the yacc code to assume that yys and yyv are * pointers, not static arrays. */ #undef YYMAXDEPTH #define YYMAXDEPTH 0 %} %union { char *l_string; char l_char; uintmax_t l_immediate; mdb_var_t *l_var; mdb_idcmd_t *l_dcmd; } %token MDB_TOK_SYMBOL %token MDB_TOK_STRING %token MDB_TOK_CHAR %token MDB_TOK_IMMEDIATE %token MDB_TOK_DCMD %token MDB_TOK_VAR_REF %token MDB_TOK_LEXPR %token MDB_TOK_REXPR %token MDB_TOK_COR1_DEREF %token MDB_TOK_COR2_DEREF %token MDB_TOK_COR4_DEREF %token MDB_TOK_COR8_DEREF %token MDB_TOK_OBJ1_DEREF %token MDB_TOK_OBJ2_DEREF %token MDB_TOK_OBJ4_DEREF %token MDB_TOK_OBJ8_DEREF %left '|' %left '^' %left '&' %left MDB_TOK_EQUAL MDB_TOK_NOTEQUAL %left MDB_TOK_LSHIFT MDB_TOK_RSHIFT %left '-' '+' %left '*' '%' '#' %left MDB_TOK_MODULUS %right MDB_COR_VALUE %right MDB_OBJ_VALUE %right MDB_INT_NEGATE %right MDB_BIT_COMPLEMENT %right MDB_LOG_NEGATE %right MDB_VAR_REFERENCE %type expression %type command %% statement_list: /* Empty */ | statement_list statement { return (0); } ; terminator: '\n' | ';' statement: pipeline shell_pipe terminator { if (!mdb_call(mdb_nv_get_value(mdb.m_dot), 1, 0)) return (0); } | expression pipeline shell_pipe terminator { if (!mdb_call($1, 1, DCMD_ADDRSPEC)) return (0); } | expression ',' expression pipeline shell_pipe terminator { if (!mdb_call($1, $3, DCMD_ADDRSPEC | DCMD_LOOP)) return (0); } | ',' expression pipeline shell_pipe terminator { if (!mdb_call(mdb_nv_get_value(mdb.m_dot), $2, DCMD_LOOP)) return (0); } | expression terminator { mdb_frame_t *pfp = mdb_frame_pipe(); /* * The handling of naked expressions is slightly tricky: * in a string context, we want to just set dot to the * expression value. In a pipe context, we also set * dot but need to record the address in the right- * hand command's addrv and update any vcbs that are * active. Otherwise, on the command-line, we have to * support this as an alias for executing the previous * command with the new value of dot. Sigh. */ if (mdb_iob_isastr(mdb.m_in)) { mdb_nv_set_value(mdb.m_dot, $1); mdb.m_incr = 0; } else if (pfp != NULL && pfp->f_pcmd != NULL) { mdb_addrvec_unshift(&pfp->f_pcmd->c_addrv, (uintptr_t)$1); mdb_vcb_update(pfp, (uintptr_t)$1); mdb_nv_set_value(mdb.m_dot, $1); } else { mdb_list_move(&mdb.m_lastc, &mdb.m_frame->f_cmds); if (!mdb_call($1, 1, DCMD_ADDRSPEC)) return (0); } } | expression ',' expression shell_pipe terminator { mdb_list_move(&mdb.m_lastc, &mdb.m_frame->f_cmds); if (!mdb_call($1, $3, DCMD_ADDRSPEC | DCMD_LOOP)) return (0); } | ',' expression shell_pipe terminator { uintmax_t dot = mdb_dot_incr(","); mdb_list_move(&mdb.m_lastc, &mdb.m_frame->f_cmds); if (!mdb_call(dot, $2, DCMD_LOOP)) return (0); } | '!' MDB_TOK_STRING terminator { if (mdb_iob_isapipe(mdb.m_in)) yyerror("syntax error"); mdb_shell_exec($2); } | terminator { if ((mdb.m_flags & MDB_FL_REPLAST) && !mdb_iob_isastr(mdb.m_in)) { uintmax_t dot = mdb_dot_incr("\\n"); /* * If a bare terminator is encountered, execute * the previous command if -o repeatlast is set * and stdin is not an mdb_eval() string. */ mdb_list_move(&mdb.m_lastc, &mdb.m_frame->f_cmds); if (!mdb_call(dot, 1, 0)) return (0); } } ; pipeline: pipeline '|' command { mdb_cmd_create($3, &mdb.m_frame->f_argvec); } | command { mdb_cmd_create($1, &mdb.m_frame->f_argvec); } ; command: '?' format_list { $$ = mdb_dcmd_lookup("?"); } | '/' format_list { $$ = mdb_dcmd_lookup("/"); } | '\\' format_list { $$ = mdb_dcmd_lookup("\\"); } | '@' format_list { $$ = mdb_dcmd_lookup("@"); } | '=' format_list { $$ = mdb_dcmd_lookup("="); } | MDB_TOK_DCMD argument_list { $$ = $1; } | '$' { $$ = mdb_dcmd_lookup("$?"); } ; shell_pipe: /* Empty */ | '!' MDB_TOK_STRING { mdb_shell_pipe($2); } ; format_list: /* Empty */ | format_list MDB_TOK_LEXPR expression MDB_TOK_REXPR { mdb_arg_t arg; arg.a_type = MDB_TYPE_IMMEDIATE; arg.a_un.a_val = $3; mdb_argvec_append(&mdb.m_frame->f_argvec, &arg); } | format_list MDB_TOK_IMMEDIATE { mdb_arg_t arg; arg.a_type = MDB_TYPE_IMMEDIATE; arg.a_un.a_val = $2; mdb_argvec_append(&mdb.m_frame->f_argvec, &arg); } | format_list MDB_TOK_STRING { mdb_arg_t arg; arg.a_type = MDB_TYPE_STRING; arg.a_un.a_str = $2; mdb_argvec_append(&mdb.m_frame->f_argvec, &arg); } | format_list MDB_TOK_CHAR { mdb_arg_t arg; arg.a_type = MDB_TYPE_CHAR; arg.a_un.a_char = $2; mdb_argvec_append(&mdb.m_frame->f_argvec, &arg); } ; argument_list: /* Empty */ | argument_list MDB_TOK_LEXPR expression MDB_TOK_REXPR { mdb_arg_t arg; arg.a_type = MDB_TYPE_IMMEDIATE; arg.a_un.a_val = $3; mdb_argvec_append(&mdb.m_frame->f_argvec, &arg); } | argument_list MDB_TOK_STRING { mdb_arg_t arg; arg.a_type = MDB_TYPE_STRING; arg.a_un.a_str = $2; mdb_argvec_append(&mdb.m_frame->f_argvec, &arg); } ; expression: expression '+' expression { $$ = $1 + $3; } | expression '-' expression { $$ = $1 - $3; } | expression '*' expression { $$ = $1 * $3; } | expression '%' expression { if ($3 == 0UL) yyerror("attempted to divide by zero"); /* * Annoyingly, x86 generates a #DE when dividing * LONG_MIN by -1; check for this case explicitly. */ if ($1 == LONG_MIN && $3 == -1L) yyerror("divide overflow"); $$ = (intmax_t)$1 / (intmax_t)$3; } | expression MDB_TOK_MODULUS expression { if ($3 == 0UL) yyerror("attempted to divide by zero"); $$ = $1 % $3; } | expression '&' expression { $$ = $1 & $3; } | expression '|' expression { $$ = $1 | $3; } | expression '^' expression { $$ = $1 ^ $3; } | expression MDB_TOK_EQUAL expression { $$ = ($1 == $3); } | expression MDB_TOK_NOTEQUAL expression { $$ = ($1 != $3); } | expression MDB_TOK_LSHIFT expression { $$ = $1 << $3; } | expression MDB_TOK_RSHIFT expression { $$ = $1 >> $3; } | expression '#' expression { if ($3 == 0UL) yyerror("attempted to divide by zero"); $$ = ((intptr_t)($1 + ($3 - 1)) / (intptr_t)$3) * $3; } | '*' expression %prec MDB_COR_VALUE { uintptr_t value; vfetch(&value, sizeof (value), $2); $$ = value; } | MDB_TOK_COR1_DEREF expression %prec MDB_COR_VALUE { uint8_t value; vfetch(&value, sizeof (value), $2); $$ = value; } | MDB_TOK_COR2_DEREF expression %prec MDB_COR_VALUE { uint16_t value; vfetch(&value, sizeof (value), $2); $$ = value; } | MDB_TOK_COR4_DEREF expression %prec MDB_COR_VALUE { uint32_t value; vfetch(&value, sizeof (value), $2); $$ = value; } | MDB_TOK_COR8_DEREF expression %prec MDB_COR_VALUE { uint64_t value; vfetch(&value, sizeof (value), $2); $$ = value; } | '%' expression %prec MDB_OBJ_VALUE { uintptr_t value; ffetch(&value, sizeof (value), $2); $$ = value; } | MDB_TOK_OBJ1_DEREF expression %prec MDB_OBJ_VALUE { uint8_t value; ffetch(&value, sizeof (value), $2); $$ = value; } | MDB_TOK_OBJ2_DEREF expression %prec MDB_OBJ_VALUE { uint16_t value; ffetch(&value, sizeof (value), $2); $$ = value; } | MDB_TOK_OBJ4_DEREF expression %prec MDB_OBJ_VALUE { uint32_t value; ffetch(&value, sizeof (value), $2); $$ = value; } | MDB_TOK_OBJ8_DEREF expression %prec MDB_OBJ_VALUE { uint64_t value; ffetch(&value, sizeof (value), $2); $$ = value; } | '-' expression %prec MDB_INT_NEGATE { $$ = -$2; } | '~' expression %prec MDB_BIT_COMPLEMENT { $$ = ~$2; } | '#' expression %prec MDB_LOG_NEGATE { $$ = !$2; } | '(' expression ')' { $$ = $2; } | MDB_TOK_VAR_REF %prec MDB_VAR_REFERENCE { $$ = mdb_nv_get_value($1); } | MDB_TOK_SYMBOL { if (strcmp($1, ".") == 0) { $$ = mdb_nv_get_value(mdb.m_dot); strfree($1); } else { const char *obj = MDB_TGT_OBJ_EVERY, *name = $1; char *s = (char *)$1; GElf_Sym sym; if ((s = strrsplit(s, '`')) != NULL) { name = s; obj = $1; } if (mdb_tgt_lookup_by_name(mdb.m_target, obj, name, &sym, NULL) == -1) { strfree($1); yyperror("failed to dereference " "symbol"); } strfree($1); $$ = (uintmax_t)sym.st_value; } } | '+' { $$ = mdb_dot_incr("+"); } | '^' { $$ = mdb_dot_decr("^"); } | '&' { $$ = mdb.m_raddr; } | MDB_TOK_IMMEDIATE ; %% /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2012, Joyent, Inc. All rights reserved. * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. */ #include #include #include #include #include #include #include const char _mdb_help[] = "\nEach debugger command in %s is structured as follows:\n\n" " [ address [, count]] verb [ arguments ... ]\n" " ^ ^ ^ ^\n" " the start --+ | | +-- arguments are strings which can be\n" " address can be an | | quoted using \"\" or '' or\n" " expression | | expressions enclosed in $[ ]\n" " | |\n" " the repeat count --+ +--------- the verb is a name which begins\n" " is also an expression with either $, :, or ::. it can also\n" " be a format specifier (/ \\ ? or =)\n\n" "For information on debugger commands (dcmds) and walkers, type:\n\n" " ::help cmdname ... for more detailed information on a command\n" " ::dcmds ... for a list of dcmds and their descriptions\n" " ::walkers ... for a list of walkers and their descriptions\n" " ::dmods -l ... for a list of modules and their dcmds and walkers\n" " ::formats ... for a list of format characters for / \\ ? and =\n\n" "For information on command-line options, type:\n\n" " $ %s -? ... in your shell for a complete list of options\n\n"; /*ARGSUSED*/ static int print_dcmd(mdb_var_t *v, void *ignored) { const mdb_idcmd_t *idcp = mdb_nv_get_cookie(v); if (idcp->idc_descr != NULL) mdb_printf(" dcmd %-20s - %s\n", idcp->idc_name, idcp->idc_descr); return (0); } /*ARGSUSED*/ static int print_walk(mdb_var_t *v, void *ignored) { const mdb_iwalker_t *iwp = mdb_nv_get_cookie(v); if (iwp->iwlk_descr != NULL) mdb_printf(" walk %-20s - %s\n", iwp->iwlk_name, iwp->iwlk_descr); return (0); } /*ARGSUSED*/ static int print_dmod_long(mdb_var_t *v, void *ignored) { mdb_module_t *mod = mdb_nv_get_cookie(v); mdb_printf("\n%%-70s%\n", mod->mod_name); if (mod->mod_tgt_ctor != NULL) { mdb_printf(" ctor 0x%-18lx - target constructor\n", (ulong_t)mod->mod_tgt_ctor); } if (mod->mod_dis_ctor != NULL) { mdb_printf(" ctor 0x%-18lx - disassembler constructor\n", (ulong_t)mod->mod_dis_ctor); } mdb_nv_sort_iter(&mod->mod_dcmds, print_dcmd, NULL, UM_SLEEP | UM_GC); mdb_nv_sort_iter(&mod->mod_walkers, print_walk, NULL, UM_SLEEP | UM_GC); return (0); } /*ARGSUSED*/ static int print_dmod_short(mdb_var_t *v, void *ignored) { mdb_printf("%s\n", mdb_nv_get_name(v)); return (0); } /*ARGSUSED*/ int cmd_dmods(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int (*func)(mdb_var_t *, void *); uint_t opt_l = FALSE; mdb_var_t *v; int i; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); i = mdb_getopts(argc, argv, 'l', MDB_OPT_SETBITS, TRUE, &opt_l, NULL); func = opt_l ? print_dmod_long : print_dmod_short; if (i != argc) { if (argc - i != 1 || argv[i].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); v = mdb_nv_lookup(&mdb.m_modules, argv[i].a_un.a_str); if (v == NULL) mdb_warn("%s module not loaded\n", argv[i].a_un.a_str); else (void) func(v, NULL); } else mdb_nv_sort_iter(&mdb.m_modules, func, NULL, UM_SLEEP | UM_GC); return (DCMD_OK); } #define FILTER_NAMEONLY 0x1 typedef struct filter_data { const char *pattern; int flags; #ifndef _KMDB regex_t reg; #endif } filter_data_t; static void filter_help(void) { mdb_printf("Options:\n" " -n Match only the name, not the description.\n" #ifdef _KMDB " pattern Substring to match against name/description." #else " pattern RE to match against name/description." #endif "\n"); } void cmd_dcmds_help(void) { mdb_printf( "List all of the dcmds that are currently available. If a pattern\n" "is provided then list only the commands that\n" #ifdef _KMDB "contain the provided substring." #else "match the provided regular expression." #endif "\n"); filter_help(); } void cmd_walkers_help(void) { mdb_printf( "List all of the walkers that are currently available. If a\n" "pattern is provided then list only the walkers that\n" #ifdef _KMDB "contain the provided substring." #else "match the provided regular expression." #endif "\n"); filter_help(); } static int print_wdesc(mdb_var_t *v, void *data) { filter_data_t *f = data; mdb_iwalker_t *iwp = mdb_nv_get_cookie(mdb_nv_get_cookie(v)); const char *name = mdb_nv_get_name(v); boolean_t output = FALSE; if (name == NULL || iwp->iwlk_descr == NULL) return (0); if (f->pattern == NULL) { output = TRUE; } else { #ifdef _KMDB /* * kmdb doesn't have access to the reg* functions, so we fall * back to strstr. */ if (strstr(name, f->pattern) != NULL || (!(f->flags & FILTER_NAMEONLY) && strstr(iwp->iwlk_descr, f->pattern) != NULL)) output = TRUE; #else regmatch_t pmatch; if (regexec(&f->reg, name, 1, &pmatch, 0) == 0 || (!(f->flags & FILTER_NAMEONLY) && regexec(&f->reg, iwp->iwlk_descr, 1, &pmatch, 0) == 0)) output = TRUE; #endif } if (output) mdb_printf("%-24s - %s\n", name, iwp->iwlk_descr); return (0); } /*ARGSUSED*/ int cmd_walkers(uintptr_t addr __unused, uint_t flags, int argc, const mdb_arg_t *argv) { filter_data_t f; int i; #ifndef _KMDB int err; #endif if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); f.pattern = NULL; f.flags = 0; i = mdb_getopts(argc, argv, 'n', MDB_OPT_SETBITS, FILTER_NAMEONLY, &f.flags, NULL); argc -= i; argv += i; if (argc == 1) { if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); f.pattern = argv->a_un.a_str; #ifndef _KMDB if ((err = regcomp(&f.reg, f.pattern, REG_EXTENDED)) != 0) { size_t nbytes; char *buf; nbytes = regerror(err, &f.reg, NULL, 0); buf = mdb_alloc(nbytes + 1, UM_SLEEP | UM_GC); (void) regerror(err, &f.reg, buf, nbytes); mdb_warn("%s\n", buf); return (DCMD_ERR); } #endif } else if (argc != 0) { return (DCMD_USAGE); } mdb_nv_sort_iter(&mdb.m_walkers, print_wdesc, &f, UM_SLEEP | UM_GC); return (DCMD_OK); } static int print_ddesc(mdb_var_t *v, void *data) { filter_data_t *f = data; mdb_idcmd_t *idcp = mdb_nv_get_cookie(mdb_nv_get_cookie(v)); const char *name = mdb_nv_get_name(v); boolean_t output = FALSE; if (name == NULL || idcp->idc_descr == NULL) return (0); if (f->pattern == NULL) { output = TRUE; } else { #ifdef _KMDB /* * kmdb doesn't have access to the reg* functions, so we fall * back to strstr. */ if (strstr(name, f->pattern) != NULL || (!(f->flags & FILTER_NAMEONLY) && strstr(idcp->idc_descr, f->pattern) != NULL)) output = TRUE; #else regmatch_t pmatch; if (regexec(&f->reg, name, 1, &pmatch, 0) == 0 || (!(f->flags & FILTER_NAMEONLY) && regexec(&f->reg, idcp->idc_descr, 1, &pmatch, 0) == 0)) output = TRUE; #endif } if (output) mdb_printf("%-24s - %s\n", name, idcp->idc_descr); return (0); } /*ARGSUSED*/ int cmd_dcmds(uintptr_t addr __unused, uint_t flags, int argc, const mdb_arg_t *argv) { filter_data_t f; int i; #ifndef _KMDB int err; #endif if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); f.pattern = NULL; f.flags = 0; i = mdb_getopts(argc, argv, 'n', MDB_OPT_SETBITS, FILTER_NAMEONLY, &f.flags, NULL); argc -= i; argv += i; if (argc == 1) { if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); f.pattern = argv->a_un.a_str; #ifndef _KMDB if ((err = regcomp(&f.reg, f.pattern, REG_EXTENDED)) != 0) { size_t nbytes; char *buf; nbytes = regerror(err, &f.reg, NULL, 0); buf = mdb_alloc(nbytes + 1, UM_SLEEP | UM_GC); (void) regerror(err, &f.reg, buf, nbytes); mdb_warn("%s\n", buf); return (DCMD_ERR); } #endif } else if (argc != 0) { return (DCMD_USAGE); } mdb_nv_sort_iter(&mdb.m_dcmds, print_ddesc, &f, UM_SLEEP | UM_GC); return (DCMD_OK); } /*ARGSUSED*/ int cmd_help(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *prefix, *usage; const mdb_idcmd_t *idcp; if ((flags & DCMD_ADDRSPEC) || argc > 1) return (DCMD_USAGE); if (argc == 0) { mdb_printf(_mdb_help, mdb.m_pname, mdb.m_pname); return (DCMD_OK); } if (argv->a_type != MDB_TYPE_STRING) { warn("expected string argument\n"); return (DCMD_USAGE); } if (strncmp(argv->a_un.a_str, "::", 2) == 0) idcp = mdb_dcmd_lookup(argv->a_un.a_str + 2); else idcp = mdb_dcmd_lookup(argv->a_un.a_str); if (idcp == NULL) { mdb_warn("unknown command: %s\n", argv->a_un.a_str); return (DCMD_ERR); } prefix = strchr(":$=/\\?>", idcp->idc_name[0]) ? "" : "::"; usage = idcp->idc_usage ? idcp->idc_usage : ""; mdb_printf("\n%NAME%\n %s - %s\n\n", idcp->idc_name, idcp->idc_descr); mdb_printf("%SYNOPSIS%\n "); if (usage[0] == '?') { mdb_printf("[ %addr% ] "); usage++; } else if (usage[0] == ':') { mdb_printf("%addr% "); usage++; } mdb_printf("%s%s %s\n\n", prefix, idcp->idc_name, usage); if (idcp->idc_help != NULL) { mdb_printf("%DESCRIPTION%\n"); (void) mdb_inc_indent(2); idcp->idc_help(); (void) mdb_dec_indent(2); mdb_printf("\n"); } /* * For now, modules that are built-in mark their interfaces Evolving * (documented in mdb(1)) and modules that are loaded mark their * interfaces Unstable. In the future we could extend the dmod linkage * to include the module's intended stability and then show it here. */ mdb_printf("%ATTRIBUTES%\n\n"); mdb_printf(" Target: %s\n", mdb_tgt_name(mdb.m_target)); mdb_printf(" Module: %s\n", idcp->idc_modp->mod_name); mdb_printf(" Interface Stability: %s\n\n", (idcp->idc_descr != NULL && idcp->idc_modp->mod_hdl == NULL) ? "Evolving" : "Unstable"); return (DCMD_OK); } int cmd_help_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc == 0 && !(flags & DCMD_TAB_SPACE)) return (0); if (argc > 1) return (0); if (argc == 0) return (mdb_tab_complete_dcmd(mcp, NULL)); else return (mdb_tab_complete_dcmd(mcp, argv[0].a_un.a_str)); } static int print_dcmd_def(mdb_var_t *v, void *private) { mdb_idcmd_t *idcp = mdb_nv_get_cookie(mdb_nv_get_cookie(v)); int *ip = private; mdb_printf(" [%d] %s`%s\n", (*ip)++, idcp->idc_modp->mod_name, idcp->idc_name); return (0); } static int print_walker_def(mdb_var_t *v, void *private) { mdb_iwalker_t *iwp = mdb_nv_get_cookie(mdb_nv_get_cookie(v)); int *ip = private; mdb_printf(" [%d] %s`%s\n", (*ip)++, iwp->iwlk_modp->mod_name, iwp->iwlk_name); return (0); } /*ARGSUSED*/ int cmd_which(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char defn_hdr[] = " > definition list:\n"; uint_t opt_v = FALSE; int i; i = mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL); for (; i < argc; i++) { const char *s = argv[i].a_un.a_str; int found = FALSE; mdb_iwalker_t *iwp; mdb_idcmd_t *idcp; const char *alias; if (argv->a_type != MDB_TYPE_STRING) continue; if (s[0] == '$' && s[1] == '<') s += 2; if ((idcp = mdb_dcmd_lookup(s)) != NULL) { mdb_var_t *v = idcp->idc_var; int i = 1; if (idcp->idc_modp != &mdb.m_rmod) { mdb_printf("%s is a dcmd from module %s\n", s, idcp->idc_modp->mod_name); } else mdb_printf("%s is a built-in dcmd\n", s); if (opt_v) { mdb_printf(defn_hdr); mdb_nv_defn_iter(v, print_dcmd_def, &i); } found = TRUE; } if ((iwp = mdb_walker_lookup(s)) != NULL) { mdb_var_t *v = iwp->iwlk_var; int i = 1; if (iwp->iwlk_modp != &mdb.m_rmod) { mdb_printf("%s is a walker from module %s\n", s, iwp->iwlk_modp->mod_name); } else mdb_printf("%s is a built-in walker\n", s); if (opt_v) { mdb_printf(defn_hdr); mdb_nv_defn_iter(v, print_walker_def, &i); } found = TRUE; } if ((alias = mdb_macalias_lookup(s)) != NULL) { mdb_printf("%s is a macro alias for '%s'\n", s, alias); found = TRUE; } if (!found) mdb_warn("%s not found\n", s); } return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1998-1999 by Sun Microsystems, Inc. * All rights reserved. * Copyright (c) 2012, Joyent, Inc. All rights reserved. * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. */ #ifndef _MDB_HELP_H #define _MDB_HELP_H #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern int cmd_dmods(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_dcmds(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_walkers(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_formats(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_help(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_help_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); extern int cmd_which(uintptr_t, uint_t, int, const mdb_arg_t *); extern void cmd_dcmds_help(void); extern void cmd_walkers_help(void); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_HELP_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2020 Joyent, Inc. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2022 Oxide Computer Company * Copyright 2026 Edgecast Cloud LLC. */ /* * MDB uses its own enhanced standard i/o mechanism for all input and output. * This file provides the underpinnings of this mechanism, including the * printf-style formatting code, the output pager, and APIs for raw input * and output. This mechanism is used throughout the debugger for everything * from simple sprintf and printf-style formatting, to input to the lexer * and parser, to raw file i/o for reading ELF files. In general, we divide * our i/o implementation into two parts: * * (1) An i/o buffer (mdb_iob_t) provides buffered read or write capabilities, * as well as access to formatting and the ability to invoke a pager. The * buffer is constructed explicitly for use in either reading or writing; it * may not be used for both simultaneously. * * (2) Each i/o buffer is associated with an underlying i/o backend (mdb_io_t). * The backend provides, through an ops-vector, equivalents for the standard * read, write, lseek, ioctl, and close operations. In addition, the backend * can provide an IOP_NAME entry point for returning a name for the backend, * IOP_LINK and IOP_UNLINK entry points that are called when the backend is * connected or disconnected from an mdb_iob_t, and an IOP_SETATTR entry point * for manipulating terminal attributes. * * The i/o objects themselves are reference counted so that more than one i/o * buffer may make use of the same i/o backend. In addition, each buffer * provides the ability to push or pop backends to interpose on input or output * behavior. We make use of this, for example, to implement interactive * session logging. Normally, the stdout iob has a backend that is either * file descriptor 1, or a terminal i/o backend associated with the tty. * However, we can push a log i/o backend on top that multiplexes stdout to * the original back-end and another backend that writes to a log file. The * use of i/o backends is also used for simplifying tasks such as making * lex and yacc read from strings for mdb_eval(), and making our ELF file * processing code read executable "files" from a crash dump via kvm_uread. * * Additionally, the formatting code provides auto-wrap and indent facilities * that are necessary for compatibility with adb macro formatting. In auto- * wrap mode, the formatting code examines each new chunk of output to determine * if it will fit on the current line. If not, instead of having the chunk * divided between the current line of output and the next, the auto-wrap * code will automatically output a newline, auto-indent the next line, * and then continue. Auto-indent is implemented by simply prepending a number * of blanks equal to iob_margin to the start of each line. The margin is * inserted when the iob is created, and following each flush of the buffer. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Define list of possible integer sizes for conversion routines: */ typedef enum { SZ_SHORT, /* format %h? */ SZ_INT, /* format %? */ SZ_LONG, /* format %l? */ SZ_LONGLONG, /* format %ll? */ SZ_SIZE, /* format %z? */ SZ_INTMAX, /* format %j? */ } intsize_t; /* * The iob snprintf family of functions makes use of a special "sprintf * buffer" i/o backend in order to provide the appropriate snprintf semantics. * This structure is maintained as the backend-specific private storage, * and its use is described in more detail below (see spbuf_write()). */ typedef struct { char *spb_buf; /* pointer to underlying buffer */ size_t spb_bufsiz; /* length of underlying buffer */ size_t spb_total; /* total of all bytes passed via IOP_WRITE */ } spbuf_t; /* * Define VA_ARG macro for grabbing the next datum to format for the printf * family of functions. We use VA_ARG so that we can support two kinds of * argument lists: the va_list type supplied by used for printf and * vprintf, and an array of mdb_arg_t structures, which we expect will be * either type STRING or IMMEDIATE. The vec_arg function takes care of * handling the mdb_arg_t case. */ typedef enum { VAT_VARARGS, /* va_list is a va_list */ VAT_ARGVEC /* va_list is a const mdb_arg_t[] in disguise */ } vatype_t; typedef struct { vatype_t val_type; union { va_list _val_valist; const mdb_arg_t *_val_argv; } _val_u; } varglist_t; #define val_valist _val_u._val_valist #define val_argv _val_u._val_argv #define VA_ARG(ap, type) ((ap->val_type == VAT_VARARGS) ? \ va_arg(ap->val_valist, type) : (type)vec_arg(&ap->val_argv)) #define VA_PTRARG(ap) ((ap->val_type == VAT_VARARGS) ? \ (void *)va_arg(ap->val_valist, uintptr_t) : \ (void *)(uintptr_t)vec_arg(&ap->val_argv)) /* * Define macro for converting char constant to Ctrl-char equivalent: */ #ifndef CTRL #define CTRL(c) ((c) & 0x01f) #endif #define IOB_AUTOWRAP(iob) \ ((mdb.m_flags & MDB_FL_AUTOWRAP) && \ ((iob)->iob_flags & MDB_IOB_AUTOWRAP)) /* * Define macro for determining if we should automatically wrap to the next * line of output, based on the amount of consumed buffer space and the * specified size of the next thing to be inserted (n) -- being careful to * not force a spurious wrap if we're autoindented and already at the margin. */ #define IOB_WRAPNOW(iob, n) \ (IOB_AUTOWRAP(iob) && (iob)->iob_nbytes != 0 && \ ((n) + (iob)->iob_nbytes > (iob)->iob_cols) && \ !(((iob)->iob_flags & MDB_IOB_INDENT) && \ (iob)->iob_nbytes == (iob)->iob_margin)) /* * Define prompt string and string to erase prompt string for iob_pager * function, which is invoked if the pager is enabled on an i/o buffer * and we're about to print a line which would be the last on the screen. */ static const char io_prompt[] = ">> More [, , q, n, c, a] ? "; static const char io_perase[] = " "; static const char io_pbcksp[] = /*CSTYLED*/ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"; static const size_t io_promptlen = sizeof (io_prompt) - 1; static const size_t io_peraselen = sizeof (io_perase) - 1; static const size_t io_pbcksplen = sizeof (io_pbcksp) - 1; static ssize_t iob_write(mdb_iob_t *iob, mdb_io_t *io, const void *buf, size_t n) { ssize_t resid = n; ssize_t len; while (resid != 0) { if ((len = IOP_WRITE(io, buf, resid)) <= 0) break; buf = (char *)buf + len; resid -= len; } /* * Note that if we had a partial write before an error, we still want * to return the fact something was written. The caller will get an * error next time it tries to write anything. */ if (resid == n && n != 0) { iob->iob_flags |= MDB_IOB_ERR; return (-1); } return (n - resid); } static ssize_t iob_read(mdb_iob_t *iob, mdb_io_t *io) { ssize_t len; ASSERT(iob->iob_nbytes == 0); len = IOP_READ(io, iob->iob_buf, iob->iob_bufsiz); iob->iob_bufp = &iob->iob_buf[0]; switch (len) { case -1: iob->iob_flags |= MDB_IOB_ERR; break; case 0: iob->iob_flags |= MDB_IOB_EOF; break; default: iob->iob_nbytes = len; } return (len); } static void iob_winch(int sig, siginfo_t *sip __unused, ucontext_t *ucp __unused, void *data) { siglongjmp(*((sigjmp_buf *)data), sig); } static int iob_pager(mdb_iob_t *iob) { int status = 0; sigjmp_buf env; uchar_t c; mdb_signal_f *termio_winch; void *termio_data; size_t old_rows; if (iob->iob_pgp == NULL || (iob->iob_flags & MDB_IOB_PGCONT)) return (0); termio_winch = mdb_signal_gethandler(SIGWINCH, &termio_data); (void) mdb_signal_sethandler(SIGWINCH, iob_winch, &env); if (sigsetjmp(env, 1) != 0) { /* * Reset the cursor back to column zero before printing a new * prompt, since its position is unreliable after a SIGWINCH. */ (void) iob_write(iob, iob->iob_pgp, "\r", sizeof (char)); old_rows = iob->iob_rows; /* * If an existing SIGWINCH handler was present, call it. We * expect that this will be termio: the handler will read the * new window size, and then resize this iob appropriately. */ if (termio_winch != (mdb_signal_f *)NULL) termio_winch(SIGWINCH, NULL, NULL, termio_data); /* * If the window has increased in size, we treat this like a * request to fill out the new remainder of the page. */ if (iob->iob_rows > old_rows) { iob->iob_flags &= ~MDB_IOB_PGSINGLE; iob->iob_nlines = old_rows; status = 0; goto winch; } } (void) iob_write(iob, iob->iob_pgp, io_prompt, io_promptlen); for (;;) { if (IOP_READ(iob->iob_pgp, &c, sizeof (c)) != sizeof (c)) { status = MDB_ERR_PAGER; break; } switch (c) { case 'N': case 'n': case '\n': case '\r': iob->iob_flags |= MDB_IOB_PGSINGLE; goto done; case CTRL('c'): case CTRL('\\'): case 'Q': case 'q': mdb_iob_discard(iob); status = MDB_ERR_PAGER; goto done; case 'A': case 'a': mdb_iob_discard(iob); status = MDB_ERR_ABORT; goto done; case 'C': case 'c': iob->iob_flags |= MDB_IOB_PGCONT; /*FALLTHRU*/ case ' ': iob->iob_flags &= ~MDB_IOB_PGSINGLE; goto done; } } done: (void) iob_write(iob, iob->iob_pgp, io_pbcksp, io_pbcksplen); winch: (void) iob_write(iob, iob->iob_pgp, io_perase, io_peraselen); (void) iob_write(iob, iob->iob_pgp, io_pbcksp, io_pbcksplen); (void) mdb_signal_sethandler(SIGWINCH, termio_winch, termio_data); if ((iob->iob_flags & MDB_IOB_ERR) && status == 0) status = MDB_ERR_OUTPUT; return (status); } static void iob_indent(mdb_iob_t *iob) { if (iob->iob_nbytes == 0 && iob->iob_margin != 0 && (iob->iob_flags & MDB_IOB_INDENT)) { size_t i; ASSERT(iob->iob_margin < iob->iob_cols); ASSERT(iob->iob_bufp == iob->iob_buf); for (i = 0; i < iob->iob_margin; i++) *iob->iob_bufp++ = ' '; iob->iob_nbytes = iob->iob_margin; } } static void iob_unindent(mdb_iob_t *iob) { if (iob->iob_nbytes != 0 && iob->iob_nbytes == iob->iob_margin) { const char *p = iob->iob_buf; while (p < &iob->iob_buf[iob->iob_margin]) { if (*p++ != ' ') return; } iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; } } mdb_iob_t * mdb_iob_create(mdb_io_t *io, uint_t flags) { mdb_iob_t *iob = mdb_alloc(sizeof (mdb_iob_t), UM_SLEEP); iob->iob_buf = mdb_alloc(BUFSIZ, UM_SLEEP); iob->iob_bufsiz = BUFSIZ; iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; iob->iob_nlines = 0; iob->iob_lineno = 1; iob->iob_rows = MDB_IOB_DEFROWS; iob->iob_cols = MDB_IOB_DEFCOLS; iob->iob_tabstop = MDB_IOB_DEFTAB; iob->iob_margin = MDB_IOB_DEFMARGIN; iob->iob_flags = flags & ~(MDB_IOB_EOF|MDB_IOB_ERR) | MDB_IOB_AUTOWRAP; iob->iob_iop = mdb_io_hold(io); iob->iob_pgp = NULL; iob->iob_next = NULL; IOP_LINK(io, iob); iob_indent(iob); return (iob); } void mdb_iob_pipe(mdb_iob_t **iobs, mdb_iobsvc_f *rdsvc, mdb_iobsvc_f *wrsvc) { mdb_io_t *pio = mdb_pipeio_create(rdsvc, wrsvc); int i; iobs[0] = mdb_iob_create(pio, MDB_IOB_RDONLY); iobs[1] = mdb_iob_create(pio, MDB_IOB_WRONLY); for (i = 0; i < 2; i++) { iobs[i]->iob_flags &= ~MDB_IOB_AUTOWRAP; iobs[i]->iob_cols = iobs[i]->iob_bufsiz; } } void mdb_iob_destroy(mdb_iob_t *iob) { /* * Don't flush a pipe, since it may cause a context switch when the * other side has already been destroyed. */ if (!mdb_iob_isapipe(iob)) mdb_iob_flush(iob); if (iob->iob_pgp != NULL) mdb_io_rele(iob->iob_pgp); while (iob->iob_iop != NULL) { IOP_UNLINK(iob->iob_iop, iob); (void) mdb_iob_pop_io(iob); } mdb_free(iob->iob_buf, iob->iob_bufsiz); mdb_free(iob, sizeof (mdb_iob_t)); } void mdb_iob_discard(mdb_iob_t *iob) { iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; } void mdb_iob_flush(mdb_iob_t *iob) { int pgerr = 0; if (iob->iob_nbytes == 0) return; /* Nothing to do if buffer is empty */ if (iob->iob_flags & MDB_IOB_WRONLY) { if (iob->iob_flags & MDB_IOB_PGSINGLE) { iob->iob_flags &= ~MDB_IOB_PGSINGLE; iob->iob_nlines = 0; pgerr = iob_pager(iob); } else if (iob->iob_nlines >= iob->iob_rows - 1) { iob->iob_nlines = 0; if (iob->iob_flags & MDB_IOB_PGENABLE) pgerr = iob_pager(iob); } if (pgerr == 0) { /* * We only jump out of the dcmd on error if the iob is * m_out. Presumably, if a dcmd has opened a special * file and is writing to it, it will handle errors * properly. */ if (iob_write(iob, iob->iob_iop, iob->iob_buf, iob->iob_nbytes) < 0 && iob == mdb.m_out) pgerr = MDB_ERR_OUTPUT; iob->iob_nlines++; } } iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; iob_indent(iob); if (pgerr) longjmp(mdb.m_frame->f_pcb, pgerr); } void mdb_iob_nlflush(mdb_iob_t *iob) { iob_unindent(iob); if (iob->iob_nbytes != 0) mdb_iob_nl(iob); else iob_indent(iob); } void mdb_iob_push_io(mdb_iob_t *iob, mdb_io_t *io) { ASSERT(io->io_next == NULL); io->io_next = iob->iob_iop; iob->iob_iop = mdb_io_hold(io); } mdb_io_t * mdb_iob_pop_io(mdb_iob_t *iob) { mdb_io_t *io = iob->iob_iop; if (io != NULL) { iob->iob_iop = io->io_next; io->io_next = NULL; mdb_io_rele(io); } return (io); } void mdb_iob_resize(mdb_iob_t *iob, size_t rows, size_t cols) { if (cols > iob->iob_bufsiz) iob->iob_cols = iob->iob_bufsiz; else iob->iob_cols = cols != 0 ? cols : MDB_IOB_DEFCOLS; iob->iob_rows = rows != 0 ? rows : MDB_IOB_DEFROWS; } void mdb_iob_setpager(mdb_iob_t *iob, mdb_io_t *pgio) { struct winsize winsz; if (iob->iob_pgp != NULL) { IOP_UNLINK(iob->iob_pgp, iob); mdb_io_rele(iob->iob_pgp); } iob->iob_flags |= MDB_IOB_PGENABLE; iob->iob_flags &= ~(MDB_IOB_PGSINGLE | MDB_IOB_PGCONT); iob->iob_pgp = mdb_io_hold(pgio); IOP_LINK(iob->iob_pgp, iob); if (IOP_CTL(pgio, TIOCGWINSZ, &winsz) == 0) mdb_iob_resize(iob, (size_t)winsz.ws_row, (size_t)winsz.ws_col); } void mdb_iob_tabstop(mdb_iob_t *iob, size_t tabstop) { iob->iob_tabstop = MIN(tabstop, iob->iob_cols - 1); } void mdb_iob_margin(mdb_iob_t *iob, size_t margin) { iob_unindent(iob); iob->iob_margin = MIN(margin, iob->iob_cols - 1); iob_indent(iob); } void mdb_iob_setbuf(mdb_iob_t *iob, void *buf, size_t bufsiz) { ASSERT(buf != NULL && bufsiz != 0); mdb_free(iob->iob_buf, iob->iob_bufsiz); iob->iob_buf = buf; iob->iob_bufsiz = bufsiz; if (iob->iob_flags & MDB_IOB_WRONLY) iob->iob_cols = MIN(iob->iob_cols, iob->iob_bufsiz); } void mdb_iob_clearlines(mdb_iob_t *iob) { iob->iob_flags &= ~(MDB_IOB_PGSINGLE | MDB_IOB_PGCONT); iob->iob_nlines = 0; } void mdb_iob_setflags(mdb_iob_t *iob, uint_t flags) { iob->iob_flags |= flags; if (flags & MDB_IOB_INDENT) iob_indent(iob); } void mdb_iob_clrflags(mdb_iob_t *iob, uint_t flags) { iob->iob_flags &= ~flags; if (flags & MDB_IOB_INDENT) iob_unindent(iob); } uint_t mdb_iob_getflags(mdb_iob_t *iob) { return (iob->iob_flags); } static uintmax_t vec_arg(const mdb_arg_t **app) { uintmax_t value; if ((*app)->a_type == MDB_TYPE_STRING) value = (uintmax_t)(uintptr_t)(*app)->a_un.a_str; else value = (*app)->a_un.a_val; (*app)++; return (value); } static const char * iob_size2str(intsize_t size) { switch (size) { case SZ_SHORT: return ("short"); case SZ_INT: return ("int"); case SZ_LONG: return ("long"); case SZ_LONGLONG: return ("long long"); case SZ_SIZE: return ("size"); case SZ_INTMAX: return ("int max"); } return (""); } /* * In order to simplify maintenance of the ::formats display, we provide an * unparser for mdb_printf format strings that converts a simple format * string with one specifier into a descriptive representation, e.g. * mdb_iob_format2str("%llx") returns "hexadecimal long long". */ const char * mdb_iob_format2str(const char *format) { intsize_t size = SZ_INT; const char *p; static char buf[64]; buf[0] = '\0'; if ((p = strchr(format, '%')) == NULL) goto done; fmt_switch: switch (*++p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': while (*p >= '0' && *p <= '9') p++; p--; goto fmt_switch; case 'a': case 'A': return ("symbol"); case 'b': (void) strcpy(buf, "unsigned "); (void) strcat(buf, iob_size2str(size)); (void) strcat(buf, " bitfield"); break; case 'c': return ("character"); case 'd': case 'i': (void) strcpy(buf, "decimal signed "); (void) strcat(buf, iob_size2str(size)); break; case 'e': case 'E': case 'g': case 'G': return ("double"); case 'h': size = SZ_SHORT; goto fmt_switch; case 'H': return ("human-readable size"); case 'I': return ("IPv4 address"); case 'j': size = SZ_INTMAX; goto fmt_switch; case 'l': if (size >= SZ_LONG) size = SZ_LONGLONG; else size = SZ_LONG; goto fmt_switch; case 'm': return ("margin"); case 'N': return ("IPv6 address"); case 'o': (void) strcpy(buf, "octal unsigned "); (void) strcat(buf, iob_size2str(size)); break; case 'p': return ("pointer"); case 'q': (void) strcpy(buf, "octal signed "); (void) strcat(buf, iob_size2str(size)); break; case 'r': (void) strcpy(buf, "default radix unsigned "); (void) strcat(buf, iob_size2str(size)); break; case 'R': (void) strcpy(buf, "default radix signed "); (void) strcat(buf, iob_size2str(size)); break; case 's': return ("string"); case 't': case 'T': return ("tab"); case 'u': (void) strcpy(buf, "decimal unsigned "); (void) strcat(buf, iob_size2str(size)); break; case 'x': case 'X': (void) strcat(buf, "hexadecimal "); (void) strcat(buf, iob_size2str(size)); break; case 'Y': return ("time_t"); case 'z': size = SZ_SIZE; goto fmt_switch; case '<': return ("terminal attribute"); case '?': case '#': case '+': case '-': goto fmt_switch; } done: if (buf[0] == '\0') (void) strcpy(buf, "text"); return ((const char *)buf); } static const char * iob_int2str(varglist_t *ap, intsize_t size, int base, uint_t flags, int *zero, u_longlong_t *value) { uintmax_t i; switch (size) { case SZ_INTMAX: if (flags & NTOS_UNSIGNED) i = (uintmax_t)VA_ARG(ap, uintmax_t); else i = (intmax_t)VA_ARG(ap, intmax_t); break; case SZ_SIZE: if (flags & NTOS_UNSIGNED) i = (size_t)VA_ARG(ap, size_t); else i = (ssize_t)VA_ARG(ap, ssize_t); break; case SZ_LONGLONG: if (flags & NTOS_UNSIGNED) i = (u_longlong_t)VA_ARG(ap, u_longlong_t); else i = (longlong_t)VA_ARG(ap, longlong_t); break; case SZ_LONG: if (flags & NTOS_UNSIGNED) i = (ulong_t)VA_ARG(ap, ulong_t); else i = (long)VA_ARG(ap, long); break; case SZ_SHORT: if (flags & NTOS_UNSIGNED) i = (ushort_t)VA_ARG(ap, uint_t); else i = (short)VA_ARG(ap, int); break; default: if (flags & NTOS_UNSIGNED) i = (uint_t)VA_ARG(ap, uint_t); else i = (int)VA_ARG(ap, int); } *zero = i == 0; /* Return flag indicating if result was zero */ *value = i; /* Return value retrieved from va_list */ return (numtostr(i, base, flags)); } static const char * iob_time2str(time_t *tmp) { /* * ctime(3c) returns a string of the form * "Fri Sep 13 00:00:00 1986\n\0". We turn this into the canonical * adb /y format "1986 Sep 13 00:00:00" below. */ const char *src = ctime(tmp); static char buf[32]; char *dst = buf; int i; if (src == NULL) return (numtostr((uintmax_t)*tmp, mdb.m_radix, 0)); for (i = 20; i < 24; i++) *dst++ = src[i]; /* Copy the 4-digit year */ for (i = 3; i < 19; i++) *dst++ = src[i]; /* Copy month, day, and h:m:s */ *dst = '\0'; return (buf); } static const char * iob_addr2str(uintptr_t addr) { static char buf[MDB_TGT_SYM_NAMLEN]; size_t buflen = sizeof (buf); longlong_t offset; GElf_Sym sym; if (mdb_tgt_lookup_by_addr(mdb.m_target, addr, MDB_TGT_SYM_FUZZY, buf, sizeof (buf), &sym, NULL) == -1) return (NULL); if (mdb.m_demangler != NULL && (mdb.m_flags & MDB_FL_DEMANGLE)) { /* * The mdb demangler attempts to either return us our original * name or a pointer to something it has changed. If it has * returned our original name, we want to update buf with that * so we can later modify it. Unfortunately if we find we exceed * the buffer, there's not an easy way to warn the user about * this, so we just truncate the symbol with a '???' and return * it. To someone finding this due to having seen that in a * symbol, sorry. */ const char *dem = mdb_dem_convert(mdb.m_demangler, buf); if (dem != buf) { if (strlcpy(buf, dem, buflen) >= buflen) { buf[buflen - 1] = '?'; buf[buflen - 2] = '?'; buf[buflen - 3] = '?'; return (buf); } } } /* * Here we provide a little cooperation between the %a formatting code * and the proc target: if the initial address passed to %a is in fact * a PLT address, the proc target's lookup_by_addr code will convert * this to the PLT destination (a different address). We do not want * to append a "+/-offset" suffix based on comparison with the query * symbol in this case because the proc target has really done a hidden * query for us with a different address. We detect this case by * comparing the initial characters of buf to the special PLT= string. */ if (sym.st_value != addr && strncmp(buf, "PLT=", 4) != 0) { if (sym.st_value > addr) offset = -(longlong_t)(sym.st_value - addr); else offset = (longlong_t)(addr - sym.st_value); /* * See the earlier note in this function about how we handle * demangler output for why we've dealt with things this way. */ if (strlcat(buf, numtostr(offset, mdb.m_radix, NTOS_SIGNPOS | NTOS_SHOWBASE), buflen) >= buflen) { buf[buflen - 1] = '?'; buf[buflen - 2] = '?'; buf[buflen - 3] = '?'; } } return (buf); } /* * Produce human-readable size, similar in spirit (and identical in output) * to libzfs's zfs_nicenum() -- but made significantly more complicated by * the constraint that we cannot use snprintf() as an implementation detail. * Recall, floating point is verboten in kmdb. */ static const char * iob_bytes2str(varglist_t *ap, intsize_t size) { #ifndef _KMDB const int sigfig = 3; uint64_t orig; #endif uint64_t n; static char buf[68], *c; int index = 0; char u; switch (size) { case SZ_INTMAX: n = (uintmax_t)VA_ARG(ap, uintmax_t); break; case SZ_SIZE: n = (size_t)VA_ARG(ap, size_t); break; case SZ_LONGLONG: n = (u_longlong_t)VA_ARG(ap, u_longlong_t); break; case SZ_LONG: n = (ulong_t)VA_ARG(ap, ulong_t); break; case SZ_SHORT: n = (ushort_t)VA_ARG(ap, uint_t); break; default: n = (uint_t)VA_ARG(ap, uint_t); } #ifndef _KMDB orig = n; #endif while (n >= 1024) { n /= 1024; index++; } u = " KMGTPE"[index]; buf[0] = '\0'; if (index == 0) { return (numtostr(n, 10, 0)); #ifndef _KMDB } else if ((orig & ((1ULL << 10 * index) - 1)) == 0) { #else } else { #endif /* * If this is an even multiple of the base or we are in an * environment where floating point is verboten (i.e., kmdb), * always display without any decimal precision. */ (void) strcat(buf, numtostr(n, 10, 0)); #ifndef _KMDB } else { /* * We want to choose a precision that results in the specified * number of significant figures (by default, 3). This is * similar to the output that one would get specifying the %.*g * format specifier (where the asterisk denotes the number of * significant digits), but (1) we include trailing zeros if * the there are non-zero digits beyond the number of * significant digits (that is, 10241 is '10.0K', not the * '10K' that it would be with %.3g) and (2) we never resort * to %e notation when the number of digits exceeds the * number of significant figures (that is, 1043968 is '1020K', * not '1.02e+03K'). This is also made somewhat complicated * by the fact that we need to deal with rounding (10239 is * '10.0K', not '9.99K'), for which we perform nearest-even * rounding. */ double val = (double)orig / (1ULL << 10 * index); int i, mag = 1, thresh; for (i = 0; i < sigfig - 1; i++) mag *= 10; for (thresh = mag * 10; mag >= 1; mag /= 10, i--) { double mult = val * (double)mag; uint32_t v; /* * Note that we cast mult to a 32-bit value. We know * that val is less than 1024 due to the logic above, * and that mag is at most 10^(sigfig - 1). This means * that as long as sigfig is 9 or lower, this will not * overflow. (We perform this cast because it assures * that we are never converting a double to a uint64_t, * which for some compilers requires a call to a * function not guaranteed to be in libstand.) */ if (mult - (double)(uint32_t)mult != 0.5) { v = (uint32_t)(mult + 0.5); } else { /* * We are exactly between integer multiples * of units; perform nearest-even rounding * to be consistent with the behavior of * printf(). */ if ((v = (uint32_t)mult) & 1) v++; } if (mag == 1) { (void) strcat(buf, numtostr(v, 10, 0)); break; } if (v < thresh) { (void) strcat(buf, numtostr(v / mag, 10, 0)); (void) strcat(buf, "."); c = (char *)numtostr(v % mag, 10, 0); i -= strlen(c); /* * We need to zero-fill from the right of the * decimal point to the first significant digit * of the fractional component. */ while (i--) (void) strcat(buf, "0"); (void) strcat(buf, c); break; } } #endif } c = &buf[strlen(buf)]; *c++ = u; *c++ = '\0'; return (buf); } static int iob_setattr(mdb_iob_t *iob, const char *s, size_t nbytes) { uint_t attr; int req; if (iob->iob_pgp == NULL) return (set_errno(ENOTTY)); if (nbytes != 0 && *s == '/') { req = ATT_OFF; nbytes--; s++; } else req = ATT_ON; if (nbytes != 1) return (set_errno(EINVAL)); switch (*s) { case 's': attr = ATT_STANDOUT; break; case 'u': attr = ATT_UNDERLINE; break; case 'r': attr = ATT_REVERSE; break; case 'b': attr = ATT_BOLD; break; case 'd': attr = ATT_DIM; break; case 'a': attr = ATT_ALTCHARSET; break; default: return (set_errno(EINVAL)); } /* * We need to flush the current buffer contents before calling * IOP_SETATTR because IOP_SETATTR may need to synchronously output * terminal escape sequences directly to the underlying device. */ (void) iob_write(iob, iob->iob_iop, iob->iob_buf, iob->iob_nbytes); iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; return (IOP_SETATTR(iob->iob_pgp, req, attr)); } static void iob_bits2str(mdb_iob_t *iob, u_longlong_t value, const mdb_bitmask_t *bmp, mdb_bool_t altflag) { mdb_bool_t delim = FALSE; const char *str; size_t width; if (bmp == NULL) goto out; for (; bmp->bm_name != NULL; bmp++) { if ((value & bmp->bm_mask) == bmp->bm_bits) { width = strlen(bmp->bm_name) + delim; if (IOB_WRAPNOW(iob, width)) mdb_iob_nl(iob); if (delim) mdb_iob_putc(iob, ','); else delim = TRUE; mdb_iob_puts(iob, bmp->bm_name); value &= ~bmp->bm_bits; } } out: if (altflag == TRUE && (delim == FALSE || value != 0)) { str = numtostr(value, 16, NTOS_UNSIGNED | NTOS_SHOWBASE); width = strlen(str) + delim; if (IOB_WRAPNOW(iob, width)) mdb_iob_nl(iob); if (delim) mdb_iob_putc(iob, ','); mdb_iob_puts(iob, str); } } static const char * iob_inaddr2str(uint32_t addr) { static char buf[INET_ADDRSTRLEN]; (void) mdb_inet_ntop(AF_INET, &addr, buf, sizeof (buf)); return (buf); } static const char * iob_ipv6addr2str(void *addr) { static char buf[INET6_ADDRSTRLEN]; (void) mdb_inet_ntop(AF_INET6, addr, buf, sizeof (buf)); return (buf); } static const char * iob_getvar(const char *s, size_t len) { mdb_var_t *val; char *var; if (len == 0) { (void) set_errno(EINVAL); return (NULL); } var = strndup(s, len); val = mdb_nv_lookup(&mdb.m_nv, var); strfree(var); if (val == NULL) { (void) set_errno(EINVAL); return (NULL); } return (numtostr(mdb_nv_get_value(val), 10, 0)); } /* * The iob_doprnt function forms the main engine of the debugger's output * formatting capabilities. Note that this is NOT exactly compatible with * the printf(3C) family, nor is it intended to be so. We support some * extensions and format characters not supported by printf(3C), and we * explicitly do NOT provide support for %C, %S, %ws (wide-character strings), * do NOT provide for the complete functionality of %f, %e, %E, %g, %G * (alternate double formats), and do NOT support %.x (precision specification). * Note that iob_doprnt consumes varargs off the original va_list. */ static void iob_doprnt(mdb_iob_t *iob, const char *format, varglist_t *ap) { char c[2] = { 0, 0 }; /* Buffer for single character output */ const char *p; /* Current position in format string */ size_t len; /* Length of format string to copy verbatim */ size_t altlen; /* Length of alternate print format prefix */ const char *altstr; /* Alternate print format prefix */ const char *symstr; /* Symbol + offset string */ u_longlong_t val; /* Current integer value */ intsize_t size; /* Current integer value size */ uint_t flags; /* Current flags to pass to iob_int2str */ size_t width; /* Current field width */ int zero; /* If != 0, then integer value == 0 */ mdb_bool_t f_alt; /* Use alternate print format (%#) */ mdb_bool_t f_altsuff; /* Alternate print format is a suffix */ mdb_bool_t f_zfill; /* Zero-fill field (%0) */ mdb_bool_t f_left; /* Left-adjust field (%-) */ mdb_bool_t f_digits; /* Explicit digits used to set field width */ union { const char *str; uint32_t ui32; void *ptr; time_t tm; char c; double d; long double ld; } u; ASSERT(iob->iob_flags & MDB_IOB_WRONLY); while ((p = strchr(format, '%')) != NULL) { /* * Output the format string verbatim up to the next '%' char */ if (p != format) { len = p - format; if (IOB_WRAPNOW(iob, len) && *format != '\n') mdb_iob_nl(iob); mdb_iob_nputs(iob, format, len); } /* * Now we need to parse the sequence of format characters * following the % marker and do the appropriate thing. */ size = SZ_INT; /* Use normal-sized int by default */ flags = 0; /* Clear numtostr() format flags */ width = 0; /* No field width limit by default */ altlen = 0; /* No alternate format string yet */ altstr = NULL; /* No alternate format string yet */ f_alt = FALSE; /* Alternate format off by default */ f_altsuff = FALSE; /* Alternate format is a prefix */ f_zfill = FALSE; /* Zero-fill off by default */ f_left = FALSE; /* Left-adjust off by default */ f_digits = FALSE; /* No digits for width specified yet */ fmt_switch: switch (*++p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (f_digits == FALSE && *p == '0') { f_zfill = TRUE; goto fmt_switch; } if (f_digits == FALSE) width = 0; /* clear any other width specifier */ for (u.c = *p; u.c >= '0' && u.c <= '9'; u.c = *++p) width = width * 10 + u.c - '0'; p--; f_digits = TRUE; goto fmt_switch; case 'a': if (size < SZ_LONG) size = SZ_LONG; /* Bump to size of uintptr_t */ u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED | NTOS_SHOWBASE, &zero, &val); if ((symstr = iob_addr2str(val)) != NULL) u.str = symstr; if (f_alt == TRUE) { f_altsuff = TRUE; altstr = ":"; altlen = 1; } break; case 'A': if (size < SZ_LONG) size = SZ_LONG; /* Bump to size of uintptr_t */ (void) iob_int2str(ap, size, 16, NTOS_UNSIGNED, &zero, &val); u.str = iob_addr2str(val); if (f_alt == TRUE && u.str == NULL) u.str = "?"; break; case 'b': u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED | NTOS_SHOWBASE, &zero, &val); iob_bits2str(iob, val, VA_PTRARG(ap), f_alt); format = ++p; continue; case 'c': c[0] = (char)VA_ARG(ap, int); u.str = c; break; case 'd': case 'i': if (f_alt) flags |= NTOS_SHOWBASE; u.str = iob_int2str(ap, size, 10, flags, &zero, &val); break; /* No floating point in kmdb */ #ifndef _KMDB case 'e': case 'E': u.d = VA_ARG(ap, double); u.str = doubletos(u.d, 7, *p); break; case 'g': case 'G': if (size >= SZ_LONG) { u.ld = VA_ARG(ap, long double); u.str = longdoubletos(&u.ld, 16, (*p == 'g') ? 'e' : 'E'); } else { u.d = VA_ARG(ap, double); u.str = doubletos(u.d, 16, (*p == 'g') ? 'e' : 'E'); } break; #endif case 'h': size = SZ_SHORT; goto fmt_switch; case 'H': u.str = iob_bytes2str(ap, size); break; case 'I': u.ui32 = VA_ARG(ap, uint32_t); u.str = iob_inaddr2str(u.ui32); break; case 'j': size = SZ_INTMAX; goto fmt_switch; case 'l': if (size >= SZ_LONG) size = SZ_LONGLONG; else size = SZ_LONG; goto fmt_switch; case 'm': if (iob->iob_nbytes == 0) { mdb_iob_ws(iob, (width != 0) ? width : iob->iob_margin); } format = ++p; continue; case 'N': u.ptr = VA_PTRARG(ap); u.str = iob_ipv6addr2str(u.ptr); break; case 'o': u.str = iob_int2str(ap, size, 8, NTOS_UNSIGNED, &zero, &val); if (f_alt && !zero) { altstr = "0"; altlen = 1; } break; case 'p': u.ptr = VA_PTRARG(ap); u.str = numtostr((uintptr_t)u.ptr, 16, NTOS_UNSIGNED); break; case 'q': u.str = iob_int2str(ap, size, 8, flags, &zero, &val); if (f_alt && !zero) { altstr = "0"; altlen = 1; } break; case 'r': if (f_alt) flags |= NTOS_SHOWBASE; u.str = iob_int2str(ap, size, mdb.m_radix, NTOS_UNSIGNED | flags, &zero, &val); break; case 'R': if (f_alt) flags |= NTOS_SHOWBASE; u.str = iob_int2str(ap, size, mdb.m_radix, flags, &zero, &val); break; case 's': u.str = VA_PTRARG(ap); if (u.str == NULL) u.str = ""; /* Be forgiving of NULL */ break; case 't': if (width != 0) { while (width-- > 0) mdb_iob_tab(iob); } else mdb_iob_tab(iob); format = ++p; continue; case 'T': if (width != 0 && (iob->iob_nbytes % width) != 0) { size_t ots = iob->iob_tabstop; iob->iob_tabstop = width; mdb_iob_tab(iob); iob->iob_tabstop = ots; } format = ++p; continue; case 'u': if (f_alt) flags |= NTOS_SHOWBASE; u.str = iob_int2str(ap, size, 10, flags | NTOS_UNSIGNED, &zero, &val); break; case 'x': u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED, &zero, &val); if (f_alt && !zero) { altstr = "0x"; altlen = 2; } break; case 'X': u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED | NTOS_UPCASE, &zero, &val); if (f_alt && !zero) { altstr = "0X"; altlen = 2; } break; case 'Y': u.tm = VA_ARG(ap, time_t); u.str = iob_time2str(&u.tm); break; case 'z': size = SZ_SIZE; goto fmt_switch; case '<': /* * Used to turn attributes on (), to turn them * off (), or to print variables (<_var>). */ for (u.str = ++p; *p != '\0' && *p != '>'; p++) continue; if (*p == '>') { size_t paramlen = p - u.str; if (paramlen > 0) { if (*u.str == '_') { u.str = iob_getvar(u.str + 1, paramlen - 1); break; } else { (void) iob_setattr(iob, u.str, paramlen); } } p++; } format = p; continue; case '*': width = (size_t)(uint_t)VA_ARG(ap, int); goto fmt_switch; case '%': u.str = "%"; break; case '?': width = sizeof (uintptr_t) * 2; goto fmt_switch; case '#': f_alt = TRUE; goto fmt_switch; case '+': flags |= NTOS_SIGNPOS; goto fmt_switch; case '-': f_left = TRUE; goto fmt_switch; default: c[0] = p[0]; u.str = c; } len = u.str != NULL ? strlen(u.str) : 0; if (len + altlen > width) width = len + altlen; /* * If the string and the option altstr won't fit on this line * and auto-wrap is set (default), skip to the next line. * If the string contains \n, and the \n terminated substring * + altstr is shorter than the above, use the shorter lf_len. */ if (u.str != NULL) { char *np = strchr(u.str, '\n'); if (np != NULL) { int lf_len = (np - u.str) + altlen; if (lf_len < width) width = lf_len; } } if (IOB_WRAPNOW(iob, width)) mdb_iob_nl(iob); /* * Optionally add whitespace or zeroes prefixing the value if * we haven't filled the minimum width and we're right-aligned. */ if (len < (width - altlen) && f_left == FALSE) { mdb_iob_fill(iob, f_zfill ? '0' : ' ', width - altlen - len); } /* * Print the alternate string if it's a prefix, and then * print the value string itself. */ if (altstr != NULL && f_altsuff == FALSE) mdb_iob_nputs(iob, altstr, altlen); if (len != 0) mdb_iob_nputs(iob, u.str, len); /* * If we have an alternate string and it's a suffix, print it. */ if (altstr != NULL && f_altsuff == TRUE) mdb_iob_nputs(iob, altstr, altlen); /* * Finally, if we haven't filled the field width and we're * left-aligned, pad out the rest with whitespace. */ if ((len + altlen) < width && f_left == TRUE) mdb_iob_ws(iob, width - altlen - len); format = (*p != '\0') ? ++p : p; } /* * If there's anything left in the format string, output it now */ if (*format != '\0') { len = strlen(format); if (IOB_WRAPNOW(iob, len) && *format != '\n') mdb_iob_nl(iob); mdb_iob_nputs(iob, format, len); } } void mdb_iob_vprintf(mdb_iob_t *iob, const char *format, va_list alist) { varglist_t ap = { VAT_VARARGS }; va_copy(ap.val_valist, alist); iob_doprnt(iob, format, &ap); } void mdb_iob_aprintf(mdb_iob_t *iob, const char *format, const mdb_arg_t *argv) { varglist_t ap = { VAT_ARGVEC }; ap.val_argv = argv; iob_doprnt(iob, format, &ap); } void mdb_iob_printf(mdb_iob_t *iob, const char *format, ...) { va_list alist; va_start(alist, format); mdb_iob_vprintf(iob, format, alist); va_end(alist); } /* * In order to handle the sprintf family of functions, we define a special * i/o backend known as a "sprintf buf" (or spbuf for short). This back end * provides an IOP_WRITE entry point that concatenates each buffer sent from * mdb_iob_flush() onto the caller's buffer until the caller's buffer is * exhausted. We also keep an absolute count of how many bytes were sent to * this function during the lifetime of the snprintf call. This allows us * to provide the ability to (1) return the total size required for the given * format string and argument list, and (2) support a call to snprintf with a * NULL buffer argument with no special case code elsewhere. */ static ssize_t spbuf_write(mdb_io_t *io, const void *buf, size_t buflen) { spbuf_t *spb = io->io_data; if (spb->spb_bufsiz != 0) { size_t n = MIN(spb->spb_bufsiz, buflen); bcopy(buf, spb->spb_buf, n); spb->spb_buf += n; spb->spb_bufsiz -= n; } spb->spb_total += buflen; return (buflen); } static const mdb_io_ops_t spbuf_ops = { .io_read = no_io_read, .io_write = spbuf_write, .io_seek = no_io_seek, .io_ctl = no_io_ctl, .io_close = no_io_close, .io_name = no_io_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume }; /* * The iob_spb_create function initializes an iob suitable for snprintf calls, * a spbuf i/o backend, and the spbuf private data, and then glues these * objects together. The caller (either vsnprintf or asnprintf below) is * expected to have allocated the various structures on their stack. */ static void iob_spb_create(mdb_iob_t *iob, char *iob_buf, size_t iob_len, mdb_io_t *io, spbuf_t *spb, char *spb_buf, size_t spb_len) { spb->spb_buf = spb_buf; spb->spb_bufsiz = spb_len; spb->spb_total = 0; io->io_ops = &spbuf_ops; io->io_data = spb; io->io_next = NULL; io->io_refcnt = 1; iob->iob_buf = iob_buf; iob->iob_bufsiz = iob_len; iob->iob_bufp = iob_buf; iob->iob_nbytes = 0; iob->iob_nlines = 0; iob->iob_lineno = 1; iob->iob_rows = MDB_IOB_DEFROWS; iob->iob_cols = iob_len; iob->iob_tabstop = MDB_IOB_DEFTAB; iob->iob_margin = MDB_IOB_DEFMARGIN; iob->iob_flags = MDB_IOB_WRONLY; iob->iob_iop = io; iob->iob_pgp = NULL; iob->iob_next = NULL; } /*ARGSUSED*/ ssize_t null_io_write(mdb_io_t *io, const void *buf, size_t nbytes) { return (nbytes); } static const mdb_io_ops_t null_ops = { .io_read = no_io_read, .io_write = null_io_write, .io_seek = no_io_seek, .io_ctl = no_io_ctl, .io_close = no_io_close, .io_name = no_io_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume, }; mdb_io_t * mdb_nullio_create(void) { static mdb_io_t null_io = { &null_ops, NULL, NULL, 1 }; return (&null_io); } size_t mdb_iob_vsnprintf(char *buf, size_t nbytes, const char *format, va_list alist) { varglist_t ap = { VAT_VARARGS }; char iob_buf[64]; mdb_iob_t iob; mdb_io_t io; spbuf_t spb; ASSERT(buf != NULL || nbytes == 0); iob_spb_create(&iob, iob_buf, sizeof (iob_buf), &io, &spb, buf, nbytes); va_copy(ap.val_valist, alist); iob_doprnt(&iob, format, &ap); mdb_iob_flush(&iob); if (spb.spb_bufsiz != 0) *spb.spb_buf = '\0'; else if (buf != NULL && nbytes > 0) *--spb.spb_buf = '\0'; return (spb.spb_total); } size_t mdb_iob_asnprintf(char *buf, size_t nbytes, const char *format, const mdb_arg_t *argv) { varglist_t ap = { VAT_ARGVEC }; char iob_buf[64]; mdb_iob_t iob; mdb_io_t io; spbuf_t spb; ASSERT(buf != NULL || nbytes == 0); iob_spb_create(&iob, iob_buf, sizeof (iob_buf), &io, &spb, buf, nbytes); ap.val_argv = argv; iob_doprnt(&iob, format, &ap); mdb_iob_flush(&iob); if (spb.spb_bufsiz != 0) *spb.spb_buf = '\0'; else if (buf != NULL && nbytes > 0) *--spb.spb_buf = '\0'; return (spb.spb_total); } /*PRINTFLIKE3*/ size_t mdb_iob_snprintf(char *buf, size_t nbytes, const char *format, ...) { va_list alist; va_start(alist, format); nbytes = mdb_iob_vsnprintf(buf, nbytes, format, alist); va_end(alist); return (nbytes); } /* * Return how many bytes we can copy into our buffer, limited by either cols or * bufsiz depending on whether AUTOWRAP is on. Note that typically, * mdb_iob_set_autowrap() will have already checked for an existing * "->iob_nbytes > ->iob_cols" situation, but we double check here anyway. */ static size_t iob_bufleft(mdb_iob_t *iob) { if (IOB_AUTOWRAP(iob)) { if (iob->iob_cols < iob->iob_nbytes) { mdb_iob_nl(iob); ASSERT(iob->iob_cols >= iob->iob_nbytes); } return (iob->iob_cols - iob->iob_nbytes); } ASSERT(iob->iob_bufsiz >= iob->iob_nbytes); return (iob->iob_bufsiz - iob->iob_nbytes); } void mdb_iob_nputs(mdb_iob_t *iob, const char *s, size_t nbytes) { size_t m, n, nleft = nbytes; const char *p, *q = s; ASSERT(iob->iob_flags & MDB_IOB_WRONLY); if (nbytes == 0) return; /* Return immediately if there is no work to do */ /* * If the string contains embedded newlines or tabs, invoke ourself * recursively for each string component, followed by a call to the * newline or tab routine. This insures that strings with these * characters obey our wrapping and indenting rules, and that strings * with embedded newlines are flushed after each newline, allowing * the output pager to take over if it is enabled. */ while ((p = strnpbrk(q, "\t\n", nleft)) != NULL) { if (p > q) mdb_iob_nputs(iob, q, (size_t)(p - q)); if (*p == '\t') mdb_iob_tab(iob); else mdb_iob_nl(iob); nleft -= (size_t)(p - q) + 1; /* Update byte count */ q = p + 1; /* Advance past delimiter */ } /* * For a given string component, we copy a chunk into the buffer, and * flush the buffer if we reach the end of a line. */ while (nleft != 0) { n = iob_bufleft(iob); m = MIN(nleft, n); /* copy at most n bytes in this pass */ bcopy(q, iob->iob_bufp, m); nleft -= m; q += m; iob->iob_bufp += m; iob->iob_nbytes += m; if (m == n && nleft != 0) { if (IOB_AUTOWRAP(iob)) { mdb_iob_nl(iob); } else { mdb_iob_flush(iob); } } } } void mdb_iob_puts(mdb_iob_t *iob, const char *s) { mdb_iob_nputs(iob, s, strlen(s)); } void mdb_iob_putc(mdb_iob_t *iob, int c) { mdb_iob_fill(iob, c, 1); } void mdb_iob_tab(mdb_iob_t *iob) { ASSERT(iob->iob_flags & MDB_IOB_WRONLY); if (iob->iob_tabstop != 0) { /* * Round up to the next multiple of the tabstop. If this puts * us off the end of the line, just insert a newline; otherwise * insert sufficient whitespace to reach position n. */ size_t n = (iob->iob_nbytes + iob->iob_tabstop) / iob->iob_tabstop * iob->iob_tabstop; if (n < iob->iob_cols) mdb_iob_fill(iob, ' ', n - iob->iob_nbytes); else mdb_iob_nl(iob); } } void mdb_iob_fill(mdb_iob_t *iob, int c, size_t nfill) { size_t i, m, n; ASSERT(iob->iob_flags & MDB_IOB_WRONLY); while (nfill != 0) { n = iob_bufleft(iob); m = MIN(nfill, n); /* fill at most n bytes in this pass */ for (i = 0; i < m; i++) *iob->iob_bufp++ = (char)c; iob->iob_nbytes += m; nfill -= m; if (m == n && nfill != 0) { if (IOB_AUTOWRAP(iob)) { mdb_iob_nl(iob); } else { mdb_iob_flush(iob); } } } } void mdb_iob_ws(mdb_iob_t *iob, size_t n) { if (!IOB_AUTOWRAP(iob) || iob->iob_nbytes + n < iob->iob_cols) mdb_iob_fill(iob, ' ', n); else mdb_iob_nl(iob); } void mdb_iob_nl(mdb_iob_t *iob) { ASSERT(iob->iob_flags & MDB_IOB_WRONLY); if (iob->iob_nbytes == iob->iob_bufsiz) mdb_iob_flush(iob); *iob->iob_bufp++ = '\n'; iob->iob_nbytes++; mdb_iob_flush(iob); } ssize_t mdb_iob_ngets(mdb_iob_t *iob, char *buf, size_t n) { ssize_t resid = n - 1; ssize_t len; int c; if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF)) return (EOF); /* can't gets a write buf or a read buf at EOF */ if (n == 0) return (0); /* we need room for a terminating \0 */ while (resid != 0) { if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0) goto done; /* failed to refill buffer */ for (len = MIN(iob->iob_nbytes, resid); len != 0; len--) { c = *iob->iob_bufp++; iob->iob_nbytes--; if (c == EOF || c == '\n') goto done; *buf++ = (char)c; resid--; } } done: *buf = '\0'; return (n - resid - 1); } int mdb_iob_getc(mdb_iob_t *iob) { int c; if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF | MDB_IOB_ERR)) return (EOF); /* can't getc if write-only, EOF, or error bit */ if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0) return (EOF); /* failed to refill buffer */ c = (uchar_t)*iob->iob_bufp++; iob->iob_nbytes--; return (c); } int mdb_iob_ungetc(mdb_iob_t *iob, int c) { if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_ERR)) return (EOF); /* can't ungetc if write-only or error bit set */ if (c == EOF || iob->iob_nbytes == iob->iob_bufsiz) return (EOF); /* can't ungetc EOF, or ungetc if buffer full */ *--iob->iob_bufp = (char)c; iob->iob_nbytes++; iob->iob_flags &= ~MDB_IOB_EOF; return (c); } int mdb_iob_eof(mdb_iob_t *iob) { return ((iob->iob_flags & (MDB_IOB_RDONLY | MDB_IOB_EOF)) == (MDB_IOB_RDONLY | MDB_IOB_EOF)); } int mdb_iob_err(mdb_iob_t *iob) { return ((iob->iob_flags & MDB_IOB_ERR) == MDB_IOB_ERR); } ssize_t mdb_iob_read(mdb_iob_t *iob, void *buf, size_t n) { ssize_t resid = n; ssize_t len; if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF | MDB_IOB_ERR)) return (0); /* can't read if write-only, eof, or error */ while (resid != 0) { if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0) break; /* failed to refill buffer */ len = MIN(resid, iob->iob_nbytes); bcopy(iob->iob_bufp, buf, len); iob->iob_bufp += len; iob->iob_nbytes -= len; buf = (char *)buf + len; resid -= len; } return (n - resid); } /* * For now, all binary writes are performed unbuffered. This has the * side effect that the pager will not be triggered by mdb_iob_write. */ ssize_t mdb_iob_write(mdb_iob_t *iob, const void *buf, size_t n) { ssize_t ret; if (iob->iob_flags & MDB_IOB_ERR) return (set_errno(EIO)); if (iob->iob_flags & MDB_IOB_RDONLY) return (set_errno(EMDB_IORO)); mdb_iob_flush(iob); ret = iob_write(iob, iob->iob_iop, buf, n); if (ret < 0 && iob == mdb.m_out) longjmp(mdb.m_frame->f_pcb, MDB_ERR_OUTPUT); return (ret); } int mdb_iob_ctl(mdb_iob_t *iob, int req, void *arg) { return (IOP_CTL(iob->iob_iop, req, arg)); } const char * mdb_iob_name(mdb_iob_t *iob) { if (iob == NULL) return (""); return (IOP_NAME(iob->iob_iop)); } size_t mdb_iob_lineno(mdb_iob_t *iob) { return (iob->iob_lineno); } size_t mdb_iob_gettabstop(mdb_iob_t *iob) { return (iob->iob_tabstop); } size_t mdb_iob_getmargin(mdb_iob_t *iob) { return (iob->iob_margin); } mdb_io_t * mdb_io_hold(mdb_io_t *io) { io->io_refcnt++; return (io); } void mdb_io_rele(mdb_io_t *io) { ASSERT(io->io_refcnt != 0); if (--io->io_refcnt == 0) { IOP_CLOSE(io); mdb_free(io, sizeof (mdb_io_t)); } } void mdb_io_destroy(mdb_io_t *io) { ASSERT(io->io_refcnt == 0); IOP_CLOSE(io); mdb_free(io, sizeof (mdb_io_t)); } void mdb_iob_stack_create(mdb_iob_stack_t *stk) { stk->stk_top = NULL; stk->stk_size = 0; } void mdb_iob_stack_destroy(mdb_iob_stack_t *stk) { mdb_iob_t *top, *ntop; for (top = stk->stk_top; top != NULL; top = ntop) { ntop = top->iob_next; mdb_iob_destroy(top); } } void mdb_iob_stack_push(mdb_iob_stack_t *stk, mdb_iob_t *iob, size_t lineno) { iob->iob_lineno = lineno; iob->iob_next = stk->stk_top; stk->stk_top = iob; stk->stk_size++; yylineno = 1; } mdb_iob_t * mdb_iob_stack_pop(mdb_iob_stack_t *stk) { mdb_iob_t *top = stk->stk_top; ASSERT(top != NULL); stk->stk_top = top->iob_next; top->iob_next = NULL; stk->stk_size--; return (top); } size_t mdb_iob_stack_size(mdb_iob_stack_t *stk) { return (stk->stk_size); } /* * This only enables autowrap for iobs that are already autowrap themselves such * as mdb.m_out typically. * * Note that we might be the middle of the iob buffer at this point, and * specifically, iob->iob_nbytes could be more than iob->iob_cols. As that's * not a valid situation, we may need to do an autowrap *now*. * * In theory, we would need to do this across all MDB_IOB_AUTOWRAP iob's; * instead, we have a failsafe in iob_bufleft(). */ void mdb_iob_set_autowrap(mdb_iob_t *iob) { mdb.m_flags |= MDB_FL_AUTOWRAP; if (IOB_WRAPNOW(iob, 0)) mdb_iob_nl(iob); ASSERT(iob->iob_cols >= iob->iob_nbytes); } /* * Stub functions for i/o backend implementors: these stubs either act as * pass-through no-ops or return ENOTSUP as appropriate. */ ssize_t no_io_read(mdb_io_t *io, void *buf, size_t nbytes) { if (io->io_next != NULL) return (IOP_READ(io->io_next, buf, nbytes)); return (set_errno(EMDB_IOWO)); } ssize_t no_io_write(mdb_io_t *io, const void *buf, size_t nbytes) { if (io->io_next != NULL) return (IOP_WRITE(io->io_next, buf, nbytes)); return (set_errno(EMDB_IORO)); } off64_t no_io_seek(mdb_io_t *io, off64_t offset, int whence) { if (io->io_next != NULL) return (IOP_SEEK(io->io_next, offset, whence)); return (set_errno(ENOTSUP)); } int no_io_ctl(mdb_io_t *io, int req, void *arg) { if (io->io_next != NULL) return (IOP_CTL(io->io_next, req, arg)); return (set_errno(ENOTSUP)); } /*ARGSUSED*/ void no_io_close(mdb_io_t *io) { /* * Note that we do not propagate IOP_CLOSE down the io stack. IOP_CLOSE should * only be called by mdb_io_rele when an io's reference count has gone to zero. */ } const char * no_io_name(mdb_io_t *io) { if (io->io_next != NULL) return (IOP_NAME(io->io_next)); return ("(anonymous)"); } void no_io_link(mdb_io_t *io, mdb_iob_t *iob) { if (io->io_next != NULL) IOP_LINK(io->io_next, iob); } void no_io_unlink(mdb_io_t *io, mdb_iob_t *iob) { if (io->io_next != NULL) IOP_UNLINK(io->io_next, iob); } int no_io_setattr(mdb_io_t *io, int req, uint_t attrs) { if (io->io_next != NULL) return (IOP_SETATTR(io->io_next, req, attrs)); return (set_errno(ENOTSUP)); } void no_io_suspend(mdb_io_t *io) { if (io->io_next != NULL) IOP_SUSPEND(io->io_next); } void no_io_resume(mdb_io_t *io) { if (io->io_next != NULL) IOP_RESUME(io->io_next); } /* * Iterate over the varargs. The first item indicates the mode: * MDB_TBL_PRNT * pull out the next vararg as a const char * and pass it and the * remaining varargs to iob_doprnt; if we want to print the column, * direct the output to mdb.m_out otherwise direct it to mdb.m_null * * MDB_TBL_FUNC * pull out the next vararg as type mdb_table_print_f and the * following one as a void * argument to the function; call the * function with the given argument if we want to print the column * * The second item indicates the flag; if the flag is set in the flags * argument, then the column is printed. A flag value of 0 indicates * that the column should always be printed. */ void mdb_table_print(uint_t flags, const char *delimeter, ...) { va_list alist; uint_t flg; uint_t type; const char *fmt; mdb_table_print_f *func; void *arg; mdb_iob_t *out; mdb_bool_t first = TRUE; mdb_bool_t print; va_start(alist, delimeter); while ((type = va_arg(alist, uint_t)) != MDB_TBL_DONE) { flg = va_arg(alist, uint_t); print = flg == 0 || (flg & flags) != 0; if (print) { if (first) first = FALSE; else mdb_printf("%s", delimeter); } switch (type) { case MDB_TBL_PRNT: { varglist_t ap = { VAT_VARARGS }; fmt = va_arg(alist, const char *); out = print ? mdb.m_out : mdb.m_null; va_copy(ap.val_valist, alist); iob_doprnt(out, fmt, &ap); va_end(alist); va_copy(alist, ap.val_valist); break; } case MDB_TBL_FUNC: func = va_arg(alist, mdb_table_print_f *); arg = va_arg(alist, void *); if (print) func(arg); break; default: warn("bad format type %x\n", type); break; } } va_end(alist); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2020 Joyent, Inc. */ #ifndef _MDB_IO_H #define _MDB_IO_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB #include #include #include #include typedef struct mdb_iob mdb_iob_t; /* I/O buffer */ typedef struct mdb_io mdb_io_t; /* I/O implementation */ struct mdb_arg; /* Argument structure */ #define MDB_IOB_DEFTAB 8 /* Default tabstop */ #define MDB_IOB_DEFMARGIN 16 /* Default margin width */ #define MDB_IOB_DEFROWS 24 /* Default rows */ #define MDB_IOB_DEFCOLS 80 /* Default columns */ #define MDB_IOB_RDONLY 0x0001 /* Buffer is for reading */ #define MDB_IOB_WRONLY 0x0002 /* Buffer is for writing */ #define MDB_IOB_EOF 0x0004 /* Read buffer has reached EOF */ #define MDB_IOB_ERR 0x0008 /* Underlying i/o error occurred */ #define MDB_IOB_INDENT 0x0010 /* Lines are auto-indented */ #define MDB_IOB_PGENABLE 0x0020 /* Pager enabled */ #define MDB_IOB_PGSINGLE 0x0040 /* Line-at-a-time pager active */ #define MDB_IOB_PGCONT 0x0080 /* Continue paging until next reset */ #define MDB_IOB_AUTOWRAP 0x0100 /* Auto-wrap if next chunk won't fit */ #define MDB_IOB_TTYLIKE 0x0200 /* Input is interactive like a tty */ typedef struct mdb_iob_stack { mdb_iob_t *stk_top; /* Topmost stack element */ size_t stk_size; /* Number of stack elements */ } mdb_iob_stack_t; typedef struct mdb_iob_ctx { jmp_buf ctx_rpcb; /* Read-side context label */ jmp_buf ctx_wpcb; /* Write-side context label */ void *ctx_rptr; /* Read-side client data */ void *ctx_wptr; /* Write-side client data */ void *ctx_data; /* Pointer to client data */ mdb_iob_t *ctx_iob; /* Storage for iob save/restore */ } mdb_iob_ctx_t; #define MDB_IOB_RDIOB 0 /* Index for pipe's read-side iob */ #define MDB_IOB_WRIOB 1 /* Index for pipe's write-side iob */ typedef void mdb_iobsvc_f(mdb_iob_t *, mdb_iob_t *, mdb_iob_ctx_t *); #define MDBIOC (('m' << 24) | ('d' << 16) | ('b' << 8)) #define MDB_IOC_CTTY (MDBIOC | 0x01) /* Clear child tty settings */ #define MDB_IOC_TSET (MDBIOC | 0x02) /* Set terminal type */ #define MDB_IOC_GETFD (MDBIOC | 0x04) /* Get file descriptor (if any) */ typedef void mdb_table_print_f(void *); #define MDB_TBL_DONE 0 #define MDB_TBL_PRNT 1 #define MDB_TBL_FUNC 2 extern mdb_io_t *mdb_io_hold(mdb_io_t *); extern void mdb_io_rele(mdb_io_t *); extern void mdb_io_destroy(mdb_io_t *); extern mdb_iob_t *mdb_iob_create(mdb_io_t *, uint_t); extern void mdb_iob_pipe(mdb_iob_t **, mdb_iobsvc_f *, mdb_iobsvc_f *); extern void mdb_iob_destroy(mdb_iob_t *); extern void mdb_iob_flush(mdb_iob_t *); extern void mdb_iob_nlflush(mdb_iob_t *); extern void mdb_iob_discard(mdb_iob_t *); extern void mdb_iob_push_io(mdb_iob_t *, mdb_io_t *); extern mdb_io_t *mdb_iob_pop_io(mdb_iob_t *); extern void mdb_iob_resize(mdb_iob_t *, size_t, size_t); extern void mdb_iob_setpager(mdb_iob_t *, mdb_io_t *); extern void mdb_iob_clearlines(mdb_iob_t *); extern void mdb_iob_tabstop(mdb_iob_t *, size_t); extern void mdb_iob_margin(mdb_iob_t *, size_t); extern void mdb_iob_setbuf(mdb_iob_t *, void *, size_t); extern void mdb_iob_setflags(mdb_iob_t *, uint_t); extern void mdb_iob_clrflags(mdb_iob_t *, uint_t); extern uint_t mdb_iob_getflags(mdb_iob_t *); extern void mdb_iob_vprintf(mdb_iob_t *, const char *, va_list); extern void mdb_iob_aprintf(mdb_iob_t *, const char *, const struct mdb_arg *); extern void mdb_iob_printf(mdb_iob_t *, const char *, ...); extern size_t mdb_iob_vsnprintf(char *, size_t, const char *, va_list); extern size_t mdb_iob_asnprintf(char *, size_t, const char *, const struct mdb_arg *); extern size_t mdb_iob_snprintf(char *, size_t, const char *, ...); extern void mdb_iob_nputs(mdb_iob_t *, const char *, size_t); extern void mdb_iob_puts(mdb_iob_t *, const char *); extern void mdb_iob_putc(mdb_iob_t *, int); extern void mdb_iob_fill(mdb_iob_t *, int, size_t); extern void mdb_iob_ws(mdb_iob_t *, size_t); extern void mdb_iob_tab(mdb_iob_t *); extern void mdb_iob_nl(mdb_iob_t *); extern ssize_t mdb_iob_ngets(mdb_iob_t *, char *, size_t); extern int mdb_iob_getc(mdb_iob_t *); extern int mdb_iob_ungetc(mdb_iob_t *, int); extern int mdb_iob_eof(mdb_iob_t *); extern int mdb_iob_err(mdb_iob_t *); extern ssize_t mdb_iob_read(mdb_iob_t *, void *, size_t); extern ssize_t mdb_iob_write(mdb_iob_t *, const void *, size_t); extern int mdb_iob_ctl(mdb_iob_t *, int, void *); extern const char *mdb_iob_name(mdb_iob_t *); extern size_t mdb_iob_lineno(mdb_iob_t *); extern size_t mdb_iob_gettabstop(mdb_iob_t *); extern size_t mdb_iob_getmargin(mdb_iob_t *); extern void mdb_iob_set_autowrap(mdb_iob_t *); extern void mdb_iob_stack_create(mdb_iob_stack_t *); extern void mdb_iob_stack_destroy(mdb_iob_stack_t *); extern void mdb_iob_stack_push(mdb_iob_stack_t *, mdb_iob_t *, size_t); extern mdb_iob_t *mdb_iob_stack_pop(mdb_iob_stack_t *); extern size_t mdb_iob_stack_size(mdb_iob_stack_t *); extern const char *mdb_iob_format2str(const char *); /* * Available i/o backend constructors for common MDB code. These are * implemented in the corresponding .c files. */ extern mdb_io_t *mdb_logio_create(mdb_io_t *); extern mdb_io_t *mdb_fdio_create_path(const char **, const char *, int, mode_t); extern mdb_io_t *mdb_fdio_create_named(int fd, const char *); extern mdb_io_t *mdb_fdio_create(int); extern mdb_io_t *mdb_strio_create(const char *); extern mdb_io_t *mdb_termio_create(const char *, mdb_io_t *, mdb_io_t *); extern mdb_io_t *mdb_pipeio_create(mdb_iobsvc_f *, mdb_iobsvc_f *); extern mdb_io_t *mdb_nullio_create(void); extern mdb_io_t *mdb_memio_create(char *, size_t); /* * Functions for testing whether the given iob is of a given backend type: */ extern int mdb_iob_isastr(mdb_iob_t *); extern int mdb_iob_isatty(mdb_iob_t *); extern int mdb_iob_isapipe(mdb_iob_t *); extern void mdb_table_print(uint_t, const char *, ...); extern int mdb_setupterm(const char *, mdb_io_t *, int *); extern int mdb_fdio_fileno(mdb_io_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_IO_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_IO_IMPL_H #define _MDB_IO_IMPL_H #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB typedef struct mdb_io_ops { ssize_t (*io_read)(mdb_io_t *, void *, size_t); ssize_t (*io_write)(mdb_io_t *, const void *, size_t); off64_t (*io_seek)(mdb_io_t *, off64_t, int); int (*io_ctl)(mdb_io_t *, int, void *); void (*io_close)(mdb_io_t *); const char *(*io_name)(mdb_io_t *); void (*io_link)(mdb_io_t *, mdb_iob_t *); void (*io_unlink)(mdb_io_t *, mdb_iob_t *); int (*io_setattr)(mdb_io_t *, int, uint_t); void (*io_suspend)(mdb_io_t *); void (*io_resume)(mdb_io_t *); } mdb_io_ops_t; #define IOP_READ(io, buf, len) ((io)->io_ops->io_read((io), (buf), (len))) #define IOP_WRITE(io, buf, len) ((io)->io_ops->io_write((io), (buf), (len))) #define IOP_SEEK(io, off, whence) ((io)->io_ops->io_seek((io), (off), (whence))) #define IOP_CTL(io, req, arg) ((io)->io_ops->io_ctl((io), (req), (arg))) #define IOP_CLOSE(io) ((io)->io_ops->io_close((io))) #define IOP_NAME(io) ((io)->io_ops->io_name((io))) #define IOP_LINK(io, iob) ((io)->io_ops->io_link((io), (iob))) #define IOP_UNLINK(io, iob) ((io)->io_ops->io_unlink((io), (iob))) #define IOP_SETATTR(io, r, a) ((io)->io_ops->io_setattr((io), (r), (a))) #define IOP_SUSPEND(io) ((io)->io_ops->io_suspend((io))) #define IOP_RESUME(io) ((io)->io_ops->io_resume((io))) #define IOPF_READ(io) \ ((ssize_t (*)(mdb_io_t *, void *, size_t))(io)->io_ops->io_read) #define IOPF_WRITE(io) \ ((ssize_t (*)(mdb_io_t *, void *, size_t))(io)->io_ops->io_write) #define ATT_STANDOUT 0x01 /* Standout mode */ #define ATT_UNDERLINE 0x02 /* Underline mode */ #define ATT_REVERSE 0x04 /* Reverse video mode */ #define ATT_BOLD 0x08 /* Bold text mode */ #define ATT_DIM 0x10 /* Dim text mode */ #define ATT_ALTCHARSET 0x20 /* Alternate character set mode */ #define ATT_ALL 0x3f /* Mask of all valid attributes */ #define ATT_OFF 0 /* Turn attributes off */ #define ATT_ON 1 /* Turn attributes on */ struct mdb_io { const mdb_io_ops_t *io_ops; /* I/O type-specific operations */ void *io_data; /* I/O type-specific data pointer */ mdb_io_t *io_next; /* Link to next i/o object on stack */ size_t io_refcnt; /* Reference count */ }; struct mdb_iob { char *iob_buf; /* Input/output buffer */ size_t iob_bufsiz; /* Size of iob_buf in bytes */ char *iob_bufp; /* Current buffer location */ size_t iob_nbytes; /* Number of bytes in io_buf */ size_t iob_nlines; /* Lines output on current page */ size_t iob_lineno; /* Storage for saved yylineno */ size_t iob_rows; /* Terminal height */ size_t iob_cols; /* Terminal width */ size_t iob_tabstop; /* Tab stop width */ size_t iob_margin; /* Margin width */ uint_t iob_flags; /* Flags (see ) */ mdb_io_t *iob_iop; /* I/o implementation pointer */ mdb_io_t *iob_pgp; /* Pager i/o implementation pointer */ mdb_iob_t *iob_next; /* Stack next pointer */ }; /* * Stub functions for i/o backend implementors: these stubs either act as * pass-through no-ops or return ENOTSUP as appropriate. */ extern ssize_t no_io_read(mdb_io_t *, void *, size_t); extern ssize_t no_io_write(mdb_io_t *, const void *, size_t); extern off64_t no_io_seek(mdb_io_t *, off64_t, int); extern int no_io_ctl(mdb_io_t *, int, void *); extern void no_io_close(mdb_io_t *); extern const char *no_io_name(mdb_io_t *); extern void no_io_link(mdb_io_t *, mdb_iob_t *); extern void no_io_unlink(mdb_io_t *, mdb_iob_t *); extern int no_io_setattr(mdb_io_t *, int, uint_t); extern void no_io_suspend(mdb_io_t *); extern void no_io_resume(mdb_io_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_IO_IMPL_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_KB_H #define _MDB_KB_H /* * A KVM backend is used by the KVM target to interrogate the address space of * the subject binary. This is almost always via direct calls into libkvm, * except for hypervisor core dumps, which implement its own backend via the * mdb_kb support module. */ #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB struct as; struct privmregs; typedef struct mdb_kb_ops { void *(*kb_open)(const char *, const char *, const char *, int, const char *); int (*kb_close)(void *); mdb_io_t *(*kb_sym_io)(void *, const char *); ssize_t (*kb_kread)(void *, uintptr_t, void *, size_t); ssize_t (*kb_kwrite)(void *, uintptr_t, const void *, size_t); ssize_t (*kb_aread)(void *, uintptr_t, void *, size_t, struct as *); ssize_t (*kb_awrite)(void *, uintptr_t, const void *, size_t, struct as *); ssize_t (*kb_pread)(void *, uint64_t, void *, size_t); ssize_t (*kb_pwrite)(void *, uint64_t, const void *, size_t); uint64_t (*kb_vtop)(void *, struct as *, uintptr_t); int (*kb_getmregs)(void *, uint_t, struct privmregs *); } mdb_kb_ops_t; extern mdb_kb_ops_t *libkvm_kb_ops(void); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_KB_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ /* * The default KVM backend, which simply calls directly into libkvm for all * operations. */ #include #include #include #include #include /*ARGSUSED*/ static mdb_io_t * libkvm_sym_io(void *kvm, const char *ignored) { mdb_io_t *io; const char *symfile = kvm_namelist(kvm); if ((io = mdb_fdio_create_path(NULL, symfile, O_RDONLY, 0)) == NULL) mdb_warn("failed to open %s", symfile); return (io); } mdb_kb_ops_t * libkvm_kb_ops(void) { static mdb_kb_ops_t ops = { .kb_open = (void *(*)())kvm_open, .kb_close = (int (*)())kvm_close, .kb_sym_io = libkvm_sym_io, .kb_kread = (ssize_t (*)())kvm_kread, .kb_kwrite = (ssize_t (*)())kvm_kwrite, .kb_aread = (ssize_t (*)())kvm_aread, .kb_awrite = (ssize_t (*)())kvm_awrite, .kb_pread = (ssize_t (*)())kvm_pread, .kb_pwrite = (ssize_t (*)())kvm_pwrite, .kb_getmregs = (int (*)())(uintptr_t)mdb_tgt_notsup, .kb_vtop = (uint64_t (*)())kvm_physaddr, }; return (&ops); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Joyent, Inc. * Copyright 2024 Oxide Computer Company */ /* * Kernel Process View Target * * The kproc target is activated when the user is debugging a kernel using the * kvm target and executes a ::context dcmd to change the debugger view to one * of the running processes. The kvm target's t_setcontext operation will * create and activate a kproc target in response to this call. The kproc * target itself is built upon the kvm target's libkvm cookie and the ability * to read information from the kernel itself and the ability to read the * address space of a particular user process with kvm_aread(). It also relies * on a special set of functions provided by the kvm target's mdb_ks support * module in order to bootstrap: specifically, given the initial proc pointer, * mdb_ks provides functions to return the set of address space mappings, the * address space pointer itself, the aux vector vector saved in the u-area, * and the process data model. The kproc target maintains a list of address * space mappings (kp_map_t) and load objects (kp_file_t), and for each load * object will attempt to read the corresponding dynamic symbol table. In * order to bootstrap, the target uses the AT_BASE and AT_ENTRY aux vector * elements to locate the dynamic linker and executable mappings. With these * mappings in place, we initialize a librtld_db agent on the target (see * mdb_pservice.c for how this is done), and then process each load object * found in the link-map chain. In order to simplify the construction of * symbol tables for each load object, we would like make use of our existing * library of GElf processing code. Since the MDB GElf code uses mdb_io * objects to read in an ELF file, we simply define a new type of mdb_io object * where each read operation is translated into a call to kproc's t_vread * function to read from the range of the address space defined by the mapping * as if it were a file. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct kp_symarg { mdb_tgt_sym_f *sym_cb; /* Caller's callback function */ void *sym_data; /* Callback function argument */ uint_t sym_type; /* Symbol type/binding filter */ uintptr_t sym_adjust; /* Symbol value adjustment */ mdb_syminfo_t sym_info; /* Symbol id and table id */ const char *sym_obj; /* Containing object */ } kp_symarg_t; typedef struct kp_file { mdb_gelf_file_t *kpf_file; /* ELF file object */ mdb_io_t *kpf_fio; /* ELF file back-end */ mdb_gelf_symtab_t *kpf_dynsym; /* Dynamic symbol table */ struct kp_map *kpf_map; /* Primary (text) mapping */ const char *kpf_basename; /* Mapping basename */ uintptr_t kpf_dyn_base; /* Load address for ET_DYN files */ uintptr_t kpf_text_base; /* Base address of text mapping */ uintptr_t kpf_data_base; /* Base address of data mapping */ struct kp_file *kpf_next; /* Pointer to next file */ } kp_file_t; typedef struct kp_map { mdb_map_t kpm_map; /* Mapping information */ kp_file_t *kpm_file; /* Pointer to load object */ struct kp_map *kpm_next; /* Pointer to next mapping */ } kp_map_t; typedef struct kp_io { mdb_tgt_t *kpi_tgt; /* Backpointer to kproc target */ kp_map_t *kpi_map; /* Mapping for this i/o */ uintptr_t kpi_ptr; /* Virtual address pointer */ uintptr_t kpi_lim; /* Virtual address limit */ } kp_io_t; typedef struct kp_data { mdb_tgt_t *kp_parent; /* Parent kvm target */ kvm_t *kp_cookie; /* Cookie for libkvm routines */ rd_agent_t *kp_rap; /* Cookie for librtld_db routines */ proc_t *kp_proc; /* Proc address in dump */ struct as *kp_as; /* Proc as address in dump */ pid_t kp_pid; /* Process ID */ auxv_t *kp_auxv; /* Auxv array from u-area */ int kp_nauxv; /* Length of kp_auxv */ const char *kp_platform; /* Platform string from kvm target */ uint_t kp_model; /* Process data model */ kp_file_t *kp_file_head; /* Head of load object list */ kp_file_t *kp_file_tail; /* Tail of load object list */ kp_map_t *kp_map_head; /* Head of mapping list */ kp_map_t *kp_map_tail; /* Tail of mapping list */ int kp_num_files; /* Length of load object list */ int kp_num_maps; /* Length of mapping list */ kp_map_t *kp_map_exec; /* Executable mapping */ kp_map_t *kp_map_ldso; /* Interpreter mapping */ kp_file_t kp_prfile; /* Fake file for mdb.m_prsym */ } kp_data_t; static mdb_io_t *kp_io_create(mdb_tgt_t *, kp_map_t *); static kp_map_t * kp_addr_to_kpmap(kp_data_t *kp, uintptr_t addr) { kp_map_t *kpm; for (kpm = kp->kp_map_head; kpm != NULL; kpm = kpm->kpm_next) { if (addr >= kpm->kpm_map.map_base && addr < kpm->kpm_map.map_base + kpm->kpm_map.map_size) return (kpm); } return (NULL); } static long kp_getauxval(kp_data_t *kp, int type) { auxv_t *auxp; for (auxp = kp->kp_auxv; auxp->a_type != AT_NULL; auxp++) { if (auxp->a_type == type) return (auxp->a_un.a_val); } return (-1L); } static void kp_add_mapping(const mdb_map_t *pmp, void *data) { kp_map_t *kpm = mdb_zalloc(sizeof (kp_map_t), UM_SLEEP); kp_data_t *kp = data; bcopy(pmp, &kpm->kpm_map, sizeof (mdb_map_t)); if (kp->kp_map_tail != NULL) kp->kp_map_tail->kpm_next = kpm; else kp->kp_map_head = kpm; kp->kp_map_tail = kpm; kp->kp_num_maps++; } static kp_file_t * kp_file_create(mdb_tgt_t *t, kp_map_t *kpm, GElf_Half etype) { kp_file_t *kpf = mdb_zalloc(sizeof (kp_file_t), UM_SLEEP); kp_data_t *kp = t->t_data; size_t dyns_sz; void *dyns; kpf->kpf_fio = kp_io_create(t, kpm); kpf->kpf_map = kpm; kpf->kpf_basename = strbasename(kpm->kpm_map.map_name); kpf->kpf_file = mdb_gelf_create(kpf->kpf_fio, etype, GF_PROGRAM); kpf->kpf_text_base = kpm->kpm_map.map_base; if (kpm != kp->kp_map_exec) kpf->kpf_dyn_base = kpf->kpf_text_base; if (kpf->kpf_file == NULL) goto err; /* Failed to create ELF file */ mdb_dprintf(MDB_DBG_TGT, "loading symbols for %s\n", kpm->kpm_map.map_name); if ((kp->kp_rap != NULL) && (rd_get_dyns(kp->kp_rap, kpf->kpf_text_base, &dyns, &dyns_sz) == RD_OK)) mdb_gelf_dyns_set(kpf->kpf_file, dyns, dyns_sz); kpf->kpf_dynsym = mdb_gelf_symtab_create_dynamic(kpf->kpf_file, MDB_TGT_DYNSYM); if (kpf->kpf_dynsym == NULL) goto err; /* Failed to create symbol table */ kpm->kpm_file = kpf; if (kp->kp_file_tail != NULL) kp->kp_file_tail->kpf_next = kpf; else kp->kp_file_head = kpf; kp->kp_file_tail = kpf; kp->kp_num_files++; return (kpf); err: if (kpf->kpf_file != NULL) mdb_gelf_destroy(kpf->kpf_file); else mdb_io_destroy(kpf->kpf_fio); mdb_free(kpf, sizeof (kp_file_t)); return (NULL); } static void kp_file_destroy(kp_file_t *kpf) { if (kpf->kpf_dynsym != NULL) mdb_gelf_symtab_destroy(kpf->kpf_dynsym); mdb_gelf_destroy(kpf->kpf_file); mdb_free(kpf, sizeof (kp_file_t)); } static int kp_setcontext(mdb_tgt_t *t, void *context) { kp_data_t *kp = t->t_data; if (kp->kp_proc != context) { mdb_tgt_destroy(t); return (mdb_tgt_setcontext(mdb.m_target, context)); } mdb_warn("debugger context is already set to proc %p\n", context); return (0); } static kp_map_t * kp_find_data(kp_data_t *kp, kp_file_t *kpf, const rd_loadobj_t *rlp) { GElf_Phdr *gpp = kpf->kpf_file->gf_phdrs; size_t i, n = kpf->kpf_file->gf_npload; /* * Find the first loadable, writeable Phdr and compute kpf_data_base * as the virtual address at which is was loaded. */ for (i = 0; i < n; i++, gpp++) { if (gpp->p_type == PT_LOAD && (gpp->p_flags & PF_W)) { kpf->kpf_data_base = gpp->p_vaddr; if (kpf->kpf_map != kp->kp_map_exec) kpf->kpf_data_base += rlp->rl_base; break; } } /* * If we found a suitable Phdr and set kpf_data_base, return * the mapping information for this address; otherwise fail. */ if (kpf->kpf_data_base != 0) return (kp_addr_to_kpmap(kp, kpf->kpf_data_base)); return (NULL); } static int kp_iter_mapping(const rd_loadobj_t *rlp, mdb_tgt_t *t) { kp_data_t *kp = t->t_data; kp_file_t *kpf; kp_map_t *kpm; char name[MDB_TGT_MAPSZ]; if (mdb_tgt_readstr(t, MDB_TGT_AS_VIRT, name, sizeof (name), (mdb_tgt_addr_t)rlp->rl_nameaddr) <= 0) { mdb_dprintf(MDB_DBG_TGT, "failed to read name %p", (void *)rlp->rl_nameaddr); return (1); /* Keep going; forget this if we can't read name */ } mdb_dprintf(MDB_DBG_TGT, "rd_loadobj name = \"%s\" rl_base = %p\n", name, (void *)rlp->rl_base); if ((kpm = kp_addr_to_kpmap(kp, rlp->rl_base)) == NULL) return (1); /* Keep going; no mapping at this address */ (void) strncpy(kpm->kpm_map.map_name, name, MDB_TGT_MAPSZ); kpm->kpm_map.map_name[MDB_TGT_MAPSZ - 1] = '\0'; if ((kpf = kpm->kpm_file) == NULL) { if (kpm == kp->kp_map_exec) kpf = kp_file_create(t, kpm, ET_EXEC); else kpf = kp_file_create(t, kpm, ET_DYN); if (kpf == NULL) return (1); /* Keep going; failed to build ELF file */ } else kpf->kpf_basename = strbasename(kpm->kpm_map.map_name); if ((kpm = kp_find_data(kp, kpf, rlp)) != NULL) { mdb_dprintf(MDB_DBG_TGT, "found data for %s at %p\n", kpf->kpf_basename, (void *)kpm->kpm_map.map_base); kpm->kpm_file = kpf; } return (1); } /*ARGSUSED*/ static int kp_status_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kp_data_t *kp = mdb.m_target->t_data; mdb_printf("debugging PID %d (%d-bit) in kernel crash dump\n", kp->kp_pid, kp->kp_model == PR_MODEL_ILP32 ? 32 : 64); if (kp->kp_map_exec != NULL) { mdb_printf("executable file: %s\n", kp->kp_map_exec->kpm_map.map_name); } return (DCMD_OK); } static const mdb_dcmd_t kp_dcmds[] = { { "status", NULL, "print summary of current target", kp_status_dcmd }, { NULL } }; static void kp_activate(mdb_tgt_t *t) { kp_data_t *kp = t->t_data; mdb_prop_postmortem = TRUE; mdb_prop_kernel = FALSE; if (kp->kp_model == PR_MODEL_ILP32) mdb_prop_datamodel = MDB_TGT_MODEL_ILP32; else mdb_prop_datamodel = MDB_TGT_MODEL_LP64; /* * Initialize our rtld_db agent and then iterate over the link map, * instantiating kp_file objects as we go. */ if ((kp->kp_rap = rd_new((struct ps_prochandle *)t)) != NULL) { (void) rd_loadobj_iter(kp->kp_rap, (rl_iter_f *) kp_iter_mapping, t); } else { mdb_warn("unable to initialize rtld_db agent for proc %p\n", (void *)kp->kp_proc); } (void) mdb_tgt_register_dcmds(t, &kp_dcmds[0], MDB_MOD_FORCE); if (kp->kp_map_exec != NULL && kp->kp_map_exec->kpm_file != NULL) mdb_tgt_elf_export(kp->kp_map_exec->kpm_file->kpf_file); else mdb_tgt_elf_export(NULL); } static void kp_deactivate(mdb_tgt_t *t) { const mdb_dcmd_t *dcp; for (dcp = &kp_dcmds[0]; dcp->dc_name != NULL; dcp++) { if (mdb_module_remove_dcmd(t->t_module, dcp->dc_name) == -1) warn("failed to remove dcmd %s", dcp->dc_name); } mdb_prop_postmortem = FALSE; mdb_prop_kernel = FALSE; mdb_prop_datamodel = MDB_TGT_MODEL_UNKNOWN; } static void kp_destroy(mdb_tgt_t *t) { kp_data_t *kp = t->t_data; kp_map_t *kpm, *nkpm; kp_file_t *kpf, *nkpf; if (kp->kp_rap != NULL) rd_delete(kp->kp_rap); for (kpm = kp->kp_map_head; kpm != NULL; kpm = nkpm) { nkpm = kpm->kpm_next; mdb_free(kpm, sizeof (kp_map_t)); } for (kpf = kp->kp_file_head; kpf != NULL; kpf = nkpf) { nkpf = kpf->kpf_next; kp_file_destroy(kpf); } mdb_free(kp->kp_auxv, kp->kp_nauxv * sizeof (auxv_t)); mdb_free(kp, sizeof (kp_data_t)); } /*ARGSUSED*/ static const char * kp_name(mdb_tgt_t *t) { return ("kproc"); } static const char * kp_isa(mdb_tgt_t *t) { kp_data_t *kp = t->t_data; #ifdef __sparc return (kp->kp_model == PR_MODEL_ILP32 ? "sparc" : "sparcv9"); #else return (kp->kp_model == PR_MODEL_ILP32 ? "i386" : "amd64"); #endif } static const char * kp_platform(mdb_tgt_t *t) { return (((kp_data_t *)t->t_data)->kp_platform); } static int kp_uname(mdb_tgt_t *t, struct utsname *utsp) { kp_data_t *kp = t->t_data; return (mdb_tgt_uname(kp->kp_parent, utsp)); } static int kp_dmodel(mdb_tgt_t *t) { kp_data_t *kp = t->t_data; switch (kp->kp_model) { case PR_MODEL_ILP32: return (MDB_TGT_MODEL_ILP32); case PR_MODEL_LP64: return (MDB_TGT_MODEL_LP64); } return (MDB_TGT_MODEL_UNKNOWN); } static kp_map_t * kp_name_to_kpmap(kp_data_t *kp, const char *name) { size_t namelen; kp_file_t *kpf; kp_map_t *kpm; /* * Handle special reserved names (except for MDB_TGT_OBJ_EVERY): */ if (name == MDB_TGT_OBJ_EXEC) return (kp->kp_map_exec); if (name == MDB_TGT_OBJ_RTLD) return (kp->kp_map_ldso); /* * First pass: look for exact matches on the entire pathname * associated with the mapping or its basename. */ for (kpm = kp->kp_map_head; kpm != NULL; kpm = kpm->kpm_next) { if ((kpf = kpm->kpm_file) != NULL) { if (strcmp(kpm->kpm_map.map_name, name) == 0 || strcmp(kpf->kpf_basename, name) == 0) return (kpf->kpf_map); } } namelen = strlen(name); /* * Second pass: look for partial matches (initial basename match * up to a '.' suffix); allows "libc.so" or "libc" to match "libc.so.1" */ for (kpm = kp->kp_map_head; kpm != NULL; kpm = kpm->kpm_next) { if ((kpf = kpm->kpm_file) != NULL) { if (strncmp(kpf->kpf_basename, name, namelen) == 0 && kpf->kpf_basename[namelen] == '.') return (kpf->kpf_map); } } /* * One last check: we allow "a.out" to always alias the executable, * assuming this name was not in use for something else. */ if (strcmp(name, "a.out") == 0) return (kp->kp_map_exec); return (NULL); } static ssize_t kp_vread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr) { kp_data_t *kp = t->t_data; ssize_t n = kvm_aread(kp->kp_cookie, addr, buf, nbytes, kp->kp_as); if (n == -1) return (set_errno(EMDB_NOMAP)); return (n); } static ssize_t kp_vwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr) { kp_data_t *kp = t->t_data; ssize_t n = kvm_awrite(kp->kp_cookie, addr, buf, nbytes, kp->kp_as); if (n == -1) return (set_errno(EMDB_NOMAP)); return (n); } int kp_vtop(mdb_tgt_t *t, mdb_tgt_as_t as, uintptr_t va, physaddr_t *pap) { kp_data_t *kp = t->t_data; physaddr_t pa; if (as != MDB_TGT_AS_VIRT && as != MDB_TGT_AS_VIRT_I && as != MDB_TGT_AS_VIRT_S) return (set_errno(EINVAL)); if ((pa = kvm_physaddr(kp->kp_cookie, kp->kp_as, va)) != -1ULL) { *pap = pa; return (0); } return (set_errno(EMDB_NOMAP)); } static int kp_lookup_by_name(mdb_tgt_t *t, const char *object, const char *name, GElf_Sym *symp, mdb_syminfo_t *sip) { kp_data_t *kp = t->t_data; kp_file_t *kpf; int n; GElf_Sym sym; uint_t symid; int rv = -1; /* * Simplify our task: if object is EVERY, then we need to search * kp_num_files files beginning at kp_file_head; otherwise we are * searching 1 file whose file pointer is obtained via object_to_map. */ if (object != MDB_TGT_OBJ_EVERY) { kp_map_t *kpm = kp_name_to_kpmap(kp, object); if (kpm == NULL || kpm->kpm_file == NULL) return (set_errno(EMDB_NOOBJ)); kpf = kpm->kpm_file; n = 1; } else { kpf = kp->kp_file_head; n = kp->kp_num_files; } /* * Iterate through the load object files and look for the symbol name * in the .dynsym of each. If we encounter a match with SHN_UNDEF, * keep looking in hopes of finding a better match. This means that * a name such as "puts" will match the puts function in libc instead * of matching the puts PLT entry in the a.out file. */ for (; n > 0; n--, kpf = kpf->kpf_next) { if (kpf->kpf_dynsym == NULL) continue; /* No symbols for this file */ if (mdb_gelf_symtab_lookup_by_name(kpf->kpf_dynsym, name, symp, &sip->sym_id) != 0) continue; /* Symbol name not found */ symp->st_value += kpf->kpf_dyn_base; if (symp->st_shndx != SHN_UNDEF) { sip->sym_table = MDB_TGT_DYNSYM; return (0); } if (rv != 0) { sym = *symp; symid = sip->sym_id; rv = 0; } } if (rv != 0) return (set_errno(EMDB_NOSYM)); sip->sym_table = MDB_TGT_DYNSYM; sip->sym_id = symid; *symp = sym; return (0); } static int kp_lookup_by_addr(mdb_tgt_t *t, uintptr_t addr, uint_t flags, char *buf, size_t nbytes, GElf_Sym *symp, mdb_syminfo_t *sip) { kp_data_t *kp = t->t_data; kp_map_t *kpm = kp_addr_to_kpmap(kp, addr); kp_file_t *sym_kpf = NULL; GElf_Sym sym; uint_t symid; const char *name; kp_file_t *kpf; int n; /* * Check the user's private symbol table first; if a match is * found there, we're done or we have a first guess. */ if (mdb_gelf_symtab_lookup_by_addr(mdb.m_prsym, addr, flags, buf, nbytes, symp, &sip->sym_id) == 0) { sym_kpf = &kp->kp_prfile; if (flags & MDB_TGT_SYM_EXACT) goto found; sym = *symp; symid = sip->sym_id; } /* * If no mapping contains the address and EXACT mode is set, we're done. * Otherwise we need to search all the symbol tables in fuzzy mode. * If we find a mapping, then we only need to search that symtab. */ if (kpm == NULL || kpm->kpm_file == NULL) { if (flags & MDB_TGT_SYM_EXACT) return (set_errno(EMDB_NOSYMADDR)); kpf = kp->kp_file_head; n = kp->kp_num_files; } else { kpf = kpm->kpm_file; n = 1; } /* * Iterate through our list of load objects, scanning each one which * has a symbol table. In fuzzy mode, we continue looking and * improve our choice if we find a closer symbol. */ for (; n > 0; n--, kpf = kpf->kpf_next) { if (kpf->kpf_dynsym == NULL) continue; /* No symbols for this file */ if (mdb_gelf_symtab_lookup_by_addr(kpf->kpf_dynsym, addr - kpf->kpf_dyn_base, flags, buf, nbytes, symp, &sip->sym_id) != 0) continue; /* No symbol for this address */ symp->st_value += kpf->kpf_dyn_base; if (flags & MDB_TGT_SYM_EXACT) { sym_kpf = kpf; goto found; } if (sym_kpf == NULL || mdb_gelf_sym_closer(symp, &sym, addr)) { sym_kpf = kpf; sym = *symp; symid = sip->sym_id; } } if (sym_kpf == NULL) return (set_errno(EMDB_NOSYMADDR)); *symp = sym; /* Copy our best symbol into the caller's symbol */ sip->sym_id = symid; found: /* * Once we've found something, copy the final name into the caller's * buffer and prefix it with the load object name if appropriate. */ name = mdb_gelf_sym_name(sym_kpf->kpf_dynsym, symp); if (sym_kpf != kp->kp_map_exec->kpm_file && sym_kpf != &kp->kp_prfile) { (void) mdb_snprintf(buf, nbytes, "%s`%s", sym_kpf->kpf_basename, name); } else if (nbytes > 0) { (void) strncpy(buf, name, nbytes); buf[nbytes - 1] = '\0'; } if (sym_kpf == &kp->kp_prfile) sip->sym_table = MDB_TGT_PRVSYM; else sip->sym_table = MDB_TGT_DYNSYM; return (0); } static int kp_symtab_func(void *data, const GElf_Sym *symp, const char *name, uint_t id) { kp_symarg_t *argp = data; if (mdb_tgt_sym_match(symp, argp->sym_type)) { GElf_Sym sym = *symp; sym.st_value += argp->sym_adjust; argp->sym_info.sym_id = id; return (argp->sym_cb(argp->sym_data, &sym, name, &argp->sym_info, argp->sym_obj)); } return (0); } static void kp_symtab_iter(kp_file_t *kpf, uint_t type, const char *obj, mdb_tgt_sym_f *cb, void *data) { if (kpf->kpf_dynsym != NULL) { kp_symarg_t arg; arg.sym_cb = cb; arg.sym_data = data; arg.sym_type = type; arg.sym_adjust = kpf->kpf_dyn_base; arg.sym_info.sym_table = kpf->kpf_dynsym->gst_tabid; arg.sym_obj = obj; mdb_gelf_symtab_iter(kpf->kpf_dynsym, kp_symtab_func, &arg); } } /*ARGSUSED*/ static int kp_symbol_iter(mdb_tgt_t *t, const char *object, uint_t which, uint_t type, mdb_tgt_sym_f *func, void *private) { kp_data_t *kp = t->t_data; kp_file_t *kpf = NULL; kp_map_t *kpm; switch ((uintptr_t)object) { case (uintptr_t)MDB_TGT_OBJ_EVERY: if (kp->kp_map_exec && kp->kp_map_exec->kpm_file) { kpf = kp->kp_map_exec->kpm_file; kp_symtab_iter(kpf, type, MDB_TGT_OBJ_EXEC, func, private); } if (kp->kp_map_ldso && kp->kp_map_ldso->kpm_file) { kpf = kp->kp_map_ldso->kpm_file; kp_symtab_iter(kpf, type, MDB_TGT_OBJ_RTLD, func, private); } return (0); case (uintptr_t)MDB_TGT_OBJ_EXEC: if (kp->kp_map_exec && kp->kp_map_exec->kpm_file) kpf = kp->kp_map_exec->kpm_file; break; case (uintptr_t)MDB_TGT_OBJ_RTLD: if (kp->kp_map_ldso && kp->kp_map_ldso->kpm_file) kpf = kp->kp_map_ldso->kpm_file; break; default: if ((kpm = kp_name_to_kpmap(kp, object)) != NULL) { kpf = kpm->kpm_file; break; } else return (set_errno(EMDB_NOOBJ)); } if (kpf != NULL) kp_symtab_iter(kpf, type, object, func, private); return (0); } static int kp_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { kp_data_t *kp = t->t_data; kp_map_t *kpm; for (kpm = kp->kp_map_head; kpm != NULL; kpm = kpm->kpm_next) { if (func(private, &kpm->kpm_map, kpm->kpm_map.map_name) != 0) break; } return (0); } static int kp_object_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { kp_data_t *kp = t->t_data; kp_file_t *kpf; for (kpf = kp->kp_file_head; kpf != NULL; kpf = kpf->kpf_next) { if (func(private, &kpf->kpf_map->kpm_map, kpf->kpf_map->kpm_map.map_name) != 0) break; } return (0); } static const mdb_map_t * kp_addr_to_map(mdb_tgt_t *t, uintptr_t addr) { kp_map_t *kpm = kp_addr_to_kpmap(t->t_data, addr); if (kpm != NULL) return (&kpm->kpm_map); (void) set_errno(EMDB_NOMAP); return (NULL); } static const mdb_map_t * kp_name_to_map(mdb_tgt_t *t, const char *name) { kp_map_t *kpm = kp_name_to_kpmap(t->t_data, name); if (kpm != NULL) return (&kpm->kpm_map); (void) set_errno(EMDB_NOOBJ); return (NULL); } /*ARGSUSED*/ static int kp_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { bzero(tsp, sizeof (mdb_tgt_status_t)); tsp->st_state = MDB_TGT_DEAD; return (0); } static int kp_auxv(mdb_tgt_t *t, const auxv_t **auxvp) { kp_data_t *kp = t->t_data; *auxvp = kp->kp_auxv; return (0); } static const mdb_tgt_ops_t kproc_ops = { .t_setflags = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_setcontext = kp_setcontext, .t_activate = kp_activate, .t_deactivate = kp_deactivate, .t_periodic = (void (*)())(uintptr_t)mdb_tgt_nop, .t_destroy = kp_destroy, .t_name = kp_name, .t_isa = kp_isa, .t_platform = kp_platform, .t_uname = kp_uname, .t_dmodel = kp_dmodel, .t_aread = (ssize_t (*)())mdb_tgt_notsup, .t_awrite = (ssize_t (*)())mdb_tgt_notsup, .t_vread = kp_vread, .t_vwrite = kp_vwrite, .t_pread = (ssize_t (*)())mdb_tgt_notsup, .t_pwrite = (ssize_t (*)())mdb_tgt_notsup, .t_fread = (ssize_t (*)())mdb_tgt_notsup, .t_fwrite = (ssize_t (*)())mdb_tgt_notsup, .t_ioread = (ssize_t (*)())mdb_tgt_notsup, .t_iowrite = (ssize_t (*)())mdb_tgt_notsup, .t_vtop = kp_vtop, .t_lookup_by_name = kp_lookup_by_name, .t_lookup_by_addr = kp_lookup_by_addr, .t_symbol_iter = kp_symbol_iter, .t_mapping_iter = kp_mapping_iter, .t_object_iter = kp_object_iter, .t_addr_to_map = kp_addr_to_map, .t_name_to_map = kp_name_to_map, .t_addr_to_ctf = (struct ctf_file *(*)())mdb_tgt_null, .t_name_to_ctf = (struct ctf_file *(*)())mdb_tgt_null, .t_status = kp_status, .t_run = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_step = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_step_out = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_next = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_cont = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_signal = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_add_sbrkpt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_vbrkpt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_pwapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_vwapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_iowapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sysenter = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sysexit = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_signal = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_fault = (int (*)())(uintptr_t)mdb_tgt_null, .t_getareg = (int (*)())(uintptr_t)mdb_tgt_notsup, /* XXX */ .t_putareg = (int (*)())(uintptr_t)mdb_tgt_notsup, /* XXX */ .t_stack_iter = (int (*)())(uintptr_t)mdb_tgt_notsup, /* XXX */ .t_auxv = kp_auxv, .t_thread_name = (int (*)())(uintptr_t)mdb_tgt_notsup, }; int mdb_kproc_tgt_create(mdb_tgt_t *t, int argc, const char *argv[]) { kp_data_t *kp = mdb_zalloc(sizeof (kp_data_t), UM_SLEEP); void *proc = (void *)argv[0]; long at_entry, at_base; GElf_Sym sym; int (*f_asiter)(uintptr_t, void (*)(const mdb_map_t *, void *), void *); int (*f_auxv)(uintptr_t, auxv_t *); uintptr_t (*f_as)(uintptr_t); uint_t (*f_model)(uintptr_t); pid_t (*f_pid)(uintptr_t); if (argc != 1) return (set_errno(EINVAL)); t->t_flags &= ~MDB_TGT_F_RDWR; t->t_data = kp; t->t_ops = &kproc_ops; f_asiter = (int (*)()) dlsym(RTLD_NEXT, "mdb_kproc_asiter"); f_auxv = (int (*)()) dlsym(RTLD_NEXT, "mdb_kproc_auxv"); f_as = (uintptr_t (*)()) dlsym(RTLD_NEXT, "mdb_kproc_as"); f_model = (model_t (*)()) dlsym(RTLD_NEXT, "mdb_kproc_model"); f_pid = (pid_t (*)()) dlsym(RTLD_NEXT, "mdb_kproc_pid"); if (f_asiter == NULL || f_auxv == NULL || f_as == NULL || f_model == NULL || f_pid == NULL) { warn("required kernel support module is not loaded\n"); goto err; } /* * Here the kproc target relies on the fact that at the time of its * instantiation, mdb.m_target is pointing at a kvm target, and * that the kvm target has stored its libkvm handle in t_pshandle. */ kp->kp_parent = mdb.m_target; kp->kp_cookie = mdb.m_target->t_pshandle; kp->kp_platform = mdb_tgt_platform(mdb.m_target); kp->kp_proc = proc; kp->kp_as = (struct as *)f_as((uintptr_t)proc); kp->kp_pid = f_pid((uintptr_t)proc); if (kp->kp_as == NULL) { warn("failed to obtain address space for proc %p\n", proc); goto err; } if (kp->kp_pid == -1) { warn("failed to obtain PID for proc %p\n", proc); goto err; } if (mdb_tgt_lookup_by_name(kp->kp_parent, MDB_TGT_OBJ_EXEC, "kas", &sym, NULL) == 0 && kp->kp_as == (struct as *)(uintptr_t)sym.st_value) { warn("specified process is a system process (no context)\n"); goto err; } if ((kp->kp_model = f_model((uintptr_t)proc)) == PR_MODEL_UNKNOWN) { warn("failed to obtain data model for proc %p\n", proc); goto err; } if (f_asiter((uintptr_t)kp->kp_as, kp_add_mapping, kp) == -1) { warn("failed to load mappings for proc %p", proc); goto err; } kp->kp_nauxv = f_auxv((uintptr_t)proc, NULL) + 1; kp->kp_auxv = mdb_alloc(sizeof (auxv_t) * kp->kp_nauxv, UM_SLEEP); if (f_auxv((uintptr_t)proc, kp->kp_auxv) == -1) { warn("failed to load auxv for proc %p", proc); goto err; } kp->kp_auxv[kp->kp_nauxv - 1].a_type = AT_NULL; kp->kp_auxv[kp->kp_nauxv - 1].a_un.a_val = 0; if ((at_entry = kp_getauxval(kp, AT_ENTRY)) == -1L) { warn("auxv for proc %p is missing AT_ENTRY\n", proc); goto err; } if ((at_base = kp_getauxval(kp, AT_BASE)) == -1L) { warn("auxv for proc %p is missing AT_BASE\n", proc); goto err; } /* * If we're applying kproc to a live kernel, we need to force libkvm * to set the current process to the process in question so we can * read from its address space. If kvm_getproc returns NULL, the * process may have gone away since our previous calls to mdb_ks. */ if (mdb_prop_postmortem == FALSE && kvm_getproc(kp->kp_cookie, kp->kp_pid) == NULL) warn("failed to attach to PID %d\n", (int)kp->kp_pid); kp->kp_map_exec = kp_addr_to_kpmap(kp, at_entry); kp->kp_map_ldso = kp_addr_to_kpmap(kp, at_base); (void) kp_file_create(t, kp->kp_map_exec, ET_EXEC); (void) kp_file_create(t, kp->kp_map_ldso, ET_DYN); kp->kp_prfile.kpf_dynsym = mdb.m_prsym; return (0); err: kp_destroy(t); return (-1); } static ssize_t kp_io_read(mdb_io_t *io, void *buf, size_t nbytes) { kp_io_t *kpi = io->io_data; kp_data_t *kp = kpi->kpi_tgt->t_data; kp_map_t *kpm = kp_addr_to_kpmap(kp, kpi->kpi_ptr); size_t left; if (kpm != NULL) { const mdb_map_t *mp = &kpm->kpm_map; left = mp->map_base + mp->map_size - kpi->kpi_ptr; } else left = 0; if (left != 0) { ssize_t rbytes = kp_vread(kpi->kpi_tgt, buf, MIN(nbytes, left), kpi->kpi_ptr); if (rbytes >= 0) kpi->kpi_ptr += rbytes; return (rbytes); } return (0); /* At end of segment or in hole; return EOF */ } static off64_t kp_io_seek(mdb_io_t *io, off64_t offset, int whence) { kp_io_t *kpi = io->io_data; const mdb_map_t *mp = &kpi->kpi_map->kpm_map; uintptr_t nptr; if (io->io_next != NULL) return (IOP_SEEK(io->io_next, offset, whence)); switch (whence) { case SEEK_SET: nptr = mp->map_base + offset; break; case SEEK_CUR: nptr = kpi->kpi_ptr + offset; break; case SEEK_END: nptr = kpi->kpi_lim + offset; break; default: return (set_errno(EINVAL)); } if (nptr < mp->map_base || nptr >= kpi->kpi_lim) return (set_errno(EINVAL)); kpi->kpi_ptr = nptr; return ((off64_t)(nptr - mp->map_base)); } static void kp_io_close(mdb_io_t *io) { mdb_free(io->io_data, sizeof (kp_io_t)); } static const char * kp_io_name(mdb_io_t *io) { kp_io_t *kpi = io->io_data; if (io->io_next != NULL) return (IOP_NAME(io->io_next)); return (kpi->kpi_map->kpm_map.map_name); } static const mdb_io_ops_t kp_io_ops = { .io_read = kp_io_read, .io_write = no_io_write, .io_seek = kp_io_seek, .io_ctl = no_io_ctl, .io_close = kp_io_close, .io_name = kp_io_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume, }; static mdb_io_t * kp_io_create(mdb_tgt_t *t, kp_map_t *kpm) { kp_data_t *kp = t->t_data; mdb_map_t *mp = &kp->kp_map_tail->kpm_map; mdb_io_t *io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP); kp_io_t *kpi = mdb_alloc(sizeof (kp_io_t), UM_SLEEP); kpi->kpi_tgt = t; kpi->kpi_map = kpm; kpi->kpi_ptr = kpm->kpm_map.map_base; kpi->kpi_lim = mp->map_base + mp->map_size; io->io_ops = &kp_io_ops; io->io_data = kpi; io->io_next = NULL; io->io_refcnt = 0; return (io); } /* * 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 2019 Joyent, Inc. */ #ifndef _MDB_KS_H #define _MDB_KS_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /* * MDB Kernel Support Interfaces: * * Debugger modules for kernel crash dumps can make use of these utility * functions. This module also provides support for . */ extern int mdb_vnode2path(uintptr_t, char *, size_t); extern uintptr_t mdb_page_lookup(uintptr_t, u_offset_t); extern pfn_t mdb_page2pfn(uintptr_t); extern uintptr_t mdb_pfn2page(pfn_t); extern uintptr_t mdb_pid2proc(pid_t, proc_t *); extern char mdb_vtype2chr(vtype_t, mode_t); extern uintptr_t mdb_addr2modctl(uintptr_t); extern ssize_t mdb_read_refstr(uintptr_t, char *, size_t); extern int mdb_name_to_major(const char *, major_t *); extern const char *mdb_major_to_name(major_t); extern int mdb_devinfo2driver(uintptr_t, char *, size_t); extern int mdb_devinfo2statep(uintptr_t, char *, uintptr_t *); extern int mdb_cpu2cpuid(uintptr_t); extern int mdb_cpuset_find(uintptr_t); extern hrtime_t mdb_gethrtime(void); extern int64_t mdb_get_lbolt(void); /* * Returns a pointer to the top of the soft state struct for the instance * specified, given the address of the global soft state pointer and size * of the struct. Also fills in the buffer pointed to by state_buf_p (if * non-NULL) with the contents of the state struct. */ extern int mdb_get_soft_state_byaddr(uintptr_t, uint_t, uintptr_t *, void *, size_t); /* * Returns a pointer to the top of the soft state struct for the instance * specified, given the name of the global soft state pointer and size * of the struct. Also fills in the buffer pointed to by state_buf_p (if * non-NULL) with the contents of the state struct. */ extern int mdb_get_soft_state_byname(char *, uint_t, uintptr_t *, void *, size_t); /* * Returns the pathname from the root devinfo node to the dip supplied. * Just like ddi_pathname in sunddi.c. */ extern char *mdb_ddi_pathname(uintptr_t, char *, size_t); /* * MDB Kernel STREAMS Subsystem: * * Debugger modules such as ip can provide facilities for decoding private * q_ptr data for STREAMS queues using this mechanism. The module first * registers a set of functions which may be invoked when q->q_qinfo matches * a given qinit address (such as ip`winit). The q_info function provides * a way for the module to return an information string about the particular * queue. The q_rnext and q_wnext functions provide a way for the generic * queue walker to ask how to proceed deeper in the STREAM when q_next is * NULL. This allows ip, for example, to provide access to the link-layer * queues beneath the ip-client queue. */ typedef struct mdb_qops { void (*q_info)(const queue_t *, char *, size_t); uintptr_t (*q_rnext)(const queue_t *); uintptr_t (*q_wnext)(const queue_t *); } mdb_qops_t; extern void mdb_qops_install(const mdb_qops_t *, uintptr_t); extern void mdb_qops_remove(const mdb_qops_t *, uintptr_t); extern char *mdb_qname(const queue_t *, char *, size_t); extern void mdb_qinfo(const queue_t *, char *, size_t); extern uintptr_t mdb_qrnext(const queue_t *); extern uintptr_t mdb_qwnext(const queue_t *); /* * These functions, provided by mdb_ks, may be used to fill in the q_rnext * and q_wnext members of mdb_qops_t, in the case where the client wishes * to simply return q->q_next: */ extern uintptr_t mdb_qrnext_default(const queue_t *); extern uintptr_t mdb_qwnext_default(const queue_t *); extern int mdb_mblk_count(const mblk_t *); /* DLPI primitive to string; returns NULL for unknown primitives */ extern const char *mdb_dlpi_prim(int); /* Generic function for working with MAC (network layer 2) addresses. */ extern void mdb_mac_addr(const uint8_t *, size_t, char *, size_t); extern void mdb_print_buildversion(void); /* * Target-specific interfaces * * The existence and accessibility of the functions listed below is relied upon * by the indicated targets. The targets look up and invoke these functions in * mdb_ks so that dependencies on the current kernel implementation are * isolated in mdb_ks. */ /* * MDB KPROC Target Interface: * (user processes from kernel crash dump) */ struct mdb_map; /* Private between kproc and ks */ extern int mdb_kproc_asiter(uintptr_t, void (*)(const struct mdb_map *, void *), void *); extern int mdb_kproc_auxv(uintptr_t, auxv_t *); extern uintptr_t mdb_kproc_as(uintptr_t); extern pid_t mdb_kproc_pid(uintptr_t); /* * MDB KVM Target Interface: * (kernel dump) */ extern void mdb_dump_print_content(dumphdr_t *, pid_t); extern int mdb_dump_find_curproc(void); /* * KMDB Target Interface: */ #ifdef _KMDB extern const mdb_modinfo_t *mdb_ks_init(void); #endif #ifdef __cplusplus } #endif #endif /* _MDB_KS_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright 2019 Joyent, Inc. * Copyright 2025 Oxide Computer Company */ /* * Libkvm Kernel Target * * The libkvm kernel target provides access to both crash dumps and live * kernels through /dev/ksyms and /dev/kmem, using the facilities provided by * the libkvm.so library. The target-specific data structures are shared * between this file (common code) and the ISA-dependent parts of the target, * and so they are defined in the mdb_kvm.h header. The target processes an * "executable" (/dev/ksyms or the unix.X file) which contains a primary * .symtab and .dynsym, and then also iterates over the krtld module chain in * the kernel in order to obtain a list of loaded modules and per-module symbol * tables. To improve startup performance, the per-module symbol tables are * instantiated on-the-fly whenever an address lookup falls within the text * section of a given module. The target also relies on services from the * mdb_ks (kernel support) module, which contains pieces of the implementation * that must be compiled against the kernel implementation. */ #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 #define KT_RELOC_BUF(buf, obase, nbase) \ ((uintptr_t)(buf) - (uintptr_t)(obase) + (uintptr_t)(nbase)) #define KT_BAD_BUF(buf, base, size) \ ((uintptr_t)(buf) < (uintptr_t)(base) || \ ((uintptr_t)(buf) >= (uintptr_t)(base) + (uintptr_t)(size))) typedef struct kt_symarg { mdb_tgt_sym_f *sym_cb; /* Caller's callback function */ void *sym_data; /* Callback function argument */ uint_t sym_type; /* Symbol type/binding filter */ mdb_syminfo_t sym_info; /* Symbol id and table id */ const char *sym_obj; /* Containing object */ } kt_symarg_t; typedef struct kt_maparg { mdb_tgt_t *map_target; /* Target used for mapping iter */ mdb_tgt_map_f *map_cb; /* Caller's callback function */ void *map_data; /* Callback function argument */ } kt_maparg_t; static const char KT_MODULE[] = "mdb_ks"; static const char KT_CTFPARENT[] = "genunix"; static void (*print_buildversion)(void); static void kt_load_module(kt_data_t *kt, mdb_tgt_t *t, kt_module_t *km) { km->km_data = mdb_alloc(km->km_datasz, UM_SLEEP); (void) mdb_tgt_vread(t, km->km_data, km->km_datasz, km->km_symspace_va); km->km_symbuf = (void *) KT_RELOC_BUF(km->km_symtab_va, km->km_symspace_va, km->km_data); km->km_strtab = (char *) KT_RELOC_BUF(km->km_strtab_va, km->km_symspace_va, km->km_data); km->km_symtab = mdb_gelf_symtab_create_raw(&kt->k_file->gf_ehdr, &km->km_symtab_hdr, km->km_symbuf, &km->km_strtab_hdr, km->km_strtab, MDB_TGT_SYMTAB); } static bool kt_adjust_module(const struct modctl *ctl, struct module *kmod) { /* * "struct module" was changed in illumos 17467 to accommodate * data from an extended ELF header. Unfortunately the new * fields were inserted into the middle of the structure * instead of the end where things would have been much easier * for us. In case we are loading a crash dump from an older * kernel everything will be misaligned from where we expect it * to be. Attempt to detect such cases and fix things up so we * can still load the module. Using CTF is not an option at * this point as we need to parse the module in order to find * its CTF data. */ if (kmod->text == ctl->mod_text && kmod->text_size == ctl->mod_text_size) { return (true); } bcopy(&kmod->shnum, &kmod->shdrs, sizeof (struct module) - offsetof(struct module, shdrs)); if (kmod->text != ctl->mod_text || kmod->text_size != ctl->mod_text_size) { mdb_printf("couldn't adjust old modctl %p's module", (void *)ctl->mod_mp); return (false); } kmod->shnum = kmod->hdr.e_shnum; kmod->phnum = kmod->hdr.e_phnum; kmod->shstrndx = kmod->hdr.e_shstrndx; return (true); } static void kt_load_modules(kt_data_t *kt, mdb_tgt_t *t) { char name[MAXNAMELEN]; uintptr_t addr, head; struct module kmod; struct modctl ctl; Shdr symhdr, strhdr; GElf_Sym sym; kt_module_t *km; if (mdb_tgt_lookup_by_name(t, MDB_TGT_OBJ_EXEC, "modules", &sym, NULL) == -1) { warn("failed to get 'modules' symbol"); return; } if (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &ctl, sizeof (ctl), MDB_TGT_OBJ_EXEC, "modules") != sizeof (ctl)) { warn("failed to read 'modules' struct"); return; } addr = head = (uintptr_t)sym.st_value; do { if (addr == 0) break; /* Avoid spurious NULL pointers in list */ if (mdb_tgt_vread(t, &ctl, sizeof (ctl), addr) == -1) { warn("failed to read modctl at %p", (void *)addr); return; } if (ctl.mod_mp == NULL) continue; /* No associated krtld structure */ if (mdb_tgt_readstr(t, MDB_TGT_AS_VIRT, name, MAXNAMELEN, (uintptr_t)ctl.mod_modname) <= 0) { warn("failed to read module name at %p", (void *)ctl.mod_modname); continue; } mdb_dprintf(MDB_DBG_KMOD, "reading mod %s (%p)\n", name, (void *)addr); if (mdb_nv_lookup(&kt->k_modules, name) != NULL) { warn("skipping duplicate module '%s', id=%d\n", name, ctl.mod_id); continue; } if (mdb_tgt_vread(t, &kmod, sizeof (kmod), (uintptr_t)ctl.mod_mp) == -1) { warn("failed to read module at %p\n", (void *)ctl.mod_mp); continue; } if (!kt_adjust_module(&ctl, &kmod)) continue; if (kmod.symspace == NULL || kmod.symhdr == NULL || kmod.strhdr == NULL) { /* * If no buffer for the symbols has been allocated, * or the shdrs for .symtab and .strtab are missing, * then we're out of luck. */ continue; } if (mdb_tgt_vread(t, &symhdr, sizeof (Shdr), (uintptr_t)kmod.symhdr) == -1) { warn("failed to read .symtab header for '%s', id=%d", name, ctl.mod_id); continue; } if (mdb_tgt_vread(t, &strhdr, sizeof (Shdr), (uintptr_t)kmod.strhdr) == -1) { warn("failed to read .strtab header for '%s', id=%d", name, ctl.mod_id); continue; } /* * Now get clever: f(*^ing krtld didn't used to bother updating * its own kmod.symsize value. We know that prior to this bug * being fixed, symspace was a contiguous buffer containing * .symtab, .strtab, and the symbol hash table in that order. * So if symsize is zero, recompute it as the size of .symtab * plus the size of .strtab. We don't need to load the hash * table anyway since we re-hash all the symbols internally. */ if (kmod.symsize == 0) kmod.symsize = symhdr.sh_size + strhdr.sh_size; /* * Similar logic can be used to make educated guesses * at the values of kmod.symtbl and kmod.strings. */ if (kmod.symtbl == NULL) kmod.symtbl = kmod.symspace; if (kmod.strings == NULL) kmod.strings = kmod.symspace + symhdr.sh_size; /* * Make sure things seem reasonable before we proceed * to actually read and decipher the symspace. */ if (KT_BAD_BUF(kmod.symtbl, kmod.symspace, kmod.symsize) || KT_BAD_BUF(kmod.strings, kmod.symspace, kmod.symsize)) { warn("skipping module '%s', id=%d (corrupt symspace)\n", name, ctl.mod_id); continue; } km = mdb_zalloc(sizeof (kt_module_t), UM_SLEEP); km->km_name = strdup(name); (void) mdb_nv_insert(&kt->k_modules, km->km_name, NULL, (uintptr_t)km, MDB_NV_EXTNAME); km->km_datasz = kmod.symsize; km->km_symspace_va = (uintptr_t)kmod.symspace; km->km_symtab_va = (uintptr_t)kmod.symtbl; km->km_strtab_va = (uintptr_t)kmod.strings; km->km_symtab_hdr = symhdr; km->km_strtab_hdr = strhdr; km->km_text_va = (uintptr_t)kmod.text; km->km_text_size = kmod.text_size; km->km_data_va = (uintptr_t)kmod.data; km->km_data_size = kmod.data_size; km->km_bss_va = (uintptr_t)kmod.bss; km->km_bss_size = kmod.bss_size; if (kt->k_ctfvalid) { km->km_ctf_va = (uintptr_t)kmod.ctfdata; km->km_ctf_size = kmod.ctfsize; } /* * Add the module to the end of the list of modules in load- * dependency order. This is needed to load the corresponding * debugger modules in the same order for layering purposes. */ mdb_list_append(&kt->k_modlist, km); if (t->t_flags & MDB_TGT_F_PRELOAD) { mdb_iob_printf(mdb.m_out, " %s", name); mdb_iob_flush(mdb.m_out); kt_load_module(kt, t, km); } } while ((addr = (uintptr_t)ctl.mod_next) != head); } int kt_setflags(mdb_tgt_t *t, int flags) { int iochg = ((flags ^ t->t_flags) & MDB_TGT_F_ALLOWIO) && !mdb_prop_postmortem; int rwchg = (flags ^ t->t_flags) & MDB_TGT_F_RDWR; kt_data_t *kt = t->t_data; const char *kvmfile; void *cookie; int mode; if (!iochg && !rwchg) return (0); if (kt->k_xpv_domu) { warn("read-only target"); return (-1); } if (iochg) { kvmfile = (flags & MDB_TGT_F_ALLOWIO) ? "/dev/allkmem" : "/dev/kmem"; } else { kvmfile = kt->k_kvmfile; } mode = (flags & MDB_TGT_F_RDWR) ? O_RDWR : O_RDONLY; if ((cookie = kt->k_kb_ops->kb_open(kt->k_symfile, kvmfile, NULL, mode, mdb.m_pname)) == NULL) { /* We failed to re-open, so don't change t_flags */ warn("failed to re-open target"); return (-1); } /* * We successfully reopened the target, so update k_kvmfile. Also set * the RDWR and ALLOWIO bits in t_flags to match those in flags. */ (void) kt->k_kb_ops->kb_close(kt->k_cookie); kt->k_cookie = cookie; if (kvmfile != kt->k_kvmfile) { strfree(kt->k_kvmfile); kt->k_kvmfile = strdup(kvmfile); } t->t_flags = (t->t_flags & ~(MDB_TGT_F_RDWR | MDB_TGT_F_ALLOWIO)) | (flags & (MDB_TGT_F_RDWR | MDB_TGT_F_ALLOWIO)); return (0); } /* * Determine which PIDs (if any) have their pages saved in the dump. We * do this by looking for content flags in dump_flags in the header. These * flags, which won't be set in older dumps, tell us whether a single process * has had its pages included in the dump. If a single process has been * included, we need to get the PID for that process from the dump_pids * array in the dump. */ static int kt_find_dump_contents(kt_data_t *kt) { dumphdr_t *dh = kt->k_dumphdr; pid_t pid = -1; if (dh->dump_flags & DF_ALL) return (KT_DUMPCONTENT_ALL); if (dh->dump_flags & DF_CURPROC) { if ((pid = kt->k_dump_find_curproc()) == -1) return (KT_DUMPCONTENT_INVALID); else return (pid); } else { return (KT_DUMPCONTENT_KERNEL); } } static int kt_dump_contains_proc(mdb_tgt_t *t, void *context) { kt_data_t *kt = t->t_data; pid_t (*f_pid)(uintptr_t); pid_t reqpid; switch (kt->k_dumpcontent) { case KT_DUMPCONTENT_KERNEL: return (0); case KT_DUMPCONTENT_ALL: return (1); case KT_DUMPCONTENT_INVALID: goto procnotfound; default: f_pid = (pid_t (*)()) dlsym(RTLD_NEXT, "mdb_kproc_pid"); if (f_pid == NULL) goto procnotfound; reqpid = f_pid((uintptr_t)context); if (reqpid == -1) goto procnotfound; return (kt->k_dumpcontent == reqpid); } procnotfound: warn("unable to determine whether dump contains proc %p\n", context); return (1); } int kt_setcontext(mdb_tgt_t *t, void *context) { if (context != NULL) { const char *argv[2]; int argc = 0; mdb_tgt_t *ct; kt_data_t *kt = t->t_data; argv[argc++] = (const char *)context; argv[argc] = NULL; if (kt->k_dumphdr != NULL && !kt_dump_contains_proc(t, context)) { warn("dump does not contain pages for proc %p\n", context); return (-1); } if ((ct = mdb_tgt_create(mdb_kproc_tgt_create, t->t_flags, argc, argv)) == NULL) return (-1); mdb_printf("debugger context set to proc %p\n", context); mdb_tgt_activate(ct); } else mdb_printf("debugger context set to kernel\n"); return (0); } static int kt_stack(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kt_data_t *kt = mdb.m_target->t_data; return (kt->k_dcmd_stack(addr, flags, argc, argv)); } static int kt_stackv(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kt_data_t *kt = mdb.m_target->t_data; return (kt->k_dcmd_stackv(addr, flags, argc, argv)); } static int kt_stackr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kt_data_t *kt = mdb.m_target->t_data; return (kt->k_dcmd_stackr(addr, flags, argc, argv)); } static int kt_regs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kt_data_t *kt = mdb.m_target->t_data; if (argc != 0 || (flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); addr = (uintptr_t)kt->k_regs; return (kt->k_dcmd_regs(addr, flags, argc, argv)); } #ifdef __x86 static int kt_cpustack(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kt_data_t *kt = mdb.m_target->t_data; return (kt->k_dcmd_cpustack(addr, flags, argc, argv)); } static int kt_cpuregs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kt_data_t *kt = mdb.m_target->t_data; return (kt->k_dcmd_cpuregs(addr, flags, argc, argv)); } #endif /* __x86 */ /*ARGSUSED*/ static int kt_status_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kt_data_t *kt = mdb.m_target->t_data; struct utsname uts; bzero(&uts, sizeof (uts)); (void) strcpy(uts.nodename, "unknown machine"); (void) kt_uname(mdb.m_target, &uts); if (mdb_prop_postmortem) { mdb_printf("debugging %scrash dump %s (%d-bit) from %s\n", kt->k_xpv_domu ? "domain " : "", kt->k_kvmfile, (int)(sizeof (void *) * NBBY), uts.nodename); } else { mdb_printf("debugging live kernel (%d-bit) on %s\n", (int)(sizeof (void *) * NBBY), uts.nodename); } mdb_printf("operating system: %s %s (%s)\n", uts.release, uts.version, uts.machine); if (print_buildversion != NULL) print_buildversion(); if (kt->k_dumphdr) { dumphdr_t *dh = kt->k_dumphdr; mdb_printf("image uuid: %s\n", dh->dump_uuid[0] != '\0' ? dh->dump_uuid : "(not set)"); mdb_printf("panic message: %s\n", dh->dump_panicstring); kt->k_dump_print_content(dh, kt->k_dumpcontent); } else { char uuid[UUID_PRINTABLE_STRING_LENGTH]; if (mdb_readsym(uuid, sizeof (uuid), "dump_osimage_uuid") == sizeof (uuid) && uuid[sizeof (uuid) - 1] == '\0') { mdb_printf("image uuid: %s\n", uuid[0] != '\0' ? uuid : "(not set)"); } } return (DCMD_OK); } static void kt_stack_help(void) { mdb_printf( "Options:\n" " -n do not resolve addresses to names\n" " -s show the size of each stack frame to the left\n" " -t where CTF is present, show types for functions and " "arguments\n" " -v include frame pointer information (this is the default " "with %$C%)\n" "\n" "If the optional %cnt% is given, no more than %cnt% " "arguments are shown\nfor each stack frame.\n"); } static const mdb_dcmd_t kt_dcmds[] = { { "$c", "?[-nstv] [cnt]", "print stack backtrace", kt_stack, kt_stack_help }, { "$C", "?[-nstv] [cnt]", "print stack backtrace", kt_stackv, kt_stack_help }, { "$r", NULL, "print general-purpose registers", kt_regs }, { "$?", NULL, "print status and registers", kt_regs }, { "regs", NULL, "print general-purpose registers", kt_regs }, { "stack", "?[-nstv] [cnt]", "print stack backtrace", kt_stack, kt_stack_help }, { "stackregs", "?[-nstv] [cnt]", "print stack backtrace and registers", kt_stackr, kt_stack_help }, #ifdef __x86 { "cpustack", "?[-v] [-c cpuid] [cnt]", "print stack backtrace for a " "specific CPU", kt_cpustack }, { "cpuregs", "?[-c cpuid]", "print general-purpose registers for a " "specific CPU", kt_cpuregs }, #endif { "status", NULL, "print summary of current target", kt_status_dcmd }, { NULL } }; static uintmax_t reg_disc_get(const mdb_var_t *v) { mdb_tgt_t *t = MDB_NV_COOKIE(v); kt_data_t *kt = t->t_data; mdb_tgt_reg_t r = 0; (void) mdb_tgt_getareg(t, kt->k_tid, mdb_nv_get_name(v), &r); return (r); } static kt_module_t * kt_module_by_name(kt_data_t *kt, const char *name) { kt_module_t *km; for (km = mdb_list_next(&kt->k_modlist); km; km = mdb_list_next(km)) { if (strcmp(name, km->km_name) == 0) return (km); } return (NULL); } void kt_activate(mdb_tgt_t *t) { static const mdb_nv_disc_t reg_disc = { .disc_get = reg_disc_get }; kt_data_t *kt = t->t_data; void *sym; int oflag = 0; mdb_prop_postmortem = kt->k_xpv_domu || (kt->k_dumphdr != NULL); mdb_prop_kernel = TRUE; mdb_prop_datamodel = MDB_TGT_MODEL_NATIVE; if (kt->k_activated == FALSE) { struct utsname u1, u2; /* * If we're examining a crash dump, root is /, and uname(2) * does not match the utsname in the dump, issue a warning. * Note that we are assuming that the modules and macros in * /usr/lib are compiled against the kernel from uname -rv. */ if (mdb_prop_postmortem && strcmp(mdb.m_root, "/") == 0 && uname(&u1) >= 0 && kt_uname(t, &u2) >= 0 && (strcmp(u1.release, u2.release) || strcmp(u1.version, u2.version))) { mdb_warn("warning: dump is from %s %s %s; dcmds and " "macros may not match kernel implementation\n", u2.sysname, u2.release, u2.version); } if (mdb_module_load(KT_MODULE, MDB_MOD_GLOBAL) < 0) { warn("failed to load kernel support module -- " "some modules may not load\n"); } print_buildversion = (void (*)(void))dlsym(RTLD_NEXT, "mdb_print_buildversion"); if (mdb_prop_postmortem && kt->k_dumphdr != NULL) { sym = dlsym(RTLD_NEXT, "mdb_dump_print_content"); if (sym != NULL) kt->k_dump_print_content = (void (*)())sym; sym = dlsym(RTLD_NEXT, "mdb_dump_find_curproc"); if (sym != NULL) kt->k_dump_find_curproc = (int (*)())sym; kt->k_dumpcontent = kt_find_dump_contents(kt); } if (t->t_flags & MDB_TGT_F_PRELOAD) { oflag = mdb_iob_getflags(mdb.m_out) & MDB_IOB_PGENABLE; mdb_iob_clrflags(mdb.m_out, oflag); mdb_iob_puts(mdb.m_out, "Preloading module symbols: ["); mdb_iob_flush(mdb.m_out); } if (!(t->t_flags & MDB_TGT_F_NOLOAD)) { kt_load_modules(kt, t); /* * Determine where the CTF data for krtld is. If krtld * is rolled into unix, force load the MDB krtld * module. */ kt->k_rtld_name = "krtld"; if (kt_module_by_name(kt, "krtld") == NULL) { (void) mdb_module_load("krtld", MDB_MOD_SILENT); kt->k_rtld_name = "unix"; } } if (t->t_flags & MDB_TGT_F_PRELOAD) { mdb_iob_puts(mdb.m_out, " ]\n"); mdb_iob_setflags(mdb.m_out, oflag); } kt->k_activated = TRUE; } (void) mdb_tgt_register_dcmds(t, &kt_dcmds[0], MDB_MOD_FORCE); /* Export some of our registers as named variables */ mdb_tgt_register_regvars(t, kt->k_rds, ®_disc, MDB_NV_RDONLY); mdb_tgt_elf_export(kt->k_file); } void kt_deactivate(mdb_tgt_t *t) { kt_data_t *kt = t->t_data; const mdb_tgt_regdesc_t *rdp; const mdb_dcmd_t *dcp; for (rdp = kt->k_rds; rdp->rd_name != NULL; rdp++) { mdb_var_t *v; if (!(rdp->rd_flags & MDB_TGT_R_EXPORT)) continue; /* Didn't export register as a variable */ if ((v = mdb_nv_lookup(&mdb.m_nv, rdp->rd_name)) != NULL) { v->v_flags &= ~MDB_NV_PERSIST; mdb_nv_remove(&mdb.m_nv, v); } } for (dcp = &kt_dcmds[0]; dcp->dc_name != NULL; dcp++) { if (mdb_module_remove_dcmd(t->t_module, dcp->dc_name) == -1) warn("failed to remove dcmd %s", dcp->dc_name); } mdb_prop_postmortem = FALSE; mdb_prop_kernel = FALSE; mdb_prop_datamodel = MDB_TGT_MODEL_UNKNOWN; } /*ARGSUSED*/ const char * kt_name(mdb_tgt_t *t) { return ("kvm"); } const char * kt_platform(mdb_tgt_t *t) { kt_data_t *kt = t->t_data; return (kt->k_platform); } int kt_uname(mdb_tgt_t *t, struct utsname *utsp) { return (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, utsp, sizeof (struct utsname), MDB_TGT_OBJ_EXEC, "utsname")); } /*ARGSUSED*/ int kt_dmodel(mdb_tgt_t *t) { return (MDB_TGT_MODEL_NATIVE); } ssize_t kt_aread(mdb_tgt_t *t, mdb_tgt_as_t as, void *buf, size_t nbytes, mdb_tgt_addr_t addr) { kt_data_t *kt = t->t_data; ssize_t rval; if ((rval = kt->k_kb_ops->kb_aread(kt->k_cookie, addr, buf, nbytes, as)) == -1) return (set_errno(EMDB_NOMAP)); return (rval); } ssize_t kt_awrite(mdb_tgt_t *t, mdb_tgt_as_t as, const void *buf, size_t nbytes, mdb_tgt_addr_t addr) { kt_data_t *kt = t->t_data; ssize_t rval; if ((rval = kt->k_kb_ops->kb_awrite(kt->k_cookie, addr, buf, nbytes, as)) == -1) return (set_errno(EMDB_NOMAP)); return (rval); } ssize_t kt_vread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr) { kt_data_t *kt = t->t_data; ssize_t rval; if ((rval = kt->k_kb_ops->kb_kread(kt->k_cookie, addr, buf, nbytes)) == -1) return (set_errno(EMDB_NOMAP)); return (rval); } ssize_t kt_vwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr) { kt_data_t *kt = t->t_data; ssize_t rval; if ((rval = kt->k_kb_ops->kb_kwrite(kt->k_cookie, addr, buf, nbytes)) == -1) return (set_errno(EMDB_NOMAP)); return (rval); } ssize_t kt_fread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr) { return (kt_vread(t, buf, nbytes, addr)); } ssize_t kt_fwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr) { return (kt_vwrite(t, buf, nbytes, addr)); } ssize_t kt_pread(mdb_tgt_t *t, void *buf, size_t nbytes, physaddr_t addr) { kt_data_t *kt = t->t_data; ssize_t rval; if ((rval = kt->k_kb_ops->kb_pread(kt->k_cookie, addr, buf, nbytes)) == -1) return (set_errno(EMDB_NOMAP)); return (rval); } ssize_t kt_pwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, physaddr_t addr) { kt_data_t *kt = t->t_data; ssize_t rval; if ((rval = kt->k_kb_ops->kb_pwrite(kt->k_cookie, addr, buf, nbytes)) == -1) return (set_errno(EMDB_NOMAP)); return (rval); } int kt_vtop(mdb_tgt_t *t, mdb_tgt_as_t as, uintptr_t va, physaddr_t *pap) { kt_data_t *kt = t->t_data; struct as *asp; physaddr_t pa; mdb_module_t *mod; mdb_var_t *v; int (*fptr)(uintptr_t, struct as *, physaddr_t *); switch ((uintptr_t)as) { case (uintptr_t)MDB_TGT_AS_PHYS: case (uintptr_t)MDB_TGT_AS_FILE: case (uintptr_t)MDB_TGT_AS_IO: return (set_errno(EINVAL)); case (uintptr_t)MDB_TGT_AS_VIRT: case (uintptr_t)MDB_TGT_AS_VIRT_I: case (uintptr_t)MDB_TGT_AS_VIRT_S: asp = kt->k_as; break; default: asp = (struct as *)as; } if ((pa = kt->k_kb_ops->kb_vtop(kt->k_cookie, asp, va)) != -1ULL) { *pap = pa; return (0); } if ((v = mdb_nv_lookup(&mdb.m_modules, "unix")) != NULL && (mod = mdb_nv_get_cookie(v)) != NULL) { fptr = (int (*)(uintptr_t, struct as *, physaddr_t *)) dlsym(mod->mod_hdl, "platform_vtop"); if ((fptr != NULL) && ((*fptr)(va, asp, pap) == 0)) return (0); } return (set_errno(EMDB_NOMAP)); } int kt_lookup_by_name(mdb_tgt_t *t, const char *obj, const char *name, GElf_Sym *symp, mdb_syminfo_t *sip) { kt_data_t *kt = t->t_data; kt_module_t *km, kmod; mdb_var_t *v; int n; /* * To simplify the implementation, we create a fake module on the stack * which is "prepended" to k_modlist and whose symtab is kt->k_symtab. */ kmod.km_symtab = kt->k_symtab; kmod.km_list.ml_next = mdb_list_next(&kt->k_modlist); switch ((uintptr_t)obj) { case (uintptr_t)MDB_TGT_OBJ_EXEC: km = &kmod; n = 1; break; case (uintptr_t)MDB_TGT_OBJ_EVERY: km = &kmod; n = mdb_nv_size(&kt->k_modules) + 1; break; case (uintptr_t)MDB_TGT_OBJ_RTLD: obj = kt->k_rtld_name; /*FALLTHRU*/ default: if ((v = mdb_nv_lookup(&kt->k_modules, obj)) == NULL) return (set_errno(EMDB_NOOBJ)); km = mdb_nv_get_cookie(v); n = 1; if (km->km_symtab == NULL) kt_load_module(kt, t, km); } for (; n > 0; n--, km = mdb_list_next(km)) { if (mdb_gelf_symtab_lookup_by_name(km->km_symtab, name, symp, &sip->sym_id) == 0) { sip->sym_table = MDB_TGT_SYMTAB; return (0); } } return (set_errno(EMDB_NOSYM)); } int kt_lookup_by_addr(mdb_tgt_t *t, uintptr_t addr, uint_t flags, char *buf, size_t nbytes, GElf_Sym *symp, mdb_syminfo_t *sip) { kt_data_t *kt = t->t_data; kt_module_t kmods[3], *kmods_begin = &kmods[0], *kmods_end; const char *name; kt_module_t *km = &kmods[0]; /* Point km at first fake module */ kt_module_t *sym_km = NULL; /* Module associated with best sym */ GElf_Sym sym; /* Best symbol found so far if !exact */ uint_t symid; /* ID of best symbol found so far */ /* * To simplify the implementation, we create fake modules on the stack * that are "prepended" to k_modlist and whose symtab is set to * each of three special symbol tables, in order of precedence. */ km->km_symtab = mdb.m_prsym; if (kt->k_symtab != NULL) { km->km_list.ml_next = (mdb_list_t *)(km + 1); km = mdb_list_next(km); km->km_symtab = kt->k_symtab; } if (kt->k_dynsym != NULL) { km->km_list.ml_next = (mdb_list_t *)(km + 1); km = mdb_list_next(km); km->km_symtab = kt->k_dynsym; } km->km_list.ml_next = mdb_list_next(&kt->k_modlist); kmods_end = km; /* * Now iterate over the list of fake and real modules. If the module * has no symbol table and the address is in the text section, * instantiate the module's symbol table. In exact mode, we can * jump to 'found' immediately if we match. Otherwise we continue * looking and improve our choice if we find a closer symbol. */ for (km = &kmods[0]; km != NULL; km = mdb_list_next(km)) { if (km->km_symtab == NULL && addr >= km->km_text_va && addr < km->km_text_va + km->km_text_size) kt_load_module(kt, t, km); if (mdb_gelf_symtab_lookup_by_addr(km->km_symtab, addr, flags, buf, nbytes, symp, &sip->sym_id) != 0 || symp->st_value == 0) continue; if (flags & MDB_TGT_SYM_EXACT) { sym_km = km; goto found; } if (sym_km == NULL || mdb_gelf_sym_closer(symp, &sym, addr)) { sym_km = km; sym = *symp; symid = sip->sym_id; } } if (sym_km == NULL) return (set_errno(EMDB_NOSYMADDR)); *symp = sym; /* Copy our best symbol into the caller's symbol */ sip->sym_id = symid; found: /* * Once we've found something, copy the final name into the caller's * buffer and prefix it with the load object name if appropriate. */ if (sym_km != NULL) { name = mdb_gelf_sym_name(sym_km->km_symtab, symp); if (sym_km < kmods_begin || sym_km > kmods_end) { (void) mdb_snprintf(buf, nbytes, "%s`%s", sym_km->km_name, name); } else if (nbytes > 0) { (void) strncpy(buf, name, nbytes); buf[nbytes - 1] = '\0'; } if (sym_km->km_symtab == mdb.m_prsym) sip->sym_table = MDB_TGT_PRVSYM; else sip->sym_table = MDB_TGT_SYMTAB; } else { sip->sym_table = MDB_TGT_SYMTAB; } return (0); } static int kt_symtab_func(void *data, const GElf_Sym *sym, const char *name, uint_t id) { kt_symarg_t *argp = data; if (mdb_tgt_sym_match(sym, argp->sym_type)) { argp->sym_info.sym_id = id; return (argp->sym_cb(argp->sym_data, sym, name, &argp->sym_info, argp->sym_obj)); } return (0); } static void kt_symtab_iter(mdb_gelf_symtab_t *gst, uint_t type, const char *obj, mdb_tgt_sym_f *cb, void *p) { kt_symarg_t arg; arg.sym_cb = cb; arg.sym_data = p; arg.sym_type = type; arg.sym_info.sym_table = gst->gst_tabid; arg.sym_obj = obj; mdb_gelf_symtab_iter(gst, kt_symtab_func, &arg); } int kt_symbol_iter(mdb_tgt_t *t, const char *obj, uint_t which, uint_t type, mdb_tgt_sym_f *cb, void *data) { kt_data_t *kt = t->t_data; kt_module_t *km; mdb_gelf_symtab_t *symtab = NULL; mdb_var_t *v; switch ((uintptr_t)obj) { case (uintptr_t)MDB_TGT_OBJ_EXEC: if (which == MDB_TGT_SYMTAB) symtab = kt->k_symtab; else symtab = kt->k_dynsym; break; case (uintptr_t)MDB_TGT_OBJ_EVERY: if (which == MDB_TGT_DYNSYM) { symtab = kt->k_dynsym; obj = MDB_TGT_OBJ_EXEC; break; } mdb_nv_rewind(&kt->k_modules); while ((v = mdb_nv_advance(&kt->k_modules)) != NULL) { km = mdb_nv_get_cookie(v); if (km->km_symtab == NULL) kt_load_module(kt, t, km); if (km->km_symtab != NULL) kt_symtab_iter(km->km_symtab, type, km->km_name, cb, data); } break; case (uintptr_t)MDB_TGT_OBJ_RTLD: obj = kt->k_rtld_name; /*FALLTHRU*/ default: v = mdb_nv_lookup(&kt->k_modules, obj); if (v == NULL) return (set_errno(EMDB_NOOBJ)); km = mdb_nv_get_cookie(v); if (km->km_symtab == NULL) kt_load_module(kt, t, km); symtab = km->km_symtab; } if (symtab) kt_symtab_iter(symtab, type, obj, cb, data); return (0); } static int kt_mapping_walk(uintptr_t addr, const void *data, kt_maparg_t *marg) { /* * This is a bit sketchy but avoids problematic compilation of this * target against the current VM implementation. Now that we have * vmem, we can make this less broken and more informative by changing * this code to invoke the vmem walker in the near future. */ const struct kt_seg { caddr_t s_base; size_t s_size; } *segp = (const struct kt_seg *)data; mdb_map_t map; GElf_Sym sym; mdb_syminfo_t info; map.map_base = (uintptr_t)segp->s_base; map.map_size = segp->s_size; map.map_flags = MDB_TGT_MAP_R | MDB_TGT_MAP_W | MDB_TGT_MAP_X; if (kt_lookup_by_addr(marg->map_target, addr, MDB_TGT_SYM_EXACT, map.map_name, MDB_TGT_MAPSZ, &sym, &info) == -1) { (void) mdb_iob_snprintf(map.map_name, MDB_TGT_MAPSZ, "%lr", addr); } return (marg->map_cb(marg->map_data, &map, map.map_name)); } int kt_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { kt_data_t *kt = t->t_data; kt_maparg_t m; m.map_target = t; m.map_cb = func; m.map_data = private; return (mdb_pwalk("seg", (mdb_walk_cb_t)kt_mapping_walk, &m, (uintptr_t)kt->k_as)); } static const mdb_map_t * kt_module_to_map(kt_module_t *km, mdb_map_t *map) { (void) strncpy(map->map_name, km->km_name, MDB_TGT_MAPSZ); map->map_name[MDB_TGT_MAPSZ - 1] = '\0'; map->map_base = km->km_text_va; map->map_size = km->km_text_size; map->map_flags = MDB_TGT_MAP_R | MDB_TGT_MAP_W | MDB_TGT_MAP_X; return (map); } int kt_object_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { kt_data_t *kt = t->t_data; kt_module_t *km; mdb_map_t m; for (km = mdb_list_next(&kt->k_modlist); km; km = mdb_list_next(km)) { if (func(private, kt_module_to_map(km, &m), km->km_name) == -1) break; } return (0); } const mdb_map_t * kt_addr_to_map(mdb_tgt_t *t, uintptr_t addr) { kt_data_t *kt = t->t_data; kt_module_t *km; for (km = mdb_list_next(&kt->k_modlist); km; km = mdb_list_next(km)) { if (addr - km->km_text_va < km->km_text_size || addr - km->km_data_va < km->km_data_size || addr - km->km_bss_va < km->km_bss_size) return (kt_module_to_map(km, &kt->k_map)); } (void) set_errno(EMDB_NOMAP); return (NULL); } const mdb_map_t * kt_name_to_map(mdb_tgt_t *t, const char *name) { kt_data_t *kt = t->t_data; kt_module_t *km; mdb_map_t m; /* * If name is MDB_TGT_OBJ_EXEC, return the first module on the list, * which will be unix since we keep k_modlist in load order. */ if (name == MDB_TGT_OBJ_EXEC) return (kt_module_to_map(mdb_list_next(&kt->k_modlist), &m)); if (name == MDB_TGT_OBJ_RTLD) name = kt->k_rtld_name; if ((km = kt_module_by_name(kt, name)) != NULL) return (kt_module_to_map(km, &m)); (void) set_errno(EMDB_NOOBJ); return (NULL); } static ctf_file_t * kt_load_ctfdata(mdb_tgt_t *t, kt_module_t *km) { kt_data_t *kt = t->t_data; int err; if (km->km_ctfp != NULL) return (km->km_ctfp); if (km->km_ctf_va == 0) { (void) set_errno(EMDB_NOCTF); return (NULL); } if (km->km_symtab == NULL) kt_load_module(t->t_data, t, km); if ((km->km_ctf_buf = mdb_alloc(km->km_ctf_size, UM_NOSLEEP)) == NULL) { warn("failed to allocate memory to load %s debugging " "information", km->km_name); return (NULL); } if (mdb_tgt_vread(t, km->km_ctf_buf, km->km_ctf_size, km->km_ctf_va) != km->km_ctf_size) { warn("failed to read %lu bytes of debug data for %s at %p", (ulong_t)km->km_ctf_size, km->km_name, (void *)km->km_ctf_va); mdb_free(km->km_ctf_buf, km->km_ctf_size); km->km_ctf_buf = NULL; return (NULL); } if ((km->km_ctfp = mdb_ctf_bufopen((const void *)km->km_ctf_buf, km->km_ctf_size, km->km_symbuf, &km->km_symtab_hdr, km->km_strtab, &km->km_strtab_hdr, &err)) == NULL) { mdb_free(km->km_ctf_buf, km->km_ctf_size); km->km_ctf_buf = NULL; (void) set_errno(ctf_to_errno(err)); return (NULL); } mdb_dprintf(MDB_DBG_KMOD, "loaded %lu bytes of CTF data for %s\n", (ulong_t)km->km_ctf_size, km->km_name); if (ctf_parent_name(km->km_ctfp) != NULL) { mdb_var_t *v; if ((v = mdb_nv_lookup(&kt->k_modules, ctf_parent_name(km->km_ctfp))) == NULL) { warn("failed to load CTF data for %s - parent %s not " "loaded\n", km->km_name, ctf_parent_name(km->km_ctfp)); } if (v != NULL) { kt_module_t *pm = mdb_nv_get_cookie(v); if (pm->km_ctfp == NULL) (void) kt_load_ctfdata(t, pm); if (pm->km_ctfp != NULL && ctf_import(km->km_ctfp, pm->km_ctfp) == CTF_ERR) { warn("failed to import parent types into " "%s: %s\n", km->km_name, ctf_errmsg(ctf_errno(km->km_ctfp))); } } } return (km->km_ctfp); } ctf_file_t * kt_addr_to_ctf(mdb_tgt_t *t, uintptr_t addr) { kt_data_t *kt = t->t_data; kt_module_t *km; for (km = mdb_list_next(&kt->k_modlist); km; km = mdb_list_next(km)) { if (addr - km->km_text_va < km->km_text_size || addr - km->km_data_va < km->km_data_size || addr - km->km_bss_va < km->km_bss_size) return (kt_load_ctfdata(t, km)); } (void) set_errno(EMDB_NOMAP); return (NULL); } ctf_file_t * kt_name_to_ctf(mdb_tgt_t *t, const char *name) { kt_data_t *kt = t->t_data; kt_module_t *km; if (name == MDB_TGT_OBJ_EXEC) name = KT_CTFPARENT; else if (name == MDB_TGT_OBJ_RTLD) name = kt->k_rtld_name; if ((km = kt_module_by_name(kt, name)) != NULL) return (kt_load_ctfdata(t, km)); (void) set_errno(EMDB_NOOBJ); return (NULL); } /*ARGSUSED*/ int kt_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { kt_data_t *kt = t->t_data; bzero(tsp, sizeof (mdb_tgt_status_t)); tsp->st_state = (kt->k_xpv_domu || (kt->k_dumphdr != NULL)) ? MDB_TGT_DEAD : MDB_TGT_RUNNING; return (0); } static ssize_t kt_xd_dumphdr(mdb_tgt_t *t, void *buf, size_t nbytes) { kt_data_t *kt = t->t_data; if (buf == NULL && nbytes == 0) return (sizeof (dumphdr_t)); if (kt->k_dumphdr == NULL) return (set_errno(ENODATA)); nbytes = MIN(nbytes, sizeof (dumphdr_t)); bcopy(kt->k_dumphdr, buf, nbytes); return (nbytes); } void kt_destroy(mdb_tgt_t *t) { kt_data_t *kt = t->t_data; kt_module_t *km, *nkm; (void) mdb_module_unload(KT_MODULE, 0); if (kt->k_regs != NULL) mdb_free(kt->k_regs, kt->k_regsize); if (kt->k_symtab != NULL) mdb_gelf_symtab_destroy(kt->k_symtab); if (kt->k_dynsym != NULL) mdb_gelf_symtab_destroy(kt->k_dynsym); if (kt->k_dumphdr != NULL) mdb_free(kt->k_dumphdr, sizeof (dumphdr_t)); mdb_gelf_destroy(kt->k_file); (void) kt->k_kb_ops->kb_close(kt->k_cookie); for (km = mdb_list_next(&kt->k_modlist); km; km = nkm) { if (km->km_symtab) mdb_gelf_symtab_destroy(km->km_symtab); if (km->km_data) mdb_free(km->km_data, km->km_datasz); if (km->km_ctfp) ctf_close(km->km_ctfp); if (km->km_ctf_buf != NULL) mdb_free(km->km_ctf_buf, km->km_ctf_size); nkm = mdb_list_next(km); strfree(km->km_name); mdb_free(km, sizeof (kt_module_t)); } mdb_nv_destroy(&kt->k_modules); strfree(kt->k_kvmfile); if (kt->k_symfile != NULL) strfree(kt->k_symfile); mdb_free(kt, sizeof (kt_data_t)); } static int kt_data_stub(void) { return (-1); } int mdb_kvm_tgt_create(mdb_tgt_t *t, int argc, const char *argv[]) { kt_data_t *kt = mdb_zalloc(sizeof (kt_data_t), UM_SLEEP); mdb_kb_ops_t *kvm_kb_ops = libkvm_kb_ops(); int oflag = (t->t_flags & MDB_TGT_F_RDWR) ? O_RDWR : O_RDONLY; struct utsname uts; GElf_Sym sym; pgcnt_t pmem; if (argc == 2) { kt->k_symfile = strdup(argv[0]); kt->k_kvmfile = strdup(argv[1]); kt->k_cookie = kvm_kb_ops->kb_open(kt->k_symfile, kt->k_kvmfile, NULL, oflag, (char *)mdb.m_pname); if (kt->k_cookie == NULL) goto err; kt->k_xpv_domu = 0; kt->k_kb_ops = kvm_kb_ops; } else { #ifndef __x86 return (set_errno(EINVAL)); #else mdb_kb_ops_t *(*getops)(void); kt->k_symfile = NULL; kt->k_kvmfile = strdup(argv[0]); getops = (mdb_kb_ops_t *(*)())dlsym(RTLD_NEXT, "mdb_kb_ops"); /* * Load mdb_kb if it's not already loaded during * identification. */ if (getops == NULL) { (void) mdb_module_load("mdb_kb", MDB_MOD_GLOBAL | MDB_MOD_SILENT); getops = (mdb_kb_ops_t *(*)()) dlsym(RTLD_NEXT, "mdb_kb_ops"); } if (getops == NULL || (kt->k_kb_ops = getops()) == NULL) { warn("failed to load KVM backend ops\n"); goto err; } kt->k_cookie = kt->k_kb_ops->kb_open(NULL, kt->k_kvmfile, NULL, oflag, (char *)mdb.m_pname); if (kt->k_cookie == NULL) goto err; kt->k_xpv_domu = 1; #endif } if ((kt->k_fio = kt->k_kb_ops->kb_sym_io(kt->k_cookie, kt->k_symfile)) == NULL) goto err; if ((kt->k_file = mdb_gelf_create(kt->k_fio, ET_EXEC, GF_FILE)) == NULL) { mdb_io_destroy(kt->k_fio); goto err; } kt->k_symtab = mdb_gelf_symtab_create_file(kt->k_file, SHT_SYMTAB, MDB_TGT_SYMTAB); kt->k_dynsym = mdb_gelf_symtab_create_file(kt->k_file, SHT_DYNSYM, MDB_TGT_DYNSYM); if (mdb_gelf_symtab_lookup_by_name(kt->k_symtab, "kas", &sym, NULL) == -1) { warn("'kas' symbol is missing from kernel\n"); goto err; } kt->k_as = (struct as *)(uintptr_t)sym.st_value; if (mdb_gelf_symtab_lookup_by_name(kt->k_symtab, "platform", &sym, NULL) == -1) { warn("'platform' symbol is missing from kernel\n"); goto err; } if (kt->k_kb_ops->kb_kread(kt->k_cookie, sym.st_value, kt->k_platform, MAXNAMELEN) <= 0) { warn("failed to read 'platform' string from kernel"); goto err; } if (mdb_gelf_symtab_lookup_by_name(kt->k_symtab, "utsname", &sym, NULL) == -1) { warn("'utsname' symbol is missing from kernel\n"); goto err; } if (kt->k_kb_ops->kb_kread(kt->k_cookie, sym.st_value, &uts, sizeof (uts)) <= 0) { warn("failed to read 'utsname' struct from kernel"); goto err; } kt->k_dump_print_content = (void (*)())(uintptr_t)kt_data_stub; kt->k_dump_find_curproc = kt_data_stub; /* * We set k_ctfvalid based on the presence of the CTF vmem arena * symbol. The CTF members were added to the end of struct module at * the same time, so this allows us to know whether we can use them. */ if (mdb_gelf_symtab_lookup_by_name(kt->k_symtab, "ctf_arena", &sym, NULL) == 0 && !(mdb.m_flags & MDB_FL_NOCTF)) kt->k_ctfvalid = 1; (void) mdb_nv_create(&kt->k_modules, UM_SLEEP); t->t_pshandle = kt->k_cookie; t->t_data = kt; #if defined(__sparc) #if defined(__sparcv9) kt_sparcv9_init(t); #else kt_sparcv7_init(t); #endif #elif defined(__amd64) kt_amd64_init(t); #elif defined(__i386) kt_ia32_init(t); #else #error "unknown ISA" #endif /* * We read our representative thread ID (address) from the kernel's * global panic_thread. It will remain 0 if this is a live kernel. */ (void) mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &kt->k_tid, sizeof (void *), MDB_TGT_OBJ_EXEC, "panic_thread"); if ((mdb.m_flags & MDB_FL_ADB) && mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &pmem, sizeof (pmem), MDB_TGT_OBJ_EXEC, "physmem") == sizeof (pmem)) mdb_printf("physmem %lx\n", (ulong_t)pmem); /* * If this is not a live kernel or a hypervisor dump, read the dump * header. We don't have to sanity-check the header, as the open would * not have succeeded otherwise. */ if (!kt->k_xpv_domu && strcmp(kt->k_symfile, "/dev/ksyms") != 0) { mdb_io_t *vmcore; kt->k_dumphdr = mdb_alloc(sizeof (dumphdr_t), UM_SLEEP); if ((vmcore = mdb_fdio_create_path(NULL, kt->k_kvmfile, O_RDONLY, 0)) == NULL) { mdb_warn("failed to open %s", kt->k_kvmfile); goto err; } if (IOP_READ(vmcore, kt->k_dumphdr, sizeof (dumphdr_t)) != sizeof (dumphdr_t)) { mdb_warn("failed to read dump header"); mdb_io_destroy(vmcore); goto err; } mdb_io_destroy(vmcore); (void) mdb_tgt_xdata_insert(t, "dumphdr", "dump header structure", kt_xd_dumphdr); } return (0); err: if (kt->k_dumphdr != NULL) mdb_free(kt->k_dumphdr, sizeof (dumphdr_t)); if (kt->k_symtab != NULL) mdb_gelf_symtab_destroy(kt->k_symtab); if (kt->k_dynsym != NULL) mdb_gelf_symtab_destroy(kt->k_dynsym); if (kt->k_file != NULL) mdb_gelf_destroy(kt->k_file); if (kt->k_cookie != NULL) (void) kt->k_kb_ops->kb_close(kt->k_cookie); mdb_free(kt, sizeof (kt_data_t)); return (-1); } int mdb_kvm_is_dump(mdb_io_t *io) { dumphdr_t h; (void) IOP_SEEK(io, (off64_t)0L, SEEK_SET); return (IOP_READ(io, &h, sizeof (dumphdr_t)) == sizeof (dumphdr_t) && h.dump_magic == DUMP_MAGIC); } int mdb_kvm_is_compressed_dump(mdb_io_t *io) { dumphdr_t h; (void) IOP_SEEK(io, (off64_t)0L, SEEK_SET); return (IOP_READ(io, &h, sizeof (dumphdr_t)) == sizeof (dumphdr_t) && h.dump_magic == DUMP_MAGIC && (h.dump_flags & DF_COMPRESSED) != 0); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_KVM_H #define _MDB_KVM_H #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB typedef struct kt_module { mdb_list_t km_list; /* List forward/back pointers */ char *km_name; /* Module name */ void *km_data; /* Data buffer (module->symspace) */ size_t km_datasz; /* Size of km_data in bytes */ void *km_symbuf; /* Base of symbol table in km_data */ char *km_strtab; /* Base of string table in km_data */ mdb_gelf_symtab_t *km_symtab; /* Symbol table for module */ uintptr_t km_symspace_va; /* Kernel VA of krtld symspace */ uintptr_t km_symtab_va; /* Kernel VA of krtld symtab */ uintptr_t km_strtab_va; /* Kernel VA of krtld strtab */ Shdr km_symtab_hdr; /* Native .symtab section header */ Shdr km_strtab_hdr; /* Native .strtab section header */ uintptr_t km_text_va; /* Kernel VA of start of module text */ size_t km_text_size; /* Size of module text */ uintptr_t km_data_va; /* Kernel VA of start of module data */ size_t km_data_size; /* Size of module data */ uintptr_t km_bss_va; /* Kernel VA of start of module BSS */ size_t km_bss_size; /* Size of module BSS */ uintptr_t km_ctf_va; /* Kernel VA of CTF data */ size_t km_ctf_size; /* Size of CTF data */ void *km_ctf_buf; /* CTF data for this module */ ctf_file_t *km_ctfp; /* CTF container for this module */ } kt_module_t; typedef struct kt_data { mdb_kb_ops_t *k_kb_ops; /* KVM backend ops */ void (*k_dump_print_content)(); /* mdb_ks dump_print_content routine */ int (*k_dump_find_curproc)(); /* mdb_ks dump_find_curproc routine */ char *k_symfile; /* Symbol table pathname */ char *k_kvmfile; /* Core file pathname */ int k_xpv_domu; /* Hypervisor domain dump? */ const char *k_rtld_name; /* module containing krtld */ mdb_map_t k_map; /* Persistant map for callers */ void *k_cookie; /* Cookie for libkvm routines */ struct as *k_as; /* Kernel VA of kas struct */ mdb_io_t *k_fio; /* File i/o backend */ mdb_gelf_file_t *k_file; /* ELF file object */ mdb_gelf_symtab_t *k_symtab; /* Standard symbol table */ mdb_gelf_symtab_t *k_dynsym; /* Dynamic symbol table */ mdb_nv_t k_modules; /* Hash table of modules */ mdb_list_t k_modlist; /* List of modules in load order */ char k_platform[MAXNAMELEN]; /* Platform string */ const mdb_tgt_regdesc_t *k_rds; /* Register description table */ mdb_tgt_gregset_t *k_regs; /* Representative register set */ size_t k_regsize; /* Size of k_regs in bytes */ mdb_tgt_tid_t k_tid; /* Pointer to representative thread */ mdb_dcmd_f *k_dcmd_regs; /* Dcmd to print registers */ mdb_dcmd_f *k_dcmd_stack; /* Dcmd to print stack trace */ mdb_dcmd_f *k_dcmd_stackv; /* Dcmd to print verbose stack trace */ mdb_dcmd_f *k_dcmd_stackr; /* Dcmd to print stack trace and regs */ mdb_dcmd_f *k_dcmd_cpustack; /* Dcmd to print CPU stack trace */ mdb_dcmd_f *k_dcmd_cpuregs; /* Dcmd to print CPU registers */ GElf_Sym k_intr_sym; /* Kernel locore cmnint symbol */ GElf_Sym k_trap_sym; /* Kernel locore cmntrap symbol */ struct dumphdr *k_dumphdr; /* Dump header for post-mortem */ pid_t k_dumpcontent; /* The pid(s) (if any) in the dump */ int k_activated; /* Set if kt_activate called */ int k_ctfvalid; /* Set if kernel has a CTF arena */ } kt_data_t; /* values for k_dumpcontent */ #define KT_DUMPCONTENT_KERNEL 0 #define KT_DUMPCONTENT_INVALID -1 #define KT_DUMPCONTENT_ALL -2 extern int kt_setflags(mdb_tgt_t *, int); extern int kt_setcontext(mdb_tgt_t *, void *); extern void kt_activate(mdb_tgt_t *); extern void kt_deactivate(mdb_tgt_t *); extern void kt_destroy(mdb_tgt_t *); extern const char *kt_name(mdb_tgt_t *); extern const char *kt_platform(mdb_tgt_t *); extern int kt_uname(mdb_tgt_t *, struct utsname *); extern int kt_dmodel(mdb_tgt_t *); extern ssize_t kt_aread(mdb_tgt_t *, mdb_tgt_as_t, void *, size_t, mdb_tgt_addr_t); extern ssize_t kt_awrite(mdb_tgt_t *, mdb_tgt_as_t, const void *, size_t, mdb_tgt_addr_t); extern ssize_t kt_vread(mdb_tgt_t *, void *, size_t, uintptr_t); extern ssize_t kt_vwrite(mdb_tgt_t *, const void *, size_t, uintptr_t); extern ssize_t kt_pread(mdb_tgt_t *, void *, size_t, physaddr_t); extern ssize_t kt_pwrite(mdb_tgt_t *, const void *, size_t, physaddr_t); extern ssize_t kt_fread(mdb_tgt_t *, void *, size_t, uintptr_t); extern ssize_t kt_fwrite(mdb_tgt_t *, const void *, size_t, uintptr_t); extern int kt_vtop(mdb_tgt_t *, mdb_tgt_as_t, uintptr_t, physaddr_t *); extern int kt_lookup_by_name(mdb_tgt_t *, const char *, const char *, GElf_Sym *, mdb_syminfo_t *); extern int kt_lookup_by_addr(mdb_tgt_t *, uintptr_t, uint_t, char *, size_t, GElf_Sym *, mdb_syminfo_t *); extern int kt_symbol_iter(mdb_tgt_t *, const char *, uint_t, uint_t, mdb_tgt_sym_f *, void *); extern int kt_mapping_iter(mdb_tgt_t *, mdb_tgt_map_f *, void *); extern int kt_object_iter(mdb_tgt_t *, mdb_tgt_map_f *, void *); extern const mdb_map_t *kt_addr_to_map(mdb_tgt_t *, uintptr_t); extern const mdb_map_t *kt_name_to_map(mdb_tgt_t *, const char *); extern struct ctf_file *kt_addr_to_ctf(mdb_tgt_t *, uintptr_t); extern struct ctf_file *kt_name_to_ctf(mdb_tgt_t *, const char *); extern int kt_status(mdb_tgt_t *, mdb_tgt_status_t *); #ifdef __sparc extern void kt_sparcv9_init(mdb_tgt_t *); extern void kt_sparcv7_init(mdb_tgt_t *); #else /* __sparc */ extern void kt_ia32_init(mdb_tgt_t *); extern void kt_amd64_init(mdb_tgt_t *); #endif /* __sparc */ typedef int (*mdb_name_lookup_fcn_t)(const char *, GElf_Sym *); typedef int (*mdb_addr_lookup_fcn_t)(uintptr_t, int, char *, size_t, GElf_Sym *); extern void mdb_kvm_add_name_lookup(mdb_name_lookup_fcn_t); extern void mdb_kvm_add_addr_lookup(mdb_addr_lookup_fcn_t); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_KVM_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_LEX_H #define _MDB_LEX_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern void mdb_lex_debug(int); extern void mdb_lex_reset(void); extern void yyerror(const char *, ...); extern void yyperror(const char *, ...); extern void yydiscard(void); extern int yyparse(void); extern int yywrap(void); struct mdb_lex_state; struct mdb_frame; void mdb_lex_state_save(struct mdb_lex_state *); void mdb_lex_state_restore(struct mdb_lex_state *); void mdb_lex_state_create(struct mdb_frame *); void mdb_lex_state_destroy(struct mdb_frame *); /* * The lex and yacc debugging code as generated uses printf and fprintf * for debugging output. We redefine these to refer to our yyprintf * and yyfprintf routines, which are wrappers around mdb_iob_vprintf. */ #define printf (void) yyprintf #define fprintf (void) yyfprintf extern int yyprintf(const char *, ...); extern int yyfprintf(FILE *, const char *, ...); extern int yylineno; /* * Maximum depth we can have in our yacc state stack. The yacc default is 150, * but this should be more than enough for our simple mdb expressions. */ #define YYMAXDEPTH 100 /* * Maximum size of our lex yytext buffer. */ #define YYLMAX BUFSIZ #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_LEX_H */ %pointer /* Make yytext a pointer, not an array */ %{ /* * 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 2011 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2017 by Delphix. All rights reserved. * Copyright 2019, Joyent, Inc. * Copyright 2021 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdb_grammar.h" /* * lex hardcodes yyin and yyout to stdin and stdout, respectively, before we get * control. We've redirected printf and fprintf (see mdb_lex.h) to yyprintf and * yyfprintf, which ignore the FILE * provided by yyout. __iob-based stdin and * stdout are useless in kmdb, since we don't have stdio. We define __iob here * to shut the linker up. */ #ifdef _KMDB FILE __iob[_NFILE]; #endif /* * We need to undefine lex's input, unput, and output macros so that references * to these call the functions we provide at the end of this source file, * instead of the default versions based on libc's stdio. */ #ifdef input #undef input #endif #ifdef unput #undef unput #endif #ifdef output #undef output #endif static int input(void); static void unput(int); static void output(int); static void string_unquote(char *); extern int yydebug; /* * This will prevent lex from trying to malloc() and resize our yytext variable, * instead it will just print out an error message and exit(), which seems * a lesser of two evils. */ #define YYISARRAY %} %o 9000 %a 5000 %s S_SHELLCMD %s S_INITIAL %s S_FMTLIST %s S_ARGLIST %s S_EXPR RGX_CMD_CHAR [?%@A-Z\^_`a-z] RGX_SYMBOL [a-zA-Z_.][0-9a-zA-Z_.`]* RGX_SIMPLE_CHAR [^ \t\n;!|"'\$] RGX_CHR_SEQ ([^'\n]|\\[^'\n]|\\')* RGX_STR_SEQ ([^"\\\n]|\\[^"\n]|\\\")* RGX_COMMENT "//".*\n %% {RGX_COMMENT} | {RGX_COMMENT} | {RGX_COMMENT} { /* * Comments are legal in these three states -- if we see one * eat the line and return the newline character. */ BEGIN(S_INITIAL); return ('\n'); } "==" | "==" return (MDB_TOK_EQUAL); /* Equality operator */ "!=" | "!=" return (MDB_TOK_NOTEQUAL); /* Inequality operator */ "%%" | "%%" return (MDB_TOK_MODULUS); /* Modulus operator */ "!" | "!" | "!" { /* * Shell escapes are legal in all of these states -- switch to * the shell command state and return the ! character. */ BEGIN(S_SHELLCMD); return (yytext[0]); } "|" | "|" { /* * Pipelines can appear in any of these states -- switch to * the initial state and return the | character. */ BEGIN(S_INITIAL); return (yytext[0]); } [^;\n]+ { /* * Once in the shell-command state, we return all remaining * characters up to a newline or ';' delimiter as a single * string which will be passed to $SHELL -c. */ yylval.l_string = strdup(yytext); BEGIN(S_INITIAL); return (MDB_TOK_STRING); } "::"{RGX_SYMBOL} { /* * Verb ::command-name -- lookup the correspond dcmd and * switch to the argument list state. */ if ((yylval.l_dcmd = mdb_dcmd_lookup(yytext + 2)) == NULL) yyperror("invalid command '%s'", yytext); BEGIN(S_ARGLIST); return (MDB_TOK_DCMD); } "$<<"|"$<"|"$>" | [\$:]{RGX_CMD_CHAR} { /* * Old-style :c or $c command -- lookup the corresponding dcmd * and switch to the argument list state. */ if ((yylval.l_dcmd = mdb_dcmd_lookup(yytext)) == NULL) yyperror("invalid command '%s'", yytext); BEGIN(S_ARGLIST); return (MDB_TOK_DCMD); } ">/"[a-zA-Z0-9]"/" { /* * Variable assignment with size cast -- append the cast letter * to the argument list, and switch to the argument list state. */ mdb_arg_t arg; arg.a_un.a_char = yytext[2]; arg.a_type = MDB_TYPE_CHAR; mdb_argvec_append(&mdb.m_frame->f_argvec, &arg); yylval.l_dcmd = mdb_dcmd_lookup(">"); BEGIN(S_ARGLIST); return (MDB_TOK_DCMD); } ">" { /* * Variable assignment -- switch to the argument list state. */ yylval.l_dcmd = mdb_dcmd_lookup(yytext); BEGIN(S_ARGLIST); return (MDB_TOK_DCMD); } [/\\?][ \t]*[vwzWZlLM] { /* * Format verb followed by write or match signifier -- switch * to the value list state and return the verb character. We * also append the actual format character to the arg list. */ mdb_arg_t arg; arg.a_un.a_char = yytext[yyleng - 1]; arg.a_type = MDB_TYPE_CHAR; mdb_argvec_append(&mdb.m_frame->f_argvec, &arg); BEGIN(S_ARGLIST); return yytext[0]; } [/\\@?=] { /* * Format verb -- switch to the format list state and return * the actual verb character verbatim. */ BEGIN(S_FMTLIST); return (yytext[0]); } '{RGX_CHR_SEQ}$ | '{RGX_CHR_SEQ}$ yyerror("syntax error: ' unmatched"); '{RGX_CHR_SEQ}' | '{RGX_CHR_SEQ}' { char *s, *p, *q; size_t nbytes; /* * If the character sequence is zero-length, return 0. */ if (yyleng == 2) { yylval.l_immediate = 0; return (MDB_TOK_IMMEDIATE); } s = yytext + 1; /* Skip past initial quote */ yytext[yyleng - 1] = '\0'; /* Overwrite final quote */ nbytes = stresc2chr(s); /* Convert escapes */ yylval.l_immediate = 0; /* Initialize token value */ if (nbytes > sizeof (uintmax_t)) { yyerror("character constant may not exceed %lu bytes\n", (ulong_t)sizeof (uintmax_t)); } #ifdef _LITTLE_ENDIAN p = ((char*)&yylval.l_immediate) + nbytes - 1; for (q = s; nbytes != 0; nbytes--) *p-- = *q++; #else bcopy(s, ((char *)&yylval.l_immediate) + sizeof (uintmax_t) - nbytes, nbytes); #endif return (MDB_TOK_IMMEDIATE); } \"{RGX_STR_SEQ}$ yyerror("syntax error: \" unmatched"); \"{RGX_STR_SEQ}\" { /* * Quoted string -- convert C escape sequences and return the * string as a token. */ yylval.l_string = strndup(yytext + 1, yyleng - 2); (void) stresc2chr(yylval.l_string); return (MDB_TOK_STRING); } "$[" | "$[" { /* * Start of expression -- begin expression state and save the * current state so we can return at the end of the expression. */ mdb.m_frame->f_oldstate = YYSTATE; BEGIN(S_EXPR); return (MDB_TOK_LEXPR); } {RGX_SIMPLE_CHAR}*("'"{RGX_CHR_SEQ}"'"|\"{RGX_STR_SEQ}\"|{RGX_SIMPLE_CHAR}+)* { /* * String token -- create a copy of the string and return it. * We need to handle embedded single and double-quote pairs, * which overcomplicates this slightly. */ yylval.l_string = strdup(yytext); string_unquote(yylval.l_string); return (MDB_TOK_STRING); } [0-9]+ { /* * Immediate value -- in the format list, all immediates * are assumed to be in decimal. */ yylval.l_immediate = mdb_strtonum(yytext, 10); return (MDB_TOK_IMMEDIATE); } {RGX_SIMPLE_CHAR} { /* * Non-meta character -- in the format list, we return each * character as a separate token to be added as an argument. */ yylval.l_char = yytext[0]; return (MDB_TOK_CHAR); } ";"|"!"|\n { /* * In the expression state only, we cannot see a command * delimiter or shell escape before we end the expression. */ yyerror("syntax error: $[ unmatched"); } "]" { /* * End of expression state. Restore the state we were in * before the "$[" which started this expression. */ BEGIN(mdb.m_frame->f_oldstate); return (MDB_TOK_REXPR); } "<"{RGX_SYMBOL} | "<"[0-9] | "<"{RGX_SYMBOL} | "<"[0-9] { /* * Variable reference -- lookup the variable and return a * pointer to it. Referencing undefined variables is an error. */ yylval.l_var = mdb_nv_lookup(&mdb.m_nv, &yytext[1]); if (yylval.l_var == NULL) yyerror("variable '%s' is not defined", &yytext[1]); return (MDB_TOK_VAR_REF); } "<<" | "<<" return (MDB_TOK_LSHIFT); /* Logical shift left operator */ ">>" | ">>" return (MDB_TOK_RSHIFT); /* Logical shift right operator */ "*/"[a-zA-Z0-9]"/" | "*/"[a-zA-Z0-9]"/" { switch (yytext[2]) { case 'c': case '1': return (MDB_TOK_COR1_DEREF); case 's': case '2': return (MDB_TOK_COR2_DEREF); case 'i': case '4': #ifdef _ILP32 case 'l': #endif return (MDB_TOK_COR4_DEREF); #ifdef _LP64 case 'l': #endif case '8': return (MDB_TOK_COR8_DEREF); } yyerror("invalid cast -- %s\n", yytext); } "%/"[a-zA-Z0-9]"/" | "%/"[a-zA-Z0-9]"/" { switch (yytext[2]) { case 'c': case '1': return (MDB_TOK_OBJ1_DEREF); case 's': case '2': return (MDB_TOK_OBJ2_DEREF); case 'i': case '4': #ifdef _ILP32 case 'l': #endif return (MDB_TOK_OBJ4_DEREF); #ifdef _LP64 case 'l': #endif case '8': return (MDB_TOK_OBJ8_DEREF); } yyerror("invalid cast -- %s\n", yytext); } 0[iI][0-1][0-1_]* | 0[iI][0-1][0-1_]* { /* * Binary immediate value. */ yylval.l_immediate = mdb_strtonum(yytext + 2, 2); return (MDB_TOK_IMMEDIATE); } 0[oO][0-7][0-7_]* | 0[oO][0-7][0-7_]* { /* * Octal immediate value. */ yylval.l_immediate = mdb_strtonum(yytext + 2, 8); return (MDB_TOK_IMMEDIATE); } 0[tT][0-9][0-9_]*"."[0-9][0-9_]* | 0[tT][0-9][0-9_]*"."[0-9][0-9_]* { #ifdef _KMDB yyerror("floating point not supported\n"); #else /* * Decimal floating point value. */ char *p, c; double d; int i; if ((p = strsplit(yytext, '.')) == NULL) yyerror("internal scanning error -- expected '.'\n"); d = (double)mdb_strtonum(yytext + 2, 10); i = 0; while (*p != '\0') { c = *p++; if (c == '_') continue; d = d * 10 + c - '0'; i++; } while (i-- != 0) d /= 10; yylval.l_immediate = *((uintmax_t *)&d); return (MDB_TOK_IMMEDIATE); #endif } 0[tT][0-9][0-9_]* | 0[tT][0-9][0-9_]* { /* * Decimal immediate value. */ yylval.l_immediate = mdb_strtonum(yytext + 2, 10); return (MDB_TOK_IMMEDIATE); } 0[xX][0-9a-fA-F][0-9a-fA-F_]* | 0[xX][0-9a-fA-F][0-9a-fA-F_]* { /* * Hexadecimal value. */ yylval.l_immediate = mdb_strtonum(yytext + 2, 16); return (MDB_TOK_IMMEDIATE); } [0-9a-fA-F][0-9a-fA-F_]* | [0-9a-fA-F][0-9a-fA-F_]* { GElf_Sym sym; /* * Immediate values without an explicit base are converted * using the default radix (user configurable). However, if * the token does *not* begin with a digit, it is also a * potential symbol (e.g. "f") so we have to check that first. */ if (strchr("0123456789", yytext[0]) == NULL && mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EVERY, yytext, &sym, NULL) == 0) yylval.l_immediate = (uintmax_t)sym.st_value; else yylval.l_immediate = mdb_strtonum(yytext, mdb.m_radix); return (MDB_TOK_IMMEDIATE); } {RGX_SYMBOL} | {RGX_SYMBOL} { /* * Symbol -- parser will look up in symbol table. */ yylval.l_string = strdup(yytext); return (MDB_TOK_SYMBOL); } ";"|\n { /* * End of command -- return to start state and return literal. */ BEGIN(S_INITIAL); return (yytext[0]); } [ \t] ; /* Ignore whitespace */ . return (yytext[0]); /* Return anything else */ %% void mdb_lex_debug(int i) { yydebug = i; } void mdb_lex_reset(void) { BEGIN(S_INITIAL); } void yydiscard(void) { int c; /* * If stdin is a string, pipeline, or tty, throw away all our buffered * data. Otherwise discard characters up to the next likely delimiter. */ if (mdb_iob_isastr(mdb.m_in) || mdb_iob_isatty(mdb.m_in) || mdb_iob_isapipe(mdb.m_in)) mdb_iob_discard(mdb.m_in); else { while ((c = mdb_iob_getc(mdb.m_in)) != (int)EOF) { if (c == ';' || c == '\n') break; } } BEGIN(S_INITIAL); } static void yyerror_reset(void) { yydiscard(); mdb_argvec_reset(&mdb.m_frame->f_argvec); longjmp(mdb.m_frame->f_pcb, MDB_ERR_PARSE); } void yyerror(const char *format, ...) { va_list alist; char *s; mdb_iob_printf(mdb.m_err, "%s: ", mdb.m_pname); va_start(alist, format); mdb_iob_vprintf(mdb.m_err, format, alist); va_end(alist); if (strchr(format, '\n') == NULL) { if (!mdb_iob_isatty(mdb.m_in)) { mdb_iob_printf(mdb.m_err, " on line %d of %s", yylineno, mdb_iob_name(mdb.m_in)); } s = strchr2esc(yytext, strlen(yytext)); mdb_iob_printf(mdb.m_err, " near \"%s\"\n", s); strfree(s); } yyerror_reset(); } void yyperror(const char *format, ...) { va_list alist; va_start(alist, format); vwarn(format, alist); va_end(alist); yyerror_reset(); } int yywrap(void) { mdb_dprintf(MDB_DBG_PARSER, "yywrap at line %d\n", yylineno); return (1); /* indicate that lex should return a zero token for EOF */ } /*PRINTFLIKE2*/ /*ARGSUSED*/ int yyfprintf(FILE *stream, const char *format, ...) { va_list alist; va_start(alist, format); mdb_iob_vprintf(mdb.m_err, format, alist); va_end(alist); return (0); } /*PRINTFLIKE1*/ int yyprintf(const char *format, ...) { va_list alist; va_start(alist, format); mdb_iob_vprintf(mdb.m_err, format, alist); va_end(alist); return (0); } static int input(void) { int c = mdb_iob_getc(mdb.m_in); if (c == '\n') yylineno++; return (c == EOF ? 0 : c); } static void unput(int c) { if (c == '\n') yylineno--; (void) mdb_iob_ungetc(mdb.m_in, c == 0 ? EOF : c); } static void output(int c) { char ch = c; mdb_iob_nputs(mdb.m_out, &ch, sizeof (ch)); } static char * string_nextquote(char *s, char q1, char q2) { char c = 0; do { if (c != '\\' && (*s == q1 || *s == q2)) return (s); } while ((c = *s++) != '\0'); return (NULL); } static void string_unquote(char *s) { char *o, *p, *q, c; for (o = p = s; (p = string_nextquote(p, '\'', '"')) != NULL; o = p) { /* * If the quote wasn't the first character, advance * the destination buffer past what we skipped. */ if (p > o) { /* Using memmove to prevent possible overlap. */ (void) memmove(s, o, p - o); s += p - o; } c = *p; /* Save the current quote */ /* * Look ahead and find the matching quote. If none is * found, use yyerror to longjmp out of the lexer. */ if (c == '"') q = string_nextquote(p + 1, c, c); else q = strchr(p + 1, c); if (q == NULL) yyerror("syntax error: %c unmatched", c); /* * If the string is non-empty, copy it to the destination * and convert escape sequences if *p is double-quote. */ if (q > p + 1) { (void) memmove(s, p + 1, q - p - 1); if (c == '"') { s[q - p - 1] = '\0'; s += stresc2chr(s); } else s += q - p - 1; } p = q + 1; /* Advance p past matching quote */ } (void) memmove(s, o, strlen(o) + 1); } /* * Unfortunately, lex and yacc produces code that is inherently global. They do * not provide routines to save and restore state, instead relying on global * variables. There is one single lex state, so that if a frame switch then * tries to perform any evaluation, the old values are corrupted. This * structure and corresponding function provide a means of preserving lex state * across frame switches. Note that this is tied to the lex implementation, so * if the lex compiler is changed or upgraded to a different format, then this * may need to be altered. This is unavoidable due to the implementation of lex * and yacc. This is essentially a collection of all the global variables * defined by the lex code, excluding those that do not change through the * course of yylex() and yyparse(). */ extern struct yysvf *yylstate[], **yylsp, **yyolsp; extern int yyprevious; extern int *yyfnd; extern YYSTYPE *yypv; extern int *yyps; extern int yytmp; extern int yystate; extern int yynerrs; extern int yyerrflag; extern int yychar; extern YYSTYPE yylval; extern YYSTYPE yyval; extern int *yys; extern YYSTYPE *yyv; typedef struct mdb_lex_state { /* Variables needed by yylex */ int yyleng; char yytext[YYLMAX]; int yymorfg; int yylineno; void *yyestate; void *yylstate[BUFSIZ]; void *yylsp; void *yyolsp; int *yyfnd; int yyprevious; void *yybgin; /* Variables needed by yyparse */ void *yypv; int *yyps; int yytmp; int yystate; int yynerrs; int yyerrflag; int yychar; YYSTYPE yylval; YYSTYPE yyval; int yys[YYMAXDEPTH]; YYSTYPE yyv[YYMAXDEPTH]; } mdb_lex_state_t; void mdb_lex_state_save(mdb_lex_state_t *s) { ASSERT(s != NULL); s->yyleng = yyleng; s->yymorfg = yymorfg; s->yylineno = yylineno; s->yyestate = yyestate; bcopy(yylstate, s->yylstate, YYLMAX * sizeof (void *)); s->yylsp = yylsp; s->yyolsp = yyolsp; s->yyfnd = yyfnd; s->yyprevious = yyprevious; s->yybgin = yybgin; s->yypv = yypv; s->yyps = yyps; s->yystate = yystate; s->yytmp = yytmp; s->yynerrs = yynerrs; s->yyerrflag = yyerrflag; s->yychar = yychar; s->yylval = yylval; s->yyval = yyval; } void mdb_lex_state_restore(mdb_lex_state_t *s) { ASSERT(s != NULL); yyleng = s->yyleng; yytext = s->yytext; yymorfg = s->yymorfg; yylineno = s->yylineno; yyestate = s->yyestate; bcopy(s->yylstate, yylstate, YYLMAX * sizeof (void *)); yylsp = s->yylsp; yyolsp = s->yyolsp; yyfnd = s->yyfnd; yyprevious = s->yyprevious; yybgin = s->yybgin; yypv = s->yypv; yyps = s->yyps; yystate = s->yystate; yytmp = s->yytmp; yynerrs = s->yynerrs; yyerrflag = s->yyerrflag; yychar = s->yychar; yylval = s->yylval; yyval = s->yyval; yys = s->yys; yyv = s->yyv; } /* * Create and initialize the lex/yacc-specific state associated with a frame * structure. We set all fields to known safe values so that * mdb_lex_state_restore() can be used safely before mdb_lex_state_save(). */ void mdb_lex_state_create(mdb_frame_t *f) { f->f_lstate = mdb_alloc(sizeof (mdb_lex_state_t), UM_SLEEP); yyleng = 0; yymorfg = 0; /* yytext is fine with garbage in it */ yytext = f->f_lstate->yytext; yylineno = 1; yyestate = NULL; bzero(yylstate, YYLMAX * sizeof (void *)); yylsp = NULL; yyolsp = NULL; yyfnd = 0; yyprevious = YYNEWLINE; yys = f->f_lstate->yys; yyv = f->f_lstate->yyv; mdb_argvec_create(&f->f_argvec); f->f_oldstate = 0; mdb_lex_reset(); /* Responsible for setting yybgin */ } void mdb_lex_state_destroy(mdb_frame_t *f) { mdb_free(f->f_lstate, sizeof (mdb_lex_state_t)); f->f_lstate = NULL; mdb_argvec_destroy(&f->f_argvec); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include /* * A linker set is an array of pointers. The start of the set will have a * weak symbol of the form START_PREFIX + name that will have the address * of the first element (pointer) and another weak symbol that points just * past the end of the final element. E.g. for a linker set 'foo', the * first element will have a symbol __start_set_foo, and all __stop_set_foo * will have the address just after the last element (e.g. &(last_element + 1)) */ #define START_PREFIX "__start_set_" #define STOP_PREFIX "__stop_set_" /* * The pointers that comprise the linker set have names that follow * the pattern __set__sym_. */ #define SYM_PREFIX "__set_" #define SYM_DELIM "_sym_" typedef struct ldset_info { char ldsi_name[MDB_SYM_NAMLEN]; uintptr_t ldsi_addr; uintptr_t ldsi_endaddr; size_t ldsi_ptrsize; size_t ldsi_nelem; ssize_t ldsi_elsize; } ldset_info_t; /* * Similar to ldset_name_from_start(), except that it uses a linker set item * name (e.g. '__set_foo_set_sym_foo_item') and writes the set name ('foo_set') * into buf. */ static int ldset_name_from_item(const char *item_name, char *buf, size_t buflen) { const char *startp; const char *endp; size_t setname_len; /* The item name must start with '__sym_' */ if (strncmp(item_name, SYM_PREFIX, sizeof (SYM_PREFIX) - 1) != 0) { return (set_errno(EINVAL)); } startp = item_name + sizeof (SYM_PREFIX) - 1; /* The item name must have stuff after '__sym_' */ if (*startp == '\0') { return (set_errno(EINVAL)); } /* Find the start of '_sym_' after the prefix */ endp = strstr(startp, SYM_DELIM); if (endp == NULL) { /* '_sym_' not in the name, not a valid item name */ return (set_errno(EINVAL)); } setname_len = (size_t)(endp - startp); if (setname_len + 1 > buflen) { return (set_errno(ENAMETOOLONG)); } /* * We've verified buf has enough room for the linker set name + NUL. * For sanity, we guarantee any trailing bytes in buf are zero, and * use strncpy() so we copy only the bytes from item_name that are * a part of the linker set name. The result should always be NUL * terminated as a result. */ (void) memset(buf, '\0', buflen); (void) strncpy(buf, item_name + sizeof (SYM_PREFIX) - 1, setname_len); return (0); } static int ldset_get_sym(const char *prefix, const char *name, GElf_Sym *sym) { char symname[MDB_SYM_NAMLEN] = { 0 }; if (mdb_snprintf(symname, sizeof (symname), "%s%s", prefix, name) > sizeof (symname) - 1) { return (set_errno(ENAMETOOLONG)); } return (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EVERY, symname, sym, NULL)); } /* * Given the address of a pointer in a linker set, return the address of the * item in the set in *addrp. */ static int ldset_get_entry(uintptr_t addr, uintptr_t *addrp, size_t ptrsize) { union { uint64_t u64; uint32_t u32; } val; ssize_t n; switch (ptrsize) { case sizeof (uint32_t): n = mdb_vread(&val.u32, sizeof (uint32_t), addr); *addrp = (uintptr_t)val.u32; break; case sizeof (uint64_t): n = mdb_vread(&val.u64, sizeof (uint64_t), addr); *addrp = (uintptr_t)val.u64; break; default: return (set_errno(ENOTSUP)); } if (n != ptrsize) { /* XXX: Better error value? */ return (set_errno(ENODATA)); } return (0); } static ssize_t ldset_item_size(uintptr_t addr) { mdb_ctf_id_t id; int ret; ret = mdb_ctf_lookup_by_addr(addr, &id); if (ret != 0) { return ((ssize_t)ret); } return (mdb_ctf_type_size(id)); } static int ldset_get_info(uintptr_t addr, ldset_info_t *ldsi) { GElf_Sym start_sym = { 0 }; GElf_Sym stop_sym = { 0 }; char name[MDB_SYM_NAMLEN] = { 0 }; uintptr_t item_addr; int ret; switch (mdb_tgt_dmodel(mdb.m_target)) { case MDB_TGT_MODEL_LP64: ldsi->ldsi_ptrsize = sizeof (uint64_t); break; case MDB_TGT_MODEL_ILP32: ldsi->ldsi_ptrsize = sizeof (uint32_t); break; default: return (set_errno(ENOTSUP)); } ret = mdb_tgt_lookup_by_addr(mdb.m_target, addr, MDB_TGT_SYM_EXACT, name, sizeof (name), &start_sym, NULL); if (ret != 0) { return (ret); } if (ldset_name_from_item(name, ldsi->ldsi_name, sizeof (ldsi->ldsi_name)) != 0) { return (-1); } ret = ldset_get_sym(STOP_PREFIX, ldsi->ldsi_name, &stop_sym); if (ret != 0) { return (-1); } if (stop_sym.st_value < addr) { return (set_errno(EINVAL)); } if (ldset_get_entry(addr, &item_addr, ldsi->ldsi_ptrsize) != 0) { return (-1); } ldsi->ldsi_addr = addr; ldsi->ldsi_endaddr = stop_sym.st_value; ldsi->ldsi_nelem = (stop_sym.st_value - addr) / ldsi->ldsi_ptrsize; ldsi->ldsi_elsize = ldset_item_size(item_addr); return (0); } static int ldsets_init_cb(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { mdb_nv_t *nv = data; const char *ldset_name; GElf_Sym stop_sym = { 0 }; int ret; if (strncmp(name, START_PREFIX, sizeof (START_PREFIX) - 1) != 0) { return (0); } /* * The name of the linker set should follow START_PREFIX. If there's * nothing there, then it's not a linker set, so skip this symbol. */ ldset_name = name + sizeof (START_PREFIX) - 1; if (*ldset_name == '\0') { return (0); } ret = ldset_get_sym(STOP_PREFIX, ldset_name, &stop_sym); if (ret != 0) { /* If there's no stop symbol, we just ignore */ if (errno == ENOENT) { errno = 0; return (0); } return (-1); } /* * The stop symbol should be at the same or higher address than * the start symbol. If not, we ignore. */ if (stop_sym.st_value < sym->st_value) { return (0); } if (mdb_nv_insert(nv, ldset_name, NULL, sym->st_value, MDB_NV_RDONLY) == NULL) { return (-1); } return (0); } /* * Initialize an mdb_nv_t with the name/addr of all the linkersets found in * the target. */ static int ldsets_nv_init(mdb_nv_t *nv, uint_t flags) { if (mdb_nv_create(nv, flags) == NULL) return (-1); return (mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY, MDB_TGT_SYMTAB, MDB_TGT_BIND_ANY | MDB_TGT_TYPE_NOTYPE, ldsets_init_cb, nv)); } int ldsets_walk_init(mdb_walk_state_t *wsp) { mdb_nv_t *nv; int ret; nv = mdb_zalloc(sizeof (*nv), UM_SLEEP | UM_GC); ret = ldsets_nv_init(nv, UM_SLEEP | UM_GC); if (ret != 0) { return (ret); } mdb_nv_rewind(nv); wsp->walk_data = nv; return (WALK_NEXT); } int ldsets_walk_step(mdb_walk_state_t *wsp) { mdb_nv_t *nv = wsp->walk_data; mdb_var_t *v = mdb_nv_advance(nv); int status; if (v == NULL) { return (WALK_DONE); } wsp->walk_addr = mdb_nv_get_value(v); status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); return (status); } int ldset_walk_init(mdb_walk_state_t *wsp) { ldset_info_t *ldsi; int ret; ldsi = mdb_zalloc(sizeof (*ldsi), UM_SLEEP | UM_GC); ret = ldset_get_info(wsp->walk_addr, ldsi); if (ret != 0) return (WALK_ERR); wsp->walk_data = ldsi; return (WALK_NEXT); } int ldset_walk_step(mdb_walk_state_t *wsp) { ldset_info_t *ldsi = wsp->walk_data; uintptr_t addr; int ret; if (wsp->walk_addr >= ldsi->ldsi_endaddr) { return (WALK_DONE); } ret = ldset_get_entry(wsp->walk_addr, &addr, ldsi->ldsi_ptrsize); if (ret != 0) { return (WALK_ERR); } ret = wsp->walk_callback(addr, NULL, wsp->walk_cbdata); wsp->walk_addr += ldsi->ldsi_ptrsize; return (ret); } static int linkerset_walk_cb(uintptr_t addr, const void *data, void *cbarg) { mdb_printf("%lr\n", addr); return (0); } static int linkersets_walk_cb(uintptr_t addr, const void *data, void *cbarg) { ldset_info_t info = { 0 }; int ret; char buf[64]; /* big enough for element size in any radix */ ret = ldset_get_info(addr, &info); if (ret != 0) return (WALK_ERR); if (info.ldsi_elsize > 0) { (void) mdb_snprintf(buf, sizeof (buf), "%#r", info.ldsi_elsize); } else { (void) strlcpy(buf, "?", sizeof (buf)); } mdb_printf("%-20s %8s %9u\n", info.ldsi_name, buf, info.ldsi_nelem); return (WALK_NEXT); } int cmd_linkerset(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int ret; if (argc > 1) { return (DCMD_USAGE); } /* Walk a linkerset given by the first argument */ if (argc == 1) { const char *setname = argv->a_un.a_str; GElf_Sym start_sym = { 0 }; ldset_info_t info = { 0 }; if (argv->a_type != MDB_TYPE_STRING) { return (DCMD_USAGE); } ret = ldset_get_sym(START_PREFIX, setname, &start_sym); if (ret != 0) { mdb_warn("Failed to get address of linkerset"); return (-1); } ret = ldset_get_info((uintptr_t)start_sym.st_value, &info); if (ret != 0) { mdb_warn("Failed to get information on linkerset"); return (-1); } return (mdb_pwalk("linkerset", linkerset_walk_cb, NULL, info.ldsi_addr)); } /* Display all the known linkersets */ if (DCMD_HDRSPEC(flags)) { mdb_printf("%%%-20s %-8s %-9s%%\n", "NAME", "ITEMSIZE", "ITEMCOUNT"); } return (mdb_walk("linkersets", linkersets_walk_cb, NULL)); } static int ldset_complete(mdb_var_t *v, void *arg) { mdb_tab_cookie_t *mcp = arg; mdb_tab_insert(mcp, mdb_nv_get_name(v)); return (0); } static int ldset_tab_complete(mdb_tab_cookie_t *mcp, const char *ldset) { mdb_nv_t nv = { 0 }; int ret; ret = ldsets_nv_init(&nv, UM_GC | UM_SLEEP); if (ret != 0) { return (ret); } if (ldset != NULL) { mdb_tab_setmbase(mcp, ldset); } mdb_nv_sort_iter(&nv, ldset_complete, mcp, UM_GC | UM_SLEEP); return (1); } int cmd_linkerset_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc > 1) return (1); if (argc == 1) { ASSERT(argv[0].a_type == MDB_TYPE_STRING); return (ldset_tab_complete(mcp, argv[0].a_un.a_str)); } if (argc == 0 && (flags & DCMD_TAB_SPACE) != 0) { return (ldset_tab_complete(mcp, NULL)); } return (1); } void linkerset_help(void) { static const char ldset_desc[] = "A linker set is an array of pointers to objects in a target that have been\n" "collected by the linker. The start and end location of each linker set\n" "is designated by weak symbols with well known strings prefixed to the\n" "name of the linker set.\n" "\n" "When invoked without any arguments, the ::linkerset command will attempt to\n" "enumerate all linker sets present in the target. For each linker set, the \n" "name, number of objects in the set, as well as the size of each object (when\n" "known) is displayed. The ::linkerset command uses the CTF information to\n" "determine the size of each object. If the CTF data is unavailable for a\n" "given linkerset, '?' will displayed instead of the size.\n" "\n" "The ::linkerset command can also be invoked with a single argument -- the\n" "name of a specific linker set. In this invocation, the ::linkerset command\n" "will display the addresses of each object in the set and can be used as\n" "part of a command pipeline.\n"; static const char ldset_examples[] = " ::linkerset\n" " ::linkerset sysinit_set | ::print 'struct sysinit'\n"; mdb_printf("%s\n", ldset_desc); (void) mdb_dec_indent(2); mdb_printf("%EXAMPLES%\n"); (void) mdb_inc_indent(2); mdb_printf("%s\n", ldset_examples); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Joyent, Inc. */ #ifndef _MDB_LINKERSET_H #define _MDB_LINKERSET_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern int ldsets_walk_init(mdb_walk_state_t *); extern int ldsets_walk_step(mdb_walk_state_t *); extern int ldset_walk_init(mdb_walk_state_t *); extern int ldset_walk_step(mdb_walk_state_t *); extern int cmd_linkerset(uintptr_t, uint_t, int, const mdb_arg_t *); extern void linkerset_help(void); extern int cmd_linkerset_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_LINKERSET_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include /* * Simple doubly-linked list implementation. This implementation assumes that * each list element contains an embedded mdb_list_t (previous and next * pointers), which is typically the first member of the element struct. * An additional mdb_list_t is used to store the head (ml_next) and tail * (ml_prev) pointers. The current head and tail list elements have their * previous and next pointers set to NULL, respectively. */ void mdb_list_append(mdb_list_t *mlp, void *new) { mdb_list_t *p = mlp->ml_prev; /* p = tail list element */ mdb_list_t *q = new; /* q = new list element */ mlp->ml_prev = q; q->ml_prev = p; q->ml_next = NULL; if (p != NULL) { ASSERT(p->ml_next == NULL); p->ml_next = q; } else { ASSERT(mlp->ml_next == NULL); mlp->ml_next = q; } } void mdb_list_prepend(mdb_list_t *mlp, void *new) { mdb_list_t *p = new; /* p = new list element */ mdb_list_t *q = mlp->ml_next; /* q = head list element */ mlp->ml_next = p; p->ml_prev = NULL; p->ml_next = q; if (q != NULL) { ASSERT(q->ml_prev == NULL); q->ml_prev = p; } else { ASSERT(mlp->ml_prev == NULL); mlp->ml_prev = p; } } void mdb_list_insert(mdb_list_t *mlp, void *after_me, void *new) { mdb_list_t *p = after_me; mdb_list_t *q = new; if (p == NULL || p->ml_next == NULL) { mdb_list_append(mlp, new); return; } q->ml_next = p->ml_next; q->ml_prev = p; p->ml_next = q; q->ml_next->ml_prev = q; } void mdb_list_delete(mdb_list_t *mlp, void *existing) { mdb_list_t *p = existing; if (p->ml_prev != NULL) p->ml_prev->ml_next = p->ml_next; else mlp->ml_next = p->ml_next; if (p->ml_next != NULL) p->ml_next->ml_prev = p->ml_prev; else mlp->ml_prev = p->ml_prev; } void mdb_list_move(mdb_list_t *src, mdb_list_t *dst) { dst->ml_prev = src->ml_prev; dst->ml_next = src->ml_next; src->ml_prev = NULL; src->ml_next = NULL; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_LIST_H #define _MDB_LIST_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB /* * Simple doubly-linked list implementation. This implementation assumes that * each element contains an embedded mdb_list_t structure. An additional * mdb_list_t is used to store the head and tail pointers. The caller can * use mdb_list_prev() on the master list_t to obtain the tail element, or * mdb_list_next() to obtain the head element. The head and tail list elements * have their previous and next pointers set to NULL, respectively. */ typedef struct mdb_list { struct mdb_list *ml_prev; /* Link to previous list element */ struct mdb_list *ml_next; /* Link to next list element */ } mdb_list_t; #define mdb_list_prev(elem) ((void *)(((mdb_list_t *)(elem))->ml_prev)) #define mdb_list_next(elem) ((void *)(((mdb_list_t *)(elem))->ml_next)) extern void mdb_list_append(mdb_list_t *, void *); extern void mdb_list_prepend(mdb_list_t *, void *); extern void mdb_list_insert(mdb_list_t *, void *, void *); extern void mdb_list_delete(mdb_list_t *, void *); extern void mdb_list_move(mdb_list_t *, mdb_list_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_LIST_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1997-2001 by Sun Microsystems, Inc. * All rights reserved. */ /* * Log I/O Backend * * This backend provides the ability to form a T in an iob's output routine. * We use this capability to provide interactive session logging. We create * a log i/o and give it a pointer to another i/o backend representing the * log file, and then stack this on top of the existing stdio i/o backend. * As each write occurs, the log i/o writes to the log file, and also passes * the write request along to io->io_next. */ #include #include #include #include static ssize_t logio_read(mdb_io_t *io, void *buf, size_t nbytes) { mdb_io_t *logio = io->io_data; ssize_t rbytes; if (io->io_next != NULL) { rbytes = IOP_READ(io->io_next, buf, nbytes); if (rbytes > 0) { (void) IOP_WRITE(logio, mdb.m_prompt, mdb.m_promptlen); (void) IOP_WRITE(logio, buf, rbytes); } return (rbytes); } return (-1); } static ssize_t logio_write(mdb_io_t *io, const void *buf, size_t nbytes) { mdb_io_t *logio = io->io_data; ssize_t wbytes; if (io->io_next != NULL) { wbytes = IOP_WRITE(io->io_next, buf, nbytes); if (wbytes > 0) (void) IOP_WRITE(logio, buf, wbytes); return (wbytes); } return (-1); } static void logio_close(mdb_io_t *io) { mdb_io_rele(io->io_data); } static const char * logio_name(mdb_io_t *io) { if (io->io_next != NULL) return (IOP_NAME(io->io_next)); return ("(log)"); } static const mdb_io_ops_t logio_ops = { .io_read = logio_read, .io_write = logio_write, .io_seek = no_io_seek, .io_ctl = no_io_ctl, .io_close = logio_close, .io_name = logio_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume }; mdb_io_t * mdb_logio_create(mdb_io_t *logio) { mdb_io_t *io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP); io->io_ops = &logio_ops; io->io_data = mdb_io_hold(logio); io->io_next = NULL; io->io_refcnt = 0; return (io); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * (k)adb Macro Aliases * * Provides aliases for popular ADB macros. These macros, which have been * removed from the workspace, were documented in various locations, and need * continued support. While we don't provide the same output format that was * provided by the original macros, we do map the macro names to the equivalent * MDB functionality. */ #include #include #include typedef struct mdb_macalias { const char *ma_name; const char *ma_defn; } mdb_macalias_t; static const mdb_macalias_t mdb_macaliases[] = { { "bufctl", "::bufctl" }, { "bufctl_audit", "::bufctl -v" }, { "cpu", "::cpuinfo -v" }, { "cpun", "::cpuinfo -v" }, { "cpus", "::walk cpu |::cpuinfo -v" }, { "devinfo", "::print struct dev_info" }, { "devinfo.minor", "::minornodes" }, { "devinfo.next", "::walk devi_next |::devinfo -s" }, { "devinfo.parent", "::walk devinfo_parents |::devinfo -s" }, { "devinfo.prop", "::devinfo" }, { "devinfo.sibling", "::walk devinfo_siblings |::devinfo -s" }, { "devinfo_brief", "::devinfo -s" }, { "devinfo_major", "::devbindings -s" }, { "devnames_major", "::devnames -m" }, { "devt", "::devt" }, { "devt2snode", "::dev2snode" }, { "findthreads", "::walk thread |::thread" }, { "major2snode", "::major2snode" }, { "mblk", "::mblk -v" }, { "modctl.brief", "::modctl" }, { "modules", "::modinfo" }, { "mount", "::fsinfo" }, { "msgbuf", "::msgbuf" }, { "mutex", "::mutex" }, { "panicbuf", "::panicinfo" }, { "pid2proc", "::pid2proc |::print proc_t" }, { "proc2u", "::print proc_t p_user" }, { "procargs", "::print proc_t p_user.u_psargs" }, { "queue", "::queue -v" }, { "sema", "::print sema_impl_t" }, { "stackregs", "::stackregs" }, { "stacktrace", "::stackregs" }, #if defined(__sparc) { "systemdump", "0>pc;0>npc;nopanicdebug/W 1;:c" }, #elif defined(__i386) { "systemdump", "0>eip;nopanicdebug/W 1;:c" }, #else { "systemdump", "0>rip;nopanicdebug/W 1;:c" }, #endif { "thread", "::print kthread_t" }, { "threadlist", "::threadlist -v" }, { "u", "::print user_t" }, { "utsname", "utsname::print" }, { NULL } }; void mdb_macalias_create(void) { int i; (void) mdb_nv_create(&mdb.m_macaliases, UM_SLEEP); for (i = 0; mdb_macaliases[i].ma_name != NULL; i++) { const mdb_macalias_t *ma = &mdb_macaliases[i]; (void) mdb_nv_insert(&mdb.m_macaliases, ma->ma_name, NULL, (uintptr_t)ma->ma_defn, MDB_NV_RDONLY | MDB_NV_EXTNAME | MDB_NV_PERSIST); } } const char * mdb_macalias_lookup(const char *name) { mdb_var_t *v; if ((v = mdb_nv_lookup(&mdb.m_macaliases, name)) == NULL) return (NULL); return (MDB_NV_COOKIE(v)); } /*ARGSUSED*/ int cmd_macalias_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; if (flags & DCMD_ADDRSPEC || argc != 0) return (DCMD_USAGE); mdb_printf("%%-20s% %%-59s%\n", "MACRO", "NATIVE EQUIVALENT"); for (i = 0; mdb_macaliases[i].ma_name != NULL; i++) { const mdb_macalias_t *ma = &mdb_macaliases[i]; mdb_printf("%-20s %s\n", ma->ma_name, ma->ma_defn); } return (DCMD_OK); } void mdb_macalias_destroy(void) { mdb_nv_destroy(&mdb.m_macaliases); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_MACALIASES_H #define _MDB_MACALIASES_H #ifdef __cplusplus extern "C" { #endif extern void mdb_macalias_create(void); extern void mdb_macalias_destroy(void); extern const char *mdb_macalias_lookup(const char *); extern int cmd_macalias_list(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_MACALIASES_H */ /* * 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 2012, Josef 'Jeff' Sipek . All rights reserved. */ /* * Copyright 2019 Joyent, Inc. */ #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 #include #include #include #include #ifndef STACK_BIAS #define STACK_BIAS 0 #endif #if defined(__sparc) #define STACK_REGISTER SP #else #define STACK_REGISTER REG_FP #endif #ifdef _LP64 #define MDB_DEF_IPATH \ "%r/usr/platform/%p/lib/adb/%i:" \ "%r/usr/platform/%m/lib/adb/%i:" \ "%r/usr/lib/adb/%i" #define MDB_DEF_LPATH \ "%r/usr/platform/%p/lib/mdb/%t/%i:" \ "%r/usr/platform/%m/lib/mdb/%t/%i:" \ "%r/usr/lib/mdb/%t/%i" #else #define MDB_DEF_IPATH \ "%r/usr/platform/%p/lib/adb:" \ "%r/usr/platform/%m/lib/adb:" \ "%r/usr/lib/adb" #define MDB_DEF_LPATH \ "%r/usr/platform/%p/lib/mdb/%t:" \ "%r/usr/platform/%m/lib/mdb/%t:" \ "%r/usr/lib/mdb/%t" #endif #define MDB_DEF_PROMPT "> " /* * Similar to the panic_* variables in the kernel, we keep some relevant * information stored in a set of global _mdb_abort_* variables; in the * event that the debugger dumps core, these will aid core dump analysis. */ const char *volatile _mdb_abort_str; /* reason for failure */ siginfo_t _mdb_abort_info; /* signal info for fatal signal */ ucontext_t _mdb_abort_ctx; /* context fatal signal interrupted */ int _mdb_abort_rcount; /* number of times resume requested */ int _mdb_self_fd = -1; /* fd for self as for valid_frame */ __NORETURN static void terminate(int status) { (void) mdb_signal_blockall(); mdb_destroy(); exit(status); } static void print_frame(uintptr_t pc, int fnum) { Dl_info dli; if (dladdr((void *)pc, &dli)) { mdb_iob_printf(mdb.m_err, " [%d] %s`%s+0x%lx()\n", fnum, strbasename(dli.dli_fname), dli.dli_sname, pc - (uintptr_t)dli.dli_saddr); } else mdb_iob_printf(mdb.m_err, " [%d] %p()\n", fnum, pc); } static int valid_frame(struct frame *fr) { static struct frame fake; uintptr_t addr = (uintptr_t)fr; if (pread(_mdb_self_fd, &fake, sizeof (fake), addr) != sizeof (fake)) { mdb_iob_printf(mdb.m_err, " invalid frame (%p)\n", fr); return (0); } if (addr & (STACK_ALIGN - 1)) { mdb_iob_printf(mdb.m_err, " mis-aligned frame (%p)\n", fr); return (0); } return (1); } /*ARGSUSED*/ static void flt_handler(int sig, siginfo_t *sip, ucontext_t *ucp, void *data) { static const struct rlimit rl = { (rlim_t)RLIM_INFINITY, (rlim_t)RLIM_INFINITY }; const mdb_idcmd_t *idcp = NULL; if (mdb.m_frame != NULL && mdb.m_frame->f_cp != NULL) idcp = mdb.m_frame->f_cp->c_dcmd; if (sip != NULL) bcopy(sip, &_mdb_abort_info, sizeof (_mdb_abort_info)); if (ucp != NULL) bcopy(ucp, &_mdb_abort_ctx, sizeof (_mdb_abort_ctx)); _mdb_abort_info.si_signo = sig; (void) mdb_signal_sethandler(sig, MDB_SIG_DFL, NULL); /* * If there is no current dcmd, or the current dcmd comes from a * builtin module, we don't allow resume and always core dump. */ if (idcp == NULL || idcp->idc_modp == NULL || idcp->idc_modp == &mdb.m_rmod || idcp->idc_modp->mod_hdl == NULL) goto dump; if (mdb.m_term != NULL) { struct frame *fr = (struct frame *) (ucp->uc_mcontext.gregs[STACK_REGISTER] + STACK_BIAS); char signame[SIG2STR_MAX]; int i = 1; char c; if (sig2str(sig, signame) == -1) { mdb_iob_printf(mdb.m_err, "\n*** %s: received signal %d at:\n", mdb.m_pname, sig); } else { mdb_iob_printf(mdb.m_err, "\n*** %s: received signal %s at:\n", mdb.m_pname, signame); } if (ucp->uc_mcontext.gregs[REG_PC] != 0) print_frame(ucp->uc_mcontext.gregs[REG_PC], i++); while (fr != NULL && valid_frame(fr) && fr->fr_savpc != 0) { print_frame(fr->fr_savpc, i++); fr = (struct frame *) ((uintptr_t)fr->fr_savfp + STACK_BIAS); } query: mdb_iob_printf(mdb.m_err, "\n%s: (c)ore dump, (q)uit, " "(r)ecover, or (s)top for debugger [cqrs]? ", mdb.m_pname); mdb_iob_flush(mdb.m_err); for (;;) { if (IOP_READ(mdb.m_term, &c, sizeof (c)) != sizeof (c)) goto dump; switch (c) { case 'c': case 'C': (void) setrlimit(RLIMIT_CORE, &rl); mdb_iob_printf(mdb.m_err, "\n%s: attempting " "to dump core ...\n", mdb.m_pname); goto dump; case 'q': case 'Q': mdb_iob_discard(mdb.m_out); mdb_iob_nl(mdb.m_err); (void) mdb_signal_unblockall(); terminate(1); /*NOTREACHED*/ case 'r': case 'R': mdb_iob_printf(mdb.m_err, "\n%s: unloading " "module '%s' ...\n", mdb.m_pname, idcp->idc_modp->mod_name); (void) mdb_module_unload( idcp->idc_modp->mod_name, 0); (void) mdb_signal_sethandler(sig, flt_handler, NULL); _mdb_abort_rcount++; mdb.m_intr = 0; mdb.m_pend = 0; (void) mdb_signal_unblockall(); longjmp(mdb.m_frame->f_pcb, MDB_ERR_ABORT); /*NOTREACHED*/ case 's': case 'S': mdb_iob_printf(mdb.m_err, "\n%s: " "attempting to stop pid %d ...\n", mdb.m_pname, (int)getpid()); /* * Stop ourself; if this fails or we are * subsequently continued, ask again. */ (void) mdb_signal_raise(SIGSTOP); (void) mdb_signal_unblockall(); goto query; } } } dump: if (SI_FROMUSER(sip)) { (void) mdb_signal_block(sig); (void) mdb_signal_raise(sig); } (void) sigfillset(&ucp->uc_sigmask); (void) sigdelset(&ucp->uc_sigmask, sig); if (_mdb_abort_str == NULL) _mdb_abort_str = "fatal signal received"; ucp->uc_flags |= UC_SIGMASK; (void) setcontext(ucp); } /*ARGSUSED*/ static void int_handler(int sig, siginfo_t *sip, ucontext_t *ucp, void *data) { if (mdb.m_intr == 0) longjmp(mdb.m_frame->f_pcb, MDB_ERR_SIGINT); else mdb.m_pend++; } static void control_kmdb(int start) { int fd; if ((fd = open("/dev/kmdb", O_RDONLY)) < 0) die("failed to open /dev/kmdb"); if (start) { char *state = mdb_get_config(); if (ioctl(fd, KMDB_IOC_START, state) < 0) die("failed to start kmdb"); strfree(state); } else { if (ioctl(fd, KMDB_IOC_STOP) < 0) die("failed to stop kmdb"); } (void) close(fd); } static void usage(int status) { mdb_iob_printf(mdb.m_err, "Usage: %s [-fkmuwyAFKMSUW] [+/-o option] " "[-b VM] [-p pid] [-s dist] [-I path] [-L path]\n\t[-P prompt] " "[-R root] [-V dis-version] [-e expr] " "[object [core] | core | suffix]\n\n", mdb.m_pname); mdb_iob_puts(mdb.m_err, "\t-b attach to specified bhyve VM\n" "\t-e evaluate expr and return status\n" "\t-f force raw file debugging mode\n" "\t-k force kernel debugging mode\n" "\t-m disable demand-loading of module symbols\n" "\t-o set specified debugger option (+o to unset)\n" "\t-p attach to specified process-id\n" "\t-s set symbol matching distance\n" "\t-u force user program debugging mode\n" "\t-w enable write mode\n" "\t-y send terminal initialization sequences for tty mode\n" "\t-A disable automatic loading of mdb modules\n" "\t-F enable forcible takeover mode\n" "\t-K stop operating system and enter live kernel debugger\n" "\t-M preload all module symbols\n" "\t-I set initial path for macro files\n" "\t-L set initial path for module libs\n" "\t-P set command-line prompt\n" "\t-R set root directory for pathname expansion\n" "\t-S suppress processing of ~/.mdbrc file\n" "\t-U unload live kernel debugger\n" "\t-W enable I/O-mapped memory access (kernel only)\n" "\t-V set disassembler version\n"); terminate(status); } static char * mdb_scf_console_term(void) { scf_simple_prop_t *prop; char *term = NULL; if ((prop = scf_simple_prop_get(NULL, "svc:/system/console-login:default", "ttymon", "terminal_type")) == NULL) return (NULL); if (scf_simple_prop_type(prop) == SCF_TYPE_ASTRING && (term = scf_simple_prop_next_astring(prop)) != NULL) term = strdup(term); scf_simple_prop_free(prop); return (term); } /* * Unpleasant hack: we might be debugging a hypervisor domain dump. * Earlier versions use a non-ELF file. Later versions are ELF, but are * /always/ ELF64, so our standard ehdr check isn't good enough. Since * we don't want to know too much about the file format, we'll ask * mdb_kb. */ #ifdef __x86 static int identify_xvm_file(const char *file, int *longmode) { int (*identify)(const char *, int *); if (mdb_module_load("mdb_kb", MDB_MOD_GLOBAL | MDB_MOD_SILENT) != 0) return (0); identify = (int (*)())dlsym(RTLD_NEXT, "xkb_identify"); if (identify == NULL) return (0); return (identify(file, longmode)); } #else /*ARGSUSED*/ static int identify_xvm_file(const char *file, int *longmode) { return (0); } #endif /* __x86 */ #ifndef __amd64 /* * There is no bhyve target in a 32bit x86 or any SPARC mdb. This dummy helps * keep the code simpler. */ /*ARGSUSED*/ static int mdb_bhyve_tgt_create(mdb_tgt_t *t, int argc, const char *argv[]) { return (set_errno(EINVAL)); } #endif int main(int argc, char *argv[], char *envp[]) { extern int mdb_kvm_is_compressed_dump(mdb_io_t *); extern int mdb_kvm_is_dump(mdb_io_t *); mdb_tgt_ctor_f *tgt_ctor = NULL; const char **tgt_argv = alloca((argc + 2) * sizeof (char *)); int tgt_argc = 0; mdb_tgt_t *tgt; char object[MAXPATHLEN], execname[MAXPATHLEN]; mdb_io_t *in_io, *out_io, *err_io, *null_io; struct termios tios; int status, c; char *p; const char *Iflag = NULL, *Lflag = NULL, *Vflag = NULL, *pidarg = NULL; const char *eflag = NULL; int fflag = 0, Kflag = 0, Rflag = 0, Sflag = 0, Oflag = 0, Uflag = 0; int bflag = 0; int ttylike; int longmode = 0; stack_t sigstack; if (realpath(getexecname(), execname) == NULL) { (void) strncpy(execname, argv[0], MAXPATHLEN); execname[MAXPATHLEN - 1] = '\0'; } mdb_create(execname, argv[0]); bzero(tgt_argv, argc * sizeof (char *)); argv[0] = (char *)mdb.m_pname; _mdb_self_fd = open("/proc/self/as", O_RDONLY); mdb.m_env = envp; out_io = mdb_fdio_create(STDOUT_FILENO); mdb.m_out = mdb_iob_create(out_io, MDB_IOB_WRONLY); err_io = mdb_fdio_create(STDERR_FILENO); mdb.m_err = mdb_iob_create(err_io, MDB_IOB_WRONLY); mdb_iob_clrflags(mdb.m_err, MDB_IOB_AUTOWRAP); null_io = mdb_nullio_create(); mdb.m_null = mdb_iob_create(null_io, MDB_IOB_WRONLY); in_io = mdb_fdio_create(STDIN_FILENO); if ((mdb.m_termtype = getenv("TERM")) != NULL) { mdb.m_termtype = strdup(mdb.m_termtype); mdb.m_flags |= MDB_FL_TERMGUESS; } mdb.m_term = NULL; mdb_dmode(mdb_dstr2mode(getenv("MDB_DEBUG"))); mdb.m_pgid = getpgrp(); if (getenv("_MDB_EXEC") != NULL) mdb.m_flags |= MDB_FL_EXEC; /* * Setup an alternate signal stack. When tearing down pipelines in * terminate(), we may have to destroy the stack of the context in * which we are currently executing the signal handler. */ sigstack.ss_sp = mmap(NULL, SIGSTKSZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (sigstack.ss_sp == MAP_FAILED) die("could not allocate signal stack"); sigstack.ss_size = SIGSTKSZ; sigstack.ss_flags = 0; if (sigaltstack(&sigstack, NULL) != 0) die("could not set signal stack"); (void) mdb_signal_sethandler(SIGPIPE, MDB_SIG_IGN, NULL); (void) mdb_signal_sethandler(SIGQUIT, MDB_SIG_IGN, NULL); (void) mdb_signal_sethandler(SIGILL, flt_handler, NULL); (void) mdb_signal_sethandler(SIGTRAP, flt_handler, NULL); (void) mdb_signal_sethandler(SIGIOT, flt_handler, NULL); (void) mdb_signal_sethandler(SIGEMT, flt_handler, NULL); (void) mdb_signal_sethandler(SIGFPE, flt_handler, NULL); (void) mdb_signal_sethandler(SIGBUS, flt_handler, NULL); (void) mdb_signal_sethandler(SIGSEGV, flt_handler, NULL); (void) mdb_signal_sethandler(SIGHUP, (mdb_signal_f *)(uintptr_t)terminate, NULL); (void) mdb_signal_sethandler(SIGTERM, (mdb_signal_f *)(uintptr_t)terminate, NULL); for (mdb.m_rdvers = RD_VERSION; mdb.m_rdvers > 0; mdb.m_rdvers--) { if (rd_init(mdb.m_rdvers) == RD_OK) break; } for (mdb.m_ctfvers = CTF_VERSION; mdb.m_ctfvers > 0; mdb.m_ctfvers--) { if (ctf_version(mdb.m_ctfvers) != -1) break; } if ((p = getenv("HISTSIZE")) != NULL && strisnum(p)) { mdb.m_histlen = strtoi(p); if (mdb.m_histlen < 1) mdb.m_histlen = 1; } while (optind < argc) { while ((c = getopt(argc, argv, "be:fkmo:p:s:uwyACD:FI:KL:MOP:R:SUV:W")) != (int)EOF) { switch (c) { case 'b': bflag++; tgt_ctor = mdb_bhyve_tgt_create; break; case 'e': if (eflag != NULL) { warn("-e already specified\n"); terminate(2); } eflag = optarg; break; case 'f': fflag++; tgt_ctor = mdb_rawfile_tgt_create; break; case 'k': tgt_ctor = mdb_kvm_tgt_create; break; case 'm': mdb.m_tgtflags |= MDB_TGT_F_NOLOAD; mdb.m_tgtflags &= ~MDB_TGT_F_PRELOAD; break; case 'o': if (!mdb_set_options(optarg, TRUE)) terminate(2); break; case 'p': tgt_ctor = mdb_proc_tgt_create; pidarg = optarg; break; case 's': if (!strisnum(optarg)) { warn("expected integer following -s\n"); terminate(2); } mdb.m_symdist = (size_t)(uint_t)strtoi(optarg); break; case 'u': tgt_ctor = mdb_proc_tgt_create; break; case 'w': mdb.m_tgtflags |= MDB_TGT_F_RDWR; break; case 'y': mdb.m_flags |= MDB_FL_USECUP; break; case 'A': (void) mdb_set_options("nomods", TRUE); break; case 'C': (void) mdb_set_options("noctf", TRUE); break; case 'D': mdb_dmode(mdb_dstr2mode(optarg)); break; case 'F': mdb.m_tgtflags |= MDB_TGT_F_FORCE; break; case 'I': Iflag = optarg; break; case 'L': Lflag = optarg; break; case 'K': Kflag++; break; case 'M': mdb.m_tgtflags |= MDB_TGT_F_PRELOAD; mdb.m_tgtflags &= ~MDB_TGT_F_NOLOAD; break; case 'O': Oflag++; break; case 'P': if (!mdb_set_prompt(optarg)) terminate(2); break; case 'R': (void) strncpy(mdb.m_root, optarg, MAXPATHLEN); mdb.m_root[MAXPATHLEN - 1] = '\0'; Rflag++; break; case 'S': Sflag++; break; case 'U': Uflag++; break; case 'V': Vflag = optarg; break; case 'W': mdb.m_tgtflags |= MDB_TGT_F_ALLOWIO; break; case '?': if (optopt == '?') usage(0); /* FALLTHROUGH */ default: usage(2); } } if (optind < argc) { const char *arg = argv[optind++]; if (arg[0] == '+' && strlen(arg) == 2) { if (arg[1] != 'o') { warn("illegal option -- %s\n", arg); terminate(2); } if (optind >= argc) { warn("option requires an argument -- " "%s\n", arg); terminate(2); } if (!mdb_set_options(argv[optind++], FALSE)) terminate(2); } else tgt_argv[tgt_argc++] = arg; } } if (rd_ctl(RD_CTL_SET_HELPPATH, (void *)mdb.m_root) != RD_OK) { warn("cannot set librtld_db helper path to %s\n", mdb.m_root); terminate(2); } if (mdb.m_debug & MDB_DBG_HELP) terminate(0); /* Quit here if we've printed out the tokens */ if (Iflag != NULL && strchr(Iflag, ';') != NULL) { warn("macro path cannot contain semicolons\n"); terminate(2); } if (Lflag != NULL && strchr(Lflag, ';') != NULL) { warn("module path cannot contain semicolons\n"); terminate(2); } if (Kflag || Uflag) { char *nm; if (tgt_ctor != NULL || Iflag != NULL) { warn("neither -f, -k, -p, -u, nor -I " "may be used with -K\n"); usage(2); } if (Lflag != NULL) mdb_set_lpath(Lflag); if ((nm = ttyname(STDIN_FILENO)) == NULL || strcmp(nm, "/dev/console") != 0) { /* * Due to the consequences of typing mdb -K instead of * mdb -k on a tty other than /dev/console, we require * -F when starting kmdb from a tty other than * /dev/console. */ if (!(mdb.m_tgtflags & MDB_TGT_F_FORCE)) { die("-F must also be supplied to start kmdb " "from non-console tty\n"); } if (mdb.m_termtype == NULL || (mdb.m_flags & MDB_FL_TERMGUESS)) { if (mdb.m_termtype != NULL) strfree(mdb.m_termtype); if ((mdb.m_termtype = mdb_scf_console_term()) != NULL) mdb.m_flags |= MDB_FL_TERMGUESS; } } else { /* * When on console, $TERM (if set) takes precedence over * the SMF setting. */ if (mdb.m_termtype == NULL && (mdb.m_termtype = mdb_scf_console_term()) != NULL) mdb.m_flags |= MDB_FL_TERMGUESS; } control_kmdb(Kflag); terminate(0); /*NOTREACHED*/ } if (eflag != NULL) { IOP_CLOSE(in_io); in_io = mdb_strio_create(eflag); mdb.m_lastret = 0; } /* * If standard input appears to have tty attributes, attempt to * initialize a terminal i/o backend on top of stdin and stdout. */ ttylike = (IOP_CTL(in_io, TCGETS, &tios) == 0); if (ttylike) { if ((mdb.m_term = mdb_termio_create(mdb.m_termtype, in_io, out_io)) == NULL) { if (!(mdb.m_flags & MDB_FL_EXEC)) { warn("term init failed: command-line editing " "and prompt will not be available\n"); } } else { in_io = mdb.m_term; } } mdb.m_in = mdb_iob_create(in_io, MDB_IOB_RDONLY); if (mdb.m_term != NULL) { mdb_iob_setpager(mdb.m_out, mdb.m_term); if (mdb.m_flags & MDB_FL_PAGER) mdb_iob_setflags(mdb.m_out, MDB_IOB_PGENABLE); else mdb_iob_clrflags(mdb.m_out, MDB_IOB_PGENABLE); } else if (ttylike) mdb_iob_setflags(mdb.m_in, MDB_IOB_TTYLIKE); else mdb_iob_setbuf(mdb.m_in, mdb_alloc(1, UM_SLEEP), 1); mdb_pservice_init(); mdb_lex_reset(); if ((mdb.m_shell = getenv("SHELL")) == NULL) mdb.m_shell = "/bin/sh"; /* * If the debugger state is to be inherited from a previous instance, * restore it now prior to path evaluation so that %R is updated. */ if ((p = getenv(MDB_CONFIG_ENV_VAR)) != NULL) { mdb_set_config(p); (void) unsetenv(MDB_CONFIG_ENV_VAR); } /* * Path evaluation part 1: Create the initial module path to allow * the target constructor to load a support module. Then expand * any command-line arguments that modify the paths. */ if (Iflag != NULL) mdb_set_ipath(Iflag); else mdb_set_ipath(MDB_DEF_IPATH); if (Lflag != NULL) mdb_set_lpath(Lflag); else mdb_set_lpath(MDB_DEF_LPATH); if (mdb_get_prompt() == NULL && !(mdb.m_flags & MDB_FL_ADB)) (void) mdb_set_prompt(MDB_DEF_PROMPT); if (tgt_ctor == mdb_kvm_tgt_create) { if (pidarg != NULL) { warn("-p and -k options are mutually exclusive\n"); terminate(2); } if (tgt_argc == 0) tgt_argv[tgt_argc++] = "/dev/ksyms"; if (tgt_argc == 1 && strisnum(tgt_argv[0]) == 0) { if (mdb.m_tgtflags & MDB_TGT_F_ALLOWIO) tgt_argv[tgt_argc++] = "/dev/allkmem"; else tgt_argv[tgt_argc++] = "/dev/kmem"; } } if (pidarg != NULL) { if (tgt_argc != 0) { warn("-p may not be used with other arguments\n"); terminate(2); } if (proc_arg_psinfo(pidarg, PR_ARG_PIDS, NULL, &status) == -1) { die("cannot attach to %s: %s\n", pidarg, Pgrab_error(status)); } if (strchr(pidarg, '/') != NULL) (void) mdb_iob_snprintf(object, MAXPATHLEN, "%s/object/a.out", pidarg); else (void) mdb_iob_snprintf(object, MAXPATHLEN, "/proc/%s/object/a.out", pidarg); tgt_argv[tgt_argc++] = object; tgt_argv[tgt_argc++] = pidarg; } /* * Find the first argument that is not a special "-" token. If one is * found, we will examine this file and make some inferences below. */ for (c = 0; c < tgt_argc && strcmp(tgt_argv[c], "-") == 0; c++) continue; if (c < tgt_argc) { Elf32_Ehdr ehdr; mdb_io_t *io; /* * If special "-" tokens preceded an argument, shift the entire * argument list to the left to remove the leading "-" args. */ if (c > 0) { bcopy(&tgt_argv[c], tgt_argv, sizeof (const char *) * (tgt_argc - c)); tgt_argc -= c; } if (fflag) goto tcreate; /* skip re-exec and just create target */ /* bhyve: directly create target, or re-exec in case of 32bit */ if (bflag) { #ifndef __amd64 goto reexec; #else goto tcreate; #endif } /* * If we just have an object file name, and that file doesn't * exist, and it's a string of digits, infer it to be a * sequence number referring to a pair of crash dump files. */ if (tgt_argc == 1 && access(tgt_argv[0], F_OK) == -1 && strisnum(tgt_argv[0])) { size_t len = strlen(tgt_argv[0]) + 8; const char *object = tgt_argv[0]; tgt_argv[0] = alloca(len); tgt_argv[1] = alloca(len); (void) strcpy((char *)tgt_argv[0], "unix."); (void) strcat((char *)tgt_argv[0], object); (void) strcpy((char *)tgt_argv[1], "vmcore."); (void) strcat((char *)tgt_argv[1], object); if (access(tgt_argv[0], F_OK) == -1 && access(tgt_argv[1], F_OK) != -1) { /* * If we have a vmcore but not a unix file, * set the symbol table to be the vmcore to * force libkvm to extract it out of the dump. */ tgt_argv[0] = tgt_argv[1]; } else if (access(tgt_argv[0], F_OK) == -1 && access(tgt_argv[1], F_OK) == -1) { (void) strcpy((char *)tgt_argv[1], "vmdump."); (void) strcat((char *)tgt_argv[1], object); if (access(tgt_argv[1], F_OK) == 0) { mdb_iob_printf(mdb.m_err, "cannot open compressed dump; " "decompress using savecore -f %s\n", tgt_argv[1]); terminate(0); } } tgt_argc = 2; } /* * We need to open the object file in order to determine its * ELF class and potentially re-exec ourself. */ if ((io = mdb_fdio_create_path(NULL, tgt_argv[0], O_RDONLY, 0)) == NULL) die("failed to open %s", tgt_argv[0]); if (tgt_argc == 1) { if (mdb_kvm_is_compressed_dump(io)) { /* * We have a single vmdump.N compressed dump * file; give a helpful message. */ mdb_iob_printf(mdb.m_err, "cannot open compressed dump; " "decompress using savecore -f %s\n", tgt_argv[0]); terminate(0); } else if (mdb_kvm_is_dump(io)) { /* * We have an uncompressed dump as our only * argument; specify the dump as the symbol * table to force libkvm to dig it out of the * dump. */ tgt_argv[tgt_argc++] = tgt_argv[0]; } } /* * If the target is unknown or is not the rawfile target, do * a gelf_check to determine if the file is an ELF file. If * it is not and the target is unknown, use the rawfile tgt. * Otherwise an ELF-based target is needed, so we must abort. */ if (mdb_gelf_check(io, &ehdr, ET_NONE) == -1) { if (tgt_ctor != NULL) { (void) mdb_gelf_check(io, &ehdr, ET_EXEC); mdb_io_destroy(io); terminate(1); } else tgt_ctor = mdb_rawfile_tgt_create; } mdb_io_destroy(io); if (identify_xvm_file(tgt_argv[0], &longmode) == 1) { #ifdef _LP64 if (!longmode) goto reexec; #else if (longmode) goto reexec; #endif tgt_ctor = mdb_kvm_tgt_create; goto tcreate; } /* * The object file turned out to be a user core file (ET_CORE), * and no other arguments were specified, swap 0 and 1. The * proc target will infer the executable for us. */ if (ehdr.e_type == ET_CORE) { tgt_argv[tgt_argc++] = tgt_argv[0]; tgt_argv[0] = NULL; tgt_ctor = mdb_proc_tgt_create; } /* * If tgt_argv[1] is filled in, open it up and determine if it * is a vmcore file. If it is, gelf_check will fail and we * set tgt_ctor to 'kvm'; otherwise we use the default. */ if (tgt_argc > 1 && strcmp(tgt_argv[1], "-") != 0 && tgt_argv[0] != NULL && pidarg == NULL) { Elf32_Ehdr chdr; if (access(tgt_argv[1], F_OK) == -1) die("failed to access %s", tgt_argv[1]); /* *.N case: drop vmdump.N from the list */ if (tgt_argc == 3) { if ((io = mdb_fdio_create_path(NULL, tgt_argv[2], O_RDONLY, 0)) == NULL) die("failed to open %s", tgt_argv[2]); if (mdb_kvm_is_compressed_dump(io)) tgt_argv[--tgt_argc] = NULL; mdb_io_destroy(io); } if ((io = mdb_fdio_create_path(NULL, tgt_argv[1], O_RDONLY, 0)) == NULL) die("failed to open %s", tgt_argv[1]); if (mdb_gelf_check(io, &chdr, ET_NONE) == -1) tgt_ctor = mdb_kvm_tgt_create; mdb_io_destroy(io); } /* * At this point, we've read the ELF header for either an * object file or core into ehdr. If the class does not match * ours, attempt to exec the mdb of the appropriate class. */ #ifdef _LP64 if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) goto reexec; #else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) goto reexec; #endif } tcreate: if (tgt_ctor == NULL) tgt_ctor = mdb_proc_tgt_create; tgt = mdb_tgt_create(tgt_ctor, mdb.m_tgtflags, tgt_argc, tgt_argv); if (tgt == NULL) { if (errno == EINVAL) usage(2); /* target can return EINVAL to get usage */ if (errno == EMDB_TGT) terminate(1); /* target already printed error msg */ die("failed to initialize target"); } mdb_tgt_activate(tgt); mdb_create_loadable_disasms(); if (Vflag != NULL && mdb_dis_select(Vflag) == -1) warn("invalid disassembler mode -- %s\n", Vflag); if (Rflag && mdb.m_term != NULL) warn("Using proto area %s\n", mdb.m_root); /* * If the target was successfully constructed and -O was specified, * we now attempt to enter piggy-mode for debugging jurassic problems. */ if (Oflag) { pcinfo_t pci; (void) strcpy(pci.pc_clname, "RT"); if (priocntl(P_LWPID, P_MYID, PC_GETCID, (caddr_t)&pci) != -1) { pcparms_t pcp; rtparms_t *rtp = (rtparms_t *)pcp.pc_clparms; rtp->rt_pri = 35; rtp->rt_tqsecs = 0; rtp->rt_tqnsecs = RT_TQDEF; pcp.pc_cid = pci.pc_cid; if (priocntl(P_LWPID, P_MYID, PC_SETPARMS, (caddr_t)&pcp) == -1) { warn("failed to set RT parameters"); Oflag = 0; } } else { warn("failed to get RT class id"); Oflag = 0; } if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { warn("failed to lock address space"); Oflag = 0; } if (Oflag) mdb_printf("%s: oink, oink!\n", mdb.m_pname); } /* * Path evaluation part 2: Re-evaluate the path now that the target * is ready (and thus we have access to the real platform string). * Do this before reading ~/.mdbrc to allow path modifications prior * to performing module auto-loading. */ mdb_set_ipath(mdb.m_ipathstr); mdb_set_lpath(mdb.m_lpathstr); if (!Sflag && (p = getenv("HOME")) != NULL) { char rcpath[MAXPATHLEN]; mdb_io_t *rc_io; int fd; (void) mdb_iob_snprintf(rcpath, MAXPATHLEN, "%s/.mdbrc", p); fd = open64(rcpath, O_RDONLY); if (fd >= 0 && (rc_io = mdb_fdio_create_named(fd, rcpath))) { mdb_iob_t *iob = mdb_iob_create(rc_io, MDB_IOB_RDONLY); mdb_iob_t *old = mdb.m_in; mdb.m_in = iob; (void) mdb_run(); mdb.m_in = old; } } if (!(mdb.m_flags & MDB_FL_NOMODS)) mdb_module_load_all(0); (void) mdb_signal_sethandler(SIGINT, int_handler, NULL); while ((status = mdb_run()) == MDB_ERR_ABORT || status == MDB_ERR_OUTPUT) { /* * If a write failed on stdout, give up. A more informative * error message will already have been printed by mdb_run(). */ if (status == MDB_ERR_OUTPUT && mdb_iob_getflags(mdb.m_out) & MDB_IOB_ERR) { mdb_warn("write to stdout failed, exiting\n"); break; } continue; } terminate((status == MDB_ERR_QUIT || status == 0) ? (eflag != NULL && mdb.m_lastret != 0 ? 1 : 0) : 1); /*NOTREACHED*/ reexec: if ((p = strrchr(execname, '/')) == NULL) die("cannot determine absolute pathname\n"); #ifdef _LP64 #ifdef __sparc (void) strcpy(p, "/../sparcv7/"); #else (void) strcpy(p, "/../i86/"); #endif #else #ifdef __sparc (void) strcpy(p, "/../sparcv9/"); #else (void) strcpy(p, "/../amd64/"); #endif #endif (void) strcat(p, mdb.m_pname); if (mdb.m_term != NULL) (void) IOP_CTL(in_io, TCSETSW, &tios); (void) putenv("_MDB_EXEC=1"); (void) execv(execname, argv); /* * If execv fails, suppress ENOEXEC. Experience shows the most common * reason is that the machine is booted under a 32-bit kernel, in which * case it is clearer to only print the message below. */ if (errno != ENOEXEC) warn("failed to exec %s", execname); #ifdef _LP64 die("64-bit %s cannot debug 32-bit program %s\n", mdb.m_pname, tgt_argv[0] ? tgt_argv[0] : tgt_argv[1]); #else die("32-bit %s cannot debug 64-bit program %s\n", mdb.m_pname, tgt_argv[0] ? tgt_argv[0] : tgt_argv[1]); #endif goto tcreate; } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Memory I/O backend. * * Simple backend that has main memory as its backing store. */ #include #include typedef struct mem_data { char *md_buf; size_t md_size; offset_t md_off; } mem_data_t; static ssize_t memio_read(mdb_io_t *io, void *buf, size_t nbytes) { mem_data_t *mdp = io->io_data; if (io->io_next == NULL) { if (mdp->md_off + nbytes > mdp->md_size) nbytes = (mdp->md_size - mdp->md_off); bcopy(mdp->md_buf + mdp->md_off, buf, nbytes); mdp->md_off += nbytes; return (nbytes); } return (IOP_READ(io->io_next, buf, nbytes)); } static off64_t memio_seek(mdb_io_t *io, off64_t offset, int whence) { mem_data_t *mdp = io->io_data; if (io->io_next == NULL) { switch (whence) { case SEEK_SET: mdp->md_off = offset; break; case SEEK_CUR: mdp->md_off += offset; break; case SEEK_END: mdp->md_off = mdp->md_size + offset; if (mdp->md_off > mdp->md_size) mdp->md_off = mdp->md_size; break; default: return (-1); } return (mdp->md_off); } return (IOP_SEEK(io->io_next, offset, whence)); } static const mdb_io_ops_t memio_ops = { .io_read = memio_read, .io_write = no_io_write, .io_seek = memio_seek, .io_ctl = no_io_ctl, .io_close = no_io_close, .io_name = no_io_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume, }; mdb_io_t * mdb_memio_create(char *buf, size_t size) { mdb_io_t *io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP); mem_data_t *mdp = mdb_alloc(sizeof (mem_data_t), UM_SLEEP); mdp->md_buf = buf; mdp->md_size = size; mdp->md_off = 0; io->io_ops = &memio_ops; io->io_data = mdp; io->io_next = NULL; io->io_refcnt = 0; return (io); } /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013 by Delphix. All rights reserved. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright 2024 Oxide Computer Company * Copyright 2023 RackTop Systems, Inc. * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * Private callback structure for implementing mdb_walk_dcmd, below. */ typedef struct { mdb_idcmd_t *dw_dcmd; mdb_argvec_t dw_argv; uint_t dw_flags; } dcmd_walk_arg_t; /* * Global properties which modules are allowed to look at. These are * re-initialized by the target activation callbacks. */ int mdb_prop_postmortem = FALSE; /* Are we examining a dump? */ int mdb_prop_kernel = FALSE; /* Are we examining a kernel? */ int mdb_prop_datamodel = 0; /* Data model (see mdb_target_impl.h) */ static int call_idcmd(mdb_idcmd_t *idcp, uintmax_t addr, uintmax_t count, uint_t flags, mdb_argvec_t *argv); int mdb_snprintfrac(char *buf, int len, uint64_t numerator, uint64_t denom, int frac_digits) { int mul = 1; int whole, frac, i; for (i = frac_digits; i; i--) mul *= 10; whole = numerator / denom; frac = mul * numerator / denom - mul * whole; return (mdb_snprintf(buf, len, "%u.%0*u", whole, frac_digits, frac)); } void mdb_nicenum(uint64_t num, char *buf) { uint64_t n = num; int index = 0; char *u; while (n >= 1024) { n = (n + (1024 / 2)) / 1024; /* Round up or down */ index++; } u = &" \0K\0M\0G\0T\0P\0E\0"[index*2]; if (index == 0) { (void) mdb_snprintf(buf, MDB_NICENUM_BUFLEN, "%llu", (u_longlong_t)n); } else if (n < 10 && (num & (num - 1)) != 0) { (void) mdb_snprintfrac(buf, MDB_NICENUM_BUFLEN, num, 1ULL << 10 * index, 2); (void) strcat(buf, u); } else if (n < 100 && (num & (num - 1)) != 0) { (void) mdb_snprintfrac(buf, MDB_NICENUM_BUFLEN, num, 1ULL << 10 * index, 1); (void) strcat(buf, u); } else { (void) mdb_snprintf(buf, MDB_NICENUM_BUFLEN, "%llu%s", (u_longlong_t)n, u); } } void mdb_nicetime(int64_t delta, char *buf, size_t buflen) { const char *sign = (delta < 0) ? "-" : "+"; char daybuf[32] = { 0 }; char fracbuf[32] = { 0 }; if (delta < 0) delta = -delta; if (delta == 0) { (void) mdb_snprintf(buf, buflen, "0ns"); return; } /* Handle values < 1s */ if (delta < NANOSEC) { static const char f_units[] = "num"; uint_t idx = 0; while (delta >= 1000) { delta /= 1000; idx++; } (void) mdb_snprintf(buf, buflen, "t%s%lld%cs", sign, delta, f_units[idx]); return; } uint64_t days, hours, mins, secs, frac; frac = delta % NANOSEC; delta /= NANOSEC; secs = delta % 60; delta /= 60; mins = delta % 60; delta /= 60; hours = delta % 24; delta /= 24; days = delta; if (days > 0) (void) mdb_snprintf(daybuf, sizeof (daybuf), "%llud ", days); if (frac > 0) (void) mdb_snprintf(fracbuf, sizeof (fracbuf), ".%llu", frac); (void) mdb_snprintf(buf, buflen, "t%s%s%02llu:%02llu:%02llu%s", sign, daybuf, hours, mins, secs, fracbuf); } ssize_t mdb_vread(void *buf, size_t nbytes, uintptr_t addr) { ssize_t rbytes = mdb_tgt_vread(mdb.m_target, buf, nbytes, addr); if (rbytes > 0 && rbytes < nbytes) return (set_errbytes(rbytes, nbytes)); return (rbytes); } ssize_t mdb_vwrite(const void *buf, size_t nbytes, uintptr_t addr) { return (mdb_tgt_vwrite(mdb.m_target, buf, nbytes, addr)); } ssize_t mdb_aread(void *buf, size_t nbytes, uintptr_t addr, void *as) { ssize_t rbytes = mdb_tgt_aread(mdb.m_target, as, buf, nbytes, addr); if (rbytes > 0 && rbytes < nbytes) return (set_errbytes(rbytes, nbytes)); return (rbytes); } ssize_t mdb_awrite(const void *buf, size_t nbytes, uintptr_t addr, void *as) { return (mdb_tgt_awrite(mdb.m_target, as, buf, nbytes, addr)); } ssize_t mdb_fread(void *buf, size_t nbytes, uintptr_t addr) { ssize_t rbytes = mdb_tgt_fread(mdb.m_target, buf, nbytes, addr); if (rbytes > 0 && rbytes < nbytes) return (set_errbytes(rbytes, nbytes)); return (rbytes); } ssize_t mdb_fwrite(const void *buf, size_t nbytes, uintptr_t addr) { return (mdb_tgt_fwrite(mdb.m_target, buf, nbytes, addr)); } ssize_t mdb_pread(void *buf, size_t nbytes, physaddr_t addr) { ssize_t rbytes = mdb_tgt_pread(mdb.m_target, buf, nbytes, addr); if (rbytes > 0 && rbytes < nbytes) return (set_errbytes(rbytes, nbytes)); return (rbytes); } ssize_t mdb_pwrite(const void *buf, size_t nbytes, physaddr_t addr) { return (mdb_tgt_pwrite(mdb.m_target, buf, nbytes, addr)); } ssize_t mdb_readstr(char *buf, size_t nbytes, uintptr_t addr) { return (mdb_tgt_readstr(mdb.m_target, MDB_TGT_AS_VIRT, buf, nbytes, addr)); } ssize_t mdb_writestr(const char *buf, uintptr_t addr) { return (mdb_tgt_writestr(mdb.m_target, MDB_TGT_AS_VIRT, buf, addr)); } ssize_t mdb_readsym(void *buf, size_t nbytes, const char *name) { ssize_t rbytes = mdb_tgt_readsym(mdb.m_target, MDB_TGT_AS_VIRT, buf, nbytes, MDB_TGT_OBJ_EVERY, name); if (rbytes > 0 && rbytes < nbytes) return (set_errbytes(rbytes, nbytes)); return (rbytes); } ssize_t mdb_writesym(const void *buf, size_t nbytes, const char *name) { return (mdb_tgt_writesym(mdb.m_target, MDB_TGT_AS_VIRT, buf, nbytes, MDB_TGT_OBJ_EVERY, name)); } ssize_t mdb_readvar(void *buf, const char *name) { GElf_Sym sym; if (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EVERY, name, &sym, NULL)) return (-1); if (mdb_tgt_vread(mdb.m_target, buf, sym.st_size, (uintptr_t)sym.st_value) == sym.st_size) return ((ssize_t)sym.st_size); return (-1); } ssize_t mdb_writevar(const void *buf, const char *name) { GElf_Sym sym; if (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EVERY, name, &sym, NULL)) return (-1); if (mdb_tgt_vwrite(mdb.m_target, buf, sym.st_size, (uintptr_t)sym.st_value) == sym.st_size) return ((ssize_t)sym.st_size); return (-1); } int mdb_lookup_by_name(const char *name, GElf_Sym *sym) { return (mdb_lookup_by_obj(MDB_TGT_OBJ_EVERY, name, sym)); } int mdb_lookup_by_obj(const char *obj, const char *name, GElf_Sym *sym) { return (mdb_tgt_lookup_by_name(mdb.m_target, obj, name, sym, NULL)); } int mdb_lookup_by_addr(uintptr_t addr, uint_t flags, char *buf, size_t nbytes, GElf_Sym *sym) { return (mdb_tgt_lookup_by_addr(mdb.m_target, addr, flags, buf, nbytes, sym, NULL)); } int mdb_getareg(mdb_tid_t tid, const char *rname, mdb_reg_t *rp) { return (mdb_tgt_getareg(mdb.m_target, tid, rname, rp)); } int mdb_thread_name(mdb_tid_t tid, char *buf, size_t bufsize) { return (mdb_tgt_thread_name(mdb.m_target, tid, buf, bufsize)); } static u_longlong_t mdb_strtoull_int(const char *s, int radix) { if (s[0] == '0') { switch (s[1]) { case 'I': case 'i': radix = 2; s += 2; break; case 'O': case 'o': radix = 8; s += 2; break; case 'T': case 't': radix = 10; s += 2; break; case 'X': case 'x': radix = 16; s += 2; break; } } return (mdb_strtonum(s, radix)); } u_longlong_t mdb_strtoullx(const char *s, mdb_strtoull_flags_t flags) { int radix; if ((flags & ~MDB_STRTOULL_F_BASE_C) != 0) { mdb_warn("invalid options specified: 0x%lx" PRIx64 "\n", (uint64_t)flags); return ((uintmax_t)ULLONG_MAX); } if ((flags & MDB_STRTOULL_F_BASE_C) != 0) { radix = 10; } else { radix = mdb.m_radix; } return (mdb_strtoull_int(s, radix)); } u_longlong_t mdb_strtoull(const char *s) { return (mdb_strtoull_int(s, mdb.m_radix)); } size_t mdb_snprintf(char *buf, size_t nbytes, const char *format, ...) { va_list alist; va_start(alist, format); nbytes = mdb_iob_vsnprintf(buf, nbytes, format, alist); va_end(alist); return (nbytes); } void mdb_printf(const char *format, ...) { va_list alist; va_start(alist, format); mdb_iob_vprintf(mdb.m_out, format, alist); va_end(alist); } void mdb_warn(const char *format, ...) { va_list alist; va_start(alist, format); vwarn(format, alist); va_end(alist); } void mdb_flush(void) { mdb_iob_flush(mdb.m_out); } /* * Convert an object of len bytes pointed to by srcraw between * network-order and host-order and store in dstraw. The length len must * be the actual length of the objects pointed to by srcraw and dstraw (or * zero) or the results are undefined. srcraw and dstraw may be the same, * in which case the object is converted in-place. Note that this routine * will convert from host-order to network-order or network-order to * host-order, since the conversion is the same in either case. */ /* ARGSUSED */ void mdb_nhconvert(void *dstraw, const void *srcraw, size_t len) { #ifdef _LITTLE_ENDIAN uint8_t b1, b2; uint8_t *dst, *src; size_t i; dst = (uint8_t *)dstraw; src = (uint8_t *)srcraw; for (i = 0; i < len / 2; i++) { b1 = src[i]; b2 = src[len - i - 1]; dst[i] = b2; dst[len - i - 1] = b1; } #else if (dstraw != srcraw) bcopy(srcraw, dstraw, len); #endif } /* * Bit formatting functions: Note the interesting use of UM_GC here to * allocate a buffer for the caller which will be automatically freed * when the dcmd completes or is forcibly aborted. */ #define NBNB (NBBY / 2) /* number of bits per nibble */ #define SETBIT(buf, j, c) { \ if (((j) + 1) % (NBNB + 1) == 0) \ (buf)[(j)++] = ' '; \ (buf)[(j)++] = (c); \ } const char * mdb_one_bit(int width, int bit, int on) { int i, j = 0; char *buf; buf = mdb_zalloc(width + (width / NBNB) + 2, UM_GC | UM_SLEEP); for (i = --width; i > bit; i--) SETBIT(buf, j, '.'); SETBIT(buf, j, on ? '1' : '0'); for (i = bit - 1; i >= 0; i--) SETBIT(buf, j, '.'); return (buf); } const char * mdb_inval_bits(int width, int start, int stop) { int i, j = 0; char *buf; buf = mdb_zalloc(width + (width / NBNB) + 2, UM_GC | UM_SLEEP); for (i = --width; i > stop; i--) SETBIT(buf, j, '.'); for (i = stop; i >= start; i--) SETBIT(buf, j, 'x'); for (; i >= 0; i--) SETBIT(buf, j, '.'); return (buf); } ulong_t mdb_inc_indent(ulong_t i) { if (mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT) { ulong_t margin = mdb_iob_getmargin(mdb.m_out); mdb_iob_margin(mdb.m_out, margin + i); return (margin); } mdb_iob_margin(mdb.m_out, i); mdb_iob_setflags(mdb.m_out, MDB_IOB_INDENT); return (0); } ulong_t mdb_dec_indent(ulong_t i) { if (mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT) { ulong_t margin = mdb_iob_getmargin(mdb.m_out); if (margin < i || margin - i == 0) { mdb_iob_clrflags(mdb.m_out, MDB_IOB_INDENT); mdb_iob_margin(mdb.m_out, MDB_IOB_DEFMARGIN); } else mdb_iob_margin(mdb.m_out, margin - i); return (margin); } return (0); } int mdb_eval(const char *s) { mdb_frame_t *ofp = mdb.m_fmark; mdb_frame_t *fp = mdb.m_frame; int err; if (s == NULL) return (set_errno(EINVAL)); /* * Push m_in down onto the input stack, then set m_in to point to the * i/o buffer for our command string, and reset the frame marker. * The mdb_run() function returns when the new m_in iob reaches EOF. */ mdb_iob_stack_push(&fp->f_istk, mdb.m_in, yylineno); mdb.m_in = mdb_iob_create(mdb_strio_create(s), MDB_IOB_RDONLY); mdb.m_fmark = NULL; err = mdb_run(); mdb.m_fmark = ofp; /* * Now pop the old standard input stream and restore mdb.m_in and * the parser's saved current line number. */ mdb.m_in = mdb_iob_stack_pop(&fp->f_istk); yylineno = mdb_iob_lineno(mdb.m_in); /* * If mdb_run() returned an error, propagate this backward * up the stack of debugger environment frames. */ if (MDB_ERR_IS_FATAL(err)) longjmp(fp->f_pcb, err); if (err == MDB_ERR_PAGER || err == MDB_ERR_SIGINT) return (set_errno(EMDB_CANCEL)); if (err != 0) return (set_errno(EMDB_EVAL)); return (0); } void mdb_set_dot(uintmax_t addr) { mdb_nv_set_value(mdb.m_dot, addr); mdb.m_incr = 0; } uintmax_t mdb_get_dot(void) { return (mdb_nv_get_value(mdb.m_dot)); } static int walk_step(mdb_wcb_t *wcb) { mdb_wcb_t *nwcb = wcb->w_lyr_head; int status; /* * If the control block has no layers, we just invoke the walker's * step function and return status indicating whether to continue * or stop. If the control block has layers, we need to invoke * ourself recursively for the next layer, until eventually we * percolate down to an unlayered walk. */ if (nwcb == NULL) return (wcb->w_walker->iwlk_step(&wcb->w_state)); if ((status = walk_step(nwcb)) != WALK_NEXT) { wcb->w_lyr_head = nwcb->w_lyr_link; nwcb->w_lyr_link = NULL; mdb_wcb_destroy(nwcb); } if (status == WALK_DONE && wcb->w_lyr_head != NULL) return (WALK_NEXT); return (status); } static int walk_common(mdb_wcb_t *wcb) { int status, rval = 0; mdb_frame_t *pfp; /* * Enter the control block in the active list so that mdb can clean * up after it in case we abort out of the current command. */ if ((pfp = mdb_list_prev(mdb.m_frame)) != NULL && pfp->f_pcmd != NULL) mdb_wcb_insert(wcb, pfp); else mdb_wcb_insert(wcb, mdb.m_frame); /* * The per-walk constructor performs private buffer initialization * and locates whatever symbols are necessary. */ if ((status = wcb->w_walker->iwlk_init(&wcb->w_state)) != WALK_NEXT) { if (status != WALK_DONE) rval = set_errno(EMDB_WALKINIT); goto done; } /* * Mark wcb to indicate that walk_init has been called (which means * we can call walk_fini if the walk is aborted at this point). */ wcb->w_inited = TRUE; while (walk_step(wcb) == WALK_NEXT) continue; done: if ((pfp = mdb_list_prev(mdb.m_frame)) != NULL && pfp->f_pcmd != NULL) mdb_wcb_delete(wcb, pfp); else mdb_wcb_delete(wcb, mdb.m_frame); mdb_wcb_destroy(wcb); return (rval); } typedef struct pwalk_step { mdb_walk_cb_t ps_cb; void *ps_private; } pwalk_step_t; static int pwalk_step(uintptr_t addr, const void *data, void *private) { pwalk_step_t *psp = private; int ret; mdb.m_frame->f_cbactive = B_TRUE; ret = psp->ps_cb(addr, data, psp->ps_private); mdb.m_frame->f_cbactive = B_FALSE; return (ret); } int mdb_pwalk(const char *name, mdb_walk_cb_t func, void *private, uintptr_t addr) { mdb_iwalker_t *iwp = mdb_walker_lookup(name); pwalk_step_t p; if (func == NULL) return (set_errno(EINVAL)); p.ps_cb = func; p.ps_private = private; if (iwp != NULL) { int ret; int cbactive = mdb.m_frame->f_cbactive; mdb.m_frame->f_cbactive = B_FALSE; ret = walk_common(mdb_wcb_create(iwp, pwalk_step, &p, addr)); mdb.m_frame->f_cbactive = cbactive; return (ret); } return (-1); /* errno is set for us */ } int mdb_walk(const char *name, mdb_walk_cb_t func, void *data) { return (mdb_pwalk(name, func, data, 0)); } /*ARGSUSED*/ static int walk_dcmd(uintptr_t addr, const void *ignored, dcmd_walk_arg_t *dwp) { int status; mdb.m_frame->f_cbactive = B_TRUE; status = call_idcmd(dwp->dw_dcmd, addr, 1, dwp->dw_flags, &dwp->dw_argv); mdb.m_frame->f_cbactive = B_FALSE; if (status == DCMD_USAGE || status == DCMD_ABORT) return (WALK_ERR); dwp->dw_flags &= ~DCMD_LOOPFIRST; return (WALK_NEXT); } static int i_mdb_pwalk_dcmd(const char *wname, const char *dcname, int argc, const mdb_arg_t *argv, uintptr_t addr, uint_t flags) { mdb_argvec_t args; dcmd_walk_arg_t dw; mdb_iwalker_t *iwp; mdb_wcb_t *wcb; int status; if (wname == NULL || dcname == NULL) return (set_errno(EINVAL)); if ((dw.dw_dcmd = mdb_dcmd_lookup(dcname)) == NULL) return (-1); /* errno is set for us */ if ((iwp = mdb_walker_lookup(wname)) == NULL) return (-1); /* errno is set for us */ args.a_data = (mdb_arg_t *)argv; args.a_nelems = args.a_size = argc; mdb_argvec_create(&dw.dw_argv); mdb_argvec_copy(&dw.dw_argv, &args); dw.dw_flags = flags | DCMD_LOOP | DCMD_LOOPFIRST | DCMD_ADDRSPEC; wcb = mdb_wcb_create(iwp, (mdb_walk_cb_t)walk_dcmd, &dw, addr); status = walk_common(wcb); mdb_argvec_zero(&dw.dw_argv); mdb_argvec_destroy(&dw.dw_argv); return (status); } int mdb_pwalk_dcmd(const char *wname, const char *dcname, int argc, const mdb_arg_t *argv, uintptr_t addr) { return (i_mdb_pwalk_dcmd(wname, dcname, argc, argv, addr, 0)); } int mdb_fpwalk_dcmd(const char *wname, const char *dcname, int argc, const mdb_arg_t *argv, uintptr_t addr, uint_t flags) { return (i_mdb_pwalk_dcmd(wname, dcname, argc, argv, addr, flags)); } int mdb_walk_dcmd(const char *wname, const char *dcname, int argc, const mdb_arg_t *argv) { return (i_mdb_pwalk_dcmd(wname, dcname, argc, argv, 0, 0)); } /*ARGSUSED*/ static int layered_walk_step(uintptr_t addr, const void *data, mdb_wcb_t *wcb) { /* * Prior to calling the top-level walker's step function, reset its * mdb_walk_state_t walk_addr and walk_layer members to refer to the * target virtual address and data buffer of the underlying object. */ wcb->w_state.walk_addr = addr; wcb->w_state.walk_layer = data; return (wcb->w_walker->iwlk_step(&wcb->w_state)); } int mdb_layered_walk(const char *wname, mdb_walk_state_t *wsp) { mdb_wcb_t *cwcb, *wcb; mdb_iwalker_t *iwp; if (wname == NULL || wsp == NULL) return (set_errno(EINVAL)); if ((iwp = mdb_walker_lookup(wname)) == NULL) return (-1); /* errno is set for us */ if ((cwcb = mdb_wcb_from_state(wsp)) == NULL) return (set_errno(EMDB_BADWCB)); if (cwcb->w_walker == iwp) return (set_errno(EMDB_WALKLOOP)); wcb = mdb_wcb_create(iwp, (mdb_walk_cb_t)layered_walk_step, cwcb, wsp->walk_addr); if (iwp->iwlk_init(&wcb->w_state) != WALK_NEXT) { mdb_wcb_destroy(wcb); return (set_errno(EMDB_WALKINIT)); } wcb->w_inited = TRUE; mdb_dprintf(MDB_DBG_WALK, "added %s`%s as %s`%s layer\n", iwp->iwlk_modp->mod_name, iwp->iwlk_name, cwcb->w_walker->iwlk_modp->mod_name, cwcb->w_walker->iwlk_name); if (cwcb->w_lyr_head != NULL) { for (cwcb = cwcb->w_lyr_head; cwcb->w_lyr_link != NULL; ) cwcb = cwcb->w_lyr_link; cwcb->w_lyr_link = wcb; } else cwcb->w_lyr_head = wcb; return (0); } int mdb_call_dcmd(const char *name, uintptr_t dot, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_idcmd_t *idcp; mdb_argvec_t args; int status; if (name == NULL || argc < 0) return (set_errno(EINVAL)); if ((idcp = mdb_dcmd_lookup(name)) == NULL) return (-1); /* errno is set for us */ args.a_data = (mdb_arg_t *)argv; args.a_nelems = args.a_size = argc; status = call_idcmd(idcp, dot, 1, flags, &args); if (status == DCMD_ERR || status == DCMD_ABORT) return (set_errno(EMDB_DCFAIL)); if (status == DCMD_USAGE) return (set_errno(EMDB_DCUSAGE)); return (0); } /* * When dcmds or walkers call a dcmd that might be in another module, * we need to set mdb.m_frame->f_cp to an mdb_cmd that represents the * dcmd we're currently executing, otherwise mdb_get_module gets the * module of the caller instead of the module for the current dcmd. */ static int call_idcmd(mdb_idcmd_t *idcp, uintmax_t addr, uintmax_t count, uint_t flags, mdb_argvec_t *argv) { mdb_cmd_t *save_cp; mdb_cmd_t cmd; int ret; bzero(&cmd, sizeof (cmd)); cmd.c_dcmd = idcp; cmd.c_argv = *argv; save_cp = mdb.m_frame->f_cp; mdb.m_frame->f_cp = &cmd; ret = mdb_call_idcmd(cmd.c_dcmd, addr, count, flags, &cmd.c_argv, NULL, NULL); mdb.m_frame->f_cp = save_cp; return (ret); } int mdb_add_walker(const mdb_walker_t *wp) { mdb_module_t *mp; if (mdb.m_lmod == NULL) { mdb_cmd_t *cp = mdb.m_frame->f_cp; mp = cp->c_dcmd->idc_modp; } else mp = mdb.m_lmod; return (mdb_module_add_walker(mp, wp, 0)); } int mdb_remove_walker(const char *name) { mdb_module_t *mp; if (mdb.m_lmod == NULL) { mdb_cmd_t *cp = mdb.m_frame->f_cp; mp = cp->c_dcmd->idc_modp; } else mp = mdb.m_lmod; return (mdb_module_remove_walker(mp, name)); } void mdb_get_pipe(mdb_pipe_t *p) { mdb_cmd_t *cp = mdb.m_frame->f_cp; mdb_addrvec_t *adp = &cp->c_addrv; if (p == NULL) { warn("dcmd failure: mdb_get_pipe invoked with NULL pointer\n"); longjmp(mdb.m_frame->f_pcb, MDB_ERR_API); } if (adp->ad_nelems != 0) { ASSERT(adp->ad_ndx != 0); p->pipe_data = &adp->ad_data[adp->ad_ndx - 1]; p->pipe_len = adp->ad_nelems - adp->ad_ndx + 1; adp->ad_ndx = adp->ad_nelems; } else { p->pipe_data = NULL; p->pipe_len = 0; } } void mdb_set_pipe(const mdb_pipe_t *p) { mdb_cmd_t *cp = mdb.m_frame->f_pcmd; if (p == NULL) { warn("dcmd failure: mdb_set_pipe invoked with NULL pointer\n"); longjmp(mdb.m_frame->f_pcb, MDB_ERR_API); } if (cp != NULL) { size_t nbytes = sizeof (uintptr_t) * p->pipe_len; mdb_cmd_reset(cp); cp->c_addrv.ad_data = mdb_alloc(nbytes, UM_SLEEP); bcopy(p->pipe_data, cp->c_addrv.ad_data, nbytes); cp->c_addrv.ad_nelems = p->pipe_len; cp->c_addrv.ad_size = p->pipe_len; } } ssize_t mdb_get_xdata(const char *name, void *buf, size_t nbytes) { return (mdb_tgt_getxdata(mdb.m_target, name, buf, nbytes)); } /* * Private callback structure for implementing mdb_object_iter, below. */ typedef struct { mdb_object_cb_t oi_cb; void *oi_arg; int oi_rval; } object_iter_arg_t; /*ARGSUSED*/ static int mdb_object_cb(void *data, const mdb_map_t *map, const char *fullname) { object_iter_arg_t *arg = data; mdb_object_t obj; if (arg->oi_rval != 0) return (0); bzero(&obj, sizeof (obj)); obj.obj_base = map->map_base; obj.obj_name = strbasename(map->map_name); obj.obj_size = map->map_size; obj.obj_fullname = fullname; arg->oi_rval = arg->oi_cb(&obj, arg->oi_arg); return (0); } int mdb_object_iter(mdb_object_cb_t cb, void *data) { object_iter_arg_t arg; arg.oi_cb = cb; arg.oi_arg = data; arg.oi_rval = 0; if (mdb_tgt_object_iter(mdb.m_target, mdb_object_cb, &arg) != 0) return (-1); return (arg.oi_rval); } /* * Private callback structure for implementing mdb_symbol_iter, below. */ typedef struct { mdb_symbol_cb_t si_cb; void *si_arg; int si_rval; } symbol_iter_arg_t; /*ARGSUSED*/ static int mdb_symbol_cb(void *data, const GElf_Sym *gsym, const char *name, const mdb_syminfo_t *sip, const char *obj) { symbol_iter_arg_t *arg = data; mdb_symbol_t sym; if (arg->si_rval != 0) return (0); bzero(&sym, sizeof (sym)); sym.sym_name = name; sym.sym_object = obj; sym.sym_sym = gsym; sym.sym_table = sip->sym_table; sym.sym_id = sip->sym_id; arg->si_rval = arg->si_cb(&sym, arg->si_arg); return (0); } int mdb_symbol_iter(const char *obj, uint_t which, uint_t type, mdb_symbol_cb_t cb, void *data) { symbol_iter_arg_t arg; arg.si_cb = cb; arg.si_arg = data; arg.si_rval = 0; if (mdb_tgt_symbol_iter(mdb.m_target, obj, which, type, mdb_symbol_cb, &arg) != 0) return (-1); return (arg.si_rval); } /* * Private structure and function for implementing mdb_dumpptr on top * of mdb_dump_internal */ typedef struct dptrdat { mdb_dumpptr_cb_t func; void *arg; } dptrdat_t; static ssize_t mdb_dump_aux_ptr(void *buf, size_t nbyte, uint64_t offset, void *arg) { dptrdat_t *dat = arg; return (dat->func(buf, nbyte, offset, dat->arg)); } /* * Private structure and function for handling callbacks which return * EMDB_PARTIAL */ typedef struct d64dat { mdb_dump64_cb_t func; void *arg; } d64dat_t; static ssize_t mdb_dump_aux_partial(void *buf, size_t nbyte, uint64_t offset, void *arg) { d64dat_t *dat = arg; int result; int count; result = dat->func(buf, nbyte, offset, dat->arg); if (result == -1 && errno == EMDB_PARTIAL) { count = 0; do { result = dat->func((char *)buf + count, 1, offset + count, dat->arg); if (result == 1) count++; } while (count < nbyte && result == 1); if (count) result = count; } return (result); } /* Default callback for mdb_dumpptr() is calling mdb_vread(). */ static ssize_t mdb_dumpptr_cb(void *buf, size_t nbytes, uintptr_t addr, void *arg __unused) { return (mdb_vread(buf, nbytes, addr)); } int mdb_dumpptr(uintptr_t addr, size_t len, uint_t flags, mdb_dumpptr_cb_t fp, void *arg) { dptrdat_t dat; d64dat_t dat64; if (fp == NULL) dat.func = mdb_dumpptr_cb; else dat.func = fp; dat.arg = arg; dat64.func = mdb_dump_aux_ptr; dat64.arg = &dat; return (mdb_dump_internal(addr, len, flags, mdb_dump_aux_partial, &dat64, sizeof (uintptr_t))); } int mdb_dump64(uint64_t addr, uint64_t len, uint_t flags, mdb_dump64_cb_t fp, void *arg) { d64dat_t dat64; dat64.func = fp; dat64.arg = arg; return (mdb_dump_internal(addr, len, flags, mdb_dump_aux_partial, &dat64, sizeof (uint64_t))); } int mdb_get_state(void) { mdb_tgt_status_t ts; (void) mdb_tgt_status(mdb.m_target, &ts); return (ts.st_state); } void * mdb_callback_add(int class, mdb_callback_f fp, void *arg) { mdb_module_t *m; if (class != MDB_CALLBACK_STCHG && class != MDB_CALLBACK_PROMPT) { (void) set_errno(EINVAL); return (NULL); } if (mdb.m_lmod != NULL) m = mdb.m_lmod; else m = mdb.m_frame->f_cp->c_dcmd->idc_modp; return (mdb_callb_add(m, class, fp, arg)); } void mdb_callback_remove(void *hdl) { mdb_callb_remove(hdl); } /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013 by Delphix. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright 2023 RackTop Systems, Inc. * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. * Copyright 2025 Oxide Computer Company */ #ifndef _MDB_MODAPI_H #define _MDB_MODAPI_H /* * MDB Module API * * The debugger provides a set of interfaces for use in writing loadable * debugger modules. Modules that call functions not listed in this header * file may not be compatible with future versions of the debugger. */ #include #include #include #ifdef __cplusplus extern "C" { #endif /* * Make sure that TRUE, FALSE, MIN, and MAX have the usual definitions * so module writers can depend on these macros and defines. * Make sure NULL is available to module writers by including . */ #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #endif #ifndef MAX #define MAX(x, y) ((x) > (y) ? (x) : (y)) #endif #define MDB_API_VERSION 5 /* Current API version number */ /* * Debugger command function flags: */ #define DCMD_ADDRSPEC 0x01 /* Dcmd invoked with explicit address */ #define DCMD_LOOP 0x02 /* Dcmd invoked in loop with ,cnt syntax */ #define DCMD_LOOPFIRST 0x04 /* Dcmd invoked as first iteration of LOOP */ #define DCMD_PIPE 0x08 /* Dcmd invoked with input from pipe */ #define DCMD_PIPE_OUT 0x10 /* Dcmd invoked with output set to pipe */ #define DCMD_HDRSPEC(fl) (((fl) & DCMD_LOOPFIRST) || !((fl) & DCMD_LOOP)) /* * Debugger tab command function flags */ #define DCMD_TAB_SPACE 0x01 /* Tab cb invoked with trailing space */ /* * Debugger command function return values: */ #define DCMD_OK 0 /* Dcmd completed successfully */ #define DCMD_ERR 1 /* Dcmd failed due to an error */ #define DCMD_USAGE 2 /* Dcmd usage error; abort and print usage */ #define DCMD_NEXT 3 /* Invoke next dcmd in precedence list */ #define DCMD_ABORT 4 /* Dcmd failed; abort current loop or pipe */ #define OFFSETOF(s, m) (size_t)(&(((s *)0)->m)) extern int mdb_prop_postmortem; /* Are we looking at a static dump? */ extern int mdb_prop_kernel; /* Are we looking at a kernel? */ typedef enum { MDB_TYPE_STRING, /* a_un.a_str is valid */ MDB_TYPE_IMMEDIATE, /* a_un.a_val is valid */ MDB_TYPE_CHAR /* a_un.a_char is valid */ } mdb_type_t; typedef struct mdb_arg { mdb_type_t a_type; union { const char *a_str; uintmax_t a_val; char a_char; } a_un; } mdb_arg_t; typedef struct mdb_tab_cookie mdb_tab_cookie_t; typedef int mdb_dcmd_f(uintptr_t, uint_t, int, const mdb_arg_t *); typedef int mdb_dcmd_tab_f(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); typedef struct mdb_dcmd { const char *dc_name; /* Command name */ const char *dc_usage; /* Usage message (optional) */ const char *dc_descr; /* Description */ mdb_dcmd_f *dc_funcp; /* Command function */ void (*dc_help)(void); /* Command help function (or NULL) */ mdb_dcmd_tab_f *dc_tabp; /* Tab completion function */ } mdb_dcmd_t; #define WALK_ERR -1 /* Walk fatal error (terminate walk) */ #define WALK_NEXT 0 /* Walk should continue to next step */ #define WALK_DONE 1 /* Walk is complete (no errors) */ typedef int (*mdb_walk_cb_t)(uintptr_t, const void *, void *); typedef struct mdb_walk_state { mdb_walk_cb_t walk_callback; /* Callback to issue */ void *walk_cbdata; /* Callback private data */ uintptr_t walk_addr; /* Current address */ void *walk_data; /* Walk private data */ void *walk_arg; /* Walk private argument */ const void *walk_layer; /* Data from underlying layer */ } mdb_walk_state_t; typedef struct mdb_walker { const char *walk_name; /* Walk type name */ const char *walk_descr; /* Walk description */ int (*walk_init)(mdb_walk_state_t *); /* Walk constructor */ int (*walk_step)(mdb_walk_state_t *); /* Walk iterator */ void (*walk_fini)(mdb_walk_state_t *); /* Walk destructor */ void *walk_init_arg; /* Walk constructor argument */ } mdb_walker_t; typedef struct mdb_modinfo { ushort_t mi_dvers; /* Debugger version number */ const mdb_dcmd_t *mi_dcmds; /* NULL-terminated list of dcmds */ const mdb_walker_t *mi_walkers; /* NULL-terminated list of walks */ } mdb_modinfo_t; typedef struct mdb_bitmask { const char *bm_name; /* String name to print */ u_longlong_t bm_mask; /* Mask for bits */ u_longlong_t bm_bits; /* Result required for value & mask */ } mdb_bitmask_t; typedef struct mdb_pipe { uintptr_t *pipe_data; /* Array of pipe values */ size_t pipe_len; /* Array length */ } mdb_pipe_t; typedef struct mdb_object { const char *obj_name; /* name of object */ const char *obj_fullname; /* full name of object */ uintptr_t obj_base; /* base address of object */ uintptr_t obj_size; /* in memory size of object in bytes */ } mdb_object_t; typedef struct mdb_symbol { const char *sym_name; /* name of symbol */ const char *sym_object; /* name of containing object */ const GElf_Sym *sym_sym; /* ELF symbol information */ uint_t sym_table; /* symbol table id */ uint_t sym_id; /* symbol identifier */ } mdb_symbol_t; extern int mdb_pwalk(const char *, mdb_walk_cb_t, void *, uintptr_t); extern int mdb_walk(const char *, mdb_walk_cb_t, void *); extern int mdb_pwalk_dcmd(const char *, const char *, int, const mdb_arg_t *, uintptr_t); extern int mdb_fpwalk_dcmd(const char *, const char *, int, const mdb_arg_t *, uintptr_t, uint_t); extern int mdb_walk_dcmd(const char *, const char *, int, const mdb_arg_t *); extern int mdb_layered_walk(const char *, mdb_walk_state_t *); extern int mdb_call_dcmd(const char *, uintptr_t, uint_t, int, const mdb_arg_t *); extern int mdb_add_walker(const mdb_walker_t *); extern int mdb_remove_walker(const char *); extern ssize_t mdb_vread(void *, size_t, uintptr_t); extern ssize_t mdb_vwrite(const void *, size_t, uintptr_t); extern ssize_t mdb_aread(void *, size_t, uintptr_t, void *); extern ssize_t mdb_awrite(const void *, size_t, uintptr_t, void *); extern ssize_t mdb_fread(void *, size_t, uintptr_t); extern ssize_t mdb_fwrite(const void *, size_t, uintptr_t); extern ssize_t mdb_pread(void *, size_t, uint64_t); extern ssize_t mdb_pwrite(const void *, size_t, uint64_t); extern ssize_t mdb_readstr(char *, size_t, uintptr_t); extern ssize_t mdb_writestr(const char *, uintptr_t); extern ssize_t mdb_readsym(void *, size_t, const char *); extern ssize_t mdb_writesym(const void *, size_t, const char *); extern ssize_t mdb_readvar(void *, const char *); extern ssize_t mdb_writevar(const void *, const char *); #define MDB_SYM_NAMLEN 1024 /* Recommended max name len */ #define MDB_SYM_FUZZY 0 /* Match closest address */ #define MDB_SYM_EXACT 1 /* Match exact address only */ #define MDB_OBJ_EXEC ((const char *)0L) /* Primary executable file */ #define MDB_OBJ_RTLD ((const char *)1L) /* Run-time link-editor */ #define MDB_OBJ_EVERY ((const char *)-1L) /* All known symbols */ extern int mdb_lookup_by_name(const char *, GElf_Sym *); extern int mdb_lookup_by_obj(const char *, const char *, GElf_Sym *); extern int mdb_lookup_by_addr(uintptr_t, uint_t, char *, size_t, GElf_Sym *); typedef uintptr_t mdb_tid_t; typedef uint64_t mdb_reg_t; extern int mdb_getareg(mdb_tid_t, const char *, mdb_reg_t *); extern int mdb_thread_name(mdb_tid_t, char *, size_t); #define MDB_OPT_SETBITS 1 /* Set specified flag bits */ #define MDB_OPT_CLRBITS 2 /* Clear specified flag bits */ #define MDB_OPT_STR 3 /* const char * argument */ #define MDB_OPT_UINTPTR 4 /* uintptr_t argument */ #define MDB_OPT_UINT64 5 /* uint64_t argument */ #define MDB_OPT_UINTPTR_SET 6 /* boolean_t+uintptr_t args */ extern int mdb_getopts(int, const mdb_arg_t *, ...) __sentinel(0); extern u_longlong_t mdb_strtoull(const char *); extern u_longlong_t mdb_argtoull(const mdb_arg_t *); #define UM_NOSLEEP 0x0 /* Do not call failure handler; may fail */ #define UM_SLEEP 0x1 /* Can block for memory; will always succeed */ #define UM_GC 0x2 /* Garbage-collect this block automatically */ extern void *mdb_alloc(size_t, uint_t); extern void *mdb_zalloc(size_t, uint_t); extern void mdb_free(void *, size_t); #define MDB_NICENUM_BUFLEN 6 extern int mdb_snprintfrac(char *, int, uint64_t, uint64_t, int); extern void mdb_nicenum(uint64_t, char *); extern void mdb_nicetime(int64_t, char *, size_t); extern size_t mdb_snprintf(char *, size_t, const char *, ...); extern void mdb_printf(const char *, ...); extern void mdb_warn(const char *, ...); extern void mdb_flush(void); extern int mdb_ffs(uintmax_t); extern void mdb_nhconvert(void *, const void *, size_t); #define MDB_DUMP_RELATIVE 0x0001 /* Start numbering at 0 */ #define MDB_DUMP_ALIGN 0x0002 /* Enforce paragraph alignment */ #define MDB_DUMP_PEDANT 0x0004 /* Full-width addresses */ #define MDB_DUMP_ASCII 0x0008 /* Display ASCII values */ #define MDB_DUMP_HEADER 0x0010 /* Display a header */ #define MDB_DUMP_TRIM 0x0020 /* Trim at boundaries */ #define MDB_DUMP_SQUISH 0x0040 /* Eliminate redundant lines */ #define MDB_DUMP_NEWDOT 0x0080 /* Update dot when done */ #define MDB_DUMP_ENDIAN 0x0100 /* Adjust for endianness */ #define MDB_DUMP_WIDTH(x) ((((x) - 1) & 0xf) << 16) /* paragraphs/line */ #define MDB_DUMP_GROUP(x) ((((x) - 1) & 0xff) << 20) /* bytes/group */ typedef ssize_t (*mdb_dumpptr_cb_t)(void *, size_t, uintptr_t, void *); typedef ssize_t (*mdb_dump64_cb_t)(void *, size_t, uint64_t, void *); extern int mdb_dumpptr(uintptr_t, size_t, uint_t, mdb_dumpptr_cb_t, void *); extern int mdb_dump64(uint64_t, uint64_t, uint_t, mdb_dump64_cb_t, void *); extern const char *mdb_one_bit(int, int, int); extern const char *mdb_inval_bits(int, int, int); extern ulong_t mdb_inc_indent(ulong_t); extern ulong_t mdb_dec_indent(ulong_t); extern int mdb_eval(const char *); extern void mdb_set_dot(uintmax_t); extern uintmax_t mdb_get_dot(void); extern void mdb_get_pipe(mdb_pipe_t *); extern void mdb_set_pipe(const mdb_pipe_t *); extern ssize_t mdb_get_xdata(const char *, void *, size_t); typedef int (*mdb_object_cb_t)(mdb_object_t *, void *); extern int mdb_object_iter(mdb_object_cb_t, void *); #define MDB_SYMTAB 1 /* Normal symbol table (.symtab) */ #define MDB_DYNSYM 2 /* Dynamic symbol table (.dynsym) */ #define MDB_BIND_LOCAL 0x0001 /* Local (static-scope) symbols */ #define MDB_BIND_GLOBAL 0x0002 /* Global symbols */ #define MDB_BIND_WEAK 0x0004 /* Weak binding symbols */ #define MDB_BIND_ANY 0x0007 /* Any of the above */ #define MDB_TYPE_NOTYPE 0x0100 /* Symbol has no type */ #define MDB_TYPE_OBJECT 0x0200 /* Symbol refers to data */ #define MDB_TYPE_FUNC 0x0400 /* Symbol refers to text */ #define MDB_TYPE_SECT 0x0800 /* Symbol refers to a section */ #define MDB_TYPE_FILE 0x1000 /* Symbol refers to a source file */ #define MDB_TYPE_COMMON 0x2000 /* Symbol refers to a common block */ #define MDB_TYPE_TLS 0x4000 /* Symbol refers to TLS */ #define MDB_TYPE_ANY 0x7f00 /* Any of the above */ typedef int (*mdb_symbol_cb_t)(mdb_symbol_t *, void *); extern int mdb_symbol_iter(const char *, uint_t, uint_t, mdb_symbol_cb_t, void *); #define MDB_STATE_IDLE 0 /* Target is idle (not running yet) */ #define MDB_STATE_RUNNING 1 /* Target is currently executing */ #define MDB_STATE_STOPPED 2 /* Target is stopped */ #define MDB_STATE_UNDEAD 3 /* Target is undead (zombie) */ #define MDB_STATE_DEAD 4 /* Target is dead (core dump) */ #define MDB_STATE_LOST 5 /* Target lost by debugger */ extern int mdb_get_state(void); #define MDB_CALLBACK_STCHG 1 #define MDB_CALLBACK_PROMPT 2 typedef void (*mdb_callback_f)(void *); extern void *mdb_callback_add(int, mdb_callback_f, void *); extern void mdb_callback_remove(void *); #define MDB_TABC_ALL_TYPES 0x1 /* Include array types in type output */ #define MDB_TABC_MEMBERS 0x2 /* Tab comp. types with members */ #define MDB_TABC_NOPOINT 0x4 /* Tab comp. everything but pointers */ #define MDB_TABC_NOARRAY 0x8 /* Don't include array data in output */ /* * Module's interaction path */ extern void mdb_tab_insert(mdb_tab_cookie_t *, const char *); extern void mdb_tab_setmbase(mdb_tab_cookie_t *, const char *); /* * Tab completion utility functions for modules. */ extern int mdb_tab_complete_type(mdb_tab_cookie_t *, const char *, uint_t); extern int mdb_tab_complete_member(mdb_tab_cookie_t *, const char *, const char *); extern int mdb_tab_typename(int *, const mdb_arg_t **, char *buf, size_t len); /* * Tab completion functions for common signatures. */ extern int mdb_tab_complete_mt(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); extern size_t strlcat(char *, const char *, size_t); extern char *strcat(char *, const char *); extern char *strcpy(char *, const char *); extern char *strncpy(char *, const char *, size_t); /* Need to be consistent with C++ definitions */ #if __cplusplus >= 199711L extern const char *strchr(const char *, int); #ifndef _STRCHR_INLINE #define _STRCHR_INLINE extern "C++" { inline char *strchr(char *__s, int __c) { return (char *)strchr((const char *)__s, __c); } } #endif /* _STRCHR_INLINE */ extern const char *strrchr(const char *, int); #ifndef _STRRCHR_INLINE #define _STRRCHR_INLINE extern "C++" { inline char *strrchr(char *__s, int __c) { return (char *)strrchr((const char *)__s, __c); } } #endif /* _STRRCHR_INLINE */ extern const char *strstr(const char *, const char *); #ifndef _STRSTR_INLINE #define _STRSTR_INLINE extern "C++" { inline char *strstr(char *__s1, const char *__s2) { return (char *)strstr((const char *)__s1, __s2); } } #endif /* _STRSTR_INLINE */ #else extern char *strchr(const char *, int); extern char *strrchr(const char *, int); extern char *strstr(const char *, const char *); #endif /* __cplusplus >= 199711L */ extern int strcmp(const char *, const char *); extern int strncmp(const char *, const char *, size_t); extern int strcasecmp(const char *, const char *); extern int strncasecmp(const char *, const char *, size_t); extern size_t strlen(const char *); extern int bcmp(const void *, const void *, size_t); extern void bcopy(const void *, void *, size_t); extern void bzero(void *, size_t); extern void *memcpy(void *, const void *, size_t); extern void *memmove(void *, const void *, size_t); extern int memcmp(const void *, const void *, size_t); /* Need to be consistent with C++ definitions */ #if __cplusplus >= 199711L extern const void *memchr(const void *, int, size_t); #ifndef _MEMCHR_INLINE #define _MEMCHR_INLINE extern "C++" { inline void *memchr(void * __s, int __c, size_t __n) { return (void *)memchr((const void *)__s, __c, __n); } } #endif /* _MEMCHR_INLINE */ #else extern void *memchr(const void *, int, size_t); #endif /* __cplusplus >= 199711L */ extern void *memset(void *, int, size_t); extern void *memccpy(void *, const void *, int, size_t); extern void *bsearch(const void *, const void *, size_t, size_t, int (*)(const void *, const void *)); extern void qsort(void *, size_t, size_t, int (*)(const void *, const void *)); #ifdef __cplusplus } #endif #endif /* _MDB_MODAPI_H */ /* * 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 #include #include #include #include #include #include #include #include #include /* * The format of an mdb dcmd changed between MDB_API_VERSION 3 and 4, with an * addition of a new field to the public interface. To maintain backwards * compatibility with older versions, we know to keep around the old version of * the structure so we can correctly read the set of dcmds passed in. */ typedef struct mdb_dcmd_v3 { const char *dco_name; /* Command name */ const char *dco_usage; /* Usage message (optional) */ const char *dco_descr; /* Description */ mdb_dcmd_f *dco_funcp; /* Command function */ void (*dco_help)(void); /* Command help function (or NULL) */ } mdb_dcmd_v3_t; /* * For builtin modules, we set mod_init to this function, which just * returns a constant modinfo struct with no dcmds and walkers. */ static const mdb_modinfo_t * builtin_init(void) { static const mdb_modinfo_t info = { MDB_API_VERSION }; return (&info); } int mdb_module_validate_name(const char *name, const char **errmsgp) { if (strlen(name) == 0) { *errmsgp = "no module name was specified\n"; return (0); } if (strlen(name) > MDB_NV_NAMELEN) { *errmsgp = "module name '%s' exceeds name length limit\n"; return (0); } if (strbadid(name) != NULL) { *errmsgp = "module name '%s' contains illegal characters\n"; return (0); } if (mdb_nv_lookup(&mdb.m_modules, name) != NULL) { *errmsgp = "%s module is already loaded\n"; return (0); } return (1); } int mdb_module_create(const char *name, const char *fname, int mode, mdb_module_t **mpp) { static const mdb_walker_t empty_walk_list[] = { 0 }; static const mdb_dcmd_t empty_dcmd_list[] = { 0 }; int dlmode = (mode & MDB_MOD_GLOBAL) ? RTLD_GLOBAL : RTLD_LOCAL; const mdb_modinfo_t *info; const mdb_dcmd_t *dcp; const mdb_walker_t *wp; const mdb_dcmd_v3_t *dcop; mdb_dcmd_t *dctp = NULL; mdb_module_t *mod; mod = mdb_zalloc(sizeof (mdb_module_t), UM_SLEEP); mod->mod_info = mdb_alloc(sizeof (mdb_modinfo_t), UM_SLEEP); (void) mdb_nv_create(&mod->mod_dcmds, UM_SLEEP); (void) mdb_nv_create(&mod->mod_walkers, UM_SLEEP); mod->mod_name = strdup(name); mdb.m_lmod = mod; /* Mark module as currently loading */ if (!(mode & MDB_MOD_BUILTIN)) { mdb_dprintf(MDB_DBG_MODULE, "dlopen %s %x\n", fname, dlmode); mod->mod_hdl = dlmopen(LM_ID_BASE, fname, RTLD_NOW | dlmode); if (mod->mod_hdl == NULL) { warn("%s\n", dlerror()); goto err; } mod->mod_init = (const mdb_modinfo_t *(*)(void)) dlsym(mod->mod_hdl, "_mdb_init"); mod->mod_fini = (void (*)(void)) dlsym(mod->mod_hdl, "_mdb_fini"); mod->mod_tgt_ctor = (mdb_tgt_ctor_f *) dlsym(mod->mod_hdl, "_mdb_tgt_create"); mod->mod_dis_ctor = (mdb_dis_ctor_f *) dlsym(mod->mod_hdl, "_mdb_dis_create"); if (!(mdb.m_flags & MDB_FL_NOCTF)) mod->mod_ctfp = mdb_ctf_open(fname, NULL); } else { #ifdef _KMDB /* * mdb_ks is a special case - a builtin with _mdb_init and * _mdb_fini routines. If we don't hack it in here, we'll have * to duplicate most of the module creation code elsewhere. */ if (strcmp(name, "mdb_ks") == 0) mod->mod_init = mdb_ks_init; else #endif mod->mod_init = builtin_init; } if (mod->mod_init == NULL) { warn("%s module is missing _mdb_init definition\n", name); goto err; } if ((info = mod->mod_init()) == NULL) { warn("%s module failed to initialize\n", name); goto err; } /* * Reject modules compiled for a newer version of the debugger. */ if (info->mi_dvers > MDB_API_VERSION) { warn("%s module requires newer mdb API version (%hu) than " "debugger (%d)\n", name, info->mi_dvers, MDB_API_VERSION); goto err; } /* * Load modules compiled for the current API version. */ #if MDB_API_VERSION != 5 #error "MDB_API_VERSION needs to be checked here" #endif switch (info->mi_dvers) { case MDB_API_VERSION: case 4: case 3: case 2: case 1: /* * Current API version -- copy entire modinfo * structure into our own private storage. */ bcopy(info, mod->mod_info, sizeof (mdb_modinfo_t)); if (mod->mod_info->mi_dcmds == NULL) mod->mod_info->mi_dcmds = empty_dcmd_list; if (mod->mod_info->mi_walkers == NULL) mod->mod_info->mi_walkers = empty_walk_list; break; default: /* * Too old to be compatible -- abort the load. */ warn("%s module is compiled for obsolete mdb API " "version %hu\n", name, info->mi_dvers); goto err; } /* * In MDB_API_VERSION 4, the size of the mdb_dcmd_t struct changed. If * our module is from an earlier version, we need to walk it in the old * structure and convert it to the new one. * * Note that we purposefully don't predicate on whether or not we have * the empty list case and duplicate it anyways. That case is rare and * it makes our logic simpler when we need to unload the module. */ if (info->mi_dvers < 4) { int ii = 0; for (dcop = (mdb_dcmd_v3_t *)&mod->mod_info->mi_dcmds[0]; dcop->dco_name != NULL; dcop++) ii++; /* Don't forget null terminated one at the end */ dctp = mdb_zalloc(sizeof (mdb_dcmd_t) * (ii + 1), UM_SLEEP); ii = 0; for (dcop = (mdb_dcmd_v3_t *)&mod->mod_info->mi_dcmds[0]; dcop->dco_name != NULL; dcop++, ii++) { dctp[ii].dc_name = dcop->dco_name; dctp[ii].dc_usage = dcop->dco_usage; dctp[ii].dc_descr = dcop->dco_descr; dctp[ii].dc_funcp = dcop->dco_funcp; dctp[ii].dc_help = dcop->dco_help; dctp[ii].dc_tabp = NULL; } mod->mod_info->mi_dcmds = dctp; } /* * Before we actually go ahead with the load, we need to check * each dcmd and walk structure for any invalid values: */ for (dcp = &mod->mod_info->mi_dcmds[0]; dcp->dc_name != NULL; dcp++) { if (strbadid(dcp->dc_name) != NULL) { warn("dcmd name '%s' contains illegal characters\n", dcp->dc_name); goto err; } if (dcp->dc_descr == NULL) { warn("dcmd '%s' must have a description\n", dcp->dc_name); goto err; } if (dcp->dc_funcp == NULL) { warn("dcmd '%s' has a NULL function pointer\n", dcp->dc_name); goto err; } } for (wp = &mod->mod_info->mi_walkers[0]; wp->walk_name != NULL; wp++) { if (strbadid(wp->walk_name) != NULL) { warn("walk name '%s' contains illegal characters\n", wp->walk_name); goto err; } if (wp->walk_descr == NULL) { warn("walk '%s' must have a description\n", wp->walk_name); goto err; } if (wp->walk_step == NULL) { warn("walk '%s' has a NULL walk_step function\n", wp->walk_name); goto err; } } /* * Now that we've established that there are no problems, * we can go ahead and hash the module, and its dcmds and walks: */ (void) mdb_nv_insert(&mdb.m_modules, mod->mod_name, NULL, (uintptr_t)mod, MDB_NV_RDONLY|MDB_NV_EXTNAME); for (dcp = &mod->mod_info->mi_dcmds[0]; dcp->dc_name != NULL; dcp++) { if (mdb_module_add_dcmd(mod, dcp, mode) == -1) warn("failed to load dcmd %s`%s", name, dcp->dc_name); } for (wp = &mod->mod_info->mi_walkers[0]; wp->walk_name != NULL; wp++) { if (mdb_module_add_walker(mod, wp, mode) == -1) warn("failed to load walk %s`%s", name, wp->walk_name); } /* * Add the module to the end of the list of modules in load-dependency * order. We maintain this list so we can unload in reverse order. */ if (mdb.m_mtail != NULL) { ASSERT(mdb.m_mtail->mod_next == NULL); mdb.m_mtail->mod_next = mod; mod->mod_prev = mdb.m_mtail; mdb.m_mtail = mod; } else { ASSERT(mdb.m_mhead == NULL); mdb.m_mtail = mdb.m_mhead = mod; } mdb.m_lmod = NULL; if (mpp != NULL) *mpp = mod; return (0); err: mdb_whatis_unregister_module(mod); if (mod->mod_ctfp != NULL) ctf_close(mod->mod_ctfp); if (mod->mod_hdl != NULL) (void) dlclose(mod->mod_hdl); mdb_nv_destroy(&mod->mod_dcmds); mdb_nv_destroy(&mod->mod_walkers); strfree((char *)mod->mod_name); mdb_free(mod->mod_info, sizeof (mdb_modinfo_t)); mdb_free(mod, sizeof (mdb_module_t)); mdb.m_lmod = NULL; return (-1); } mdb_module_t * mdb_module_load_builtin(const char *name) { mdb_module_t *mp; if (mdb_module_create(name, NULL, MDB_MOD_BUILTIN, &mp) < 0) return (NULL); return (mp); } int mdb_module_unload_common(const char *name) { mdb_var_t *v = mdb_nv_lookup(&mdb.m_modules, name); mdb_module_t *mod; const mdb_dcmd_t *dcp; if (v == NULL) return (set_errno(EMDB_NOMOD)); mod = mdb_nv_get_cookie(v); if (mod == &mdb.m_rmod || mod->mod_hdl == NULL) return (set_errno(EMDB_BUILTINMOD)); mdb_dprintf(MDB_DBG_MODULE, "unloading %s\n", name); if (mod->mod_fini != NULL) { mdb_dprintf(MDB_DBG_MODULE, "calling %s`_mdb_fini\n", name); mod->mod_fini(); } mdb_whatis_unregister_module(mod); if (mod->mod_ctfp != NULL) ctf_close(mod->mod_ctfp); if (mod->mod_cb != NULL) mdb_callb_remove_by_mod(mod); if (mod->mod_prev == NULL) { ASSERT(mdb.m_mhead == mod); mdb.m_mhead = mod->mod_next; } else mod->mod_prev->mod_next = mod->mod_next; if (mod->mod_next == NULL) { ASSERT(mdb.m_mtail == mod); mdb.m_mtail = mod->mod_prev; } else mod->mod_next->mod_prev = mod->mod_prev; while (mdb_nv_size(&mod->mod_walkers) != 0) { mdb_nv_rewind(&mod->mod_walkers); v = mdb_nv_peek(&mod->mod_walkers); (void) mdb_module_remove_walker(mod, mdb_nv_get_name(v)); } while (mdb_nv_size(&mod->mod_dcmds) != 0) { mdb_nv_rewind(&mod->mod_dcmds); v = mdb_nv_peek(&mod->mod_dcmds); (void) mdb_module_remove_dcmd(mod, mdb_nv_get_name(v)); } v = mdb_nv_lookup(&mdb.m_modules, name); ASSERT(v != NULL); mdb_nv_remove(&mdb.m_modules, v); (void) dlclose(mod->mod_hdl); mdb_nv_destroy(&mod->mod_walkers); mdb_nv_destroy(&mod->mod_dcmds); strfree((char *)mod->mod_name); if (mod->mod_info->mi_dvers < 4) { int ii = 0; for (dcp = &mod->mod_info->mi_dcmds[0]; dcp->dc_name != NULL; dcp++) ii++; mdb_free((void *)mod->mod_info->mi_dcmds, sizeof (mdb_dcmd_t) * (ii + 1)); } mdb_free(mod->mod_info, sizeof (mdb_modinfo_t)); mdb_free(mod, sizeof (mdb_module_t)); return (0); } int mdb_module_add_dcmd(mdb_module_t *mod, const mdb_dcmd_t *dcp, int flags) { mdb_var_t *v = mdb_nv_lookup(&mod->mod_dcmds, dcp->dc_name); mdb_idcmd_t *idcp; uint_t nflag = MDB_NV_OVERLOAD | MDB_NV_SILENT; if (flags & MDB_MOD_FORCE) nflag |= MDB_NV_INTERPOS; if (v != NULL) return (set_errno(EMDB_DCMDEXISTS)); idcp = mdb_alloc(sizeof (mdb_idcmd_t), UM_SLEEP); idcp->idc_usage = dcp->dc_usage; idcp->idc_descr = dcp->dc_descr; idcp->idc_help = dcp->dc_help; idcp->idc_funcp = dcp->dc_funcp; idcp->idc_tabp = dcp->dc_tabp; idcp->idc_modp = mod; v = mdb_nv_insert(&mod->mod_dcmds, dcp->dc_name, NULL, (uintptr_t)idcp, MDB_NV_SILENT | MDB_NV_RDONLY); idcp->idc_name = mdb_nv_get_name(v); idcp->idc_var = mdb_nv_insert(&mdb.m_dcmds, idcp->idc_name, NULL, (uintptr_t)v, nflag); mdb_dprintf(MDB_DBG_DCMD, "added dcmd %s`%s\n", mod->mod_name, idcp->idc_name); return (0); } int mdb_module_remove_dcmd(mdb_module_t *mod, const char *dname) { mdb_var_t *v = mdb_nv_lookup(&mod->mod_dcmds, dname); mdb_idcmd_t *idcp; mdb_cmd_t *cp; if (v == NULL) return (set_errno(EMDB_NODCMD)); mdb_dprintf(MDB_DBG_DCMD, "removed dcmd %s`%s\n", mod->mod_name, dname); idcp = mdb_nv_get_cookie(v); /* * If we're removing a dcmd that is part of the most recent command, * we need to free mdb.m_lastcp so we don't attempt to execute some * text we've removed from our address space if -o repeatlast is set. */ for (cp = mdb_list_next(&mdb.m_lastc); cp; cp = mdb_list_next(cp)) { if (cp->c_dcmd == idcp) { while ((cp = mdb_list_next(&mdb.m_lastc)) != NULL) { mdb_list_delete(&mdb.m_lastc, cp); mdb_cmd_destroy(cp); } break; } } mdb_nv_remove(&mdb.m_dcmds, idcp->idc_var); mdb_nv_remove(&mod->mod_dcmds, v); mdb_free(idcp, sizeof (mdb_idcmd_t)); return (0); } /*ARGSUSED*/ static int default_walk_init(mdb_walk_state_t *wsp) { return (WALK_NEXT); } /*ARGSUSED*/ static void default_walk_fini(mdb_walk_state_t *wsp) { /* Nothing to do here */ } int mdb_module_add_walker(mdb_module_t *mod, const mdb_walker_t *wp, int flags) { mdb_var_t *v = mdb_nv_lookup(&mod->mod_walkers, wp->walk_name); mdb_iwalker_t *iwp; uint_t nflag = MDB_NV_OVERLOAD | MDB_NV_SILENT; if (flags & MDB_MOD_FORCE) nflag |= MDB_NV_INTERPOS; if (v != NULL) return (set_errno(EMDB_WALKEXISTS)); if (wp->walk_descr == NULL || wp->walk_step == NULL) return (set_errno(EINVAL)); iwp = mdb_alloc(sizeof (mdb_iwalker_t), UM_SLEEP); iwp->iwlk_descr = strdup(wp->walk_descr); iwp->iwlk_init = wp->walk_init; iwp->iwlk_step = wp->walk_step; iwp->iwlk_fini = wp->walk_fini; iwp->iwlk_init_arg = wp->walk_init_arg; iwp->iwlk_modp = mod; if (iwp->iwlk_init == NULL) iwp->iwlk_init = default_walk_init; if (iwp->iwlk_fini == NULL) iwp->iwlk_fini = default_walk_fini; v = mdb_nv_insert(&mod->mod_walkers, wp->walk_name, NULL, (uintptr_t)iwp, MDB_NV_SILENT | MDB_NV_RDONLY); iwp->iwlk_name = mdb_nv_get_name(v); iwp->iwlk_var = mdb_nv_insert(&mdb.m_walkers, iwp->iwlk_name, NULL, (uintptr_t)v, nflag); mdb_dprintf(MDB_DBG_WALK, "added walk %s`%s\n", mod->mod_name, iwp->iwlk_name); return (0); } int mdb_module_remove_walker(mdb_module_t *mod, const char *wname) { mdb_var_t *v = mdb_nv_lookup(&mod->mod_walkers, wname); mdb_iwalker_t *iwp; if (v == NULL) return (set_errno(EMDB_NOWALK)); mdb_dprintf(MDB_DBG_WALK, "removed walk %s`%s\n", mod->mod_name, wname); iwp = mdb_nv_get_cookie(v); mdb_nv_remove(&mdb.m_walkers, iwp->iwlk_var); mdb_nv_remove(&mod->mod_walkers, v); strfree(iwp->iwlk_descr); mdb_free(iwp, sizeof (mdb_iwalker_t)); return (0); } void mdb_module_unload_all(int mode) { mdb_module_t *mod, *pmod; /* * We unload modules in the reverse order in which they were loaded * so as to allow _mdb_fini routines to invoke code which may be * present in a previously-loaded module (such as mdb_ks, etc.). */ for (mod = mdb.m_mtail; mod != NULL; mod = pmod) { pmod = mod->mod_prev; (void) mdb_module_unload(mod->mod_name, mode); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 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. */ #ifndef _MDB_MODULE_H #define _MDB_MODULE_H #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB struct mdb_callb; typedef struct mdb_module { mdb_nv_t mod_dcmds; /* Module dcmds hash */ mdb_nv_t mod_walkers; /* Module walkers hash */ const char *mod_name; /* Module name */ void *mod_hdl; /* Module object handle */ mdb_modinfo_t *mod_info; /* Module information */ const mdb_modinfo_t *(*mod_init)(void); /* Module load callback */ void (*mod_fini)(void); /* Module unload callback */ mdb_tgt_ctor_f *mod_tgt_ctor; /* Module target constructor */ mdb_dis_ctor_f *mod_dis_ctor; /* Module disassembler constructor */ struct mdb_module *mod_prev; /* Previous module on dependency list */ struct mdb_module *mod_next; /* Next module on dependency list */ ctf_file_t *mod_ctfp; /* CTF container for this module */ struct mdb_callb *mod_cb; /* First callback for this module */ } mdb_module_t; typedef struct mdb_idcmd { const char *idc_name; /* Backpointer to variable name */ const char *idc_usage; /* Usage message */ const char *idc_descr; /* Description */ mdb_dcmd_f *idc_funcp; /* Command function */ void (*idc_help)(void); /* Help function */ mdb_dcmd_tab_f *idc_tabp; /* Tab completion pointer */ mdb_module_t *idc_modp; /* Backpointer to module */ mdb_var_t *idc_var; /* Backpointer to global variable */ } mdb_idcmd_t; typedef struct mdb_iwalker { const char *iwlk_name; /* Walk type name */ char *iwlk_descr; /* Walk description */ int (*iwlk_init)(struct mdb_walk_state *); /* Walk constructor */ int (*iwlk_step)(struct mdb_walk_state *); /* Walk iterator */ void (*iwlk_fini)(struct mdb_walk_state *); /* Walk destructor */ void *iwlk_init_arg; /* Walk constructor argument */ mdb_module_t *iwlk_modp; /* Backpointer to module */ mdb_var_t *iwlk_var; /* Backpointer to global variable */ } mdb_iwalker_t; #define MDB_MOD_LOCAL 0x00 /* Load module RTLD_LOCAL */ #define MDB_MOD_GLOBAL 0x01 /* Load module RTLD_GLOBAL */ #define MDB_MOD_SILENT 0x02 /* Remain silent if no module found */ #define MDB_MOD_FORCE 0x04 /* Forcibly interpose module defs */ #define MDB_MOD_BUILTIN 0x08 /* Module is compiled into debugger */ #define MDB_MOD_DEFER 0x10 /* Defer load/unload (kmdb only) */ extern int mdb_module_load(const char *, int); extern mdb_module_t *mdb_module_load_builtin(const char *); extern void mdb_module_load_all(int); extern int mdb_module_unload(const char *, int); extern void mdb_module_unload_all(int); extern int mdb_module_unload_common(const char *); extern int mdb_module_add_dcmd(mdb_module_t *, const mdb_dcmd_t *, int); extern int mdb_module_remove_dcmd(mdb_module_t *, const char *); extern int mdb_module_add_walker(mdb_module_t *, const mdb_walker_t *, int); extern int mdb_module_remove_walker(mdb_module_t *, const char *); extern int mdb_module_create(const char *, const char *, int, mdb_module_t **); extern int mdb_module_validate_name(const char *, const char **); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_MODULE_H */ /* * 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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright 2021 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include int mdb_module_load(const char *name, int mode) { const char *wformat = "no module '%s' could be found\n"; const char *fullname = NULL; char buf[MAXPATHLEN], *p, *q; int i; ASSERT(!(mode & MDB_MOD_DEFER)); if (strchr(name, '/') != NULL) { ASSERT(!(mode & MDB_MOD_BUILTIN)); (void) mdb_iob_snprintf(buf, sizeof (buf), "%s", strbasename(name)); /* * Remove any .so(.[0-9]+)? suffix */ if ((p = strrchr(buf, '.')) != NULL) { for (q = p + 1; isdigit(*q); q++) ; if (*q == '\0') { if (q > p + 1) { /* found digits to remove */ *p = '\0'; } } if ((p = strrchr(buf, '.')) != NULL) { if (strcmp(p, ".so") == 0) { *p = '\0'; } } } fullname = name; name = buf; } if (!mdb_module_validate_name(name, &wformat)) goto err; if (fullname != NULL) { if (access(fullname, F_OK) != 0) { name = fullname; /* for warn() below */ goto err; } return (mdb_module_create(name, fullname, mode, NULL)); } /* * If a simple name is specified, search for it in the module path. * The module path is searched in order, and for each element we * look for the following files: * * 1. If the module name ends in ".so(.[0-9]+)?", search for the literal * name and then search for the name without the [0-9]+ suffix. * 2. If the module name ends in ".so", search for the literal name. * 3. Search for the module name with ".so" appended. * * Once a matching file is detected, we attempt to load that module * and do not resume our search in the case of an error. */ for (i = 0; mdb.m_lpath[i] != NULL; i++) { if ((p = strrchr(name, '.')) != NULL && *++p != '\0') { if (strisnum(p) || strcmp(p, "so") == 0) { (void) mdb_iob_snprintf(buf, sizeof (buf), "%s/%s", mdb.m_lpath[i], name); mdb_dprintf(MDB_DBG_MODULE, "checking for %s\n", buf); if (access(buf, F_OK) == 0) { return (mdb_module_create(name, buf, mode, NULL)); } } while (strisnum(p) && (p = strrchr(buf, '.')) != NULL) { *p = '\0'; /* strip trailing digits */ mdb_dprintf(MDB_DBG_MODULE, "checking for %s\n", buf); if (access(buf, F_OK) == 0) { return (mdb_module_create(name, buf, mode, NULL)); } } } (void) mdb_iob_snprintf(buf, sizeof (buf), "%s/%s.so", mdb.m_lpath[i], name); mdb_dprintf(MDB_DBG_MODULE, "checking for %s\n", buf); if (access(buf, F_OK) == 0) return (mdb_module_create(name, buf, mode, NULL)); } err: if (!(mode & MDB_MOD_SILENT)) warn(wformat, name); return (-1); } typedef struct mdb_modload_data { int mld_first; int mld_mode; } mdb_modload_data_t; /*ARGSUSED*/ static int module_load(void *fp, const mdb_map_t *map, const char *fullname) { mdb_modload_data_t *mld = fp; const char *name = strbasename(fullname); if (mdb_module_load(name, mld->mld_mode) == 0 && mdb.m_term != NULL) { if (mld->mld_first == TRUE) { mdb_iob_puts(mdb.m_out, "Loading modules: ["); mld->mld_first = FALSE; } mdb_iob_printf(mdb.m_out, " %s", name); mdb_iob_flush(mdb.m_out); } if (strstr(fullname, "/libc/") != NULL) { /* * A bit of a kludge: because we manage alternately capable * libc instances by mounting the appropriately capable * instance over /lib/libc.so.1, we may find that our object * list does not contain libc.so.1, but rather one of its * hwcap variants. Unfortunately, there is not a way of * getting from this shared object to the object that it is * effectively interposing on -- which means that without * special processing, we will not load any libc.so dmod. So * if we see that we have a shared object coming out of the * "libc" directory, we assume that we have a "libc-like" * object, and explicitly load the "libc.so" dmod. */ return (module_load(fp, map, "libc.so.1")); } if (strstr(fullname, "ld.so") != NULL) { /* * This is even more of a kludge. But if we find something has * basically tried to load ld, we will always implicitly load * the list dmod because several binaries and libraries just * build it as a .o and include it in their ELF object. */ (void) mdb_module_load("list", mld->mld_mode); } return (0); } void mdb_module_load_all(int mode) { uint_t oflag = mdb_iob_getflags(mdb.m_out) & MDB_IOB_PGENABLE; mdb_modload_data_t mld; mld.mld_first = TRUE; mld.mld_mode = mode | MDB_MOD_LOCAL | MDB_MOD_SILENT; mdb_iob_clrflags(mdb.m_out, oflag); (void) mdb_tgt_object_iter(mdb.m_target, module_load, &mld); if (mdb.m_term != NULL && mld.mld_first == FALSE) mdb_iob_puts(mdb.m_out, " ]\n"); mdb_iob_setflags(mdb.m_out, oflag); } /*ARGSUSED*/ int mdb_module_unload(const char *name, int mode) { ASSERT((mode & ~MDB_MOD_SILENT) == 0); return (mdb_module_unload_common(name)); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2018, Joyent, Inc. */ #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 enum { NM_FMT_INDEX = 0x0001, /* -f ndx */ NM_FMT_VALUE = 0x0002, /* -f val */ NM_FMT_SIZE = 0x0004, /* -f size */ NM_FMT_TYPE = 0x0008, /* -f type */ NM_FMT_BIND = 0x0010, /* -f bind */ NM_FMT_OTHER = 0x0020, /* -f oth */ NM_FMT_SHNDX = 0x0040, /* -f shndx */ NM_FMT_NAME = 0x0080, /* -f name */ NM_FMT_CTYPE = 0x0100, /* -f ctype */ NM_FMT_OBJECT = 0x0200, /* -f obj */ NM_FMT_CTFID = 0x1000 /* -f ctfid */ }; enum { NM_TYPE_NOTY = 1 << STT_NOTYPE, /* -t noty */ NM_TYPE_OBJT = 1 << STT_OBJECT, /* -t objt */ NM_TYPE_FUNC = 1 << STT_FUNC, /* -t func */ NM_TYPE_SECT = 1 << STT_SECTION, /* -t sect */ NM_TYPE_FILE = 1 << STT_FILE, /* -t file */ NM_TYPE_COMM = 1 << STT_COMMON, /* -t comm */ NM_TYPE_TLS = 1 << STT_TLS, /* -t tls */ NM_TYPE_REGI = 1 << STT_SPARC_REGISTER /* -t regi */ }; typedef struct { GElf_Sym nm_sym; const char *nm_name; mdb_syminfo_t nm_si; const char *nm_object; ctf_file_t *nm_fp; } nm_sym_t; typedef struct { ctf_file_t *nii_fp; uint_t nii_flags; uint_t nii_types; ulong_t nii_id; const char *nii_pfmt; const char *nii_ofmt; const GElf_Sym *nii_symp; nm_sym_t **nii_sympp; } nm_iter_info_t; typedef struct { mdb_tgt_sym_f *ngs_cb; void *ngs_arg; mdb_syminfo_t ngs_si; const char *ngs_object; } nm_gelf_symtab_t; typedef struct { uint_t noi_which; uint_t noi_type; mdb_tgt_sym_f *noi_cb; nm_iter_info_t *noi_niip; } nm_object_iter_t; static const char * nm_type2str(uchar_t info) { switch (GELF_ST_TYPE(info)) { case STT_NOTYPE: return ("NOTY"); case STT_OBJECT: return ("OBJT"); case STT_FUNC: return ("FUNC"); case STT_SECTION: return ("SECT"); case STT_FILE: return ("FILE"); case STT_COMMON: return ("COMM"); case STT_TLS: return ("TLS"); case STT_SPARC_REGISTER: return ("REGI"); default: return ("?"); } } static const char * nm_bind2str(uchar_t info) { switch (GELF_ST_BIND(info)) { case STB_LOCAL: return ("LOCL"); case STB_GLOBAL: return ("GLOB"); case STB_WEAK: return ("WEAK"); default: return ("?"); } } static const char * nm_sect2str(GElf_Half shndx) { static char buf[16]; switch (shndx) { case SHN_UNDEF: return ("UNDEF"); case SHN_ABS: return ("ABS"); case SHN_COMMON: return ("COMMON"); default: (void) mdb_iob_snprintf(buf, sizeof (buf), "%hu", shndx); return (buf); } } static char * nm_func_signature(ctf_file_t *fp, uint_t index, char *buf, size_t len) { int n; ctf_funcinfo_t f; ctf_id_t argv[32]; char arg[32]; char *start = buf; char *sep = ""; int i; if (ctf_func_info(fp, index, &f) == CTF_ERR) return (NULL); if (ctf_type_name(fp, f.ctc_return, arg, sizeof (arg)) != NULL) n = mdb_snprintf(buf, len, "%s (*)(", arg); else n = mdb_snprintf(buf, len, "<%ld> (*)(", f.ctc_return); if (len <= n) return (start); buf += n; len -= n; (void) ctf_func_args(fp, index, sizeof (argv) / sizeof (argv[0]), argv); for (i = 0; i < f.ctc_argc; i++) { if (ctf_type_name(fp, argv[i], arg, sizeof (arg)) != NULL) n = mdb_snprintf(buf, len, "%s%s", sep, arg); else n = mdb_snprintf(buf, len, "%s<%ld>", sep, argv[i]); if (len <= n) return (start); buf += n; len -= n; sep = ", "; } if (f.ctc_flags & CTF_FUNC_VARARG) { n = mdb_snprintf(buf, len, "%s...", sep); if (len <= n) return (start); buf += n; len -= n; } else if (f.ctc_argc == 0) { n = mdb_snprintf(buf, len, "void"); if (len <= n) return (start); buf += n; len -= n; } (void) mdb_snprintf(buf, len, ")"); return (start); } static void nm_print_ctype(void *data) { nm_iter_info_t *niip = data; char buf[256]; ctf_id_t id; char *str = NULL; uint_t index = niip->nii_id; ctf_file_t *fp = niip->nii_fp; if (fp != NULL) { if (GELF_ST_TYPE(niip->nii_symp->st_info) == STT_FUNC) str = nm_func_signature(fp, index, buf, sizeof (buf)); else if ((id = ctf_lookup_by_symbol(fp, index)) != CTF_ERR) str = ctf_type_name(fp, id, buf, sizeof (buf)); } if (str == NULL) str = ""; mdb_printf("%-50s", str); } static void nm_print_ctfid(void *data) { nm_iter_info_t *niip = data; ctf_id_t id; uint_t index = niip->nii_id; ctf_file_t *fp = niip->nii_fp; if (fp != NULL && (id = ctf_lookup_by_symbol(fp, index)) != CTF_ERR) { mdb_printf("%-9ld", id); } else { mdb_printf("%9s", ""); } } static void nm_print_obj(void *data) { const char *obj = (const char *)data; if (obj == MDB_TGT_OBJ_EXEC) obj = "exec"; else if (obj == MDB_TGT_OBJ_RTLD) obj = "rtld"; else if (obj == MDB_TGT_OBJ_EVERY) obj = ""; mdb_printf("%-15s", obj); } /*ARGSUSED*/ static int nm_print(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { nm_iter_info_t *niip = data; if (!((1 << GELF_ST_TYPE(sym->st_info)) & niip->nii_types)) return (0); niip->nii_id = sip->sym_id; niip->nii_symp = sym; mdb_table_print(niip->nii_flags, "|", MDB_TBL_PRNT, NM_FMT_INDEX, "%5u", sip->sym_id, MDB_TBL_FUNC, NM_FMT_OBJECT, nm_print_obj, obj, MDB_TBL_PRNT, NM_FMT_VALUE, niip->nii_pfmt, sym->st_value, MDB_TBL_PRNT, NM_FMT_SIZE, niip->nii_pfmt, sym->st_size, MDB_TBL_PRNT, NM_FMT_TYPE, "%-5s", nm_type2str(sym->st_info), MDB_TBL_PRNT, NM_FMT_BIND, "%-5s", nm_bind2str(sym->st_info), MDB_TBL_PRNT, NM_FMT_OTHER, niip->nii_ofmt, sym->st_other, MDB_TBL_PRNT, NM_FMT_SHNDX, "%-8s", nm_sect2str(sym->st_shndx), MDB_TBL_FUNC, NM_FMT_CTFID, nm_print_ctfid, niip, MDB_TBL_FUNC, NM_FMT_CTYPE, nm_print_ctype, niip, MDB_TBL_PRNT, NM_FMT_NAME, "%s", name, MDB_TBL_DONE); mdb_printf("\n"); return (0); } /*ARGSUSED*/ static int nm_any(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { return (nm_print(data, sym, name, sip, obj)); } /*ARGSUSED*/ static int nm_undef(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { if (sym->st_shndx == SHN_UNDEF) return (nm_print(data, sym, name, sip, obj)); return (0); } /*ARGSUSED*/ static int nm_asgn(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { const char *opts; switch (GELF_ST_TYPE(sym->st_info)) { case STT_FUNC: opts = "-f"; break; case STT_OBJECT: opts = "-o"; break; default: opts = ""; } mdb_printf("%#llr::nmadd %s -s %#llr %s\n", sym->st_value, opts, sym->st_size, name); return (0); } /*ARGSUSED*/ static int nm_cnt_any(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { size_t *cntp = (size_t *)data; (*cntp)++; return (0); } /*ARGSUSED*/ static int nm_cnt_undef(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { if (sym->st_shndx == SHN_UNDEF) return (nm_cnt_any(data, sym, name, sip, obj)); return (0); } /*ARGSUSED*/ static int nm_get_any(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { nm_iter_info_t *niip = data; nm_sym_t **sympp = niip->nii_sympp; (*sympp)->nm_sym = *sym; (*sympp)->nm_name = name; (*sympp)->nm_si = *sip; (*sympp)->nm_object = obj; (*sympp)->nm_fp = niip->nii_fp; (*sympp)++; return (0); } /*ARGSUSED*/ static int nm_get_undef(void *data, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { if (sym->st_shndx == SHN_UNDEF) return (nm_get_any(data, sym, name, sip, obj)); return (0); } static int nm_compare_name(const void *lp, const void *rp) { const nm_sym_t *lhs = (nm_sym_t *)lp; const nm_sym_t *rhs = (nm_sym_t *)rp; return (strcmp(lhs->nm_name, rhs->nm_name)); } static int nm_compare_val(const void *lp, const void *rp) { const nm_sym_t *lhs = (nm_sym_t *)lp; const nm_sym_t *rhs = (nm_sym_t *)rp; return (lhs->nm_sym.st_value < rhs->nm_sym.st_value ? -1 : (lhs->nm_sym.st_value > rhs->nm_sym.st_value ? 1 : 0)); } static int nm_gelf_symtab_cb(void *data, const GElf_Sym *symp, const char *name, uint_t id) { nm_gelf_symtab_t *ngsp = data; ngsp->ngs_si.sym_id = id; return (ngsp->ngs_cb(ngsp->ngs_arg, symp, name, &ngsp->ngs_si, ngsp->ngs_object)); } static void nm_gelf_symtab_iter(mdb_gelf_symtab_t *gst, const char *object, uint_t table, mdb_tgt_sym_f *cb, void *arg) { nm_gelf_symtab_t ngs; ngs.ngs_cb = cb; ngs.ngs_arg = arg; ngs.ngs_si.sym_table = table; ngs.ngs_object = object; mdb_gelf_symtab_iter(gst, nm_gelf_symtab_cb, &ngs); } static int nm_symbol_iter(const char *, uint_t, uint_t, mdb_tgt_sym_f *, nm_iter_info_t *); /*ARGSUSED*/ static int nm_object_iter_cb(void *data, const mdb_map_t *mp, const char *name) { nm_object_iter_t *noip = data; /* * Since we're interating over all the objects in a target, * don't return an error if we hit an object that we can't * get symbol data for. */ if (nm_symbol_iter(name, noip->noi_which, noip->noi_type, noip->noi_cb, noip->noi_niip) != 0) mdb_warn("unable to dump symbol data for: %s\n", name); return (0); } int nm_symbol_iter(const char *object, uint_t which, uint_t type, mdb_tgt_sym_f *cb, nm_iter_info_t *niip) { mdb_tgt_t *t = mdb.m_target; if (object == MDB_TGT_OBJ_EVERY) { nm_object_iter_t noi; noi.noi_which = which; noi.noi_type = type; noi.noi_cb = cb; noi.noi_niip = niip; return (mdb_tgt_object_iter(t, nm_object_iter_cb, &noi)); } niip->nii_fp = mdb_tgt_name_to_ctf(t, object); return (mdb_tgt_symbol_iter(t, object, which, type, cb, niip)); } /*ARGSUSED*/ int cmd_nm(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { enum { NM_DYNSYM = 0x0001, /* -D (use dynsym) */ NM_DEC = 0x0002, /* -d (decimal output) */ NM_GLOBAL = 0x0004, /* -g (globals only) */ NM_NOHDRS = 0x0008, /* -h (suppress header) */ NM_OCT = 0x0010, /* -o (octal output) */ NM_UNDEF = 0x0020, /* -u (undefs only) */ NM_HEX = 0x0040, /* -x (hex output) */ NM_SORT_NAME = 0x0080, /* -n (sort by name) */ NM_SORT_VALUE = 0x0100, /* -v (sort by value) */ NM_PRVSYM = 0x0200, /* -P (use private symtab) */ NM_PRTASGN = 0x0400 /* -p (print in asgn syntax) */ }; mdb_subopt_t opt_fmt_opts[] = { { NM_FMT_INDEX, "ndx" }, { NM_FMT_VALUE, "val" }, { NM_FMT_SIZE, "sz" }, { NM_FMT_TYPE, "type" }, { NM_FMT_BIND, "bind" }, { NM_FMT_OTHER, "oth" }, { NM_FMT_SHNDX, "shndx" }, { NM_FMT_NAME, "name" }, { NM_FMT_CTYPE, "ctype" }, { NM_FMT_OBJECT, "obj" }, { NM_FMT_CTFID, "ctfid" }, { 0, NULL } }; mdb_subopt_t opt_type_opts[] = { { NM_TYPE_NOTY, "noty" }, { NM_TYPE_OBJT, "objt" }, { NM_TYPE_FUNC, "func" }, { NM_TYPE_SECT, "sect" }, { NM_TYPE_FILE, "file" }, { NM_TYPE_COMM, "comm" }, { NM_TYPE_TLS, "tls" }, { NM_TYPE_REGI, "regi" }, { 0, NULL } }; uint_t optf = 0; uint_t opt_fmt; uint_t opt_types; int i; mdb_tgt_sym_f *callback; uint_t which, type; char *object = (char *)MDB_TGT_OBJ_EVERY; int hwidth; size_t nsyms = 0; nm_sym_t *syms = NULL, *symp; nm_iter_info_t nii; /* default output columns */ opt_fmt = NM_FMT_VALUE | NM_FMT_SIZE | NM_FMT_TYPE | NM_FMT_BIND | NM_FMT_OTHER | NM_FMT_SHNDX | NM_FMT_NAME; /* default output types */ opt_types = NM_TYPE_NOTY | NM_TYPE_OBJT | NM_TYPE_FUNC | NM_TYPE_SECT | NM_TYPE_FILE | NM_TYPE_COMM | NM_TYPE_TLS | NM_TYPE_REGI; i = mdb_getopts(argc, argv, 'D', MDB_OPT_SETBITS, NM_DYNSYM, &optf, 'P', MDB_OPT_SETBITS, NM_PRVSYM, &optf, 'd', MDB_OPT_SETBITS, NM_DEC, &optf, 'g', MDB_OPT_SETBITS, NM_GLOBAL, &optf, 'h', MDB_OPT_SETBITS, NM_NOHDRS, &optf, 'n', MDB_OPT_SETBITS, NM_SORT_NAME, &optf, 'o', MDB_OPT_SETBITS, NM_OCT, &optf, 'p', MDB_OPT_SETBITS, NM_PRTASGN | NM_NOHDRS, &optf, 'u', MDB_OPT_SETBITS, NM_UNDEF, &optf, 'v', MDB_OPT_SETBITS, NM_SORT_VALUE, &optf, 'x', MDB_OPT_SETBITS, NM_HEX, &optf, 'f', MDB_OPT_SUBOPTS, opt_fmt_opts, &opt_fmt, 't', MDB_OPT_SUBOPTS, opt_type_opts, &opt_types, NULL); if (i != argc) { if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); if (argc != 0 && (argc - i) == 1) { if (argv[i].a_type != MDB_TYPE_STRING || argv[i].a_un.a_str[0] == '-') return (DCMD_USAGE); else object = (char *)argv[i].a_un.a_str; } else return (DCMD_USAGE); } if ((optf & (NM_DEC | NM_HEX | NM_OCT)) == 0) { switch (mdb.m_radix) { case 8: optf |= NM_OCT; break; case 10: optf |= NM_DEC; break; default: optf |= NM_HEX; } } switch (optf & (NM_DEC | NM_HEX | NM_OCT)) { case NM_DEC: #ifdef _LP64 nii.nii_pfmt = "%-20llu"; nii.nii_ofmt = "%-5u"; hwidth = 20; #else nii.nii_pfmt = "%-10llu"; nii.nii_ofmt = "%-5u"; hwidth = 10; #endif break; case NM_HEX: #ifdef _LP64 nii.nii_pfmt = "0x%016llx"; nii.nii_ofmt = "0x%-3x"; hwidth = 18; #else nii.nii_pfmt = "0x%08llx"; nii.nii_ofmt = "0x%-3x"; hwidth = 10; #endif break; case NM_OCT: #ifdef _LP64 nii.nii_pfmt = "%-22llo"; nii.nii_ofmt = "%-5o"; hwidth = 22; #else nii.nii_pfmt = "%-11llo"; nii.nii_ofmt = "%-5o"; hwidth = 11; #endif break; default: mdb_warn("-d/-o/-x options are mutually exclusive\n"); return (DCMD_USAGE); } if (object != MDB_TGT_OBJ_EVERY && (optf & NM_PRVSYM)) { mdb_warn("-P/object options are mutually exclusive\n"); return (DCMD_USAGE); } if ((flags & DCMD_ADDRSPEC) && (optf & NM_PRVSYM)) { mdb_warn("-P/address options are mutually exclusive\n"); return (DCMD_USAGE); } if (!(optf & NM_NOHDRS)) { mdb_printf("%"); mdb_table_print(opt_fmt, " ", MDB_TBL_PRNT, NM_FMT_INDEX, "Index", MDB_TBL_PRNT, NM_FMT_OBJECT, "%-15s", "Object", MDB_TBL_PRNT, NM_FMT_VALUE, "%-*s", hwidth, "Value", MDB_TBL_PRNT, NM_FMT_SIZE, "%-*s", hwidth, "Size", MDB_TBL_PRNT, NM_FMT_TYPE, "%-5s", "Type", MDB_TBL_PRNT, NM_FMT_BIND, "%-5s", "Bind", MDB_TBL_PRNT, NM_FMT_OTHER, "%-5s", "Other", MDB_TBL_PRNT, NM_FMT_SHNDX, "%-8s", "Shndx", MDB_TBL_PRNT, NM_FMT_CTFID, "%-9s", "CTF ID", MDB_TBL_PRNT, NM_FMT_CTYPE, "%-50s", "C Type", MDB_TBL_PRNT, NM_FMT_NAME, "%s", "Name", MDB_TBL_DONE); mdb_printf("%\n"); } nii.nii_flags = opt_fmt; nii.nii_types = opt_types; if (optf & NM_DYNSYM) which = MDB_TGT_DYNSYM; else which = MDB_TGT_SYMTAB; if (optf & NM_GLOBAL) type = MDB_TGT_BIND_GLOBAL | MDB_TGT_TYPE_ANY; else type = MDB_TGT_BIND_ANY | MDB_TGT_TYPE_ANY; if (flags & DCMD_ADDRSPEC) optf |= NM_SORT_NAME; /* use sorting path if only one symbol */ if (optf & (NM_SORT_NAME | NM_SORT_VALUE)) { char name[MDB_SYM_NAMLEN]; GElf_Sym sym; mdb_syminfo_t si; if (optf & NM_UNDEF) callback = nm_cnt_undef; else callback = nm_cnt_any; if (flags & DCMD_ADDRSPEC) { const mdb_map_t *mp; /* gather relevant data for the specified addr */ nii.nii_fp = mdb_tgt_addr_to_ctf(mdb.m_target, addr); if (mdb_tgt_lookup_by_addr(mdb.m_target, addr, MDB_SYM_FUZZY, name, sizeof (name), &sym, &si) == -1) { mdb_warn("%lr", addr); return (DCMD_ERR); } if ((mp = mdb_tgt_addr_to_map(mdb.m_target, addr)) != NULL) { object = mdb_alloc(strlen(mp->map_name) + 1, UM_SLEEP | UM_GC); (void) strcpy(object, mp->map_name); /* * Try to find a better match for the syminfo. */ (void) mdb_tgt_lookup_by_name(mdb.m_target, object, name, &sym, &si); } (void) callback(&nsyms, &sym, name, &si, object); } else if (optf & NM_PRVSYM) { nsyms = mdb_gelf_symtab_size(mdb.m_prsym); } else { (void) mdb_tgt_symbol_iter(mdb.m_target, object, which, type, callback, &nsyms); } if (nsyms == 0) return (DCMD_OK); syms = symp = mdb_alloc(sizeof (nm_sym_t) * nsyms, UM_SLEEP | UM_GC); nii.nii_sympp = &symp; if (optf & NM_UNDEF) callback = nm_get_undef; else callback = nm_get_any; if (flags & DCMD_ADDRSPEC) { (void) callback(&nii, &sym, name, &si, object); } else if (optf & NM_PRVSYM) { nm_gelf_symtab_iter(mdb.m_prsym, object, MDB_TGT_PRVSYM, callback, &nii); } else if (nm_symbol_iter(object, which, type, callback, &nii) == -1) { mdb_warn("failed to iterate over symbols"); return (DCMD_ERR); } if (optf & NM_SORT_NAME) qsort(syms, nsyms, sizeof (nm_sym_t), nm_compare_name); else qsort(syms, nsyms, sizeof (nm_sym_t), nm_compare_val); } if ((optf & (NM_PRVSYM | NM_PRTASGN)) == (NM_PRVSYM | NM_PRTASGN)) callback = nm_asgn; else if (optf & NM_UNDEF) callback = nm_undef; else callback = nm_any; if (optf & (NM_SORT_NAME | NM_SORT_VALUE)) { for (symp = syms; nsyms-- != 0; symp++) { nii.nii_fp = symp->nm_fp; (void) callback(&nii, &symp->nm_sym, symp->nm_name, &symp->nm_si, symp->nm_object); } } else { if (optf & NM_PRVSYM) { nm_gelf_symtab_iter(mdb.m_prsym, object, MDB_TGT_PRVSYM, callback, &nii); } else if (nm_symbol_iter(object, which, type, callback, &nii) == -1) { mdb_warn("failed to iterate over symbols"); return (DCMD_ERR); } } return (DCMD_OK); } int cmd_nmadd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t opt_e = 0, opt_s = 0; uint_t opt_f = FALSE, opt_o = FALSE; GElf_Sym sym; int i; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); i = mdb_getopts(argc, argv, 'f', MDB_OPT_SETBITS, TRUE, &opt_f, 'o', MDB_OPT_SETBITS, TRUE, &opt_o, 'e', MDB_OPT_UINTPTR, &opt_e, 's', MDB_OPT_UINTPTR, &opt_s, NULL); if (i != (argc - 1) || argv[i].a_type != MDB_TYPE_STRING || argv[i].a_un.a_str[0] == '-' || argv[i].a_un.a_str[0] == '+') return (DCMD_USAGE); if (opt_e && opt_e < addr) { mdb_warn("end (%p) is less than start address (%p)\n", (void *)opt_e, (void *)addr); return (DCMD_USAGE); } if (mdb_gelf_symtab_lookup_by_name(mdb.m_prsym, argv[i].a_un.a_str, &sym, NULL) == -1) { bzero(&sym, sizeof (sym)); sym.st_info = GELF_ST_INFO(STB_GLOBAL, STT_NOTYPE); } if (opt_f) sym.st_info = GELF_ST_INFO(STB_GLOBAL, STT_FUNC); if (opt_o) sym.st_info = GELF_ST_INFO(STB_GLOBAL, STT_OBJECT); if (opt_e) sym.st_size = (GElf_Xword)(opt_e - addr); if (opt_s) sym.st_size = (GElf_Xword)(opt_s); sym.st_value = (GElf_Addr)addr; mdb_gelf_symtab_insert(mdb.m_prsym, argv[i].a_un.a_str, &sym); mdb_iob_printf(mdb.m_out, "added %s, value=%llr size=%llr\n", argv[i].a_un.a_str, sym.st_value, sym.st_size); return (DCMD_OK); } /*ARGSUSED*/ int cmd_nmdel(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *name; GElf_Sym sym; uint_t id; if (argc != 1 || argv->a_type != MDB_TYPE_STRING || argv->a_un.a_str[0] == '-' || (flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); name = argv->a_un.a_str; if (mdb_gelf_symtab_lookup_by_name(mdb.m_prsym, name, &sym, &id) == 0) { mdb_gelf_symtab_delete(mdb.m_prsym, name, &sym); mdb_printf("deleted %s, value=%llr size=%llr\n", name, sym.st_value, sym.st_size); return (DCMD_OK); } mdb_warn("symbol '%s' not found in private symbol table\n", name); return (DCMD_ERR); } void nm_help(void) { mdb_printf("-D print .dynsym instead of .symtab\n" "-P print private symbol table instead of .symtab\n" "-d print value and size in decimal\n" "-g only print global symbols\n" "-h suppress header line\n" "-n sort symbols by name\n" "-o print value and size in octal\n" "-p print symbols as a series of ::nmadd commands\n" "-u only print undefined symbols\n" "-v sort symbols by value\n" "-x print value and size in hexadecimal\n" "-f format use specified format\n" " ndx, val, sz, type, bind, oth, shndx, " "name, ctype, obj\n" "-t types display symbols with the specified types\n" " noty, objt, func, sect, file, regi\n" "obj specify object whose symbol table should be used\n"); } void nmadd_help(void) { mdb_printf("-f set type of symbol to STT_FUNC\n" "-o set type of symbol to STT_OBJECT\n" "-e end set size of symbol to end - start address\n" "-s size set size of symbol to explicit value\n" "name specify symbol name to add\n"); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_NM_H #define _MDB_NM_H #include #ifdef __cplusplus extern "C" { #endif extern int cmd_nm(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_nmadd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_nmdel(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nm_help(void); extern void nmadd_help(void); #ifdef __cplusplus } #endif #endif /* _MDB_NM_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2013 Josef 'Jeff' Sipek * Copyright 2024 Oxide Computer Company */ #include #include #include #include #include #include #define NV_NAME(v) \ (((v)->v_flags & MDB_NV_EXTNAME) ? (v)->v_ename : (v)->v_lname) #define NV_SIZE(v) \ (((v)->v_flags & MDB_NV_EXTNAME) ? sizeof (mdb_var_t) : \ sizeof (mdb_var_t) + strlen((v)->v_lname)) #define NV_HASHSZ 211 static size_t nv_hashstring(const char *key) { size_t g, h = 0; const char *p; ASSERT(key != NULL); for (p = key; *p != '\0'; p++) { h = (h << 4) + *p; if ((g = (h & 0xf0000000)) != 0) { h ^= (g >> 24); h ^= g; } } return (h); } static mdb_var_t * nv_var_alloc(const char *name, const mdb_nv_disc_t *disc, uintmax_t value, uint_t flags, uint_t um_flags, mdb_var_t *next) { size_t nbytes; mdb_var_t *v; if (flags & MDB_NV_EXTNAME) nbytes = sizeof (mdb_var_t); else nbytes = sizeof (mdb_var_t) + strlen(name); v = mdb_alloc(nbytes, um_flags); if (v == NULL) return (NULL); if (flags & MDB_NV_EXTNAME) { v->v_ename = name; v->v_lname[0] = '\0'; } else { /* * We don't overflow here since the mdb_var_t itself has * room for the trailing \0. */ (void) strcpy(v->v_lname, name); v->v_ename = NULL; } v->v_uvalue = value; v->v_flags = flags & ~(MDB_NV_SILENT | MDB_NV_INTERPOS); v->v_disc = disc; v->v_next = next; return (v); } static void nv_var_free(mdb_var_t *v, uint_t um_flags) { if (um_flags & UM_GC) return; if (v->v_flags & MDB_NV_OVERLOAD) { mdb_var_t *w, *nw; for (w = v->v_ndef; w != NULL; w = nw) { nw = w->v_ndef; mdb_free(w, NV_SIZE(w)); } } mdb_free(v, NV_SIZE(v)); } /* * Can return NULL only if the nv's memory allocation flags include UM_NOSLEEP */ mdb_nv_t * mdb_nv_create(mdb_nv_t *nv, uint_t um_flags) { nv->nv_hash = mdb_zalloc(sizeof (mdb_var_t *) * NV_HASHSZ, um_flags); if (nv->nv_hash == NULL) return (NULL); nv->nv_hashsz = NV_HASHSZ; nv->nv_nelems = 0; nv->nv_iter_elt = NULL; nv->nv_iter_bucket = 0; nv->nv_um_flags = um_flags; return (nv); } void mdb_nv_destroy(mdb_nv_t *nv) { mdb_var_t *v, *w; size_t i; if (nv->nv_um_flags & UM_GC) return; for (i = 0; i < nv->nv_hashsz; i++) { for (v = nv->nv_hash[i]; v != NULL; v = w) { w = v->v_next; nv_var_free(v, nv->nv_um_flags); } } mdb_free(nv->nv_hash, sizeof (mdb_var_t *) * nv->nv_hashsz); } mdb_var_t * mdb_nv_lookup(mdb_nv_t *nv, const char *name) { size_t i = nv_hashstring(name) % nv->nv_hashsz; mdb_var_t *v; for (v = nv->nv_hash[i]; v != NULL; v = v->v_next) { if (strcmp(NV_NAME(v), name) == 0) return (v); } return (NULL); } /* * Interpose W in place of V. We replace V with W in nv_hash, and then * set W's v_ndef overload chain to point at V. */ static mdb_var_t * nv_var_interpos(mdb_nv_t *nv, size_t i, mdb_var_t *v, mdb_var_t *w) { mdb_var_t **pvp = &nv->nv_hash[i]; while (*pvp != v) { mdb_var_t *vp = *pvp; ASSERT(vp != NULL); pvp = &vp->v_next; } *pvp = w; w->v_next = v->v_next; w->v_ndef = v; v->v_next = NULL; return (w); } /* * Add W to the end of V's overload chain. We simply follow v_ndef to the * end, and then append W. We don't expect these chains to grow very long. */ static mdb_var_t * nv_var_overload(mdb_var_t *v, mdb_var_t *w) { while (v->v_ndef != NULL) v = v->v_ndef; v->v_ndef = w; return (w); } static void nv_resize(mdb_nv_t *nv) { size_t i, bucket, new_hashsz = (nv->nv_hashsz << 1) - 1; mdb_var_t *v, *w, **new_hash = mdb_zalloc(sizeof (mdb_var_t *) * new_hashsz, nv->nv_um_flags); if (new_hash == NULL) { /* * If this fails (possible only if UM_NOSLEEP was set in our * flags), we will simply return -- and will presumably attempt * to rehash again on a subsequent insert. */ ASSERT(nv->nv_um_flags & UM_NOSLEEP); return; } /* * This is a point of no return: we are going to iterate over our * hash table, rehashing everything. Note that the ordering within * hash chains is not preserved: if every element of a bucket were * to rehash to the same bucket in the larger table, the ordering * will be flipped. */ for (i = 0; i < nv->nv_hashsz; i++) { for (v = nv->nv_hash[i]; v != NULL; v = w) { w = v->v_next; bucket = nv_hashstring(NV_NAME(v)) % new_hashsz; v->v_next = new_hash[bucket]; new_hash[bucket] = v; } } /* * Everything has been rehashed; free our old hash table and point * ourselves to the new one. */ if (!(nv->nv_um_flags & UM_GC)) mdb_free(nv->nv_hash, sizeof (mdb_var_t *) * nv->nv_hashsz); nv->nv_hash = new_hash; nv->nv_hashsz = new_hashsz; } /* * Can return NULL only if the nv's memory allocation flags include UM_NOSLEEP */ mdb_var_t * mdb_nv_insert(mdb_nv_t *nv, const char *name, const mdb_nv_disc_t *disc, uintmax_t value, uint_t flags) { size_t i; mdb_var_t *v; ASSERT(!(flags & MDB_NV_EXTNAME) || !(flags & MDB_NV_OVERLOAD)); ASSERT(!(flags & MDB_NV_RDONLY) || !(flags & MDB_NV_OVERLOAD)); if (nv->nv_nelems > nv->nv_hashsz && nv->nv_iter_elt == NULL) { nv_resize(nv); } i = nv_hashstring(name) % nv->nv_hashsz; /* * If the specified name is already hashed, * and MDB_NV_OVERLOAD is set: insert new var into overload chain * and MDB_NV_RDONLY is set: leave var unchanged, issue warning * otherwise: update var with new value */ for (v = nv->nv_hash[i]; v != NULL; v = v->v_next) { if (strcmp(NV_NAME(v), name) == 0) { if (v->v_flags & MDB_NV_OVERLOAD) { mdb_var_t *w = nv_var_alloc(NV_NAME(v), disc, value, flags, nv->nv_um_flags, NULL); if (w == NULL) { ASSERT(nv->nv_um_flags & UM_NOSLEEP); return (NULL); } if (flags & MDB_NV_INTERPOS) v = nv_var_interpos(nv, i, v, w); else v = nv_var_overload(v, w); } else if (v->v_flags & MDB_NV_RDONLY) { if (!(flags & MDB_NV_SILENT)) { warn("cannot modify read-only " "variable '%s'\n", NV_NAME(v)); } } else v->v_uvalue = value; ASSERT(v != NULL); return (v); } } /* * If the specified name was not found, initialize a new element * and add it to the hash table at the beginning of this chain: */ v = nv_var_alloc(name, disc, value, flags, nv->nv_um_flags, nv->nv_hash[i]); if (v == NULL) { ASSERT(nv->nv_um_flags & UM_NOSLEEP); return (NULL); } nv->nv_hash[i] = v; nv->nv_nelems++; return (v); } static void nv_var_defn_remove(mdb_var_t *v, mdb_var_t *corpse, uint_t um_flags) { mdb_var_t *w = v; while (v->v_ndef != NULL && v->v_ndef != corpse) v = v->v_ndef; if (v == NULL) { fail("var %p ('%s') not found on defn chain of %p\n", (void *)corpse, NV_NAME(corpse), (void *)w); } v->v_ndef = corpse->v_ndef; corpse->v_ndef = NULL; nv_var_free(corpse, um_flags); } void mdb_nv_remove(mdb_nv_t *nv, mdb_var_t *corpse) { const char *cname = NV_NAME(corpse); size_t i = nv_hashstring(cname) % nv->nv_hashsz; mdb_var_t *v = nv->nv_hash[i]; mdb_var_t **pvp; if (corpse->v_flags & MDB_NV_PERSIST) { warn("cannot remove persistent variable '%s'\n", cname); return; } if (v != corpse) { do { if (strcmp(NV_NAME(v), cname) == 0) { if (corpse->v_flags & MDB_NV_OVERLOAD) { nv_var_defn_remove(v, corpse, nv->nv_um_flags); return; /* No v_next changes needed */ } else goto notfound; } if (v->v_next == corpse) break; /* Corpse is next on the chain */ } while ((v = v->v_next) != NULL); if (v == NULL) goto notfound; pvp = &v->v_next; } else pvp = &nv->nv_hash[i]; if ((corpse->v_flags & MDB_NV_OVERLOAD) && corpse->v_ndef != NULL) { corpse->v_ndef->v_next = corpse->v_next; *pvp = corpse->v_ndef; corpse->v_ndef = NULL; } else { *pvp = corpse->v_next; nv->nv_nelems--; } nv_var_free(corpse, nv->nv_um_flags); return; notfound: fail("var %p ('%s') not found on hash chain: nv=%p [%lu]\n", (void *)corpse, cname, (void *)nv, (ulong_t)i); } void mdb_nv_rewind(mdb_nv_t *nv) { size_t i; for (i = 0; i < nv->nv_hashsz; i++) { if (nv->nv_hash[i] != NULL) break; } nv->nv_iter_elt = i < nv->nv_hashsz ? nv->nv_hash[i] : NULL; nv->nv_iter_bucket = i; } mdb_var_t * mdb_nv_advance(mdb_nv_t *nv) { mdb_var_t *v = nv->nv_iter_elt; size_t i; if (v == NULL) return (NULL); if (v->v_next != NULL) { nv->nv_iter_elt = v->v_next; return (v); } for (i = nv->nv_iter_bucket + 1; i < nv->nv_hashsz; i++) { if (nv->nv_hash[i] != NULL) break; } nv->nv_iter_elt = i < nv->nv_hashsz ? nv->nv_hash[i] : NULL; nv->nv_iter_bucket = i; return (v); } mdb_var_t * mdb_nv_peek(mdb_nv_t *nv) { return (nv->nv_iter_elt); } size_t mdb_nv_size(mdb_nv_t *nv) { return (nv->nv_nelems); } static int nv_compare(const mdb_var_t **lp, const mdb_var_t **rp) { return (strcmp(mdb_nv_get_name(*lp), mdb_nv_get_name(*rp))); } void mdb_nv_sort_iter(mdb_nv_t *nv, int (*func)(mdb_var_t *, void *), void *private, uint_t um_flags) { mdb_var_t **vps = mdb_alloc(nv->nv_nelems * sizeof (mdb_var_t *), um_flags); if (nv->nv_nelems != 0 && vps != NULL) { mdb_var_t *v, **vpp = vps; size_t i; for (mdb_nv_rewind(nv); (v = mdb_nv_advance(nv)) != NULL; ) *vpp++ = v; qsort(vps, nv->nv_nelems, sizeof (mdb_var_t *), (int (*)(const void *, const void *))nv_compare); for (vpp = vps, i = 0; i < nv->nv_nelems; i++) { if (func(*vpp++, private) == -1) break; } if (!(um_flags & UM_GC)) mdb_free(vps, nv->nv_nelems * sizeof (mdb_var_t *)); } } void mdb_nv_defn_iter(mdb_var_t *v, int (*func)(mdb_var_t *, void *), void *private) { if (func(v, private) == -1 || !(v->v_flags & MDB_NV_OVERLOAD)) return; for (v = v->v_ndef; v != NULL; v = v->v_ndef) { if (func(v, private) == -1) break; } } uintmax_t mdb_nv_get_value(const mdb_var_t *v) { if (v->v_disc) return (v->v_disc->disc_get(v)); return (v->v_uvalue); } void mdb_nv_set_value(mdb_var_t *v, uintmax_t l) { if (v->v_flags & MDB_NV_RDONLY) { warn("cannot modify read-only variable '%s'\n", NV_NAME(v)); return; } if (v->v_disc) v->v_disc->disc_set(v, l); else v->v_uvalue = l; } void * mdb_nv_get_cookie(const mdb_var_t *v) { if (v->v_disc) return ((void *)(uintptr_t)v->v_disc->disc_get(v)); return (MDB_NV_COOKIE(v)); } void mdb_nv_set_cookie(mdb_var_t *v, void *cookie) { mdb_nv_set_value(v, (uintmax_t)(uintptr_t)cookie); } const char * mdb_nv_get_name(const mdb_var_t *v) { return (NV_NAME(v)); } mdb_var_t * mdb_nv_get_ndef(const mdb_var_t *v) { if (v->v_flags & MDB_NV_OVERLOAD) return (v->v_ndef); return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2013 Josef 'Jeff' Sipek */ #ifndef _MDB_NV_H #define _MDB_NV_H #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB /* * There used to be a cap (MDB_NV_NAMELEN bytes including null) on the * length of variable names stored in-line. This cap is no longer there, * however parts of mdb use the constant to sanitize input. */ #define MDB_NV_NAMELEN 31 /* Max variable name length including null */ /* * These flags are stored inside each variable in v_flags: */ #define MDB_NV_PERSIST 0x01 /* Variable is persistent (cannot be unset) */ #define MDB_NV_RDONLY 0x02 /* Variable is read-only (cannot insert over) */ #define MDB_NV_EXTNAME 0x04 /* Variable name is stored externally */ #define MDB_NV_TAGGED 0x08 /* Variable is tagged (user-defined) */ #define MDB_NV_OVERLOAD 0x10 /* Variable can be overloaded (multiple defs) */ /* * These flags may be passed to mdb_nv_insert() but are not stored * inside the variable (and thus use bits outside of 0x00 - 0xff): */ #define MDB_NV_SILENT 0x100 /* Silence warnings about existing defs */ #define MDB_NV_INTERPOS 0x200 /* Interpose definition over previous defs */ struct mdb_var; /* Forward declaration */ struct mdb_walk_state; /* Forward declaration */ /* * Each variable's behavior with respect to the get-value and set-value * operations can be changed using a discipline: a pointer to an ops * vector which can re-define these operations: */ typedef struct mdb_nv_disc { void (*disc_set)(struct mdb_var *, uintmax_t); uintmax_t (*disc_get)(const struct mdb_var *); } mdb_nv_disc_t; /* * Each variable is defined by the following variable-length structure. * The debugger uses name/value collections to hash almost everything, so * we make a few simple space optimizations: * * A variable's name can be a pointer to external storage (v_ename and * MDB_NV_EXTNAME set), or it can be stored locally (bytes of storage are * allocated immediately after v_lname[0]). * * A variable may have multiple definitions (v_ndef chain), but this feature * is mutually exclusive with MDB_NV_EXTNAME in order to save space. */ typedef struct mdb_var { uintmax_t v_uvalue; /* Value as unsigned integral type */ union { const char *v_ename; /* Variable name if stored externally */ struct mdb_var *v_ndef; /* Variable's next definition */ } v_du; const mdb_nv_disc_t *v_disc; /* Link to variable discipline */ struct mdb_var *v_next; /* Link to next var in hash chain */ uchar_t v_flags; /* Variable flags (see above) */ char v_lname[1]; /* Variable name if stored locally */ } mdb_var_t; #define MDB_NV_VALUE(v) ((v)->v_uvalue) #define MDB_NV_COOKIE(v) ((void *)(uintptr_t)((v)->v_uvalue)) #define v_ename v_du.v_ename #define v_ndef v_du.v_ndef /* * The name/value collection itself is a simple array of hash buckets, * as well as a persistent bucket index and pointer for iteration: */ typedef struct mdb_nv { mdb_var_t **nv_hash; /* Hash bucket array */ size_t nv_hashsz; /* Size of hash bucket array */ size_t nv_nelems; /* Total number of hashed elements */ mdb_var_t *nv_iter_elt; /* Iterator element pointer */ size_t nv_iter_bucket; /* Iterator bucket index */ uint_t nv_um_flags; /* Flags for the memory allocator */ } mdb_nv_t; extern mdb_nv_t *mdb_nv_create(mdb_nv_t *, uint_t); extern void mdb_nv_destroy(mdb_nv_t *); extern mdb_var_t *mdb_nv_insert(mdb_nv_t *, const char *, const mdb_nv_disc_t *, uintmax_t, uint_t); extern mdb_var_t *mdb_nv_lookup(mdb_nv_t *, const char *); extern void mdb_nv_remove(mdb_nv_t *, mdb_var_t *); extern void mdb_nv_rewind(mdb_nv_t *); extern mdb_var_t *mdb_nv_advance(mdb_nv_t *); extern mdb_var_t *mdb_nv_peek(mdb_nv_t *); extern size_t mdb_nv_size(mdb_nv_t *); extern void mdb_nv_sort_iter(mdb_nv_t *, int (*)(mdb_var_t *, void *), void *, uint_t); extern void mdb_nv_defn_iter(mdb_var_t *, int (*)(mdb_var_t *, void *), void *); extern uintmax_t mdb_nv_get_value(const mdb_var_t *); extern void mdb_nv_set_value(mdb_var_t *, uintmax_t); extern void *mdb_nv_get_cookie(const mdb_var_t *); extern void mdb_nv_set_cookie(mdb_var_t *, void *); extern const char *mdb_nv_get_name(const mdb_var_t *); extern mdb_var_t *mdb_nv_get_ndef(const mdb_var_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_NV_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _MDB_PARAM_H #define _MDB_PARAM_H #ifdef __cplusplus extern "C" { #endif /* * mdb_param.h * * Support header file for mdb_ks module for module developers wishing * to access macros in which expand to the current value * of kernel global variables. Developers should include * rather than . This will arrange for the inclusion of * , plus redefinition of all the macros therein to expand * to the value of globals defined in mdb_ks. The following cpp goop * is necessary to get to *not* define those macros. */ #ifdef _SYS_PARAM_H #error "You should not include prior to " #endif #ifndef _MACHDEP #define _MACHDEP #ifndef _SYS_MACHPARAM_H #define _SYS_MACHPARAM_H /* * Case 1: We defined both _MACHDEP and _SYS_MACHPARAM_H. Undef both * after we include . */ #include #undef _SYS_MACHPARAM_H #undef _MACHDEP #else /* _SYS_MACHPARAM_H */ /* * Case 2: We defined _MACHDEP only. */ #include #undef _MACHDEP #endif /* _SYS_MACHPARAM_H */ #else /* _MACHDEP */ #ifndef _SYS_MACHPARAM_H #define _SYS_MACHPARAM_H /* * Case 3: We defined _SYS_MACHPARAM_H. */ #include #undef _SYS_MACHPARAM_H #else /* _SYS_MACHPARAM_H */ /* * Case 4: _MACHDEP and _SYS_MACHPARAM_H are both already defined. */ #include #endif /* _SYS_MACHPARAM_H */ #endif /* _MACHDEP */ /* * Extern declarations for global variables defined in the mdb_ks module. * All of these will be filled in during ks's _mdb_init routine. */ extern unsigned long _mdb_ks_pagesize; extern unsigned int _mdb_ks_pageshift; extern unsigned long _mdb_ks_pageoffset; extern unsigned long long _mdb_ks_pagemask; extern unsigned long _mdb_ks_mmu_pagesize; extern unsigned int _mdb_ks_mmu_pageshift; extern unsigned long _mdb_ks_mmu_pageoffset; extern unsigned long _mdb_ks_mmu_pagemask; extern uintptr_t _mdb_ks_kernelbase; extern uintptr_t _mdb_ks_userlimit; extern uintptr_t _mdb_ks_userlimit32; extern unsigned long _mdb_ks_msg_bsize; extern unsigned long _mdb_ks_defaultstksz; extern int _mdb_ks_ncpu; extern int _mdb_ks_ncpu_log2; extern int _mdb_ks_ncpu_p2; /* * Now derive all the macros using the global variables defined in * the support library. These macros will in turn be referenced in * other kernel macros. */ #define PAGESIZE _mdb_ks_pagesize #define PAGESHIFT _mdb_ks_pageshift #define PAGEOFFSET _mdb_ks_pageoffset #define PAGEMASK _mdb_ks_pagemask #define MMU_PAGESIZE _mdb_ks_mmu_pagesize #define MMU_PAGESHIFT _mdb_ks_mmu_pageshift #define MMU_PAGEOFFSET _mdb_ks_mmu_pageoffset #define MMU_PAGEMASK _mdb_ks_mmu_pagemask #define KERNELBASE _mdb_ks_kernelbase #define USERLIMIT _mdb_ks_userlimit #define USERLIMIT32 _mdb_ks_userlimit32 #define MSG_BSIZE _mdb_ks_msg_bsize #define DEFAULTSTKSZ _mdb_ks_defaultstksz #define NCPU _mdb_ks_ncpu #define NCPU_LOG2 _mdb_ks_ncpu_log2 #define NCPU_P2 _mdb_ks_ncpu_p2 #define _STRING_H /* Do not re-include */ #ifdef __cplusplus } #endif #endif /* _MDB_PARAM_H */ /* * 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. */ /* * Pipe I/O Backend * * In order to implement dcmd pipelines, we provide a pipe i/o backend that * can be used to connect two mdb_iob structures (a read and write end). * This backend is selected when mdb_iob_pipe is used to construct a pair of * iobs. Each iob points at the same i/o backend (the pipe i/o), and the * backend manages a circular fixed-size buffer which moves data between * the reader and writer. The caller provides read and write-side service * routines that are expected to perform context switching (see mdb_context.c). * The pipe implementation is relatively simple: the writer calls any of the * mdb_iob_* routines to fill the write-side iob, and when this iob needs to * flush data to the underlying i/o, pio_write() below is called. This * routine copies data into the pipe buffer until no more free space is * available, and then calls the read-side service routine (presuming that * when it returns, more free space will be available). On the read-side, * pio_read() copies data up from the pipe buffer into the read-side iob. * If pio_read() is called and the pipe buffer is empty, pio_read() calls * the write-side service routine to force the writer to produce more data. */ #include #include #include #include #include #include #include #include #include #include #include typedef struct pipe_data { mdb_iobsvc_f *pipe_rdsvc; /* Read-side service routine */ mdb_iob_t *pipe_rdiob; /* Read-side i/o buffer */ mdb_iobsvc_f *pipe_wrsvc; /* Write-side service routine */ mdb_iob_t *pipe_wriob; /* Write-side i/o buffer */ char pipe_buf[BUFSIZ]; /* Ring buffer for pipe contents */ mdb_iob_ctx_t pipe_ctx; /* Context data for service routines */ uint_t pipe_rdndx; /* Next byte index for reading */ uint_t pipe_wrndx; /* Next byte index for writing */ uint_t pipe_free; /* Free space for writing in bytes */ uint_t pipe_used; /* Used space for reading in bytes */ } pipe_data_t; static ssize_t pio_read(mdb_io_t *io, void *buf, size_t nbytes) { pipe_data_t *pd = io->io_data; size_t n, nleft; if (nbytes == 0) return (0); /* return 0 for zero-length read */ for (nleft = nbytes; nleft == nbytes; nleft -= n) { if (pd->pipe_used == 0) { if (pd->pipe_wriob != NULL) { pd->pipe_wrsvc(pd->pipe_rdiob, pd->pipe_wriob, &pd->pipe_ctx); } if (pd->pipe_used == 0) break; } n = MIN(pd->pipe_used, nleft); if (BUFSIZ - pd->pipe_rdndx < n) { /* * Case 1: The amount to read overlaps the end of the * circular buffer. 'n1' will be the amount to copy * from the end of the buffer, and 'n2' will be the * amount to copy from the beginning. Note that since * n <= pipe_used, it is impossible to read past * pipe_wrndx into undefined territory. */ size_t n1 = BUFSIZ - pd->pipe_rdndx; size_t n2 = n - n1; ASSERT(n2 <= pd->pipe_wrndx); bcopy(&pd->pipe_buf[pd->pipe_rdndx], buf, n1); buf = (char *)buf + n1; bcopy(&pd->pipe_buf[0], buf, n2); buf = (char *)buf + n2; } else { /* * Case 2: The easy case. Simply copy the data over * to the buffer. */ bcopy(&pd->pipe_buf[pd->pipe_rdndx], buf, n); buf = (char *)buf + n; } pd->pipe_rdndx = (pd->pipe_rdndx + n) % BUFSIZ; pd->pipe_free += n; pd->pipe_used -= n; } /* * If we have a writer, but pipe_wrsvc failed to produce any data, * we return EAGAIN. If there is no writer, then return 0 for EOF. */ if (nleft == nbytes) { if (pd->pipe_wriob != NULL) return (set_errno(EAGAIN)); else return (0); } return (nbytes - nleft); } static ssize_t pio_write(mdb_io_t *io, const void *buf, size_t nbytes) { pipe_data_t *pd = io->io_data; size_t n, nleft; if (pd->pipe_rdiob == NULL) return (set_errno(EPIPE)); /* fail with EPIPE if no reader */ for (nleft = nbytes; nleft != 0; nleft -= n) { if (pd->pipe_free == 0) { pd->pipe_rdsvc(pd->pipe_rdiob, pd->pipe_wriob, &pd->pipe_ctx); if (pd->pipe_free == 0) break; /* if nothing consumed by reader, exit */ } n = MIN(pd->pipe_free, nleft); if (BUFSIZ - pd->pipe_wrndx < n) { /* * Case 1: The data will overlap the circular buffer * boundary. In this case, 'n1' will be the number of * bytes to put at the end of the buffer, and 'n2' will * be the number of bytes to put at the beginning. * Note that since n <= pipe_free, it is impossible to * overlap rdndx with the initial data. */ size_t n1 = BUFSIZ - pd->pipe_wrndx; size_t n2 = n - n1; ASSERT(n2 <= pd->pipe_rdndx); bcopy(buf, &pd->pipe_buf[pd->pipe_wrndx], n1); buf = (const char *)buf + n1; bcopy(buf, &pd->pipe_buf[0], n2); buf = (const char *)buf + n2; } else { /* * Case 2: The easy case. Simply copy the data into * the buffer. */ bcopy(buf, &pd->pipe_buf[pd->pipe_wrndx], n); buf = (const char *)buf + n; } pd->pipe_wrndx = (pd->pipe_wrndx + n) % BUFSIZ; pd->pipe_free -= n; pd->pipe_used += n; } if (nleft == nbytes && nbytes != 0) return (set_errno(EAGAIN)); return (nbytes - nleft); } /* * Provide support for STREAMS-style write-side flush ioctl. This can be * used by the caller to force a context switch to the read-side. */ static int pio_ctl(mdb_io_t *io, int req, void *arg) { pipe_data_t *pd = io->io_data; if (io->io_next != NULL) return (IOP_CTL(io->io_next, req, arg)); if (req != I_FLUSH || (intptr_t)arg != FLUSHW) return (set_errno(ENOTSUP)); if (pd->pipe_used != 0) pd->pipe_rdsvc(pd->pipe_rdiob, pd->pipe_wriob, &pd->pipe_ctx); return (0); } static void pio_close(mdb_io_t *io) { mdb_free(io->io_data, sizeof (pipe_data_t)); } /*ARGSUSED*/ static const char * pio_name(mdb_io_t *io) { return ("(pipeline)"); } static void pio_link(mdb_io_t *io, mdb_iob_t *iob) { pipe_data_t *pd = io->io_data; /* * Here we take advantage of the IOP_LINK calls made to associate each * i/o backend with its iob to determine our read and write iobs. */ if (io->io_next == NULL) { if (iob->iob_flags & MDB_IOB_RDONLY) pd->pipe_rdiob = iob; else pd->pipe_wriob = iob; } else IOP_LINK(io->io_next, iob); } static void pio_unlink(mdb_io_t *io, mdb_iob_t *iob) { pipe_data_t *volatile pd = io->io_data; /* * The IOP_UNLINK call will be made when one of our associated iobs is * destroyed. If the read-side iob is being destroyed, we simply set * pipe_rdiob to NULL, forcing subsequent pio_write() calls to fail * with EPIPE. Things are more complicated when the write-side is * being destroyed. If this is the last close prior to destroying the * pipe, we need to arrange for any in-transit data to be consumed by * the reader. We first set pipe_wriob to NULL, which forces pio_read * to return EOF when all in-transit data is consumed. We then call * the read-service routine while there is still a reader and pipe_used * is non-zero, indicating there is still data in the pipe. */ if (io->io_next == NULL) { if (pd->pipe_wriob == iob) { pd->pipe_wriob = NULL; /* remove writer */ if (pd->pipe_used == 0 && pd->pipe_ctx.ctx_data == NULL) return; /* no reader and nothing to read */ /* * Note that we need to use a do-while construct here * so that we resume the reader's context at *least* * once. This forces it to read EOF and exit even if * the pipeline is already completely flushed. */ do { if (pd->pipe_rdiob == NULL) break; if (mdb_iob_err(pd->pipe_rdiob) != 0) { if (pd->pipe_ctx.ctx_wptr != NULL) { mdb_frame_pop( pd->pipe_ctx.ctx_wptr, MDB_ERR_ABORT); pd->pipe_ctx.ctx_wptr = NULL; } break; /* don't read if error bit set */ } if (pd->pipe_ctx.ctx_data == NULL || setjmp(*mdb_context_getpcb( pd->pipe_ctx.ctx_data)) == 0) { pd->pipe_rdsvc(pd->pipe_rdiob, pd->pipe_wriob, &pd->pipe_ctx); } } while (pd->pipe_used != 0); if (pd->pipe_ctx.ctx_data != NULL) { mdb_context_destroy(pd->pipe_ctx.ctx_data); pd->pipe_ctx.ctx_data = NULL; } } else if (pd->pipe_rdiob == iob) pd->pipe_rdiob = NULL; /* remove reader */ } else IOP_UNLINK(io->io_next, iob); } static const mdb_io_ops_t pipeio_ops = { .io_read = pio_read, .io_write = pio_write, .io_seek = no_io_seek, .io_ctl = pio_ctl, .io_close = pio_close, .io_name = pio_name, .io_link = pio_link, .io_unlink = pio_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume, }; mdb_io_t * mdb_pipeio_create(mdb_iobsvc_f *rdsvc, mdb_iobsvc_f *wrsvc) { mdb_io_t *io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP); pipe_data_t *pd = mdb_zalloc(sizeof (pipe_data_t), UM_SLEEP); ASSERT(rdsvc != NULL && wrsvc != NULL); pd->pipe_rdsvc = rdsvc; pd->pipe_wrsvc = wrsvc; pd->pipe_free = BUFSIZ; io->io_ops = &pipeio_ops; io->io_data = pd; io->io_next = NULL; io->io_refcnt = 0; return (io); } int mdb_iob_isapipe(mdb_iob_t *iob) { mdb_io_t *io; for (io = iob->iob_iop; io != NULL; io = io->io_next) { if (io->io_ops == &pipeio_ops) return (1); } return (0); } /* * 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, 2014 by Delphix. All rights reserved. * Copyright 2020 Joyent, Inc. * Copyright (c) 2014 Nexenta Systems, Inc. All rights reserved. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct holeinfo { ulong_t hi_offset; /* expected offset */ uchar_t hi_isunion; /* represents a union */ } holeinfo_t; typedef struct printarg { mdb_tgt_t *pa_tgt; /* current target */ mdb_tgt_t *pa_realtgt; /* real target (for -i) */ mdb_tgt_t *pa_immtgt; /* immediate target (for -i) */ mdb_tgt_as_t pa_as; /* address space to use for i/o */ mdb_tgt_addr_t pa_addr; /* base address for i/o */ ulong_t pa_armemlim; /* limit on array elements to print */ ulong_t pa_arstrlim; /* limit on array chars to print */ const char *pa_delim; /* element delimiter string */ const char *pa_prefix; /* element prefix string */ const char *pa_suffix; /* element suffix string */ holeinfo_t *pa_holes; /* hole detection information */ int pa_nholes; /* size of holes array */ int pa_flags; /* formatting flags (see below) */ int pa_depth; /* previous depth */ int pa_nest; /* array nesting depth */ int pa_tab; /* tabstop width */ uint_t pa_maxdepth; /* Limit max depth */ uint_t pa_nooutdepth; /* don't print output past this depth */ } printarg_t; #define PA_SHOWTYPE 0x001 /* print type name */ #define PA_SHOWBASETYPE 0x002 /* print base type name */ #define PA_SHOWNAME 0x004 /* print member name */ #define PA_SHOWADDR 0x008 /* print address */ #define PA_SHOWVAL 0x010 /* print value */ #define PA_SHOWHOLES 0x020 /* print holes in structs */ #define PA_INTHEX 0x040 /* print integer values in hex */ #define PA_INTDEC 0x080 /* print integer values in decimal */ #define PA_NOSYMBOLIC 0x100 /* don't print ptrs as func+offset */ #define IS_CHAR(e) \ (((e).cte_format & (CTF_INT_CHAR | CTF_INT_SIGNED)) == \ (CTF_INT_CHAR | CTF_INT_SIGNED) && (e).cte_bits == NBBY) #define COMPOSITE_MASK ((1 << CTF_K_STRUCT) | \ (1 << CTF_K_UNION) | (1 << CTF_K_ARRAY)) #define IS_COMPOSITE(k) (((1 << k) & COMPOSITE_MASK) != 0) #define SOU_MASK ((1 << CTF_K_STRUCT) | (1 << CTF_K_UNION)) #define IS_SOU(k) (((1 << k) & SOU_MASK) != 0) #define MEMBER_DELIM_ERR -1 #define MEMBER_DELIM_DONE 0 #define MEMBER_DELIM_PTR 1 #define MEMBER_DELIM_DOT 2 #define MEMBER_DELIM_LBR 3 typedef int printarg_f(const char *, const char *, mdb_ctf_id_t, mdb_ctf_id_t, ulong_t, printarg_t *); static int elt_print(const char *, mdb_ctf_id_t, mdb_ctf_id_t, ulong_t, int, void *); static void print_close_sou(printarg_t *, int); /* * Given an address, look up the symbol ID of the specified symbol in its * containing module. We only support lookups for exact matches. */ static const char * addr_to_sym(mdb_tgt_t *t, uintptr_t addr, char *name, size_t namelen, GElf_Sym *symp, mdb_syminfo_t *sip) { const mdb_map_t *mp; const char *p; if (mdb_tgt_lookup_by_addr(t, addr, MDB_TGT_SYM_EXACT, name, namelen, NULL, NULL) == -1) return (NULL); /* address does not exactly match a symbol */ if ((p = strrsplit(name, '`')) != NULL) { if (mdb_tgt_lookup_by_name(t, name, p, symp, sip) == -1) return (NULL); return (p); } if ((mp = mdb_tgt_addr_to_map(t, addr)) == NULL) return (NULL); /* address does not fall within a mapping */ if (mdb_tgt_lookup_by_name(t, mp->map_name, name, symp, sip) == -1) return (NULL); return (name); } /* * This lets dcmds be a little fancy with their processing of type arguments * while still treating them more or less as a single argument. * For example, if a command is invokes like this: * * :: proc_t ... * * this function will just copy "proc_t" into the provided buffer. If the * command is instead invoked like this: * * :: struct proc ... * * this function will place the string "struct proc" into the provided buffer * and increment the caller's argv and argc. This allows the caller to still * treat the type argument logically as it would an other atomic argument. */ int args_to_typename(int *argcp, const mdb_arg_t **argvp, char *buf, size_t len) { int argc = *argcp; const mdb_arg_t *argv = *argvp; if (argc < 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (strcmp(argv->a_un.a_str, "struct") == 0 || strcmp(argv->a_un.a_str, "enum") == 0 || strcmp(argv->a_un.a_str, "union") == 0) { if (argc <= 1) { mdb_warn("%s is not a valid type\n", argv->a_un.a_str); return (DCMD_ABORT); } if (argv[1].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); (void) mdb_snprintf(buf, len, "%s %s", argv[0].a_un.a_str, argv[1].a_un.a_str); *argcp = argc - 1; *argvp = argv + 1; } else { (void) mdb_snprintf(buf, len, "%s", argv[0].a_un.a_str); } return (0); } /*ARGSUSED*/ int cmd_sizeof(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_ctf_id_t id; char tn[MDB_SYM_NAMLEN]; int ret; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); if ((ret = args_to_typename(&argc, &argv, tn, sizeof (tn))) != 0) return (ret); if (argc != 1) return (DCMD_USAGE); if (mdb_ctf_lookup_by_name(tn, &id) != 0) { mdb_warn("failed to look up type %s", tn); return (DCMD_ERR); } if (flags & DCMD_PIPE_OUT) mdb_printf("%#lr\n", mdb_ctf_type_size(id)); else mdb_printf("sizeof (%s) = %#lr\n", tn, mdb_ctf_type_size(id)); return (DCMD_OK); } int cmd_sizeof_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, const mdb_arg_t *argv) { char tn[MDB_SYM_NAMLEN]; int ret; if (argc == 0 && !(flags & DCMD_TAB_SPACE)) return (0); if (argc == 0 && (flags & DCMD_TAB_SPACE)) return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_NOPOINT)); if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0) return (ret); if (argc == 1) return (mdb_tab_complete_type(mcp, tn, MDB_TABC_NOPOINT)); return (0); } /*ARGSUSED*/ int cmd_offsetof(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *member; mdb_ctf_id_t id; ulong_t off; char tn[MDB_SYM_NAMLEN]; ssize_t sz; int ret; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); if ((ret = args_to_typename(&argc, &argv, tn, sizeof (tn))) != 0) return (ret); if (argc != 2 || argv[1].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (mdb_ctf_lookup_by_name(tn, &id) != 0) { mdb_warn("failed to look up type %s", tn); return (DCMD_ERR); } member = argv[1].a_un.a_str; if (mdb_ctf_member_info(id, member, &off, &id) != 0) { mdb_warn("failed to find member %s of type %s", member, tn); return (DCMD_ERR); } if (flags & DCMD_PIPE_OUT) { if (off % NBBY != 0) { mdb_warn("member %s of type %s is not byte-aligned\n", member, tn); return (DCMD_ERR); } mdb_printf("%#lr", off / NBBY); return (DCMD_OK); } mdb_printf("offsetof (%s, %s) = %#lr", tn, member, off / NBBY); if (off % NBBY != 0) mdb_printf(".%lr", off % NBBY); if ((sz = mdb_ctf_type_size(id)) > 0) mdb_printf(", sizeof (...->%s) = %#lr", member, sz); mdb_printf("\n"); return (DCMD_OK); } /*ARGSUSED*/ static int enum_prefix_scan_cb(const char *name, int value, void *arg) { char *str = arg; /* * This function is called with every name in the enum. We make * "arg" be the common prefix, if any. */ if (str[0] == 0) { if (strlcpy(arg, name, MDB_SYM_NAMLEN) >= MDB_SYM_NAMLEN) return (1); return (0); } while (*name == *str) { if (*str == 0) { if (str != arg) { str--; /* don't smother a name completely */ } break; } name++; str++; } *str = 0; return (str == arg); /* only continue if prefix is non-empty */ } struct enum_p2_info { intmax_t e_value; /* value we're processing */ char *e_buf; /* buffer for holding names */ size_t e_size; /* size of buffer */ size_t e_prefix; /* length of initial prefix */ uint_t e_allprefix; /* apply prefix to first guy, too */ uint_t e_bits; /* bits seen */ uint8_t e_found; /* have we seen anything? */ uint8_t e_first; /* does buf contain the first one? */ uint8_t e_zero; /* have we seen a zero value? */ }; static int enum_p2_cb(const char *name, int bit_arg, void *arg) { struct enum_p2_info *eiip = arg; uintmax_t bit = bit_arg; if (bit != 0 && !ISP2(bit)) return (1); /* non-power-of-2; abort processing */ if ((bit == 0 && eiip->e_zero) || (bit != 0 && (eiip->e_bits & bit) != 0)) { return (0); /* already seen this value */ } if (bit == 0) eiip->e_zero = 1; else eiip->e_bits |= bit; if (eiip->e_buf != NULL && (eiip->e_value & bit) != 0) { char *buf = eiip->e_buf; size_t prefix = eiip->e_prefix; if (eiip->e_found) { (void) strlcat(buf, "|", eiip->e_size); if (eiip->e_first && !eiip->e_allprefix && prefix > 0) { char c1 = buf[prefix]; char c2 = buf[prefix + 1]; buf[prefix] = '{'; buf[prefix + 1] = 0; mdb_printf("%s", buf); buf[prefix] = c1; buf[prefix + 1] = c2; mdb_printf("%s", buf + prefix); } else { mdb_printf("%s", buf); } } /* skip the common prefix as necessary */ if ((eiip->e_found || eiip->e_allprefix) && strlen(name) > prefix) name += prefix; (void) strlcpy(eiip->e_buf, name, eiip->e_size); eiip->e_first = !eiip->e_found; eiip->e_found = 1; } return (0); } static int enum_is_p2(mdb_ctf_id_t id) { struct enum_p2_info eii; bzero(&eii, sizeof (eii)); return (mdb_ctf_type_kind(id) == CTF_K_ENUM && mdb_ctf_enum_iter(id, enum_p2_cb, &eii) == 0 && eii.e_bits != 0); } static int enum_value_print_p2(mdb_ctf_id_t id, intmax_t value, uint_t allprefix) { struct enum_p2_info eii; char prefix[MDB_SYM_NAMLEN + 2]; intmax_t missed; bzero(&eii, sizeof (eii)); eii.e_value = value; eii.e_buf = prefix; eii.e_size = sizeof (prefix); eii.e_allprefix = allprefix; prefix[0] = 0; if (mdb_ctf_enum_iter(id, enum_prefix_scan_cb, prefix) == 0) eii.e_prefix = strlen(prefix); if (mdb_ctf_enum_iter(id, enum_p2_cb, &eii) != 0 || eii.e_bits == 0) return (-1); missed = (value & ~(intmax_t)eii.e_bits); if (eii.e_found) { /* push out any final value, with a | if we missed anything */ if (!eii.e_first) (void) strlcat(prefix, "}", sizeof (prefix)); if (missed != 0) (void) strlcat(prefix, "|", sizeof (prefix)); mdb_printf("%s", prefix); } if (!eii.e_found || missed) { mdb_printf("%#llx", missed); } return (0); } struct enum_cbinfo { uint_t e_flags; const char *e_string; /* NULL for value searches */ size_t e_prefix; intmax_t e_value; uint_t e_found; mdb_ctf_id_t e_id; }; #define E_PRETTY 0x01 #define E_HEX 0x02 #define E_SEARCH_STRING 0x04 #define E_SEARCH_VALUE 0x08 #define E_ELIDE_PREFIX 0x10 static void enum_print(struct enum_cbinfo *info, const char *name, int value) { uint_t flags = info->e_flags; uint_t elide_prefix = (info->e_flags & E_ELIDE_PREFIX); if (name != NULL && info->e_prefix && strlen(name) > info->e_prefix) name += info->e_prefix; if (flags & E_PRETTY) { uint_t indent = 5 + ((flags & E_HEX) ? 8 : 11); mdb_printf((flags & E_HEX)? "%8x " : "%11d ", value); (void) mdb_inc_indent(indent); if (name != NULL) { mdb_iob_puts(mdb.m_out, name); } else { (void) enum_value_print_p2(info->e_id, value, elide_prefix); } (void) mdb_dec_indent(indent); mdb_printf("\n"); } else { mdb_printf("%#r\n", value); } } static int enum_cb(const char *name, int value, void *arg) { struct enum_cbinfo *info = arg; uint_t flags = info->e_flags; if (flags & E_SEARCH_STRING) { if (strcmp(name, info->e_string) != 0) return (0); } else if (flags & E_SEARCH_VALUE) { if (value != info->e_value) return (0); } enum_print(info, name, value); info->e_found = 1; return (0); } void enum_help(void) { mdb_printf("%s", "Without an address and name, print all values for the enumeration \"enum\".\n" "With an address, look up a particular value in \"enum\". With a name, look\n" "up a particular name in \"enum\".\n"); (void) mdb_dec_indent(2); mdb_printf("\n%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf("%s", " -e remove common prefixes from enum names\n" " -x report enum values in hexadecimal\n"); } /*ARGSUSED*/ int cmd_enum(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct enum_cbinfo info; char type[MDB_SYM_NAMLEN + sizeof ("enum ")]; char tn2[MDB_SYM_NAMLEN + sizeof ("enum ")]; char prefix[MDB_SYM_NAMLEN]; mdb_ctf_id_t id; mdb_ctf_id_t idr; int i; intmax_t search = 0; uint_t isp2; info.e_flags = (flags & DCMD_PIPE_OUT)? 0 : E_PRETTY; info.e_string = NULL; info.e_value = 0; info.e_found = 0; i = mdb_getopts(argc, argv, 'e', MDB_OPT_SETBITS, E_ELIDE_PREFIX, &info.e_flags, 'x', MDB_OPT_SETBITS, E_HEX, &info.e_flags, NULL); argc -= i; argv += i; if ((i = args_to_typename(&argc, &argv, type, MDB_SYM_NAMLEN)) != 0) return (i); if (strchr(type, ' ') == NULL) { /* * Check as an enumeration tag first, and fall back * to checking for a typedef. Yes, this means that * anonymous enumerations whose typedefs conflict with * an enum tag can't be accessed. Don't do that. */ (void) mdb_snprintf(tn2, sizeof (tn2), "enum %s", type); if (mdb_ctf_lookup_by_name(tn2, &id) == 0) { (void) strcpy(type, tn2); } else if (mdb_ctf_lookup_by_name(type, &id) != 0) { mdb_warn("types '%s', '%s'", tn2, type); return (DCMD_ERR); } } else { if (mdb_ctf_lookup_by_name(type, &id) != 0) { mdb_warn("'%s'", type); return (DCMD_ERR); } } /* resolve it, and make sure we're looking at an enumeration */ if (mdb_ctf_type_resolve(id, &idr) == -1) { mdb_warn("unable to resolve '%s'", type); return (DCMD_ERR); } if (mdb_ctf_type_kind(idr) != CTF_K_ENUM) { mdb_warn("'%s': not an enumeration\n", type); return (DCMD_ERR); } info.e_id = idr; if (argc > 2) return (DCMD_USAGE); if (argc == 2) { if (flags & DCMD_ADDRSPEC) { mdb_warn("may only specify one of: name, address\n"); return (DCMD_USAGE); } if (argv[1].a_type == MDB_TYPE_STRING) { info.e_flags |= E_SEARCH_STRING; info.e_string = argv[1].a_un.a_str; } else if (argv[1].a_type == MDB_TYPE_IMMEDIATE) { info.e_flags |= E_SEARCH_VALUE; search = argv[1].a_un.a_val; } else { return (DCMD_USAGE); } } if (flags & DCMD_ADDRSPEC) { info.e_flags |= E_SEARCH_VALUE; search = mdb_get_dot(); } if (info.e_flags & E_SEARCH_VALUE) { if ((int)search != search) { mdb_warn("value '%lld' out of enumeration range\n", search); } info.e_value = search; } isp2 = enum_is_p2(idr); if (isp2) info.e_flags |= E_HEX; if (DCMD_HDRSPEC(flags) && (info.e_flags & E_PRETTY)) { if (info.e_flags & E_HEX) mdb_printf("%%8s %-64s%\n", "VALUE", "NAME"); else mdb_printf("%%11s %-64s%\n", "VALUE", "NAME"); } /* if the enum is a power-of-two one, process it that way */ if ((info.e_flags & E_SEARCH_VALUE) && isp2) { enum_print(&info, NULL, info.e_value); return (DCMD_OK); } prefix[0] = 0; if ((info.e_flags & E_ELIDE_PREFIX) && mdb_ctf_enum_iter(id, enum_prefix_scan_cb, prefix) == 0) info.e_prefix = strlen(prefix); if (mdb_ctf_enum_iter(idr, enum_cb, &info) == -1) { mdb_warn("cannot walk '%s' as enum", type); return (DCMD_ERR); } if (info.e_found == 0 && (info.e_flags & (E_SEARCH_STRING | E_SEARCH_VALUE)) != 0) { if (info.e_flags & E_SEARCH_STRING) mdb_warn("name \"%s\" not in '%s'\n", info.e_string, type); else mdb_warn("value %#lld not in '%s'\n", info.e_value, type); return (DCMD_ERR); } return (DCMD_OK); } static int setup_vcb(const char *name, uintptr_t addr) { const char *p; mdb_var_t *v; if ((v = mdb_nv_lookup(&mdb.m_nv, name)) == NULL) { if ((p = strbadid(name)) != NULL) { mdb_warn("'%c' may not be used in a variable " "name\n", *p); return (DCMD_ABORT); } if ((v = mdb_nv_insert(&mdb.m_nv, name, NULL, addr, 0)) == NULL) return (DCMD_ERR); } else { if (v->v_flags & MDB_NV_RDONLY) { mdb_warn("variable %s is read-only\n", name); return (DCMD_ABORT); } } /* * If there already exists a vcb for this variable, we may be * calling the dcmd in a loop. We only create a vcb for this * variable on the first invocation. */ if (mdb_vcb_find(v, mdb.m_frame) == NULL) mdb_vcb_insert(mdb_vcb_create(v), mdb.m_frame); return (0); } /*ARGSUSED*/ int cmd_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int offset; uintptr_t a, tmp; int ret; if (!(flags & DCMD_ADDRSPEC) || argc == 0) return (DCMD_USAGE); if (argv->a_type != MDB_TYPE_STRING) { /* * We are being given a raw offset in lieu of a type and * member; confirm the number of arguments and argument * type. */ if (argc != 1 || argv->a_type != MDB_TYPE_IMMEDIATE) return (DCMD_USAGE); offset = argv->a_un.a_val; argv++; argc--; if (offset % sizeof (uintptr_t)) { mdb_warn("offset must fall on a word boundary\n"); return (DCMD_ABORT); } } else { const char *member; char buf[MDB_SYM_NAMLEN]; int ret; ret = args_to_typename(&argc, &argv, buf, sizeof (buf)); if (ret != 0) return (ret); argv++; argc--; /* * If we make it here, we were provided a type name. We should * only continue if we still have arguments left (e.g. member * name and potentially a variable name). */ if (argc == 0) return (DCMD_USAGE); member = argv->a_un.a_str; offset = mdb_ctf_offsetof_by_name(buf, member); if (offset == -1) return (DCMD_ABORT); argv++; argc--; if (offset % (sizeof (uintptr_t)) != 0) { mdb_warn("%s is not a word-aligned member\n", member); return (DCMD_ABORT); } } /* * If we have any unchewed arguments, a variable name must be present. */ if (argc == 1) { if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if ((ret = setup_vcb(argv->a_un.a_str, addr)) != 0) return (ret); } else if (argc != 0) { return (DCMD_USAGE); } a = addr; do { mdb_printf("%lr\n", a); if (mdb_vread(&tmp, sizeof (tmp), a + offset) == -1) { mdb_warn("failed to read next pointer from object %p", a); return (DCMD_ERR); } a = tmp; } while (a != addr && a != 0); return (DCMD_OK); } int cmd_array(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_ctf_id_t id; ssize_t elemsize = 0; char tn[MDB_SYM_NAMLEN]; int ret, nelem = -1; mdb_tgt_t *t = mdb.m_target; GElf_Sym sym; mdb_ctf_arinfo_t ar; mdb_syminfo_t s_info; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (argc >= 2) { ret = args_to_typename(&argc, &argv, tn, sizeof (tn)); if (ret != 0) return (ret); if (argc == 1) /* unquoted compound type without count */ return (DCMD_USAGE); if (mdb_ctf_lookup_by_name(tn, &id) != 0) { mdb_warn("failed to look up type %s", tn); return (DCMD_ABORT); } nelem = (int)mdb_argtoull(&argv[1]); elemsize = mdb_ctf_type_size(id); } else if (addr_to_sym(t, addr, tn, sizeof (tn), &sym, &s_info) != NULL && mdb_ctf_lookup_by_symbol(&sym, &s_info, &id) == 0 && mdb_ctf_type_kind(id) == CTF_K_ARRAY && mdb_ctf_array_info(id, &ar) != -1) { if (ar.mta_nelems == 0) { mdb_warn("array has 0 elements\n"); return (DCMD_ERR); } elemsize = mdb_ctf_type_size(id) / ar.mta_nelems; nelem = ar.mta_nelems; } else { mdb_warn("no symbol information for %a", addr); return (DCMD_ERR); } if (argc == 3 || argc == 1) { if (argv[argc - 1].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if ((ret = setup_vcb(argv[argc - 1].a_un.a_str, addr)) != 0) return (ret); } else if (argc > 3) { return (DCMD_USAGE); } for (; nelem > 0; nelem--) { mdb_printf("%lr\n", addr); addr = addr + elemsize; } return (DCMD_OK); } /* * This is a shared implementation to determine if we should treat a type as a * bitfield. The parameters are the CTF encoding and the bit offset of the * integer. This also exists in mdb_print.c. We consider something a bitfield * if: * * o The type is more than 8 bytes. This is a bit of a historical choice from * mdb and is a stranger one. The normal integer handling code generally * doesn't handle integers more than 64-bits in size. Of course neither does * the bitfield code... * o The bit count is not a multiple of 8. * o The size in bytes is not a power of 2. * o The offset is not a multiple of 8. */ boolean_t is_bitfield(const ctf_encoding_t *ep, ulong_t off) { size_t bsize = ep->cte_bits / NBBY; return (bsize > 8 || (ep->cte_bits % NBBY) != 0 || (bsize & (bsize - 1)) != 0 || (off % NBBY) != 0); } /* * Print an integer bitfield in hexadecimal by reading the enclosing byte(s) * and then shifting and masking the data in the lower bits of a uint64_t. */ static int print_bitfield(ulong_t off, printarg_t *pap, ctf_encoding_t *ep) { mdb_tgt_addr_t addr = pap->pa_addr + off / NBBY; uint64_t mask = (1ULL << ep->cte_bits) - 1; uint64_t value = 0; uint8_t *buf = (uint8_t *)&value; uint8_t shift; const char *format; /* * Our bitfield may straddle a byte boundary. We explicitly take the * offset of the bitfield within its byte into account when determining * the overall amount of data to copy and mask off from the underlying * data. */ uint_t nbits = ep->cte_bits + (off % NBBY); size_t size = P2ROUNDUP(nbits, NBBY) / NBBY; if (!(pap->pa_flags & PA_SHOWVAL)) return (0); if (ep->cte_bits > sizeof (value) * NBBY - 1) { mdb_printf("??? (invalid bitfield size %u)", ep->cte_bits); return (0); } if (size > sizeof (value)) { mdb_printf("??? (total bitfield too large after alignment"); return (0); } /* * On big-endian machines, we need to adjust the buf pointer to refer * to the lowest 'size' bytes in 'value', and we need shift based on * the offset from the end of the data, not the offset of the start. */ #ifdef _BIG_ENDIAN buf += sizeof (value) - size; off += ep->cte_bits; #endif if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, buf, size, addr) != size) { mdb_warn("failed to read %lu bytes at %llx", (ulong_t)size, addr); return (1); } shift = off % NBBY; /* * Offsets are counted from opposite ends on little- and * big-endian machines. */ #ifdef _BIG_ENDIAN shift = NBBY - shift; #endif /* * If the bits we want do not begin on a byte boundary, shift the data * right so that the value is in the lowest 'cte_bits' of 'value'. */ if (off % NBBY != 0) value >>= shift; value &= mask; /* * We default to printing signed bitfields as decimals, * and unsigned bitfields in hexadecimal. If they specify * hexadecimal, we treat the field as unsigned. */ if ((pap->pa_flags & PA_INTHEX) || !(ep->cte_format & CTF_INT_SIGNED)) { format = (pap->pa_flags & PA_INTDEC)? "%#llu" : "%#llx"; } else { int sshift = sizeof (value) * NBBY - ep->cte_bits; /* sign-extend value, and print as a signed decimal */ value = ((int64_t)value << sshift) >> sshift; format = "%#lld"; } mdb_printf(format, value); return (0); } /* * We want to print an escaped char as e.g. '\0'. We don't use mdb_fmt_print() * as it won't get auto-wrap right here (although even now, we don't include any * trailing comma). */ static int print_char_val(mdb_tgt_addr_t addr, printarg_t *pap) { char cval; char *s; if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &cval, 1, addr) != 1) return (1); if (mdb.m_flags & MDB_FL_ADB) s = strchr2adb(&cval, 1); else s = strchr2esc(&cval, 1); mdb_printf("'%s'", s); strfree(s); return (0); } /* * Print out a character or integer value. We use some simple heuristics, * described below, to determine the appropriate radix to use for output. */ static int print_int_val(const char *type, ctf_encoding_t *ep, ulong_t off, printarg_t *pap) { static const char *const sformat[] = { "%#d", "%#d", "%#d", "%#lld" }; static const char *const uformat[] = { "%#u", "%#u", "%#u", "%#llu" }; static const char *const xformat[] = { "%#x", "%#x", "%#x", "%#llx" }; mdb_tgt_addr_t addr = pap->pa_addr + off / NBBY; const char *const *fsp; size_t size; union { uint64_t i8; uint32_t i4; uint16_t i2; uint8_t i1; time_t t; ipaddr_t I; } u; if (!(pap->pa_flags & PA_SHOWVAL)) return (0); if (ep->cte_format & CTF_INT_VARARGS) { mdb_printf("...\n"); return (0); } size = ep->cte_bits / NBBY; if (is_bitfield(ep, off)) { return (print_bitfield(off, pap, ep)); } if (IS_CHAR(*ep)) return (print_char_val(addr, pap)); if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &u.i8, size, addr) != size) { mdb_warn("failed to read %lu bytes at %llx", (ulong_t)size, addr); return (1); } /* * We pretty-print some integer based types. time_t values are * printed as a calendar date and time, and IPv4 addresses as human * readable dotted quads. */ if (!(pap->pa_flags & (PA_INTHEX | PA_INTDEC))) { if (strcmp(type, "time_t") == 0 && u.t != 0) { mdb_printf("%Y", u.t); return (0); } if (strcmp(type, "ipaddr_t") == 0 || strcmp(type, "in_addr_t") == 0) { mdb_printf("%I", u.I); return (0); } } /* * The default format is hexadecimal. */ if (!(pap->pa_flags & PA_INTDEC)) fsp = xformat; else if (ep->cte_format & CTF_INT_SIGNED) fsp = sformat; else fsp = uformat; switch (size) { case sizeof (uint8_t): mdb_printf(fsp[0], u.i1); break; case sizeof (uint16_t): mdb_printf(fsp[1], u.i2); break; case sizeof (uint32_t): mdb_printf(fsp[2], u.i4); break; case sizeof (uint64_t): mdb_printf(fsp[3], u.i8); break; } return (0); } /*ARGSUSED*/ static int print_int(const char *type, const char *name, mdb_ctf_id_t id, mdb_ctf_id_t base, ulong_t off, printarg_t *pap) { ctf_encoding_t e; if (!(pap->pa_flags & PA_SHOWVAL)) return (0); if (mdb_ctf_type_encoding(base, &e) != 0) { mdb_printf("??? (%s)", mdb_strerror(errno)); return (0); } return (print_int_val(type, &e, off, pap)); } /* * Print out a floating point value. We only provide support for floats in * the ANSI-C float, double, and long double formats. */ /*ARGSUSED*/ static int print_float(const char *type, const char *name, mdb_ctf_id_t id, mdb_ctf_id_t base, ulong_t off, printarg_t *pap) { #ifndef _KMDB mdb_tgt_addr_t addr = pap->pa_addr + off / NBBY; ctf_encoding_t e; union { float f; double d; long double ld; } u; if (!(pap->pa_flags & PA_SHOWVAL)) return (0); if (mdb_ctf_type_encoding(base, &e) == 0) { if (e.cte_format == CTF_FP_SINGLE && e.cte_bits == sizeof (float) * NBBY) { if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &u.f, sizeof (u.f), addr) != sizeof (u.f)) { mdb_warn("failed to read float at %llx", addr); return (1); } mdb_printf("%s", doubletos(u.f, 7, 'e')); } else if (e.cte_format == CTF_FP_DOUBLE && e.cte_bits == sizeof (double) * NBBY) { if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &u.d, sizeof (u.d), addr) != sizeof (u.d)) { mdb_warn("failed to read float at %llx", addr); return (1); } mdb_printf("%s", doubletos(u.d, 7, 'e')); } else if (e.cte_format == CTF_FP_LDOUBLE && e.cte_bits == sizeof (long double) * NBBY) { if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &u.ld, sizeof (u.ld), addr) != sizeof (u.ld)) { mdb_warn("failed to read float at %llx", addr); return (1); } mdb_printf("%s", longdoubletos(&u.ld, 16, 'e')); } else { mdb_printf("??? (unsupported FP format %u / %u bits\n", e.cte_format, e.cte_bits); } } else mdb_printf("??? (%s)", mdb_strerror(errno)); #else mdb_printf(""); #endif return (0); } /* * Print out a pointer value as a symbol name + offset or a hexadecimal value. * If the pointer itself is a char *, we attempt to read a bit of the data * referenced by the pointer and display it if it is a printable ASCII string. */ /*ARGSUSED*/ static int print_ptr(const char *type, const char *name, mdb_ctf_id_t id, mdb_ctf_id_t base, ulong_t off, printarg_t *pap) { mdb_tgt_addr_t addr = pap->pa_addr + off / NBBY; ctf_encoding_t e; uintptr_t value; char buf[256]; ssize_t len; if (!(pap->pa_flags & PA_SHOWVAL)) return (0); if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &value, sizeof (value), addr) != sizeof (value)) { mdb_warn("failed to read %s pointer at %llx", name, addr); return (1); } if (pap->pa_flags & PA_NOSYMBOLIC) { mdb_printf("%#lx", value); return (0); } mdb_printf("%a", value); if (value == 0 || strcmp(type, "caddr_t") == 0) return (0); if (mdb_ctf_type_kind(base) == CTF_K_POINTER && mdb_ctf_type_reference(base, &base) != -1 && mdb_ctf_type_resolve(base, &base) != -1 && mdb_ctf_type_encoding(base, &e) == 0 && IS_CHAR(e)) { if ((len = mdb_tgt_readstr(pap->pa_realtgt, pap->pa_as, buf, sizeof (buf), value)) >= 0 && strisprint(buf)) { if (len == sizeof (buf)) (void) strabbr(buf, sizeof (buf)); mdb_printf(" \"%s\"", buf); } } return (0); } /* * Print out a fixed-size array. We special-case arrays of characters * and attempt to print them out as ASCII strings if possible. For other * arrays, we iterate over a maximum of pa_armemlim members and call * mdb_ctf_type_visit() again on each element to print its value. */ /*ARGSUSED*/ static int print_array(const char *type, const char *name, mdb_ctf_id_t id, mdb_ctf_id_t base, ulong_t off, printarg_t *pap) { mdb_tgt_addr_t addr = pap->pa_addr + off / NBBY; printarg_t pa = *pap; ssize_t eltsize; mdb_ctf_arinfo_t r; ctf_encoding_t e; uint_t i, kind, limit; int d, sou; char buf[8]; char *str; if (!(pap->pa_flags & PA_SHOWVAL)) return (0); if (pap->pa_depth == pap->pa_maxdepth) { mdb_printf("[ ... ]"); return (0); } /* * Determine the base type and size of the array's content. If this * fails, we cannot print anything and just give up. */ if (mdb_ctf_array_info(base, &r) == -1 || mdb_ctf_type_resolve(r.mta_contents, &base) == -1 || (eltsize = mdb_ctf_type_size(base)) == -1) { mdb_printf("[ ??? ] (%s)", mdb_strerror(errno)); return (0); } /* * Read a few bytes and determine if the content appears to be * printable ASCII characters. If so, read the entire array and * attempt to display it as a string if it is printable. */ if ((pap->pa_arstrlim == MDB_ARR_NOLIMIT || r.mta_nelems <= pap->pa_arstrlim) && mdb_ctf_type_encoding(base, &e) == 0 && IS_CHAR(e) && mdb_tgt_readstr(pap->pa_tgt, pap->pa_as, buf, MIN(sizeof (buf), r.mta_nelems), addr) > 0 && strisprint(buf)) { str = mdb_alloc(r.mta_nelems + 1, UM_SLEEP | UM_GC); str[r.mta_nelems] = '\0'; if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, str, r.mta_nelems, addr) != r.mta_nelems) { mdb_warn("failed to read char array at %llx", addr); return (1); } if (strisprint(str)) { mdb_printf("[ \"%s\" ]", str); return (0); } } if (pap->pa_armemlim != MDB_ARR_NOLIMIT) limit = MIN(r.mta_nelems, pap->pa_armemlim); else limit = r.mta_nelems; if (limit == 0) { mdb_printf("[ ... ]"); return (0); } kind = mdb_ctf_type_kind(base); sou = IS_COMPOSITE(kind); pa.pa_addr = addr; /* set base address to start of array */ pa.pa_maxdepth = pa.pa_maxdepth - pa.pa_depth - 1; pa.pa_nest += pa.pa_depth + 1; /* nesting level is current depth + 1 */ pa.pa_depth = 0; /* reset depth to 0 for new scope */ pa.pa_prefix = NULL; if (sou) { pa.pa_delim = "\n"; mdb_printf("[\n"); } else { pa.pa_flags &= ~(PA_SHOWTYPE | PA_SHOWNAME | PA_SHOWADDR); pa.pa_delim = ", "; mdb_printf("[ "); } for (i = 0; i < limit; i++, pa.pa_addr += eltsize) { if (i == limit - 1 && !sou) { if (limit < r.mta_nelems) pa.pa_delim = ", ... ]"; else pa.pa_delim = " ]"; } if (mdb_ctf_type_visit(r.mta_contents, elt_print, &pa) == -1) { mdb_warn("failed to print array data"); return (1); } } if (sou) { for (d = pa.pa_depth - 1; d >= 0; d--) print_close_sou(&pa, d); if (limit < r.mta_nelems) { mdb_printf("%*s... ]", (pap->pa_depth + pap->pa_nest) * pap->pa_tab, ""); } else { mdb_printf("%*s]", (pap->pa_depth + pap->pa_nest) * pap->pa_tab, ""); } } /* copy the hole array info, since it may have been grown */ pap->pa_holes = pa.pa_holes; pap->pa_nholes = pa.pa_nholes; return (0); } /* * Print out a struct or union header. We need only print the open brace * because mdb_ctf_type_visit() itself will automatically recurse through * all members of the given struct or union. */ /*ARGSUSED*/ static int print_sou(const char *type, const char *name, mdb_ctf_id_t id, mdb_ctf_id_t base, ulong_t off, printarg_t *pap) { mdb_tgt_addr_t addr = pap->pa_addr + off / NBBY; /* * We have pretty-printing for some structures where displaying * structure contents has no value. */ if (pap->pa_flags & PA_SHOWVAL) { if (strcmp(type, "in6_addr_t") == 0 || strcmp(type, "struct in6_addr") == 0) { in6_addr_t in6addr; if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &in6addr, sizeof (in6addr), addr) != sizeof (in6addr)) { mdb_warn("failed to read %s pointer at %llx", name, addr); return (1); } mdb_printf("%N", &in6addr); /* * Don't print anything further down in the * structure. */ pap->pa_nooutdepth = pap->pa_depth; return (0); } if (strcmp(type, "struct in_addr") == 0) { in_addr_t inaddr; if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &inaddr, sizeof (inaddr), addr) != sizeof (inaddr)) { mdb_warn("failed to read %s pointer at %llx", name, addr); return (1); } mdb_printf("%I", inaddr); pap->pa_nooutdepth = pap->pa_depth; return (0); } } if (pap->pa_depth == pap->pa_maxdepth) mdb_printf("{ ... }"); else mdb_printf("{"); pap->pa_delim = "\n"; return (0); } /* * Print an enum value. We attempt to convert the value to the corresponding * enum name and print that if possible. */ /*ARGSUSED*/ static int print_enum(const char *type, const char *name, mdb_ctf_id_t id, mdb_ctf_id_t base, ulong_t off, printarg_t *pap) { mdb_tgt_addr_t addr = pap->pa_addr + off / NBBY; const char *ename; int value; int isp2 = enum_is_p2(base); int flags = pap->pa_flags | (isp2 ? PA_INTHEX : 0); if (!(flags & PA_SHOWVAL)) return (0); if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &value, sizeof (value), addr) != sizeof (value)) { mdb_warn("failed to read %s integer at %llx", name, addr); return (1); } if (flags & PA_INTHEX) mdb_printf("%#x", value); else mdb_printf("%#d", value); (void) mdb_inc_indent(8); mdb_printf(" ("); if (!isp2 || enum_value_print_p2(base, value, 0) != 0) { ename = mdb_ctf_enum_name(base, value); if (ename == NULL) { ename = "???"; } mdb_printf("%s", ename); } mdb_printf(")"); (void) mdb_dec_indent(8); return (0); } /* * This will only get called if the structure isn't found in any available CTF * data. */ /*ARGSUSED*/ static int print_tag(const char *type, const char *name, mdb_ctf_id_t id, mdb_ctf_id_t base, ulong_t off, printarg_t *pap) { char basename[MDB_SYM_NAMLEN]; if (pap->pa_flags & PA_SHOWVAL) mdb_printf("; "); if (mdb_ctf_type_name(base, basename, sizeof (basename)) != NULL) mdb_printf("", basename); else mdb_printf(""); return (0); } static void print_hole(printarg_t *pap, int depth, ulong_t off, ulong_t endoff) { ulong_t bits = endoff - off; ulong_t size = bits / NBBY; ctf_encoding_t e; static const char *const name = "<>"; char type[MDB_SYM_NAMLEN]; int bitfield = (off % NBBY != 0 || bits % NBBY != 0 || size > 8 || (size & (size - 1)) != 0); ASSERT(off < endoff); if (bits > NBBY * sizeof (uint64_t)) { ulong_t end; /* * The hole is larger than the largest integer type. To * handle this, we split up the hole at 8-byte-aligned * boundaries, recursing to print each subsection. For * normal C structures, we'll loop at most twice. */ for (; off < endoff; off = end) { end = P2END(off, NBBY * sizeof (uint64_t)); if (end > endoff) end = endoff; ASSERT((end - off) <= NBBY * sizeof (uint64_t)); print_hole(pap, depth, off, end); } ASSERT(end == endoff); return; } if (bitfield) (void) mdb_snprintf(type, sizeof (type), "unsigned"); else (void) mdb_snprintf(type, sizeof (type), "uint%d_t", bits); if (pap->pa_flags & (PA_SHOWTYPE | PA_SHOWNAME | PA_SHOWADDR)) mdb_printf("%*s", (depth + pap->pa_nest) * pap->pa_tab, ""); if (pap->pa_flags & PA_SHOWADDR) { if (off % NBBY == 0) mdb_printf("%llx ", pap->pa_addr + off / NBBY); else mdb_printf("%llx.%lx ", pap->pa_addr + off / NBBY, off % NBBY); } if (pap->pa_flags & PA_SHOWTYPE) mdb_printf("%s ", type); if (pap->pa_flags & PA_SHOWNAME) mdb_printf("%s", name); if (bitfield && (pap->pa_flags & PA_SHOWTYPE)) mdb_printf(" :%d", bits); mdb_printf("%s ", (pap->pa_flags & PA_SHOWVAL)? " =" : ""); /* * We fake up a ctf_encoding_t, and use print_int_val() to print * the value. Holes are always processed as unsigned integers. */ bzero(&e, sizeof (e)); e.cte_format = 0; e.cte_offset = 0; e.cte_bits = bits; if (print_int_val(type, &e, off, pap) != 0) mdb_iob_discard(mdb.m_out); else mdb_iob_puts(mdb.m_out, pap->pa_delim); } /* * The print_close_sou() function is called for each structure or union * which has been completed. For structures, we detect and print any holes * before printing the closing brace. */ static void print_close_sou(printarg_t *pap, int newdepth) { int d = newdepth + pap->pa_nest; if ((pap->pa_flags & PA_SHOWHOLES) && !pap->pa_holes[d].hi_isunion) { ulong_t end = pap->pa_holes[d + 1].hi_offset; ulong_t expected = pap->pa_holes[d].hi_offset; if (end < expected) print_hole(pap, newdepth + 1, end, expected); } /* if the struct is an array element, print a comma after the } */ mdb_printf("%*s}%s\n", d * pap->pa_tab, "", (newdepth == 0 && pap->pa_nest > 0)? "," : ""); } static printarg_f *const printfuncs[] = { print_int, /* CTF_K_INTEGER */ print_float, /* CTF_K_FLOAT */ print_ptr, /* CTF_K_POINTER */ print_array, /* CTF_K_ARRAY */ print_ptr, /* CTF_K_FUNCTION */ print_sou, /* CTF_K_STRUCT */ print_sou, /* CTF_K_UNION */ print_enum, /* CTF_K_ENUM */ print_tag /* CTF_K_FORWARD */ }; /* * The elt_print function is used as the mdb_ctf_type_visit callback. For * each element, we print an appropriate name prefix and then call the * print subroutine for this type class in the array above. */ static int elt_print(const char *name, mdb_ctf_id_t id, mdb_ctf_id_t base, ulong_t off, int depth, void *data) { char type[MDB_SYM_NAMLEN + sizeof (" <<12345678...>>")]; int kind, rc, d; printarg_t *pap = data; for (d = pap->pa_depth - 1; d >= depth; d--) { if (d < pap->pa_nooutdepth) print_close_sou(pap, d); } /* * Reset pa_nooutdepth if we've come back out of the structure we * didn't want to print. */ if (depth <= pap->pa_nooutdepth) pap->pa_nooutdepth = (uint_t)-1; if (depth > pap->pa_maxdepth || depth > pap->pa_nooutdepth) return (0); if (!mdb_ctf_type_valid(base) || (kind = mdb_ctf_type_kind(base)) == -1) return (-1); /* errno is set for us */ if (mdb_ctf_type_name(id, type, MDB_SYM_NAMLEN) == NULL) (void) strcpy(type, "(?)"); if (pap->pa_flags & PA_SHOWBASETYPE) { /* * If basetype is different and informative, concatenate * <> (or <> if it doesn't fit) * * We just use the end of the buffer to store the type name, and * only connect it up if that's necessary. */ char *type_end = type + strlen(type); char *basetype; size_t sz; (void) strlcat(type, " <<", sizeof (type)); basetype = type + strlen(type); sz = sizeof (type) - (basetype - type); *type_end = '\0'; /* restore the end of type for strcmp() */ if (mdb_ctf_type_name(base, basetype, sz) != NULL && strcmp(basetype, type) != 0 && strcmp(basetype, "struct ") != 0 && strcmp(basetype, "enum ") != 0 && strcmp(basetype, "union ") != 0) { type_end[0] = ' '; /* reconnect */ if (strlcat(type, ">>", sizeof (type)) >= sizeof (type)) (void) strlcpy( type + sizeof (type) - 6, "...>>", 6); } } if (pap->pa_flags & PA_SHOWHOLES) { ctf_encoding_t e; ssize_t nsize; ulong_t newoff; holeinfo_t *hole; int extra = IS_COMPOSITE(kind)? 1 : 0; /* * grow the hole array, if necessary */ if (pap->pa_nest + depth + extra >= pap->pa_nholes) { int new = MAX(MAX(8, pap->pa_nholes * 2), pap->pa_nest + depth + extra + 1); holeinfo_t *nhi = mdb_zalloc( sizeof (*nhi) * new, UM_NOSLEEP | UM_GC); bcopy(pap->pa_holes, nhi, pap->pa_nholes * sizeof (*nhi)); pap->pa_holes = nhi; pap->pa_nholes = new; } hole = &pap->pa_holes[depth + pap->pa_nest]; if (depth != 0 && off > hole->hi_offset) print_hole(pap, depth, hole->hi_offset, off); /* compute the next expected offset */ if (kind == CTF_K_INTEGER && mdb_ctf_type_encoding(base, &e) == 0) newoff = off + e.cte_bits; else if ((nsize = mdb_ctf_type_size(base)) >= 0) newoff = off + nsize * NBBY; else { /* something bad happened, disable hole checking */ newoff = -1UL; /* ULONG_MAX */ } hole->hi_offset = newoff; if (IS_COMPOSITE(kind)) { hole->hi_isunion = (kind == CTF_K_UNION); hole++; hole->hi_offset = off; } } if (pap->pa_flags & (PA_SHOWTYPE | PA_SHOWNAME | PA_SHOWADDR)) mdb_printf("%*s", (depth + pap->pa_nest) * pap->pa_tab, ""); if (pap->pa_flags & PA_SHOWADDR) { if (off % NBBY == 0) mdb_printf("%llx ", pap->pa_addr + off / NBBY); else mdb_printf("%llx.%lx ", pap->pa_addr + off / NBBY, off % NBBY); } if ((pap->pa_flags & PA_SHOWTYPE)) { mdb_printf("%s", type); /* * We want to avoid printing a trailing space when * dealing with pointers in a structure, so we end * up with: * * label_t *t_onfault = 0 * * If depth is zero, always print the trailing space unless * we also have a prefix. */ if (type[strlen(type) - 1] != '*' || (depth == 0 && (!(pap->pa_flags & PA_SHOWNAME) || pap->pa_prefix == NULL))) mdb_printf(" "); } if (pap->pa_flags & PA_SHOWNAME) { if (pap->pa_prefix != NULL && depth <= 1) mdb_printf("%s%s", pap->pa_prefix, (depth == 0) ? "" : pap->pa_suffix); /* * Figure out if we're printing an anonymous struct or union. If * so, indicate that this is anonymous. */ if (depth != 0 && *name == '\0' && (kind == CTF_K_STRUCT || kind == CTF_K_UNION)) { name = ""; } mdb_printf("%s", name); } if ((pap->pa_flags & PA_SHOWTYPE) && kind == CTF_K_INTEGER) { ctf_encoding_t e; if (mdb_ctf_type_encoding(base, &e) == 0) { ulong_t bits = e.cte_bits; ulong_t size = bits / NBBY; if (bits % NBBY != 0 || off % NBBY != 0 || size > 8 || size != mdb_ctf_type_size(base)) mdb_printf(" :%d", bits); } } if (depth != 0 || ((pap->pa_flags & PA_SHOWNAME) && pap->pa_prefix != NULL)) mdb_printf("%s ", pap->pa_flags & PA_SHOWVAL ? " =" : ""); if (depth == 0 && pap->pa_prefix != NULL) name = pap->pa_prefix; pap->pa_depth = depth; if (kind <= CTF_K_UNKNOWN || kind >= CTF_K_TYPEDEF) { mdb_warn("unknown ctf for %s type %s kind %d\n", name, type, kind); return (-1); } rc = printfuncs[kind - 1](type, name, id, base, off, pap); if (rc != 0) mdb_iob_discard(mdb.m_out); else mdb_iob_puts(mdb.m_out, pap->pa_delim); return (rc); } /* * Special semantics for pipelines. */ static int pipe_print(mdb_ctf_id_t id, ulong_t off, void *data) { printarg_t *pap = data; size_t size; static const char *const fsp[] = { "%#r", "%#r", "%#r", "%#llr" }; uintptr_t value; uintptr_t addr = pap->pa_addr + off / NBBY; mdb_ctf_id_t base; int enum_value; ctf_encoding_t e; union { uint64_t i8; uint32_t i4; uint16_t i2; uint8_t i1; } u; if (mdb_ctf_type_resolve(id, &base) == -1) { mdb_warn("could not resolve type"); return (-1); } /* * If the user gives -a, then always print out the address of the * member. */ if ((pap->pa_flags & PA_SHOWADDR)) { mdb_printf("%#lr\n", addr); return (0); } switch (mdb_ctf_type_kind(base)) { case CTF_K_POINTER: if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &value, sizeof (value), addr) != sizeof (value)) { mdb_warn("failed to read pointer at %p", addr); return (-1); } mdb_printf("%#lr\n", value); break; case CTF_K_ENUM: if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &enum_value, sizeof (enum_value), addr) != sizeof (enum_value)) { mdb_warn("failed to read enum at %llx", addr); return (-1); } mdb_printf("%#r\n", enum_value); break; case CTF_K_INTEGER: if (mdb_ctf_type_encoding(base, &e) != 0) { mdb_warn("could not get type encoding\n"); return (-1); } /* * For immediate values, we just print out the value. */ size = e.cte_bits / NBBY; if (is_bitfield(&e, off)) { return (print_bitfield(off, pap, &e)); } if (mdb_tgt_aread(pap->pa_tgt, pap->pa_as, &u.i8, size, addr) != (size_t)size) { mdb_warn("failed to read %lu bytes at %p", (ulong_t)size, pap->pa_addr); return (-1); } switch (size) { case sizeof (uint8_t): mdb_printf(fsp[0], u.i1); break; case sizeof (uint16_t): mdb_printf(fsp[1], u.i2); break; case sizeof (uint32_t): mdb_printf(fsp[2], u.i4); break; case sizeof (uint64_t): mdb_printf(fsp[3], u.i8); break; } mdb_printf("\n"); break; case CTF_K_FUNCTION: case CTF_K_FLOAT: case CTF_K_ARRAY: case CTF_K_UNKNOWN: case CTF_K_STRUCT: case CTF_K_UNION: case CTF_K_FORWARD: /* * For these types, always print the address of the member */ mdb_printf("%#lr\n", addr); break; default: mdb_warn("unknown type %d", mdb_ctf_type_kind(base)); break; } return (0); } static int parse_delimiter(char **strp) { switch (**strp) { case '\0': return (MEMBER_DELIM_DONE); case '.': *strp = *strp + 1; return (MEMBER_DELIM_DOT); case '[': *strp = *strp + 1; return (MEMBER_DELIM_LBR); case '-': *strp = *strp + 1; if (**strp == '>') { *strp = *strp + 1; return (MEMBER_DELIM_PTR); } *strp = *strp - 1; /*FALLTHROUGH*/ default: return (MEMBER_DELIM_ERR); } } static int deref(printarg_t *pap, size_t size) { uint32_t a32; mdb_tgt_as_t as = pap->pa_as; mdb_tgt_addr_t *ap = &pap->pa_addr; if (size == sizeof (mdb_tgt_addr_t)) { if (mdb_tgt_aread(mdb.m_target, as, ap, size, *ap) == -1) { mdb_warn("could not dereference pointer %llx\n", *ap); return (-1); } } else { if (mdb_tgt_aread(mdb.m_target, as, &a32, size, *ap) == -1) { mdb_warn("could not dereference pointer %x\n", *ap); return (-1); } *ap = (mdb_tgt_addr_t)a32; } /* * We've dereferenced at least once, we must be on the real * target. If we were in the immediate target, reset to the real * target; it's reset as needed when we return to the print * routines. */ if (pap->pa_tgt == pap->pa_immtgt) pap->pa_tgt = pap->pa_realtgt; return (0); } static int parse_member(printarg_t *pap, const char *str, mdb_ctf_id_t id, mdb_ctf_id_t *idp, ulong_t *offp, int *last_deref) { int delim; char member[64]; char buf[128]; uint_t index; char *start = (char *)str; char *end; ulong_t off = 0; mdb_ctf_arinfo_t ar; mdb_ctf_id_t rid; int kind; ssize_t size; int non_array = FALSE; /* * id always has the unresolved type for printing error messages * that include the type; rid always has the resolved type for * use in mdb_ctf_* calls. It is possible for this command to fail, * however, if the resolved type is in the parent and it is currently * unavailable. Note that we also can't print out the name of the * type, since that would also rely on looking up the resolved name. */ if (mdb_ctf_type_resolve(id, &rid) != 0) { mdb_warn("failed to resolve type"); return (-1); } delim = parse_delimiter(&start); /* * If the user fails to specify an initial delimiter, guess -> for * pointer types and . for non-pointer types. */ if (delim == MEMBER_DELIM_ERR) delim = (mdb_ctf_type_kind(rid) == CTF_K_POINTER) ? MEMBER_DELIM_PTR : MEMBER_DELIM_DOT; *last_deref = FALSE; while (delim != MEMBER_DELIM_DONE) { switch (delim) { case MEMBER_DELIM_PTR: kind = mdb_ctf_type_kind(rid); if (kind != CTF_K_POINTER) { mdb_warn("%s is not a pointer type\n", mdb_ctf_type_name(id, buf, sizeof (buf))); return (-1); } size = mdb_ctf_type_size(id); if (deref(pap, size) != 0) return (-1); (void) mdb_ctf_type_reference(rid, &id); (void) mdb_ctf_type_resolve(id, &rid); off = 0; break; case MEMBER_DELIM_DOT: kind = mdb_ctf_type_kind(rid); if (kind != CTF_K_STRUCT && kind != CTF_K_UNION) { mdb_warn("%s is not a struct or union type\n", mdb_ctf_type_name(id, buf, sizeof (buf))); return (-1); } break; case MEMBER_DELIM_LBR: end = strchr(start, ']'); if (end == NULL) { mdb_warn("no trailing ']'\n"); return (-1); } (void) mdb_snprintf(member, end - start + 1, "%s", start); index = mdb_strtoull(member); switch (mdb_ctf_type_kind(rid)) { case CTF_K_POINTER: size = mdb_ctf_type_size(rid); if (deref(pap, size) != 0) return (-1); (void) mdb_ctf_type_reference(rid, &id); (void) mdb_ctf_type_resolve(id, &rid); size = mdb_ctf_type_size(id); if (size <= 0) { mdb_warn("cannot dereference void " "type\n"); return (-1); } pap->pa_addr += index * size; off = 0; if (index == 0 && non_array) *last_deref = TRUE; break; case CTF_K_ARRAY: (void) mdb_ctf_array_info(rid, &ar); if (index >= ar.mta_nelems) { mdb_warn("index %r is outside of " "array bounds [0 .. %r]\n", index, ar.mta_nelems - 1); } id = ar.mta_contents; (void) mdb_ctf_type_resolve(id, &rid); size = mdb_ctf_type_size(id); if (size <= 0) { mdb_warn("cannot dereference void " "type\n"); return (-1); } pap->pa_addr += index * size; off = 0; break; default: mdb_warn("cannot index into non-array, " "non-pointer type\n"); return (-1); } start = end + 1; delim = parse_delimiter(&start); continue; case MEMBER_DELIM_ERR: default: mdb_warn("'%c' is not a valid delimiter\n", *start); return (-1); } *last_deref = FALSE; non_array = TRUE; /* * Find the end of the member name; assume that a member * name is at least one character long. */ for (end = start + 1; isalnum(*end) || *end == '_'; end++) continue; (void) mdb_snprintf(member, end - start + 1, "%s", start); if (mdb_ctf_member_info(rid, member, &off, &id) != 0) { mdb_warn("failed to find member %s of %s", member, mdb_ctf_type_name(id, buf, sizeof (buf))); return (-1); } (void) mdb_ctf_type_resolve(id, &rid); pap->pa_addr += off / NBBY; start = end; delim = parse_delimiter(&start); } *idp = id; *offp = off; return (0); } static int cmd_print_tab_common(mdb_tab_cookie_t *mcp, uint_t flags, int argc, const mdb_arg_t *argv) { char tn[MDB_SYM_NAMLEN]; char member[64]; int delim, kind; int ret = 0; mdb_ctf_id_t id, rid; mdb_ctf_arinfo_t ar; char *start, *end; ulong_t dul; if (argc == 0 && !(flags & DCMD_TAB_SPACE)) return (0); if (argc == 0 && (flags & DCMD_TAB_SPACE)) return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_NOPOINT | MDB_TABC_NOARRAY)); if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0) return (ret); if (argc == 1 && (!(flags & DCMD_TAB_SPACE) || ret == 1)) return (mdb_tab_complete_type(mcp, tn, MDB_TABC_NOPOINT | MDB_TABC_NOARRAY)); if (argc == 1 && (flags & DCMD_TAB_SPACE)) return (mdb_tab_complete_member(mcp, tn, NULL)); /* * This is the reason that tab completion was created. We're going to go * along and walk the delimiters until we find something a member that * we don't recognize, at which point we'll try and tab complete it. * Note that ::print takes multiple args, so this is going to operate on * whatever the last arg that we have is. */ if (mdb_ctf_lookup_by_name(tn, &id) != 0) return (1); (void) mdb_ctf_type_resolve(id, &rid); start = (char *)argv[argc-1].a_un.a_str; delim = parse_delimiter(&start); /* * If we hit the case where we actually have no delimiters, than we need * to make sure that we properly set up the fields the loops would. */ if (delim == MEMBER_DELIM_DONE) (void) mdb_snprintf(member, sizeof (member), "%s", start); while (delim != MEMBER_DELIM_DONE) { switch (delim) { case MEMBER_DELIM_PTR: kind = mdb_ctf_type_kind(rid); if (kind != CTF_K_POINTER) return (1); (void) mdb_ctf_type_reference(rid, &id); (void) mdb_ctf_type_resolve(id, &rid); break; case MEMBER_DELIM_DOT: kind = mdb_ctf_type_kind(rid); if (kind != CTF_K_STRUCT && kind != CTF_K_UNION) return (1); break; case MEMBER_DELIM_LBR: end = strchr(start, ']'); /* * We're not going to try and tab complete the indexes * here. So for now, punt on it. Also, we're not going * to try and validate you're within the bounds, just * that you get the type you asked for. */ if (end == NULL) return (1); switch (mdb_ctf_type_kind(rid)) { case CTF_K_POINTER: (void) mdb_ctf_type_reference(rid, &id); (void) mdb_ctf_type_resolve(id, &rid); break; case CTF_K_ARRAY: (void) mdb_ctf_array_info(rid, &ar); id = ar.mta_contents; (void) mdb_ctf_type_resolve(id, &rid); break; default: return (1); } start = end + 1; delim = parse_delimiter(&start); break; case MEMBER_DELIM_ERR: default: break; } for (end = start + 1; isalnum(*end) || *end == '_'; end++) continue; (void) mdb_snprintf(member, end - start + 1, start); /* * We are going to try to resolve this name as a member. There * are a few two different questions that we need to answer. The * first is do we recognize this member. The second is are we at * the end of the string. If we encounter a member that we don't * recognize before the end, then we have to error out and can't * complete it. But if there are no more delimiters then we can * try and complete it. */ ret = mdb_ctf_member_info(rid, member, &dul, &id); start = end; delim = parse_delimiter(&start); if (ret != 0 && errno == EMDB_CTFNOMEMB) { if (delim != MEMBER_DELIM_DONE) return (1); continue; } else if (ret != 0) return (1); if (delim == MEMBER_DELIM_DONE) return (mdb_tab_complete_member_by_id(mcp, rid, member)); (void) mdb_ctf_type_resolve(id, &rid); } /* * If we've reached here, then we need to try and tab complete the last * field, which is currently member, based on the ctf type id that we * already have in rid. */ return (mdb_tab_complete_member_by_id(mcp, rid, member)); } int cmd_print_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, const mdb_arg_t *argv) { int i, dummy; /* * This getopts is only here to make the tab completion work better when * including options in the ::print arguments. None of the values should * be used. This should only be updated with additional arguments, if * they are added to cmd_print. */ i = mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, PA_SHOWADDR, &dummy, 'C', MDB_OPT_SETBITS, TRUE, &dummy, 'c', MDB_OPT_UINTPTR, &dummy, 'd', MDB_OPT_SETBITS, PA_INTDEC, &dummy, 'h', MDB_OPT_SETBITS, PA_SHOWHOLES, &dummy, 'i', MDB_OPT_SETBITS, TRUE, &dummy, 'L', MDB_OPT_SETBITS, TRUE, &dummy, 'l', MDB_OPT_UINTPTR, &dummy, 'n', MDB_OPT_SETBITS, PA_NOSYMBOLIC, &dummy, 'p', MDB_OPT_SETBITS, TRUE, &dummy, 's', MDB_OPT_UINTPTR, &dummy, 'T', MDB_OPT_SETBITS, PA_SHOWTYPE | PA_SHOWBASETYPE, &dummy, 't', MDB_OPT_SETBITS, PA_SHOWTYPE, &dummy, 'x', MDB_OPT_SETBITS, PA_INTHEX, &dummy, NULL); argc -= i; argv += i; return (cmd_print_tab_common(mcp, flags, argc, argv)); } /* * Recursively descend a print a given data structure. We create a struct of * the relevant print arguments and then call mdb_ctf_type_visit() to do the * traversal, using elt_print() as the callback for each element. */ /*ARGSUSED*/ int cmd_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t opt_c = MDB_ARR_NOLIMIT, opt_l = MDB_ARR_NOLIMIT; uint_t opt_C = FALSE, opt_L = FALSE, opt_p = FALSE, opt_i = FALSE; uintptr_t opt_s = (uintptr_t)-1ul; int uflags = (flags & DCMD_ADDRSPEC) ? PA_SHOWVAL : 0; mdb_ctf_id_t id; int err = DCMD_OK; mdb_tgt_t *t = mdb.m_target; printarg_t pa; int d, i; char s_name[MDB_SYM_NAMLEN]; mdb_syminfo_t s_info; GElf_Sym sym; /* * If a new option is added, make sure the getopts above in * cmd_print_tab is also updated. */ i = mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, PA_SHOWADDR, &uflags, 'C', MDB_OPT_SETBITS, TRUE, &opt_C, 'c', MDB_OPT_UINTPTR, &opt_c, 'd', MDB_OPT_SETBITS, PA_INTDEC, &uflags, 'h', MDB_OPT_SETBITS, PA_SHOWHOLES, &uflags, 'i', MDB_OPT_SETBITS, TRUE, &opt_i, 'L', MDB_OPT_SETBITS, TRUE, &opt_L, 'l', MDB_OPT_UINTPTR, &opt_l, 'n', MDB_OPT_SETBITS, PA_NOSYMBOLIC, &uflags, 'p', MDB_OPT_SETBITS, TRUE, &opt_p, 's', MDB_OPT_UINTPTR, &opt_s, 'T', MDB_OPT_SETBITS, PA_SHOWTYPE | PA_SHOWBASETYPE, &uflags, 't', MDB_OPT_SETBITS, PA_SHOWTYPE, &uflags, 'x', MDB_OPT_SETBITS, PA_INTHEX, &uflags, NULL); if (uflags & PA_INTHEX) uflags &= ~PA_INTDEC; /* -x and -d are mutually exclusive */ uflags |= PA_SHOWNAME; if (opt_p && opt_i) { mdb_warn("-p and -i options are incompatible\n"); return (DCMD_ERR); } argc -= i; argv += i; if (argc != 0 && argv->a_type == MDB_TYPE_STRING) { const char *t_name = s_name; int ret; if (strchr("+-", argv->a_un.a_str[0]) != NULL) return (DCMD_USAGE); if ((ret = args_to_typename(&argc, &argv, s_name, sizeof (s_name))) != 0) return (ret); if (mdb_ctf_lookup_by_name(t_name, &id) != 0) { if (!(flags & DCMD_ADDRSPEC) || opt_i || addr_to_sym(t, addr, s_name, sizeof (s_name), &sym, &s_info) == NULL || mdb_ctf_lookup_by_symbol(&sym, &s_info, &id) != 0) { mdb_warn("failed to look up type %s", t_name); return (DCMD_ABORT); } } else { argc--; argv++; } } else if (!(flags & DCMD_ADDRSPEC) || opt_i) { return (DCMD_USAGE); } else if (addr_to_sym(t, addr, s_name, sizeof (s_name), &sym, &s_info) == NULL) { mdb_warn("no symbol information for %a", addr); return (DCMD_ERR); } else if (mdb_ctf_lookup_by_symbol(&sym, &s_info, &id) != 0) { mdb_warn("no type data available for %a [%u]", addr, s_info.sym_id); return (DCMD_ERR); } pa.pa_tgt = mdb.m_target; pa.pa_realtgt = pa.pa_tgt; pa.pa_immtgt = NULL; pa.pa_as = opt_p ? MDB_TGT_AS_PHYS : MDB_TGT_AS_VIRT; pa.pa_armemlim = mdb.m_armemlim; pa.pa_arstrlim = mdb.m_arstrlim; pa.pa_delim = "\n"; pa.pa_flags = uflags; pa.pa_nest = 0; pa.pa_tab = 4; pa.pa_prefix = NULL; pa.pa_suffix = NULL; pa.pa_holes = NULL; pa.pa_nholes = 0; pa.pa_depth = 0; pa.pa_maxdepth = opt_s; pa.pa_nooutdepth = (uint_t)-1; if ((flags & DCMD_ADDRSPEC) && !opt_i) pa.pa_addr = opt_p ? mdb_get_dot() : addr; else pa.pa_addr = 0; if (opt_i) { const char *vargv[2]; uintmax_t dot = mdb_get_dot(); size_t outsize = mdb_ctf_type_size(id); vargv[0] = (const char *)˙ vargv[1] = (const char *)&outsize; pa.pa_immtgt = mdb_tgt_create(mdb_value_tgt_create, 0, 2, vargv); pa.pa_tgt = pa.pa_immtgt; } if (opt_c != MDB_ARR_NOLIMIT) pa.pa_arstrlim = opt_c; if (opt_C) pa.pa_arstrlim = MDB_ARR_NOLIMIT; if (opt_l != MDB_ARR_NOLIMIT) pa.pa_armemlim = opt_l; if (opt_L) pa.pa_armemlim = MDB_ARR_NOLIMIT; if (argc > 0) { for (i = 0; i < argc; i++) { mdb_ctf_id_t mid; int last_deref; ulong_t off; int kind; char buf[MDB_SYM_NAMLEN]; mdb_tgt_t *oldtgt = pa.pa_tgt; mdb_tgt_as_t oldas = pa.pa_as; mdb_tgt_addr_t oldaddr = pa.pa_addr; if (argv->a_type == MDB_TYPE_STRING) { const char *member = argv[i].a_un.a_str; mdb_ctf_id_t rid; if (parse_member(&pa, member, id, &mid, &off, &last_deref) != 0) { err = DCMD_ABORT; goto out; } /* * If the member string ends with a "[0]" * (last_deref * is true) and the type is a * structure or union, * print "->" rather * than "[0]." in elt_print. */ (void) mdb_ctf_type_resolve(mid, &rid); kind = mdb_ctf_type_kind(rid); if (last_deref && IS_SOU(kind)) { char *end; (void) mdb_snprintf(buf, sizeof (buf), "%s", member); end = strrchr(buf, '['); *end = '\0'; pa.pa_suffix = "->"; member = &buf[0]; } else if (IS_SOU(kind)) { pa.pa_suffix = "."; } else { pa.pa_suffix = ""; } pa.pa_prefix = member; } else { ulong_t moff; moff = (ulong_t)argv[i].a_un.a_val; if (mdb_ctf_offset_to_name(id, moff * NBBY, buf, sizeof (buf), 0, &mid, &off) == -1) { mdb_warn("invalid offset %lx\n", moff); err = DCMD_ABORT; goto out; } pa.pa_prefix = buf; pa.pa_addr += moff - off / NBBY; pa.pa_suffix = strlen(buf) == 0 ? "" : "."; } off %= NBBY; if (flags & DCMD_PIPE_OUT) { if (pipe_print(mid, off, &pa) != 0) { mdb_warn("failed to print type"); err = DCMD_ERR; goto out; } } else if (off != 0) { mdb_ctf_id_t base; (void) mdb_ctf_type_resolve(mid, &base); if (elt_print("", mid, base, off, 0, &pa) != 0) { mdb_warn("failed to print type"); err = DCMD_ERR; goto out; } } else { if (mdb_ctf_type_visit(mid, elt_print, &pa) == -1) { mdb_warn("failed to print type"); err = DCMD_ERR; goto out; } for (d = pa.pa_depth - 1; d >= 0; d--) print_close_sou(&pa, d); } pa.pa_depth = 0; pa.pa_tgt = oldtgt; pa.pa_as = oldas; pa.pa_addr = oldaddr; pa.pa_delim = "\n"; } } else if (flags & DCMD_PIPE_OUT) { if (pipe_print(id, 0, &pa) != 0) { mdb_warn("failed to print type"); err = DCMD_ERR; goto out; } } else { if (mdb_ctf_type_visit(id, elt_print, &pa) == -1) { mdb_warn("failed to print type"); err = DCMD_ERR; goto out; } for (d = pa.pa_depth - 1; d >= 0; d--) print_close_sou(&pa, d); } mdb_set_dot(addr + mdb_ctf_type_size(id)); err = DCMD_OK; out: if (pa.pa_immtgt) mdb_tgt_destroy(pa.pa_immtgt); return (err); } void print_help(void) { mdb_printf( "-a show address of object\n" "-C unlimit the length of character arrays\n" "-c limit limit the length of character arrays\n" "-d output values in decimal\n" "-h print holes in structures\n" "-i interpret address as data of the given type\n" "-L unlimit the length of standard arrays\n" "-l limit limit the length of standard arrays\n" "-n don't print pointers as symbol offsets\n" "-p interpret address as a physical memory address\n" "-s depth limit the recursion depth\n" "-T show type and <> of object\n" "-t show type of object\n" "-x output values in hexadecimal\n" "\n" "type may be omitted if the C type of addr can be inferred.\n" "\n" "Members may be specified with standard C syntax using the\n" "array indexing operator \"[index]\", structure member\n" "operator \".\", or structure pointer operator \"->\".\n" "\n" "Offsets must use the $[ expression ] syntax\n"); } static int printf_signed(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt, boolean_t sign) { size_t size; mdb_ctf_id_t base; ctf_encoding_t e; union { uint64_t ui8; uint32_t ui4; uint16_t ui2; uint8_t ui1; int64_t i8; int32_t i4; int16_t i2; int8_t i1; } u; if (mdb_ctf_type_resolve(id, &base) == -1) { mdb_warn("could not resolve type"); return (DCMD_ABORT); } switch (mdb_ctf_type_kind(base)) { case CTF_K_ENUM: e.cte_format = CTF_INT_SIGNED; e.cte_offset = 0; e.cte_bits = mdb_ctf_type_size(id) * NBBY; break; case CTF_K_INTEGER: if (mdb_ctf_type_encoding(base, &e) != 0) { mdb_warn("could not get type encoding"); return (DCMD_ABORT); } break; default: mdb_warn("expected integer type\n"); return (DCMD_ABORT); } if (sign) sign = e.cte_format & CTF_INT_SIGNED; size = e.cte_bits / NBBY; /* * Check to see if our life has been complicated by the presence of * a bitfield. If it has, we will print it using logic that is only * slightly different than that found in print_bitfield(), above. (In * particular, see the comments there for an explanation of the * endianness differences in this code.) */ if (is_bitfield(&e, off)) { uint64_t mask = (1ULL << e.cte_bits) - 1; uint64_t value = 0; uint8_t *buf = (uint8_t *)&value; uint8_t shift; uint_t nbits; /* * Our bitfield may straddle a byte boundary. We explicitly take * the offset of the bitfield within its byte into account when * determining the overall amount of data to copy and mask off * from the underlying data. */ nbits = e.cte_bits + (off % NBBY); size = P2ROUNDUP(nbits, NBBY) / NBBY; if (e.cte_bits > sizeof (value) * NBBY - 1) { mdb_printf("invalid bitfield size %u", e.cte_bits); return (DCMD_ABORT); } /* * Our bitfield may straddle a byte boundary, if so, the * calculation of size may not correctly capture that. However, * off is relative to the entire bitfield, so we first have to * make that relative to the byte. */ if ((off % NBBY) + e.cte_bits > NBBY * size) { size++; } if (size > sizeof (value)) { mdb_warn("??? (total bitfield too large after " "alignment\n"); return (DCMD_ABORT); } #ifdef _BIG_ENDIAN buf += sizeof (value) - size; off += e.cte_bits; #endif if (mdb_vread(buf, size, addr) == -1) { mdb_warn("failed to read %lu bytes at %p", size, addr); return (DCMD_ERR); } shift = off % NBBY; #ifdef _BIG_ENDIAN shift = NBBY - shift; #endif /* * If we have a bit offset within the byte, shift it down. */ if (off % NBBY != 0) value >>= shift; value &= mask; if (sign) { int sshift = sizeof (value) * NBBY - e.cte_bits; value = ((int64_t)value << sshift) >> sshift; } mdb_printf(fmt, value); return (0); } if (mdb_vread(&u.i8, size, addr) == -1) { mdb_warn("failed to read %lu bytes at %p", (ulong_t)size, addr); return (DCMD_ERR); } switch (size) { case sizeof (uint8_t): mdb_printf(fmt, (uint64_t)(sign ? u.i1 : u.ui1)); break; case sizeof (uint16_t): mdb_printf(fmt, (uint64_t)(sign ? u.i2 : u.ui2)); break; case sizeof (uint32_t): mdb_printf(fmt, (uint64_t)(sign ? u.i4 : u.ui4)); break; case sizeof (uint64_t): mdb_printf(fmt, (uint64_t)(sign ? u.i8 : u.ui8)); break; } return (0); } static int printf_int(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt) { return (printf_signed(id, addr, off, fmt, B_TRUE)); } static int printf_uint(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt) { return (printf_signed(id, addr, off, fmt, B_FALSE)); } /*ARGSUSED*/ static int printf_uint32(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt) { mdb_ctf_id_t base; ctf_encoding_t e; uint32_t value; if (mdb_ctf_type_resolve(id, &base) == -1) { mdb_warn("could not resolve type\n"); return (DCMD_ABORT); } if (mdb_ctf_type_kind(base) != CTF_K_INTEGER || mdb_ctf_type_encoding(base, &e) != 0 || e.cte_bits / NBBY != sizeof (value)) { mdb_warn("expected 32-bit integer type\n"); return (DCMD_ABORT); } if (mdb_vread(&value, sizeof (value), addr) == -1) { mdb_warn("failed to read 32-bit value at %p", addr); return (DCMD_ERR); } mdb_printf(fmt, value); return (0); } /*ARGSUSED*/ static int printf_ptr(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt) { uintptr_t value; mdb_ctf_id_t base; if (mdb_ctf_type_resolve(id, &base) == -1) { mdb_warn("could not resolve type\n"); return (DCMD_ABORT); } if (mdb_ctf_type_kind(base) != CTF_K_POINTER) { mdb_warn("expected pointer type\n"); return (DCMD_ABORT); } if (mdb_vread(&value, sizeof (value), addr) == -1) { mdb_warn("failed to read pointer at %llx", addr); return (DCMD_ERR); } mdb_printf(fmt, value); return (0); } /*ARGSUSED*/ static int printf_string(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt) { mdb_ctf_id_t base; mdb_ctf_arinfo_t r; char buf[1024]; ssize_t size; if (mdb_ctf_type_resolve(id, &base) == -1) { mdb_warn("could not resolve type"); return (DCMD_ABORT); } if (mdb_ctf_type_kind(base) == CTF_K_POINTER) { uintptr_t value; if (mdb_vread(&value, sizeof (value), addr) == -1) { mdb_warn("failed to read pointer at %llx", addr); return (DCMD_ERR); } if (mdb_readstr(buf, sizeof (buf) - 1, value) < 0) { mdb_warn("failed to read string at %llx", value); return (DCMD_ERR); } mdb_printf(fmt, buf); return (0); } if (mdb_ctf_type_kind(base) == CTF_K_ENUM) { const char *strval; int value; if (mdb_vread(&value, sizeof (value), addr) == -1) { mdb_warn("failed to read pointer at %llx", addr); return (DCMD_ERR); } if ((strval = mdb_ctf_enum_name(id, value))) { mdb_printf(fmt, strval); } else { (void) mdb_snprintf(buf, sizeof (buf), "<%d>", value); mdb_printf(fmt, buf); } return (0); } if (mdb_ctf_type_kind(base) != CTF_K_ARRAY) { mdb_warn("exepected pointer or array type\n"); return (DCMD_ABORT); } if (mdb_ctf_array_info(base, &r) == -1 || mdb_ctf_type_resolve(r.mta_contents, &base) == -1 || (size = mdb_ctf_type_size(base)) == -1) { mdb_warn("can't determine array type"); return (DCMD_ABORT); } if (size != 1) { mdb_warn("string format specifier requires " "an array of characters\n"); return (DCMD_ABORT); } bzero(buf, sizeof (buf)); if (r.mta_nelems != 0) { const size_t read_sz = MIN(r.mta_nelems, sizeof (buf) - 1); if (mdb_vread(buf, read_sz, addr) == -1) { mdb_warn("failed to read array at %p", addr); return (DCMD_ERR); } } else { /* * If the element count is zero, assume that the input is a * flexible length array which is NUL terminated. */ if (mdb_readstr(buf, sizeof (buf), addr) < 0) { mdb_warn("failed to read string at %llx", addr); return (DCMD_ERR); } } mdb_printf(fmt, buf); return (0); } /*ARGSUSED*/ static int printf_ipv6(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt) { mdb_ctf_id_t base; mdb_ctf_id_t ipv6_type, ipv6_base; in6_addr_t ipv6; if (mdb_ctf_lookup_by_name("in6_addr_t", &ipv6_type) == -1) { mdb_warn("could not resolve in6_addr_t type\n"); return (DCMD_ABORT); } if (mdb_ctf_type_resolve(id, &base) == -1) { mdb_warn("could not resolve type\n"); return (DCMD_ABORT); } if (mdb_ctf_type_resolve(ipv6_type, &ipv6_base) == -1) { mdb_warn("could not resolve in6_addr_t type\n"); return (DCMD_ABORT); } if (mdb_ctf_type_cmp(base, ipv6_base) != 0) { mdb_warn("requires argument of type in6_addr_t\n"); return (DCMD_ABORT); } if (mdb_vread(&ipv6, sizeof (ipv6), addr) == -1) { mdb_warn("couldn't read in6_addr_t at %p", addr); return (DCMD_ERR); } mdb_printf(fmt, &ipv6); return (0); } /* * To validate the format string specified to ::printf, we run the format * string through a very simple state machine that restricts us to a subset * of mdb_printf() functionality. */ enum { PRINTF_NOFMT = 1, /* no current format specifier */ PRINTF_PERC, /* processed '%' */ PRINTF_FMT, /* processing format specifier */ PRINTF_LEFT, /* processed '-', expecting width */ PRINTF_WIDTH, /* processing width */ PRINTF_QUES /* processed '?', expecting format */ }; int cmd_printf_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, const mdb_arg_t *argv) { int ii; char *f; /* * If argc doesn't have more than what should be the format string, * ignore it. */ if (argc <= 1) return (0); /* * Because we aren't leveraging the lex and yacc engine, we have to * manually walk the arguments to find both the first and last * open/close quote of the format string. */ f = strchr(argv[0].a_un.a_str, '"'); if (f == NULL) return (0); f = strchr(f + 1, '"'); if (f != NULL) { ii = 0; } else { for (ii = 1; ii < argc; ii++) { if (argv[ii].a_type != MDB_TYPE_STRING) continue; f = strchr(argv[ii].a_un.a_str, '"'); if (f != NULL) break; } /* Never found */ if (ii == argc) return (0); } ii++; argc -= ii; argv += ii; return (cmd_print_tab_common(mcp, flags, argc, argv)); } int cmd_printf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char type[MDB_SYM_NAMLEN]; int i, nfmts = 0, ret; mdb_ctf_id_t id; const char *fmt, *member; char **fmts, *last, *dest, f; int (**funcs)(mdb_ctf_id_t, uintptr_t, ulong_t, char *); int state = PRINTF_NOFMT; printarg_t pa; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); bzero(&pa, sizeof (pa)); pa.pa_as = MDB_TGT_AS_VIRT; pa.pa_realtgt = pa.pa_tgt = mdb.m_target; if (argc == 0 || argv[0].a_type != MDB_TYPE_STRING) { mdb_warn("expected a format string\n"); return (DCMD_USAGE); } /* * Our first argument is a format string; rip it apart and run it * through our state machine to validate that our input is within the * subset of mdb_printf() format strings that we allow. */ fmt = argv[0].a_un.a_str; /* * 'dest' must be large enough to hold a copy of the format string, * plus a NUL and up to 2 additional characters for each conversion * in the format string. This gives us a bloat factor of 5/2 ~= 3. * e.g. "%d" (strlen of 2) --> "%lld\0" (need 5 bytes) */ dest = mdb_zalloc(strlen(fmt) * 3, UM_SLEEP | UM_GC); fmts = mdb_zalloc(strlen(fmt) * sizeof (char *), UM_SLEEP | UM_GC); funcs = mdb_zalloc(strlen(fmt) * sizeof (void *), UM_SLEEP | UM_GC); last = dest; for (i = 0; fmt[i] != '\0'; i++) { *dest++ = f = fmt[i]; switch (state) { case PRINTF_NOFMT: state = f == '%' ? PRINTF_PERC : PRINTF_NOFMT; break; case PRINTF_PERC: state = f == '-' ? PRINTF_LEFT : f >= '0' && f <= '9' ? PRINTF_WIDTH : f == '?' ? PRINTF_QUES : f == '%' ? PRINTF_NOFMT : PRINTF_FMT; break; case PRINTF_LEFT: state = f >= '0' && f <= '9' ? PRINTF_WIDTH : f == '?' ? PRINTF_QUES : PRINTF_FMT; break; case PRINTF_WIDTH: state = f >= '0' && f <= '9' ? PRINTF_WIDTH : PRINTF_FMT; break; case PRINTF_QUES: state = PRINTF_FMT; break; } if (state != PRINTF_FMT) continue; dest--; /* * Now check that we have one of our valid format characters. */ switch (f) { case 'a': case 'A': case 'p': funcs[nfmts] = printf_ptr; break; case 'd': case 'q': case 'R': funcs[nfmts] = printf_int; *dest++ = 'l'; *dest++ = 'l'; break; case 'I': funcs[nfmts] = printf_uint32; break; case 'N': funcs[nfmts] = printf_ipv6; break; case 'H': case 'o': case 'r': case 'u': case 'x': case 'X': funcs[nfmts] = printf_uint; *dest++ = 'l'; *dest++ = 'l'; break; case 's': funcs[nfmts] = printf_string; break; case 'Y': funcs[nfmts] = sizeof (time_t) == sizeof (int) ? printf_uint32 : printf_uint; break; default: mdb_warn("illegal format string at or near " "'%c' (position %d)\n", f, i + 1); return (DCMD_ABORT); } *dest++ = f; *dest++ = '\0'; fmts[nfmts++] = last; last = dest; state = PRINTF_NOFMT; } argc--; argv++; /* * Now we expect a type name. */ if ((ret = args_to_typename(&argc, &argv, type, sizeof (type))) != 0) return (ret); argv++; argc--; if (mdb_ctf_lookup_by_name(type, &id) != 0) { mdb_warn("failed to look up type %s", type); return (DCMD_ABORT); } if (argc == 0) { mdb_warn("at least one member must be specified\n"); return (DCMD_USAGE); } if (argc != nfmts) { mdb_warn("%s format specifiers (found %d, expected %d)\n", argc > nfmts ? "missing" : "extra", nfmts, argc); return (DCMD_ABORT); } for (i = 0; i < argc; i++) { mdb_ctf_id_t mid; ulong_t off; int ignored; if (argv[i].a_type != MDB_TYPE_STRING) { mdb_warn("expected only type member arguments\n"); return (DCMD_ABORT); } if (strcmp((member = argv[i].a_un.a_str), ".") == 0) { /* * We allow "." to be specified to denote the current * value of dot. */ if (funcs[i] != printf_ptr && funcs[i] != printf_uint && funcs[i] != printf_int) { mdb_warn("expected integer or pointer format " "specifier for '.'\n"); return (DCMD_ABORT); } mdb_printf(fmts[i], mdb_get_dot()); continue; } pa.pa_addr = addr; if (parse_member(&pa, member, id, &mid, &off, &ignored) != 0) return (DCMD_ABORT); if ((ret = funcs[i](mid, pa.pa_addr, off, fmts[i])) != 0) { mdb_warn("failed to print member '%s'\n", member); return (ret); } } mdb_printf("%s", last); mdb_set_dot(addr + mdb_ctf_type_size(id)); return (DCMD_OK); } static char _mdb_printf_help[] = "The format string argument is a printf(3C)-like format string that is a\n" "subset of the format strings supported by mdb_printf(). The type argument\n" "is the name of a type to be used to interpret the memory referenced by dot.\n" "The member should either be a field in the specified structure, or the\n" "special member '.', denoting the value of dot (and treated as a pointer).\n" "The number of members must match the number of format specifiers in the\n" "format string.\n" "\n" "The following format specifiers are recognized by ::printf:\n" "\n" " %% Prints the '%' symbol.\n" " %a Prints the member in symbolic form.\n" " %d Prints the member as a decimal integer. If the member is a signed\n" " integer type, the output will be signed.\n" " %H Prints the member as a human-readable size.\n" " %I Prints the member as an IPv4 address (must be 32-bit integer type).\n" " %N Prints the member as an IPv6 address (must be of type in6_addr_t).\n" " %o Prints the member as an unsigned octal integer.\n" " %p Prints the member as a pointer, in hexadecimal.\n" " %q Prints the member in signed octal. Honk if you ever use this!\n" " %r Prints the member as an unsigned value in the current output radix.\n" " %R Prints the member as a signed value in the current output radix.\n" " %s Prints the member as a string (requires a pointer or an array of\n" " characters).\n" " %u Prints the member as an unsigned decimal integer.\n" " %x Prints the member in hexadecimal.\n" " %X Prints the member in hexadecimal, using the characters A-F as the\n" " digits for the values 10-15.\n" " %Y Prints the member as a time_t as the string " "'year month day HH:MM:SS'.\n" "\n" "The following field width specifiers are recognized by ::printf:\n" "\n" " %n Field width is set to the specified decimal value.\n" " %? Field width is set to the maximum width of a hexadecimal pointer\n" " value. This is 8 in an ILP32 environment, and 16 in an LP64\n" " environment.\n" "\n" "The following flag specifers are recognized by ::printf:\n" "\n" " %- Left-justify the output within the specified field width. If the\n" " width of the output is less than the specified field width, the\n" " output will be padded with blanks on the right-hand side. Without\n" " %-, values are right-justified by default.\n" "\n" " %0 Zero-fill the output field if the output is right-justified and the\n" " width of the output is less than the specified field width. Without\n" " %0, right-justified values are prepended with blanks in order to\n" " fill the field.\n" "\n" "Examples: \n" "\n" " ::walk proc | " "::printf \"%-6d %s\\n\" proc_t p_pidp->pid_id p_user.u_psargs\n" " ::walk thread | " "::printf \"%?p %3d %a\\n\" kthread_t . t_pri t_startpc\n" " ::walk zone | " "::printf \"%-40s %20s\\n\" zone_t zone_name zone_nodename\n" " ::walk ire | " "::printf \"%Y %I\\n\" ire_t ire_create_time ire_u.ire4_u.ire4_addr\n" "\n"; void printf_help(void) { mdb_printf("%s", _mdb_printf_help); } /* * 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. */ #ifndef _MDB_PRINT_H #define _MDB_PRINT_H #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern int cmd_enum(uintptr_t, uint_t, int, const mdb_arg_t *); extern void enum_help(void); extern int cmd_sizeof(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_sizeof_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); extern int cmd_offsetof(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_list(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_array(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_print(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_print_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); extern void print_help(void); extern int cmd_printf(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_printf_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); extern void printf_help(void); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_PRINT_H */ /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2018 Joyent, Inc. * Copyright (c) 2014 by Delphix. All rights reserved. * Copyright 2025 Oxide Computer Company */ /* * User Process Target * * The user process target is invoked when the -u or -p command-line options * are used, or when an ELF executable file or ELF core file is specified on * the command-line. This target is also selected by default when no target * options are present. In this case, it defaults the executable name to * "a.out". If no process or core file is currently attached, the target * functions as a kind of virtual /dev/zero (in accordance with adb(1) * semantics); reads from the virtual address space return zeroes and writes * fail silently. The proc target itself is designed as a wrapper around the * services provided by libproc.so: t->t_pshandle is set to the struct * ps_prochandle pointer returned as a handle by libproc. The target also * opens the executable file itself using the MDB GElf services, for * interpreting the .symtab and .dynsym if no libproc handle has been * initialized, and for handling i/o to and from the object file. Currently, * the only ISA-dependent portions of the proc target are the $r and ::fpregs * dcmds, the callbacks for t_next() and t_step_out(), and the list of named * registers; these are linked in from the proc_isadep.c file for each ISA and * called from the common code in this file. * * The user process target implements complete user process control using the * facilities provided by libproc.so. The MDB execution control model and * an overview of software event management is described in mdb_target.c. The * proc target implements breakpoints by replacing the instruction of interest * with a trap instruction, and then restoring the original instruction to step * over the breakpoint. The idea of replacing program text with instructions * that transfer control to the debugger dates back as far as 1951 [1]. When * the target stops, we replace each breakpoint with the original instruction * as part of the disarm operation. This means that no special processing is * required for t_vread() because the instrumented instructions will never be * seen by the debugger once the target stops. Some debuggers have improved * start/stop performance by leaving breakpoint traps in place and then * handling a read from a breakpoint address as a special case. Although this * improves efficiency for a source-level debugger, it runs somewhat contrary * to the philosophy of the low-level debugger. Since we remove the * instructions, users can apply other external debugging tools to the process * once it has stopped (e.g. the proc(1) tools) and not be misled by MDB * instrumentation. The tracing of faults, signals, system calls, and * watchpoints and general process inspection is implemented directly using * the mechanisms provided by /proc, as described originally in [2] and [3]. * * References * * [1] S. Gill, "The Diagnosis Of Mistakes In Programmes on the EDSAC", * Proceedings of the Royal Society Series A Mathematical and Physical * Sciences, Cambridge University Press, 206(1087), May 1951, pp. 538-554. * * [2] T.J. Killian, "Processes as Files", Proceedings of the USENIX Association * Summer Conference, Salt Lake City, June 1984, pp. 203-207. * * [3] Roger Faulkner and Ron Gomes, "The Process File System and Process * Model in UNIX System V", Proceedings of the USENIX Association * Winter Conference, Dallas, January 1991, pp. 243-252. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PC_FAKE -1UL /* illegal pc value unequal 0 */ #define PANIC_BUFSIZE 1024 static const char PT_EXEC_PATH[] = "a.out"; /* Default executable */ static const char PT_CORE_PATH[] = "core"; /* Default core file */ static const pt_ptl_ops_t proc_lwp_ops; static const pt_ptl_ops_t proc_tdb_ops; static const mdb_se_ops_t proc_brkpt_ops; static const mdb_se_ops_t proc_wapt_ops; static int pt_setrun(mdb_tgt_t *, mdb_tgt_status_t *, int); static void pt_activate_common(mdb_tgt_t *); static mdb_tgt_vespec_f pt_ignore_sig; static mdb_tgt_se_f pt_fork; static mdb_tgt_se_f pt_exec; static int pt_lookup_by_name_thr(mdb_tgt_t *, const char *, const char *, GElf_Sym *, mdb_syminfo_t *, mdb_tgt_tid_t); static int tlsbase(mdb_tgt_t *, mdb_tgt_tid_t, Lmid_t, const char *, psaddr_t *); /* * When debugging postmortem, we don't resolve names as we may very well not * be on a system on which those names resolve. */ #define PT_LIBPROC_RESOLVE(P) \ (!(mdb.m_flags & MDB_FL_LMRAW) && Pstate(P) != PS_DEAD) /* * The Perror_printf() function interposes on the default, empty libproc * definition. It will be called to report additional information on complex * errors, such as a corrupt core file. We just pass the args to vwarn. */ /*ARGSUSED*/ void Perror_printf(struct ps_prochandle *P, const char *format, ...) { va_list alist; va_start(alist, format); vwarn(format, alist); va_end(alist); } /* * Open the specified i/o backend as the a.out executable file, and attempt to * load its standard and dynamic symbol tables. Note that if mdb_gelf_create * succeeds, io is assigned to p_fio and is automatically held by gelf_create. */ static mdb_gelf_file_t * pt_open_aout(mdb_tgt_t *t, mdb_io_t *io) { pt_data_t *pt = t->t_data; GElf_Sym s1, s2; if ((pt->p_file = mdb_gelf_create(io, ET_NONE, GF_FILE)) == NULL) return (NULL); pt->p_symtab = mdb_gelf_symtab_create_file(pt->p_file, SHT_SYMTAB, MDB_TGT_SYMTAB); pt->p_dynsym = mdb_gelf_symtab_create_file(pt->p_file, SHT_DYNSYM, MDB_TGT_DYNSYM); /* * If we've got an _start symbol with a zero size, prime the private * symbol table with a copy of _start with its size set to the distance * between _mcount and _start. We do this because DevPro has shipped * the Intel crt1.o without proper .size directives for years, which * precludes proper identification of _start in stack traces. */ if (mdb_gelf_symtab_lookup_by_name(pt->p_dynsym, "_start", &s1, NULL) == 0 && s1.st_size == 0 && GELF_ST_TYPE(s1.st_info) == STT_FUNC) { if (mdb_gelf_symtab_lookup_by_name(pt->p_dynsym, "_mcount", &s2, NULL) == 0 && GELF_ST_TYPE(s2.st_info) == STT_FUNC) { s1.st_size = s2.st_value - s1.st_value; mdb_gelf_symtab_insert(mdb.m_prsym, "_start", &s1); } } pt->p_fio = io; return (pt->p_file); } /* * Destroy the symbol tables and GElf file object associated with p_fio. Note * that we do not need to explicitly free p_fio: its reference count is * automatically decremented by mdb_gelf_destroy, which will free it if needed. */ static void pt_close_aout(mdb_tgt_t *t) { pt_data_t *pt = t->t_data; if (pt->p_symtab != NULL) { mdb_gelf_symtab_destroy(pt->p_symtab); pt->p_symtab = NULL; } if (pt->p_dynsym != NULL) { mdb_gelf_symtab_destroy(pt->p_dynsym); pt->p_dynsym = NULL; } if (pt->p_file != NULL) { mdb_gelf_destroy(pt->p_file); pt->p_file = NULL; } mdb_gelf_symtab_delete(mdb.m_prsym, "_start", NULL); pt->p_fio = NULL; } typedef struct tdb_mapping { const char *tm_thr_lib; const char *tm_db_dir; const char *tm_db_name; } tdb_mapping_t; static const tdb_mapping_t tdb_map[] = { { "/lwp/amd64/libthread.so", "/usr/lib/lwp/", "libthread_db.so" }, { "/lwp/sparcv9/libthread.so", "/usr/lib/lwp/", "libthread_db.so" }, { "/lwp/libthread.so", "/usr/lib/lwp/", "libthread_db.so" }, { "/libthread.so", "/lib/", "libthread_db.so" }, { "/libc_hwcap", "/lib/", "libc_db.so" }, { "/libc.so", "/lib/", "libc_db.so" } }; /* * Pobject_iter callback that we use to search for the presence of libthread in * order to load the corresponding libthread_db support. We derive the * libthread_db path dynamically based on the libthread path. If libthread is * found, this function returns 1 (and thus Pobject_iter aborts and returns 1) * regardless of whether it was successful in loading the libthread_db support. * If we iterate over all objects and no libthread is found, 0 is returned. * Since libthread_db support was then merged into libc_db, we load either * libc_db or libthread_db, depending on which library we see first. */ /*ARGSUSED*/ static int thr_check(mdb_tgt_t *t, const prmap_t *pmp, const char *name) { pt_data_t *pt = t->t_data; const mdb_tdb_ops_t *ops; char *p; char path[MAXPATHLEN]; int libn; if (name == NULL) return (0); /* no rtld_db object name; keep going */ for (libn = 0; libn < sizeof (tdb_map) / sizeof (tdb_map[0]); libn++) { if ((p = strstr(name, tdb_map[libn].tm_thr_lib)) != NULL) break; } if (p == NULL) return (0); /* no match; keep going */ path[0] = '\0'; (void) strlcat(path, mdb.m_root, sizeof (path)); (void) strlcat(path, tdb_map[libn].tm_db_dir, sizeof (path)); #if !defined(_ILP32) (void) strlcat(path, "64/", sizeof (path)); #endif /* !_ILP32 */ (void) strlcat(path, tdb_map[libn].tm_db_name, sizeof (path)); /* Append the trailing library version number. */ (void) strlcat(path, strrchr(name, '.'), sizeof (path)); if ((ops = mdb_tdb_load(path)) == NULL) { if (libn != 0 || errno != ENOENT) warn("failed to load %s", path); goto err; } if (ops == pt->p_tdb_ops) return (1); /* no changes needed */ PTL_DTOR(t); pt->p_tdb_ops = ops; pt->p_ptl_ops = &proc_tdb_ops; pt->p_ptl_hdl = NULL; if (PTL_CTOR(t) == -1) { warn("failed to initialize %s", path); goto err; } mdb_dprintf(MDB_DBG_TGT, "loaded %s for debugging %s\n", path, name); (void) mdb_tgt_status(t, &t->t_status); return (1); err: PTL_DTOR(t); pt->p_tdb_ops = NULL; pt->p_ptl_ops = &proc_lwp_ops; pt->p_ptl_hdl = NULL; if (libn != 0 || errno != ENOENT) { warn("warning: debugger will only be able to " "examine raw LWPs\n"); } (void) mdb_tgt_status(t, &t->t_status); return (1); } /* * Whenever the link map is consistent following an add or delete event, we ask * libproc to update its mappings, check to see if we need to load libthread_db, * and then update breakpoints which have been mapped or unmapped. */ /*ARGSUSED*/ static void pt_rtld_event(mdb_tgt_t *t, int vid, void *private) { struct ps_prochandle *P = t->t_pshandle; pt_data_t *pt = t->t_data; rd_event_msg_t rdm; int docontinue = 1; if (rd_event_getmsg(pt->p_rtld, &rdm) == RD_OK) { mdb_dprintf(MDB_DBG_TGT, "rtld event type 0x%x state 0x%x\n", rdm.type, rdm.u.state); if (rdm.type == RD_DLACTIVITY && rdm.u.state == RD_CONSISTENT) { mdb_sespec_t *sep, *nsep = mdb_list_next(&t->t_active); pt_brkpt_t *ptb; Pupdate_maps(P); if (Pobject_iter(P, (proc_map_f *)thr_check, t) == 0 && pt->p_ptl_ops != &proc_lwp_ops) { mdb_dprintf(MDB_DBG_TGT, "unloading thread_db " "support after dlclose\n"); PTL_DTOR(t); pt->p_tdb_ops = NULL; pt->p_ptl_ops = &proc_lwp_ops; pt->p_ptl_hdl = NULL; (void) mdb_tgt_status(t, &t->t_status); } for (sep = nsep; sep != NULL; sep = nsep) { nsep = mdb_list_next(sep); ptb = sep->se_data; if (sep->se_ops == &proc_brkpt_ops && Paddr_to_map(P, ptb->ptb_addr) == NULL) mdb_tgt_sespec_idle_one(t, sep, EMDB_NOMAP); } if (!mdb_tgt_sespec_activate_all(t) && (mdb.m_flags & MDB_FL_BPTNOSYMSTOP) && pt->p_rtld_finished) { /* * We weren't able to activate the breakpoints. * If so requested, we'll return without * calling continue, thus throwing the user into * the debugger. */ docontinue = 0; } if (pt->p_rdstate == PT_RD_ADD) pt->p_rdstate = PT_RD_CONSIST; } if (rdm.type == RD_PREINIT) (void) mdb_tgt_sespec_activate_all(t); if (rdm.type == RD_POSTINIT) { pt->p_rtld_finished = TRUE; if (!mdb_tgt_sespec_activate_all(t) && (mdb.m_flags & MDB_FL_BPTNOSYMSTOP)) { /* * Now that rtld has been initialized, we * should be able to initialize all deferred * breakpoints. If we can't, don't let the * target continue. */ docontinue = 0; } } if (rdm.type == RD_DLACTIVITY && rdm.u.state == RD_ADD && pt->p_rtld_finished) pt->p_rdstate = MAX(pt->p_rdstate, PT_RD_ADD); } if (docontinue) (void) mdb_tgt_continue(t, NULL); } static void pt_post_attach(mdb_tgt_t *t) { struct ps_prochandle *P = t->t_pshandle; const lwpstatus_t *psp = &Pstatus(P)->pr_lwp; pt_data_t *pt = t->t_data; int hflag = MDB_TGT_SPEC_HIDDEN; mdb_dprintf(MDB_DBG_TGT, "attach pr_flags=0x%x pr_why=%d pr_what=%d\n", psp->pr_flags, psp->pr_why, psp->pr_what); /* * When we grab a process, the initial setting of p_rtld_finished * should be false if the process was just created by exec; otherwise * we permit unscoped references to resolve because we do not know how * far the process has proceeded through linker initialization. */ if ((psp->pr_flags & PR_ISTOP) && psp->pr_why == PR_SYSEXIT && psp->pr_errno == 0 && psp->pr_what == SYS_execve) { if (mdb.m_target == NULL) { warn("target performed exec of %s\n", IOP_NAME(pt->p_fio)); } pt->p_rtld_finished = FALSE; } else pt->p_rtld_finished = TRUE; /* * When we grab a process, if it is stopped by job control and part of * the same session (i.e. same controlling tty), set MDB_FL_JOBCTL so * we will know to bring it to the foreground when we continue it. */ if (mdb.m_term != NULL && (psp->pr_flags & PR_STOPPED) && psp->pr_why == PR_JOBCONTROL && getsid(0) == Pstatus(P)->pr_sid) mdb.m_flags |= MDB_FL_JOBCTL; /* * When we grab control of a live process, set F_RDWR so that the * target layer permits writes to the target's address space. */ t->t_flags |= MDB_TGT_F_RDWR; (void) Pfault(P, FLTBPT, TRUE); /* always trace breakpoints */ (void) Pfault(P, FLTWATCH, TRUE); /* always trace watchpoints */ (void) Pfault(P, FLTTRACE, TRUE); /* always trace single-step */ (void) Punsetflags(P, PR_ASYNC); /* require synchronous mode */ (void) Psetflags(P, PR_BPTADJ); /* always adjust eip on x86 */ (void) Psetflags(P, PR_FORK); /* inherit tracing on fork */ /* * Install event specifiers to track fork and exec activities: */ (void) mdb_tgt_add_sysexit(t, SYS_vfork, hflag, pt_fork, NULL); (void) mdb_tgt_add_sysexit(t, SYS_forksys, hflag, pt_fork, NULL); (void) mdb_tgt_add_sysexit(t, SYS_execve, hflag, pt_exec, NULL); /* * Attempt to instantiate the librtld_db agent and set breakpoints * to track rtld activity. We will legitimately fail to instantiate * the rtld_db agent if the target is statically linked. */ if (pt->p_rtld == NULL && (pt->p_rtld = Prd_agent(P)) != NULL) { rd_notify_t rdn; rd_err_e err; if ((err = rd_event_enable(pt->p_rtld, TRUE)) != RD_OK) { warn("failed to enable rtld_db event tracing: %s\n", rd_errstr(err)); goto out; } if ((err = rd_event_addr(pt->p_rtld, RD_PREINIT, &rdn)) == RD_OK && rdn.type == RD_NOTIFY_BPT) { (void) mdb_tgt_add_vbrkpt(t, rdn.u.bptaddr, hflag, pt_rtld_event, NULL); } else { warn("failed to install rtld_db preinit tracing: %s\n", rd_errstr(err)); } if ((err = rd_event_addr(pt->p_rtld, RD_POSTINIT, &rdn)) == RD_OK && rdn.type == RD_NOTIFY_BPT) { (void) mdb_tgt_add_vbrkpt(t, rdn.u.bptaddr, hflag, pt_rtld_event, NULL); } else { warn("failed to install rtld_db postinit tracing: %s\n", rd_errstr(err)); } if ((err = rd_event_addr(pt->p_rtld, RD_DLACTIVITY, &rdn)) == RD_OK && rdn.type == RD_NOTIFY_BPT) { (void) mdb_tgt_add_vbrkpt(t, rdn.u.bptaddr, hflag, pt_rtld_event, NULL); } else { warn("failed to install rtld_db activity tracing: %s\n", rd_errstr(err)); } } out: Pupdate_maps(P); Psync(P); /* * If librtld_db failed to initialize due to an error or because we are * debugging a statically linked executable, allow unscoped references. */ if (pt->p_rtld == NULL) pt->p_rtld_finished = TRUE; (void) mdb_tgt_sespec_activate_all(t); } /*ARGSUSED*/ static int pt_vespec_delete(mdb_tgt_t *t, void *private, int id, void *data) { if (id < 0) { ASSERT(data == NULL); /* we don't use any ve_data */ (void) mdb_tgt_vespec_delete(t, id); } return (0); } static void pt_pre_detach(mdb_tgt_t *t, int clear_matched) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; pt_data_t *pt = t->t_data; long cmd = 0; /* * If we are about to release the process and it is stopped on a traced * SIGINT, breakpoint fault, single-step fault, or watchpoint, make * sure to clear this event prior to releasing the process so that it * does not subsequently reissue the fault and die from SIGTRAP. */ if (psp->pr_flags & PR_ISTOP) { if (psp->pr_why == PR_FAULTED && (psp->pr_what == FLTBPT || psp->pr_what == FLTTRACE || psp->pr_what == FLTWATCH)) cmd = PCCFAULT; else if (psp->pr_why == PR_SIGNALLED && psp->pr_what == SIGINT) cmd = PCCSIG; if (cmd != 0) (void) write(Pctlfd(t->t_pshandle), &cmd, sizeof (cmd)); } if (Pstate(t->t_pshandle) == PS_UNDEAD) (void) waitpid(Pstatus(t->t_pshandle)->pr_pid, NULL, WNOHANG); (void) mdb_tgt_vespec_iter(t, pt_vespec_delete, NULL); mdb_tgt_sespec_idle_all(t, EMDB_NOPROC, clear_matched); if (pt->p_fio != pt->p_aout_fio) { pt_close_aout(t); (void) pt_open_aout(t, pt->p_aout_fio); } PTL_DTOR(t); pt->p_tdb_ops = NULL; pt->p_ptl_ops = &proc_lwp_ops; pt->p_ptl_hdl = NULL; pt->p_rtld = NULL; pt->p_signal = 0; pt->p_rtld_finished = FALSE; pt->p_rdstate = PT_RD_NONE; } static void pt_release_parents(mdb_tgt_t *t) { struct ps_prochandle *P = t->t_pshandle; pt_data_t *pt = t->t_data; mdb_sespec_t *sep; pt_vforkp_t *vfp; while ((vfp = mdb_list_next(&pt->p_vforkp)) != NULL) { mdb_dprintf(MDB_DBG_TGT, "releasing vfork parent %d\n", (int)Pstatus(vfp->p_pshandle)->pr_pid); /* * To release vfork parents, we must also wipe out any armed * events in the parent by switching t_pshandle and calling * se_disarm(). Do not change states or lose the matched list. */ t->t_pshandle = vfp->p_pshandle; for (sep = mdb_list_next(&t->t_active); sep != NULL; sep = mdb_list_next(sep)) { if (sep->se_state == MDB_TGT_SPEC_ARMED) (void) sep->se_ops->se_disarm(t, sep); } t->t_pshandle = P; Prelease(vfp->p_pshandle, PRELEASE_CLEAR); mdb_list_delete(&pt->p_vforkp, vfp); mdb_free(vfp, sizeof (pt_vforkp_t)); } } /*ARGSUSED*/ static void pt_fork(mdb_tgt_t *t, int vid, void *private) { struct ps_prochandle *P = t->t_pshandle; const lwpstatus_t *psp = &Pstatus(P)->pr_lwp; pt_data_t *pt = t->t_data; mdb_sespec_t *sep; int follow_parent = mdb.m_forkmode != MDB_FM_CHILD; int is_vfork = (psp->pr_what == SYS_vfork || (psp->pr_what == SYS_forksys && psp->pr_sysarg[0] == 2)); struct ps_prochandle *C; const lwpstatus_t *csp; char sysname[32]; int gcode; char c; mdb_dprintf(MDB_DBG_TGT, "parent %s: errno=%d rv1=%ld rv2=%ld\n", proc_sysname(psp->pr_what, sysname, sizeof (sysname)), psp->pr_errno, psp->pr_rval1, psp->pr_rval2); if (psp->pr_errno != 0) { (void) mdb_tgt_continue(t, NULL); return; /* fork failed */ } /* * If forkmode is ASK and stdout is a terminal, then ask the user to * explicitly set the fork behavior for this particular fork. */ if (mdb.m_forkmode == MDB_FM_ASK && mdb.m_term != NULL) { mdb_iob_printf(mdb.m_err, "%s: %s detected: follow (p)arent " "or (c)hild? ", mdb.m_pname, sysname); mdb_iob_flush(mdb.m_err); while (IOP_READ(mdb.m_term, &c, sizeof (c)) == sizeof (c)) { if (c == 'P' || c == 'p') { mdb_iob_printf(mdb.m_err, "%c\n", c); follow_parent = TRUE; break; } else if (c == 'C' || c == 'c') { mdb_iob_printf(mdb.m_err, "%c\n", c); follow_parent = FALSE; break; } } } /* * The parent is now stopped on exit from its fork call. We must now * grab the child on its return from fork in order to manipulate it. */ if ((C = Pgrab(psp->pr_rval1, PGRAB_RETAIN, &gcode)) == NULL) { warn("failed to grab forked child process %ld: %s\n", psp->pr_rval1, Pgrab_error(gcode)); return; /* just stop if we failed to grab the child */ } /* * We may have grabbed the child and stopped it prematurely before it * stopped on exit from fork. If so, wait up to 1 sec for it to settle. */ if (Pstatus(C)->pr_lwp.pr_why != PR_SYSEXIT) (void) Pwait(C, MILLISEC); csp = &Pstatus(C)->pr_lwp; if (csp->pr_why != PR_SYSEXIT || (csp->pr_what != SYS_vfork && csp->pr_what != SYS_forksys)) { warn("forked child process %ld did not stop on exit from " "fork as expected\n", psp->pr_rval1); } warn("target forked child process %ld (debugger following %s)\n", psp->pr_rval1, follow_parent ? "parent" : "child"); (void) Punsetflags(C, PR_ASYNC); /* require synchronous mode */ (void) Psetflags(C, PR_BPTADJ); /* always adjust eip on x86 */ (void) Prd_agent(C); /* initialize librtld_db */ /* * At the time pt_fork() is called, the target event engine has already * disarmed the specifiers on the active list, clearing out events in * the parent process. However, this means that events that change * the address space (e.g. breakpoints) have not been effectively * disarmed in the child since its address space reflects the state of * the process at the time of fork when events were armed. We must * therefore handle this as a special case and re-invoke the disarm * callback of each active specifier to clean out the child process. */ if (!is_vfork) { for (t->t_pshandle = C, sep = mdb_list_next(&t->t_active); sep != NULL; sep = mdb_list_next(sep)) { if (sep->se_state == MDB_TGT_SPEC_ACTIVE) (void) sep->se_ops->se_disarm(t, sep); } t->t_pshandle = P; /* restore pshandle to parent */ } /* * If we're following the parent process, we need to temporarily change * t_pshandle to refer to the child handle C so that we can clear out * all the events in the child prior to releasing it below. If we are * tracing a vfork, we also need to explicitly wait for the child to * exec, exit, or die before we can reset and continue the parent. We * avoid having to deal with the vfork child forking again by clearing * PR_FORK and setting PR_RLC; if it does fork it will effectively be * released from our control and we will continue following the parent. */ if (follow_parent) { if (is_vfork) { mdb_tgt_status_t status; ASSERT(psp->pr_flags & PR_VFORKP); mdb_tgt_sespec_idle_all(t, EBUSY, FALSE); t->t_pshandle = C; (void) Psysexit(C, SYS_execve, TRUE); (void) Punsetflags(C, PR_FORK | PR_KLC); (void) Psetflags(C, PR_RLC); do { if (pt_setrun(t, &status, 0) == -1 || status.st_state == MDB_TGT_UNDEAD || status.st_state == MDB_TGT_LOST) break; /* failure or process died */ } while (csp->pr_why != PR_SYSEXIT || csp->pr_errno != 0 || csp->pr_what != SYS_execve); } else t->t_pshandle = C; } /* * If we are following the child, destroy any active libthread_db * handle before we release the parent process. */ if (!follow_parent) { PTL_DTOR(t); pt->p_tdb_ops = NULL; pt->p_ptl_ops = &proc_lwp_ops; pt->p_ptl_hdl = NULL; } /* * Idle all events to make sure the address space and tracing flags are * restored, and then release the process we are not tracing. If we * are following the child of a vfork, we push the parent's pshandle * on to a list of vfork parents to be released when we exec or exit. */ if (is_vfork && !follow_parent) { pt_vforkp_t *vfp = mdb_alloc(sizeof (pt_vforkp_t), UM_SLEEP); ASSERT(psp->pr_flags & PR_VFORKP); vfp->p_pshandle = P; mdb_list_append(&pt->p_vforkp, vfp); mdb_tgt_sespec_idle_all(t, EBUSY, FALSE); } else { mdb_tgt_sespec_idle_all(t, EBUSY, FALSE); Prelease(t->t_pshandle, PRELEASE_CLEAR); if (!follow_parent) pt_release_parents(t); } /* * Now that all the hard stuff is done, switch t_pshandle back to the * process we are following and reset our events to the ACTIVE state. * If we are following the child, reset the libthread_db handle as well * as the rtld agent. */ if (follow_parent) t->t_pshandle = P; else { t->t_pshandle = C; pt->p_rtld = Prd_agent(C); (void) Pobject_iter(t->t_pshandle, (proc_map_f *)thr_check, t); } (void) mdb_tgt_sespec_activate_all(t); (void) mdb_tgt_continue(t, NULL); } /*ARGSUSED*/ static void pt_exec(mdb_tgt_t *t, int vid, void *private) { struct ps_prochandle *P = t->t_pshandle; const pstatus_t *psp = Pstatus(P); pt_data_t *pt = t->t_data; int follow_exec = mdb.m_execmode == MDB_EM_FOLLOW; pid_t pid = psp->pr_pid; char execname[MAXPATHLEN]; mdb_sespec_t *sep, *nsep; mdb_io_t *io; char c; mdb_dprintf(MDB_DBG_TGT, "exit from %s: errno=%d\n", proc_sysname( psp->pr_lwp.pr_what, execname, sizeof (execname)), psp->pr_lwp.pr_errno); if (psp->pr_lwp.pr_errno != 0) { (void) mdb_tgt_continue(t, NULL); return; /* exec failed */ } /* * If execmode is ASK and stdout is a terminal, then ask the user to * explicitly set the exec behavior for this particular exec. If * Pstate() still shows PS_LOST, we are being called from pt_setrun() * directly and therefore we must resume the terminal since it is still * in the suspended state as far as tgt_continue() is concerned. */ if (mdb.m_execmode == MDB_EM_ASK && mdb.m_term != NULL) { if (Pstate(P) == PS_LOST) IOP_RESUME(mdb.m_term); mdb_iob_printf(mdb.m_err, "%s: %s detected: (f)ollow new " "program or (s)top? ", mdb.m_pname, execname); mdb_iob_flush(mdb.m_err); while (IOP_READ(mdb.m_term, &c, sizeof (c)) == sizeof (c)) { if (c == 'F' || c == 'f') { mdb_iob_printf(mdb.m_err, "%c\n", c); follow_exec = TRUE; break; } else if (c == 'S' || c == 's') { mdb_iob_printf(mdb.m_err, "%c\n", c); follow_exec = FALSE; break; } } if (Pstate(P) == PS_LOST) IOP_SUSPEND(mdb.m_term); } pt_release_parents(t); /* release any waiting vfork parents */ pt_pre_detach(t, FALSE); /* remove our breakpoints and idle events */ Preset_maps(P); /* libproc must delete mappings and symtabs */ pt_close_aout(t); /* free pt symbol tables and GElf file data */ /* * If we lost control of the process across the exec and are not able * to reopen it, we have no choice but to clear the matched event list * and wait for the user to quit or otherwise release the process. */ if (Pstate(P) == PS_LOST && Preopen(P) == -1) { int error = errno; warn("lost control of PID %d due to exec of %s executable\n", (int)pid, error == EOVERFLOW ? "64-bit" : "set-id"); for (sep = t->t_matched; sep != T_SE_END; sep = nsep) { nsep = sep->se_matched; sep->se_matched = NULL; mdb_tgt_sespec_rele(t, sep); } if (error != EOVERFLOW) return; /* just stop if we exec'd a set-id executable */ } if (Pstate(P) != PS_LOST) { if (Pexecname(P, execname, sizeof (execname)) == NULL) { (void) mdb_iob_snprintf(execname, sizeof (execname), "/proc/%d/object/a.out", (int)pid); } if (follow_exec == FALSE || psp->pr_dmodel == PR_MODEL_NATIVE) warn("target performed exec of %s\n", execname); io = mdb_fdio_create_path(NULL, execname, pt->p_oflags, 0); if (io == NULL) { warn("failed to open %s", execname); warn("a.out symbol tables will not be available\n"); } else if (pt_open_aout(t, io) == NULL) { (void) mdb_dis_select(pt_disasm(NULL)); mdb_io_destroy(io); } else (void) mdb_dis_select(pt_disasm(&pt->p_file->gf_ehdr)); } /* * We reset our libthread_db state here, but deliberately do NOT call * PTL_DTOR because we do not want to call libthread_db's td_ta_delete. * This interface is hopelessly broken in that it writes to the process * address space (which we do not want it to do after an exec) and it * doesn't bother deallocating any of its storage anyway. */ pt->p_tdb_ops = NULL; pt->p_ptl_ops = &proc_lwp_ops; pt->p_ptl_hdl = NULL; if (follow_exec && psp->pr_dmodel != PR_MODEL_NATIVE) { const char *argv[3]; char *state, *env; char pidarg[16]; size_t envlen; if (realpath(getexecname(), execname) == NULL) { warn("cannot follow PID %d -- failed to resolve " "debugger pathname for re-exec", (int)pid); return; } warn("restarting debugger to follow PID %d ...\n", (int)pid); mdb_dprintf(MDB_DBG_TGT, "re-exec'ing %s\n", execname); (void) mdb_snprintf(pidarg, sizeof (pidarg), "-p%d", (int)pid); state = mdb_get_config(); envlen = strlen(MDB_CONFIG_ENV_VAR) + 1 + strlen(state) + 1; env = mdb_alloc(envlen, UM_SLEEP); (void) snprintf(env, envlen, "%s=%s", MDB_CONFIG_ENV_VAR, state); (void) putenv(env); argv[0] = mdb.m_pname; argv[1] = pidarg; argv[2] = NULL; if (mdb.m_term != NULL) IOP_SUSPEND(mdb.m_term); Prelease(P, PRELEASE_CLEAR | PRELEASE_HANG); (void) execv(execname, (char *const *)argv); warn("failed to re-exec debugger"); if (mdb.m_term != NULL) IOP_RESUME(mdb.m_term); t->t_pshandle = pt->p_idlehandle; return; } pt_post_attach(t); /* install tracing flags and activate events */ pt_activate_common(t); /* initialize librtld_db and libthread_db */ if (psp->pr_dmodel != PR_MODEL_NATIVE && mdb.m_term != NULL) { warn("loadable dcmds will not operate on non-native %d-bit " "data model\n", psp->pr_dmodel == PR_MODEL_ILP32 ? 32 : 64); warn("use ::release -a and then run mdb -p %d to restart " "debugger\n", (int)pid); } if (follow_exec) (void) mdb_tgt_continue(t, NULL); } static int pt_setflags(mdb_tgt_t *t, int flags) { pt_data_t *pt = t->t_data; if ((flags ^ t->t_flags) & MDB_TGT_F_RDWR) { int mode = (flags & MDB_TGT_F_RDWR) ? O_RDWR : O_RDONLY; mdb_io_t *io; if (pt->p_fio == NULL) return (set_errno(EMDB_NOEXEC)); io = mdb_fdio_create_path(NULL, IOP_NAME(pt->p_fio), mode, 0); if (io == NULL) return (-1); /* errno is set for us */ t->t_flags = (t->t_flags & ~MDB_TGT_F_RDWR) | (flags & MDB_TGT_F_RDWR); pt->p_fio = mdb_io_hold(io); mdb_io_rele(pt->p_file->gf_io); pt->p_file->gf_io = pt->p_fio; } if (flags & MDB_TGT_F_FORCE) { t->t_flags |= MDB_TGT_F_FORCE; pt->p_gflags |= PGRAB_FORCE; } return (0); } static int pt_frame(void *argp, uintptr_t pc, uint_t argc, const long *argv, const mdb_tgt_gregset_t *gregs) { mdb_stack_frame_hdl_t *hdl = argp; uint64_t bp; #if defined(__i386) || defined(__amd64) bp = gregs->gregs[R_FP]; #else bp = gregs->gregs[R_SP]; #endif mdb_stack_frame(hdl, pc, bp, argc, argv); return (0); } static int pt_framer(void *argp, uintptr_t pc, uint_t argc, const long *argv, const mdb_tgt_gregset_t *gregs) { mdb_stack_frame_hdl_t *hdl = argp; uint_t arglim = mdb_stack_frame_arglim(hdl); if (pt_frameregs((void *)(uintptr_t)arglim, pc, argc, argv, gregs, pc == PC_FAKE) == -1) { /* * Use verbose format if register format is not supported. */ mdb_stack_frame_flags_set(hdl, MSF_VERBOSE); return (pt_frame((void *)hdl, pc, argc, argv, gregs)); } return (0); } static int pt_stack_common(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, mdb_stack_frame_flags_t sflags, mdb_tgt_stack_f *func, prgreg_t saved_pc) { mdb_tgt_t *t = mdb.m_target; mdb_tgt_gregset_t gregs; mdb_stack_frame_hdl_t *hdl; uint_t arglim = mdb.m_nargs; int i; i = mdb_getopts(argc, argv, 'n', MDB_OPT_SETBITS, MSF_ADDR, &sflags, 's', MDB_OPT_SETBITS, MSF_SIZES, &sflags, 't', MDB_OPT_SETBITS, MSF_TYPES, &sflags, 'v', MDB_OPT_SETBITS, MSF_VERBOSE, &sflags, NULL); argc -= i; argv += i; if (argc != 0) { if (argv->a_type == MDB_TYPE_CHAR || argc > 1) return (DCMD_USAGE); arglim = mdb_argtoull(argv); } if (t->t_pshandle == NULL || Pstate(t->t_pshandle) == PS_IDLE) { mdb_warn("no process active\n"); return (DCMD_ERR); } if ((hdl = mdb_stack_frame_init(t, arglim, sflags)) == NULL) { mdb_warn("failed to init stack frame\n"); return (DCMD_ERR); } /* * In the universe of sparcv7, sparcv9, ia32, and amd64 this code can be * common: conveniently #defines R_FP to be the * appropriate register we need to set in order to perform a stack * traceback from a given frame address. */ if (flags & DCMD_ADDRSPEC) { bzero(&gregs, sizeof (gregs)); gregs.gregs[R_FP] = addr; #ifdef __sparc gregs.gregs[R_I7] = saved_pc; #endif /* __sparc */ } else if (PTL_GETREGS(t, PTL_TID(t), gregs.gregs) != 0) { mdb_warn("failed to get current register set"); return (DCMD_ERR); } (void) mdb_tgt_stack_iter(t, &gregs, func, (void *)hdl); return (DCMD_OK); } static int pt_stack(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (pt_stack_common(addr, flags, argc, argv, 0, pt_frame, 0)); } static int pt_stackv(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (pt_stack_common(addr, flags, argc, argv, MSF_VERBOSE, pt_frame, 0)); } static int pt_stackr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { /* * Force printing of first register window, by setting the * saved pc (%i7) to PC_FAKE. */ return (pt_stack_common(addr, flags, argc, argv, 0, pt_framer, PC_FAKE)); } /*ARGSUSED*/ static int pt_ignored(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct ps_prochandle *P = mdb.m_target->t_pshandle; char buf[PRSIGBUFSZ]; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (P == NULL) { mdb_warn("no process is currently active\n"); return (DCMD_ERR); } mdb_printf("%s\n", proc_sigset2str(&Pstatus(P)->pr_sigtrace, " ", FALSE, buf, sizeof (buf))); return (DCMD_OK); } /*ARGSUSED*/ static int pt_lwpid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct ps_prochandle *P = mdb.m_target->t_pshandle; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (P == NULL) { mdb_warn("no process is currently active\n"); return (DCMD_ERR); } mdb_printf("%d\n", Pstatus(P)->pr_lwp.pr_lwpid); return (DCMD_OK); } static int pt_print_lwpid(int *n, const lwpstatus_t *psp) { struct ps_prochandle *P = mdb.m_target->t_pshandle; int nlwp = Pstatus(P)->pr_nlwp; if (*n == nlwp - 2) mdb_printf("%d and ", (int)psp->pr_lwpid); else if (*n == nlwp - 1) mdb_printf("%d are", (int)psp->pr_lwpid); else mdb_printf("%d, ", (int)psp->pr_lwpid); (*n)++; return (0); } /*ARGSUSED*/ static int pt_lwpids(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct ps_prochandle *P = mdb.m_target->t_pshandle; int n = 0; if (P == NULL) { mdb_warn("no process is currently active\n"); return (DCMD_ERR); } switch (Pstatus(P)->pr_nlwp) { case 0: mdb_printf("no lwps are"); break; case 1: mdb_printf("lwpid %d is the only lwp", Pstatus(P)->pr_lwp.pr_lwpid); break; default: mdb_printf("lwpids "); (void) Plwp_iter(P, (proc_lwp_f *)pt_print_lwpid, &n); } switch (Pstate(P)) { case PS_DEAD: mdb_printf(" in core of process %d.\n", Pstatus(P)->pr_pid); break; case PS_IDLE: mdb_printf(" in idle target.\n"); break; default: mdb_printf(" in process %d.\n", (int)Pstatus(P)->pr_pid); break; } return (DCMD_OK); } /*ARGSUSED*/ static int pt_ignore(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pt_data_t *pt = mdb.m_target->t_data; if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (addr < 1 || addr > pt->p_maxsig) { mdb_warn("invalid signal number -- 0t%lu\n", addr); return (DCMD_ERR); } (void) mdb_tgt_vespec_iter(mdb.m_target, pt_ignore_sig, (void *)addr); return (DCMD_OK); } /*ARGSUSED*/ static int pt_attach(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; pt_data_t *pt = t->t_data; int state, perr; if (!(flags & DCMD_ADDRSPEC) && argc == 0) return (DCMD_USAGE); if (((flags & DCMD_ADDRSPEC) && argc != 0) || argc > 1 || (argc != 0 && argv->a_type != MDB_TYPE_STRING)) return (DCMD_USAGE); if (t->t_pshandle != NULL && Pstate(t->t_pshandle) != PS_IDLE) { mdb_warn("debugger is already attached to a %s\n", (Pstate(t->t_pshandle) == PS_DEAD) ? "core" : "process"); return (DCMD_ERR); } if (pt->p_fio == NULL) { mdb_warn("attach requires executable to be specified on " "command-line (or use -p)\n"); return (DCMD_ERR); } if (flags & DCMD_ADDRSPEC) t->t_pshandle = Pgrab((pid_t)addr, pt->p_gflags, &perr); else t->t_pshandle = proc_arg_grab(argv->a_un.a_str, PR_ARG_ANY, pt->p_gflags, &perr); if (t->t_pshandle == NULL) { t->t_pshandle = pt->p_idlehandle; mdb_warn("cannot attach: %s\n", Pgrab_error(perr)); return (DCMD_ERR); } state = Pstate(t->t_pshandle); if (state != PS_DEAD && state != PS_IDLE) { (void) Punsetflags(t->t_pshandle, PR_KLC); (void) Psetflags(t->t_pshandle, PR_RLC); pt_post_attach(t); pt_activate_common(t); } (void) mdb_tgt_status(t, &t->t_status); mdb_module_load_all(0); return (DCMD_OK); } static int pt_regstatus(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; if (t->t_pshandle != NULL) { const pstatus_t *psp = Pstatus(t->t_pshandle); int cursig = psp->pr_lwp.pr_cursig; char signame[SIG2STR_MAX]; int state = Pstate(t->t_pshandle); if (state != PS_DEAD && state != PS_IDLE) mdb_printf("process id = %d\n", psp->pr_pid); else mdb_printf("no process\n"); if (cursig != 0 && sig2str(cursig, signame) == 0) mdb_printf("SIG%s: %s\n", signame, strsignal(cursig)); } return (pt_regs(addr, flags, argc, argv)); } static int pt_thread_name(mdb_tgt_t *t, mdb_tgt_tid_t tid, char *buf, size_t bufsize) { char name[THREAD_NAME_MAX]; buf[0] = '\0'; if (t->t_pshandle == NULL || Plwp_getname(t->t_pshandle, tid, name, sizeof (name)) != 0 || name[0] == '\0') { if (mdb_snprintf(buf, bufsize, "%lu", tid) > bufsize) { return (set_errno(EMDB_NAME2BIG)); } return (0); } if (mdb_snprintf(buf, bufsize, "%lu [%s]", tid, name) > bufsize) { return (set_errno(EMDB_NAME2BIG)); } return (0); } static int pt_findstack(uintptr_t tid, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; mdb_tgt_gregset_t gregs; boolean_t showargs = B_FALSE; boolean_t types = B_FALSE; boolean_t sizes = B_FALSE; boolean_t addrs = B_FALSE; int count; uintptr_t pc, sp; char buf[128]; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); count = mdb_getopts(argc, argv, 'n', MDB_OPT_SETBITS, TRUE, &addrs, 's', MDB_OPT_SETBITS, TRUE, &sizes, 't', MDB_OPT_SETBITS, TRUE, &types, 'v', MDB_OPT_SETBITS, TRUE, &showargs, NULL); argc -= count; argv += count; if (argc > 1 || (argc == 1 && argv->a_type != MDB_TYPE_STRING)) return (DCMD_USAGE); if (PTL_GETREGS(t, tid, gregs.gregs) != 0) { mdb_warn("failed to get register set for thread %p", tid); return (DCMD_ERR); } pc = gregs.gregs[R_PC]; #if defined(__i386) || defined(__amd64) sp = gregs.gregs[R_FP]; #else sp = gregs.gregs[R_SP]; #endif (void) pt_thread_name(t, tid, buf, sizeof (buf)); mdb_printf("stack pointer for thread %s: %p\n", buf, sp); if (pc != 0) mdb_printf("[ %0?lr %a() ]\n", sp, pc); (void) mdb_inc_indent(2); mdb_set_dot(sp); if (argc == 1) { (void) mdb_eval(argv->a_un.a_str); } else { (void) mdb_snprintf(buf, sizeof (buf), "<.$C%s%s%s%s", addrs ? " -n" : "", sizes ? " -s" : "", types ? " -t" : "", showargs ? "" : " 0"); (void) mdb_eval(buf); } (void) mdb_dec_indent(2); return (DCMD_OK); } /*ARGSUSED*/ static int pt_gcore(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; char *prefix = "core"; char *content_str = NULL; core_content_t content = CC_CONTENT_DEFAULT; size_t size; char *fname; pid_t pid; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'o', MDB_OPT_STR, &prefix, 'c', MDB_OPT_STR, &content_str, NULL) != argc) return (DCMD_USAGE); if (content_str != NULL && (proc_str2content(content_str, &content) != 0 || content == CC_CONTENT_INVALID)) { mdb_warn("invalid content string '%s'\n", content_str); return (DCMD_ERR); } if (t->t_pshandle == NULL) { mdb_warn("no process active\n"); return (DCMD_ERR); } pid = Pstatus(t->t_pshandle)->pr_pid; size = 1 + mdb_snprintf(NULL, 0, "%s.%d", prefix, (int)pid); fname = mdb_alloc(size, UM_SLEEP | UM_GC); (void) mdb_snprintf(fname, size, "%s.%d", prefix, (int)pid); if (Pgcore(t->t_pshandle, fname, content) != 0) { /* * Short writes during dumping are specifically described by * EBADE, just as ZFS uses this otherwise-unused code for * checksum errors. Translate to and mdb errno. */ if (errno == EBADE) (void) set_errno(EMDB_SHORTWRITE); mdb_warn("couldn't dump core"); return (DCMD_ERR); } mdb_warn("%s dumped\n", fname); return (DCMD_OK); } /*ARGSUSED*/ static int pt_kill(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; pt_data_t *pt = t->t_data; int state; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (t->t_pshandle != NULL && (state = Pstate(t->t_pshandle)) != PS_DEAD && state != PS_IDLE) { mdb_warn("victim process PID %d forcibly terminated\n", (int)Pstatus(t->t_pshandle)->pr_pid); pt_pre_detach(t, TRUE); pt_release_parents(t); Prelease(t->t_pshandle, PRELEASE_KILL); t->t_pshandle = pt->p_idlehandle; (void) mdb_tgt_status(t, &t->t_status); mdb.m_flags &= ~(MDB_FL_VCREATE | MDB_FL_JOBCTL); } else mdb_warn("no victim process is currently under control\n"); return (DCMD_OK); } /*ARGSUSED*/ static int pt_detach(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; pt_data_t *pt = t->t_data; int rflags = pt->p_rflags; if (argc != 0 && argv->a_type == MDB_TYPE_STRING && strcmp(argv->a_un.a_str, "-a") == 0) { rflags = PRELEASE_HANG | PRELEASE_CLEAR; argv++; argc--; } if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (t->t_pshandle == NULL || Pstate(t->t_pshandle) == PS_IDLE) { mdb_warn("debugger is not currently attached to a process " "or core file\n"); return (DCMD_ERR); } pt_pre_detach(t, TRUE); pt_release_parents(t); Prelease(t->t_pshandle, rflags); t->t_pshandle = pt->p_idlehandle; (void) mdb_tgt_status(t, &t->t_status); mdb.m_flags &= ~(MDB_FL_VCREATE | MDB_FL_JOBCTL); return (DCMD_OK); } static uintmax_t reg_disc_get(const mdb_var_t *v) { mdb_tgt_t *t = MDB_NV_COOKIE(v); mdb_tgt_tid_t tid = PTL_TID(t); mdb_tgt_reg_t r = 0; if (tid != (mdb_tgt_tid_t)-1L) (void) mdb_tgt_getareg(t, tid, mdb_nv_get_name(v), &r); return (r); } static void reg_disc_set(mdb_var_t *v, uintmax_t r) { mdb_tgt_t *t = MDB_NV_COOKIE(v); mdb_tgt_tid_t tid = PTL_TID(t); if (tid != (mdb_tgt_tid_t)-1L && mdb_tgt_putareg(t, tid, mdb_nv_get_name(v), r) == -1) mdb_warn("failed to modify %%%s register", mdb_nv_get_name(v)); } static void pt_print_reason(const lwpstatus_t *psp) { char name[SIG2STR_MAX + 4]; /* enough for SIG+name+\0, syscall or flt */ const char *desc; switch (psp->pr_why) { case PR_REQUESTED: mdb_printf("stopped by debugger"); break; case PR_SIGNALLED: mdb_printf("stopped on %s (%s)", proc_signame(psp->pr_what, name, sizeof (name)), strsignal(psp->pr_what)); break; case PR_SYSENTRY: mdb_printf("stopped on entry to %s system call", proc_sysname(psp->pr_what, name, sizeof (name))); break; case PR_SYSEXIT: mdb_printf("stopped on exit from %s system call", proc_sysname(psp->pr_what, name, sizeof (name))); break; case PR_JOBCONTROL: mdb_printf("stopped by job control"); break; case PR_FAULTED: if (psp->pr_what == FLTBPT) { mdb_printf("stopped on a breakpoint"); } else if (psp->pr_what == FLTWATCH) { switch (psp->pr_info.si_code) { case TRAP_RWATCH: desc = "read"; break; case TRAP_WWATCH: desc = "write"; break; case TRAP_XWATCH: desc = "execute"; break; default: desc = "unknown"; } mdb_printf("stopped %s a watchpoint (%s access to %p)", psp->pr_info.si_trapafter ? "after" : "on", desc, psp->pr_info.si_addr); } else if (psp->pr_what == FLTTRACE) { mdb_printf("stopped after a single-step"); } else { mdb_printf("stopped on a %s fault", proc_fltname(psp->pr_what, name, sizeof (name))); } break; case PR_SUSPENDED: case PR_CHECKPOINT: mdb_printf("suspended by the kernel"); break; default: mdb_printf("stopped for unknown reason (%d/%d)", psp->pr_why, psp->pr_what); } } static void pt_status_dcmd_upanic(prupanic_t *pru) { size_t i; mdb_printf("process panicked\n"); if ((pru->pru_flags & PRUPANIC_FLAG_MSG_ERROR) != 0) { mdb_printf("warning: process upanic message was bad\n"); return; } if ((pru->pru_flags & PRUPANIC_FLAG_MSG_VALID) == 0) return; if ((pru->pru_flags & PRUPANIC_FLAG_MSG_TRUNC) != 0) { mdb_printf("warning: process upanic message truncated\n"); } mdb_printf("upanic message: "); for (i = 0; i < PRUPANIC_BUFLEN; i++) { if (pru->pru_data[i] == '\0') break; if (isascii(pru->pru_data[i]) && isprint(pru->pru_data[i])) { mdb_printf("%c", pru->pru_data[i]); } else { mdb_printf("\\x%02x", pru->pru_data[i]); } } mdb_printf("\n"); } /*ARGSUSED*/ static int pt_status_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; struct ps_prochandle *P = t->t_pshandle; pt_data_t *pt = t->t_data; if (P != NULL) { const psinfo_t *pip = Ppsinfo(P); const pstatus_t *psp = Pstatus(P); int cursig = 0, bits = 0, coredump = 0; int state; GElf_Sym sym; uintptr_t panicstr; char *panicbuf = mdb_alloc(PANIC_BUFSIZE, UM_SLEEP); const siginfo_t *sip = &(psp->pr_lwp.pr_info); prupanic_t *pru = NULL; char execname[MAXPATHLEN], buf[BUFSIZ]; char signame[SIG2STR_MAX + 4]; /* enough for SIG+name+\0 */ mdb_tgt_spec_desc_t desc; mdb_sespec_t *sep; struct utsname uts; prcred_t cred; psinfo_t pi; (void) strcpy(uts.nodename, "unknown machine"); (void) Puname(P, &uts); if (pip != NULL) { bcopy(pip, &pi, sizeof (psinfo_t)); proc_unctrl_psinfo(&pi); } else bzero(&pi, sizeof (psinfo_t)); bits = pi.pr_dmodel == PR_MODEL_ILP32 ? 32 : 64; state = Pstate(P); if (psp != NULL && state != PS_UNDEAD && state != PS_IDLE) cursig = psp->pr_lwp.pr_cursig; if (state == PS_DEAD && pip != NULL) { mdb_printf("debugging core file of %s (%d-bit) " "from %s\n", pi.pr_fname, bits, uts.nodename); } else if (state == PS_DEAD) { mdb_printf("debugging core file\n"); } else if (state == PS_IDLE) { const GElf_Ehdr *ehp = &pt->p_file->gf_ehdr; mdb_printf("debugging %s file (%d-bit)\n", ehp->e_type == ET_EXEC ? "executable" : "object", ehp->e_ident[EI_CLASS] == ELFCLASS32 ? 32 : 64); } else if (state == PS_UNDEAD && pi.pr_pid == 0) { mdb_printf("debugging defunct process\n"); } else { mdb_printf("debugging PID %d (%d-bit)\n", pi.pr_pid, bits); } if (Pexecname(P, execname, sizeof (execname)) != NULL) mdb_printf("file: %s\n", execname); if (pip != NULL && state == PS_DEAD) mdb_printf("initial argv: %s\n", pi.pr_psargs); if (state != PS_UNDEAD && state != PS_IDLE) { mdb_printf("threading model: "); if (pt->p_ptl_ops == &proc_lwp_ops) mdb_printf("raw lwps\n"); else mdb_printf("native threads\n"); } mdb_printf("status: "); switch (state) { case PS_RUN: ASSERT(!(psp->pr_flags & PR_STOPPED)); mdb_printf("process is running"); if (psp->pr_flags & PR_DSTOP) mdb_printf(", debugger stop directive pending"); mdb_printf("\n"); break; case PS_STOP: ASSERT(psp->pr_flags & PR_STOPPED); pt_print_reason(&psp->pr_lwp); if (psp->pr_flags & PR_DSTOP) mdb_printf(", debugger stop directive pending"); if (psp->pr_flags & PR_ASLEEP) mdb_printf(", sleeping in %s system call", proc_sysname(psp->pr_lwp.pr_syscall, signame, sizeof (signame))); mdb_printf("\n"); for (sep = t->t_matched; sep != T_SE_END; sep = sep->se_matched) { mdb_printf("event: %s\n", sep->se_ops->se_info( t, sep, mdb_list_next(&sep->se_velist), &desc, buf, sizeof (buf))); } break; case PS_LOST: mdb_printf("debugger lost control of process\n"); break; case PS_UNDEAD: coredump = WIFSIGNALED(pi.pr_wstat) && WCOREDUMP(pi.pr_wstat); /*FALLTHRU*/ case PS_DEAD: if (cursig == 0 && WIFSIGNALED(pi.pr_wstat)) cursig = WTERMSIG(pi.pr_wstat); (void) Pupanic(P, &pru); /* * Test for upanic first. We can only use pr_wstat == 0 * as a test for gcore if an NT_PRCRED note is present; * these features were added at the same time in Solaris * 8. */ if (pru != NULL) { pt_status_dcmd_upanic(pru); Pupanic_free(pru); } else if (pi.pr_wstat == 0 && Pstate(P) == PS_DEAD && Pcred(P, &cred, 1) == 0) { mdb_printf("process core file generated " "with gcore(1)\n"); } else if (cursig != 0) { mdb_printf("process terminated by %s (%s)", proc_signame(cursig, signame, sizeof (signame)), strsignal(cursig)); if (sip->si_signo != 0 && SI_FROMUSER(sip) && sip->si_pid != 0) { mdb_printf(", pid=%d uid=%u", (int)sip->si_pid, sip->si_uid); if (sip->si_code != 0) { mdb_printf(" code=%d", sip->si_code); } } else { switch (sip->si_signo) { case SIGILL: case SIGTRAP: case SIGFPE: case SIGSEGV: case SIGBUS: case SIGEMT: mdb_printf(", addr=%p", sip->si_addr); default: break; } } if (coredump) mdb_printf(" - core file dumped"); mdb_printf("\n"); } else { mdb_printf("process terminated with exit " "status %d\n", WEXITSTATUS(pi.pr_wstat)); } if (Plookup_by_name(t->t_pshandle, "libc.so", "panicstr", &sym) == 0 && Pread(t->t_pshandle, &panicstr, sizeof (panicstr), sym.st_value) == sizeof (panicstr) && Pread_string(t->t_pshandle, panicbuf, PANIC_BUFSIZE, panicstr) > 0) { mdb_printf("libc panic message: %s", panicbuf); } break; case PS_IDLE: mdb_printf("idle\n"); break; default: mdb_printf("unknown libproc Pstate: %d\n", Pstate(P)); } mdb_free(panicbuf, PANIC_BUFSIZE); } else if (pt->p_file != NULL) { const GElf_Ehdr *ehp = &pt->p_file->gf_ehdr; mdb_printf("debugging %s file (%d-bit)\n", ehp->e_type == ET_EXEC ? "executable" : "object", ehp->e_ident[EI_CLASS] == ELFCLASS32 ? 32 : 64); mdb_printf("executable file: %s\n", IOP_NAME(pt->p_fio)); mdb_printf("status: idle\n"); } return (DCMD_OK); } static int pt_tls(uintptr_t tid, uint_t flags, int argc, const mdb_arg_t *argv) { const char *name; const char *object; GElf_Sym sym; mdb_syminfo_t si; mdb_tgt_t *t = mdb.m_target; if (!(flags & DCMD_ADDRSPEC) || argc > 1) return (DCMD_USAGE); if (argc == 0) { psaddr_t b; if (tlsbase(t, tid, PR_LMID_EVERY, MDB_TGT_OBJ_EXEC, &b) != 0) { mdb_warn("failed to lookup tlsbase for %r", tid); return (DCMD_ERR); } mdb_printf("%lr\n", b); mdb_set_dot(b); return (DCMD_OK); } name = argv[0].a_un.a_str; object = MDB_TGT_OBJ_EVERY; if (pt_lookup_by_name_thr(t, object, name, &sym, &si, tid) != 0) { mdb_warn("failed to lookup %s", name); return (DCMD_ABORT); /* avoid repeated failure */ } if (GELF_ST_TYPE(sym.st_info) != STT_TLS && DCMD_HDRSPEC(flags)) mdb_warn("%s does not refer to thread local storage\n", name); mdb_printf("%llr\n", sym.st_value); mdb_set_dot(sym.st_value); return (DCMD_OK); } /*ARGSUSED*/ static int pt_tmodel(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; pt_data_t *pt = t->t_data; const pt_ptl_ops_t *ptl_ops; if (argc != 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (strcmp(argv->a_un.a_str, "thread") == 0) ptl_ops = &proc_tdb_ops; else if (strcmp(argv->a_un.a_str, "lwp") == 0) ptl_ops = &proc_lwp_ops; else return (DCMD_USAGE); if (t->t_pshandle != NULL && pt->p_ptl_ops != ptl_ops) { PTL_DTOR(t); pt->p_tdb_ops = NULL; pt->p_ptl_ops = &proc_lwp_ops; pt->p_ptl_hdl = NULL; if (ptl_ops == &proc_tdb_ops) { (void) Pobject_iter(t->t_pshandle, (proc_map_f *) thr_check, t); } } (void) mdb_tgt_status(t, &t->t_status); return (DCMD_OK); } static const char * env_match(const char *cmp, const char *nameval) { const char *loc; size_t cmplen = strlen(cmp); loc = strchr(nameval, '='); if (loc != NULL && (loc - nameval) == cmplen && strncmp(nameval, cmp, cmplen) == 0) { return (loc + 1); } return (NULL); } /*ARGSUSED*/ static int print_env(void *data, struct ps_prochandle *P, uintptr_t addr, const char *nameval) { const char *value; if (nameval == NULL) { mdb_printf("<0x%p>\n", addr); } else { if (data == NULL) mdb_printf("%s\n", nameval); else if ((value = env_match(data, nameval)) != NULL) mdb_printf("%s\n", value); } return (0); } /*ARGSUSED*/ static int pt_getenv(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; pt_data_t *pt = t->t_data; int i; uint_t opt_t = 0; mdb_var_t *v; i = mdb_getopts(argc, argv, 't', MDB_OPT_SETBITS, TRUE, &opt_t, NULL); argc -= i; argv += i; if ((flags & DCMD_ADDRSPEC) || argc > 1) return (DCMD_USAGE); if (argc == 1 && argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (opt_t && t->t_pshandle == NULL) { mdb_warn("no process active\n"); return (DCMD_ERR); } if (opt_t && (Pstate(t->t_pshandle) == PS_IDLE || Pstate(t->t_pshandle) == PS_UNDEAD)) { mdb_warn("-t option requires target to be running\n"); return (DCMD_ERR); } if (opt_t != 0) { if (Penv_iter(t->t_pshandle, print_env, argc == 0 ? NULL : (void *)argv->a_un.a_str) != 0) return (DCMD_ERR); } else if (argc == 1) { if ((v = mdb_nv_lookup(&pt->p_env, argv->a_un.a_str)) == NULL) return (DCMD_ERR); ASSERT(strchr(mdb_nv_get_cookie(v), '=') != NULL); mdb_printf("%s\n", strchr(mdb_nv_get_cookie(v), '=') + 1); } else { mdb_nv_rewind(&pt->p_env); while ((v = mdb_nv_advance(&pt->p_env)) != NULL) mdb_printf("%s\n", mdb_nv_get_cookie(v)); } return (DCMD_OK); } /* * Function to set a variable in the internal environment, which is used when * creating new processes. Note that it is possible that 'nameval' can refer to * read-only memory, if mdb calls putenv() on an existing value before calling * this function. While we should avoid this situation, this function is * designed to be robust in the face of such changes. */ static void pt_env_set(pt_data_t *pt, const char *nameval) { mdb_var_t *v; char *equals, *val; const char *name; size_t len; if ((equals = strchr(nameval, '=')) != NULL) { val = strdup(nameval); equals = val + (equals - nameval); } else { /* * nameval doesn't contain an equals character. Convert this to * be 'nameval='. */ len = strlen(nameval); val = mdb_alloc(len + 2, UM_SLEEP); (void) mdb_snprintf(val, len + 2, "%s=", nameval); equals = val + len; } /* temporary truncate the string for lookup/insert */ *equals = '\0'; v = mdb_nv_lookup(&pt->p_env, val); if (v != NULL) { char *old = mdb_nv_get_cookie(v); mdb_free(old, strlen(old) + 1); name = mdb_nv_get_name(v); } else { /* * The environment is created using MDB_NV_EXTNAME, so we must * provide external storage for the variable names. */ name = strdup(val); } *equals = '='; (void) mdb_nv_insert(&pt->p_env, name, NULL, (uintptr_t)val, MDB_NV_EXTNAME); *equals = '='; } /* * Clears the internal environment. */ static void pt_env_clear(pt_data_t *pt) { mdb_var_t *v; char *val, *name; mdb_nv_rewind(&pt->p_env); while ((v = mdb_nv_advance(&pt->p_env)) != NULL) { name = (char *)mdb_nv_get_name(v); val = mdb_nv_get_cookie(v); mdb_nv_remove(&pt->p_env, v); mdb_free(name, strlen(name) + 1); mdb_free(val, strlen(val) + 1); } } /*ARGSUSED*/ static int pt_setenv(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; pt_data_t *pt = t->t_data; char *nameval; size_t len; int alloc; if ((flags & DCMD_ADDRSPEC) || argc == 0 || argc > 2) return (DCMD_USAGE); if ((argc > 0 && argv[0].a_type != MDB_TYPE_STRING) || (argc > 1 && argv[1].a_type != MDB_TYPE_STRING)) return (DCMD_USAGE); if (t->t_pshandle == NULL) { mdb_warn("no process active\n"); return (DCMD_ERR); } /* * If the process is in some sort of running state, warn the user that * changes won't immediately take effect. */ if (Pstate(t->t_pshandle) == PS_RUN || Pstate(t->t_pshandle) == PS_STOP) { mdb_warn("warning: changes will not take effect until process" " is restarted\n"); } /* * We allow two forms of operation. The first is the usual "name=value" * parameter. We also allow the user to specify two arguments, where * the first is the name of the variable, and the second is the value. */ alloc = 0; if (argc == 1) { nameval = (char *)argv->a_un.a_str; } else { len = strlen(argv[0].a_un.a_str) + strlen(argv[1].a_un.a_str) + 2; nameval = mdb_alloc(len, UM_SLEEP); (void) mdb_snprintf(nameval, len, "%s=%s", argv[0].a_un.a_str, argv[1].a_un.a_str); alloc = 1; } pt_env_set(pt, nameval); if (alloc) mdb_free(nameval, strlen(nameval) + 1); return (DCMD_OK); } /*ARGSUSED*/ static int pt_unsetenv(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; pt_data_t *pt = t->t_data; mdb_var_t *v; char *value, *name; if ((flags & DCMD_ADDRSPEC) || argc > 1) return (DCMD_USAGE); if (argc == 1 && argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (t->t_pshandle == NULL) { mdb_warn("no process active\n"); return (DCMD_ERR); } /* * If the process is in some sort of running state, warn the user that * changes won't immediately take effect. */ if (Pstate(t->t_pshandle) == PS_RUN || Pstate(t->t_pshandle) == PS_STOP) { mdb_warn("warning: changes will not take effect until process" " is restarted\n"); } if (argc == 0) { pt_env_clear(pt); } else { if ((v = mdb_nv_lookup(&pt->p_env, argv->a_un.a_str)) != NULL) { name = (char *)mdb_nv_get_name(v); value = mdb_nv_get_cookie(v); mdb_nv_remove(&pt->p_env, v); mdb_free(name, strlen(name) + 1); mdb_free(value, strlen(value) + 1); } } return (DCMD_OK); } void getenv_help(void) { mdb_printf("-t show current process environment" " instead of initial environment.\n"); } static void pt_stack_help(void) { mdb_printf( "Options:\n" " -s show the size of each stack frame to the left\n" " -t where CTF is present, show types for functions and " "arguments\n" " -v include frame pointer information (this is the default " "for %$C%)\n" "\n" "If the optional %cnt% is given, no more than %cnt% " "arguments are shown\nfor each stack frame.\n"); } static void pt_findstack_help(void) { mdb_printf( "Options:\n" " -n do not resolve addresses to names\n" " -s show the size of each stack frame to the left\n" " -t where CTF is present, show types for functions and " "arguments\n" " -v show function arguments\n" "\n" "If the optional %cnt% is given, no more than %cnt% " "arguments are shown\nfor each stack frame.\n"); } static const mdb_dcmd_t pt_dcmds[] = { { "$c", "?[-nstv] [cnt]", "print stack backtrace", pt_stack, pt_stack_help }, { "$C", "?[-nstv] [cnt]", "print stack backtrace", pt_stackv, pt_stack_help }, { "$i", NULL, "print signals that are ignored", pt_ignored }, { "$l", NULL, "print the representative thread's lwp id", pt_lwpid }, { "$L", NULL, "print list of the active lwp ids", pt_lwpids }, { "$r", "?[-u]", "print general-purpose registers", pt_regs }, { "$x", "?", "print floating point registers", pt_fpregs }, { "$X", "?", "print floating point registers", pt_fpregs }, { "$y", "?", "print floating point registers", pt_fpregs }, { "$Y", "?", "print floating point registers", pt_fpregs }, { "$?", "?", "print status and registers", pt_regstatus }, { ":A", "?[core|pid]", "attach to process or core file", pt_attach }, { ":i", ":", "ignore signal (delete all matching events)", pt_ignore }, { ":k", NULL, "forcibly kill and release target", pt_kill }, { ":R", "[-a]", "release the previously attached process", pt_detach }, { "attach", "?[core|pid]", "attach to process or core file", pt_attach }, { "findstack", ":[-nstv]", "find user thread stack", pt_findstack, pt_findstack_help }, { "gcore", "[-o prefix] [-c content]", "produce a core file for the attached process", pt_gcore }, { "getenv", "[-t] [name]", "display an environment variable", pt_getenv, getenv_help }, { "kill", NULL, "forcibly kill and release target", pt_kill }, { "release", "[-a]", "release the previously attached process", pt_detach }, { "regs", "?[-u]", "print general-purpose registers", pt_regs }, { "fpregs", "?[-dqs]", "print floating point registers", pt_fpregs }, { "setenv", "name=value", "set an environment variable", pt_setenv }, { "stack", "?[-nstv] [cnt]", "print stack backtrace", pt_stack, pt_stack_help }, { "stackregs", "?[-nstv]", "print stack backtrace and registers", pt_stackr, pt_stack_help }, { "status", NULL, "print summary of current target", pt_status_dcmd }, { "tls", ":symbol", "lookup TLS data in the context of a given thread", pt_tls }, { "tmodel", "{thread|lwp}", NULL, pt_tmodel }, { "unsetenv", "[name]", "clear an environment variable", pt_unsetenv }, { NULL } }; static void pt_thr_walk_fini(mdb_walk_state_t *wsp) { mdb_addrvec_destroy(wsp->walk_data); mdb_free(wsp->walk_data, sizeof (mdb_addrvec_t)); } static int pt_thr_walk_init(mdb_walk_state_t *wsp) { wsp->walk_data = mdb_zalloc(sizeof (mdb_addrvec_t), UM_SLEEP); mdb_addrvec_create(wsp->walk_data); if (PTL_ITER(mdb.m_target, wsp->walk_data) == -1) { mdb_warn("failed to iterate over threads"); pt_thr_walk_fini(wsp); return (WALK_ERR); } return (WALK_NEXT); } static int pt_thr_walk_step(mdb_walk_state_t *wsp) { if (mdb_addrvec_length(wsp->walk_data) != 0) { return (wsp->walk_callback(mdb_addrvec_shift(wsp->walk_data), NULL, wsp->walk_cbdata)); } return (WALK_DONE); } static const mdb_walker_t pt_walkers[] = { { "thread", "walk list of valid thread identifiers", pt_thr_walk_init, pt_thr_walk_step, pt_thr_walk_fini }, { NULL } }; static int pt_agent_check(boolean_t *agent, const lwpstatus_t *psp) { if (psp->pr_flags & PR_AGENT) *agent = B_TRUE; return (0); } static void pt_activate_common(mdb_tgt_t *t) { pt_data_t *pt = t->t_data; boolean_t hasagent = B_FALSE; GElf_Sym sym; /* * If we have a libproc handle and AT_BASE is set, the process or core * is dynamically linked. We call Prd_agent() to force libproc to * try to initialize librtld_db, and issue a warning if that fails. */ if (t->t_pshandle != NULL && Pgetauxval(t->t_pshandle, AT_BASE) != -1L && Prd_agent(t->t_pshandle) == NULL) { mdb_warn("warning: librtld_db failed to initialize; shared " "library information will not be available\n"); } if (t->t_pshandle != NULL) { (void) Plwp_iter(t->t_pshandle, (proc_lwp_f *)pt_agent_check, &hasagent); } if (hasagent) { mdb_warn("agent lwp detected; forcing " "lwp thread model (use ::tmodel to change)\n"); } else if (t->t_pshandle != NULL && Pstate(t->t_pshandle) != PS_IDLE) { /* * If we have a libproc handle and we do not have an agent LWP, * look for the correct thread debugging library. (If we have * an agent LWP, we leave the model as the raw LWP model to * allow the agent LWP to be visible to the debugger.) */ (void) Pobject_iter(t->t_pshandle, (proc_map_f *)thr_check, t); } /* * If there's a global object named '_mdb_abort_info', assuming we're * debugging mdb itself and load the developer support module. */ if (mdb_gelf_symtab_lookup_by_name(pt->p_symtab, "_mdb_abort_info", &sym, NULL) == 0 && GELF_ST_TYPE(sym.st_info) == STT_OBJECT) { if (mdb_module_load("mdb_ds", MDB_MOD_SILENT) < 0) mdb_warn("warning: failed to load developer support\n"); } mdb_tgt_elf_export(pt->p_file); } static void pt_activate(mdb_tgt_t *t) { static const mdb_nv_disc_t reg_disc = { .disc_set = reg_disc_set, .disc_get = reg_disc_get }; pt_data_t *pt = t->t_data; struct utsname u1, u2; mdb_var_t *v; core_content_t content; if (t->t_pshandle) { mdb_prop_postmortem = (Pstate(t->t_pshandle) == PS_DEAD); mdb_prop_kernel = FALSE; } else mdb_prop_kernel = mdb_prop_postmortem = FALSE; mdb_prop_datamodel = MDB_TGT_MODEL_NATIVE; /* * If we're examining a core file that doesn't contain program text, * and uname(2) doesn't match the NT_UTSNAME note recorded in the * core file, issue a warning. */ if (mdb_prop_postmortem == TRUE && ((content = Pcontent(t->t_pshandle)) == CC_CONTENT_INVALID || !(content & CC_CONTENT_TEXT)) && uname(&u1) >= 0 && Puname(t->t_pshandle, &u2) == 0 && (strcmp(u1.release, u2.release) != 0 || strcmp(u1.version, u2.version) != 0)) { mdb_warn("warning: core file is from %s %s %s; shared text " "mappings may not match installed libraries\n", u2.sysname, u2.release, u2.version); } /* * Perform the common initialization tasks -- these are shared with * the pt_exec() and pt_run() subroutines. */ pt_activate_common(t); (void) mdb_tgt_register_dcmds(t, &pt_dcmds[0], MDB_MOD_FORCE); (void) mdb_tgt_register_walkers(t, &pt_walkers[0], MDB_MOD_FORCE); /* * Iterate through our register description list and export * each register as a named variable. */ mdb_nv_rewind(&pt->p_regs); while ((v = mdb_nv_advance(&pt->p_regs)) != NULL) { ushort_t rd_flags = MDB_TGT_R_FLAGS(mdb_nv_get_value(v)); if (!(rd_flags & MDB_TGT_R_EXPORT)) continue; /* Don't export register as a variable */ (void) mdb_nv_insert(&mdb.m_nv, mdb_nv_get_name(v), ®_disc, (uintptr_t)t, MDB_NV_PERSIST); } } static void pt_deactivate(mdb_tgt_t *t) { pt_data_t *pt = t->t_data; const mdb_dcmd_t *dcp; const mdb_walker_t *wp; mdb_var_t *v, *w; mdb_nv_rewind(&pt->p_regs); while ((v = mdb_nv_advance(&pt->p_regs)) != NULL) { ushort_t rd_flags = MDB_TGT_R_FLAGS(mdb_nv_get_value(v)); if (!(rd_flags & MDB_TGT_R_EXPORT)) continue; /* Didn't export register as a variable */ if (w = mdb_nv_lookup(&mdb.m_nv, mdb_nv_get_name(v))) { w->v_flags &= ~MDB_NV_PERSIST; mdb_nv_remove(&mdb.m_nv, w); } } for (wp = &pt_walkers[0]; wp->walk_name != NULL; wp++) { if (mdb_module_remove_walker(t->t_module, wp->walk_name) == -1) warn("failed to remove walk %s", wp->walk_name); } for (dcp = &pt_dcmds[0]; dcp->dc_name != NULL; dcp++) { if (mdb_module_remove_dcmd(t->t_module, dcp->dc_name) == -1) warn("failed to remove dcmd %s", dcp->dc_name); } mdb_prop_postmortem = FALSE; mdb_prop_kernel = FALSE; mdb_prop_datamodel = MDB_TGT_MODEL_UNKNOWN; } static void pt_periodic(mdb_tgt_t *t) { pt_data_t *pt = t->t_data; if (pt->p_rdstate == PT_RD_CONSIST) { if (t->t_pshandle != NULL && Pstate(t->t_pshandle) < PS_LOST && !(mdb.m_flags & MDB_FL_NOMODS)) { mdb_printf("%s: You've got symbols!\n", mdb.m_pname); mdb_module_load_all(0); } pt->p_rdstate = PT_RD_NONE; } } static void pt_destroy(mdb_tgt_t *t) { pt_data_t *pt = t->t_data; if (pt->p_idlehandle != NULL && pt->p_idlehandle != t->t_pshandle) Prelease(pt->p_idlehandle, 0); if (t->t_pshandle != NULL) { PTL_DTOR(t); pt_release_parents(t); pt_pre_detach(t, TRUE); Prelease(t->t_pshandle, pt->p_rflags); } mdb.m_flags &= ~(MDB_FL_VCREATE | MDB_FL_JOBCTL); pt_close_aout(t); if (pt->p_aout_fio != NULL) mdb_io_rele(pt->p_aout_fio); pt_env_clear(pt); mdb_nv_destroy(&pt->p_env); mdb_nv_destroy(&pt->p_regs); mdb_free(pt, sizeof (pt_data_t)); } /*ARGSUSED*/ static const char * pt_name(mdb_tgt_t *t) { return ("proc"); } static const char * pt_platform(mdb_tgt_t *t) { pt_data_t *pt = t->t_data; if (t->t_pshandle != NULL && Pplatform(t->t_pshandle, pt->p_platform, MAXNAMELEN) != NULL) return (pt->p_platform); return (mdb_conf_platform()); } static int pt_uname(mdb_tgt_t *t, struct utsname *utsp) { if (t->t_pshandle != NULL) return (Puname(t->t_pshandle, utsp)); return (uname(utsp) >= 0 ? 0 : -1); } static int pt_dmodel(mdb_tgt_t *t) { if (t->t_pshandle == NULL) return (MDB_TGT_MODEL_NATIVE); switch (Pstatus(t->t_pshandle)->pr_dmodel) { case PR_MODEL_ILP32: return (MDB_TGT_MODEL_ILP32); case PR_MODEL_LP64: return (MDB_TGT_MODEL_LP64); } return (MDB_TGT_MODEL_UNKNOWN); } static ssize_t pt_vread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr) { ssize_t n; /* * If no handle is open yet, reads from virtual addresses are * allowed to succeed but return zero-filled memory. */ if (t->t_pshandle == NULL) { bzero(buf, nbytes); return (nbytes); } if ((n = Pread(t->t_pshandle, buf, nbytes, addr)) <= 0) return (set_errno(EMDB_NOMAP)); return (n); } static ssize_t pt_vwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr) { ssize_t n; /* * If no handle is open yet, writes to virtual addresses are * allowed to succeed but do not actually modify anything. */ if (t->t_pshandle == NULL) return (nbytes); n = Pwrite(t->t_pshandle, buf, nbytes, addr); if (n == -1 && errno == EIO) return (set_errno(EMDB_NOMAP)); return (n); } static ssize_t pt_fread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr) { pt_data_t *pt = t->t_data; if (pt->p_file != NULL) { return (mdb_gelf_rw(pt->p_file, buf, nbytes, addr, IOPF_READ(pt->p_fio), GIO_READ)); } bzero(buf, nbytes); return (nbytes); } static ssize_t pt_fwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr) { pt_data_t *pt = t->t_data; if (pt->p_file != NULL) { return (mdb_gelf_rw(pt->p_file, (void *)buf, nbytes, addr, IOPF_WRITE(pt->p_fio), GIO_WRITE)); } return (nbytes); } static const char * pt_resolve_lmid(const char *object, Lmid_t *lmidp) { Lmid_t lmid = PR_LMID_EVERY; const char *p; if (object == MDB_TGT_OBJ_EVERY || object == MDB_TGT_OBJ_EXEC) lmid = LM_ID_BASE; /* restrict scope to a.out's link map */ else if (object != MDB_TGT_OBJ_RTLD && strncmp(object, "LM", 2) == 0 && (p = strchr(object, '`')) != NULL) { object += 2; /* skip past initial "LM" prefix */ lmid = strntoul(object, (size_t)(p - object), mdb.m_radix); object = p + 1; /* skip past link map specifier */ } *lmidp = lmid; return (object); } static int tlsbase(mdb_tgt_t *t, mdb_tgt_tid_t tid, Lmid_t lmid, const char *object, psaddr_t *basep) { pt_data_t *pt = t->t_data; const rd_loadobj_t *loadobjp; td_thrhandle_t th; td_err_e err; if (object == MDB_TGT_OBJ_EVERY) return (set_errno(EINVAL)); if (t->t_pshandle == NULL || Pstate(t->t_pshandle) == PS_IDLE) return (set_errno(EMDB_NOPROC)); if (pt->p_tdb_ops == NULL) return (set_errno(EMDB_TDB)); err = pt->p_tdb_ops->td_ta_map_id2thr(pt->p_ptl_hdl, tid, &th); if (err != TD_OK) return (set_errno(tdb_to_errno(err))); /* * If this fails, rtld_db has failed to initialize properly. */ if ((loadobjp = Plmid_to_loadobj(t->t_pshandle, lmid, object)) == NULL) return (set_errno(EMDB_NORTLD)); /* * This will fail if the TLS block has not been allocated for the * object that contains the TLS symbol in question. */ err = pt->p_tdb_ops->td_thr_tlsbase(&th, loadobjp->rl_tlsmodid, basep); if (err != TD_OK) return (set_errno(tdb_to_errno(err))); return (0); } typedef struct { mdb_tgt_t *pl_tgt; const char *pl_name; Lmid_t pl_lmid; GElf_Sym *pl_symp; mdb_syminfo_t *pl_sip; mdb_tgt_tid_t pl_tid; mdb_bool_t pl_found; } pt_lookup_t; /*ARGSUSED*/ static int pt_lookup_cb(void *data, const prmap_t *pmp, const char *object) { pt_lookup_t *plp = data; struct ps_prochandle *P = plp->pl_tgt->t_pshandle; prsyminfo_t si; GElf_Sym sym; if (Pxlookup_by_name(P, plp->pl_lmid, object, plp->pl_name, &sym, &si) != 0) return (0); /* * If we encounter a match with SHN_UNDEF, keep looking for a * better match. Return the first match with SHN_UNDEF set if no * better match is found. */ if (sym.st_shndx == SHN_UNDEF) { if (!plp->pl_found) { plp->pl_found = TRUE; *plp->pl_symp = sym; plp->pl_sip->sym_table = si.prs_table; plp->pl_sip->sym_id = si.prs_id; } return (0); } /* * Note that if the symbol's st_shndx is SHN_UNDEF we don't have the * TLS offset anyway, so adding in the tlsbase would be worthless. */ if (GELF_ST_TYPE(sym.st_info) == STT_TLS && plp->pl_tid != (mdb_tgt_tid_t)-1) { psaddr_t base; if (tlsbase(plp->pl_tgt, plp->pl_tid, plp->pl_lmid, object, &base) != 0) return (-1); /* errno is set for us */ sym.st_value += base; } plp->pl_found = TRUE; *plp->pl_symp = sym; plp->pl_sip->sym_table = si.prs_table; plp->pl_sip->sym_id = si.prs_id; return (1); } /* * Lookup the symbol with a thread context so that we can adjust TLS symbols * to get the values as they would appear in the context of the given thread. */ static int pt_lookup_by_name_thr(mdb_tgt_t *t, const char *object, const char *name, GElf_Sym *symp, mdb_syminfo_t *sip, mdb_tgt_tid_t tid) { struct ps_prochandle *P = t->t_pshandle; pt_data_t *pt = t->t_data; Lmid_t lmid; uint_t i; const rd_loadobj_t *aout_lop; object = pt_resolve_lmid(object, &lmid); if (P != NULL) { pt_lookup_t pl; pl.pl_tgt = t; pl.pl_name = name; pl.pl_lmid = lmid; pl.pl_symp = symp; pl.pl_sip = sip; pl.pl_tid = tid; pl.pl_found = FALSE; if (object == MDB_TGT_OBJ_EVERY) { if (Pobject_iter_resolved(P, pt_lookup_cb, &pl) == -1) return (-1); /* errno is set for us */ if ((!pl.pl_found) && (Pobject_iter(P, pt_lookup_cb, &pl) == -1)) return (-1); /* errno is set for us */ } else { const prmap_t *pmp; /* * This can fail either due to an invalid lmid or * an invalid object. To determine which is * faulty, we test the lmid against known valid * lmids and then see if using a wild-card lmid * improves ths situation. */ if ((pmp = Plmid_to_map(P, lmid, object)) == NULL) { if (lmid != PR_LMID_EVERY && lmid != LM_ID_BASE && lmid != LM_ID_LDSO && Plmid_to_map(P, PR_LMID_EVERY, object) != NULL) return (set_errno(EMDB_NOLMID)); else return (set_errno(EMDB_NOOBJ)); } if (pt_lookup_cb(&pl, pmp, object) == -1) return (-1); /* errno is set for us */ } if (pl.pl_found) return (0); } /* * If libproc doesn't have the symbols for rtld, we're cooked -- * mdb doesn't have those symbols either. */ if (object == MDB_TGT_OBJ_RTLD) return (set_errno(EMDB_NOSYM)); if (object != MDB_TGT_OBJ_EXEC && object != MDB_TGT_OBJ_EVERY) { int status = mdb_gelf_symtab_lookup_by_file(pt->p_symtab, object, name, symp, &sip->sym_id); if (status != 0) { if (P != NULL && Plmid_to_map(P, PR_LMID_EVERY, object) != NULL) return (set_errno(EMDB_NOSYM)); else return (-1); /* errno set from lookup_by_file */ } goto found; } if (mdb_gelf_symtab_lookup_by_name(pt->p_symtab, name, symp, &i) == 0) { sip->sym_table = MDB_TGT_SYMTAB; sip->sym_id = i; goto local_found; } if (mdb_gelf_symtab_lookup_by_name(pt->p_dynsym, name, symp, &i) == 0) { sip->sym_table = MDB_TGT_DYNSYM; sip->sym_id = i; goto local_found; } return (set_errno(EMDB_NOSYM)); local_found: if (pt->p_file != NULL && pt->p_file->gf_ehdr.e_type == ET_DYN && P != NULL && (aout_lop = Pname_to_loadobj(P, PR_OBJ_EXEC)) != NULL) symp->st_value += aout_lop->rl_base; found: /* * If the symbol has type TLS, libproc should have found the symbol * if it exists and has been allocated. */ if (GELF_ST_TYPE(symp->st_info) == STT_TLS) return (set_errno(EMDB_TLS)); return (0); } static int pt_lookup_by_name(mdb_tgt_t *t, const char *object, const char *name, GElf_Sym *symp, mdb_syminfo_t *sip) { return (pt_lookup_by_name_thr(t, object, name, symp, sip, PTL_TID(t))); } static int pt_lookup_by_addr(mdb_tgt_t *t, uintptr_t addr, uint_t flags, char *buf, size_t nbytes, GElf_Sym *symp, mdb_syminfo_t *sip) { struct ps_prochandle *P = t->t_pshandle; pt_data_t *pt = t->t_data; rd_plt_info_t rpi = { 0 }; const char *pltsym; int rv, match, i; mdb_gelf_symtab_t *gsts[3]; /* mdb.m_prsym, .symtab, .dynsym */ int gstc = 0; /* number of valid gsts[] entries */ mdb_gelf_symtab_t *gst = NULL; /* set if 'sym' is from a gst */ const prmap_t *pmp = NULL; /* set if 'sym' is from libproc */ GElf_Sym sym; /* best symbol found so far if !exact */ prsyminfo_t si; /* * Fill in our array of symbol table pointers with the private symbol * table, static symbol table, and dynamic symbol table if applicable. * These are done in order of precedence so that if we match and * MDB_TGT_SYM_EXACT is set, we need not look any further. */ if (mdb.m_prsym != NULL) gsts[gstc++] = mdb.m_prsym; if (P == NULL && pt->p_symtab != NULL) gsts[gstc++] = pt->p_symtab; if (P == NULL && pt->p_dynsym != NULL) gsts[gstc++] = pt->p_dynsym; /* * Loop through our array attempting to match the address. If we match * and we're in exact mode, we're done. Otherwise save the symbol in * the local sym variable if it is closer than our previous match. * We explicitly watch for zero-valued symbols since DevPro insists * on storing __fsr_init_value's value as the symbol value instead * of storing it in a constant integer. */ for (i = 0; i < gstc; i++) { if (mdb_gelf_symtab_lookup_by_addr(gsts[i], addr, flags, buf, nbytes, symp, &sip->sym_id) != 0 || symp->st_value == 0) continue; if (flags & MDB_TGT_SYM_EXACT) { gst = gsts[i]; goto found; } if (gst == NULL || mdb_gelf_sym_closer(symp, &sym, addr)) { gst = gsts[i]; sym = *symp; } } /* * If we have no libproc handle active, we're done: fail if gst is * NULL; otherwise copy out our best symbol and skip to the end. * We also skip to found if gst is the private symbol table: we * want this to always take precedence over PLT re-vectoring. */ if (P == NULL || (gst != NULL && gst == mdb.m_prsym)) { if (gst == NULL) return (set_errno(EMDB_NOSYMADDR)); *symp = sym; goto found; } /* * Check to see if the address is in a PLT: if it is, use librtld_db to * attempt to resolve the PLT entry. If the entry is bound, reset addr * to the bound address, add a special prefix to the caller's buf, * forget our previous guess, and then continue using the new addr. * If the entry is not bound, copy the corresponding symbol name into * buf and return a fake symbol for the given address. */ if ((pltsym = Ppltdest(P, addr)) != NULL) { const rd_loadobj_t *rlp; rd_agent_t *rap; if ((rap = Prd_agent(P)) != NULL && (rlp = Paddr_to_loadobj(P, addr)) != NULL && rd_plt_resolution(rap, addr, Pstatus(P)->pr_lwp.pr_lwpid, rlp->rl_plt_base, &rpi) == RD_OK && (rpi.pi_flags & RD_FLG_PI_PLTBOUND)) { size_t n; n = mdb_iob_snprintf(buf, nbytes, "PLT="); addr = rpi.pi_baddr; if (n > nbytes) { buf += nbytes; nbytes = 0; } else { buf += n; nbytes -= n; } gst = NULL; } else { (void) mdb_iob_snprintf(buf, nbytes, "PLT:%s", pltsym); bzero(symp, sizeof (GElf_Sym)); symp->st_value = addr; symp->st_info = GELF_ST_INFO(STB_GLOBAL, STT_FUNC); return (0); } } /* * Ask libproc to convert the address to the closest symbol for us. * Once we get the closest symbol, we perform the EXACT match or * smart-mode or absolute distance check ourself: */ if (PT_LIBPROC_RESOLVE(P)) { rv = Pxlookup_by_addr_resolved(P, addr, buf, nbytes, symp, &si); } else { rv = Pxlookup_by_addr(P, addr, buf, nbytes, symp, &si); } if ((rv == 0) && (symp->st_value != 0) && (gst == NULL || mdb_gelf_sym_closer(symp, &sym, addr))) { if (flags & MDB_TGT_SYM_EXACT) match = (addr == symp->st_value); else if (mdb.m_symdist == 0) match = (addr >= symp->st_value && addr < symp->st_value + symp->st_size); else match = (addr >= symp->st_value && addr < symp->st_value + mdb.m_symdist); if (match) { pmp = Paddr_to_map(P, addr); gst = NULL; sip->sym_table = si.prs_table; sip->sym_id = si.prs_id; goto found; } } /* * If we get here, Plookup_by_addr has failed us. If we have no * previous best symbol (gst == NULL), we've failed completely. * Otherwise we copy out that symbol and continue on to 'found'. */ if (gst == NULL) return (set_errno(EMDB_NOSYMADDR)); *symp = sym; found: /* * Once we've found something, copy the final name into the caller's * buffer and prefix it with the mapping name if appropriate. */ if (pmp != NULL && pmp != Pname_to_map(P, PR_OBJ_EXEC)) { const char *prefix = pmp->pr_mapname; Lmid_t lmid; if (PT_LIBPROC_RESOLVE(P)) { if (Pobjname_resolved(P, addr, pt->p_objname, MDB_TGT_MAPSZ)) prefix = pt->p_objname; } else { if (Pobjname(P, addr, pt->p_objname, MDB_TGT_MAPSZ)) prefix = pt->p_objname; } if (buf != NULL && nbytes > 1) { (void) strncpy(pt->p_symname, buf, MDB_TGT_SYM_NAMLEN); pt->p_symname[MDB_TGT_SYM_NAMLEN - 1] = '\0'; } else { pt->p_symname[0] = '\0'; } if (prefix == pt->p_objname && Plmid(P, addr, &lmid) == 0 && ( (lmid != LM_ID_BASE && lmid != LM_ID_LDSO) || (mdb.m_flags & MDB_FL_SHOWLMID))) { (void) mdb_iob_snprintf(buf, nbytes, "LM%lr`%s`%s", lmid, strbasename(prefix), pt->p_symname); } else { (void) mdb_iob_snprintf(buf, nbytes, "%s`%s", strbasename(prefix), pt->p_symname); } } else if (gst != NULL && buf != NULL && nbytes > 0) { (void) strncpy(buf, mdb_gelf_sym_name(gst, symp), nbytes); buf[nbytes - 1] = '\0'; } return (0); } static int pt_symbol_iter_cb(void *arg, const GElf_Sym *sym, const char *name, const prsyminfo_t *sip) { pt_symarg_t *psp = arg; psp->psym_info.sym_id = sip->prs_id; return (psp->psym_func(psp->psym_private, sym, name, &psp->psym_info, psp->psym_obj)); } static int pt_objsym_iter(void *arg, const prmap_t *pmp, const char *object) { Lmid_t lmid = PR_LMID_EVERY; pt_symarg_t *psp = arg; psp->psym_obj = object; (void) Plmid(psp->psym_targ->t_pshandle, pmp->pr_vaddr, &lmid); (void) Pxsymbol_iter(psp->psym_targ->t_pshandle, lmid, object, psp->psym_which, psp->psym_type, pt_symbol_iter_cb, arg); return (0); } static int pt_symbol_filt(void *arg, const GElf_Sym *sym, const char *name, uint_t id) { pt_symarg_t *psp = arg; if (mdb_tgt_sym_match(sym, psp->psym_type)) { psp->psym_info.sym_id = id; return (psp->psym_func(psp->psym_private, sym, name, &psp->psym_info, psp->psym_obj)); } return (0); } static int pt_symbol_iter(mdb_tgt_t *t, const char *object, uint_t which, uint_t type, mdb_tgt_sym_f *func, void *private) { pt_data_t *pt = t->t_data; mdb_gelf_symtab_t *gst; pt_symarg_t ps; Lmid_t lmid; object = pt_resolve_lmid(object, &lmid); ps.psym_targ = t; ps.psym_which = which; ps.psym_type = type; ps.psym_func = func; ps.psym_private = private; ps.psym_obj = object; if (t->t_pshandle != NULL) { if (object != MDB_TGT_OBJ_EVERY) { if (Plmid_to_map(t->t_pshandle, lmid, object) == NULL) return (set_errno(EMDB_NOOBJ)); (void) Pxsymbol_iter(t->t_pshandle, lmid, object, which, type, pt_symbol_iter_cb, &ps); return (0); } else if (Prd_agent(t->t_pshandle) != NULL) { if (PT_LIBPROC_RESOLVE(t->t_pshandle)) { (void) Pobject_iter_resolved(t->t_pshandle, pt_objsym_iter, &ps); } else { (void) Pobject_iter(t->t_pshandle, pt_objsym_iter, &ps); } return (0); } } if (lmid != LM_ID_BASE && lmid != PR_LMID_EVERY) return (set_errno(EMDB_NOLMID)); if (object != MDB_TGT_OBJ_EXEC && object != MDB_TGT_OBJ_EVERY && pt->p_fio != NULL && strcmp(object, IOP_NAME(pt->p_fio)) != 0) return (set_errno(EMDB_NOOBJ)); if (which == MDB_TGT_SYMTAB) gst = pt->p_symtab; else gst = pt->p_dynsym; if (gst != NULL) { ps.psym_info.sym_table = gst->gst_tabid; mdb_gelf_symtab_iter(gst, pt_symbol_filt, &ps); } return (0); } static const mdb_map_t * pt_prmap_to_mdbmap(mdb_tgt_t *t, const prmap_t *prp, mdb_map_t *mp) { struct ps_prochandle *P = t->t_pshandle; char *rv, name[MAXPATHLEN]; Lmid_t lmid; if (PT_LIBPROC_RESOLVE(P)) { rv = Pobjname_resolved(P, prp->pr_vaddr, name, sizeof (name)); } else { rv = Pobjname(P, prp->pr_vaddr, name, sizeof (name)); } if (rv != NULL) { if (Plmid(P, prp->pr_vaddr, &lmid) == 0 && ( (lmid != LM_ID_BASE && lmid != LM_ID_LDSO) || (mdb.m_flags & MDB_FL_SHOWLMID))) { (void) mdb_iob_snprintf(mp->map_name, MDB_TGT_MAPSZ, "LM%lr`%s", lmid, name); } else { (void) strncpy(mp->map_name, name, MDB_TGT_MAPSZ - 1); mp->map_name[MDB_TGT_MAPSZ - 1] = '\0'; } } else { (void) strncpy(mp->map_name, prp->pr_mapname, MDB_TGT_MAPSZ - 1); mp->map_name[MDB_TGT_MAPSZ - 1] = '\0'; } mp->map_base = prp->pr_vaddr; mp->map_size = prp->pr_size; mp->map_flags = 0; if (prp->pr_mflags & MA_READ) mp->map_flags |= MDB_TGT_MAP_R; if (prp->pr_mflags & MA_WRITE) mp->map_flags |= MDB_TGT_MAP_W; if (prp->pr_mflags & MA_EXEC) mp->map_flags |= MDB_TGT_MAP_X; if (prp->pr_mflags & MA_SHM) mp->map_flags |= MDB_TGT_MAP_SHMEM; if (prp->pr_mflags & MA_BREAK) mp->map_flags |= MDB_TGT_MAP_HEAP; if (prp->pr_mflags & MA_STACK) mp->map_flags |= MDB_TGT_MAP_STACK; if (prp->pr_mflags & MA_ANON) mp->map_flags |= MDB_TGT_MAP_ANON; return (mp); } /*ARGSUSED*/ static int pt_map_apply(void *arg, const prmap_t *prp, const char *name) { pt_maparg_t *pmp = arg; mdb_map_t map; return (pmp->pmap_func(pmp->pmap_private, pt_prmap_to_mdbmap(pmp->pmap_targ, prp, &map), map.map_name)); } static int pt_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { if (t->t_pshandle != NULL) { pt_maparg_t pm; pm.pmap_targ = t; pm.pmap_func = func; pm.pmap_private = private; if (PT_LIBPROC_RESOLVE(t->t_pshandle)) { (void) Pmapping_iter_resolved(t->t_pshandle, pt_map_apply, &pm); } else { (void) Pmapping_iter(t->t_pshandle, pt_map_apply, &pm); } return (0); } return (set_errno(EMDB_NOPROC)); } static int pt_object_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { pt_data_t *pt = t->t_data; /* * If we have a libproc handle, we can just call Pobject_iter to * iterate over its list of load object information. */ if (t->t_pshandle != NULL) { pt_maparg_t pm; pm.pmap_targ = t; pm.pmap_func = func; pm.pmap_private = private; if (PT_LIBPROC_RESOLVE(t->t_pshandle)) { (void) Pobject_iter_resolved(t->t_pshandle, pt_map_apply, &pm); } else { (void) Pobject_iter(t->t_pshandle, pt_map_apply, &pm); } return (0); } /* * If we're examining an executable or other ELF file but we have no * libproc handle, fake up some information based on DT_NEEDED entries. */ if (pt->p_dynsym != NULL && pt->p_file->gf_dyns != NULL && pt->p_fio != NULL) { mdb_gelf_sect_t *gsp = pt->p_dynsym->gst_ssect; GElf_Dyn *dynp = pt->p_file->gf_dyns; mdb_map_t *mp = &pt->p_map; const char *s = IOP_NAME(pt->p_fio); size_t i; (void) strncpy(mp->map_name, s, MDB_TGT_MAPSZ); mp->map_name[MDB_TGT_MAPSZ - 1] = '\0'; mp->map_flags = MDB_TGT_MAP_R | MDB_TGT_MAP_X; mp->map_base = 0; mp->map_size = 0; if (func(private, mp, s) != 0) return (0); for (i = 0; i < pt->p_file->gf_ndyns; i++, dynp++) { if (dynp->d_tag == DT_NEEDED) { s = (char *)gsp->gs_data + dynp->d_un.d_val; (void) strncpy(mp->map_name, s, MDB_TGT_MAPSZ); mp->map_name[MDB_TGT_MAPSZ - 1] = '\0'; if (func(private, mp, s) != 0) return (0); } } return (0); } return (set_errno(EMDB_NOPROC)); } static const mdb_map_t * pt_addr_to_map(mdb_tgt_t *t, uintptr_t addr) { pt_data_t *pt = t->t_data; const prmap_t *pmp; if (t->t_pshandle == NULL) { (void) set_errno(EMDB_NOPROC); return (NULL); } if ((pmp = Paddr_to_map(t->t_pshandle, addr)) == NULL) { (void) set_errno(EMDB_NOMAP); return (NULL); } return (pt_prmap_to_mdbmap(t, pmp, &pt->p_map)); } static const mdb_map_t * pt_name_to_map(mdb_tgt_t *t, const char *object) { pt_data_t *pt = t->t_data; const prmap_t *pmp; Lmid_t lmid; if (t->t_pshandle == NULL) { (void) set_errno(EMDB_NOPROC); return (NULL); } object = pt_resolve_lmid(object, &lmid); if ((pmp = Plmid_to_map(t->t_pshandle, lmid, object)) == NULL) { (void) set_errno(EMDB_NOOBJ); return (NULL); } return (pt_prmap_to_mdbmap(t, pmp, &pt->p_map)); } static ctf_file_t * pt_addr_to_ctf(mdb_tgt_t *t, uintptr_t addr) { ctf_file_t *ret; if (t->t_pshandle == NULL) { (void) set_errno(EMDB_NOPROC); return (NULL); } if ((ret = Paddr_to_ctf(t->t_pshandle, addr)) == NULL) { (void) set_errno(EMDB_NOOBJ); return (NULL); } return (ret); } static ctf_file_t * pt_name_to_ctf(mdb_tgt_t *t, const char *name) { ctf_file_t *ret; if (t->t_pshandle == NULL) { (void) set_errno(EMDB_NOPROC); return (NULL); } if ((ret = Pname_to_ctf(t->t_pshandle, name)) == NULL) { (void) set_errno(EMDB_NOOBJ); return (NULL); } return (ret); } static int pt_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { const pstatus_t *psp; prgregset_t gregs; int state; bzero(tsp, sizeof (mdb_tgt_status_t)); if (t->t_pshandle == NULL) { tsp->st_state = MDB_TGT_IDLE; return (0); } switch (state = Pstate(t->t_pshandle)) { case PS_RUN: tsp->st_state = MDB_TGT_RUNNING; break; case PS_STOP: tsp->st_state = MDB_TGT_STOPPED; psp = Pstatus(t->t_pshandle); tsp->st_tid = PTL_TID(t); if (PTL_GETREGS(t, tsp->st_tid, gregs) == 0) tsp->st_pc = gregs[R_PC]; if (psp->pr_flags & PR_ISTOP) tsp->st_flags |= MDB_TGT_ISTOP; if (psp->pr_flags & PR_DSTOP) tsp->st_flags |= MDB_TGT_DSTOP; break; case PS_LOST: tsp->st_state = MDB_TGT_LOST; break; case PS_UNDEAD: tsp->st_state = MDB_TGT_UNDEAD; break; case PS_DEAD: tsp->st_state = MDB_TGT_DEAD; break; case PS_IDLE: tsp->st_state = MDB_TGT_IDLE; break; default: fail("unknown libproc state (%d)\n", state); } if (t->t_flags & MDB_TGT_F_BUSY) tsp->st_flags |= MDB_TGT_BUSY; return (0); } static void pt_dupfd(const char *file, int oflags, mode_t mode, int dfd) { int fd; if ((fd = open(file, oflags, mode)) >= 0) { (void) fcntl(fd, F_DUP2FD, dfd); (void) close(fd); } else warn("failed to open %s as descriptor %d", file, dfd); } /* * The Pcreate_callback() function interposes on the default, empty libproc * definition. It will be called following a fork of a new child process by * Pcreate() below, but before the exec of the new process image. We use this * callback to optionally redirect stdin and stdout and reset the dispositions * of SIGPIPE and SIGQUIT from SIG_IGN back to SIG_DFL. */ /*ARGSUSED*/ void Pcreate_callback(struct ps_prochandle *P) { pt_data_t *pt = mdb.m_target->t_data; if (pt->p_stdin != NULL) pt_dupfd(pt->p_stdin, O_RDWR, 0, STDIN_FILENO); if (pt->p_stdout != NULL) pt_dupfd(pt->p_stdout, O_CREAT | O_WRONLY, 0666, STDOUT_FILENO); (void) mdb_signal_sethandler(SIGPIPE, MDB_SIG_DFL, NULL); (void) mdb_signal_sethandler(SIGQUIT, MDB_SIG_DFL, NULL); } static int pt_run(mdb_tgt_t *t, int argc, const mdb_arg_t *argv) { pt_data_t *pt = t->t_data; struct ps_prochandle *P; char execname[MAXPATHLEN]; const char **pargv; int pargc = 0; int i, perr; char **penv; mdb_var_t *v; if (pt->p_aout_fio == NULL) { warn("run requires executable to be specified on " "command-line\n"); return (set_errno(EMDB_TGT)); } pargv = mdb_alloc(sizeof (char *) * (argc + 2), UM_SLEEP); pargv[pargc++] = strbasename(IOP_NAME(pt->p_aout_fio)); for (i = 0; i < argc; i++) { if (argv[i].a_type != MDB_TYPE_STRING) { mdb_free(pargv, sizeof (char *) * (argc + 2)); return (set_errno(EINVAL)); } if (argv[i].a_un.a_str[0] == '<') pt->p_stdin = argv[i].a_un.a_str + 1; else if (argv[i].a_un.a_str[0] == '>') pt->p_stdout = argv[i].a_un.a_str + 1; else pargv[pargc++] = argv[i].a_un.a_str; } pargv[pargc] = NULL; /* * Since Pcreate() uses execvp() and "." may not be present in $PATH, * we must manually prepend "./" when the executable is a simple name. */ if (strchr(IOP_NAME(pt->p_aout_fio), '/') == NULL) { (void) snprintf(execname, sizeof (execname), "./%s", IOP_NAME(pt->p_aout_fio)); } else { (void) snprintf(execname, sizeof (execname), "%s", IOP_NAME(pt->p_aout_fio)); } penv = mdb_alloc((mdb_nv_size(&pt->p_env)+ 1) * sizeof (char *), UM_SLEEP); for (mdb_nv_rewind(&pt->p_env), i = 0; (v = mdb_nv_advance(&pt->p_env)) != NULL; i++) penv[i] = mdb_nv_get_cookie(v); penv[i] = NULL; P = Pxcreate(execname, (char **)pargv, penv, &perr, NULL, 0); mdb_free(pargv, sizeof (char *) * (argc + 2)); pt->p_stdin = pt->p_stdout = NULL; mdb_free(penv, i * sizeof (char *)); if (P == NULL) { warn("failed to create process: %s\n", Pcreate_error(perr)); return (set_errno(EMDB_TGT)); } if (t->t_pshandle != NULL) { pt_pre_detach(t, TRUE); if (t->t_pshandle != pt->p_idlehandle) Prelease(t->t_pshandle, pt->p_rflags); } (void) Punsetflags(P, PR_RLC); /* make sure run-on-last-close is off */ (void) Psetflags(P, PR_KLC); /* kill on last close by debugger */ pt->p_rflags = PRELEASE_KILL; /* kill on debugger Prelease */ t->t_pshandle = P; pt_post_attach(t); pt_activate_common(t); (void) mdb_tgt_status(t, &t->t_status); mdb.m_flags |= MDB_FL_VCREATE; return (0); } /* * Forward a signal to the victim process in order to force it to stop or die. * Refer to the comments above pt_setrun(), below, for more info. */ /*ARGSUSED*/ static void pt_sigfwd(int sig, siginfo_t *sip, ucontext_t *ucp, mdb_tgt_t *t) { struct ps_prochandle *P = t->t_pshandle; const lwpstatus_t *psp = &Pstatus(P)->pr_lwp; pid_t pid = Pstatus(P)->pr_pid; long ctl[2]; if (getpgid(pid) != mdb.m_pgid) { mdb_dprintf(MDB_DBG_TGT, "fwd SIG#%d to %d\n", sig, (int)pid); (void) kill(pid, sig); } if (Pwait(P, 1) == 0 && (psp->pr_flags & PR_STOPPED) && psp->pr_why == PR_JOBCONTROL && Pdstop(P) == 0) { /* * If we're job control stopped and our DSTOP is pending, the * victim will never see our signal, so undo the kill() and * then send SIGCONT the victim to kick it out of the job * control stop and force our DSTOP to take effect. */ if ((psp->pr_flags & PR_DSTOP) && prismember(&Pstatus(P)->pr_sigpend, sig)) { ctl[0] = PCUNKILL; ctl[1] = sig; (void) write(Pctlfd(P), ctl, sizeof (ctl)); } mdb_dprintf(MDB_DBG_TGT, "fwd SIGCONT to %d\n", (int)pid); (void) kill(pid, SIGCONT); } } /* * Common code for step and continue: if no victim process has been created, * call pt_run() to create one. Then set the victim running, clearing any * pending fault. One special case is that if the victim was previously * stopped on reception of SIGINT, we know that SIGINT was traced and the user * requested the victim to stop, so clear this signal before continuing. * For all other traced signals, the signal will be delivered on continue. * * Once the victim process is running, we wait for it to stop on an event of * interest. Although libproc provides the basic primitive to wait for the * victim, we must be careful in our handling of signals. We want to allow the * user to issue a SIGINT or SIGQUIT using the designated terminal control * character (typically ^C and ^\), and have these signals stop the target and * return control to the debugger if the signals are traced. There are three * cases to be considered in our implementation: * * (1) If the debugger and victim are in the same process group, both receive * the signal from the terminal driver. The debugger returns from Pwait() with * errno = EINTR, so we want to loop back and continue waiting until the victim * stops on receipt of its SIGINT or SIGQUIT. * * (2) If the debugger and victim are in different process groups, and the * victim is a member of the foreground process group, it will receive the * signal from the terminal driver and the debugger will not. As such, we * will remain blocked in Pwait() until the victim stops on its signal. * * (3) If the debugger and victim are in different process groups, and the * debugger is a member of the foreground process group, it will receive the * signal from the terminal driver, and the victim will not. The debugger * returns from Pwait() with errno = EINTR, so we need to forward the signal * to the victim process directly and then Pwait() again for it to stop. * * We can observe that all three cases are handled by simply calling Pwait() * repeatedly if it fails with EINTR, and forwarding SIGINT and SIGQUIT to * the victim if it is in a different process group, using pt_sigfwd() above. * * An additional complication is that the process may not be able to field * the signal if it is currently stopped by job control. In this case, we * also DSTOP the process, and then send it a SIGCONT to wake it up from * job control and force it to re-enter stop() under the control of /proc. * * Finally, we would like to allow the user to suspend the process using the * terminal suspend character (typically ^Z) if both are in the same session. * We again employ pt_sigfwd() to forward SIGTSTP to the victim, wait for it to * stop from job control, and then capture it using /proc. Once the process * has stopped, normal SIGTSTP processing is restored and the user can issue * another ^Z in order to suspend the debugger and return to the parent shell. */ static int pt_setrun(mdb_tgt_t *t, mdb_tgt_status_t *tsp, int flags) { struct ps_prochandle *P = t->t_pshandle; pt_data_t *pt = t->t_data; pid_t old_pgid = -1; mdb_signal_f *intf, *quitf, *tstpf; const lwpstatus_t *psp; void *intd, *quitd, *tstpd; int sig = pt->p_signal; int error = 0; int pgid = -1; pt->p_signal = 0; /* clear pending signal */ if (P == NULL && pt_run(t, 0, NULL) == -1) return (-1); /* errno is set for us */ P = t->t_pshandle; psp = &Pstatus(P)->pr_lwp; if (sig == 0 && psp->pr_why == PR_SIGNALLED && psp->pr_what == SIGINT) flags |= PRCSIG; /* clear pending SIGINT */ else flags |= PRCFAULT; /* clear any pending fault (e.g. BPT) */ intf = mdb_signal_gethandler(SIGINT, &intd); quitf = mdb_signal_gethandler(SIGQUIT, &quitd); tstpf = mdb_signal_gethandler(SIGTSTP, &tstpd); (void) mdb_signal_sethandler(SIGINT, (mdb_signal_f *)pt_sigfwd, t); (void) mdb_signal_sethandler(SIGQUIT, (mdb_signal_f *)pt_sigfwd, t); (void) mdb_signal_sethandler(SIGTSTP, (mdb_signal_f *)pt_sigfwd, t); if (sig != 0 && Pstate(P) == PS_RUN && kill(Pstatus(P)->pr_pid, sig) == -1) { error = errno; goto out; } /* * If we attached to a job stopped background process in the same * session, make its pgid the foreground process group before running * it. Ignore SIGTTOU while doing this to avoid being suspended. */ if (mdb.m_flags & MDB_FL_JOBCTL) { (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_IGN, NULL); (void) IOP_CTL(mdb.m_term, TIOCGPGRP, &old_pgid); (void) IOP_CTL(mdb.m_term, TIOCSPGRP, (void *)&Pstatus(P)->pr_pgid); (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_DFL, NULL); } if (Pstate(P) != PS_RUN && Psetrun(P, sig, flags) == -1) { error = errno; goto out; } /* * If the process is stopped on job control, resume its process group * by sending it a SIGCONT if we are in the same session. Otherwise * we have no choice but to wait for someone else to foreground it. */ if (psp->pr_why == PR_JOBCONTROL) { if (mdb.m_flags & MDB_FL_JOBCTL) (void) kill(-Pstatus(P)->pr_pgid, SIGCONT); else if (mdb.m_term != NULL) warn("process is still suspended by job control ...\n"); } /* * Wait for the process to stop. As described above, we loop around if * we are interrupted (EINTR). If we lose control, attempt to re-open * the process, or call pt_exec() if that fails to handle a re-exec. * If the process dies (ENOENT) or Pwait() fails, break out of the loop. */ while (Pwait(P, 0) == -1) { if (errno != EINTR) { if (Pstate(P) == PS_LOST) { if (Preopen(P) == 0) continue; /* Pwait() again */ else pt_exec(t, 0, NULL); } else if (errno != ENOENT) warn("failed to wait for event"); break; } } /* * If we changed the foreground process group, restore the old pgid * while ignoring SIGTTOU so we are not accidentally suspended. */ if (old_pgid != -1) { (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_IGN, NULL); (void) IOP_CTL(mdb.m_term, TIOCSPGRP, &pgid); (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_DFL, NULL); } /* * If we're now stopped on exit from a successful exec, release any * vfork parents and clean out their address space before returning * to tgt_continue() and perturbing the list of armed event specs. * If we're stopped for any other reason, just update the mappings. */ switch (Pstate(P)) { case PS_STOP: if (psp->pr_why == PR_SYSEXIT && psp->pr_errno == 0 && psp->pr_what == SYS_execve) pt_release_parents(t); else Pupdate_maps(P); break; case PS_UNDEAD: case PS_LOST: pt_release_parents(t); break; } out: (void) mdb_signal_sethandler(SIGINT, intf, intd); (void) mdb_signal_sethandler(SIGQUIT, quitf, quitd); (void) mdb_signal_sethandler(SIGTSTP, tstpf, tstpd); (void) pt_status(t, tsp); return (error ? set_errno(error) : 0); } static int pt_step(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { return (pt_setrun(t, tsp, PRSTEP)); } static int pt_continue(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { return (pt_setrun(t, tsp, 0)); } static int pt_signal(mdb_tgt_t *t, int sig) { pt_data_t *pt = t->t_data; if (sig > 0 && sig <= pt->p_maxsig) { pt->p_signal = sig; /* pending until next pt_setrun */ return (0); } return (set_errno(EMDB_BADSIGNUM)); } static int pt_sysenter_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { struct ps_prochandle *P = t->t_pshandle; if (P != NULL && Pstate(P) < PS_LOST) { sep->se_data = args; /* data is raw system call number */ return (Psysentry(P, (intptr_t)args, TRUE) < 0 ? -1 : 0); } return (set_errno(EMDB_NOPROC)); } static void pt_sysenter_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { (void) Psysentry(t->t_pshandle, (intptr_t)sep->se_data, FALSE); } /*ARGSUSED*/ static char * pt_sysenter_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { char name[32]; int sysnum; if (vep != NULL) sysnum = (intptr_t)vep->ve_args; else sysnum = (intptr_t)sep->se_data; (void) proc_sysname(sysnum, name, sizeof (name)); (void) mdb_iob_snprintf(buf, nbytes, "stop on entry to %s", name); return (buf); } /*ARGSUSED*/ static int pt_sysenter_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; int sysnum = (intptr_t)sep->se_data; return (psp->pr_why == PR_SYSENTRY && psp->pr_what == sysnum); } static const mdb_se_ops_t proc_sysenter_ops = { .se_ctor = pt_sysenter_ctor, .se_dtor = pt_sysenter_dtor, .se_info = pt_sysenter_info, .se_secmp = no_se_secmp, .se_vecmp = no_se_vecmp, .se_arm = no_se_arm, .se_disarm = no_se_disarm, .se_cont = no_se_cont, .se_match = pt_sysenter_match, }; static int pt_sysexit_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { struct ps_prochandle *P = t->t_pshandle; if (P != NULL && Pstate(P) < PS_LOST) { sep->se_data = args; /* data is raw system call number */ return (Psysexit(P, (intptr_t)args, TRUE) < 0 ? -1 : 0); } return (set_errno(EMDB_NOPROC)); } static void pt_sysexit_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { (void) Psysexit(t->t_pshandle, (intptr_t)sep->se_data, FALSE); } /*ARGSUSED*/ static char * pt_sysexit_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { char name[32]; int sysnum; if (vep != NULL) sysnum = (intptr_t)vep->ve_args; else sysnum = (intptr_t)sep->se_data; (void) proc_sysname(sysnum, name, sizeof (name)); (void) mdb_iob_snprintf(buf, nbytes, "stop on exit from %s", name); return (buf); } /*ARGSUSED*/ static int pt_sysexit_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; int sysnum = (intptr_t)sep->se_data; return (psp->pr_why == PR_SYSEXIT && psp->pr_what == sysnum); } static const mdb_se_ops_t proc_sysexit_ops = { .se_ctor = pt_sysexit_ctor, .se_dtor = pt_sysexit_dtor, .se_info = pt_sysexit_info, .se_secmp = no_se_secmp, .se_vecmp = no_se_vecmp, .se_arm = no_se_arm, .se_disarm = no_se_disarm, .se_cont = no_se_cont, .se_match = pt_sysexit_match, }; static int pt_signal_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { struct ps_prochandle *P = t->t_pshandle; if (P != NULL && Pstate(P) < PS_LOST) { sep->se_data = args; /* data is raw signal number */ return (Psignal(P, (intptr_t)args, TRUE) < 0 ? -1 : 0); } return (set_errno(EMDB_NOPROC)); } static void pt_signal_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { (void) Psignal(t->t_pshandle, (intptr_t)sep->se_data, FALSE); } /*ARGSUSED*/ static char * pt_signal_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { char name[SIG2STR_MAX]; int signum; if (vep != NULL) signum = (intptr_t)vep->ve_args; else signum = (intptr_t)sep->se_data; (void) proc_signame(signum, name, sizeof (name)); (void) mdb_iob_snprintf(buf, nbytes, "stop on %s", name); return (buf); } /*ARGSUSED*/ static int pt_signal_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; int signum = (intptr_t)sep->se_data; return (psp->pr_why == PR_SIGNALLED && psp->pr_what == signum); } static const mdb_se_ops_t proc_signal_ops = { .se_ctor = pt_signal_ctor, .se_dtor = pt_signal_dtor, .se_info = pt_signal_info, .se_secmp = no_se_secmp, .se_vecmp = no_se_vecmp, .se_arm = no_se_arm, .se_disarm = no_se_disarm, .se_cont = no_se_cont, .se_match = pt_signal_match, }; static int pt_fault_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { struct ps_prochandle *P = t->t_pshandle; if (P != NULL && Pstate(P) < PS_LOST) { sep->se_data = args; /* data is raw fault number */ return (Pfault(P, (intptr_t)args, TRUE) < 0 ? -1 : 0); } return (set_errno(EMDB_NOPROC)); } static void pt_fault_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { int fault = (intptr_t)sep->se_data; if (fault != FLTBPT && fault != FLTTRACE && fault != FLTWATCH) (void) Pfault(t->t_pshandle, fault, FALSE); } /*ARGSUSED*/ static char * pt_fault_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { char name[32]; int fltnum; if (vep != NULL) fltnum = (intptr_t)vep->ve_args; else fltnum = (intptr_t)sep->se_data; (void) proc_fltname(fltnum, name, sizeof (name)); (void) mdb_iob_snprintf(buf, nbytes, "stop on %s", name); return (buf); } /*ARGSUSED*/ static int pt_fault_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; int fltnum = (intptr_t)sep->se_data; return (psp->pr_why == PR_FAULTED && psp->pr_what == fltnum); } static const mdb_se_ops_t proc_fault_ops = { .se_ctor = pt_fault_ctor, .se_dtor = pt_fault_dtor, .se_info = pt_fault_info, .se_secmp = no_se_secmp, .se_vecmp = no_se_vecmp, .se_arm = no_se_arm, .se_disarm = no_se_disarm, .se_cont = no_se_cont, .se_match = pt_fault_match, }; /* * Callback for pt_ignore() dcmd above: for each VID, determine if it * corresponds to a vespec that traces the specified signal, and delete it. */ /*ARGSUSED*/ static int pt_ignore_sig(mdb_tgt_t *t, void *sig, int vid, void *data) { mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, vid); if (vep->ve_se->se_ops == &proc_signal_ops && vep->ve_args == sig) (void) mdb_tgt_vespec_delete(t, vid); return (0); } static int pt_brkpt_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { pt_data_t *pt = t->t_data; pt_bparg_t *pta = args; pt_brkpt_t *ptb; GElf_Sym s; if (t->t_pshandle == NULL || Pstate(t->t_pshandle) >= PS_LOST) return (set_errno(EMDB_NOPROC)); if (pta->pta_symbol != NULL) { if (!pt->p_rtld_finished && strchr(pta->pta_symbol, '`') == NULL) return (set_errno(EMDB_NOSYM)); if (mdb_tgt_lookup_by_scope(t, pta->pta_symbol, &s, NULL) == -1) { if (errno != EMDB_NOOBJ && !(errno == EMDB_NOSYM && (!(mdb.m_flags & MDB_FL_BPTNOSYMSTOP) || !pt->p_rtld_finished))) { warn("breakpoint %s activation failed", pta->pta_symbol); } return (-1); /* errno is set for us */ } pta->pta_addr = (uintptr_t)s.st_value; } #ifdef __sparc if (pta->pta_addr & 3) return (set_errno(EMDB_BPALIGN)); #endif if (Paddr_to_map(t->t_pshandle, pta->pta_addr) == NULL) return (set_errno(EMDB_NOMAP)); ptb = mdb_alloc(sizeof (pt_brkpt_t), UM_SLEEP); ptb->ptb_addr = pta->pta_addr; ptb->ptb_instr = 0; sep->se_data = ptb; return (0); } /*ARGSUSED*/ static void pt_brkpt_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { mdb_free(sep->se_data, sizeof (pt_brkpt_t)); } /*ARGSUSED*/ static char * pt_brkpt_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { uintptr_t addr = 0; if (vep != NULL) { pt_bparg_t *pta = vep->ve_args; if (pta->pta_symbol != NULL) { (void) mdb_iob_snprintf(buf, nbytes, "stop at %s", pta->pta_symbol); } else { (void) mdb_iob_snprintf(buf, nbytes, "stop at %a", pta->pta_addr); addr = pta->pta_addr; } } else { addr = ((pt_brkpt_t *)sep->se_data)->ptb_addr; (void) mdb_iob_snprintf(buf, nbytes, "stop at %a", addr); } sp->spec_base = addr; sp->spec_size = sizeof (instr_t); return (buf); } static int pt_brkpt_secmp(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { pt_brkpt_t *ptb = sep->se_data; pt_bparg_t *pta = args; GElf_Sym sym; if (pta->pta_symbol != NULL) { return (mdb_tgt_lookup_by_scope(t, pta->pta_symbol, &sym, NULL) == 0 && sym.st_value == ptb->ptb_addr); } return (pta->pta_addr == ptb->ptb_addr); } /*ARGSUSED*/ static int pt_brkpt_vecmp(mdb_tgt_t *t, mdb_vespec_t *vep, void *args) { pt_bparg_t *pta1 = vep->ve_args; pt_bparg_t *pta2 = args; if (pta1->pta_symbol != NULL && pta2->pta_symbol != NULL) return (strcmp(pta1->pta_symbol, pta2->pta_symbol) == 0); if (pta1->pta_symbol == NULL && pta2->pta_symbol == NULL) return (pta1->pta_addr == pta2->pta_addr); return (0); /* fail if one is symbolic, other is an explicit address */ } static int pt_brkpt_arm(mdb_tgt_t *t, mdb_sespec_t *sep) { pt_brkpt_t *ptb = sep->se_data; return (Psetbkpt(t->t_pshandle, ptb->ptb_addr, &ptb->ptb_instr)); } /* * In order to disarm a breakpoint, we replace the trap instruction at ptb_addr * with the saved instruction. However, if we have stopped after a successful * exec(2), we do not want to restore ptb_instr because the address space has * now been replaced with the text of a different executable, and so restoring * the saved instruction would be incorrect. The exec itself has effectively * removed all breakpoint trap instructions for us, so we can just return. */ static int pt_brkpt_disarm(mdb_tgt_t *t, mdb_sespec_t *sep) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; pt_brkpt_t *ptb = sep->se_data; if (psp->pr_why == PR_SYSEXIT && psp->pr_errno == 0 && psp->pr_what == SYS_execve) return (0); /* do not restore saved instruction */ return (Pdelbkpt(t->t_pshandle, ptb->ptb_addr, ptb->ptb_instr)); } /* * Determine whether the specified sespec is an armed watchpoint that overlaps * with the given breakpoint and has the given flags set. We use this to find * conflicts with breakpoints, below. */ static int pt_wp_overlap(mdb_sespec_t *sep, pt_brkpt_t *ptb, int flags) { const prwatch_t *wp = sep->se_data; return (sep->se_state == MDB_TGT_SPEC_ARMED && sep->se_ops == &proc_wapt_ops && (wp->pr_wflags & flags) && ptb->ptb_addr - wp->pr_vaddr < wp->pr_size); } /* * We step over breakpoints using Pxecbkpt() in libproc. If a conflicting * watchpoint is present, we must temporarily remove it before stepping over * the breakpoint so we do not immediately re-trigger the watchpoint. We know * the watchpoint has already triggered on our trap instruction as part of * fetching it. Before we return, we must re-install any disabled watchpoints. */ static int pt_brkpt_cont(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { pt_brkpt_t *ptb = sep->se_data; int status = -1; int error; const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; /* * If the PC no longer matches our original address, then the user has * changed it while we have been stopped. In this case, it no longer * makes any sense to continue over this breakpoint. We return as if we * continued normally. */ if ((uintptr_t)psp->pr_info.si_addr != psp->pr_reg[R_PC]) return (pt_status(t, tsp)); for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (pt_wp_overlap(sep, ptb, WA_EXEC)) (void) Pdelwapt(t->t_pshandle, sep->se_data); } if (Pxecbkpt(t->t_pshandle, ptb->ptb_instr) == 0 && Pdelbkpt(t->t_pshandle, ptb->ptb_addr, ptb->ptb_instr) == 0) status = pt_status(t, tsp); error = errno; /* save errno from Pxecbkpt, Pdelbkpt, or pt_status */ for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (pt_wp_overlap(sep, ptb, WA_EXEC) && Psetwapt(t->t_pshandle, sep->se_data) == -1) { sep->se_state = MDB_TGT_SPEC_ERROR; sep->se_errno = errno; } } (void) set_errno(error); return (status); } /*ARGSUSED*/ static int pt_brkpt_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; pt_brkpt_t *ptb = sep->se_data; return (psp->pr_why == PR_FAULTED && psp->pr_what == FLTBPT && psp->pr_reg[R_PC] == ptb->ptb_addr); } static const mdb_se_ops_t proc_brkpt_ops = { .se_ctor = pt_brkpt_ctor, .se_dtor = pt_brkpt_dtor, .se_info = pt_brkpt_info, .se_secmp = pt_brkpt_secmp, .se_vecmp = pt_brkpt_vecmp, .se_arm = pt_brkpt_arm, .se_disarm = pt_brkpt_disarm, .se_cont = pt_brkpt_cont, .se_match = pt_brkpt_match, }; static int pt_wapt_ctor(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { if (t->t_pshandle == NULL || Pstate(t->t_pshandle) >= PS_LOST) return (set_errno(EMDB_NOPROC)); sep->se_data = mdb_alloc(sizeof (prwatch_t), UM_SLEEP); bcopy(args, sep->se_data, sizeof (prwatch_t)); return (0); } /*ARGSUSED*/ static void pt_wapt_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { mdb_free(sep->se_data, sizeof (prwatch_t)); } /*ARGSUSED*/ static char * pt_wapt_info(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_vespec_t *vep, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { prwatch_t *wp = vep != NULL ? vep->ve_args : sep->se_data; char desc[24]; ASSERT(wp->pr_wflags != 0); desc[0] = '\0'; switch (wp->pr_wflags) { case WA_READ: (void) strcat(desc, "/read"); break; case WA_WRITE: (void) strcat(desc, "/write"); break; case WA_EXEC: (void) strcat(desc, "/exec"); break; default: if (wp->pr_wflags & WA_READ) (void) strcat(desc, "/r"); if (wp->pr_wflags & WA_WRITE) (void) strcat(desc, "/w"); if (wp->pr_wflags & WA_EXEC) (void) strcat(desc, "/x"); } (void) mdb_iob_snprintf(buf, nbytes, "stop on %s of [%la, %la)", desc + 1, wp->pr_vaddr, wp->pr_vaddr + wp->pr_size); sp->spec_base = wp->pr_vaddr; sp->spec_size = wp->pr_size; return (buf); } /*ARGSUSED*/ static int pt_wapt_secmp(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { prwatch_t *wp1 = sep->se_data; prwatch_t *wp2 = args; return (wp1->pr_vaddr == wp2->pr_vaddr && wp1->pr_size == wp2->pr_size && wp1->pr_wflags == wp2->pr_wflags); } /*ARGSUSED*/ static int pt_wapt_vecmp(mdb_tgt_t *t, mdb_vespec_t *vep, void *args) { prwatch_t *wp1 = vep->ve_args; prwatch_t *wp2 = args; return (wp1->pr_vaddr == wp2->pr_vaddr && wp1->pr_size == wp2->pr_size && wp1->pr_wflags == wp2->pr_wflags); } static int pt_wapt_arm(mdb_tgt_t *t, mdb_sespec_t *sep) { return (Psetwapt(t->t_pshandle, sep->se_data)); } static int pt_wapt_disarm(mdb_tgt_t *t, mdb_sespec_t *sep) { return (Pdelwapt(t->t_pshandle, sep->se_data)); } /* * Determine whether the specified sespec is an armed breakpoint at the * given %pc. We use this to find conflicts with watchpoints below. */ static int pt_bp_overlap(mdb_sespec_t *sep, uintptr_t pc) { pt_brkpt_t *ptb = sep->se_data; return (sep->se_state == MDB_TGT_SPEC_ARMED && sep->se_ops == &proc_brkpt_ops && ptb->ptb_addr == pc); } /* * We step over watchpoints using Pxecwapt() in libproc. If a conflicting * breakpoint is present, we must temporarily disarm it before stepping * over the watchpoint so we do not immediately re-trigger the breakpoint. * This is similar to the case handled in pt_brkpt_cont(), above. */ static int pt_wapt_cont(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; mdb_sespec_t *bep = NULL; int status = -1; int error; /* * If the PC no longer matches our original address, then the user has * changed it while we have been stopped. In this case, it no longer * makes any sense to continue over this instruction. We return as if * we continued normally. */ if ((uintptr_t)psp->pr_info.si_pc != psp->pr_reg[R_PC]) return (pt_status(t, tsp)); if (psp->pr_info.si_code != TRAP_XWATCH) { for (bep = mdb_list_next(&t->t_active); bep != NULL; bep = mdb_list_next(bep)) { if (pt_bp_overlap(bep, psp->pr_reg[R_PC])) { (void) bep->se_ops->se_disarm(t, bep); bep->se_state = MDB_TGT_SPEC_ACTIVE; break; } } } if (Pxecwapt(t->t_pshandle, sep->se_data) == 0) status = pt_status(t, tsp); error = errno; /* save errno from Pxecwapt or pt_status */ if (bep != NULL) mdb_tgt_sespec_arm_one(t, bep); (void) set_errno(error); return (status); } /*ARGSUSED*/ static int pt_wapt_match(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; prwatch_t *wp = sep->se_data; return (psp->pr_why == PR_FAULTED && psp->pr_what == FLTWATCH && (uintptr_t)psp->pr_info.si_addr - wp->pr_vaddr < wp->pr_size); } static const mdb_se_ops_t proc_wapt_ops = { .se_ctor = pt_wapt_ctor, .se_dtor = pt_wapt_dtor, .se_info = pt_wapt_info, .se_secmp = pt_wapt_secmp, .se_vecmp = pt_wapt_vecmp, .se_arm = pt_wapt_arm, .se_disarm = pt_wapt_disarm, .se_cont = pt_wapt_cont, .se_match = pt_wapt_match, }; static void pt_bparg_dtor(mdb_vespec_t *vep) { pt_bparg_t *pta = vep->ve_args; if (pta->pta_symbol != NULL) strfree(pta->pta_symbol); mdb_free(pta, sizeof (pt_bparg_t)); } static int pt_add_vbrkpt(mdb_tgt_t *t, uintptr_t addr, int spec_flags, mdb_tgt_se_f *func, void *data) { pt_bparg_t *pta = mdb_alloc(sizeof (pt_bparg_t), UM_SLEEP); pta->pta_symbol = NULL; pta->pta_addr = addr; return (mdb_tgt_vespec_insert(t, &proc_brkpt_ops, spec_flags, func, data, pta, pt_bparg_dtor)); } static int pt_add_sbrkpt(mdb_tgt_t *t, const char *sym, int spec_flags, mdb_tgt_se_f *func, void *data) { pt_bparg_t *pta; if (sym[0] == '`') { (void) set_errno(EMDB_NOOBJ); return (0); } if (sym[strlen(sym) - 1] == '`') { (void) set_errno(EMDB_NOSYM); return (0); } pta = mdb_alloc(sizeof (pt_bparg_t), UM_SLEEP); pta->pta_symbol = strdup(sym); pta->pta_addr = 0; return (mdb_tgt_vespec_insert(t, &proc_brkpt_ops, spec_flags, func, data, pta, pt_bparg_dtor)); } static int pt_wparg_overlap(const prwatch_t *wp1, const prwatch_t *wp2) { if (wp2->pr_vaddr + wp2->pr_size <= wp1->pr_vaddr) return (0); /* no range overlap */ if (wp1->pr_vaddr + wp1->pr_size <= wp2->pr_vaddr) return (0); /* no range overlap */ return (wp1->pr_vaddr != wp2->pr_vaddr || wp1->pr_size != wp2->pr_size || wp1->pr_wflags != wp2->pr_wflags); } static void pt_wparg_dtor(mdb_vespec_t *vep) { mdb_free(vep->ve_args, sizeof (prwatch_t)); } static int pt_add_vwapt(mdb_tgt_t *t, uintptr_t addr, size_t len, uint_t wflags, int spec_flags, mdb_tgt_se_f *func, void *data) { prwatch_t *wp = mdb_alloc(sizeof (prwatch_t), UM_SLEEP); mdb_sespec_t *sep; wp->pr_vaddr = addr; wp->pr_size = len; wp->pr_wflags = 0; if (wflags & MDB_TGT_WA_R) wp->pr_wflags |= WA_READ; if (wflags & MDB_TGT_WA_W) wp->pr_wflags |= WA_WRITE; if (wflags & MDB_TGT_WA_X) wp->pr_wflags |= WA_EXEC; for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (sep->se_ops == &proc_wapt_ops && mdb_list_next(&sep->se_velist) != NULL && pt_wparg_overlap(wp, sep->se_data)) goto dup; } for (sep = mdb_list_next(&t->t_idle); sep; sep = mdb_list_next(sep)) { if (sep->se_ops == &proc_wapt_ops && pt_wparg_overlap(wp, ((mdb_vespec_t *)mdb_list_next(&sep->se_velist))->ve_args)) goto dup; } return (mdb_tgt_vespec_insert(t, &proc_wapt_ops, spec_flags, func, data, wp, pt_wparg_dtor)); dup: mdb_free(wp, sizeof (prwatch_t)); (void) set_errno(EMDB_WPDUP); return (0); } static int pt_add_sysenter(mdb_tgt_t *t, int sysnum, int spec_flags, mdb_tgt_se_f *func, void *data) { if (sysnum <= 0 || sysnum > PRMAXSYS) { (void) set_errno(EMDB_BADSYSNUM); return (0); } return (mdb_tgt_vespec_insert(t, &proc_sysenter_ops, spec_flags, func, data, (void *)(uintptr_t)sysnum, no_ve_dtor)); } static int pt_add_sysexit(mdb_tgt_t *t, int sysnum, int spec_flags, mdb_tgt_se_f *func, void *data) { if (sysnum <= 0 || sysnum > PRMAXSYS) { (void) set_errno(EMDB_BADSYSNUM); return (0); } return (mdb_tgt_vespec_insert(t, &proc_sysexit_ops, spec_flags, func, data, (void *)(uintptr_t)sysnum, no_ve_dtor)); } static int pt_add_signal(mdb_tgt_t *t, int signum, int spec_flags, mdb_tgt_se_f *func, void *data) { pt_data_t *pt = t->t_data; if (signum <= 0 || signum > pt->p_maxsig) { (void) set_errno(EMDB_BADSIGNUM); return (0); } return (mdb_tgt_vespec_insert(t, &proc_signal_ops, spec_flags, func, data, (void *)(uintptr_t)signum, no_ve_dtor)); } static int pt_add_fault(mdb_tgt_t *t, int fltnum, int spec_flags, mdb_tgt_se_f *func, void *data) { if (fltnum <= 0 || fltnum > PRMAXFAULT) { (void) set_errno(EMDB_BADFLTNUM); return (0); } return (mdb_tgt_vespec_insert(t, &proc_fault_ops, spec_flags, func, data, (void *)(uintptr_t)fltnum, no_ve_dtor)); } static int pt_getareg(mdb_tgt_t *t, mdb_tgt_tid_t tid, const char *rname, mdb_tgt_reg_t *rp) { pt_data_t *pt = t->t_data; prgregset_t grs; mdb_var_t *v; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((v = mdb_nv_lookup(&pt->p_regs, rname)) != NULL) { uintmax_t rd_nval = mdb_nv_get_value(v); ushort_t rd_num = MDB_TGT_R_NUM(rd_nval); ushort_t rd_flags = MDB_TGT_R_FLAGS(rd_nval); if (!MDB_TGT_R_IS_FP(rd_flags)) { mdb_tgt_reg_t r = 0; #if defined(__sparc) && defined(_ILP32) /* * If we are debugging on 32-bit SPARC, the globals and * outs can have 32 upper bits hiding in the xregs. */ /* gcc doesn't like >= R_G0 because R_G0 == 0 */ int is_g = (rd_num == R_G0 || rd_num >= R_G1 && rd_num <= R_G7); int is_o = (rd_num >= R_O0 && rd_num <= R_O7); prxregset_t xrs; if (is_g && PTL_GETXREGS(t, tid, &xrs) == 0 && xrs.pr_type == XR_TYPE_V8P) { r |= (uint64_t)xrs.pr_un.pr_v8p.pr_xg[ rd_num - R_G0 + XR_G0] << 32; } if (is_o && PTL_GETXREGS(t, tid, &xrs) == 0 && xrs.pr_type == XR_TYPE_V8P) { r |= (uint64_t)xrs.pr_un.pr_v8p.pr_xo[ rd_num - R_O0 + XR_O0] << 32; } #endif /* __sparc && _ILP32 */ /* * Avoid sign-extension by casting: recall that procfs * defines prgreg_t as a long or int and our native * register handling uses uint64_t's. */ if (PTL_GETREGS(t, tid, grs) == 0) { *rp = r | (ulong_t)grs[rd_num]; if (rd_flags & MDB_TGT_R_32) *rp &= 0xffffffffULL; else if (rd_flags & MDB_TGT_R_16) *rp &= 0xffffULL; else if (rd_flags & MDB_TGT_R_8H) *rp = (*rp & 0xff00ULL) >> 8; else if (rd_flags & MDB_TGT_R_8L) *rp &= 0xffULL; return (0); } return (-1); } else return (pt_getfpreg(t, tid, rd_num, rd_flags, rp)); } return (set_errno(EMDB_BADREG)); } static int pt_putareg(mdb_tgt_t *t, mdb_tgt_tid_t tid, const char *rname, mdb_tgt_reg_t r) { pt_data_t *pt = t->t_data; prgregset_t grs; mdb_var_t *v; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((v = mdb_nv_lookup(&pt->p_regs, rname)) != NULL) { uintmax_t rd_nval = mdb_nv_get_value(v); ushort_t rd_num = MDB_TGT_R_NUM(rd_nval); ushort_t rd_flags = MDB_TGT_R_FLAGS(rd_nval); if (!MDB_TGT_R_IS_FP(rd_flags)) { if (rd_flags & MDB_TGT_R_32) r &= 0xffffffffULL; else if (rd_flags & MDB_TGT_R_16) r &= 0xffffULL; else if (rd_flags & MDB_TGT_R_8H) r = (r & 0xffULL) << 8; else if (rd_flags & MDB_TGT_R_8L) r &= 0xffULL; if (PTL_GETREGS(t, tid, grs) == 0) { grs[rd_num] = (prgreg_t)r; return (PTL_SETREGS(t, tid, grs)); } return (-1); } else return (pt_putfpreg(t, tid, rd_num, rd_flags, r)); } return (set_errno(EMDB_BADREG)); } static int pt_stack_call(pt_stkarg_t *psp, const prgregset_t grs, uint_t argc, long *argv) { psp->pstk_gotpc |= (grs[R_PC] != 0); if (!psp->pstk_gotpc) return (0); /* skip initial zeroed frames */ return (psp->pstk_func(psp->pstk_private, grs[R_PC], argc, argv, (const struct mdb_tgt_gregset *)grs)); } static int pt_stack_iter(mdb_tgt_t *t, const mdb_tgt_gregset_t *gsp, mdb_tgt_stack_f *func, void *arg) { if (t->t_pshandle != NULL) { pt_stkarg_t pstk; pstk.pstk_func = func; pstk.pstk_private = arg; pstk.pstk_gotpc = FALSE; (void) Pstack_iter(t->t_pshandle, gsp->gregs, (proc_stack_f *)pt_stack_call, &pstk); return (0); } return (set_errno(EMDB_NOPROC)); } static int pt_auxv(mdb_tgt_t *t, const auxv_t **auxvp) { if (t->t_pshandle != NULL) { *auxvp = Pgetauxvec(t->t_pshandle); return (0); } return (set_errno(EMDB_NOPROC)); } static const mdb_tgt_ops_t proc_ops = { .t_setflags = pt_setflags, .t_setcontext = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_activate = pt_activate, .t_deactivate = pt_deactivate, .t_periodic = pt_periodic, .t_destroy = pt_destroy, .t_name = pt_name, .t_isa = (const char *(*)())mdb_conf_isa, .t_platform = pt_platform, .t_uname = pt_uname, .t_dmodel = pt_dmodel, .t_aread = (ssize_t (*)())mdb_tgt_notsup, .t_awrite = (ssize_t (*)())mdb_tgt_notsup, .t_vread = pt_vread, .t_vwrite = pt_vwrite, .t_pread = (ssize_t (*)())mdb_tgt_notsup, .t_pwrite = (ssize_t (*)())mdb_tgt_notsup, .t_fread = pt_fread, .t_fwrite = pt_fwrite, .t_ioread = (ssize_t (*)())mdb_tgt_notsup, .t_iowrite = (ssize_t (*)())mdb_tgt_notsup, .t_vtop = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_lookup_by_name = pt_lookup_by_name, .t_lookup_by_addr = pt_lookup_by_addr, .t_symbol_iter = pt_symbol_iter, .t_mapping_iter = pt_mapping_iter, .t_object_iter = pt_object_iter, .t_addr_to_map = pt_addr_to_map, .t_name_to_map = pt_name_to_map, .t_addr_to_ctf = pt_addr_to_ctf, .t_name_to_ctf = pt_name_to_ctf, .t_status = pt_status, .t_run = pt_run, .t_step = pt_step, .t_step_out = pt_step_out, .t_next = pt_next, .t_cont = pt_continue, .t_signal = pt_signal, .t_add_vbrkpt = pt_add_vbrkpt, .t_add_sbrkpt = pt_add_sbrkpt, .t_add_pwapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_vwapt = pt_add_vwapt, .t_add_iowapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sysenter = pt_add_sysenter, .t_add_sysexit = pt_add_sysexit, .t_add_signal = pt_add_signal, .t_add_fault = pt_add_fault, .t_getareg = pt_getareg, .t_putareg = pt_putareg, .t_stack_iter = pt_stack_iter, .t_auxv = pt_auxv, .t_thread_name = pt_thread_name, }; /* * Utility function for converting libproc errno values to mdb error values * for the ptl calls below. Currently, we only need to convert ENOENT to * EMDB_NOTHREAD to produce a more useful error message for the user. */ static int ptl_err(int error) { if (error != 0 && errno == ENOENT) return (set_errno(EMDB_NOTHREAD)); return (error); } /*ARGSUSED*/ static mdb_tgt_tid_t pt_lwp_tid(mdb_tgt_t *t, void *tap) { if (t->t_pshandle != NULL) return (Pstatus(t->t_pshandle)->pr_lwp.pr_lwpid); return (set_errno(EMDB_NOPROC)); } static int pt_lwp_add(mdb_addrvec_t *ap, const lwpstatus_t *psp) { mdb_addrvec_unshift(ap, psp->pr_lwpid); return (0); } /*ARGSUSED*/ static int pt_lwp_iter(mdb_tgt_t *t, void *tap, mdb_addrvec_t *ap) { if (t->t_pshandle != NULL) return (Plwp_iter(t->t_pshandle, (proc_lwp_f *)pt_lwp_add, ap)); return (set_errno(EMDB_NOPROC)); } /*ARGSUSED*/ static int pt_lwp_getregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prgregset_t gregs) { if (t->t_pshandle != NULL) { return (ptl_err(Plwp_getregs(t->t_pshandle, (lwpid_t)tid, gregs))); } return (set_errno(EMDB_NOPROC)); } /*ARGSUSED*/ static int pt_lwp_setregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prgregset_t gregs) { if (t->t_pshandle != NULL) { return (ptl_err(Plwp_setregs(t->t_pshandle, (lwpid_t)tid, gregs))); } return (set_errno(EMDB_NOPROC)); } /*ARGSUSED*/ static int pt_lwp_getxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prxregset_t **xregs, size_t *sizep) { if (t->t_pshandle != NULL) { return (ptl_err(Plwp_getxregs(t->t_pshandle, (lwpid_t)tid, xregs, sizep))); } return (set_errno(EMDB_NOPROC)); } static void pt_lwp_freexregs(mdb_tgt_t *t, void *tap, prxregset_t *xregs, size_t size) { if (t->t_pshandle != NULL) { Plwp_freexregs(t->t_pshandle, xregs, size); } } static int pt_lwp_setxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, const prxregset_t *xregs, size_t len) { if (t->t_pshandle != NULL) { return (ptl_err(Plwp_setxregs(t->t_pshandle, (lwpid_t)tid, xregs, len))); } return (set_errno(EMDB_NOPROC)); } /*ARGSUSED*/ static int pt_lwp_getfpregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prfpregset_t *fpregs) { if (t->t_pshandle != NULL) { return (ptl_err(Plwp_getfpregs(t->t_pshandle, (lwpid_t)tid, fpregs))); } return (set_errno(EMDB_NOPROC)); } /*ARGSUSED*/ static int pt_lwp_setfpregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, const prfpregset_t *fpregs) { if (t->t_pshandle != NULL) { return (ptl_err(Plwp_setfpregs(t->t_pshandle, (lwpid_t)tid, fpregs))); } return (set_errno(EMDB_NOPROC)); } static const pt_ptl_ops_t proc_lwp_ops = { .ptl_ctor = (int (*)())(uintptr_t)mdb_tgt_nop, .ptl_dtor = (void (*)())(uintptr_t)mdb_tgt_nop, .ptl_tid = pt_lwp_tid, .ptl_iter = pt_lwp_iter, .ptl_getregs = pt_lwp_getregs, .ptl_setregs = pt_lwp_setregs, .ptl_getxregs = pt_lwp_getxregs, .ptl_freexregs = pt_lwp_freexregs, .ptl_setxregs = pt_lwp_setxregs, .ptl_getfpregs = pt_lwp_getfpregs, .ptl_setfpregs = pt_lwp_setfpregs }; static int pt_tdb_ctor(mdb_tgt_t *t) { pt_data_t *pt = t->t_data; td_thragent_t *tap; td_err_e err; if ((err = pt->p_tdb_ops->td_ta_new(t->t_pshandle, &tap)) != TD_OK) return (set_errno(tdb_to_errno(err))); pt->p_ptl_hdl = tap; return (0); } static void pt_tdb_dtor(mdb_tgt_t *t, void *tap) { pt_data_t *pt = t->t_data; ASSERT(tap == pt->p_ptl_hdl); (void) pt->p_tdb_ops->td_ta_delete(tap); pt->p_ptl_hdl = NULL; } static mdb_tgt_tid_t pt_tdb_tid(mdb_tgt_t *t, void *tap) { pt_data_t *pt = t->t_data; td_thrhandle_t th; td_thrinfo_t ti; td_err_e err; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((err = pt->p_tdb_ops->td_ta_map_lwp2thr(tap, Pstatus(t->t_pshandle)->pr_lwp.pr_lwpid, &th)) != TD_OK) return (set_errno(tdb_to_errno(err))); if ((err = pt->p_tdb_ops->td_thr_get_info(&th, &ti)) != TD_OK) return (set_errno(tdb_to_errno(err))); return (ti.ti_tid); } static int pt_tdb_add(const td_thrhandle_t *thp, pt_addarg_t *pap) { td_thrinfo_t ti; if (pap->pa_pt->p_tdb_ops->td_thr_get_info(thp, &ti) == TD_OK && ti.ti_state != TD_THR_ZOMBIE) mdb_addrvec_unshift(pap->pa_ap, ti.ti_tid); return (0); } static int pt_tdb_iter(mdb_tgt_t *t, void *tap, mdb_addrvec_t *ap) { pt_data_t *pt = t->t_data; pt_addarg_t arg; int err; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); arg.pa_pt = pt; arg.pa_ap = ap; if ((err = pt->p_tdb_ops->td_ta_thr_iter(tap, (td_thr_iter_f *) pt_tdb_add, &arg, TD_THR_ANY_STATE, TD_THR_LOWEST_PRIORITY, TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS)) != TD_OK) return (set_errno(tdb_to_errno(err))); return (0); } static int pt_tdb_getregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prgregset_t gregs) { pt_data_t *pt = t->t_data; td_thrhandle_t th; td_err_e err; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((err = pt->p_tdb_ops->td_ta_map_id2thr(tap, tid, &th)) != TD_OK) return (set_errno(tdb_to_errno(err))); err = pt->p_tdb_ops->td_thr_getgregs(&th, gregs); if (err != TD_OK && err != TD_PARTIALREG) return (set_errno(tdb_to_errno(err))); return (0); } static int pt_tdb_setregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prgregset_t gregs) { pt_data_t *pt = t->t_data; td_thrhandle_t th; td_err_e err; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((err = pt->p_tdb_ops->td_ta_map_id2thr(tap, tid, &th)) != TD_OK) return (set_errno(tdb_to_errno(err))); err = pt->p_tdb_ops->td_thr_setgregs(&th, gregs); if (err != TD_OK && err != TD_PARTIALREG) return (set_errno(tdb_to_errno(err))); return (0); } static int pt_tdb_getxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prxregset_t **xregs, size_t *sizep) { pt_data_t *pt = t->t_data; td_thrhandle_t th; td_err_e err; int xregsize; prxregset_t *pxr; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((err = pt->p_tdb_ops->td_ta_map_id2thr(tap, tid, &th)) != TD_OK) return (set_errno(tdb_to_errno(err))); if ((err = pt->p_tdb_ops->td_thr_getxregsize(&th, &xregsize)) != TD_OK) return (set_errno(tdb_to_errno(err))); if (xregsize == 0) { return (set_errno(ENODATA)); } pxr = mdb_alloc(xregsize, UM_SLEEP); err = pt->p_tdb_ops->td_thr_getxregs(&th, pxr); if (err != TD_OK && err != TD_PARTIALREG) { mdb_free(pxr, xregsize); return (set_errno(tdb_to_errno(err))); } *xregs = pxr; *sizep = xregsize; return (0); } static void pt_tdb_freexregs(mdb_tgt_t *t __unused, void *tap __unused, prxregset_t *pxr, size_t size) { mdb_free(pxr, size); } static int pt_tdb_setxregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, const prxregset_t *xregs, size_t len __unused) { pt_data_t *pt = t->t_data; td_thrhandle_t th; td_err_e err; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((err = pt->p_tdb_ops->td_ta_map_id2thr(tap, tid, &th)) != TD_OK) return (set_errno(tdb_to_errno(err))); err = pt->p_tdb_ops->td_thr_setxregs(&th, xregs); if (err != TD_OK && err != TD_PARTIALREG) return (set_errno(tdb_to_errno(err))); return (0); } static int pt_tdb_getfpregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, prfpregset_t *fpregs) { pt_data_t *pt = t->t_data; td_thrhandle_t th; td_err_e err; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((err = pt->p_tdb_ops->td_ta_map_id2thr(tap, tid, &th)) != TD_OK) return (set_errno(tdb_to_errno(err))); err = pt->p_tdb_ops->td_thr_getfpregs(&th, fpregs); if (err != TD_OK && err != TD_PARTIALREG) return (set_errno(tdb_to_errno(err))); return (0); } static int pt_tdb_setfpregs(mdb_tgt_t *t, void *tap, mdb_tgt_tid_t tid, const prfpregset_t *fpregs) { pt_data_t *pt = t->t_data; td_thrhandle_t th; td_err_e err; if (t->t_pshandle == NULL) return (set_errno(EMDB_NOPROC)); if ((err = pt->p_tdb_ops->td_ta_map_id2thr(tap, tid, &th)) != TD_OK) return (set_errno(tdb_to_errno(err))); err = pt->p_tdb_ops->td_thr_setfpregs(&th, fpregs); if (err != TD_OK && err != TD_PARTIALREG) return (set_errno(tdb_to_errno(err))); return (0); } static const pt_ptl_ops_t proc_tdb_ops = { .ptl_ctor = pt_tdb_ctor, .ptl_dtor = pt_tdb_dtor, .ptl_tid = pt_tdb_tid, .ptl_iter = pt_tdb_iter, .ptl_getregs = pt_tdb_getregs, .ptl_setregs = pt_tdb_setregs, .ptl_getxregs = pt_tdb_getxregs, .ptl_freexregs = pt_tdb_freexregs, .ptl_setxregs = pt_tdb_setxregs, .ptl_getfpregs = pt_tdb_getfpregs, .ptl_setfpregs = pt_tdb_setfpregs }; static ssize_t pt_xd_auxv(mdb_tgt_t *t, void *buf, size_t nbytes) { struct ps_prochandle *P = t->t_pshandle; const auxv_t *auxp, *auxv = NULL; int auxn = 0; if (P != NULL && (auxv = Pgetauxvec(P)) != NULL && auxv->a_type != AT_NULL) { for (auxp = auxv, auxn = 1; auxp->a_type != 0; auxp++) auxn++; } if (buf == NULL && nbytes == 0) return (sizeof (auxv_t) * auxn); if (auxn == 0) return (set_errno(ENODATA)); nbytes = MIN(nbytes, sizeof (auxv_t) * auxn); bcopy(auxv, buf, nbytes); return (nbytes); } static ssize_t pt_xd_cred(mdb_tgt_t *t, void *buf, size_t nbytes) { prcred_t cr, *crp; size_t cbytes = 0; if (t->t_pshandle != NULL && Pcred(t->t_pshandle, &cr, 1) == 0) { cbytes = (cr.pr_ngroups <= 1) ? sizeof (prcred_t) : (sizeof (prcred_t) + (cr.pr_ngroups - 1) * sizeof (gid_t)); } if (buf == NULL && nbytes == 0) return (cbytes); if (cbytes == 0) return (set_errno(ENODATA)); crp = mdb_alloc(cbytes, UM_SLEEP); if (Pcred(t->t_pshandle, crp, cr.pr_ngroups) == -1) return (set_errno(ENODATA)); nbytes = MIN(nbytes, cbytes); bcopy(crp, buf, nbytes); mdb_free(crp, cbytes); return (nbytes); } static ssize_t pt_xd_ehdr(mdb_tgt_t *t, void *buf, size_t nbytes) { pt_data_t *pt = t->t_data; if (buf == NULL && nbytes == 0) return (sizeof (GElf_Ehdr)); if (pt->p_file == NULL) return (set_errno(ENODATA)); nbytes = MIN(nbytes, sizeof (GElf_Ehdr)); bcopy(&pt->p_file->gf_ehdr, buf, nbytes); return (nbytes); } static int pt_copy_lwp(lwpstatus_t **lspp, const lwpstatus_t *lsp) { bcopy(lsp, *lspp, sizeof (lwpstatus_t)); (*lspp)++; return (0); } static ssize_t pt_xd_lwpstatus(mdb_tgt_t *t, void *buf, size_t nbytes) { lwpstatus_t *lsp, *lbuf; const pstatus_t *psp; int nlwp = 0; if (t->t_pshandle != NULL && (psp = Pstatus(t->t_pshandle)) != NULL) nlwp = psp->pr_nlwp; if (buf == NULL && nbytes == 0) return (sizeof (lwpstatus_t) * nlwp); if (nlwp == 0) return (set_errno(ENODATA)); lsp = lbuf = mdb_alloc(sizeof (lwpstatus_t) * nlwp, UM_SLEEP); nbytes = MIN(nbytes, sizeof (lwpstatus_t) * nlwp); (void) Plwp_iter(t->t_pshandle, (proc_lwp_f *)pt_copy_lwp, &lsp); bcopy(lbuf, buf, nbytes); mdb_free(lbuf, sizeof (lwpstatus_t) * nlwp); return (nbytes); } static ssize_t pt_xd_pshandle(mdb_tgt_t *t, void *buf, size_t nbytes) { if (buf == NULL && nbytes == 0) return (sizeof (struct ps_prochandle *)); if (t->t_pshandle == NULL || nbytes != sizeof (struct ps_prochandle *)) return (set_errno(ENODATA)); bcopy(&t->t_pshandle, buf, nbytes); return (nbytes); } static ssize_t pt_xd_psinfo(mdb_tgt_t *t, void *buf, size_t nbytes) { const psinfo_t *psp; if (buf == NULL && nbytes == 0) return (sizeof (psinfo_t)); if (t->t_pshandle == NULL || (psp = Ppsinfo(t->t_pshandle)) == NULL) return (set_errno(ENODATA)); nbytes = MIN(nbytes, sizeof (psinfo_t)); bcopy(psp, buf, nbytes); return (nbytes); } static ssize_t pt_xd_pstatus(mdb_tgt_t *t, void *buf, size_t nbytes) { const pstatus_t *psp; if (buf == NULL && nbytes == 0) return (sizeof (pstatus_t)); if (t->t_pshandle == NULL || (psp = Pstatus(t->t_pshandle)) == NULL) return (set_errno(ENODATA)); nbytes = MIN(nbytes, sizeof (pstatus_t)); bcopy(psp, buf, nbytes); return (nbytes); } static ssize_t pt_xd_utsname(mdb_tgt_t *t, void *buf, size_t nbytes) { struct utsname uts; if (buf == NULL && nbytes == 0) return (sizeof (struct utsname)); if (t->t_pshandle == NULL || Puname(t->t_pshandle, &uts) != 0) return (set_errno(ENODATA)); nbytes = MIN(nbytes, sizeof (struct utsname)); bcopy(&uts, buf, nbytes); return (nbytes); } int mdb_proc_tgt_create(mdb_tgt_t *t, int argc, const char *argv[]) { pt_data_t *pt = mdb_zalloc(sizeof (pt_data_t), UM_SLEEP); const char *aout_path = argc > 0 ? argv[0] : PT_EXEC_PATH; const char *core_path = argc > 1 ? argv[1] : NULL; const mdb_tgt_regdesc_t *rdp; char execname[MAXPATHLEN]; struct stat64 st; int perr; int state = 0; struct rlimit rlim; int i; if (argc > 2) { mdb_free(pt, sizeof (pt_data_t)); return (set_errno(EINVAL)); } if (t->t_flags & MDB_TGT_F_RDWR) pt->p_oflags = O_RDWR; else pt->p_oflags = O_RDONLY; if (t->t_flags & MDB_TGT_F_FORCE) pt->p_gflags |= PGRAB_FORCE; if (t->t_flags & MDB_TGT_F_NOSTOP) pt->p_gflags |= PGRAB_NOSTOP; pt->p_ptl_ops = &proc_lwp_ops; pt->p_maxsig = sysconf(_SC_SIGRT_MAX); (void) mdb_nv_create(&pt->p_regs, UM_SLEEP); (void) mdb_nv_create(&pt->p_env, UM_SLEEP); t->t_ops = &proc_ops; t->t_data = pt; /* * If no core file name was specified, but the file ./core is present, * infer that we want to debug it. I find this behavior confusing, * so we only do this when precise adb(1) compatibility is required. */ if (core_path == NULL && (mdb.m_flags & MDB_FL_ADB) && access(PT_CORE_PATH, F_OK) == 0) core_path = PT_CORE_PATH; /* * For compatibility with adb(1), the special name "-" may be used * to suppress the loading of the executable or core file. */ if (aout_path != NULL && strcmp(aout_path, "-") == 0) aout_path = NULL; if (core_path != NULL && strcmp(core_path, "-") == 0) core_path = NULL; /* * If a core file or pid was specified, attempt to grab it now using * proc_arg_grab(); otherwise we'll create a fresh process later. */ if (core_path != NULL && (t->t_pshandle = proc_arg_xgrab(core_path, aout_path == PT_EXEC_PATH ? NULL : aout_path, PR_ARG_ANY, pt->p_gflags, &perr, NULL)) == NULL) { mdb_warn("cannot debug %s: %s\n", core_path, Pgrab_error(perr)); goto err; } if (aout_path != NULL && (pt->p_idlehandle = Pgrab_file(aout_path, &perr)) != NULL && t->t_pshandle == NULL) t->t_pshandle = pt->p_idlehandle; if (t->t_pshandle != NULL) state = Pstate(t->t_pshandle); /* * Make sure we'll have enough file descriptors to handle a target * has many many mappings. */ if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) { rlim.rlim_cur = rlim.rlim_max; (void) setrlimit(RLIMIT_NOFILE, &rlim); (void) enable_extended_FILE_stdio(-1, -1); } /* * If we don't have an executable path or the executable path is the * /proc//object/a.out path, but we now have a libproc handle (and * it didn't come from a core file), attempt to derive the executable * path using Pexecname(). We need to do this in the /proc case in * order to open the executable for writing because /proc/object/ * permission are masked with 0555. If Pexecname() fails us, fall back * to /proc//object/a.out. */ if (t->t_pshandle != NULL && core_path == NULL && (aout_path == NULL || (stat64(aout_path, &st) == 0 && strcmp(st.st_fstype, "proc") == 0))) { GElf_Sym s; aout_path = Pexecname(t->t_pshandle, execname, MAXPATHLEN); if (aout_path == NULL && state != PS_DEAD && state != PS_IDLE) { (void) mdb_iob_snprintf(execname, sizeof (execname), "/proc/%d/object/a.out", (int)Pstatus(t->t_pshandle)->pr_pid); aout_path = execname; } if (aout_path == NULL && Plookup_by_name(t->t_pshandle, "a.out", "_start", &s) != 0) mdb_warn("warning: failed to infer pathname to " "executable; symbol table will not be available\n"); mdb_dprintf(MDB_DBG_TGT, "a.out is %s\n", aout_path); } /* * Attempt to open the executable file. We only want this operation * to actually cause the constructor to abort if the executable file * name was given explicitly. If we defaulted to PT_EXEC_PATH or * derived the executable using Pexecname, then we want to continue * along with p_fio and p_file set to NULL. */ if (aout_path != NULL && (pt->p_aout_fio = mdb_fdio_create_path(NULL, aout_path, pt->p_oflags, 0)) == NULL && argc > 0) { mdb_warn("failed to open %s", aout_path); goto err; } /* * Now create an ELF file from the input file, if we have one. Again, * only abort the constructor if the name was given explicitly. */ if (pt->p_aout_fio != NULL && pt_open_aout(t, mdb_io_hold(pt->p_aout_fio)) == NULL && argc > 0) goto err; /* * If we've successfully opened an ELF file, select the appropriate * disassembler based on the ELF header. */ if (pt->p_file != NULL) (void) mdb_dis_select(pt_disasm(&pt->p_file->gf_ehdr)); else (void) mdb_dis_select(pt_disasm(NULL)); /* * Add each register described in the target ISA register description * list to our hash table of register descriptions and then add any * appropriate ISA-specific floating-point register descriptions. */ for (rdp = pt_regdesc; rdp->rd_name != NULL; rdp++) { (void) mdb_nv_insert(&pt->p_regs, rdp->rd_name, NULL, MDB_TGT_R_NVAL(rdp->rd_num, rdp->rd_flags), MDB_NV_RDONLY); } pt_addfpregs(t); /* * Certain important /proc structures may be of interest to mdb * modules and their dcmds. Export these using the xdata interface: */ (void) mdb_tgt_xdata_insert(t, "auxv", "procfs auxv_t array", pt_xd_auxv); (void) mdb_tgt_xdata_insert(t, "cred", "procfs prcred_t structure", pt_xd_cred); (void) mdb_tgt_xdata_insert(t, "ehdr", "executable file GElf_Ehdr structure", pt_xd_ehdr); (void) mdb_tgt_xdata_insert(t, "lwpstatus", "procfs lwpstatus_t array", pt_xd_lwpstatus); (void) mdb_tgt_xdata_insert(t, "pshandle", "libproc proc service API handle", pt_xd_pshandle); (void) mdb_tgt_xdata_insert(t, "psinfo", "procfs psinfo_t structure", pt_xd_psinfo); (void) mdb_tgt_xdata_insert(t, "pstatus", "procfs pstatus_t structure", pt_xd_pstatus); (void) mdb_tgt_xdata_insert(t, "utsname", "utsname structure", pt_xd_utsname); /* * Force a status update now so that we fill in t_status with the * latest information based on any successful grab. */ (void) mdb_tgt_status(t, &t->t_status); /* * If we're not examining a core file, trace SIGINT and all signals * that cause the process to dump core as part of our initialization. */ if ((t->t_pshandle != NULL && state != PS_DEAD && state != PS_IDLE) || (pt->p_file != NULL && pt->p_file->gf_ehdr.e_type == ET_EXEC)) { int tflag = MDB_TGT_SPEC_STICKY; /* default sigs are sticky */ (void) mdb_tgt_add_signal(t, SIGINT, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGQUIT, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGILL, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGTRAP, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGABRT, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGEMT, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGFPE, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGBUS, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGSEGV, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGSYS, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGXCPU, tflag, no_se_f, NULL); (void) mdb_tgt_add_signal(t, SIGXFSZ, tflag, no_se_f, NULL); } /* * If we've grabbed a live process, establish our initial breakpoints * and librtld_db agent so we can track rtld activity. If FL_VCREATE * is set, this process was created by a previous instantiation of * the debugger, so reset pr_flags to kill it; otherwise we attached * to an already running process. Pgrab() has already set the PR_RLC * flag appropriately based on whether the process was stopped when we * attached. */ if (t->t_pshandle != NULL && state != PS_DEAD && state != PS_IDLE) { if (mdb.m_flags & MDB_FL_VCREATE) { (void) Punsetflags(t->t_pshandle, PR_RLC); (void) Psetflags(t->t_pshandle, PR_KLC); pt->p_rflags = PRELEASE_KILL; } else { (void) Punsetflags(t->t_pshandle, PR_KLC); } pt_post_attach(t); } /* * Initialize a local copy of the environment, which can be modified * before running the program. */ for (i = 0; mdb.m_env[i] != NULL; i++) pt_env_set(pt, mdb.m_env[i]); /* * If adb(1) compatibility mode is on, then print the appropriate * greeting message if we have grabbed a core file. */ if ((mdb.m_flags & MDB_FL_ADB) && t->t_pshandle != NULL && state == PS_DEAD) { const pstatus_t *psp = Pstatus(t->t_pshandle); int cursig = psp->pr_lwp.pr_cursig; char signame[SIG2STR_MAX]; mdb_printf("core file = %s -- program ``%s'' on platform %s\n", core_path, aout_path ? aout_path : "?", pt_platform(t)); if (cursig != 0 && sig2str(cursig, signame) == 0) mdb_printf("SIG%s: %s\n", signame, strsignal(cursig)); } return (0); err: pt_destroy(t); return (-1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_PROC_H #define _MDB_PROC_H #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB /* * The proc target must provide support for examining multi-threaded processes * that use the raw LWP interface, as well as those that use either of the * existing libthread.so implementations. We must also support multiple active * instances of the proc target, as well as the notion that a clean process * can dlopen() libthread after startup, at which point we need to switch to * using libthread_db interfaces to properly debug it. To satisfy these * constraints, we declare an ops vector of functions for obtaining the * register sets of each thread. The proc target will define two versions * of this vector, one for the LWP mode and one for the libthread_db mode, * and then switch the ops vector pointer as appropriate during debugging. * The macros defined below expand to calls to the appropriate entry point. */ typedef struct pt_ptl_ops { int (*ptl_ctor)(mdb_tgt_t *); void (*ptl_dtor)(mdb_tgt_t *, void *); mdb_tgt_tid_t (*ptl_tid)(mdb_tgt_t *, void *); int (*ptl_iter)(mdb_tgt_t *, void *, mdb_addrvec_t *); int (*ptl_getregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t, prgregset_t); int (*ptl_setregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t, prgregset_t); int (*ptl_getxregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t, prxregset_t **, size_t *); void (*ptl_freexregs)(mdb_tgt_t *, void *, prxregset_t *, size_t); int (*ptl_setxregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t, const prxregset_t *, size_t); int (*ptl_getfpregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t, prfpregset_t *); int (*ptl_setfpregs)(mdb_tgt_t *, void *, mdb_tgt_tid_t, const prfpregset_t *); } pt_ptl_ops_t; #define PTL_CTOR(t) \ (((pt_data_t *)(t)->t_data)->p_ptl_ops->ptl_ctor(t)) #define PTL_DTOR(t) \ (((pt_data_t *)(t)->t_data)->p_ptl_ops->ptl_dtor((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl)) #define PTL_TID(t) \ (((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_tid((t), \ ((pt_data_t *)(t)->t_data)->p_ptl_hdl)) #define PTL_ITER(t, ap) \ (((pt_data_t *)(t)->t_data)->p_ptl_ops->ptl_iter((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl, (ap))) #define PTL_GETREGS(t, tid, gregs) \ (((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_getregs((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (gregs))) #define PTL_SETREGS(t, tid, gregs) \ (((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_setregs((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (gregs))) #define PTL_GETXREGS(t, tid, xregs, size) \ (((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_getxregs((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (xregs), (size))) #define PTL_FREEXREGS(t, xregs, size) \ (((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_freexregs((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl, (xregs), (size))) #define PTL_SETXREGS(t, tid, xregs, size) \ (((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_setxregs((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (xregs), (size))) #define PTL_GETFPREGS(t, tid, fpregs) \ (((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_getfpregs((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (fpregs))) #define PTL_SETFPREGS(t, tid, fpregs) \ (((pt_data_t *)((t)->t_data))->p_ptl_ops->ptl_setfpregs((t), \ ((pt_data_t *)((t)->t_data))->p_ptl_hdl, (tid), (fpregs))) /* * When we are following children and a vfork(2) occurs, we append the libproc * handle for the parent to a list of vfork parents. We need to keep track of * this handle so that when the child subsequently execs or dies, we clear out * our breakpoints before releasing the parent. */ typedef struct pt_vforkp { mdb_list_t p_list; /* List forward/back pointers */ struct ps_prochandle *p_pshandle; /* libproc handle */ } pt_vforkp_t; /* * Private data structure for the proc target. Among other things, we keep * pointers to the various symbol tables and the ELF file for the executable * here, along with handles for our ops vector defined above. */ typedef struct pt_data { struct ps_prochandle *p_idlehandle; /* idle libproc handle */ mdb_gelf_symtab_t *p_symtab; /* Standard symbol table */ mdb_gelf_symtab_t *p_dynsym; /* Dynamic symbol table */ mdb_gelf_file_t *p_file; /* ELF file object */ mdb_io_t *p_fio; /* Current file i/o backend */ mdb_io_t *p_aout_fio; /* Original file i/o backend */ char p_platform[MAXNAMELEN]; /* Platform string */ char p_symname[MDB_TGT_SYM_NAMLEN]; /* Temporary buffer for syms */ char p_objname[MDB_TGT_MAPSZ]; /* Temporary buffer for objs */ mdb_map_t p_map; /* Persistent map for callers */ mdb_list_t p_vforkp; /* List of vfork parents */ mdb_nv_t p_regs; /* Register descriptions */ const mdb_tdb_ops_t *p_tdb_ops; /* libthread_db ops */ const pt_ptl_ops_t *p_ptl_ops; /* Proc thread layer ops */ void *p_ptl_hdl; /* Proc thread layer handle */ rd_agent_t *p_rtld; /* librtld_db agent handle */ const char *p_stdin; /* File for stdin redirect */ const char *p_stdout; /* File for stdout redirect */ int p_oflags; /* Flags for open(2) */ int p_gflags; /* Flags for Pgrab() */ int p_rflags; /* Flags for Prelease() */ int p_signal; /* Signal to post at next run */ int p_rtld_finished; /* Has rtld init completed? */ int p_rdstate; /* Dlopen state (see below) */ int p_maxsig; /* Maximum valid signal */ mdb_nv_t p_env; /* Current environment */ } pt_data_t; #define PT_RD_NONE 0 /* No update pending */ #define PT_RD_ADD 1 /* Dlopen detected */ #define PT_RD_CONSIST 2 /* Link maps consistent */ /* * The mdb_tgt_gregset type is opaque to callers of the target interface. * Inside the target we define it explicitly to be a prgregset_t. */ struct mdb_tgt_gregset { prgregset_t gregs; }; typedef struct pt_symarg { mdb_tgt_t *psym_targ; /* Target pointer */ uint_t psym_which; /* Type of symbol table */ uint_t psym_type; /* Type of symbols to match */ mdb_tgt_sym_f *psym_func; /* Callback function */ void *psym_private; /* Callback data */ mdb_syminfo_t psym_info; /* Symbol id and table id */ const char *psym_obj; /* Containing object */ } pt_symarg_t; typedef struct pt_maparg { mdb_tgt_t *pmap_targ; /* Target pointer */ mdb_tgt_map_f *pmap_func; /* Callback function */ void *pmap_private; /* Callback data */ } pt_maparg_t; typedef struct pt_stkarg { mdb_tgt_stack_f *pstk_func; /* Callback function */ void *pstk_private; /* Callback data */ uint_t pstk_gotpc; /* Non-zero pc found */ } pt_stkarg_t; typedef struct pt_addarg_t { pt_data_t *pa_pt; /* Proc target data */ mdb_addrvec_t *pa_ap; /* Addrvec pointer */ } pt_addarg_t; typedef struct pt_brkpt { uintptr_t ptb_addr; /* Breakpoint address */ ulong_t ptb_instr; /* Saved instruction */ } pt_brkpt_t; typedef struct pt_bparg { char *pta_symbol; /* Symbolic name */ uintptr_t pta_addr; /* Explicit address */ } pt_bparg_t; /* * The proc_isadep.c file is expected to define the following * ISA-dependent pieces of the proc target: */ extern int pt_regs(uintptr_t, uint_t, int, const mdb_arg_t *); extern int pt_fpregs(uintptr_t, uint_t, int, const mdb_arg_t *); extern int pt_step_out(mdb_tgt_t *, uintptr_t *); extern int pt_next(mdb_tgt_t *, uintptr_t *); extern int pt_getfpreg(mdb_tgt_t *, mdb_tgt_tid_t, ushort_t, ushort_t, mdb_tgt_reg_t *); extern int pt_putfpreg(mdb_tgt_t *, mdb_tgt_tid_t, ushort_t, ushort_t, mdb_tgt_reg_t); extern void pt_addfpregs(mdb_tgt_t *); extern const char *pt_disasm(const GElf_Ehdr *); extern int pt_frameregs(void *, uintptr_t, uint_t, const long *, const mdb_tgt_gregset_t *, boolean_t); extern const mdb_tgt_regdesc_t pt_regdesc[]; #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_PROC_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Proc Service API Interposition Layer * * In order to allow multiple MDB targets to make use of librtld_db, we * provide an interposition layer for functions in the proc_service.h API * that are used by librtld_db. Each of the functions used by librtld_db * can be conveniently expressed in terms of the MDB target API, so this * layer simply selects the appropriate target, invokes the corresponding * target API function, and then translates the error codes appropriately. * We expect that each proc_service entry point will be invoked with a * cookie (struct ps_prochandle *) which matches either a known MDB target, * or the value of a target's t->t_pshandle. This allows us to re-vector * calls to the proc service API around libproc (which also contains an * implementation of the proc_service API) like this: * * Linker map: * +---------+ +---------+ +------------+ Legend: * | MDB | ->| libproc | ->| librtld_db | <1> function in this file * +---------+ +---------+ +------------+ <2> function in libproc * ps_pread<1> ps_pread<2> call ps_pread() --+ * | * +---------------------------------------------+ * | * +-> ps_pread<1>(P, ...) * t = mdb_tgt_from_pshandle(P); * mdb_tgt_vread(t, ...); * * If we are debugging a user process, we then make these calls (which form * the equivalent of libproc's proc_service implementation): * * mdb_tgt_vread() -> proc target t->t_vread() -> libproc.so`Pread() * * If we are debugging a user process through a kernel crash dump (kproc * target), we make these calls: * * mdb_tgt_vread() -> kproc target t->t_vread() -> mdb_tgt_aread(kvm target) -> * kvm target t->t_aread() -> libkvm.so`kvm_aread() * * This design allows us to support both kproc's use of librtld_db, as well * as libproc's use of librtld_db, but it does lead to one unfortunate problem * in the creation of a proc target: when the proc target invokes libproc to * construct a ps_prochandle, and libproc in turn invokes librtld_db, MDB does * not yet know what ps_prochandle has been allocated inside of libproc since * this call has not yet returned. We also can't translate this ps_prochandle * to the target itself, since that target isn't ready to handle requests yet; * we actually need to pass the call back through to libproc. In order to * do that, we use libdl to lookup the address of libproc's definition of the * various functions (RTLD_NEXT on the link map chain) and store these in the * ps_ops structure below. If we ever fail to translate a ps_prochandle to * an MDB target, we simply pass the call through to libproc. */ #include #include #include #include #include static struct { ps_err_e (*ps_pread)(struct ps_prochandle *, psaddr_t, void *, size_t); ps_err_e (*ps_pwrite)(struct ps_prochandle *, psaddr_t, const void *, size_t); ps_err_e (*ps_pglobal_lookup)(struct ps_prochandle *, const char *, const char *, psaddr_t *); ps_err_e (*ps_pglobal_sym)(struct ps_prochandle *P, const char *, const char *, ps_sym_t *); ps_err_e (*ps_pauxv)(struct ps_prochandle *, const auxv_t **); ps_err_e (*ps_pbrandname)(struct ps_prochandle *, char *, size_t); ps_err_e (*ps_pdmodel)(struct ps_prochandle *, int *); } ps_ops; static mdb_tgt_t * mdb_tgt_from_pshandle(void *P) { mdb_tgt_t *t; for (t = mdb_list_next(&mdb.m_tgtlist); t; t = mdb_list_next(t)) { if (t == P || t->t_pshandle == P) return (t); } return (NULL); } /* * Read from the specified target virtual address. */ ps_err_e ps_pread(struct ps_prochandle *P, psaddr_t addr, void *buf, size_t size) { mdb_tgt_t *t = mdb_tgt_from_pshandle(P); if (t == NULL) return (ps_ops.ps_pread(P, addr, buf, size)); if (mdb_tgt_vread(t, buf, size, addr) != size) return (PS_BADADDR); return (PS_OK); } /* * Write to the specified target virtual address. */ ps_err_e ps_pwrite(struct ps_prochandle *P, psaddr_t addr, const void *buf, size_t size) { mdb_tgt_t *t = mdb_tgt_from_pshandle(P); if (t == NULL) return (ps_ops.ps_pwrite(P, addr, buf, size)); if (mdb_tgt_vwrite(t, buf, size, addr) != size) return (PS_BADADDR); return (PS_OK); } /* * Search for a symbol by name and return the corresponding address. */ ps_err_e ps_pglobal_lookup(struct ps_prochandle *P, const char *object, const char *name, psaddr_t *symp) { mdb_tgt_t *t = mdb_tgt_from_pshandle(P); GElf_Sym sym; if (t == NULL) return (ps_ops.ps_pglobal_lookup(P, object, name, symp)); if (mdb_tgt_lookup_by_name(t, object, name, &sym, NULL) == 0) { *symp = (psaddr_t)sym.st_value; return (PS_OK); } return (PS_NOSYM); } /* * Search for a symbol by name and return the corresponding symbol data. * If we're compiled _LP64, we just call mdb_tgt_lookup_by_name and return * because ps_sym_t is defined to be an Elf64_Sym, which is the same as a * GElf_Sym. In the _ILP32 case, we have to convert mdb_tgt_lookup_by_name's * result back to a ps_sym_t (which is an Elf32_Sym). */ ps_err_e ps_pglobal_sym(struct ps_prochandle *P, const char *object, const char *name, ps_sym_t *symp) { mdb_tgt_t *t = mdb_tgt_from_pshandle(P); #if defined(_ILP32) GElf_Sym sym; if (t == NULL) return (ps_ops.ps_pglobal_sym(P, object, name, symp)); if (mdb_tgt_lookup_by_name(t, object, name, &sym, NULL) == 0) { symp->st_name = (Elf32_Word)sym.st_name; symp->st_value = (Elf32_Addr)sym.st_value; symp->st_size = (Elf32_Word)sym.st_size; symp->st_info = ELF32_ST_INFO( GELF_ST_BIND(sym.st_info), GELF_ST_TYPE(sym.st_info)); symp->st_other = sym.st_other; symp->st_shndx = sym.st_shndx; return (PS_OK); } #elif defined(_LP64) if (t == NULL) return (ps_ops.ps_pglobal_sym(P, object, name, symp)); if (mdb_tgt_lookup_by_name(t, object, name, symp, NULL) == 0) return (PS_OK); #endif return (PS_NOSYM); } /* * Report a debug message. We allow proc_service API clients to report * messages via our debug stream if the MDB_DBG_PSVC token is enabled. */ void ps_plog(const char *format, ...) { va_list alist; va_start(alist, format); mdb_dvprintf(MDB_DBG_PSVC, format, alist); va_end(alist); } /* * Return the auxv structure from the process being examined. */ ps_err_e ps_pauxv(struct ps_prochandle *P, const auxv_t **auxvp) { mdb_tgt_t *t = mdb_tgt_from_pshandle(P); if (t == NULL) return (ps_ops.ps_pauxv(P, auxvp)); if (mdb_tgt_auxv(t, auxvp) != 0) return (PS_ERR); return (PS_OK); } ps_err_e ps_pbrandname(struct ps_prochandle *P, char *buf, size_t len) { mdb_tgt_t *t = mdb_tgt_from_pshandle(P); const auxv_t *auxv; if (t == NULL) return (ps_ops.ps_pbrandname(P, buf, len)); if (mdb_tgt_auxv(t, &auxv) != 0) return (PS_ERR); while (auxv->a_type != AT_NULL) { if (auxv->a_type == AT_SUN_BRANDNAME) break; auxv++; } if (auxv->a_type == AT_NULL) return (PS_ERR); if (mdb_tgt_readstr(t, MDB_TGT_AS_VIRT, buf, len, auxv->a_un.a_val) <= 0) return (PS_ERR); return (PS_OK); } /* * Return the data model of the target. */ ps_err_e ps_pdmodel(struct ps_prochandle *P, int *dm) { mdb_tgt_t *t = mdb_tgt_from_pshandle(P); if (t == NULL) return (ps_ops.ps_pdmodel(P, dm)); switch (mdb_tgt_dmodel(t)) { case MDB_TGT_MODEL_LP64: *dm = PR_MODEL_LP64; return (PS_OK); case MDB_TGT_MODEL_ILP32: *dm = PR_MODEL_ILP32; return (PS_OK); } return (PS_ERR); } /* * Stub function in case we cannot find the necessary symbols from libproc. */ static ps_err_e ps_fail(struct ps_prochandle *P) { mdb_dprintf(MDB_DBG_PSVC, "failing call to pshandle %p\n", (void *)P); return (PS_BADPID); } /* * Initialization function for the proc service interposition layer: we use * libdl to look up the next definition of each function in the link map. */ void mdb_pservice_init(void) { if ((ps_ops.ps_pread = (ps_err_e (*)()) dlsym(RTLD_NEXT, "ps_pread")) == NULL) ps_ops.ps_pread = (ps_err_e (*)())ps_fail; if ((ps_ops.ps_pwrite = (ps_err_e (*)()) dlsym(RTLD_NEXT, "ps_pwrite")) == NULL) ps_ops.ps_pwrite = (ps_err_e (*)())ps_fail; if ((ps_ops.ps_pglobal_lookup = (ps_err_e (*)()) dlsym(RTLD_NEXT, "ps_pglobal_lookup")) == NULL) ps_ops.ps_pglobal_lookup = (ps_err_e (*)())ps_fail; if ((ps_ops.ps_pglobal_sym = (ps_err_e (*)()) dlsym(RTLD_NEXT, "ps_pglobal_sym")) == NULL) ps_ops.ps_pglobal_sym = (ps_err_e (*)())ps_fail; if ((ps_ops.ps_pauxv = (ps_err_e (*)()) dlsym(RTLD_NEXT, "ps_pauxv")) == NULL) ps_ops.ps_pauxv = (ps_err_e (*)())ps_fail; if ((ps_ops.ps_pbrandname = (ps_err_e (*)()) dlsym(RTLD_NEXT, "ps_pbrandname")) == NULL) ps_ops.ps_pbrandname = (ps_err_e (*)())ps_fail; if ((ps_ops.ps_pdmodel = (ps_err_e (*)()) dlsym(RTLD_NEXT, "ps_pdmodel")) == NULL) ps_ops.ps_pdmodel = (ps_err_e (*)())ps_fail; } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Joyent, Inc. * Copyright 2024 Oxide Computer Company */ /* * Raw File Target * * The raw file target is invoked whenever a file of unrecognizable type is * specified on the command line, or when raw file examination is forced using * the -f option. If one file is specified, that file will be opened as the * "object" file. If two files are specified, the second one will be opened * as the "core" file. Each file is opened using the fdio backend, which * internally supports both byte-oriented i/o and block-oriented i/o as needed. */ #include #include #include #include #include #include #include #include typedef struct rf_data { mdb_io_t *r_object_fio; mdb_io_t *r_core_fio; } rf_data_t; #define RF_OBJECT(p) (((rf_data_t *)(p))->r_object_fio) #define RF_CORE(p) (((rf_data_t *)(p))->r_core_fio) static void rf_data_destroy(rf_data_t *rf) { if (rf->r_object_fio != NULL) mdb_io_destroy(rf->r_object_fio); if (rf->r_core_fio != NULL) mdb_io_destroy(rf->r_core_fio); mdb_free(rf, sizeof (rf_data_t)); } static int rf_setflags(mdb_tgt_t *t, int flags) { if ((flags ^ t->t_flags) & MDB_TGT_F_RDWR) { uint_t otflags = t->t_flags; rf_data_t *orf = t->t_data; const char *argv[2]; int argc = 0; if (orf->r_object_fio != NULL) argv[argc++] = IOP_NAME(orf->r_object_fio); if (orf->r_core_fio != NULL) argv[argc++] = IOP_NAME(orf->r_core_fio); t->t_flags = (t->t_flags & ~MDB_TGT_F_RDWR) | (flags & MDB_TGT_F_RDWR); if (mdb_rawfile_tgt_create(t, argc, argv) == -1) { t->t_flags = otflags; t->t_data = orf; return (-1); } rf_data_destroy(orf); } return (0); } static void rf_destroy(mdb_tgt_t *t) { rf_data_destroy(t->t_data); } /*ARGSUSED*/ static const char * rf_name(mdb_tgt_t *t) { return ("raw"); } static ssize_t rf_read(mdb_io_t *io, void *buf, size_t nbytes, uint64_t addr) { ssize_t rbytes; if (io == NULL) return (set_errno(EMDB_NOMAP)); if (IOP_SEEK(io, addr, SEEK_SET) == -1) return (-1); /* errno is set for us */ if ((rbytes = IOP_READ(io, buf, nbytes)) == 0) (void) set_errno(EMDB_EOF); return (rbytes); } static ssize_t rf_write(mdb_io_t *io, const void *buf, size_t nbytes, uint64_t addr) { if (io == NULL) return (set_errno(EMDB_NOMAP)); if (IOP_SEEK(io, addr, SEEK_SET) == -1) return (-1); /* errno is set for us */ return (IOP_WRITE(io, buf, nbytes)); } static ssize_t rf_aread(mdb_tgt_t *t, mdb_tgt_as_t as, void *buf, size_t len, mdb_tgt_addr_t addr) { switch ((uintptr_t)as) { case (uintptr_t)MDB_TGT_AS_VIRT: case (uintptr_t)MDB_TGT_AS_VIRT_I: case (uintptr_t)MDB_TGT_AS_VIRT_S: case (uintptr_t)MDB_TGT_AS_PHYS: if (RF_CORE(t->t_data) != NULL) return (rf_read(RF_CORE(t->t_data), buf, len, addr)); /*FALLTHRU*/ case (uintptr_t)MDB_TGT_AS_FILE: return (rf_read(RF_OBJECT(t->t_data), buf, len, addr)); default: return (set_errno(EMDB_NOMAP)); } } static ssize_t rf_awrite(mdb_tgt_t *t, mdb_tgt_as_t as, const void *buf, size_t len, mdb_tgt_addr_t addr) { switch ((uintptr_t)as) { case (uintptr_t)MDB_TGT_AS_VIRT: case (uintptr_t)MDB_TGT_AS_VIRT_I: case (uintptr_t)MDB_TGT_AS_VIRT_S: case (uintptr_t)MDB_TGT_AS_PHYS: if (RF_CORE(t->t_data) != NULL) return (rf_write(RF_CORE(t->t_data), buf, len, addr)); /*FALLTHRU*/ case (uintptr_t)MDB_TGT_AS_FILE: return (rf_write(RF_OBJECT(t->t_data), buf, len, addr)); default: return (set_errno(EMDB_NOMAP)); } } static ssize_t rf_vread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr) { if (RF_CORE(t->t_data) != NULL) return (rf_read(RF_CORE(t->t_data), buf, nbytes, addr)); return (rf_read(RF_OBJECT(t->t_data), buf, nbytes, addr)); } static ssize_t rf_vwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr) { if (RF_CORE(t->t_data) != NULL) return (rf_write(RF_CORE(t->t_data), buf, nbytes, addr)); return (rf_write(RF_OBJECT(t->t_data), buf, nbytes, addr)); } static ssize_t rf_pread(mdb_tgt_t *t, void *buf, size_t nbytes, physaddr_t addr) { if (RF_CORE(t->t_data) != NULL) return (rf_read(RF_CORE(t->t_data), buf, nbytes, addr)); return (rf_read(RF_OBJECT(t->t_data), buf, nbytes, addr)); } static ssize_t rf_pwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, physaddr_t addr) { if (RF_CORE(t->t_data) != NULL) return (rf_write(RF_CORE(t->t_data), buf, nbytes, addr)); return (rf_write(RF_OBJECT(t->t_data), buf, nbytes, addr)); } static ssize_t rf_fread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr) { return (rf_read(RF_OBJECT(t->t_data), buf, nbytes, addr)); } static ssize_t rf_fwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr) { return (rf_write(RF_OBJECT(t->t_data), buf, nbytes, addr)); } static int rf_print_map(mdb_io_t *io, const char *type, int tflags, mdb_tgt_map_f *func, void *private) { mdb_map_t map; (void) mdb_iob_snprintf(map.map_name, MDB_TGT_MAPSZ, "%s (%s)", IOP_NAME(io), type); map.map_base = 0; map.map_size = IOP_SEEK(io, 0, SEEK_END); map.map_flags = MDB_TGT_MAP_R; if (tflags & MDB_TGT_F_RDWR) map.map_flags |= MDB_TGT_MAP_W; return (func(private, &map, map.map_name)); } static int rf_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private) { rf_data_t *rf = t->t_data; if (rf->r_object_fio != NULL && rf_print_map(rf->r_object_fio, "object file", t->t_flags, func, private) != 0) return (0); if (rf->r_core_fio != NULL && rf_print_map(rf->r_core_fio, "core file", t->t_flags, func, private) != 0) return (0); return (0); } /*ARGSUSED*/ static int rf_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { bzero(tsp, sizeof (mdb_tgt_status_t)); if (RF_CORE(t->t_data) != NULL) tsp->st_state = MDB_TGT_DEAD; else tsp->st_state = MDB_TGT_IDLE; return (0); } /*ARGSUSED*/ static int rf_status_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rf_data_t *rf = mdb.m_target->t_data; if (rf->r_object_fio != NULL) { mdb_printf("debugging file '%s' (object file)", IOP_NAME(rf->r_object_fio)); if (rf->r_core_fio != NULL) { mdb_printf(" and file '%s' (core file)", IOP_NAME(rf->r_core_fio)); } mdb_printf("\n"); } else { mdb_printf("debugging empty target\n"); } return (DCMD_OK); } static const mdb_dcmd_t rf_dcmds[] = { { "status", NULL, "print summary of current target", rf_status_dcmd }, { NULL } }; static const struct rf_magic { const char *rfm_str; size_t rfm_len; const char *rfm_mod; } rf_magic[] = { { DOF_MAG_STRING, DOF_MAG_STRLEN, "dof" }, { NULL, 0, NULL } }; static void rf_activate(mdb_tgt_t *t) { rf_data_t *rf = t->t_data; const struct rf_magic *m; mdb_var_t *v; off64_t size; (void) mdb_tgt_register_dcmds(t, &rf_dcmds[0], MDB_MOD_FORCE); /* * We set the legacy adb variable 'd' to be the size of the file (data * segment). To get this value, we call seek() on the underlying fdio. */ if (rf->r_object_fio != NULL) { size = IOP_SEEK(rf->r_object_fio, 0, SEEK_END); if ((v = mdb_nv_lookup(&mdb.m_nv, "d")) != NULL) mdb_nv_set_value(v, size); } /* * Load any debugging support modules that match the file type, as * determined by our poor man's /etc/magic. If many clients need * to use this feature, rf_magic[] should be computed dynamically. */ for (m = rf_magic; m->rfm_str != NULL; m++) { char *buf = mdb_alloc(m->rfm_len, UM_SLEEP); if (mdb_tgt_vread(t, buf, m->rfm_len, 0) == m->rfm_len && bcmp(buf, m->rfm_str, m->rfm_len) == 0) { (void) mdb_module_load(m->rfm_mod, MDB_MOD_LOCAL | MDB_MOD_SILENT); } mdb_free(buf, m->rfm_len); } } static void rf_deactivate(mdb_tgt_t *t) { const mdb_dcmd_t *dcp; for (dcp = &rf_dcmds[0]; dcp->dc_name != NULL; dcp++) { if (mdb_module_remove_dcmd(t->t_module, dcp->dc_name) == -1) warn("failed to remove dcmd %s", dcp->dc_name); } } static const mdb_tgt_ops_t rawfile_ops = { .t_setflags = rf_setflags, .t_setcontext = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_activate = rf_activate, .t_deactivate = rf_deactivate, .t_periodic = (void (*)())(uintptr_t)mdb_tgt_nop, .t_destroy = rf_destroy, .t_name = rf_name, .t_isa = (const char *(*)())mdb_conf_isa, .t_platform = (const char *(*)())mdb_conf_platform, .t_uname = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_dmodel = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_aread = rf_aread, .t_awrite = rf_awrite, .t_vread = rf_vread, .t_vwrite = rf_vwrite, .t_pread = rf_pread, .t_pwrite = rf_pwrite, .t_fread = rf_fread, .t_fwrite = rf_fwrite, .t_ioread = (ssize_t (*)())mdb_tgt_notsup, .t_iowrite = (ssize_t (*)())mdb_tgt_notsup, .t_vtop = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_lookup_by_name = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_lookup_by_addr = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_symbol_iter = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_mapping_iter = rf_mapping_iter, .t_object_iter = rf_mapping_iter, .t_addr_to_map = (const mdb_map_t *(*)())mdb_tgt_null, .t_name_to_map = (const mdb_map_t *(*)())mdb_tgt_null, .t_addr_to_ctf = (struct ctf_file *(*)())mdb_tgt_null, .t_name_to_ctf = (struct ctf_file *(*)())mdb_tgt_null, .t_status = rf_status, .t_run = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_step = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_step_out = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_next = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_cont = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_signal = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_add_vbrkpt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sbrkpt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_pwapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_vwapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_iowapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sysenter = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sysexit = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_signal = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_fault = (int (*)())(uintptr_t)mdb_tgt_null, .t_getareg = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_putareg = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_stack_iter = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_auxv = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_thread_name = (int (*)())(uintptr_t)mdb_tgt_notsup, }; int mdb_rawfile_tgt_create(mdb_tgt_t *t, int argc, const char *argv[]) { mdb_io_t *io[2] = { NULL, NULL }; rf_data_t *rf; int oflags, i; if (argc > 2) return (set_errno(EINVAL)); rf = mdb_zalloc(sizeof (rf_data_t), UM_SLEEP); t->t_ops = &rawfile_ops; t->t_data = rf; if (t->t_flags & MDB_TGT_F_RDWR) oflags = O_RDWR; else oflags = O_RDONLY; for (i = 0; i < argc; i++) { io[i] = mdb_fdio_create_path(NULL, argv[i], oflags, 0); if (io[i] == NULL) { warn("failed to open %s", argv[i]); goto err; } } rf->r_object_fio = io[0]; /* first file is the "object" */ rf->r_core_fio = io[1]; /* second file is the "core" */ t->t_flags |= MDB_TGT_F_ASIO; /* do i/o using aread and awrite */ return (0); err: for (i = 0; i < argc; i++) { if (io[i] != NULL) mdb_io_destroy(io[i]); } mdb_free(rf, sizeof (rf_data_t)); return (set_errno(EMDB_TGT)); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2020 Joyent, Inc. */ /* * Support for ::set dcmd. The +/-o option processing code is provided in a * stand-alone function so it can be used by the command-line option processing * code in mdb_main.c. This facility provides an easy way for us to add more * configurable options without having to add a new dcmd each time. */ #include #include #include #include #include /*ARGSUSED*/ static int opt_set_mflags(int enable, uint_t bits, const char *arg) { mdb.m_flags = (mdb.m_flags & ~bits) | (bits & -enable); return (1); } /*ARGSUSED*/ static int opt_set_tflags(int enable, uint_t bits, const char *arg) { mdb.m_tgtflags = (mdb.m_tgtflags & ~bits) | (bits & -enable); return (1); } static int opt_pager(int enable, uint_t bits, const char *arg) { if (enable) mdb_iob_setflags(mdb.m_out, MDB_IOB_PGENABLE); else mdb_iob_clrflags(mdb.m_out, MDB_IOB_PGENABLE); return (opt_set_mflags(enable, bits, arg)); } static int opt_adb(int enable, uint_t bits, const char *arg) { if (enable) (void) mdb_set_prompt(""); else if (mdb.m_promptlen == 0) (void) mdb_set_prompt("> "); (void) opt_pager(1 - enable, MDB_FL_PAGER, arg); return (opt_set_mflags(enable, bits, arg)); } /*ARGSUSED*/ static int opt_armemlim(int enable, uint_t bits, const char *arg) { if (strisnum(arg)) { mdb.m_armemlim = strtoi(arg); return (1); } if (strcmp(arg, "none") == 0) { mdb.m_armemlim = MDB_ARR_NOLIMIT; return (1); } return (0); } /*ARGSUSED*/ static int opt_arstrlim(int enable, uint_t bits, const char *arg) { if (strisnum(arg)) { mdb.m_arstrlim = strtoi(arg); return (1); } if (strcmp(arg, "none") == 0) { mdb.m_arstrlim = MDB_ARR_NOLIMIT; return (1); } return (0); } /*ARGSUSED*/ static int opt_exec_mode(int enable, uint_t bits, const char *arg) { if (strcmp(arg, "ask") == 0) { mdb.m_execmode = MDB_EM_ASK; return (1); } else if (strcmp(arg, "stop") == 0) { mdb.m_execmode = MDB_EM_STOP; return (1); } else if (strcmp(arg, "follow") == 0) { mdb.m_execmode = MDB_EM_FOLLOW; return (1); } return (0); } /*ARGSUSED*/ static int opt_fork_mode(int enable, uint_t bits, const char *arg) { if (strcmp(arg, "ask") == 0) { mdb.m_forkmode = MDB_FM_ASK; return (1); } else if (strcmp(arg, "parent") == 0) { mdb.m_forkmode = MDB_FM_PARENT; return (1); } else if (strcmp(arg, "child") == 0) { mdb.m_forkmode = MDB_FM_CHILD; return (1); } return (0); } /*ARGSUSED*/ static int opt_set_term(int enable, uint_t bits, const char *arg) { mdb.m_termtype = strdup(arg); mdb.m_flags &= ~MDB_FL_TERMGUESS; return (1); } int mdb_set_options(const char *s, int enable) { static const struct opdesc { const char *opt_name; int (*opt_func)(int, uint_t, const char *); uint_t opt_bits; } opdtab[] = { { "adb", opt_adb, MDB_FL_REPLAST | MDB_FL_NOMODS | MDB_FL_ADB }, { "array_mem_limit", opt_armemlim, 0 }, { "array_str_limit", opt_arstrlim, 0 }, { "follow_exec_mode", opt_exec_mode, 0 }, { "follow_fork_mode", opt_fork_mode, 0 }, { "pager", opt_pager, MDB_FL_PAGER }, { "term", opt_set_term, 0 }, { "autowrap", opt_set_mflags, MDB_FL_AUTOWRAP }, { "ignoreeof", opt_set_mflags, MDB_FL_IGNEOF }, { "repeatlast", opt_set_mflags, MDB_FL_REPLAST }, { "latest", opt_set_mflags, MDB_FL_LATEST }, { "noctf", opt_set_mflags, MDB_FL_NOCTF }, { "nomods", opt_set_mflags, MDB_FL_NOMODS }, { "showlmid", opt_set_mflags, MDB_FL_SHOWLMID }, { "lmraw", opt_set_mflags, MDB_FL_LMRAW }, { "stop_on_bpt_nosym", opt_set_mflags, MDB_FL_BPTNOSYMSTOP }, { "write_readback", opt_set_mflags, MDB_FL_READBACK }, { "allow_io_access", opt_set_tflags, MDB_TGT_F_ALLOWIO }, { "nostop", opt_set_tflags, MDB_TGT_F_NOSTOP }, { NULL, NULL, 0 } }; const struct opdesc *opp; char *buf = strdup(s); char *opt, *arg; int status = 1; for (opt = strtok(buf, ","); opt != NULL; opt = strtok(NULL, ",")) { if ((arg = strchr(opt, '=')) != NULL) *arg++ = '\0'; for (opp = opdtab; opp->opt_name != NULL; opp++) { if (strcmp(opt, opp->opt_name) == 0) { if (opp->opt_bits != 0 && arg != NULL) { mdb_warn("option does not accept an " "argument -- %s\n", opt); status = 0; } else if (opp->opt_bits == 0 && arg == NULL) { mdb_warn("option requires an argument " "-- %s\n", opt); status = 0; } else if (opp->opt_func(enable != 0, opp->opt_bits, arg) == 0) { mdb_warn("invalid argument for option " "%s -- %s\n", opt, arg); status = 0; } break; } } if (opp->opt_name == NULL) { mdb_warn("invalid debugger option -- %s\n", opt); status = 0; } } mdb_free(buf, strlen(s) + 1); return (status); } static void print_path(const char **path, int indent) { if (path != NULL && *path != NULL) { for (mdb_printf("%s\n", *path++); *path != NULL; path++) mdb_printf("%*s%s\n", indent, " ", *path); } mdb_printf("\n"); } #define LABEL_INDENT 26 static void print_properties(void) { int tflags = mdb_tgt_getflags(mdb.m_target); uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_AUTOWRAP; mdb_iob_clrflags(mdb.m_out, MDB_IOB_AUTOWRAP); mdb_printf("\n macro path: "); print_path(mdb.m_ipath, 14); mdb_printf(" module path: "); print_path(mdb.m_lpath, 14); mdb_iob_setflags(mdb.m_out, oflags); mdb_printf("%*s %lr (%s)\n", LABEL_INDENT, "symbol matching distance:", mdb.m_symdist, mdb.m_symdist ? "absolute mode" : "smart mode"); mdb_printf("%*s ", LABEL_INDENT, "array member print limit:"); if (mdb.m_armemlim != MDB_ARR_NOLIMIT) mdb_printf("%u\n", mdb.m_armemlim); else mdb_printf("none\n"); mdb_printf(" array string print limit: "); if (mdb.m_arstrlim != MDB_ARR_NOLIMIT) mdb_printf("%u\n", mdb.m_arstrlim); else mdb_printf("none\n"); mdb_printf("%*s \"%s\"\n", LABEL_INDENT, "command prompt:", mdb.m_prompt); mdb_printf("%*s ", LABEL_INDENT, "debugger options:"); (void) mdb_inc_indent(LABEL_INDENT + 1); /* * The ::set output implicitly relies on "autowrap" being enabled, so * we enable it for the duration of the command. */ oflags = mdb.m_flags; mdb_iob_set_autowrap(mdb.m_out); mdb_printf("follow_exec_mode="); switch (mdb.m_execmode) { case MDB_EM_ASK: mdb_printf("ask"); break; case MDB_EM_STOP: mdb_printf("stop"); break; case MDB_EM_FOLLOW: mdb_printf("follow"); break; } #define COMMAFLAG(name) { mdb_printf(", "); mdb_printf(name); } COMMAFLAG("follow_fork_mode"); switch (mdb.m_forkmode) { case MDB_FM_ASK: mdb_printf("ask"); break; case MDB_FM_PARENT: mdb_printf("parent"); break; case MDB_FM_CHILD: mdb_printf("child"); break; } if (mdb.m_flags & MDB_FL_ADB) COMMAFLAG("adb"); if (oflags & MDB_FL_AUTOWRAP) COMMAFLAG("autowrap"); if (mdb.m_flags & MDB_FL_IGNEOF) COMMAFLAG("ignoreeof"); if (mdb.m_flags & MDB_FL_LMRAW) COMMAFLAG("lmraw"); if (mdb.m_flags & MDB_FL_PAGER) COMMAFLAG("pager"); if (mdb.m_flags & MDB_FL_REPLAST) COMMAFLAG("repeatlast"); if (mdb.m_flags & MDB_FL_SHOWLMID) COMMAFLAG("showlmid"); if (mdb.m_flags & MDB_FL_BPTNOSYMSTOP) COMMAFLAG("stop_on_bpt_nosym"); if (mdb.m_flags & MDB_FL_READBACK) COMMAFLAG("write_readback"); mdb_printf("\n"); (void) mdb_dec_indent(LABEL_INDENT + 1); mdb_printf("%*s ", LABEL_INDENT, "target options:"); (void) mdb_inc_indent(LABEL_INDENT + 1); if (tflags & MDB_TGT_F_RDWR) mdb_printf("read-write"); else mdb_printf("read-only"); if (tflags & MDB_TGT_F_ALLOWIO) COMMAFLAG("allow-io-access"); if (tflags & MDB_TGT_F_FORCE) COMMAFLAG("force-attach"); if (tflags & MDB_TGT_F_PRELOAD) COMMAFLAG("preload-syms"); if (tflags & MDB_TGT_F_NOLOAD) COMMAFLAG("no-load-objs"); if (tflags & MDB_TGT_F_NOSTOP) COMMAFLAG("no-stop"); mdb_printf("\n"); (void) mdb_dec_indent(LABEL_INDENT + 1); mdb.m_flags = oflags; } /*ARGSUSED*/ int cmd_set(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *opt_I = NULL, *opt_L = NULL, *opt_P = NULL, *opt_o = NULL; const char *opt_plus_o = NULL, *opt_D = NULL; uint_t opt_w = FALSE, opt_plus_w = FALSE, opt_W = FALSE; uint_t opt_plus_W = FALSE, opt_F = FALSE; uintptr_t opt_s = (uintptr_t)(long)-1; int tflags = 0; int i; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); /* * If no options are specified, print out the current set of target * and debugger properties that can be modified with ::set. */ if (argc == 0) { print_properties(); return (DCMD_OK); } while ((i = mdb_getopts(argc, argv, 'F', MDB_OPT_SETBITS, TRUE, &opt_F, 'I', MDB_OPT_STR, &opt_I, 'L', MDB_OPT_STR, &opt_L, 'P', MDB_OPT_STR, &opt_P, 'o', MDB_OPT_STR, &opt_o, 's', MDB_OPT_UINTPTR, &opt_s, 'w', MDB_OPT_SETBITS, TRUE, &opt_w, 'W', MDB_OPT_SETBITS, TRUE, &opt_W, 'D', MDB_OPT_STR, &opt_D, NULL)) != argc) { uint_t n = 1; argv += i; /* skip past args we processed */ argc -= i; /* adjust argc */ if (argv[0].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (strcmp(argv->a_un.a_str, "+W") == 0) opt_plus_W = TRUE; else if (strcmp(argv->a_un.a_str, "+w") == 0) opt_plus_w = TRUE; else if (strcmp(argv->a_un.a_str, "+o") == 0 && argc >= 2 && argv[1].a_type == MDB_TYPE_STRING) { opt_plus_o = argv[1].a_un.a_str; n = 2; } else return (DCMD_USAGE); /* remove the flag and possible argument */ argv += n; argc -= n; } if ((opt_w && opt_plus_w) || (opt_W && opt_plus_W)) return (DCMD_USAGE); /* * Handle -w, -/+W and -F first: as these options modify the target, * they are the only ::set changes that can potentially fail. We'll * use these flags to modify a copy of the target's t_flags, which we'll * then pass to the target's setflags op. This allows the target to * detect newly-set and newly-cleared flags by comparing the passed * value to the current t_flags. */ tflags = mdb_tgt_getflags(mdb.m_target); if (opt_w) tflags |= MDB_TGT_F_RDWR; if (opt_plus_w) tflags &= ~MDB_TGT_F_RDWR; if (opt_W) tflags |= MDB_TGT_F_ALLOWIO; if (opt_plus_W) tflags &= ~MDB_TGT_F_ALLOWIO; if (opt_F) tflags |= MDB_TGT_F_FORCE; if (tflags != mdb_tgt_getflags(mdb.m_target) && mdb_tgt_setflags(mdb.m_target, tflags) == -1) return (DCMD_ERR); /* * Now handle everything that either can't fail or we don't care if * it does. Note that we handle +/-o first in case another option * overrides a change made implicity by a +/-o argument (e.g. -P). */ if (opt_o != NULL) (void) mdb_set_options(opt_o, TRUE); if (opt_plus_o != NULL) (void) mdb_set_options(opt_plus_o, FALSE); if (opt_I != NULL) { #ifdef _KMDB mdb_warn("macro path cannot be set under kmdb\n"); #else mdb_set_ipath(opt_I); #endif } if (opt_L != NULL) mdb_set_lpath(opt_L); if (opt_P != NULL) (void) mdb_set_prompt(opt_P); if (opt_s != (uintptr_t)-1) mdb.m_symdist = (size_t)opt_s; if (opt_D != NULL && (i = mdb_dstr2mode(opt_D)) != MDB_DBG_HELP) mdb_dmode((uint_t)i); return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_SET_H #define _MDB_SET_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern int mdb_set_options(const char *, int); extern int cmd_set(uintptr_t, uint_t, int, const mdb_arg_t *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_SET_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Shell Escape I/O Backend * * The MDB parser implements two forms of shell escapes: (1) traditional adb(1) * style shell escapes of the form "!command", which simply allows the user to * invoke a command (or shell pipeline) as if they had executed sh -c command * and then return to the debugger, and (2) shell pipes of the form "dcmds ! * command", in which the output of one or more MDB dcmds is sent as standard * input to a shell command (or shell pipeline). Form (1) can be handled * entirely from the parser by calling mdb_shell_exec (below); it simply * forks the shell, executes the desired command, and waits for completion. * Form (2) is slightly more complicated: we construct a UNIX pipe, fork * the shell, and then built an fdio object out of the write end of the pipe. * We then layer a shellio object (implemented below) and iob on top, and * set mdb.m_out to point to this new iob. The shellio is simply a pass-thru * to the fdio, except that its io_close routine performs a waitpid for the * forked child process. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define E_BADEXEC 127 /* Exit status for failed exec */ /* * We must manually walk the open file descriptors and set FD_CLOEXEC, because * we need to be able to print an error if execlp() fails. If mdb.m_err has * been altered, then using closefrom() could close our output file descriptor, * preventing us from displaying an error message. Using FD_CLOEXEC ensures * that the file descriptors are only closed if execlp() succeeds. */ /*ARGSUSED*/ static int closefd_walk(void *unused, int fd) { if (fd > 2) (void) fcntl(fd, F_SETFD, FD_CLOEXEC); return (0); } void mdb_shell_exec(char *cmd) { int status; pid_t pid; if (access(mdb.m_shell, X_OK) == -1) yyperror("cannot access %s", mdb.m_shell); if ((pid = vfork()) == -1) yyperror("failed to fork"); if (pid == 0) { (void) fdwalk(closefd_walk, NULL); (void) execlp(mdb.m_shell, strbasename(mdb.m_shell), "-c", cmd, NULL); warn("failed to exec %s", mdb.m_shell); _exit(E_BADEXEC); } do { mdb_dprintf(MDB_DBG_SHELL, "waiting for PID %d\n", (int)pid); } while (waitpid(pid, &status, 0) == -1 && errno == EINTR); mdb_dprintf(MDB_DBG_SHELL, "waitpid %d -> 0x%x\n", (int)pid, status); strfree(cmd); } /* * This use of the io_unlink entry point is a little strange: we have stacked * the shellio on top of the fdio, but before the shellio's close routine can * wait for the child process, we need to close the UNIX pipe file descriptor * in order to generate an EOF to terminate the child. Since each io is * unlinked from its iob before being popped by mdb_iob_destroy, we use the * io_unlink entry point to release the underlying fdio (forcing its io_close * routine to be called) and remove it from the iob's i/o stack out of order. */ /*ARGSUSED*/ static void shellio_unlink(mdb_io_t *io, mdb_iob_t *iob) { mdb_io_t *fdio = io->io_next; ASSERT(iob->iob_iop == io); ASSERT(fdio != NULL); io->io_next = fdio->io_next; fdio->io_next = NULL; mdb_io_rele(fdio); } static void shellio_close(mdb_io_t *io) { pid_t pid = (pid_t)(intptr_t)io->io_data; int status; do { mdb_dprintf(MDB_DBG_SHELL, "waiting for PID %d\n", (int)pid); } while (waitpid(pid, &status, 0) == -1 && errno == EINTR); mdb_dprintf(MDB_DBG_SHELL, "waitpid %d -> 0x%x\n", (int)pid, status); } static const mdb_io_ops_t shellio_ops = { .io_read = no_io_read, .io_write = no_io_write, .io_seek = no_io_seek, .io_ctl = no_io_ctl, .io_close = shellio_close, .io_name = no_io_name, .io_link = no_io_link, .io_unlink = shellio_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume }; void mdb_shell_pipe(char *cmd) { uint_t iflag = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT; mdb_iob_t *iob; mdb_io_t *io; int pfds[2]; pid_t pid; if (access(mdb.m_shell, X_OK) == -1) yyperror("cannot access %s", mdb.m_shell); if (pipe(pfds) == -1) yyperror("failed to open pipe"); iob = mdb_iob_create(mdb_fdio_create(pfds[1]), MDB_IOB_WRONLY | iflag); mdb_iob_clrflags(iob, MDB_IOB_AUTOWRAP | MDB_IOB_INDENT); mdb_iob_resize(iob, BUFSIZ, BUFSIZ); if ((pid = vfork()) == -1) { (void) close(pfds[0]); (void) close(pfds[1]); mdb_iob_destroy(iob); yyperror("failed to fork"); } if (pid == 0) { (void) close(pfds[1]); (void) close(STDIN_FILENO); (void) dup2(pfds[0], STDIN_FILENO); (void) fdwalk(closefd_walk, NULL); (void) execlp(mdb.m_shell, strbasename(mdb.m_shell), "-c", cmd, NULL); warn("failed to exec %s", mdb.m_shell); _exit(E_BADEXEC); } (void) close(pfds[0]); strfree(cmd); io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP); io->io_ops = &shellio_ops; io->io_data = (void *)(intptr_t)pid; io->io_next = NULL; io->io_refcnt = 0; mdb_iob_stack_push(&mdb.m_frame->f_ostk, mdb.m_out, yylineno); mdb_iob_push_io(iob, io); mdb.m_out = iob; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1997-1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_SHELL_H #define _MDB_SHELL_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB extern void mdb_shell_exec(char *); extern void mdb_shell_pipe(char *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_SHELL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include static mdb_signal_f *sig_handlers[NSIG]; static void *sig_data[NSIG]; static void sig_stub(int sig, siginfo_t *sip, void *ucp) { sig_handlers[sig](sig, sip, (ucontext_t *)ucp, sig_data[sig]); } int mdb_signal_sethandler(int sig, mdb_signal_f *handler, void *data) { struct sigaction act; int status; ASSERT(sig > 0 && sig < NSIG && sig != SIGKILL && sig != SIGSTOP); sig_handlers[sig] = handler; sig_data[sig] = data; if (handler == MDB_SIG_DFL) { act.sa_handler = SIG_DFL; act.sa_flags = SA_RESTART; } else if (handler == MDB_SIG_IGN) { act.sa_handler = SIG_IGN; act.sa_flags = SA_RESTART; } else { act.sa_sigaction = sig_stub; act.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK; } (void) sigemptyset(&act.sa_mask); if (sig == SIGWINCH || sig == SIGTSTP) { (void) sigaddset(&act.sa_mask, SIGWINCH); (void) sigaddset(&act.sa_mask, SIGTSTP); (void) sigaddset(&act.sa_mask, SIGHUP); (void) sigaddset(&act.sa_mask, SIGTERM); } if ((status = sigaction(sig, &act, NULL)) == 0) (void) mdb_signal_unblock(sig); return (status); } mdb_signal_f * mdb_signal_gethandler(int sig, void **datap) { if (datap != NULL) *datap = sig_data[sig]; return (sig_handlers[sig]); } int mdb_signal_raise(int sig) { return (kill(getpid(), sig)); } int mdb_signal_pgrp(int sig) { return (kill(0, sig)); } int mdb_signal_block(int sig) { sigset_t set; (void) sigemptyset(&set); (void) sigaddset(&set, sig); return (sigprocmask(SIG_BLOCK, &set, NULL)); } int mdb_signal_unblock(int sig) { sigset_t set; (void) sigemptyset(&set); (void) sigaddset(&set, sig); return (sigprocmask(SIG_UNBLOCK, &set, NULL)); } int mdb_signal_blockall(void) { sigset_t set; (void) sigfillset(&set); return (sigprocmask(SIG_BLOCK, &set, NULL)); } int mdb_signal_unblockall(void) { sigset_t set; (void) sigfillset(&set); return (sigprocmask(SIG_UNBLOCK, &set, NULL)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1997-1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_SIGNAL_H #define _MDB_SIGNAL_H #ifdef __cplusplus extern "C" { #endif #include #include typedef void mdb_signal_f(int, siginfo_t *, ucontext_t *, void *); #ifdef _MDB #define MDB_SIG_DFL (mdb_signal_f *)0 #define MDB_SIG_IGN (mdb_signal_f *)1 extern int mdb_signal_sethandler(int, mdb_signal_f *, void *); extern mdb_signal_f *mdb_signal_gethandler(int, void **); extern int mdb_signal_raise(int); extern int mdb_signal_pgrp(int); extern int mdb_signal_block(int); extern int mdb_signal_unblock(int); extern int mdb_signal_blockall(void); extern int mdb_signal_unblockall(void); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_SIGNAL_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Common code to help printing stack frames in a consistent way, and with * options to include frame size and type data where it can be retrieved from * CTF data. */ #include #include #include #include #include #include #include #include typedef struct { mdb_tgt_t *msfd_tgt; uint_t msfd_arglim; mdb_stack_frame_flags_t msfd_flags; uintptr_t msfd_lastbp; boolean_t (*msfd_callcheck)(uintptr_t); char *msfd_buf; size_t msfd_buflen; } mdb_stack_frame_data_t; mdb_stack_frame_hdl_t * mdb_stack_frame_init(mdb_tgt_t *tgt, uint_t arglim, mdb_stack_frame_flags_t flags) { mdb_stack_frame_data_t *data; data = mdb_alloc(sizeof (*data), UM_SLEEP | UM_GC); if (data == NULL) return (NULL); data->msfd_tgt = tgt; data->msfd_arglim = arglim; data->msfd_flags = flags; data->msfd_lastbp = 0; data->msfd_buf = NULL; data->msfd_buflen = 0; return (data); } uint_t mdb_stack_frame_arglim(mdb_stack_frame_hdl_t *datap) { mdb_stack_frame_data_t *data = datap; return (data->msfd_arglim); } void mdb_stack_frame_flags_set(mdb_stack_frame_hdl_t *datap, mdb_stack_frame_flags_t flags) { mdb_stack_frame_data_t *data = datap; ASSERT((flags & ~MSF_ALL) == 0); data->msfd_flags |= flags; } static char * mdb_stack_typename(mdb_ctf_id_t id, char **bufp, size_t *lenp) { char *buf = *bufp; size_t len = *lenp; ssize_t newlen; if (mdb_ctf_type_name(id, buf, len) != NULL) return (buf); /* * Retrieve the buffer size required to store this type. */ newlen = mdb_ctf_type_lname(id, NULL, 0) + 1; /* * To avoid reallocations in most cases, we always allocate at least * space for 32 characters and the NUL terminator. This will * accommodate most types. */ newlen = MAX(newlen, 33); if (newlen > len) { char *newbuf = mdb_alloc(newlen, UM_SLEEP | UM_GC); if (newbuf == NULL) return (NULL); mdb_free(buf, len); *bufp = newbuf; *lenp = newlen; return (mdb_ctf_type_name(id, newbuf, newlen)); } return (NULL); } void mdb_stack_frame(mdb_stack_frame_hdl_t *datap, uintptr_t pc, uintptr_t bp, uint_t argc, const long *argv) { mdb_stack_frame_data_t *data = datap; uint_t nargc = MIN(argc, data->msfd_arglim); mdb_ctf_id_t argtypes[nargc]; mdb_ctf_funcinfo_t mcfi; boolean_t ctf; mdb_syminfo_t msi; uintptr_t npc; GElf_Sym sym; uint_t i; int ret; ctf = B_FALSE; npc = pc; ret = mdb_tgt_lookup_by_addr(data->msfd_tgt, pc, MDB_TGT_SYM_FUZZY, NULL, 0, &sym, &msi); if (ret != 0 || sym.st_value == pc) { /* * One of two things is going on here. Either: * * - this address is not covered by a symbol, or * - there is a symbol but our address points directly to the * start of it. * * Both cases can arise when the return address is from a call * to a function that the compiler knows will never return. In * these cases the compiler may elide the caller’s epilogue, * leaving the return address pointing just past the end of the * callee; either into the next function or into padding * between functions. * * If the previous address is covered by a symbol, we use that * symbol instead and mark it as approximate with a tilde (~) * in the output. The platform must provide a callback that * uses heuristics to to determine whether the preceding * instruction could plausibly represent a function call that * would result in the current return address. For example, an * unconditional jump is typically not valid as it would not * preserve the return address. */ if (pc > 0) { ret = mdb_tgt_lookup_by_addr(data->msfd_tgt, pc - 1, MDB_TGT_SYM_FUZZY, NULL, 0, &sym, &msi); if (ret == 0 && mdb_isa_prev_callcheck(pc)) npc = pc - 1; } } if (ret == 0 && (data->msfd_flags & MSF_TYPES)) { if (mdb_ctf_func_info(&sym, &msi, &mcfi) == 0) ctf = B_TRUE; } if (data->msfd_flags & MSF_SIZES) { if (data->msfd_lastbp != 0) mdb_printf("[%4lr] ", bp - data->msfd_lastbp); else mdb_printf("%7s", ""); data->msfd_lastbp = bp; } if (data->msfd_flags & MSF_VERBOSE) mdb_printf("%0?lr ", bp); if (ctf) { if (mdb_stack_typename(mcfi.mtf_return, &data->msfd_buf, &data->msfd_buflen) != NULL) { mdb_printf("%s ", data->msfd_buf); } } if (data->msfd_flags & MSF_ADDR) { mdb_printf("%0?lr(", pc); } else { if (npc != pc) mdb_printf("~"); mdb_printf("%a(", npc); } if (ctf && mdb_ctf_func_args(&mcfi, nargc, argtypes) != 0) ctf = B_FALSE; for (i = 0; i < nargc; i++) { if (i > 0) mdb_printf(", "); if (ctf && mdb_stack_typename(argtypes[i], &data->msfd_buf, &data->msfd_buflen) != NULL) { const char *type = data->msfd_buf; switch (mdb_ctf_type_kind(argtypes[i])) { case CTF_K_POINTER: if (argv[i] == 0) mdb_printf("(%s)NULL", type); else mdb_printf("(%s)%lr", type, argv[i]); break; case CTF_K_ENUM: { const char *cp; cp = mdb_ctf_enum_name(argtypes[i], argv[i]); if (cp != NULL) mdb_printf("(%s)%s", type, cp); else mdb_printf("(%s)%lr", type, argv[i]); break; } default: mdb_printf("(%s)%lr", type, argv[i]); } } else { mdb_printf("%lr", argv[i]); } } mdb_printf(")\n"); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ #ifndef _MDB_STACK_H #define _MDB_STACK_H #include #include #ifdef __cplusplus extern "C" { #endif typedef void mdb_stack_frame_hdl_t; typedef enum { MSF_VERBOSE = 1 << 0, MSF_TYPES = 1 << 1, MSF_SIZES = 1 << 2, MSF_ADDR = 1 << 3 } mdb_stack_frame_flags_t; #define MSF_ALL (MSF_VERBOSE|MSF_TYPES|MSF_SIZES|MSF_ADDR) extern mdb_stack_frame_hdl_t *mdb_stack_frame_init(mdb_tgt_t *, uint_t, mdb_stack_frame_flags_t); extern void mdb_stack_frame(mdb_stack_frame_hdl_t *, uintptr_t, uintptr_t, uint_t, const long *); extern uint_t mdb_stack_frame_arglim(mdb_stack_frame_hdl_t *); extern void mdb_stack_frame_flags_set(mdb_stack_frame_hdl_t *, mdb_stack_frame_flags_t); #ifdef __cplusplus } #endif #endif /* _MDB_STACK_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include /* * Post-processing routine for econvert and qeconvert. This function is * called by both doubletos() and longdoubletos() below. */ static const char * fptos(const char *p, char *buf, size_t buflen, int decpt, int sign, char expchr) { char *q = buf; *q++ = sign ? '-' : '+'; /* * If the initial character is not a digit, the result is a special * identifier such as "NaN" or "Inf"; just copy it verbatim. */ if (*p < '0' || *p > '9') { (void) strncpy(q, p, buflen); buf[buflen - 1] = '\0'; return (buf); } *q++ = *p++; *q++ = '.'; (void) strcpy(q, p); q += strlen(q); *q++ = expchr; if (--decpt < 0) { decpt = -decpt; *q++ = '-'; } else *q++ = '+'; if (decpt < 10) *q++ = '0'; (void) strcpy(q, numtostr((uint_t)decpt, 10, 0)); return (buf); } /* * Convert the specified double to a string, and return a pointer to a static * buffer containing the string value. The double is converted using the * same formatting conventions as sprintf(buf, "%+.*e", precision, d). The * expchr parameter specifies the character used to denote the exponent, * and is usually 'e' or 'E'. */ const char * doubletos(double d, int precision, char expchr) { static char buf[DECIMAL_STRING_LENGTH]; char digits[DECIMAL_STRING_LENGTH]; int decpt, sign; char *p; p = econvert(d, precision + 1, &decpt, &sign, digits); return (fptos(p, buf, sizeof (buf), decpt, sign, expchr)); } /* * Same as doubletos(), but for long doubles (quad precision floating point). */ const char * longdoubletos(long double *ldp, int precision, char expchr) { static char buf[DECIMAL_STRING_LENGTH]; char digits[DECIMAL_STRING_LENGTH]; int decpt, sign; char *p; p = qeconvert(ldp, precision + 1, &decpt, &sign, digits); return (fptos(p, buf, sizeof (buf), decpt, sign, expchr)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_STDLIB_H #define _MDB_STDLIB_H #include #include #ifdef __cplusplus extern "C" { #endif extern const char *longdoubletos(long double *, int, char); extern const char *doubletos(double, int, char); extern const char *timetos(const time_t *); #ifdef __cplusplus } #endif #endif /* _MDB_STDLIB_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2021 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include /* * Convert the specified integer value to a string represented in the given * base. The flags parameter is a bitfield of the formatting flags defined in * mdb_string.h. A pointer to a static conversion buffer is returned. */ const char * numtostr(uintmax_t uvalue, int base, uint_t flags) { static const char ldigits[] = "0123456789abcdef"; static const char udigits[] = "0123456789ABCDEF"; static char buf[68]; /* Enough for ULLONG_MAX in binary plus prefixes */ const char *digits = (flags & NTOS_UPCASE) ? udigits : ldigits; int i = sizeof (buf); intmax_t value = (intmax_t)uvalue; int neg = (flags & NTOS_UNSIGNED) == 0 && value < 0; uintmax_t rem = neg ? -value : value; buf[--i] = 0; do { buf[--i] = digits[rem % base]; rem /= base; } while (rem != 0); if (flags & NTOS_SHOWBASE) { uintmax_t lim; char c = 0; switch (base) { case 2: lim = 1; c = 'i'; break; case 8: lim = 7; c = 'o'; break; case 10: lim = 9; c = 't'; break; case 16: lim = 9; c = 'x'; break; } if (c != 0 && uvalue > lim) { buf[--i] = c; buf[--i] = '0'; } } if (neg) buf[--i] = '-'; else if (flags & NTOS_SIGNPOS) buf[--i] = '+'; return ((const char *)(&buf[i])); } #define CTOI(x) (((x) >= '0' && (x) <= '9') ? (x) - '0' : \ ((x) >= 'a' && (x) <= 'z') ? (x) + 10 - 'a' : (x) + 10 - 'A') /* * Convert a string to an unsigned integer value using the specified base. * In the event of overflow or an invalid character, we generate an * error message and longjmp back to the main loop using yyerror(). */ uintmax_t mdb_strtonum(const char *s, int base) { uintmax_t multmax = (uintmax_t)ULLONG_MAX / (uintmax_t)(uint_t)base; uintmax_t val = 0; int c, i, neg = 0; switch (c = *s) { case '-': neg++; /*FALLTHRU*/ case '+': c = *++s; } if (c == '\0') goto done; if ((val = CTOI(c)) >= base) yyerror("digit '%c' is invalid in current base\n", c); for (c = *++s; c != '\0'; c = *++s) { if (val > multmax) goto oflow; if (c == '_') continue; if ((i = CTOI(c)) >= base) yyerror("digit '%c' is invalid in current base\n", c); val *= base; if ((uintmax_t)ULLONG_MAX - val < (uintmax_t)i) goto oflow; val += i; } done: return (neg ? -val : val); oflow: yyerror("specified value exceeds maximum immediate value\n"); return ((uintmax_t)ULLONG_MAX); } /* * Quick string to unsigned long conversion function. This function performs * no overflow checking and is only meant for internal mdb use. It allows * the caller to specify the length of the string in bytes and a base. */ ulong_t strntoul(const char *s, size_t nbytes, int base) { ulong_t n; int c; for (n = 0; nbytes != 0 && (c = *s) != '\0'; s++, nbytes--) n = n * base + CTOI(c); return (n); } /* * Return a boolean value indicating whether or not a string consists * solely of characters which are digits 0..9. */ int strisnum(const char *s) { for (; *s != '\0'; s++) { if (*s < '0' || *s > '9') return (0); } return (1); } /* * Return a boolean value indicating whether or not a string contains a * number. The number may be in the current radix, or it may have an * explicit radix qualifier. The number will be validated against the * legal characters for the given radix. */ int strisbasenum(const char *s) { char valid[] = "0123456789aAbBcCdDeEfF"; int radix = mdb.m_radix; if (s[0] == '0') { switch (s[1]) { case 'I': case 'i': radix = 2; s += 2; break; case 'O': case 'o': radix = 8; s += 2; break; case 'T': case 't': radix = 10; s += 2; break; case 'x': case 'X': radix = 16; s += 2; break; } } /* limit `valid' to the digits valid for this base */ valid[radix > 10 ? 10 + (radix - 10) * 2 : radix] = '\0'; do { if (!strchr(valid, *s)) return (0); } while (*++s != '\0'); return (1); } /* * Quick string to integer (base 10) conversion function. This performs * no overflow checking and is only meant for internal mdb use. */ int strtoi(const char *s) { int c, n; for (n = 0; (c = *s) >= '0' && c <= '9'; s++) n = n * 10 + c - '0'; return (n); } /* * Create a copy of string s using the mdb allocator interface. */ char * strdup(const char *s) { char *s1 = mdb_alloc(strlen(s) + 1, UM_SLEEP); (void) strcpy(s1, s); return (s1); } /* * Create a copy of string s, but only duplicate the first n bytes. */ char * strndup(const char *s, size_t n) { char *s2 = mdb_alloc(n + 1, UM_SLEEP); (void) strncpy(s2, s, n); s2[n] = '\0'; return (s2); } /* * Convenience routine for freeing strings. */ void strfree(char *s) { mdb_free(s, strlen(s) + 1); } /* * Transform string s inline, converting each embedded C escape sequence string * to the corresponding character. For example, the substring "\n" is replaced * by an inline '\n' character. The length of the resulting string is returned. */ size_t stresc2chr(char *s) { char *p, *q, c; int esc = 0; for (p = q = s; (c = *p) != '\0'; p++) { if (esc) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c -= '0'; p++; if (*p >= '0' && *p <= '7') { c = c * 8 + *p++ - '0'; if (*p >= '0' && *p <= '7') c = c * 8 + *p - '0'; else p--; } else p--; *q++ = c; break; case 'a': *q++ = '\a'; break; case 'b': *q++ = '\b'; break; case 'f': *q++ = '\f'; break; case 'n': *q++ = '\n'; break; case 'r': *q++ = '\r'; break; case 't': *q++ = '\t'; break; case 'v': *q++ = '\v'; break; case '"': case '\\': *q++ = c; break; default: *q++ = '\\'; *q++ = c; } esc = 0; } else { if ((esc = c == '\\') == 0) *q++ = c; } } *q = '\0'; return ((size_t)(q - s)); } /* * Create a copy of string s in which certain unprintable or special characters * have been converted to the string representation of their C escape sequence. * For example, the newline character is expanded to the string "\n". */ char * strchr2esc(const char *s, size_t n) { const char *p; char *q, *s2, c; size_t addl = 0; for (p = s; p < s + n; p++) { switch (c = *p) { case '\0': case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': case '"': case '\\': addl++; /* 1 add'l char needed to follow \ */ break; case ' ': break; default: if (c < '!' || c > '~') addl += 3; /* 3 add'l chars following \ */ } } s2 = mdb_alloc(n + addl + 1, UM_SLEEP); for (p = s, q = s2; p < s + n; p++) { switch (c = *p) { case '\0': *q++ = '\\'; *q++ = '0'; break; case '\a': *q++ = '\\'; *q++ = 'a'; break; case '\b': *q++ = '\\'; *q++ = 'b'; break; case '\f': *q++ = '\\'; *q++ = 'f'; break; case '\n': *q++ = '\\'; *q++ = 'n'; break; case '\r': *q++ = '\\'; *q++ = 'r'; break; case '\t': *q++ = '\\'; *q++ = 't'; break; case '\v': *q++ = '\\'; *q++ = 'v'; break; case '"': *q++ = '\\'; *q++ = '"'; break; case '\\': *q++ = '\\'; *q++ = '\\'; break; case ' ': *q++ = c; break; default: if (c < '!' || c > '~') { *q++ = '\\'; *q++ = ((c >> 6) & 3) + '0'; *q++ = ((c >> 3) & 7) + '0'; *q++ = (c & 7) + '0'; } else *q++ = c; } } *q = '\0'; return (s2); } /* * Create a copy of string s in which certain unprintable or special characters * have been converted to an odd representation of their escape sequence. * This algorithm is the old adb convention for representing such sequences. */ char * strchr2adb(const char *s, size_t n) { size_t addl = 0; const char *p; char *q, *s2; for (p = s; p < s + n; p++) { char c = *p & CHAR_MAX; if (c < ' ' || c == CHAR_MAX) addl++; /* 1 add'l char needed for "^" */ } s2 = mdb_alloc(n + addl + 1, UM_SLEEP); for (p = s, q = s2; p < s + n; p++) { char c = *p & CHAR_MAX; if (c == CHAR_MAX) { *q++ = '^'; *q++ = '?'; } else if (c < ' ') { *q++ = '^'; *q++ = c + '@'; } else *q++ = c; } *q = '\0'; return (s2); } /* * Same as strchr, but we only search the first n characters */ char * strnchr(const char *s, int c, size_t n) { int i = 0; for (i = 0; i < n; i++) { if (*(s + i) == (char)c) return ((char *)(s + i)); } return (NULL); } /* * Split the string s at the first occurrence of character c. This character * is replaced by \0, and a pointer to the remainder of the string is returned. */ char * strsplit(char *s, char c) { char *p; if ((p = strchr(s, c)) == NULL) return (NULL); *p++ = '\0'; return (p); } /* * Same as strsplit, but split from the last occurrence of character c. */ char * strrsplit(char *s, char c) { char *p; if ((p = strrchr(s, c)) == NULL) return (NULL); *p++ = '\0'; return (p); } /* * Return the address of the first occurrence of any character from s2 * in the string s1, or NULL if none exists. This is similar to libc's * strpbrk, but we add a third parameter to limit the search to the * specified number of bytes in s1, or a \0 character, whichever is * encountered first. */ const char * strnpbrk(const char *s1, const char *s2, size_t nbytes) { const char *p; if (nbytes == 0) return (NULL); do { for (p = s2; *p != '\0' && *p != *s1; p++) continue; if (*p != '\0') return (s1); } while (--nbytes != 0 && *s1++ != '\0'); return (NULL); } /* * Abbreviate a string if it meets or exceeds the specified length, including * the terminating null character. The string is abbreviated by replacing the * last four characters with " ...". strabbr is useful in constructs such as * this one, where nbytes = sizeof (buf): * * if (mdb_snprintf(buf, nbytes, "%s %d %c", ...) >= nbytes) * (void) strabbr(buf, nbytes); * * No modifications are made if nbytes is too small to hold the suffix itself. */ char * strabbr(char *s, size_t nbytes) { static const char suffix[] = " ..."; if (nbytes > sizeof (suffix) && strlen(s) >= nbytes - 1) (void) strcpy(&s[nbytes - sizeof (suffix)], suffix); return (s); } /* * Return the basename (name after final /) of the given string. We use * strbasename rather than basename to avoid conflicting with libgen.h's * non-const function prototype. */ const char * strbasename(const char *s) { const char *p = strrchr(s, '/'); if (p == NULL) return (s); return (++p); } /* * Return the directory name (name prior to the final /) of the given string. * The string itself is modified. */ char * strdirname(char *s) { static char slash[] = "/"; static char dot[] = "."; char *p; if (s == NULL || *s == '\0') return (dot); for (p = s + strlen(s); p != s && *--p == '/'; ) continue; if (p == s && *p == '/') return (slash); while (p != s) { if (*--p == '/') { while (*p == '/' && p != s) p--; *++p = '\0'; return (s); } } return (dot); } /* * Return a pointer to the first character in the string that makes it an * invalid identifer (i.e. incompatible with the mdb syntax), or NULL if * the string is a valid identifier. */ const char * strbadid(const char *s) { return (strpbrk(s, "#%^&*-+=,:$/\\?<>;|!`'\"[]\n\t() {}")); } /* * Return a boolean value indicating if the given string consists solely * of printable ASCII characters terminated by \0. */ int strisprint(const char *s) { for (; *s != '\0'; s++) { if (*s < ' ' || *s > '~') return (0); } return (1); } /* * This is a near direct copy of the inet_ntop() code in * uts/common/inet/ip/ipv6.c, duplicated here for kmdb's sake. */ static void convert2ascii(char *buf, const in6_addr_t *addr) { int hexdigits; int head_zero = 0; int tail_zero = 0; /* tempbuf must be big enough to hold ffff:\0 */ char tempbuf[6]; char *ptr; uint16_t *addr_component, host_component; size_t len; int first = FALSE; int med_zero = FALSE; int end_zero = FALSE; addr_component = (uint16_t *)addr; ptr = buf; /* First count if trailing zeroes higher in number */ for (hexdigits = 0; hexdigits < 8; hexdigits++) { if (*addr_component == 0) { if (hexdigits < 4) head_zero++; else tail_zero++; } addr_component++; } addr_component = (uint16_t *)addr; if (tail_zero > head_zero && (head_zero + tail_zero) != 7) end_zero = TRUE; for (hexdigits = 0; hexdigits < 8; hexdigits++) { /* if entry is a 0 */ if (*addr_component == 0) { if (!first && *(addr_component + 1) == 0) { if (end_zero && (hexdigits < 4)) { *ptr++ = '0'; *ptr++ = ':'; } else { if (hexdigits == 0) *ptr++ = ':'; /* add another */ *ptr++ = ':'; first = TRUE; med_zero = TRUE; } } else if (first && med_zero) { if (hexdigits == 7) *ptr++ = ':'; addr_component++; continue; } else { *ptr++ = '0'; *ptr++ = ':'; } addr_component++; continue; } if (med_zero) med_zero = FALSE; tempbuf[0] = '\0'; mdb_nhconvert(&host_component, addr_component, sizeof (uint16_t)); (void) mdb_snprintf(tempbuf, sizeof (tempbuf), "%x:", host_component & 0xffff); len = strlen(tempbuf); bcopy(tempbuf, ptr, len); ptr = ptr + len; addr_component++; } *--ptr = '\0'; } char * mdb_inet_ntop(int af, const void *addr, char *buf, size_t buflen) { in6_addr_t *v6addr; uchar_t *v4addr; char *caddr; #define UC(b) (((int)b) & 0xff) switch (af) { case AF_INET: ASSERT(buflen >= INET_ADDRSTRLEN); v4addr = (uchar_t *)addr; (void) mdb_snprintf(buf, buflen, "%d.%d.%d.%d", UC(v4addr[0]), UC(v4addr[1]), UC(v4addr[2]), UC(v4addr[3])); return (buf); case AF_INET6: ASSERT(buflen >= INET6_ADDRSTRLEN); v6addr = (in6_addr_t *)addr; if (IN6_IS_ADDR_V4MAPPED(v6addr)) { caddr = (char *)addr; (void) mdb_snprintf(buf, buflen, "::ffff:%d.%d.%d.%d", UC(caddr[12]), UC(caddr[13]), UC(caddr[14]), UC(caddr[15])); } else if (IN6_IS_ADDR_V4COMPAT(v6addr)) { caddr = (char *)addr; (void) mdb_snprintf(buf, buflen, "::%d.%d.%d.%d", UC(caddr[12]), UC(caddr[13]), UC(caddr[14]), UC(caddr[15])); } else if (IN6_IS_ADDR_UNSPECIFIED(v6addr)) { (void) mdb_snprintf(buf, buflen, "::"); } else { convert2ascii(buf, v6addr); } return (buf); } #undef UC return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2022 Oxide Computer Company */ #ifndef _MDB_STRING_H #define _MDB_STRING_H #include #include #ifdef __cplusplus extern "C" { #endif #define NTOS_UPCASE 0x1 /* Use upper-case hexadecimal digits */ #define NTOS_UNSIGNED 0x2 /* Value is meant to be unsigned */ #define NTOS_SIGNPOS 0x4 /* Prefix positive values with sign '+' */ #define NTOS_SHOWBASE 0x8 /* Show base under appropriate circumstances */ extern const char *numtostr(uintmax_t, int, uint_t); extern uintmax_t mdb_strtonum(const char *, int); extern ulong_t strntoul(const char *, size_t, int); extern int strisnum(const char *); extern int strisbasenum(const char *); extern int strtoi(const char *); extern char *strdup(const char *); extern char *strndup(const char *, size_t); extern void strfree(char *); extern size_t stresc2chr(char *); extern char *strchr2esc(const char *, size_t); extern char *strchr2adb(const char *, size_t); extern char *strnchr(const char *, int, size_t); extern char *strsplit(char *, char); extern char *strrsplit(char *, char); extern const char *strnpbrk(const char *, const char *, size_t); extern char *strabbr(char *, size_t); extern const char *strbasename(const char *); extern char *strdirname(char *); extern const char *strbadid(const char *); extern int strisprint(const char *); extern char *mdb_inet_ntop(int, const void *, char *, size_t); /* * This is a private version of mdb's strtoull that allows us to modify the * behavior. We expect this will get more nuanced when C23 lands and therefore * it is not currently part of the module API. */ typedef enum { MDB_STRTOULL_F_BASE_C = 1 << 0 /* Default to ISO C's radix */ } mdb_strtoull_flags_t; extern u_longlong_t mdb_strtoullx(const char *, mdb_strtoull_flags_t); #ifdef __cplusplus } #endif #endif /* _MDB_STRING_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1997-2001 by Sun Microsystems, Inc. * All rights reserved. */ /* * String I/O Backend * * Simple backend to provide the ability to perform i/o reads from an in-memory * string. This allows us to mdb_eval() a string -- by creating an i/o object * out of it, we can then pass it to the parser as stdin. */ #include #include #include #include typedef struct str_data { char *str_base; /* Pointer to private copy of string */ char *str_ptr; /* Current seek pointer */ size_t str_len; /* Length of string */ } str_data_t; static ssize_t strio_read(mdb_io_t *io, void *buf, size_t nbytes) { str_data_t *sd = io->io_data; size_t left = sd->str_base + sd->str_len - sd->str_ptr; if (left != 0) { size_t obytes = nbytes < left ? nbytes : left; (void) strncpy(buf, sd->str_ptr, obytes); sd->str_ptr += obytes; return (obytes); } return (0); /* At end of string: return EOF */ } static off64_t strio_seek(mdb_io_t *io, off64_t offset, int whence) { str_data_t *sd = io->io_data; char *nptr; if (io->io_next != NULL) return (IOP_SEEK(io->io_next, offset, whence)); switch (whence) { case SEEK_SET: nptr = sd->str_base + offset; break; case SEEK_CUR: nptr = sd->str_ptr + offset; break; case SEEK_END: nptr = sd->str_base + sd->str_len + offset; break; default: return (set_errno(EINVAL)); } if (nptr < sd->str_base || nptr > sd->str_ptr + sd->str_len) return (set_errno(EINVAL)); sd->str_ptr = nptr; return ((off64_t)(nptr - sd->str_base)); } static void strio_close(mdb_io_t *io) { str_data_t *sd = io->io_data; strfree(sd->str_base); mdb_free(sd, sizeof (str_data_t)); } static const char * strio_name(mdb_io_t *io) { if (io->io_next != NULL) return (IOP_NAME(io->io_next)); return ("(string)"); } static const mdb_io_ops_t strio_ops = { .io_read = strio_read, .io_write = no_io_write, .io_seek = strio_seek, .io_ctl = no_io_ctl, .io_close = strio_close, .io_name = strio_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume, }; mdb_io_t * mdb_strio_create(const char *s) { mdb_io_t *io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP); str_data_t *sd = mdb_alloc(sizeof (str_data_t), UM_SLEEP); /* * Our parser expects each command to end with '\n' or ';'. To * simplify things for the caller, we append a trailing newline * so the argvec string can be passed directly sans modifications. */ sd->str_len = strlen(s) + 1; sd->str_base = strndup(s, sd->str_len); (void) strcat(sd->str_base, "\n"); sd->str_ptr = sd->str_base; io->io_ops = &strio_ops; io->io_data = sd; io->io_next = NULL; io->io_refcnt = 0; return (io); } int mdb_iob_isastr(mdb_iob_t *iob) { mdb_io_t *io; for (io = iob->iob_iop; io != NULL; io = io->io_next) { if (io->io_ops == &strio_ops) return (1); } return (0); } /* * 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 (c) 2013 by Delphix. All rights reserved. * Copyright (c) 2018, Joyent, Inc. * Copyright (c) 2013 Josef 'Jeff' Sipek */ /* * This file contains all of the interfaces for mdb's tab completion engine. * Currently some interfaces are private to mdb and its internal implementation, * those are in mdb_tab.h. Other pieces are public interfaces. Those are in * mdb_modapi.h. * * Memory allocations in tab completion context have to be done very carefully. * We need to think of ourselves as the same as any other command that is being * executed by the user, which means we must use UM_GC to handle being * interrupted. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * There may be another way to do this, but this works well enough. */ #define COMMAND_SEPARATOR "::" /* * find_command_start -- * * Given a buffer find the start of the last command. */ static char * tab_find_command_start(char *buf) { char *offset = strstr(buf, COMMAND_SEPARATOR); if (offset == NULL) return (NULL); for (;;) { char *next = strstr(offset + strlen(COMMAND_SEPARATOR), COMMAND_SEPARATOR); if (next == NULL) { return (offset); } offset = next; } } /* * get_dcmd -- * * Given a buffer containing a command and its argument return * the name of the command and the offset in the buffer where * the command arguments start. * * Note: This will modify the buffer. */ char * tab_get_dcmd(char *buf, char **args, uint_t *flags) { char *start = buf + strlen(COMMAND_SEPARATOR); char *separator = start; const char *end = buf + strlen(buf); uint_t space = 0; while (separator < end && !isspace(*separator)) separator++; if (separator == end) { *args = NULL; } else { if (isspace(*separator)) space = 1; *separator++ = '\0'; *args = separator; } if (space) *flags |= DCMD_TAB_SPACE; return (start); } /* * count_args -- * * Given a buffer containing dmcd arguments return the total number * of arguments. * * While parsing arguments we need to keep track of whether or not the last * arguments ends with a trailing space. */ static int tab_count_args(const char *input, uint_t *flags) { const char *index; int argc = 0; uint_t space = *flags & DCMD_TAB_SPACE; index = input; while (*index != '\0') { while (*index != '\0' && isspace(*index)) { index++; space = 1; } if (*index != '\0' && !isspace(*index)) { argc++; space = 0; while (*index != '\0' && !isspace (*index)) { index++; } } } if (space) *flags |= DCMD_TAB_SPACE; else *flags &= ~DCMD_TAB_SPACE; return (argc); } /* * copy_args -- * * Given a buffer containing dcmd arguments and an array of mdb_arg_t's * initialize the string value of each mdb_arg_t. * * Note: This will modify the buffer. */ static int tab_copy_args(char *input, int argc, mdb_arg_t *argv) { int i = 0; char *index; index = input; while (*index) { while (*index && isspace(*index)) { index++; } if (*index && !isspace(*index)) { char *end = index; while (*end && !isspace(*end)) { end++; } if (*end) { *end++ = '\0'; } argv[i].a_type = MDB_TYPE_STRING; argv[i].a_un.a_str = index; index = end; i++; } } if (i != argc) return (-1); return (0); } /* * parse-buf -- * * Parse the given buffer and return the specified dcmd, the number * of arguments, and array of mdb_arg_t containing the argument * values. * * Note: this will modify the specified buffer. Caller is responisble * for freeing argvp. */ static int tab_parse_buf(char *buf, char **dcmdp, int *argcp, mdb_arg_t **argvp, uint_t *flags) { char *data = tab_find_command_start(buf); char *args_data = NULL; char *dcmd = NULL; int argc = 0; mdb_arg_t *argv = NULL; if (data == NULL) { return (-1); } dcmd = tab_get_dcmd(data, &args_data, flags); if (dcmd == NULL) { return (-1); } if (args_data != NULL) { argc = tab_count_args(args_data, flags); if (argc != 0) { argv = mdb_alloc(sizeof (mdb_arg_t) * argc, UM_SLEEP | UM_GC); if (tab_copy_args(args_data, argc, argv) == -1) return (-1); } } *dcmdp = dcmd; *argcp = argc; *argvp = argv; return (0); } /* * tab_command -- * * This function is executed anytime a tab is entered. It checks * the current buffer to determine if there is a valid dmcd, * if that dcmd has a tab completion handler it will invoke it. * * This function returns the string (if any) that should be added to the * existing buffer to complete it. */ int mdb_tab_command(mdb_tab_cookie_t *mcp, const char *buf) { char *data; char *dcmd = NULL; int argc = 0; mdb_arg_t *argv = NULL; int ret = 0; mdb_idcmd_t *cp; uint_t flags = 0; /* * Parsing the command and arguments will modify the buffer * (replacing spaces with \0), so make a copy of the specified * buffer first. */ data = mdb_alloc(strlen(buf) + 1, UM_SLEEP | UM_GC); (void) strcpy(data, buf); /* * Get the specified dcmd and arguments from the buffer. */ ret = tab_parse_buf(data, &dcmd, &argc, &argv, &flags); /* * Match against global symbols if the input is not a dcmd */ if (ret != 0) { (void) mdb_tab_complete_global(mcp, buf); goto out; } /* * Check to see if the buffer contains a valid dcmd */ cp = mdb_dcmd_lookup(dcmd); /* * When argc is zero it indicates that we are trying to tab complete * a dcmd or a global symbol. Note, that if there isn't the start of * a dcmd, i.e. ::, then we will have already bailed in the call to * tab_parse_buf. */ if (cp == NULL && argc != 0) { goto out; } /* * Invoke the command specific tab completion handler or the built in * dcmd one if there is no dcmd. */ if (cp == NULL) (void) mdb_tab_complete_dcmd(mcp, dcmd); else mdb_call_tab(cp, mcp, flags, argc, argv); out: return (mdb_tab_size(mcp)); } static int tab_complete_dcmd(mdb_var_t *v, void *arg) { mdb_idcmd_t *idcp = mdb_nv_get_cookie(mdb_nv_get_cookie(v)); mdb_tab_cookie_t *mcp = (mdb_tab_cookie_t *)arg; /* * The way that mdb is implemented, even commands like $C will show up * here. As such, we don't want to match anything that doesn't start * with an alpha or number. While nothing currently appears (via a * cursory search with mdb -k) to start with a capital letter or a * number, we'll support them anyways. */ if (!isalnum(idcp->idc_name[0])) return (0); mdb_tab_insert(mcp, idcp->idc_name); return (0); } int mdb_tab_complete_dcmd(mdb_tab_cookie_t *mcp, const char *dcmd) { if (dcmd != NULL) mdb_tab_setmbase(mcp, dcmd); mdb_nv_sort_iter(&mdb.m_dcmds, tab_complete_dcmd, mcp, UM_GC | UM_SLEEP); return (0); } static int tab_complete_walker(mdb_var_t *v, void *arg) { mdb_iwalker_t *iwp = mdb_nv_get_cookie(mdb_nv_get_cookie(v)); mdb_tab_cookie_t *mcp = arg; mdb_tab_insert(mcp, iwp->iwlk_name); return (0); } int mdb_tab_complete_walker(mdb_tab_cookie_t *mcp, const char *walker) { if (walker != NULL) mdb_tab_setmbase(mcp, walker); mdb_nv_sort_iter(&mdb.m_walkers, tab_complete_walker, mcp, UM_GC | UM_SLEEP); return (0); } mdb_tab_cookie_t * mdb_tab_init(void) { mdb_tab_cookie_t *mcp; mcp = mdb_zalloc(sizeof (mdb_tab_cookie_t), UM_SLEEP | UM_GC); (void) mdb_nv_create(&mcp->mtc_nv, UM_SLEEP | UM_GC); return (mcp); } size_t mdb_tab_size(mdb_tab_cookie_t *mcp) { return (mdb_nv_size(&mcp->mtc_nv)); } /* * Determine whether the specified name is a valid tab completion for * the given command. If the name is a valid tab completion then * it will be saved in the mdb_tab_cookie_t. */ void mdb_tab_insert(mdb_tab_cookie_t *mcp, const char *name) { size_t matches, index; mdb_var_t *v; /* * If we have a match set, then we want to verify that we actually match * it. */ if (strncmp(name, mcp->mtc_base, strlen(mcp->mtc_base)) != 0) return; v = mdb_nv_lookup(&mcp->mtc_nv, name); if (v != NULL) return; (void) mdb_nv_insert(&mcp->mtc_nv, name, NULL, 0, MDB_NV_RDONLY); matches = mdb_tab_size(mcp); if (matches == 1) { (void) strlcpy(mcp->mtc_match, name, MDB_SYM_NAMLEN); } else { index = 0; while (mcp->mtc_match[index] && mcp->mtc_match[index] == name[index]) index++; mcp->mtc_match[index] = '\0'; } } /*ARGSUSED*/ static int tab_print_cb(mdb_var_t *v, void *ignored) { mdb_printf("%s\n", mdb_nv_get_name(v)); return (0); } void mdb_tab_print(mdb_tab_cookie_t *mcp) { mdb_nv_sort_iter(&mcp->mtc_nv, tab_print_cb, NULL, UM_SLEEP | UM_GC); } const char * mdb_tab_match(mdb_tab_cookie_t *mcp) { return (mcp->mtc_match + strlen(mcp->mtc_base)); } void mdb_tab_setmbase(mdb_tab_cookie_t *mcp, const char *base) { (void) strlcpy(mcp->mtc_base, base, MDB_SYM_NAMLEN); } /* * This function is currently a no-op due to the fact that we have to GC because * we're in command context. */ /*ARGSUSED*/ void mdb_tab_fini(mdb_tab_cookie_t *mcp) { } /*ARGSUSED*/ static int tab_complete_global(void *arg, const GElf_Sym *sym, const char *name, const mdb_syminfo_t *sip, const char *obj) { mdb_tab_cookie_t *mcp = arg; mdb_tab_insert(mcp, name); return (0); } /* * This function tab completes against all loaded global symbols. */ int mdb_tab_complete_global(mdb_tab_cookie_t *mcp, const char *name) { mdb_tab_setmbase(mcp, name); (void) mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY, MDB_TGT_SYMTAB, MDB_TGT_BIND_ANY | MDB_TGT_TYPE_OBJECT | MDB_TGT_TYPE_FUNC, tab_complete_global, mcp); return (0); } /* * This function takes a ctf id and determines whether or not the associated * type should be considered as a potential match for the given tab * completion command. We verify that the type itself is valid * for completion given the current context of the command, resolve * its actual name, and then pass it off to mdb_tab_insert to determine * if it's an actual match. */ static int tab_complete_type(mdb_ctf_id_t id, void *arg) { int rkind; char buf[MDB_SYM_NAMLEN]; mdb_ctf_id_t rid; mdb_tab_cookie_t *mcp = arg; uint_t flags = (uint_t)(uintptr_t)mcp->mtc_cba; /* * CTF data includes types that mdb commands don't understand. Before * we resolve the actual type prune any entry that is a type we * don't care about. */ switch (mdb_ctf_type_kind(id)) { case CTF_K_CONST: case CTF_K_RESTRICT: case CTF_K_VOLATILE: return (0); } if (mdb_ctf_type_resolve(id, &rid) != 0) return (1); rkind = mdb_ctf_type_kind(rid); if ((flags & MDB_TABC_MEMBERS) && rkind != CTF_K_STRUCT && rkind != CTF_K_UNION) return (0); if ((flags & MDB_TABC_NOPOINT) && rkind == CTF_K_POINTER) return (0); if ((flags & MDB_TABC_NOARRAY) && rkind == CTF_K_ARRAY) return (0); (void) mdb_ctf_type_name(id, buf, sizeof (buf)); mdb_tab_insert(mcp, buf); return (0); } /*ARGSUSED*/ static int mdb_tab_complete_module(void *data, const mdb_map_t *mp, const char *name) { (void) mdb_ctf_type_iter(name, tab_complete_type, data); return (0); } int mdb_tab_complete_type(mdb_tab_cookie_t *mcp, const char *name, uint_t flags) { mdb_tgt_t *t = mdb.m_target; mcp->mtc_cba = (void *)(uintptr_t)flags; if (name != NULL) mdb_tab_setmbase(mcp, name); (void) mdb_tgt_object_iter(t, mdb_tab_complete_module, mcp); (void) mdb_ctf_type_iter(MDB_CTF_SYNTHETIC_ITER, tab_complete_type, mcp); return (0); } /*ARGSUSED*/ static int tab_complete_member(const char *name, mdb_ctf_id_t id, ulong_t off, void *arg) { mdb_tab_cookie_t *mcp = arg; mdb_tab_insert(mcp, name); return (0); } int mdb_tab_complete_member_by_id(mdb_tab_cookie_t *mcp, mdb_ctf_id_t id, const char *member) { if (member != NULL) mdb_tab_setmbase(mcp, member); (void) mdb_ctf_member_iter(id, tab_complete_member, mcp, MDB_CTF_F_ITER_ANON); return (0); } int mdb_tab_complete_member(mdb_tab_cookie_t *mcp, const char *type, const char *member) { mdb_ctf_id_t id; if (mdb_ctf_lookup_by_name(type, &id) != 0) return (-1); return (mdb_tab_complete_member_by_id(mcp, id, member)); } int mdb_tab_complete_mt(mdb_tab_cookie_t *mcp, uint_t flags, int argc, const mdb_arg_t *argv) { char tn[MDB_SYM_NAMLEN]; int ret; if (argc == 0 && !(flags & DCMD_TAB_SPACE)) return (0); if (argc == 0) return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_MEMBERS)); if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0) return (ret); if (argc == 1 && (!(flags & DCMD_TAB_SPACE) || ret == 1)) return (mdb_tab_complete_type(mcp, tn, MDB_TABC_MEMBERS)); if (argc == 1 && (flags & DCMD_TAB_SPACE)) return (mdb_tab_complete_member(mcp, tn, NULL)); if (argc == 2) return (mdb_tab_complete_member(mcp, tn, argv[1].a_un.a_str)); return (0); } /* * This is similar to mdb_print.c's args_to_typename, but it has subtle * differences surrounding how the strings of one element are handled that have * 'struct', 'enum', or 'union' in them and instead works with them for tab * completion purposes. */ int mdb_tab_typename(int *argcp, const mdb_arg_t **argvp, char *buf, size_t len) { int argc = *argcp; const mdb_arg_t *argv = *argvp; if (argc < 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (strcmp(argv->a_un.a_str, "struct") == 0 || strcmp(argv->a_un.a_str, "enum") == 0 || strcmp(argv->a_un.a_str, "union") == 0) { if (argc == 1) { (void) mdb_snprintf(buf, len, "%s ", argv[0].a_un.a_str); return (1); } if (argv[1].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); (void) mdb_snprintf(buf, len, "%s %s", argv[0].a_un.a_str, argv[1].a_un.a_str); *argcp = argc - 1; *argvp = argv + 1; } else { (void) mdb_snprintf(buf, len, "%s", argv[0].a_un.a_str); } return (0); } /* * 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 (c) 2013 by Delphix. All rights reserved. * Copyright (c) 2012 Joyent, Inc. All rights reserved. */ /* * This file contains mdb private tab completion related functions. Public * functions for modules are put into the module API, see mdb_modapi.h. Note * that the mdb_ctf_id_t value is private to mdb and not a part of the module * api, hence it has to stay in here. */ #ifndef _MDB_TAB_H #define _MDB_TAB_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB struct mdb_tab_cookie { mdb_nv_t mtc_nv; char mtc_match[MDB_SYM_NAMLEN]; char mtc_base[MDB_SYM_NAMLEN]; void *mtc_cba; }; extern mdb_tab_cookie_t *mdb_tab_init(void); extern size_t mdb_tab_size(mdb_tab_cookie_t *); extern const char *mdb_tab_match(mdb_tab_cookie_t *); extern void mdb_tab_print(mdb_tab_cookie_t *); extern void mdb_tab_fini(mdb_tab_cookie_t *); extern int mdb_tab_complete_global(mdb_tab_cookie_t *, const char *); extern int mdb_tab_complete_dcmd(mdb_tab_cookie_t *, const char *); extern int mdb_tab_complete_walker(mdb_tab_cookie_t *, const char *); extern int mdb_tab_complete_member_by_id(mdb_tab_cookie_t *, mdb_ctf_id_t, const char *); extern int mdb_tab_command(mdb_tab_cookie_t *, const char *); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_TAB_H */ /* * 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 2018 Joyent, Inc. * Copyright 2024 Oxide Computer Company */ /* * MDB Target Layer * * The *target* is the program being inspected by the debugger. The MDB target * layer provides a set of functions that insulate common debugger code, * including the MDB Module API, from the implementation details of how the * debugger accesses information from a given target. Each target exports a * standard set of properties, including one or more address spaces, one or * more symbol tables, a set of load objects, and a set of threads that can be * examined using the interfaces in . This technique has * been employed successfully in other debuggers, including [1], primarily * to improve portability, although the term "target" often refers to the * encapsulation of architectural or operating system-specific details. The * target abstraction is useful for MDB because it allows us to easily extend * the debugger to examine a variety of different program forms. Primarily, * the target functions validate input arguments and then call an appropriate * function in the target ops vector, defined in . * However, this interface layer provides a very high level of flexibility for * separating the debugger interface from instrumentation details. Experience * has shown this kind of design can facilitate separating out debugger * instrumentation into an external agent [2] and enable the development of * advanced instrumentation frameworks [3]. We want MDB to be an ideal * extensible framework for the development of such applications. * * Aside from a set of wrapper functions, the target layer also provides event * management for targets that represent live executing programs. Our model of * events is also extensible, and is based upon work in [3] and [4]. We define * a *software event* as a state transition in the target program (for example, * the transition of the program counter to a location of interest) that is * observed by the debugger or its agent. A *software event specifier* is a * description of a class of software events that is used by the debugger to * instrument the target so that the corresponding software events can be * observed. In MDB, software event specifiers are represented by the * mdb_sespec_t structure, defined in . As the user, * the internal debugger code, and MDB modules may all wish to observe software * events and receive appropriate notification and callbacks, we do not expose * software event specifiers directly as part of the user interface. Instead, * clients of the target layer request that events be observed by creating * new *virtual event specifiers*. Each virtual specifier is named by a unique * non-zero integer (the VID), and is represented by a mdb_vespec_t structure. * One or more virtual specifiers are then associated with each underlying * software event specifier. This design enforces the constraint that the * target must only insert one set of instrumentation, regardless of how many * times the target layer was asked to trace a given event. For example, if * multiple clients request a breakpoint at a particular address, the virtual * specifiers will map to the same sespec, ensuring that only one breakpoint * trap instruction is actually planted at the given target address. When no * virtual specifiers refer to an sespec, it is no longer needed and can be * removed, along with the corresponding instrumentation. * * The following state transition diagram illustrates the life cycle of a * software event specifier and example transitions: * * cont/ * +--------+ delete +--------+ stop +-------+ * (|( DEAD )|) <------- ( ACTIVE ) <------> ( ARMED ) * +--------+ +--------+ +-------+ * ^ load/unload ^ ^ failure/ | * delete | object / \ reset | failure * | v v | * | +--------+ +-------+ | * +---- ( IDLE ) ( ERR ) <----+ * | +--------+ +-------+ * | | * +------------------------------+ * * The MDB execution control model is based upon the synchronous debugging * model exported by Solaris proc(5). A target program is set running or the * debugger is attached to a running target. On ISTOP (stop on event of * interest), one target thread is selected as the representative. The * algorithm for selecting the representative is target-specific, but we assume * that if an observed software event has occurred, the target will select the * thread that triggered the state transition of interest. The other threads * are stopped in sympathy with the representative as soon as possible. Prior * to continuing the target, we plant our instrumentation, transitioning event * specifiers from the ACTIVE to the ARMED state, and then back again when the * target stops. We then query each active event specifier to learn which ones * are matched, and then invoke the callbacks associated with their vespecs. * If an OS error occurs while attempting to arm or disarm a specifier, the * specifier is transitioned to the ERROR state; we will attempt to arm it * again at the next continue. If no target process is under our control or * if an event is not currently applicable (e.g. a deferred breakpoint on an * object that is not yet loaded), it remains in the IDLE state. The target * implementation should intercept object load events and then transition the * specifier to the ACTIVE state when the corresponding object is loaded. * * To simplify the debugger implementation and allow targets to easily provide * new types of observable events, most of the event specifier management is * done by the target layer. Each software event specifier provides an ops * vector of subroutines that the target layer can call to perform the * various state transitions described above. The target maintains two lists * of mdb_sespec_t's: the t_idle list (IDLE state) and the t_active list * (ACTIVE, ARMED, and ERROR states). Each mdb_sespec_t maintains a list of * associated mdb_vespec_t's. If an sespec is IDLE or ERROR, its se_errno * field will have an errno value specifying the reason for its inactivity. * The vespec stores the client's callback function and private data, and the * arguments used to construct the sespec. All objects are reference counted * so we can destroy an object when it is no longer needed. The mdb_sespec_t * invariants for the respective states are as follows: * * IDLE: on t_idle list, se_data == NULL, se_errno != 0, se_ctor not called * ACTIVE: on t_active list, se_data valid, se_errno == 0, se_ctor called * ARMED: on t_active list, se_data valid, se_errno == 0, se_ctor called * ERROR: on t_active list, se_data valid, se_errno != 0, se_ctor called * * Additional commentary on specific state transitions and issues involving * event management can be found below near the target layer functions. * * References * * [1] John Gilmore, "Working in GDB", Technical Report, Cygnus Support, * 1.84 edition, 1994. * * [2] David R. Hanson and Mukund Raghavachari, "A Machine-Independent * Debugger", Software--Practice and Experience, 26(11), 1277-1299(1996). * * [3] Michael W. Shapiro, "RDB: A System for Incremental Replay Debugging", * Technical Report CS-97-12, Department of Computer Science, * Brown University. * * [4] Daniel B. Price, "New Techniques for Replay Debugging", Technical * Report CS-98-05, Department of Computer Science, Brown University. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Define convenience macros for referencing the set of vespec flag bits that * are preserved by the target implementation, and the set of bits that * determine automatic ve_hits == ve_limit behavior. */ #define T_IMPL_BITS \ (MDB_TGT_SPEC_INTERNAL | MDB_TGT_SPEC_SILENT | MDB_TGT_SPEC_MATCHED | \ MDB_TGT_SPEC_DELETED) #define T_AUTO_BITS \ (MDB_TGT_SPEC_AUTOSTOP | MDB_TGT_SPEC_AUTODEL | MDB_TGT_SPEC_AUTODIS) /* * Define convenience macro for referencing target flag pending continue bits. */ #define T_CONT_BITS \ (MDB_TGT_F_STEP | MDB_TGT_F_STEP_OUT | MDB_TGT_F_NEXT | MDB_TGT_F_CONT) mdb_tgt_t * mdb_tgt_create(mdb_tgt_ctor_f *ctor, int flags, int argc, const char *argv[]) { mdb_module_t *mp; mdb_tgt_t *t; if (flags & ~MDB_TGT_F_ALL) { (void) set_errno(EINVAL); return (NULL); } t = mdb_zalloc(sizeof (mdb_tgt_t), UM_SLEEP); mdb_list_append(&mdb.m_tgtlist, t); t->t_module = &mdb.m_rmod; t->t_matched = T_SE_END; t->t_flags = flags; t->t_vepos = 1; t->t_veneg = 1; for (mp = mdb.m_mhead; mp != NULL; mp = mp->mod_next) { if (ctor == mp->mod_tgt_ctor) { t->t_module = mp; break; } } if (ctor(t, argc, argv) != 0) { mdb_list_delete(&mdb.m_tgtlist, t); mdb_free(t, sizeof (mdb_tgt_t)); return (NULL); } mdb_dprintf(MDB_DBG_TGT, "t_create %s (%p)\n", t->t_module->mod_name, (void *)t); (void) t->t_ops->t_status(t, &t->t_status); return (t); } int mdb_tgt_getflags(mdb_tgt_t *t) { return (t->t_flags); } int mdb_tgt_setflags(mdb_tgt_t *t, int flags) { if (flags & ~MDB_TGT_F_ALL) return (set_errno(EINVAL)); return (t->t_ops->t_setflags(t, flags)); } int mdb_tgt_setcontext(mdb_tgt_t *t, void *context) { return (t->t_ops->t_setcontext(t, context)); } /*ARGSUSED*/ static int tgt_delete_vespec(mdb_tgt_t *t, void *private, int vid, void *data) { (void) mdb_tgt_vespec_delete(t, vid); return (0); } void mdb_tgt_destroy(mdb_tgt_t *t) { mdb_xdata_t *xdp, *nxdp; if (mdb.m_target == t) { mdb_dprintf(MDB_DBG_TGT, "t_deactivate %s (%p)\n", t->t_module->mod_name, (void *)t); t->t_ops->t_deactivate(t); mdb.m_target = NULL; } mdb_dprintf(MDB_DBG_TGT, "t_destroy %s (%p)\n", t->t_module->mod_name, (void *)t); for (xdp = mdb_list_next(&t->t_xdlist); xdp != NULL; xdp = nxdp) { nxdp = mdb_list_next(xdp); mdb_list_delete(&t->t_xdlist, xdp); mdb_free(xdp, sizeof (mdb_xdata_t)); } mdb_tgt_sespec_idle_all(t, EBUSY, TRUE); (void) mdb_tgt_vespec_iter(t, tgt_delete_vespec, NULL); t->t_ops->t_destroy(t); mdb_list_delete(&mdb.m_tgtlist, t); mdb_free(t, sizeof (mdb_tgt_t)); if (mdb.m_target == NULL) mdb_tgt_activate(mdb_list_prev(&mdb.m_tgtlist)); } void mdb_tgt_activate(mdb_tgt_t *t) { mdb_tgt_t *otgt = mdb.m_target; if (mdb.m_target != NULL) { mdb_dprintf(MDB_DBG_TGT, "t_deactivate %s (%p)\n", mdb.m_target->t_module->mod_name, (void *)mdb.m_target); mdb.m_target->t_ops->t_deactivate(mdb.m_target); } if ((mdb.m_target = t) != NULL) { const char *v = strstr(mdb.m_root, "%V"); mdb_dprintf(MDB_DBG_TGT, "t_activate %s (%p)\n", t->t_module->mod_name, (void *)t); /* * If the root was explicitly set with -R and contains %V, * expand it like a path. If the resulting directory is * not present, then replace %V with "latest" and re-evaluate. */ if (v != NULL) { char old_root[MAXPATHLEN]; const char **p; #ifndef _KMDB struct stat s; #endif size_t len; p = mdb_path_alloc(mdb.m_root, &len); (void) strcpy(old_root, mdb.m_root); (void) strncpy(mdb.m_root, p[0], MAXPATHLEN); mdb.m_root[MAXPATHLEN - 1] = '\0'; mdb_path_free(p, len); #ifndef _KMDB if (stat(mdb.m_root, &s) == -1 && errno == ENOENT) { mdb.m_flags |= MDB_FL_LATEST; p = mdb_path_alloc(old_root, &len); (void) strncpy(mdb.m_root, p[0], MAXPATHLEN); mdb.m_root[MAXPATHLEN - 1] = '\0'; mdb_path_free(p, len); } #endif } /* * Re-evaluate the macro and dmod paths now that we have the * new target set and m_root figured out. */ if (otgt == NULL) { mdb_set_ipath(mdb.m_ipathstr); mdb_set_lpath(mdb.m_lpathstr); } t->t_ops->t_activate(t); } } void mdb_tgt_periodic(mdb_tgt_t *t) { t->t_ops->t_periodic(t); } const char * mdb_tgt_name(mdb_tgt_t *t) { return (t->t_ops->t_name(t)); } const char * mdb_tgt_isa(mdb_tgt_t *t) { return (t->t_ops->t_isa(t)); } const char * mdb_tgt_platform(mdb_tgt_t *t) { return (t->t_ops->t_platform(t)); } int mdb_tgt_uname(mdb_tgt_t *t, struct utsname *utsp) { return (t->t_ops->t_uname(t, utsp)); } int mdb_tgt_dmodel(mdb_tgt_t *t) { return (t->t_ops->t_dmodel(t)); } int mdb_tgt_auxv(mdb_tgt_t *t, const auxv_t **auxvp) { return (t->t_ops->t_auxv(t, auxvp)); } ssize_t mdb_tgt_aread(mdb_tgt_t *t, mdb_tgt_as_t as, void *buf, size_t n, mdb_tgt_addr_t addr) { if (t->t_flags & MDB_TGT_F_ASIO) return (t->t_ops->t_aread(t, as, buf, n, addr)); switch ((uintptr_t)as) { case (uintptr_t)MDB_TGT_AS_VIRT: case (uintptr_t)MDB_TGT_AS_VIRT_I: case (uintptr_t)MDB_TGT_AS_VIRT_S: return (t->t_ops->t_vread(t, buf, n, addr)); case (uintptr_t)MDB_TGT_AS_PHYS: return (t->t_ops->t_pread(t, buf, n, addr)); case (uintptr_t)MDB_TGT_AS_FILE: return (t->t_ops->t_fread(t, buf, n, addr)); case (uintptr_t)MDB_TGT_AS_IO: return (t->t_ops->t_ioread(t, buf, n, addr)); } return (t->t_ops->t_aread(t, as, buf, n, addr)); } ssize_t mdb_tgt_awrite(mdb_tgt_t *t, mdb_tgt_as_t as, const void *buf, size_t n, mdb_tgt_addr_t addr) { if (!(t->t_flags & MDB_TGT_F_RDWR)) return (set_errno(EMDB_TGTRDONLY)); if (t->t_flags & MDB_TGT_F_ASIO) return (t->t_ops->t_awrite(t, as, buf, n, addr)); switch ((uintptr_t)as) { case (uintptr_t)MDB_TGT_AS_VIRT: case (uintptr_t)MDB_TGT_AS_VIRT_I: case (uintptr_t)MDB_TGT_AS_VIRT_S: return (t->t_ops->t_vwrite(t, buf, n, addr)); case (uintptr_t)MDB_TGT_AS_PHYS: return (t->t_ops->t_pwrite(t, buf, n, addr)); case (uintptr_t)MDB_TGT_AS_FILE: return (t->t_ops->t_fwrite(t, buf, n, addr)); case (uintptr_t)MDB_TGT_AS_IO: return (t->t_ops->t_iowrite(t, buf, n, addr)); } return (t->t_ops->t_awrite(t, as, buf, n, addr)); } ssize_t mdb_tgt_vread(mdb_tgt_t *t, void *buf, size_t n, uintptr_t addr) { return (t->t_ops->t_vread(t, buf, n, addr)); } ssize_t mdb_tgt_vwrite(mdb_tgt_t *t, const void *buf, size_t n, uintptr_t addr) { if (t->t_flags & MDB_TGT_F_RDWR) return (t->t_ops->t_vwrite(t, buf, n, addr)); return (set_errno(EMDB_TGTRDONLY)); } ssize_t mdb_tgt_pread(mdb_tgt_t *t, void *buf, size_t n, physaddr_t addr) { return (t->t_ops->t_pread(t, buf, n, addr)); } ssize_t mdb_tgt_pwrite(mdb_tgt_t *t, const void *buf, size_t n, physaddr_t addr) { if (t->t_flags & MDB_TGT_F_RDWR) return (t->t_ops->t_pwrite(t, buf, n, addr)); return (set_errno(EMDB_TGTRDONLY)); } ssize_t mdb_tgt_fread(mdb_tgt_t *t, void *buf, size_t n, uintptr_t addr) { return (t->t_ops->t_fread(t, buf, n, addr)); } ssize_t mdb_tgt_fwrite(mdb_tgt_t *t, const void *buf, size_t n, uintptr_t addr) { if (t->t_flags & MDB_TGT_F_RDWR) return (t->t_ops->t_fwrite(t, buf, n, addr)); return (set_errno(EMDB_TGTRDONLY)); } ssize_t mdb_tgt_ioread(mdb_tgt_t *t, void *buf, size_t n, uintptr_t addr) { return (t->t_ops->t_ioread(t, buf, n, addr)); } ssize_t mdb_tgt_iowrite(mdb_tgt_t *t, const void *buf, size_t n, uintptr_t addr) { if (t->t_flags & MDB_TGT_F_RDWR) return (t->t_ops->t_iowrite(t, buf, n, addr)); return (set_errno(EMDB_TGTRDONLY)); } int mdb_tgt_vtop(mdb_tgt_t *t, mdb_tgt_as_t as, uintptr_t va, physaddr_t *pap) { return (t->t_ops->t_vtop(t, as, va, pap)); } ssize_t mdb_tgt_readstr(mdb_tgt_t *t, mdb_tgt_as_t as, char *buf, size_t nbytes, mdb_tgt_addr_t addr) { ssize_t n = -1, nread = mdb_tgt_aread(t, as, buf, nbytes, addr); char *p; if (nread >= 0) { if ((p = memchr(buf, '\0', nread)) != NULL) nread = (size_t)(p - buf); goto done; } nread = 0; p = &buf[0]; while (nread < nbytes && (n = mdb_tgt_aread(t, as, p, 1, addr)) == 1) { if (*p == '\0') return (nread); nread++; addr++; p++; } if (nread == 0 && n == -1) return (-1); /* If we can't even read a byte, return -1 */ done: if (nbytes != 0) buf[MIN(nread, nbytes - 1)] = '\0'; return (nread); } ssize_t mdb_tgt_writestr(mdb_tgt_t *t, mdb_tgt_as_t as, const char *buf, mdb_tgt_addr_t addr) { ssize_t nwritten = mdb_tgt_awrite(t, as, buf, strlen(buf) + 1, addr); return (nwritten > 0 ? nwritten - 1 : nwritten); } int mdb_tgt_lookup_by_name(mdb_tgt_t *t, const char *obj, const char *name, GElf_Sym *symp, mdb_syminfo_t *sip) { mdb_syminfo_t info; GElf_Sym sym; uint_t id; if (name == NULL || t == NULL) return (set_errno(EINVAL)); if (obj == MDB_TGT_OBJ_EVERY && mdb_gelf_symtab_lookup_by_name(mdb.m_prsym, name, &sym, &id) == 0) { info.sym_table = MDB_TGT_PRVSYM; info.sym_id = id; goto found; } if (t->t_ops->t_lookup_by_name(t, obj, name, &sym, &info) == 0) goto found; return (-1); found: if (symp != NULL) *symp = sym; if (sip != NULL) *sip = info; return (0); } int mdb_tgt_lookup_by_addr(mdb_tgt_t *t, uintptr_t addr, uint_t flags, char *buf, size_t len, GElf_Sym *symp, mdb_syminfo_t *sip) { mdb_syminfo_t info; GElf_Sym sym; if (t == NULL) return (set_errno(EINVAL)); if (t->t_ops->t_lookup_by_addr(t, addr, flags, buf, len, &sym, &info) == 0) { if (symp != NULL) *symp = sym; if (sip != NULL) *sip = info; return (0); } return (-1); } /* * The mdb_tgt_lookup_by_scope function is a convenience routine for code that * wants to look up a scoped symbol name such as "object`symbol". It is * implemented as a simple wrapper around mdb_tgt_lookup_by_name. Note that * we split on the *last* occurrence of "`", so the object name itself may * contain additional scopes whose evaluation is left to the target. This * allows targets to implement additional scopes, such as source files, * function names, link map identifiers, etc. */ int mdb_tgt_lookup_by_scope(mdb_tgt_t *t, const char *s, GElf_Sym *symp, mdb_syminfo_t *sip) { const char *object = MDB_TGT_OBJ_EVERY; const char *name = s; char buf[MDB_TGT_SYM_NAMLEN]; if (t == NULL) return (set_errno(EINVAL)); if (strchr(name, '`') != NULL) { (void) strncpy(buf, s, sizeof (buf)); buf[sizeof (buf) - 1] = '\0'; name = buf; if ((s = strrsplit(buf, '`')) != NULL) { object = buf; name = s; if (*object == '\0') return (set_errno(EMDB_NOOBJ)); if (*name == '\0') return (set_errno(EMDB_NOSYM)); } } return (mdb_tgt_lookup_by_name(t, object, name, symp, sip)); } int mdb_tgt_symbol_iter(mdb_tgt_t *t, const char *obj, uint_t which, uint_t type, mdb_tgt_sym_f *cb, void *p) { if ((which != MDB_TGT_SYMTAB && which != MDB_TGT_DYNSYM) || (type & ~(MDB_TGT_BIND_ANY | MDB_TGT_TYPE_ANY)) != 0) return (set_errno(EINVAL)); return (t->t_ops->t_symbol_iter(t, obj, which, type, cb, p)); } ssize_t mdb_tgt_readsym(mdb_tgt_t *t, mdb_tgt_as_t as, void *buf, size_t nbytes, const char *obj, const char *name) { GElf_Sym sym; if (mdb_tgt_lookup_by_name(t, obj, name, &sym, NULL) == 0) return (mdb_tgt_aread(t, as, buf, nbytes, sym.st_value)); return (-1); } ssize_t mdb_tgt_writesym(mdb_tgt_t *t, mdb_tgt_as_t as, const void *buf, size_t nbytes, const char *obj, const char *name) { GElf_Sym sym; if (mdb_tgt_lookup_by_name(t, obj, name, &sym, NULL) == 0) return (mdb_tgt_awrite(t, as, buf, nbytes, sym.st_value)); return (-1); } int mdb_tgt_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *cb, void *p) { return (t->t_ops->t_mapping_iter(t, cb, p)); } int mdb_tgt_object_iter(mdb_tgt_t *t, mdb_tgt_map_f *cb, void *p) { return (t->t_ops->t_object_iter(t, cb, p)); } const mdb_map_t * mdb_tgt_addr_to_map(mdb_tgt_t *t, uintptr_t addr) { return (t->t_ops->t_addr_to_map(t, addr)); } const mdb_map_t * mdb_tgt_name_to_map(mdb_tgt_t *t, const char *name) { return (t->t_ops->t_name_to_map(t, name)); } struct ctf_file * mdb_tgt_addr_to_ctf(mdb_tgt_t *t, uintptr_t addr) { return (t->t_ops->t_addr_to_ctf(t, addr)); } struct ctf_file * mdb_tgt_name_to_ctf(mdb_tgt_t *t, const char *name) { return (t->t_ops->t_name_to_ctf(t, name)); } /* * Return the latest target status. We just copy out our cached copy. The * status only needs to change when the target is run, stepped, or continued. */ int mdb_tgt_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { uint_t dstop = (t->t_status.st_flags & MDB_TGT_DSTOP); uint_t istop = (t->t_status.st_flags & MDB_TGT_ISTOP); uint_t state = t->t_status.st_state; if (tsp == NULL) return (set_errno(EINVAL)); /* * If we're called with the address of the target's internal status, * then call down to update it; otherwise copy out the saved status. */ if (tsp == &t->t_status && t->t_ops->t_status(t, &t->t_status) != 0) return (-1); /* errno is set for us */ /* * Assert that our state is valid before returning it. The state must * be valid, and DSTOP and ISTOP cannot be set simultaneously. ISTOP * is only valid when stopped. DSTOP is only valid when running or * stopped. If any test fails, abort the debugger. */ if (state > MDB_TGT_LOST) fail("invalid target state (%u)\n", state); if (state != MDB_TGT_STOPPED && istop) fail("target state is (%u) and ISTOP is set\n", state); if (state != MDB_TGT_STOPPED && state != MDB_TGT_RUNNING && dstop) fail("target state is (%u) and DSTOP is set\n", state); if (istop && dstop) fail("target has ISTOP and DSTOP set simultaneously\n"); if (tsp != &t->t_status) bcopy(&t->t_status, tsp, sizeof (mdb_tgt_status_t)); return (0); } /* * For the given sespec, scan its list of vespecs for ones that are marked * temporary and delete them. We use the same method as vespec_delete below. */ /*ARGSUSED*/ void mdb_tgt_sespec_prune_one(mdb_tgt_t *t, mdb_sespec_t *sep) { mdb_vespec_t *vep, *nvep; for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) { nvep = mdb_list_next(vep); if ((vep->ve_flags & (MDB_TGT_SPEC_DELETED | MDB_TGT_SPEC_TEMPORARY)) == MDB_TGT_SPEC_TEMPORARY) { vep->ve_flags |= MDB_TGT_SPEC_DELETED; mdb_tgt_vespec_rele(t, vep); } } } /* * Prune each sespec on the active list of temporary vespecs. This function * is called, for example, after the target finishes a continue operation. */ void mdb_tgt_sespec_prune_all(mdb_tgt_t *t) { mdb_sespec_t *sep, *nsep; for (sep = mdb_list_next(&t->t_active); sep != NULL; sep = nsep) { nsep = mdb_list_next(sep); mdb_tgt_sespec_prune_one(t, sep); } } /* * Transition the given sespec to the IDLE state. We invoke the destructor, * and then move the sespec from the active list to the idle list. */ void mdb_tgt_sespec_idle_one(mdb_tgt_t *t, mdb_sespec_t *sep, int reason) { ASSERT(sep->se_state != MDB_TGT_SPEC_IDLE); if (sep->se_state == MDB_TGT_SPEC_ARMED) (void) sep->se_ops->se_disarm(t, sep); sep->se_ops->se_dtor(t, sep); sep->se_data = NULL; sep->se_state = MDB_TGT_SPEC_IDLE; sep->se_errno = reason; mdb_list_delete(&t->t_active, sep); mdb_list_append(&t->t_idle, sep); mdb_tgt_sespec_prune_one(t, sep); } /* * Transition each sespec on the active list to the IDLE state. This function * is called, for example, after the target terminates execution. */ void mdb_tgt_sespec_idle_all(mdb_tgt_t *t, int reason, int clear_matched) { mdb_sespec_t *sep, *nsep; mdb_vespec_t *vep; while ((sep = t->t_matched) != T_SE_END && clear_matched) { for (vep = mdb_list_next(&sep->se_velist); vep != NULL; ) { vep->ve_flags &= ~MDB_TGT_SPEC_MATCHED; vep = mdb_list_next(vep); } t->t_matched = sep->se_matched; sep->se_matched = NULL; mdb_tgt_sespec_rele(t, sep); } for (sep = mdb_list_next(&t->t_active); sep != NULL; sep = nsep) { nsep = mdb_list_next(sep); mdb_tgt_sespec_idle_one(t, sep, reason); } } /* * Attempt to transition the given sespec from the IDLE to ACTIVE state. We * do this by invoking se_ctor -- if this fails, we save the reason in se_errno * and return -1 with errno set. One strange case we need to deal with here is * the possibility that a given vespec is sitting on the idle list with its * corresponding sespec, but it is actually a duplicate of another sespec on the * active list. This can happen if the sespec is associated with a * MDB_TGT_SPEC_DISABLED vespec that was just enabled, and is now ready to be * activated. A more interesting reason this situation might arise is the case * where a virtual address breakpoint is set at an address just mmap'ed by * dlmopen. Since no symbol table information is available for this mapping * yet, a pre-existing deferred symbolic breakpoint may already exist for this * address, but it is on the idle list. When the symbol table is ready and the * DLACTIVITY event occurs, we now discover that the virtual address obtained by * evaluating the symbolic breakpoint matches the explicit virtual address of * the active virtual breakpoint. To resolve this conflict in either case, we * destroy the idle sespec, and attach its list of vespecs to the existing * active sespec. */ int mdb_tgt_sespec_activate_one(mdb_tgt_t *t, mdb_sespec_t *sep) { mdb_vespec_t *vep = mdb_list_next(&sep->se_velist); mdb_vespec_t *nvep; mdb_sespec_t *dup; ASSERT(sep->se_state == MDB_TGT_SPEC_IDLE); ASSERT(vep != NULL); if (vep->ve_flags & MDB_TGT_SPEC_DISABLED) return (0); /* cannot be activated while disabled bit set */ /* * First search the active list for an existing, duplicate sespec to * handle the special case described above. */ for (dup = mdb_list_next(&t->t_active); dup; dup = mdb_list_next(dup)) { if (dup->se_ops == sep->se_ops && dup->se_ops->se_secmp(t, dup, vep->ve_args)) { ASSERT(dup != sep); break; } } /* * If a duplicate is found, destroy the existing, idle sespec, and * attach all of its vespecs to the duplicate sespec. */ if (dup != NULL) { for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) { mdb_dprintf(MDB_DBG_TGT, "merge [ %d ] to sespec %p\n", vep->ve_id, (void *)dup); if (dup->se_matched != NULL) vep->ve_flags |= MDB_TGT_SPEC_MATCHED; nvep = mdb_list_next(vep); vep->ve_hits = 0; mdb_list_delete(&sep->se_velist, vep); mdb_tgt_sespec_rele(t, sep); mdb_list_append(&dup->se_velist, vep); mdb_tgt_sespec_hold(t, dup); vep->ve_se = dup; } mdb_dprintf(MDB_DBG_TGT, "merged idle sespec %p with %p\n", (void *)sep, (void *)dup); return (0); } /* * If no duplicate is found, call the sespec's constructor. If this * is successful, move the sespec to the active list. */ if (sep->se_ops->se_ctor(t, sep, vep->ve_args) < 0) { sep->se_errno = errno; sep->se_data = NULL; return (-1); } for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) { nvep = mdb_list_next(vep); vep->ve_hits = 0; } mdb_list_delete(&t->t_idle, sep); mdb_list_append(&t->t_active, sep); sep->se_state = MDB_TGT_SPEC_ACTIVE; sep->se_errno = 0; return (0); } /* * Transition each sespec on the idle list to the ACTIVE state. This function * is called, for example, after the target's t_run() function returns. If * the se_ctor() function fails, the specifier is not yet applicable; it will * remain on the idle list and can be activated later. * * Returns 1 if there weren't any unexpected activation failures; 0 if there * were. */ int mdb_tgt_sespec_activate_all(mdb_tgt_t *t) { mdb_sespec_t *sep, *nsep; int rc = 1; for (sep = mdb_list_next(&t->t_idle); sep != NULL; sep = nsep) { nsep = mdb_list_next(sep); if (mdb_tgt_sespec_activate_one(t, sep) < 0 && sep->se_errno != EMDB_NOOBJ) rc = 0; } return (rc); } /* * Transition the given sespec to the ARMED state. Note that we attempt to * re-arm sespecs previously in the ERROR state. If se_arm() fails the sespec * transitions to the ERROR state but stays on the active list. */ void mdb_tgt_sespec_arm_one(mdb_tgt_t *t, mdb_sespec_t *sep) { ASSERT(sep->se_state != MDB_TGT_SPEC_IDLE); if (sep->se_state == MDB_TGT_SPEC_ARMED) return; /* do not arm sespecs more than once */ if (sep->se_ops->se_arm(t, sep) == -1) { sep->se_state = MDB_TGT_SPEC_ERROR; sep->se_errno = errno; } else { sep->se_state = MDB_TGT_SPEC_ARMED; sep->se_errno = 0; } } /* * Transition each sespec on the active list (except matched specs) to the * ARMED state. This function is called prior to continuing the target. */ void mdb_tgt_sespec_arm_all(mdb_tgt_t *t) { mdb_sespec_t *sep, *nsep; for (sep = mdb_list_next(&t->t_active); sep != NULL; sep = nsep) { nsep = mdb_list_next(sep); if (sep->se_matched == NULL) mdb_tgt_sespec_arm_one(t, sep); } } /* * Transition each sespec on the active list that is in the ARMED state to * the ACTIVE state. If se_disarm() fails, the sespec is transitioned to * the ERROR state instead, but left on the active list. */ static void tgt_disarm_sespecs(mdb_tgt_t *t) { mdb_sespec_t *sep; for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (sep->se_state != MDB_TGT_SPEC_ARMED) continue; /* do not disarm if in ERROR state */ if (sep->se_ops->se_disarm(t, sep) == -1) { sep->se_state = MDB_TGT_SPEC_ERROR; sep->se_errno = errno; } else { sep->se_state = MDB_TGT_SPEC_ACTIVE; sep->se_errno = 0; } } } /* * Determine if the software event that triggered the most recent stop matches * any of the active event specifiers. If 'all' is TRUE, we consider all * sespecs in our search. If 'all' is FALSE, we only consider ARMED sespecs. * If we successfully match an event, we add it to the t_matched list and * place an additional hold on it. */ static mdb_sespec_t * tgt_match_sespecs(mdb_tgt_t *t, int all) { mdb_sespec_t *sep; for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (all == FALSE && sep->se_state != MDB_TGT_SPEC_ARMED) continue; /* restrict search to ARMED sespecs */ if (sep->se_state != MDB_TGT_SPEC_ERROR && sep->se_ops->se_match(t, sep, &t->t_status)) { mdb_dprintf(MDB_DBG_TGT, "match se %p\n", (void *)sep); mdb_tgt_sespec_hold(t, sep); sep->se_matched = t->t_matched; t->t_matched = sep; } } return (t->t_matched); } /* * This function provides the low-level target continue algorithm. We proceed * in three phases: (1) we arm the active sespecs, except the specs matched at * the time we last stopped, (2) we call se_cont() on any matched sespecs to * step over these event transitions, and then arm the corresponding sespecs, * and (3) we call the appropriate low-level continue routine. Once the * target stops again, we determine which sespecs were matched, and invoke the * appropriate vespec callbacks and perform other vespec maintenance. */ static int tgt_continue(mdb_tgt_t *t, mdb_tgt_status_t *tsp, int (*t_cont)(mdb_tgt_t *, mdb_tgt_status_t *)) { mdb_var_t *hitv = mdb_nv_lookup(&mdb.m_nv, "hits"); uintptr_t pc = t->t_status.st_pc; int error = 0; mdb_sespec_t *sep, *nsep, *matched; mdb_vespec_t *vep, *nvep; uintptr_t addr; uint_t cbits = 0; /* union of pending continue bits */ uint_t ncont = 0; /* # of callbacks that requested cont */ uint_t n = 0; /* # of callbacks */ /* * If the target is undead, dead, or lost, we no longer allow continue. * This effectively forces the user to use ::kill or ::run after death. */ if (t->t_status.st_state == MDB_TGT_UNDEAD) return (set_errno(EMDB_TGTZOMB)); if (t->t_status.st_state == MDB_TGT_DEAD) return (set_errno(EMDB_TGTCORE)); if (t->t_status.st_state == MDB_TGT_LOST) return (set_errno(EMDB_TGTLOST)); /* * If any of single-step, step-over, or step-out is pending, it takes * precedence over an explicit or pending continue, because these are * all different specialized forms of continue. */ if (t->t_flags & MDB_TGT_F_STEP) t_cont = t->t_ops->t_step; else if (t->t_flags & MDB_TGT_F_NEXT) t_cont = t->t_ops->t_step; else if (t->t_flags & MDB_TGT_F_STEP_OUT) t_cont = t->t_ops->t_cont; /* * To handle step-over, we ask the target to find the address past the * next control transfer instruction. If an address is found, we plant * a temporary breakpoint there and continue; otherwise just step. */ if ((t->t_flags & MDB_TGT_F_NEXT) && !(t->t_flags & MDB_TGT_F_STEP)) { if (t->t_ops->t_next(t, &addr) == -1 || mdb_tgt_add_vbrkpt(t, addr, MDB_TGT_SPEC_HIDDEN | MDB_TGT_SPEC_TEMPORARY, no_se_f, NULL) == 0) { mdb_dprintf(MDB_DBG_TGT, "next falling back to step: " "%s\n", mdb_strerror(errno)); } else t_cont = t->t_ops->t_cont; } /* * To handle step-out, we ask the target to find the return address of * the current frame, plant a temporary breakpoint there, and continue. */ if (t->t_flags & MDB_TGT_F_STEP_OUT) { if (t->t_ops->t_step_out(t, &addr) == -1) return (-1); /* errno is set for us */ if (mdb_tgt_add_vbrkpt(t, addr, MDB_TGT_SPEC_HIDDEN | MDB_TGT_SPEC_TEMPORARY, no_se_f, NULL) == 0) return (-1); /* errno is set for us */ } (void) mdb_signal_block(SIGHUP); (void) mdb_signal_block(SIGTERM); mdb_intr_disable(); t->t_flags &= ~T_CONT_BITS; t->t_flags |= MDB_TGT_F_BUSY; mdb_tgt_sespec_arm_all(t); ASSERT(t->t_matched != NULL); matched = t->t_matched; t->t_matched = T_SE_END; if (mdb.m_term != NULL) IOP_SUSPEND(mdb.m_term); /* * Iterate over the matched sespec list, performing autostop processing * and clearing the matched bit for each associated vespec. We then * invoke each sespec's se_cont callback in order to continue past * the corresponding event. If the matched list has more than one * sespec, we assume that the se_cont callbacks are non-interfering. */ for (sep = matched; sep != T_SE_END; sep = sep->se_matched) { for (vep = mdb_list_next(&sep->se_velist); vep != NULL; ) { if ((vep->ve_flags & MDB_TGT_SPEC_AUTOSTOP) && (vep->ve_limit && vep->ve_hits == vep->ve_limit)) vep->ve_hits = 0; vep->ve_flags &= ~MDB_TGT_SPEC_MATCHED; vep = mdb_list_next(vep); } if (sep->se_ops->se_cont(t, sep, &t->t_status) == -1) { error = errno ? errno : -1; tgt_disarm_sespecs(t); break; } if (!(t->t_status.st_flags & MDB_TGT_ISTOP)) { tgt_disarm_sespecs(t); if (t->t_status.st_state == MDB_TGT_UNDEAD) mdb_tgt_sespec_idle_all(t, EMDB_TGTZOMB, TRUE); else if (t->t_status.st_state == MDB_TGT_LOST) mdb_tgt_sespec_idle_all(t, EMDB_TGTLOST, TRUE); break; } } /* * Clear the se_matched field for each matched sespec, and drop the * reference count since the sespec is no longer on the matched list. */ for (sep = matched; sep != T_SE_END; sep = nsep) { nsep = sep->se_matched; sep->se_matched = NULL; mdb_tgt_sespec_rele(t, sep); } /* * If the matched list was non-empty, see if we hit another event while * performing se_cont() processing. If so, don't bother continuing any * further. If not, arm the sespecs on the old matched list by calling * mdb_tgt_sespec_arm_all() again and then continue by calling t_cont. */ if (matched != T_SE_END) { if (error != 0 || !(t->t_status.st_flags & MDB_TGT_ISTOP)) goto out; /* abort now if se_cont() failed */ if ((t->t_matched = tgt_match_sespecs(t, FALSE)) != T_SE_END) { tgt_disarm_sespecs(t); goto out; } mdb_tgt_sespec_arm_all(t); } if (t_cont != t->t_ops->t_step || pc == t->t_status.st_pc) { if (t_cont(t, &t->t_status) != 0) error = errno ? errno : -1; } tgt_disarm_sespecs(t); if (t->t_flags & MDB_TGT_F_UNLOAD) longjmp(mdb.m_frame->f_pcb, MDB_ERR_QUIT); if (t->t_status.st_state == MDB_TGT_UNDEAD) mdb_tgt_sespec_idle_all(t, EMDB_TGTZOMB, TRUE); else if (t->t_status.st_state == MDB_TGT_LOST) mdb_tgt_sespec_idle_all(t, EMDB_TGTLOST, TRUE); else if (t->t_status.st_flags & MDB_TGT_ISTOP) t->t_matched = tgt_match_sespecs(t, TRUE); out: if (mdb.m_term != NULL) IOP_RESUME(mdb.m_term); (void) mdb_signal_unblock(SIGTERM); (void) mdb_signal_unblock(SIGHUP); mdb_intr_enable(); for (sep = t->t_matched; sep != T_SE_END; sep = sep->se_matched) { /* * When we invoke a ve_callback, it may in turn request that the * target continue immediately after callback processing is * complete. We only allow this to occur if *all* callbacks * agree to continue. To implement this behavior, we keep a * count (ncont) of such requests, and only apply the cumulative * continue bits (cbits) to the target if ncont is equal to the * total number of callbacks that are invoked (n). */ for (vep = mdb_list_next(&sep->se_velist); vep != NULL; vep = nvep, n++) { /* * Place an extra hold on the current vespec and pick * up the next pointer before invoking the callback: we * must be prepared for the vespec to be deleted or * moved to a different list by the callback. */ mdb_tgt_vespec_hold(t, vep); nvep = mdb_list_next(vep); vep->ve_flags |= MDB_TGT_SPEC_MATCHED; vep->ve_hits++; mdb_nv_set_value(mdb.m_dot, t->t_status.st_pc); mdb_nv_set_value(hitv, vep->ve_hits); ASSERT((t->t_flags & T_CONT_BITS) == 0); vep->ve_callback(t, vep->ve_id, vep->ve_data); ncont += (t->t_flags & T_CONT_BITS) != 0; cbits |= (t->t_flags & T_CONT_BITS); t->t_flags &= ~T_CONT_BITS; if (vep->ve_limit && vep->ve_hits == vep->ve_limit) { if (vep->ve_flags & MDB_TGT_SPEC_AUTODEL) (void) mdb_tgt_vespec_delete(t, vep->ve_id); else if (vep->ve_flags & MDB_TGT_SPEC_AUTODIS) (void) mdb_tgt_vespec_disable(t, vep->ve_id); } if (vep->ve_limit && vep->ve_hits < vep->ve_limit) { if (vep->ve_flags & MDB_TGT_SPEC_AUTOSTOP) (void) mdb_tgt_continue(t, NULL); } mdb_tgt_vespec_rele(t, vep); } } if (t->t_matched != T_SE_END && ncont == n) t->t_flags |= cbits; /* apply continues (see above) */ mdb_tgt_sespec_prune_all(t); t->t_status.st_flags &= ~MDB_TGT_BUSY; t->t_flags &= ~MDB_TGT_F_BUSY; if (tsp != NULL) bcopy(&t->t_status, tsp, sizeof (mdb_tgt_status_t)); if (error != 0) return (set_errno(error)); return (0); } /* * This function is the common glue that connects the high-level target layer * continue functions (e.g. step and cont below) with the low-level * tgt_continue() function above. Since vespec callbacks may perform any * actions, including attempting to continue the target itself, we must be * prepared to be called while the target is still marked F_BUSY. In this * case, we just set a pending bit and return. When we return from the call * to tgt_continue() that made us busy into the tgt_request_continue() call * that is still on the stack, we will loop around and call tgt_continue() * again. This allows vespecs to continue the target without recursion. */ static int tgt_request_continue(mdb_tgt_t *t, mdb_tgt_status_t *tsp, uint_t tflag, int (*t_cont)(mdb_tgt_t *, mdb_tgt_status_t *)) { mdb_tgt_spec_desc_t desc; mdb_sespec_t *sep; char buf[BUFSIZ]; int status; if (t->t_flags & MDB_TGT_F_BUSY) { t->t_flags |= tflag; return (0); } do { status = tgt_continue(t, tsp, t_cont); } while (status == 0 && (t->t_flags & T_CONT_BITS)); if (status == 0) { for (sep = t->t_matched; sep != T_SE_END; sep = sep->se_matched) { mdb_vespec_t *vep; for (vep = mdb_list_next(&sep->se_velist); vep; vep = mdb_list_next(vep)) { if (vep->ve_flags & MDB_TGT_SPEC_SILENT) continue; warn("%s\n", sep->se_ops->se_info(t, sep, vep, &desc, buf, sizeof (buf))); } } mdb_callb_fire(MDB_CALLB_STCHG); } t->t_flags &= ~T_CONT_BITS; return (status); } /* * Restart target execution: we rely upon the underlying target implementation * to do most of the work for us. In particular, we assume it will properly * preserve the state of our event lists if the run fails for some reason, * and that it will reset all events to the IDLE state if the run succeeds. * If it is successful, we attempt to activate all of the idle sespecs. The * t_run() operation is defined to leave the target stopped at the earliest * possible point in execution, and then return control to the debugger, * awaiting a step or continue operation to set it running again. */ int mdb_tgt_run(mdb_tgt_t *t, int argc, const mdb_arg_t *argv) { int i; for (i = 0; i < argc; i++) { if (argv->a_type != MDB_TYPE_STRING) return (set_errno(EINVAL)); } if (t->t_ops->t_run(t, argc, argv) == -1) return (-1); /* errno is set for us */ t->t_flags &= ~T_CONT_BITS; (void) mdb_tgt_sespec_activate_all(t); if (mdb.m_term != NULL) IOP_CTL(mdb.m_term, MDB_IOC_CTTY, NULL); return (0); } int mdb_tgt_step(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { return (tgt_request_continue(t, tsp, MDB_TGT_F_STEP, t->t_ops->t_step)); } int mdb_tgt_step_out(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { t->t_flags |= MDB_TGT_F_STEP_OUT; /* set flag even if tgt not busy */ return (tgt_request_continue(t, tsp, 0, t->t_ops->t_cont)); } int mdb_tgt_next(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { t->t_flags |= MDB_TGT_F_NEXT; /* set flag even if tgt not busy */ return (tgt_request_continue(t, tsp, 0, t->t_ops->t_step)); } int mdb_tgt_continue(mdb_tgt_t *t, mdb_tgt_status_t *tsp) { return (tgt_request_continue(t, tsp, MDB_TGT_F_CONT, t->t_ops->t_cont)); } int mdb_tgt_signal(mdb_tgt_t *t, int sig) { return (t->t_ops->t_signal(t, sig)); } void * mdb_tgt_vespec_data(mdb_tgt_t *t, int vid) { mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, vid); if (vep == NULL) { (void) set_errno(EMDB_NOSESPEC); return (NULL); } return (vep->ve_data); } /* * Return a structured description and comment string for the given vespec. * We fill in the common information from the vespec, and then call down to * the underlying sespec to provide the comment string and modify any * event type-specific information. */ char * mdb_tgt_vespec_info(mdb_tgt_t *t, int vid, mdb_tgt_spec_desc_t *sp, char *buf, size_t nbytes) { mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, vid); mdb_tgt_spec_desc_t desc; mdb_sespec_t *sep; if (vep == NULL) { if (sp != NULL) bzero(sp, sizeof (mdb_tgt_spec_desc_t)); (void) set_errno(EMDB_NOSESPEC); return (NULL); } if (sp == NULL) sp = &desc; sep = vep->ve_se; sp->spec_id = vep->ve_id; sp->spec_flags = vep->ve_flags; sp->spec_hits = vep->ve_hits; sp->spec_limit = vep->ve_limit; sp->spec_state = sep->se_state; sp->spec_errno = sep->se_errno; sp->spec_base = 0; sp->spec_size = 0; sp->spec_data = vep->ve_data; return (sep->se_ops->se_info(t, sep, vep, sp, buf, nbytes)); } /* * Qsort callback for sorting vespecs by VID, used below. */ static int tgt_vespec_compare(const mdb_vespec_t **lp, const mdb_vespec_t **rp) { return ((*lp)->ve_id - (*rp)->ve_id); } /* * Iterate over all vespecs and call the specified callback function with the * corresponding VID and caller data pointer. We want the callback function * to see a consistent, sorted snapshot of the vespecs, and allow the callback * to take actions such as deleting the vespec itself, so we cannot simply * iterate over the lists. Instead, we pre-allocate an array of vespec * pointers, fill it in and place an additional hold on each vespec, and then * sort it. After the callback has been executed on each vespec in the * sorted array, we remove our hold and free the temporary array. */ int mdb_tgt_vespec_iter(mdb_tgt_t *t, mdb_tgt_vespec_f *func, void *p) { mdb_vespec_t **veps, **vepp, **vend; mdb_vespec_t *vep, *nvep; mdb_sespec_t *sep; uint_t vecnt = t->t_vecnt; veps = mdb_alloc(sizeof (mdb_vespec_t *) * vecnt, UM_SLEEP); vend = veps + vecnt; vepp = veps; for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) { mdb_tgt_vespec_hold(t, vep); nvep = mdb_list_next(vep); *vepp++ = vep; } } for (sep = mdb_list_next(&t->t_idle); sep; sep = mdb_list_next(sep)) { for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) { mdb_tgt_vespec_hold(t, vep); nvep = mdb_list_next(vep); *vepp++ = vep; } } if (vepp != vend) { fail("target has %u vespecs on list but vecnt shows %u\n", (uint_t)(vepp - veps), vecnt); } qsort(veps, vecnt, sizeof (mdb_vespec_t *), (int (*)(const void *, const void *))tgt_vespec_compare); for (vepp = veps; vepp < vend; vepp++) { if (func(t, p, (*vepp)->ve_id, (*vepp)->ve_data) != 0) break; } for (vepp = veps; vepp < vend; vepp++) mdb_tgt_vespec_rele(t, *vepp); mdb_free(veps, sizeof (mdb_vespec_t *) * vecnt); return (0); } /* * Reset the vespec flags, match limit, and callback data to the specified * values. We silently correct invalid parameters, except for the VID. * The caller is required to query the existing properties and pass back * the existing values for any properties that should not be modified. * If the callback data is modified, the caller is responsible for cleaning * up any state associated with the previous value. */ int mdb_tgt_vespec_modify(mdb_tgt_t *t, int id, uint_t flags, uint_t limit, void *data) { mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, id); if (vep == NULL) return (set_errno(EMDB_NOSESPEC)); /* * If the value of the MDB_TGT_SPEC_DISABLED bit is changing, call the * appropriate vespec function to do the enable/disable work. */ if ((flags & MDB_TGT_SPEC_DISABLED) != (vep->ve_flags & MDB_TGT_SPEC_DISABLED)) { if (flags & MDB_TGT_SPEC_DISABLED) (void) mdb_tgt_vespec_disable(t, id); else (void) mdb_tgt_vespec_enable(t, id); } /* * Make that only one MDB_TGT_SPEC_AUTO* bit is set in the new flags * value: extra bits are cleared according to order of precedence. */ if (flags & MDB_TGT_SPEC_AUTOSTOP) flags &= ~(MDB_TGT_SPEC_AUTODEL | MDB_TGT_SPEC_AUTODIS); else if (flags & MDB_TGT_SPEC_AUTODEL) flags &= ~MDB_TGT_SPEC_AUTODIS; /* * The TEMPORARY property always takes precedence over STICKY. */ if (flags & MDB_TGT_SPEC_TEMPORARY) flags &= ~MDB_TGT_SPEC_STICKY; /* * If any MDB_TGT_SPEC_AUTO* bits are changing, reset the hit count * back to zero and clear all of the old auto bits. */ if ((flags & T_AUTO_BITS) != (vep->ve_flags & T_AUTO_BITS)) { vep->ve_flags &= ~T_AUTO_BITS; vep->ve_hits = 0; } vep->ve_flags = (vep->ve_flags & T_IMPL_BITS) | (flags & ~T_IMPL_BITS); vep->ve_data = data; /* * If any MDB_TGT_SPEC_AUTO* flags are set, make sure the limit is at * least one. If none are set, reset it back to zero. */ if (vep->ve_flags & T_AUTO_BITS) vep->ve_limit = MAX(limit, 1); else vep->ve_limit = 0; /* * As a convenience, we allow the caller to specify SPEC_DELETED in * the flags field as indication that the event should be deleted. */ if (flags & MDB_TGT_SPEC_DELETED) (void) mdb_tgt_vespec_delete(t, id); return (0); } /* * Remove the user disabled bit from the specified vespec, and attempt to * activate the underlying sespec and move it to the active list if possible. */ int mdb_tgt_vespec_enable(mdb_tgt_t *t, int id) { mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, id); if (vep == NULL) return (set_errno(EMDB_NOSESPEC)); if (vep->ve_flags & MDB_TGT_SPEC_DISABLED) { ASSERT(mdb_list_next(vep) == NULL); vep->ve_flags &= ~MDB_TGT_SPEC_DISABLED; if (mdb_tgt_sespec_activate_one(t, vep->ve_se) < 0) return (-1); /* errno is set for us */ } return (0); } /* * Set the user disabled bit on the specified vespec, and move it to the idle * list. If the vespec is not alone with its sespec or if it is a currently * matched event, we must always create a new idle sespec and move the vespec * there. If the vespec was alone and active, we can simply idle the sespec. */ int mdb_tgt_vespec_disable(mdb_tgt_t *t, int id) { mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, id); mdb_sespec_t *sep; if (vep == NULL) return (set_errno(EMDB_NOSESPEC)); if (vep->ve_flags & MDB_TGT_SPEC_DISABLED) return (0); /* already disabled */ if (mdb_list_prev(vep) != NULL || mdb_list_next(vep) != NULL || vep->ve_se->se_matched != NULL) { sep = mdb_tgt_sespec_insert(t, vep->ve_se->se_ops, &t->t_idle); mdb_list_delete(&vep->ve_se->se_velist, vep); mdb_tgt_sespec_rele(t, vep->ve_se); mdb_list_append(&sep->se_velist, vep); mdb_tgt_sespec_hold(t, sep); vep->ve_flags &= ~MDB_TGT_SPEC_MATCHED; vep->ve_se = sep; } else if (vep->ve_se->se_state != MDB_TGT_SPEC_IDLE) mdb_tgt_sespec_idle_one(t, vep->ve_se, EMDB_SPECDIS); vep->ve_flags |= MDB_TGT_SPEC_DISABLED; return (0); } /* * Delete the given vespec. We use the MDB_TGT_SPEC_DELETED flag to ensure that * multiple calls to mdb_tgt_vespec_delete to not attempt to decrement the * reference count on the vespec more than once. This is because the vespec * may remain referenced if it is currently held by another routine (e.g. * vespec_iter), and so the user could attempt to delete it more than once * since it reference count will be >= 2 prior to the first delete call. */ int mdb_tgt_vespec_delete(mdb_tgt_t *t, int id) { mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, id); if (vep == NULL) return (set_errno(EMDB_NOSESPEC)); if (vep->ve_flags & MDB_TGT_SPEC_DELETED) return (set_errno(EBUSY)); vep->ve_flags |= MDB_TGT_SPEC_DELETED; mdb_tgt_vespec_rele(t, vep); return (0); } int mdb_tgt_add_vbrkpt(mdb_tgt_t *t, uintptr_t addr, int spec_flags, mdb_tgt_se_f *func, void *p) { return (t->t_ops->t_add_vbrkpt(t, addr, spec_flags, func, p)); } int mdb_tgt_add_sbrkpt(mdb_tgt_t *t, const char *symbol, int spec_flags, mdb_tgt_se_f *func, void *p) { return (t->t_ops->t_add_sbrkpt(t, symbol, spec_flags, func, p)); } int mdb_tgt_add_pwapt(mdb_tgt_t *t, physaddr_t pa, size_t n, uint_t flags, int spec_flags, mdb_tgt_se_f *func, void *p) { if ((flags & ~MDB_TGT_WA_RWX) || flags == 0) { (void) set_errno(EINVAL); return (0); } if (pa + n < pa) { (void) set_errno(EMDB_WPRANGE); return (0); } return (t->t_ops->t_add_pwapt(t, pa, n, flags, spec_flags, func, p)); } int mdb_tgt_add_vwapt(mdb_tgt_t *t, uintptr_t va, size_t n, uint_t flags, int spec_flags, mdb_tgt_se_f *func, void *p) { if ((flags & ~MDB_TGT_WA_RWX) || flags == 0) { (void) set_errno(EINVAL); return (0); } if (va + n < va) { (void) set_errno(EMDB_WPRANGE); return (0); } return (t->t_ops->t_add_vwapt(t, va, n, flags, spec_flags, func, p)); } int mdb_tgt_add_iowapt(mdb_tgt_t *t, uintptr_t addr, size_t n, uint_t flags, int spec_flags, mdb_tgt_se_f *func, void *p) { if ((flags & ~MDB_TGT_WA_RWX) || flags == 0) { (void) set_errno(EINVAL); return (0); } if (addr + n < addr) { (void) set_errno(EMDB_WPRANGE); return (0); } return (t->t_ops->t_add_iowapt(t, addr, n, flags, spec_flags, func, p)); } int mdb_tgt_add_sysenter(mdb_tgt_t *t, int sysnum, int spec_flags, mdb_tgt_se_f *func, void *p) { return (t->t_ops->t_add_sysenter(t, sysnum, spec_flags, func, p)); } int mdb_tgt_add_sysexit(mdb_tgt_t *t, int sysnum, int spec_flags, mdb_tgt_se_f *func, void *p) { return (t->t_ops->t_add_sysexit(t, sysnum, spec_flags, func, p)); } int mdb_tgt_add_signal(mdb_tgt_t *t, int sig, int spec_flags, mdb_tgt_se_f *func, void *p) { return (t->t_ops->t_add_signal(t, sig, spec_flags, func, p)); } int mdb_tgt_add_fault(mdb_tgt_t *t, int flt, int spec_flags, mdb_tgt_se_f *func, void *p) { return (t->t_ops->t_add_fault(t, flt, spec_flags, func, p)); } int mdb_tgt_getareg(mdb_tgt_t *t, mdb_tgt_tid_t tid, const char *rname, mdb_tgt_reg_t *rp) { return (t->t_ops->t_getareg(t, tid, rname, rp)); } int mdb_tgt_putareg(mdb_tgt_t *t, mdb_tgt_tid_t tid, const char *rname, mdb_tgt_reg_t r) { return (t->t_ops->t_putareg(t, tid, rname, r)); } int mdb_tgt_thread_name(mdb_tgt_t *t, mdb_tgt_tid_t tid, char *buf, size_t bufsize) { return (t->t_ops->t_thread_name(t, tid, buf, bufsize)); } int mdb_tgt_stack_iter(mdb_tgt_t *t, const mdb_tgt_gregset_t *gregs, mdb_tgt_stack_f *cb, void *p) { return (t->t_ops->t_stack_iter(t, gregs, cb, p)); } int mdb_tgt_xdata_iter(mdb_tgt_t *t, mdb_tgt_xdata_f *func, void *private) { mdb_xdata_t *xdp; for (xdp = mdb_list_next(&t->t_xdlist); xdp; xdp = mdb_list_next(xdp)) { if (func(private, xdp->xd_name, xdp->xd_desc, xdp->xd_copy(t, NULL, 0)) != 0) break; } return (0); } ssize_t mdb_tgt_getxdata(mdb_tgt_t *t, const char *name, void *buf, size_t nbytes) { mdb_xdata_t *xdp; for (xdp = mdb_list_next(&t->t_xdlist); xdp; xdp = mdb_list_next(xdp)) { if (strcmp(xdp->xd_name, name) == 0) return (xdp->xd_copy(t, buf, nbytes)); } return (set_errno(ENODATA)); } long mdb_tgt_notsup() { return (set_errno(EMDB_TGTNOTSUP)); } void * mdb_tgt_null() { (void) set_errno(EMDB_TGTNOTSUP); return (NULL); } long mdb_tgt_nop() { return (0L); } int mdb_tgt_xdata_insert(mdb_tgt_t *t, const char *name, const char *desc, ssize_t (*copy)(mdb_tgt_t *, void *, size_t)) { mdb_xdata_t *xdp; for (xdp = mdb_list_next(&t->t_xdlist); xdp; xdp = mdb_list_next(xdp)) { if (strcmp(xdp->xd_name, name) == 0) return (set_errno(EMDB_XDEXISTS)); } xdp = mdb_alloc(sizeof (mdb_xdata_t), UM_SLEEP); mdb_list_append(&t->t_xdlist, xdp); xdp->xd_name = name; xdp->xd_desc = desc; xdp->xd_copy = copy; return (0); } int mdb_tgt_xdata_delete(mdb_tgt_t *t, const char *name) { mdb_xdata_t *xdp; for (xdp = mdb_list_next(&t->t_xdlist); xdp; xdp = mdb_list_next(xdp)) { if (strcmp(xdp->xd_name, name) == 0) { mdb_list_delete(&t->t_xdlist, xdp); mdb_free(xdp, sizeof (mdb_xdata_t)); return (0); } } return (set_errno(EMDB_NOXD)); } int mdb_tgt_sym_match(const GElf_Sym *sym, uint_t mask) { #if STT_NUM != (STT_TLS + 1) #error "STT_NUM has grown. update mdb_tgt_sym_match()" #endif uchar_t s_bind = GELF_ST_BIND(sym->st_info); uchar_t s_type = GELF_ST_TYPE(sym->st_info); /* * In case you haven't already guessed, this relies on the bitmask * used by and for encoding symbol * type and binding matching the order of STB and STT constants * in . Changes to ELF must maintain binary * compatibility, so I think this is reasonably fair game. */ if (s_bind < STB_NUM && s_type < STT_NUM) { uint_t type = (1 << (s_type + 8)) | (1 << s_bind); return ((type & ~mask) == 0); } return (0); /* Unknown binding or type; fail to match */ } void mdb_tgt_elf_export(mdb_gelf_file_t *gf) { GElf_Xword d = 0, t = 0; GElf_Addr b = 0, e = 0; uint32_t m = 0; mdb_var_t *v; /* * Reset legacy adb variables based on the specified ELF object file * provided by the target. We define these variables: * * b - the address of the data segment (first writeable Phdr) * d - the size of the data segment * e - the address of the entry point * m - the magic number identifying the file * t - the address of the text segment (first executable Phdr) */ if (gf != NULL) { const GElf_Phdr *text = NULL, *data = NULL; size_t i; e = gf->gf_ehdr.e_entry; bcopy(&gf->gf_ehdr.e_ident[EI_MAG0], &m, sizeof (m)); for (i = 0; i < gf->gf_npload; i++) { if (text == NULL && (gf->gf_phdrs[i].p_flags & PF_X)) text = &gf->gf_phdrs[i]; if (data == NULL && (gf->gf_phdrs[i].p_flags & PF_W)) data = &gf->gf_phdrs[i]; } if (text != NULL) t = text->p_memsz; if (data != NULL) { b = data->p_vaddr; d = data->p_memsz; } } if ((v = mdb_nv_lookup(&mdb.m_nv, "b")) != NULL) mdb_nv_set_value(v, b); if ((v = mdb_nv_lookup(&mdb.m_nv, "d")) != NULL) mdb_nv_set_value(v, d); if ((v = mdb_nv_lookup(&mdb.m_nv, "e")) != NULL) mdb_nv_set_value(v, e); if ((v = mdb_nv_lookup(&mdb.m_nv, "m")) != NULL) mdb_nv_set_value(v, m); if ((v = mdb_nv_lookup(&mdb.m_nv, "t")) != NULL) mdb_nv_set_value(v, t); } /*ARGSUSED*/ void mdb_tgt_sespec_hold(mdb_tgt_t *t, mdb_sespec_t *sep) { sep->se_refs++; ASSERT(sep->se_refs != 0); } void mdb_tgt_sespec_rele(mdb_tgt_t *t, mdb_sespec_t *sep) { ASSERT(sep->se_refs != 0); if (--sep->se_refs == 0) { mdb_dprintf(MDB_DBG_TGT, "destroying sespec %p\n", (void *)sep); ASSERT(mdb_list_next(&sep->se_velist) == NULL); if (sep->se_state != MDB_TGT_SPEC_IDLE) { sep->se_ops->se_dtor(t, sep); mdb_list_delete(&t->t_active, sep); } else mdb_list_delete(&t->t_idle, sep); mdb_free(sep, sizeof (mdb_sespec_t)); } } mdb_sespec_t * mdb_tgt_sespec_insert(mdb_tgt_t *t, const mdb_se_ops_t *ops, mdb_list_t *list) { mdb_sespec_t *sep = mdb_zalloc(sizeof (mdb_sespec_t), UM_SLEEP); if (list == &t->t_active) sep->se_state = MDB_TGT_SPEC_ACTIVE; else sep->se_state = MDB_TGT_SPEC_IDLE; mdb_list_append(list, sep); sep->se_ops = ops; return (sep); } mdb_sespec_t * mdb_tgt_sespec_lookup_active(mdb_tgt_t *t, const mdb_se_ops_t *ops, void *args) { mdb_sespec_t *sep; for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { if (sep->se_ops == ops && sep->se_ops->se_secmp(t, sep, args)) break; } return (sep); } mdb_sespec_t * mdb_tgt_sespec_lookup_idle(mdb_tgt_t *t, const mdb_se_ops_t *ops, void *args) { mdb_sespec_t *sep; for (sep = mdb_list_next(&t->t_idle); sep; sep = mdb_list_next(sep)) { if (sep->se_ops == ops && sep->se_ops->se_vecmp(t, mdb_list_next(&sep->se_velist), args)) break; } return (sep); } /*ARGSUSED*/ void mdb_tgt_vespec_hold(mdb_tgt_t *t, mdb_vespec_t *vep) { vep->ve_refs++; ASSERT(vep->ve_refs != 0); } void mdb_tgt_vespec_rele(mdb_tgt_t *t, mdb_vespec_t *vep) { ASSERT(vep->ve_refs != 0); if (--vep->ve_refs == 0) { /* * Remove this vespec from the sespec's velist and decrement * the reference count on the sespec. */ mdb_list_delete(&vep->ve_se->se_velist, vep); mdb_tgt_sespec_rele(t, vep->ve_se); /* * If we are deleting the most recently assigned VID, reset * t_vepos or t_veneg as appropriate to re-use that number. * This could be enhanced to re-use any free number by * maintaining a bitmap or hash of the allocated IDs. */ if (vep->ve_id > 0 && t->t_vepos == vep->ve_id + 1) t->t_vepos = vep->ve_id; else if (vep->ve_id < 0 && t->t_veneg == -vep->ve_id + 1) t->t_veneg = -vep->ve_id; /* * Call the destructor to clean up ve_args, and then free * the actual vespec structure. */ vep->ve_dtor(vep); mdb_free(vep, sizeof (mdb_vespec_t)); ASSERT(t->t_vecnt != 0); t->t_vecnt--; } } int mdb_tgt_vespec_insert(mdb_tgt_t *t, const mdb_se_ops_t *ops, int flags, mdb_tgt_se_f *func, void *data, void *args, void (*dtor)(mdb_vespec_t *)) { mdb_vespec_t *vep = mdb_zalloc(sizeof (mdb_vespec_t), UM_SLEEP); int id, mult, *seqp; mdb_sespec_t *sep; /* * Make that only one MDB_TGT_SPEC_AUTO* bit is set in the new flags * value: extra bits are cleared according to order of precedence. */ if (flags & MDB_TGT_SPEC_AUTOSTOP) flags &= ~(MDB_TGT_SPEC_AUTODEL | MDB_TGT_SPEC_AUTODIS); else if (flags & MDB_TGT_SPEC_AUTODEL) flags &= ~MDB_TGT_SPEC_AUTODIS; /* * The TEMPORARY property always takes precedence over STICKY. */ if (flags & MDB_TGT_SPEC_TEMPORARY) flags &= ~MDB_TGT_SPEC_STICKY; /* * Find a matching sespec or create a new one on the appropriate list. * We always create a new sespec if the vespec is created disabled. */ if (flags & MDB_TGT_SPEC_DISABLED) sep = mdb_tgt_sespec_insert(t, ops, &t->t_idle); else if ((sep = mdb_tgt_sespec_lookup_active(t, ops, args)) == NULL && (sep = mdb_tgt_sespec_lookup_idle(t, ops, args)) == NULL) sep = mdb_tgt_sespec_insert(t, ops, &t->t_active); /* * Generate a new ID for the vespec. Increasing positive integers are * assigned to visible vespecs; decreasing negative integers are * assigned to hidden vespecs. The target saves our most recent choice. */ if (flags & MDB_TGT_SPEC_INTERNAL) { seqp = &t->t_veneg; mult = -1; } else { seqp = &t->t_vepos; mult = 1; } id = *seqp; while (mdb_tgt_vespec_lookup(t, id * mult) != NULL) id = MAX(id + 1, 1); *seqp = MAX(id + 1, 1); vep->ve_id = id * mult; vep->ve_flags = flags & ~(MDB_TGT_SPEC_MATCHED | MDB_TGT_SPEC_DELETED); vep->ve_se = sep; vep->ve_callback = func; vep->ve_data = data; vep->ve_args = args; vep->ve_dtor = dtor; mdb_list_append(&sep->se_velist, vep); mdb_tgt_sespec_hold(t, sep); mdb_tgt_vespec_hold(t, vep); t->t_vecnt++; /* * If this vespec is the first reference to the sespec and it's active, * then it is newly created and we should attempt to initialize it. * If se_ctor fails, then move the sespec back to the idle list. */ if (sep->se_refs == 1 && sep->se_state == MDB_TGT_SPEC_ACTIVE && sep->se_ops->se_ctor(t, sep, vep->ve_args) == -1) { mdb_list_delete(&t->t_active, sep); mdb_list_append(&t->t_idle, sep); sep->se_state = MDB_TGT_SPEC_IDLE; sep->se_errno = errno; sep->se_data = NULL; } /* * If the sespec is active and the target is currently running (because * we grabbed it using PGRAB_NOSTOP), then go ahead and attempt to arm * the sespec so it will take effect immediately. */ if (sep->se_state == MDB_TGT_SPEC_ACTIVE && t->t_status.st_state == MDB_TGT_RUNNING) mdb_tgt_sespec_arm_one(t, sep); mdb_dprintf(MDB_DBG_TGT, "inserted [ %d ] sep=%p refs=%u state=%d\n", vep->ve_id, (void *)sep, sep->se_refs, sep->se_state); return (vep->ve_id); } /* * Search the target's active, idle, and disabled lists for the vespec matching * the specified VID, and return a pointer to it, or NULL if no match is found. */ mdb_vespec_t * mdb_tgt_vespec_lookup(mdb_tgt_t *t, int vid) { mdb_sespec_t *sep; mdb_vespec_t *vep; if (vid == 0) return (NULL); /* 0 is never a valid VID */ for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) { for (vep = mdb_list_next(&sep->se_velist); vep; vep = mdb_list_next(vep)) { if (vep->ve_id == vid) return (vep); } } for (sep = mdb_list_next(&t->t_idle); sep; sep = mdb_list_next(sep)) { for (vep = mdb_list_next(&sep->se_velist); vep; vep = mdb_list_next(vep)) { if (vep->ve_id == vid) return (vep); } } return (NULL); } /*ARGSUSED*/ void no_ve_dtor(mdb_vespec_t *vep) { /* default destructor does nothing */ } /*ARGSUSED*/ void no_se_f(mdb_tgt_t *t, int vid, void *data) { /* default callback does nothing */ } /*ARGSUSED*/ void no_se_dtor(mdb_tgt_t *t, mdb_sespec_t *sep) { /* default destructor does nothing */ } /*ARGSUSED*/ int no_se_secmp(mdb_tgt_t *t, mdb_sespec_t *sep, void *args) { return (sep->se_data == args); } /*ARGSUSED*/ int no_se_vecmp(mdb_tgt_t *t, mdb_vespec_t *vep, void *args) { return (vep->ve_args == args); } /*ARGSUSED*/ int no_se_arm(mdb_tgt_t *t, mdb_sespec_t *sep) { return (0); /* return success */ } /*ARGSUSED*/ int no_se_disarm(mdb_tgt_t *t, mdb_sespec_t *sep) { return (0); /* return success */ } /*ARGSUSED*/ int no_se_cont(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp) { if (tsp != &t->t_status) bcopy(&t->t_status, tsp, sizeof (mdb_tgt_status_t)); return (0); /* return success */ } int mdb_tgt_register_dcmds(mdb_tgt_t *t, const mdb_dcmd_t *dcp, int flags) { int fail = 0; for (; dcp->dc_name != NULL; dcp++) { if (mdb_module_add_dcmd(t->t_module, dcp, flags) == -1) { warn("failed to add dcmd %s", dcp->dc_name); fail++; } } return (fail > 0 ? -1 : 0); } int mdb_tgt_register_walkers(mdb_tgt_t *t, const mdb_walker_t *wp, int flags) { int fail = 0; for (; wp->walk_name != NULL; wp++) { if (mdb_module_add_walker(t->t_module, wp, flags) == -1) { warn("failed to add walk %s", wp->walk_name); fail++; } } return (fail > 0 ? -1 : 0); } void mdb_tgt_register_regvars(mdb_tgt_t *t, const mdb_tgt_regdesc_t *rdp, const mdb_nv_disc_t *disc, int flags) { for (; rdp->rd_name != NULL; rdp++) { if (!(rdp->rd_flags & MDB_TGT_R_EXPORT)) continue; /* Don't export register as a variable */ if (rdp->rd_flags & MDB_TGT_R_RDONLY) flags |= MDB_NV_RDONLY; (void) mdb_nv_insert(&mdb.m_nv, rdp->rd_name, disc, (uintptr_t)t, MDB_NV_PERSIST | flags); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Joyent, Inc. * Copyright 2024 Oxide Computer Company */ #ifndef _MDB_TARGET_H #define _MDB_TARGET_H #include #include #include #ifdef __cplusplus extern "C" { #endif /* * Forward declaration of the target structure: the target itself is defined in * mdb_tgt_impl.h and is opaque with respect to callers of this interface. */ struct mdb_tgt; struct mdb_arg; struct ctf_file; typedef struct mdb_tgt mdb_tgt_t; extern void mdb_create_builtin_tgts(void); extern void mdb_create_loadable_disasms(void); /* * Target Constructors * * These functions are used to create a complete debugger target. The * constructor is passed as an argument to mdb_tgt_create(). */ extern int mdb_value_tgt_create(mdb_tgt_t *, int, const char *[]); #ifndef _KMDB extern int mdb_kvm_tgt_create(mdb_tgt_t *, int, const char *[]); extern int mdb_proc_tgt_create(mdb_tgt_t *, int, const char *[]); extern int mdb_kproc_tgt_create(mdb_tgt_t *, int, const char *[]); extern int mdb_rawfile_tgt_create(mdb_tgt_t *, int, const char *[]); #ifdef __amd64 extern int mdb_bhyve_tgt_create(mdb_tgt_t *, int, const char *[]); #endif #else extern int kmdb_kvm_create(mdb_tgt_t *, int, const char *[]); #endif /* * Targets are created by calling mdb_tgt_create() with an optional set of * target flags, an argument list, and a target constructor (see above): */ #define MDB_TGT_F_RDWR 0x0001 /* Open for writing (else read-only) */ #define MDB_TGT_F_ALLOWIO 0x0002 /* Allow I/O mem access (live only) */ #define MDB_TGT_F_FORCE 0x0004 /* Force open (even if non-exclusive) */ #define MDB_TGT_F_PRELOAD 0x0008 /* Preload all symbol tables */ #define MDB_TGT_F_NOLOAD 0x0010 /* Do not do load-object processing */ #define MDB_TGT_F_NOSTOP 0x0020 /* Do not stop target on attach */ #define MDB_TGT_F_STEP 0x0040 /* Single-step is pending */ #define MDB_TGT_F_STEP_OUT 0x0080 /* Step-out is pending */ #define MDB_TGT_F_NEXT 0x0100 /* Step-over is pending */ #define MDB_TGT_F_CONT 0x0200 /* Continue is pending */ #define MDB_TGT_F_BUSY 0x0400 /* Target is busy executing */ #define MDB_TGT_F_ASIO 0x0800 /* Use t_aread and t_awrite for i/o */ #define MDB_TGT_F_UNLOAD 0x1000 /* Unload has been requested */ #define MDB_TGT_F_ALL 0x1fff /* Mask of all valid flags */ typedef int mdb_tgt_ctor_f(mdb_tgt_t *, int, const char *[]); extern mdb_tgt_t *mdb_tgt_create(mdb_tgt_ctor_f *, int, int, const char *[]); extern void mdb_tgt_destroy(mdb_tgt_t *); extern int mdb_tgt_getflags(mdb_tgt_t *); extern int mdb_tgt_setflags(mdb_tgt_t *, int); extern int mdb_tgt_setcontext(mdb_tgt_t *, void *); /* * Targets are activated and de-activated by the debugger framework. An * activation occurs after construction when the target becomes the current * target in the debugger. A target is de-activated prior to its destructor * being called by mdb_tgt_destroy, or when another target is activated. * These callbacks are suitable for loading support modules and other tasks. */ extern void mdb_tgt_activate(mdb_tgt_t *); /* * Prior to issuing a new command prompt, the debugger framework calls the * target's periodic callback to allow it to load new modules or perform * other background tasks. */ extern void mdb_tgt_periodic(mdb_tgt_t *); /* * Convenience functions for accessing miscellaneous target information. */ extern const char *mdb_tgt_name(mdb_tgt_t *); extern const char *mdb_tgt_isa(mdb_tgt_t *); extern const char *mdb_tgt_platform(mdb_tgt_t *); extern int mdb_tgt_uname(mdb_tgt_t *, struct utsname *); extern int mdb_tgt_dmodel(mdb_tgt_t *); /* * Address Space Interface * * Each target can provide access to a set of address spaces, which may include * a primary virtual address space, a physical address space, an object file * address space (where virtual addresses are converted to file offsets in an * object file), and an I/O port address space. Additionally, the target can * provide access to alternate address spaces, which are identified by the * opaque mdb_tgt_as_t type. If the 'as' parameter to mdb_tgt_aread or * mdb_tgt_awrite is one of the listed constants, these calls are equivalent * to mdb_tgt_{v|p|f|io}read or write. */ typedef void * mdb_tgt_as_t; /* Opaque address space id */ typedef uint64_t mdb_tgt_addr_t; /* Generic unsigned address */ typedef uint64_t physaddr_t; /* Physical memory address */ #define MDB_TGT_AS_VIRT ((mdb_tgt_as_t)-1L) /* Virtual address space: */ #define MDB_TGT_AS_VIRT_I ((mdb_tgt_as_t)-2L) /* special case for code */ #define MDB_TGT_AS_VIRT_S ((mdb_tgt_as_t)-3L) /* special case for stack */ #define MDB_TGT_AS_PHYS ((mdb_tgt_as_t)-4L) /* Physical address space */ #define MDB_TGT_AS_FILE ((mdb_tgt_as_t)-5L) /* Object file address space */ #define MDB_TGT_AS_IO ((mdb_tgt_as_t)-6L) /* I/o address space */ extern ssize_t mdb_tgt_aread(mdb_tgt_t *, mdb_tgt_as_t, void *, size_t, mdb_tgt_addr_t); extern ssize_t mdb_tgt_awrite(mdb_tgt_t *, mdb_tgt_as_t, const void *, size_t, mdb_tgt_addr_t); extern ssize_t mdb_tgt_vread(mdb_tgt_t *, void *, size_t, uintptr_t); extern ssize_t mdb_tgt_vwrite(mdb_tgt_t *, const void *, size_t, uintptr_t); extern ssize_t mdb_tgt_pread(mdb_tgt_t *, void *, size_t, physaddr_t); extern ssize_t mdb_tgt_pwrite(mdb_tgt_t *, const void *, size_t, physaddr_t); extern ssize_t mdb_tgt_fread(mdb_tgt_t *, void *, size_t, uintptr_t); extern ssize_t mdb_tgt_fwrite(mdb_tgt_t *, const void *, size_t, uintptr_t); extern ssize_t mdb_tgt_ioread(mdb_tgt_t *, void *, size_t, uintptr_t); extern ssize_t mdb_tgt_iowrite(mdb_tgt_t *, const void *, size_t, uintptr_t); /* * Convert an address-space's virtual address to the corresponding * physical address (only useful for kernel targets): */ extern int mdb_tgt_vtop(mdb_tgt_t *, mdb_tgt_as_t, uintptr_t, physaddr_t *); /* * Convenience functions for reading and writing null-terminated * strings from any of the target address spaces: */ extern ssize_t mdb_tgt_readstr(mdb_tgt_t *, mdb_tgt_as_t, char *, size_t, mdb_tgt_addr_t); extern ssize_t mdb_tgt_writestr(mdb_tgt_t *, mdb_tgt_as_t, const char *, mdb_tgt_addr_t); /* * Symbol Table Interface * * Each target can provide access to one or more symbol tables, which can be * iterated over, or used to lookup symbols by either name or address. The * target can support a primary executable and primary dynamic symbol table, * a symbol table for its run-time link-editor, and symbol tables for one or * more loaded objects. A symbol is uniquely identified by an object name, * a symbol table id, and a symbol id. Symbols can be discovered by iterating * over them, looking them up by name, or looking them up by address. */ typedef struct mdb_syminfo { uint_t sym_table; /* Symbol table id (see symbol_iter, below) */ uint_t sym_id; /* Symbol identifier */ } mdb_syminfo_t; /* * Reserved object names for mdb_tgt_lookup_by_name(): */ #define MDB_TGT_OBJ_EXEC ((const char *)0L) /* Executable symbols */ #define MDB_TGT_OBJ_RTLD ((const char *)1L) /* Ldso/krtld symbols */ #define MDB_TGT_OBJ_EVERY ((const char *)-1L) /* All known symbols */ extern int mdb_tgt_lookup_by_scope(mdb_tgt_t *, const char *, GElf_Sym *, mdb_syminfo_t *); extern int mdb_tgt_lookup_by_name(mdb_tgt_t *, const char *, const char *, GElf_Sym *, mdb_syminfo_t *); /* * Flag bit passed to mdb_tgt_lookup_by_addr(): */ #define MDB_TGT_SYM_FUZZY 0 /* Match closest address */ #define MDB_TGT_SYM_EXACT 1 /* Match exact address only */ #define MDB_TGT_SYM_NAMLEN 1024 /* Recommended max symbol name length */ extern int mdb_tgt_lookup_by_addr(mdb_tgt_t *, uintptr_t, uint_t, char *, size_t, GElf_Sym *, mdb_syminfo_t *); /* * Callback function prototype for mdb_tgt_symbol_iter(): */ typedef int mdb_tgt_sym_f(void *, const GElf_Sym *, const char *, const mdb_syminfo_t *sip, const char *); /* * Values for selecting symbol tables with mdb_tgt_symbol_iter(): */ #define MDB_TGT_PRVSYM 0 /* User's private symbol table */ #define MDB_TGT_SYMTAB 1 /* Normal symbol table (.symtab) */ #define MDB_TGT_DYNSYM 2 /* Dynamic symbol table (.dynsym) */ /* * Values for selecting symbols of interest by binding and type. These flags * can be used to construct a bitmask to pass to mdb_tgt_symbol_iter(). The * module API has its own slightly different names for these values. If you are * adding a new flag here, you should consider exposing it in the module API. * If you are changing these flags and their meanings, you will need to update * the module API implementation to account for those changes. */ #define MDB_TGT_BIND_LOCAL 0x0001 /* Local (static-scope) symbols */ #define MDB_TGT_BIND_GLOBAL 0x0002 /* Global symbols */ #define MDB_TGT_BIND_WEAK 0x0004 /* Weak binding symbols */ #define MDB_TGT_BIND_ANY 0x0007 /* Any of the above */ #define MDB_TGT_TYPE_NOTYPE 0x0100 /* Symbol has no type */ #define MDB_TGT_TYPE_OBJECT 0x0200 /* Symbol refers to data */ #define MDB_TGT_TYPE_FUNC 0x0400 /* Symbol refers to text */ #define MDB_TGT_TYPE_SECT 0x0800 /* Symbol refers to a section */ #define MDB_TGT_TYPE_FILE 0x1000 /* Symbol refers to a source file */ #define MDB_TGT_TYPE_COMMON 0x2000 /* Symbol refers to a common block */ #define MDB_TGT_TYPE_TLS 0x4000 /* Symbol refers to TLS */ #define MDB_TGT_TYPE_ANY 0x7f00 /* Any of the above */ extern int mdb_tgt_symbol_iter(mdb_tgt_t *, const char *, uint_t, uint_t, mdb_tgt_sym_f *, void *); /* * Convenience functions for reading and writing at the address specified * by a given object file and symbol name: */ extern ssize_t mdb_tgt_readsym(mdb_tgt_t *, mdb_tgt_as_t, void *, size_t, const char *, const char *); extern ssize_t mdb_tgt_writesym(mdb_tgt_t *, mdb_tgt_as_t, const void *, size_t, const char *, const char *); /* * Virtual Address Mapping and Load Object interface * * These interfaces allow the caller to iterate over the various virtual * address space mappings, or only those mappings corresponding to load objects. * The mapping name (MDB_TGT_MAPSZ) is defined to be large enough for a string * of length MAXPATHLEN, plus space for "LM`" where lmid is a hex number. */ #define MDB_TGT_MAPSZ 1048 /* Maximum length of mapping name */ #define MDB_TGT_MAP_R 0x01 /* Mapping is readable */ #define MDB_TGT_MAP_W 0x02 /* Mapping is writeable */ #define MDB_TGT_MAP_X 0x04 /* Mapping is executable */ #define MDB_TGT_MAP_SHMEM 0x08 /* Mapping is shared memory */ #define MDB_TGT_MAP_STACK 0x10 /* Mapping is a stack of some kind */ #define MDB_TGT_MAP_HEAP 0x20 /* Mapping is a heap of some kind */ #define MDB_TGT_MAP_ANON 0x40 /* Mapping is anonymous memory */ typedef struct mdb_map { char map_name[MDB_TGT_MAPSZ]; /* Name of mapped object */ uintptr_t map_base; /* Virtual address of base of mapping */ size_t map_size; /* Size of mapping in bytes */ uint_t map_flags; /* Flags (see above) */ } mdb_map_t; typedef int mdb_tgt_map_f(void *, const mdb_map_t *, const char *); extern int mdb_tgt_mapping_iter(mdb_tgt_t *, mdb_tgt_map_f *, void *); extern int mdb_tgt_object_iter(mdb_tgt_t *, mdb_tgt_map_f *, void *); extern const mdb_map_t *mdb_tgt_addr_to_map(mdb_tgt_t *, uintptr_t); extern const mdb_map_t *mdb_tgt_name_to_map(mdb_tgt_t *, const char *); extern struct ctf_file *mdb_tgt_addr_to_ctf(mdb_tgt_t *, uintptr_t); extern struct ctf_file *mdb_tgt_name_to_ctf(mdb_tgt_t *, const char *); /* * Execution Control Interface * * For in-situ debugging, we provide a relatively simple interface for target * execution control. The target can be continued, or the representative * thread of control can be single-stepped. Once the target has stopped, the * status of the representative thread is returned (this status can also be * obtained using mdb_tgt_status()). Upon continue, the target's internal list * of software event specifiers determines what types of events will cause the * target to stop and transfer control back to the debugger. The target * allows any number of virtual event specifiers to be registered, along with * an associated callback. These virtual specifiers are layered on top of * underlying software event specifiers that are private to the target. The * virtual event specifier list can be manipulated by the functions described * below. We currently support the following types of traced events: * breakpoints, watchpoints, system call entry, system call exit, signals, * and machine faults. */ typedef uintptr_t mdb_tgt_tid_t; /* Opaque thread identifier */ typedef struct mdb_tgt_status { mdb_tgt_tid_t st_tid; /* Id of thread in question */ uintptr_t st_pc; /* Program counter, if stopped */ uint_t st_state; /* Program state (see below) */ uint_t st_flags; /* Status flags (see below) */ } mdb_tgt_status_t; /* * Program state (st_state): * (MDB_STATE_* definitions in the module API need to be in sync with these) */ #define MDB_TGT_IDLE 0 /* Target is idle (not running yet) */ #define MDB_TGT_RUNNING 1 /* Target is currently executing */ #define MDB_TGT_STOPPED 2 /* Target is stopped */ #define MDB_TGT_UNDEAD 3 /* Target is undead (zombie) */ #define MDB_TGT_DEAD 4 /* Target is dead (core dump) */ #define MDB_TGT_LOST 5 /* Target lost by debugger */ /* * Status flags (st_flags): */ #define MDB_TGT_ISTOP 0x1 /* Stop on event of interest */ #define MDB_TGT_DSTOP 0x2 /* Stop directive is pending */ #define MDB_TGT_BUSY 0x4 /* Busy in debugger */ extern int mdb_tgt_status(mdb_tgt_t *, mdb_tgt_status_t *); extern int mdb_tgt_run(mdb_tgt_t *, int, const struct mdb_arg *); extern int mdb_tgt_step(mdb_tgt_t *, mdb_tgt_status_t *); extern int mdb_tgt_step_out(mdb_tgt_t *, mdb_tgt_status_t *); extern int mdb_tgt_next(mdb_tgt_t *, mdb_tgt_status_t *); extern int mdb_tgt_continue(mdb_tgt_t *, mdb_tgt_status_t *); extern int mdb_tgt_signal(mdb_tgt_t *, int); /* * Iterating through the specifier list yields the integer id (VID) and private * data pointer for each specifier. */ typedef int mdb_tgt_vespec_f(mdb_tgt_t *, void *, int, void *); /* * Each event specifier is defined to be in one of the following states. The * state transitions are discussed in detail in the comments in mdb_target.c. */ #define MDB_TGT_SPEC_IDLE 1 /* Inactive (e.g. object not loaded) */ #define MDB_TGT_SPEC_ACTIVE 2 /* Active but not armed in target */ #define MDB_TGT_SPEC_ARMED 3 /* Active and armed (e.g. bkpt set) */ #define MDB_TGT_SPEC_ERROR 4 /* Failed to arm event */ /* * Event specifiers may also have one or more of the following additional * properties (spec_flags bits): */ #define MDB_TGT_SPEC_INTERNAL 0x0001 /* Internal to target implementation */ #define MDB_TGT_SPEC_SILENT 0x0002 /* Do not describe when matched */ #define MDB_TGT_SPEC_TEMPORARY 0x0004 /* Delete next time target stops */ #define MDB_TGT_SPEC_MATCHED 0x0008 /* Specifier matched at last stop */ #define MDB_TGT_SPEC_DISABLED 0x0010 /* Specifier cannot be armed */ #define MDB_TGT_SPEC_DELETED 0x0020 /* Specifier has been deleted */ #define MDB_TGT_SPEC_AUTODEL 0x0040 /* Delete when match limit reached */ #define MDB_TGT_SPEC_AUTODIS 0x0080 /* Disable when match limit reached */ #define MDB_TGT_SPEC_AUTOSTOP 0x0100 /* Stop when match limit reached */ #define MDB_TGT_SPEC_STICKY 0x0200 /* Do not delete as part of :z */ #define MDB_TGT_SPEC_HIDDEN (MDB_TGT_SPEC_INTERNAL | MDB_TGT_SPEC_SILENT) typedef struct mdb_tgt_spec_desc { int spec_id; /* Event specifier id (VID) */ uint_t spec_flags; /* Flags (see above) */ uint_t spec_hits; /* Count of number of times matched */ uint_t spec_limit; /* Limit on number of times matched */ int spec_state; /* State (see above) */ int spec_errno; /* Last error code (if IDLE or ERROR) */ uintptr_t spec_base; /* Start of affected memory region */ size_t spec_size; /* Size of affected memory region */ void *spec_data; /* Callback private data */ } mdb_tgt_spec_desc_t; /* * The target provides functions to convert a VID into the private data pointer, * or a complete description of the event specifier and its state. */ extern void *mdb_tgt_vespec_data(mdb_tgt_t *, int); extern char *mdb_tgt_vespec_info(mdb_tgt_t *, int, mdb_tgt_spec_desc_t *, char *, size_t); /* * The common target layer provides functions to iterate over the list of * registered event specifiers, modify or disable them, and delete them. */ extern int mdb_tgt_vespec_iter(mdb_tgt_t *, mdb_tgt_vespec_f *, void *); extern int mdb_tgt_vespec_modify(mdb_tgt_t *, int, uint_t, uint_t, void *); extern int mdb_tgt_vespec_enable(mdb_tgt_t *, int); extern int mdb_tgt_vespec_disable(mdb_tgt_t *, int); extern int mdb_tgt_vespec_delete(mdb_tgt_t *, int); /* * The mdb_tgt_add_* functions are used to add software event specifiers to the * target. The caller provides a bitmask of flags (spec_flags above), callback * function pointer, and callback data as arguments. Whenever a matching event * is detected, a software event callback function is invoked. The callback * receives a pointer to the target, the VID of the corresponding event * specifier, and a private data pointer as arguments. If no callback is * desired, the caller can specify a pointer to the no_se_f default callback. * Unlike other target layer functions, the mdb_tgt_add_* interfaces return the * VID of the new event (which may be positive or negative), or 0 if the new * event could not be created. */ typedef void mdb_tgt_se_f(mdb_tgt_t *, int, void *); extern void no_se_f(mdb_tgt_t *, int, void *); /* * Breakpoints can be set at a specified virtual address or using MDB's * symbol notation: */ extern int mdb_tgt_add_vbrkpt(mdb_tgt_t *, uintptr_t, int, mdb_tgt_se_f *, void *); extern int mdb_tgt_add_sbrkpt(mdb_tgt_t *, const char *, int, mdb_tgt_se_f *, void *); /* * Watchpoints can be set at physical, virtual, or I/O port addresses for any * combination of read, write, or execute operations. */ #define MDB_TGT_WA_R 0x1 /* Read watchpoint */ #define MDB_TGT_WA_W 0x2 /* Write watchpoint */ #define MDB_TGT_WA_X 0x4 /* Execute watchpoint */ #define MDB_TGT_WA_RWX (MDB_TGT_WA_R | MDB_TGT_WA_W | MDB_TGT_WA_X) extern int mdb_tgt_add_pwapt(mdb_tgt_t *, physaddr_t, size_t, uint_t, int, mdb_tgt_se_f *, void *); extern int mdb_tgt_add_vwapt(mdb_tgt_t *, uintptr_t, size_t, uint_t, int, mdb_tgt_se_f *, void *); extern int mdb_tgt_add_iowapt(mdb_tgt_t *, uintptr_t, size_t, uint_t, int, mdb_tgt_se_f *, void *); /* * For user process debugging, tracepoints can be set on entry or exit from * a system call, or on receipt of a software signal or fault. */ extern int mdb_tgt_add_sysenter(mdb_tgt_t *, int, int, mdb_tgt_se_f *, void *); extern int mdb_tgt_add_sysexit(mdb_tgt_t *, int, int, mdb_tgt_se_f *, void *); extern int mdb_tgt_add_signal(mdb_tgt_t *, int, int, mdb_tgt_se_f *, void *); extern int mdb_tgt_add_fault(mdb_tgt_t *, int, int, mdb_tgt_se_f *, void *); /* * Machine Register Interface * * The machine registers for a given thread can be manipulated using the * getareg and putareg interface; the caller must know the naming convention * for registers for the given target architecture. For the purposes of * this interface, we declare the register container to be the largest * current integer container. */ typedef uint64_t mdb_tgt_reg_t; extern int mdb_tgt_getareg(mdb_tgt_t *, mdb_tgt_tid_t, const char *, mdb_tgt_reg_t *); extern int mdb_tgt_putareg(mdb_tgt_t *, mdb_tgt_tid_t, const char *, mdb_tgt_reg_t); /* * Thread name interface * * If the underlying target supports it, copy the thread name (if any) for the * specified thread into the specified buffer. */ extern int mdb_tgt_thread_name(mdb_tgt_t *, mdb_tgt_tid_t, char *, size_t); /* * Stack Interface * * The target stack interface provides the ability to iterate backward through * the frames of an execution stack. For the purposes of this interface, the * mdb_tgt_gregset (general purpose register set) is an opaque type: there must * be an implicit contract between the target implementation and any debugger * modules that must interpret the contents of this structure. The callback * function is provided with the only elements of a stack frame which we can * reasonably abstract: the virtual address corresponding to a program counter * value, and an array of arguments passed to the function call represented by * this frame. The rest of the frame is presumed to be contained within the * mdb_tgt_gregset_t, and is architecture-specific. */ typedef struct mdb_tgt_gregset mdb_tgt_gregset_t; typedef int mdb_tgt_stack_f(void *, uintptr_t, uint_t, const long *, const mdb_tgt_gregset_t *); typedef int mdb_tgt_stack_iter_f(mdb_tgt_t *, const mdb_tgt_gregset_t *, mdb_tgt_stack_f *, void *); extern mdb_tgt_stack_iter_f mdb_tgt_stack_iter; /* * External Data Interface * * The external data interface provides each target with the ability to export * a set of named buffers that contain data which is associated with the * target, but is somehow not accessible through one of its address spaces and * does not correspond to a machine register. A process credential is an * example of such a buffer: the credential is associated with the given * process, but is stored in the kernel (not the process's address space) and * thus is not accessible through any other target interface. Since it is * exported via /proc, the user process target can export this information as a * named buffer for target-specific dcmds to consume. */ typedef int mdb_tgt_xdata_f(void *, const char *, const char *, size_t); extern int mdb_tgt_xdata_iter(mdb_tgt_t *, mdb_tgt_xdata_f *, void *); extern ssize_t mdb_tgt_getxdata(mdb_tgt_t *, const char *, void *, size_t); #ifdef __cplusplus } #endif #endif /* _MDB_TARGET_H */ /* * 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. */ /* * Copyright (c) 2018, Joyent, Inc. All rights reserved. * Copyright 2024 Oxide Computer Company */ #ifndef _MDB_TARGET_IMPL_H #define _MDB_TARGET_IMPL_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB /* * Target Operations * * This ops vector implements the set of primitives which can be used by the * debugger to interact with the target, and encompasses most of the calls * found in . The remainder of the target interface is * implemented by common code that invokes these primitives or manipulates * the common target structures directly. */ typedef struct mdb_tgt_ops { int (*t_setflags)(mdb_tgt_t *, int); int (*t_setcontext)(mdb_tgt_t *, void *); void (*t_activate)(mdb_tgt_t *); void (*t_deactivate)(mdb_tgt_t *); void (*t_periodic)(mdb_tgt_t *); void (*t_destroy)(mdb_tgt_t *); const char *(*t_name)(mdb_tgt_t *); const char *(*t_isa)(mdb_tgt_t *); const char *(*t_platform)(mdb_tgt_t *); int (*t_uname)(mdb_tgt_t *, struct utsname *); int (*t_dmodel)(mdb_tgt_t *); ssize_t (*t_aread)(mdb_tgt_t *, mdb_tgt_as_t, void *, size_t, mdb_tgt_addr_t); ssize_t (*t_awrite)(mdb_tgt_t *, mdb_tgt_as_t, const void *, size_t, mdb_tgt_addr_t); ssize_t (*t_vread)(mdb_tgt_t *, void *, size_t, uintptr_t); ssize_t (*t_vwrite)(mdb_tgt_t *, const void *, size_t, uintptr_t); ssize_t (*t_pread)(mdb_tgt_t *, void *, size_t, physaddr_t); ssize_t (*t_pwrite)(mdb_tgt_t *, const void *, size_t, physaddr_t); ssize_t (*t_fread)(mdb_tgt_t *, void *, size_t, uintptr_t); ssize_t (*t_fwrite)(mdb_tgt_t *, const void *, size_t, uintptr_t); ssize_t (*t_ioread)(mdb_tgt_t *, void *, size_t, uintptr_t); ssize_t (*t_iowrite)(mdb_tgt_t *, const void *, size_t, uintptr_t); int (*t_vtop)(mdb_tgt_t *, mdb_tgt_as_t, uintptr_t, physaddr_t *); int (*t_lookup_by_name)(mdb_tgt_t *, const char *, const char *, GElf_Sym *, mdb_syminfo_t *); int (*t_lookup_by_addr)(mdb_tgt_t *, uintptr_t, uint_t, char *, size_t, GElf_Sym *, mdb_syminfo_t *); int (*t_symbol_iter)(mdb_tgt_t *, const char *, uint_t, uint_t, mdb_tgt_sym_f *, void *); int (*t_mapping_iter)(mdb_tgt_t *, mdb_tgt_map_f *, void *); int (*t_object_iter)(mdb_tgt_t *, mdb_tgt_map_f *, void *); const mdb_map_t *(*t_addr_to_map)(mdb_tgt_t *, uintptr_t); const mdb_map_t *(*t_name_to_map)(mdb_tgt_t *, const char *); struct ctf_file *(*t_addr_to_ctf)(mdb_tgt_t *, uintptr_t); struct ctf_file *(*t_name_to_ctf)(mdb_tgt_t *, const char *); int (*t_status)(mdb_tgt_t *, mdb_tgt_status_t *); int (*t_run)(mdb_tgt_t *, int, const struct mdb_arg *); int (*t_step)(mdb_tgt_t *, mdb_tgt_status_t *); int (*t_step_out)(mdb_tgt_t *, uintptr_t *); int (*t_next)(mdb_tgt_t *, uintptr_t *); int (*t_cont)(mdb_tgt_t *, mdb_tgt_status_t *); int (*t_signal)(mdb_tgt_t *, int); int (*t_add_vbrkpt)(mdb_tgt_t *, uintptr_t, int, mdb_tgt_se_f *, void *); int (*t_add_sbrkpt)(mdb_tgt_t *, const char *, int, mdb_tgt_se_f *, void *); int (*t_add_pwapt)(mdb_tgt_t *, physaddr_t, size_t, uint_t, int, mdb_tgt_se_f *, void *); int (*t_add_vwapt)(mdb_tgt_t *, uintptr_t, size_t, uint_t, int, mdb_tgt_se_f *, void *); int (*t_add_iowapt)(mdb_tgt_t *, uintptr_t, size_t, uint_t, int, mdb_tgt_se_f *, void *); int (*t_add_sysenter)(mdb_tgt_t *, int, int, mdb_tgt_se_f *, void *); int (*t_add_sysexit)(mdb_tgt_t *, int, int, mdb_tgt_se_f *, void *); int (*t_add_signal)(mdb_tgt_t *, int, int, mdb_tgt_se_f *, void *); int (*t_add_fault)(mdb_tgt_t *, int, int, mdb_tgt_se_f *, void *); int (*t_getareg)(mdb_tgt_t *, mdb_tgt_tid_t, const char *, mdb_tgt_reg_t *); int (*t_putareg)(mdb_tgt_t *, mdb_tgt_tid_t, const char *, mdb_tgt_reg_t); int (*t_stack_iter)(mdb_tgt_t *, const mdb_tgt_gregset_t *, mdb_tgt_stack_f *, void *); int (*t_auxv)(mdb_tgt_t *, const auxv_t **auxvp); int (*t_thread_name)(mdb_tgt_t *, mdb_tgt_tid_t, char *, size_t); } mdb_tgt_ops_t; /* * Software Event Specifiers * * The common target layer provides support for the management of software * event specifiers, used to describe conditions under which a live executing * target program instance will stop and transfer control back to the debugger. * Software event management design is discussed in more detail in mdb_target.c. */ struct mdb_sespec; /* Software event specifier */ struct mdb_vespec; /* Virtual event specifier */ typedef struct mdb_se_ops { int (*se_ctor)(mdb_tgt_t *, struct mdb_sespec *, void *); void (*se_dtor)(mdb_tgt_t *, struct mdb_sespec *); char *(*se_info)(mdb_tgt_t *, struct mdb_sespec *, struct mdb_vespec *, mdb_tgt_spec_desc_t *, char *, size_t); int (*se_secmp)(mdb_tgt_t *, struct mdb_sespec *, void *); int (*se_vecmp)(mdb_tgt_t *, struct mdb_vespec *, void *); int (*se_arm)(mdb_tgt_t *, struct mdb_sespec *); int (*se_disarm)(mdb_tgt_t *, struct mdb_sespec *); int (*se_cont)(mdb_tgt_t *, struct mdb_sespec *, mdb_tgt_status_t *); int (*se_match)(mdb_tgt_t *, struct mdb_sespec *, mdb_tgt_status_t *); } mdb_se_ops_t; #define T_SE_END ((void *)-1L) /* Sentinel for end of t_matched list */ typedef struct mdb_sespec { mdb_list_t se_selist; /* Sespec list forward/back pointers */ mdb_list_t se_velist; /* List of layered virtual specifiers */ struct mdb_sespec *se_matched; /* Pointer to next se on matched list */ const mdb_se_ops_t *se_ops; /* Pointer to ops vector */ void *se_data; /* Private storage for ops vector */ uint_t se_refs; /* Reference count */ int se_state; /* Event specifier state */ int se_errno; /* Last error code (if error state) */ } mdb_sespec_t; typedef struct mdb_vespec { mdb_list_t ve_list; /* Vespec list forward/back pointers */ int ve_id; /* Virtual event specifier ID (VID) */ int ve_flags; /* Flags (see mdb_target.h) */ uint_t ve_refs; /* Reference count */ uint_t ve_hits; /* Count of number of times matched */ uint_t ve_limit; /* Limit on number of times matched */ mdb_sespec_t *ve_se; /* Backpointer to sespec */ mdb_tgt_se_f *ve_callback; /* Callback for event owner */ void *ve_data; /* Private storage for callback */ void *ve_args; /* Arguments for sespec constructor */ void (*ve_dtor)(struct mdb_vespec *); /* Destructor for ve_args */ } mdb_vespec_t; /* * Xdata Descriptors * * Each external data item (xdata) exported by the target has a corresponding * descriptor associated with the target. The descriptor provides the name * and description of the data, as well as the routine which is used to * retrieve the actual data or its size. */ typedef struct mdb_xdata { mdb_list_t xd_list; /* Xdata list forward/back pointers */ const char *xd_name; /* Buffer name */ const char *xd_desc; /* Buffer description */ ssize_t (*xd_copy)(mdb_tgt_t *, void *, size_t); /* Copy routine */ } mdb_xdata_t; /* * Target Structure * * The target itself contains a few common data members, and then a pointer to * the underlying ops vector and its private storage pointer. MDB can manage * multiple targets simultaneously, and the list of all constructed targets is * pointed to by the mdb_t structure. */ struct mdb_tgt { mdb_list_t t_tgtlist; /* Target list forward/back pointers */ mdb_list_t t_active; /* List of active event specifiers */ mdb_list_t t_idle; /* List of inactive event specifiers */ mdb_list_t t_xdlist; /* List of xdata descriptors */ mdb_module_t *t_module; /* Backpointer to containing module */ void *t_pshandle; /* Proc service handle (if not tgt) */ const mdb_tgt_ops_t *t_ops; /* Pointer to target ops vector */ void *t_data; /* Private storage for implementation */ mdb_tgt_status_t t_status; /* Cached target status */ mdb_sespec_t *t_matched; /* List of matched event specifiers */ uint_t t_flags; /* Mode flags (see ) */ uint_t t_vecnt; /* Total number of vespecs */ int t_vepos; /* Sequence # for next vespec id > 0 */ int t_veneg; /* Sequence # for next vespec id < 0 */ }; /* * Special functions which targets can use to fill ops vector slots: */ extern long mdb_tgt_notsup(); /* Return -1, errno EMDB_TGTNOTSUP */ extern long mdb_tgt_hwnotsup(); /* return -1, errno EMDB_TGTHWNOTSUP */ extern void *mdb_tgt_null(); /* Return NULL, errno EMDB_TGTNOTSUP */ extern long mdb_tgt_nop(); /* Return 0 for success */ /* * Utility structures for target implementations: */ #define MDB_TGT_R_PRIV 0x001 /* Privileged register */ #define MDB_TGT_R_EXPORT 0x002 /* Export register as a variable */ #define MDB_TGT_R_ALIAS 0x004 /* Alias for another register name */ #define MDB_TGT_R_XREG 0x008 /* Extended register */ #define MDB_TGT_R_FPS 0x010 /* Single-precision floating-point */ #define MDB_TGT_R_FPD 0x020 /* Double-precision floating-point */ #define MDB_TGT_R_FPQ 0x040 /* Quad-precision floating-point */ #define MDB_TGT_R_FPU 0x080 /* FPU control/status register */ #define MDB_TGT_R_RDONLY 0x100 /* Register is read-only */ #define MDB_TGT_R_32 0x200 /* 32-bit version of register */ #define MDB_TGT_R_16 0x400 /* 16-bit version of register */ #define MDB_TGT_R_8H 0x800 /* upper half of a 16-bit reg */ #define MDB_TGT_R_8L 0x1000 /* lower half of a 16-bit reg */ #define MDB_TGT_R_IS_FP(f) ((f) & 0xf0) /* Test MDB_TGT_R_FP* bits */ #define MDB_TGT_R_NVAL(n, f) ((((ulong_t)(n)) << 16UL) | (f)) #define MDB_TGT_R_NUM(v) (((v) >> 16) & 0xffff) #define MDB_TGT_R_FLAGS(v) ((v) & 0xffff) typedef struct mdb_tgt_regdesc { const char *rd_name; /* Register string name */ ushort_t rd_num; /* Register index number */ ushort_t rd_flags; /* Register flags (see above) */ } mdb_tgt_regdesc_t; /* * Utility functions for target implementations to use in order to simplify * the implementation of various routines and to insert and delete xdata * specifiers and software event specifiers. Refer to the associated comments * in mdb_target.c for more information about each function. */ extern int mdb_tgt_xdata_insert(mdb_tgt_t *, const char *, const char *, ssize_t (*)(mdb_tgt_t *, void *, size_t)); extern int mdb_tgt_xdata_delete(mdb_tgt_t *, const char *); extern int mdb_tgt_sym_match(const GElf_Sym *, uint_t); extern void mdb_tgt_elf_export(mdb_gelf_file_t *); extern int mdb_tgt_sespec_activate_one(mdb_tgt_t *t, mdb_sespec_t *); extern int mdb_tgt_sespec_activate_all(mdb_tgt_t *t); extern void mdb_tgt_sespec_idle_one(mdb_tgt_t *t, mdb_sespec_t *, int); extern void mdb_tgt_sespec_idle_all(mdb_tgt_t *t, int, int); extern void mdb_tgt_sespec_arm_one(mdb_tgt_t *t, mdb_sespec_t *); extern void mdb_tgt_sespec_arm_all(mdb_tgt_t *t); extern void mdb_tgt_sespec_idle_one(mdb_tgt_t *t, mdb_sespec_t *, int); extern void mdb_tgt_sespec_idle_all(mdb_tgt_t *t, int, int); extern void mdb_tgt_sespec_prune_one(mdb_tgt_t *t, mdb_sespec_t *); extern void mdb_tgt_sespec_prune_all(mdb_tgt_t *t); extern mdb_sespec_t *mdb_tgt_sespec_insert(mdb_tgt_t *, const mdb_se_ops_t *, mdb_list_t *); extern mdb_sespec_t *mdb_tgt_sespec_lookup_active(mdb_tgt_t *, const mdb_se_ops_t *, void *); extern mdb_sespec_t *mdb_tgt_sespec_lookup_idle(mdb_tgt_t *, const mdb_se_ops_t *, void *); extern void mdb_tgt_sespec_hold(mdb_tgt_t *, mdb_sespec_t *); extern void mdb_tgt_sespec_rele(mdb_tgt_t *, mdb_sespec_t *); extern void mdb_tgt_sespec_prune_one(mdb_tgt_t *t, mdb_sespec_t *); extern void mdb_tgt_sespec_prune_all(mdb_tgt_t *t); extern mdb_sespec_t *mdb_tgt_sespec_insert(mdb_tgt_t *, const mdb_se_ops_t *, mdb_list_t *); extern mdb_sespec_t *mdb_tgt_sespec_lookup_active(mdb_tgt_t *, const mdb_se_ops_t *, void *); extern mdb_sespec_t *mdb_tgt_sespec_lookup_idle(mdb_tgt_t *, const mdb_se_ops_t *, void *); extern void mdb_tgt_sespec_hold(mdb_tgt_t *, mdb_sespec_t *); extern void mdb_tgt_sespec_rele(mdb_tgt_t *, mdb_sespec_t *); extern int mdb_tgt_vespec_insert(mdb_tgt_t *, const mdb_se_ops_t *, int, mdb_tgt_se_f *, void *, void *, void (*)(mdb_vespec_t *)); extern mdb_vespec_t *mdb_tgt_vespec_lookup(mdb_tgt_t *, int); extern int mdb_tgt_auxv(mdb_tgt_t *, const auxv_t **); extern void mdb_tgt_vespec_hold(mdb_tgt_t *, mdb_vespec_t *); extern void mdb_tgt_vespec_rele(mdb_tgt_t *, mdb_vespec_t *); /* * Utility function that target implementations can use to register dcmds, * walkers, and to create named variables for registers */ extern int mdb_tgt_register_dcmds(mdb_tgt_t *, const mdb_dcmd_t *, int); extern int mdb_tgt_register_walkers(mdb_tgt_t *, const mdb_walker_t *, int); extern void mdb_tgt_register_regvars(mdb_tgt_t *, const mdb_tgt_regdesc_t *, const mdb_nv_disc_t *, int); /* * Utility functions that target implementations can use to fill in the * mdb_se_ops_t structure and vespec destructor. Each software event specifier * must minimally supply its own constructor, info function, and match function. */ extern void no_ve_dtor(mdb_vespec_t *); extern void no_se_dtor(mdb_tgt_t *, mdb_sespec_t *); extern int no_se_secmp(mdb_tgt_t *, mdb_sespec_t *, void *); extern int no_se_vecmp(mdb_tgt_t *, mdb_vespec_t *, void *); extern int no_se_arm(mdb_tgt_t *, mdb_sespec_t *); extern int no_se_disarm(mdb_tgt_t *, mdb_sespec_t *); extern int no_se_cont(mdb_tgt_t *, mdb_sespec_t *, mdb_tgt_status_t *); /* * In the initial version of MDB, the data model property is not part of the * public API. However, I am providing this as a hidden part of the ABI as * one way we can handle the situation. If this turns out to be the right * decision, we can document it later without having to rev the API version. */ #define MDB_TGT_MODEL_UNKNOWN 0 /* Unknown data model */ #define MDB_TGT_MODEL_ILP32 1 /* Target data model is ILP32 */ #define MDB_TGT_MODEL_LP64 2 /* Target data model is LP64 */ #ifdef _LP64 #define MDB_TGT_MODEL_NATIVE MDB_TGT_MODEL_LP64 #else #define MDB_TGT_MODEL_NATIVE MDB_TGT_MODEL_ILP32 #endif extern int mdb_prop_datamodel; #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_TARGET_IMPL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ /* * libthread_db (tdb) cache * * In order to properly debug multi-threaded programs, the proc target must be * able to query and modify information such as a thread's register set using * either the native LWP services provided by libproc (if the process is not * linked with libthread), or using the services provided by libthread_db (if * the process is linked with libthread). Additionally, a process may begin * life as a single-threaded process and then later dlopen() libthread, so we * must be prepared to switch modes on-the-fly. There are also two possible * libthread implementations (one in /usr/lib and one in /usr/lib/lwp) so we * cannot link mdb against libthread_db directly; instead, we must dlopen the * appropriate libthread_db on-the-fly based on which libthread.so the victim * process has open. Finally, mdb is designed so that multiple targets can be * active simultaneously, so we could even have *both* libthread_db's open at * the same time. This might happen if you were looking at two multi-threaded * user processes inside of a crash dump, one using /usr/lib/libthread.so and * the other using /usr/lib/lwp/libthread.so. To meet these requirements, we * implement a libthread_db "cache" in this file. The proc target calls * mdb_tdb_load() with the pathname of a libthread_db to load, and if it is * not already open, we dlopen() it, look up the symbols we need to reference, * and fill in an ops vector which we return to the caller. Once an object is * loaded, we don't bother unloading it unless the entire cache is explicitly * flushed. This mechanism also has the nice property that we don't bother * loading libthread_db until we need it, so the debugger starts up faster. */ #include #include #include #include #include #include #include static mdb_tdb_lib_t *tdb_list; static td_err_e tdb_notsup() { return (TD_NOCAPAB); /* return thread_db code for not supported */ } const mdb_tdb_ops_t * mdb_tdb_load(const char *path) { td_err_e (*tdb_init)(void); mdb_tdb_lib_t *t; td_err_e err; void *hdl; /* * Search through the existing cache of thread_db libraries and see if * we have this one loaded already. If so, just return its ops vector. */ for (t = tdb_list; t != NULL; t = t->tdb_next) { if (strcmp(path, t->tdb_pathname) == 0) break; } if (t != NULL) return (&t->tdb_ops); /* * Otherwise dlmopen the new library, look up its td_init() function, * and call it. If any of this fails, we return NULL for failure. */ if (access(path, F_OK) == -1) return (NULL); if ((hdl = dlmopen(LM_ID_BASE, path, RTLD_LAZY | RTLD_LOCAL)) == NULL) { (void) set_errno(EMDB_RTLD); return (NULL); } if ((tdb_init = (td_err_e (*)(void))dlsym(hdl, "td_init")) == NULL) { (void) dlclose(hdl); (void) set_errno(tdb_to_errno(TD_NOCAPAB)); return (NULL); } if ((err = tdb_init()) != TD_OK) { (void) dlclose(hdl); (void) set_errno(tdb_to_errno(err)); return (NULL); } /* * If td_init() succeeds, we can't fail from here on. Allocate a new * library entry and add it to our linked list. */ t = mdb_alloc(sizeof (mdb_tdb_lib_t), UM_SLEEP); (void) strncpy(t->tdb_pathname, path, MAXPATHLEN); t->tdb_pathname[MAXPATHLEN - 1] = '\0'; t->tdb_handle = hdl; t->tdb_next = tdb_list; tdb_list = t; /* * For each function we need to call in the thread_db library, look it * up using dlsym(). If we find it, add it to the ops vector. If not, * put the address of our default function (see above) in that slot. */ t->tdb_ops.td_ta_new = (td_err_e (*)())dlsym(hdl, "td_ta_new"); if (t->tdb_ops.td_ta_new == NULL) t->tdb_ops.td_ta_new = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_ta_delete = (td_err_e (*)())dlsym(hdl, "td_ta_delete"); if (t->tdb_ops.td_ta_delete == NULL) t->tdb_ops.td_ta_delete = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_ta_thr_iter = (td_err_e (*)()) dlsym(hdl, "td_ta_thr_iter"); if (t->tdb_ops.td_ta_thr_iter == NULL) t->tdb_ops.td_ta_thr_iter = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_ta_map_id2thr = (td_err_e (*)()) dlsym(hdl, "td_ta_map_id2thr"); if (t->tdb_ops.td_ta_map_id2thr == NULL) t->tdb_ops.td_ta_map_id2thr = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_ta_map_lwp2thr = (td_err_e (*)()) dlsym(hdl, "td_ta_map_lwp2thr"); if (t->tdb_ops.td_ta_map_lwp2thr == NULL) t->tdb_ops.td_ta_map_lwp2thr = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_get_info = (td_err_e (*)()) dlsym(hdl, "td_thr_get_info"); if (t->tdb_ops.td_thr_get_info == NULL) t->tdb_ops.td_thr_get_info = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_getgregs = (td_err_e (*)()) dlsym(hdl, "td_thr_getgregs"); if (t->tdb_ops.td_thr_getgregs == NULL) t->tdb_ops.td_thr_getgregs = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_setgregs = (td_err_e (*)()) dlsym(hdl, "td_thr_setgregs"); if (t->tdb_ops.td_thr_setgregs == NULL) t->tdb_ops.td_thr_setgregs = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_getfpregs = (td_err_e (*)()) dlsym(hdl, "td_thr_getfpregs"); if (t->tdb_ops.td_thr_getfpregs == NULL) t->tdb_ops.td_thr_getfpregs = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_setfpregs = (td_err_e (*)()) dlsym(hdl, "td_thr_setfpregs"); if (t->tdb_ops.td_thr_setfpregs == NULL) t->tdb_ops.td_thr_setfpregs = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_tlsbase = (td_err_e (*)()) dlsym(hdl, "td_thr_tlsbase"); if (t->tdb_ops.td_thr_tlsbase == NULL) t->tdb_ops.td_thr_tlsbase = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_getxregsize = (td_err_e (*)()) dlsym(hdl, "td_thr_getxregsize"); if (t->tdb_ops.td_thr_getxregsize == NULL) t->tdb_ops.td_thr_getxregsize = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_getxregs = (td_err_e (*)()) dlsym(hdl, "td_thr_getxregs"); if (t->tdb_ops.td_thr_getxregs == NULL) t->tdb_ops.td_thr_getxregs = (td_err_e (*)())tdb_notsup; t->tdb_ops.td_thr_setxregs = (td_err_e (*)()) dlsym(hdl, "td_thr_setxregs"); if (t->tdb_ops.td_thr_setxregs == NULL) t->tdb_ops.td_thr_setxregs = (td_err_e (*)())tdb_notsup; return (&t->tdb_ops); } void mdb_tdb_flush(void) { mdb_tdb_lib_t *t, *u; for (t = tdb_list; t != NULL; t = u) { u = t->tdb_next; (void) dlclose(t->tdb_handle); mdb_free(t, sizeof (mdb_tdb_lib_t)); } tdb_list = NULL; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_TDB_H #define _MDB_TDB_H #ifdef __cplusplus extern "C" { #endif #ifdef _MDB #include #include #include typedef struct mdb_tdb_ops { td_err_e (*td_ta_new)(struct ps_prochandle *, td_thragent_t **); td_err_e (*td_ta_delete)(td_thragent_t *); td_err_e (*td_ta_thr_iter)(const td_thragent_t *, td_thr_iter_f *, void *, td_thr_state_e, int, sigset_t *, uint_t); td_err_e (*td_ta_map_id2thr)(const td_thragent_t *, thread_t, td_thrhandle_t *); td_err_e (*td_ta_map_lwp2thr)(const td_thragent_t *, lwpid_t, td_thrhandle_t *); td_err_e (*td_thr_get_info)(const td_thrhandle_t *, td_thrinfo_t *); td_err_e (*td_thr_getgregs)(const td_thrhandle_t *, prgregset_t); td_err_e (*td_thr_setgregs)(const td_thrhandle_t *, const prgregset_t); td_err_e (*td_thr_getfpregs)(const td_thrhandle_t *, prfpregset_t *); td_err_e (*td_thr_setfpregs)(const td_thrhandle_t *, const prfpregset_t *); td_err_e (*td_thr_tlsbase)(const td_thrhandle_t *, ulong_t, psaddr_t *); td_err_e (*td_thr_getxregsize)(const td_thrhandle_t *, int *); td_err_e (*td_thr_getxregs)(const td_thrhandle_t *, void *); td_err_e (*td_thr_setxregs)(const td_thrhandle_t *, const void *); } mdb_tdb_ops_t; typedef struct mdb_tdb_lib { char tdb_pathname[MAXPATHLEN]; /* Absolute pathname of library */ mdb_tdb_ops_t tdb_ops; /* Ops vector for this library */ void *tdb_handle; /* Library rtld object handle */ struct mdb_tdb_lib *tdb_next; /* Pointer to next library in cache */ } mdb_tdb_lib_t; extern const mdb_tdb_ops_t *mdb_tdb_load(const char *); extern void mdb_tdb_flush(void); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_TDB_H */ /* * 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 2017 Joyent, Inc. */ /* * Terminal I/O Backend * * Terminal editing backend for standard input. The terminal i/o backend is * actually built on top of two other i/o backends: one for raw input and * another for raw output (presumably stdin and stdout). When IOP_READ is * invoked, the terminal backend enters a read-loop in which it can perform * command-line editing and access a history buffer. Once a newline is read, * the entire buffered command-line is returned to the caller. The termio * code makes use of a command buffer (see mdb_cmdbuf.c) to maintain and * manipulate the state of a command line, and store it for re-use in a * history list. The termio code manipulates the terminal to keep it in * sync with the contents of the command buffer, and moves the cursor in * response to editing commands. * * The terminal backend is also responsible for maintaining and manipulating * the settings (see stty(1) and termio(4I)) associated with the terminal. * The debugger makes use of four distinct sets of terminal attributes: * * (1) the settings used by the debugger's parent process (tio_ptios), * (2) the settings used by a controlled child process (tio_ctios), * (3) the settings used for reading and command-line editing (tio_rtios), and * (4) the settings used when mdb dcmds are executing (tio_dtios). * * The parent settings (1) are read from the terminal during initialization. * These settings are restored before the debugger exits or when it is stopped * by SIGTSTP. The child settings (2) are initially a copy of (1), but are * then restored prior to continuing execution of a victim process. The new * settings (3) and (4) are both derived from (1). The raw settings (3) used * for reading from the terminal allow the terminal code to respond instantly * to keypresses and perform all the necessary handling. The dcmd settings (4) * are essentially the same as (1), except that we make sure ISIG is enabled * so that we will receive asynchronous SIGINT notification from the terminal * driver if the user types the interrupt character (typically ^C). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ERR #undef ERR #endif #include #define KEY_ESC (0x01b) /* Escape key code */ #define KEY_DEL (0x07f) /* ASCII DEL key code */ /* * These macros support the use of various ranges within the "tio_keymap" * member of "termio_data_t" objects. This array maps from an input byte, or * special control code, to the appropriate terminal handling callback. The * array has KEY_MAX (0x1ff) entries, partitioned as follows: * * 0 - 7f 7-bit ASCII byte * 80 - ff META() ASCII byte with Meta key modifier * 100 - 119 KPAD() Alphabetic character received as part of a single-byte * cursor control sequence, e.g. ESC [ A * 11a - 123 FKEY() Numeric character received as part of a function key * control sequence, e.g. ESC [ 4 ~ * 124 - 1ff Unused */ #define META(c) (((c) & 0x7f) | 0x80) #define KPAD(c) (((c) < 'A' || (c) > 'Z') ? 0 : ((c) - 'A' + 0x100)) #define FKEY(c) (((c) < '0' || (c) > '9') ? 0 : ((c) - '0' + 0x11a)) /* * These macros allow for composition of control sequences for xterm and other * terminals that support certain features of the VT102 and later VT terminals. * Refer to the classic monograph "Xterm Control Sequences" for more info. */ #define TI_DECSET(Pm) "\033[?" Pm "h" /* Compose DEC private mode set */ #define TI_DECRST(Pm) "\033[?" Pm "l" /* Compose DEC private mode reset */ #define TI_DECSAV(Pm) "\033[?" Pm "s" /* Compose DEC private mode save */ #define TI_DECRES(Pm) "\033[?" Pm "r" /* Compose DEC private mode restore */ #define TI_DECCOLM "3" /* Ps = DEC 80/132 column mode */ #define TI_COLENAB "40" /* Ps = 80/132 column switch enable */ #define TIO_DEFAULT_ROWS 24 /* Default number of rows */ #define TIO_DEFAULT_COLS 80 /* Default number of columns */ typedef union termio_attr_val { const char *at_str; /* String value */ int at_val; /* Integer or boolean value */ } termio_attr_val_t; typedef struct termio_info { termio_attr_val_t ti_cub1; /* Move back one space */ termio_attr_val_t ti_cuf1; /* Move forward one space */ termio_attr_val_t ti_cuu1; /* Move up one line */ termio_attr_val_t ti_cud1; /* Move down one line */ termio_attr_val_t ti_pad; /* Pad character */ termio_attr_val_t ti_el; /* Clear to end-of-line */ termio_attr_val_t ti_am; /* Automatic right margin? */ termio_attr_val_t ti_bw; /* Backward motion at left edge? */ termio_attr_val_t ti_npc; /* No padding character? */ termio_attr_val_t ti_xenl; /* Newline ignored at autowrap? */ termio_attr_val_t ti_xon; /* Use xon/xoff handshaking? */ termio_attr_val_t ti_cols; /* # of columns */ termio_attr_val_t ti_lines; /* # of rows */ termio_attr_val_t ti_pb; /* Lowest baud rate that requires pad */ termio_attr_val_t ti_smso; /* Set standout mode */ termio_attr_val_t ti_rmso; /* Remove standout mode */ termio_attr_val_t ti_smul; /* Set underline mode */ termio_attr_val_t ti_rmul; /* Remove underline mode */ termio_attr_val_t ti_enacs; /* Enable alternate character set */ termio_attr_val_t ti_smacs; /* Set alternate character set */ termio_attr_val_t ti_rmacs; /* Remove alternate character set */ termio_attr_val_t ti_smcup; /* Set mode where cup is active */ termio_attr_val_t ti_rmcup; /* Remove mode where cup is active */ termio_attr_val_t ti_rev; /* Set reverse video mode */ termio_attr_val_t ti_bold; /* Set bold text mode */ termio_attr_val_t ti_dim; /* Set dim text mode */ termio_attr_val_t ti_sgr0; /* Remove all video attributes */ termio_attr_val_t ti_smir; /* Set insert mode */ termio_attr_val_t ti_rmir; /* Remove insert mode */ termio_attr_val_t ti_ich1; /* Insert character */ termio_attr_val_t ti_ip; /* Insert pad delay in msecs */ termio_attr_val_t ti_clear; /* Clear screen and home cursor */ termio_attr_val_t ti_cnorm; /* Make cursor appear normal */ termio_attr_val_t ti_nel; /* Newline */ termio_attr_val_t ti_cr; /* Carriage return */ termio_attr_val_t ti_smam; /* Turn on automatic margins */ } termio_info_t; typedef enum { TIO_ATTR_REQSTR, /* String attribute that is required */ TIO_ATTR_STR, /* String attribute */ TIO_ATTR_BOOL, /* Boolean attribute */ TIO_ATTR_INT /* Integer attribute */ } termio_attr_type_t; typedef struct termio_attr { const char *ta_name; /* Capability name */ termio_attr_type_t ta_type; /* Capability type */ termio_attr_val_t *ta_valp; /* String pointer location */ } termio_attr_t; struct termio_data; typedef const char *(*keycb_t)(struct termio_data *, int); typedef void (*putp_t)(struct termio_data *, const char *, uint_t); #define TIO_FINDHIST 0x01 /* Find-history-mode */ #define TIO_AUTOWRAP 0x02 /* Terminal has autowrap */ #define TIO_BACKLEFT 0x04 /* Terminal can go back at left edge */ #define TIO_INSERT 0x08 /* Terminal has insert mode */ #define TIO_USECUP 0x10 /* Use smcup/rmcup sequences */ #define TIO_TTYWARN 0x20 /* Warnings about tty issued */ #define TIO_CAPWARN 0x40 /* Warnings about terminfo issued */ #define TIO_XTERM 0x80 /* Terminal is xterm compatible */ #define TIO_TAB 0x100 /* Tab completion mode */ #define TIO_LAZYWRAP 0x200 /* Lazy cursor on autowrap */ static const mdb_bitmask_t tio_flag_masks[] = { { "FINDHIST", TIO_FINDHIST, TIO_FINDHIST }, { "AUTOWRAP", TIO_AUTOWRAP, TIO_AUTOWRAP }, { "BACKLEFT", TIO_BACKLEFT, TIO_BACKLEFT }, { "INSERT", TIO_INSERT, TIO_INSERT }, { "USECUP", TIO_USECUP, TIO_USECUP }, { "TTYWARN", TIO_TTYWARN, TIO_TTYWARN }, { "CAPWARN", TIO_CAPWARN, TIO_CAPWARN }, { "XTERM", TIO_XTERM, TIO_XTERM }, { "TAB", TIO_TAB, TIO_TAB }, { "LAZYWRAP", TIO_LAZYWRAP, TIO_LAZYWRAP }, { NULL, 0, 0 } }; typedef struct termio_data { mdb_io_t *tio_io; /* Pointer back to containing i/o */ mdb_io_t *tio_out_io; /* Terminal output backend */ mdb_io_t *tio_in_io; /* Terminal input backend */ mdb_iob_t *tio_out; /* I/o buffer for terminal output */ mdb_iob_t *tio_in; /* I/o buffer for terminal input */ mdb_iob_t *tio_link; /* I/o buffer to resize on WINCH */ keycb_t tio_keymap[KEY_MAX]; /* Keymap (see comments atop file) */ mdb_cmdbuf_t tio_cmdbuf; /* Editable command-line buffer */ struct termios tio_ptios; /* Parent terminal settings */ struct termios tio_ctios; /* Child terminal settings */ struct termios tio_rtios; /* Settings for read loop */ struct termios tio_dtios; /* Settings for dcmd execution */ sigjmp_buf tio_env; /* Read loop setjmp(3c) environment */ termio_info_t tio_info; /* Terminal attribute strings */ char *tio_attrs; /* Attribute string buffer */ size_t tio_attrslen; /* Length in bytes of tio_attrs */ const char *tio_prompt; /* Prompt string for this read */ size_t tio_promptlen; /* Length of prompt string */ size_t tio_rows; /* Terminal height */ size_t tio_cols; /* Terminal width */ size_t tio_x; /* Cursor x coordinate */ size_t tio_y; /* Cursor y coordinate */ size_t tio_max_x; /* Previous maximum x coordinate */ size_t tio_max_y; /* Previous maximum y coordinate */ int tio_intr; /* Interrupt char */ int tio_quit; /* Quit char */ int tio_erase; /* Erase char */ int tio_werase; /* Word-erase char */ int tio_kill; /* Kill char */ int tio_eof; /* End-of-file char */ int tio_susp; /* Suspend char */ uint_t tio_flags; /* Miscellaneous flags */ volatile mdb_bool_t tio_active; /* Flag denoting read loop active */ volatile mdb_bool_t tio_rti_on; /* Flag denoting rtios in use */ putp_t tio_putp; /* termio_tput() subroutine */ uint_t tio_baud; /* Baud rate (chars per second) */ uint_t tio_usecpc; /* Usecs per char at given baud rate */ pid_t tio_opgid; /* Old process group id for terminal */ uint_t tio_suspended; /* termio_suspend_tty() nesting count */ } termio_data_t; static ssize_t termio_read(mdb_io_t *, void *, size_t); static ssize_t termio_write(mdb_io_t *, const void *, size_t); static off64_t termio_seek(mdb_io_t *, off64_t, int); static int termio_ctl(mdb_io_t *, int, void *); static void termio_close(mdb_io_t *); static const char *termio_name(mdb_io_t *); static void termio_link(mdb_io_t *, mdb_iob_t *); static void termio_unlink(mdb_io_t *, mdb_iob_t *); static int termio_setattr(mdb_io_t *, int, uint_t); static void termio_suspend(mdb_io_t *); static void termio_resume(mdb_io_t *); static void termio_suspend_tty(termio_data_t *, struct termios *); static void termio_resume_tty(termio_data_t *, struct termios *); static void termio_putp(termio_data_t *, const char *, uint_t); static void termio_puts(termio_data_t *, const char *, uint_t); static void termio_tput(termio_data_t *, const char *, uint_t); static void termio_addch(termio_data_t *, char, size_t); static void termio_insch(termio_data_t *, char, size_t); static void termio_mvcur(termio_data_t *); static void termio_bspch(termio_data_t *); static void termio_delch(termio_data_t *); static void termio_clear(termio_data_t *); static void termio_redraw(termio_data_t *); static void termio_prompt(termio_data_t *); static const char *termio_tab(termio_data_t *, int); static const char *termio_insert(termio_data_t *, int); static const char *termio_accept(termio_data_t *, int); static const char *termio_backspace(termio_data_t *, int); static const char *termio_delchar(termio_data_t *, int); static const char *termio_fwdchar(termio_data_t *, int); static const char *termio_backchar(termio_data_t *, int); static const char *termio_transpose(termio_data_t *, int); static const char *termio_home(termio_data_t *, int); static const char *termio_end(termio_data_t *, int); static const char *termio_fwdword(termio_data_t *, int); static const char *termio_backword(termio_data_t *, int); static const char *termio_kill(termio_data_t *, int); static const char *termio_killfwdword(termio_data_t *, int); static const char *termio_killbackword(termio_data_t *, int); static const char *termio_reset(termio_data_t *, int); static const char *termio_widescreen(termio_data_t *, int); static const char *termio_prevhist(termio_data_t *, int); static const char *termio_nexthist(termio_data_t *, int); static const char *termio_accel(termio_data_t *, int); static const char *termio_findhist(termio_data_t *, int); static const char *termio_refresh(termio_data_t *, int); static const char *termio_intr(termio_data_t *, int); static const char *termio_quit(termio_data_t *, int); static const char *termio_susp(termio_data_t *, int); static void termio_winch(int, siginfo_t *, ucontext_t *, void *); static void termio_tstp(int, siginfo_t *, ucontext_t *, void *); extern const char *tigetstr(const char *); extern int tigetflag(const char *); extern int tigetnum(const char *); static const mdb_io_ops_t termio_ops = { .io_read = termio_read, .io_write = termio_write, .io_seek = termio_seek, .io_ctl = termio_ctl, .io_close = termio_close, .io_name = termio_name, .io_link = termio_link, .io_unlink = termio_unlink, .io_setattr = termio_setattr, .io_suspend = termio_suspend, .io_resume = termio_resume, }; static termio_info_t termio_info; static const termio_attr_t termio_attrs[] = { { "cub1", TIO_ATTR_REQSTR, &termio_info.ti_cub1 }, { "cuf1", TIO_ATTR_REQSTR, &termio_info.ti_cuf1 }, { "cuu1", TIO_ATTR_REQSTR, &termio_info.ti_cuu1 }, { "cud1", TIO_ATTR_REQSTR, &termio_info.ti_cud1 }, { "pad", TIO_ATTR_STR, &termio_info.ti_pad }, { "el", TIO_ATTR_REQSTR, &termio_info.ti_el }, { "am", TIO_ATTR_BOOL, &termio_info.ti_am }, { "bw", TIO_ATTR_BOOL, &termio_info.ti_bw }, { "npc", TIO_ATTR_BOOL, &termio_info.ti_npc }, { "xenl", TIO_ATTR_BOOL, &termio_info.ti_xenl }, { "xon", TIO_ATTR_BOOL, &termio_info.ti_xon }, { "cols", TIO_ATTR_INT, &termio_info.ti_cols }, { "lines", TIO_ATTR_INT, &termio_info.ti_lines }, { "pb", TIO_ATTR_INT, &termio_info.ti_pb }, { "smso", TIO_ATTR_STR, &termio_info.ti_smso }, { "rmso", TIO_ATTR_STR, &termio_info.ti_rmso }, { "smul", TIO_ATTR_STR, &termio_info.ti_smul }, { "rmul", TIO_ATTR_STR, &termio_info.ti_rmul }, { "enacs", TIO_ATTR_STR, &termio_info.ti_enacs }, { "smacs", TIO_ATTR_STR, &termio_info.ti_smacs }, { "rmacs", TIO_ATTR_STR, &termio_info.ti_rmacs }, { "smcup", TIO_ATTR_STR, &termio_info.ti_smcup }, { "rmcup", TIO_ATTR_STR, &termio_info.ti_rmcup }, { "rev", TIO_ATTR_STR, &termio_info.ti_rev }, { "bold", TIO_ATTR_STR, &termio_info.ti_bold }, { "dim", TIO_ATTR_STR, &termio_info.ti_dim }, { "sgr0", TIO_ATTR_STR, &termio_info.ti_sgr0 }, { "smir", TIO_ATTR_STR, &termio_info.ti_smir }, { "rmir", TIO_ATTR_STR, &termio_info.ti_rmir }, { "ich1", TIO_ATTR_STR, &termio_info.ti_ich1 }, { "ip", TIO_ATTR_STR, &termio_info.ti_ip }, { "clear", TIO_ATTR_STR, &termio_info.ti_clear }, { "cnorm", TIO_ATTR_STR, &termio_info.ti_cnorm }, { "nel", TIO_ATTR_STR, &termio_info.ti_nel }, { "cr", TIO_ATTR_STR, &termio_info.ti_cr }, { "smam", TIO_ATTR_STR, &termio_info.ti_smam }, { NULL, 0, NULL } }; /* * One-key accelerators. Some commands are used so frequently as to need * single-key equivalents. termio_accelkeys contains a list of the accelerator * keys, with termio_accel listing the accelerated commands. The array is * indexed by the offset of the accelerator in the macro string, and as such * *must* stay in the same order. */ static const char *const termio_accelkeys = "[]"; static const char *const termio_accelstrings[] = { "::step over", /* [ */ "::step" /* ] */ }; static const char * termio_accel_lookup(int c) { const char *acc; if ((acc = strchr(termio_accelkeys, c)) == NULL) return (NULL); return (termio_accelstrings[(int)(acc - termio_accelkeys)]); } static ssize_t termio_read(mdb_io_t *io, void *buf, size_t nbytes) { termio_data_t *td = io->io_data; mdb_bool_t esc = FALSE, pad = FALSE; ssize_t rlen = 0; int c, fkey = 0; const char *s; size_t len; if (io->io_next != NULL) return (IOP_READ(io->io_next, buf, nbytes)); td->tio_rti_on = TRUE; if (termio_ctl(td->tio_io, TCSETSW, &td->tio_rtios) == -1) warn("failed to set terminal attributes"); if (nbytes == 1) { if ((c = mdb_iob_getc(td->tio_in)) == EOF) goto out; *((uchar_t *)buf) = (uchar_t)c; rlen = 1; goto out; } if (td->tio_flags & TIO_TAB) termio_redraw(td); else termio_prompt(td); /* * We need to redraw the entire command-line and restart our read loop * in the event of a SIGWINCH or resume following SIGTSTP (SIGCONT). */ if (sigsetjmp(td->tio_env, 1) != 0) { td->tio_active = FALSE; td->tio_x = td->tio_y = 0; len = td->tio_cmdbuf.cmd_buflen + td->tio_promptlen; td->tio_max_x = len % td->tio_cols; td->tio_max_y = len / td->tio_cols; esc = pad = FALSE; termio_tput(td, td->tio_info.ti_cr.at_str, 1); mdb_iob_flush(td->tio_out); termio_redraw(td); } /* * Since we're about to start the read loop, we know our linked iob * is quiescent. We can now safely resize it to the latest term size. */ if (td->tio_link != NULL) mdb_iob_resize(td->tio_link, td->tio_rows, td->tio_cols); td->tio_active = TRUE; /* * We may have had some error while in tab completion mode which sent us * longjmping all over the place. If that's the case, come back here and * make sure the flag is off. */ td->tio_flags &= ~TIO_TAB; do { char_loop: if ((c = mdb_iob_getc(td->tio_in)) == EOF) { td->tio_active = FALSE; goto out; } if (c == KEY_ESC && esc == FALSE) { esc = TRUE; goto char_loop; } if (esc) { esc = FALSE; if (c == '[') { pad++; goto char_loop; } c = META(c); } if (pad) { pad = FALSE; if ((fkey = FKEY(c)) != 0) { /* * Some terminals send a multibyte control * sequence for particular function keys. * These sequences are of the form: * * ESC [ n ~ * * where "n" is a numeric character from * '0' to '9'. */ goto char_loop; } if ((c = KPAD(c)) == 0) { /* * This was not a valid keypad control * sequence. */ goto char_loop; } } if (fkey != 0) { if (c == '~') { /* * This is a valid special function key * sequence. Use the value we stashed * earlier. */ c = fkey; } fkey = 0; } len = td->tio_cmdbuf.cmd_buflen + td->tio_promptlen; td->tio_max_x = len % td->tio_cols; td->tio_max_y = len / td->tio_cols; } while ((s = (*td->tio_keymap[c])(td, c)) == NULL); td->tio_active = FALSE; mdb_iob_nl(td->tio_out); if ((rlen = strlen(s)) >= nbytes - 1) rlen = nbytes - 1; (void) strncpy(buf, s, rlen); ((char *)buf)[rlen++] = '\n'; out: td->tio_rti_on = FALSE; if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1) warn("failed to restore terminal attributes"); return (rlen); } static ssize_t termio_write(mdb_io_t *io, const void *buf, size_t nbytes) { termio_data_t *td = io->io_data; if (io->io_next != NULL) return (IOP_WRITE(io->io_next, buf, nbytes)); return (IOP_WRITE(td->tio_out_io, buf, nbytes)); } /*ARGSUSED*/ static off64_t termio_seek(mdb_io_t *io, off64_t offset, int whence) { return (set_errno(ENOTSUP)); } static int termio_ctl(mdb_io_t *io, int req, void *arg) { termio_data_t *td = io->io_data; if (io->io_next != NULL) return (IOP_CTL(io->io_next, req, arg)); if (req == MDB_IOC_CTTY) { bcopy(&td->tio_ptios, &td->tio_ctios, sizeof (struct termios)); return (0); } return (IOP_CTL(td->tio_in_io, req, arg)); } static void termio_close(mdb_io_t *io) { termio_data_t *td = io->io_data; (void) mdb_signal_sethandler(SIGWINCH, MDB_SIG_DFL, NULL); (void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_DFL, NULL); termio_suspend_tty(td, &td->tio_ptios); if (td->tio_attrs) mdb_free(td->tio_attrs, td->tio_attrslen); mdb_cmdbuf_destroy(&td->tio_cmdbuf); mdb_iob_destroy(td->tio_out); mdb_iob_destroy(td->tio_in); mdb_free(td, sizeof (termio_data_t)); } static const char * termio_name(mdb_io_t *io) { termio_data_t *td = io->io_data; if (io->io_next != NULL) return (IOP_NAME(io->io_next)); return (IOP_NAME(td->tio_in_io)); } static void termio_link(mdb_io_t *io, mdb_iob_t *iob) { termio_data_t *td = io->io_data; if (io->io_next == NULL) { mdb_iob_resize(iob, td->tio_rows, td->tio_cols); td->tio_link = iob; } else IOP_LINK(io->io_next, iob); } static void termio_unlink(mdb_io_t *io, mdb_iob_t *iob) { termio_data_t *td = io->io_data; if (io->io_next == NULL) { if (td->tio_link == iob) td->tio_link = NULL; } else IOP_UNLINK(io->io_next, iob); } static int termio_setattr(mdb_io_t *io, int req, uint_t attrs) { termio_data_t *td = io->io_data; if (io->io_next != NULL) return (IOP_SETATTR(io->io_next, req, attrs)); if ((req != ATT_ON && req != ATT_OFF) || (attrs & ~ATT_ALL) != 0) return (set_errno(EINVAL)); if (req == ATT_ON) { if (attrs & ATT_STANDOUT) termio_tput(td, td->tio_info.ti_smso.at_str, 1); if (attrs & ATT_UNDERLINE) termio_tput(td, td->tio_info.ti_smul.at_str, 1); if (attrs & ATT_REVERSE) termio_tput(td, td->tio_info.ti_rev.at_str, 1); if (attrs & ATT_BOLD) termio_tput(td, td->tio_info.ti_bold.at_str, 1); if (attrs & ATT_DIM) termio_tput(td, td->tio_info.ti_dim.at_str, 1); if (attrs & ATT_ALTCHARSET) termio_tput(td, td->tio_info.ti_smacs.at_str, 1); } else { if (attrs & ATT_STANDOUT) termio_tput(td, td->tio_info.ti_rmso.at_str, 1); if (attrs & ATT_UNDERLINE) termio_tput(td, td->tio_info.ti_rmul.at_str, 1); if (attrs & ATT_ALTCHARSET) termio_tput(td, td->tio_info.ti_rmacs.at_str, 1); if (attrs & (ATT_REVERSE | ATT_BOLD | ATT_DIM)) termio_tput(td, td->tio_info.ti_sgr0.at_str, 1); } mdb_iob_flush(td->tio_out); return (0); } /* * Issue a warning message if the given warning flag is clear. Then set the * flag bit so that we do not issue multiple instances of the same warning. */ static void termio_warn(termio_data_t *td, uint_t flag, const char *format, ...) { if (!(td->tio_flags & flag)) { va_list alist; va_start(alist, format); vwarn(format, alist); va_end(alist); td->tio_flags |= flag; } } /* * Restore the terminal to its previous state before relinquishing control of * it to the shell (on a SIGTSTP) or the victim process (on a continue). If * we need to change the foreground process group, we must temporarily ignore * SIGTTOU because TIOCSPGRP could trigger it. */ static void termio_suspend_tty(termio_data_t *td, struct termios *iosp) { if (td->tio_suspended++ != 0) return; /* already suspended; do not restore state */ if (td->tio_flags & TIO_XTERM) termio_tput(td, TI_DECRES(TI_COLENAB), 1); if (td->tio_flags & TIO_USECUP) termio_tput(td, td->tio_info.ti_rmcup.at_str, 1); termio_tput(td, td->tio_info.ti_sgr0.at_str, 1); mdb_iob_flush(td->tio_out); if (termio_ctl(td->tio_io, TCSETSW, iosp) == -1) warn("failed to restore terminal attributes"); if (td->tio_opgid > 0 && td->tio_opgid != mdb.m_pgid) { mdb_dprintf(MDB_DBG_CMDBUF, "fg pgid=%d\n", (int)td->tio_opgid); (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_IGN, NULL); (void) termio_ctl(td->tio_io, TIOCSPGRP, &td->tio_opgid); (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_DFL, NULL); } } /* * Resume the debugger's terminal state. We first save the existing terminal * state so we can restore it later, and then install our own state. We * derive our state dynamically from the existing terminal state so that we * always reflect the latest modifications made by the user with stty(1). */ static void termio_resume_tty(termio_data_t *td, struct termios *iosp) { /* * We use this table of bauds to convert the baud constant returned by * the terminal code to a baud rate in characters per second. The * values are in the order of the B* speed defines in . * We then compute tio_usecpc (microseconds-per-char) in order to * determine how many pad characters need to be issued at the current * terminal speed to delay for a given number of microseconds. For * example, at 300 baud (B300 = 7), we look up baud[7] = 300, and then * compute usecpc as MICROSEC / 300 = 3333 microseconds per character. */ static const uint_t baud[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 76800, 115200, 153600, 230400, 307200, 460800, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000 }; struct termios *ntios; struct winsize winsz; uint_t speed; if (td->tio_suspended == 0) fail("termio_resume called without matching termio_suspend\n"); if (--td->tio_suspended != 0) return; /* nested suspends; do not resume yet */ td->tio_opgid = -1; /* set to invalid pgid in case TIOCPGRP fails */ (void) termio_ctl(td->tio_io, TIOCGPGRP, &td->tio_opgid); /* * If the foreground process group does not include the debugger, reset * the foreground process group so we are in control of the terminal. * We temporarily ignore TTOU because TIOCSPGRP could trigger it. */ if (td->tio_opgid != mdb.m_pgid) { (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_IGN, NULL); (void) termio_ctl(td->tio_io, TIOCSPGRP, &mdb.m_pgid); (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_DFL, NULL); mdb_dprintf(MDB_DBG_CMDBUF, "fg pgid=%d\n", (int)mdb.m_pgid); } /* * Read the current set of terminal attributes, and save them in iosp * so we can restore them later. Then derive rtios, dtios, and winsz. */ if (termio_ctl(td->tio_io, TCGETS, iosp) < 0) warn("failed to get terminal attributes"); if (termio_ctl(td->tio_io, TIOCGWINSZ, &winsz) == 0) { if (winsz.ws_row != 0) td->tio_rows = (size_t)winsz.ws_row; if (winsz.ws_col != 0) td->tio_cols = (size_t)winsz.ws_col; } mdb_iob_resize(td->tio_out, td->tio_rows, td->tio_cols); td->tio_intr = td->tio_ptios.c_cc[VINTR]; td->tio_quit = td->tio_ptios.c_cc[VQUIT]; td->tio_erase = td->tio_ptios.c_cc[VERASE]; td->tio_werase = td->tio_ptios.c_cc[VWERASE]; td->tio_kill = td->tio_ptios.c_cc[VKILL]; td->tio_eof = td->tio_ptios.c_cc[VEOF]; td->tio_susp = td->tio_ptios.c_cc[VSUSP]; bcopy(&td->tio_ptios, &td->tio_rtios, sizeof (struct termios)); td->tio_rtios.c_iflag &= ~(ISTRIP | INPCK | ICRNL | INLCR | IUCLC); td->tio_rtios.c_oflag &= ~(OCRNL | ONLRET); td->tio_rtios.c_oflag |= ONLCR; td->tio_rtios.c_lflag &= ~(ISIG | ICANON | ECHO); td->tio_rtios.c_cflag |= CS8; td->tio_rtios.c_cc[VTIME] = 0; td->tio_rtios.c_cc[VMIN] = 1; bcopy(&td->tio_ptios, &td->tio_dtios, sizeof (struct termios)); td->tio_dtios.c_oflag &= ~(OCRNL | ONLRET); td->tio_dtios.c_oflag |= ONLCR; td->tio_dtios.c_lflag |= ISIG | ICANON | ECHO; /* * Select the appropriate modified settings to restore based on our * current state, and then install them. */ if (td->tio_rti_on) ntios = &td->tio_rtios; else ntios = &td->tio_dtios; if (termio_ctl(td->tio_io, TCSETSW, ntios) < 0) warn("failed to reset terminal attributes"); /* * Compute the terminal speed as described in termio(4I), and then * look up the corresponding microseconds-per-char in our table. */ if (ntios->c_cflag & CBAUDEXT) speed = (ntios->c_cflag & CBAUD) + CBAUD + 1; else speed = (ntios->c_cflag & CBAUD); if (speed >= sizeof (baud) / sizeof (baud[0])) { termio_warn(td, TIO_TTYWARN, "invalid speed %u -- assuming " "9600 baud\n", speed); speed = B9600; } td->tio_baud = baud[speed]; td->tio_usecpc = MICROSEC / td->tio_baud; mdb_dprintf(MDB_DBG_CMDBUF, "speed = %u baud (%u usec / char), " "putp = %s\n", td->tio_baud, td->tio_usecpc, td->tio_putp == &termio_puts ? "fast" : "slow"); /* * Send the necessary terminal initialization sequences to enable * enable cursor positioning. Clear the screen afterward if possible. */ if (td->tio_flags & TIO_USECUP) { termio_tput(td, td->tio_info.ti_smcup.at_str, 1); if (td->tio_info.ti_clear.at_str) { termio_tput(td, td->tio_info.ti_clear.at_str, 1); td->tio_x = td->tio_y = 0; } } /* * If the terminal is xterm-compatible, enable column mode switching. * Save the previous value in the terminal so we can restore it. */ if (td->tio_flags & TIO_XTERM) { termio_tput(td, TI_DECSAV(TI_COLENAB), 1); termio_tput(td, TI_DECSET(TI_COLENAB), 1); } termio_tput(td, td->tio_info.ti_cnorm.at_str, 1); /* cursor visible */ termio_tput(td, td->tio_info.ti_enacs.at_str, 1); /* alt char set */ /* * If the terminal is automargin-capable and we have an initialization * sequence to enable automargins, we must send it now. Note that * we don't have a way of querying this mode and restoring it; if * we are fighting with (say) a target that is depending on automargin * being turned off, it will lose. */ if ((td->tio_flags & TIO_AUTOWRAP) && td->tio_info.ti_smam.at_str != NULL) { termio_tput(td, td->tio_info.ti_smam.at_str, 1); } mdb_iob_flush(td->tio_out); } static void termio_suspend(mdb_io_t *io) { termio_data_t *td = io->io_data; termio_suspend_tty(td, &td->tio_ctios); } static void termio_resume(mdb_io_t *io) { termio_data_t *td = io->io_data; termio_resume_tty(td, &td->tio_ctios); } /* * Delay for the specified number of microseconds by sending the pad character * to the terminal. We round up by half a frame and then divide by the usecs * per character to determine the number of pad characters to send. */ static void termio_delay(termio_data_t *td, uint_t usec) { char pad = td->tio_info.ti_pad.at_str[0]; uint_t usecpc = td->tio_usecpc; for (usec = (usec + usecpc / 2) / usecpc; usec != 0; usec--) { mdb_iob_putc(td->tio_out, pad); mdb_iob_flush(td->tio_out); } } /* * Parse the terminfo(5) padding sequence "$<...>" and delay for the specified * amount of time by sending pad characters to the terminal. */ static const char * termio_pad(termio_data_t *td, const char *s, uint_t lines) { int xon = td->tio_info.ti_xon.at_val; int pb = td->tio_info.ti_pb.at_val; const char *p = s; uint_t usec = 0; /* * The initial string is a number of milliseconds, followed by an * optional decimal point and number of tenths of milliseconds. * We convert this to microseconds for greater accuracy. Only a single * digit is permitted after the decimal point; we ignore any others. */ while (*p >= '0' && *p <= '9') usec = usec * 10 + *p++ - '0'; usec *= 1000; /* convert msecs to usecs */ if (*p == '.') { if (p[1] >= '0' && p[1] <= '9') usec += (p[1] - '0') * 100; for (p++; *p >= '0' && *p <= '9'; p++) continue; } /* * Following the time delay specifier, * * 1. An optional "/" indicates that the delay should be done * regardless of the value of the terminal's xon property, * 2. An optional "*" indicates that the delay is proportional to the * count of affected lines, and * 3. A mandatory ">" terminates the sequence. * * If we encounter any other characters, we assume that we found "$<" * accidentally embedded in another sequence, so we just output "$". */ for (;;) { switch (*p++) { case '/': xon = FALSE; continue; case '*': usec *= lines; continue; case '>': if (xon == FALSE && usec != 0 && td->tio_baud >= pb) termio_delay(td, usec); return (p); default: mdb_iob_putc(td->tio_out, *s); return (s + 1); } } } /* * termio_tput() subroutine for terminals that require padding. We look ahead * for "$<>" sequences, and call termio_pad() to process them; all other chars * are output directly to the underlying device and then flushed at the end. */ static void termio_putp(termio_data_t *td, const char *s, uint_t lines) { while (s[0] != '\0') { if (s[0] == '$' && s[1] == '<') s = termio_pad(td, s + 2, lines); else mdb_iob_putc(td->tio_out, *s++); } mdb_iob_flush(td->tio_out); } /* * termio_tput() subroutine for terminals that do not require padding. We * simply output the string to the underlying i/o buffer; we let the caller * take care of flushing so that multiple sequences can be concatenated. */ /*ARGSUSED*/ static void termio_puts(termio_data_t *td, const char *s, uint_t lines) { mdb_iob_puts(td->tio_out, s); } /* * Print a padded escape sequence string to the terminal. The caller specifies * the string 's' and a count of the affected lines. If the string contains an * embedded delay sequence delimited by "$<>" (see terminfo(5)), appropriate * padding will be included in the output. We determine whether or not padding * is required during initialization, and set tio_putp to the proper subroutine. */ static void termio_tput(termio_data_t *td, const char *s, uint_t lines) { if (s != NULL) td->tio_putp(td, s, lines); } static void termio_addch(termio_data_t *td, char c, size_t width) { if (width == 1) { mdb_iob_putc(td->tio_out, c); td->tio_x++; if (td->tio_x >= td->tio_cols) { if (!(td->tio_flags & TIO_AUTOWRAP)) termio_tput(td, td->tio_info.ti_nel.at_str, 1); td->tio_x = 0; td->tio_y++; } mdb_iob_flush(td->tio_out); } else termio_redraw(td); } static void termio_insch(termio_data_t *td, char c, size_t width) { if (width == 1 && (td->tio_flags & TIO_INSERT) && td->tio_y == td->tio_max_y) { termio_tput(td, td->tio_info.ti_smir.at_str, 1); termio_tput(td, td->tio_info.ti_ich1.at_str, 1); mdb_iob_putc(td->tio_out, c); td->tio_x++; termio_tput(td, td->tio_info.ti_ip.at_str, 1); termio_tput(td, td->tio_info.ti_rmir.at_str, 1); if (td->tio_x >= td->tio_cols) { if (!(td->tio_flags & TIO_AUTOWRAP)) termio_tput(td, td->tio_info.ti_nel.at_str, 1); td->tio_x = 0; td->tio_y++; } mdb_iob_flush(td->tio_out); } else termio_redraw(td); } static void termio_mvcur(termio_data_t *td) { size_t tipos = td->tio_cmdbuf.cmd_bufidx + td->tio_promptlen; size_t dst_x = tipos % td->tio_cols; size_t dst_y = tipos / td->tio_cols; const char *str; size_t cnt, i; if (td->tio_y != dst_y) { if (td->tio_y < dst_y) { str = td->tio_info.ti_cud1.at_str; cnt = dst_y - td->tio_y; td->tio_x = 0; /* Note: cud1 moves cursor to column 0 */ } else { str = td->tio_info.ti_cuu1.at_str; cnt = td->tio_y - dst_y; } for (i = 0; i < cnt; i++) termio_tput(td, str, 1); mdb_iob_flush(td->tio_out); td->tio_y = dst_y; } if (td->tio_x != dst_x) { if (td->tio_x < dst_x) { str = td->tio_info.ti_cuf1.at_str; cnt = dst_x - td->tio_x; } else { str = td->tio_info.ti_cub1.at_str; cnt = td->tio_x - dst_x; } for (i = 0; i < cnt; i++) termio_tput(td, str, 1); mdb_iob_flush(td->tio_out); td->tio_x = dst_x; } } static void termio_backleft(termio_data_t *td) { size_t i; if (td->tio_flags & TIO_BACKLEFT) termio_tput(td, td->tio_info.ti_cub1.at_str, 1); else { termio_tput(td, td->tio_info.ti_cuu1.at_str, 1); for (i = 0; i < td->tio_cols - 1; i++) termio_tput(td, td->tio_info.ti_cuf1.at_str, 1); } } static void termio_bspch(termio_data_t *td) { if (td->tio_x == 0) { /* * If TIO_LAZYWRAP is set, we are regrettably in one of two * states that we cannot differentiate between: the cursor is * either on the right margin of the previous line (having not * advanced due to the lazy wrap) _or_ the cursor is on the * left margin of the current line because a wrap has already * been induced due to prior activity. Because these cases are * impossible to differentiate, we force the latter case by * emitting a space and then moving the cursor back. If the * wrap had not been induced, emitting this space will induce * it -- and will assure that our termio_backleft() is the * correct behavior with respect to the cursor. */ if (td->tio_flags & TIO_LAZYWRAP) { mdb_iob_putc(td->tio_out, ' '); termio_tput(td, td->tio_info.ti_cub1.at_str, 1); } termio_backleft(td); td->tio_x = td->tio_cols - 1; td->tio_y--; } else { termio_tput(td, td->tio_info.ti_cub1.at_str, 1); td->tio_x--; } termio_delch(td); } static void termio_delch(termio_data_t *td) { mdb_iob_putc(td->tio_out, ' '); if (td->tio_x == td->tio_cols - 1 && (td->tio_flags & TIO_AUTOWRAP)) { /* * We just overwrote a space in the final column, so we know * that we have autowrapped and we therefore definitely don't * want to issue the ti_cub1. If we have TIO_LAZYWRAP, we know * that the cursor remains on the final column, and we want to * do nothing -- but if TIO_LAZYWRAP isn't set, we have wrapped * and we need to move the cursor back up and all the way to * the right via termio_backleft(). */ if (!(td->tio_flags & TIO_LAZYWRAP)) termio_backleft(td); } else { termio_tput(td, td->tio_info.ti_cub1.at_str, 1); } mdb_iob_flush(td->tio_out); } static void termio_clear(termio_data_t *td) { while (td->tio_x-- != 0) termio_tput(td, td->tio_info.ti_cub1.at_str, 1); while (td->tio_y < td->tio_max_y) { termio_tput(td, td->tio_info.ti_cud1.at_str, 1); td->tio_y++; } while (td->tio_y-- != 0) { termio_tput(td, td->tio_info.ti_el.at_str, 1); termio_tput(td, td->tio_info.ti_cuu1.at_str, 1); } termio_tput(td, td->tio_info.ti_el.at_str, 1); mdb_iob_flush(td->tio_out); termio_prompt(td); } static void termio_redraw(termio_data_t *td) { const char *buf = td->tio_cmdbuf.cmd_buf; size_t len = td->tio_cmdbuf.cmd_buflen; size_t pos, n; termio_clear(td); if (len == 0) return; /* if the buffer is empty, we're done */ if (td->tio_flags & TIO_AUTOWRAP) mdb_iob_nputs(td->tio_out, buf, len); else { for (pos = td->tio_promptlen; len != 0; pos = 0) { n = MIN(td->tio_cols - pos, len); mdb_iob_nputs(td->tio_out, buf, n); buf += n; len -= n; if (pos + n == td->tio_cols) termio_tput(td, td->tio_info.ti_nel.at_str, 1); } } pos = td->tio_promptlen + td->tio_cmdbuf.cmd_buflen; td->tio_x = pos % td->tio_cols; td->tio_y = pos / td->tio_cols; mdb_iob_flush(td->tio_out); termio_mvcur(td); } static void termio_prompt(termio_data_t *td) { mdb_callb_fire(MDB_CALLB_PROMPT); /* * Findhist (^R) overrides the displayed prompt. We should only update * the main prompt (which may have been changed by the callback) if * findhist isn't active. */ if (!(td->tio_flags & TIO_FINDHIST)) { td->tio_prompt = mdb.m_prompt; td->tio_promptlen = mdb.m_promptlen; } mdb_iob_puts(td->tio_out, td->tio_prompt); mdb_iob_flush(td->tio_out); td->tio_x = td->tio_promptlen; td->tio_y = 0; } /* * For debugging purposes, iterate over the table of attributes and output them * in human readable form for verification. */ static void termio_dump(termio_data_t *td, const termio_attr_t *ta) { char *str; for (; ta->ta_name != NULL; ta++) { switch (ta->ta_type) { case TIO_ATTR_REQSTR: case TIO_ATTR_STR: if (ta->ta_valp->at_str != NULL) { str = strchr2esc(ta->ta_valp->at_str, strlen(ta->ta_valp->at_str)); mdb_dprintf(MDB_DBG_CMDBUF, "%s = \"%s\"\n", ta->ta_name, str); strfree(str); } else { mdb_dprintf(MDB_DBG_CMDBUF, "%s = \n", ta->ta_name); } break; case TIO_ATTR_INT: mdb_dprintf(MDB_DBG_CMDBUF, "%s = %d\n", ta->ta_name, ta->ta_valp->at_val); break; case TIO_ATTR_BOOL: mdb_dprintf(MDB_DBG_CMDBUF, "%s = %s\n", ta->ta_name, ta->ta_valp->at_val ? "TRUE" : "FALSE"); break; } } mdb_dprintf(MDB_DBG_CMDBUF, "tio_flags = <%#b>\n", td->tio_flags, tio_flag_masks); } static int termio_setup_attrs(termio_data_t *td, const char *name) { const termio_attr_t *ta; const char *str; size_t nbytes; char *bufp; int need_padding = 0; int i; /* * Load terminal attributes: */ for (nbytes = 0, ta = &termio_attrs[0]; ta->ta_name != NULL; ta++) { switch (ta->ta_type) { case TIO_ATTR_REQSTR: case TIO_ATTR_STR: str = tigetstr(ta->ta_name); if (str == (const char *)-1) { termio_warn(td, TIO_CAPWARN, "terminal capability '%s' is not of type " "string as expected\n", ta->ta_name); return (0); } if (str != NULL) nbytes += strlen(str) + 1; else if (ta->ta_type == TIO_ATTR_REQSTR) { termio_warn(td, TIO_CAPWARN, "terminal capability '%s' is not " "available\n", ta->ta_name); return (0); } break; case TIO_ATTR_BOOL: if (tigetflag(ta->ta_name) == -1) { termio_warn(td, TIO_CAPWARN, "terminal capability '%s' is not of type " "boolean as expected\n", ta->ta_name); return (0); } break; case TIO_ATTR_INT: if (tigetnum(ta->ta_name) == -2) { termio_warn(td, TIO_CAPWARN, "terminal capability '%s' is not of type " "integer as expected\n", ta->ta_name); return (0); } break; } } if (nbytes != 0) td->tio_attrs = mdb_alloc(nbytes, UM_SLEEP); else td->tio_attrs = NULL; td->tio_attrslen = nbytes; bufp = td->tio_attrs; /* * Now make another pass through the terminal attributes and load the * actual pointers into our static data structure: */ for (ta = &termio_attrs[0]; ta->ta_name != NULL; ta++) { switch (ta->ta_type) { case TIO_ATTR_REQSTR: case TIO_ATTR_STR: if ((str = tigetstr(ta->ta_name)) != NULL) { /* * Copy the result string into our contiguous * buffer, and store a pointer to it in at_str. */ (void) strcpy(bufp, str); ta->ta_valp->at_str = bufp; bufp += strlen(str) + 1; /* * Check the string for a "$<>" pad sequence; * if none are found, we can optimize later. */ if ((str = strstr(ta->ta_valp->at_str, "$<")) != NULL && strchr(str, '>') != NULL) need_padding++; } else { ta->ta_valp->at_str = NULL; } break; case TIO_ATTR_BOOL: ta->ta_valp->at_val = tigetflag(ta->ta_name); break; case TIO_ATTR_INT: ta->ta_valp->at_val = tigetnum(ta->ta_name); break; } } /* * Copy attribute pointers from temporary struct into td->tio_info: */ bcopy(&termio_info, &td->tio_info, sizeof (termio_info_t)); /* * Initialize the terminal size based on the terminfo database. If it * does not have the relevant properties, fall back to the environment * settings or to a hardcoded default. These settings will only be * used if we subsequently fail to derive the size with TIOCGWINSZ. */ td->tio_rows = MAX(td->tio_info.ti_lines.at_val, 0); td->tio_cols = MAX(td->tio_info.ti_cols.at_val, 0); if (td->tio_rows == 0) { if ((str = getenv("LINES")) != NULL && strisnum(str) != 0 && (i = strtoi(str)) > 0) td->tio_rows = i; else td->tio_rows = TIO_DEFAULT_ROWS; } if (td->tio_cols == 0) { if ((str = getenv("COLUMNS")) != NULL && strisnum(str) != 0 && (i = strtoi(str)) > 0) td->tio_cols = i; else td->tio_cols = TIO_DEFAULT_COLS; } td->tio_flags = 0; /* * We turn on TIO_AUTOWRAP if "am" (automargin) is set on the terminal. * Previously, this check was too smart for its own good, turning * TIO_AUTOWRAP on only when both "am" was set _and_ "xenl" was unset. * "xenl" is one of the (infamous?) so-called "Xanthippe glitches", * this one pertaining to the Concept 100's feature of not autowrapping * if the autowrap-inducing character is a newline. (That is, the * newline was "eaten" in this case -- and "xenl" accordingly stands * for "Xanthippe eat newline", which itself sounds like a command from * an Infocom game about line discipline set in ancient Greece.) This * (now removed) condition misunderstood the semantics of "xenl", * apparently inferring that its presence denoted that the terminal * could not autowrap at all. In fact, "xenl" is commonly set, and * denotes that the terminal will not autowrap on a newline -- a * feature (nee glitch) that is not particularly relevant with respect * to our input processing. (Ironically, disabling TIO_AUTOWRAP in * this case only results in correct-seeming output because of the * presence of the feature, as the inserted newline to force the wrap * isn't actually displayed; were it displayed, the input buffer would * appear double-spaced.) So with respect to "xenl" and autowrapping, * the correct behavior is to ignore it entirely, and adopt the * (simpler and less arcane) logic of setting TIO_AUTOWRAP if (and only * if) "am" is set on the terminal. This does not, however, mean that * "xenl" is meaningless; see below... */ if (td->tio_info.ti_am.at_val) td->tio_flags |= TIO_AUTOWRAP; /* * While "xenl" doesn't dictate our TIO_AUTOWRAP setting, it does have * a subtle impact on the way we process input: in addition to its * eponymous behavior of eating newlines, "xenl" denotes a second, * entirely orthogonal idiosyncracy. As terminfo(5) tells it: "Those * terminals whose cursor remains on the right-most column until * another character has been received, rather than wrapping * immediately upon receiving the right- most character, such as the * VT100, should also indicate xenl." So yes, "xenl" in fact has two * entirely different meanings -- it is a glitch-within-a-glitch, in * what one assumes (hopes?) to be an accident of history rather than a * deliberate act of sabotage. This second behavior is relevant to us * because it affects the way we redraw the screen after a backspace in * the left-most column. We call this second behavior "lazy wrapping", * and we set it if (and only if) "xenl" is set on the terminal. */ if (td->tio_info.ti_xenl.at_val) td->tio_flags |= TIO_LAZYWRAP; if (td->tio_info.ti_bw.at_val) td->tio_flags |= TIO_BACKLEFT; if (td->tio_info.ti_smir.at_str != NULL || td->tio_info.ti_ich1.at_str != NULL) td->tio_flags |= TIO_INSERT; if (mdb.m_flags & MDB_FL_USECUP) td->tio_flags |= TIO_USECUP; if (name != NULL && (strncmp(name, "xterm", 5) == 0 || strcmp(name, "dtterm") == 0)) td->tio_flags |= TIO_XTERM; /* * Optimizations for padding: (1) if no pad attribute is present, set * its value to "\0" to avoid testing later; (2) if no pad sequences * were found, force "npc" to TRUE so we pick the optimized tio_putp; * (3) if the padding baud property is not present, reset it to zero * since we need to compare it to an unsigned baud value. */ if (td->tio_info.ti_pad.at_str == NULL) td->tio_info.ti_pad.at_str = ""; /* \0 is the pad char */ if (need_padding == 0) td->tio_info.ti_npc.at_val = TRUE; if (td->tio_info.ti_npc.at_val) td->tio_putp = &termio_puts; else td->tio_putp = &termio_putp; if (td->tio_info.ti_pb.at_val < 0) td->tio_info.ti_pb.at_val = 0; /* * If no newline capability is available, assume \r\n will work. If no * carriage return capability is available, assume \r will work. */ if (td->tio_info.ti_nel.at_str == NULL) td->tio_info.ti_nel.at_str = "\r\n"; if (td->tio_info.ti_cr.at_str == NULL) td->tio_info.ti_cr.at_str = "\r"; return (1); } mdb_io_t * mdb_termio_create(const char *name, mdb_io_t *rio, mdb_io_t *wio) { struct termios otios; termio_data_t *td; int rv, err, i; td = mdb_zalloc(sizeof (termio_data_t), UM_SLEEP); td->tio_io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP); /* * Save the original user settings before calling setupterm(), which * cleverly changes them without telling us what it did or why. */ if (IOP_CTL(rio, TCGETS, &otios) == -1) { warn("failed to read terminal attributes for stdin"); goto err; } rv = setupterm((char *)name, IOP_CTL(rio, MDB_IOC_GETFD, NULL), &err); IOP_CTL(rio, TCSETSW, &otios); /* undo setupterm() stupidity */ if (rv == ERR) { if (err == 0) warn("no terminal data available for TERM=%s\n", name); else if (err == -1) warn("failed to locate terminfo database\n"); else warn("failed to initialize terminal (err=%d)\n", err); goto err; } if (!termio_setup_attrs(td, name)) goto err; /* * Do not re-issue terminal capability warnings when mdb re-execs. */ if (mdb.m_flags & MDB_FL_EXEC) td->tio_flags |= TIO_TTYWARN | TIO_CAPWARN; /* * Initialize i/o structures and command-line buffer: */ td->tio_io->io_ops = &termio_ops; td->tio_io->io_data = td; td->tio_io->io_next = NULL; td->tio_io->io_refcnt = 0; td->tio_in_io = rio; td->tio_in = mdb_iob_create(td->tio_in_io, MDB_IOB_RDONLY); td->tio_out_io = wio; td->tio_out = mdb_iob_create(td->tio_out_io, MDB_IOB_WRONLY); mdb_iob_clrflags(td->tio_out, MDB_IOB_AUTOWRAP); td->tio_link = NULL; mdb_cmdbuf_create(&td->tio_cmdbuf); /* * Fill in all the keymap entries with the insert function: */ for (i = 0; i < KEY_MAX; i++) td->tio_keymap[i] = termio_insert; /* * Now override selected entries with editing functions: */ td->tio_keymap['\n'] = termio_accept; td->tio_keymap['\r'] = termio_accept; td->tio_keymap[CTRL('f')] = termio_fwdchar; td->tio_keymap[CTRL('b')] = termio_backchar; td->tio_keymap[CTRL('t')] = termio_transpose; td->tio_keymap[CTRL('a')] = termio_home; td->tio_keymap[CTRL('e')] = termio_end; td->tio_keymap[META('f')] = termio_fwdword; td->tio_keymap[META('b')] = termio_backword; td->tio_keymap[META('d')] = termio_killfwdword; td->tio_keymap[META('\b')] = termio_killbackword; td->tio_keymap[CTRL('k')] = termio_kill; td->tio_keymap[CTRL('p')] = termio_prevhist; td->tio_keymap[CTRL('n')] = termio_nexthist; td->tio_keymap[CTRL('r')] = termio_findhist; td->tio_keymap[CTRL('l')] = termio_refresh; td->tio_keymap[CTRL('d')] = termio_delchar; td->tio_keymap[CTRL('?')] = termio_widescreen; td->tio_keymap[KPAD('A')] = termio_prevhist; td->tio_keymap[KPAD('B')] = termio_nexthist; td->tio_keymap[KPAD('C')] = termio_fwdchar; td->tio_keymap[KPAD('D')] = termio_backchar; /* * Many modern terminal emulators treat the "Home" and "End" keys on a * PC keyboard as cursor keys. Some others use a multibyte function * key control sequence. We handle both styles here: */ td->tio_keymap[KPAD('H')] = termio_home; td->tio_keymap[FKEY('1')] = termio_home; td->tio_keymap[KPAD('F')] = termio_end; td->tio_keymap[FKEY('4')] = termio_end; /* * We default both ASCII BS and DEL to termio_backspace for safety. We * want backspace to work whenever possible, regardless of whether or * not we're able to ask the terminal for the specific character that * it will use. kmdb, for example, is not able to make this request, * and must be prepared to accept both. */ td->tio_keymap[CTRL('h')] = termio_backspace; td->tio_keymap[KEY_DEL] = termio_backspace; /* * Overrides for single-key accelerators */ td->tio_keymap['['] = termio_accel; td->tio_keymap[']'] = termio_accel; /* * Grab tabs */ td->tio_keymap['\t'] = termio_tab; td->tio_x = 0; td->tio_y = 0; td->tio_max_x = 0; td->tio_max_y = 0; td->tio_active = FALSE; td->tio_rti_on = FALSE; td->tio_suspended = 1; /* * Perform a resume operation to complete our terminal initialization, * and then adjust the keymap according to the terminal settings. */ termio_resume_tty(td, &td->tio_ptios); bcopy(&td->tio_ptios, &td->tio_ctios, sizeof (struct termios)); td->tio_keymap[td->tio_intr] = termio_intr; td->tio_keymap[td->tio_quit] = termio_quit; td->tio_keymap[td->tio_erase] = termio_backspace; td->tio_keymap[td->tio_werase] = termio_killbackword; td->tio_keymap[td->tio_kill] = termio_reset; td->tio_keymap[td->tio_susp] = termio_susp; (void) mdb_signal_sethandler(SIGWINCH, termio_winch, td); (void) mdb_signal_sethandler(SIGTSTP, termio_tstp, td); if (mdb.m_debug & MDB_DBG_CMDBUF) termio_dump(td, &termio_attrs[0]); return (td->tio_io); err: mdb_free(td->tio_io, sizeof (mdb_io_t)); mdb_free(td, sizeof (termio_data_t)); return (NULL); } int mdb_iob_isatty(mdb_iob_t *iob) { mdb_io_t *io; if (iob->iob_flags & MDB_IOB_TTYLIKE) return (1); for (io = iob->iob_iop; io != NULL; io = io->io_next) { if (io->io_ops == &termio_ops) return (1); } return (0); } static const char * termio_insert(termio_data_t *td, int c) { size_t olen = td->tio_cmdbuf.cmd_buflen; if (mdb_cmdbuf_insert(&td->tio_cmdbuf, c) == 0) { if (mdb_cmdbuf_atend(&td->tio_cmdbuf)) termio_addch(td, c, td->tio_cmdbuf.cmd_buflen - olen); else termio_insch(td, c, td->tio_cmdbuf.cmd_buflen - olen); } return (NULL); } static const char * termio_accept(termio_data_t *td, int c) { if (td->tio_flags & TIO_FINDHIST) { (void) mdb_cmdbuf_findhist(&td->tio_cmdbuf, c); td->tio_prompt = mdb.m_prompt; td->tio_promptlen = mdb.m_promptlen; td->tio_flags &= ~TIO_FINDHIST; termio_redraw(td); return (NULL); } /* Ensure that the cursor is at the end of the line */ (void) termio_end(td, c); return (mdb_cmdbuf_accept(&td->tio_cmdbuf)); } static const char * termio_backspace(termio_data_t *td, int c) { if (mdb_cmdbuf_backspace(&td->tio_cmdbuf, c) == 0) { if (mdb_cmdbuf_atend(&td->tio_cmdbuf)) termio_bspch(td); else termio_redraw(td); } return (NULL); } /* * This function may end up calling termio_read recursively as part of invoking * the mdb pager. To work around this fact, we need to go through and make sure * that we change the underlying terminal settings before and after this * function call. If we don't do this, we invoke the pager, and don't abort * (which will longjmp us elsewhere) we're going to return to the read loop with * the wrong termio settings. * * Furthermore, because of the fact that we're being invoked in a user context * that allows us to be interrupted, we need to actually allocate the memory * that we're using with GC so that it gets cleaned up in case of the pager * resetting us and never reaching the end. */ /*ARGSUSED*/ static const char * termio_tab(termio_data_t *td, int c) { char *buf; const char *result; int nres; mdb_tab_cookie_t *mtp; if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1) warn("failed to restore terminal attributes"); buf = mdb_alloc(td->tio_cmdbuf.cmd_bufidx + 1, UM_SLEEP | UM_GC); (void) strncpy(buf, td->tio_cmdbuf.cmd_buf, td->tio_cmdbuf.cmd_bufidx); buf[td->tio_cmdbuf.cmd_bufidx] = '\0'; td->tio_flags |= TIO_TAB; mtp = mdb_tab_init(); nres = mdb_tab_command(mtp, buf); if (nres == 0) { result = NULL; } else { result = mdb_tab_match(mtp); if (nres != 1) { mdb_printf("\n"); mdb_tab_print(mtp); } } if (result != NULL) { int index = 0; while (result[index] != '\0') { (void) termio_insert(td, result[index]); index++; } } termio_redraw(td); mdb_tab_fini(mtp); td->tio_flags &= ~TIO_TAB; if (termio_ctl(td->tio_io, TCSETSW, &td->tio_rtios) == -1) warn("failed to set terminal attributes"); return (NULL); } static const char * termio_delchar(termio_data_t *td, int c) { if (!(mdb.m_flags & MDB_FL_IGNEOF) && mdb_cmdbuf_atend(&td->tio_cmdbuf) && mdb_cmdbuf_atstart(&td->tio_cmdbuf)) return (termio_quit(td, c)); if (mdb_cmdbuf_delchar(&td->tio_cmdbuf, c) == 0) { if (mdb_cmdbuf_atend(&td->tio_cmdbuf)) termio_delch(td); else termio_redraw(td); } return (NULL); } static const char * termio_fwdchar(termio_data_t *td, int c) { if (mdb_cmdbuf_fwdchar(&td->tio_cmdbuf, c) == 0) termio_mvcur(td); return (NULL); } static const char * termio_backchar(termio_data_t *td, int c) { if (mdb_cmdbuf_backchar(&td->tio_cmdbuf, c) == 0) termio_mvcur(td); return (NULL); } static const char * termio_transpose(termio_data_t *td, int c) { if (mdb_cmdbuf_transpose(&td->tio_cmdbuf, c) == 0) termio_redraw(td); return (NULL); } static const char * termio_home(termio_data_t *td, int c) { if (mdb_cmdbuf_home(&td->tio_cmdbuf, c) == 0) termio_mvcur(td); return (NULL); } static const char * termio_end(termio_data_t *td, int c) { if (mdb_cmdbuf_end(&td->tio_cmdbuf, c) == 0) termio_mvcur(td); return (NULL); } static const char * termio_fwdword(termio_data_t *td, int c) { if (mdb_cmdbuf_fwdword(&td->tio_cmdbuf, c) == 0) termio_mvcur(td); return (NULL); } static const char * termio_backword(termio_data_t *td, int c) { if (mdb_cmdbuf_backword(&td->tio_cmdbuf, c) == 0) termio_mvcur(td); return (NULL); } static const char * termio_kill(termio_data_t *td, int c) { if (mdb_cmdbuf_kill(&td->tio_cmdbuf, c) == 0) termio_redraw(td); return (NULL); } static const char * termio_killfwdword(termio_data_t *td, int c) { if (mdb_cmdbuf_killfwdword(&td->tio_cmdbuf, c) == 0) termio_redraw(td); return (NULL); } static const char * termio_killbackword(termio_data_t *td, int c) { if (mdb_cmdbuf_killbackword(&td->tio_cmdbuf, c) == 0) termio_redraw(td); return (NULL); } static const char * termio_reset(termio_data_t *td, int c) { if (mdb_cmdbuf_reset(&td->tio_cmdbuf, c) == 0) termio_clear(td); return (NULL); } /*ARGSUSED*/ static const char * termio_widescreen(termio_data_t *td, int c) { if (td->tio_flags & TIO_XTERM) { if (td->tio_cols == 80) termio_tput(td, TI_DECSET(TI_DECCOLM), 1); else termio_tput(td, TI_DECRST(TI_DECCOLM), 1); mdb_iob_flush(td->tio_out); termio_winch(SIGWINCH, NULL, NULL, td); } return (NULL); } static const char * termio_prevhist(termio_data_t *td, int c) { if (mdb_cmdbuf_prevhist(&td->tio_cmdbuf, c) == 0) termio_redraw(td); return (NULL); } static const char * termio_nexthist(termio_data_t *td, int c) { if (mdb_cmdbuf_nexthist(&td->tio_cmdbuf, c) == 0) termio_redraw(td); return (NULL); } /* * Single-key accelerator support. Several commands are so commonly used as to * require a single-key equivalent. If we see one of these accelerator * characters at the beginning of an otherwise-empty line, we'll replace it with * the expansion. */ static const char * termio_accel(termio_data_t *td, int c) { const char *p; if (td->tio_cmdbuf.cmd_buflen != 0 || (p = termio_accel_lookup(c)) == NULL) return (termio_insert(td, c)); while (*p != '\0') (void) termio_insert(td, *p++); return (termio_accept(td, '\n')); } static const char * termio_findhist(termio_data_t *td, int c) { if (mdb_cmdbuf_reset(&td->tio_cmdbuf, c) == 0) { td->tio_prompt = "Search: "; td->tio_promptlen = strlen(td->tio_prompt); td->tio_flags |= TIO_FINDHIST; termio_redraw(td); } return (NULL); } /*ARGSUSED*/ static const char * termio_refresh(termio_data_t *td, int c) { if (td->tio_info.ti_clear.at_str) { termio_tput(td, td->tio_info.ti_clear.at_str, 1); td->tio_x = td->tio_y = 0; } termio_redraw(td); return (NULL); } /* * Leave the terminal read code by longjmp'ing up the stack of mdb_frame_t's * back to the main parsing loop (see mdb_run() in mdb.c). */ static const char * termio_abort(termio_data_t *td, int c, int err) { (void) mdb_cmdbuf_reset(&td->tio_cmdbuf, c); td->tio_active = FALSE; td->tio_rti_on = FALSE; if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1) warn("failed to restore terminal attributes"); longjmp(mdb.m_frame->f_pcb, err); /*NOTREACHED*/ return (NULL); } static const char * termio_intr(termio_data_t *td, int c) { return (termio_abort(td, c, MDB_ERR_SIGINT)); } static const char * termio_quit(termio_data_t *td, int c) { return (termio_abort(td, c, MDB_ERR_QUIT)); } /*ARGSUSED*/ static const char * termio_susp(termio_data_t *td, int c) { (void) mdb_signal_sethandler(SIGWINCH, MDB_SIG_IGN, NULL); (void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_IGN, NULL); termio_suspend_tty(td, &td->tio_ptios); mdb_iob_nl(td->tio_out); (void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_DFL, NULL); (void) mdb_signal_pgrp(SIGTSTP); /* * When we call mdb_signal_pgrp(SIGTSTP), we are expecting the entire * debugger process group to be stopped by the kernel. Once we return * from that call, we assume we are resuming from a subsequent SIGCONT. */ (void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_IGN, NULL); termio_resume_tty(td, &td->tio_ptios); (void) mdb_signal_sethandler(SIGWINCH, termio_winch, td); (void) mdb_signal_sethandler(SIGTSTP, termio_tstp, td); if (td->tio_active) siglongjmp(td->tio_env, SIGCONT); return (NULL); } /*ARGSUSED*/ static void termio_winch(int sig, siginfo_t *sip, ucontext_t *ucp, void *data) { termio_data_t *td = data; mdb_bool_t change = FALSE; struct winsize winsz; if (termio_ctl(td->tio_io, TIOCGWINSZ, &winsz) == -1) return; /* just ignore this WINCH if the ioctl fails */ if (td->tio_rows != (size_t)winsz.ws_row || td->tio_cols != (size_t)winsz.ws_col) { if (td->tio_active) termio_clear(td); if (winsz.ws_row != 0) td->tio_rows = (size_t)winsz.ws_row; if (winsz.ws_col != 0) td->tio_cols = (size_t)winsz.ws_col; if (td->tio_active) termio_clear(td); mdb_iob_resize(td->tio_out, td->tio_rows, td->tio_cols); change = TRUE; } if (change && td->tio_active) siglongjmp(td->tio_env, sig); if (change && td->tio_link != NULL) mdb_iob_resize(td->tio_link, td->tio_rows, td->tio_cols); } /*ARGSUSED*/ static void termio_tstp(int sig, siginfo_t *sip, ucontext_t *ucp, void *data) { (void) termio_susp(data, CTRL('Z')); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2015 Joyent, Inc. All rights reserved. * Copyright 2022 Oxide Computer Company */ /* * ::typedef exists to allow a user to create and import auxiliary CTF * information for the currently running target. ::typedef is similar to the C * typedef keyword. However, ::typedef has no illusions of grandeur. It is not a * standards complaint version of C's typedef. For specifics on what it does and * does not support, please see the help message for ::typedef later on in this * file. * * In addition to allowing the user to create types, it has a notion of a * built-in set of types that a compiler might provide. Currently ::typedef * supports both the standard illumos 32-bit and 64-bit environments, mainly * LP32 and LP64. These are not present by default; it is up to the user to * request that they be inserted. * * To facilitate this, ::typedef adds all of its type information to an * auxiliary CTF container that is a part of the global mdb state. This is * abstracted away from ::typedef by the mdb_ctf_* apis. This container is * referred to as the synthetic container, as it holds these synthetic types. * The synthetic container does not have a parent CTF container. This is rather * important to its operation, as a user can end up referencing types that come * from many different such containers (eg. different kernel modules). As such, * whenever a type is referenced that we do not know about, we search all of the * CTF containers that mdb knows about it. If we find it, then that type is * imported (along with all of its dependent types) into the synthetic * container. * * Finally, ::typedef can source CTF information from external files with the -r * option. This will copy in every type from their container into the synthetic * container, because of this the parent and child relationship between * containers with parents cannot be maintained. */ #include #include #include #include #include struct parse_node; #define PN_F_POINTER 0x01 #define PN_F_ARRAY 0x02 typedef struct parse_node { mdb_list_t pn_list; /* list entry, must be first */ char *pn_type; /* name of base type */ char *pn_name; /* name of the member */ int pn_flags; /* flags */ int pn_nptrs; /* number of pointers */ int pn_asub; /* value of array subscript */ } parse_node_t; typedef struct parse_root { mdb_list_t pr_nodes; /* list of members */ int pr_kind; /* CTF_K_* */ const char *pr_name; /* entity name */ const char *pr_tname; /* entity typedef */ } parse_root_t; static int typedef_valid_identifier(const char *str) { /* * We can't use the standard ctype.h functions because those aren't * necessairly available in kmdb. On the flip side, we only care about * ascii characters here so that isn't too bad. * * C Identifiers have to start with a letter or a _. Afterwards they can * be alphanumeric or an _. */ if (*str == '\0') return (1); if (*str != '_' && (*str < 'A' || *str > 'Z') && (*str < 'a' || *str > 'z')) return (1); str++; while (*str != '\0') { if (*str != '_' && (*str < '0' || *str > '9') && (*str < 'A' || *str > 'Z') && (*str < 'a' || *str > 'z')) return (1); str++; } return (0); } /*ARGSUSED*/ static int typedef_list_cb(mdb_ctf_id_t id, void *arg) { char buf[MDB_SYM_NAMLEN]; /* * The user may have created an anonymous structure or union as part of * running ::typedef. If this is the case, we passed a NULL pointer for * the name into the ctf routines. When we go back and ask for the name * of that, ctf goes through and loops through all the declarations. * This, however correctly, gives us back something undesirable to the * user, eg. the name is simply 'struct' and 'union'. Because a typedef * will always have a non-anonymous name for that, we instead opt to * not include these anonymous names. ctf usefully includes a space as * part of that name. */ (void) mdb_ctf_type_name(id, buf, sizeof (buf)); if (strcmp("struct ", buf) != 0 && strcmp("union ", buf) != 0) mdb_printf("%s\n", buf); return (0); } static char * typedef_join_strings(int nstr, const mdb_arg_t *args, int flags) { int i, size = 0; char *ret, *sptr; for (i = 0; i <= nstr; i++) { /* Always account for the space or the null terminator */ size += strlen(args[i].a_un.a_str) + 1; } ret = mdb_alloc(sizeof (char) * size, flags); if (ret == NULL) return (NULL); sptr = ret; for (i = 0; i <= nstr; i++) { (void) strcpy(sptr, args[i].a_un.a_str); sptr += strlen(args[i].a_un.a_str); *sptr = ' '; sptr++; } *sptr = '\0'; return (ret); } static int typedef_list(void) { (void) mdb_ctf_type_iter(MDB_CTF_SYNTHETIC_ITER, typedef_list_cb, NULL); return (DCMD_OK); } static int typedef_destroy(void) { if (mdb_ctf_synthetics_reset() != 0) { mdb_warn("failed to reset synthetic types"); return (DCMD_ERR); } return (DCMD_OK); } /* * We've been asked to create the basic types that exist. We accept the * following strings to indicate what we should create. * - LP32, ILP32 (case insensitive) * - LP64 */ static int typedef_create(const char *arg) { int kind; if (strcasecmp(arg, "LP32") == 0 || strcasecmp(arg, "ILP32") == 0) { kind = SYNTHETIC_ILP32; } else if (strcasecmp(arg, "LP64") == 0) { kind = SYNTHETIC_LP64; } else { mdb_printf("invalid data model: %s\n", arg); return (DCMD_USAGE); } if (mdb_ctf_synthetics_create_base(kind) != 0) { mdb_printf("failed to create intrinsic types, maybe " "they already exist\n"); return (DCMD_ERR); } return (DCMD_OK); } /* * Search the current arguments for a complete member declaration. This function * modifies the value of defn based on what's necessary for parsing. It returns * the appropriate parse node in pnp. */ static int typedef_parse_member(char *defn, char **next, parse_node_t **pnp) { char *c, *name, *array; int nptrs = 0; parse_node_t *pn; c = strchr(defn, ';'); if (c == NULL) { mdb_printf("Cannot find semi-colon to delineate the end " "of a member.\n"); return (DCMD_ERR); } *c = '\0'; *next = c + 1; c = strrchr(defn, ' '); if (c == NULL) { mdb_printf("Missing both a name and a type declaration for " "a member. Instead, found '%s'\n", defn); return (DCMD_ERR); } *c = '\0'; name = c + 1; c--; while (*c == '*' || *c == ' ') { if (*c == '*') nptrs++; c--; } *(c + 1) = '\0'; pn = mdb_zalloc(sizeof (parse_node_t), UM_SLEEP | UM_GC); pn->pn_type = defn; /* * Go through and prepare the name field. Note that we still have to * check if this is a pointer or an array. We also need to strip the * ending semi-colon. */ while (*name == '*') { name++; nptrs++; } if ((c = strchr(name, '[')) != NULL) { array = c; if ((c = strchr(array, ']')) == NULL) { mdb_printf("Found the beginning of an array size " "but no closing ']' in %s\n", array); return (DCMD_ERR); } *array = '\0'; array++; *c = '\0'; pn->pn_flags |= PN_F_ARRAY; pn->pn_asub = mdb_strtoullx(array, MDB_STRTOULL_F_BASE_C); if (pn->pn_asub < 0) { mdb_printf("Array lengths cannot be negative\n"); return (DCMD_ERR); } } if (typedef_valid_identifier(name) != 0) { mdb_printf("The name %s is not a valid C identifier.\n", name); return (DCMD_ERR); } if (nptrs) { pn->pn_flags |= PN_F_POINTER; pn->pn_nptrs = nptrs; } pn->pn_name = name; *pnp = pn; return (DCMD_OK); } /* * We're going to parse out our types here. Note that we are not strictly * speaking a truely ANSI C compliant parser. Currently we support normal * declarations except for the following: * o function pointers * o bit-fields */ static int typedef_parse(char *defn, const char *name, parse_root_t **prp) { int len, ret; const char *kind, *basename; char *c, *brace; parse_root_t *pr; parse_node_t *pn; mdb_ctf_id_t id; pr = mdb_zalloc(sizeof (parse_root_t), UM_SLEEP | UM_GC); basename = defn; c = strchr(defn, ' '); if (c == NULL) { mdb_printf("Invalid structure definition. Structure " "must start with either 'struct {' or 'union {'\n"); return (DCMD_ERR); } *c = '\0'; if (strcmp(defn, "struct") == 0) pr->pr_kind = CTF_K_STRUCT; else if (strcmp(defn, "union") == 0) pr->pr_kind = CTF_K_UNION; else { mdb_printf("Invalid start of definition. " "Expected 'struct' or 'union'. " "Found: '%s'\n", defn); return (DCMD_ERR); } /* * We transform this back to a space so we can validate that a * non-anonymous struct or union name is valid. */ *c = ' '; kind = defn; defn = c + 1; while (*defn == ' ') defn++; /* Check whether this is anonymous or not */ if (*defn != '{') { brace = strchr(defn, '{'); c = brace; if (c == NULL) { mdb_printf("Missing opening brace for %s definition. " "Expected '{'. " "Found: '%c'\n", kind, *defn); return (DCMD_ERR); } *c = '\0'; c--; while (*c == ' ') c--; *(c+1) = '\0'; if (typedef_valid_identifier(defn) != 0) { mdb_printf("The name %s is not a valid C identifier.\n", defn); return (DCMD_ERR); } if (mdb_ctf_lookup_by_name(basename, &id) != CTF_ERR) { mdb_printf("type name %s already in use\n", basename); return (DCMD_ERR); } pr->pr_name = defn; defn = brace; } else { pr->pr_name = NULL; } defn++; while (*defn == ' ') defn++; len = strlen(defn); if (defn[len-1] != '}') { mdb_printf("Missing closing brace for %s declaration. " "Expected '}'.\n", kind); return (DCMD_ERR); } defn[len-1] = '\0'; /* * Start walking all the arguments, looking for a terminating semicolon * for type definitions. */ for (;;) { ret = typedef_parse_member(defn, &c, &pn); if (ret == DCMD_ERR) return (DCMD_ERR); mdb_list_append(&pr->pr_nodes, pn); while (*c == ' ') c++; if (*c == '\0') break; defn = c; } pr->pr_tname = name; *prp = pr; return (DCMD_OK); } /* * Make sure that none of the member names overlap and that the type names don't * already exist. If we have an array entry that is a VLA, make sure it is the * last member and not the only member. */ static int typedef_validate(parse_root_t *pr) { mdb_nv_t nv; parse_node_t *pn; mdb_ctf_id_t id; int count = 0; (void) mdb_nv_create(&nv, UM_SLEEP | UM_GC); for (pn = mdb_list_next(&pr->pr_nodes); pn != NULL; pn = mdb_list_next(pn)) { count++; if (mdb_nv_lookup(&nv, pn->pn_name) != NULL) { mdb_printf("duplicate name detected: %s\n", pn->pn_name); return (DCMD_ERR); } /* * Our parse tree won't go away before the nv, so it's simpler * to just mark everything external. */ (void) mdb_nv_insert(&nv, pn->pn_name, NULL, 0, MDB_NV_EXTNAME); if (pn->pn_flags & PN_F_ARRAY && pn->pn_asub == 0) { if (pr->pr_kind != CTF_K_STRUCT) { mdb_printf("Flexible array members are only " "valid in structs.\n"); return (DCMD_ERR); } if (&pn->pn_list != pr->pr_nodes.ml_prev) { mdb_printf("Flexible array entries are only " "allowed to be the last entry in a " "struct\n"); return (DCMD_ERR); } if (count == 1) { mdb_printf("Structs must have members aside " "from a flexible member\n"); return (DCMD_ERR); } } } if (mdb_ctf_lookup_by_name(pr->pr_tname, &id) != CTF_ERR) { mdb_printf("typedef name %s already exists\n", pr->pr_tname); return (DCMD_ERR); } return (DCMD_OK); } static int typedef_add(parse_root_t *pr) { parse_node_t *pn; mdb_ctf_id_t id, aid, tid, pid; mdb_ctf_arinfo_t ar; int ii; /* Pre-flight checks */ if (typedef_validate(pr) == DCMD_ERR) return (DCMD_ERR); if (pr->pr_kind == CTF_K_STRUCT) { if (mdb_ctf_add_struct(pr->pr_name, &id) != 0) { mdb_printf("failed to create struct for %s\n", pr->pr_tname); return (DCMD_ERR); } } else { if (mdb_ctf_add_union(pr->pr_name, &id) != 0) { mdb_printf("failed to create union for %s\n", pr->pr_tname); return (DCMD_ERR); } } for (pn = mdb_list_next(&pr->pr_nodes); pn != NULL; pn = mdb_list_next(pn)) { if (mdb_ctf_lookup_by_name(pn->pn_type, &tid) == CTF_ERR) { mdb_printf("failed to add member %s: type %s does " "not exist\n", pn->pn_name, pn->pn_type); goto destroy; } if (pn->pn_flags & PN_F_POINTER) { for (ii = 0; ii < pn->pn_nptrs; ii++) { if (mdb_ctf_add_pointer(&tid, &pid) != 0) { mdb_printf("failed to add a pointer " "type as part of member: %s\n", pn->pn_name); goto destroy; } tid = pid; } } if (pn->pn_flags & PN_F_ARRAY) { if (mdb_ctf_lookup_by_name("long", &aid) != 0) { mdb_printf("failed to lookup the type 'long' " "for array indexes, are you running mdb " "without a target or using ::typedef -c?"); goto destroy; } ar.mta_contents = tid; ar.mta_index = aid; ar.mta_nelems = pn->pn_asub; if (mdb_ctf_add_array(&ar, &tid) != 0) { mdb_printf("failed to create array type for " "memeber%s\n", pn->pn_name); goto destroy; } } if (mdb_ctf_add_member(&id, pn->pn_name, &tid, NULL) == CTF_ERR) { mdb_printf("failed to create member %s\n", pn->pn_name); goto destroy; } } if (mdb_ctf_add_typedef(pr->pr_tname, &id, NULL) != 0) { mdb_printf("failed to add typedef for %s\n", pr->pr_tname); goto destroy; } return (DCMD_OK); destroy: return (mdb_ctf_type_delete(&id)); } static int typedef_readfile(const char *file) { int ret; ret = mdb_ctf_synthetics_from_file(file); if (ret != DCMD_OK) mdb_warn("failed to create synthetics from file %s\n", file); return (ret); } static int typedef_writefile(const char *file) { int ret; ret = mdb_ctf_synthetics_to_file(file); if (ret != DCMD_OK) mdb_warn("failed to write synthetics to file %s", file); return (ret); } /* ARGSUSED */ int cmd_typedef(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_ctf_id_t id; int i; int destroy = 0, list = 0; const char *cmode = NULL, *rfile = NULL, *wfile = NULL; const char *dst, *src; char *dup; parse_root_t *pr; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); i = mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &destroy, 'l', MDB_OPT_SETBITS, TRUE, &list, 'c', MDB_OPT_STR, &cmode, 'r', MDB_OPT_STR, &rfile, 'w', MDB_OPT_STR, &wfile, NULL); argc -= i; argv += i; /* * All our options are mutually exclusive currently. */ i = 0; if (destroy) i++; if (cmode != NULL) i++; if (list) i++; if (rfile != NULL) i++; if (wfile != NULL) i++; if (i > 1) return (DCMD_USAGE); if ((destroy || cmode != NULL || list || rfile != NULL || wfile != NULL) && argc != 0) return (DCMD_USAGE); if (destroy) return (typedef_destroy()); if (cmode) return (typedef_create(cmode)); if (list) return (typedef_list()); if (rfile) return (typedef_readfile(rfile)); if (wfile) return (typedef_writefile(wfile)); if (argc < 2) return (DCMD_USAGE); /* * Check to see if we are defining a struct or union. Note that we have * to distinguish between struct foo and struct {. All typedef structs * are annonymous structs that are only known by their typedef name. The * same is true with unions. The problem that we have to deal with is * that the ';' character in mdb causes mdb to begin another command. To * work around that fact we require users to put the whole struct * definition in a pair of "" or ''. */ if (argc == 2 && strchr(argv[0].a_un.a_str, '{') != NULL) { dup = mdb_alloc(strlen(argv[0].a_un.a_str) + 1, UM_GC | UM_SLEEP); (void) strcpy(dup, argv[0].a_un.a_str); if (typedef_parse(dup, argv[1].a_un.a_str, &pr) == DCMD_ERR) return (DCMD_ERR); if (typedef_add(pr) == DCMD_ERR) return (DCMD_ERR); return (DCMD_OK); } /* * Someone could give us something like struct foobar or unsigned int or * even long double imaginary. In this case we end up conjoining all * arguments except the last one into one large string that we look up. */ if (argc - 1 == 1) { src = argv[0].a_un.a_str; } else { src = typedef_join_strings(argc - 2, argv, UM_GC | UM_SLEEP); } dst = argv[argc-1].a_un.a_str; if (mdb_ctf_lookup_by_name(dst, &id) != -1) { mdb_printf("%s already exists\n", dst); return (DCMD_ERR); } if (mdb_ctf_lookup_by_name(src, &id) != 0) { mdb_printf("%s does not exist\n", src); return (DCMD_ERR); } if (mdb_ctf_add_typedef(dst, &id, NULL) != 0) { mdb_printf("failed to create typedef\n"); return (DCMD_ERR); } return (DCMD_OK); } static char typedef_desc[] = "::typedef operates like the C typedef keyword and creates a synthetic type\n" "that is usable across mdb just like a type that is embedded in CTF data.\n" "This includes familiar dcmds like ::print as well as mdb's tab completion\n" "engine. The \"type\" argument can either be a named structure or union\n" "declaration, like \"struct proc { int p_id; }\" declartion, an anonymous\n" "structure or union declaration, like \"struct { int count; }\", or simply\n" "the name of an existing type, like \"uint64_t\". Either form may refer to\n" "other types already defined in CTF or a previous ::typedef invocation. When\n" "debugging binaries without CTF, definitions for intrinsic types may be\n" "created using the -c option. See the OPTIONS section for more information.\n" "If a named struct or union is used, then a type will be created for it just\n" "like in C. This may be used to mimic a forward declaration and an example of\n" "this is in the EXAMPLES section. Regardless of whether a struct or union is\n" "anonymous or named, the \"name\" argument is always required.\n" "\n" "When declaring anonymous structures and unions, the entire definition must\n" "be enclosed within \"\" or ''. The ';' is used by mdb to separate commands\n" "in a similar fashion to the shell. The ';' cannot be escaped, therefore\n" "quoting your argument is necessary. See the EXAMPLES sections for examples\n" "of what this looks like.\n" "\n" "All member and type names must be valid C identifiers. They must start with\n" "an underscore or a letter. Subsequent characters are allowed to be letters,\n" "numbers, or an underscore. When specifying an array, mdb's default base is\n" "not used. Instead, base 10 is assumed to make it easier to use declarations\n" "from C files and transform them. (Other features remain.)\n" "\n" "Declaring arrays and any number of pointers in anonymous structures is \n" "supported. However the following C features are not supported: \n" " o function pointers (use a void * instead)\n" " o bitfields (use an integer of the appropriate size instead)\n" " o packed structures (all structures currently use their natural alignment)\n" "\n" "::typedef also allows you to read type definitions from a file. Definitions\n" "can be read from any ELF file that has a CTF section that libctf can parse\n" "or any raw CTF data files, such as those that can be created with ::typedef.\n" "You can check if a file has such a section with elfdump(1). If a binary or\n" "core dump does not have any type information, but you do have it elsewhere,\n" "then you can use ::typedef -r to read in that type information.\n" "\n" "All built up definitions may be exported as a valid CTF container that can\n" "be used again with ::typedef -r or anything that uses libctf. To write them\n" "out, use ::typedef -w and specify the name of a file. For more information\n" "on the CTF file format, see ctf(5).\n" "\n"; static char typedef_opts[] = " -c model create intrinsic types based on the specified data model.\n" " The INTRINSICS section lists the built-in types and typedefs.\n" " The following data models are supported:\n" " o LP64 - Traditional illumos 64-bit program.\n" " o LP32 - Traditional illumos 32-bit program.\n" " o ILP32 - An alternate name for LP32.\n" " -d delete all synthetic types\n" " -l list all synthetic types\n" " -r file import type definitions (CTF) from another ELF file\n" " -w file write all synthetic type definitions out to file\n" "\n"; static char typedef_examps[] = " ::typedef -c LP64\n" " ::typedef uint64_t bender_t\n" " ::typedef struct proc new_proc_t\n" " ::typedef \"union { int frodo; char sam; long gandalf; }\" ringbearer_t;\n" " ::typedef \"struct { uintptr_t stone[7]; void **white; }\" gift_t\n" " ::typedef \"struct list { struct list *l_next; struct list *l_prev; }\" " "list_t\n" " ::typedef -r /var/tmp/qemu-system-x86_64\n" " ::typedef -w defs.ctf\n" "\n"; static char typedef_intrins[] = "The following C types and typedefs are provided when \n" "::typedef -c is used\n" "\n" " signed unsigned void\n" " char short int\n" " long long long signed char\n" " signed short signed int signed long\n" " singed long long unsigned char unsigned short\n" " unsigned int unsigned long unsigned long long\n" " _Bool float double\n" " long double float imaginary double imaginary\n" " long double imaginary float complex\n" " double complex long double complex\n" "\n" " int8_t int16_t int32_t\n" " int64_t intptr_t uint8_t\n" " uint16_t uint32_t uint64_t\n" " uchar_t ushort_t uint_t\n" " ulong_t u_longlong_t ptrdiff_t\n" " uintptr_t\n" "\n"; void cmd_typedef_help(void) { mdb_printf("%s", typedef_desc); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf("%s", typedef_opts); (void) mdb_dec_indent(2); mdb_printf("%EXAMPLES%\n"); (void) mdb_inc_indent(2); mdb_printf("%s", typedef_examps); (void) mdb_dec_indent(2); mdb_printf("%INTRINSICS%\n"); (void) mdb_inc_indent(2); mdb_printf("%s", typedef_intrins); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2012 Joyent, Inc. All rights reserved. */ #ifndef _MDB_TYPEDEF_H #define _MDB_TYPEDEF_H #include #ifdef __cplusplus extern "C" { #endif int cmd_typedef(uintptr_t, uint_t, int, const mdb_arg_t *); void cmd_typedef_help(void); #ifdef __cplusplus } #endif #endif /* _MDB_TYPEDEF_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1997-1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_TYPES_H #define _MDB_TYPES_H #include #ifdef __cplusplus extern "C" { #endif /* * If we're compiling on a system before existed, * make sure we have uintptr_t appropriately defined. */ #ifndef _SYS_INT_TYPES_H #if defined(_LP64) typedef unsigned long uintptr_t; #else typedef unsigned int uintptr_t; #endif #endif typedef uchar_t mdb_bool_t; #ifdef __cplusplus } #endif #endif /* _MDB_TYPES_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * These routines simply provide wrappers around malloc(3C) and free(3C) * for now. In the future we hope to provide a userland equivalent to * the kmem allocator, including cache allocators. */ #include #include #include #ifdef _KMDB #include #endif #include #include #include #include #include #include #define UMF_DEBUG 0x1 #ifdef DEBUG int mdb_umem_flags = UMF_DEBUG; #else int mdb_umem_flags = 0; #endif struct mdb_mblk { void *blk_addr; /* address of allocated block */ size_t blk_size; /* size of block in bytes */ struct mdb_mblk *blk_next; /* link to next block */ }; /*ARGSUSED*/ static void * mdb_umem_handler(size_t nbytes, size_t align, uint_t flags) { #ifdef _KMDB /* * kmdb has a fixed, dedicated VA range in which to play. This range * won't change size while the debugger is running, regardless of how * long we wait. As a result, the only sensible course of action is * to fail the request. If we're here, however, the request was made * with UM_SLEEP. The caller is thus not expecting a NULL back. We'll * have to fail the current dcmd set. */ if (mdb.m_depth > 0) { warn("failed to allocate %lu bytes -- recovering\n", (ulong_t)nbytes); kmdb_print_stack(); longjmp(mdb.m_frame->f_pcb, MDB_ERR_NOMEM); } #else /* * mdb, on the other hand, can afford to wait, as someone may actually * free something. */ if (errno == EAGAIN) { void *ptr = NULL; char buf[64]; (void) mdb_iob_snprintf(buf, sizeof (buf), "[ sleeping for %lu bytes of free memory ... ]", (ulong_t)nbytes); (void) mdb_iob_puts(mdb.m_err, buf); (void) mdb_iob_flush(mdb.m_err); do { (void) poll(NULL, 0, 1000); if (align != 0) ptr = memalign(align, nbytes); else ptr = malloc(nbytes); } while (ptr == NULL && errno == EAGAIN); if (ptr != NULL) return (ptr); (void) memset(buf, '\b', strlen(buf)); (void) mdb_iob_puts(mdb.m_err, buf); (void) mdb_iob_flush(mdb.m_err); (void) memset(buf, ' ', strlen(buf)); (void) mdb_iob_puts(mdb.m_err, buf); (void) mdb_iob_flush(mdb.m_err); (void) memset(buf, '\b', strlen(buf)); (void) mdb_iob_puts(mdb.m_err, buf); (void) mdb_iob_flush(mdb.m_err); } #endif die("failed to allocate %lu bytes -- terminating\n", (ulong_t)nbytes); /*NOTREACHED*/ return (NULL); } static void mdb_umem_gc_enter(void *ptr, size_t nbytes) { mdb_mblk_t *blkp = mdb_alloc(sizeof (mdb_mblk_t), UM_SLEEP); blkp->blk_addr = ptr; blkp->blk_size = nbytes; blkp->blk_next = mdb.m_frame->f_mblks; mdb.m_frame->f_mblks = blkp; } /* * If we're compiled in debug mode, we use this function (gratuitously * stolen from kmem.c) to set uninitialized and freed regions to * special bit patterns. */ static void mdb_umem_copy_pattern(uint32_t pattern, void *buf_arg, size_t size) { /* LINTED - alignment of bufend */ uint32_t *bufend = (uint32_t *)((char *)buf_arg + size); uint32_t *buf = buf_arg; while (buf < bufend - 3) { buf[3] = buf[2] = buf[1] = buf[0] = pattern; buf += 4; } while (buf < bufend) *buf++ = pattern; } void * mdb_alloc_align(size_t nbytes, size_t align, uint_t flags) { void *ptr; size_t obytes = nbytes; if (nbytes == 0 || nbytes > MDB_ALLOC_MAX) return (NULL); nbytes = (nbytes + sizeof (uint32_t) - 1) & ~(sizeof (uint32_t) - 1); if (nbytes < obytes || nbytes == 0) return (NULL); if (align != 0) ptr = memalign(align, nbytes); else ptr = malloc(nbytes); if (flags & UM_SLEEP) { while (ptr == NULL) ptr = mdb_umem_handler(nbytes, align, flags); } if (ptr != NULL && (mdb_umem_flags & UMF_DEBUG) != 0) mdb_umem_copy_pattern(UMEM_UNINITIALIZED_PATTERN, ptr, nbytes); if (flags & UM_GC) mdb_umem_gc_enter(ptr, nbytes); return (ptr); } void * mdb_alloc(size_t nbytes, uint_t flags) { return (mdb_alloc_align(nbytes, 0, flags)); } void * mdb_zalloc(size_t nbytes, uint_t flags) { void *ptr = mdb_alloc(nbytes, flags); if (ptr != NULL) bzero(ptr, nbytes); return (ptr); } void mdb_free(void *ptr, size_t nbytes) { ASSERT(ptr != NULL || nbytes == 0); nbytes = (nbytes + sizeof (uint32_t) - 1) & ~(sizeof (uint32_t) - 1); if (ptr != NULL) { if (mdb_umem_flags & UMF_DEBUG) mdb_umem_copy_pattern(UMEM_FREE_PATTERN, ptr, nbytes); free(ptr); } } void mdb_free_align(void *ptr, size_t nbytes) { mdb_free(ptr, nbytes); } void mdb_recycle(mdb_mblk_t **blkpp) { mdb_mblk_t *blkp, *nblkp; for (blkp = *blkpp; blkp != NULL; blkp = nblkp) { mdb_dprintf(MDB_DBG_UMEM, "garbage collect %p size %lu bytes\n", blkp->blk_addr, (ulong_t)blkp->blk_size); nblkp = blkp->blk_next; mdb_free(blkp->blk_addr, blkp->blk_size); mdb_free(blkp, sizeof (mdb_mblk_t)); } *blkpp = NULL; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_UMEM_H #define _MDB_UMEM_H #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB #define UMEM_FREE_PATTERN 0xdefacedd #define UMEM_UNINITIALIZED_PATTERN 0xbeefbabe typedef struct mdb_mblk mdb_mblk_t; /* Aligned allocation/frees are not available through the module API */ extern void *mdb_alloc_align(size_t, size_t, uint_t); extern void mdb_free_align(void *, size_t); extern void mdb_recycle(mdb_mblk_t **); /* * These values represent an attempt to help constrain dmods that have bugs and * have accidentally underflowed their size arguments. They represent * allocations that are impossible. */ #if defined(_ILP32) #define MDB_ALLOC_MAX INT32_MAX #elif defined(_LP64) #define MDB_ALLOC_MAX INT64_MAX #else #error "Unknown data model" #endif #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_UMEM_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Joyent, Inc. * Copyright 2024 Oxide Computer Company */ /* * Immediate Value Target * * The immediate value target is used when the '=' verb is used to * format an immediate value, or with ::print -i. The target is * initialized with a specific value, and then simply copies bytes from * this integer in its read routine. Two notes: * * (1) the address parameter of value_read is treated as an offset into * the immediate value. * * (2) on big-endian systems, we need to be careful about the place we * copy data from. If the caller specified a typesize in the argv array * we use that for offsetting, otherwise we use the read size. * If the user didn't specify the typesize, then 'addr' is ignored, * and all reads are at an offset of 0 into the immediate value. This * covers both the usage of ::print -i, and the semantics of adb * commands like "0x1234=X", which should produce 0x1234 as a result; * the adb model is for it to act like a cast down to the smaller * integer type; this is handled as mentioned. */ #include #include #include #include #include #include void mdb_value_tgt_destroy(mdb_tgt_t *); typedef struct mdb_value_data { uintmax_t mvd_data; size_t mvd_typesize; } mdb_value_data_t; static ssize_t value_read(mdb_tgt_t *t, void *dst, size_t nbytes, uintptr_t addr) { mdb_value_data_t *data = t->t_data; size_t size = data->mvd_typesize; const char *src = (const char *)&data->mvd_data; size_t off; /* * If no output size was specified, use the current read size. * In this case, "addr" is not an offset into the mvd_data, * so we ignore it. */ if (size == 0) { size = nbytes; addr = 0; } else { nbytes = MIN(size, nbytes); } off = addr; #ifdef _BIG_ENDIAN if (sizeof (uintmax_t) >= size) off += sizeof (uintmax_t) - size; #endif if (off > sizeof (uintmax_t)) return (0); if (off + nbytes > sizeof (uintmax_t)) nbytes = sizeof (uintmax_t) - off; if (nbytes != 0) bcopy(src + off, dst, nbytes); return (nbytes); } /*ARGSUSED*/ static ssize_t value_write(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr) { return (nbytes); /* We allow writes to silently fail */ } static const mdb_tgt_ops_t value_ops = { .t_setflags = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_setcontext = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_activate = (void (*)())(uintptr_t)mdb_tgt_nop, .t_deactivate = (void (*)())(uintptr_t)mdb_tgt_nop, .t_periodic = (void (*)())(uintptr_t)mdb_tgt_nop, .t_destroy = mdb_value_tgt_destroy, .t_name = (const char *(*)())mdb_tgt_null, .t_isa = (const char *(*)())mdb_conf_isa, .t_platform = (const char *(*)())mdb_conf_platform, .t_uname = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_dmodel = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_aread = (ssize_t (*)())mdb_tgt_notsup, .t_awrite = (ssize_t (*)())mdb_tgt_notsup, .t_vread = value_read, .t_vwrite = value_write, .t_pread = (ssize_t (*)())mdb_tgt_notsup, .t_pwrite = (ssize_t (*)())mdb_tgt_notsup, .t_fread = value_read, .t_fwrite = value_write, .t_ioread = value_read, .t_iowrite = value_write, .t_vtop = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_lookup_by_name = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_lookup_by_addr = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_symbol_iter = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_mapping_iter = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_object_iter = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_addr_to_map = (const mdb_map_t *(*)())mdb_tgt_null, .t_name_to_map = (const mdb_map_t *(*)())mdb_tgt_null, .t_addr_to_ctf = (struct ctf_file *(*)())mdb_tgt_null, .t_name_to_ctf = (struct ctf_file *(*)())mdb_tgt_null, .t_status = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_run = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_step = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_step_out = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_next = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_cont = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_signal = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_add_vbrkpt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sbrkpt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_pwapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_vwapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_iowapt = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sysenter = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_sysexit = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_signal = (int (*)())(uintptr_t)mdb_tgt_null, .t_add_fault = (int (*)())(uintptr_t)mdb_tgt_null, .t_getareg = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_putareg = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_stack_iter = (int (*)())(uintptr_t)mdb_tgt_nop, .t_auxv = (int (*)())(uintptr_t)mdb_tgt_notsup, .t_thread_name = (int (*)())(uintptr_t)mdb_tgt_notsup, }; int mdb_value_tgt_create(mdb_tgt_t *t, int argc, const char *argv[]) { mdb_value_data_t *data; if (argc < 1 || argv[0] == NULL) return (set_errno(EINVAL)); if (argc == 2 && argv[1] == NULL) return (set_errno(EINVAL)); if (argc > 2) return (set_errno(EINVAL)); t->t_ops = &value_ops; data = mdb_zalloc(sizeof (mdb_value_data_t), UM_SLEEP); t->t_data = data; data->mvd_data = *((uintmax_t *)(void *)argv[0]); if (argc == 2) data->mvd_typesize = *((size_t *)(void *)argv[1]); return (0); } void mdb_value_tgt_destroy(mdb_tgt_t *t) { mdb_free(t->t_data, sizeof (mdb_value_data_t)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * In order to implement walk iteration variables (that is, ::walk walk varname) * we need to keep track of the active walk variables as the pipeline is * processed. Each variable is tracked using a VCB (Variable Control Block) * that keeps a pointer to the variable in the MDB variable hash table, as * well as an addrvec (array of values) and parent pointer. Each command in * the pipeline keeps its own list of VCBs, and these are inherited from left * to right in the pipeline. The diagram shows an example pipeline and the * contents of c_addrv and VCBs at each stage: * * > ::walk proc p | ::map .+1 | ::eval ' ::walk proc p | ::walk proc v | ::eval " #include #include #include #include mdb_vcb_t * mdb_vcb_create(mdb_var_t *v) { mdb_vcb_t *vcb = mdb_zalloc(sizeof (mdb_vcb_t), UM_SLEEP); vcb->vc_var = v; return (vcb); } void mdb_vcb_destroy(mdb_vcb_t *vcb) { mdb_dprintf(MDB_DBG_DSTK, "delete vcb %p (%s)\n", (void *)vcb, mdb_nv_get_name(vcb->vc_var)); mdb_addrvec_destroy(&vcb->vc_addrv); mdb_free(vcb, sizeof (mdb_vcb_t)); } void mdb_vcb_propagate(mdb_vcb_t *vcb) { while (vcb != NULL) { mdb_addrvec_t *adp = &vcb->vc_addrv; ASSERT(vcb->vc_adnext < adp->ad_nelems); mdb_nv_set_value(vcb->vc_var, adp->ad_data[vcb->vc_adnext++]); vcb = vcb->vc_link; } } void mdb_vcb_purge(mdb_vcb_t *vcb) { while (vcb != NULL) { mdb_vcb_t *n = vcb->vc_link; mdb_vcb_destroy(vcb); vcb = n; } } void mdb_vcb_inherit(mdb_cmd_t *src, mdb_cmd_t *dst) { mdb_vcb_t *vc1, *vc2; for (vc1 = src->c_vcbs; vc1 != NULL; vc1 = vc1->vc_link) { vc2 = mdb_vcb_create(vc1->vc_var); vc2->vc_parent = vc1; vc2->vc_link = dst->c_vcbs; dst->c_vcbs = vc2; } } void mdb_vcb_insert(mdb_vcb_t *vcb, mdb_frame_t *fp) { if (fp->f_pcmd != NULL) { mdb_cmd_t *cp = fp->f_pcmd; mdb_dprintf(MDB_DBG_DSTK, "insert vcb %p (%s)\n", (void *)vcb, mdb_nv_get_name(vcb->vc_var)); ASSERT(vcb->vc_link == NULL); vcb->vc_link = cp->c_vcbs; cp->c_vcbs = vcb; } } void mdb_vcb_update(struct mdb_frame *fp, uintptr_t value) { mdb_vcb_t *vcb; for (vcb = fp->f_pcmd->c_vcbs; vcb != NULL; vcb = vcb->vc_link) { if (vcb->vc_parent != NULL) { mdb_addrvec_t *adp = &vcb->vc_parent->vc_addrv; adp->ad_ndx = vcb->vc_parent->vc_adnext - 1; ASSERT(adp->ad_ndx < adp->ad_nelems); value = adp->ad_data[adp->ad_ndx++]; } mdb_addrvec_unshift(&vcb->vc_addrv, value); } } mdb_vcb_t * mdb_vcb_find(mdb_var_t *var, mdb_frame_t *fp) { mdb_vcb_t *vcb; if (fp->f_pcmd != NULL) { vcb = fp->f_pcmd->c_vcbs; while (vcb != NULL) { if (vcb->vc_var == var) return (vcb); vcb = vcb->vc_link; } } return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_VCB_H #define _MDB_VCB_H #include #include #ifdef __cplusplus extern "C" { #endif #ifdef _MDB struct mdb_frame; /* Forward declaration */ struct mdb_cmd; /* Forward declaration */ typedef struct mdb_vcb { mdb_var_t *vc_var; /* Pointer to dependent variable */ mdb_addrvec_t vc_addrv; /* List of address values */ size_t vc_adnext; /* Next index for vc_addrv */ struct mdb_vcb *vc_link; /* Pointer to next vcb in list */ struct mdb_vcb *vc_parent; /* Pointer to parent vcb */ } mdb_vcb_t; extern mdb_vcb_t *mdb_vcb_create(mdb_var_t *); extern void mdb_vcb_destroy(mdb_vcb_t *); extern void mdb_vcb_propagate(mdb_vcb_t *); extern void mdb_vcb_purge(mdb_vcb_t *); extern void mdb_vcb_inherit(struct mdb_cmd *, struct mdb_cmd *); extern void mdb_vcb_insert(mdb_vcb_t *, struct mdb_frame *); extern mdb_vcb_t *mdb_vcb_find(mdb_var_t *, struct mdb_frame *); extern void mdb_vcb_update(struct mdb_frame *, uintptr_t); #endif /* _MDB */ #ifdef __cplusplus } #endif #endif /* _MDB_VCB_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Joyent, Inc. */ #include #include const mdb_walker_t mdb_walker_builtins[] = { { "linkerset", "walk a linkerset", ldset_walk_init, ldset_walk_step }, { "linkersets", "walk all linkersets", ldsets_walk_init, ldsets_walk_step }, NULL }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Active walks are tracked using a WCB (Walk Control Block), which is a simple * data structure that contains the mdb_walk_state_t passed to the various * walker functions for this particular walk, as well as links to other walk * layers if this is a layered walk. The control block is kept in a list * associated with the MDB frame, so that we can clean up all the walks and * call their respective fini routines at the end of command processing. */ #include #include #include #include #include #include #include mdb_wcb_t * mdb_wcb_from_state(mdb_walk_state_t *wsp) { /* * The walk state passed to a walker sits at the start of the * walk control block, so we can ask the walker to pass this * back to us and quickly obtain the control block: */ mdb_wcb_t *wcb = (mdb_wcb_t *)wsp; if (wcb->w_buftag != WCB_TAG_ACTIVE && wcb->w_buftag != WCB_TAG_INITIAL) fail("walk state %p is corrupt or not active\n", (void *)wcb); return (wcb); } mdb_wcb_t * mdb_wcb_create(mdb_iwalker_t *iwp, mdb_walk_cb_t cb, void *data, uintptr_t addr) { mdb_wcb_t *wcb = mdb_zalloc(sizeof (mdb_wcb_t), UM_SLEEP); wcb->w_buftag = WCB_TAG_INITIAL; wcb->w_walker = iwp; wcb->w_state.walk_callback = cb; wcb->w_state.walk_cbdata = data; wcb->w_state.walk_addr = addr; wcb->w_state.walk_arg = iwp->iwlk_init_arg; return (wcb); } void mdb_wcb_destroy(mdb_wcb_t *wcb) { mdb_wcb_t *p, *q; for (p = wcb->w_lyr_head; p != NULL; p = q) { q = wcb->w_lyr_link; mdb_wcb_destroy(p); } if (wcb->w_inited) wcb->w_walker->iwlk_fini(&wcb->w_state); mdb_free(wcb, sizeof (mdb_wcb_t)); } void mdb_wcb_insert(mdb_wcb_t *wcb, mdb_frame_t *fp) { mdb_dprintf(MDB_DBG_WALK, "activate walk %s`%s wcb %p\n", wcb->w_walker->iwlk_modp->mod_name, wcb->w_walker->iwlk_name, (void *)wcb); wcb->w_buftag = WCB_TAG_ACTIVE; wcb->w_link = fp->f_wcbs; fp->f_wcbs = wcb; } void mdb_wcb_delete(mdb_wcb_t *wcb, mdb_frame_t *fp) { mdb_wcb_t **pp = &fp->f_wcbs; mdb_wcb_t *w; mdb_dprintf(MDB_DBG_WALK, "deactivate walk %s`%s wcb %p\n", wcb->w_walker->iwlk_modp->mod_name, wcb->w_walker->iwlk_name, (void *)wcb); for (w = fp->f_wcbs; w != NULL; pp = &w->w_link, w = w->w_link) { if (w == wcb) { w->w_buftag = WCB_TAG_PASSIVE; *pp = w->w_link; return; } } fail("attempted to remove wcb not on list: %p\n", (void *)wcb); } void mdb_wcb_purge(mdb_wcb_t **wcbpp) { mdb_wcb_t *n, *wcb = *wcbpp; while (wcb != NULL) { mdb_dprintf(MDB_DBG_WALK, "purge walk %s`%s wcb %p\n", wcb->w_walker->iwlk_modp->mod_name, wcb->w_walker->iwlk_name, (void *)wcb); n = wcb->w_link; mdb_wcb_destroy(wcb); wcb = n; } *wcbpp = NULL; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_WCB_H #define _MDB_WCB_H #include #ifdef __cplusplus extern "C" { #endif /* * Values for w_buftag, used as a guard to ensure that the walk state hasn't * overflowed into the wcb. w_buftag is INITIAL when the wcb is first * allocated, ACTIVE when added to a frame, and PASSIVE when removed. */ #define WCB_TAG_INITIAL 0xcbbabecb /* Magic tag for initialized wcb */ #define WCB_TAG_ACTIVE 0xcba1cba1 /* Magic tag for active wcb */ #define WCB_TAG_PASSIVE 0xcbdeadcb /* Magic tag for inactive wcb */ struct mdb_frame; /* Forward declaration */ typedef struct mdb_wcb { mdb_walk_state_t w_state; /* Walk soft state */ uint32_t w_buftag; /* WCB_TAG_* */ int w_inited; /* Set if we've called walk_init */ struct mdb_wcb *w_lyr_head; /* Link to head wcb in layer chain */ struct mdb_wcb *w_lyr_link; /* Link to next wcb in layer chain */ struct mdb_wcb *w_link; /* Link to next wcb in global chain */ const mdb_iwalker_t *w_walker; /* Walker corresponding to this wcb */ } mdb_wcb_t; extern mdb_wcb_t *mdb_wcb_create(mdb_iwalker_t *, mdb_walk_cb_t, void *, uintptr_t); extern void mdb_wcb_destroy(mdb_wcb_t *); extern mdb_wcb_t *mdb_wcb_from_state(mdb_walk_state_t *); extern void mdb_wcb_insert(mdb_wcb_t *, struct mdb_frame *); extern void mdb_wcb_delete(mdb_wcb_t *, struct mdb_frame *); extern void mdb_wcb_purge(mdb_wcb_t **); #ifdef __cplusplus } #endif #endif /* _MDB_WCB_H */ /* * 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 #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", name); 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%\n", "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]); mdb_printf("\n"); } ret = 0; /* call in to the registered handlers */ for (idx = 0; idx < whatis_cb_count; idx++) { whatis_callback_t *cur = whatis_cb[idx]; mdb_idcmd_t *dcmd = NULL; mdb_module_t *mod; /* 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); } /* * We need to run each whatis callback in the context of the * module that added it. That means that it will be able to * access things relevant to that module such as, for example, * CTF data. We do this by updating the "whatis" command * structure in place and restoring the original module * afterwards. */ if (mdb.m_frame->f_cp != NULL) { dcmd = mdb.m_frame->f_cp->c_dcmd; if (dcmd != NULL) { mod = dcmd->idc_modp; dcmd->idc_modp = cur->wcb_module; } } if (cur->wcb_func(&w, cur->wcb_arg) != 0) ret = 1; if (dcmd != NULL) dcmd->idc_modp = mod; /* 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); } /* * 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. */ #ifndef _MDB_WHATIS_H #define _MDB_WHATIS_H #ifdef __cplusplus extern "C" { #endif struct mdb_whatis; typedef struct mdb_whatis mdb_whatis_t; /* * int mdb_whatis_overlaps(mdb_whatis_t *w, uintptr_t base, size_t size): * * Returns non-zero if and only if a call to * * mdb_whatis_match(w, base, size, ...) * * will succeed; that is, there is an address of interest in the * range [base, base+size). */ extern int mdb_whatis_overlaps(mdb_whatis_t *, uintptr_t, size_t); /* * int mdb_whatis_match(mdb_whatis_t *w, uintptr_t base, size_t size, * uintptr_t *out) * * Perform an iterative search for an address of interest in [base, base+size). * Each call returning a non-zero value returns the next interesting address * in the range. This must be called repeatedly until it returns a zero * value, indicating that the search is complete. * * For example: * uintptr_t cur; * * while (mdb_whatis_match(w, base, size, &cur)) * mdb_whatis_report_object(w, cur, base, "allocated from ..."); */ extern int mdb_whatis_match(mdb_whatis_t *, uintptr_t, size_t, uintptr_t *); /* * void mdb_whatis_report_address(mdb_whatis_t *w, uintptr_t addr, * uintptr_t base, const char *format, ...) * * Reports addr (an address from mdb_whatis_match()). If addr is inside * a symbol, that will be reported. (format, ...) is an mdb_printf() * format string and associated arguments, and will follow a string like * "addr is ". For example, it could be "in libfoo's text segment\n": * * addr is in libfoo's text segment * * The caller should make sure to output a newline, either in format or in a * separate mdb_printf() call. */ extern void mdb_whatis_report_address(mdb_whatis_t *, uintptr_t, const char *, ...); /* * void mdb_whatis_report_object(mdb_whatis_t *w, uintptr_t addr, * uintptr_t base, const char *format, ...) * * Reports addr (an address from mdb_whatis_match()) as being part of an * object beginning at base. (format, ...) is an mdb_printf() format * string and associated arguments, and will follow a string like * "addr is base+offset, ". For example, it could be "allocated from foo\n": * * addr is base+offset, allocated from foo * * The caller should make sure to output a newline, either in format or in a * separate mdb_printf() call. */ extern void mdb_whatis_report_object(mdb_whatis_t *, uintptr_t, uintptr_t, const char *, ...); /* * uint_t mdb_whatis_flags(mdb_whatis_t *w) * * Reports which flags were passed to ::whatis. See the flag definitions * for more details. */ extern uint_t mdb_whatis_flags(mdb_whatis_t *); #define WHATIS_BUFCTL 0x1 /* -b, the caller requested bufctls */ #define WHATIS_IDSPACE 0x2 /* -i, only search identifiers */ #define WHATIS_QUIET 0x4 /* -q, single-line reports only */ #define WHATIS_VERBOSE 0x8 /* -v, report information about the search */ /* * uint_t mdb_whatis_done(mdb_whatis_t *w) * * Returns non-zero if and only if all addresses have been reported, and it * is time to get out of the callback as quickly as possible. */ extern uint_t mdb_whatis_done(mdb_whatis_t *); /* Macro for returning from a walker callback */ #define WHATIS_WALKRET(w) (mdb_whatis_done(w) ? WALK_DONE : WALK_NEXT) typedef int mdb_whatis_cb_f(mdb_whatis_t *, void *); /* * void mdb_whatis_register(const char *name, mdb_whatis_cb_f *cb, void *arg, * uint_t prio, uint_t flags) * * May only be called from _mdb_init() for a module. * * Registers a whatis callback named "name" (which must be an MDB identifier), * with a callback function cb and argument arg. prio determines when the * callback will be invoked, compared to other registered ones, and flags * determines when the callback will be invoked (see below). * * Callbacks with the same priority registered by the same module will be * executed in the order they were added. The callbacks will be invoked as: * * int ret = (*cb)(w, arg) * * Where w is an opaque mdb_whatis_t pointer which is to be passed to the API * routines, above. The function should return 0 unless an error occurs. */ extern void mdb_whatis_register(const char *, mdb_whatis_cb_f *, void *, uint_t, uint_t); #define WHATIS_PRIO_EARLY 10 /* execute before allocator callbacks */ #define WHATIS_PRIO_ALLOCATOR 20 #define WHATIS_PRIO_LATE 30 /* execute after allocator callbacks */ #define WHATIS_REG_ID_ONLY 0x1 /* only invoke for '-i' */ #define WHATIS_REG_NO_ID 0x2 /* don't invoke for '-i' */ #ifdef __cplusplus } #endif #endif /* _MDB_WHATIS_H */ /* * 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. */ #ifndef _MDB_WHATIS_IMPL_H #define _MDB_WHATIS_IMPL_H #include #ifdef __cplusplus extern "C" { #endif #define WHATIS_MS(c, s) (((uint64_t)(c)) << (s)) #define WHATIS_MAGIC /* whatis 0x2009 */ \ (WHATIS_MS('w', 56) | WHATIS_MS('h', 48) | WHATIS_MS('a', 40) | \ WHATIS_MS('t', 32) | WHATIS_MS('i', 24) | WHATIS_MS('s', 16) | \ WHATIS_MS(0x2009, 0)) struct mdb_whatis { uint64_t w_magic; /* just for sanity */ uintptr_t *w_addrs; /* w_naddr sorted addresses */ char *w_addrfound; /* array of w_naddr "found" flags */ size_t w_naddrs; size_t w_match_next; /* next match offset, or 0 if no active match */ uintptr_t w_match_base; /* base of current match */ size_t w_match_size; /* size of current match */ size_t w_found; /* count of set entries in w_addrfound */ uint_t w_flags; /* see WHATIS_* for details */ uint8_t w_done; /* set when no more processing is needed */ }; #define WHATIS_PUBLIC 0x0ffff /* flags which aren't part of the public interface */ #define WHATIS_ALL 0x10000 /* -a, report all matches */ #define WHATIS_PRIO_MIN 99 extern int cmd_whatis(uintptr_t, uint_t, int, const mdb_arg_t *); extern void whatis_help(void); /* built-in callbacks */ extern int whatis_run_mappings(struct mdb_whatis *, void *); /* callback at module unload time */ extern void mdb_whatis_unregister_module(mdb_module_t *); #ifdef __cplusplus } #endif #endif /* _MDB_WHATIS_IMPL_H */ /* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct { uint32_t act_cmd; char *act_name; char *act_type; } arp_cmd_tbl; /* * removed all the ace/arl related stuff. The only thing that remains * is code for dealing with ioctls and printing out arp header that * should probably be moved into the ip/mdb module. */ /* * Print an ARP hardware and protocol address pair; used when printing an ARP * message. */ static void print_arp(char field_id, const uchar_t *buf, const arh_t *arh, uint16_t ptype) { char macstr[ARP_MAX_ADDR_LEN*3]; in_addr_t inaddr; if (arh->arh_hlen == 0) (void) strcpy(macstr, "(none)"); else mdb_mac_addr(buf, arh->arh_hlen, macstr, sizeof (macstr)); mdb_printf("%?s ar$%cha %s\n", "", field_id, macstr); if (arh->arh_plen == 0) { mdb_printf("%?s ar$%cpa (none)\n", "", field_id); } else if (ptype == IP_ARP_PROTO_TYPE) { mdb_printf("%?s ar$%cpa (unknown)\n", "", field_id); } else if (arh->arh_plen == sizeof (in_addr_t)) { (void) memcpy(&inaddr, buf + arh->arh_hlen, sizeof (inaddr)); mdb_printf("%?s ar$%cpa %I\n", "", field_id, inaddr); } else { mdb_printf("%?s ar$%cpa (malformed IP)\n", "", field_id); } } /* * Decode an ARP message and display it. */ /* ARGSUSED2 */ static int arphdr_cmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct { arh_t arh; uchar_t addrs[4 * ARP_MAX_ADDR_LEN]; } arp; size_t blen; uint16_t htype, ptype, op; const char *cp; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("address required to print ARP header\n"); return (DCMD_ERR); } if (mdb_vread(&arp.arh, sizeof (arp.arh), addr) == -1) { mdb_warn("unable to read ARP header at %p", addr); return (DCMD_ERR); } mdb_nhconvert(&htype, arp.arh.arh_hardware, sizeof (htype)); mdb_nhconvert(&ptype, arp.arh.arh_proto, sizeof (ptype)); mdb_nhconvert(&op, arp.arh.arh_operation, sizeof (op)); switch (htype) { case ARPHRD_ETHER: cp = "Ether"; break; case ARPHRD_IEEE802: cp = "IEEE802"; break; case ARPHRD_IB: cp = "InfiniBand"; break; default: cp = "Unknown"; break; } mdb_printf("%?p: ar$hrd %x (%s)\n", addr, htype, cp); mdb_printf("%?s ar$pro %x (%s)\n", "", ptype, ptype == IP_ARP_PROTO_TYPE ? "IP" : "Unknown"); switch (op) { case ARPOP_REQUEST: cp = "ares_op$REQUEST"; break; case ARPOP_REPLY: cp = "ares_op$REPLY"; break; case REVARP_REQUEST: cp = "arev_op$REQUEST"; break; case REVARP_REPLY: cp = "arev_op$REPLY"; break; default: cp = "Unknown"; break; } mdb_printf("%?s ar$op %d (%s)\n", "", op, cp); /* * Note that we go to some length to attempt to print out the fixed * header data before trying to decode the variable-length data. This * is done to maximize the amount of useful information shown when the * buffer is truncated or otherwise corrupt. */ blen = 2 * (arp.arh.arh_hlen + arp.arh.arh_plen); if (mdb_vread(&arp.addrs, blen, addr + sizeof (arp.arh)) == -1) { mdb_warn("unable to read ARP body at %p", addr); return (DCMD_ERR); } print_arp('s', arp.addrs, &arp.arh, ptype); print_arp('t', arp.addrs + arp.arh.arh_hlen + arp.arh.arh_plen, &arp.arh, ptype); return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "arphdr", ":", "display an ARP header", arphdr_cmd, NULL }, { NULL } }; /* Note: ar_t walker is in genunix.c and net.c; generic MI walker */ static const mdb_walker_t walkers[] = { { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } void _mdb_fini(void) { } # # Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. # # 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 # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 # # Module mapfile # # Modules aren't allowed to export any symbols # SYMBOL_SCOPE { local: *; }; # # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2013, 2015 by Delphix. All rights reserved. # Copyright 2023 RackTop Systems, Inc. # Copyright 2023 OmniOS Community Edition (OmniOSce) Association. # Copyright 2025 Oxide Computer Company # # 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 # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 # External interface requirements SYMBOL_SCOPE { global: # Plwp_iter { FLAGS = EXTERN }; # Pmapping_iter { FLAGS = EXTERN }; _mdb_ks_ncpu { FLAGS = EXTERN }; _mdb_ks_pagemask { FLAGS = EXTERN }; _mdb_ks_pageoffset { FLAGS = EXTERN }; _mdb_ks_pageshift { FLAGS = EXTERN }; _mdb_ks_pagesize { FLAGS = EXTERN }; mdb { FLAGS = EXTERN }; mdb_add_walker { FLAGS = EXTERN }; mdb_alloc { FLAGS = EXTERN }; mdb_aread { FLAGS = EXTERN }; mdb_argtoull { FLAGS = EXTERN }; mdb_awrite { FLAGS = EXTERN }; mdb_call_dcmd { FLAGS = EXTERN }; mdb_callback_add { FLAGS = EXTERN }; mdb_callback_remove { FLAGS = EXTERN }; mdb_cpuset_find { FLAGS = EXTERN }; mdb_ctf_array_info { FLAGS = EXTERN }; mdb_ctf_enum_name { FLAGS = EXTERN }; mdb_ctf_lookup_by_addr { FLAGS = EXTERN }; mdb_ctf_lookup_by_name { FLAGS = EXTERN }; mdb_ctf_member_iter { FLAGS = EXTERN }; mdb_ctf_module_lookup { FLAGS = EXTERN }; mdb_ctf_offsetof { FLAGS = EXTERN }; mdb_ctf_offsetof_by_name { FLAGS = EXTERN }; mdb_ctf_sizeof_by_name { FLAGS = EXTERN }; mdb_ctf_readsym { FLAGS = EXTERN }; mdb_ctf_type_cmp { FLAGS = EXTERN }; mdb_ctf_type_invalidate { FLAGS = EXTERN }; mdb_ctf_type_kind { FLAGS = EXTERN }; mdb_ctf_type_name { FLAGS = EXTERN }; mdb_ctf_type_reference { FLAGS = EXTERN }; mdb_ctf_type_resolve { FLAGS = EXTERN }; mdb_ctf_type_size { FLAGS = EXTERN }; mdb_ctf_type_valid { FLAGS = EXTERN }; mdb_ctf_vread { FLAGS = EXTERN }; mdb_ddi_pathname { FLAGS = EXTERN }; mdb_dec_indent { FLAGS = EXTERN }; mdb_devinfo2driver { FLAGS = EXTERN }; mdb_devinfo2statep { FLAGS = EXTERN }; mdb_dlpi_prim { FLAGS = EXTERN }; mdb_dump64 { FLAGS = EXTERN }; mdb_dumpptr { FLAGS = EXTERN }; mdb_eval { FLAGS = EXTERN }; mdb_fdio_create_path { FLAGS = EXTERN }; mdb_fdio_fileno { FLAGS = EXTERN }; mdb_ffs { FLAGS = EXTERN }; mdb_flush { FLAGS = EXTERN }; mdb_fpwalk_dcmd { FLAGS = EXTERN }; mdb_fread { FLAGS = EXTERN }; mdb_free { FLAGS = EXTERN }; mdb_fwrite { FLAGS = EXTERN }; mdb_gelf_create { FLAGS = EXTERN }; mdb_gelf_destroy { FLAGS = EXTERN }; mdb_gelf_sect_by_name { FLAGS = EXTERN }; mdb_gelf_sect_load { FLAGS = EXTERN }; mdb_getareg { FLAGS = EXTERN }; mdb_get_dot { FLAGS = EXTERN }; mdb_get_lbolt { FLAGS = EXTERN }; mdb_get_pipe { FLAGS = EXTERN }; mdb_get_soft_state_byaddr { FLAGS = EXTERN }; mdb_get_soft_state_byname { FLAGS = EXTERN }; mdb_get_state { FLAGS = EXTERN }; mdb_get_xdata { FLAGS = EXTERN }; mdb_gethrtime { FLAGS = EXTERN }; mdb_getopts { FLAGS = EXTERN }; mdb_inc_indent { FLAGS = EXTERN }; mdb_inval_bits { FLAGS = EXTERN }; mdb_io_destroy { FLAGS = EXTERN }; mdb_iob_clrflags { FLAGS = EXTERN }; mdb_iob_getflags { FLAGS = EXTERN }; mdb_iob_resize { FLAGS = EXTERN }; mdb_iob_setflags { FLAGS = EXTERN }; mdb_layered_walk { FLAGS = EXTERN }; mdb_lookup_by_addr { FLAGS = EXTERN }; mdb_lookup_by_name { FLAGS = EXTERN }; mdb_lookup_by_obj { FLAGS = EXTERN }; mdb_mac_addr { FLAGS = EXTERN }; mdb_major_to_name { FLAGS = EXTERN }; mdb_mblk_count { FLAGS = EXTERN }; mdb_memio_create { FLAGS = EXTERN }; mdb_name_to_major { FLAGS = EXTERN }; mdb_nhconvert { FLAGS = EXTERN }; mdb_nicenum { FLAGS = EXTERN }; mdb_nicetime { FLAGS = EXTERN }; mdb_object_iter { FLAGS = EXTERN }; mdb_one_bit { FLAGS = EXTERN }; mdb_page2pfn { FLAGS = EXTERN }; mdb_page_lookup { FLAGS = EXTERN }; mdb_pfn2page { FLAGS = EXTERN }; mdb_pid2proc { FLAGS = EXTERN }; mdb_pread { FLAGS = EXTERN }; mdb_printf { FLAGS = EXTERN }; mdb_snprintfrac { FLAGS = EXTERN }; mdb_prop_kernel { FLAGS = EXTERN }; mdb_prop_postmortem { FLAGS = EXTERN }; mdb_pwalk { FLAGS = EXTERN }; mdb_pwalk_dcmd { FLAGS = EXTERN }; mdb_pwrite { FLAGS = EXTERN }; mdb_qinfo { FLAGS = EXTERN }; mdb_qname { FLAGS = EXTERN }; mdb_qops_install { FLAGS = EXTERN }; mdb_qops_remove { FLAGS = EXTERN }; mdb_qrnext_default { FLAGS = EXTERN }; mdb_qwnext { FLAGS = EXTERN }; mdb_qwnext_default { FLAGS = EXTERN }; mdb_read_refstr { FLAGS = EXTERN }; mdb_readstr { FLAGS = EXTERN }; mdb_readsym { FLAGS = EXTERN }; mdb_readvar { FLAGS = EXTERN }; mdb_remove_walker { FLAGS = EXTERN }; mdb_set_dot { FLAGS = EXTERN }; mdb_set_pipe { FLAGS = EXTERN }; mdb_snprintf { FLAGS = EXTERN }; mdb_strtoull { FLAGS = EXTERN }; mdb_stack_frame { FLAGS = EXTERN }; mdb_stack_frame_arglim { FLAGS = EXTERN }; mdb_stack_frame_flags_set { FLAGS = EXTERN }; mdb_stack_frame_init { FLAGS = EXTERN }; mdb_symbol_iter { FLAGS = EXTERN }; mdb_tgt_notsup { FLAGS = EXTERN }; mdb_thread_name { FLAGS = EXTERN }; mdb_vnode2path { FLAGS = EXTERN }; mdb_vread { FLAGS = EXTERN }; mdb_vtype2chr { FLAGS = EXTERN }; mdb_vwrite { FLAGS = EXTERN }; mdb_walk { FLAGS = EXTERN }; mdb_walk_dcmd { FLAGS = EXTERN }; mdb_warn { FLAGS = EXTERN }; mdb_whatis_done { FLAGS = EXTERN }; mdb_whatis_flags { FLAGS = EXTERN }; mdb_whatis_match { FLAGS = EXTERN }; mdb_whatis_overlaps { FLAGS = EXTERN }; mdb_whatis_register { FLAGS = EXTERN }; mdb_whatis_report_address { FLAGS = EXTERN }; mdb_whatis_report_object { FLAGS = EXTERN }; mdb_writestr { FLAGS = EXTERN }; mdb_writesym { FLAGS = EXTERN }; mdb_writevar { FLAGS = EXTERN }; mdb_zalloc { FLAGS = EXTERN }; }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2019 Joyent, Inc. */ #include #include #include #define KCPC_HASH_BUCKETS (1l << KCPC_LOG2_HASH_BUCKETS) /* * Assume 64-bit kernel address max is 100000000000 - 1. */ #ifdef _LP64 #define ADDR_WIDTH 11 #else #define ADDR_WIDTH 8 #endif struct cpc_ctx_aux { uintptr_t cca_hash[KCPC_HASH_BUCKETS]; int cca_bucket; }; /*ARGSUSED*/ static int cpc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kcpc_ctx_t ctx; kcpc_set_t set; kcpc_request_t *reqs; uint64_t *data; kcpc_attr_t *attr; int i; int j; uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { /* * We weren't given the address of any specific cpc ctx, so * invoke the walker to find them all. */ mdb_walk_dcmd("cpc_ctx", "cpc", argc, argv); return (DCMD_OK); } if (mdb_vread(&ctx, sizeof (ctx), addr) == -1) { mdb_warn("failed to read kcpc_ctx_t at %p", addr); return (DCMD_ABORT); } if (mdb_vread(&set, sizeof (set), (uintptr_t)ctx.kc_set) == -1) { mdb_warn("failed to read kcpc_set_t at %p", ctx.kc_set); return (DCMD_ABORT); } reqs = mdb_alloc(set.ks_nreqs * sizeof (*reqs), UM_GC); data = mdb_alloc(set.ks_nreqs * sizeof (*data), UM_GC); if (mdb_vread(reqs, set.ks_nreqs * sizeof (*reqs), (uintptr_t)set.ks_req) == -1) { mdb_warn("failed to read requests at %p", set.ks_req); return (DCMD_ABORT); } if (mdb_vread(data, set.ks_nreqs * sizeof (*data), (uintptr_t)set.ks_data) == -1) { mdb_warn("failed to read set data at %p", set.ks_data); return (DCMD_ABORT); } if (DCMD_HDRSPEC(flags)) mdb_printf("N PIC NDX %16s FLG %16s %*s EVENT\n", "VAL", "PRESET", ADDR_WIDTH, "CFG"); mdb_printf("-----------------------------------------------------------" "---------------------\n"); if (opt_v) mdb_printf("Set: %p\t%d requests. Flags = %x\n", ctx.kc_set, set.ks_nreqs, set.ks_flags); for (i = 0; i < set.ks_nreqs; i++) { mdb_printf("%d %3d %3d %16llx %1s%1s%1s %16llx %8p %s\n", i, reqs[i].kr_picnum, reqs[i].kr_index, data[reqs[i].kr_index], (reqs[i].kr_flags & CPC_OVF_NOTIFY_EMT) ? "O" : "", (reqs[i].kr_flags & CPC_COUNT_USER) ? "U" : "", (reqs[i].kr_flags & CPC_COUNT_SYSTEM) ? "S" : "", reqs[i].kr_preset, reqs[i].kr_config, reqs[i].kr_event); if (opt_v == 0) continue; if (reqs[i].kr_nattrs > 0) { attr = mdb_alloc(reqs[i].kr_nattrs * sizeof (*attr), UM_GC); if (mdb_vread(attr, reqs[i].kr_nattrs * sizeof (*attr), (uintptr_t)reqs[i].kr_attr) == -1) { mdb_warn("failed to read attributes at %p", reqs[i].kr_attr); return (DCMD_ABORT); } for (j = 0; j < reqs[i].kr_nattrs; j++) mdb_printf("\t%s = %llx", attr[j].ka_name, attr[j].ka_val); mdb_printf("\n"); } } return (DCMD_OK); } static void cpc_help(void) { mdb_printf("Displays the contents of the CPC context at the supplied " "address. If no address is given, displays contents of all active " "CPC contexts.\n"); mdb_printf("Flag codes: \n" "O = overflow notify U = count user events " "S = count system events\n"); } /* * Initialize the global walk by grabbing the hash table in the * cpc module. */ static int cpc_ctx_walk_init(mdb_walk_state_t *wsp) { struct cpc_ctx_aux *cca; if (wsp->walk_addr != 0) { mdb_warn("only global cpc_ctx walk supported\n"); return (WALK_ERR); } cca = mdb_zalloc(sizeof (*cca), UM_SLEEP); if (mdb_readsym(&cca->cca_hash, sizeof (cca->cca_hash), "kcpc_ctx_list") == -1) { mdb_warn("cannot read cpc_ctx hash table"); mdb_free(cca, sizeof (*cca)); return (WALK_ERR); } wsp->walk_data = cca; wsp->walk_addr = 0; return (WALK_NEXT); } static int cpc_ctx_walk_step(mdb_walk_state_t *wsp) { int status; kcpc_ctx_t ctx; struct cpc_ctx_aux *cca = wsp->walk_data; while (wsp->walk_addr == 0) { if (cca->cca_bucket == KCPC_HASH_BUCKETS) return (WALK_DONE); wsp->walk_addr = cca->cca_hash[cca->cca_bucket++]; } if (mdb_vread(&ctx, sizeof (ctx), wsp->walk_addr) == -1) { mdb_warn("failed to read cpc_ctx at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &ctx, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)ctx.kc_next; return (status); } static void cpc_ctx_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct cpc_ctx_aux)); } static const mdb_walker_t walkers[] = { { "cpc_ctx", "walk global list of cpc contexts", cpc_ctx_walk_init, cpc_ctx_walk_step, cpc_ctx_walk_fini }, { NULL } }; static const mdb_dcmd_t dcmds[] = { { "cpc", "?[-v]", "Display contents of CPC context", cpc, cpc_help }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * mdb dcmds for selected structures from * usr/src/uts/common/sys/crypto/common.h */ #include #include #include #include #include #include #include #include "crypto_cmds.h" /*ARGSUSED*/ int crypto_mechanism(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { crypto_mechanism_t mch; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&mch, sizeof (crypto_mechanism_t), addr) == -1) { mdb_warn("cannot read %p", addr); return (DCMD_ERR); } /* XXX a future RFE will interpret cm_type */ mdb_printf("cm_type\t%ll#x\n", mch.cm_type); mdb_printf("cm_param\t%p\n", mch.cm_param); mdb_printf("cm_param_len\t%u\n", mch.cm_param_len); return (DCMD_OK); } /*ARGSUSED*/ static void iovec_prt(iovec_t *addr) { mdb_printf("iov_base\t%p\n", addr->iov_base); mdb_printf("iov_len\t\t%d\n", addr->iov_len); } /*ARGSUSED*/ static void uio_prt(uio_t *addr) { char *segstrings[] = { "UIO_USERSPACE", "UIO_SYSSPACE", "UIO_USERISPACE" }; iovec_t iov; uio_t uio; int i; mdb_printf("uio\t%p\n", addr); if (mdb_vread(&uio, sizeof (uio_t), (uintptr_t)addr) == -1) { mdb_warn("uio_prt: could not read uio"); } mdb_inc_indent(4); for (i = 0; i < uio.uio_iovcnt; i++) { if (mdb_vread(&iov, sizeof (iovec_t), (uintptr_t)(uio.uio_iov +i)) == -1) { mdb_printf("uio_iov\t?????"); mdb_warn("uio_prt: could not read uio_iov[%s]", i); } else iovec_prt(&iov); } mdb_dec_indent(4); mdb_printf("uio_iovcnt\t%d\n", uio.uio_iovcnt); mdb_printf("uio_offset\t%lld\n", uio.uio_offset); mdb_printf("uio_segflg\t%s", segstrings[uio.uio_segflg]); mdb_printf("uio_fmode\t0%o", (int)uio.uio_fmode); mdb_printf("uio_limit\t%lld", uio.uio_limit); mdb_printf("uio_resid\t%ld", uio.uio_resid); } static char *cdstrings[] = { "INVALID FORMAT", "CRYPTO_DATA_RAW", "CRYPTO_DATA_UIO", "CRYPTO_DATA_MBLK" }; /* * Routine to print either of two structrually identical sub-structures -- * with different naming conventions. Might be changed if we decide * to merge the two. They are the cdu union from crypto_data_t and * the one from crypto_dual_data_t. */ typedef union crypto_data_union { iovec_t cdu_raw; /* Raw format */ uio_t *cdu_uio; /* uio scatter-gather format */ mblk_t *cdu_mp; /* The mblk chain */ } crypto_data_union_t; /*ARGSUSED*/ static void prt_cdu(crypto_data_union_t *cdu, int format, const char *prefix) { switch (format) { case CRYPTO_DATA_RAW: mdb_printf("%s_raw:\n", prefix); mdb_inc_indent(4); iovec_prt(&cdu->cdu_raw); mdb_dec_indent(4); break; case CRYPTO_DATA_UIO: mdb_printf("%s_uio:\n", prefix); mdb_inc_indent(4); uio_prt(cdu->cdu_uio); mdb_dec_indent(4); break; case CRYPTO_DATA_MBLK: mdb_printf("%s_mp:\t\t%p\n", prefix, cdu->cdu_mp); break; default: mdb_printf("cm_format\t??????\n"); break; } } /*ARGSUSED*/ int crypto_data(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { crypto_data_t data; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&data, sizeof (crypto_data_t), addr) == -1) { mdb_warn("cannot read %p", addr); return (DCMD_ERR); } if ((data.cd_format >= CRYPTO_DATA_RAW) && (data.cd_format <= CRYPTO_DATA_MBLK)) mdb_printf("cm_format\t%s\n", cdstrings[data.cd_format]); else mdb_printf("bad cm_format\t%d\n", data.cd_format); mdb_printf("cm_offset\t%ld\n", data.cd_offset); mdb_printf("cm_length\t%ld\n", data.cd_length); mdb_printf("cm_miscdata\t%p\n", data.cd_miscdata); mdb_inc_indent(4); prt_cdu((crypto_data_union_t *)&data.cdu, data.cd_format, "cdu"); mdb_dec_indent(4); return (DCMD_OK); } /*ARGSUSED*/ int crypto_dual_data(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { crypto_dual_data_t ddata; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&ddata, sizeof (crypto_dual_data_t), addr) == -1) { mdb_warn("cannot read %p", addr); return (DCMD_ERR); } if ((ddata.dd_format > CRYPTO_DATA_RAW) && (ddata.dd_format <= CRYPTO_DATA_MBLK)) mdb_printf("dd_format\t%s\n", cdstrings[ddata.dd_format]); else mdb_printf("bad dd_format\t%d\n", ddata.dd_format); mdb_printf("dd_offset1\t%ld\n", ddata.dd_offset1); mdb_printf("dd_len1\t%ld\n", ddata.dd_len1); mdb_printf("dd_offset2\t%ld\n", ddata.dd_offset2); mdb_printf("dd_len2\t%ld\n", ddata.dd_len2); mdb_printf("dd_miscdata\t%p\n", ddata.dd_miscdata); mdb_printf("cdu:\n"); mdb_inc_indent(4); prt_cdu((crypto_data_union_t *)&ddata.dd_data.cdu, ddata.dd_format, "ddu"); mdb_dec_indent(4); return (DCMD_OK); } /*ARGSUSED*/ int crypto_key(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { crypto_key_t key; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&key, sizeof (crypto_key_t), addr) == -1) { mdb_warn("cannot read %p", addr); return (DCMD_ERR); } switch (key.ck_format) { case CRYPTO_KEY_RAW: mdb_printf("ck_format:\tCRYPTO_KEY_RAW\n"); mdb_printf( "cku_data.cku_key_value.cku_data.cku_v_length:\t%d\n", key.cku_data.cku_key_value.cku_v_length); mdb_printf("cku_data.cku_key_value.cku_v_data:\t%p\n", key.cku_data.cku_key_value.cku_v_data); break; case CRYPTO_KEY_REFERENCE: mdb_printf("ck_format:\tCRYPTO_KEY_REFERENCE\n"); mdb_printf("cku_data.cku_key_id:\t%u\n", key.cku_data.cku_key_id); break; case CRYPTO_KEY_ATTR_LIST: mdb_printf("ck_format:\tCRYPTO_KEY_ATTR_LIST\n"); mdb_printf("cku_data.cku_key_attrs.cku_a_count:\t%u\n", key.cku_data.cku_key_attrs.cku_a_count); mdb_printf("cku_data.cku_key_attrs.cku_o_oattr:\t%p\n", key.cku_data.cku_key_attrs.cku_a_oattr); break; default: mdb_printf("ck_format:\t\t?????\n"); break; } return (DCMD_OK); } /* * 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. */ #ifndef _CRYPTO_CMDS_H #define _CRYPTO_CMDS_H #ifdef __cplusplus extern "C" { #endif extern int crypto_provider_ext_info(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int crypto_mech_info(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int crypto_mechanism(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int crypto_data(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int crypto_dual_data(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int crypto_key(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int kcf_provider_desc(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int prov_tab(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int policy_tab(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int kcf_areq_node(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int kcf_global_swq(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int kcf_reqid_table_dcmd(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int crypto_find_reqid(uintptr_t addr, uint_t flags, int argc, \ const mdb_arg_t *argv); extern int areq_first_walk_init(mdb_walk_state_t *); extern int an_idnext_walk_init(mdb_walk_state_t *); extern int an_idprev_walk_init(mdb_walk_state_t *); extern int an_ctxchain_walk_init(mdb_walk_state_t *); extern int areq_last_walk_init(mdb_walk_state_t *); extern int an_next_walk_step(mdb_walk_state_t *); extern int an_idnext_walk_step(mdb_walk_state_t *); extern int an_idprev_walk_step(mdb_walk_state_t *); extern int an_ctxchain_walk_step(mdb_walk_state_t *); extern void areq_walk_fini(mdb_walk_state_t *); extern int an_prev_walk_step(mdb_walk_state_t *); extern int reqid_table_walk_init(mdb_walk_state_t *); extern int reqid_table_walk_step(mdb_walk_state_t *); extern void reqid_table_walk_fini(mdb_walk_state_t *); extern int soft_conf_walk_init(mdb_walk_state_t *); extern int soft_conf_walk_step(mdb_walk_state_t *); extern void soft_conf_walk_fini(mdb_walk_state_t *); extern int kcf_soft_conf_entry(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int kcf_policy_desc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); #ifdef __cplusplus } #endif #endif /* _CRYPTO_CMDS_H */ /* * 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 (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * mdb dcmds for selected structures from * usr/src/uts/common/sys/crypto/impl.h */ #include #include #include #include #include #include #include #include "crypto_cmds.h" static const char *prov_states[] = { "none", "KCF_PROV_ALLOCATED", "KCF_PROV_UNVERIFIED", "KCF_PROV_VERIFICATION_FAILED", "KCF_PROV_READY", "KCF_PROV_BUSY", "KCF_PROV_FAILED", "KCF_PROV_DISABLED", "KCF_PROV_UNREGISTERING", "KCF_PROV_UNREGISTERED" }; /*ARGSUSED*/ int kcf_provider_desc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kcf_provider_desc_t desc; kcf_provider_desc_t *ptr; char string[MAXNAMELEN + 1]; int i, j; crypto_mech_info_t *mech_pointer; kcf_prov_cpu_t stats; uint64_t dtotal, ftotal, btotal; int holdcnt, jobcnt; if ((flags & DCMD_ADDRSPEC) != DCMD_ADDRSPEC) return (DCMD_USAGE); ptr = (kcf_provider_desc_t *)addr; #ifdef DEBUG mdb_printf("DEBUG: reading kcf_provider_desc at %p\n", ptr); #endif if (mdb_vread(&desc, sizeof (kcf_provider_desc_t), (uintptr_t)ptr) == -1) { mdb_warn("cannot read at address %p", (uintptr_t)ptr); return (DCMD_ERR); } mdb_printf("%kcf_provider_desc at %p%\n", ptr); switch (desc.pd_prov_type) { case CRYPTO_HW_PROVIDER: mdb_printf("pd_prov_type:\t\tCRYPTO_HW_PROVIDER\n"); break; case CRYPTO_SW_PROVIDER: mdb_printf("pd_prov_type:\t\tCRYPTO_SW_PROVIDER\n"); break; case CRYPTO_LOGICAL_PROVIDER: mdb_printf("pd_prov_type:\t\tCRYPTO_LOGICAL_PROVIDER\n"); break; default: mdb_printf("bad pd_prov_type:\t%d\n", desc.pd_prov_type); } mdb_printf("pd_prov_id:\t\t%u\n", desc.pd_prov_id); if (desc.pd_description == NULL) mdb_printf("pd_description:\t\tNULL\n"); else if (mdb_readstr(string, MAXNAMELEN + 1, (uintptr_t)desc.pd_description) == -1) { mdb_warn("cannot read %p", desc.pd_description); } else mdb_printf("pd_description:\t\t%s\n", string); mdb_printf("pd_sid:\t\t\t%u\n", desc.pd_sid); mdb_printf("pd_taskq:\t\t%p\n", desc.pd_taskq); mdb_printf("pd_nbins:\t\t%u\n", desc.pd_nbins); mdb_printf("pd_percpu_bins:\t\t%p\n", desc.pd_percpu_bins); dtotal = ftotal = btotal = 0; holdcnt = jobcnt = 0; for (i = 0; i < desc.pd_nbins; i++) { if (mdb_vread(&stats, sizeof (kcf_prov_cpu_t), (uintptr_t)(desc.pd_percpu_bins + i)) == -1) { mdb_warn("cannot read addr %p", desc.pd_percpu_bins + i); return (DCMD_ERR); } holdcnt += stats.kp_holdcnt; jobcnt += stats.kp_jobcnt; dtotal += stats.kp_ndispatches; ftotal += stats.kp_nfails; btotal += stats.kp_nbusy_rval; } mdb_inc_indent(4); mdb_printf("total kp_holdcnt:\t\t%d\n", holdcnt); mdb_printf("total kp_jobcnt:\t\t%u\n", jobcnt); mdb_printf("total kp_ndispatches:\t%llu\n", dtotal); mdb_printf("total kp_nfails:\t\t%llu\n", ftotal); mdb_printf("total kp_nbusy_rval:\t%llu\n", btotal); mdb_dec_indent(4); mdb_printf("pd_prov_handle:\t\t%p\n", desc.pd_prov_handle); mdb_printf("pd_kcf_prov_handle:\t%u\n", desc.pd_kcf_prov_handle); mdb_printf("pd_ops_vector:\t\t%p\n", desc.pd_ops_vector); mdb_printf("pd_mech_list_count:\t%u\n", desc.pd_mech_list_count); /* mechanisms */ mdb_inc_indent(4); for (i = 0; i < desc.pd_mech_list_count; i++) { mech_pointer = desc.pd_mechanisms + i; mdb_call_dcmd("crypto_mech_info", (uintptr_t)mech_pointer, DCMD_ADDRSPEC, 0, NULL); } mdb_dec_indent(4); mdb_printf("pd_mech_indx:\n"); mdb_inc_indent(8); for (i = 0; i < KCF_OPS_CLASSSIZE; i++) { for (j = 0; j < KCF_MAXMECHTAB; j++) { if (desc.pd_mech_indx[i][j] == KCF_INVALID_INDX) mdb_printf("N "); else mdb_printf("%u ", desc.pd_mech_indx[i][j]); } mdb_printf("\n"); } mdb_dec_indent(8); if (desc.pd_name == NULL) mdb_printf("pd_name:\t\t NULL\n"); else if (mdb_readstr(string, MAXNAMELEN + 1, (uintptr_t)desc.pd_name) == -1) mdb_warn("could not read pd_name from %X\n", desc.pd_name); else mdb_printf("pd_name:\t\t%s\n", string); mdb_printf("pd_instance:\t\t%u\n", desc.pd_instance); mdb_printf("pd_module_id:\t\t%d\n", desc.pd_module_id); mdb_printf("pd_mctlp:\t\t%p\n", desc.pd_mctlp); mdb_printf("pd_lock:\t\t%p\n", desc.pd_lock); if (desc.pd_state < KCF_PROV_ALLOCATED || desc.pd_state > KCF_PROV_UNREGISTERED) mdb_printf("pd_state is invalid:\t%d\n", desc.pd_state); else mdb_printf("pd_state:\t%s\n", prov_states[desc.pd_state]); mdb_printf("pd_provider_list:\t%p\n", desc.pd_provider_list); mdb_printf("pd_resume_cv:\t\t%hd\n", desc.pd_resume_cv._opaque); mdb_printf("pd_flags:\t\t%s %s %s %s %s\n", (desc.pd_flags & CRYPTO_HIDE_PROVIDER) ? "CRYPTO_HIDE_PROVIDER" : " ", (desc.pd_flags & CRYPTO_HASH_NO_UPDATE) ? "CRYPTO_HASH_NO_UPDATE" : " ", (desc.pd_flags & CRYPTO_HMAC_NO_UPDATE) ? "CRYPTO_HMAC_NO_UPDATE" : " ", (desc.pd_flags & CRYPTO_SYNCHRONOUS) ? "CRYPTO_SYNCHRONOUS" : " ", (desc.pd_flags & KCF_LPROV_MEMBER) ? "KCF_LPROV_MEMBER" : " "); if (desc.pd_flags & CRYPTO_HASH_NO_UPDATE) mdb_printf("pd_hash_limit:\t\t%u\n", desc.pd_hash_limit); if (desc.pd_flags & CRYPTO_HMAC_NO_UPDATE) mdb_printf("pd_hmac_limit:\t\t%u\n", desc.pd_hmac_limit); mdb_printf("pd_kstat:\t\t%p\n", desc.pd_kstat); return (DCMD_OK); } #define GOT_NONE (-2) /*ARGSUSED*/ int prov_tab(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kcf_provider_desc_t **tab; kcf_provider_desc_t desc; kcf_provider_desc_t *ptr; uint_t prov_tab_max; int i; int gotzero = GOT_NONE; char string[MAXNAMELEN + 1]; if ((flags & DCMD_ADDRSPEC) == DCMD_ADDRSPEC) { return (DCMD_USAGE); } else if (mdb_readsym(&ptr, sizeof (void *), "prov_tab") == -1) { mdb_warn("cannot read prov_tab"); return (DCMD_ERR); } else if (mdb_readvar(&prov_tab_max, "prov_tab_max") == -1) { mdb_warn("cannot read prov_tab_max"); return (DCMD_ERR); } mdb_printf("%prov_tab = %p%\n", ptr); tab = mdb_zalloc(prov_tab_max * sizeof (kcf_provider_desc_t *), UM_SLEEP| UM_GC); #ifdef DEBUG mdb_printf("DEBUG: tab = %p, prov_tab_max = %d\n", tab, prov_tab_max); #endif if (mdb_vread(tab, prov_tab_max * sizeof (kcf_provider_desc_t *), (uintptr_t)ptr) == -1) { mdb_warn("cannot read prov_tab"); return (DCMD_ERR); } #ifdef DEBUG mdb_printf("DEBUG: got past mdb_vread of tab\n"); mdb_printf("DEBUG: *tab = %p\n", *tab); #endif for (i = 0; i < prov_tab_max; i++) { /* save space, only print range for long list of nulls */ if (tab[i] == NULL) { if (gotzero == GOT_NONE) { mdb_printf("prov_tab[%d", i); gotzero = i; } } else { /* first non-null in awhile, print index of prev null */ if (gotzero != GOT_NONE) { if (gotzero == (i - 1)) mdb_printf("] = NULL\n", i - 1); else mdb_printf(" - %d] = NULL\n", i - 1); gotzero = GOT_NONE; } /* interesting value, print it */ mdb_printf("prov_tab[%d] = %p ", i, tab[i]); if (mdb_vread(&desc, sizeof (kcf_provider_desc_t), (uintptr_t)tab[i]) == -1) { mdb_warn("cannot read at address %p", (uintptr_t)tab[i]); return (DCMD_ERR); } (void) mdb_readstr(string, MAXNAMELEN + 1, (uintptr_t)desc.pd_name); mdb_printf("(%s\t%s)\n", string, prov_states[desc.pd_state]); } } /* if we've printed the first of many nulls but left the brace open */ if ((i > 0) && (tab[i-1] == NULL)) { if (gotzero == GOT_NONE) mdb_printf("] = NULL\n"); else mdb_printf(" - %d] = NULL\n", i - 1); } return (DCMD_OK); } /*ARGSUSED*/ int policy_tab(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kcf_policy_desc_t **tab; kcf_policy_desc_t *ptr; uint_t policy_tab_max; int num, i; int gotzero = GOT_NONE; if ((flags & DCMD_ADDRSPEC) == DCMD_ADDRSPEC) { return (DCMD_USAGE); } else if (mdb_readsym(&ptr, sizeof (void *), "policy_tab") == -1) { mdb_warn("cannot read policy_tab"); return (DCMD_ERR); } else if (mdb_readvar(&policy_tab_max, "policy_tab_max") == -1) { mdb_warn("cannot read policy_tab_max"); return (DCMD_ERR); } /* get the current number of descriptors in the table */ if (mdb_readvar(&num, "policy_tab_num") == -1) { mdb_warn("cannot read policy_tab_num"); return (DCMD_ERR); } mdb_printf("%policy_tab = %p% \tpolicy_tab_num = %d\n", ptr, num); tab = mdb_zalloc(policy_tab_max * sizeof (kcf_policy_desc_t *), UM_SLEEP| UM_GC); if (mdb_vread(tab, policy_tab_max * sizeof (kcf_policy_desc_t *), (uintptr_t)ptr) == -1) { mdb_warn("cannot read policy_tab"); return (DCMD_ERR); } #ifdef DEBUG mdb_printf("DEBUG: got past mdb_vread of tab\n"); mdb_printf("DEBUG: *tab = %p\n", *tab); #endif for (i = 0; i < policy_tab_max; i++) { /* save space, only print range for long list of nulls */ if (tab[i] == NULL) { if (gotzero == GOT_NONE) { mdb_printf("policy_tab[%d", i); gotzero = i; } } else { /* first non-null in awhile, print index of prev null */ if (gotzero != GOT_NONE) { if (gotzero == (i - 1)) mdb_printf("] = NULL\n", i - 1); else mdb_printf(" - %d] = NULL\n", i - 1); gotzero = GOT_NONE; } /* interesting value, print it */ mdb_printf("policy_tab[%d] = %p\n", i, tab[i]); } } /* if we've printed the first of many nulls but left the brace open */ if ((i > 0) && (tab[i-1] == NULL)) { if (gotzero == GOT_NONE) mdb_printf("] = NULL\n"); else mdb_printf(" - %d] = NULL\n", i - 1); } return (DCMD_OK); } static void prt_mechs(int count, crypto_mech_name_t *mechs) { int i; char name[CRYPTO_MAX_MECH_NAME + 1]; char name2[CRYPTO_MAX_MECH_NAME + 3]; for (i = 0; i < count; i++) { if (mdb_readstr(name, CRYPTO_MAX_MECH_NAME, (uintptr_t)((char *)mechs)) == -1) continue; /* put in quotes */ (void) mdb_snprintf(name2, sizeof (name2), "\"%s\"", name); /* yes, length is 32, but then it will wrap */ /* this shorter size formats nicely for most cases */ mdb_printf("mechs[%d]=%-28s", i, name2); mdb_printf("%s", i%2 ? "\n" : " "); /* 2-columns */ mechs++; } } /* ARGSUSED2 */ static int prt_soft_conf_entry(kcf_soft_conf_entry_t *addr, kcf_soft_conf_entry_t *entry, void *cbdata) { char name[MAXNAMELEN + 1]; mdb_printf("\n%kcf_soft_conf_entry_t at %p:%\n", addr); mdb_printf("ce_next: %p", entry->ce_next); if (entry->ce_name == NULL) mdb_printf("\tce_name: NULL\n"); else if (mdb_readstr(name, MAXNAMELEN, (uintptr_t)entry->ce_name) == -1) mdb_printf("could not read ce_name from %p\n", entry->ce_name); else mdb_printf("\tce_name: %s\n", name); mdb_printf("ce_count: %d\n", entry->ce_count); prt_mechs(entry->ce_count, entry->ce_mechs); return (WALK_NEXT); } int soft_conf_walk_init(mdb_walk_state_t *wsp) { uintptr_t *soft; if (mdb_readsym(&soft, sizeof (kcf_soft_conf_entry_t *), "soft_config_list") == -1) { mdb_warn("failed to find 'soft_config_list'"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)soft; wsp->walk_data = mdb_alloc(sizeof (kcf_soft_conf_entry_t), UM_SLEEP); wsp->walk_callback = (mdb_walk_cb_t)(uintptr_t)prt_soft_conf_entry; return (WALK_NEXT); } /* * At each step, read a kcf_soft_conf_entry_t into our private storage, then * invoke the callback function. We terminate when we reach a NULL ce_next * pointer. */ int soft_conf_walk_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) /* then we're done */ return (WALK_DONE); #ifdef DEBUG else mdb_printf("DEBUG: wsp->walk_addr == %p\n", wsp->walk_addr); #endif if (mdb_vread(wsp->walk_data, sizeof (kcf_soft_conf_entry_t), wsp->walk_addr) == -1) { mdb_warn("failed to read kcf_soft_conf_entry at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((kcf_soft_conf_entry_t *)wsp->walk_data)->ce_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. Since we * dynamically allocated a kcf_soft_conf_entry_t in soft_conf_walk_init, * we must free it now. */ void soft_conf_walk_fini(mdb_walk_state_t *wsp) { #ifdef DEBUG mdb_printf("...end of kcf_soft_conf_entry walk\n"); #endif mdb_free(wsp->walk_data, sizeof (kcf_soft_conf_entry_t)); } /* ARGSUSED2 */ int kcf_soft_conf_entry(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kcf_soft_conf_entry_t entry; kcf_soft_conf_entry_t *ptr; if ((flags & DCMD_ADDRSPEC) == DCMD_ADDRSPEC) { if (addr == 0) /* not allowed with DCMD_ADDRSPEC */ return (DCMD_USAGE); else ptr = (kcf_soft_conf_entry_t *)addr; } else if (mdb_readsym(&ptr, sizeof (void *), "soft_config_list") == -1) { mdb_warn("cannot read soft_config_list"); return (DCMD_ERR); } else mdb_printf("soft_config_list = %p\n", ptr); if (ptr == NULL) return (DCMD_OK); if (mdb_vread(&entry, sizeof (kcf_soft_conf_entry_t), (uintptr_t)ptr) == -1) { mdb_warn("cannot read at address %p", (uintptr_t)ptr); return (DCMD_ERR); } /* this could change in the future to have more than one ret val */ if (prt_soft_conf_entry(ptr, &entry, NULL) != WALK_ERR) return (DCMD_OK); return (DCMD_ERR); } /* ARGSUSED1 */ int kcf_policy_desc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kcf_policy_desc_t desc; char name[MAXNAMELEN + 1]; if ((flags & DCMD_ADDRSPEC) != DCMD_ADDRSPEC) return (DCMD_USAGE); if (mdb_vread(&desc, sizeof (kcf_policy_desc_t), (uintptr_t)addr) == -1) { mdb_warn("Could not read kcf_policy_desc_t at %p\n", addr); return (DCMD_ERR); } mdb_printf("pd_prov_type: %s", desc.pd_prov_type == CRYPTO_HW_PROVIDER ? "CRYPTO_HW_PROVIDER" : "CRYPTO_SW_PROVIDER"); if (desc.pd_name == NULL) mdb_printf("\tpd_name: NULL\n"); else if (mdb_readstr(name, MAXNAMELEN, (uintptr_t)desc.pd_name) == -1) mdb_printf("could not read pd_name from %p\n", desc.pd_name); else mdb_printf("\tpd_name: %s\n", name); mdb_printf("pd_instance: %d ", desc.pd_instance); mdb_printf("\t\tpd_refcnt: %d\n", desc.pd_refcnt); mdb_printf("pd_mutex: %p", desc.pd_mutex); mdb_printf("\t\tpd_disabled_count: %d", desc.pd_disabled_count); mdb_printf("\npd_disabled_mechs:\n"); mdb_inc_indent(4); prt_mechs(desc.pd_disabled_count, desc.pd_disabled_mechs); mdb_dec_indent(4); return (DCMD_OK); } /* * 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 (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include "crypto_cmds.h" static void prt_an_state(int state) { switch (state) { case REQ_ALLOCATED: mdb_printf("REQ_ALLOCATED "); break; case REQ_WAITING: mdb_printf("REQ_WAITING "); break; case REQ_INPROGRESS: mdb_printf("REQ_INPROGRESS "); break; case REQ_DONE: mdb_printf("REQ_DONE "); break; case REQ_CANCELED: mdb_printf("REQ_CANCELED "); break; default: mdb_printf("? %d ?? ", state); break; } } static const mdb_bitmask_t call_flags[] = { { "CRYPTO_ALWAYS_QUEUE", CRYPTO_ALWAYS_QUEUE, CRYPTO_ALWAYS_QUEUE }, { "CRYPTO_NOTIFY_OPDONE", CRYPTO_NOTIFY_OPDONE, CRYPTO_NOTIFY_OPDONE }, { "CRYPTO_SKIP_REQID", CRYPTO_SKIP_REQID, CRYPTO_SKIP_REQID }, { NULL, 0, 0 } }; /*ARGSUSED*/ static int kcf_areq_node_simple(kcf_areq_node_t *areqn) { mdb_printf("\nan_type: "); if (areqn->an_type != CRYPTO_ASYNCH) mdb_printf("%-8d ", areqn->an_type); else mdb_printf("CRYPTO_ASYNCH"); mdb_printf("\nan_state: "); prt_an_state(areqn->an_state); mdb_printf("\nan_context: %-16p\t", areqn->an_context); mdb_printf("an_is_my_turn: %s\t ", areqn->an_is_my_turn == B_FALSE ? "B_FALSE" : "B_TRUE"); mdb_printf("\ncr_reqid: %lx\n", areqn->an_reqarg.cr_reqid); return (DCMD_OK); } /* * Verbose print of kcf_areq_node_t */ static int v_kcf_areq_node(kcf_areq_node_t *areqn) { /* contents only -- the address is printed elsewhere */ /* First column */ mdb_printf("\n%16s: ", "an_type"); if (areqn->an_type != CRYPTO_ASYNCH) mdb_printf("%-8d ", areqn->an_type); else mdb_printf("CRYPTO_ASYNCH"); /* Second column */ mdb_printf("\t\t%16s: %p\n", "an_lock", areqn->an_lock); /* First column */ mdb_printf("%16s: ", "an_state"); prt_an_state(areqn->an_state); /* Second column */ mdb_printf("%14s: next 4 items\n", "an_reqarg"); /* First column again */ mdb_printf("%16s: '%16b'", "cr_flag", areqn->an_reqarg.cr_flag, call_flags); /* Second column */ mdb_printf("\t%16s: %p\n", "cr_callback_func", areqn->an_reqarg.cr_callback_func); /* First column again */ mdb_printf("%16s: %-16p", "cr_callback_arg", areqn->an_reqarg.cr_callback_arg); /* Second column */ mdb_printf("\t%16s: %lx\n", "cr_reqid", (ulong_t)areqn->an_reqarg.cr_reqid); /* First column again */ mdb_printf("%16s: %d", "an_params.rp_opgrp", areqn->an_params.rp_opgrp); /* Second column */ mdb_printf("\t%16s: %d\n", "an_params.rp_optype", areqn->an_params.rp_optype); /* First column again */ mdb_printf("%16s: %-16p", "an_context", areqn->an_context); /* Second column */ mdb_printf("\t%16s: %p\n", "an_ctxchain_next", areqn->an_ctxchain_next); /* First column again */ mdb_printf("%16s: %s", "an_is_my_turn", areqn->an_is_my_turn == B_FALSE ? "B_FALSE" : "B_TRUE"); /* Second column */ mdb_printf("\t\t%16s: %s\n", "an_isdual", areqn->an_isdual == B_FALSE ? "B_FALSE" : "B_TRUE"); /* First column again */ mdb_printf("%16s: %p", "an_next", areqn->an_next); /* Second column */ mdb_printf("\t\t%16s: %p\n", "an_prev", areqn->an_prev); /* First column again */ mdb_printf("%16s: %p", "an_provider", areqn->an_provider); /* Second column */ mdb_printf("\t\t%16s: %p\n", "an_idnext", areqn->an_idnext); /* First column again */ mdb_printf("%16s: %p", "an_idprev", areqn->an_idprev); /* Second column */ mdb_printf("\t\t%16s: %hx\n", "an_done", areqn->an_done); /* First column again */ mdb_printf("%16s: %d\n", "an_refcnt", areqn->an_refcnt); return (DCMD_OK); } /*ARGSUSED*/ int kcf_areq_node(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kcf_areq_node_t areqn; uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); /* * read even if we're looping, because the cbdata design does not * apply to mdb_pwalk_dcmd */ if (mdb_vread(&areqn, sizeof (kcf_areq_node_t), addr) == -1) { mdb_warn("cannot read %p", addr); return (DCMD_ERR); } if (opt_v) /* verbose */ return (v_kcf_areq_node(&areqn)); else return (kcf_areq_node_simple(&areqn)); } /*ARGSUSED*/ int kcf_global_swq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kcf_global_swq_t swq; kcf_global_swq_t *ptr; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_readsym(&ptr, sizeof (uintptr_t), "gswq") == -1) { mdb_warn("cannot read gswq"); return (DCMD_ERR); } } else ptr = (kcf_global_swq_t *)addr; if (mdb_vread(&swq, sizeof (kcf_global_swq_t), (uintptr_t)ptr) == -1) { mdb_warn("cannot read %p", ptr); return (DCMD_ERR); } mdb_printf("gs_lock (mutex):\t%p\n", swq.gs_lock); mdb_printf("gs_cv:\t%hx\n", swq.gs_cv._opaque); mdb_printf("gs_njobs:\t%u\n", swq.gs_njobs); mdb_printf("gs_maxjobs:\t%u\n", swq.gs_maxjobs); mdb_printf("gs_first:\t%p\n", swq.gs_first); mdb_printf("gs_last:\t%p\n", swq.gs_last); return (mdb_pwalk_dcmd("an_next", "kcf_areq_node", argc, argv, (uintptr_t)swq.gs_first)); } static int areq_walk_init_common(mdb_walk_state_t *wsp, boolean_t use_first) { kcf_global_swq_t gswq_copy; uintptr_t gswq_ptr; if (mdb_readsym(&gswq_ptr, sizeof (gswq_ptr), "gswq") == -1) { mdb_warn("failed to read 'gswq'"); return (WALK_ERR); } if (mdb_vread(&gswq_copy, sizeof (gswq_copy), gswq_ptr) == -1) { mdb_warn("cannot read %p", gswq_ptr); return (WALK_ERR); } if ((wsp->walk_addr = (use_first ? (uintptr_t)gswq_copy.gs_first : (uintptr_t)gswq_copy.gs_last)) == 0) { mdb_printf("Global swq is empty\n"); return (WALK_DONE); } wsp->walk_data = mdb_alloc(sizeof (kcf_areq_node_t), UM_SLEEP); return (WALK_NEXT); } int areq_first_walk_init(mdb_walk_state_t *wsp) { return (areq_walk_init_common(wsp, B_TRUE)); } int areq_last_walk_init(mdb_walk_state_t *wsp) { return (areq_walk_init_common(wsp, B_FALSE)); } typedef enum idwalk_type { IDNEXT, /* an_idnext */ IDPREV, /* an_idprev */ CTXCHAIN /* an_ctxchain_next */ } idwalk_type_t; static int an_id_walk_init(mdb_walk_state_t *wsp, idwalk_type_t type) { kcf_areq_node_t *adn; if (wsp->walk_addr == 0) { mdb_warn("must give kcf_areq_node address\n"); return (WALK_ERR); } adn = wsp->walk_data = mdb_alloc(sizeof (kcf_areq_node_t), UM_SLEEP); if (mdb_vread(adn, sizeof (kcf_areq_node_t), wsp->walk_addr) == -1) { mdb_warn("cannot read %p", wsp->walk_addr); return (WALK_ERR); } switch (type) { case IDNEXT: wsp->walk_addr = (uintptr_t)adn->an_idnext; break; case IDPREV: wsp->walk_addr = (uintptr_t)adn->an_idprev; break; case CTXCHAIN: wsp->walk_addr = (uintptr_t)adn->an_ctxchain_next; break; default: mdb_warn("Bad structure member in walk_init\n"); return (WALK_ERR); } return (WALK_NEXT); } int an_idnext_walk_init(mdb_walk_state_t *wsp) { return (an_id_walk_init(wsp, IDNEXT)); } int an_idprev_walk_init(mdb_walk_state_t *wsp) { return (an_id_walk_init(wsp, IDPREV)); } int an_ctxchain_walk_init(mdb_walk_state_t *wsp) { return (an_id_walk_init(wsp, CTXCHAIN)); } /* * At each step, read a kcf_areq_node_t into our private storage, then invoke * the callback function. We terminate when we reach a NULL type pointer. */ static int an_id_walk_step(mdb_walk_state_t *wsp, idwalk_type_t type) { int status; kcf_areq_node_t *ptr; if (wsp->walk_addr == 0) /* then we're done */ return (WALK_DONE); ptr = wsp->walk_data; if (mdb_vread(wsp->walk_data, sizeof (kcf_areq_node_t), wsp->walk_addr) == -1) { mdb_warn("cannot read %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); switch (type) { case IDNEXT: if ((wsp->walk_addr = (uintptr_t)ptr->an_idnext) == 0) return (WALK_DONE); break; case IDPREV: if ((wsp->walk_addr = (uintptr_t)ptr->an_idprev) == 0) return (WALK_DONE); break; case CTXCHAIN: if ((wsp->walk_addr = (uintptr_t)ptr->an_ctxchain_next) == 0) return (WALK_DONE); break; default: mdb_warn("Bad structure member in walk_step\n"); return (WALK_ERR); } return (status); } int an_idnext_walk_step(mdb_walk_state_t *wsp) { return (an_id_walk_step(wsp, IDNEXT)); } int an_idprev_walk_step(mdb_walk_state_t *wsp) { return (an_id_walk_step(wsp, IDPREV)); } int an_ctxchain_walk_step(mdb_walk_state_t *wsp) { return (an_id_walk_step(wsp, CTXCHAIN)); } /* * The walker's fini function is invoked at the end of each walk. Since we * dynamically allocated a kcf_areq_node_t in areq_walk_init, * we must free it now. */ void areq_walk_fini(mdb_walk_state_t *wsp) { #ifdef DEBUG mdb_printf("...end of kcf_areq_node walk\n"); #endif mdb_free(wsp->walk_data, sizeof (kcf_areq_node_t)); } /* * At each step, read a kcf_areq_node_t into our private storage, then invoke * the callback function. We terminate when we reach a NULL an_next pointer * or a NULL an_prev pointer. use_next flag indicates which one to check. */ static int an_walk_step_common(mdb_walk_state_t *wsp, boolean_t use_next) { int status; kcf_areq_node_t *ptr; ptr = (kcf_areq_node_t *)wsp->walk_data; if (mdb_vread(wsp->walk_data, sizeof (kcf_areq_node_t), wsp->walk_addr) == -1) { mdb_warn("failed to read kcf_areq_node at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); if ((wsp->walk_addr = (use_next ? (uintptr_t)ptr->an_next : (uintptr_t)ptr->an_prev)) == 0) return (WALK_DONE); return (status); } int an_next_walk_step(mdb_walk_state_t *wsp) { return (an_walk_step_common(wsp, B_TRUE)); } int an_prev_walk_step(mdb_walk_state_t *wsp) { return (an_walk_step_common(wsp, B_FALSE)); } /* * Walker data for reqid_table walking */ typedef struct reqid_data { kcf_reqid_table_t rd_table; kcf_reqid_table_t *rd_tbl_ptrs[REQID_TABLES]; int rd_cur_index; } reqid_data_t; typedef struct reqid_cb_data { crypto_req_id_t cb_reqid; int verbose; int found; } reqid_cb_data_t; extern int crypto_pr_reqid(uintptr_t, reqid_data_t *, reqid_cb_data_t *); int reqid_table_walk_init(mdb_walk_state_t *wsp) { reqid_data_t *wdata; reqid_cb_data_t *cbdata; wsp->walk_callback = (mdb_walk_cb_t)crypto_pr_reqid; wsp->walk_data = mdb_alloc(sizeof (reqid_data_t), UM_SLEEP); /* see if the walker was called from the command line or mdb_pwalk */ if (wsp->walk_cbdata == NULL) { /* command line */ if ((wsp->walk_cbdata = mdb_zalloc(sizeof (reqid_cb_data_t), UM_SLEEP)) == NULL) { mdb_warn("couldn't get cb memory for " "reqid_table_walker"); return (WALK_ERR); } /* initialize for a simple walk, as opposed to a reqid search */ cbdata = wsp->walk_cbdata; cbdata->verbose = TRUE; cbdata->cb_reqid = 0; } wdata = (reqid_data_t *)wsp->walk_data; if (mdb_readsym(wdata->rd_tbl_ptrs, sizeof (wdata->rd_tbl_ptrs), "kcf_reqid_table") == -1) { mdb_warn("failed to read 'kcf_reqid_table'"); return (WALK_ERR); } wdata->rd_cur_index = 0; wsp->walk_addr = (uintptr_t)wdata->rd_tbl_ptrs[wdata->rd_cur_index]; return (WALK_NEXT); } /* * At each step, read a kcf_reqid_table_t into our private storage, then invoke * the callback function. We terminate when we reach a */ int reqid_table_walk_step(mdb_walk_state_t *wsp) { int status; reqid_data_t *wdata; wdata = wsp->walk_data; wsp->walk_addr = (uintptr_t)wdata->rd_tbl_ptrs[wdata->rd_cur_index]; #ifdef DEBUG mdb_printf( "DEBUG: kcf_reqid_table at %p, sizeof kcf_reqid_table_t = %d\n", wsp->walk_addr, sizeof (kcf_reqid_table_t)); #endif status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); /* get ready for next call */ wdata->rd_cur_index++; if (wdata->rd_cur_index >= REQID_TABLES) return (WALK_DONE); return (status); } /* * The walker's fini function is invoked at the end of each walk. Since we * dynamically allocated a reqid_data_t in areq_walk_init, * we must free it now. */ void reqid_table_walk_fini(mdb_walk_state_t *wsp) { #ifdef DEBUG mdb_printf("...end of kcf_reqid walk\n"); #endif mdb_free(wsp->walk_data, sizeof (reqid_data_t)); } /* * If there's an argument beyond -v, then we're looking for a specific * reqid, otherwise, print any non-null kcf_areq things we run across. */ int crypto_pr_reqid(uintptr_t addr, reqid_data_t *data, reqid_cb_data_t *cbdata) { kcf_areq_node_t node; int i; int needhdr = TRUE; if (addr == 0) { mdb_printf("kcf_reqid_table[%d] = NULL\n", data->rd_cur_index); return (WALK_NEXT); } if (mdb_vread(&(data->rd_table), sizeof (kcf_reqid_table_t), addr) == -1) { mdb_warn("failed to read kcf_reqid_table at %p", addr); return (WALK_ERR); } /* Loop over all rt_idhash's */ for (i = 0; i < REQID_BUCKETS; i++) { uint_t number_in_chain = 0; uintptr_t node_addr; /* follow the an_idnext chains for each bucket */ do { /* read kcf_areq_node */ if (number_in_chain == 0) node_addr = (uintptr_t)data->rd_table.rt_idhash[i]; else /*LINTED*/ node_addr = (uintptr_t)node.an_idnext; #ifdef DEBUG mdb_printf("DEBUG: node_addr = %p\n", node_addr); #endif if (node_addr == 0) break; /* skip */ if (mdb_vread(&node, sizeof (kcf_areq_node_t), node_addr) == -1) { if (cbdata->verbose == TRUE) mdb_printf( "cannot read rt_idhash %d an_idnext %d\n", i, number_in_chain); break; } /* see if we want to print it */ if ((cbdata->cb_reqid == 0) || (node.an_reqarg.cr_reqid == cbdata->cb_reqid)) { cbdata->found = TRUE; /* printed if false || reqid */ /* is this the first rd_idhash found for this table? */ if (needhdr == TRUE) { /* print both indices in bold */ mdb_printf("%kcf_reqid_table[%lu] at %p:%\n", data->rd_cur_index, addr); mdb_printf("\trt_lock: %p\trt_curid: %llx\n", data->rd_table.rt_lock, data->rd_table.rt_curid); needhdr = FALSE; } /* print kcf_areq_node */ if (number_in_chain < 1) mdb_printf( " %rt_idhash[%lu%]% = %%p:%\n", i, node_addr); else mdb_printf( " rt_idhash[%lu%]" " an_idnext %d = %%p:%\n", i, number_in_chain, node_addr); mdb_inc_indent(8); /* if we're looking for one and only one reqid */ /* do it REALLY verbose */ if ((node.an_reqarg.cr_reqid == cbdata->cb_reqid) && (cbdata->cb_reqid != 0)) v_kcf_areq_node(&node); else if (cbdata->verbose == TRUE) /* * verbose for this walker means non-verbose for * the kcf_areq_node details */ kcf_areq_node_simple(&node); mdb_dec_indent(8); } /* if we only wanted one reqid, quit now */ if (node.an_reqarg.cr_reqid == cbdata->cb_reqid) { return (WALK_DONE); } number_in_chain++; } while (node.an_idnext != NULL); /* follow chain in same bucket */ } /* for each REQID_BUCKETS */ if ((needhdr == TRUE) && (cbdata->cb_reqid == 0)) { mdb_printf("%kcf_reqid_table[%lu]: %p\n", data->rd_cur_index, addr); } return (WALK_NEXT); } /*ARGSUSED*/ int crypto_find_reqid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const mdb_arg_t *argp = NULL; reqid_cb_data_t cbdata; int i, status; cbdata.cb_reqid = 0L; cbdata.verbose = FALSE; cbdata.found = FALSE; if (flags & DCMD_ADDRSPEC) { mdb_printf("use addr ::kcf_reqid_table\n"); return (DCMD_USAGE); } if ((i = mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &cbdata.verbose, NULL)) != argc) { if (argc - i > 1) return (DCMD_USAGE); } if (argc > i) argp = &argv[i]; if ((argp != NULL)) cbdata.cb_reqid = (crypto_req_id_t)mdb_argtoull(argp); status = mdb_pwalk("kcf_reqid_table", (mdb_walk_cb_t)crypto_pr_reqid, &cbdata, addr); if ((cbdata.cb_reqid != 0L) && (cbdata.found == FALSE)) mdb_printf("ID 0x%lx not found\n", cbdata.cb_reqid); #ifdef DEBUG else mdb_printf("DEBUG: cbdata.db_reqid = %lx, cbdata.found = %d\n", cbdata.cb_reqid, cbdata.found); #endif return (status); } int kcf_reqid_table_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { reqid_data_t wdata; reqid_cb_data_t cbdata; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); memset(&wdata, 0, sizeof (wdata)); memset(&cbdata, 0, sizeof (cbdata)); if ((mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &cbdata.verbose, NULL)) != argc) { return (DCMD_USAGE); } crypto_pr_reqid(addr, &wdata, &cbdata); return (DCMD_OK); } /* * 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) 2018, Joyent, Inc. */ /* * mdb dcmds for selected structures from * usr/src/uts/common/sys/crypto/spi.h * * Also the mdb module housekeeping */ #include #include #include #include #include #include #include "crypto_cmds.h" const mdb_bitmask_t extf_flags[] = { { "NIL", (ulong_t)-1, 0L }, { "CRYPTO_EXTF_RNG", CRYPTO_EXTF_RNG, CRYPTO_EXTF_RNG }, { "CRYPTO_EXTF_WRITE_PROTECTED", CRYPTO_EXTF_WRITE_PROTECTED, CRYPTO_EXTF_WRITE_PROTECTED }, { "CRYPTO_EXTF_LOGIN_REQUIRED", CRYPTO_EXTF_LOGIN_REQUIRED, CRYPTO_EXTF_LOGIN_REQUIRED }, { "CRYPTO_EXTF_USER_PIN_INITIALIZED", CRYPTO_EXTF_USER_PIN_INITIALIZED, CRYPTO_EXTF_USER_PIN_INITIALIZED }, { "CRYPTO_EXTF_CLOCK_ON_TOKEN", CRYPTO_EXTF_CLOCK_ON_TOKEN, CRYPTO_EXTF_CLOCK_ON_TOKEN }, { "CRYPTO_EXTF_PROTECTED_AUTHENTICATION_PATH", CRYPTO_EXTF_PROTECTED_AUTHENTICATION_PATH, CRYPTO_EXTF_PROTECTED_AUTHENTICATION_PATH }, { "CRYPTO_EXTF_DUAL_CRYPTO_OPERATIONS", CRYPTO_EXTF_DUAL_CRYPTO_OPERATIONS, CRYPTO_EXTF_DUAL_CRYPTO_OPERATIONS }, { "CRYPTO_EXTF_TOKEN_INITIALIZED", CRYPTO_EXTF_TOKEN_INITIALIZED, CRYPTO_EXTF_TOKEN_INITIALIZED }, { "CRYPTO_EXTF_USER_PIN_COUNT_LOW", CRYPTO_EXTF_USER_PIN_COUNT_LOW, CRYPTO_EXTF_USER_PIN_COUNT_LOW }, { "CRYPTO_EXTF_USER_PIN_FINAL_TRY", CRYPTO_EXTF_USER_PIN_FINAL_TRY, CRYPTO_EXTF_USER_PIN_FINAL_TRY }, { "CRYPTO_EXTF_USER_PIN_LOCKED", CRYPTO_EXTF_USER_PIN_LOCKED, CRYPTO_EXTF_USER_PIN_LOCKED }, { "CRYPTO_EXTF_USER_PIN_TO_BE_CHANGED", CRYPTO_EXTF_USER_PIN_TO_BE_CHANGED, CRYPTO_EXTF_USER_PIN_TO_BE_CHANGED }, { "CRYPTO_EXTF_SO_PIN_COUNT_LOW", CRYPTO_EXTF_SO_PIN_COUNT_LOW, CRYPTO_EXTF_SO_PIN_COUNT_LOW }, { "CRYPTO_EXTF_SO_PIN_FINAL_TRY", CRYPTO_EXTF_SO_PIN_FINAL_TRY, CRYPTO_EXTF_SO_PIN_FINAL_TRY }, { "CRYPTO_EXTF_SO_PIN_LOCKED", CRYPTO_EXTF_SO_PIN_LOCKED, CRYPTO_EXTF_SO_PIN_LOCKED }, { "CRYPTO_EXTF_SO_PIN_TO_BE_CHANGED", CRYPTO_EXTF_SO_PIN_TO_BE_CHANGED, CRYPTO_EXTF_SO_PIN_TO_BE_CHANGED }, { NULL, 0, 0 } }; /*ARGSUSED*/ int crypto_provider_ext_info(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { crypto_provider_ext_info_t ext_prov; /* * 33 is 1 + MAX(CRYPTO_EXT_SIZE_LABEL, CRYPTO_EXT_SIZE_MANUF, * CRYPTO_EXT_SIZE_MODEL, CRYPTO_EXT_SIZE_SERIAL) */ char scratch[33]; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&ext_prov, sizeof (crypto_provider_ext_info_t), addr) == -1) { mdb_warn("cannot read addr"); return (DCMD_ERR); } bcopy(ext_prov.ei_label, scratch, CRYPTO_EXT_SIZE_LABEL); scratch[CRYPTO_EXT_SIZE_LABEL] = '\0'; mdb_printf("ei_label\t\t%s\n", scratch); bcopy(ext_prov.ei_manufacturerID, scratch, CRYPTO_EXT_SIZE_MANUF); scratch[CRYPTO_EXT_SIZE_MANUF] = '\0'; mdb_printf("ei_manufacturerID\t%s\n", scratch); bcopy(ext_prov.ei_model, scratch, CRYPTO_EXT_SIZE_MODEL); scratch[CRYPTO_EXT_SIZE_MODEL] = '\0'; mdb_printf("ei_model\t\t%s\n", scratch); bcopy(ext_prov.ei_serial_number, scratch, CRYPTO_EXT_SIZE_SERIAL); scratch[CRYPTO_EXT_SIZE_SERIAL] = '\0'; mdb_printf("ei_serial_number\t%s\n", scratch); mdb_printf("ei_flags\t0x%x:\t<%lb>\n", ext_prov.ei_flags, ext_prov.ei_flags, extf_flags); mdb_printf("ei_max_session_count\t%lu\n", ext_prov.ei_max_session_count); mdb_printf("ei_max_pin_len\t\t%lu\n", ext_prov.ei_max_pin_len); mdb_printf("ei_min_pin_len\t\t%lu\n", ext_prov.ei_min_pin_len); mdb_printf("ei_total_public_memory\t%lu\n", ext_prov.ei_total_public_memory); mdb_printf("ei_free_public_memory\t%lu\n", ext_prov.ei_free_public_memory); mdb_printf("ei_total_private_memory\t%lu\n", ext_prov.ei_total_private_memory); mdb_printf("ei_free_private_memory\t%lu\n", ext_prov.ei_free_private_memory); mdb_printf("ei_hardware_version\tmajor %c minor %c\n", ext_prov.ei_hardware_version.cv_major, ext_prov.ei_hardware_version.cv_minor); mdb_printf("ei_firmware_version\tmajor %c minor %c\n", ext_prov.ei_firmware_version.cv_major, ext_prov.ei_firmware_version.cv_minor); mdb_printf("ei_time\t%s\n", ext_prov.ei_time); return (DCMD_OK); } const mdb_bitmask_t mech_bits[] = { { "NIL", (uint32_t)-1, 0 }, { "CRYPTO_FG_ENCRYPT", CRYPTO_FG_ENCRYPT, CRYPTO_FG_ENCRYPT }, { "CRYPTO_FG_DECRYPT", CRYPTO_FG_DECRYPT, CRYPTO_FG_DECRYPT }, { "CRYPTO_FG_DIGEST", CRYPTO_FG_DIGEST, CRYPTO_FG_DIGEST }, { "CRYPTO_FG_SIGN", CRYPTO_FG_SIGN, CRYPTO_FG_SIGN }, { "CRYPTO_FG_SIGN_RECOVER", CRYPTO_FG_SIGN_RECOVER, CRYPTO_FG_SIGN_RECOVER }, { "CRYPTO_FG_VERIFY", CRYPTO_FG_VERIFY, CRYPTO_FG_VERIFY }, { "CRYPTO_FG_VERIFY_RECOVER", CRYPTO_FG_VERIFY_RECOVER, CRYPTO_FG_VERIFY_RECOVER }, { "CRYPTO_FG_GENERATE", CRYPTO_FG_GENERATE, CRYPTO_FG_GENERATE }, { "CRYPTO_FG_GENERATE_KEY_PAIR", CRYPTO_FG_GENERATE_KEY_PAIR, CRYPTO_FG_GENERATE_KEY_PAIR }, { "CRYPTO_FG_WRAP", CRYPTO_FG_WRAP, CRYPTO_FG_WRAP }, { "CRYPTO_FG_UNWRAP", CRYPTO_FG_UNWRAP, CRYPTO_FG_UNWRAP }, { "CRYPTO_FG_DERIVE", CRYPTO_FG_DERIVE, CRYPTO_FG_DERIVE }, { "CRYPTO_FG_MAC", CRYPTO_FG_MAC, CRYPTO_FG_MAC }, { "CRYPTO_FG_ENCRYPT_MAC", CRYPTO_FG_ENCRYPT_MAC, CRYPTO_FG_ENCRYPT_MAC }, { "CRYPTO_FG_MAC_DECRYPT", CRYPTO_FG_MAC_DECRYPT, CRYPTO_FG_MAC_DECRYPT }, { "CRYPTO_FG_ENCRYPT_ATOMIC", CRYPTO_FG_ENCRYPT_ATOMIC, CRYPTO_FG_ENCRYPT_ATOMIC }, { "CRYPTO_FG_DECRYPT_ATOMIC", CRYPTO_FG_DECRYPT_ATOMIC, CRYPTO_FG_DECRYPT_ATOMIC }, { "CRYPTO_FG_MAC_ATOMIC", CRYPTO_FG_MAC_ATOMIC, CRYPTO_FG_MAC_ATOMIC }, { "CRYPTO_FG_DIGEST_ATOMIC", CRYPTO_FG_DIGEST_ATOMIC, CRYPTO_FG_DIGEST_ATOMIC }, { "CRYPTO_FG_SIGN_ATOMIC", CRYPTO_FG_SIGN_ATOMIC, CRYPTO_FG_SIGN_ATOMIC }, { "CRYPTO_FG_SIGN_RECOVER_ATOMIC", CRYPTO_FG_SIGN_RECOVER_ATOMIC, CRYPTO_FG_SIGN_RECOVER_ATOMIC }, { "CRYPTO_FG_VERIFY_ATOMIC", CRYPTO_FG_VERIFY_ATOMIC, CRYPTO_FG_VERIFY_ATOMIC }, { "CRYPTO_FG_VERIFY_RECOVER_ATOMIC", CRYPTO_FG_VERIFY_RECOVER_ATOMIC, CRYPTO_FG_VERIFY_RECOVER_ATOMIC }, { "CRYPTO_FG_ENCRYPT_MAC_ATOMIC", CRYPTO_FG_ENCRYPT_MAC_ATOMIC, CRYPTO_FG_ENCRYPT_MAC_ATOMIC }, { "CRYPTO_FG_MAC_DECRYPT_ATOMIC", CRYPTO_FG_MAC_DECRYPT_ATOMIC, CRYPTO_FG_MAC_DECRYPT_ATOMIC }, { "CRYPTO_FG_RANDOM", CRYPTO_FG_RANDOM, CRYPTO_FG_RANDOM}, { NULL, 0, 0 } }; /*ARGSUSED*/ int crypto_mech_info(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { crypto_mech_info_t minfo; const char *unit = "bits"; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&minfo, sizeof (crypto_mech_info_t), addr) == -1) { mdb_warn("cannot read addr %p", addr); return (DCMD_ERR); } mdb_printf("cm_mech_name_t\t%s\n", minfo.cm_mech_name); mdb_printf("cm_mech_number\t%lld\n", minfo.cm_mech_number); mdb_printf("cm_func_group_mask\t0x%x:\t<%b>\n", minfo.cm_func_group_mask, minfo.cm_func_group_mask, mech_bits); if (minfo.cm_keysize_unit & CRYPTO_KEYSIZE_UNIT_IN_BYTES) unit = "bytes"; mdb_printf("cm_min_key_length\t%lu %s\n", minfo.cm_min_key_length, unit); mdb_printf("cm_max_key_length\t%lu %s\n", minfo.cm_max_key_length, unit); return (DCMD_OK); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, and a function * named _mdb_init to return a pointer to our module information. */ static const mdb_dcmd_t dcmds[] = { /* spi.c */ { "crypto_provider_ext_info", ":", "module-private crypto provider info", crypto_provider_ext_info, NULL }, { "crypto_mech_info", ":", "print as crypto_mech_info", crypto_mech_info, NULL }, /* common.c */ { "crypto_mechanism", ":", "details about a crypto mechanism", crypto_mechanism, NULL }, { "crypto_data", ":", "print as crypto_data", crypto_data, NULL }, { "crypto_dual_data", ":", "print as crypto_dual_data", crypto_dual_data, NULL }, { "crypto_key", ":", "print as crypto_key", crypto_key, NULL }, /* impl.c */ { "kcf_provider_desc", ":", "crypto provider description struct", kcf_provider_desc, NULL }, { "prov_tab", "", "global table of crypto providers ", prov_tab, NULL }, { "policy_tab", "", "print global policy_tab", policy_tab, NULL }, /* sched_impl.c */ { "kcf_areq_node", ":[-v]", "print asynchronous crypto request struct, [ verbose ]", kcf_areq_node, NULL }, { "kcf_global_swq", "?[-v]", "global or addr global crypto queue. [ -v = verbose ]", kcf_global_swq, NULL }, { "crypto_find_reqid", "?[-v] reqid", "look for reqid, print if found [ -v = verbose ]", crypto_find_reqid, NULL }, { "kcf_reqid_table", ":[-v]", "print contents of a request ID hash table [ -v = verbose ]", kcf_reqid_table_dcmd, NULL }, { "kcf_soft_conf_entry", "?", "head or addr of configured software crypto providers", kcf_soft_conf_entry, NULL }, { "kcf_policy_desc", ":", "policy descriptors for crypto", kcf_policy_desc, NULL }, { NULL } }; static const mdb_walker_t walkers[] = { { "an_next", "walk kcf_areq_node's by an_next", areq_first_walk_init, an_next_walk_step, areq_walk_fini }, { "an_prev", "walk kcf_areq_node's by an_prev", areq_last_walk_init, an_prev_walk_step, areq_walk_fini }, { "an_idnext", "walk kcf_areq_node's by an_idnext", an_idnext_walk_init, an_idnext_walk_step, areq_walk_fini }, { "an_idprev", "walk kcf_areq_node's by an_idprev", an_idprev_walk_init, an_idprev_walk_step, areq_walk_fini }, { "an_ctxchain_next", "walk kcf_areq_node's by an_ctxchain_next", an_ctxchain_walk_init, an_ctxchain_walk_step, areq_walk_fini }, { "kcf_reqid_table", "table of asynchronous crypto requests", reqid_table_walk_init, reqid_table_walk_step, reqid_table_walk_fini }, { "soft_conf_entry", "table of software providers or addr", soft_conf_walk_init, soft_conf_walk_step, soft_conf_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2019, Joyent, Inc. * Copyright 2024 MNX Cloud, Inc. */ /* * The on-disk elements here are all little-endian, and this code doesn't make * any attempt to adjust for running on a big-endian system. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "installboot.h" #ifdef _BIG_ENDIAN #error needs porting for big-endian system #endif /* See usr/src/grub/grub-0.97/stage1/stage1.h */ #define GRUB_VERSION_OFF (0x3e) #define GRUB_COMPAT_VERSION_MAJOR 3 #define GRUB_COMPAT_VERSION_MINOR 2 #define GRUB_VERSION (2 << 8 | 3) /* 3.2 */ #define LOADER_VERSION (1) #define LOADER_JOYENT_VERSION (2) typedef enum { MBR_TYPE_UNKNOWN, MBR_TYPE_GRUB1, MBR_TYPE_LOADER, MBR_TYPE_LOADER_JOYENT, } mbr_type_t; typedef struct stringval { const char *sv_text; int sv_value; } stringval_t; stringval_t ptag_array[] = { { "unassigned", V_UNASSIGNED }, { "boot", V_BOOT }, { "root", V_ROOT }, { "swap", V_SWAP }, { "usr", V_USR }, { "backup", V_BACKUP }, { "stand", V_STAND }, { "var", V_VAR }, { "home", V_HOME }, { "alternates", V_ALTSCTR }, { "reserved", V_RESERVED }, { "system", V_SYSTEM }, { "BIOS_boot", V_BIOS_BOOT }, { "FreeBSD boot", V_FREEBSD_BOOT }, { "FreeBSD swap", V_FREEBSD_SWAP }, { "FreeBSD UFS", V_FREEBSD_UFS }, { "FreeBSD ZFS", V_FREEBSD_ZFS }, { "FreeBSD NANDFS", V_FREEBSD_NANDFS }, { NULL } }; stringval_t pflag_array[] = { { "wm", 0 }, { "wu", V_UNMNT }, { "rm", V_RONLY }, { "ru", V_RONLY | V_UNMNT }, { NULL } }; size_t sector_size = SECTOR_SIZE; static const char * array_find_string(stringval_t *array, int match_value) { for (; array->sv_text != NULL; array++) { if (array->sv_value == match_value) { return (array->sv_text); } } return (NULL); } static int array_widest_str(stringval_t *array) { int i; int width; width = 0; for (; array->sv_text != NULL; array++) { if ((i = strlen(array->sv_text)) > width) width = i; } return (width); } static void print_fdisk_part(struct ipart *ip, size_t nr) { char typestr[128]; char begchs[128]; char endchs[128]; char *c = NULL; if (ip->systid == UNUSED) { mdb_printf("%-4llu %s:%#lx\n", nr, "UNUSED", ip->systid); return; } switch (ip->systid) { case DOSOS12: c = "DOSOS12"; break; case PCIXOS: c = "PCIXOS"; break; case DOSOS16: c = "DOSOS16"; break; case EXTDOS: c = "EXTDOS"; break; case DOSHUGE: c = "DOSHUGE"; break; case FDISK_IFS: c = "FDISK_IFS"; break; case FDISK_AIXBOOT: c = "FDISK_AIXBOOT"; break; case FDISK_AIXDATA: c = "FDISK_AIXDATA"; break; case FDISK_OS2BOOT: c = "FDISK_OS2BOOT"; break; case FDISK_WINDOWS: c = "FDISK_WINDOWS"; break; case FDISK_EXT_WIN: c = "FDISK_EXT_WIN"; break; case FDISK_FAT95: c = "FDISK_FAT95"; break; case FDISK_EXTLBA: c = "FDISK_EXTLBA"; break; case DIAGPART: c = "DIAGPART"; break; case FDISK_LINUX: c = "FDISK_LINUX"; break; case FDISK_LINUXDSWAP: c = "FDISK_LINUXDSWAP"; break; case FDISK_LINUXDNAT: c = "FDISK_LINUXDNAT"; break; case FDISK_CPM: c = "FDISK_CPM"; break; case DOSDATA: c = "DOSDATA"; break; case OTHEROS: c = "OTHEROS"; break; case UNIXOS: c = "UNIXOS"; break; case FDISK_NOVELL2: c = "FDISK_NOVELL2"; break; case FDISK_NOVELL3: c = "FDISK_NOVELL3"; break; case FDISK_QNX4: c = "FDISK_QNX4"; break; case FDISK_QNX42: c = "FDISK_QNX42"; break; case FDISK_QNX43: c = "FDISK_QNX43"; break; case SUNIXOS: c = "SUNIXOS"; break; case FDISK_LINUXNAT: c = "FDISK_LINUXNAT"; break; case FDISK_NTFSVOL1: c = "FDISK_NTFSVOL1"; break; case FDISK_NTFSVOL2: c = "FDISK_NTFSVOL2"; break; case FDISK_BSD: c = "FDISK_BSD"; break; case FDISK_NEXTSTEP: c = "FDISK_NEXTSTEP"; break; case FDISK_BSDIFS: c = "FDISK_BSDIFS"; break; case FDISK_BSDISWAP: c = "FDISK_BSDISWAP"; break; case X86BOOT: c = "X86BOOT"; break; case SUNIXOS2: c = "SUNIXOS2"; break; case EFI_PMBR: c = "EFI_PMBR"; break; case EFI_FS: c = "EFI_FS"; break; default: c = NULL; break; } if (c != NULL) { mdb_snprintf(typestr, sizeof (typestr), "%s:%#lx", c, ip->systid); } else { mdb_snprintf(typestr, sizeof (typestr), "%#lx", ip->systid); } mdb_snprintf(begchs, sizeof (begchs), "%hu/%hu/%hu", (uint16_t)ip->begcyl | (uint16_t)(ip->begsect & ~0x3f) << 2, (uint16_t)ip->beghead, (uint16_t)ip->begsect & 0x3f); mdb_snprintf(endchs, sizeof (endchs), "%hu/%hu/%hu", (uint16_t)ip->endcyl | (uint16_t)(ip->endsect & ~0x3f) << 2, (uint16_t)ip->endhead, (uint16_t)ip->endsect & 0x3f); mdb_printf("%-4llu %-21s %#-7x %-11s %-11s %-10u %-9u\n", nr, typestr, ip->bootid, begchs, endchs, ip->relsect, ip->numsect); } /* * Based on pcfs driver and: * "Microsoft Extensible Firmware Initiative FAT32 File System Specification, * FAT: General Overview of On-Disk Format" * * http://download.microsoft.com/download/1/6/1/ * 161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc */ static void show_bpb(char *bpb) { bool fat32 = false; uint32_t reserved, rdirsec, spc; uint32_t rec, fsisec, bkbootsec; uint32_t totsec16, totsec32, totsec; uint32_t fatsec16, fatsec32, fatsec; uint32_t numfat, datasec; uint32_t ncl; char buf[12]; mdb_printf("\n"); mdb_printf("BPB JMP: "); if (VALID_JMPBOOT(bpb_jmpBoot(bpb))) { mdb_printf("valid"); } else { mdb_printf("invalid"); } mdb_printf("\n"); mdb_printf("BPB OEMName: "); if (VALID_OEMNAME(bpb_OEMName(bpb))) { mdb_printf("valid"); mdb_printf(" : %*s", 8, bpb_OEMName(bpb)); } else { mdb_printf("invalid"); } mdb_printf("\n"); mdb_printf("BPB Bytes per Sector: "); if (VALID_SECSIZE(bpb_get_BytesPerSec(bpb))) { mdb_printf("valid"); } else { mdb_printf("invalid"); } mdb_printf(" : %hu", bpb_get_BytesPerSec(bpb)); mdb_printf("\n"); mdb_printf("BPB Sectors per Cluster: "); spc = bpb_get_SecPerClus(bpb); if (VALID_SPCL(spc)) { mdb_printf("valid"); } else { mdb_printf("invalid"); } mdb_printf(" : %u", spc); mdb_printf("\n"); reserved = bpb_get_RsvdSecCnt(bpb); mdb_printf("BPB Reserved Sectors: "); if (VALID_RSVDSEC(reserved)) { mdb_printf("valid"); } else { mdb_printf("invalid"); } mdb_printf(" : %u", reserved); mdb_printf("\n"); mdb_printf("BPB Number of FATs: "); numfat = bpb_get_NumFATs(bpb); if (VALID_NUMFATS(numfat)) { mdb_printf("valid"); } else { mdb_printf("invalid"); } mdb_printf(" : %u", numfat); mdb_printf("\n"); rec = bpb_get_RootEntCnt(bpb); mdb_printf("BPB Root Entry Count: "); mdb_printf("%u", rec); mdb_printf("\n"); totsec16 = bpb_get_TotSec16(bpb); mdb_printf("BPB Total Sectors 16: "); mdb_printf("%u", totsec16); mdb_printf("\n"); mdb_printf("BPB Media Type: "); if (VALID_MEDIA(bpb_get_Media(bpb))) { mdb_printf("valid"); } else { mdb_printf("invalid"); } mdb_printf(" : 0x%02x", bpb_get_Media(bpb)); mdb_printf("\n"); fatsec16 = bpb_get_FatSz16(bpb); mdb_printf("BPB FAT Sectors 16: "); mdb_printf("%u", fatsec16); mdb_printf("\n"); mdb_printf("BPB Sectors Per Track: "); mdb_printf("%u", bpb_get_SecPerTrk(bpb)); mdb_printf("\n"); mdb_printf("BPB Number of Heads: "); mdb_printf("%u", bpb_get_NumHeads(bpb)); mdb_printf("\n"); mdb_printf("BPB Hidden Sectors: "); mdb_printf("%u", bpb_get_HiddSec(bpb)); mdb_printf("\n"); totsec32 = bpb_get_TotSec32(bpb); mdb_printf("BPB Total Sectors 32: "); mdb_printf("%u", totsec32); mdb_printf("\n"); mdb_printf("BPB Signature: "); if (VALID_BPBSIG(bpb_get_BPBSig(bpb))) { mdb_printf("valid"); } else { mdb_printf("invalid"); } mdb_printf("\n"); /* * This does conclude the legacy BPB fields. * FAT12/FAT16 and FAT32 have overlapping fields, * and we need to determine the fat type to decide which * alternative we will print (if any). */ fatsec32 = bpb_get_FatSz32(bpb); if (totsec16 == 0 && fatsec16 == 0) { totsec = totsec32; fatsec = fatsec32; fat32 = true; /* there is no FAT12/FAT16 */ } else { totsec = totsec16; fatsec = fatsec16; } if (totsec == 0 || fatsec == 0) { mdb_printf("There is no FAT file system\n"); return; } if (!fat32) { mdb_printf("BPB Drive Number: "); mdb_printf("0x%02x", bpb_get_DrvNum16(bpb)); mdb_printf("\n"); mdb_printf("BPB Extended Boot Signature: "); mdb_printf("0x%02x", bpb_get_BootSig16(bpb)); mdb_printf("\n"); if (bpb_get_BootSig16(bpb) == 0x29) { mdb_printf("BPB Volume ID: "); mdb_printf("0x%04x", bpb_get_VolID16(bpb)); mdb_printf("\n"); mdb_printf("BPB Volume Label: "); bcopy(bpb_VolLab16(bpb), buf, 11); buf[11] = '\0'; mdb_printf("\"%s\"", buf); mdb_printf("\n"); mdb_printf("BPB File System Type: "); if (VALID_FSTYPSTR16(bpb_FilSysType16(bpb))) { mdb_printf("valid"); } else { mdb_printf("invalid"); } bcopy(bpb_FilSysType16(bpb), buf, 8); buf[8] = '\0'; mdb_printf(" : \"%s\"", buf); mdb_printf("\n"); } } else { mdb_printf("BPB FAT Sectors 32: "); mdb_printf("%u", fatsec32); mdb_printf("\n"); mdb_printf("BPB Extended Flags 32: "); mdb_printf("0x%04x", bpb_get_ExtFlags32(bpb)); mdb_printf("\n"); mdb_printf("BPB FS Version: "); mdb_printf("0x%02x", bpb_get_FSVer32(bpb)); mdb_printf("\n"); mdb_printf("BPB Root Cluster: "); mdb_printf("%u", bpb_get_RootClus32(bpb)); mdb_printf("\n"); fsisec = bpb_get_FSInfo32(bpb); mdb_printf("BPB FS Info Sector: "); mdb_printf("%u", fsisec); mdb_printf("\n"); bkbootsec = bpb_get_BkBootSec32(bpb); mdb_printf("BPB Backup Boot Sector: "); mdb_printf("%u", bkbootsec); mdb_printf("\n"); mdb_printf("BPB Drive Number: "); mdb_printf("0x%02x", bpb_get_DrvNum32(bpb)); mdb_printf("\n"); mdb_printf("BPB Boot Signature: "); mdb_printf("0x%02x", bpb_get_BootSig32(bpb)); mdb_printf("\n"); if (bpb_get_BootSig32(bpb) == 0x29) { mdb_printf("BPB Volume ID: "); mdb_printf("0x%04x", bpb_get_VolID32(bpb)); mdb_printf("\n"); mdb_printf("BPB Volume Label: "); bcopy(bpb_VolLab32(bpb), buf, 11); buf[11] = '\0'; mdb_printf("\"%s\"", buf); mdb_printf("\n"); mdb_printf("BPB File System Type: "); if (VALID_FSTYPSTR32(bpb_FilSysType32(bpb))) { mdb_printf("valid"); } else { mdb_printf("invalid"); } bcopy(bpb_FilSysType32(bpb), buf, 8); buf[8] = '\0'; mdb_printf(" : \"%s\"", buf); mdb_printf("\n"); } } rdirsec = (rec * 32 + (sector_size - 1)) / sector_size; datasec = totsec - fatsec * numfat - rdirsec - reserved; ncl = datasec / spc; mdb_printf("count of clusters on the volume: %u\n", ncl); if (ncl < 4085) mdb_printf("Volume should be FAT12\n"); else if (ncl < 65525) mdb_printf("Volume should be FAT16\n"); else mdb_printf("Volume should be FAT32\n"); } static mbr_type_t mbr_info(struct mboot *mbr, uint_t bpb) { mbr_type_t type = MBR_TYPE_UNKNOWN; if (*((uint16_t *)&mbr->bootinst[GRUB_VERSION_OFF]) == GRUB_VERSION) { type = MBR_TYPE_GRUB1; } else if (mbr->bootinst[STAGE1_MBR_VERSION] == LOADER_VERSION) { type = MBR_TYPE_LOADER; } else if (mbr->bootinst[STAGE1_MBR_VERSION] == LOADER_JOYENT_VERSION) { type = MBR_TYPE_LOADER_JOYENT; } switch (type) { case MBR_TYPE_UNKNOWN: mdb_printf("Format: unknown\n"); break; case MBR_TYPE_GRUB1: mdb_printf("Format: grub1\n"); break; case MBR_TYPE_LOADER: mdb_printf("Format: loader (illumos)\n"); break; case MBR_TYPE_LOADER_JOYENT: mdb_printf("Format: loader (joyent)\n"); break; } mdb_printf("Signature: 0x%hx (%s)\n", mbr->signature, mbr->signature == MBB_MAGIC ? "valid" : "invalid"); mdb_printf("UniqueMBRDiskSignature: %#lx\n", *(uint32_t *)&mbr->bootinst[STAGE1_SIG]); if (type == MBR_TYPE_LOADER || type == MBR_TYPE_LOADER_JOYENT) { char uuid[UUID_PRINTABLE_STRING_LENGTH]; mdb_printf("Loader STAGE1_STAGE2_LBA: %llu\n", *(uint64_t *)&mbr->bootinst[STAGE1_STAGE2_LBA]); mdb_printf("Loader STAGE1_STAGE2_SIZE: %hu\n", *(uint16_t *)&mbr->bootinst[STAGE1_STAGE2_SIZE]); uuid_unparse((uchar_t *)&mbr->bootinst[STAGE1_STAGE2_UUID], uuid); mdb_printf("Loader STAGE1_STAGE2_UUID: %s\n", uuid); } if (bpb) { show_bpb(mbr->bootinst); } return (type); } static void mbr_help(void) { mdb_printf("Display a Master Boot Record.\n\n" "-b Show BIOS Parameter Block (BPB)\n"); } static int cmd_mbr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct mboot *mbr; mbr_type_t type; uint_t opt_b = FALSE; CTASSERT(sizeof (*mbr) == SECTOR_SIZE); if (mdb_getopts(argc, argv, 'b', MDB_OPT_SETBITS, TRUE, &opt_b, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) addr = 0; mbr = mdb_zalloc(sector_size, UM_SLEEP | UM_GC); if (mdb_vread(mbr, sector_size, addr) == -1) { mdb_warn("failed to read MBR"); return (DCMD_ERR); } if (VALID_SECSIZE(bpb_get_BytesPerSec(mbr->bootinst))) { sector_size = bpb_get_BytesPerSec(mbr->bootinst); } type = mbr_info(mbr, opt_b); /* If the magic is wrong, stop here. */ if (mbr->signature != MBB_MAGIC) return (DCMD_ERR); /* Also print volume boot record */ switch (type) { case MBR_TYPE_LOADER: case MBR_TYPE_LOADER_JOYENT: if (*(uint16_t *)&mbr->bootinst[STAGE1_STAGE2_SIZE] == 1) { struct mboot vbr; uintptr_t vbrp; vbrp = *(uint64_t *)&mbr->bootinst[STAGE1_STAGE2_LBA]; vbrp *= sector_size; vbrp += addr; if (mdb_vread(&vbr, sizeof (vbr), vbrp) == -1) { mdb_warn("failed to read VBR"); } else { mdb_printf("\nSTAGE1 in VBR:\n"); (void) mbr_info(&vbr, opt_b); } } break; default: break; } if (*(uint16_t *)&mbr->bootinst[STAGE1_STAGE2_SIZE] == 1) { /* * This is MBR, display partition information. */ mdb_printf( "\n%%-4s %-21s %-7s %-11s %-11s %-10s %-9s%\n", "PART", "TYPE", "ACTIVE", "STARTCHS", "ENDCHS", "SECTOR", "NUMSECT"); for (size_t i = 0; i < FD_NUMPART; i++) { struct ipart *ip = (struct ipart *) (mbr->parts + (sizeof (struct ipart) * i)); print_fdisk_part(ip, i); } } return (DCMD_OK); } static unsigned int crc32_tab[] = { CRC32_TABLE }; static unsigned int efi_crc32(const unsigned char *s, unsigned int len) { unsigned int crc32val; CRC32(crc32val, s, len, -1U, crc32_tab); return (crc32val ^ -1U); } typedef struct { struct uuid eg_uuid; const char *eg_name; } efi_guid_t; static efi_guid_t efi_guids[] = { { EFI_UNUSED, "EFI_UNUSED" }, { EFI_RESV1, "EFI_RESV1" }, { EFI_BOOT, "EFI_BOOT" }, { EFI_ROOT, "EFI_ROOT" }, { EFI_SWAP, "EFI_SWAP" }, { EFI_USR, "EFI_USR" }, { EFI_BACKUP, "EFI_BACKUP" }, { EFI_RESV2, "EFI_RESV2" }, { EFI_VAR, "EFI_VAR" }, { EFI_HOME, "EFI_HOME" }, { EFI_ALTSCTR, "EFI_ALTSCTR" }, { EFI_RESERVED, "EFI_RESERVED" }, { EFI_SYSTEM, "EFI_SYSTEM" }, { EFI_LEGACY_MBR, "EFI_LEGACY_MBR" }, { EFI_SYMC_PUB, "EFI_SYMC_PUB" }, { EFI_SYMC_CDS, "EFI_SYMC_CDS" }, { EFI_MSFT_RESV, "EFI_MSFT_RESV" }, { EFI_DELL_BASIC, "EFI_DELL_BASIC" }, { EFI_DELL_RAID, "EFI_DELL_RAID" }, { EFI_DELL_SWAP, "EFI_DELL_SWAP" }, { EFI_DELL_LVM, "EFI_DELL_LVM" }, { EFI_DELL_RESV, "EFI_DELL_RESV" }, { EFI_AAPL_BOOT, "EFI_AAPL_BOOT" }, { EFI_AAPL_HFS, "EFI_AAPL_HFS" }, { EFI_AAPL_UFS, "EFI_AAPL_UFS" }, { EFI_AAPL_ZFS, "EFI_AAPL_ZFS" }, { EFI_AAPL_APFS, "EFI_AAPL_APFS" }, { EFI_FREEBSD_BOOT, "EFI_FREEBSD_BOOT" }, { EFI_FREEBSD_NANDFS, "EFI_FREEBSD_NANDFS" }, { EFI_FREEBSD_SWAP, "EFI_FREEBSD_SWAP" }, { EFI_FREEBSD_UFS, "EFI_FREEBSD_UFS" }, { EFI_FREEBSD_VINUM, "EFI_FREEBSD_VINUM" }, { EFI_FREEBSD_ZFS, "EFI_FREEBSD_ZFS" }, { EFI_BIOS_BOOT, "EFI_BIOS_BOOT" }, }; static void print_gpe(efi_gpe_t *gpe, size_t nr, int show_guid) { const char *type = "unknown"; for (size_t i = 0; i < ARRAY_SIZE(efi_guids); i++) { if (memcmp((void *)&efi_guids[i].eg_uuid, (void *)&gpe->efi_gpe_PartitionTypeGUID, sizeof (efi_guids[i].eg_uuid)) == 0) { type = efi_guids[i].eg_name; break; } } if (strcmp(type, "EFI_UNUSED") == 0) { mdb_printf("%-4u %-19s\n", nr, type); return; } if (show_guid) { char guid[UUID_PRINTABLE_STRING_LENGTH]; uuid_unparse((uchar_t *)&gpe->efi_gpe_UniquePartitionGUID, guid); mdb_printf("%-4u %-19s %s\n", nr, type, guid); } else { char name[EFI_PART_NAME_LEN + 1] = ""; /* * Hopefully, ASCII is sufficient for any naming we care about. */ for (size_t i = 0; i < sizeof (name); i++) { ushort_t wchar = gpe->efi_gpe_PartitionName[i]; name[i] = (char)(isascii(wchar) ? wchar : '?'); } mdb_printf("%-4u %-19s %-13llu %-13llu %#-8llx %s\n", nr, type, gpe->efi_gpe_StartingLBA, gpe->efi_gpe_EndingLBA, gpe->efi_gpe_Attributes, name); } } static int cmd_gpt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv __unused) { char uuid[UUID_PRINTABLE_STRING_LENGTH]; int show_alternate = B_FALSE; int show_guid = B_FALSE; efi_gpt_t *altheader; size_t table_size; efi_gpt_t *header; efi_gpe_t *gpet; uint_t orig_crc; uint_t crc; if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &show_alternate, 'g', MDB_OPT_SETBITS, TRUE, &show_guid, NULL) != argc) return (DCMD_USAGE); /* Primary header is at LBA 1. */ if (!(flags & DCMD_ADDRSPEC)) addr = sector_size; header = mdb_zalloc(sector_size, UM_SLEEP | UM_GC); if (mdb_vread(header, sector_size, addr) == -1) { mdb_warn("failed to read GPT header"); return (DCMD_ERR); } if (show_alternate) { addr = header->efi_gpt_AlternateLBA * sector_size; if (mdb_vread(header, sector_size, addr) == -1) { mdb_warn("failed to read GPT header"); return (DCMD_ERR); } } mdb_printf("Signature: %s (%s)\n", (char *)&header->efi_gpt_Signature, strncmp((char *)&header->efi_gpt_Signature, "EFI PART", 8) == 0 ? "valid" : "invalid"); mdb_printf("Revision: %hu.%hu\n", header->efi_gpt_Revision >> 16, header->efi_gpt_Revision); mdb_printf("HeaderSize: %u bytes\n", header->efi_gpt_HeaderSize); if (header->efi_gpt_HeaderSize > SECTOR_SIZE) { mdb_warn("invalid header size: skipping CRC\n"); } else { orig_crc = header->efi_gpt_HeaderCRC32; header->efi_gpt_HeaderCRC32 = 0; crc = efi_crc32((unsigned char *)header, header->efi_gpt_HeaderSize); mdb_printf("HeaderCRC32: %#x (should be %#x)\n", orig_crc, crc); } mdb_printf("Reserved1: %#x (should be 0x0)\n", header->efi_gpt_Reserved1); mdb_printf("MyLBA: %llu (should be %llu)\n", header->efi_gpt_MyLBA, addr / sector_size); mdb_printf("AlternateLBA: %llu\n", header->efi_gpt_AlternateLBA); mdb_printf("FirstUsableLBA: %llu\n", header->efi_gpt_FirstUsableLBA); mdb_printf("LastUsableLBA: %llu\n", header->efi_gpt_LastUsableLBA); if (header->efi_gpt_MyLBA >= header->efi_gpt_FirstUsableLBA && header->efi_gpt_MyLBA <= header->efi_gpt_LastUsableLBA) { mdb_warn("MyLBA is within usable LBA range\n"); } if (header->efi_gpt_AlternateLBA >= header->efi_gpt_FirstUsableLBA && header->efi_gpt_AlternateLBA <= header->efi_gpt_LastUsableLBA) { mdb_warn("AlternateLBA is within usable LBA range\n"); } altheader = mdb_zalloc(sector_size, UM_SLEEP | UM_GC); if (mdb_vread(altheader, sector_size, header->efi_gpt_AlternateLBA * sector_size) == -1) { mdb_warn("failed to read alternate GPT header"); } else { if (strncmp((char *)&altheader->efi_gpt_Signature, "EFI PART", 8) != 0) { mdb_warn("found invalid alternate GPT header with " "Signature: %s\n", (char *)&altheader->efi_gpt_Signature); } if (altheader->efi_gpt_MyLBA != header->efi_gpt_AlternateLBA) { mdb_warn("alternate GPT header at offset %#llx has " "invalid MyLBA %llu\n", header->efi_gpt_AlternateLBA * sector_size, altheader->efi_gpt_MyLBA); } if (altheader->efi_gpt_AlternateLBA != header->efi_gpt_MyLBA) { mdb_warn("alternate GPT header at offset %#llx has " "invalid AlternateLBA %llu\n", header->efi_gpt_AlternateLBA * sector_size, altheader->efi_gpt_AlternateLBA); } /* * We could go ahead and verify all the alternate checksums, * etc. here too... */ } uuid_unparse((uchar_t *)&header->efi_gpt_DiskGUID, uuid); mdb_printf("DiskGUID: %s\n", uuid); mdb_printf("PartitionEntryLBA: %llu\n", header->efi_gpt_PartitionEntryLBA); mdb_printf("NumberOfPartitionEntries: %u\n", header->efi_gpt_NumberOfPartitionEntries); /* * While the spec allows a different size, in practice the table * is always packed. */ if (header->efi_gpt_SizeOfPartitionEntry != sizeof (efi_gpe_t)) { mdb_warn("SizeOfPartitionEntry: %#x bytes " "(expected %#x bytes)\n", header->efi_gpt_SizeOfPartitionEntry, sizeof (efi_gpe_t)); return (DCMD_ERR); } mdb_printf("SizeOfPartitionEntry: %#x bytes\n", header->efi_gpt_SizeOfPartitionEntry); table_size = header->efi_gpt_SizeOfPartitionEntry * header->efi_gpt_NumberOfPartitionEntries; /* * While this is a minimum reservation, it serves us ably as a * maximum value to reasonably expect. */ if (table_size > EFI_MIN_ARRAY_SIZE) { mdb_warn("Skipping GPT array of %#lx bytes.\n", table_size); return (DCMD_ERR); } table_size = P2ROUNDUP(table_size, sector_size); gpet = mdb_alloc(table_size, UM_SLEEP | UM_GC); if (mdb_vread(gpet, table_size, header->efi_gpt_PartitionEntryLBA * sector_size) == -1) { mdb_warn("couldn't read GPT array"); return (DCMD_ERR); } crc = efi_crc32((unsigned char *)gpet, header->efi_gpt_SizeOfPartitionEntry * header->efi_gpt_NumberOfPartitionEntries); mdb_printf("PartitionEntryArrayCRC32: %#x (should be %#x)\n", header->efi_gpt_PartitionEntryArrayCRC32, crc); if (show_guid) { mdb_printf("\n%%-4s %-19s %-37s%\n", "PART", "TYPE", "GUID"); } else { mdb_printf("\n%%-4s %-19s %-13s %-13s %-8s %s%\n", "PART", "TYPE", "STARTLBA", "ENDLBA", "ATTR", "NAME"); } for (size_t i = 0; i < header->efi_gpt_NumberOfPartitionEntries; i++) print_gpe(&gpet[i], i, show_guid); return (DCMD_OK); } static void gpt_help(void) { mdb_printf("Display an EFI GUID Partition Table.\n\n" "-a Display the alternate GPT\n" "-g Show unique GUID for each table entry\n"); } static int cmd_vtoc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint8_t *buf; struct dk_label *dl; struct dk_vtoc *dv; uintptr_t vaddr; int i, tag_width, cyl_width; int show_absolute = B_TRUE; int show_sectors = B_TRUE; uint32_t cyl; if (mdb_getopts(argc, argv, 'c', MDB_OPT_CLRBITS, TRUE, &show_sectors, 'r', MDB_OPT_CLRBITS, TRUE, &show_absolute, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) addr = 0; else addr *= sector_size; buf = mdb_zalloc(sector_size, UM_SLEEP | UM_GC); #if defined(_SUNOS_VTOC_16) if (mdb_vread(buf, sector_size, addr) == -1) { mdb_warn("failed to read VBR"); return (DCMD_ERR); } mdb_printf("VBR info:\n"); (void) mbr_info((struct mboot *)buf, FALSE); #endif vaddr = addr + DK_LABEL_LOC * sector_size; if (mdb_vread(buf, sector_size, vaddr) == -1) { mdb_warn("failed to read VTOC"); return (DCMD_ERR); } dl = (struct dk_label *)buf; dv = (struct dk_vtoc *)&dl->dkl_vtoc; mdb_printf("Label magic: 0x%hx (%s)\n", dl->dkl_magic, dl->dkl_magic == DKL_MAGIC ? "valid" : "invalid"); if (dl->dkl_magic != DKL_MAGIC) return (DCMD_ERR); mdb_printf("Label %s sane\n", dv->v_sanity == VTOC_SANE ? "is" : "is not"); mdb_printf("Label version: %#x\n", dv->v_version); mdb_printf("Volume name = <%s>\n", dv->v_volume); mdb_printf("ASCII name = <%s>\n", dv->v_asciilabel); mdb_printf("pcyl = %4d\n", dl->dkl_pcyl); mdb_printf("ncyl = %4d\n", dl->dkl_ncyl); mdb_printf("acyl = %4d\n", dl->dkl_acyl); #if defined(_SUNOS_VTOC_16) mdb_printf("bcyl = %4d\n", dl->dkl_bcyl); #endif /* defined(_SUNOS_VTOC_16) */ mdb_printf("nhead = %4d\n", dl->dkl_nhead); mdb_printf("nsect = %4d\n", dl->dkl_nsect); if (!show_absolute) addr = 0; cyl = dl->dkl_nhead * dl->dkl_nsect; if (show_sectors) cyl = 1; else addr /= (cyl * sector_size); tag_width = array_widest_str(ptag_array); cyl_width = sizeof ("CYLINDERS"); for (i = 0; i < dv->v_nparts; i++) { uint32_t start, end, size; int w; #if defined(_SUNOS_VTOC_16) start = addr + (dv->v_part[i].p_start / cyl); size = dv->v_part[i].p_size; #elif defined(_SUNOS_VTOC_8) start = dl->dkl_map[i].dkl_cylno; start *= dl->dkl_nhead * dl->dkl_nsect; /* compute bytes */ start /= cyl; start += addr; size = dl->dkl_map[i].dkl_nblk; #else #error "No VTOC format defined." #endif if (size == 0) end = start = 0; else end = start + size / cyl - 1; w = mdb_snprintf(NULL, 0, "%u - %u", start, end); if (w > cyl_width) cyl_width = w; } if (show_sectors == B_TRUE) { mdb_printf("\n%%-4s %-*s %-7s %-11s %-11s %-*s " "%-10s%\n", "PART", tag_width, "TAG", "FLAG", "STARTLBA", "ENDLBA", MDB_NICENUM_BUFLEN, "SIZE", "BLOCKS"); } else { mdb_printf("\n%%-4s %-*s %-7s %-*s %-*s %-10s%\n", "PART", tag_width, "TAG", "FLAG", cyl_width, "CYLINDERS", MDB_NICENUM_BUFLEN, "SIZE", "BLOCKS"); } for (i = 0; i < dv->v_nparts; i++) { uint16_t tag, flag; uint32_t start, end, size; const char *stag, *sflag; char nnum[MDB_NICENUM_BUFLEN]; #if defined(_SUNOS_VTOC_16) tag = dv->v_part[i].p_tag; flag = dv->v_part[i].p_flag; start = addr + (dv->v_part[i].p_start / cyl); size = dv->v_part[i].p_size; #elif defined(_SUNOS_VTOC_8) tag = dv->v_part[i].p_tag; flag = dv->v_part[i].p_flag; start = dl->dkl_map[i].dkl_cylno; start *= dl->dkl_nhead * dl->dkl_nsect; /* compute bytes */ start /= cyl; start += addr; size = dl->dkl_map[i].dkl_nblk; #else #error "No VTOC format defined." #endif if (size == 0) end = start = 0; else end = start + size / cyl - 1; stag = array_find_string(ptag_array, tag); if (stag == NULL) stag = "?"; sflag = array_find_string(pflag_array, flag); if (sflag == NULL) sflag = "?"; mdb_printf("%-4d %-*s %-7s ", i, tag_width, stag, sflag); mdb_nicenum(size * sector_size, nnum); if (show_sectors) { mdb_printf("%-11u %-11u %-*s %-10u\n", start, end, MDB_NICENUM_BUFLEN, nnum, size); } else { char cyls[10 * 2 + 4]; if (size == 0) { mdb_snprintf(cyls, sizeof (cyls), "%-*u", cyl_width, size); } else { mdb_snprintf(cyls, sizeof (cyls), "%u - %u", start, end); } mdb_printf("%-*s %-*s %-10u\n", cyl_width, cyls, MDB_NICENUM_BUFLEN, nnum, size); } } return (DCMD_OK); } static void vtoc_help(void) { mdb_printf("Display a Virtual Table of Content (VTOC).\n\n" "-r Display relative addresses\n" "-c Use cylinder based addressing\n"); mdb_printf("\nThe addr is in %u-byte disk blocks.\n", sector_size); } static int cmd_sect(uintptr_t addr __unused, uint_t flags __unused, int argc, const mdb_arg_t *argv) { uint64_t size = SECTOR_SIZE; if (argc < 1) { mdb_printf("Current sector size is %u (%#x)\n", sector_size, sector_size); return (DCMD_OK); } if (argc != 1) return (DCMD_USAGE); switch (argv[0].a_type) { case MDB_TYPE_STRING: size = mdb_strtoull(argv[0].a_un.a_str); break; case MDB_TYPE_IMMEDIATE: size = argv[0].a_un.a_val; break; default: return (DCMD_USAGE); } if (!ISP2(size)) { mdb_printf("sector size must be power of 2\n"); return (DCMD_USAGE); } sector_size = size; return (DCMD_OK); } static void sect_help(void) { mdb_printf("Show or set sector size.\n"); } static const mdb_dcmd_t dcmds[] = { { "mbr", "?[-b]", "dump Master Boot Record information", cmd_mbr, mbr_help }, { "gpt", "?[-ag]", "dump an EFI GPT", cmd_gpt, gpt_help }, { "vtoc", "?[-cr]", "dump VTOC information", cmd_vtoc, vtoc_help }, { "sectorsize", NULL, "set or show sector size", cmd_sect, sect_help }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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. */ #include #include extern int dof_sec(uintptr_t, uint_t, int, const mdb_arg_t *); extern const char *dof_sec_name(uint32_t); extern const mdb_walker_t kernel_walkers[]; extern const mdb_dcmd_t kernel_dcmds[]; /*ARGSUSED*/ static void dis_log(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s %%r%u, %%r%u, %%r%u", name, DIF_INSTR_R1(instr), DIF_INSTR_R2(instr), DIF_INSTR_RD(instr)); } /*ARGSUSED*/ static void dis_branch(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s %u", name, DIF_INSTR_LABEL(instr)); } /*ARGSUSED*/ static void dis_load(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s [%%r%u], %%r%u", name, DIF_INSTR_R1(instr), DIF_INSTR_RD(instr)); } /*ARGSUSED*/ static void dis_store(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s %%r%u, [%%r%u]", name, DIF_INSTR_R1(instr), DIF_INSTR_RD(instr)); } /*ARGSUSED*/ static void dis_str(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%s", name); } /*ARGSUSED*/ static void dis_r1rd(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s %%r%u, %%r%u", name, DIF_INSTR_R1(instr), DIF_INSTR_RD(instr)); } /*ARGSUSED*/ static void dis_cmp(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s %%r%u, %%r%u", name, DIF_INSTR_R1(instr), DIF_INSTR_R2(instr)); } /*ARGSUSED*/ static void dis_tst(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s %%r%u", name, DIF_INSTR_R1(instr)); } static const char * dis_varname(const dtrace_difo_t *dp, uint_t id, uint_t scope) { dtrace_difv_t *dvp; size_t varsize; caddr_t addr = NULL, str; uint_t i; if (dp == NULL) return (NULL); varsize = sizeof (dtrace_difv_t) * dp->dtdo_varlen; dvp = mdb_alloc(varsize, UM_SLEEP); if (mdb_vread(dvp, varsize, (uintptr_t)dp->dtdo_vartab) == -1) { mdb_free(dvp, varsize); return (""); } for (i = 0; i < dp->dtdo_varlen; i++) { if (dvp[i].dtdv_id == id && dvp[i].dtdv_scope == scope) { if (dvp[i].dtdv_name < dp->dtdo_strlen) addr = dp->dtdo_strtab + dvp[i].dtdv_name; break; } } mdb_free(dvp, varsize); if (addr == NULL) return (NULL); str = mdb_zalloc(dp->dtdo_strlen + 1, UM_SLEEP | UM_GC); for (i = 0; i == 0 || str[i - 1] != '\0'; i++, addr++) { if (mdb_vread(&str[i], sizeof (char), (uintptr_t)addr) == -1) return (""); } return (str); } static uint_t dis_scope(const char *name) { switch (name[2]) { case 'l': return (DIFV_SCOPE_LOCAL); case 't': return (DIFV_SCOPE_THREAD); case 'g': return (DIFV_SCOPE_GLOBAL); default: return (-1u); } } static void dis_lda(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { uint_t var = DIF_INSTR_R1(instr); const char *vname; mdb_printf("%-4s DIF_VAR(%x), %%r%u, %%r%u", name, var, DIF_INSTR_R2(instr), DIF_INSTR_RD(instr)); if ((vname = dis_varname(dp, var, dis_scope(name))) != NULL) mdb_printf("\t\t! %s", vname); } static void dis_ldv(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { uint_t var = DIF_INSTR_VAR(instr); const char *vname; mdb_printf("%-4s DIF_VAR(%x), %%r%u", name, var, DIF_INSTR_RD(instr)); if ((vname = dis_varname(dp, var, dis_scope(name))) != NULL) mdb_printf("\t\t! %s", vname); } static void dis_stv(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { uint_t var = DIF_INSTR_VAR(instr); const char *vname; mdb_printf("%-4s %%r%u, DIF_VAR(%x)", name, DIF_INSTR_RS(instr), var); if ((vname = dis_varname(dp, var, dis_scope(name))) != NULL) mdb_printf("\t\t! %s", vname); } static void dis_setx(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { uint_t intptr = DIF_INSTR_INTEGER(instr); mdb_printf("%-4s DIF_INTEGER[%u], %%r%u", name, intptr, DIF_INSTR_RD(instr)); if (dp != NULL && intptr < dp->dtdo_intlen) { uint64_t *ip = mdb_alloc(dp->dtdo_intlen * sizeof (uint64_t), UM_SLEEP | UM_GC); if (mdb_vread(ip, dp->dtdo_intlen * sizeof (uint64_t), (uintptr_t)dp->dtdo_inttab) == -1) mdb_warn("failed to read data at %p", dp->dtdo_inttab); else mdb_printf("\t\t! 0x%llx", ip[intptr]); } } static void dis_sets(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { uint_t strptr = DIF_INSTR_STRING(instr); mdb_printf("%-4s DIF_STRING[%u], %%r%u", name, strptr, DIF_INSTR_RD(instr)); if (dp != NULL && strptr < dp->dtdo_strlen) { char *str = mdb_alloc(dp->dtdo_strlen, UM_SLEEP | UM_GC); if (mdb_vread(str, dp->dtdo_strlen, (uintptr_t)dp->dtdo_strtab) == -1) mdb_warn("failed to read data at %p", dp->dtdo_strtab); else mdb_printf("\t\t! \"%s\"", str + strptr); } } /*ARGSUSED*/ static void dis_ret(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s %%r%u", name, DIF_INSTR_RD(instr)); } /*ARGSUSED*/ static void dis_call(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { uint_t subr = DIF_INSTR_SUBR(instr); mdb_printf("%-4s DIF_SUBR(%u), %%r%u\t\t! %s", name, subr, DIF_INSTR_RD(instr), dtrace_subrstr(NULL, subr)); } /*ARGSUSED*/ static void dis_pushts(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { static const char *const tnames[] = { "TYPE_CTF", "TYPE_STRING" }; uint_t type = DIF_INSTR_TYPE(instr); mdb_printf("%-4s DIF_TYPE(%u), %%r%u, %%r%u", name, type, DIF_INSTR_R2(instr), DIF_INSTR_RS(instr)); if (type < sizeof (tnames) / sizeof (tnames[0])) mdb_printf("\t\t! %s", tnames[type]); } /*ARGSUSED*/ static void dis_xlate(const dtrace_difo_t *dp, const char *name, dif_instr_t instr) { mdb_printf("%-4s DIF_XLREF[%u], %%r%u", name, DIF_INSTR_XLREF(instr), DIF_INSTR_RD(instr)); } static char * dis_typestr(const dtrace_diftype_t *t, char *buf, size_t len) { char kind[8]; switch (t->dtdt_kind) { case DIF_TYPE_CTF: (void) strcpy(kind, "D type"); break; case DIF_TYPE_STRING: (void) strcpy(kind, "string"); break; default: (void) mdb_snprintf(kind, sizeof (kind), "0x%x", t->dtdt_kind); } if (t->dtdt_flags & DIF_TF_BYREF) { (void) mdb_snprintf(buf, len, "%s by ref (size %lu)", kind, (ulong_t)t->dtdt_size); } else { (void) mdb_snprintf(buf, len, "%s (size %lu)", kind, (ulong_t)t->dtdt_size); } return (buf); } static int dis(uintptr_t addr, dtrace_difo_t *dp) { static const struct opent { const char *op_name; void (*op_func)(const dtrace_difo_t *, const char *, dif_instr_t); } optab[] = { { "(illegal opcode)", dis_str }, { "or", dis_log }, /* DIF_OP_OR */ { "xor", dis_log }, /* DIF_OP_XOR */ { "and", dis_log }, /* DIF_OP_AND */ { "sll", dis_log }, /* DIF_OP_SLL */ { "srl", dis_log }, /* DIF_OP_SRL */ { "sub", dis_log }, /* DIF_OP_SUB */ { "add", dis_log }, /* DIF_OP_ADD */ { "mul", dis_log }, /* DIF_OP_MUL */ { "sdiv", dis_log }, /* DIF_OP_SDIV */ { "udiv", dis_log }, /* DIF_OP_UDIV */ { "srem", dis_log }, /* DIF_OP_SREM */ { "urem", dis_log }, /* DIF_OP_UREM */ { "not", dis_r1rd }, /* DIF_OP_NOT */ { "mov", dis_r1rd }, /* DIF_OP_MOV */ { "cmp", dis_cmp }, /* DIF_OP_CMP */ { "tst", dis_tst }, /* DIF_OP_TST */ { "ba", dis_branch }, /* DIF_OP_BA */ { "be", dis_branch }, /* DIF_OP_BE */ { "bne", dis_branch }, /* DIF_OP_BNE */ { "bg", dis_branch }, /* DIF_OP_BG */ { "bgu", dis_branch }, /* DIF_OP_BGU */ { "bge", dis_branch }, /* DIF_OP_BGE */ { "bgeu", dis_branch }, /* DIF_OP_BGEU */ { "bl", dis_branch }, /* DIF_OP_BL */ { "blu", dis_branch }, /* DIF_OP_BLU */ { "ble", dis_branch }, /* DIF_OP_BLE */ { "bleu", dis_branch }, /* DIF_OP_BLEU */ { "ldsb", dis_load }, /* DIF_OP_LDSB */ { "ldsh", dis_load }, /* DIF_OP_LDSH */ { "ldsw", dis_load }, /* DIF_OP_LDSW */ { "ldub", dis_load }, /* DIF_OP_LDUB */ { "lduh", dis_load }, /* DIF_OP_LDUH */ { "lduw", dis_load }, /* DIF_OP_LDUW */ { "ldx", dis_load }, /* DIF_OP_LDX */ { "ret", dis_ret }, /* DIF_OP_RET */ { "nop", dis_str }, /* DIF_OP_NOP */ { "setx", dis_setx }, /* DIF_OP_SETX */ { "sets", dis_sets }, /* DIF_OP_SETS */ { "scmp", dis_cmp }, /* DIF_OP_SCMP */ { "ldga", dis_lda }, /* DIF_OP_LDGA */ { "ldgs", dis_ldv }, /* DIF_OP_LDGS */ { "stgs", dis_stv }, /* DIF_OP_STGS */ { "ldta", dis_lda }, /* DIF_OP_LDTA */ { "ldts", dis_ldv }, /* DIF_OP_LDTS */ { "stts", dis_stv }, /* DIF_OP_STTS */ { "sra", dis_log }, /* DIF_OP_SRA */ { "call", dis_call }, /* DIF_OP_CALL */ { "pushtr", dis_pushts }, /* DIF_OP_PUSHTR */ { "pushtv", dis_pushts }, /* DIF_OP_PUSHTV */ { "popts", dis_str }, /* DIF_OP_POPTS */ { "flushts", dis_str }, /* DIF_OP_FLUSHTS */ { "ldgaa", dis_ldv }, /* DIF_OP_LDGAA */ { "ldtaa", dis_ldv }, /* DIF_OP_LDTAA */ { "stgaa", dis_stv }, /* DIF_OP_STGAA */ { "sttaa", dis_stv }, /* DIF_OP_STTAA */ { "ldls", dis_ldv }, /* DIF_OP_LDLS */ { "stls", dis_stv }, /* DIF_OP_STLS */ { "allocs", dis_r1rd }, /* DIF_OP_ALLOCS */ { "copys", dis_log }, /* DIF_OP_COPYS */ { "stb", dis_store }, /* DIF_OP_STB */ { "sth", dis_store }, /* DIF_OP_STH */ { "stw", dis_store }, /* DIF_OP_STW */ { "stx", dis_store }, /* DIF_OP_STX */ { "uldsb", dis_load }, /* DIF_OP_ULDSB */ { "uldsh", dis_load }, /* DIF_OP_ULDSH */ { "uldsw", dis_load }, /* DIF_OP_ULDSW */ { "uldub", dis_load }, /* DIF_OP_ULDUB */ { "ulduh", dis_load }, /* DIF_OP_ULDUH */ { "ulduw", dis_load }, /* DIF_OP_ULDUW */ { "uldx", dis_load }, /* DIF_OP_ULDX */ { "rldsb", dis_load }, /* DIF_OP_RLDSB */ { "rldsh", dis_load }, /* DIF_OP_RLDSH */ { "rldsw", dis_load }, /* DIF_OP_RLDSW */ { "rldub", dis_load }, /* DIF_OP_RLDUB */ { "rlduh", dis_load }, /* DIF_OP_RLDUH */ { "rlduw", dis_load }, /* DIF_OP_RLDUW */ { "rldx", dis_load }, /* DIF_OP_RLDX */ { "xlate", dis_xlate }, /* DIF_OP_XLATE */ { "xlarg", dis_xlate }, /* DIF_OP_XLARG */ }; dif_instr_t instr, opcode; const struct opent *op; if (mdb_vread(&instr, sizeof (dif_instr_t), addr) == -1) { mdb_warn("failed to read DIF instruction at %p", addr); return (DCMD_ERR); } opcode = DIF_INSTR_OP(instr); if (opcode >= sizeof (optab) / sizeof (optab[0])) opcode = 0; /* force invalid opcode message */ op = &optab[opcode]; mdb_printf("%0?p %08x ", addr, instr); op->op_func(dp, op->op_name, instr); mdb_printf("\n"); mdb_set_dot(addr + sizeof (dif_instr_t)); return (DCMD_OK); } /*ARGSUSED*/ int difo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dtrace_difo_t difo, *dp = &difo; uintptr_t instr, limit; dtrace_difv_t *dvp; size_t varsize; ulong_t i; char type[64]; char *str; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(dp, sizeof (dtrace_difo_t), addr) == -1) { mdb_warn("couldn't read dtrace_difo_t at %p", addr); return (DCMD_ERR); } mdb_printf("%DIF Object 0x%p% (refcnt=%d)\n\n", addr, dp->dtdo_refcnt); mdb_printf("%%-?s %-8s %s%\n", "ADDR", "OPCODE", "INSTRUCTION"); mdb_set_dot((uintmax_t)(uintptr_t)dp->dtdo_buf); limit = (uintptr_t)dp->dtdo_buf + dp->dtdo_len * sizeof (dif_instr_t); while ((instr = mdb_get_dot()) < limit) dis(instr, dp); if (dp->dtdo_varlen != 0) { mdb_printf("\n%%-16s %-4s %-3s %-3s %-4s %s%\n", "NAME", "ID", "KND", "SCP", "FLAG", "TYPE"); } varsize = sizeof (dtrace_difv_t) * dp->dtdo_varlen; dvp = mdb_alloc(varsize, UM_SLEEP | UM_GC); if (mdb_vread(dvp, varsize, (uintptr_t)dp->dtdo_vartab) == -1) { mdb_warn("couldn't read dtdo_vartab"); return (DCMD_ERR); } str = mdb_alloc(dp->dtdo_strlen, UM_SLEEP | UM_GC); if (mdb_vread(str, dp->dtdo_strlen, (uintptr_t)dp->dtdo_strtab) == -1) { mdb_warn("couldn't read dtdo_strtab"); return (DCMD_ERR); } for (i = 0; i < dp->dtdo_varlen; i++) { dtrace_difv_t *v = &dvp[i]; char kind[4], scope[4], flags[16] = { 0 }; switch (v->dtdv_kind) { case DIFV_KIND_ARRAY: (void) strcpy(kind, "arr"); break; case DIFV_KIND_SCALAR: (void) strcpy(kind, "scl"); break; default: (void) mdb_snprintf(kind, sizeof (kind), "%u", v->dtdv_kind); } switch (v->dtdv_scope) { case DIFV_SCOPE_GLOBAL: (void) strcpy(scope, "glb"); break; case DIFV_SCOPE_THREAD: (void) strcpy(scope, "tls"); break; case DIFV_SCOPE_LOCAL: (void) strcpy(scope, "loc"); break; default: (void) mdb_snprintf(scope, sizeof (scope), "%u", v->dtdv_scope); } if (v->dtdv_flags & ~(DIFV_F_REF | DIFV_F_MOD)) { (void) mdb_snprintf(flags, sizeof (flags), "/0x%x", v->dtdv_flags & ~(DIFV_F_REF | DIFV_F_MOD)); } if (v->dtdv_flags & DIFV_F_REF) (void) strcat(flags, "/r"); if (v->dtdv_flags & DIFV_F_MOD) (void) strcat(flags, "/w"); mdb_printf("%-16s %-4x %-3s %-3s %-4s %s\n", &str[v->dtdv_name], v->dtdv_id, kind, scope, flags + 1, dis_typestr(&v->dtdv_type, type, sizeof (type))); } mdb_printf("\n%RETURN%\n%s\n\n", dis_typestr(&dp->dtdo_rtype, type, sizeof (type))); return (DCMD_OK); } /*ARGSUSED*/ int difinstr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); return (dis(addr, NULL)); } /*ARGSUSED*/ int dof_hdr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dof_hdr_t h; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) addr = 0; /* assume base of file in file target */ if (mdb_vread(&h, sizeof (h), addr) != sizeof (h)) { mdb_warn("failed to read header at %p", addr); return (DCMD_ERR); } mdb_printf("dofh_ident.id_magic = 0x%x, %c, %c, %c\n", h.dofh_ident[DOF_ID_MAG0], h.dofh_ident[DOF_ID_MAG1], h.dofh_ident[DOF_ID_MAG2], h.dofh_ident[DOF_ID_MAG3]); switch (h.dofh_ident[DOF_ID_MODEL]) { case DOF_MODEL_ILP32: mdb_printf("dofh_ident.id_model = ILP32\n"); break; case DOF_MODEL_LP64: mdb_printf("dofh_ident.id_model = LP64\n"); break; default: mdb_printf("dofh_ident.id_model = 0x%x\n", h.dofh_ident[DOF_ID_MODEL]); } switch (h.dofh_ident[DOF_ID_ENCODING]) { case DOF_ENCODE_LSB: mdb_printf("dofh_ident.id_encoding = LSB\n"); break; case DOF_ENCODE_MSB: mdb_printf("dofh_ident.id_encoding = MSB\n"); break; default: mdb_printf("dofh_ident.id_encoding = 0x%x\n", h.dofh_ident[DOF_ID_ENCODING]); } mdb_printf("dofh_ident.id_version = %u\n", h.dofh_ident[DOF_ID_VERSION]); mdb_printf("dofh_ident.id_difvers = %u\n", h.dofh_ident[DOF_ID_DIFVERS]); mdb_printf("dofh_ident.id_difireg = %u\n", h.dofh_ident[DOF_ID_DIFIREG]); mdb_printf("dofh_ident.id_diftreg = %u\n", h.dofh_ident[DOF_ID_DIFTREG]); mdb_printf("dofh_flags = 0x%x\n", h.dofh_flags); mdb_printf("dofh_hdrsize = %u\n", h.dofh_hdrsize); mdb_printf("dofh_secsize = %u\n", h.dofh_secsize); mdb_printf("dofh_secnum = %u\n", h.dofh_secnum); mdb_printf("dofh_secoff = %llu\n", h.dofh_secoff); mdb_printf("dofh_loadsz = %llu\n", h.dofh_loadsz); mdb_printf("dofh_filesz = %llu\n", h.dofh_filesz); return (DCMD_OK); } /*ARGSUSED*/ static int dof_sec_walk(uintptr_t addr, void *ignored, int *sec) { mdb_printf("%3d ", (*sec)++); (void) dof_sec(addr, DCMD_ADDRSPEC | DCMD_LOOP, 0, NULL); return (WALK_NEXT); } /*ARGSUSED*/ int dof_sec(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *name; dof_sec_t s; if (!(flags & DCMD_ADDRSPEC)) mdb_printf("%%-3s ", "NDX"); if (!(flags & DCMD_ADDRSPEC) || DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %-10s %-5s %-5s %-5s %-6s %-5s%\n", "ADDR", "TYPE", "ALIGN", "FLAGS", "ENTSZ", "OFFSET", "SIZE"); } if (!(flags & DCMD_ADDRSPEC)) { int sec = 0; if (mdb_walk("dof_sec", (mdb_walk_cb_t)dof_sec_walk, &sec) == -1) { mdb_warn("failed to walk dof_sec"); return (DCMD_ERR); } return (DCMD_OK); } if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&s, sizeof (s), addr) != sizeof (s)) { mdb_warn("failed to read section header at %p", addr); return (DCMD_ERR); } mdb_printf("%?p ", addr); if ((name = dof_sec_name(s.dofs_type)) != NULL) mdb_printf("%-10s ", name); else mdb_printf("%-10u ", s.dofs_type); mdb_printf("%-5u %-#5x %-#5x %-6llx %-#5llx\n", s.dofs_align, s.dofs_flags, s.dofs_entsize, s.dofs_offset, s.dofs_size); return (DCMD_OK); } int dof_sec_walk_init(mdb_walk_state_t *wsp) { dof_hdr_t h, *hp; size_t size; if (mdb_vread(&h, sizeof (h), wsp->walk_addr) != sizeof (h)) { mdb_warn("failed to read DOF header at %p", wsp->walk_addr); return (WALK_ERR); } size = sizeof (dof_hdr_t) + sizeof (dof_sec_t) * h.dofh_secnum; hp = mdb_alloc(size, UM_SLEEP); if (mdb_vread(hp, size, wsp->walk_addr) != size) { mdb_warn("failed to read DOF sections at %p", wsp->walk_addr); mdb_free(hp, size); return (WALK_ERR); } wsp->walk_arg = (void *)0; wsp->walk_data = hp; return (WALK_NEXT); } int dof_sec_walk_step(mdb_walk_state_t *wsp) { uint_t i = (uintptr_t)wsp->walk_arg; size_t off = sizeof (dof_hdr_t) + sizeof (dof_sec_t) * i; dof_hdr_t *hp = wsp->walk_data; dof_sec_t *sp = (dof_sec_t *)((uintptr_t)hp + off); if (i >= hp->dofh_secnum) return (WALK_DONE); wsp->walk_arg = (void *)(uintptr_t)(i + 1); return (wsp->walk_callback(wsp->walk_addr + off, sp, wsp->walk_cbdata)); } void dof_sec_walk_fini(mdb_walk_state_t *wsp) { dof_hdr_t *hp = wsp->walk_data; mdb_free(hp, sizeof (dof_hdr_t) + sizeof (dof_sec_t) * hp->dofh_secnum); } /*ARGSUSED*/ int dof_ecbdesc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dof_ecbdesc_t e; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&e, sizeof (e), addr) != sizeof (e)) { mdb_warn("failed to read ecbdesc at %p", addr); return (DCMD_ERR); } mdb_printf("dofe_probes = %d\n", e.dofe_probes); mdb_printf("dofe_actions = %d\n", e.dofe_actions); mdb_printf("dofe_pred = %d\n", e.dofe_pred); mdb_printf("dofe_uarg = 0x%llx\n", e.dofe_uarg); return (DCMD_OK); } /*ARGSUSED*/ int dof_probedesc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dof_probedesc_t p; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&p, sizeof (p), addr) != sizeof (p)) { mdb_warn("failed to read probedesc at %p", addr); return (DCMD_ERR); } mdb_printf("dofp_strtab = %d\n", p.dofp_strtab); mdb_printf("dofp_provider = %u\n", p.dofp_provider); mdb_printf("dofp_mod = %u\n", p.dofp_mod); mdb_printf("dofp_func = %u\n", p.dofp_func); mdb_printf("dofp_name = %u\n", p.dofp_name); mdb_printf("dofp_id = %u\n", p.dofp_id); return (DCMD_OK); } /*ARGSUSED*/ int dof_actdesc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dof_actdesc_t a; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&a, sizeof (a), addr) != sizeof (a)) { mdb_warn("failed to read actdesc at %p", addr); return (DCMD_ERR); } mdb_printf("dofa_difo = %d\n", a.dofa_difo); mdb_printf("dofa_strtab = %d\n", a.dofa_strtab); mdb_printf("dofa_kind = %u\n", a.dofa_kind); mdb_printf("dofa_ntuple = %u\n", a.dofa_ntuple); mdb_printf("dofa_arg = 0x%llx\n", a.dofa_arg); mdb_printf("dofa_uarg = 0x%llx\n", a.dofa_uarg); return (DCMD_OK); } /*ARGSUSED*/ int dof_relohdr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dof_relohdr_t r; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&r, sizeof (r), addr) != sizeof (r)) { mdb_warn("failed to read relohdr at %p", addr); return (DCMD_ERR); } mdb_printf("dofr_strtab = %d\n", r.dofr_strtab); mdb_printf("dofr_relsec = %d\n", r.dofr_relsec); mdb_printf("dofr_tgtsec = %d\n", r.dofr_tgtsec); return (DCMD_OK); } /*ARGSUSED*/ int dof_relodesc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dof_relodesc_t r; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&r, sizeof (r), addr) != sizeof (r)) { mdb_warn("failed to read relodesc at %p", addr); return (DCMD_ERR); } mdb_printf("dofr_name = %u\n", r.dofr_name); mdb_printf("dofr_type = %u\n", r.dofr_type); mdb_printf("dofr_offset = 0x%llx\n", r.dofr_offset); mdb_printf("dofr_data = 0x%llx\n", r.dofr_data); return (DCMD_OK); } static int dof_sect_strtab(uintptr_t addr, dof_sec_t *sec) { char *strtab; size_t sz, i; sz = (size_t)sec->dofs_size; strtab = mdb_alloc(sz, UM_SLEEP | UM_GC); if (mdb_vread(strtab, sz, addr + sec->dofs_offset) != sz) { mdb_warn("failed to read string table"); return (1); } mdb_printf("size = %lx\n", sz); for (i = 0; i < sz; i++) { if (strtab[i] == '\0') mdb_printf("\\0"); else mdb_printf("%c", strtab[i]); } mdb_printf("\n"); return (0); } static int dof_sect_provider(dof_hdr_t *dofh, uintptr_t addr, dof_sec_t *sec, dof_sec_t *dofs) { dof_provider_t pv; dof_probe_t *pb; char *strtab, *p; uint32_t *offs, *enoffs; uint8_t *args = NULL; size_t sz; int i, j; dof_stridx_t narg, xarg; sz = MIN(sec->dofs_size, sizeof (dof_provider_t)); if (mdb_vread(&pv, sz, addr + sec->dofs_offset) != sz) { mdb_warn("failed to read DOF provider"); return (-1); } sz = dofs[pv.dofpv_strtab].dofs_size; strtab = mdb_alloc(sz, UM_SLEEP | UM_GC); if (mdb_vread(strtab, sz, addr + dofs[pv.dofpv_strtab].dofs_offset) != sz) { mdb_warn("failed to read string table"); return (-1); } mdb_printf("%lx provider %s {\n", (ulong_t)(addr + sec->dofs_offset), strtab + pv.dofpv_name); sz = dofs[pv.dofpv_prargs].dofs_size; if (sz != 0) { args = mdb_alloc(sz, UM_SLEEP | UM_GC); if (mdb_vread(args, sz, addr + dofs[pv.dofpv_prargs].dofs_offset) != sz) { mdb_warn("failed to read args"); return (-1); } } sz = dofs[pv.dofpv_proffs].dofs_size; offs = mdb_alloc(sz, UM_SLEEP | UM_GC); if (mdb_vread(offs, sz, addr + dofs[pv.dofpv_proffs].dofs_offset) != sz) { mdb_warn("failed to read offsets"); return (-1); } enoffs = NULL; if (dofh->dofh_ident[DOF_ID_VERSION] != DOF_VERSION_1 || pv.dofpv_prenoffs == 0) { sz = dofs[pv.dofpv_prenoffs].dofs_size; enoffs = mdb_alloc(sz, UM_SLEEP | UM_GC); if (mdb_vread(enoffs, sz, addr + dofs[pv.dofpv_prenoffs].dofs_offset) != sz) { mdb_warn("failed to read is-enabled offsets"); return (-1); } } sz = dofs[pv.dofpv_probes].dofs_size; p = mdb_alloc(sz, UM_SLEEP | UM_GC); if (mdb_vread(p, sz, addr + dofs[pv.dofpv_probes].dofs_offset) != sz) { mdb_warn("failed to read probes"); return (-1); } (void) mdb_inc_indent(2); for (i = 0; i < sz / dofs[pv.dofpv_probes].dofs_entsize; i++) { pb = (dof_probe_t *)(uintptr_t)(p + i * dofs[pv.dofpv_probes].dofs_entsize); mdb_printf("%lx probe %s:%s {\n", (ulong_t)(addr + dofs[pv.dofpv_probes].dofs_offset + i * dofs[pv.dofpv_probes].dofs_entsize), strtab + pb->dofpr_func, strtab + pb->dofpr_name); (void) mdb_inc_indent(2); mdb_printf("addr: %p\n", (ulong_t)pb->dofpr_addr); mdb_printf("offs: "); for (j = 0; j < pb->dofpr_noffs; j++) { mdb_printf("%s %x", "," + (j == 0), offs[pb->dofpr_offidx + j]); } mdb_printf("\n"); if (dofh->dofh_ident[DOF_ID_VERSION] != DOF_VERSION_1) { mdb_printf("enoffs: "); if (enoffs == NULL) { if (pb->dofpr_nenoffs != 0) mdb_printf(""); } else { for (j = 0; j < pb->dofpr_nenoffs; j++) { mdb_printf("%s %x", "," + (j == 0), enoffs[pb->dofpr_enoffidx + j]); } } mdb_printf("\n"); } mdb_printf("nargs:"); narg = pb->dofpr_nargv; for (j = 0; j < pb->dofpr_nargc; j++) { mdb_printf("%s %s", "," + (j == 0), strtab + narg); narg += strlen(strtab + narg) + 1; } mdb_printf("\n"); mdb_printf("xargs:"); xarg = pb->dofpr_xargv; for (j = 0; j < pb->dofpr_xargc; j++) { mdb_printf("%s %s", "," + (j == 0), strtab + xarg); xarg += strlen(strtab + xarg) + 1; } mdb_printf("\n"); mdb_printf("map: "); for (j = 0; j < pb->dofpr_xargc; j++) { mdb_printf("%s %d->%d", "," + (j == 0), args[pb->dofpr_argidx + j], j); } (void) mdb_dec_indent(2); mdb_printf("\n}\n"); } (void) mdb_dec_indent(2); mdb_printf("}\n"); return (0); } static int dof_sect_prargs(uintptr_t addr, dof_sec_t *sec) { int i; uint8_t arg; for (i = 0; i < sec->dofs_size; i++) { if (mdb_vread(&arg, sizeof (arg), addr + sec->dofs_offset + i) != sizeof (arg)) { mdb_warn("failed to read argument"); return (1); } mdb_printf("%d ", arg); if (i % 20 == 19) mdb_printf("\n"); } mdb_printf("\n"); return (0); } /*ARGSUSED*/ static int dofdump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dof_hdr_t dofh; dof_sec_t *dofs; const char *name; int i; if (mdb_vread(&dofh, sizeof (dof_hdr_t), addr) != sizeof (dof_hdr_t)) { mdb_warn("failed to read DOF header"); return (DCMD_ERR); } dofs = mdb_alloc(sizeof (dof_sec_t) * dofh.dofh_secnum, UM_SLEEP | UM_GC); for (i = 0; i < dofh.dofh_secnum; i++) { if (mdb_vread(&dofs[i], sizeof (dof_sec_t), dofh.dofh_secoff + addr + i * dofh.dofh_secsize) != sizeof (dof_sec_t)) { mdb_warn("failed to read DOF sections"); return (DCMD_ERR); } } for (i = 0; i < dofh.dofh_secnum; i++) { mdb_printf("%lx Section %d: ", (ulong_t) (dofh.dofh_secoff + addr + i * dofh.dofh_secsize), i); if ((name = dof_sec_name(dofs[i].dofs_type)) != NULL) mdb_printf("%s\n", name); else mdb_printf("%u\n", dofs[i].dofs_type); (void) mdb_inc_indent(2); switch (dofs[i].dofs_type) { case DOF_SECT_PROVIDER: (void) dof_sect_provider(&dofh, addr, &dofs[i], dofs); break; case DOF_SECT_STRTAB: (void) dof_sect_strtab(addr, &dofs[i]); break; case DOF_SECT_PRARGS: (void) dof_sect_prargs(addr, &dofs[i]); break; } (void) mdb_dec_indent(2); mdb_printf("\n"); } return (DCMD_OK); } static const mdb_dcmd_t common_dcmds[] = { { "difinstr", ":", "disassemble a DIF instruction", difinstr }, { "difo", ":", "print a DIF object", difo }, { "dof_hdr", "?", "print a DOF header", dof_hdr }, { "dof_sec", ":", "print a DOF section header", dof_sec }, { "dof_ecbdesc", ":", "print a DOF ecbdesc", dof_ecbdesc }, { "dof_probedesc", ":", "print a DOF probedesc", dof_probedesc }, { "dof_actdesc", ":", "print a DOF actdesc", dof_actdesc }, { "dof_relohdr", ":", "print a DOF relocation header", dof_relohdr }, { "dof_relodesc", ":", "print a DOF relodesc", dof_relodesc }, { "dofdump", ":", "dump DOF", dofdump }, { NULL } }; static const mdb_walker_t common_walkers[] = { { "dof_sec", "walk DOF section header table given header address", dof_sec_walk_init, dof_sec_walk_step, dof_sec_walk_fini }, { NULL } }; static mdb_modinfo_t modinfo = { MDB_API_VERSION, NULL, NULL }; const mdb_modinfo_t * _mdb_init(void) { uint_t d = 0, kd = 0, w = 0, kw = 0; const mdb_walker_t *wp; const mdb_dcmd_t *dp; for (dp = common_dcmds; dp->dc_name != NULL; dp++) d++; /* count common dcmds */ for (wp = common_walkers; wp->walk_name != NULL; wp++) w++; /* count common walkers */ #ifdef _KERNEL for (dp = kernel_dcmds; dp->dc_name != NULL; dp++) kd++; /* count kernel dcmds */ for (wp = kernel_walkers; wp->walk_name != NULL; wp++) kw++; /* count common walkers */ #endif modinfo.mi_dcmds = mdb_zalloc(sizeof (*dp) * (d + kd + 1), UM_SLEEP); modinfo.mi_walkers = mdb_zalloc(sizeof (*wp) * (w + kw + 1), UM_SLEEP); bcopy(common_dcmds, (void *)modinfo.mi_dcmds, sizeof (*dp) * d); bcopy(common_walkers, (void *)modinfo.mi_walkers, sizeof (*wp) * w); #ifdef _KERNEL bcopy(kernel_dcmds, (void *) (modinfo.mi_dcmds + d), sizeof (*dp) * kd); bcopy(kernel_walkers, (void *) (modinfo.mi_walkers + w), sizeof (*wp) * kw); #endif return (&modinfo); } void _mdb_fini(void) { const mdb_walker_t *wp; const mdb_dcmd_t *dp; uint_t d = 0, w = 0; for (dp = modinfo.mi_dcmds; dp->dc_name != NULL; dp++) d++; for (wp = modinfo.mi_walkers; wp->walk_name != NULL; wp++) w++; mdb_free((void *)modinfo.mi_dcmds, sizeof (*dp) * (d + 1)); mdb_free((void *)modinfo.mi_walkers, sizeof (*wp) * (w + 1)); } /* * 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 (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013 by Delphix. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright 2022 Racktop Systems, Inc. * Copyright 2025 Oxide Computer Company */ /* * explicitly define DTRACE_ERRDEBUG to pull in definition of dtrace_errhash_t * explicitly define _STDARG_H to avoid stdarg.h/varargs.h u/k defn conflict */ #define DTRACE_ERRDEBUG #define _STDARG_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*ARGSUSED*/ int id2probe(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t probe = 0; uintptr_t probes; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (addr == DTRACE_IDNONE || addr > UINT32_MAX) goto out; if (mdb_readvar(&probes, "dtrace_probes") == -1) { mdb_warn("failed to read 'dtrace_probes'"); return (DCMD_ERR); } probes += (addr - 1) * sizeof (dtrace_probe_t *); if (mdb_vread(&probe, sizeof (uintptr_t), probes) == -1) { mdb_warn("failed to read dtrace_probes[%d]", addr - 1); return (DCMD_ERR); } out: mdb_printf("%p\n", probe); return (DCMD_OK); } void dtrace_help(void) { mdb_printf("Given a dtrace_state_t structure that represents a " "DTrace consumer, prints\n" "dtrace(8)-like output for in-kernel DTrace data. (The " "dtrace_state_t\n" "structures for all DTrace consumers may be obtained by running " "the \n" "::dtrace_state dcmd.) When data is present on multiple CPUs, " "data are\n" "presented in CPU order, with records within each CPU ordered " "oldest to \n" "youngest. Options:\n\n" "-c cpu Only provide output for specified CPU.\n"); } static int dtracemdb_eprobe(dtrace_state_t *state, dtrace_eprobedesc_t *epd) { dtrace_epid_t epid = epd->dtepd_epid; dtrace_probe_t probe; dtrace_ecb_t ecb; uintptr_t addr, paddr, ap; dtrace_action_t act; int nactions, nrecs; addr = (uintptr_t)state->dts_ecbs + (epid - 1) * sizeof (dtrace_ecb_t *); if (mdb_vread(&addr, sizeof (addr), addr) == -1) { mdb_warn("failed to read ecb for epid %d", epid); return (-1); } if (addr == 0) { mdb_warn("epid %d doesn't match an ecb\n", epid); return (-1); } if (mdb_vread(&ecb, sizeof (ecb), addr) == -1) { mdb_warn("failed to read ecb at %p", addr); return (-1); } paddr = (uintptr_t)ecb.dte_probe; if (mdb_vread(&probe, sizeof (probe), paddr) == -1) { mdb_warn("failed to read probe for ecb %p", addr); return (-1); } /* * This is a little painful: in order to find the number of actions, * we need to first walk through them. */ for (ap = (uintptr_t)ecb.dte_action, nactions = 0; ap != 0; ) { if (mdb_vread(&act, sizeof (act), ap) == -1) { mdb_warn("failed to read action %p on ecb %p", ap, addr); return (-1); } if (!DTRACEACT_ISAGG(act.dta_kind) && !act.dta_intuple) nactions++; ap = (uintptr_t)act.dta_next; } nrecs = epd->dtepd_nrecs; epd->dtepd_nrecs = nactions; epd->dtepd_probeid = probe.dtpr_id; epd->dtepd_uarg = ecb.dte_uarg; epd->dtepd_size = ecb.dte_size; for (ap = (uintptr_t)ecb.dte_action, nactions = 0; ap != 0; ) { if (mdb_vread(&act, sizeof (act), ap) == -1) { mdb_warn("failed to read action %p on ecb %p", ap, addr); return (-1); } if (!DTRACEACT_ISAGG(act.dta_kind) && !act.dta_intuple) { if (nrecs-- == 0) break; epd->dtepd_rec[nactions++] = act.dta_rec; } ap = (uintptr_t)act.dta_next; } return (0); } /*ARGSUSED*/ static int dtracemdb_probe(dtrace_state_t *state, dtrace_probedesc_t *pd) { uintptr_t base, addr, paddr, praddr; int nprobes, i; dtrace_probe_t probe; dtrace_provider_t prov; if (pd->dtpd_id == DTRACE_IDNONE) pd->dtpd_id++; if (mdb_readvar(&base, "dtrace_probes") == -1) { mdb_warn("failed to read 'dtrace_probes'"); return (-1); } if (mdb_readvar(&nprobes, "dtrace_nprobes") == -1) { mdb_warn("failed to read 'dtrace_nprobes'"); return (-1); } for (i = pd->dtpd_id; i <= nprobes; i++) { addr = base + (i - 1) * sizeof (dtrace_probe_t *); if (mdb_vread(&paddr, sizeof (paddr), addr) == -1) { mdb_warn("couldn't read probe pointer at %p", addr); return (-1); } if (paddr != 0) break; } if (paddr == 0) { errno = ESRCH; return (-1); } if (mdb_vread(&probe, sizeof (probe), paddr) == -1) { mdb_warn("couldn't read probe at %p", paddr); return (-1); } pd->dtpd_id = probe.dtpr_id; if (mdb_vread(pd->dtpd_name, DTRACE_NAMELEN, (uintptr_t)probe.dtpr_name) == -1) { mdb_warn("failed to read probe name for probe %p", paddr); return (-1); } if (mdb_vread(pd->dtpd_func, DTRACE_FUNCNAMELEN, (uintptr_t)probe.dtpr_func) == -1) { mdb_warn("failed to read function name for probe %p", paddr); return (-1); } if (mdb_vread(pd->dtpd_mod, DTRACE_MODNAMELEN, (uintptr_t)probe.dtpr_mod) == -1) { mdb_warn("failed to read module name for probe %p", paddr); return (-1); } praddr = (uintptr_t)probe.dtpr_provider; if (mdb_vread(&prov, sizeof (prov), praddr) == -1) { mdb_warn("failed to read provider for probe %p", paddr); return (-1); } if (mdb_vread(pd->dtpd_provider, DTRACE_PROVNAMELEN, (uintptr_t)prov.dtpv_name) == -1) { mdb_warn("failed to read provider name for probe %p", paddr); return (-1); } return (0); } /*ARGSUSED*/ static int dtracemdb_aggdesc(dtrace_state_t *state, dtrace_aggdesc_t *agd) { dtrace_aggid_t aggid = agd->dtagd_id; dtrace_aggregation_t agg; dtrace_ecb_t ecb; uintptr_t addr, eaddr, ap, last; dtrace_action_t act; dtrace_recdesc_t *lrec; int nactions, nrecs; addr = (uintptr_t)state->dts_aggregations + (aggid - 1) * sizeof (dtrace_aggregation_t *); if (mdb_vread(&addr, sizeof (addr), addr) == -1) { mdb_warn("failed to read aggregation for aggid %d", aggid); return (-1); } if (addr == 0) { mdb_warn("aggid %d doesn't match an aggregation\n", aggid); return (-1); } if (mdb_vread(&agg, sizeof (agg), addr) == -1) { mdb_warn("failed to read aggregation at %p", addr); return (-1); } eaddr = (uintptr_t)agg.dtag_ecb; if (mdb_vread(&ecb, sizeof (ecb), eaddr) == -1) { mdb_warn("failed to read ecb for aggregation %p", addr); return (-1); } last = (uintptr_t)addr + offsetof(dtrace_aggregation_t, dtag_action); /* * This is a little painful: in order to find the number of actions, * we need to first walk through them. */ ap = (uintptr_t)agg.dtag_first; nactions = 0; for (;;) { if (mdb_vread(&act, sizeof (act), ap) == -1) { mdb_warn("failed to read action %p on aggregation %p", ap, addr); return (-1); } nactions++; if (ap == last) break; ap = (uintptr_t)act.dta_next; } lrec = &act.dta_rec; agd->dtagd_size = lrec->dtrd_offset + lrec->dtrd_size - agg.dtag_base; nrecs = agd->dtagd_nrecs; agd->dtagd_nrecs = nactions; agd->dtagd_epid = ecb.dte_epid; ap = (uintptr_t)agg.dtag_first; nactions = 0; for (;;) { dtrace_recdesc_t rec; if (mdb_vread(&act, sizeof (act), ap) == -1) { mdb_warn("failed to read action %p on aggregation %p", ap, addr); return (-1); } if (nrecs-- == 0) break; rec = act.dta_rec; rec.dtrd_offset -= agg.dtag_base; rec.dtrd_uarg = 0; agd->dtagd_rec[nactions++] = rec; if (ap == last) break; ap = (uintptr_t)act.dta_next; } return (0); } static int dtracemdb_bufsnap(dtrace_buffer_t *which, dtrace_bufdesc_t *desc) { static hrtime_t hr_offset = 0; static boolean_t offset_set = B_FALSE; uintptr_t addr; size_t bufsize; dtrace_buffer_t buf; caddr_t data = desc->dtbd_data; processorid_t max_cpuid, cpu = desc->dtbd_cpu; if (mdb_readvar(&max_cpuid, "max_cpuid") == -1) { mdb_warn("failed to read 'max_cpuid'"); errno = EIO; return (-1); } if (cpu < 0 || cpu > max_cpuid) { errno = EINVAL; return (-1); } addr = (uintptr_t)which + cpu * sizeof (dtrace_buffer_t); if (mdb_vread(&buf, sizeof (buf), addr) == -1) { mdb_warn("failed to read buffer description at %p", addr); errno = EIO; return (-1); } if (buf.dtb_tomax == NULL) { errno = ENOENT; return (-1); } if (buf.dtb_flags & DTRACEBUF_WRAPPED) { bufsize = buf.dtb_size; } else { bufsize = buf.dtb_offset; } if (mdb_vread(data, bufsize, (uintptr_t)buf.dtb_tomax) == -1) { mdb_warn("couldn't read buffer for CPU %d", cpu); errno = EIO; return (-1); } if (buf.dtb_offset > buf.dtb_size) { mdb_warn("buffer for CPU %d has corrupt offset\n", cpu); errno = EIO; return (-1); } if (buf.dtb_flags & DTRACEBUF_WRAPPED) { if (buf.dtb_xamot_offset > buf.dtb_size) { mdb_warn("ringbuffer for CPU %d has corrupt " "wrapped offset\n", cpu); errno = EIO; return (-1); } /* * If the ring buffer has wrapped, it needs to be polished. * See the comment in dtrace_buffer_polish() for details. */ if (buf.dtb_offset < buf.dtb_xamot_offset) { bzero(data + buf.dtb_offset, buf.dtb_xamot_offset - buf.dtb_offset); } if (buf.dtb_offset > buf.dtb_xamot_offset) { bzero(data + buf.dtb_offset, buf.dtb_size - buf.dtb_offset); bzero(data, buf.dtb_xamot_offset); } desc->dtbd_oldest = buf.dtb_xamot_offset; } else { desc->dtbd_oldest = 0; } /* * On a live system, dtbd_timestamp is set to gethrtime() when the * DTRACEIOC_BUFSNAP ioctl is called. The effect of this is that the * timestamps of all the enabled probe records in the buf will always * be less than dtbd_timestamp. dtrace_consume() relies on this * invariant to determine when it needs to retrieve more dtrace bufs * from the kernel. * * However when mdb is reading a crash dump, the value of * gethrtime() on the system running mdb may smaller than the * enabled probe records in the crash dump, violating the invariant * dtrace_consume() is relying on. This can cause dtrace_consume() * to prematurely stop processing records. * * To preserve the invariant dtrace_consume() requires, we simply * add the value of panic_hrtime to gethrtime() when setting * dtdb_timestamp. On a live system, panic_hrtime will be 0, and * the invariant will be preserved by virtue of being running on * a live system. On a crash dump, no valid probe record can have a * timestamp greater than panic_hrtime, so adding this to the value * of gethrtime() will guarantee the invariant expected by * dtrace_consume() is preserved. */ if (!offset_set) { hrtime_t panic_hrtime; /* * We could be slightly more clever and only set hr_offset * if gethrtime() in mdb is < panic_hrtime, but it doesn't * seem necessary. If for some reason, we cannot read * panic_hrtime, we'll try to continue -- ::dtrace may * still succeed, so we just warn and continue. */ if (mdb_readvar(&panic_hrtime, "panic_hrtime") == -1) { mdb_warn("failed to read 'panic_hrtime' -- " "some dtrace data may not be displayed"); } else { hr_offset = panic_hrtime; } offset_set = B_TRUE; } desc->dtbd_size = bufsize; desc->dtbd_drops = buf.dtb_drops; desc->dtbd_errors = buf.dtb_errors; desc->dtbd_timestamp = gethrtime() + hr_offset; return (0); } /* * This is essentially identical to its cousin in the kernel -- with the * notable exception that we automatically set DTRACEOPT_GRABANON if this * state is an anonymous enabling. */ static dof_hdr_t * dtracemdb_dof_create(dtrace_state_t *state, int isanon) { dof_hdr_t *dof; dof_sec_t *sec; dof_optdesc_t *opt; int i, len = sizeof (dof_hdr_t) + roundup(sizeof (dof_sec_t), sizeof (uint64_t)) + sizeof (dof_optdesc_t) * DTRACEOPT_MAX; dof = mdb_zalloc(len, UM_SLEEP); dof->dofh_ident[DOF_ID_MAG0] = DOF_MAG_MAG0; dof->dofh_ident[DOF_ID_MAG1] = DOF_MAG_MAG1; dof->dofh_ident[DOF_ID_MAG2] = DOF_MAG_MAG2; dof->dofh_ident[DOF_ID_MAG3] = DOF_MAG_MAG3; dof->dofh_ident[DOF_ID_MODEL] = DOF_MODEL_NATIVE; dof->dofh_ident[DOF_ID_ENCODING] = DOF_ENCODE_NATIVE; dof->dofh_ident[DOF_ID_VERSION] = DOF_VERSION; dof->dofh_ident[DOF_ID_DIFVERS] = DIF_VERSION; dof->dofh_ident[DOF_ID_DIFIREG] = DIF_DIR_NREGS; dof->dofh_ident[DOF_ID_DIFTREG] = DIF_DTR_NREGS; dof->dofh_flags = 0; dof->dofh_hdrsize = sizeof (dof_hdr_t); dof->dofh_secsize = sizeof (dof_sec_t); dof->dofh_secnum = 1; /* only DOF_SECT_OPTDESC */ dof->dofh_secoff = sizeof (dof_hdr_t); dof->dofh_loadsz = len; dof->dofh_filesz = len; dof->dofh_pad = 0; /* * Fill in the option section header... */ sec = (dof_sec_t *)((uintptr_t)dof + sizeof (dof_hdr_t)); sec->dofs_type = DOF_SECT_OPTDESC; sec->dofs_align = sizeof (uint64_t); sec->dofs_flags = DOF_SECF_LOAD; sec->dofs_entsize = sizeof (dof_optdesc_t); opt = (dof_optdesc_t *)((uintptr_t)sec + roundup(sizeof (dof_sec_t), sizeof (uint64_t))); sec->dofs_offset = (uintptr_t)opt - (uintptr_t)dof; sec->dofs_size = sizeof (dof_optdesc_t) * DTRACEOPT_MAX; for (i = 0; i < DTRACEOPT_MAX; i++) { opt[i].dofo_option = i; opt[i].dofo_strtab = DOF_SECIDX_NONE; opt[i].dofo_value = state->dts_options[i]; } if (isanon) opt[DTRACEOPT_GRABANON].dofo_value = 1; return (dof); } static int dtracemdb_format(dtrace_state_t *state, dtrace_fmtdesc_t *desc) { uintptr_t addr, faddr; char c; int len = 0; if (desc->dtfd_format == 0 || desc->dtfd_format > state->dts_nformats) { errno = EINVAL; return (-1); } faddr = (uintptr_t)state->dts_formats + (desc->dtfd_format - 1) * sizeof (char *); if (mdb_vread(&addr, sizeof (addr), faddr) == -1) { mdb_warn("failed to read format string pointer at %p", faddr); return (-1); } do { if (mdb_vread(&c, sizeof (c), addr + len++) == -1) { mdb_warn("failed to read format string at %p", addr); return (-1); } } while (c != '\0'); if (len > desc->dtfd_length) { desc->dtfd_length = len; return (0); } if (mdb_vread(desc->dtfd_string, len, addr) == -1) { mdb_warn("failed to reread format string at %p", addr); return (-1); } return (0); } static int dtracemdb_status(dtrace_state_t *state, dtrace_status_t *status) { dtrace_dstate_t *dstate; int i, j; uint64_t nerrs; uintptr_t addr; int ncpu; if (mdb_readvar(&ncpu, "_ncpu") == -1) { mdb_warn("failed to read '_ncpu'"); return (DCMD_ERR); } bzero(status, sizeof (dtrace_status_t)); if (state->dts_activity == DTRACE_ACTIVITY_INACTIVE) { errno = ENOENT; return (-1); } /* * For the MDB backend, we never set dtst_exiting or dtst_filled. This * is by design: we don't want the library to try to stop tracing, * because it doesn't particularly mean anything. */ nerrs = state->dts_errors; dstate = &state->dts_vstate.dtvs_dynvars; for (i = 0; i < ncpu; i++) { dtrace_dstate_percpu_t dcpu; dtrace_buffer_t buf; addr = (uintptr_t)&dstate->dtds_percpu[i]; if (mdb_vread(&dcpu, sizeof (dcpu), addr) == -1) { mdb_warn("failed to read per-CPU dstate at %p", addr); return (-1); } status->dtst_dyndrops += dcpu.dtdsc_drops; status->dtst_dyndrops_dirty += dcpu.dtdsc_dirty_drops; status->dtst_dyndrops_rinsing += dcpu.dtdsc_rinsing_drops; addr = (uintptr_t)&state->dts_buffer[i]; if (mdb_vread(&buf, sizeof (buf), addr) == -1) { mdb_warn("failed to read per-CPU buffer at %p", addr); return (-1); } nerrs += buf.dtb_errors; for (j = 0; j < state->dts_nspeculations; j++) { dtrace_speculation_t spec; addr = (uintptr_t)&state->dts_speculations[j]; if (mdb_vread(&spec, sizeof (spec), addr) == -1) { mdb_warn("failed to read " "speculation at %p", addr); return (-1); } addr = (uintptr_t)&spec.dtsp_buffer[i]; if (mdb_vread(&buf, sizeof (buf), addr) == -1) { mdb_warn("failed to read " "speculative buffer at %p", addr); return (-1); } status->dtst_specdrops += buf.dtb_xamot_drops; } } status->dtst_specdrops_busy = state->dts_speculations_busy; status->dtst_specdrops_unavail = state->dts_speculations_unavail; status->dtst_errors = nerrs; return (0); } typedef struct dtracemdb_data { dtrace_state_t *dtmd_state; char *dtmd_symstr; char *dtmd_modstr; uintptr_t dtmd_addr; int dtmd_isanon; } dtracemdb_data_t; static int dtracemdb_ioctl(void *varg, int cmd, void *arg) { dtracemdb_data_t *data = varg; dtrace_state_t *state = data->dtmd_state; switch (cmd) { case DTRACEIOC_CONF: { dtrace_conf_t *conf = arg; bzero(conf, sizeof (conf)); conf->dtc_difversion = DIF_VERSION; conf->dtc_difintregs = DIF_DIR_NREGS; conf->dtc_diftupregs = DIF_DTR_NREGS; conf->dtc_ctfmodel = CTF_MODEL_NATIVE; return (0); } case DTRACEIOC_DOFGET: { dof_hdr_t *hdr = arg, *dof; dof = dtracemdb_dof_create(state, data->dtmd_isanon); bcopy(dof, hdr, MIN(hdr->dofh_loadsz, dof->dofh_loadsz)); mdb_free(dof, dof->dofh_loadsz); return (0); } case DTRACEIOC_BUFSNAP: return (dtracemdb_bufsnap(state->dts_buffer, arg)); case DTRACEIOC_AGGSNAP: return (dtracemdb_bufsnap(state->dts_aggbuffer, arg)); case DTRACEIOC_AGGDESC: return (dtracemdb_aggdesc(state, arg)); case DTRACEIOC_EPROBE: return (dtracemdb_eprobe(state, arg)); case DTRACEIOC_PROBES: return (dtracemdb_probe(state, arg)); case DTRACEIOC_FORMAT: return (dtracemdb_format(state, arg)); case DTRACEIOC_STATUS: return (dtracemdb_status(state, arg)); case DTRACEIOC_GO: *(processorid_t *)arg = -1; return (0); case DTRACEIOC_ENABLE: errno = ENOTTY; /* see dt_open.c:dtrace_go() */ return (-1); case DTRACEIOC_PROVIDER: case DTRACEIOC_PROBEMATCH: errno = ESRCH; return (-1); default: mdb_warn("unexpected ioctl 0x%x (%s)\n", cmd, cmd == DTRACEIOC_PROVIDER ? "DTRACEIOC_PROVIDER" : cmd == DTRACEIOC_PROBES ? "DTRACEIOC_PROBES" : cmd == DTRACEIOC_BUFSNAP ? "DTRACEIOC_BUFSNAP" : cmd == DTRACEIOC_PROBEMATCH ? "DTRACEIOC_PROBEMATCH" : cmd == DTRACEIOC_ENABLE ? "DTRACEIOC_ENABLE" : cmd == DTRACEIOC_AGGSNAP ? "DTRACEIOC_AGGSNAP" : cmd == DTRACEIOC_EPROBE ? "DTRACEIOC_EPROBE" : cmd == DTRACEIOC_PROBEARG ? "DTRACEIOC_PROBEARG" : cmd == DTRACEIOC_CONF ? "DTRACEIOC_CONF" : cmd == DTRACEIOC_STATUS ? "DTRACEIOC_STATUS" : cmd == DTRACEIOC_GO ? "DTRACEIOC_GO" : cmd == DTRACEIOC_STOP ? "DTRACEIOC_STOP" : cmd == DTRACEIOC_AGGDESC ? "DTRACEIOC_AGGDESC" : cmd == DTRACEIOC_FORMAT ? "DTRACEIOC_FORMAT" : cmd == DTRACEIOC_DOFGET ? "DTRACEIOC_DOFGET" : cmd == DTRACEIOC_REPLICATE ? "DTRACEIOC_REPLICATE" : "???"); errno = ENXIO; return (-1); } } struct dtrace_ctf_module { char *text; size_t text_size; }; static int dtracemdb_modctl(uintptr_t addr, const struct modctl *m, dtracemdb_data_t *data) { struct dtrace_ctf_module mod; if (m->mod_mp == NULL) return (WALK_NEXT); if (mdb_ctf_vread(&mod, "struct module", "struct dtrace_ctf_module", (uintptr_t)m->mod_mp, 0) == -1) { mdb_warn("couldn't read modctl %p's module", addr); return (WALK_NEXT); } if ((uintptr_t)mod.text > data->dtmd_addr) return (WALK_NEXT); if ((uintptr_t)mod.text + mod.text_size <= data->dtmd_addr) return (WALK_NEXT); if (mdb_readstr(data->dtmd_modstr, MDB_SYM_NAMLEN, (uintptr_t)m->mod_modname) == -1) return (WALK_ERR); return (WALK_DONE); } static int dtracemdb_lookup_by_addr(void *varg, GElf_Addr addr, GElf_Sym *symp, dtrace_syminfo_t *sip) { dtracemdb_data_t *data = varg; if (data->dtmd_symstr == NULL) { data->dtmd_symstr = mdb_zalloc(MDB_SYM_NAMLEN, UM_SLEEP | UM_GC); } if (data->dtmd_modstr == NULL) { data->dtmd_modstr = mdb_zalloc(MDB_SYM_NAMLEN, UM_SLEEP | UM_GC); } if (symp != NULL) { if (mdb_lookup_by_addr(addr, MDB_SYM_FUZZY, data->dtmd_symstr, MDB_SYM_NAMLEN, symp) == -1) return (-1); } if (sip != NULL) { data->dtmd_addr = addr; (void) strcpy(data->dtmd_modstr, "???"); if (mdb_walk("modctl", (mdb_walk_cb_t)dtracemdb_modctl, varg) == -1) { mdb_warn("couldn't walk 'modctl'"); return (-1); } sip->dts_object = data->dtmd_modstr; sip->dts_id = 0; sip->dts_name = symp != NULL ? data->dtmd_symstr : NULL; } return (0); } /*ARGSUSED*/ static int dtracemdb_stat(void *varg, processorid_t cpu) { GElf_Sym sym; cpu_t c; uintptr_t caddr, addr; if (mdb_lookup_by_name("cpu", &sym) == -1) { mdb_warn("failed to find symbol for 'cpu'"); return (-1); } if (cpu * sizeof (uintptr_t) > sym.st_size) return (-1); addr = (uintptr_t)sym.st_value + cpu * sizeof (uintptr_t); if (mdb_vread(&caddr, sizeof (caddr), addr) == -1) { mdb_warn("failed to read cpu[%d]", cpu); return (-1); } if (caddr == 0) return (-1); if (mdb_vread(&c, sizeof (c), caddr) == -1) { mdb_warn("failed to read cpu at %p", caddr); return (-1); } if (c.cpu_flags & CPU_POWEROFF) { return (P_POWEROFF); } else if (c.cpu_flags & CPU_SPARE) { return (P_SPARE); } else if (c.cpu_flags & CPU_FAULTED) { return (P_FAULTED); } else if (c.cpu_flags & CPU_DISABLED) { return (P_DISABLED); } else if ((c.cpu_flags & (CPU_READY | CPU_OFFLINE)) != CPU_READY) { return (P_OFFLINE); } else if (c.cpu_flags & CPU_ENABLE) { return (P_ONLINE); } else { return (P_NOINTR); } } /*ARGSUSED*/ static long dtracemdb_sysconf(void *varg, int name) { int max_ncpus; processorid_t max_cpuid; switch (name) { case _SC_CPUID_MAX: if (mdb_readvar(&max_cpuid, "max_cpuid") == -1) { mdb_warn("failed to read 'max_cpuid'"); return (-1); } return (max_cpuid); case _SC_NPROCESSORS_MAX: if (mdb_readvar(&max_ncpus, "max_ncpus") == -1) { mdb_warn("failed to read 'max_ncpus'"); return (-1); } return (max_ncpus); default: mdb_warn("unexpected sysconf code %d\n", name); return (-1); } } const dtrace_vector_t dtrace_mdbops = { dtracemdb_ioctl, dtracemdb_lookup_by_addr, dtracemdb_stat, dtracemdb_sysconf }; typedef struct dtrace_dcmddata { dtrace_hdl_t *dtdd_dtp; int dtdd_cpu; int dtdd_quiet; int dtdd_flowindent; int dtdd_heading; FILE *dtdd_output; } dtrace_dcmddata_t; /* * Helper to grab all the content from a file, spit it into a string, and erase * and reset the file. */ static void print_and_truncate_file(FILE *fp) { long len; char *out; /* flush, find length of file, seek to beginning, initialize buffer */ if (fflush(fp) || (len = ftell(fp)) < 0 || fseek(fp, 0, SEEK_SET) < 0) { mdb_warn("couldn't prepare DTrace output file: %d\n", errno); return; } out = mdb_alloc(len + 1, UM_SLEEP); out[len] = '\0'; /* read file into buffer, truncate file, and seek to beginning */ if ((fread(out, len + 1, sizeof (char), fp) == 0 && ferror(fp)) || ftruncate(fileno(fp), 0) < 0 || fseek(fp, 0, SEEK_SET) < 0) { mdb_warn("couldn't read DTrace output file: %d\n", errno); mdb_free(out, len + 1); return; } mdb_printf("%s", out); mdb_free(out, len + 1); } /*ARGSUSED*/ static int dtrace_dcmdrec(const dtrace_probedata_t *data, const dtrace_recdesc_t *rec, void *arg) { dtrace_dcmddata_t *dd = arg; print_and_truncate_file(dd->dtdd_output); if (rec == NULL) { /* * We have processed the final record; output the newline if * we're not in quiet mode. */ if (!dd->dtdd_quiet) mdb_printf("\n"); return (DTRACE_CONSUME_NEXT); } return (DTRACE_CONSUME_THIS); } /*ARGSUSED*/ static int dtrace_dcmdprobe(const dtrace_probedata_t *data, void *arg) { dtrace_probedesc_t *pd = data->dtpda_pdesc; processorid_t cpu = data->dtpda_cpu; dtrace_dcmddata_t *dd = arg; char name[DTRACE_FUNCNAMELEN + DTRACE_NAMELEN + 2]; if (dd->dtdd_cpu != -1UL && dd->dtdd_cpu != cpu) return (DTRACE_CONSUME_NEXT); if (dd->dtdd_heading == 0) { if (!dd->dtdd_flowindent) { if (!dd->dtdd_quiet) { mdb_printf("%3s %6s %32s\n", "CPU", "ID", "FUNCTION:NAME"); } } else { mdb_printf("%3s %-41s\n", "CPU", "FUNCTION"); } dd->dtdd_heading = 1; } if (!dd->dtdd_flowindent) { if (!dd->dtdd_quiet) { (void) mdb_snprintf(name, sizeof (name), "%s:%s", pd->dtpd_func, pd->dtpd_name); mdb_printf("%3d %6d %32s ", cpu, pd->dtpd_id, name); } } else { int indent = data->dtpda_indent; if (data->dtpda_flow == DTRACEFLOW_NONE) { (void) mdb_snprintf(name, sizeof (name), "%*s%s%s:%s", indent, "", data->dtpda_prefix, pd->dtpd_func, pd->dtpd_name); } else { (void) mdb_snprintf(name, sizeof (name), "%*s%s%s", indent, "", data->dtpda_prefix, pd->dtpd_func); } mdb_printf("%3d %-41s ", cpu, name); } return (DTRACE_CONSUME_THIS); } /*ARGSUSED*/ static int dtrace_dcmderr(const dtrace_errdata_t *data, void *arg) { mdb_warn(data->dteda_msg); return (DTRACE_HANDLE_OK); } /*ARGSUSED*/ static int dtrace_dcmddrop(const dtrace_dropdata_t *data, void *arg) { mdb_warn(data->dtdda_msg); return (DTRACE_HANDLE_OK); } /*ARGSUSED*/ static int dtrace_dcmdbuffered(const dtrace_bufdata_t *bufdata, void *arg) { mdb_printf("%s", bufdata->dtbda_buffered); return (DTRACE_HANDLE_OK); } /*ARGSUSED*/ int dtrace(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dtrace_state_t state; dtrace_hdl_t *dtp; int ncpu, err; uintptr_t c = -1UL; dtrace_dcmddata_t dd; dtrace_optval_t val; dtracemdb_data_t md; int rval = DCMD_ERR; dtrace_anon_t anon; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'c', MDB_OPT_UINTPTR, &c, NULL) != argc) return (DCMD_USAGE); if (mdb_readvar(&ncpu, "_ncpu") == -1) { mdb_warn("failed to read '_ncpu'"); return (DCMD_ERR); } if (mdb_vread(&state, sizeof (state), addr) == -1) { mdb_warn("couldn't read dtrace_state_t at %p", addr); return (DCMD_ERR); } if (state.dts_anon != NULL) { addr = (uintptr_t)state.dts_anon; if (mdb_vread(&state, sizeof (state), addr) == -1) { mdb_warn("couldn't read anonymous state at %p", addr); return (DCMD_ERR); } } bzero(&md, sizeof (md)); md.dtmd_state = &state; if ((dtp = dtrace_vopen(DTRACE_VERSION, DTRACE_O_NOSYS, &err, &dtrace_mdbops, &md)) == NULL) { mdb_warn("failed to initialize dtrace: %s\n", dtrace_errmsg(NULL, err)); return (DCMD_ERR); } /* * If this is the anonymous enabling, we need to set a bit indicating * that DTRACEOPT_GRABANON should be set. */ if (mdb_readvar(&anon, "dtrace_anon") == -1) { mdb_warn("failed to read 'dtrace_anon'"); return (DCMD_ERR); } md.dtmd_isanon = ((uintptr_t)anon.dta_state == addr); if (dtrace_go(dtp) != 0) { mdb_warn("failed to initialize dtrace: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } bzero(&dd, sizeof (dd)); dd.dtdd_dtp = dtp; dd.dtdd_cpu = c; if (dtrace_getopt(dtp, "flowindent", &val) == -1) { mdb_warn("couldn't get 'flowindent' option: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } dd.dtdd_flowindent = (val != DTRACEOPT_UNSET); if (dtrace_getopt(dtp, "quiet", &val) == -1) { mdb_warn("couldn't get 'quiet' option: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } dd.dtdd_quiet = (val != DTRACEOPT_UNSET); if (dtrace_handle_err(dtp, dtrace_dcmderr, NULL) == -1) { mdb_warn("couldn't add err handler: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } if (dtrace_handle_drop(dtp, dtrace_dcmddrop, NULL) == -1) { mdb_warn("couldn't add drop handler: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } if (dtrace_handle_buffered(dtp, dtrace_dcmdbuffered, NULL) == -1) { mdb_warn("couldn't add buffered handler: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } if (dtrace_status(dtp) == -1) { mdb_warn("couldn't get status: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } if (dtrace_aggregate_snap(dtp) == -1) { mdb_warn("couldn't snapshot aggregation: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } if ((dd.dtdd_output = tmpfile()) == NULL) { mdb_warn("couldn't open DTrace output file: %d\n", errno); goto err; } if (dtrace_consume(dtp, dd.dtdd_output, dtrace_dcmdprobe, dtrace_dcmdrec, &dd) == -1) { mdb_warn("couldn't consume DTrace buffers: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); } if (dtrace_aggregate_print(dtp, NULL, NULL) == -1) { mdb_warn("couldn't print aggregation: %s\n", dtrace_errmsg(dtp, dtrace_errno(dtp))); goto err; } rval = DCMD_OK; err: dtrace_close(dtp); fclose(dd.dtdd_output); return (rval); } static int dtrace_errhash_cmp(const void *l, const void *r) { uintptr_t lhs = *((uintptr_t *)l); uintptr_t rhs = *((uintptr_t *)r); dtrace_errhash_t lerr, rerr; char lmsg[256], rmsg[256]; (void) mdb_vread(&lerr, sizeof (lerr), lhs); (void) mdb_vread(&rerr, sizeof (rerr), rhs); if (lerr.dter_msg == NULL) return (-1); if (rerr.dter_msg == NULL) return (1); (void) mdb_readstr(lmsg, sizeof (lmsg), (uintptr_t)lerr.dter_msg); (void) mdb_readstr(rmsg, sizeof (rmsg), (uintptr_t)rerr.dter_msg); return (strcmp(lmsg, rmsg)); } int dtrace_errhash_init(mdb_walk_state_t *wsp) { GElf_Sym sym; uintptr_t *hash, addr; int i; if (wsp->walk_addr != 0) { mdb_warn("dtrace_errhash walk only supports global walks\n"); return (WALK_ERR); } if (mdb_lookup_by_name("dtrace_errhash", &sym) == -1) { mdb_warn("couldn't find 'dtrace_errhash' (non-DEBUG kernel?)"); return (WALK_ERR); } addr = (uintptr_t)sym.st_value; hash = mdb_alloc(DTRACE_ERRHASHSZ * sizeof (uintptr_t), UM_SLEEP | UM_GC); for (i = 0; i < DTRACE_ERRHASHSZ; i++) hash[i] = addr + i * sizeof (dtrace_errhash_t); qsort(hash, DTRACE_ERRHASHSZ, sizeof (uintptr_t), dtrace_errhash_cmp); wsp->walk_addr = 0; wsp->walk_data = hash; return (WALK_NEXT); } int dtrace_errhash_step(mdb_walk_state_t *wsp) { int ndx = (int)wsp->walk_addr; uintptr_t *hash = wsp->walk_data; dtrace_errhash_t err; uintptr_t addr; if (ndx >= DTRACE_ERRHASHSZ) return (WALK_DONE); wsp->walk_addr = ndx + 1; addr = hash[ndx]; if (mdb_vread(&err, sizeof (err), addr) == -1) { mdb_warn("failed to read dtrace_errhash_t at %p", addr); return (WALK_DONE); } if (err.dter_msg == NULL) return (WALK_NEXT); return (wsp->walk_callback(addr, &err, wsp->walk_cbdata)); } /*ARGSUSED*/ int dtrace_errhash(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dtrace_errhash_t err; char msg[256]; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("dtrace_errhash", "dtrace_errhash", argc, argv) == -1) { mdb_warn("can't walk 'dtrace_errhash'"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%8s %s\n", "COUNT", "ERROR"); if (mdb_vread(&err, sizeof (err), addr) == -1) { mdb_warn("failed to read dtrace_errhash_t at %p", addr); return (DCMD_ERR); } addr = (uintptr_t)err.dter_msg; if (mdb_readstr(msg, sizeof (msg), addr) == -1) { mdb_warn("failed to read error msg at %p", addr); return (DCMD_ERR); } mdb_printf("%8d %s", err.dter_count, msg); /* * Some error messages include a newline -- only print the newline * if the message doesn't have one. */ if (msg[strlen(msg) - 1] != '\n') mdb_printf("\n"); return (DCMD_OK); } int dtrace_helptrace_init(mdb_walk_state_t *wsp) { uint32_t next; uintptr_t buffer; if (wsp->walk_addr != 0) { mdb_warn("dtrace_helptrace only supports global walks\n"); return (WALK_ERR); } if (mdb_readvar(&buffer, "dtrace_helptrace_buffer") == -1) { mdb_warn("couldn't read 'dtrace_helptrace_buffer'"); return (WALK_ERR); } if (buffer == 0) { mdb_warn("helper tracing is not enabled\n"); return (WALK_ERR); } if (mdb_readvar(&next, "dtrace_helptrace_next") == -1) { mdb_warn("couldn't read 'dtrace_helptrace_next'"); return (WALK_ERR); } wsp->walk_addr = next; return (WALK_NEXT); } int dtrace_helptrace_step(mdb_walk_state_t *wsp) { uint32_t next, size, nlocals, bufsize; uintptr_t buffer, addr; dtrace_helptrace_t *ht; int rval; if (mdb_readvar(&next, "dtrace_helptrace_next") == -1) { mdb_warn("couldn't read 'dtrace_helptrace_next'"); return (WALK_ERR); } if (mdb_readvar(&bufsize, "dtrace_helptrace_bufsize") == -1) { mdb_warn("couldn't read 'dtrace_helptrace_bufsize'"); return (WALK_ERR); } if (mdb_readvar(&buffer, "dtrace_helptrace_buffer") == -1) { mdb_warn("couldn't read 'dtrace_helptrace_buffer'"); return (WALK_ERR); } if (mdb_readvar(&nlocals, "dtrace_helptrace_nlocals") == -1) { mdb_warn("couldn't read 'dtrace_helptrace_nlocals'"); return (WALK_ERR); } size = sizeof (dtrace_helptrace_t) + nlocals * sizeof (uint64_t) - sizeof (uint64_t); if (wsp->walk_addr + size > bufsize) { if (next == 0) return (WALK_DONE); wsp->walk_addr = 0; } addr = buffer + wsp->walk_addr; ht = alloca(size); if (mdb_vread(ht, size, addr) == -1) { mdb_warn("couldn't read entry at %p", addr); return (WALK_ERR); } if (ht->dtht_helper != NULL) { rval = wsp->walk_callback(addr, ht, wsp->walk_cbdata); if (rval != WALK_NEXT) return (rval); } if (wsp->walk_addr < next && wsp->walk_addr + size >= next) return (WALK_DONE); wsp->walk_addr += size; return (WALK_NEXT); } int dtrace_helptrace(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dtrace_helptrace_t help; dtrace_helper_action_t helper; char where[30]; uint_t opt_v = FALSE; uintptr_t haddr; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("dtrace_helptrace", "dtrace_helptrace", argc, argv) == -1) { mdb_warn("can't walk 'dtrace_helptrace'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf(" %?s %?s %12s %s\n", "ADDR", "HELPER", "WHERE", "DIFO"); } if (mdb_vread(&help, sizeof (help), addr) == -1) { mdb_warn("failed to read dtrace_helptrace_t at %p", addr); return (DCMD_ERR); } switch (help.dtht_where) { case 0: (void) mdb_snprintf(where, sizeof (where), "predicate"); break; case DTRACE_HELPTRACE_NEXT: (void) mdb_snprintf(where, sizeof (where), "next"); break; case DTRACE_HELPTRACE_DONE: (void) mdb_snprintf(where, sizeof (where), "done"); break; case DTRACE_HELPTRACE_ERR: (void) mdb_snprintf(where, sizeof (where), "err"); break; default: (void) mdb_snprintf(where, sizeof (where), "action #%d", help.dtht_where); break; } mdb_printf(" %?p %?p %12s ", addr, help.dtht_helper, where); haddr = (uintptr_t)help.dtht_helper; if (mdb_vread(&helper, sizeof (helper), haddr) == -1) { /* * We're not going to warn in this case -- we're just not going * to print anything exciting. */ mdb_printf("???\n"); } else { switch (help.dtht_where) { case 0: mdb_printf("%p\n", helper.dtha_predicate); break; case DTRACE_HELPTRACE_NEXT: case DTRACE_HELPTRACE_DONE: case DTRACE_HELPTRACE_ERR: mdb_printf("-\n"); break; default: haddr = (uintptr_t)helper.dtha_actions + (help.dtht_where - 1) * sizeof (uintptr_t); if (mdb_vread(&haddr, sizeof (haddr), haddr) == -1) { mdb_printf("???\n"); } else { mdb_printf("%p\n", haddr); } } } if (opt_v) { int i; if (help.dtht_where == DTRACE_HELPTRACE_ERR) { int f = help.dtht_fault; mdb_printf("%?s| %?s %10s |\n", "", "", ""); mdb_printf("%?s| %?s %10s +-> fault: %s\n", "", "", "", f == DTRACEFLT_BADADDR ? "BADADDR" : f == DTRACEFLT_BADALIGN ? "BADALIGN" : f == DTRACEFLT_ILLOP ? "ILLOP" : f == DTRACEFLT_DIVZERO ? "DIVZERO" : f == DTRACEFLT_NOSCRATCH ? "NOSCRATCH" : f == DTRACEFLT_KPRIV ? "KPRIV" : f == DTRACEFLT_UPRIV ? "UPRIV" : f == DTRACEFLT_TUPOFLOW ? "TUPOFLOW" : f == DTRACEFLT_BADSTACK ? "BADSTACK" : "DTRACEFLT_UNKNOWN"); mdb_printf("%?s| %?s %12s addr: 0x%x\n", "", "", "", help.dtht_illval); mdb_printf("%?s| %?s %12s offset: %d\n", "", "", "", help.dtht_fltoffs); } mdb_printf("%?s|\n%?s+--> %?s %4s %s\n", "", "", "ADDR", "NDX", "VALUE"); addr += sizeof (help) - sizeof (uint64_t); for (i = 0; i < help.dtht_nlocals; i++) { uint64_t val; if (mdb_vread(&val, sizeof (val), addr) == -1) { mdb_warn("couldn't read local at %p", addr); continue; } mdb_printf("%?s %?p %4d %p\n", "", addr, i, val); addr += sizeof (uint64_t); } mdb_printf("\n"); } return (DCMD_OK); } /*ARGSUSED*/ static int dtrace_state_walk(uintptr_t addr, const vmem_seg_t *seg, minor_t *highest) { if (seg->vs_end > *highest) *highest = seg->vs_end; return (WALK_NEXT); } typedef struct dtrace_state_walk { uintptr_t dtsw_softstate; minor_t dtsw_max; minor_t dtsw_current; } dtrace_state_walk_t; int dtrace_state_init(mdb_walk_state_t *wsp) { uintptr_t dtrace_minor; minor_t max = 0; dtrace_state_walk_t *dw; if (wsp->walk_addr != 0) { mdb_warn("dtrace_state only supports global walks\n"); return (WALK_ERR); } /* * Find the dtrace_minor vmem arena and walk it to get the maximum * minor number. */ if (mdb_readvar(&dtrace_minor, "dtrace_minor") == -1) { mdb_warn("failed to read 'dtrace_minor'"); return (WALK_ERR); } if (mdb_pwalk("vmem_alloc", (mdb_walk_cb_t)dtrace_state_walk, &max, dtrace_minor) == -1) { mdb_warn("couldn't walk 'vmem_alloc'"); return (WALK_ERR); } dw = mdb_zalloc(sizeof (dtrace_state_walk_t), UM_SLEEP | UM_GC); dw->dtsw_current = 0; dw->dtsw_max = max; if (mdb_readvar(&dw->dtsw_softstate, "dtrace_softstate") == -1) { mdb_warn("failed to read 'dtrace_softstate'"); return (DCMD_ERR); } wsp->walk_data = dw; return (WALK_NEXT); } int dtrace_state_step(mdb_walk_state_t *wsp) { dtrace_state_walk_t *dw = wsp->walk_data; uintptr_t statep; dtrace_state_t state; int rval; while (mdb_get_soft_state_byaddr(dw->dtsw_softstate, dw->dtsw_current, &statep, NULL, 0) == -1) { if (dw->dtsw_current >= dw->dtsw_max) return (WALK_DONE); dw->dtsw_current++; } if (mdb_vread(&state, sizeof (state), statep) == -1) { mdb_warn("couldn't read dtrace_state_t at %p", statep); return (WALK_NEXT); } rval = wsp->walk_callback(statep, &state, wsp->walk_cbdata); dw->dtsw_current++; return (rval); } typedef struct dtrace_state_data { int dtsd_major; uintptr_t dtsd_proc; uintptr_t dtsd_softstate; uintptr_t dtsd_state; } dtrace_state_data_t; static int dtrace_state_file(uintptr_t addr, struct file *f, dtrace_state_data_t *data) { vnode_t vnode; proc_t proc; minor_t minor; uintptr_t statep; if (mdb_vread(&vnode, sizeof (vnode), (uintptr_t)f->f_vnode) == -1) { mdb_warn("couldn't read vnode at %p", (uintptr_t)f->f_vnode); return (WALK_NEXT); } if (getmajor(vnode.v_rdev) != data->dtsd_major) return (WALK_NEXT); minor = getminor(vnode.v_rdev); if (mdb_vread(&proc, sizeof (proc), data->dtsd_proc) == -1) { mdb_warn("failed to read proc at %p", data->dtsd_proc); return (WALK_NEXT); } if (mdb_get_soft_state_byaddr(data->dtsd_softstate, minor, &statep, NULL, 0) == -1) { mdb_warn("failed to read softstate for minor %d", minor); return (WALK_NEXT); } if (statep != data->dtsd_state) return (WALK_NEXT); mdb_printf("%?p %5d %?p %-*s %?p\n", statep, minor, data->dtsd_proc, MAXCOMLEN, proc.p_user.u_comm, addr); return (WALK_NEXT); } /*ARGSUSED*/ static int dtrace_state_proc(uintptr_t addr, void *ignored, dtrace_state_data_t *data) { data->dtsd_proc = addr; if (mdb_pwalk("file", (mdb_walk_cb_t)dtrace_state_file, data, addr) == -1) { mdb_warn("couldn't walk 'file' for proc %p", addr); return (WALK_ERR); } return (WALK_NEXT); } void dtrace_state_help(void) { mdb_printf("Given a dtrace_state_t structure, displays all " /*CSTYLED*/ "consumers, or \"\"\nif the consumer is anonymous. If " "no state structure is provided, iterates\nover all state " "structures.\n\n" "Addresses in ADDR column may be provided to ::dtrace to obtain\n" "dtrace(8)-like output for in-kernel DTrace data.\n"); } int dtrace_state(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t devi; struct dev_info info; dtrace_state_data_t data; dtrace_anon_t anon; dtrace_state_t state; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("dtrace_state", "dtrace_state", argc, argv) == -1) { mdb_warn("can't walk dtrace_state"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %5s %?s %-*s %?s\n", "ADDR", "MINOR", "PROC", MAXCOMLEN, "NAME", "FILE"); } /* * First determine if this is anonymous state. */ if (mdb_readvar(&anon, "dtrace_anon") == -1) { mdb_warn("failed to read 'dtrace_anon'"); return (DCMD_ERR); } if ((uintptr_t)anon.dta_state == addr) { if (mdb_vread(&state, sizeof (state), addr) == -1) { mdb_warn("failed to read anon at %p", addr); return (DCMD_ERR); } mdb_printf("%?p %5d %?s %-*s %?s\n", addr, getminor(state.dts_dev), "-", MAXCOMLEN, "", "-"); return (DCMD_OK); } if (mdb_readvar(&devi, "dtrace_devi") == -1) { mdb_warn("failed to read 'dtrace_devi'"); return (DCMD_ERR); } if (mdb_vread(&info, sizeof (struct dev_info), devi) == -1) { mdb_warn("failed to read 'dev_info'"); return (DCMD_ERR); } data.dtsd_major = info.devi_major; if (mdb_readvar(&data.dtsd_softstate, "dtrace_softstate") == -1) { mdb_warn("failed to read 'dtrace_softstate'"); return (DCMD_ERR); } data.dtsd_state = addr; /* * Walk through all processes and all open files looking for this * state. It must be open somewhere... */ if (mdb_walk("proc", (mdb_walk_cb_t)dtrace_state_proc, &data) == -1) { mdb_warn("couldn't walk 'proc'"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct dtrace_aggkey_data { uintptr_t *dtakd_hash; uintptr_t dtakd_hashsize; uintptr_t dtakd_next; uintptr_t dtakd_ndx; } dtrace_aggkey_data_t; int dtrace_aggkey_init(mdb_walk_state_t *wsp) { dtrace_buffer_t buf; uintptr_t addr; dtrace_aggbuffer_t agb; dtrace_aggkey_data_t *data; size_t hsize; if ((addr = wsp->walk_addr) == 0) { mdb_warn("dtrace_aggkey walk needs aggregation buffer\n"); return (WALK_ERR); } if (mdb_vread(&buf, sizeof (buf), addr) == -1) { mdb_warn("failed to read aggregation buffer at %p", addr); return (WALK_ERR); } addr = (uintptr_t)buf.dtb_tomax + buf.dtb_size - sizeof (dtrace_aggbuffer_t); if (mdb_vread(&agb, sizeof (agb), addr) == -1) { mdb_warn("failed to read dtrace_aggbuffer_t at %p", addr); return (WALK_ERR); } data = mdb_zalloc(sizeof (dtrace_aggkey_data_t), UM_SLEEP); data->dtakd_hashsize = agb.dtagb_hashsize; hsize = agb.dtagb_hashsize * sizeof (dtrace_aggkey_t *); data->dtakd_hash = mdb_alloc(hsize, UM_SLEEP); if (mdb_vread(data->dtakd_hash, hsize, (uintptr_t)agb.dtagb_hash) == -1) { mdb_warn("failed to read hash at %p", (uintptr_t)agb.dtagb_hash); mdb_free(data->dtakd_hash, hsize); mdb_free(data, sizeof (dtrace_aggkey_data_t)); return (WALK_ERR); } wsp->walk_data = data; return (WALK_NEXT); } int dtrace_aggkey_step(mdb_walk_state_t *wsp) { dtrace_aggkey_data_t *data = wsp->walk_data; dtrace_aggkey_t key; uintptr_t addr; while ((addr = data->dtakd_next) == 0) { if (data->dtakd_ndx == data->dtakd_hashsize) return (WALK_DONE); data->dtakd_next = data->dtakd_hash[data->dtakd_ndx++]; } if (mdb_vread(&key, sizeof (key), addr) == -1) { mdb_warn("failed to read dtrace_aggkey_t at %p", addr); return (WALK_ERR); } data->dtakd_next = (uintptr_t)key.dtak_next; return (wsp->walk_callback(addr, &key, wsp->walk_cbdata)); } void dtrace_aggkey_fini(mdb_walk_state_t *wsp) { dtrace_aggkey_data_t *data = wsp->walk_data; size_t hsize; hsize = data->dtakd_hashsize * sizeof (dtrace_aggkey_t *); mdb_free(data->dtakd_hash, hsize); mdb_free(data, sizeof (dtrace_aggkey_data_t)); } typedef struct dtrace_dynvar_data { dtrace_dynhash_t *dtdvd_hash; uintptr_t dtdvd_hashsize; uintptr_t dtdvd_next; uintptr_t dtdvd_ndx; uintptr_t dtdvd_sink; } dtrace_dynvar_data_t; int dtrace_dynvar_init(mdb_walk_state_t *wsp) { uintptr_t addr; dtrace_dstate_t dstate; dtrace_dynvar_data_t *data; size_t hsize; GElf_Sym sym; if ((addr = wsp->walk_addr) == 0) { mdb_warn("dtrace_dynvar walk needs dtrace_dstate_t\n"); return (WALK_ERR); } if (mdb_vread(&dstate, sizeof (dstate), addr) == -1) { mdb_warn("failed to read dynamic state at %p", addr); return (WALK_ERR); } if (mdb_lookup_by_name("dtrace_dynhash_sink", &sym) == -1) { mdb_warn("couldn't find 'dtrace_dynhash_sink'"); return (WALK_ERR); } data = mdb_zalloc(sizeof (dtrace_dynvar_data_t), UM_SLEEP); data->dtdvd_hashsize = dstate.dtds_hashsize; hsize = dstate.dtds_hashsize * sizeof (dtrace_dynhash_t); data->dtdvd_hash = mdb_alloc(hsize, UM_SLEEP); data->dtdvd_sink = (uintptr_t)sym.st_value; if (mdb_vread(data->dtdvd_hash, hsize, (uintptr_t)dstate.dtds_hash) == -1) { mdb_warn("failed to read hash at %p", (uintptr_t)dstate.dtds_hash); mdb_free(data->dtdvd_hash, hsize); mdb_free(data, sizeof (dtrace_dynvar_data_t)); return (WALK_ERR); } data->dtdvd_next = (uintptr_t)data->dtdvd_hash[0].dtdh_chain; wsp->walk_data = data; return (WALK_NEXT); } int dtrace_dynvar_step(mdb_walk_state_t *wsp) { dtrace_dynvar_data_t *data = wsp->walk_data; dtrace_dynvar_t dynvar, *dvar; size_t dvarsize; uintptr_t addr; int nkeys; while ((addr = data->dtdvd_next) == data->dtdvd_sink) { if (data->dtdvd_ndx == data->dtdvd_hashsize) return (WALK_DONE); data->dtdvd_next = (uintptr_t)data->dtdvd_hash[data->dtdvd_ndx++].dtdh_chain; } if (mdb_vread(&dynvar, sizeof (dynvar), addr) == -1) { mdb_warn("failed to read dtrace_dynvar_t at %p", addr); return (WALK_ERR); } /* * Now we need to allocate the correct size. */ nkeys = dynvar.dtdv_tuple.dtt_nkeys; dvarsize = (uintptr_t)&dynvar.dtdv_tuple.dtt_key[nkeys] - (uintptr_t)&dynvar; dvar = alloca(dvarsize); if (mdb_vread(dvar, dvarsize, addr) == -1) { mdb_warn("failed to read dtrace_dynvar_t at %p", addr); return (WALK_ERR); } data->dtdvd_next = (uintptr_t)dynvar.dtdv_next; return (wsp->walk_callback(addr, dvar, wsp->walk_cbdata)); } void dtrace_dynvar_fini(mdb_walk_state_t *wsp) { dtrace_dynvar_data_t *data = wsp->walk_data; size_t hsize; hsize = data->dtdvd_hashsize * sizeof (dtrace_dynvar_t *); mdb_free(data->dtdvd_hash, hsize); mdb_free(data, sizeof (dtrace_dynvar_data_t)); } typedef struct dtrace_hashstat_data { size_t *dthsd_counts; size_t dthsd_hashsize; char *dthsd_data; size_t dthsd_size; int dthsd_header; } dtrace_hashstat_data_t; typedef void (*dtrace_hashstat_func_t)(dtrace_hashstat_data_t *); static void dtrace_hashstat_additive(dtrace_hashstat_data_t *data) { int i; int hval = 0; for (i = 0; i < data->dthsd_size; i++) hval += data->dthsd_data[i]; data->dthsd_counts[hval % data->dthsd_hashsize]++; } static void dtrace_hashstat_shifty(dtrace_hashstat_data_t *data) { uint64_t hval = 0; int i; if (data->dthsd_size < sizeof (uint64_t)) { dtrace_hashstat_additive(data); return; } for (i = 0; i < data->dthsd_size; i += sizeof (uint64_t)) { /* LINTED - alignment */ uint64_t val = *((uint64_t *)&data->dthsd_data[i]); hval += (val & ((1 << NBBY) - 1)) + ((val >> NBBY) & ((1 << NBBY) - 1)) + ((val >> (NBBY << 1)) & ((1 << NBBY) - 1)) + ((val >> (NBBY << 2)) & ((1 << NBBY) - 1)) + (val & USHRT_MAX) + (val >> (NBBY << 1) & USHRT_MAX); } data->dthsd_counts[hval % data->dthsd_hashsize]++; } static void dtrace_hashstat_knuth(dtrace_hashstat_data_t *data) { int i; int hval = data->dthsd_size; for (i = 0; i < data->dthsd_size; i++) hval = (hval << 4) ^ (hval >> 28) ^ data->dthsd_data[i]; data->dthsd_counts[hval % data->dthsd_hashsize]++; } static void dtrace_hashstat_oneatatime(dtrace_hashstat_data_t *data) { int i; uint32_t hval = 0; for (i = 0; i < data->dthsd_size; i++) { hval += data->dthsd_data[i]; hval += (hval << 10); hval ^= (hval >> 6); } hval += (hval << 3); hval ^= (hval >> 11); hval += (hval << 15); data->dthsd_counts[hval % data->dthsd_hashsize]++; } static void dtrace_hashstat_fnv(dtrace_hashstat_data_t *data) { static const uint32_t prime = 0x01000193; uint32_t hval = 0; int i; for (i = 0; i < data->dthsd_size; i++) { hval *= prime; hval ^= data->dthsd_data[i]; } data->dthsd_counts[hval % data->dthsd_hashsize]++; } /* * Hammerhead: Integer square root for use in kmod context where FP/SSE is * disabled (-mno-sse). Newton's method converges in ~32 iterations for * 64-bit inputs. */ static uint_t dtrace_isqrt(uint64_t n) { uint64_t x, y; if (n == 0) return (0); x = n; y = (x + 1) / 2; while (y < x) { x = y; y = (x + n / x) / 2; } return ((uint_t)x); } static void dtrace_hashstat_stats(char *name, dtrace_hashstat_data_t *data) { size_t nz = 0, i; int longest = 0; size_t ttl = 0; int64_t avg_x10; uint64_t sum_x100 = 0; uint_t util, stddev; if (!data->dthsd_header) { mdb_printf("%15s %11s %11s %11s %11s %11s\n", "NAME", "HASHSIZE", "%UTIL", "LONGEST", "AVERAGE", "STDDEV"); data->dthsd_header = 1; } for (i = 0; i < data->dthsd_hashsize; i++) { if (data->dthsd_counts[i] != 0) { nz++; if (data->dthsd_counts[i] > longest) longest = data->dthsd_counts[i]; ttl += data->dthsd_counts[i]; } } if (nz == 0) { mdb_printf("%15s %11d %11s %11s %11s %11s\n", name, data->dthsd_hashsize, "-", "-", "-", "-"); return; } /* * Use 10x-scaled integer arithmetic to avoid floating point. * avg_x10 = avg * 10, delta_x10 = delta * 10, so * sum_x100 = sum(delta^2) * 100 and isqrt(sum_x100/nz) * gives stddev * 10 directly (matching the display format). */ avg_x10 = (int64_t)(ttl * 10) / (int64_t)nz; for (i = 0; i < data->dthsd_hashsize; i++) { int64_t delta_x10; if (data->dthsd_counts[i] == 0) continue; delta_x10 = (int64_t)data->dthsd_counts[i] * 10 - avg_x10; sum_x100 += (uint64_t)(delta_x10 * delta_x10); } util = (nz * 1000) / data->dthsd_hashsize; stddev = dtrace_isqrt(sum_x100 / (uint64_t)nz); mdb_printf("%15s %11d %9u.%1u %11d %11d %9u.%1u\n", name, data->dthsd_hashsize, util / 10, util % 10, longest, ttl / nz, stddev / 10, stddev % 10); } static struct dtrace_hashstat { char *dths_name; dtrace_hashstat_func_t dths_func; } _dtrace_hashstat[] = { { "", NULL }, { "additive", dtrace_hashstat_additive }, { "shifty", dtrace_hashstat_shifty }, { "knuth", dtrace_hashstat_knuth }, { "one-at-a-time", dtrace_hashstat_oneatatime }, { "fnv", dtrace_hashstat_fnv }, { NULL, 0 } }; typedef struct dtrace_aggstat_data { dtrace_hashstat_data_t dtagsd_hash; dtrace_hashstat_func_t dtagsd_func; } dtrace_aggstat_data_t; static int dtrace_aggstat_walk(uintptr_t addr, dtrace_aggkey_t *key, dtrace_aggstat_data_t *data) { dtrace_hashstat_data_t *hdata = &data->dtagsd_hash; size_t size; if (data->dtagsd_func == NULL) { size_t bucket = key->dtak_hashval % hdata->dthsd_hashsize; hdata->dthsd_counts[bucket]++; return (WALK_NEXT); } /* * We need to read the data. */ size = key->dtak_size - sizeof (dtrace_aggid_t); addr = (uintptr_t)key->dtak_data + sizeof (dtrace_aggid_t); hdata->dthsd_data = alloca(size); hdata->dthsd_size = size; if (mdb_vread(hdata->dthsd_data, size, addr) == -1) { mdb_warn("couldn't read data at %p", addr); return (WALK_ERR); } data->dtagsd_func(hdata); return (WALK_NEXT); } /*ARGSUSED*/ int dtrace_aggstat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dtrace_buffer_t buf; uintptr_t aaddr; dtrace_aggbuffer_t agb; size_t hsize, i, actual, prime, evenpow; dtrace_aggstat_data_t data; dtrace_hashstat_data_t *hdata = &data.dtagsd_hash; bzero(&data, sizeof (data)); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&buf, sizeof (buf), addr) == -1) { mdb_warn("failed to read aggregation buffer at %p", addr); return (DCMD_ERR); } aaddr = (uintptr_t)buf.dtb_tomax + buf.dtb_size - sizeof (dtrace_aggbuffer_t); if (mdb_vread(&agb, sizeof (agb), aaddr) == -1) { mdb_warn("failed to read dtrace_aggbuffer_t at %p", aaddr); return (DCMD_ERR); } hsize = (actual = agb.dtagb_hashsize) * sizeof (size_t); hdata->dthsd_counts = mdb_alloc(hsize, UM_SLEEP | UM_GC); /* * Now pick the largest prime smaller than the hash size. (If the * existing size is prime, we'll pick a smaller prime just for the * hell of it.) */ for (prime = agb.dtagb_hashsize - 1; prime > 7; prime--) { size_t limit = prime / 7; for (i = 2; i < limit; i++) { if ((prime % i) == 0) break; } if (i == limit) break; } /* * And now we want to pick the largest power of two smaller than the * hashsize. */ for (i = 0; (1 << i) < agb.dtagb_hashsize; i++) continue; evenpow = (1 << (i - 1)); for (i = 0; _dtrace_hashstat[i].dths_name != NULL; i++) { data.dtagsd_func = _dtrace_hashstat[i].dths_func; hdata->dthsd_hashsize = actual; hsize = hdata->dthsd_hashsize * sizeof (size_t); bzero(hdata->dthsd_counts, hsize); if (mdb_pwalk("dtrace_aggkey", (mdb_walk_cb_t)dtrace_aggstat_walk, &data, addr) == -1) { mdb_warn("failed to walk dtrace_aggkey at %p", addr); return (DCMD_ERR); } dtrace_hashstat_stats(_dtrace_hashstat[i].dths_name, hdata); /* * If we were just printing the actual value, we won't try * any of the sizing experiments. */ if (data.dtagsd_func == NULL) continue; hdata->dthsd_hashsize = prime; hsize = hdata->dthsd_hashsize * sizeof (size_t); bzero(hdata->dthsd_counts, hsize); if (mdb_pwalk("dtrace_aggkey", (mdb_walk_cb_t)dtrace_aggstat_walk, &data, addr) == -1) { mdb_warn("failed to walk dtrace_aggkey at %p", addr); return (DCMD_ERR); } dtrace_hashstat_stats(_dtrace_hashstat[i].dths_name, hdata); hdata->dthsd_hashsize = evenpow; hsize = hdata->dthsd_hashsize * sizeof (size_t); bzero(hdata->dthsd_counts, hsize); if (mdb_pwalk("dtrace_aggkey", (mdb_walk_cb_t)dtrace_aggstat_walk, &data, addr) == -1) { mdb_warn("failed to walk dtrace_aggkey at %p", addr); return (DCMD_ERR); } dtrace_hashstat_stats(_dtrace_hashstat[i].dths_name, hdata); } return (DCMD_OK); } /*ARGSUSED*/ static int dtrace_dynstat_walk(uintptr_t addr, dtrace_dynvar_t *dynvar, dtrace_aggstat_data_t *data) { dtrace_hashstat_data_t *hdata = &data->dtagsd_hash; dtrace_tuple_t *tuple = &dynvar->dtdv_tuple; dtrace_key_t *key = tuple->dtt_key; size_t size = 0, offs = 0; int i, nkeys = tuple->dtt_nkeys; char *buf; if (data->dtagsd_func == NULL) { size_t bucket = dynvar->dtdv_hashval % hdata->dthsd_hashsize; hdata->dthsd_counts[bucket]++; return (WALK_NEXT); } /* * We want to hand the hashing algorithm a contiguous buffer. First * run through the tuple and determine the size. */ for (i = 0; i < nkeys; i++) { if (key[i].dttk_size == 0) { size += sizeof (uint64_t); } else { size += key[i].dttk_size; } } buf = alloca(size); /* * Now go back through the tuple and copy the data into the buffer. */ for (i = 0; i < nkeys; i++) { if (key[i].dttk_size == 0) { bcopy(&key[i].dttk_value, &buf[offs], sizeof (uint64_t)); offs += sizeof (uint64_t); } else { if (mdb_vread(&buf[offs], key[i].dttk_size, key[i].dttk_value) == -1) { mdb_warn("couldn't read tuple data at %p", key[i].dttk_value); return (WALK_ERR); } offs += key[i].dttk_size; } } hdata->dthsd_data = buf; hdata->dthsd_size = size; data->dtagsd_func(hdata); return (WALK_NEXT); } /*ARGSUSED*/ int dtrace_dynstat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dtrace_dstate_t dstate; size_t hsize, i, actual, prime; dtrace_aggstat_data_t data; dtrace_hashstat_data_t *hdata = &data.dtagsd_hash; bzero(&data, sizeof (data)); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&dstate, sizeof (dstate), addr) == -1) { mdb_warn("failed to read dynamic variable state at %p", addr); return (DCMD_ERR); } hsize = (actual = dstate.dtds_hashsize) * sizeof (size_t); hdata->dthsd_counts = mdb_alloc(hsize, UM_SLEEP | UM_GC); /* * Now pick the largest prime smaller than the hash size. (If the * existing size is prime, we'll pick a smaller prime just for the * hell of it.) */ for (prime = dstate.dtds_hashsize - 1; prime > 7; prime--) { size_t limit = prime / 7; for (i = 2; i < limit; i++) { if ((prime % i) == 0) break; } if (i == limit) break; } for (i = 0; _dtrace_hashstat[i].dths_name != NULL; i++) { data.dtagsd_func = _dtrace_hashstat[i].dths_func; hdata->dthsd_hashsize = actual; hsize = hdata->dthsd_hashsize * sizeof (size_t); bzero(hdata->dthsd_counts, hsize); if (mdb_pwalk("dtrace_dynvar", (mdb_walk_cb_t)dtrace_dynstat_walk, &data, addr) == -1) { mdb_warn("failed to walk dtrace_dynvar at %p", addr); return (DCMD_ERR); } dtrace_hashstat_stats(_dtrace_hashstat[i].dths_name, hdata); /* * If we were just printing the actual value, we won't try * any of the sizing experiments. */ if (data.dtagsd_func == NULL) continue; hdata->dthsd_hashsize = prime; hsize = hdata->dthsd_hashsize * sizeof (size_t); bzero(hdata->dthsd_counts, hsize); if (mdb_pwalk("dtrace_dynvar", (mdb_walk_cb_t)dtrace_dynstat_walk, &data, addr) == -1) { mdb_warn("failed to walk dtrace_aggkey at %p", addr); return (DCMD_ERR); } dtrace_hashstat_stats(_dtrace_hashstat[i].dths_name, hdata); } return (DCMD_OK); } typedef struct dtrace_ecb_walk { dtrace_ecb_t **dtew_ecbs; int dtew_necbs; int dtew_curecb; } dtrace_ecb_walk_t; static int dtrace_ecb_init(mdb_walk_state_t *wsp) { uintptr_t addr; dtrace_state_t state; dtrace_ecb_walk_t *ecbwp; if ((addr = wsp->walk_addr) == 0) { mdb_warn("dtrace_ecb walk needs dtrace_state_t\n"); return (WALK_ERR); } if (mdb_vread(&state, sizeof (state), addr) == -1) { mdb_warn("failed to read dtrace state pointer at %p", addr); return (WALK_ERR); } ecbwp = mdb_zalloc(sizeof (dtrace_ecb_walk_t), UM_SLEEP | UM_GC); ecbwp->dtew_ecbs = state.dts_ecbs; ecbwp->dtew_necbs = state.dts_necbs; ecbwp->dtew_curecb = 0; wsp->walk_data = ecbwp; return (WALK_NEXT); } static int dtrace_ecb_step(mdb_walk_state_t *wsp) { uintptr_t ecbp, addr; dtrace_ecb_walk_t *ecbwp = wsp->walk_data; addr = (uintptr_t)ecbwp->dtew_ecbs + ecbwp->dtew_curecb * sizeof (dtrace_ecb_t *); if (ecbwp->dtew_curecb++ == ecbwp->dtew_necbs) return (WALK_DONE); if (mdb_vread(&ecbp, sizeof (addr), addr) == -1) { mdb_warn("failed to read ecb at entry %d\n", ecbwp->dtew_curecb); return (WALK_ERR); } if (ecbp == 0) return (WALK_NEXT); return (wsp->walk_callback(ecbp, NULL, wsp->walk_cbdata)); } static void dtrace_options_numtostr(uint64_t num, char *buf, size_t len) { uint64_t n = num; int index = 0; char u; while (n >= 1024) { n = (n + (1024 / 2)) / 1024; /* Round up or down */ index++; } u = " KMGTPE"[index]; if (index == 0) { (void) mdb_snprintf(buf, len, "%llu", (u_longlong_t)n); } else if (n < 10 && (num & (num - 1)) != 0) { uint64_t div = 1ULL << (10 * index); uint_t whole = (uint_t)(num / div); uint_t frac = (uint_t)((num % div) * 100 / div); (void) mdb_snprintf(buf, len, "%u.%02u%c", whole, frac, u); } else if (n < 100 && (num & (num - 1)) != 0) { uint64_t div = 1ULL << (10 * index); uint_t whole = (uint_t)(num / div); uint_t frac = (uint_t)((num % div) * 10 / div); (void) mdb_snprintf(buf, len, "%u.%1u%c", whole, frac, u); } else { (void) mdb_snprintf(buf, len, "%llu%c", (u_longlong_t)n, u); } } static void dtrace_options_numtohz(uint64_t num, char *buf, size_t len) { (void) mdb_snprintf(buf, len, "%dhz", NANOSEC/num); } static void dtrace_options_numtobufpolicy(uint64_t num, char *buf, size_t len) { char *policy = "unknown"; switch (num) { case DTRACEOPT_BUFPOLICY_RING: policy = "ring"; break; case DTRACEOPT_BUFPOLICY_FILL: policy = "fill"; break; case DTRACEOPT_BUFPOLICY_SWITCH: policy = "switch"; break; } (void) mdb_snprintf(buf, len, "%s", policy); } static void dtrace_options_numtocpu(uint64_t cpu, char *buf, size_t len) { if (cpu == DTRACE_CPUALL) (void) mdb_snprintf(buf, len, "%7s", "unbound"); else (void) mdb_snprintf(buf, len, "%d", cpu); } typedef void (*dtrace_options_func_t)(uint64_t, char *, size_t); static struct dtrace_options { char *dtop_optstr; dtrace_options_func_t dtop_func; } _dtrace_options[] = { { "bufsize", dtrace_options_numtostr }, { "bufpolicy", dtrace_options_numtobufpolicy }, { "dynvarsize", dtrace_options_numtostr }, { "aggsize", dtrace_options_numtostr }, { "specsize", dtrace_options_numtostr }, { "nspec", dtrace_options_numtostr }, { "strsize", dtrace_options_numtostr }, { "cleanrate", dtrace_options_numtohz }, { "cpu", dtrace_options_numtocpu }, { "bufresize", dtrace_options_numtostr }, { "grabanon", dtrace_options_numtostr }, { "flowindent", dtrace_options_numtostr }, { "quiet", dtrace_options_numtostr }, { "stackframes", dtrace_options_numtostr }, { "ustackframes", dtrace_options_numtostr }, { "aggrate", dtrace_options_numtohz }, { "switchrate", dtrace_options_numtohz }, { "statusrate", dtrace_options_numtohz }, { "destructive", dtrace_options_numtostr }, { "stackindent", dtrace_options_numtostr }, { "rawbytes", dtrace_options_numtostr }, { "jstackframes", dtrace_options_numtostr }, { "jstackstrsize", dtrace_options_numtostr }, { "aggsortkey", dtrace_options_numtostr }, { "aggsortrev", dtrace_options_numtostr }, { "aggsortpos", dtrace_options_numtostr }, { "aggsortkeypos", dtrace_options_numtostr }, { "temporal", dtrace_options_numtostr }, { "agghist", dtrace_options_numtostr }, { "aggpack", dtrace_options_numtostr }, { "aggzoom", dtrace_options_numtostr }, { "zone", dtrace_options_numtostr } }; CTASSERT(ARRAY_SIZE(_dtrace_options) == DTRACEOPT_MAX); static void dtrace_options_help(void) { mdb_printf("Given a dtrace_state_t structure, displays the " "current tunable option\nsettings.\n"); } /*ARGSUSED*/ static int dtrace_options(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dtrace_state_t state; int i = 0; dtrace_optval_t *options; char val[32]; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&state, sizeof (dtrace_state_t), (uintptr_t)addr) == -1) { mdb_warn("failed to read state pointer at %p\n", addr); return (DCMD_ERR); } options = &state.dts_options[0]; mdb_printf("%%-25s %s%\n", "OPTION", "VALUE"); for (i = 0; i < DTRACEOPT_MAX; i++) { if (options[i] == DTRACEOPT_UNSET) { mdb_printf("%-25s %s\n", _dtrace_options[i].dtop_optstr, "UNSET"); } else { (void) _dtrace_options[i].dtop_func(options[i], val, 32); mdb_printf("%-25s %s\n", _dtrace_options[i].dtop_optstr, val); } } return (DCMD_OK); } static int pid2state_init(mdb_walk_state_t *wsp) { dtrace_state_data_t *data; uintptr_t devi; uintptr_t proc; struct dev_info info; pid_t pid = (pid_t)wsp->walk_addr; if (wsp->walk_addr == 0) { mdb_warn("pid2state walk requires PID\n"); return (WALK_ERR); } data = mdb_zalloc(sizeof (dtrace_state_data_t), UM_SLEEP | UM_GC); if (mdb_readvar(&data->dtsd_softstate, "dtrace_softstate") == -1) { mdb_warn("failed to read 'dtrace_softstate'"); return (DCMD_ERR); } if ((proc = mdb_pid2proc(pid, NULL)) == 0) { mdb_warn("PID 0t%d not found\n", pid); return (DCMD_ERR); } if (mdb_readvar(&devi, "dtrace_devi") == -1) { mdb_warn("failed to read 'dtrace_devi'"); return (DCMD_ERR); } if (mdb_vread(&info, sizeof (struct dev_info), devi) == -1) { mdb_warn("failed to read 'dev_info'"); return (DCMD_ERR); } data->dtsd_major = info.devi_major; data->dtsd_proc = proc; wsp->walk_data = data; return (WALK_NEXT); } /*ARGSUSED*/ static int pid2state_file(uintptr_t addr, struct file *f, dtrace_state_data_t *data) { vnode_t vnode; minor_t minor; uintptr_t statep; /* Get the vnode for this file */ if (mdb_vread(&vnode, sizeof (vnode), (uintptr_t)f->f_vnode) == -1) { mdb_warn("couldn't read vnode at %p", (uintptr_t)f->f_vnode); return (WALK_NEXT); } /* Is this the dtrace device? */ if (getmajor(vnode.v_rdev) != data->dtsd_major) return (WALK_NEXT); /* Get the minor number for this device entry */ minor = getminor(vnode.v_rdev); if (mdb_get_soft_state_byaddr(data->dtsd_softstate, minor, &statep, NULL, 0) == -1) { mdb_warn("failed to read softstate for minor %d", minor); return (WALK_NEXT); } mdb_printf("%p\n", statep); return (WALK_NEXT); } static int pid2state_step(mdb_walk_state_t *wsp) { dtrace_state_data_t *ds = wsp->walk_data; if (mdb_pwalk("file", (mdb_walk_cb_t)pid2state_file, ds, ds->dtsd_proc) == -1) { mdb_warn("couldn't walk 'file' for proc %p", ds->dtsd_proc); return (WALK_ERR); } return (WALK_DONE); } /*ARGSUSED*/ static int dtrace_probes_walk(uintptr_t addr, void *ignored, uintptr_t *target) { dtrace_ecb_t ecb; dtrace_probe_t probe; dtrace_probedesc_t pd; if (addr == 0) return (WALK_ERR); if (mdb_vread(&ecb, sizeof (dtrace_ecb_t), addr) == -1) { mdb_warn("failed to read ecb %p\n", addr); return (WALK_ERR); } if (ecb.dte_probe == NULL) return (WALK_ERR); if (mdb_vread(&probe, sizeof (dtrace_probe_t), (uintptr_t)ecb.dte_probe) == -1) { mdb_warn("failed to read probe %p\n", ecb.dte_probe); return (WALK_ERR); } pd.dtpd_id = probe.dtpr_id; dtracemdb_probe(NULL, &pd); mdb_printf("%5d %10s %17s %33s %s\n", pd.dtpd_id, pd.dtpd_provider, pd.dtpd_mod, pd.dtpd_func, pd.dtpd_name); return (WALK_NEXT); } static void dtrace_probes_help(void) { mdb_printf("Given a dtrace_state_t structure, displays all " "its active enablings. If no\nstate structure is provided, " "all available probes are listed.\n"); } /*ARGSUSED*/ static int dtrace_probes(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dtrace_probedesc_t pd; uintptr_t caddr, base, paddr; int nprobes, i; mdb_printf("%5s %10s %17s %33s %s\n", "ID", "PROVIDER", "MODULE", "FUNCTION", "NAME"); if (!(flags & DCMD_ADDRSPEC)) { /* * If no argument is provided just display all available * probes. */ if (mdb_readvar(&base, "dtrace_probes") == -1) { mdb_warn("failed to read 'dtrace_probes'"); return (-1); } if (mdb_readvar(&nprobes, "dtrace_nprobes") == -1) { mdb_warn("failed to read 'dtrace_nprobes'"); return (-1); } for (i = 0; i < nprobes; i++) { caddr = base + i * sizeof (dtrace_probe_t *); if (mdb_vread(&paddr, sizeof (paddr), caddr) == -1) { mdb_warn("couldn't read probe pointer at %p", caddr); continue; } if (paddr == 0) continue; pd.dtpd_id = i + 1; if (dtracemdb_probe(NULL, &pd) == 0) { mdb_printf("%5d %10s %17s %33s %s\n", pd.dtpd_id, pd.dtpd_provider, pd.dtpd_mod, pd.dtpd_func, pd.dtpd_name); } } } else { if (mdb_pwalk("dtrace_ecb", (mdb_walk_cb_t)dtrace_probes_walk, NULL, addr) == -1) { mdb_warn("couldn't walk 'dtrace_ecb'"); return (DCMD_ERR); } } return (DCMD_OK); } const mdb_dcmd_t kernel_dcmds[] = { { "id2probe", ":", "translate a dtrace_id_t to a dtrace_probe_t", id2probe }, { "dtrace", ":[-c cpu]", "print dtrace(8)-like output", dtrace, dtrace_help }, { "dtrace_errhash", ":", "print DTrace error hash", dtrace_errhash }, { "dtrace_helptrace", ":", "print DTrace helper trace", dtrace_helptrace }, { "dtrace_state", ":", "print active DTrace consumers", dtrace_state, dtrace_state_help }, { "dtrace_aggstat", ":", "print DTrace aggregation hash statistics", dtrace_aggstat }, { "dtrace_dynstat", ":", "print DTrace dynamic variable hash statistics", dtrace_dynstat }, { "dtrace_options", ":", "print a DTrace consumer's current tuneable options", dtrace_options, dtrace_options_help }, { "dtrace_probes", "?", "print a DTrace consumer's enabled probes", dtrace_probes, dtrace_probes_help }, { NULL } }; const mdb_walker_t kernel_walkers[] = { { "dtrace_errhash", "walk hash of DTrace error messasges", dtrace_errhash_init, dtrace_errhash_step }, { "dtrace_helptrace", "walk DTrace helper trace entries", dtrace_helptrace_init, dtrace_helptrace_step }, { "dtrace_state", "walk DTrace per-consumer softstate", dtrace_state_init, dtrace_state_step }, { "dtrace_aggkey", "walk DTrace aggregation keys", dtrace_aggkey_init, dtrace_aggkey_step, dtrace_aggkey_fini }, { "dtrace_dynvar", "walk DTrace dynamic variables", dtrace_dynvar_init, dtrace_dynvar_step, dtrace_dynvar_fini }, { "dtrace_ecb", "walk a DTrace consumer's enabling control blocks", dtrace_ecb_init, dtrace_ecb_step }, { "pid2state", "walk a processes dtrace_state structures", pid2state_init, pid2state_step }, { NULL } }; #!/bin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # cat <<'HEADER' /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include const char * dof_sec_name(uint32_t type) { switch (type) { HEADER pattern='^#define[ ]DOF_SECT_\([A-Z0-9_]*\)[ ]*.*$' replace=' case DOF_SECT_\1: return ("\1");' sed -n "s/$pattern/$replace/p" cat <<'FOOTER' default: return (NULL); } } FOOTER /* * 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 * http://www.opensource.org/licenses/cddl1.txt. * 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 (c) 2004-2011 Emulex. All rights reserved. * Use is subject to license terms. * Copyright 2025 Oxide Computer Company */ #define DUMP_SUPPORT #include #include #include #include /* * MDB module linkage information: */ static const mdb_dcmd_t dcmds[] = { { DRIVER_NAME"_msgbuf", "", "dumps the "DRIVER_NAME " driver internal message buffer", emlxs_msgbuf, emlxs_msgbuf_help}, { DRIVER_NAME"_dump", " ", "dumps the "DRIVER_NAME " driver firmware core", emlxs_dump, emlxs_dump_help}, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * emlxs_msgbuf library */ void emlxs_msgbuf_help() { mdb_printf("Usage: ::%s_msgbuf \n\n", DRIVER_NAME); mdb_printf(" This is the %s driver instance " \ "number in hex.\n", DRIVER_NAME); mdb_printf(" (e.g. 0, 1,..., e, f, etc.)\n"); } /* emlxs_msgbuf_help() */ /*ARGSUSED*/ int emlxs_msgbuf(uintptr_t base_addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t addr; emlxs_device_t device; uint32_t brd_no; emlxs_msg_log_t log; uint32_t count; uint32_t first; uint32_t last; uint32_t idx; uint32_t i; char *level; emlxs_msg_t msg; char merge[1024]; emlxs_msg_entry_t entry; char buffer[256]; char buffer2[256]; int32_t instance[MAX_FC_BRDS]; char driver[32]; int32_t instance_count; uint32_t ddiinst; if (argc != 1 || argv[0].a_type != MDB_TYPE_STRING) { mdb_printf("Usage: ::%s_msgbuf \n", DRIVER_NAME); mdb_printf("mdb: try \"::help %s_msgbuf\" for more information", DRIVER_NAME); return (DCMD_ERR); } /* Get the device address */ mdb_snprintf(buffer, sizeof (buffer), "%s_device", DRIVER_NAME); if (mdb_readvar(&device, buffer) == -1) { mdb_snprintf(buffer2, sizeof (buffer2), "%s not found.\n", buffer); mdb_warn(buffer2); mdb_snprintf(buffer2, sizeof (buffer2), "Is the %s driver loaded ?\n", DRIVER_NAME); mdb_warn(buffer2); return (DCMD_ERR); } /* Get the device instance table */ mdb_snprintf(buffer, sizeof (buffer), "%s_instance", DRIVER_NAME); if (mdb_readvar(&instance, buffer) == -1) { mdb_snprintf(buffer2, sizeof (buffer2), "%s not found.\n", buffer); mdb_warn(buffer2); mdb_snprintf(buffer2, sizeof (buffer2), "Is the %s driver loaded ?\n", DRIVER_NAME); mdb_warn(buffer2); return (DCMD_ERR); } /* Get the device instance count */ mdb_snprintf(buffer, sizeof (buffer), "%s_instance_count", DRIVER_NAME); if (mdb_readvar(&instance_count, buffer) == -1) { mdb_snprintf(buffer2, sizeof (buffer2), "%s not found.\n", buffer); mdb_warn(buffer2); mdb_snprintf(buffer2, sizeof (buffer2), "Is the %s driver loaded ?\n", DRIVER_NAME); mdb_warn(buffer2); return (DCMD_ERR); } ddiinst = (uint32_t)mdb_strtoull(argv[0].a_un.a_str); for (brd_no = 0; brd_no < instance_count; brd_no++) { if (instance[brd_no] == ddiinst) { break; } } if (brd_no == instance_count) { mdb_warn("Device instance not found. ddinst=%d\n", ddiinst); return (DCMD_ERR); } /* Check if buffer is null */ addr = (uintptr_t)device.log[brd_no]; if (addr == 0) { mdb_warn("Device instance not found. ddinst=%d\n", ddiinst); return (0); } if (mdb_vread(&log, sizeof (emlxs_msg_log_t), addr) != sizeof (emlxs_msg_log_t)) { mdb_warn("\nUnable to read %d bytes @ %llx.\n", sizeof (emlxs_msg_log_t), addr); return (0); } /* Check if buffer is empty */ if (log.count == 0) { mdb_warn("Log buffer empty.\n"); return (0); } /* Get last entry id saved */ last = log.count - 1; /* Check if buffer has already been filled once */ if (log.count >= log.size) { first = log.count - log.size; idx = log.next; } else { /* Buffer not yet filled */ first = 0; idx = 0; } /* Get the total number of messages available for return */ count = last - first + 1; mdb_printf("\n"); /* Print the messages */ for (i = 0; i < count; i++) { if (mdb_vread(&entry, sizeof (emlxs_msg_entry_t), (uintptr_t)&log.entry[idx]) != sizeof (emlxs_msg_entry_t)) { mdb_warn("Cannot read log entry. index=%d count=%d\n", idx, count); return (DCMD_ERR); } if (mdb_vread(&msg, sizeof (emlxs_msg_t), (uintptr_t)entry.msg) != sizeof (emlxs_msg_t)) { mdb_warn("Cannot read msg. index=%d count=%d\n", idx, count); return (DCMD_ERR); } switch (msg.level) { case EMLXS_DEBUG: level = " DEBUG"; break; case EMLXS_NOTICE: level = " NOTICE"; break; case EMLXS_WARNING: level = "WARNING"; break; case EMLXS_ERROR: level = " ERROR"; break; case EMLXS_PANIC: level = " PANIC"; break; default: level = "UNKNOWN"; break; } if (entry.vpi == 0) { mdb_snprintf(driver, sizeof (driver), "%s%d", DRIVER_NAME, entry.instance); } else { mdb_snprintf(driver, sizeof (driver), "%s%d.%d", DRIVER_NAME, entry.instance, entry.vpi); } /* Generate the message string */ if (msg.buffer[0] != 0) { if (entry.buffer[0] != 0) { mdb_snprintf(merge, sizeof (merge), "[%Y:%03d:%03d:%03d] " "%6d:[%1X.%04X]%s:%7s:%4d:\n%s\n(%s)\n", entry.id_time.tv_sec, (int)entry.id_time.tv_nsec/1000000, (int)(entry.id_time.tv_nsec/1000)%1000, (int)entry.id_time.tv_nsec%1000, entry.id, entry.fileno, entry.line, driver, level, msg.id, msg.buffer, entry.buffer); } else { mdb_snprintf(merge, sizeof (merge), "[%Y:%03d:%03d:%03d] " "%6d:[%1X.%04X]%s:%7s:%4d:\n%s\n", entry.id_time.tv_sec, (int)entry.id_time.tv_nsec/1000000, (int)(entry.id_time.tv_nsec/1000)%1000, (int)entry.id_time.tv_nsec%1000, entry.id, entry.fileno, entry.line, driver, level, msg.id, msg.buffer); } } else { if (entry.buffer[0] != 0) { mdb_snprintf(merge, sizeof (merge), "[%Y:%03d:%03d:%03d] " "%6d:[%1X.%04X]%s:%7s:%4d:\n(%s)\n", entry.id_time.tv_sec, (int)entry.id_time.tv_nsec/1000000, (int)(entry.id_time.tv_nsec/1000)%1000, (int)entry.id_time.tv_nsec%1000, entry.id, entry.fileno, entry.line, driver, level, msg.id, entry.buffer); } else { mdb_snprintf(merge, sizeof (merge), "[%Y:%03d:%03d:%03d] " "%6d:[%1X.%04X]%s:%7s:%4d:\n%s\n", entry.id_time.tv_sec, (int)entry.id_time.tv_nsec/1000000, (int)(entry.id_time.tv_nsec/1000)%1000, (int)entry.id_time.tv_nsec%1000, entry.id, entry.fileno, entry.line, driver, level, msg.id, msg.buffer); } } mdb_printf("%s", merge); /* Increment index */ if (++idx >= log.size) { idx = 0; } } mdb_printf("\n"); return (0); } /* emlxs_msgbuf() */ void emlxs_dump_help() { mdb_printf("Usage: ::%s_dump all \n", DRIVER_NAME); mdb_printf(" ::%s_dump txt \n", DRIVER_NAME); mdb_printf(" ::%s_dump dmp \n", DRIVER_NAME); mdb_printf(" ::%s_dump cee \n", DRIVER_NAME); mdb_printf("\n"); mdb_printf(" txt Display firmware text summary " \ "file.\n"); mdb_printf(" dmp Display firmware dmp binary file.\n"); mdb_printf(" cee Display firmware cee binary file. " \ "(FCOE adapters only)\n"); mdb_printf(" all Display all firmware core files.\n"); mdb_printf(" This is the %s driver instance " \ "number in hex.\n", DRIVER_NAME); mdb_printf(" (e.g. 0, 1,..., e, f, etc.)\n"); } /* emlxs_dump_help() */ /*ARGSUSED*/ int emlxs_dump(uintptr_t base_addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t addr; emlxs_device_t device; uint32_t brd_no; uint32_t i; char buffer[256]; char buffer2[256]; int32_t instance[MAX_FC_BRDS]; int32_t instance_count; uint32_t ddiinst; uint8_t *bptr; char *cptr; emlxs_file_t dump_txtfile; emlxs_file_t dump_dmpfile; emlxs_file_t dump_ceefile; uint32_t size; uint32_t file; if (argc != 2 || argv[0].a_type != MDB_TYPE_STRING || argv[1].a_type != MDB_TYPE_STRING) { goto usage; } if ((strcmp(argv[0].a_un.a_str, "all") == 0) || (strcmp(argv[0].a_un.a_str, "ALL") == 0) || (strcmp(argv[0].a_un.a_str, "All") == 0)) { file = 0; } else if ((strcmp(argv[0].a_un.a_str, "txt") == 0) || (strcmp(argv[0].a_un.a_str, "TXT") == 0) || (strcmp(argv[0].a_un.a_str, "Txt") == 0)) { file = 1; } else if ((strcmp(argv[0].a_un.a_str, "dmp") == 0) || (strcmp(argv[0].a_un.a_str, "DMP") == 0) || (strcmp(argv[0].a_un.a_str, "Dmp") == 0)) { file = 2; } else if ((strcmp(argv[0].a_un.a_str, "cee") == 0) || (strcmp(argv[0].a_un.a_str, "CEE") == 0) || (strcmp(argv[0].a_un.a_str, "Cee") == 0)) { file = 3; } else { goto usage; } /* Get the device address */ mdb_snprintf(buffer, sizeof (buffer), "%s_device", DRIVER_NAME); if (mdb_readvar(&device, buffer) == -1) { mdb_snprintf(buffer2, sizeof (buffer2), "%s not found.\n", buffer); mdb_warn(buffer2); mdb_snprintf(buffer2, sizeof (buffer2), "Is the %s driver loaded ?\n", DRIVER_NAME); mdb_warn(buffer2); return (DCMD_ERR); } /* Get the device instance table */ mdb_snprintf(buffer, sizeof (buffer), "%s_instance", DRIVER_NAME); if (mdb_readvar(&instance, buffer) == -1) { mdb_snprintf(buffer2, sizeof (buffer2), "%s not found.\n", buffer); mdb_warn(buffer2); mdb_snprintf(buffer2, sizeof (buffer2), "Is the %s driver loaded ?\n", DRIVER_NAME); mdb_warn(buffer2); return (DCMD_ERR); } /* Get the device instance count */ mdb_snprintf(buffer, sizeof (buffer), "%s_instance_count", DRIVER_NAME); if (mdb_readvar(&instance_count, buffer) == -1) { mdb_snprintf(buffer2, sizeof (buffer2), "%s not found.\n", buffer); mdb_warn(buffer2); mdb_snprintf(buffer2, sizeof (buffer2), "Is the %s driver loaded ?\n", DRIVER_NAME); mdb_warn(buffer2); return (DCMD_ERR); } ddiinst = (uint32_t)mdb_strtoull(argv[1].a_un.a_str); for (brd_no = 0; brd_no < instance_count; brd_no++) { if (instance[brd_no] == ddiinst) { break; } } if (brd_no == instance_count) { mdb_warn("Device instance not found. ddinst=%d\n", ddiinst); return (DCMD_ERR); } if (file == 0 || file == 1) { addr = (uintptr_t)device.dump_txtfile[brd_no]; if (addr == 0) { mdb_warn("TXT file: Device instance not found. " \ "ddinst=%d\n", ddiinst); goto dmp_file; } if (mdb_vread(&dump_txtfile, sizeof (dump_txtfile), addr) != sizeof (dump_txtfile)) { mdb_warn("TXT file: Unable to read %d bytes @ %llx.\n", sizeof (dump_txtfile), addr); goto dmp_file; } size = (uintptr_t)dump_txtfile.ptr - (uintptr_t)dump_txtfile.buffer; if (size == 0) { mdb_printf("TXT file: Not available.\n"); goto dmp_file; } bptr = (uint8_t *)mdb_zalloc(size, UM_SLEEP|UM_GC); if (bptr == 0) { mdb_warn("TXT file: Unable to allocate file buffer. " \ "ddinst=%d size=%d\n", ddiinst, size); goto dmp_file; } if (mdb_vread(bptr, size, (uintptr_t)dump_txtfile.buffer) != size) { mdb_warn("TXT file: Unable to read %d bytes @ %llx.\n", size, dump_txtfile.buffer); goto dmp_file; } mdb_printf("\n"); mdb_printf("\n"); mdb_printf("%s", bptr); mdb_printf("\n"); mdb_printf("\n"); } dmp_file: if (file == 0 || file == 2) { addr = (uintptr_t)device.dump_dmpfile[brd_no]; if (addr == 0) { mdb_warn("DMP file: Device instance not found. " \ "ddinst=%d\n", ddiinst); goto cee_file; } if (mdb_vread(&dump_dmpfile, sizeof (dump_dmpfile), addr) != sizeof (dump_dmpfile)) { mdb_warn("DMP file: Unable to read %d bytes @ %llx.\n", sizeof (dump_dmpfile), addr); goto cee_file; } size = (uintptr_t)dump_dmpfile.ptr - (uintptr_t)dump_dmpfile.buffer; if (size == 0) { mdb_printf("DMP file: Not available.\n"); goto cee_file; } bptr = (uint8_t *)mdb_zalloc(size, UM_SLEEP|UM_GC); if (bptr == 0) { mdb_warn("DMP file: Unable to allocate file buffer. " \ "ddinst=%d size=%d\n", ddiinst, size); goto cee_file; } if (mdb_vread(bptr, size, (uintptr_t)dump_dmpfile.buffer) != size) { mdb_warn("DMP file: Unable to read %d bytes @ %llx.\n", size, dump_dmpfile.buffer); goto cee_file; } mdb_printf("\n"); mdb_printf("\n"); bzero(buffer2, sizeof (buffer2)); cptr = buffer2; for (i = 0; i < size; i++) { if (i && !(i % 16)) { mdb_printf(" %s\n", buffer2); bzero(buffer2, sizeof (buffer2)); cptr = buffer2; } if (!(i % 16)) { mdb_printf("%08X: ", i); } if (!(i % 4)) { mdb_printf(" "); } if ((*bptr >= 32) && (*bptr <= 126)) { *cptr++ = *bptr; } else { *cptr++ = '.'; } mdb_printf("%02X ", *bptr++); } size = 16 - (i % 16); for (i = 0; size < 16 && i < size; i++) { if (!(i % 4)) { mdb_printf(" "); } mdb_printf(" "); } mdb_printf(" %s\n", buffer2); mdb_printf("\n"); mdb_printf("\n"); } cee_file: if (file == 0 || file == 3) { addr = (uintptr_t)device.dump_ceefile[brd_no]; if (addr == 0) { mdb_warn("CEE file: Device instance not found. " \ "ddinst=%d\n", ddiinst); goto done; } if (mdb_vread(&dump_ceefile, sizeof (dump_ceefile), addr) != sizeof (dump_ceefile)) { mdb_warn("CEE file: Unable to read %d bytes @ %llx.\n", sizeof (dump_ceefile), addr); goto done; } size = (uintptr_t)dump_ceefile.ptr - (uintptr_t)dump_ceefile.buffer; if (size == 0) { mdb_printf("CEE file: Not available.\n"); goto done; } bptr = (uint8_t *)mdb_zalloc(size, UM_SLEEP|UM_GC); if (bptr == 0) { mdb_warn("CEE file: Unable to allocate file buffer. " \ "ddinst=%d size=%d\n", ddiinst, size); goto done; } if (mdb_vread(bptr, size, (uintptr_t)dump_ceefile.buffer) != size) { mdb_warn("CEE file: Unable to read %d bytes @ %llx.\n", size, dump_ceefile.buffer); goto done; } mdb_printf("\n"); mdb_printf("\n"); bzero(buffer2, sizeof (buffer2)); cptr = buffer2; for (i = 0; i < size; i++) { if (i && !(i % 16)) { mdb_printf(" %s\n", buffer2); bzero(buffer2, sizeof (buffer2)); cptr = buffer2; } if (!(i % 16)) { mdb_printf("%08X: ", i); } if (!(i % 4)) { mdb_printf(" "); } if ((*bptr >= 32) && (*bptr <= 126)) { *cptr++ = *bptr; } else { *cptr++ = '.'; } mdb_printf("%02X ", *bptr++); } size = 16 - (i % 16); for (i = 0; size < 16 && i < size; i++) { if (!(i % 4)) { mdb_printf(" "); } mdb_printf(" "); } mdb_printf(" %s\n", buffer2); mdb_printf("\n"); mdb_printf("\n"); } done: mdb_printf("\n"); return (0); usage: mdb_printf("Usage: ::%s_dump \n", DRIVER_NAME); mdb_printf("mdb: try \"::help %s_dump\" for more information", DRIVER_NAME); return (DCMD_ERR); } /* emlxs_dump() */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * FCIP mdb module */ #include #include #include #include #include #include #include #include /* * Leadville fcip walker/dcmd code */ static int fcip_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "fcip_port_head") == -1) { mdb_warn("failed to read 'fcip_port_head'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (fcip_port_info_t), UM_SLEEP); return (WALK_NEXT); } static int fcip_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (fcip_port_info_t), wsp->walk_addr) == -1) { mdb_warn("failed to read fcip_port_info at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((fcip_port_info_t *)wsp->walk_data)->fcipp_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. Since we * dynamically allocated a fc_fca_port_t in port_walk_i, we must free it now. */ static void fcip_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (fc_fca_port_t)); } static int fcip(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { fcip_port_info_t pinfo; if (argc != 0) { return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("fcip", "fcip", argc, argv) == -1) { mdb_warn("failed to walk 'fcip_port_head'"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%12s %12s %12s %16s %16s\n", "FCIP Struct", "Handle", "DIP", "Port WWN", "Node WWN"); /* * For each port, we just need to read the fc_fca_port_t struct, read * the port_handle */ if (mdb_vread(&pinfo, sizeof (fcip_port_info_t), addr) == sizeof (fcip_port_info_t)) { mdb_printf("%12p %12p %12p %02x%02x%02x%02x%02x%02x%02x%02x " "%02x%02x%02x%02x%02x%02x%02x%02x\n", pinfo.fcipp_fcip, pinfo.fcipp_handle, pinfo.fcipp_dip, pinfo.fcipp_pwwn.raw_wwn[0], pinfo.fcipp_pwwn.raw_wwn[1], pinfo.fcipp_pwwn.raw_wwn[2], pinfo.fcipp_pwwn.raw_wwn[3], pinfo.fcipp_pwwn.raw_wwn[4], pinfo.fcipp_pwwn.raw_wwn[5], pinfo.fcipp_pwwn.raw_wwn[6], pinfo.fcipp_pwwn.raw_wwn[7], pinfo.fcipp_nwwn.raw_wwn[0], pinfo.fcipp_nwwn.raw_wwn[1], pinfo.fcipp_nwwn.raw_wwn[2], pinfo.fcipp_nwwn.raw_wwn[3], pinfo.fcipp_nwwn.raw_wwn[4], pinfo.fcipp_nwwn.raw_wwn[5], pinfo.fcipp_nwwn.raw_wwn[6], pinfo.fcipp_nwwn.raw_wwn[7]); } else mdb_warn("failed to read port info at %p", addr); return (DCMD_OK); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, a list of structures * describing our walkers, and a function named _mdb_init to return a pointer * to our module information. */ static const mdb_dcmd_t dcmds[] = { { "fcip", NULL, "Leadville fcip instances", fcip }, { NULL } }; static const mdb_walker_t walkers[] = { { "fcip", "walk list of Leadville fcip instances", fcip_walk_i, fcip_walk_s, fcip_walk_f }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 2018, Joyent, Inc. */ #include #include #include #include #include #include #include static struct fcp_port port; static struct fcp_tgt tgt; static struct fcp_lun lun; static uint32_t tgt_hash_index; /* * Leadville fcp walker/dcmd code */ static int fcp_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "fcp_port_head") == -1) { mdb_warn("failed to read 'fcp_port_head'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (struct fcp_port), UM_SLEEP); return (WALK_NEXT); } static int fcp_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct fcp_port), wsp->walk_addr) == -1) { mdb_warn("failed to read fcp_port at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct fcp_port *)wsp->walk_data)->port_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void fcp_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct fcp_port)); } static int fcp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct fcp_port pinfo; if (argc != 0) { return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("fcp", "fcp", argc, argv) == -1) { mdb_warn("failed to walk 'fcp_port_head'"); return (DCMD_ERR); } return (DCMD_OK); } mdb_printf("FCP structure at %p\n", addr); /* * For each port, we just need to read the fc_fca_port_t struct, read * the port_handle */ if (mdb_vread(&pinfo, sizeof (struct fcp_port), addr) != sizeof (struct fcp_port)) { mdb_warn("failed to read fcp_port at %p", addr); return (DCMD_OK); } mdb_printf(" mutex : 0x%-08x\n", pinfo.port_mutex); mdb_printf(" ipkt_list : 0x%p\n", pinfo.port_ipkt_list); mdb_printf(" state : 0x%-08x\n", pinfo.port_state); mdb_printf(" phys_state : 0x%-08x\n", pinfo.port_phys_state); mdb_printf(" top : %u\n", pinfo.port_topology); mdb_printf(" sid : 0x%-06x\n", pinfo.port_id); mdb_printf(" reset_list : 0x%p\n", pinfo.port_reset_list); mdb_printf(" link_cnt : %u\n", pinfo.port_link_cnt); mdb_printf(" deadline : %d\n", pinfo.port_deadline); mdb_printf(" port wwn : " "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", pinfo.port_pwwn.raw_wwn[0], pinfo.port_pwwn.raw_wwn[1], pinfo.port_pwwn.raw_wwn[2], pinfo.port_pwwn.raw_wwn[3], pinfo.port_pwwn.raw_wwn[4], pinfo.port_pwwn.raw_wwn[5], pinfo.port_pwwn.raw_wwn[6], pinfo.port_pwwn.raw_wwn[7]); mdb_printf(" node wwn : " "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", pinfo.port_nwwn.raw_wwn[0], pinfo.port_nwwn.raw_wwn[1], pinfo.port_nwwn.raw_wwn[2], pinfo.port_nwwn.raw_wwn[3], pinfo.port_nwwn.raw_wwn[4], pinfo.port_nwwn.raw_wwn[5], pinfo.port_nwwn.raw_wwn[6], pinfo.port_nwwn.raw_wwn[7]); mdb_printf(" handle : 0x%p\n", pinfo.port_fp_handle); mdb_printf(" cmd_mutex : 0x%-08x\n", pinfo.port_pkt_mutex); mdb_printf(" ncmds : %u\n", pinfo.port_npkts); mdb_printf(" pkt_head : 0x%p\n", pinfo.port_pkt_head); mdb_printf(" pkt_tail : 0x%p\n", pinfo.port_pkt_tail); mdb_printf(" ipkt_cnt : %d\n", pinfo.port_ipkt_cnt); mdb_printf(" instance : %u\n", pinfo.port_instance); mdb_printf(" max_exch : %u\n", pinfo.port_max_exch); mdb_printf(" cmds_aborted : 0x%-08x\n", pinfo.port_reset_action); mdb_printf(" cmds_dma_flags : 0x%-08x\n", pinfo.port_cmds_dma_flags); mdb_printf(" fcp_dma : 0x%-08x\n", pinfo.port_fcp_dma); mdb_printf(" priv_pkt_len : %u\n", pinfo.port_priv_pkt_len); mdb_printf(" data_dma_attr : 0x%-08x\n", pinfo.port_data_dma_attr); mdb_printf(" cmd_dma_attr : 0x%-08x\n", pinfo.port_cmd_dma_attr); mdb_printf(" resp_dma_attr : 0x%-08x\n", pinfo.port_resp_dma_attr); mdb_printf(" dma_acc_attr : 0x%-08x\n", pinfo.port_dma_acc_attr); mdb_printf(" tran : 0x%p\n", pinfo.port_tran); mdb_printf(" dip : 0x%p\n", pinfo.port_dip); mdb_printf(" reset_notify_listf: 0x%p\n", pinfo.port_reset_notify_listf); mdb_printf(" event_defs : 0x%p\n", pinfo.port_ndi_event_defs); mdb_printf(" event_hdl : 0x%p\n", pinfo.port_ndi_event_hdl); mdb_printf(" events : 0x%p\n", pinfo.port_ndi_events); mdb_printf(" tgt_hash_table : 0x%p\n", pinfo.port_tgt_hash_table); mdb_printf(" mpxio : %d\n", pinfo.port_mpxio); mdb_printf("\n"); return (DCMD_OK); } /* * Leadville cmds walker/dcmd code */ static int cmds_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("Can not perform global walk"); return (WALK_ERR); } /* * Input should be a fcp_lun, so read it to get the fcp_pkt * lists's head */ if (mdb_vread(&lun, sizeof (struct fcp_lun), wsp->walk_addr) != sizeof (struct fcp_lun)) { mdb_warn("Unable to read in the fcp_lun structure address\n"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(lun.lun_pkt_head); wsp->walk_data = mdb_alloc(sizeof (struct fcp_pkt), UM_SLEEP); return (WALK_NEXT); } static int cmds_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct fcp_pkt), wsp->walk_addr) == -1) { mdb_warn("failed to read fcp_pkt at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct fcp_pkt *)wsp->walk_data)->cmd_forw); return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void cmds_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct fcp_pkt)); } /* * Leadville luns walker/dcmd code */ static int luns_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("Can not perform global walk"); return (WALK_ERR); } /* * Input should be a fcp_tgt, so read it to get the fcp_lun * lists's head */ if (mdb_vread(&tgt, sizeof (struct fcp_tgt), wsp->walk_addr) != sizeof (struct fcp_tgt)) { mdb_warn("Unable to read in the fcp_tgt structure address\n"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(tgt.tgt_lun); wsp->walk_data = mdb_alloc(sizeof (struct fcp_lun), UM_SLEEP); return (WALK_NEXT); } static int luns_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct fcp_lun), wsp->walk_addr) == -1) { mdb_warn("failed to read fcp_pkt at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct fcp_lun *)wsp->walk_data)->lun_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void luns_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct fcp_lun)); } /* * Leadville targets walker/dcmd code */ static int targets_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("Can not perform global walk\n"); return (WALK_ERR); } /* * Input should be a fcp_port, so read it to get the port_tgt * table's head */ if (mdb_vread(&port, sizeof (struct fcp_port), wsp->walk_addr) != sizeof (struct fcp_port)) { mdb_warn("Unable to read in the port structure address\n"); return (WALK_ERR); } tgt_hash_index = 0; while (tgt_hash_index < FCP_NUM_HASH && port.port_tgt_hash_table[tgt_hash_index] == NULL) { tgt_hash_index++; } wsp->walk_addr = (uintptr_t)(port.port_tgt_hash_table[tgt_hash_index]); wsp->walk_data = mdb_alloc(sizeof (struct fcp_tgt), UM_SLEEP); return (WALK_NEXT); } static int targets_walk_s(mdb_walk_state_t *wsp) { int status; if ((wsp->walk_addr == 0) && (tgt_hash_index >= (FCP_NUM_HASH - 1))) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (struct fcp_tgt), wsp->walk_addr) == -1) { mdb_warn("failed to read fcp_tgt at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct fcp_tgt *)wsp->walk_data)->tgt_next); if (wsp->walk_addr == 0) { /* * locate the next hash list */ tgt_hash_index++; while (tgt_hash_index < FCP_NUM_HASH && port.port_tgt_hash_table[tgt_hash_index] == NULL) tgt_hash_index++; if (tgt_hash_index == FCP_NUM_HASH) { /* You're done */ return (status); } wsp->walk_addr = (uintptr_t)(port.port_tgt_hash_table[tgt_hash_index]); } return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void targets_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct fcp_tgt)); } /* * Leadville fcp_ipkt walker/dcmd code */ static int ipkt_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("The address of a fcp_port" " structure must be given\n"); return (WALK_ERR); } /* * Input should be a fcp_port, so read it to get the ipkt * list's head */ if (mdb_vread(&port, sizeof (struct fcp_port), wsp->walk_addr) != sizeof (struct fcp_port)) { mdb_warn("Failed to read in the fcp_port" " at 0x%p\n", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(port.port_ipkt_list); wsp->walk_data = mdb_alloc(sizeof (struct fcp_ipkt), UM_SLEEP); return (WALK_NEXT); } static int ipkt_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct fcp_ipkt), wsp->walk_addr) == -1) { mdb_warn("Failed to read in the fcp_ipkt" " at 0x%p\n", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct fcp_ipkt *)wsp->walk_data)->ipkt_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void ipkt_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct fcp_ipkt)); } /* * Leadville fcp_pkt walker/dcmd code */ static int pkt_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("The address of a fcp_port" " structure must be given\n"); return (WALK_ERR); } /* * Input should be an fcp_port, so read it to get the pkt * list's head */ if (mdb_vread(&port, sizeof (struct fcp_port), wsp->walk_addr) != sizeof (struct fcp_port)) { mdb_warn("Failed to read in the fcp_port" " at 0x%p\n", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(port.port_pkt_head); wsp->walk_data = mdb_alloc(sizeof (struct fcp_pkt), UM_SLEEP); return (WALK_NEXT); } static int pkt_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct fcp_pkt), wsp->walk_addr) == -1) { mdb_warn("Failed to read in the fcp_pkt" " at 0x%p\n", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct fcp_pkt *)wsp->walk_data)->cmd_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void pkt_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct fcp_pkt)); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, a list of structures * describing our walkers, and a function named _mdb_init to return a pointer * to our module information. */ static const mdb_dcmd_t dcmds[] = { { "fcp", NULL, "Leadville fcp instances", fcp }, { NULL } }; static const mdb_walker_t walkers[] = { { "fcp", "Walk list of Leadville fcp instances", fcp_walk_i, fcp_walk_s, fcp_walk_f }, { "cmds", "Walk list of SCSI commands in fcp's per-lun queue", cmds_walk_i, cmds_walk_s, cmds_walk_f }, { "luns", "Walk list of LUNs in an fcp target", luns_walk_i, luns_walk_s, luns_walk_f }, { "targets", "Walk list of fcp targets attached to the local port", targets_walk_i, targets_walk_s, targets_walk_f }, { "fcp_ipkt", "Walk list of internal packets queued on a local port", ipkt_walk_i, ipkt_walk_s, ipkt_walk_f}, { "fcp_pkt", "Walk list of packets queued on a local port", pkt_walk_i, pkt_walk_s, pkt_walk_f}, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include /* * If we #include then other definitions fail. This is * the easiest way of getting access to the function */ extern char *strtok(char *string, const char *sepset); /* we need 26 bytes for the cftime() call */ #define TIMESTAMPSIZE 26 * sizeof (char) /* for backward compatibility */ typedef struct fc_trace_dmsgv1 { int id_size; int id_flag; time_t id_time; caddr_t id_buf; struct fc_trace_dmsgv1 *id_next; } fc_trace_dmsgv1_t; static struct pwwn_hash *fp_pwwn_table; static struct d_id_hash *fp_did_table; static uint32_t pd_hash_index; struct fc_local_port port; /* * Leadville port walker/dcmd code */ /* * Initialize the fc_fca_port_t walker by either using the given starting * address, or reading the value of the kernel's fctl_fca_portlist pointer. * We also allocate a fc_fca_port_t for storage, and save this using the * walk_data pointer. */ static int port_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "fctl_fca_portlist") == -1) { mdb_warn("failed to read 'fctl_fca_portlist'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (fc_fca_port_t), UM_SLEEP); return (WALK_NEXT); } /* * At each step, read a fc_fca_port_t into our private storage, and then invoke * the callback function. We terminate when we reach a NULL p_next pointer. */ static int port_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (fc_fca_port_t), wsp->walk_addr) == -1) { mdb_warn("failed to read fc_fca_port_t at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((fc_fca_port_t *)wsp->walk_data)->port_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. Since we * dynamically allocated a fc_fca_port_t in port_walk_i, we must free it now. */ static void port_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (fc_fca_port_t)); } static int ports(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { fc_fca_port_t portlist; fc_local_port_t port; int longlist = FALSE; if (argc > 1) { return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'l', MDB_OPT_SETBITS, TRUE, &longlist, NULL) != argc) { return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { if (longlist == 0) { if (mdb_walk_dcmd("ports", "ports", argc, argv) == -1) { mdb_warn("failed to walk 'fctl_fca_portlist'"); return (DCMD_ERR); } } else { if (mdb_walk_dcmd("ports", "fcport", argc, argv) == -1) { mdb_warn("failed to walk 'fctl_fca_portlist'"); return (DCMD_ERR); } } return (DCMD_OK); } /* * If this is the first invocation of the command, print a nice * header line for the output that will follow. */ if (DCMD_HDRSPEC(flags)) mdb_printf("%16s %-2s %4s %-4s%16s %16s %16s\n", "Port", "I#", "State", "Soft", "FCA Handle", "Port DIP", "FCA Port DIP"); /* * For each port, we just need to read the fc_fca_port_t struct, read * the port_handle */ if (mdb_vread(&portlist, sizeof (fc_fca_port_t), addr) == sizeof (fc_fca_port_t)) { /* * Now read that port in */ if (mdb_vread(&port, sizeof (fc_local_port_t), (uintptr_t) portlist.port_handle) == sizeof (fc_local_port_t)) { mdb_printf("%16p %2d %4x %4x %16p %16p %16p\n", portlist.port_handle, port.fp_instance, port.fp_state, port.fp_soft_state, port.fp_fca_handle, port.fp_port_dip, port.fp_fca_dip); } else mdb_warn("failed to read port at %p", portlist.port_handle); } else mdb_warn("failed to read port info at %p", addr); return (DCMD_OK); } /* * Leadville ULP walker/dcmd code */ static int ulp_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "fctl_ulp_list") == -1) { mdb_warn("failed to read 'fctl_ulp_list'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (fc_ulp_list_t), UM_SLEEP); return (WALK_NEXT); } static int ulp_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (fc_ulp_list_t), wsp->walk_addr) == -1) { mdb_warn("failed to read fctl_ulp_list %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((fc_ulp_list_t *)wsp->walk_data)->ulp_next); return (status); } static void ulp_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (fc_ulp_list_t)); } static int ulps(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { fc_ulp_list_t ulplist; fc_ulp_modinfo_t ulp; char ulp_name[30]; if (argc != 0) { return (DCMD_USAGE); } /* * If no fc_ulp_list_t address was specified on the command line, we can * print out all processes by invoking the walker, using this * dcmd itself as the callback. */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("ulps", "ulps", argc, argv) == -1) { mdb_warn("failed to walk 'fc_ulp_list_t'"); return (DCMD_ERR); } return (DCMD_OK); } /* * If this is the first invocation of the command, print a nice * header line for the output that will follow. */ if (DCMD_HDRSPEC(flags)) mdb_printf("%30s %4s %8s\n", "ULP Name", "Type", "Revision"); /* * For each port, we just need to read the fc_fca_port_t struct, read * the port_handle */ if (mdb_vread(&ulplist, sizeof (fc_ulp_list_t), addr) == sizeof (fc_ulp_list_t)) { /* * Now read that port in */ if (mdb_vread(&ulp, sizeof (fc_ulp_modinfo_t), (uintptr_t)ulplist.ulp_info) == sizeof (fc_ulp_modinfo_t)) { if (mdb_vread(&ulp_name, 30, (uintptr_t)ulp.ulp_name) > 0) { mdb_printf("%30s %4x %8x\n", ulp_name, ulp.ulp_type, ulp.ulp_rev); } } else mdb_warn("failed to read ulp at %p", ulplist.ulp_info); } else mdb_warn("failed to read ulplist at %p", addr); return (DCMD_OK); } /* * Leadville ULP module walker/dcmd code */ static int ulpmod_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "fctl_ulp_modules") == -1) { mdb_warn("failed to read 'fctl_ulp_modules'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (fc_ulp_module_t), UM_SLEEP); return (WALK_NEXT); } static int ulpmod_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (fc_ulp_module_t), wsp->walk_addr) == -1) { mdb_warn("failed to read fctl_ulp_modules %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((fc_ulp_module_t *)wsp->walk_data)->mod_next); return (status); } static void ulpmod_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (fc_ulp_module_t)); } static int ulpmods(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { fc_ulp_module_t modlist; fc_ulp_modinfo_t modinfo; fc_ulp_ports_t ulp_port; if (argc != 0) { return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("ulpmods", "ulpmods", argc, argv) == -1) { mdb_warn("failed to walk 'fc_ulp_module_t'"); return (DCMD_ERR); } return (DCMD_OK); } /* * If this is the first invocation of the command, print a nice * header line for the output that will follow. */ if (DCMD_HDRSPEC(flags)) mdb_printf("%4s %16s %8s %8s\n", "Type", "Port Handle", "dstate", "statec"); /* * For each port, we just need to read the fc_fca_port_t struct, read * the port_handle */ if (mdb_vread(&modlist, sizeof (fc_ulp_module_t), addr) == sizeof (fc_ulp_module_t)) { /* * Now read that module info in */ if (mdb_vread(&modinfo, sizeof (fc_ulp_modinfo_t), (uintptr_t)modlist.mod_info) == sizeof (fc_ulp_modinfo_t)) { /* Now read all the ports for this module */ if (mdb_vread(&ulp_port, sizeof (fc_ulp_ports_t), (uintptr_t)modlist.mod_ports) == sizeof (fc_ulp_ports_t)) { while (ulp_port.port_handle != NULL) { mdb_printf("%4x %16p %8x %8x\n", modinfo.ulp_type, ulp_port.port_handle, ulp_port.port_dstate, ulp_port.port_statec); if (ulp_port.port_next == NULL) break; mdb_vread(&ulp_port, sizeof (fc_ulp_ports_t), (uintptr_t)ulp_port.port_next); } } } else mdb_warn("failed to read modinfo at %p", modlist.mod_info); } else mdb_warn("failed to read modlist at %p", addr); return (DCMD_OK); } /* * Display an fc_local_port_t struct */ static int fcport(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { fc_fca_port_t portlist; fc_local_port_t port; int idx; int first = 1; int walking_fc_fca_portlist = 0; if (argc != 0) { int result; if (argc != 1) return (DCMD_USAGE); if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); walking_fc_fca_portlist = 1; } if (!(flags & DCMD_ADDRSPEC)) { mdb_printf("Sorry, you must provide an address\n"); return (DCMD_ERR); } if (walking_fc_fca_portlist) { /* * Must read the fc_fca_portlist to get the fc_local_port addr */ if (mdb_vread(&portlist, sizeof (fc_fca_port_t), addr) == sizeof (fc_fca_port_t)) { addr = (uintptr_t)portlist.port_handle; } } mdb_printf("Reading fc_local_port_t at %p:\n", addr); /* * For each port, we just need to read the fc_local_port_t struct */ if (mdb_vread(&port, sizeof (fc_local_port_t), addr) == sizeof (fc_local_port_t)) { mdb_printf(" fp_mutex : 0x%p\n", port.fp_mutex); mdb_printf(" fp_state : 0x%-8x\n", port.fp_state); mdb_printf(" fp_port_id : 0x%-06x\n", port.fp_port_id.port_id); mdb_printf(" fp_fca_handle : 0x%p\n", port.fp_fca_handle); mdb_printf(" fp_fca_tran : 0x%p\n", port.fp_fca_tran); mdb_printf(" fp_job_head : 0x%p\n", port.fp_job_head); mdb_printf(" fp_job_tail : 0x%p\n", port.fp_job_tail); mdb_printf(" fp_wait_head : 0x%p\n", port.fp_wait_head); mdb_printf(" fp_wait_tail : 0x%p\n", port.fp_wait_tail); mdb_printf(" fp_topology : %u\n", port.fp_topology); mdb_printf(" fp_task : %d\n", port.fp_task); mdb_printf(" fp_last_task : %d\n", port.fp_last_task); mdb_printf(" fp_soft_state : 0x%-4x\n", port.fp_soft_state); mdb_printf(" fp_flag : 0x%-2x\n", port.fp_flag); mdb_printf(" fp_statec_busy : 0x%-8x\n", port.fp_statec_busy); mdb_printf(" fp_port_num : %d\n", port.fp_port_num); mdb_printf(" fp_instance : %d\n", port.fp_instance); mdb_printf(" fp_ulp_attach : %d\n", port.fp_ulp_attach); mdb_printf(" fp_dev_count : %d\n", port.fp_dev_count); mdb_printf(" fp_total_devices : %d\n", port.fp_total_devices); mdb_printf(" fp_bind_state : 0x%-8x\n", port.fp_bind_state); mdb_printf(" fp_options : 0x%-8x\n", port.fp_options); mdb_printf(" fp_port_type : 0x%-2x\n", port.fp_port_type.port_type); mdb_printf(" fp_ub_count : %d\n", port.fp_ub_count); mdb_printf(" fp_active_ubs : %d\n", port.fp_active_ubs); mdb_printf(" fp_port_dip : 0x%p\n", port.fp_port_dip); mdb_printf(" fp_fca_dip : 0x%p\n", port.fp_fca_dip); for (idx = 0; idx < 16; idx++) { if (port.fp_ip_addr[idx] != 0) break; } if (idx != 16) { mdb_printf(" fp_ip_addr : %-2x:%-2x:%-2x:%-2x:" "%-2x:%-2x:%-2x:%-2x:%-2x:%-2x:%-2x:%-2x:%-2x:%-2x" ":%-2x:%-2x\n", port.fp_ip_addr[0], port.fp_ip_addr[1], port.fp_ip_addr[2], port.fp_ip_addr[3], port.fp_ip_addr[4], port.fp_ip_addr[5], port.fp_ip_addr[6], port.fp_ip_addr[7], port.fp_ip_addr[8], port.fp_ip_addr[9], port.fp_ip_addr[10], port.fp_ip_addr[11], port.fp_ip_addr[12], port.fp_ip_addr[13], port.fp_ip_addr[14], port.fp_ip_addr[15]); } else { mdb_printf(" fp_ip_addr : N/A\n"); } mdb_printf(" fp_fc4_types : "); for (idx = 0; idx < 8; idx++) { if (port.fp_fc4_types[idx] != 0) { if (first) { mdb_printf("%d", port.fp_fc4_types[idx]); first = 0; } else { mdb_printf(", %d", port.fp_fc4_types[idx]); } } } if (first) { mdb_printf("None\n"); } else { mdb_printf("\n"); } mdb_printf(" fp_pm_level : %d\n", port.fp_pm_level); mdb_printf(" fp_pm_busy : %d\n", port.fp_pm_busy); mdb_printf(" fp_pm_busy_nocomp : 0x%-8x\n", port.fp_pm_busy_nocomp); mdb_printf(" fp_hard_addr : 0x%-6x\n", port.fp_hard_addr.hard_addr); mdb_printf(" fp_sym_port_name : \"%s\"\n", port.fp_sym_port_name); mdb_printf(" fp_sym_node_name : \"%s\"\n", port.fp_sym_node_name); mdb_printf(" fp_rscn_count : %d\n", port.fp_rscn_count); } else { mdb_warn("failed to read fc_local_port_t at 0x%p", addr); } mdb_printf("\n"); return (DCMD_OK); } /* * Leadville remote_port walker/dcmd code */ /* * We need to be given the address of a port structure in order to start * walking. From that, we can read the pwwn table. */ static int pd_by_pwwn_walk_i(mdb_walk_state_t *wsp) { fc_local_port_t port; if (wsp->walk_addr == 0) { mdb_warn("pd_by_pwwn walk doesn't support global walks\n"); return (WALK_ERR); } /* * Allocate space for the pwwn_hash table */ fp_pwwn_table = mdb_alloc(sizeof (struct pwwn_hash) * PWWN_HASH_TABLE_SIZE, UM_SLEEP); /* * Input should be an fc_local_port_t, so read it to get the pwwn * table's head */ if (mdb_vread(&port, sizeof (fc_local_port_t), wsp->walk_addr) != sizeof (fc_local_port_t)) { mdb_warn("Unable to read in the port structure address\n"); return (WALK_ERR); } if (mdb_vread(fp_pwwn_table, sizeof (struct pwwn_hash) * PWWN_HASH_TABLE_SIZE, (uintptr_t)port.fp_pwwn_table) == -1) { mdb_warn("Unable to read in the pwwn hash table\n"); return (WALK_ERR); } pd_hash_index = 0; while ((fp_pwwn_table[pd_hash_index].pwwn_head == NULL) && (pd_hash_index < PWWN_HASH_TABLE_SIZE)) { pd_hash_index++; } wsp->walk_addr = (uintptr_t)fp_pwwn_table[pd_hash_index].pwwn_head; wsp->walk_data = mdb_alloc(sizeof (fc_remote_port_t), UM_SLEEP); return (WALK_NEXT); } /* * At each step, read a fc_remote_port_t into our private storage, and then * invoke the callback function. We terminate when we reach a NULL p_next * pointer. */ static int pd_by_pwwn_walk_s(mdb_walk_state_t *wsp) { int status; if ((wsp->walk_addr == 0) && (pd_hash_index >= (PWWN_HASH_TABLE_SIZE - 1))) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (fc_remote_port_t), wsp->walk_addr) == -1) { mdb_warn("failed to read fc_remote_port at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((fc_remote_port_t *)wsp->walk_data)->pd_wwn_hnext); if (wsp->walk_addr == 0) { /* * Try the next hash list, if there is one. */ pd_hash_index++; while ((fp_pwwn_table[pd_hash_index].pwwn_head == NULL) && (pd_hash_index < PWWN_HASH_TABLE_SIZE)) { pd_hash_index++; } if (pd_hash_index == PWWN_HASH_TABLE_SIZE) { /* We're done */ return (status); } wsp->walk_addr = (uintptr_t)fp_pwwn_table[pd_hash_index].pwwn_head; } return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void pd_by_pwwn_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (fc_remote_port_t)); mdb_free(fp_pwwn_table, sizeof (struct pwwn_hash) * PWWN_HASH_TABLE_SIZE); fp_pwwn_table = NULL; } /* * This is the same walker as pd_by_pwwn, but we walk the D_ID hash table */ static int pd_by_did_walk_i(mdb_walk_state_t *wsp) { fc_local_port_t port; if (wsp->walk_addr == 0) { mdb_warn("pd_by_did walk doesn't support global walks\n"); return (WALK_ERR); } /* * Allocate space for the did_hash table */ fp_did_table = mdb_alloc(sizeof (struct d_id_hash) * D_ID_HASH_TABLE_SIZE, UM_SLEEP); /* * Input should be an fc_local_port_t, so read it to get the d_id * table's head */ if (mdb_vread(&port, sizeof (fc_local_port_t), wsp->walk_addr) != sizeof (fc_local_port_t)) { mdb_warn("Unable to read in the port structure address\n"); return (WALK_ERR); } if (mdb_vread(fp_did_table, sizeof (struct d_id_hash) * D_ID_HASH_TABLE_SIZE, (uintptr_t)port.fp_did_table) == -1) { mdb_warn("Unable to read in the D_ID hash table\n"); return (WALK_ERR); } pd_hash_index = 0; while ((fp_did_table[pd_hash_index].d_id_head == NULL) && (pd_hash_index < D_ID_HASH_TABLE_SIZE)) { pd_hash_index++; } wsp->walk_addr = (uintptr_t)fp_did_table[pd_hash_index].d_id_head; wsp->walk_data = mdb_alloc(sizeof (fc_remote_port_t), UM_SLEEP); return (WALK_NEXT); } /* * At each step, read a fc_remote_port_t into our private storage, and then * invoke the callback function. We terminate when we reach a NULL p_next * pointer. */ static int pd_by_did_walk_s(mdb_walk_state_t *wsp) { int status; if ((wsp->walk_addr == 0) && (pd_hash_index >= (D_ID_HASH_TABLE_SIZE - 1))) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (fc_remote_port_t), wsp->walk_addr) == -1) { mdb_warn("failed to read fc_remote_port at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((fc_remote_port_t *)wsp->walk_data)->pd_did_hnext); if (wsp->walk_addr == 0) { /* * Try the next hash list, if there is one. */ pd_hash_index++; while ((fp_did_table[pd_hash_index].d_id_head == NULL) && (pd_hash_index < D_ID_HASH_TABLE_SIZE)) { pd_hash_index++; } if (pd_hash_index == D_ID_HASH_TABLE_SIZE) { /* We're done */ return (status); } wsp->walk_addr = (uintptr_t)fp_did_table[pd_hash_index].d_id_head; } return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void pd_by_did_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (fc_remote_port_t)); mdb_free(fp_did_table, sizeof (struct d_id_hash) * D_ID_HASH_TABLE_SIZE); fp_did_table = NULL; } /* * Display a remote_port structure */ static int remote_port(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { fc_remote_port_t pd; int idx; int first = 1; if (argc > 0) { return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { mdb_printf("Sorry, you must provide an address\n"); return (DCMD_ERR); } if (mdb_vread(&pd, sizeof (fc_remote_port_t), addr) != sizeof (fc_remote_port_t)) { mdb_warn("Error reading pd at 0x%x\n", addr); return (DCMD_ERR); } mdb_printf("Reading remote_port at 0x%p\n", addr); mdb_printf(" mutex : 0x%p\n", pd.pd_mutex); mdb_printf(" port_id : 0x%-8x\n", pd.pd_port_id); mdb_printf(" port_name : 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", pd.pd_port_name.raw_wwn[0], pd.pd_port_name.raw_wwn[1], pd.pd_port_name.raw_wwn[2], pd.pd_port_name.raw_wwn[3], pd.pd_port_name.raw_wwn[4], pd.pd_port_name.raw_wwn[5], pd.pd_port_name.raw_wwn[6], pd.pd_port_name.raw_wwn[7]); mdb_printf(" login_count : %d\n", pd.pd_login_count); mdb_printf(" state : 0x%x ", pd.pd_state); switch (pd.pd_state) { case PORT_DEVICE_INVALID: mdb_printf("(invalid)\n"); break; case PORT_DEVICE_VALID: mdb_printf("(valid)\n"); break; case PORT_DEVICE_LOGGED_IN: mdb_printf("(logged in)\n"); break; default: mdb_printf("(Unknown state)\n"); } mdb_printf(" remote node : 0x%p\n", pd.pd_remote_nodep); mdb_printf(" hard_addr : 0x%x\n", pd.pd_hard_addr); mdb_printf(" local port : 0x%p\n", pd.pd_port); mdb_printf(" type : %d ", pd.pd_type); switch (pd.pd_type) { case PORT_DEVICE_NOCHANGE: mdb_printf("(No change)\n"); break; case PORT_DEVICE_NEW: mdb_printf("(New)\n"); break; case PORT_DEVICE_OLD: mdb_printf("(Old)\n"); break; case PORT_DEVICE_CHANGED: mdb_printf("(Changed)\n"); break; case PORT_DEVICE_DELETE: mdb_printf("(Delete)\n"); break; case PORT_DEVICE_USER_LOGIN: mdb_printf("(User login)\n"); break; case PORT_DEVICE_USER_LOGOUT: mdb_printf("(User logout)\n"); break; case PORT_DEVICE_USER_CREATE: mdb_printf("(User create)\n"); break; case PORT_DEVICE_USER_DELETE: mdb_printf("(User delete)\n"); break; default: mdb_printf("(Unknown type)\n"); } mdb_printf(" flags : 0x%x ", pd.pd_flags); switch (pd.pd_flags) { case PD_IDLE: mdb_printf("(Idle)\n"); break; case PD_ELS_IN_PROGRESS: mdb_printf("(ELS in progress)\n"); break; case PD_ELS_MARK: mdb_printf("(Mark)\n"); break; default: mdb_printf("(Unknown flag value)\n"); } mdb_printf(" login_class : 0x%x\n", pd.pd_login_class); mdb_printf(" recipient : %d\n", pd.pd_recepient); mdb_printf(" ref_count : %d\n", pd.pd_ref_count); mdb_printf(" aux_flags : 0x%x ", pd.pd_aux_flags); first = 1; if (pd.pd_aux_flags & PD_IN_DID_QUEUE) { mdb_printf("(IN_DID_QUEUE"); first = 0; } if (pd.pd_aux_flags & PD_DISABLE_RELOGIN) { if (first) { mdb_printf("(DISABLE_RELOGIN"); } else { mdb_printf(", DISABLE_RELOGIN"); } first = 0; } if (pd.pd_aux_flags & PD_NEEDS_REMOVAL) { if (first) { mdb_printf("(NEEDS_REMOVAL"); } else { mdb_printf(", NEEDS_REMOVAL"); } first = 0; } if (pd.pd_aux_flags & PD_LOGGED_OUT) { if (first) { mdb_printf("(LOGGED_OUT"); } else { mdb_printf(", LOGGED_OUT"); } first = 0; } if (pd.pd_aux_flags & PD_GIVEN_TO_ULPS) { if (first) { mdb_printf("(GIVEN_TO_ULPS"); } else { mdb_printf(", GIVEN_TO_ULPS"); } first = 0; } if (first == 0) { mdb_printf(")\n"); } else { mdb_printf("\n"); } mdb_printf(" sig : %p\n", pd.pd_logo_tc.sig); mdb_printf(" active : %d\n", pd.pd_logo_tc.active); mdb_printf(" counter : %d\n", pd.pd_logo_tc.counter); mdb_printf(" max_value : %d\n", pd.pd_logo_tc.max_value); mdb_printf(" timer : %d\n", pd.pd_logo_tc.timer); mdb_printf("\n"); return (DCMD_OK); } int fc_dump_logmsg(fc_trace_dmsg_t *addr, uint_t pktstart, uint_t pktend, uint_t *printed) { fc_trace_dmsg_t msg; caddr_t buf; char merge[1024]; caddr_t tmppkt; char *tmpbuf; /* for tokenising the buffer */ uint_t pktnum = 0; while (addr != NULL) { if (mdb_vread(&msg, sizeof (msg), (uintptr_t)addr) != sizeof (msg)) { mdb_warn("failed to read message pointer in kernel"); return (DCMD_ERR); } if (msg.id_size) { buf = mdb_alloc(msg.id_size + 1, UM_SLEEP); tmppkt = mdb_alloc(msg.id_size + 1, UM_SLEEP); if (mdb_vread(buf, msg.id_size, (uintptr_t)msg.id_buf) != msg.id_size) { mdb_warn("failed to read buffer contents" " in kernel"); mdb_free(buf, msg.id_size + 1); return (DCMD_ERR); } if (buf[0] == '\n') { mdb_printf("There is a problem in" "the buffer\n"); } /* funky packet processing stuff */ bcopy(buf, tmppkt, msg.id_size + 1); /* find the equals sign, and put a null there */ tmpbuf = strchr(tmppkt, '='); *tmpbuf = 0; pktnum = (uint_t)mdb_strtoull(tmppkt); if ((pktnum >= pktstart) && (pktnum <= pktend)) { (void) mdb_snprintf(merge, sizeof (merge), "[%Y:%03d:%03d:%03d] %s", msg.id_time.tv_sec, (int)msg.id_time.tv_nsec/1000000, (int)(msg.id_time.tv_nsec/1000)%1000, (int)msg.id_time.tv_nsec%1000, buf); mdb_printf("%s", merge); if (printed != NULL) (*printed) ++; } mdb_free(buf, msg.id_size + 1); mdb_free(tmppkt, msg.id_size + 1); } addr = msg.id_next; } return (DCMD_OK); } int fc_dump_old_logmsg(fc_trace_dmsgv1_t *addr, uint_t pktstart, uint_t pktend, uint_t *printed) { fc_trace_dmsgv1_t msg; caddr_t buf; char merge[1024]; caddr_t tmppkt; char *tmpbuf; /* for tokenising the buffer */ uint_t pktnum = 0; while (addr != NULL) { if (mdb_vread(&msg, sizeof (msg), (uintptr_t)addr) != sizeof (msg)) { mdb_warn("failed to read message pointer in kernel"); return (DCMD_ERR); } if (msg.id_size) { buf = mdb_alloc(msg.id_size + 1, UM_SLEEP); tmppkt = mdb_alloc(msg.id_size + 1, UM_SLEEP); if (mdb_vread(buf, msg.id_size, (uintptr_t)msg.id_buf) != msg.id_size) { mdb_warn("failed to read buffer contents" " in kernel"); mdb_free(buf, msg.id_size + 1); return (DCMD_ERR); } if (buf[0] == '\n') { mdb_printf("There is a problem in" "the buffer\n"); } /* funky packet processing stuff */ bcopy(buf, tmppkt, msg.id_size + 1); tmpbuf = strchr(tmppkt, '='); *tmpbuf = 0; pktnum = (uint_t)mdb_strtoull(tmppkt); if ((pktnum >= pktstart) && (pktnum <= pktend)) { (void) mdb_snprintf(merge, sizeof (merge), "[%Y] %s", msg.id_time, buf); mdb_printf("%s", merge); if (printed != NULL) (*printed) ++; } mdb_free(buf, msg.id_size + 1); mdb_free(tmppkt, msg.id_size + 1); } addr = msg.id_next; } return (DCMD_OK); } int fc_trace_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { fc_trace_logq_t logq; uint_t pktnum = 0; uint_t printed = 0; /* have we printed anything? */ uintptr_t pktstart = 0; uintptr_t pktend = UINT_MAX; int rval = DCMD_OK; if (mdb_vread(&logq, sizeof (logq), addr) != sizeof (logq)) { mdb_warn("Failed to read log queue in kernel"); return (DCMD_ERR); } if (mdb_getopts(argc, argv, 's', MDB_OPT_UINTPTR, &pktstart, 'e', MDB_OPT_UINTPTR, &pktend, NULL) != argc) { return (DCMD_USAGE); } if (pktstart > pktend) { return (DCMD_USAGE); } if ((logq.il_flags & FC_TRACE_LOGQ_V2) != 0) { rval = fc_dump_logmsg((fc_trace_dmsg_t *)logq.il_msgh, pktstart, pktend, &printed); } else { rval = fc_dump_old_logmsg((fc_trace_dmsgv1_t *)logq.il_msgh, pktstart, pktend, &printed); } if (rval != DCMD_OK) { return (rval); } if (printed == 0) { mdb_printf("No packets in the buffer match the" " criteria given"); } return (rval); } int fp_trace_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (mdb_readvar(&addr, "fp_logq") == -1) { mdb_warn("failed to read fp_logq"); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { mdb_printf("fp trace buffer contents\n"); } return (fc_trace_dump(addr, flags, argc, argv)); } int fcp_trace_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (mdb_readvar(&addr, "fcp_logq") == -1) { mdb_warn("failed to read fcp_logq"); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { mdb_printf("fcp trace buffer contents\n"); } return (fc_trace_dump(addr, flags, argc, argv)); } /* * Leadville job_request walker/dcmd code */ /* * We need to be given the address of a local port structure in order to start * walking. From that, we can read the job_request list. */ static int job_request_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("The address of a fc_local_port" " structure must be given\n"); return (WALK_ERR); } /* * Input should be a fc_local_port_t, so read it to get the job_request * lists's head */ if (mdb_vread(&port, sizeof (fc_local_port_t), wsp->walk_addr) != sizeof (fc_local_port_t)) { mdb_warn("Failed to read in the fc_local_port" " at 0x%p\n", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(port.fp_job_head); wsp->walk_data = mdb_alloc(sizeof (struct job_request), UM_SLEEP); return (WALK_NEXT); } static int job_request_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct job_request), wsp->walk_addr) == -1) { mdb_warn("Failed to read in the job_request at 0x%p\n", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct job_request *)wsp->walk_data)->job_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void job_request_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct job_request)); } /* * Leadville fc_orphan walker/dcmd code */ /* * We need to be given the address of a port structure in order to start * walking. From that, we can read the orphan list. */ static int orphan_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("The address of a fc_local_port" " structure must be given\n"); return (WALK_ERR); } /* * Input should be a fc_local_port_t, so read it to get the orphan * lists's head */ if (mdb_vread(&port, sizeof (fc_local_port_t), wsp->walk_addr) != sizeof (fc_local_port_t)) { mdb_warn("Failed to read in the fc_local_port" " at 0x%p\n", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(port.fp_orphan_list); wsp->walk_data = mdb_alloc(sizeof (struct fc_orphan), UM_SLEEP); return (WALK_NEXT); } static int orphan_walk_s(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct fc_orphan), wsp->walk_addr) == -1) { mdb_warn("Failed to read in the fc_orphan at 0x%p\n", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct fc_orphan *)wsp->walk_data)->orp_next); return (status); } /* * The walker's fini function is invoked at the end of each walk. */ static void orphan_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct fc_orphan)); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, a list of structures * describing our walkers, and a function named _mdb_init to return a pointer * to our module information. */ static const mdb_dcmd_t dcmds[] = { { "ports", "[-l]", "Leadville port list", ports }, { "ulps", NULL, "Leadville ULP list", ulps }, { "ulpmods", NULL, "Leadville ULP module list", ulpmods }, { "fcport", NULL, "Display a Leadville fc_local_port structure", fcport }, { "remote_port", NULL, "Display fc_remote_port structures", remote_port }, { "fcptrace", "[-s m][-e n] (m < n)", "Dump the fcp trace buffer, " "optionally supplying starting and ending packet numbers.", fcp_trace_dump, NULL }, { "fptrace", "[-s m][-e n] (m < n)", "Dump the fp trace buffer, " "optionally supplying starting and ending packet numbers.", fp_trace_dump, NULL }, { NULL } }; static const mdb_walker_t walkers[] = { { "ports", "walk list of Leadville port structures", port_walk_i, port_walk_s, port_walk_f }, { "ulps", "walk list of Leadville ULP structures", ulp_walk_i, ulp_walk_s, ulp_walk_f }, { "ulpmods", "walk list of Leadville ULP module structures", ulpmod_walk_i, ulpmod_walk_s, ulpmod_walk_f }, { "pd_by_pwwn", "walk list of fc_remote_port structures hashed by PWWN", pd_by_pwwn_walk_i, pd_by_pwwn_walk_s, pd_by_pwwn_walk_f }, { "pd_by_did", "walk list of fc_remote_port structures hashed by D_ID", pd_by_did_walk_i, pd_by_did_walk_s, pd_by_did_walk_f }, { "job_request", "walk list of job_request structures for a local port", job_request_walk_i, job_request_walk_s, job_request_walk_f }, { "orphan", "walk list of orphan structures for a local port", orphan_walk_i, orphan_walk_s, orphan_walk_f }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } # # 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 2011 Nexenta Systems, Inc. All rights reserved. # Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2019 Joyent, Inc. # Copyright (c) 2013 by Delphix. All rights reserved. # Copyright 2022 Garrett D'Amore # # # This file simply contains the list of sources files compiled together # to create the genunix mdb module. Having them in one place saves # a bunch of unnecessary replication. # GENUNIX_SRCS = \ avl.c \ bio.c \ bitset.c \ combined.c \ contract.c \ cpupart.c \ cred.c \ ctxop.c \ cyclic.c \ damap.c \ ddi_periodic.c \ devinfo.c \ dist.c \ dnlc.c \ findstack.c \ findstack_subr.c \ fm.c \ gcore_isadep.c \ genunix.c \ group.c \ hotplug.c \ irm.c \ kgrep.c \ kmem.c \ ldi.c \ leaky.c \ leaky_subr.c \ lgrp.c \ list.c \ log.c \ mdi.c \ memory.c \ modhash.c \ ndievents.c \ net.c \ netstack.c \ nvpair.c \ pci.c \ pg.c \ rctl.c \ refstr.c \ sobj.c \ streams.c \ sysevent.c \ taskq.c \ thread.c \ tsd.c \ tsol.c \ vfs.c \ zone.c /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2013 by Delphix. All rights reserved. */ #include #include struct aw_info { void *aw_buff; /* buffer to hold tree element */ avl_tree_t aw_tree; /* copy of avl_tree_t being walked */ uintptr_t aw_end; /* last node in specified range */ const char *aw_elem_name; int (*aw_elem_check)(void *, uintptr_t, void *); void *aw_elem_check_arg; }; /* * common code used to find the addr of the the leftmost child below * an AVL node */ static uintptr_t avl_leftmostchild(uintptr_t addr, void *buff, size_t offset, size_t size, const char *elem_name) { avl_node_t *node = (avl_node_t *)((uintptr_t)buff + offset); for (;;) { addr -= offset; if (mdb_vread(buff, size, addr) == -1) { mdb_warn("failed to read %s at %#lx", elem_name, addr); return ((uintptr_t)-1L); } if (node->avl_child[0] == NULL) break; addr = (uintptr_t)node->avl_child[0]; } return (addr); } /* * initialize a forward walk thru an avl tree. * * begin and end optionally specify objects other than the first and last * objects in the tree; either or both may be NULL (defaulting to first and * last). * * avl_name and element_name specify command-specific labels other than * "avl_tree_t" and "tree element" for use in error messages. * * element_check() returns -1, 1, or 0: abort the walk with an error, stop * without an error, or allow the normal callback; arg is an optional user * argument to element_check(). */ int avl_walk_init_range(mdb_walk_state_t *wsp, uintptr_t begin, uintptr_t end, const char *avl_name, const char *element_name, int (*element_check)(void *, uintptr_t, void *), void *arg) { struct aw_info *aw; avl_tree_t *tree; uintptr_t addr; if (avl_name == NULL) avl_name = "avl_tree_t"; if (element_name == NULL) element_name = "tree element"; /* * allocate the AVL walk data */ wsp->walk_data = aw = mdb_zalloc(sizeof (struct aw_info), UM_SLEEP); /* * get an mdb copy of the avl_tree_t being walked */ tree = &aw->aw_tree; if (mdb_vread(tree, sizeof (avl_tree_t), wsp->walk_addr) == -1) { mdb_warn("failed to read %s at %#lx", avl_name, wsp->walk_addr); goto error; } if (tree->avl_size < tree->avl_offset + sizeof (avl_node_t)) { mdb_warn("invalid avl_tree_t at %p, avl_size:%d, avl_offset:%d", wsp->walk_addr, tree->avl_size, tree->avl_offset); goto error; } /* * allocate a buffer to hold the mdb copy of tree's structs * "node" always points at the avl_node_t field inside the struct */ aw->aw_buff = mdb_zalloc(tree->avl_size, UM_SLEEP); aw->aw_end = (end == 0 ? 0 : end + tree->avl_offset); aw->aw_elem_name = element_name; aw->aw_elem_check = element_check; aw->aw_elem_check_arg = arg; /* * get the first avl_node_t address, use same algorithm * as avl_start() -- leftmost child in tree from root */ if (begin == 0) { addr = (uintptr_t)tree->avl_root; if (addr == 0) { wsp->walk_addr = 0; return (WALK_NEXT); } addr = avl_leftmostchild(addr, aw->aw_buff, tree->avl_offset, tree->avl_size, aw->aw_elem_name); if (addr == (uintptr_t)-1L) goto error; wsp->walk_addr = addr; } else { wsp->walk_addr = begin + tree->avl_offset; } return (WALK_NEXT); error: if (aw->aw_buff != NULL) mdb_free(aw->aw_buff, sizeof (tree->avl_size)); mdb_free(aw, sizeof (struct aw_info)); return (WALK_ERR); } int avl_walk_init(mdb_walk_state_t *wsp) { return (avl_walk_init_range(wsp, 0, 0, NULL, NULL, NULL, NULL)); } int avl_walk_init_named(mdb_walk_state_t *wsp, const char *avl_name, const char *element_name) { return (avl_walk_init_range(wsp, 0, 0, avl_name, element_name, NULL, NULL)); } int avl_walk_init_checked(mdb_walk_state_t *wsp, const char *avl_name, const char *element_name, int (*element_check)(void *, uintptr_t, void *), void *arg) { return (avl_walk_init_range(wsp, 0, 0, avl_name, element_name, element_check, arg)); } /* * At each step, visit (callback) the current node, then move to the next * in the AVL tree. Uses the same algorithm as avl_walk(). */ int avl_walk_step(mdb_walk_state_t *wsp) { struct aw_info *aw; size_t offset; size_t size; uintptr_t addr; avl_node_t *node; int status; int was_child; /* * don't walk past the end of the tree! */ addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); aw = (struct aw_info *)wsp->walk_data; if (aw->aw_end != 0 && wsp->walk_addr == aw->aw_end) return (WALK_DONE); size = aw->aw_tree.avl_size; offset = aw->aw_tree.avl_offset; node = (avl_node_t *)((uintptr_t)aw->aw_buff + offset); /* * must read the current node for the call back to use */ if (mdb_vread(aw->aw_buff, size, addr) == -1) { mdb_warn("failed to read %s at %#lx", aw->aw_elem_name, addr); return (WALK_ERR); } if (aw->aw_elem_check != NULL) { int rc = aw->aw_elem_check(aw->aw_buff, addr, aw->aw_elem_check_arg); if (rc == -1) return (WALK_ERR); else if (rc == 1) return (WALK_DONE); } /* * do the call back */ status = wsp->walk_callback(addr, aw->aw_buff, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); /* * move to the next node.... * note we read in new nodes, so the pointer to the buffer is fixed */ /* * if the node has a right child then go to it and then all the way * thru as many left children as possible */ addr = (uintptr_t)node->avl_child[1]; if (addr != 0) { addr = avl_leftmostchild(addr, aw->aw_buff, offset, size, aw->aw_elem_name); if (addr == (uintptr_t)-1L) return (WALK_ERR); /* * othewise return to parent nodes, stopping if we ever return from * a left child */ } else { for (;;) { was_child = AVL_XCHILD(node); addr = (uintptr_t)AVL_XPARENT(node); if (addr == 0) break; addr -= offset; if (was_child == 0) /* stop on return from left child */ break; if (mdb_vread(aw->aw_buff, size, addr) == -1) { mdb_warn("failed to read %s at %#lx", aw->aw_elem_name, addr); return (WALK_ERR); } } } wsp->walk_addr = addr; return (WALK_NEXT); } /* * Release the memory allocated for the walk */ void avl_walk_fini(mdb_walk_state_t *wsp) { struct aw_info *aw; aw = (struct aw_info *)wsp->walk_data; if (aw == NULL) return; if (aw->aw_buff != NULL) mdb_free(aw->aw_buff, aw->aw_tree.avl_size); mdb_free(aw, sizeof (struct aw_info)); } /* * This function is named avl_walk_mdb to avoid a naming conflict with the * existing avl_walk function. */ int avl_walk_mdb(uintptr_t addr, mdb_walk_cb_t callback, void *cbdata) { mdb_walk_state_t ws; int ret; ws.walk_addr = addr; ws.walk_callback = callback; ws.walk_cbdata = cbdata; avl_walk_init(&ws); while ((ret = avl_walk_step(&ws)) == WALK_NEXT) continue; avl_walk_fini(&ws); return (ret); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2013 by Delphix. All rights reserved. */ #ifndef _MDB_AVL_H #define _MDB_AVL_H #ifdef __cplusplus extern "C" { #endif #define AVL_WALK_NAME "avl" #define AVL_WALK_DESC "given any avl_tree_t *, forward walk all " \ "entries in tree" extern int avl_walk_init(mdb_walk_state_t *); extern int avl_walk_init_named(mdb_walk_state_t *wsp, const char *, const char *); extern int avl_walk_init_checked(mdb_walk_state_t *wsp, const char *, const char *, int (*)(void *, uintptr_t, void *), void *); extern int avl_walk_init_range(mdb_walk_state_t *wsp, uintptr_t, uintptr_t, const char *, const char *, int (*)(void *, uintptr_t, void *), void *); extern int avl_walk_step(mdb_walk_state_t *); extern void avl_walk_fini(mdb_walk_state_t *wsp); extern int avl_walk_mdb(uintptr_t, mdb_walk_cb_t, void *); #ifdef __cplusplus } #endif #endif /* _MDB_AVL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include "bio.h" typedef struct buf_walk { uintptr_t bw_hbufbase; /* Base address of hbuf buckets */ struct hbuf *bw_hbufs; /* Snapshot of hbuf buckets */ size_t bw_nhbufs; /* Number of hbuf buckets */ size_t bw_hbufi; /* Current hbuf index */ buf_t *bw_bufp; /* Current buffer */ } buf_walk_t; int buf_walk_init(mdb_walk_state_t *wsp) { struct hbuf *hbufs; struct var v; uintptr_t hbuf_addr; size_t nbytes; buf_walk_t *bwp; if (wsp->walk_addr != 0) { mdb_warn("only global buf walk supported\n"); return (WALK_ERR); } if (mdb_readvar(&v, "v") == -1) { mdb_warn("failed to read var struct"); return (WALK_ERR); } if (mdb_readvar(&hbuf_addr, "hbuf") == -1) { mdb_warn("failed to read hbuf pointer"); return (WALK_ERR); } nbytes = sizeof (struct hbuf) * v.v_hbuf; hbufs = mdb_alloc(nbytes, UM_SLEEP); if (mdb_vread(hbufs, nbytes, hbuf_addr) != nbytes) { mdb_warn("failed to read hbufs"); mdb_free(hbufs, nbytes); return (WALK_ERR); } bwp = mdb_alloc(sizeof (buf_walk_t), UM_SLEEP); bwp->bw_hbufbase = hbuf_addr; bwp->bw_hbufs = hbufs; bwp->bw_nhbufs = v.v_hbuf; bwp->bw_hbufi = 0; bwp->bw_bufp = mdb_alloc(sizeof (buf_t), UM_SLEEP); wsp->walk_addr = (uintptr_t)hbufs[0].b_forw; wsp->walk_data = bwp; return (WALK_NEXT); } int buf_walk_step(mdb_walk_state_t *wsp) { buf_walk_t *bwp = wsp->walk_data; uintptr_t addr; /* * If the next buf_t address we want is NULL or points back at the * hbuf itself, advance to the next hash bucket. When we reach * bw_nhbufs, we're done. */ while (wsp->walk_addr == 0 || wsp->walk_addr == (bwp->bw_hbufbase + bwp->bw_hbufi * sizeof (struct hbuf))) { if (++bwp->bw_hbufi == bwp->bw_nhbufs) return (WALK_DONE); wsp->walk_addr = (uintptr_t) bwp->bw_hbufs[bwp->bw_hbufi].b_forw; } /* * When we have a buf_t address, read the buffer and invoke our * walk callback. We keep the next buf_t address in wsp->walk_addr. */ addr = wsp->walk_addr; (void) mdb_vread(bwp->bw_bufp, sizeof (buf_t), addr); wsp->walk_addr = (uintptr_t)bwp->bw_bufp->b_forw; return (wsp->walk_callback(addr, bwp->bw_bufp, wsp->walk_cbdata)); } void buf_walk_fini(mdb_walk_state_t *wsp) { buf_walk_t *bwp = wsp->walk_data; mdb_free(bwp->bw_hbufs, sizeof (struct hbuf) * bwp->bw_nhbufs); mdb_free(bwp->bw_bufp, sizeof (buf_t)); mdb_free(bwp, sizeof (buf_walk_t)); } /*ARGSUSED*/ int bufpagefind(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t b_addr = addr; uintptr_t arg; page_t p; buf_t b; if (argc != 1) return (DCMD_USAGE); arg = (uintptr_t)mdb_argtoull(argv); if (mdb_vread(&b, sizeof (buf_t), b_addr) == -1) return (DCMD_ERR); for (addr = (uintptr_t)b.b_pages; addr != 0; addr = (uintptr_t)p.p_next) { if (addr == arg) { mdb_printf("buf %p has page %p on b_pages list\n", b_addr, addr); break; } if (mdb_vread(&p, sizeof (page_t), addr) == -1) return (DCMD_ERR); } return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1998-1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _BIO_H #define _BIO_H #include #ifdef __cplusplus extern "C" { #endif extern int buf_walk_init(mdb_walk_state_t *); extern int buf_walk_step(mdb_walk_state_t *); extern void buf_walk_fini(mdb_walk_state_t *); extern int bufpagefind(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _BIO_H */ /* * 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. */ #include #include #include "bitset.h" /* XXX work out ifdef in include file... */ void bitset_help(void) { mdb_printf("Print the bitset at the address given\n"); } static void bitset_free(bitset_t *bs) { if (bs == NULL) return; if (bs->bs_set && bs->bs_words) mdb_free(bs->bs_set, bs->bs_words * sizeof (ulong_t)); mdb_free(bs, sizeof (*bs)); } static bitset_t * bitset_get(uintptr_t bsaddr) { bitset_t *bs; bs = mdb_zalloc(sizeof (*bs), UM_SLEEP); if (mdb_vread(bs, sizeof (*bs), bsaddr) == -1) { mdb_warn("couldn't read bitset 0x%p", bsaddr); bitset_free(bs); return (NULL); } bsaddr = (uintptr_t)bs->bs_set; bs->bs_set = mdb_alloc(bs->bs_words * sizeof (ulong_t), UM_SLEEP); if (mdb_vread(bs->bs_set, bs->bs_words * sizeof (ulong_t), bsaddr) == -1) { mdb_warn("couldn't read bitset bs_set 0x%p", bsaddr); bitset_free(bs); return (NULL); } return (bs); } static int bitset_highbit(bitset_t *bs) { int high; int i; if ((bs->bs_set == NULL) || (bs->bs_words == 0)) return (-1); /* move backwards through words */ for (i = bs->bs_words; i >= 0; i--) if (bs->bs_set[i]) break; if (i < 0) return (-1); /* move backwards through bits */ high = i << BT_ULSHIFT; for (i = BT_NBIPUL - 1; i; i--) if (BT_TEST(bs->bs_set, high + i)) break; return (high + i + 1); } static int pow10(int exp) { int res; for (res = 1; exp; exp--) res *= 10; return (res); } static int log10(int val) { int res = 0; do { res++; val /= 10; } while (val); return (res); } /* * The following prints a bitset with a 'ruler' that look like this * * 11111111112222222222333333333344444444445555555555666666666677 * 012345678901234567890123456789012345678901234567890123456789012345678901 * xx:........................................................................ * 11111111111111111111111111111111111111111111 * 777777778888888888999999999900000000001111111111222222222233333333334444 * 234567890123456789012345678901234567890123456789012345678901234567890123 * ........................................................................ * 111111111111111111111111111111111111111111111111111111112222222222222222 * 444444555555555566666666667777777777888888888899999999990000000000111111 * 456789012345678901234567890123456789012345678901234567890123456789012345 * ........................................................................ * 2222222222 * 1111222222 * 6789012345 * .......... * * to identify individual bits that are set. */ static void bitset_print(bitset_t *bs, char *label, int width) { int val_start; int val_max; int label_width; int ruler_width; int v, vm, vi; int nl, l; int i; int p; char c; val_start = 0; val_max = bitset_highbit(bs) + 1; if (val_max <= val_start) { mdb_printf("%s: empty-set", label); return; } label_width = strlen(label) + 1; ruler_width = width - label_width; for (v = val_start; v < val_max; v = vm) { if ((v + ruler_width) < val_max) vm = v + ruler_width; else vm = val_max; nl = log10(vm) - 1; for (l = nl; l >= 0; l--) { p = pow10(l); for (i = 0; i < label_width; i++) mdb_printf(" "); for (vi = v; vi < vm; vi++) { c = '0' + ((vi / p) % 10); if ((l == nl) && (c == '0')) c = ' '; mdb_printf("%c", c); } mdb_printf("\n"); } if (v == val_start) { mdb_printf("%s:", label); } else { for (i = 0; i < label_width; i++) mdb_printf(" "); } for (vi = v; vi < vm; vi++) { if (BT_TEST(bs->bs_set, vi)) mdb_printf("X"); else mdb_printf("."); } mdb_printf("\n"); } } /*ARGSUSED*/ int bitset(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { bitset_t *bs; bs = bitset_get(addr); if (bs == NULL) return (DCMD_ERR); bitset_print(bs, "label", 80); bitset_free(bs); return (DCMD_OK); } /* * 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. */ #ifndef _MDB_BITSET_H #define _MDB_BITSET_H #ifdef __cplusplus extern "C" { #endif #include extern int bitset(uintptr_t, uint_t, int, const mdb_arg_t *); extern void bitset_help(void); #ifdef __cplusplus } #endif #endif /* _MDB_BITSET_H */ /* * 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. */ #include typedef struct combined_walk { int (*cw_init)(mdb_walk_state_t *); int (*cw_step)(mdb_walk_state_t *); void (*cw_fini)(mdb_walk_state_t *); struct combined_walk *cw_next; void *cw_data; boolean_t cw_initialized; } combined_walk_t; typedef struct combined_walk_data { uintptr_t cwd_initial_walk_addr; /* to init each walk */ combined_walk_t *cwd_current_walk; combined_walk_t *cwd_final_walk; /* tail pointer */ struct combined_walk_data *cwd_next; struct combined_walk_data *cwd_prev; void *cwd_tag; /* used to find this data */ } combined_walk_data_t; /* * Initialize a combined walk to * A) present a single concatenated series of elements from different * structures, or * B) select from several possible walks at runtime. * Multiple walks are done in the same order passed to combined_walk_add(). Each * walk is initialized with the same wsp->walk_addr. */ void combined_walk_init(mdb_walk_state_t *wsp) { combined_walk_data_t *cwd; cwd = mdb_alloc(sizeof (combined_walk_data_t), UM_SLEEP); cwd->cwd_initial_walk_addr = wsp->walk_addr; cwd->cwd_current_walk = cwd->cwd_final_walk = NULL; cwd->cwd_next = cwd->cwd_prev = NULL; cwd->cwd_tag = NULL; wsp->walk_data = cwd; } /* * If a sub-walker's walk_step() is interrupted (by Ctrl-C or entering 'q' when * prompted for the next screenful of data), there won't be an opportunity to * switch wsp->walk_data from the sub-walker's data back to the combined walk * data, since control will not return from walk_step(). Since mdb is * single-threaded, we can save the combined walk data for combined_walk_fini() * to use in case it was reached from an interrupted walk_step(). To allow for * the possibility of nested combined walks, we'll save them on a list tagged by * the sub-walker's data. */ static combined_walk_data_t *cwd_saved; static void combined_walk_data_save(combined_walk_data_t *cwd, void *tag) { cwd->cwd_next = cwd_saved; cwd->cwd_prev = NULL; if (cwd_saved != NULL) { cwd_saved->cwd_prev = cwd; } cwd_saved = cwd; cwd->cwd_tag = tag; } static void combined_walk_data_drop(combined_walk_data_t *cwd) { if (cwd->cwd_prev == NULL) { cwd_saved = cwd->cwd_next; } else { cwd->cwd_prev->cwd_next = cwd->cwd_next; } if (cwd->cwd_next != NULL) { cwd->cwd_next->cwd_prev = cwd->cwd_prev; } cwd->cwd_next = cwd->cwd_prev = NULL; cwd->cwd_tag = NULL; } static combined_walk_data_t * combined_walk_data_find(void *tag) { combined_walk_data_t *cwd; if (tag == NULL) { return (NULL); } for (cwd = cwd_saved; cwd != NULL; cwd = cwd->cwd_next) { if (cwd->cwd_tag == tag) { return (cwd); } } return (NULL); } static void combined_walk_append(combined_walk_data_t *cwd, combined_walk_t *cw) { if (cwd->cwd_final_walk == NULL) { cwd->cwd_current_walk = cwd->cwd_final_walk = cw; } else { cwd->cwd_final_walk->cw_next = cw; cwd->cwd_final_walk = cw; } } static combined_walk_t * combined_walk_remove_current(combined_walk_data_t *cwd) { combined_walk_t *cw = cwd->cwd_current_walk; if (cw == NULL) { return (NULL); } if (cw == cwd->cwd_final_walk) { cwd->cwd_final_walk = cw->cw_next; } cwd->cwd_current_walk = cw->cw_next; cw->cw_next = NULL; return (cw); } void combined_walk_add(mdb_walk_state_t *wsp, int (*walk_init)(mdb_walk_state_t *), int (*walk_step)(mdb_walk_state_t *), void (*walk_fini)(mdb_walk_state_t *)) { combined_walk_data_t *cwd = wsp->walk_data; combined_walk_t *cw; cw = mdb_alloc(sizeof (combined_walk_t), UM_SLEEP); cw->cw_init = walk_init; cw->cw_step = walk_step; cw->cw_fini = walk_fini; cw->cw_next = NULL; cw->cw_data = NULL; cw->cw_initialized = B_FALSE; combined_walk_append(cwd, cw); } int combined_walk_step(mdb_walk_state_t *wsp) { combined_walk_data_t *cwd = wsp->walk_data; combined_walk_t *cw = cwd->cwd_current_walk; int status; if (cw == NULL) { return (WALK_DONE); } if (cw->cw_initialized) { wsp->walk_data = cw->cw_data; } else { wsp->walk_addr = cwd->cwd_initial_walk_addr; status = cw->cw_init(wsp); cw->cw_data = wsp->walk_data; if (status != WALK_NEXT) goto done; cw->cw_initialized = B_TRUE; } /* save cwd for fini() in case step() is interrupted */ combined_walk_data_save(cwd, cw->cw_data); status = cw->cw_step(wsp); /* control may never reach here */ combined_walk_data_drop(cwd); if (status == WALK_DONE) goto done; wsp->walk_data = cwd; return (status); done: (void) combined_walk_remove_current(cwd); if (cw->cw_initialized) cw->cw_fini(wsp); mdb_free(cw, sizeof (combined_walk_t)); wsp->walk_data = cwd; if (status == WALK_DONE) return (combined_walk_step(wsp)); return (status); } void combined_walk_fini(mdb_walk_state_t *wsp) { combined_walk_data_t *cwd; combined_walk_t *cw; /* * If walk_step() was interrupted, wsp->walk_data will be the * sub-walker's data, not the combined walker's data, so first check to * see if there is saved combined walk data tagged by the presumed * sub-walker's walk data. */ cwd = combined_walk_data_find(wsp->walk_data); if (cwd == NULL) { /* * walk_step() was not interrupted, so wsp->walk_data is * actually the combined walk data. */ cwd = wsp->walk_data; } else { combined_walk_data_drop(cwd); } while ((cw = combined_walk_remove_current(cwd)) != NULL) { if (cw->cw_initialized) { wsp->walk_data = cw->cw_data; cw->cw_fini(wsp); } mdb_free(cw, sizeof (combined_walk_t)); } mdb_free(cwd, sizeof (combined_walk_data_t)); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _COMBINED_H #define _COMBINED_H #include #ifdef __cplusplus extern "C" { #endif extern void combined_walk_init(mdb_walk_state_t *wsp); extern void combined_walk_add(mdb_walk_state_t *wsp, int (*walk_init)(mdb_walk_state_t *), int (*walk_step)(mdb_walk_state_t *), void (*walk_fini)(mdb_walk_state_t *)); extern int combined_walk_step(mdb_walk_state_t *wsp); extern void combined_walk_fini(mdb_walk_state_t *wsp); #ifdef __cplusplus } #endif #endif /* _COMBINED_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include int ct_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { wsp->walk_addr = wsp->walk_addr + OFFSETOF(ct_type_t, ct_type_avl); } else { GElf_Sym sym; if (mdb_lookup_by_name("contract_avl", &sym)) { mdb_warn("failed to read contract_avl"); return (WALK_ERR); } wsp->walk_addr = sym.st_value; } if (mdb_layered_walk("avl", wsp) == -1) return (WALK_ERR); return (WALK_NEXT); } int ct_event_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("ct_event walker requires ct_equeue address\n"); return (WALK_ERR); } wsp->walk_addr = wsp->walk_addr + OFFSETOF(ct_equeue_t, ctq_events); if (mdb_layered_walk("list", wsp) == -1) return (WALK_ERR); return (WALK_NEXT); } int ct_listener_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("ct_listener walker requires ct_equeue address\n"); return (WALK_ERR); } wsp->walk_addr = wsp->walk_addr + OFFSETOF(ct_equeue_t, ctq_listeners); if (mdb_layered_walk("list", wsp) == -1) return (WALK_ERR); return (WALK_NEXT); } /* ARGSUSED */ int cmd_contract(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { contract_t ct; ct_type_t ctt; char str[32]; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("contract", "contract", argc, argv) == -1) { mdb_warn("can't walk 'contract'"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %8s %8s %8s %?s %?s%\n", "ADDR", "ID", "TYPE", "STATE", "OWNER", "REGENT"); if (mdb_vread(&ct, sizeof (ct), addr) != sizeof (ct)) { mdb_warn("error reading contract_t at %p", addr); return (DCMD_ERR); } if (mdb_vread(&ctt, sizeof (ctt), (uintptr_t)ct.ct_type) != sizeof (ctt)) { mdb_warn("error reading ct_type_t at %p", ct.ct_type); return (DCMD_ERR); } if (mdb_readstr(str, sizeof (str), (uintptr_t)ctt.ct_type_name) == -1) { mdb_warn("error reading contract type name at %p", ctt.ct_type_name); return (DCMD_ERR); } mdb_printf("%0?p %8d %8s %8s %?p %?p\n", addr, ct.ct_id, str, (ct.ct_state == CTS_OWNED) ? "owned" : (ct.ct_state == CTS_INHERITED) ? "inherit" : (ct.ct_state == CTS_ORPHAN) ? "orphan" : "dead", ct.ct_owner, ct.ct_regent); return (DCMD_OK); } const mdb_bitmask_t ct_event_flags[] = { { "ACK", CTE_ACK, CTE_ACK }, { "INFO", CTE_INFO, CTE_INFO }, { "NEG", CTE_NEG, CTE_NEG }, { NULL } }; /* ARGSUSED */ int cmd_ctevent(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ct_kevent_t cte; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%%12s %8s %12s %6s %12s %12s %s%\n", "ADDR", "ID", "CONTRACT", "TYPE", "DATA", "GDATA", "FLAGS"); if (mdb_vread(&cte, sizeof (cte), addr) != sizeof (cte)) { mdb_warn("error reading ct_kevent_t at %p", addr); return (DCMD_ERR); } mdb_printf("%12p %8llu %12p %6d %12p %12p %b\n", addr, cte.cte_id, cte.cte_contract, cte.cte_type, cte.cte_data, cte.cte_gdata, cte.cte_flags, ct_event_flags); return (DCMD_OK); } typedef struct findct_data { uintptr_t fc_ctid; uintptr_t fc_addr; boolean_t fc_found; } findct_data_t; static int findct(uintptr_t addr, contract_t *ct, findct_data_t *arg) { if (ct->ct_id == arg->fc_ctid) { arg->fc_found = B_TRUE; arg->fc_addr = addr; return (WALK_DONE); } return (WALK_NEXT); } /* ARGSUSED */ int cmd_ctid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { findct_data_t fcdata; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); fcdata.fc_ctid = addr; fcdata.fc_found = B_FALSE; if (mdb_walk("contract", (mdb_walk_cb_t)findct, &fcdata) == -1 || !fcdata.fc_found) return (DCMD_ERR); mdb_printf("%lr", fcdata.fc_addr); return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _CONTRACT_H #define _CONTRACT_H #include #ifdef __cplusplus extern "C" { #endif int ct_walk_init(mdb_walk_state_t *); int ct_event_walk_init(mdb_walk_state_t *); int ct_listener_walk_init(mdb_walk_state_t *); int ct_common_walk_step(mdb_walk_state_t *); int cmd_contract(uintptr_t, uint_t, int, const mdb_arg_t *); int cmd_ctevent(uintptr_t, uint_t, int, const mdb_arg_t *); int cmd_ctid(uintptr_t, uint_t, int, const mdb_arg_t *); int cmd_ctmpl(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _CONTRACT_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include "lgrp.h" #include "cpupart_mdb.h" #include #include /* ARGSUSED */ static int cpupart_cpulist_callback(uintptr_t addr, const void *arg, void *cb_data) { cpu_t *cpu = (cpu_t *)arg; ulong_t *cpuset = cb_data; BT_SET(cpuset, cpu->cpu_id); return (WALK_NEXT); } #define CPUPART_IDWIDTH 3 #ifdef _LP64 #define CPUPART_CPUWIDTH 21 #if defined(__amd64) #define CPUPART_TWIDTH 16 #else #define CPUPART_TWIDTH 11 #endif #else #define CPUPART_CPUWIDTH 13 #define CPUPART_TWIDTH 8 #endif #define CPUPART_THRDELT (CPUPART_IDWIDTH + CPUPART_CPUWIDTH) #define CPUPART_INDENT mdb_printf("%*s", CPUPART_THRDELT, "") int cpupart_disp_threads(disp_t *disp) { dispq_t *dq; int i, npri = disp->disp_npri; proc_t p; kthread_t t; dq = mdb_alloc(sizeof (dispq_t) * npri, UM_SLEEP | UM_GC); if (mdb_vread(dq, sizeof (dispq_t) * npri, (uintptr_t)disp->disp_q) == -1) { mdb_warn("failed to read dispq_t at %p", disp->disp_q); return (DCMD_ERR); } CPUPART_INDENT; mdb_printf("|\n"); CPUPART_INDENT; mdb_printf("+--> %3s %-*s %s\n", "PRI", CPUPART_TWIDTH, "THREAD", "PROC"); for (i = npri - 1; i >= 0; i--) { uintptr_t taddr = (uintptr_t)dq[i].dq_first; while (taddr != 0) { if (mdb_vread(&t, sizeof (t), taddr) == -1) { mdb_warn("failed to read kthread_t at %p", taddr); return (DCMD_ERR); } if (mdb_vread(&p, sizeof (p), (uintptr_t)t.t_procp) == -1) { mdb_warn("failed to read proc_t at %p", t.t_procp); return (DCMD_ERR); } CPUPART_INDENT; mdb_printf("%9d %0*p %s\n", t.t_pri, CPUPART_TWIDTH, taddr, p.p_user.u_comm); taddr = (uintptr_t)t.t_link; } } return (DCMD_OK); } /* ARGSUSED */ int cpupart(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { cpupart_t cpupart; int cpusetsize; int _ncpu; ulong_t *cpuset; uint_t verbose = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("cpupart_walk", "cpupart", argc, argv) == -1) { mdb_warn("can't walk 'cpupart'"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%3s %?s %4s %4s %4s\n", "ID", "ADDR", "NRUN", "#CPU", "CPUS"); } if (mdb_vread(&cpupart, sizeof (cpupart_t), addr) == -1) { mdb_warn("unable to read 'cpupart_t' at %p", addr); return (DCMD_ERR); } mdb_printf("%3d %?p %4d %4d ", cpupart.cp_id, addr, cpupart.cp_kp_queue.disp_nrunnable, cpupart.cp_ncpus); if (cpupart.cp_ncpus == 0) { mdb_printf("\n"); return (DCMD_OK); } /* * figure out what cpus we've got */ if (mdb_readsym(&_ncpu, sizeof (int), "_ncpu") == -1) { mdb_warn("symbol '_ncpu' not found"); return (DCMD_ERR); } /* * allocate enough space for set of longs to hold cpuid bitfield */ cpusetsize = BT_BITOUL(_ncpu) * sizeof (ulong_t); cpuset = mdb_zalloc(cpusetsize, UM_SLEEP | UM_GC); if (mdb_pwalk("cpupart_cpulist", cpupart_cpulist_callback, cpuset, addr) == -1) { mdb_warn("unable to walk cpupart_cpulist"); return (DCMD_ERR); } print_cpuset_range(cpuset, cpusetsize/sizeof (ulong_t), 0); mdb_printf("\n"); /* * If there are any threads on kp queue and -v is specified */ if (verbose && cpupart.cp_kp_queue.disp_nrunnable) { if (cpupart_disp_threads(&cpupart.cp_kp_queue) != DCMD_OK) return (DCMD_ERR); } return (DCMD_OK); } typedef struct cpupart_cpulist_walk { uintptr_t ccw_firstcpu; int ccw_cpusleft; } cpupart_cpulist_walk_t; int cpupart_cpulist_walk_init(mdb_walk_state_t *wsp) { cpupart_cpulist_walk_t *ccw; cpupart_t cpupart; ccw = mdb_alloc(sizeof (cpupart_cpulist_walk_t), UM_SLEEP | UM_GC); if (mdb_vread(&cpupart, sizeof (cpupart_t), wsp->walk_addr) == -1) { mdb_warn("couldn't read 'cpupart' at %p", wsp->walk_addr); return (WALK_ERR); } ccw->ccw_firstcpu = (uintptr_t)cpupart.cp_cpulist; ccw->ccw_cpusleft = cpupart.cp_ncpus; wsp->walk_data = ccw; wsp->walk_addr = ccw->ccw_firstcpu; return (WALK_NEXT); } int cpupart_cpulist_walk_step(mdb_walk_state_t *wsp) { cpupart_cpulist_walk_t *ccw = (cpupart_cpulist_walk_t *) wsp->walk_data; uintptr_t addr = wsp->walk_addr; cpu_t cpu; int status; if (mdb_vread(&cpu, sizeof (cpu_t), addr) == -1) { mdb_warn("couldn't read 'cpupart' at %p", addr); return (WALK_ERR); } status = wsp->walk_callback(addr, &cpu, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); addr = (uintptr_t)cpu.cpu_next_part; wsp->walk_addr = addr; ccw->ccw_cpusleft--; if (ccw->ccw_cpusleft < 0) { mdb_warn("cpu count doesn't match cpupart list"); return (WALK_ERR); } if (ccw->ccw_firstcpu == addr) { if (ccw->ccw_cpusleft != 0) { mdb_warn("cpu count doesn't match cpupart list"); return (WALK_ERR); } return (WALK_DONE); } return (WALK_NEXT); } int cpupart_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; uintptr_t addr; if (mdb_lookup_by_name("cp_default", &sym) == -1) { mdb_warn("failed to find 'cp_default'\n"); return (WALK_ERR); } addr = (uintptr_t)sym.st_value; wsp->walk_data = (void *)addr; wsp->walk_addr = addr; return (WALK_NEXT); } int cpupart_walk_step(mdb_walk_state_t *wsp) { cpupart_t cpupart; int status; if (mdb_vread(&cpupart, sizeof (cpupart_t), wsp->walk_addr) == -1) { mdb_warn("unable to read cpupart at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &cpupart, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); wsp->walk_addr = (uintptr_t)cpupart.cp_next; if (wsp->walk_addr == (uintptr_t)wsp->walk_data) return (WALK_DONE); return (WALK_NEXT); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_CPUPART_MDB_H #define _MDB_CPUPART_MDB_H #include #ifdef __cplusplus extern "C" { #endif extern int cpupart_cpulist_walk_init(mdb_walk_state_t *); extern int cpupart_cpulist_walk_step(mdb_walk_state_t *); extern int cpupart_walk_init(mdb_walk_state_t *); extern int cpupart_walk_step(mdb_walk_state_t *); extern int cpupart(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_CPUPART_MDB_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2011 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include "cred.h" #define OPT_VERBOSE 1 static void print_ksid(const ksid_t *); /* * dcmd ::cred - display a credential (cred_t) */ int cmd_cred(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { credgrp_t cr_grps; cred_t *cr; mdb_arg_t cmdarg; uint_t opts = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, OPT_VERBOSE, &opts, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } cr = mdb_alloc(sizeof (*cr), UM_SLEEP | UM_GC); if (mdb_vread(cr, sizeof (*cr), addr) == -1) { mdb_warn("error reading cred_t at %p", addr); return (DCMD_ERR); } if (cr->cr_grps == NULL) { bzero(&cr_grps, sizeof (cr_grps)); } else { if (mdb_vread(&cr_grps, sizeof (cr_grps), (uintptr_t)cr->cr_grps) == -1) { mdb_warn("error reading credgrp_t at %p", cr->cr_grps); return (DCMD_ERR); } } if (opts & OPT_VERBOSE) { cmdarg.a_type = MDB_TYPE_STRING; cmdarg.a_un.a_str = "cred_t"; (void) mdb_call_dcmd("print", addr, flags, 1, &cmdarg); cmdarg.a_un.a_str = "-v"; mdb_printf("%cr_grps:%\n"); mdb_inc_indent(4); if (cr->cr_grps == NULL) { mdb_printf("(null)\n"); } else { (void) mdb_call_dcmd("credgrp", (uintptr_t)cr->cr_grps, flags, 1, &cmdarg); } mdb_dec_indent(4); mdb_printf("%cr_ksid:%\n"); mdb_inc_indent(4); if (cr->cr_ksid == NULL) { mdb_printf("(null)\n"); } else { (void) mdb_call_dcmd("credsid", (uintptr_t)cr->cr_ksid, flags, 1, &cmdarg); } mdb_dec_indent(4); return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %8s %8s %8s %8s% %8s%\n", "ADDR", "UID", "GID", "RUID", "RGID", "#GRP(+SIDS)"); mdb_printf("%0?p %8u %8u %8u %8u %4u%s\n", addr, cr->cr_uid, cr->cr_gid, cr->cr_ruid, cr->cr_rgid, cr_grps.crg_ngroups, (cr->cr_ksid == NULL) ? "" : "+"); return (DCMD_OK); } /* * dcmd ::credgrp - display cred_t groups */ int cmd_credgrp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { credgrp_t grps; gid_t gid; uint_t i, opts = FALSE; int rv = DCMD_OK; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, OPT_VERBOSE, &opts, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_vread(&grps, sizeof (grps), addr) == -1) { mdb_warn("error reading credgrp_t at %p", addr); return (DCMD_ERR); } if (opts & OPT_VERBOSE) { mdb_printf("crg_ref = 0x%x\n", grps.crg_ref); mdb_printf("crg_ngroups = 0x%x\n", grps.crg_ngroups); } mdb_printf("crg_groups = [\n"); addr += OFFSETOF(credgrp_t, crg_groups); mdb_inc_indent(4); for (i = 0; i < grps.crg_ngroups; i++, addr += sizeof (gid_t)) { if (mdb_vread(&gid, sizeof (gid), addr) == -1) { mdb_warn("error reading gid_t at %p", addr); rv = DCMD_ERR; break; } mdb_printf("\t%u,", gid); } mdb_dec_indent(4); mdb_printf("\n]\n"); return (rv); } /* * dcmd ::credsid - display a credsid_t */ int cmd_credsid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { credsid_t kr; uint_t opts = FALSE; int rv = DCMD_OK; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, OPT_VERBOSE, &opts, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_vread(&kr, sizeof (kr), addr) == -1) { mdb_warn("error reading credsid_t at %p", addr); return (DCMD_ERR); } if (opts & OPT_VERBOSE) mdb_printf("kr_ref = 0x%x\n", kr.kr_ref); mdb_printf("kr_sidx[USER] = "); print_ksid(&kr.kr_sidx[KSID_USER]); mdb_printf("kr_sidx[GROUP] = "); print_ksid(&kr.kr_sidx[KSID_GROUP]); mdb_printf("kr_sidx[OWNER] = "); print_ksid(&kr.kr_sidx[KSID_OWNER]); mdb_printf("kr_sidlist = %p\n", kr.kr_sidlist); if (kr.kr_sidlist != NULL && (opts & OPT_VERBOSE) != 0) { mdb_printf("*kr_sidlist = {\n"); mdb_inc_indent(4); rv = mdb_call_dcmd("ksidlist", (uintptr_t)kr.kr_sidlist, flags, argc, argv); mdb_dec_indent(4); mdb_printf("}\n"); } return (rv); } /* * dcmd ::ksidlist - display a ksidlist_t */ int cmd_ksidlist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ksidlist_t ksl; ksid_t ks; uint_t i, opts = FALSE; int rv = DCMD_OK; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, OPT_VERBOSE, &opts, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_vread(&ksl, sizeof (ksl), addr) == -1) { mdb_warn("error reading ksidlist_t at %p", addr); return (DCMD_ERR); } if (opts & OPT_VERBOSE) { mdb_printf("ksl_ref = 0x%x\n", ksl.ksl_ref); mdb_printf("ksl_nsid = 0x%x\n", ksl.ksl_nsid); mdb_printf("ksl_neid = 0x%x\n", ksl.ksl_neid); } mdb_printf("ksl_sids = [\n"); addr += OFFSETOF(ksidlist_t, ksl_sids); mdb_inc_indent(4); for (i = 0; i < ksl.ksl_nsid; i++, addr += sizeof (ksid_t)) { if (mdb_vread(&ks, sizeof (ks), addr) == -1) { mdb_warn("error reading ksid_t at %p", addr); rv = DCMD_ERR; break; } print_ksid(&ks); } mdb_dec_indent(4); mdb_printf("]\n"); return (rv); } static void print_ksid(const ksid_t *ks) { char str[80]; ksiddomain_t kd; uintptr_t da, sa; /* in case of errors */ strcpy(str, "(domain?)"); da = (uintptr_t)ks->ks_domain; if (da == 0 || mdb_vread(&kd, sizeof (kd), da) < 0) bzero(&kd, sizeof (kd)); sa = (uintptr_t)kd.kd_name; if (sa != 0) (void) mdb_readstr(str, sizeof (str), sa); mdb_printf("%s-%u,\n", str, ks->ks_rid); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2011 Nexenta Systems, Inc. All rights reserved. */ #ifndef _MDB_CRED_H #define _MDB_CRED_H #include #ifdef __cplusplus extern "C" { #endif int cmd_cred(uintptr_t, uint_t, int, const mdb_arg_t *); int cmd_credgrp(uintptr_t, uint_t, int, const mdb_arg_t *); int cmd_credsid(uintptr_t, uint_t, int, const mdb_arg_t *); int cmd_ksidlist(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_CRED_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2000 by Sun Microsystems, Inc. * All rights reserved. */ /* * Copyright 2018 Joyent, Inc. */ #include #include #include "ctxop.h" struct ctxop_walk_state { uintptr_t cws_head; uint_t cws_next_offset; }; int ctxop_walk_init(mdb_walk_state_t *wsp) { struct ctxop_walk_state *priv; int offset; uintptr_t addr; if (wsp->walk_addr == 0) { mdb_warn("must specify thread for ctxop walk\n"); return (WALK_ERR); } offset = mdb_ctf_offsetof_by_name("kthread_t", "t_ctx"); if (offset == -1) return (WALK_ERR); if (mdb_vread(&addr, sizeof (addr), wsp->walk_addr + offset) != sizeof (addr)) { mdb_warn("failed to read thread %p", wsp->walk_addr); return (WALK_ERR); } /* No further work for threads with a NULL t_ctx */ if (addr == 0) { wsp->walk_data = NULL; return (WALK_DONE); } /* rely on CTF for the offset of the 'next' pointer */ offset = mdb_ctf_offsetof_by_name("struct ctxop", "next"); if (offset == -1) return (WALK_ERR); priv = mdb_alloc(sizeof (*priv), UM_SLEEP); priv->cws_head = addr; priv->cws_next_offset = (uint_t)offset; wsp->walk_data = priv; wsp->walk_addr = addr; return (WALK_NEXT); } int ctxop_walk_step(mdb_walk_state_t *wsp) { struct ctxop_walk_state *priv = wsp->walk_data; uintptr_t next; int status; if (mdb_vread(&next, sizeof (next), wsp->walk_addr + priv->cws_next_offset) == -1) { mdb_warn("failed to read ctxop`next at %p", wsp->walk_addr + priv->cws_next_offset); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); if (status == WALK_NEXT) { /* * If a NULL terminator or a loop back to the head element is * encountered, the walk is done. */ if (next == 0 || next == priv->cws_head) { status = WALK_DONE; } } wsp->walk_addr = next; return (status); } void ctxop_walk_fini(mdb_walk_state_t *wsp) { struct ctxop_walk_state *priv = wsp->walk_data; if (priv != NULL) { mdb_free(priv, sizeof (*priv)); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2000 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_CTXOP_H #define _MDB_CTXOP_H #include #ifdef __cplusplus extern "C" { #endif extern int ctxop_walk_init(mdb_walk_state_t *); extern int ctxop_walk_step(mdb_walk_state_t *); extern void ctxop_walk_fini(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _MDB_CTXOP_H */ /* * 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. */ /* * Copyright 2023 Oxide Computer Company */ #include "cyclic.h" #define CYCLIC_TRACE #include #include #include #include #include int cyccpu_vread(cyc_cpu_t *cpu, uintptr_t addr) { static int inited = 0; static int cyc_trace_enabled = 0; static size_t cyccpu_size; if (!inited) { inited = 1; (void) mdb_readvar(&cyc_trace_enabled, "cyc_trace_enabled"); cyccpu_size = (cyc_trace_enabled) ? sizeof (*cpu) : OFFSETOF(cyc_cpu_t, cyp_trace); } if (mdb_vread(cpu, cyccpu_size, addr) == -1) return (-1); if (!cyc_trace_enabled) bzero(cpu->cyp_trace, sizeof (cpu->cyp_trace)); return (0); } int cyccpu_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("cpu", wsp) == -1) { mdb_warn("couldn't walk 'cpu'"); return (WALK_ERR); } return (WALK_NEXT); } int cyccpu_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = (uintptr_t)((cpu_t *)wsp->walk_layer)->cpu_cyclic; cyc_cpu_t cpu; if (cyccpu_vread(&cpu, addr) == -1) { mdb_warn("couldn't read cyc_cpu at %p", addr); return (WALK_ERR); } return (wsp->walk_callback(addr, &cpu, wsp->walk_cbdata)); } int cycomni_walk_init(mdb_walk_state_t *wsp) { cyc_id_t id; if (wsp->walk_addr == 0) { mdb_warn("must provide a cyclic id\n"); return (WALK_ERR); } if (mdb_vread(&id, sizeof (id), wsp->walk_addr) == -1) { mdb_warn("couldn't read cyc_id_t at %p", wsp->walk_addr); return (WALK_ERR); } if (id.cyi_cpu != NULL || id.cyi_omni_list == NULL || id.cyi_omni_hdlr.cyo_online == NULL) { mdb_warn("%p is not an omnipresent cyclic.\n", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)id.cyi_omni_list; return (WALK_NEXT); } int cycomni_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; cyc_omni_cpu_t omni; if (addr == 0) return (WALK_DONE); if (mdb_vread(&omni, sizeof (omni), addr) == -1) { mdb_warn("couldn't read cyc_omni_cpu at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)omni.cyo_next; return (wsp->walk_callback(addr, &omni, wsp->walk_cbdata)); } void cyclic_dump_node(cyc_cpu_t *cpu, cyc_index_t *heap, char **c, size_t w, int ndx, int l, int r, int depth) { int heap_left, heap_right; int me; int i, x = l + (r - l) / 2; size_t n = w - (x - 1); /* n bytes left for snprintf after c[][x - 1] */ heap_left = CYC_HEAP_LEFT(ndx); heap_right = CYC_HEAP_RIGHT(ndx); me = heap[ndx]; if (ndx >= cpu->cyp_nelems) return; if (me < 10) { (void) mdb_snprintf(&c[depth][x - 1], n, " %d", me); } else if (me >= 100) { (void) mdb_snprintf(&c[depth][x - 1], n, "%3d", me); } else { (void) mdb_snprintf(&c[depth][x - 1], n, "%s%2d%s", CYC_HEAP_LEFT(CYC_HEAP_PARENT(ndx)) == ndx ? " " : "", me, CYC_HEAP_LEFT(CYC_HEAP_PARENT(ndx)) == ndx ? "" : " "); } if (r - l > 5) { c[++depth][x] = '|'; depth++; for (i = l + (r - l) / 4; i < r - (r - l) / 4; i++) c[depth][i] = '-'; c[depth][l + (r - l) / 4] = '+'; c[depth][r - (r - l) / 4 - 1] = '+'; c[depth][x] = '+'; } else { if (heap_left >= cpu->cyp_nelems) return; (void) mdb_snprintf(&c[++depth][x - 1], n, "L%d", heap[heap_left]); if (heap_right >= cpu->cyp_nelems) return; (void) mdb_snprintf(&c[++depth][x - 1], n, "R%d", heap[heap_right]); return; } if (heap_left < cpu->cyp_nelems) cyclic_dump_node(cpu, heap, c, w, heap_left, l, x, depth + 1); if (heap_right < cpu->cyp_nelems) cyclic_dump_node(cpu, heap, c, w, heap_right, x, r, depth + 1); } #define LINES_PER_LEVEL 3 void cyclic_pretty_dump(cyc_cpu_t *cpu) { char **c; int i, j; int width = 80; int depth; cyc_index_t *heap; size_t hsize = sizeof (cyc_index_t) * cpu->cyp_size; heap = mdb_alloc(hsize, UM_SLEEP | UM_GC); if (mdb_vread(heap, hsize, (uintptr_t)cpu->cyp_heap) == -1) { mdb_warn("couldn't read heap at %p", (uintptr_t)cpu->cyp_heap); return; } for (depth = 0; (1 << depth) < cpu->cyp_nelems; depth++) continue; depth++; depth = (depth + 1) * LINES_PER_LEVEL; c = mdb_zalloc(sizeof (char *) * depth, UM_SLEEP|UM_GC); for (i = 0; i < depth; i++) c[i] = mdb_zalloc(width, UM_SLEEP|UM_GC); cyclic_dump_node(cpu, heap, c, width, 0, 1, width - 2, 0); for (i = 0; i < depth; i++) { int dump = 0; for (j = 0; j < width - 1; j++) { if (c[i][j] == '\0') c[i][j] = ' '; else dump = 1; } c[i][width - 2] = '\n'; if (dump) mdb_printf(c[i]); } } int cycinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { cyc_cpu_t cpu; cpu_t c; cyc_index_t root, i, *heap; size_t hsize; cyclic_t *cyc; uintptr_t caddr; uint_t verbose = FALSE, Verbose = FALSE; int header = 0; cyc_level_t lev; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("cyccpu", "cycinfo", argc, argv) == -1) { mdb_warn("can't walk 'cyccpu'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'V', MDB_OPT_SETBITS, TRUE, &Verbose, NULL) != argc) return (DCMD_USAGE); if (!DCMD_HDRSPEC(flags) && (verbose || Verbose)) mdb_printf("\n\n"); if (DCMD_HDRSPEC(flags) || verbose || Verbose) mdb_printf("%3s %?s %7s %6s %15s %s\n", "CPU", "CYC_CPU", "STATE", "NELEMS", "FIRE", "HANDLER"); if (cyccpu_vread(&cpu, addr) == -1) { mdb_warn("couldn't read cyc_cpu at %p", addr); return (DCMD_ERR); } if (mdb_vread(&c, sizeof (c), (uintptr_t)cpu.cyp_cpu) == -1) { mdb_warn("couldn't read cpu at %p", cpu.cyp_cpu); return (DCMD_ERR); } cyc = mdb_alloc(sizeof (cyclic_t) * cpu.cyp_size, UM_SLEEP | UM_GC); caddr = (uintptr_t)cpu.cyp_cyclics; if (mdb_vread(cyc, sizeof (cyclic_t) * cpu.cyp_size, caddr) == -1) { mdb_warn("couldn't read cyclic at %p", caddr); return (DCMD_ERR); } hsize = sizeof (cyc_index_t) * cpu.cyp_size; heap = mdb_alloc(hsize, UM_SLEEP | UM_GC); if (mdb_vread(heap, hsize, (uintptr_t)cpu.cyp_heap) == -1) { mdb_warn("couldn't read heap at %p", cpu.cyp_heap); return (DCMD_ERR); } root = heap[0]; mdb_printf("%3d %0?p %7s %6d ", c.cpu_id, addr, cpu.cyp_state == CYS_ONLINE ? "online" : cpu.cyp_state == CYS_OFFLINE ? "offline" : cpu.cyp_state == CYS_EXPANDING ? "expand" : cpu.cyp_state == CYS_REMOVING ? "remove" : cpu.cyp_state == CYS_SUSPENDED ? "suspend" : "????", cpu.cyp_nelems); if (cpu.cyp_nelems > 0) mdb_printf("%15llx %a\n", cyc[root].cy_expire, cyc[root].cy_handler); else mdb_printf("%15s %s\n", "-", "-"); if (!verbose && !Verbose) return (DCMD_OK); mdb_printf("\n"); cyclic_pretty_dump(&cpu); mdb_inc_indent(2); for (i = 0; i < cpu.cyp_size; i++) { int j; for (j = 0; j < cpu.cyp_size; j++) { if (heap[j] == i) break; } if (!Verbose && j >= cpu.cyp_nelems) continue; if (!header) { header = 1; mdb_printf("\n%?s %3s %3s %3s %5s %14s %s\n", "ADDR", "NDX", "HPX", "LVL", "PEND", "FIRE", "HANDLER"); } mdb_printf("%0?p %3d ", caddr + i * sizeof (cyclic_t), i); mdb_printf("%3d ", j); if (j >= cpu.cyp_nelems) { mdb_printf("%3s %5s %14s %s\n", "-", "-", "-", "-"); continue; } mdb_printf("%3s %5d ", cyc[i].cy_level == CY_HIGH_LEVEL ? "hgh" : cyc[i].cy_level == CY_LOCK_LEVEL ? "lck" : cyc[i].cy_level == CY_LOW_LEVEL ? "low" : "????", cyc[i].cy_pend); if (cyc[i].cy_expire != INT64_MAX) mdb_printf("%14llx ", cyc[i].cy_expire); else mdb_printf("%14s ", "-"); mdb_printf("%a\n", cyc[i].cy_handler); } if (!Verbose) goto out; for (lev = CY_LOW_LEVEL; lev < CY_LOW_LEVEL + CY_SOFT_LEVELS; lev++) { cyc_softbuf_t *softbuf = &cpu.cyp_softbuf[lev]; char which = softbuf->cys_hard, shared = 1; cyc_pcbuffer_t *pc; size_t bufsiz; cyc_index_t *buf; if (softbuf->cys_hard != softbuf->cys_soft) shared = 0; again: pc = &softbuf->cys_buf[which]; bufsiz = (pc->cypc_sizemask + 1) * sizeof (cyc_index_t); buf = mdb_alloc(bufsiz, UM_SLEEP | UM_GC); if (mdb_vread(buf, bufsiz, (uintptr_t)pc->cypc_buf) == -1) { mdb_warn("couldn't read cypc_buf at %p", pc->cypc_buf); continue; } mdb_printf("\n%3s %4s %4s %4s %?s %4s %?s\n", "CPU", "LEVL", "USER", "NDX", "ADDR", "CYC", "CYC_ADDR", "PEND"); for (i = 0; i <= pc->cypc_sizemask && i <= pc->cypc_prodndx; i++) { uintptr_t cyc_addr = caddr + buf[i] * sizeof (cyclic_t); mdb_printf("%3d %4s %4s ", c.cpu_id, lev == CY_HIGH_LEVEL ? "high" : lev == CY_LOCK_LEVEL ? "lock" : lev == CY_LOW_LEVEL ? "low" : "????", shared ? "shrd" : which == softbuf->cys_hard ? "hard" : "soft"); mdb_printf("%4d %0?p ", i, (uintptr_t)&buf[i] - (uintptr_t)&buf[0] + (uintptr_t)pc->cypc_buf, buf[i], caddr + buf[i] * sizeof (cyclic_t)); if (i >= pc->cypc_prodndx) mdb_printf("%4s %?s %5s ", "-", "-", "-"); else { cyclic_t c; if (mdb_vread(&c, sizeof (c), cyc_addr) == -1) { mdb_warn("\ncouldn't read cyclic at " "%p", cyc_addr); continue; } mdb_printf("%4d %0?p %5d ", buf[i], cyc_addr, c.cy_pend); } if (i == (pc->cypc_consndx & pc->cypc_sizemask)) { mdb_printf("<-- c"); if (i == (pc->cypc_prodndx & pc->cypc_sizemask)) mdb_printf(",p"); mdb_printf("\n"); continue; } if (i == (pc->cypc_prodndx & pc->cypc_sizemask)) { mdb_printf("<-- p\n"); continue; } mdb_printf("\n"); if (i >= pc->cypc_prodndx) break; } if (!shared && which == softbuf->cys_hard) { which = softbuf->cys_soft; goto again; } } out: mdb_dec_indent(2); return (DCMD_OK); } int cyctrace_walk_init(mdb_walk_state_t *wsp) { cyc_cpu_t *cpu; int i; cpu = mdb_zalloc(sizeof (cyc_cpu_t), UM_SLEEP); if (wsp->walk_addr == 0) { /* * If an address isn't provided, we'll use the passive buffer. */ GElf_Sym sym; cyc_tracebuf_t *tr = &cpu->cyp_trace[0]; uintptr_t addr; if (mdb_lookup_by_name("cyc_ptrace", &sym) == -1) { mdb_warn("couldn't find passive buffer"); return (-1); } addr = (uintptr_t)sym.st_value; if (mdb_vread(tr, sizeof (cyc_tracebuf_t), addr) == -1) { mdb_warn("couldn't read passive buffer"); return (-1); } wsp->walk_addr = addr - offsetof(cyc_cpu_t, cyp_trace[0]); } else { if (cyccpu_vread(cpu, wsp->walk_addr) == -1) { mdb_warn("couldn't read cyc_cpu at %p", wsp->walk_addr); mdb_free(cpu, sizeof (cyc_cpu_t)); return (-1); } } for (i = 0; i < CY_LEVELS; i++) { if (cpu->cyp_trace[i].cyt_ndx-- == 0) cpu->cyp_trace[i].cyt_ndx = CY_NTRACEREC - 1; } wsp->walk_data = cpu; return (0); } int cyctrace_walk_step(mdb_walk_state_t *wsp) { cyc_cpu_t *cpu = wsp->walk_data; cyc_tracebuf_t *buf = cpu->cyp_trace; hrtime_t latest = 0; int i, ndx, new_ndx, lev, rval; uintptr_t addr; for (i = 0; i < CY_LEVELS; i++) { if ((ndx = buf[i].cyt_ndx) == -1) continue; /* * Account for NPT. */ buf[i].cyt_buf[ndx].cyt_tstamp <<= 1; buf[i].cyt_buf[ndx].cyt_tstamp >>= 1; if (buf[i].cyt_buf[ndx].cyt_tstamp > latest) { latest = buf[i].cyt_buf[ndx].cyt_tstamp; lev = i; } } /* * If we didn't find one, we're done. */ if (latest == 0) return (-1); buf = &buf[lev]; ndx = buf->cyt_ndx; addr = wsp->walk_addr + (uintptr_t)&(buf->cyt_buf[ndx]) - (uintptr_t)cpu; rval = wsp->walk_callback(addr, &buf->cyt_buf[ndx], wsp->walk_cbdata); new_ndx = ndx == 0 ? CY_NTRACEREC - 1 : ndx - 1; if (buf->cyt_buf[new_ndx].cyt_tstamp != 0 && buf->cyt_buf[new_ndx].cyt_tstamp > buf->cyt_buf[ndx].cyt_tstamp) new_ndx = -1; buf->cyt_ndx = new_ndx; return (rval); } void cyctrace_walk_fini(mdb_walk_state_t *wsp) { cyc_cpu_t *cpu = wsp->walk_data; mdb_free(cpu, sizeof (cyc_cpu_t)); } #define WHYLEN 17 int cyctrace_walk(uintptr_t addr, const cyc_tracerec_t *rec, cyc_cpu_t *cpu) { int i; char c[WHYLEN]; for (i = 0; cpu != NULL && i < CY_LEVELS; i++) if (addr < (uintptr_t)&cpu->cyp_trace[i + 1].cyt_buf[0]) break; (void) mdb_readstr(c, WHYLEN, (uintptr_t)rec->cyt_why); mdb_printf("%08p %4s %15llx %-*s %15llx %15llx\n", addr & UINT_MAX, cpu == NULL ? "pasv" : i == CY_HIGH_LEVEL ? "high" : i == CY_LOCK_LEVEL ? "lock" : i == CY_LOW_LEVEL ? "low" : "????", rec->cyt_tstamp, WHYLEN, c, rec->cyt_arg0, rec->cyt_arg1); return (0); } /*ARGSUSED*/ int cyctrace(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC) || argc != 0) addr = 0; if (mdb_pwalk("cyctrace", (mdb_walk_cb_t)cyctrace_walk, (void *)addr, addr) == -1) { mdb_warn("couldn't walk cyctrace"); return (DCMD_ERR); } return (DCMD_OK); } int cyccover_comp(const void *l, const void *r) { cyc_coverage_t *lhs = (cyc_coverage_t *)l; cyc_coverage_t *rhs = (cyc_coverage_t *)r; char ly[WHYLEN], ry[WHYLEN]; if (rhs->cyv_why == lhs->cyv_why) return (0); if (rhs->cyv_why == NULL) return (-1); if (lhs->cyv_why == NULL) return (1); (void) mdb_readstr(ly, WHYLEN, (uintptr_t)lhs->cyv_why); (void) mdb_readstr(ry, WHYLEN, (uintptr_t)rhs->cyv_why); return (strcmp(ly, ry)); } /*ARGSUSED*/ int cyccover(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { cyc_coverage_t cv[CY_NCOVERAGE]; char c[WHYLEN]; GElf_Sym sym; int i; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_lookup_by_name("cyc_coverage", &sym) == -1) { mdb_warn("couldn't find coverage information"); return (DCMD_ABORT); } addr = (uintptr_t)sym.st_value; if (mdb_vread(cv, sizeof (cyc_coverage_t) * CY_NCOVERAGE, addr) == -1) { mdb_warn("couldn't read coverage array at %p", addr); return (DCMD_ABORT); } mdb_printf("%-*s %8s %8s %8s %15s %15s\n", WHYLEN, "POINT", "HIGH", "LOCK", "LOW/PASV", "ARG0", "ARG1"); qsort(cv, CY_NCOVERAGE, sizeof (cyc_coverage_t), cyccover_comp); for (i = 0; i < CY_NCOVERAGE; i++) { if (cv[i].cyv_why != NULL) { (void) mdb_readstr(c, WHYLEN, (uintptr_t)cv[i].cyv_why); mdb_printf("%-*s %8d %8d %8d %15llx %15llx\n", WHYLEN, c, cv[i].cyv_count[CY_HIGH_LEVEL], cv[i].cyv_count[CY_LOCK_LEVEL], cv[i].cyv_passive_count != 0 ? cv[i].cyv_passive_count : cv[i].cyv_count[CY_LOW_LEVEL], cv[i].cyv_arg0, cv[i].cyv_arg1); } } return (DCMD_OK); } /*ARGSUSED*/ int cyclic(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { cyclic_t cyc; if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%?s %4s %5s %5s %15s %7s %s\n", "ADDR", "LEVL", "PEND", "FLAGS", "FIRE", "USECINT", "HANDLER"); if (mdb_vread(&cyc, sizeof (cyclic_t), addr) == -1) { mdb_warn("couldn't read cyclic at %p", addr); return (DCMD_ERR); } mdb_printf("%0?p %4s %5d %04x %15llx %7lld %a\n", addr, cyc.cy_level == CY_HIGH_LEVEL ? "high" : cyc.cy_level == CY_LOCK_LEVEL ? "lock" : cyc.cy_level == CY_LOW_LEVEL ? "low" : "????", cyc.cy_pend, cyc.cy_flags, cyc.cy_expire, cyc.cy_interval / (uint64_t)(NANOSEC / MICROSEC), cyc.cy_handler); return (DCMD_OK); } static int cycid_cpu(cyc_cpu_t *addr, int ndx) { cyc_cpu_t cpu; cpu_t c; uintptr_t caddr; cyclic_t cyc; if (cyccpu_vread(&cpu, (uintptr_t)addr) == -1) { mdb_warn("couldn't read cyc_cpu at %p", addr); return (DCMD_ERR); } if (mdb_vread(&c, sizeof (c), (uintptr_t)cpu.cyp_cpu) == -1) { mdb_warn("couldn't read cpu at %p", cpu.cyp_cpu); return (DCMD_ERR); } caddr = (uintptr_t)cpu.cyp_cyclics + ndx * sizeof (cyclic_t); if (mdb_vread(&cyc, sizeof (cyc), caddr) == -1) { mdb_warn("couldn't read cyclic at %p", caddr); return (DCMD_ERR); } mdb_printf("%4d %3d %?p %a\n", c.cpu_id, ndx, caddr, cyc.cy_handler); return (DCMD_OK); } /*ARGSUSED*/ static int cycid_walk_omni(uintptr_t addr, const cyc_omni_cpu_t *omni, int *ignored) { mdb_printf("%?s "); cycid_cpu(omni->cyo_cpu, omni->cyo_ndx); return (WALK_NEXT); } /*ARGSUSED*/ int cycid(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { cyc_id_t id; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("cyclic_id_cache", "cycid", ac, av) == -1) { mdb_warn("can't walk cyclic_id_cache"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %4s %3s %?s %s\n", "ADDR", "CPU", "NDX", "CYCLIC", "HANDLER"); } if (mdb_vread(&id, sizeof (id), addr) == -1) { mdb_warn("couldn't read cyc_id_t at %p", addr); return (DCMD_ERR); } if (id.cyi_cpu == NULL) { /* * This is an omnipresent cyclic. */ mdb_printf("%?p %4s %3s %?s %a\n", addr, "omni", "-", "-", id.cyi_omni_hdlr.cyo_online); mdb_printf("%?s |\n", ""); mdb_printf("%?s +-->%4s %3s %?s %s\n", "", "CPU", "NDX", "CYCLIC", "HANDLER"); if (mdb_pwalk("cycomni", (mdb_walk_cb_t)cycid_walk_omni, NULL, addr) == -1) { mdb_warn("couldn't walk cycomni for %p", addr); return (DCMD_ERR); } mdb_printf("\n"); return (DCMD_OK); } mdb_printf("%?p ", addr); return (cycid_cpu(id.cyi_cpu, id.cyi_ndx)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_CYCLIC_H #define _MDB_CYCLIC_H #include #ifdef __cplusplus extern "C" { #endif extern int cyccpu_walk_init(mdb_walk_state_t *); extern int cyccpu_walk_step(mdb_walk_state_t *); extern int cycomni_walk_init(mdb_walk_state_t *); extern int cycomni_walk_step(mdb_walk_state_t *); extern int cyctrace_walk_init(mdb_walk_state_t *); extern int cyctrace_walk_step(mdb_walk_state_t *); extern void cyctrace_walk_fini(mdb_walk_state_t *); extern int cycid(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cycinfo(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cyclic(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cyctrace(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cyccover(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_CYCLIC_H */ /* * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include "damap.h" void damap_help(void) { mdb_printf("Print the damap at the address given.\n"); mdb_printf("\n"); mdb_printf("EXAMPLE: SCSI: To display the SCSI tgtmap damaps "); mdb_printf("associated with a scsi HBA driver iport dip:\n"); mdb_printf("\n"); mdb_printf("::devbindings -q \n"); mdb_printf("\n"); mdb_printf("::print struct dev_info devi_driver_data|"); mdb_printf("::print scsi_hba_tran_t tran_tgtmap|"); mdb_printf("::print impl_scsi_tgtmap_t "); mdb_printf("tgtmap_dam[0] tgtmap_dam[1]|::damap\n"); } static char * local_strdup(const char *s) { if (s) return (strcpy(mdb_alloc(strlen(s) + 1, UM_SLEEP), s)); else return (NULL); } static void local_strfree(const char *s) { if (s) mdb_free((void *)s, strlen(s) + 1); } static void bitset_free(bitset_t *bs, int embedded) { if (bs == NULL) return; if (bs->bs_set && bs->bs_words) mdb_free(bs->bs_set, bs->bs_words * sizeof (ulong_t)); if (!embedded) mdb_free(bs, sizeof (*bs)); /* not embedded, free */ } static bitset_t * bitset_get(uintptr_t bsaddr) { bitset_t *bs; bs = mdb_zalloc(sizeof (*bs), UM_SLEEP); if (mdb_vread(bs, sizeof (*bs), bsaddr) == -1) { mdb_warn("couldn't read bitset 0x%p", bsaddr); bitset_free(bs, 0); return (NULL); } bsaddr = (uintptr_t)bs->bs_set; bs->bs_set = mdb_alloc(bs->bs_words * sizeof (ulong_t), UM_SLEEP); if (mdb_vread(bs->bs_set, bs->bs_words * sizeof (ulong_t), bsaddr) == -1) { mdb_warn("couldn't read bitset bs_set 0x%p", bsaddr); bitset_free(bs, 0); return (NULL); } return (bs); } static void damap_free(struct dam *dam, void **kdamda, int kdamda_n) { int i; struct i_ddi_soft_state *ss; dam_da_t *da; if (dam) { /* free in dam_da_t softstate */ ss = (struct i_ddi_soft_state *)dam->dam_da; if (ss) { if (ss->n_items && ss->array) { for (i = 0; i < ss->n_items; i++) { da = ss->array[i]; if (da == NULL) continue; local_strfree(da->da_addr); mdb_free(da, sizeof (*da)); } } mdb_free(ss, sizeof (*ss)); } /* free dam_active/stable/report_set embedded in dam */ bitset_free(&dam->dam_report_set, 1); bitset_free(&dam->dam_stable_set, 1); bitset_free(&dam->dam_active_set, 1); /* free dam_name */ local_strfree(dam->dam_name); /* free dam */ mdb_free(dam, sizeof (*dam)); } if (kdamda) mdb_free(kdamda, kdamda_n * sizeof (void *)); } /* * The dam implementation uses a number of different abstractions. Given a * pointer to a damap_t, this function make an mdb instantiation of the dam - * many, but not all, of the different abstractions used in the dam * implementation are also instantiated in mdb. This means that callers of * damap_get can perform some (but not all) types of structure pointer * traversals. */ struct dam * damap_get(uintptr_t damaddr, void ***pkdamda, int *pkdamda_n) { /* variables that hold instantiation read from kernel */ struct dam kdam; char kstring[MAXPATHLEN]; struct i_ddi_soft_state kss; void **kssarray = NULL; int array_sz = 0; /* variables that hold mdb instantiation */ struct dam *dam = NULL; struct i_ddi_soft_state *ss; bitset_t *bs; dam_da_t *da; int i; /* read kernel: dam */ if (mdb_vread(&kdam, sizeof (kdam), damaddr) == -1) { mdb_warn("couldn't read dam 0x%p", damaddr); goto err; } /* read kernel: dam->dam_name */ mdb_readstr(kstring, sizeof (kstring), (uintptr_t)kdam.dam_name); /* read kernel: dam->dam_da (softstate) */ if (mdb_vread(&kss, sizeof (kss), (uintptr_t)kdam.dam_da) == -1) { mdb_warn("couldn't read dam dam_da 0x%p", (uintptr_t)kdam.dam_da); goto err; } /* read kernel ((struct i_ddi_soft_state *)dam->dam_da)->array */ array_sz = kss.n_items * sizeof (void *); kssarray = mdb_alloc(array_sz, UM_SLEEP); if (mdb_vread(kssarray, array_sz, (uintptr_t)kss.array) == -1) { mdb_warn("couldn't read dam dam_da array 0x%p", (uintptr_t)kss.array); goto err; } /* * Produce mdb instantiation of kernel data structures. * * Structure copy kdam to dam, then clear out pointers in dam (some * will be filled in by mdb instantiation code below). */ dam = mdb_zalloc(sizeof (*dam), UM_SLEEP); *dam = kdam; dam->dam_name = NULL; dam->dam_active_set.bs_set = NULL; dam->dam_stable_set.bs_set = NULL; dam->dam_report_set.bs_set = NULL; dam->dam_da = NULL; /* dam_addr_hash, dam_taskqp, dam_kstatp left as kernel addresses */ /* fill in dam_name */ dam->dam_name = local_strdup(kstring); /* fill in dam_active/stable/report_set embedded in the dam */ bs = bitset_get(damaddr + (offsetof(struct dam, dam_active_set))); if (bs) { dam->dam_active_set = *bs; mdb_free(bs, sizeof (*bs)); } bs = bitset_get(damaddr + (offsetof(struct dam, dam_stable_set))); if (bs) { dam->dam_stable_set = *bs; mdb_free(bs, sizeof (*bs)); } bs = bitset_get(damaddr + (offsetof(struct dam, dam_report_set))); if (bs) { dam->dam_report_set = *bs; mdb_free(bs, sizeof (*bs)); } /* fill in dam_da_t softstate */ ss = mdb_zalloc(sizeof (struct i_ddi_soft_state), UM_SLEEP); *ss = kss; ss->next = NULL; ss->array = mdb_zalloc(array_sz, UM_SLEEP); dam->dam_da = ss; for (i = 0; i < kss.n_items; i++) { if (kssarray[i] == NULL) continue; da = ss->array[i] = mdb_zalloc(sizeof (*da), UM_SLEEP); if (mdb_vread(da, sizeof (*da), (uintptr_t)kssarray[i]) == -1) { mdb_warn("couldn't read dam dam_da %d 0x%p", i, (uintptr_t)kss.array); goto err; } /* da_nvl, da_ppriv_rpt, da_nvl_rpt left as kernel addresses */ /* read kernel: da->da_addr */ mdb_readstr(kstring, sizeof (kstring), (uintptr_t)da->da_addr); da->da_addr = local_strdup(kstring); } /* return array of kernel dam_da_t pointers associated with each id */ *pkdamda = kssarray; *pkdamda_n = array_sz / sizeof (void *); /* return pointer to mdb instantiation of the dam */ return (dam); err: damap_free(dam, kssarray, array_sz / sizeof (void *)); *pkdamda = NULL; *pkdamda_n = 0; return (NULL); } /*ARGSUSED*/ static void damap_print(struct dam *dam, void **kdamda, int kdamda_n) { struct i_ddi_soft_state *ss; dam_da_t *da; int i; mdb_printf("%s:\n", dam->dam_name); ss = (struct i_ddi_soft_state *)dam->dam_da; if (ss == NULL) return; if ((ss->n_items == 0) || (ss->array == NULL)) return; mdb_printf(" #: %-20s [ASR] ref config-private provider-private\n", "address"); for (i = 0; i < ss->n_items; i++) { da = ss->array[i]; if (da == NULL) continue; /* Print index and address. */ mdb_printf(" %3d: %-20s [", i, da->da_addr); /* Print shorthand of Active/Stable/Report set membership */ if (BT_TEST(dam->dam_active_set.bs_set, i)) mdb_printf("A"); else mdb_printf("."); if (BT_TEST(dam->dam_stable_set.bs_set, i)) mdb_printf("S"); else mdb_printf("."); if (BT_TEST(dam->dam_report_set.bs_set, i)) mdb_printf("R"); else mdb_printf("."); /* Print the reference count and priv */ mdb_printf("] %-3d %0?lx %0?lx\n", da->da_ref, da->da_cfg_priv, da->da_ppriv); mdb_printf(" %p::print -ta dam_da_t\n", kdamda[i]); } } /*ARGSUSED*/ int damap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct dam *dam; void **kdamda; int kdamda_n; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_ERR); } dam = damap_get(addr, &kdamda, &kdamda_n); if (dam == NULL) return (DCMD_ERR); damap_print(dam, kdamda, kdamda_n); damap_free(dam, kdamda, kdamda_n); return (DCMD_OK); } /* * 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. */ #ifndef _MDB_DAMAP_H #define _MDB_DAMAP_H #ifdef __cplusplus extern "C" { #endif #include extern int damap(uintptr_t, uint_t, int, const mdb_arg_t *); extern void damap_help(void); #ifdef __cplusplus } #endif #endif /* _MDB_DAMAP_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ #include "ddi_periodic.h" #include #include #include #include /*ARGSUSED*/ int dprinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char prflags[4]; ddi_periodic_impl_t dpr; boolean_t verbose = B_FALSE; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("ddi_periodic", "ddi_periodic", argc, argv) == -1) { mdb_warn("cannot walk 'ddi_periodic'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, B_TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&dpr, sizeof (dpr), addr) == -1) { mdb_warn("could not read ddi_periodic_impl_t at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%16s %4s %3s %5s %5s %12s %s\n", "ADDR", "ID", "LVL", "FLAGS", "MS", "FIRE_COUNT", "HANDLER"); if (verbose) { mdb_printf("%16s %16s %16s %s\n", "", "THREAD", "CYCLIC_ID", "ARGUMENT"); } } prflags[0] = dpr.dpr_flags & DPF_DISPATCHED ? 'D' : '-'; prflags[1] = dpr.dpr_flags & DPF_EXECUTING ? 'X' : '-'; prflags[2] = dpr.dpr_flags & DPF_CANCELLED ? 'C' : '-'; prflags[3] = '\0'; mdb_printf("%16p %4x %3d %5s %5d %12x %a\n", addr, dpr.dpr_id, dpr.dpr_level, prflags, (int)(dpr.dpr_interval / 1000000), dpr.dpr_fire_count, dpr.dpr_handler); if (verbose) { mdb_printf("%16s %16p %16p %a\n", "", dpr.dpr_thread, dpr.dpr_cyclic_id, dpr.dpr_arg); } return (DCMD_OK); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ #ifndef _MDB_DDI_PERIODIC_H #define _MDB_DDI_PERIODIC_H #include #ifdef __cplusplus extern "C" { #endif extern int dprinfo(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_DDI_PERIODIC_H */ /* * 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 (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019, Joyent, Inc. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nvpair.h" #include "pci.h" #include "devinfo.h" #define DEVINFO_TREE_INDENT 4 /* Indent for devs one down in tree */ #define DEVINFO_PROP_INDENT 4 /* Indent for properties */ #define DEVINFO_PROPLIST_INDENT 8 /* Indent for properties lists */ /* * devinfo node state map. Used by devinfo() and devinfo_audit(). * Long words are deliberately truncated so that output * fits in 80 column with 64-bit addresses. */ static const char *const di_state[] = { "DS_INVAL", "DS_PROTO", "DS_LINKED", "DS_BOUND", "DS_INITIA", "DS_PROBED", "DS_ATTACH", "DS_READY", "?" }; #define DI_STATE_MAX ((sizeof (di_state) / sizeof (char *)) - 1) void prtconf_help(void) { mdb_printf("Prints the devinfo tree from a given node.\n" "Without the address of a \"struct devinfo\" given, " "prints from the root;\n" "with an address, prints the parents of, " "and all children of, that address.\n\n" "Switches:\n" " -v be verbose - print device property lists\n" " -p only print the ancestors of the given node\n" " -c only print the children of the given node\n" " -d driver only print instances of driver\n" " -i inst only print if the driver instance number is inst\n"); } void devinfo_help(void) { mdb_printf("Switches:\n" " -b type print bus of device if it matches type\n" " -d print device private data\n" " -q be quiet - don't print device property lists\n" " -s print summary of dev_info structures\n" "\n" "The following types are supported for -b:\n" "\n" " * pcie print the PCI Express bus (pcie_bus_t)\n"); } /* * Devinfo walker. */ typedef struct { /* * The "struct dev_info" must be the first thing in this structure. */ struct dev_info din_dev; /* * This is for the benefit of prtconf(). */ int din_depth; } devinfo_node_t; typedef struct devinfo_parents_walk_data { devinfo_node_t dip_node; #define dip_dev dip_node.din_dev #define dip_depth dip_node.din_depth struct dev_info *dip_end; /* * The following three elements are for walking the parents of a node: * "dip_base_depth" is the depth of the given node from the root. * This starts at 1 (if we're walking devinfo_root), because * it's the size of the dip_parent_{nodes,addresses} arrays, * and has to include the given node. * "dip_parent_nodes" is a collection of the parent node structures, * already read in via mdb_vread(). dip_parent_nodes[0] is the * root, dip_parent_nodes[1] is a child of the root, etc. * "dip_parent_addresses" holds the vaddrs of all the parent nodes. */ int dip_base_depth; devinfo_node_t *dip_parent_nodes; uintptr_t *dip_parent_addresses; } devinfo_parents_walk_data_t; int devinfo_parents_walk_init(mdb_walk_state_t *wsp) { devinfo_parents_walk_data_t *dip; uintptr_t addr; uintptr_t devinfo_root; /* Address of root of devinfo tree */ int i; if (mdb_readvar(&devinfo_root, "top_devinfo") == -1) { mdb_warn("failed to read 'top_devinfo'"); return (0); } if (wsp->walk_addr == 0) wsp->walk_addr = devinfo_root; addr = wsp->walk_addr; dip = mdb_alloc(sizeof (devinfo_parents_walk_data_t), UM_SLEEP); wsp->walk_data = dip; dip->dip_end = (struct dev_info *)wsp->walk_addr; dip->dip_depth = 0; dip->dip_base_depth = 1; do { if (mdb_vread(&dip->dip_dev, sizeof (dip->dip_dev), addr) == -1) { mdb_warn("failed to read devinfo at %p", addr); mdb_free(dip, sizeof (devinfo_parents_walk_data_t)); wsp->walk_data = NULL; return (WALK_ERR); } addr = (uintptr_t)dip->dip_dev.devi_parent; if (addr != 0) dip->dip_base_depth++; } while (addr != 0); addr = wsp->walk_addr; dip->dip_parent_nodes = mdb_alloc( dip->dip_base_depth * sizeof (devinfo_node_t), UM_SLEEP); dip->dip_parent_addresses = mdb_alloc( dip->dip_base_depth * sizeof (uintptr_t), UM_SLEEP); for (i = dip->dip_base_depth - 1; i >= 0; i--) { if (mdb_vread(&dip->dip_parent_nodes[i].din_dev, sizeof (struct dev_info), addr) == -1) { mdb_warn("failed to read devinfo at %p", addr); return (WALK_ERR); } dip->dip_parent_nodes[i].din_depth = i; dip->dip_parent_addresses[i] = addr; addr = (uintptr_t) dip->dip_parent_nodes[i].din_dev.devi_parent; } return (WALK_NEXT); } int devinfo_parents_walk_step(mdb_walk_state_t *wsp) { devinfo_parents_walk_data_t *dip = wsp->walk_data; int status; if (dip->dip_depth == dip->dip_base_depth) return (WALK_DONE); status = wsp->walk_callback( dip->dip_parent_addresses[dip->dip_depth], &dip->dip_parent_nodes[dip->dip_depth], wsp->walk_cbdata); dip->dip_depth++; return (status); } void devinfo_parents_walk_fini(mdb_walk_state_t *wsp) { devinfo_parents_walk_data_t *dip = wsp->walk_data; mdb_free(dip->dip_parent_nodes, dip->dip_base_depth * sizeof (devinfo_node_t)); mdb_free(dip->dip_parent_addresses, dip->dip_base_depth * sizeof (uintptr_t)); mdb_free(wsp->walk_data, sizeof (devinfo_parents_walk_data_t)); } typedef struct devinfo_children_walk_data { devinfo_node_t dic_node; #define dic_dev dic_node.din_dev #define dic_depth dic_node.din_depth struct dev_info *dic_end; int dic_print_first_node; } devinfo_children_walk_data_t; int devinfo_children_walk_init(mdb_walk_state_t *wsp) { devinfo_children_walk_data_t *dic; uintptr_t devinfo_root; /* Address of root of devinfo tree */ if (mdb_readvar(&devinfo_root, "top_devinfo") == -1) { mdb_warn("failed to read 'top_devinfo'"); return (0); } if (wsp->walk_addr == 0) wsp->walk_addr = devinfo_root; dic = mdb_alloc(sizeof (devinfo_children_walk_data_t), UM_SLEEP); wsp->walk_data = dic; dic->dic_end = (struct dev_info *)wsp->walk_addr; /* * This could be set by devinfo_walk_init(). */ if (wsp->walk_arg != NULL) { dic->dic_depth = (*(int *)wsp->walk_arg - 1); dic->dic_print_first_node = 0; } else { dic->dic_depth = 0; dic->dic_print_first_node = 1; } return (WALK_NEXT); } int devinfo_children_walk_step(mdb_walk_state_t *wsp) { devinfo_children_walk_data_t *dic = wsp->walk_data; struct dev_info *v; devinfo_node_t *cur; uintptr_t addr = wsp->walk_addr; int status = WALK_NEXT; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&dic->dic_dev, sizeof (dic->dic_dev), addr) == -1) { mdb_warn("failed to read devinfo at %p", addr); return (WALK_DONE); } cur = &dic->dic_node; if (dic->dic_print_first_node == 0) dic->dic_print_first_node = 1; else status = wsp->walk_callback(addr, cur, wsp->walk_cbdata); /* * "v" is always a virtual address pointer, * i.e. can't be deref'ed. */ v = (struct dev_info *)addr; if (dic->dic_dev.devi_child != NULL) { v = dic->dic_dev.devi_child; dic->dic_depth++; } else if (dic->dic_dev.devi_sibling != NULL && v != dic->dic_end) { v = dic->dic_dev.devi_sibling; } else { while (v != NULL && v != dic->dic_end && dic->dic_dev.devi_sibling == NULL) { v = dic->dic_dev.devi_parent; if (v == NULL) break; mdb_vread(&dic->dic_dev, sizeof (struct dev_info), (uintptr_t)v); dic->dic_depth--; } if (v != NULL && v != dic->dic_end) v = dic->dic_dev.devi_sibling; if (v == dic->dic_end) v = NULL; /* Done */ } wsp->walk_addr = (uintptr_t)v; return (status); } void devinfo_children_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (devinfo_children_walk_data_t)); } typedef struct devinfo_walk_data { mdb_walk_state_t diw_parent, diw_child; enum { DIW_PARENT, DIW_CHILD, DIW_DONE } diw_mode; } devinfo_walk_data_t; int devinfo_walk_init(mdb_walk_state_t *wsp) { devinfo_walk_data_t *diw; devinfo_parents_walk_data_t *dip; diw = mdb_alloc(sizeof (devinfo_walk_data_t), UM_SLEEP); diw->diw_parent = *wsp; diw->diw_child = *wsp; wsp->walk_data = diw; diw->diw_mode = DIW_PARENT; if (devinfo_parents_walk_init(&diw->diw_parent) == -1) { mdb_free(diw, sizeof (devinfo_walk_data_t)); return (WALK_ERR); } /* * This is why the "devinfo" walker needs to be marginally * complicated - the child walker needs this initialization * data, and the best way to get it is out of the parent walker. */ dip = diw->diw_parent.walk_data; diw->diw_child.walk_arg = &dip->dip_base_depth; if (devinfo_children_walk_init(&diw->diw_child) == -1) { devinfo_parents_walk_fini(&diw->diw_parent); mdb_free(diw, sizeof (devinfo_walk_data_t)); return (WALK_ERR); } return (WALK_NEXT); } int devinfo_walk_step(mdb_walk_state_t *wsp) { devinfo_walk_data_t *diw = wsp->walk_data; int status = WALK_NEXT; if (diw->diw_mode == DIW_PARENT) { status = devinfo_parents_walk_step(&diw->diw_parent); if (status != WALK_NEXT) { /* * Keep on going even if the parents walk hit an error. */ diw->diw_mode = DIW_CHILD; status = WALK_NEXT; } } else if (diw->diw_mode == DIW_CHILD) { status = devinfo_children_walk_step(&diw->diw_child); if (status != WALK_NEXT) { diw->diw_mode = DIW_DONE; status = WALK_DONE; } } else status = WALK_DONE; return (status); } void devinfo_walk_fini(mdb_walk_state_t *wsp) { devinfo_walk_data_t *diw = wsp->walk_data; devinfo_children_walk_fini(&diw->diw_child); devinfo_parents_walk_fini(&diw->diw_parent); mdb_free(diw, sizeof (devinfo_walk_data_t)); } /* * Given a devinfo pointer, figure out which driver is associated * with the node (by driver name, from the devnames array). */ /*ARGSUSED*/ int devinfo2driver(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char dname[MODMAXNAMELEN]; struct dev_info devi; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&devi, sizeof (devi), addr) == -1) { mdb_warn("failed to read devinfo struct at %p", addr); return (DCMD_ERR); } if (devi.devi_node_state < DS_ATTACHED) { /* No driver attached to this devinfo - nothing to do. */ mdb_warn("%p: No driver attached to this devinfo node\n", addr); return (DCMD_ERR); } if (mdb_devinfo2driver(addr, dname, sizeof (dname)) != 0) { mdb_warn("failed to determine driver name"); return (DCMD_ERR); } mdb_printf("Driver '%s' is associated with devinfo %p.\n", dname, addr); return (DCMD_OK); } typedef struct devnames_walk { struct devnames *dnw_names; int dnw_ndx; int dnw_devcnt; uintptr_t dnw_base; uintptr_t dnw_size; } devnames_walk_t; int devnames_walk_init(mdb_walk_state_t *wsp) { devnames_walk_t *dnw; int devcnt; uintptr_t devnamesp; if (wsp->walk_addr != 0) { mdb_warn("devnames walker only supports global walks\n"); return (WALK_ERR); } if (mdb_readvar(&devcnt, "devcnt") == -1) { mdb_warn("failed to read 'devcnt'"); return (WALK_ERR); } if (mdb_readvar(&devnamesp, "devnamesp") == -1) { mdb_warn("failed to read 'devnamesp'"); return (WALK_ERR); } dnw = mdb_zalloc(sizeof (devnames_walk_t), UM_SLEEP); dnw->dnw_size = sizeof (struct devnames) * devcnt; dnw->dnw_devcnt = devcnt; dnw->dnw_base = devnamesp; dnw->dnw_names = mdb_alloc(dnw->dnw_size, UM_SLEEP); if (mdb_vread(dnw->dnw_names, dnw->dnw_size, dnw->dnw_base) == -1) { mdb_warn("couldn't read devnames array at %p", devnamesp); return (WALK_ERR); } wsp->walk_data = dnw; return (WALK_NEXT); } int devnames_walk_step(mdb_walk_state_t *wsp) { devnames_walk_t *dnw = wsp->walk_data; int status; if (dnw->dnw_ndx == dnw->dnw_devcnt) return (WALK_DONE); status = wsp->walk_callback(dnw->dnw_ndx * sizeof (struct devnames) + dnw->dnw_base, &dnw->dnw_names[dnw->dnw_ndx], wsp->walk_cbdata); dnw->dnw_ndx++; return (status); } void devnames_walk_fini(mdb_walk_state_t *wsp) { devnames_walk_t *dnw = wsp->walk_data; mdb_free(dnw->dnw_names, dnw->dnw_size); mdb_free(dnw, sizeof (devnames_walk_t)); } int devinfo_siblings_walk_init(mdb_walk_state_t *wsp) { struct dev_info di; uintptr_t addr = wsp->walk_addr; if (addr == 0) { mdb_warn("a dev_info struct address must be provided\n"); return (WALK_ERR); } if (mdb_vread(&di, sizeof (di), addr) == -1) { mdb_warn("failed to read dev_info struct at %p", addr); return (WALK_ERR); } if (di.devi_parent == NULL) { mdb_warn("no parent for devinfo at %p", addr); return (WALK_DONE); } if (mdb_vread(&di, sizeof (di), (uintptr_t)di.devi_parent) == -1) { mdb_warn("failed to read parent dev_info struct at %p", (uintptr_t)di.devi_parent); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)di.devi_child; return (WALK_NEXT); } int devinfo_siblings_walk_step(mdb_walk_state_t *wsp) { struct dev_info di; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&di, sizeof (di), addr) == -1) { mdb_warn("failed to read dev_info struct at %p", addr); return (WALK_DONE); } wsp->walk_addr = (uintptr_t)di.devi_sibling; return (wsp->walk_callback(addr, &di, wsp->walk_cbdata)); } int devi_next_walk_step(mdb_walk_state_t *wsp) { struct dev_info di; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&di, sizeof (di), wsp->walk_addr) == -1) return (WALK_DONE); status = wsp->walk_callback(wsp->walk_addr, &di, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)di.devi_next; return (status); } /* * Helper functions. */ static int is_printable_string(unsigned char *prop_value) { while (*prop_value != 0) if (!isprint(*prop_value++)) return (0); return (1); } static void devinfo_print_props_type(int type) { char *type_str = NULL; switch (type) { case DDI_PROP_TYPE_ANY: type_str = "any"; break; case DDI_PROP_TYPE_COMPOSITE: type_str = "composite"; break; case DDI_PROP_TYPE_INT64: type_str = "int64"; break; case DDI_PROP_TYPE_INT: type_str = "int"; break; case DDI_PROP_TYPE_BYTE: type_str = "byte"; break; case DDI_PROP_TYPE_STRING: type_str = "string"; break; } if (type_str != NULL) mdb_printf("type=%s", type_str); else mdb_printf("type=0x%x", type); } static void devinfo_print_props_value(int elem_size, int nelem, unsigned char *prop_value, int prop_value_len) { int i; mdb_printf("value="); if (elem_size == 0) { /* if elem_size == 0, then we are printing out string(s) */ char *p = (char *)prop_value; for (i = 0; i < nelem - 1; i++) { mdb_printf("'%s' + ", p); p += strlen(p) + 1; } mdb_printf("'%s'", p); } else { /* * if elem_size != 0 then we are printing out an array * where each element is of elem_size */ mdb_nhconvert(prop_value, prop_value, elem_size); mdb_printf("%02x", *prop_value); for (i = 1; i < prop_value_len; i++) { if ((i % elem_size) == 0) { mdb_nhconvert(&prop_value[i], &prop_value[i], elem_size); mdb_printf("."); } mdb_printf("%02x", prop_value[i]); } } } /* * devinfo_print_props_guess() * Guesses how to interpret the value of the property * * Params: * type - Should be the type value of the property * prop_val - Pointer to the property value data buffer * prop_len - Length of the property value data buffer * * Return values: * nelem - The number of elements stored in the property value * data buffer pointed to by prop_val. * elem_size - The size (in bytes) of the elements stored in the property * value data buffer pointed to by prop_val. * Upon return if elem_size == 0 and nelem != 0 then * the property value data buffer contains strings * len_err - There was an error with the length of the data buffer. * Its size is not a multiple of the array value type. * It will be interpreted as an array of bytes. */ static void devinfo_print_props_guess(int type, unsigned char *prop_val, int prop_len, int *elem_size, int *nelem, int *len_err) { *len_err = 0; if (prop_len == 0) { *elem_size = 0; *nelem = 0; return; } /* by default, assume an array of bytes */ *elem_size = 1; *nelem = prop_len; switch (type) { case DDI_PROP_TYPE_BYTE: /* default case, that was easy */ break; case DDI_PROP_TYPE_INT64: if ((prop_len % sizeof (int64_t)) == 0) { *elem_size = sizeof (int64_t); *nelem = prop_len / *elem_size; } else { /* array is not a multiple of type size, error */ *len_err = 1; } break; case DDI_PROP_TYPE_INT: if ((prop_len % sizeof (int)) == 0) { *elem_size = sizeof (int); *nelem = prop_len / *elem_size; } else { /* array is not a multiple of type size, error */ *len_err = 1; } break; case DDI_PROP_TYPE_STRING: case DDI_PROP_TYPE_COMPOSITE: case DDI_PROP_TYPE_ANY: default: /* * if we made it here the type is either unknown * or a string. Try to interpret is as a string * and if that fails assume an array of bytes. */ if (prop_val[prop_len - 1] == '\0') { unsigned char *s = prop_val; int i; /* assume an array of strings */ *elem_size = 0; *nelem = 0; for (i = 0; i < prop_len; i++) { if (prop_val[i] != '\0') continue; /* * If the property is typed as a string * property, then interpret empty strings * as strings. Otherwise default to an * array of bytes. If there are unprintable * characters, always default to an array of * bytes. */ if ((*s == '\0' && type != DDI_PROP_TYPE_STRING) || !is_printable_string(s)) { *elem_size = 1; *nelem = prop_len; break; } (*nelem)++; s = &prop_val[i + 1]; } } break; } } static void devinfo_print_props(char *name, ddi_prop_t *p) { if (p == NULL) return; if (name != NULL) mdb_printf("%s ", name); mdb_printf("properties at %p:\n", p); mdb_inc_indent(DEVINFO_PROP_INDENT); while (p != NULL) { ddi_prop_t prop; char prop_name[128]; unsigned char *prop_value; int type, elem_size, nelem, prop_len_error; /* read in the property struct */ if (mdb_vread(&prop, sizeof (prop), (uintptr_t)p) == -1) { mdb_warn("could not read property at 0x%p", p); break; } /* print the property name */ if (mdb_readstr(prop_name, sizeof (prop_name), (uintptr_t)prop.prop_name) == -1) { mdb_warn("could not read property name at 0x%p", prop.prop_name); goto next; } mdb_printf("name='%s' ", prop_name); /* get the property type and print it out */ type = (prop.prop_flags & DDI_PROP_TYPE_MASK); devinfo_print_props_type(type); /* get the property value */ if (prop.prop_len > 0) { prop_value = mdb_alloc(prop.prop_len, UM_SLEEP|UM_GC); if (mdb_vread(prop_value, prop.prop_len, (uintptr_t)prop.prop_val) == -1) { mdb_warn("could not read property value at " "0x%p", prop.prop_val); goto next; } } else { prop_value = NULL; } /* take a guess at interpreting the property value */ devinfo_print_props_guess(type, prop_value, prop.prop_len, &elem_size, &nelem, &prop_len_error); /* print out the number ot items */ mdb_printf(" items=%d", nelem); /* print out any associated device information */ if (prop.prop_dev != DDI_DEV_T_NONE) { mdb_printf(" dev="); if (prop.prop_dev == DDI_DEV_T_ANY) mdb_printf("any"); else if (prop.prop_dev == DDI_MAJOR_T_UNKNOWN) mdb_printf("unknown"); else mdb_printf("(%u,%u)", getmajor(prop.prop_dev), getminor(prop.prop_dev)); } /* print out the property value */ if (prop_value != NULL) { mdb_printf("\n"); mdb_inc_indent(DEVINFO_PROP_INDENT); if (prop_len_error) mdb_printf("NOTE: prop length is not a " "multiple of element size\n"); devinfo_print_props_value(elem_size, nelem, prop_value, prop.prop_len); mdb_dec_indent(DEVINFO_PROP_INDENT); } next: mdb_printf("\n"); p = prop.prop_next; } mdb_dec_indent(DEVINFO_PROP_INDENT); } static void devinfo_pathinfo_state(mdi_pathinfo_state_t state) { char *type_str = NULL; switch (state) { case MDI_PATHINFO_STATE_INIT: type_str = "init"; break; case MDI_PATHINFO_STATE_ONLINE: type_str = "online"; break; case MDI_PATHINFO_STATE_STANDBY: type_str = "standby"; break; case MDI_PATHINFO_STATE_FAULT: type_str = "fault"; break; case MDI_PATHINFO_STATE_OFFLINE: type_str = "offline"; break; } if (type_str != NULL) mdb_printf("state=%s\n", type_str); else mdb_printf("state=0x%x\n", state); } static void devinfo_print_pathing(int mdi_component, void *mdi_client) { mdi_client_t mdi_c; struct mdi_pathinfo *pip; /* we only print out multipathing info for client nodes */ if ((mdi_component & MDI_COMPONENT_CLIENT) == 0) return; mdb_printf("Client multipath info at: 0x%p\n", mdi_client); mdb_inc_indent(DEVINFO_PROP_INDENT); /* read in the client multipathing info */ if (mdb_readstr((void*) &mdi_c, sizeof (mdi_c), (uintptr_t)mdi_client) == -1) { mdb_warn("failed to read mdi_client at %p", (uintptr_t)mdi_client); goto exit; } /* * walk through the clients list of pathinfo structures and print * out the properties for each path */ pip = (struct mdi_pathinfo *)mdi_c.ct_path_head; while (pip != NULL) { char binding_name[128]; struct mdi_pathinfo pi; mdi_phci_t ph; struct dev_info ph_di; /* read in the pathinfo structure */ if (mdb_vread((void*)&pi, sizeof (pi), (uintptr_t)pip) == -1) { mdb_warn("failed to read mdi_pathinfo at %p", (uintptr_t)pip); goto exit; } /* read in the pchi (path host adapter) info */ if (mdb_vread((void*)&ph, sizeof (ph), (uintptr_t)pi.pi_phci) == -1) { mdb_warn("failed to read mdi_pchi at %p", (uintptr_t)pi.pi_phci); goto exit; } /* read in the dip of the phci so we can get it's name */ if (mdb_vread((void*)&ph_di, sizeof (ph_di), (uintptr_t)ph.ph_dip) == -1) { mdb_warn("failed to read mdi_pchi at %p", (uintptr_t)ph.ph_dip); goto exit; } if (mdb_vread(binding_name, sizeof (binding_name), (uintptr_t)ph_di.devi_binding_name) == -1) { mdb_warn("failed to read binding_name at %p", (uintptr_t)ph_di.devi_binding_name); goto exit; } mdb_printf("%s#%d, ", binding_name, ph_di.devi_instance); devinfo_pathinfo_state(pi.pi_state); /* print out the pathing info */ mdb_inc_indent(DEVINFO_PROP_INDENT); if (mdb_pwalk_dcmd(NVPAIR_WALKER_FQNAME, NVPAIR_DCMD_FQNAME, 0, NULL, (uintptr_t)pi.pi_prop) != 0) { mdb_dec_indent(DEVINFO_PROP_INDENT); goto exit; } mdb_dec_indent(DEVINFO_PROP_INDENT); pip = pi.pi_client_link; } exit: mdb_dec_indent(DEVINFO_PROP_INDENT); } static int devinfo_print(uintptr_t addr, struct dev_info *dev, devinfo_cb_data_t *data) { /* * We know the walker passes us extra data after the dev_info. */ char binding_name[128]; char dname[MODMAXNAMELEN]; devinfo_node_t *din = (devinfo_node_t *)dev; ddi_prop_t *global_props = NULL; boolean_t hdname = B_FALSE; if (mdb_readstr(binding_name, sizeof (binding_name), (uintptr_t)dev->devi_binding_name) == -1) { mdb_warn("failed to read binding_name at %p", (uintptr_t)dev->devi_binding_name); return (WALK_ERR); } /* if there are any global properties, get a pointer to them */ if (dev->devi_global_prop_list != NULL) { ddi_prop_list_t plist; if (mdb_vread((void*)&plist, sizeof (plist), (uintptr_t)dev->devi_global_prop_list) == -1) { mdb_warn("failed to read global prop_list at %p", (uintptr_t)dev->devi_global_prop_list); return (WALK_ERR); } global_props = plist.prop_list; } if (dev->devi_node_state > DS_ATTACHED) { if (mdb_devinfo2driver(addr, dname, sizeof (dname)) == 0) hdname = B_TRUE; } /* * If a filter is installed and we don't have the driver's name, we * always skip it. Also if the filter doesn't match, then we'll also * skip the driver. */ if (data->di_filter != NULL && (!hdname || strcmp(data->di_filter, dname) != 0)) { return (WALK_NEXT); } if (data->di_instance != UINT64_MAX && data->di_instance != (uint64_t)dev->devi_instance) { return (WALK_NEXT); } /* * If we are output to a pipe, we only print the address of the * devinfo_t. */ if (data->di_flags & DEVINFO_PIPE) { mdb_printf("%-0?p\n", addr); return (WALK_NEXT); } mdb_inc_indent(din->din_depth * DEVINFO_TREE_INDENT); if ((addr == data->di_base) || (data->di_flags & DEVINFO_ALLBOLD)) mdb_printf("%"); mdb_printf("%-0?p %s", addr, binding_name); if ((addr == data->di_base) || (data->di_flags & DEVINFO_ALLBOLD)) mdb_printf("%"); if (dev->devi_instance >= 0) mdb_printf(", instance #%d", dev->devi_instance); if (dev->devi_node_state < DS_ATTACHED) mdb_printf(" (driver not attached)"); else if (hdname == B_FALSE) mdb_printf(" (could not determine driver name)"); else mdb_printf(" (driver name: %s)", dname); mdb_printf("\n"); if (data->di_flags & DEVINFO_VERBOSE) { mdb_inc_indent(DEVINFO_PROPLIST_INDENT); devinfo_print_props("System", dev->devi_sys_prop_ptr); devinfo_print_props("Driver", dev->devi_drv_prop_ptr); devinfo_print_props("Hardware", dev->devi_hw_prop_ptr); devinfo_print_props("Global", global_props); devinfo_print_pathing(dev->devi_mdi_component, dev->devi_mdi_client); mdb_dec_indent(DEVINFO_PROPLIST_INDENT); } mdb_dec_indent(din->din_depth * DEVINFO_TREE_INDENT); return (WALK_NEXT); } /*ARGSUSED*/ int prtconf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { devinfo_cb_data_t data; uintptr_t devinfo_root; /* Address of root of devinfo tree */ int status; data.di_flags = DEVINFO_PARENT | DEVINFO_CHILD; data.di_filter = NULL; data.di_instance = UINT64_MAX; if (flags & DCMD_PIPE_OUT) data.di_flags |= DEVINFO_PIPE; if (mdb_getopts(argc, argv, 'd', MDB_OPT_STR, &data.di_filter, 'i', MDB_OPT_UINT64, &data.di_instance, 'v', MDB_OPT_SETBITS, DEVINFO_VERBOSE, &data.di_flags, 'p', MDB_OPT_CLRBITS, DEVINFO_CHILD, &data.di_flags, 'c', MDB_OPT_CLRBITS, DEVINFO_PARENT, &data.di_flags, NULL) != argc) return (DCMD_USAGE); if (mdb_readvar(&devinfo_root, "top_devinfo") == -1) { mdb_warn("failed to read 'top_devinfo'"); return (0); } if ((flags & DCMD_ADDRSPEC) == 0) { addr = devinfo_root; if (data.di_flags & DEVINFO_VERBOSE) data.di_flags |= DEVINFO_ALLBOLD; } data.di_base = addr; if (!(flags & DCMD_PIPE_OUT)) mdb_printf("%%-?s %-50s%\n", "DEVINFO", "NAME"); if ((data.di_flags & (DEVINFO_PARENT | DEVINFO_CHILD)) == (DEVINFO_PARENT | DEVINFO_CHILD)) { status = mdb_pwalk("devinfo", (mdb_walk_cb_t)devinfo_print, &data, addr); } else if (data.di_flags & DEVINFO_PARENT) { status = mdb_pwalk("devinfo_parents", (mdb_walk_cb_t)devinfo_print, &data, addr); } else if (data.di_flags & DEVINFO_CHILD) { status = mdb_pwalk("devinfo_children", (mdb_walk_cb_t)devinfo_print, &data, addr); } else { devinfo_node_t din; if (mdb_vread(&din.din_dev, sizeof (din.din_dev), addr) == -1) { mdb_warn("failed to read device"); return (DCMD_ERR); } din.din_depth = 0; return (devinfo_print(addr, (struct dev_info *)&din, &data)); } if (status == -1) { mdb_warn("couldn't walk devinfo tree"); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ int devinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char tmpstr[MODMAXNAMELEN]; char nodename[MODMAXNAMELEN]; char bindname[MAXPATHLEN]; int size, length; struct dev_info devi; devinfo_node_t din; devinfo_cb_data_t data; char *bus = NULL; static const mdb_bitmask_t devi_state_masks[] = { { "DEVICE_OFFLINE", DEVI_DEVICE_OFFLINE, DEVI_DEVICE_OFFLINE }, { "DEVICE_DOWN", DEVI_DEVICE_DOWN, DEVI_DEVICE_DOWN }, { "DEVICE_DEGRADED", DEVI_DEVICE_DEGRADED, DEVI_DEVICE_DEGRADED }, { "DEVICE_REMOVED", DEVI_DEVICE_REMOVED, DEVI_DEVICE_REMOVED }, { "BUS_QUIESCED", DEVI_BUS_QUIESCED, DEVI_BUS_QUIESCED }, { "BUS_DOWN", DEVI_BUS_DOWN, DEVI_BUS_DOWN }, { "NDI_CONFIG", DEVI_NDI_CONFIG, DEVI_NDI_CONFIG }, { "S_ATTACHING", DEVI_S_ATTACHING, DEVI_S_ATTACHING }, { "S_DETACHING", DEVI_S_DETACHING, DEVI_S_DETACHING }, { "S_ONLINING", DEVI_S_ONLINING, DEVI_S_ONLINING }, { "S_OFFLINING", DEVI_S_OFFLINING, DEVI_S_OFFLINING }, { "S_INVOKING_DACF", DEVI_S_INVOKING_DACF, DEVI_S_INVOKING_DACF }, { "S_UNBOUND", DEVI_S_UNBOUND, DEVI_S_UNBOUND }, { "S_REPORT", DEVI_S_REPORT, DEVI_S_REPORT }, { "S_EVADD", DEVI_S_EVADD, DEVI_S_EVADD }, { "S_EVREMOVE", DEVI_S_EVREMOVE, DEVI_S_EVREMOVE }, { "S_NEED_RESET", DEVI_S_NEED_RESET, DEVI_S_NEED_RESET }, { NULL, 0, 0 } }; static const mdb_bitmask_t devi_flags_masks[] = { { "BUSY", DEVI_BUSY, DEVI_BUSY }, { "MADE_CHILDREN", DEVI_MADE_CHILDREN, DEVI_MADE_CHILDREN }, { "ATTACHED_CHILDREN", DEVI_ATTACHED_CHILDREN, DEVI_ATTACHED_CHILDREN}, { "BRANCH_HELD", DEVI_BRANCH_HELD, DEVI_BRANCH_HELD }, { "NO_BIND", DEVI_NO_BIND, DEVI_NO_BIND }, { "DEVI_CACHED_DEVID", DEVI_CACHED_DEVID, DEVI_CACHED_DEVID }, { "PHCI_SIGNALS_VHCI", DEVI_PHCI_SIGNALS_VHCI, DEVI_PHCI_SIGNALS_VHCI }, { "REBIND", DEVI_REBIND, DEVI_REBIND }, { NULL, 0, 0 } }; data.di_flags = DEVINFO_VERBOSE; data.di_base = addr; data.di_filter = NULL; data.di_instance = UINT64_MAX; if (mdb_getopts(argc, argv, 'b', MDB_OPT_STR, &bus, 'd', MDB_OPT_SETBITS, DEVINFO_DRIVER, &data.di_flags, 'q', MDB_OPT_CLRBITS, DEVINFO_VERBOSE, &data.di_flags, 's', MDB_OPT_SETBITS, DEVINFO_SUMMARY, &data.di_flags, NULL) != argc) return (DCMD_USAGE); if (bus != NULL && data.di_flags != DEVINFO_VERBOSE) { mdb_warn("the -b option cannot be used with other options\n"); return (DCMD_USAGE); } if ((data.di_flags & DEVINFO_DRIVER) != 0 && data.di_flags != (DEVINFO_DRIVER | DEVINFO_VERBOSE)) { mdb_warn("the -d option cannot be used with other options\n"); return (DCMD_USAGE); } if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn( "devinfo doesn't give global information (try prtconf)\n"); return (DCMD_ERR); } if (mdb_vread(&devi, sizeof (devi), addr) == -1) { mdb_warn("failed to read device"); return (DCMD_ERR); } if (bus != NULL) { if (strcmp(bus, "pcie") == 0) { uintptr_t bus_addr; if (pcie_bus_match(&devi, &bus_addr)) { mdb_printf("%p\n", bus_addr); return (DCMD_OK); } else { mdb_warn("%p does not have a PCIe bus\n", addr); } } mdb_warn("unknown bus type: %s\n", bus); return (DCMD_ERR); } if ((data.di_flags & DEVINFO_DRIVER) != 0) { if ((flags & DCMD_PIPE_OUT) != 0 && devi.devi_driver_data == NULL) { return (DCMD_OK); } mdb_printf("%p\n", devi.devi_driver_data); return (DCMD_OK); } if (DCMD_HDRSPEC(flags) && data.di_flags & DEVINFO_SUMMARY) { mdb_printf( "%-?s %5s %?s %-20s %-s\n" "%-?s %5s %?s %-20s %-s\n" "%%-?s %5s %?s %-20s %-15s%\n", "DEVINFO", "MAJ", "REFCNT", "NODENAME", "NODESTATE", "", "INST", "CIRCULAR", "BINDNAME", "STATE", "", "", "THREAD", "", "FLAGS"); } if (data.di_flags & DEVINFO_SUMMARY) { *nodename = '\0'; size = sizeof (nodename); if ((length = mdb_readstr(tmpstr, size, (uintptr_t)devi.devi_node_name)) > 0) { strcat(nodename, tmpstr); size -= length; } if (devi.devi_addr != NULL && mdb_readstr(tmpstr, size - 1, (uintptr_t)devi.devi_addr) > 0) { strcat(nodename, "@"); strcat(nodename, tmpstr); } if (mdb_readstr(bindname, sizeof (bindname), (uintptr_t)devi.devi_binding_name) == -1) *bindname = '\0'; mdb_printf("%0?p %5d %?d %-20s %s\n", addr, devi.devi_major, devi.devi_ref, nodename, di_state[MIN(devi.devi_node_state + 1, DI_STATE_MAX)]); mdb_printf("%?s %5d %?d %-20s <%b>\n", "", devi.devi_instance, devi.devi_circular, bindname, devi.devi_state, devi_state_masks); mdb_printf("%?s %5s %?p %-20s <%b>\n\n", "", "", devi.devi_busy_thread, "", devi.devi_flags, devi_flags_masks); return (DCMD_OK); } else { din.din_dev = devi; din.din_depth = 0; return (devinfo_print(addr, (struct dev_info *)&din, &data)); } } /*ARGSUSED*/ int m2d_walk_dinfo(uintptr_t addr, struct dev_info *di, char *mod_name) { char name[MODMAXNAMELEN]; if (mdb_readstr(name, MODMAXNAMELEN, (uintptr_t)di->devi_binding_name) == -1) { mdb_warn("couldn't read devi_binding_name at %p", di->devi_binding_name); return (WALK_ERR); } if (strcmp(name, mod_name) == 0) mdb_printf("%p\n", addr); return (WALK_NEXT); } /*ARGSUSED*/ int modctl2devinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct modctl modctl; char name[MODMAXNAMELEN]; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&modctl, sizeof (modctl), addr) == -1) { mdb_warn("couldn't read modctl at %p", addr); return (DCMD_ERR); } if (mdb_readstr(name, MODMAXNAMELEN, (uintptr_t)modctl.mod_modname) == -1) { mdb_warn("couldn't read modname at %p", modctl.mod_modname); return (DCMD_ERR); } if (mdb_walk("devinfo", (mdb_walk_cb_t)m2d_walk_dinfo, name) == -1) { mdb_warn("couldn't walk devinfo"); return (DCMD_ERR); } return (DCMD_OK); } static int major_to_addr(major_t major, uintptr_t *vaddr) { uint_t devcnt; uintptr_t devnamesp; if (mdb_readvar(&devcnt, "devcnt") == -1) { mdb_warn("failed to read 'devcnt'"); return (-1); } if (mdb_readvar(&devnamesp, "devnamesp") == -1) { mdb_warn("failed to read 'devnamesp'"); return (-1); } if (major >= devcnt) { mdb_warn("%x is out of range [0x0-0x%x]\n", major, devcnt - 1); return (-1); } *vaddr = devnamesp + (major * sizeof (struct devnames)); return (0); } /*ARGSUSED*/ int devnames(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { static const mdb_bitmask_t dn_flag_bits[] = { { "DN_CONF_PARSED", DN_CONF_PARSED, DN_CONF_PARSED }, { "DN_DRIVER_BUSY", DN_DRIVER_BUSY, DN_DRIVER_BUSY }, { "DN_DRIVER_HELD", DN_DRIVER_HELD, DN_DRIVER_HELD }, { "DN_TAKEN_GETUDEV", DN_TAKEN_GETUDEV, DN_TAKEN_GETUDEV }, { "DN_DRIVER_REMOVED", DN_DRIVER_REMOVED, DN_DRIVER_REMOVED}, { "DN_FORCE_ATTACH", DN_FORCE_ATTACH, DN_FORCE_ATTACH}, { "DN_LEAF_DRIVER", DN_LEAF_DRIVER, DN_LEAF_DRIVER}, { "DN_NETWORK_DRIVER", DN_NETWORK_DRIVER, DN_NETWORK_DRIVER}, { "DN_NO_AUTODETACH", DN_NO_AUTODETACH, DN_NO_AUTODETACH }, { "DN_GLDV3_DRIVER", DN_GLDV3_DRIVER, DN_GLDV3_DRIVER}, { "DN_PHCI_DRIVER", DN_PHCI_DRIVER, DN_PHCI_DRIVER}, { "DN_OPEN_RETURNS_EINTR", \ DN_OPEN_RETURNS_EINTR, DN_OPEN_RETURNS_EINTR}, { "DN_SCSI_SIZE_CLEAN", DN_SCSI_SIZE_CLEAN, DN_SCSI_SIZE_CLEAN}, { "DN_NETWORK_PHYSDRIVER", \ DN_NETWORK_PHYSDRIVER, DN_NETWORK_PHYSDRIVER}, { NULL, 0, 0 } }; const mdb_arg_t *argp = NULL; uint_t opt_v = FALSE, opt_m = FALSE; major_t major; size_t i; char name[MODMAXNAMELEN]; struct devnames dn; if ((i = mdb_getopts(argc, argv, 'm', MDB_OPT_SETBITS, TRUE, &opt_m, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL)) != argc) { if (argc - i > 1) return (DCMD_USAGE); argp = &argv[i]; } if (opt_m) { if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (major_to_addr(addr, &addr) == -1) return (DCMD_ERR); } else if (!(flags & DCMD_ADDRSPEC)) { if (argp == NULL) { if (mdb_walk_dcmd("devnames", "devnames", argc, argv)) { mdb_warn("failed to walk devnames"); return (DCMD_ERR); } return (DCMD_OK); } major = (major_t)mdb_argtoull(argp); if (major_to_addr(major, &addr) == -1) return (DCMD_ERR); } if (mdb_vread(&dn, sizeof (struct devnames), addr) == -1) { mdb_warn("failed to read devnames struct at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { if (opt_v) mdb_printf("%%-16s%\n", "NAME"); else mdb_printf("%%-16s %-?s%\n", "NAME", "DN_HEAD"); } if ((flags & DCMD_LOOP) && (dn.dn_name == NULL)) return (DCMD_OK); /* Skip empty slots if we're printing table */ if (mdb_readstr(name, sizeof (name), (uintptr_t)dn.dn_name) == -1) (void) mdb_snprintf(name, sizeof (name), "0x%p", dn.dn_name); if (opt_v) { ddi_prop_list_t prop_list; mdb_printf("%%-16s%\n", name); mdb_inc_indent(2); mdb_printf(" flags %b\n", dn.dn_flags, dn_flag_bits); mdb_printf(" pl %p\n", (void *)dn.dn_pl); mdb_printf(" head %p\n", dn.dn_head); mdb_printf(" instance %d\n", dn.dn_instance); mdb_printf(" inlist %p\n", dn.dn_inlist); mdb_printf("global_prop_ptr %p\n", dn.dn_global_prop_ptr); if (mdb_vread(&prop_list, sizeof (ddi_prop_list_t), (uintptr_t)dn.dn_global_prop_ptr) != -1) { devinfo_print_props(NULL, prop_list.prop_list); } mdb_dec_indent(2); } else mdb_printf("%-16s %-?p\n", name, dn.dn_head); return (DCMD_OK); } /*ARGSUSED*/ int name2major(uintptr_t vaddr, uint_t flags, int argc, const mdb_arg_t *argv) { major_t major; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); if (argc != 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (mdb_name_to_major(argv->a_un.a_str, &major) != 0) { mdb_warn("failed to convert name to major number\n"); return (DCMD_ERR); } mdb_printf("0x%x\n", major); return (DCMD_OK); } /* * Get a numerical argument of a dcmd from addr if an address is specified * or from argv if no address is specified. Return the argument in ret. */ static int getarg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, uintptr_t *ret) { if (argc == 0 && (flags & DCMD_ADDRSPEC)) { *ret = addr; } else if (argc == 1 && !(flags & DCMD_ADDRSPEC)) { *ret = (uintptr_t)mdb_argtoull(&argv[0]); } else { return (-1); } return (0); } /*ARGSUSED*/ int major2name(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t major; const char *name; if (getarg(addr, flags, argc, argv, &major) < 0) return (DCMD_USAGE); if ((name = mdb_major_to_name((major_t)major)) == NULL) { mdb_warn("failed to convert major number to name\n"); return (DCMD_ERR); } mdb_printf("%s\n", name); return (DCMD_OK); } /*ARGSUSED*/ int dev2major(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t dev; if (getarg(addr, flags, argc, argv, &dev) < 0) return (DCMD_USAGE); if (flags & DCMD_PIPE_OUT) mdb_printf("%x\n", getmajor(dev)); else mdb_printf("0x%x (0t%d)\n", getmajor(dev), getmajor(dev)); return (DCMD_OK); } /*ARGSUSED*/ int dev2minor(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t dev; if (getarg(addr, flags, argc, argv, &dev) < 0) return (DCMD_USAGE); if (flags & DCMD_PIPE_OUT) mdb_printf("%x\n", getminor(dev)); else mdb_printf("0x%x (0t%d)\n", getminor(dev), getminor(dev)); return (DCMD_OK); } /*ARGSUSED*/ int devt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t dev; if (getarg(addr, flags, argc, argv, &dev) < 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%10s% %%10s%\n", "MAJOR", "MINOR"); } mdb_printf("%10d %10d\n", getmajor(dev), getminor(dev)); return (DCMD_OK); } /*ARGSUSED*/ int softstate(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t statep; int instance; if (argc != 1) { return (DCMD_USAGE); } instance = (int)mdb_argtoull(&argv[0]); if (mdb_get_soft_state_byaddr(addr, instance, &statep, NULL, 0) == -1) { if (errno == ENOENT) { mdb_warn("instance %d unused\n", instance); } else { mdb_warn("couldn't determine softstate for " "instance %d", instance); } return (DCMD_ERR); } mdb_printf("%p\n", statep); return (DCMD_OK); } /* * Walker for all possible pointers to a driver state struct in an * i_ddi_soft_state instance chain. Returns all non-NULL pointers. */ typedef struct soft_state_walk { struct i_ddi_soft_state ssw_ss; /* Local copy of i_ddi_soft_state */ void **ssw_pointers; /* to driver state structs */ uint_t ssw_index; /* array entry we're using */ } soft_state_walk_t; int soft_state_walk_init(mdb_walk_state_t *wsp) { soft_state_walk_t *sst; if (wsp->walk_addr == 0) return (WALK_DONE); sst = mdb_zalloc(sizeof (soft_state_walk_t), UM_SLEEP|UM_GC); wsp->walk_data = sst; if (mdb_vread(&(sst->ssw_ss), sizeof (sst->ssw_ss), wsp->walk_addr) != sizeof (sst->ssw_ss)) { mdb_warn("failed to read i_ddi_soft_state at %p", wsp->walk_addr); return (WALK_ERR); } if (sst->ssw_ss.size == 0) { mdb_warn("read invalid softstate: softstate item size is " "zero\n"); return (WALK_ERR); } if (sst->ssw_ss.n_items == 0) { mdb_warn("read invalid softstate: softstate has no entries\n"); return (WALK_ERR); } /* * Try and pick arbitrary bounds to try and catch an illegal soft state * structure. While these may be larger than we expect, we also don't * want to throw off a valid use. */ if (sst->ssw_ss.size >= 1024 * 1024 * 1024) { mdb_warn("softstate size is larger than 1 GiB (0x%lx), invalid " "softstate?\n", sst->ssw_ss.size); return (WALK_ERR); } if (sst->ssw_ss.n_items >= INT_MAX / 1024) { mdb_warn("softstate item count seems too large: found %ld " "items\n", sst->ssw_ss.n_items); return (WALK_ERR); } /* Read array of pointers to state structs into local storage. */ sst->ssw_pointers = mdb_alloc((sst->ssw_ss.n_items * sizeof (void *)), UM_SLEEP|UM_GC); if (mdb_vread(sst->ssw_pointers, (sst->ssw_ss.n_items * sizeof (void *)), (uintptr_t)sst->ssw_ss.array) != (sst->ssw_ss.n_items * sizeof (void *))) { mdb_warn("failed to read i_ddi_soft_state at %p", wsp->walk_addr); return (WALK_ERR); } sst->ssw_index = 0; return (WALK_NEXT); } int soft_state_walk_step(mdb_walk_state_t *wsp) { soft_state_walk_t *sst = (soft_state_walk_t *)wsp->walk_data; int status = WALK_NEXT; /* * If the entry indexed has a valid pointer to a soft state struct, * invoke caller's callback func. */ if (sst->ssw_pointers[sst->ssw_index] != NULL) { status = wsp->walk_callback( (uintptr_t)(sst->ssw_pointers[sst->ssw_index]), NULL, wsp->walk_cbdata); } sst->ssw_index += 1; if (sst->ssw_index == sst->ssw_ss.n_items) return (WALK_DONE); return (status); } int soft_state_all_walk_step(mdb_walk_state_t *wsp) { soft_state_walk_t *sst = (soft_state_walk_t *)wsp->walk_data; int status = WALK_NEXT; status = wsp->walk_callback( (uintptr_t)(sst->ssw_pointers[sst->ssw_index]), NULL, wsp->walk_cbdata); sst->ssw_index += 1; if (sst->ssw_index == sst->ssw_ss.n_items) return (WALK_DONE); return (status); } /*ARGSUSED*/ int devbindings(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const mdb_arg_t *arg; struct devnames dn; uintptr_t dn_addr; major_t major; if (!(flags & DCMD_ADDRSPEC) && argc < 1) return (DCMD_USAGE); if (flags & DCMD_ADDRSPEC) { /* * If there's an address, then it's a major number */ major = addr; } else { /* * We interpret the last argument. Any other arguments are * forwarded to "devinfo" */ arg = &argv[argc - 1]; argc--; if (arg->a_type == MDB_TYPE_IMMEDIATE) { major = (uintptr_t)arg->a_un.a_val; } else if (arg->a_un.a_str[0] == '-') { /* the argument shouldn't be an option */ return (DCMD_USAGE); } else if (isdigit(arg->a_un.a_str[0])) { major = (uintptr_t)mdb_strtoull(arg->a_un.a_str); } else { if (mdb_name_to_major(arg->a_un.a_str, &major) != 0) { mdb_warn("failed to get major number for %s\n", arg->a_un.a_str); return (DCMD_ERR); } } } if (major_to_addr(major, &dn_addr) != 0) return (DCMD_ERR); if (mdb_vread(&dn, sizeof (struct devnames), dn_addr) == -1) { mdb_warn("couldn't read devnames array at %p", dn_addr); return (DCMD_ERR); } if (mdb_pwalk_dcmd("devi_next", "devinfo", argc, argv, (uintptr_t)dn.dn_head) != 0) { mdb_warn("couldn't walk the devinfo chain at %p", dn.dn_head); return (DCMD_ERR); } return (DCMD_OK); } /* * walk binding hashtable (as of of driver names (e.g., mb_hashtab)) */ int binding_hash_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) return (WALK_ERR); wsp->walk_data = mdb_alloc(sizeof (void *) * MOD_BIND_HASHSIZE, UM_SLEEP|UM_GC); if (mdb_vread(wsp->walk_data, sizeof (void *) * MOD_BIND_HASHSIZE, wsp->walk_addr) == -1) { mdb_warn("failed to read mb_hashtab"); return (WALK_ERR); } wsp->walk_arg = 0; /* index into mb_hashtab array to start */ return (WALK_NEXT); } int binding_hash_walk_step(mdb_walk_state_t *wsp) { int status; uintptr_t bind_p; struct bind bind; /* * Walk the singly-linked list of struct bind */ bind_p = ((uintptr_t *)wsp->walk_data)[(ulong_t)wsp->walk_arg]; while (bind_p != 0) { if (mdb_vread(&bind, sizeof (bind), bind_p) == -1) { mdb_warn("failed to read bind struct at %p", wsp->walk_addr); return (WALK_ERR); } if ((status = wsp->walk_callback(bind_p, &bind, wsp->walk_cbdata)) != WALK_NEXT) { return (status); } bind_p = (uintptr_t)bind.b_next; } wsp->walk_arg = (void *)((char *)wsp->walk_arg + 1); if (wsp->walk_arg == (void *)(MOD_BIND_HASHSIZE - 1)) return (WALK_DONE); return (WALK_NEXT); } /*ARGSUSED*/ int binding_hash_entry(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct bind bind; /* Arbitrary lengths based on output format below */ char name[MAXPATHLEN] = "???"; char bind_name[MAXPATHLEN] = ""; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); /* Allow null addresses to be passed (as from a walker) */ if (addr == 0) return (DCMD_OK); if (mdb_vread(&bind, sizeof (bind), addr) == -1) { mdb_warn("failed to read struct bind at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s% %-5s %s%\n", "NEXT", "MAJOR", "NAME(S)"); } if (mdb_readstr(name, sizeof (name), (uintptr_t)bind.b_name) == -1) mdb_warn("failed to read 'name'"); /* There may be bind_name, so this may fail */ if (mdb_readstr(bind_name, sizeof (bind_name), (uintptr_t)bind.b_bind_name) == -1) { mdb_printf("%?p %5d %s\n", bind.b_next, bind.b_num, name); } else { mdb_printf("%?p %5d %s %s\n", bind.b_next, bind.b_num, name, bind_name); } return (DCMD_OK); } typedef struct devinfo_audit_log_walk_data { devinfo_audit_t dil_buf; /* buffer of last entry */ uintptr_t dil_base; /* starting address of log buffer */ int dil_max; /* maximum index */ int dil_start; /* starting index */ int dil_index; /* current walking index */ } devinfo_audit_log_walk_data_t; int devinfo_audit_log_walk_init(mdb_walk_state_t *wsp) { devinfo_log_header_t header; devinfo_audit_log_walk_data_t *dil; uintptr_t devinfo_audit_log; /* read in devinfo_log_header structure */ if (mdb_readvar(&devinfo_audit_log, "devinfo_audit_log") == -1) { mdb_warn("failed to read 'devinfo_audit_log'"); return (WALK_ERR); } if (mdb_vread(&header, sizeof (devinfo_log_header_t), devinfo_audit_log) == -1) { mdb_warn("couldn't read devinfo_log_header at %p", devinfo_audit_log); return (WALK_ERR); } dil = mdb_zalloc(sizeof (devinfo_audit_log_walk_data_t), UM_SLEEP); wsp->walk_data = dil; dil->dil_start = dil->dil_index = header.dh_curr; dil->dil_max = header.dh_max; if (dil->dil_start < 0) /* no log entries */ return (WALK_DONE); dil->dil_base = devinfo_audit_log + offsetof(devinfo_log_header_t, dh_entry); wsp->walk_addr = dil->dil_base + dil->dil_index * sizeof (devinfo_audit_t); return (WALK_NEXT); } int devinfo_audit_log_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; devinfo_audit_log_walk_data_t *dil = wsp->walk_data; devinfo_audit_t *da = &dil->dil_buf; int status = WALK_NEXT; /* read in current entry and invoke callback */ if (addr == 0) return (WALK_DONE); if (mdb_vread(&dil->dil_buf, sizeof (devinfo_audit_t), addr) == -1) { mdb_warn("failed to read devinfo_audit at %p", addr); status = WALK_DONE; } status = wsp->walk_callback(wsp->walk_addr, da, wsp->walk_cbdata); /* step to the previous log entry in time */ if (--dil->dil_index < 0) dil->dil_index += dil->dil_max; if (dil->dil_index == dil->dil_start) { wsp->walk_addr = 0; return (WALK_DONE); } wsp->walk_addr = dil->dil_base + dil->dil_index * sizeof (devinfo_audit_t); return (status); } void devinfo_audit_log_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (devinfo_audit_log_walk_data_t)); } /* * display devinfo_audit_t stack trace */ /*ARGSUSED*/ int devinfo_audit(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE; devinfo_audit_t da; int i, depth; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf(" %-?s %16s %-?s %-?s %5s\n", "AUDIT", "TIMESTAMP", "THREAD", "DEVINFO", "STATE"); } if (mdb_vread(&da, sizeof (da), addr) == -1) { mdb_warn("couldn't read devinfo_audit at %p", addr); return (DCMD_ERR); } mdb_printf(" %0?p %16llx %0?p %0?p %s\n", addr, da.da_timestamp, da.da_thread, da.da_devinfo, di_state[MIN(da.da_node_state + 1, DI_STATE_MAX)]); if (!verbose) return (DCMD_OK); mdb_inc_indent(4); /* * Guard against bogus da_depth in case the devinfo_audit_t * is corrupt or the address does not really refer to a * devinfo_audit_t. */ depth = MIN(da.da_depth, DDI_STACK_DEPTH); for (i = 0; i < depth; i++) mdb_printf("%a\n", da.da_stack[i]); mdb_printf("\n"); mdb_dec_indent(4); return (DCMD_OK); } int devinfo_audit_log(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (flags & DCMD_ADDRSPEC) return (devinfo_audit(addr, flags, argc, argv)); (void) mdb_walk_dcmd("devinfo_audit_log", "devinfo_audit", argc, argv); return (DCMD_OK); } typedef struct devinfo_audit_node_walk_data { devinfo_audit_t dih_buf; /* buffer of last entry */ uintptr_t dih_dip; /* address of dev_info */ int dih_on_devinfo; /* devi_audit on dev_info struct */ } devinfo_audit_node_walk_data_t; int devinfo_audit_node_walk_init(mdb_walk_state_t *wsp) { devinfo_audit_node_walk_data_t *dih; devinfo_audit_t *da; struct dev_info devi; uintptr_t addr = wsp->walk_addr; /* read in devinfo structure */ if (mdb_vread(&devi, sizeof (struct dev_info), addr) == -1) { mdb_warn("couldn't read dev_info at %p", addr); return (WALK_ERR); } dih = mdb_zalloc(sizeof (devinfo_audit_node_walk_data_t), UM_SLEEP); wsp->walk_data = dih; da = &dih->dih_buf; /* read in devi_audit structure */ if (mdb_vread(da, sizeof (devinfo_audit_t), (uintptr_t)devi.devi_audit) == -1) { mdb_warn("couldn't read devi_audit at %p", devi.devi_audit); return (WALK_ERR); } dih->dih_dip = addr; dih->dih_on_devinfo = 1; wsp->walk_addr = (uintptr_t)devi.devi_audit; return (WALK_NEXT); } int devinfo_audit_node_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr; devinfo_audit_node_walk_data_t *dih = wsp->walk_data; devinfo_audit_t *da = &dih->dih_buf; if (wsp->walk_addr == 0) return (WALK_DONE); (void) wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); skip: /* read in previous entry */ if ((addr = (uintptr_t)da->da_lastlog) == 0) return (WALK_DONE); if (mdb_vread(&dih->dih_buf, sizeof (devinfo_audit_t), addr) == -1) { mdb_warn("failed to read devinfo_audit at %p", addr); return (WALK_DONE); } /* check if last log was over-written */ if ((uintptr_t)da->da_devinfo != dih->dih_dip) return (WALK_DONE); /* * skip the first common log entry, which is a duplicate of * the devi_audit buffer on the dev_info structure */ if (dih->dih_on_devinfo) { dih->dih_on_devinfo = 0; goto skip; } wsp->walk_addr = addr; return (WALK_NEXT); } void devinfo_audit_node_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (devinfo_audit_node_walk_data_t)); } int devinfo_audit_node(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); (void) mdb_pwalk_dcmd("devinfo_audit_node", "devinfo_audit", argc, argv, addr); return (DCMD_OK); } /* * mdb support for per-devinfo fault management data */ /*ARGSUSED*/ int devinfo_fm(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct dev_info devi; struct i_ddi_fmhdl fhdl; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s IPL CAPS DROP FMCFULL FMCMISS ACCERR " "DMAERR %?s %?s%\n", "ADDR", "DMACACHE", "ACCCACHE"); } if (mdb_vread(&devi, sizeof (devi), addr) == -1) { mdb_warn("failed to read devinfo struct at %p", addr); return (DCMD_ERR); } if (mdb_vread(&fhdl, sizeof (fhdl), (uintptr_t)devi.devi_fmhdl) == -1) { mdb_warn("failed to read devinfo fm struct at %p", (uintptr_t)devi.devi_fmhdl); return (DCMD_ERR); } mdb_printf("%?p %3u %c%c%c%c %4llu %7llu %7llu %6llu %6llu %?p %?p\n", (uintptr_t)devi.devi_fmhdl, fhdl.fh_ibc, (DDI_FM_EREPORT_CAP(fhdl.fh_cap) ? 'E' : '-'), (DDI_FM_ERRCB_CAP(fhdl.fh_cap) ? 'C' : '-'), (DDI_FM_ACC_ERR_CAP(fhdl.fh_cap) ? 'A' : '-'), (DDI_FM_DMA_ERR_CAP(fhdl.fh_cap) ? 'D' : '-'), fhdl.fh_kstat.fek_erpt_dropped.value.ui64, fhdl.fh_kstat.fek_fmc_full.value.ui64, fhdl.fh_kstat.fek_fmc_miss.value.ui64, fhdl.fh_kstat.fek_acc_err.value.ui64, fhdl.fh_kstat.fek_dma_err.value.ui64, fhdl.fh_dma_cache, fhdl.fh_acc_cache); return (DCMD_OK); } /*ARGSUSED*/ int devinfo_fmce(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct i_ddi_fmc_entry fce; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %?s %?s%\n", "ADDR", "RESOURCE", "BUS_SPECIFIC"); } if (mdb_vread(&fce, sizeof (fce), addr) == -1) { mdb_warn("failed to read fm cache struct at %p", addr); return (DCMD_ERR); } mdb_printf("%?p %?p %?p\n", (uintptr_t)addr, fce.fce_resource, fce.fce_bus_specific); return (DCMD_OK); } int devinfo_fmc_walk_init(mdb_walk_state_t *wsp) { struct i_ddi_fmc fec; if (wsp->walk_addr == 0) return (WALK_ERR); if (mdb_vread(&fec, sizeof (fec), wsp->walk_addr) == -1) { mdb_warn("failed to read fm cache at %p", wsp->walk_addr); return (WALK_ERR); } if (fec.fc_head == NULL) return (WALK_DONE); wsp->walk_addr = (uintptr_t)fec.fc_head; return (WALK_NEXT); } int devinfo_fmc_walk_step(mdb_walk_state_t *wsp) { int status; struct i_ddi_fmc_entry fe; if (mdb_vread(&fe, sizeof (fe), wsp->walk_addr) == -1) { mdb_warn("failed to read active fm cache entry at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, &fe, wsp->walk_cbdata); if (fe.fce_next == NULL) return (WALK_DONE); wsp->walk_addr = (uintptr_t)fe.fce_next; return (status); } int minornode_walk_init(mdb_walk_state_t *wsp) { struct dev_info di; uintptr_t addr = wsp->walk_addr; if (addr == 0) { mdb_warn("a dev_info struct address must be provided\n"); return (WALK_ERR); } if (mdb_vread(&di, sizeof (di), wsp->walk_addr) == -1) { mdb_warn("failed to read dev_info struct at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)di.devi_minor; return (WALK_NEXT); } int minornode_walk_step(mdb_walk_state_t *wsp) { struct ddi_minor_data md; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&md, sizeof (md), addr) == -1) { mdb_warn("failed to read dev_info struct at %p", addr); return (WALK_DONE); } wsp->walk_addr = (uintptr_t)md.next; return (wsp->walk_callback(addr, &md, wsp->walk_cbdata)); } static const char *const md_type[] = { "DDI_MINOR", "DDI_ALIAS", "DDI_DEFAULT", "DDI_I_PATH", "?" }; #define MD_TYPE_MAX ((sizeof (md_type) / sizeof (char *)) - 1) /*ARGSUSED*/ static int print_minornode(uintptr_t addr, const void *arg, void *data) { char name[128]; char nodetype[128]; char *spectype; struct ddi_minor_data *mdp = (struct ddi_minor_data *)arg; if (mdb_readstr(name, sizeof (name), (uintptr_t)mdp->ddm_name) == -1) *name = '\0'; if (mdb_readstr(nodetype, sizeof (nodetype), (uintptr_t)mdp->ddm_node_type) == -1) *nodetype = '\0'; switch (mdp->ddm_spec_type) { case S_IFCHR: spectype = "c"; break; case S_IFBLK: spectype = "b"; break; default: spectype = "?"; break; } mdb_printf("%?p %16lx %-4s %-11s %-10s %s\n", addr, mdp->ddm_dev, spectype, md_type[MIN(mdp->type, MD_TYPE_MAX)], name, nodetype); return (WALK_NEXT); } /*ARGSUSED*/ int minornodes(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %16s %-4s %-11s %-10s %-16s%\n", "ADDR", "DEV", "SPEC", "TYPE", "NAME", "NODETYPE"); if (mdb_pwalk("minornode", print_minornode, NULL, addr) == -1) { mdb_warn("can't walk minornode"); return (DCMD_ERR); } return (DCMD_OK); } /* * 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 2019, Joyent, Inc. */ #ifndef _DEVINFO_H #define _DEVINFO_H #ifdef __cplusplus extern "C" { #endif #include /* * Options for prtconf/devinfo/hotplug dcmd. */ #define DEVINFO_VERBOSE 0x1 #define DEVINFO_PARENT 0x2 #define DEVINFO_CHILD 0x4 #define DEVINFO_ALLBOLD 0x8 #define DEVINFO_SUMMARY 0x10 #define DEVINFO_HP_PHYSICAL 0x20 #define DEVINFO_PIPE 0x40 #define DEVINFO_DRIVER 0x80 typedef struct devinfo_cb_data { uintptr_t di_base; uint_t di_flags; char *di_filter; uint64_t di_instance; } devinfo_cb_data_t; extern int devinfo_walk_init(mdb_walk_state_t *); extern int devinfo_walk_step(mdb_walk_state_t *); extern void devinfo_walk_fini(mdb_walk_state_t *); extern int devinfo_parents_walk_init(mdb_walk_state_t *); extern int devinfo_parents_walk_step(mdb_walk_state_t *); extern void devinfo_parents_walk_fini(mdb_walk_state_t *); extern int devinfo_children_walk_init(mdb_walk_state_t *); extern int devinfo_children_walk_step(mdb_walk_state_t *); extern void devinfo_children_walk_fini(mdb_walk_state_t *); extern int devinfo2driver(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devnames_walk_init(mdb_walk_state_t *); extern int devnames_walk_step(mdb_walk_state_t *); extern void devnames_walk_fini(mdb_walk_state_t *); extern int devinfo_siblings_walk_init(mdb_walk_state_t *); extern int devinfo_siblings_walk_step(mdb_walk_state_t *); extern int devi_next_walk_step(mdb_walk_state_t *); extern int prtconf(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devinfo(uintptr_t, uint_t, int, const mdb_arg_t *); extern int modctl2devinfo(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devnames(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devbindings(uintptr_t, uint_t, int, const mdb_arg_t *); extern int name2major(uintptr_t, uint_t, int, const mdb_arg_t *); extern int major2name(uintptr_t, uint_t, int, const mdb_arg_t *); extern int major2snode(uintptr_t, uint_t, int, const mdb_arg_t *); extern int dev2major(uintptr_t, uint_t, int, const mdb_arg_t *); extern int dev2minor(uintptr_t, uint_t, int, const mdb_arg_t *); extern int dev2snode(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devt(uintptr_t, uint_t, int, const mdb_arg_t *); extern int softstate(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devinfo_fm(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devinfo_fmce(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devinfo2bus(uintptr_t, uint_t, int, const mdb_arg_t *); extern int soft_state_walk_init(mdb_walk_state_t *); extern int soft_state_walk_step(mdb_walk_state_t *); extern int soft_state_all_walk_step(mdb_walk_state_t *); extern void soft_state_walk_fini(mdb_walk_state_t *); extern int devinfo_fmc_walk_init(mdb_walk_state_t *); extern int devinfo_fmc_walk_step(mdb_walk_state_t *); extern int binding_hash_walk_init(mdb_walk_state_t *); extern int binding_hash_walk_step(mdb_walk_state_t *); extern void binding_hash_walk_fini(mdb_walk_state_t *); extern int binding_hash_entry(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devinfo_audit(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devinfo_audit_log_walk_init(mdb_walk_state_t *); extern int devinfo_audit_log_walk_step(mdb_walk_state_t *); extern void devinfo_audit_log_walk_fini(mdb_walk_state_t *); extern int devinfo_audit_log(uintptr_t, uint_t, int, const mdb_arg_t *); extern int devinfo_audit_node_walk_init(mdb_walk_state_t *); extern int devinfo_audit_node_walk_step(mdb_walk_state_t *); extern void devinfo_audit_node_walk_fini(mdb_walk_state_t *); extern int devinfo_audit_node(uintptr_t, uint_t, int, const mdb_arg_t *); extern int minornode_walk_init(mdb_walk_state_t *); extern int minornode_walk_step(mdb_walk_state_t *); extern int minornodes(uintptr_t, uint_t, int, const mdb_arg_t *); extern void prtconf_help(void); extern void devinfo_help(void); extern void devinfo2bus_help(void); #ifdef __cplusplus } #endif #endif /* _DEVINFO_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #ifndef _KMDB #include #endif #include "dist.h" /* * Divides the given range (inclusive at both endpoints) evenly into the given * number of buckets, adding one bucket at the end that is one past the end of * the range. The returned buckets will be automatically freed when the dcmd * completes or is forcibly aborted. */ const int * dist_linear(int buckets, int beg, int end) { int *out = mdb_alloc((buckets + 1) * sizeof (*out), UM_SLEEP | UM_GC); int pos; int dist = end - beg + 1; for (pos = 0; pos < buckets; pos++) out[pos] = beg + (pos * dist)/buckets; out[buckets] = end + 1; return (out); } /* * We want the bins to be a constant ratio: * * b_0 = beg; * b_idx = b_{idx-1} * r; * b_buckets = end + 1; * * That is: * * buckets * beg * r = end * * Which reduces to: * * buckets ___________________ * r = -------/ ((end + 1) / beg) * * log ((end + 1) / beg) * log r = --------------------- * buckets * * (log ((end + 1) / beg)) / buckets * r = e */ /* ARGSUSED */ const int * dist_geometric(int buckets, int beg, int end, int minbucketsize) { #ifdef _KMDB return (dist_linear(buckets, beg, end)); #else int *out = mdb_alloc((buckets + 1) * sizeof (*out), UM_SLEEP | UM_GC); double r; double b; int idx = 0; int last; int begzero; if (minbucketsize == 0) minbucketsize = 1; if (buckets == 1) { out[0] = beg; out[1] = end + 1; return (out); } begzero = (beg == 0); if (begzero) beg = 1; r = exp(log((double)(end + 1) / beg) / buckets); /* * We've now computed r, using the previously derived formula. We * now need to generate the array of bucket bounds. There are * two major variables: * * b holds b_idx, the current index, as a double. * last holds the integer which goes into out[idx] * * Our job is to transform the smooth function b_idx, defined * above, into integer-sized buckets, with a specified minimum * bucket size. Since b_idx is an exponentially growing function, * any inadequate buckets must be at the beginning. To deal * with this, we make buckets of minimum size until b catches up * with last. * * A final wrinkle is that beg *can* be zero. We compute r and b * as if beg was 1, then start last as 0. This can lead to a bit * of oddness around the 0 bucket, but it's mostly reasonable. */ b = last = beg; if (begzero) last = 0; for (idx = 0; idx < buckets; idx++) { int next; out[idx] = last; b *= r; next = (int)b; if (next > last + minbucketsize - 1) last = next; else last += minbucketsize; } out[buckets] = end + 1; return (out); #endif } #define NCHARS 50 /* * Print the distribution header with the given bucket label. The header is * printed on a single line, and the label is assumed to fit within the given * width (number of characters). The default label width when unspecified (0) * is eleven characters. Optionally, a label other than "count" may be specified * for the bucket counts. */ void dist_print_header(const char *label, int width, const char *count) { int n; const char *dist = " Distribution "; char dashes[NCHARS + 1]; if (width == 0) width = 11; if (count == NULL) count = "count"; n = (NCHARS - strlen(dist)) / 2; (void) memset(dashes, '-', n); dashes[n] = '\0'; mdb_printf("%*s %s%s%s %s\n", width, label, dashes, dist, dashes, count); } /* * Print one distribution bucket whose range is from distarray[i] inclusive to * distarray[i + 1] exclusive by totalling counts in that index range. The * given total is assumed to be the sum of all elements in the counts array. * Each bucket is labeled by its range in the form "first-last" (omit "-last" if * the range is a single value) where first and last are integers, and last is * one less than the first value of the next bucket range. The bucket label is * assumed to fit within the given width (number of characters), which should * match the width value passed to dist_print_header(). The default width when * unspecified (0) is eleven characters. */ void dist_print_bucket(const int *distarray, int i, const uint_t *counts, uint64_t total, int width) { int b; /* bucket range index */ int bb = distarray[i]; /* bucket begin */ int be = distarray[i + 1] - 1; /* bucket end */ uint64_t count = 0; /* bucket value */ int nats; char ats[NCHARS + 1], spaces[NCHARS + 1]; char range[40]; if (width == 0) width = 11; if (total == 0) total = 1; /* avoid divide-by-zero */ for (b = bb; b <= be; b++) count += counts[b]; nats = (NCHARS * count) / total; (void) memset(ats, '@', nats); ats[nats] = 0; (void) memset(spaces, ' ', NCHARS - nats); spaces[NCHARS - nats] = 0; if (bb == be) (void) mdb_snprintf(range, sizeof (range), "%d", bb); else (void) mdb_snprintf(range, sizeof (range), "%d-%d", bb, be); mdb_printf("%*s |%s%s %lld\n", width, range, ats, spaces, count); } #undef NCHARS /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DIST_H #define _DIST_H #ifdef __cplusplus extern "C" { #endif extern const int *dist_linear(int, int, int); extern const int *dist_geometric(int, int, int, int); extern void dist_print_header(const char *, int, const char *); extern void dist_print_bucket(const int *, int, const uint_t *, uint64_t, int); #ifdef __cplusplus } #endif #endif /* _DIST_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2016 Joyent, Inc. */ #include "dnlc.h" #include #include typedef struct dnlc_walk { int dw_hashsz; int dw_index; uintptr_t dw_hash; uintptr_t dw_head; } dnlc_walk_t; int dnlc_walk_init(mdb_walk_state_t *wsp) { dnlc_walk_t *dwp; if (wsp->walk_addr != 0) { mdb_warn("dnlc walk doesn't support global walks\n"); return (WALK_ERR); } dwp = mdb_zalloc(sizeof (dnlc_walk_t), UM_SLEEP); if (mdb_readvar(&dwp->dw_hashsz, "nc_hashsz") == -1 || dwp->dw_hashsz <= 0) { mdb_warn("failed to read 'nc_hashsz'\n"); mdb_free(dwp, sizeof (dnlc_walk_t)); return (WALK_ERR); } if (dwp->dw_hashsz <= 0) { mdb_warn("invalid 'nc_hashsz' value\n"); mdb_free(dwp, sizeof (dnlc_walk_t)); return (WALK_ERR); } if (mdb_readvar(&dwp->dw_hash, "nc_hash") == -1) { mdb_warn("failed to read 'nc_hash'\n"); mdb_free(dwp, sizeof (dnlc_walk_t)); return (WALK_ERR); } wsp->walk_data = dwp; return (WALK_NEXT); } int dnlc_walk_step(mdb_walk_state_t *wsp) { dnlc_walk_t *dwp = wsp->walk_data; nc_hash_t hash; uintptr_t result, addr = wsp->walk_addr; next: while (addr == dwp->dw_head || addr == 0) { if (dwp->dw_index >= dwp->dw_hashsz) { return (WALK_DONE); } dwp->dw_head = dwp->dw_hash + (sizeof (nc_hash_t) * dwp->dw_index); if (mdb_vread(&hash, sizeof (hash), dwp->dw_head) == -1) { mdb_warn("failed to read nc_hash_t at %#lx", dwp->dw_hash); return (WALK_ERR); } dwp->dw_index++; addr = (uintptr_t)hash.hash_next; } result = addr; if (mdb_vread(&addr, sizeof (uintptr_t), addr) == -1) { /* * This entry may have become bogus since acquiring the address * from its neighbor. Continue on if that is the case. */ addr = 0; goto next; } wsp->walk_addr = addr; return (wsp->walk_callback(result, &result, wsp->walk_cbdata)); } void dnlc_walk_fini(mdb_walk_state_t *wsp) { dnlc_walk_t *dwp = wsp->walk_data; mdb_free(dwp, sizeof (dnlc_walk_t)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2016 Joyent, Inc. */ #ifndef _MDB_DNLC_H #define _MDB_DNLC_H #include #ifdef __cplusplus extern "C" { #endif extern int dnlc_walk_init(mdb_walk_state_t *); extern int dnlc_walk_step(mdb_walk_state_t *); extern void dnlc_walk_fini(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _MDB_DNLC_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013, Josef 'Jeff' Sipek * Copyright 2018 Joyent, Inc. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include "findstack.h" #include "thread.h" #include "sobj.h" /* * Parts of this file are shared between targets, but this section is only * used for KVM and KMDB. */ #ifdef _KERNEL int findstack_debug_on = 0; /* * "sp" is a kernel VA. */ static int print_stack(uintptr_t sp, uintptr_t pc, uintptr_t addr, int argc, const mdb_arg_t *argv, int free_state) { boolean_t showargs = B_FALSE; boolean_t types = B_FALSE; boolean_t sizes = B_FALSE; boolean_t addrs = B_FALSE; int count, err; char tdesc[128] = ""; count = mdb_getopts(argc, argv, 'n', MDB_OPT_SETBITS, TRUE, &addrs, 's', MDB_OPT_SETBITS, TRUE, &sizes, 't', MDB_OPT_SETBITS, TRUE, &types, 'v', MDB_OPT_SETBITS, TRUE, &showargs, NULL); argc -= count; argv += count; if (argc > 1 || (argc == 1 && argv->a_type != MDB_TYPE_STRING)) return (DCMD_USAGE); (void) thread_getdesc(addr, B_TRUE, tdesc, sizeof (tdesc)); mdb_printf("stack pointer for thread %p%s (%s): %p\n", addr, (free_state ? " (TS_FREE)" : ""), tdesc, sp); if (pc != 0) mdb_printf("[ %0?lr %a() ]\n", sp, pc); mdb_inc_indent(2); mdb_set_dot(sp); if (argc == 1) { err = mdb_eval(argv->a_un.a_str); } else { (void) mdb_snprintf(tdesc, sizeof (tdesc), "<.$C%s%s%s%s", addrs ? " -n" : "", sizes ? " -s" : "", types ? " -t" : "", showargs ? "" : " 0"); err = mdb_eval(tdesc); } mdb_dec_indent(2); return ((err == -1) ? DCMD_ABORT : DCMD_OK); } int findstack(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { findstack_info_t fsi; int retval; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); bzero(&fsi, sizeof (fsi)); if ((retval = stacks_findstack(addr, &fsi, 1)) != DCMD_OK || fsi.fsi_failed) return (retval); return (print_stack(fsi.fsi_sp, fsi.fsi_pc, addr, argc, argv, fsi.fsi_tstate == TS_FREE)); } /*ARGSUSED*/ int findstack_debug(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *av) { findstack_debug_on ^= 1; mdb_printf("findstack: debugging is now %s\n", findstack_debug_on ? "on" : "off"); return (DCMD_OK); } #endif /* _KERNEL */ static void uppercase(char *p) { for (; *p != '\0'; p++) { if (*p >= 'a' && *p <= 'z') *p += 'A' - 'a'; } } static void sobj_to_text(uintptr_t addr, char *out, size_t out_sz) { sobj_ops_to_text(addr, out, out_sz); uppercase(out); } #define SOBJ_ALL 1 static int text_to_sobj(const char *text, uintptr_t *out) { if (strcasecmp(text, "ALL") == 0) { *out = SOBJ_ALL; return (0); } return (sobj_text_to_ops(text, out)); } #define TSTATE_PANIC -2U static int text_to_tstate(const char *text, uint_t *out) { if (strcasecmp(text, "panic") == 0) *out = TSTATE_PANIC; else if (thread_text_to_state(text, out) != 0) { mdb_warn("tstate \"%s\" not recognized\n", text); return (-1); } return (0); } static void tstate_to_text(uint_t tstate, uint_t paniced, char *out, size_t out_sz) { if (paniced) mdb_snprintf(out, out_sz, "panic"); else thread_state_to_text(tstate, out, out_sz); uppercase(out); } typedef struct stacks_entry { struct stacks_entry *se_next; struct stacks_entry *se_dup; /* dups of this stack */ uintptr_t se_thread; uintptr_t se_sp; uintptr_t se_sobj_ops; uint32_t se_tstate; uint32_t se_count; /* # threads w/ this stack */ uint8_t se_overflow; uint8_t se_depth; uint8_t se_failed; /* failure reason; FSI_FAIL_* */ uint8_t se_panic; uintptr_t se_stack[1]; } stacks_entry_t; #define STACKS_ENTRY_SIZE(x) OFFSETOF(stacks_entry_t, se_stack[(x)]) #define STACKS_HSIZE 127 /* Maximum stack depth reported in stacks */ #define STACKS_MAX_DEPTH 254 typedef struct stacks_info { size_t si_count; /* total stacks_entry_ts (incl dups) */ size_t si_entries; /* # entries in hash table */ stacks_entry_t **si_hash; /* hash table */ findstack_info_t si_fsi; /* transient callback state */ } stacks_info_t; /* global state cached between invocations */ #define STACKS_STATE_CLEAN 0 #define STACKS_STATE_DIRTY 1 #define STACKS_STATE_DONE 2 static uint_t stacks_state = STACKS_STATE_CLEAN; static stacks_entry_t **stacks_hash; static stacks_entry_t **stacks_array; static size_t stacks_array_size; static size_t stacks_hash_entry(stacks_entry_t *sep) { size_t depth = sep->se_depth; uintptr_t *stack = sep->se_stack; uint64_t total = depth; while (depth > 0) { total += *stack; stack++; depth--; } return (total % STACKS_HSIZE); } /* * This is used to both compare stacks for equality and to sort the final * list of unique stacks. forsort specifies the latter behavior, which * additionally: * compares se_count, and * sorts the stacks by text function name. * * The equality test is independent of se_count, and doesn't care about * relative ordering, so we don't do the extra work of looking up symbols * for the stack addresses. */ static int stacks_entry_comp_impl(stacks_entry_t *l, stacks_entry_t *r, uint_t forsort) { int idx; int depth = MIN(l->se_depth, r->se_depth); /* no matter what, panic stacks come last. */ if (l->se_panic > r->se_panic) return (1); if (l->se_panic < r->se_panic) return (-1); if (forsort) { /* put large counts earlier */ if (l->se_count > r->se_count) return (-1); if (l->se_count < r->se_count) return (1); } if (l->se_tstate > r->se_tstate) return (1); if (l->se_tstate < r->se_tstate) return (-1); if (l->se_failed > r->se_failed) return (1); if (l->se_failed < r->se_failed) return (-1); for (idx = 0; idx < depth; idx++) { char lbuf[MDB_SYM_NAMLEN]; char rbuf[MDB_SYM_NAMLEN]; int rval; uintptr_t laddr = l->se_stack[idx]; uintptr_t raddr = r->se_stack[idx]; if (laddr == raddr) continue; if (forsort && mdb_lookup_by_addr(laddr, MDB_SYM_FUZZY, lbuf, sizeof (lbuf), NULL) != -1 && mdb_lookup_by_addr(raddr, MDB_SYM_FUZZY, rbuf, sizeof (rbuf), NULL) != -1 && (rval = strcmp(lbuf, rbuf)) != 0) return (rval); if (laddr > raddr) return (1); return (-1); } if (l->se_overflow > r->se_overflow) return (-1); if (l->se_overflow < r->se_overflow) return (1); if (l->se_depth > r->se_depth) return (1); if (l->se_depth < r->se_depth) return (-1); if (l->se_sobj_ops > r->se_sobj_ops) return (1); if (l->se_sobj_ops < r->se_sobj_ops) return (-1); return (0); } static int stacks_entry_comp(const void *l_arg, const void *r_arg) { stacks_entry_t * const *lp = l_arg; stacks_entry_t * const *rp = r_arg; return (stacks_entry_comp_impl(*lp, *rp, 1)); } void stacks_cleanup(int force) { int idx = 0; stacks_entry_t *cur, *next; if (stacks_state == STACKS_STATE_CLEAN) return; if (!force && stacks_state == STACKS_STATE_DONE) return; /* * Until the array is sorted and stable, stacks_hash will be non-NULL. * This way, we can get at all of the data, even if qsort() was * interrupted while mucking with the array. */ if (stacks_hash != NULL) { for (idx = 0; idx < STACKS_HSIZE; idx++) { while ((cur = stacks_hash[idx]) != NULL) { while ((next = cur->se_dup) != NULL) { cur->se_dup = next->se_dup; mdb_free(next, STACKS_ENTRY_SIZE(next->se_depth)); } next = cur->se_next; stacks_hash[idx] = next; mdb_free(cur, STACKS_ENTRY_SIZE(cur->se_depth)); } } if (stacks_array != NULL) mdb_free(stacks_array, stacks_array_size * sizeof (*stacks_array)); mdb_free(stacks_hash, STACKS_HSIZE * sizeof (*stacks_hash)); } else if (stacks_array != NULL) { for (idx = 0; idx < stacks_array_size; idx++) { if ((cur = stacks_array[idx]) != NULL) { while ((next = cur->se_dup) != NULL) { cur->se_dup = next->se_dup; mdb_free(next, STACKS_ENTRY_SIZE(next->se_depth)); } stacks_array[idx] = NULL; mdb_free(cur, STACKS_ENTRY_SIZE(cur->se_depth)); } } mdb_free(stacks_array, stacks_array_size * sizeof (*stacks_array)); } stacks_findstack_cleanup(); stacks_array_size = 0; stacks_state = STACKS_STATE_CLEAN; stacks_hash = NULL; stacks_array = NULL; } /*ARGSUSED*/ static int stacks_thread_cb(uintptr_t addr, const void *ignored, void *cbarg) { stacks_info_t *sip = cbarg; findstack_info_t *fsip = &sip->si_fsi; stacks_entry_t **sepp, *nsep, *sep; int idx; size_t depth; if (stacks_findstack(addr, fsip, 0) != DCMD_OK && fsip->fsi_failed == FSI_FAIL_BADTHREAD) { mdb_warn("couldn't read thread at %p\n", addr); return (WALK_NEXT); } sip->si_count++; depth = fsip->fsi_depth; nsep = mdb_zalloc(STACKS_ENTRY_SIZE(depth), UM_SLEEP); nsep->se_thread = addr; nsep->se_sp = fsip->fsi_sp; nsep->se_sobj_ops = fsip->fsi_sobj_ops; nsep->se_tstate = fsip->fsi_tstate; nsep->se_count = 1; nsep->se_overflow = fsip->fsi_overflow; nsep->se_depth = depth; nsep->se_failed = fsip->fsi_failed; nsep->se_panic = fsip->fsi_panic; for (idx = 0; idx < depth; idx++) nsep->se_stack[idx] = fsip->fsi_stack[idx]; for (sepp = &sip->si_hash[stacks_hash_entry(nsep)]; (sep = *sepp) != NULL; sepp = &sep->se_next) { if (stacks_entry_comp_impl(sep, nsep, 0) != 0) continue; nsep->se_dup = sep->se_dup; sep->se_dup = nsep; sep->se_count++; return (WALK_NEXT); } nsep->se_next = NULL; *sepp = nsep; sip->si_entries++; return (WALK_NEXT); } static int stacks_run_tlist(mdb_pipe_t *tlist, stacks_info_t *si) { size_t idx; size_t found = 0; int ret; for (idx = 0; idx < tlist->pipe_len; idx++) { uintptr_t addr = tlist->pipe_data[idx]; found++; ret = stacks_thread_cb(addr, NULL, si); if (ret == WALK_DONE) break; if (ret != WALK_NEXT) return (-1); } if (found) return (0); return (-1); } static int stacks_run(int verbose, mdb_pipe_t *tlist) { stacks_info_t si; findstack_info_t *fsip = &si.si_fsi; size_t idx; stacks_entry_t **cur; bzero(&si, sizeof (si)); stacks_state = STACKS_STATE_DIRTY; stacks_hash = si.si_hash = mdb_zalloc(STACKS_HSIZE * sizeof (*si.si_hash), UM_SLEEP); si.si_entries = 0; si.si_count = 0; fsip->fsi_max_depth = STACKS_MAX_DEPTH; fsip->fsi_stack = mdb_alloc(fsip->fsi_max_depth * sizeof (*fsip->fsi_stack), UM_SLEEP | UM_GC); if (verbose) mdb_warn("stacks: processing kernel threads\n"); if (tlist != NULL) { if (stacks_run_tlist(tlist, &si)) return (DCMD_ERR); } else { if (mdb_walk("thread", stacks_thread_cb, &si) != 0) { mdb_warn("cannot walk \"thread\""); return (DCMD_ERR); } } if (verbose) mdb_warn("stacks: %d unique stacks / %d threads\n", si.si_entries, si.si_count); stacks_array_size = si.si_entries; stacks_array = mdb_zalloc(si.si_entries * sizeof (*stacks_array), UM_SLEEP); cur = stacks_array; for (idx = 0; idx < STACKS_HSIZE; idx++) { stacks_entry_t *sep; for (sep = si.si_hash[idx]; sep != NULL; sep = sep->se_next) *(cur++) = sep; } if (cur != stacks_array + si.si_entries) { mdb_warn("stacks: miscounted array size (%d != size: %d)\n", (cur - stacks_array), stacks_array_size); return (DCMD_ERR); } qsort(stacks_array, si.si_entries, sizeof (*stacks_array), stacks_entry_comp); /* Now that we're done, free the hash table */ stacks_hash = NULL; mdb_free(si.si_hash, STACKS_HSIZE * sizeof (*si.si_hash)); if (tlist == NULL) stacks_state = STACKS_STATE_DONE; if (verbose) mdb_warn("stacks: done\n"); return (DCMD_OK); } static int stacks_has_caller(stacks_entry_t *sep, uintptr_t addr) { uintptr_t laddr = addr; uintptr_t haddr = addr + 1; int idx; char c[MDB_SYM_NAMLEN]; GElf_Sym sym; if (mdb_lookup_by_addr(addr, MDB_SYM_FUZZY, c, sizeof (c), &sym) != -1 && addr == (uintptr_t)sym.st_value) { laddr = (uintptr_t)sym.st_value; haddr = (uintptr_t)sym.st_value + sym.st_size; } for (idx = 0; idx < sep->se_depth; idx++) if (sep->se_stack[idx] >= laddr && sep->se_stack[idx] < haddr) return (1); return (0); } static int stacks_has_module(stacks_entry_t *sep, stacks_module_t *mp) { int idx; for (idx = 0; idx < sep->se_depth; idx++) { if (sep->se_stack[idx] >= mp->sm_text && sep->se_stack[idx] < mp->sm_text + mp->sm_size) return (1); } return (0); } static int stacks_module_find(const char *name, stacks_module_t *mp) { (void) strncpy(mp->sm_name, name, sizeof (mp->sm_name)); if (stacks_module(mp) != 0) return (-1); if (mp->sm_size == 0) { mdb_warn("stacks: module \"%s\" is unknown\n", name); return (-1); } return (0); } static int uintptrcomp(const void *lp, const void *rp) { uintptr_t lhs = *(const uintptr_t *)lp; uintptr_t rhs = *(const uintptr_t *)rp; if (lhs > rhs) return (1); if (lhs < rhs) return (-1); return (0); } /*ARGSUSED*/ int stacks(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { size_t idx; char *seen = NULL; const char *caller_str = NULL; const char *excl_caller_str = NULL; uintptr_t caller = 0, excl_caller = 0; const char *module_str = NULL; const char *excl_module_str = NULL; stacks_module_t module, excl_module; const char *sobj = NULL; const char *excl_sobj = NULL; uintptr_t sobj_ops = 0, excl_sobj_ops = 0; const char *tstate_str = NULL; const char *excl_tstate_str = NULL; uint_t tstate = -1U; uint_t excl_tstate = -1U; uint_t printed = 0; uint_t all = 0; uint_t force = 0; uint_t interesting = 0; uint_t verbose = 0; /* * We have a slight behavior difference between having piped * input and 'addr::stacks'. Without a pipe, we assume the * thread pointer given is a representative thread, and so * we include all similar threads in the system in our output. * * With a pipe, we filter down to just the threads in our * input. */ uint_t addrspec = (flags & DCMD_ADDRSPEC); uint_t only_matching = addrspec && (flags & DCMD_PIPE); mdb_pipe_t p; bzero(&module, sizeof (module)); bzero(&excl_module, sizeof (excl_module)); if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &all, 'f', MDB_OPT_SETBITS, TRUE, &force, 'i', MDB_OPT_SETBITS, TRUE, &interesting, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'c', MDB_OPT_STR, &caller_str, 'C', MDB_OPT_STR, &excl_caller_str, 'm', MDB_OPT_STR, &module_str, 'M', MDB_OPT_STR, &excl_module_str, 's', MDB_OPT_STR, &sobj, 'S', MDB_OPT_STR, &excl_sobj, 't', MDB_OPT_STR, &tstate_str, 'T', MDB_OPT_STR, &excl_tstate_str, NULL) != argc) return (DCMD_USAGE); if (interesting) { if (sobj != NULL || excl_sobj != NULL || tstate_str != NULL || excl_tstate_str != NULL) { mdb_warn( "stacks: -i is incompatible with -[sStT]\n"); return (DCMD_USAGE); } excl_sobj = "CV"; excl_tstate_str = "FREE"; } if (caller_str != NULL) { mdb_set_dot(0); if (mdb_eval(caller_str) != 0) { mdb_warn("stacks: evaluation of \"%s\" failed", caller_str); return (DCMD_ABORT); } caller = mdb_get_dot(); } if (excl_caller_str != NULL) { mdb_set_dot(0); if (mdb_eval(excl_caller_str) != 0) { mdb_warn("stacks: evaluation of \"%s\" failed", excl_caller_str); return (DCMD_ABORT); } excl_caller = mdb_get_dot(); } mdb_set_dot(addr); if (module_str != NULL && stacks_module_find(module_str, &module) != 0) return (DCMD_ABORT); if (excl_module_str != NULL && stacks_module_find(excl_module_str, &excl_module) != 0) return (DCMD_ABORT); if (sobj != NULL && text_to_sobj(sobj, &sobj_ops) != 0) return (DCMD_USAGE); if (excl_sobj != NULL && text_to_sobj(excl_sobj, &excl_sobj_ops) != 0) return (DCMD_USAGE); if (sobj_ops != 0 && excl_sobj_ops != 0) { mdb_warn("stacks: only one of -s and -S can be specified\n"); return (DCMD_USAGE); } if (tstate_str != NULL && text_to_tstate(tstate_str, &tstate) != 0) return (DCMD_USAGE); if (excl_tstate_str != NULL && text_to_tstate(excl_tstate_str, &excl_tstate) != 0) return (DCMD_USAGE); if (tstate != -1U && excl_tstate != -1U) { mdb_warn("stacks: only one of -t and -T can be specified\n"); return (DCMD_USAGE); } /* * If there's an address specified, we're going to further filter * to only entries which have an address in the input. To reduce * overhead (and make the sorted output come out right), we * use mdb_get_pipe() to grab the entire pipeline of input, then * use qsort() and bsearch() to speed up the search. */ if (addrspec) { mdb_get_pipe(&p); if (p.pipe_data == NULL || p.pipe_len == 0) { p.pipe_data = &addr; p.pipe_len = 1; } qsort(p.pipe_data, p.pipe_len, sizeof (uintptr_t), uintptrcomp); /* remove any duplicates in the data */ idx = 0; while (idx < p.pipe_len - 1) { uintptr_t *data = &p.pipe_data[idx]; size_t len = p.pipe_len - idx; if (data[0] == data[1]) { memmove(data, data + 1, (len - 1) * sizeof (*data)); p.pipe_len--; continue; /* repeat without incrementing idx */ } idx++; } seen = mdb_zalloc(p.pipe_len, UM_SLEEP | UM_GC); } /* * Force a cleanup if we're connected to a live system. Never * do a cleanup after the first invocation around the loop. */ force |= (mdb_get_state() == MDB_STATE_RUNNING); if (force && (flags & (DCMD_LOOPFIRST|DCMD_LOOP)) == DCMD_LOOP) force = 0; stacks_cleanup(force); if (stacks_state == STACKS_STATE_CLEAN) { int res = stacks_run(verbose, addrspec ? &p : NULL); if (res != DCMD_OK) return (res); } for (idx = 0; idx < stacks_array_size; idx++) { stacks_entry_t *sep = stacks_array[idx]; stacks_entry_t *cur = sep; int frame; size_t count = sep->se_count; if (addrspec) { stacks_entry_t *head = NULL, *tail = NULL, *sp; size_t foundcount = 0; /* * We use the now-unused hash chain field se_next to * link together the dups which match our list. */ for (sp = sep; sp != NULL; sp = sp->se_dup) { uintptr_t *entry = bsearch(&sp->se_thread, p.pipe_data, p.pipe_len, sizeof (uintptr_t), uintptrcomp); if (entry != NULL) { foundcount++; seen[entry - p.pipe_data]++; if (head == NULL) head = sp; else tail->se_next = sp; tail = sp; sp->se_next = NULL; } } if (head == NULL) continue; /* no match, skip entry */ if (only_matching) { cur = sep = head; count = foundcount; } } if (caller != 0 && !stacks_has_caller(sep, caller)) continue; if (excl_caller != 0 && stacks_has_caller(sep, excl_caller)) continue; if (module.sm_size != 0 && !stacks_has_module(sep, &module)) continue; if (excl_module.sm_size != 0 && stacks_has_module(sep, &excl_module)) continue; if (tstate != -1U) { if (tstate == TSTATE_PANIC) { if (!sep->se_panic) continue; } else if (sep->se_panic || sep->se_tstate != tstate) continue; } if (excl_tstate != -1U) { if (excl_tstate == TSTATE_PANIC) { if (sep->se_panic) continue; } else if (!sep->se_panic && sep->se_tstate == excl_tstate) continue; } if (sobj_ops == SOBJ_ALL) { if (sep->se_sobj_ops == 0) continue; } else if (sobj_ops != 0) { if (sobj_ops != sep->se_sobj_ops) continue; } if (!(interesting && sep->se_panic)) { if (excl_sobj_ops == SOBJ_ALL) { if (sep->se_sobj_ops != 0) continue; } else if (excl_sobj_ops != 0) { if (excl_sobj_ops == sep->se_sobj_ops) continue; } } if (flags & DCMD_PIPE_OUT) { while (sep != NULL) { mdb_printf("%lr\n", sep->se_thread); sep = only_matching ? sep->se_next : sep->se_dup; } continue; } if (all || !printed) { mdb_printf("%%-?s %-8s %-?s %8s%\n", "THREAD", "STATE", "SOBJ", "COUNT"); printed = 1; } do { char state[20]; char sobj[100]; tstate_to_text(cur->se_tstate, cur->se_panic, state, sizeof (state)); sobj_to_text(cur->se_sobj_ops, sobj, sizeof (sobj)); if (cur == sep) mdb_printf("%-?p %-8s %-?s %8d\n", cur->se_thread, state, sobj, count); else mdb_printf("%-?p %-8s %-?s %8s\n", cur->se_thread, state, sobj, "-"); cur = only_matching ? cur->se_next : cur->se_dup; } while (all && cur != NULL); if (sep->se_failed != 0) { char *reason; switch (sep->se_failed) { case FSI_FAIL_NOTINMEMORY: reason = "thread not in memory"; break; case FSI_FAIL_THREADCORRUPT: reason = "thread structure stack info corrupt"; break; case FSI_FAIL_STACKNOTFOUND: reason = "no consistent stack found"; break; default: reason = "unknown failure"; break; } mdb_printf("%?s <%s>\n", "", reason); } for (frame = 0; frame < sep->se_depth; frame++) mdb_printf("%?s %a\n", "", sep->se_stack[frame]); if (sep->se_overflow) mdb_printf("%?s ... truncated ...\n", ""); mdb_printf("\n"); } if (flags & DCMD_ADDRSPEC) { for (idx = 0; idx < p.pipe_len; idx++) if (seen[idx] == 0) mdb_warn("stacks: %p not in thread list\n", p.pipe_data[idx]); } return (DCMD_OK); } /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _MDB_FINDSTACK_H #define _MDB_FINDSTACK_H #include #include #ifdef __cplusplus extern "C" { #endif typedef struct findstack_info { uintptr_t *fsi_stack; /* place to record frames */ uintptr_t fsi_sp; /* stack pointer */ uintptr_t fsi_pc; /* pc */ uintptr_t fsi_sobj_ops; /* sobj_ops */ uint_t fsi_tstate; /* t_state */ uchar_t fsi_depth; /* stack depth */ uchar_t fsi_failed; /* search failed */ uchar_t fsi_overflow; /* stack was deeper than max_depth */ uchar_t fsi_panic; /* thread called panic() */ uchar_t fsi_max_depth; /* stack frames available */ } findstack_info_t; #define FSI_FAIL_BADTHREAD 1 #define FSI_FAIL_NOTINMEMORY 2 #define FSI_FAIL_THREADCORRUPT 3 #define FSI_FAIL_STACKNOTFOUND 4 typedef struct stacks_module { char sm_name[MAXPATHLEN]; /* name of module */ uintptr_t sm_text; /* base address of text in module */ size_t sm_size; /* size of text in module */ } stacks_module_t; extern int findstack(uintptr_t, uint_t, int, const mdb_arg_t *); extern int findstack_debug(uintptr_t, uint_t, int, const mdb_arg_t *); /* * The following routines are implemented in findstack.c, shared across both * genunix and libc. */ extern int stacks(uintptr_t, uint_t, int, const mdb_arg_t *); extern void stacks_cleanup(int); /* * The following routines are specific to their context (kernel vs. user-land) * and are therefore implemented in findstack_subr.c (of which each of genunix * and libc have their own copy). */ extern void stacks_help(void); extern int stacks_findstack(uintptr_t, findstack_info_t *, uint_t); extern void stacks_findstack_cleanup(); extern int stacks_module(stacks_module_t *); extern int findstack_debug_on; #define fs_dprintf(x) \ if (findstack_debug_on) { \ mdb_printf("findstack debug: "); \ /*CSTYLED*/ \ mdb_printf x ; \ } #ifdef __cplusplus } #endif #endif /* _MDB_FINDSTACK_H */ /* * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2020 Joyent, Inc. */ #include #include #include #include #include #include #include #include "findstack.h" #include "thread.h" #include "sobj.h" #define TOO_BIG_FOR_A_STACK (1024 * 1024) #define KTOU(p) ((p) - kbase + ubase) #define UTOK(p) ((p) - ubase + kbase) #define CRAWL_FOUNDALL (-1) #if defined(__i386) || defined(__amd64) struct rwindow { uintptr_t rw_fp; uintptr_t rw_rtn; }; #endif #ifndef STACK_BIAS #define STACK_BIAS 0 #endif /* * Given a stack pointer, try to crawl down it to the bottom. * "frame" is a VA in MDB's address space. * * Returns the number of frames successfully crawled down, or * CRAWL_FOUNDALL if it got to the bottom of the stack. */ static int crawl(uintptr_t frame, uintptr_t kbase, uintptr_t ktop, uintptr_t ubase, int kill_fp, findstack_info_t *fsip) { int levels = 0; fsip->fsi_depth = 0; fsip->fsi_overflow = 0; fs_dprintf(("<0> frame = %p, kbase = %p, ktop = %p, ubase = %p\n", frame, kbase, ktop, ubase)); for (;;) { uintptr_t fp; long *fpp = (long *)&((struct rwindow *)frame)->rw_fp; fs_dprintf(("<1> fpp = %p, frame = %p\n", fpp, frame)); if ((frame & (STACK_ALIGN - 1)) != 0) break; fp = ((struct rwindow *)frame)->rw_fp + STACK_BIAS; if (fsip->fsi_depth < fsip->fsi_max_depth) fsip->fsi_stack[fsip->fsi_depth++] = ((struct rwindow *)frame)->rw_rtn; else fsip->fsi_overflow = 1; fs_dprintf(("<2> fp = %p\n", fp)); if (fp == ktop) return (CRAWL_FOUNDALL); fs_dprintf(("<3> not at base\n")); #if defined(__i386) || defined(__amd64) if (ktop - fp == sizeof (struct rwindow)) { fs_dprintf(("<4> found base\n")); return (CRAWL_FOUNDALL); } #endif fs_dprintf(("<5> fp = %p, kbase = %p, ktop - size = %p\n", fp, kbase, ktop - sizeof (struct rwindow))); if (fp < kbase || fp >= (ktop - sizeof (struct rwindow))) break; frame = KTOU(fp); fs_dprintf(("<6> frame = %p\n", frame)); /* * NULL out the old %fp so we don't go down this stack * more than once. */ if (kill_fp) { fs_dprintf(("<7> fpp = %p\n", fpp)); *fpp = 0; } fs_dprintf(("<8> levels = %d\n", levels)); levels++; } return (levels); } typedef struct mdb_findstack_kthread { struct _sobj_ops *t_sobj_ops; uint_t t_state; uint_t t_flag; ushort_t t_schedflag; caddr_t t_stk; caddr_t t_stkbase; label_t t_pcb; } mdb_findstack_kthread_t; /*ARGSUSED*/ int stacks_findstack(uintptr_t addr, findstack_info_t *fsip, uint_t print_warnings) { mdb_findstack_kthread_t thr; size_t stksz; uintptr_t ubase, utop; uintptr_t kbase, ktop; uintptr_t win, sp; fsip->fsi_failed = 0; fsip->fsi_pc = 0; fsip->fsi_sp = 0; fsip->fsi_depth = 0; fsip->fsi_overflow = 0; if (mdb_ctf_vread(&thr, "kthread_t", "mdb_findstack_kthread_t", addr, print_warnings ? 0 : MDB_CTF_VREAD_QUIET) == -1) { fsip->fsi_failed = FSI_FAIL_BADTHREAD; return (DCMD_ERR); } fsip->fsi_sobj_ops = (uintptr_t)thr.t_sobj_ops; fsip->fsi_tstate = thr.t_state; fsip->fsi_panic = !!(thr.t_flag & T_PANIC); if ((thr.t_schedflag & TS_LOAD) == 0) { if (print_warnings) mdb_warn("thread %p isn't in memory\n", addr); fsip->fsi_failed = FSI_FAIL_NOTINMEMORY; return (DCMD_ERR); } if (thr.t_stk < thr.t_stkbase) { if (print_warnings) mdb_warn( "stack base or stack top corrupt for thread %p\n", addr); fsip->fsi_failed = FSI_FAIL_THREADCORRUPT; return (DCMD_ERR); } kbase = (uintptr_t)thr.t_stkbase; ktop = (uintptr_t)thr.t_stk; stksz = ktop - kbase; #ifdef __amd64 /* * The stack on amd64 is intentionally misaligned, so ignore the top * half-frame. See thread_stk_init(). When handling traps, the frame * is automatically aligned by the hardware, so we only alter ktop if * needed. */ if ((ktop & (STACK_ALIGN - 1)) != 0) ktop -= STACK_ENTRY_ALIGN; #endif /* * If the stack size is larger than a meg, assume that it's bogus. */ if (stksz > TOO_BIG_FOR_A_STACK) { if (print_warnings) mdb_warn("stack size for thread %p is too big to be " "reasonable\n", addr); fsip->fsi_failed = FSI_FAIL_THREADCORRUPT; return (DCMD_ERR); } /* * This could be (and was) a UM_GC allocation. Unfortunately, * stksz tends to be very large. As currently implemented, dcmds * invoked as part of pipelines don't have their UM_GC-allocated * memory freed until the pipeline completes. With stksz in the * neighborhood of 20k, the popular ::walk thread |::findstack * pipeline can easily run memory-constrained debuggers (kmdb) out * of memory. This can be changed back to a gc-able allocation when * the debugger is changed to free UM_GC memory more promptly. */ ubase = (uintptr_t)mdb_alloc(stksz, UM_SLEEP); utop = ubase + stksz; if (mdb_vread((caddr_t)ubase, stksz, kbase) != stksz) { mdb_free((void *)ubase, stksz); if (print_warnings) mdb_warn("couldn't read entire stack for thread %p\n", addr); fsip->fsi_failed = FSI_FAIL_THREADCORRUPT; return (DCMD_ERR); } /* * Try the saved %sp first, if it looks reasonable. */ sp = KTOU((uintptr_t)thr.t_sp + STACK_BIAS); if (sp >= ubase && sp <= utop) { if (crawl(sp, kbase, ktop, ubase, 0, fsip) == CRAWL_FOUNDALL) { fsip->fsi_sp = (uintptr_t)thr.t_sp; #if !defined(__i386) fsip->fsi_pc = (uintptr_t)thr.t_pc; #endif goto found; } } /* * Now walk through the whole stack, starting at the base, * trying every possible "window". */ for (win = ubase; win + sizeof (struct rwindow) <= utop; win += sizeof (struct rwindow *)) { if (crawl(win, kbase, ktop, ubase, 1, fsip) == CRAWL_FOUNDALL) { fsip->fsi_sp = UTOK(win) - STACK_BIAS; goto found; } } /* * We didn't conclusively find the stack. So we'll take another lap, * and print out anything that looks possible. */ if (print_warnings) mdb_printf("Possible stack pointers for thread %p:\n", addr); (void) mdb_vread((caddr_t)ubase, stksz, kbase); for (win = ubase; win + sizeof (struct rwindow) <= utop; win += sizeof (struct rwindow *)) { uintptr_t fp = ((struct rwindow *)win)->rw_fp; int levels; if ((levels = crawl(win, kbase, ktop, ubase, 1, fsip)) > 1) { if (print_warnings) mdb_printf(" %p (%d)\n", fp, levels); } else if (levels == CRAWL_FOUNDALL) { /* * If this is a live system, the stack could change * between the two mdb_vread(ubase, utop, kbase)'s, * and we could have a fully valid stack here. */ fsip->fsi_sp = UTOK(win) - STACK_BIAS; goto found; } } fsip->fsi_depth = 0; fsip->fsi_overflow = 0; fsip->fsi_failed = FSI_FAIL_STACKNOTFOUND; mdb_free((void *)ubase, stksz); return (DCMD_ERR); found: mdb_free((void *)ubase, stksz); return (DCMD_OK); } void stacks_findstack_cleanup() {} /*ARGSUSED*/ int stacks_module_cb(uintptr_t addr, const modctl_t *mp, stacks_module_t *smp) { char mod_modname[MODMAXNAMELEN]; if (!mp->mod_modname) return (WALK_NEXT); if (mdb_readstr(mod_modname, sizeof (mod_modname), (uintptr_t)mp->mod_modname) == -1) { mdb_warn("failed to read mod_modname in \"modctl\" walk"); return (WALK_ERR); } if (strcmp(smp->sm_name, mod_modname)) return (WALK_NEXT); smp->sm_text = (uintptr_t)mp->mod_text; smp->sm_size = mp->mod_text_size; return (WALK_DONE); } int stacks_module(stacks_module_t *smp) { if (mdb_walk("modctl", (mdb_walk_cb_t)stacks_module_cb, smp) != 0) { mdb_warn("cannot walk \"modctl\""); return (-1); } return (0); } /*ARGSUSED*/ static void print_sobj_help(int type, const char *name, const char *ops_name, void *ign) { mdb_printf(" %s", name); } /*ARGSUSED*/ static void print_tstate_help(uint_t state, const char *name, void *ignored) { mdb_printf(" %s", name); } void stacks_help(void) { mdb_printf( "::stacks processes all of the thread stacks on the system, grouping\n" "together threads which have the same:\n" "\n" " * Thread state,\n" " * Sync object type, and\n" " * PCs in their stack trace.\n" "\n" "The default output (no address or options) is just a dump of the thread\n" "groups in the system. For a view of active threads, use \"::stacks -i\",\n" "which filters out FREE threads (interrupt threads which are currently\n" "inactive) and threads sleeping on a CV. (Note that those threads may still\n" "be noteworthy; this is just for a first glance.) More general filtering\n" "options are described below, in the \"FILTERS\" section.\n" "\n" "::stacks can be used in a pipeline. The input to ::stacks is one or more\n" "thread pointers. For example, to get a summary of threads in a process,\n" "you can do:\n" "\n" " %procp%::walk thread | ::stacks\n" "\n" "When output into a pipe, ::stacks prints all of the threads input,\n" "filtered by the given filtering options. This means that multiple\n" "::stacks invocations can be piped together to achieve more complicated\n" "filters. For example, to get threads which have both 'fop_read' and\n" "'cv_wait_sig_swap' in their stack trace, you could do:\n" "\n" " ::stacks -c fop_read | ::stacks -c cv_wait_sig_swap_core\n" "\n" "To get the full list of threads in each group, use the '-a' flag:\n" "\n" " ::stacks -a\n" "\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -a Print all of the grouped threads, instead of just a count.\n" " -f Force a re-run of the thread stack gathering.\n" " -v Be verbose about thread stack gathering.\n" "\n"); mdb_dec_indent(2); mdb_printf("%FILTERS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -i Show active threads; equivalent to '-S CV -T FREE'.\n" " -c func[+offset]\n" " Only print threads whose stacks contain func/func+offset.\n" " -C func[+offset]\n" " Only print threads whose stacks do not contain func/func+offset.\n" " -m module\n" " Only print threads whose stacks contain functions from module.\n" " -M module\n" " Only print threads whose stacks do not contain functions from\n" " module.\n" " -s {type | ALL}\n" " Only print threads which are on a 'type' synchronization object\n" " (SOBJ).\n" " -S {type | ALL}\n" " Only print threads which are not on a 'type' SOBJ.\n" " -t tstate\n" " Only print threads which are in thread state 'tstate'.\n" " -T tstate\n" " Only print threads which are not in thread state 'tstate'.\n" "\n"); mdb_printf(" SOBJ types:"); sobj_type_walk(print_sobj_help, NULL); mdb_printf("\n"); mdb_printf("Thread states:"); thread_walk_states(print_tstate_help, NULL); mdb_printf(" panic\n"); } /* * 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. * * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "nvpair.h" int ereportq_pend_walk_init(mdb_walk_state_t *wsp) { errorq_t eq; uintptr_t addr; if (wsp->walk_addr == 0 && mdb_readvar(&addr, "ereport_errorq") == -1) { mdb_warn("failed to read ereport_errorq"); return (WALK_ERR); } if (mdb_vread(&eq, sizeof (eq), addr) == -1) { mdb_warn("failed to read ereport_errorq at %p", addr); return (WALK_ERR); } if (!(eq.eq_flags & ERRORQ_NVLIST)) { mdb_warn("errorq at %p does not service ereports", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)eq.eq_pend; return (WALK_NEXT); } int ereportq_pend_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; nvlist_t nvl; errorq_nvelem_t eqnp; errorq_elem_t elem; if (addr == 0) return (WALK_DONE); if (mdb_vread(&elem, sizeof (elem), addr) != sizeof (elem) || mdb_vread(&eqnp, sizeof (eqnp), (uintptr_t)elem.eqe_data) != sizeof (eqnp) || mdb_vread(&nvl, sizeof (nvl), (uintptr_t)eqnp.eqn_nvl) != sizeof (nvl)) { mdb_warn("failed to read ereportq element at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)elem.eqe_prev; return (wsp->walk_callback((uintptr_t)eqnp.eqn_nvl, &nvl, wsp->walk_cbdata)); } int ereportq_dump_walk_init(mdb_walk_state_t *wsp) { errorq_t eq; uintptr_t addr; if (wsp->walk_addr == 0 && mdb_readvar(&addr, "ereport_errorq") == -1) { mdb_warn("failed to read ereport_errorq"); return (WALK_ERR); } if (mdb_vread(&eq, sizeof (eq), addr) == -1) { mdb_warn("failed to read ereport_errorq at %p", addr); return (WALK_ERR); } if (!(eq.eq_flags & ERRORQ_NVLIST)) { mdb_warn("errorq at %p does not service ereports", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)eq.eq_dump; return (WALK_NEXT); } int ereportq_dump_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; nvlist_t nvl; errorq_nvelem_t eqnp; errorq_elem_t elem; if (addr == 0) return (WALK_DONE); if (mdb_vread(&elem, sizeof (elem), addr) != sizeof (elem) || mdb_vread(&eqnp, sizeof (eqnp), (uintptr_t)elem.eqe_data) != sizeof (eqnp) || mdb_vread(&nvl, sizeof (nvl), (uintptr_t)eqnp.eqn_nvl) != sizeof (nvl)) { mdb_warn("failed to read ereportq element at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)elem.eqe_dump; return (wsp->walk_callback((uintptr_t)eqnp.eqn_nvl, &nvl, wsp->walk_cbdata)); } /*ARGSUSED*/ int ereport(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int ret; uint_t opt_v = 0; char *class = NULL; uint64_t ena = 0; nvlist_t nvl; nvpriv_t nvpriv; i_nvp_t *nvcur, i_nvp; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&nvl, sizeof (nvl), addr) == -1) { mdb_warn("failed to read nvlist at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags) && !opt_v) { mdb_printf("ENA CLASS\n"); } /* * The following code attempts to pretty print the ereport class * and ENA. The code uses project private macros from libnvpair * that could change and break this functionality. If we are unable * to get a valid class and ENA from the nvpair list, we revert to * dumping the nvlist (same as opt_v). */ if (mdb_vread(&nvpriv, sizeof (nvpriv), nvl.nvl_priv) == -1) { mdb_warn("failed to read nvpriv at %p", nvl.nvl_priv); return (DCMD_ERR); } for (nvcur = nvpriv.nvp_list; nvcur != NULL; nvcur = i_nvp.nvi_next) { nvpair_t *nvp, *nvpair; int32_t size; if (opt_v) break; if (mdb_vread(&i_nvp, sizeof (i_nvp), (uintptr_t)nvcur) == -1) { mdb_warn("failed to read i_nvp at %p", nvcur); return (DCMD_ERR); } nvp = &i_nvp.nvi_nvp; size = NVP_SIZE(nvp); if (size == 0) { mdb_warn("nvpair of size zero at %p", nvp); return (DCMD_OK); } /* read in the entire nvpair */ nvpair = mdb_alloc(size, UM_SLEEP | UM_GC); if (mdb_vread(nvpair, size, (uintptr_t)&nvcur->nvi_nvp) == -1) { mdb_warn("failed to read nvpair and data at %p", nvp); return (DCMD_ERR); } if (strcmp(FM_CLASS, NVP_NAME(nvpair)) == 0 && NVP_TYPE(nvpair) == DATA_TYPE_STRING && class == NULL) { char *p = (char *)NVP_VALUE(nvpair); class = mdb_zalloc(strlen(p) + 1, UM_SLEEP | UM_GC); bcopy(p, class, strlen(p)); } else if (strcmp(FM_EREPORT_ENA, NVP_NAME(nvpair)) == 0 && NVP_TYPE(nvpair) == DATA_TYPE_UINT64 && ena == 0) { bcopy(NVP_VALUE(nvpair), (char *)&ena, sizeof (uint64_t)); } if (class != NULL && ena != 0) { mdb_printf("0x%016llx %s\n", ena, class); return (DCMD_OK); } } /* * Dump entire nvlist */ ret = mdb_call_dcmd("nvlist", addr, flags | DCMD_ADDRSPEC, 0, argv); mdb_printf("\n"); return (ret); } /* * 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. */ #ifndef _FM_H #define _FM_H #ifdef __cplusplus extern "C" { #endif #include extern int ereportq_dump_walk_init(mdb_walk_state_t *); extern int ereportq_dump_walk_step(mdb_walk_state_t *); extern int ereportq_pend_walk_init(mdb_walk_state_t *); extern int ereportq_pend_walk_step(mdb_walk_state_t *); extern int ereport(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _FM_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2013 by Delphix. All rights reserved. */ /* * This file implements the mdb ::gcore command. The command relies on the * libproc Pgcore function to actually generate the core file but we provide * our own ops vector to populate data required by Pgcore. The ops vector * function implementations simulate the functionality implemented by procfs. * The data provided by some of the ops vector functions is not complete * (missing data is documented in function headers) but there is enough * information to generate a core file that can be loaded into mdb. * * Currently only x86 is supported. ISA-dependent functions are implemented * in gcore_isadep.c. */ #ifndef _KMDB /* * The kernel has its own definition of exit which has a different signature * than the user space definition. This seems to be the standard way to deal * with this. */ #define exit kern_exit #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 #undef exit #include #include #include #include #include #include "avl.h" #ifdef _LP64 #define LSPAN(type) (P2ROUNDUP(sizeof (type), 16)) #else #define LSPAN(type) (P2ROUNDUP(sizeof (type), 8)) #endif #define vpgtob(n) ((n) * sizeof (struct vpage)) /* Macros to invoke gcore seg operations */ #define GSOP_INIT(_gs) (_gs)->gs_ops->gsop_init((_gs)) #define GSOP_FINI(_gs) (_gs)->gs_ops->gsop_fini((_gs)) #define GSOP_INCORE(_gs, _addr, _eaddr) \ (_gs)->gs_ops->gsop_incore((_gs), (_addr), (_eaddr)) #define GSOP_GETPROT(_gs, _addr) \ (_gs)->gs_ops->gsop_getprot((_gs), (_addr)) #define GSOP_GETOFFSET(_gs, _addr) \ (_gs)->gs_ops->gsop_getoffset((_gs), (_addr)) #define GSOP_GETTYPE(_gs, _addr) \ (_gs)->gs_ops->gsop_gettype((_gs), (_addr)) #define GSOP_NAME(_gs, _name, _size) \ (_gs)->gs_ops->gsop_name((_gs), (_name), (_size)) #define GSOP_NORESERVE(_gs) \ (_gs)->gs_ops->gsop_noreserve((_gs)) #ifdef GCORE_DEBUG #define dprintf(...) mdb_printf(__VA_ARGS__) #else #define dprintf(...) #endif /* Callback function type for processing lwp entries */ typedef int (*lwp_callback_t)(mdb_proc_t *, lwpent_t *, void *); /* Private data */ static uintptr_t gcore_segvn_ops; static priv_impl_info_t prinfo; static sclass_t *gcore_sclass; static uintptr_t gcore_kas; static boolean_t gcore_initialized = B_FALSE; typedef int (*gsop_init_t)(gcore_seg_t *); typedef void (*gsop_fini_t)(gcore_seg_t *); typedef u_offset_t (*gsop_incore_t)(gcore_seg_t *, u_offset_t, u_offset_t); typedef uint_t (*gsop_getprot_t)(gcore_seg_t *, u_offset_t); typedef int (*gsop_getoffset_t)(gcore_seg_t *, u_offset_t); typedef void (*gsop_name_t)(gcore_seg_t *, char *name, size_t size); typedef int (*gsop_gettype_t)(gcore_seg_t *, u_offset_t); typedef boolean_t (*gsop_noreserve_t)(gcore_seg_t *); typedef struct gcore_segops { gsop_init_t gsop_init; gsop_fini_t gsop_fini; gsop_incore_t gsop_incore; gsop_getprot_t gsop_getprot; gsop_getoffset_t gsop_getoffset; gsop_name_t gsop_name; gsop_gettype_t gsop_gettype; gsop_noreserve_t gsop_noreserve; } gcore_segops_t; static void map_list_free(prmap_node_t *); static uintptr_t gcore_prchoose(mdb_proc_t *); /* * Segvn ops */ static int gsvn_init(gcore_seg_t *); static void gsvn_fini(gcore_seg_t *); static u_offset_t gsvn_incore(gcore_seg_t *, u_offset_t, u_offset_t); static uint_t gsvn_getprot(gcore_seg_t *, u_offset_t); static int gsvn_getoffset(gcore_seg_t *, u_offset_t); static void gsvn_name(gcore_seg_t *, char *, size_t); static int gsvn_gettype(gcore_seg_t *, u_offset_t); static boolean_t gsvn_noreserve(gcore_seg_t *); static gcore_segops_t gsvn_ops = { .gsop_init = gsvn_init, .gsop_fini = gsvn_fini, .gsop_incore = gsvn_incore, .gsop_getprot = gsvn_getprot, .gsop_getoffset = gsvn_getoffset, .gsop_name = gsvn_name, .gsop_gettype = gsvn_gettype, .gsop_noreserve = gsvn_noreserve }; static int gsvn_init(gcore_seg_t *gs) { mdb_seg_t *seg = gs->gs_seg; mdb_segvn_data_t *svd = NULL; struct vpage *vpage = NULL; size_t nvpage = 0; if (seg->s_data != 0) { svd = mdb_alloc(sizeof (*svd), UM_SLEEP); if (mdb_ctf_vread(svd, "segvn_data_t", "mdb_segvn_data_t", seg->s_data, 0) == -1) { goto error; } if (svd->pageprot != 0) { nvpage = seg_pages(seg); dprintf("vpage count: %d\n", nvpage); vpage = mdb_alloc(vpgtob(nvpage), UM_SLEEP); if (mdb_vread(vpage, vpgtob(nvpage), (uintptr_t)svd->vpage) != vpgtob(nvpage)) { mdb_warn("Failed to read vpages from %p\n", svd->vpage); goto error; } svd->vpage = vpage; } else { svd->vpage = NULL; } gs->gs_data = svd; } else { gs->gs_data = NULL; } return (0); error: mdb_free(vpage, vpgtob(nvpage)); mdb_free(svd, sizeof (*svd)); return (-1); } /*ARGSUSED*/ static int gsvn_getoffset(gcore_seg_t *gs, u_offset_t addr) { mdb_segvn_data_t *svd = gs->gs_data; mdb_seg_t *seg = gs->gs_seg; return (svd->offset + (uintptr_t)(addr - seg->s_base)); } static void gsvn_name(gcore_seg_t *gs, char *name, size_t size) { mdb_segvn_data_t *svd = gs->gs_data; name[0] = '\0'; if (svd->vp != 0) { mdb_seg_t *seg = gs->gs_seg; mdb_as_t as; mdb_proc_t p; mdb_vnode_t vn; if (mdb_ctf_vread(&vn, "vnode_t", "mdb_vnode_t", svd->vp, 0) == -1) { return; } if (mdb_ctf_vread(&as, "struct as", "mdb_as_t", seg->s_as, 0) == -1) { return; } if (mdb_ctf_vread(&p, "proc_t", "mdb_proc_t", as.a_proc, 0) == -1) { return; } if (vn.v_type == VREG && svd->vp == p.p_exec) { (void) strncpy(name, "a.out", size); } /* * procfs has more logic here to construct a name using * vfs/vnode identifiers but didn't seem worthwhile to add * here. */ } } /*ARGSUSED*/ static int gsvn_gettype(gcore_seg_t *gs, u_offset_t addr) { return (0); } static void gsvn_fini(gcore_seg_t *gs) { mdb_segvn_data_t *svd = gs->gs_data; if (svd != NULL) { if (svd->vpage != NULL) { size_t nvpage = seg_pages(gs->gs_seg); mdb_free(svd->vpage, vpgtob(nvpage)); } mdb_free(svd, sizeof (*svd)); } } static boolean_t gsvn_noreserve(gcore_seg_t *gs) { mdb_segvn_data_t *svd = gs->gs_data; if (svd == NULL) { return (B_FALSE); } if (svd->flags & MAP_NORESERVE) { mdb_vnode_t vn; if (svd->vp == 0) { return (B_TRUE); } if (mdb_ctf_vread(&vn, "vnode_t", "mdb_vnode_t", svd->vp, 0) == -1) { return (B_FALSE); } if (vn.v_type != VREG) { return (B_TRUE); } } return (B_FALSE); } static uintptr_t gcore_anon_get_ptr(uintptr_t ah_addr, ulong_t an_idx) { mdb_anon_hdr_t ah; uintptr_t anon_addr; uintptr_t anon_ptr; if (mdb_ctf_vread(&ah, "struct anon_hdr", "mdb_anon_hdr_t", ah_addr, 0) == -1) { return (0); } /* * Single level case. */ if ((ah.size <= ANON_CHUNK_SIZE) || (ah.flags & ANON_ALLOC_FORCE)) { anon_addr = ah.array_chunk + (sizeof (anon_ptr) * an_idx); if (mdb_vread(&anon_ptr, sizeof (anon_ptr), anon_addr) != sizeof (anon_ptr)) { mdb_warn("Failed to read anon_ptr from %p (1 level)\n", anon_addr); return (0); } return (anon_ptr & ANON_PTRMASK); } /* * 2 level case. */ anon_addr = ah.array_chunk + (sizeof (anon_ptr) * (an_idx >> ANON_CHUNK_SHIFT)); if (mdb_vread(&anon_ptr, sizeof (anon_ptr), anon_addr) != sizeof (anon_ptr)) { mdb_warn("Failed to read anon_ptr from %p (2a level)\n", anon_addr); return (0); } if (anon_ptr == 0) { return (0); } anon_addr = anon_ptr + (sizeof (anon_ptr) * (an_idx & ANON_CHUNK_OFF)); if (mdb_vread(&anon_ptr, sizeof (anon_ptr), anon_addr) != sizeof (anon_ptr)) { mdb_warn("Failed to read anon_ptr from %p (2b level)\n", anon_addr); return (0); } return (anon_ptr & ANON_PTRMASK); } static void gcore_anon_get(uintptr_t ahp, ulong_t an_index, uintptr_t *vp, u_offset_t *off) { mdb_anon_t anon; uintptr_t ap; ap = gcore_anon_get_ptr(ahp, an_index); if (ap != 0) { if (mdb_ctf_vread(&anon, "struct anon", "mdb_anon_t", ap, 0) == -1) { return; } *vp = anon.an_vp; *off = anon.an_off; } else { *vp = 0; *off = 0; } } static u_offset_t gsvn_incore(gcore_seg_t *gs, u_offset_t addr, u_offset_t eaddr) { mdb_segvn_data_t *svd = gs->gs_data; mdb_seg_t *seg = gs->gs_seg; mdb_amp_t amp; u_offset_t offset; uintptr_t vp; size_t p, ep; if (svd->amp != 0 && mdb_ctf_vread(&, "amp_t", "mdb_amp_t", svd->amp, 0) == -1) { return (eaddr); } p = seg_page(seg, addr); ep = seg_page(seg, eaddr); for (; p < ep; p++, addr += PAGESIZE) { /* First check the anon map */ if (svd->amp != 0) { gcore_anon_get(amp.ahp, svd->anon_index + p, &vp, &offset); if (vp != 0 && mdb_page_lookup(vp, offset) != 0) { break; } } /* Now check the segment's vnode */ vp = svd->vp; offset = svd->offset + (addr - gs->gs_seg->s_base); if (mdb_page_lookup(vp, offset) != 0) { break; } dprintf("amp: %p vp: %p addr: %p offset: %p not in core!\n", svd->amp, svd->vp, addr, offset); } return (addr); } static uint_t gsvn_getprot(gcore_seg_t *gs, u_offset_t addr) { mdb_segvn_data_t *svd = gs->gs_data; mdb_seg_t *seg = gs->gs_seg; if (svd->pageprot == 0) { return (svd->prot); } dprintf("addr: %p pgno: %p\n", addr, seg_page(seg, addr)); return (VPP_PROT(&svd->vpage[seg_page(seg, addr)])); } /* * Helper functions for constructing the process address space maps. */ /*ARGSUSED*/ static int as_segat_cb(uintptr_t seg_addr, const void *aw_buff, void *arg) { as_segat_cbarg_t *as_segat_arg = arg; mdb_seg_t seg; if (mdb_ctf_vread(&seg, "struct seg", "mdb_seg_t", seg_addr, 0) == -1) { return (WALK_ERR); } if (as_segat_arg->addr < seg.s_base) { return (WALK_NEXT); } if (as_segat_arg->addr >= seg.s_base + seg.s_size) { return (WALK_NEXT); } as_segat_arg->res = seg_addr; return (WALK_DONE); } /* * Find a segment containing addr. */ static uintptr_t gcore_as_segat(uintptr_t as_addr, uintptr_t addr) { as_segat_cbarg_t as_segat_arg; uintptr_t segtree_addr; as_segat_arg.addr = addr; as_segat_arg.res = 0; segtree_addr = as_addr + mdb_ctf_offsetof_by_name("struct as", "a_segtree"); (void) avl_walk_mdb(segtree_addr, as_segat_cb, &as_segat_arg); return (as_segat_arg.res); } static uintptr_t gcore_break_seg(mdb_proc_t *p) { uintptr_t addr = p->p_brkbase; if (p->p_brkbase != 0) addr += p->p_brksize - 1; return (gcore_as_segat(p->p_as, addr)); } static u_offset_t gcore_vnode_size(uintptr_t vnode_addr) { mdb_vnode_t vnode; mdb_vnodeops_t vnodeops; char vops_name[128]; if (mdb_ctf_vread(&vnode, "vnode_t", "mdb_vnode_t", vnode_addr, 0) == -1) { return (-1); } if (mdb_ctf_vread(&vnodeops, "vnodeops_t", "mdb_vnodeops_t", vnode.v_op, 0) == -1) { return (-1); } if (mdb_readstr(vops_name, sizeof (vops_name), vnodeops.vnop_name) == -1) { mdb_warn("Failed to read vnop_name from %p\n", vnodeops.vnop_name); return (-1); } if (strcmp(vops_name, "zfs") == 0) { mdb_znode_t znode; if (mdb_ctf_vread(&znode, "znode_t", "mdb_znode_t", vnode.v_data, 0) == -1) { return (-1); } return (znode.z_size); } if (strcmp(vops_name, "tmpfs") == 0) { mdb_tmpnode_t tnode; if (mdb_ctf_vread(&tnode, "struct tmpnode", "mdb_tmpnode_t", vnode.v_data, 0) == -1) { return (-1); } return (tnode.tn_attr.va_size); } /* Unknown file system type. */ mdb_warn("Unknown fs type: %s\n", vops_name); return (-1); } static uint64_t gcore_pr_getsegsize(mdb_seg_t *seg) { uint64_t size = seg->s_size; if (seg->s_ops == gcore_segvn_ops) { mdb_segvn_data_t svd; if (mdb_ctf_vread(&svd, "segvn_data_t", "mdb_segvn_data_t", seg->s_data, 0) == -1) { return (-1); } if (svd.vp != 0) { u_offset_t fsize; u_offset_t offset; fsize = gcore_vnode_size(svd.vp); if (fsize == -1) { return (-1); } offset = svd.offset; if (fsize < offset) { fsize = 0; } else { fsize -= offset; } fsize = roundup(fsize, PAGESIZE); } return (size); } return (size); } /*ARGSUSED*/ static int gcore_getwatchprot_cb(uintptr_t node_addr, const void *aw_buff, void *arg) { getwatchprot_cbarg_t *cbarg = arg; if (mdb_ctf_vread(&cbarg->wp, "struct watched_page", "mdb_watched_page_t", node_addr, 0) == -1) { return (WALK_ERR); } if (cbarg->wp.wp_vaddr == cbarg->wp_vaddr) { cbarg->found = B_TRUE; return (WALK_DONE); } return (WALK_NEXT); } static void gcore_getwatchprot(uintptr_t as_addr, u_offset_t addr, uint_t *prot) { getwatchprot_cbarg_t cbarg; uintptr_t wp_addr; cbarg.wp_vaddr = (uintptr_t)addr & (uintptr_t)PAGEMASK; cbarg.found = B_FALSE; wp_addr = as_addr + mdb_ctf_offsetof_by_name("struct as", "a_wpage"); (void) avl_walk_mdb(wp_addr, gcore_getwatchprot_cb, &cbarg); if (cbarg.found) { *prot = cbarg.wp.wp_oprot; } } static u_offset_t gcore_pr_nextprot(gcore_seg_t *gs, u_offset_t *saddrp, u_offset_t eaddr, uint_t *protp) { uint_t prot, nprot; u_offset_t addr = *saddrp; uintptr_t as_addr = gs->gs_seg->s_as; int noreserve = 0; noreserve = GSOP_NORESERVE(gs); dprintf("addr: %p noreserve: %d\n", addr, noreserve); if (noreserve) { addr = GSOP_INCORE(gs, addr, eaddr); if (addr == eaddr) { prot = 0; *saddrp = addr; goto out; } } prot = GSOP_GETPROT(gs, addr); gcore_getwatchprot(as_addr, addr, &prot); *saddrp = addr; for (addr += PAGESIZE; addr < eaddr; addr += PAGESIZE) { /* Discontinuity */ if (noreserve && GSOP_INCORE(gs, addr, eaddr) != addr) { goto out; } nprot = GSOP_GETPROT(gs, addr); gcore_getwatchprot(as_addr, addr, &nprot); if (nprot != prot) { break; } } out: *protp = prot; return (addr); } /* * Get the page protection for the given start address. * - saddrp: in - start address * out - contains address of first in core page * - naddrp: out - address of next in core page that has different protection * - eaddr: in - end address */ static uint_t gcore_pr_getprot(gcore_seg_t *gs, u_offset_t *saddrp, u_offset_t *naddrp, u_offset_t eaddr) { u_offset_t naddr; uint_t prot; dprintf("seg: %p saddr: %p eaddr: %p\n", gs->gs_seg, *saddrp, eaddr); naddr = gcore_pr_nextprot(gs, saddrp, eaddr, &prot); dprintf("seg: %p saddr: %p naddr: %p eaddr: %p\n", gs->gs_seg, *saddrp, naddr, eaddr); *naddrp = naddr; return (prot); } static gcore_seg_t * gcore_seg_create(mdb_seg_t *seg) { gcore_seg_t *gs; gs = mdb_alloc(sizeof (*gs), UM_SLEEP); gs->gs_seg = seg; if (seg->s_ops == gcore_segvn_ops) { gs->gs_ops = &gsvn_ops; } else { mdb_warn("Unhandled segment type, ops: %p\n", seg->s_ops); goto error; } if (GSOP_INIT(gs) != 0) { goto error; } return (gs); error: mdb_free(gs, sizeof (*gs)); return (NULL); } static void gcore_seg_destroy(gcore_seg_t *gs) { GSOP_FINI(gs); mdb_free(gs, sizeof (*gs)); } /*ARGSUSED*/ static int read_maps_cb(uintptr_t seg_addr, const void *aw_buff, void *arg) { read_maps_cbarg_t *cbarg = arg; mdb_segvn_data_t svd; mdb_seg_t s; mdb_seg_t *seg; uint_t prot; gcore_seg_t *gs; uintptr_t eaddr; u_offset_t saddr, baddr; prmap_node_t *mnode; prmap_t *mp; if (mdb_ctf_vread(&s, "struct seg", "mdb_seg_t", seg_addr, 0) == -1) { return (WALK_ERR); } seg = &s; eaddr = seg->s_base + gcore_pr_getsegsize(seg); if ((gs = gcore_seg_create(seg)) == NULL) { mdb_warn("gcore_seg_create failed!\n"); return (WALK_ERR); } /* * Iterate from the base of the segment to its end, allocating a new * prmap_node at each address boundary (baddr) between ranges that * have different virtual memory protections. */ for (saddr = seg->s_base; saddr < eaddr; saddr = baddr) { prot = gcore_pr_getprot(gs, &saddr, &baddr, eaddr); if (saddr == eaddr) { break; } mnode = mdb_alloc(sizeof (*mnode), UM_SLEEP); mnode->next = NULL; mp = &mnode->m; if (cbarg->map_head == NULL) { cbarg->map_head = cbarg->map_tail = mnode; } else { cbarg->map_tail->next = mnode; cbarg->map_tail = mnode; } cbarg->map_len++; mp->pr_vaddr = (uintptr_t)saddr; mp->pr_size = baddr - saddr; mp->pr_offset = GSOP_GETOFFSET(gs, saddr); mp->pr_mflags = 0; if (prot & PROT_READ) mp->pr_mflags |= MA_READ; if (prot & PROT_WRITE) mp->pr_mflags |= MA_WRITE; if (prot & PROT_EXEC) mp->pr_mflags |= MA_EXEC; if (GSOP_GETTYPE(gs, saddr) & MAP_SHARED) mp->pr_mflags |= MA_SHARED; if (GSOP_GETTYPE(gs, saddr) & MAP_NORESERVE) mp->pr_mflags |= MA_NORESERVE; if (seg->s_ops == gcore_segvn_ops) { if (mdb_ctf_vread(&svd, "segvn_data_t", "mdb_segvn_data_t", seg->s_data, 0) == 0 && svd.vp == 0) { mp->pr_mflags |= MA_ANON; } } if (seg_addr == cbarg->brkseg) mp->pr_mflags |= MA_BREAK; else if (seg_addr == cbarg->stkseg) mp->pr_mflags |= MA_STACK; mp->pr_pagesize = PAGESIZE; /* * Manufacture a filename for the "object" dir. */ GSOP_NAME(gs, mp->pr_mapname, sizeof (mp->pr_mapname)); } gcore_seg_destroy(gs); return (0); } /* * Helper functions for retrieving process and lwp state. */ static int pcommon_init(mdb_proc_t *p, pcommon_t *pc) { mdb_pid_t pid; mdb_sess_t sess; mdb_task_t task; mdb_kproject_t proj; mdb_zone_t zone; pc->pc_nlwp = p->p_lwpcnt; pc->pc_nzomb = p->p_zombcnt; if (mdb_ctf_vread(&pid, "struct pid", "mdb_pid_t", p->p_pidp, 0) == -1) { return (-1); } pc->pc_pid = pid.pid_id; pc->pc_ppid = p->p_ppid; if (mdb_ctf_vread(&pid, "struct pid", "mdb_pid_t", p->p_pgidp, 0) == -1) { return (-1); } pc->pc_pgid = pid.pid_id; if (mdb_ctf_vread(&sess, "sess_t", "mdb_sess_t", p->p_sessp, 0) == -1) { return (-1); } if (mdb_ctf_vread(&pid, "struct pid", "mdb_pid_t", sess.s_sidp, 0) == -1) { return (-1); } pc->pc_sid = pid.pid_id; if (mdb_ctf_vread(&task, "task_t", "mdb_task_t", p->p_task, 0) == -1) { return (-1); } pc->pc_taskid = task.tk_tkid; if (mdb_ctf_vread(&proj, "kproject_t", "mdb_kproject_t", task.tk_proj, 0) == -1) { return (-1); } pc->pc_projid = proj.kpj_id; if (mdb_ctf_vread(&zone, "zone_t", "mdb_zone_t", p->p_zone, 0) == -1) { return (-1); } pc->pc_zoneid = zone.zone_id; switch (p->p_model) { case DATAMODEL_ILP32: pc->pc_dmodel = PR_MODEL_ILP32; break; case DATAMODEL_LP64: pc->pc_dmodel = PR_MODEL_LP64; break; } return (0); } static uintptr_t gcore_prchoose(mdb_proc_t *p) { mdb_kthread_t kthr; mdb_kthread_t *t = &kthr; ushort_t t_istop_whystop = 0; ushort_t t_istop_whatstop = 0; uintptr_t t_addr = 0; uintptr_t t_onproc = 0; /* running on processor */ uintptr_t t_run = 0; /* runnable, on disp queue */ uintptr_t t_sleep = 0; /* sleeping */ uintptr_t t_susp = 0; /* suspended stop */ uintptr_t t_jstop = 0; /* jobcontrol stop, w/o directed stop */ uintptr_t t_jdstop = 0; /* jobcontrol stop with directed stop */ uintptr_t t_req = 0; /* requested stop */ uintptr_t t_istop = 0; /* event-of-interest stop */ uintptr_t t_dtrace = 0; /* DTrace stop */ /* * If the agent lwp exists, it takes precedence over all others. */ if ((t_addr = p->p_agenttp) != 0) { return (t_addr); } if ((t_addr = p->p_tlist) == 0) /* start at the head of the list */ return (t_addr); do { /* for each lwp in the process */ if (mdb_ctf_vread(&kthr, "kthread_t", "mdb_kthread_t", t_addr, 0) == -1) { return (0); } if (VSTOPPED(t)) { /* virtually stopped */ if (t_req == 0) t_req = t_addr; continue; } switch (t->t_state) { default: return (0); case TS_SLEEP: if (t_sleep == 0) t_sleep = t_addr; break; case TS_RUN: case TS_WAIT: if (t_run == 0) t_run = t_addr; break; case TS_ONPROC: if (t_onproc == 0) t_onproc = t_addr; break; /* * Threads in the zombie state have the lowest * priority when selecting a representative lwp. */ case TS_ZOMB: break; case TS_STOPPED: switch (t->t_whystop) { case PR_SUSPENDED: if (t_susp == 0) t_susp = t_addr; break; case PR_JOBCONTROL: if (t->t_proc_flag & TP_PRSTOP) { if (t_jdstop == 0) t_jdstop = t_addr; } else { if (t_jstop == 0) t_jstop = t_addr; } break; case PR_REQUESTED: if (t->t_dtrace_stop && t_dtrace == 0) t_dtrace = t_addr; else if (t_req == 0) t_req = t_addr; break; case PR_SYSENTRY: case PR_SYSEXIT: case PR_SIGNALLED: case PR_FAULTED: /* * Make an lwp calling exit() be the * last lwp seen in the process. */ if (t_istop == 0 || (t_istop_whystop == PR_SYSENTRY && t_istop_whatstop == SYS_exit)) { t_istop = t_addr; t_istop_whystop = t->t_whystop; t_istop_whatstop = t->t_whatstop; } break; case PR_CHECKPOINT: /* can't happen? */ break; default: return (0); } break; } } while ((t_addr = t->t_forw) != p->p_tlist); if (t_onproc) t_addr = t_onproc; else if (t_run) t_addr = t_run; else if (t_sleep) t_addr = t_sleep; else if (t_jstop) t_addr = t_jstop; else if (t_jdstop) t_addr = t_jdstop; else if (t_istop) t_addr = t_istop; else if (t_dtrace) t_addr = t_dtrace; else if (t_req) t_addr = t_req; else if (t_susp) t_addr = t_susp; else /* TS_ZOMB */ t_addr = p->p_tlist; return (t_addr); } /* * Fields not populated: * - pr_stype * - pr_oldpri * - pr_nice * - pr_time * - pr_pctcpu * - pr_cpu */ static int gcore_prgetlwpsinfo(uintptr_t t_addr, mdb_kthread_t *t, lwpsinfo_t *psp) { char c, state; mdb_cpu_t cpu; mdb_lpl_t lgrp; uintptr_t str_addr; bzero(psp, sizeof (*psp)); psp->pr_flag = 0; /* lwpsinfo_t.pr_flag is deprecated */ psp->pr_lwpid = t->t_tid; psp->pr_addr = t_addr; psp->pr_wchan = (uintptr_t)t->t_wchan; /* map the thread state enum into a process state enum */ state = VSTOPPED(t) ? TS_STOPPED : t->t_state; switch (state) { case TS_SLEEP: state = SSLEEP; c = 'S'; break; case TS_RUN: state = SRUN; c = 'R'; break; case TS_ONPROC: state = SONPROC; c = 'O'; break; case TS_ZOMB: state = SZOMB; c = 'Z'; break; case TS_STOPPED: state = SSTOP; c = 'T'; break; case TS_WAIT: state = SWAIT; c = 'W'; break; default: state = 0; c = '?'; break; } psp->pr_state = state; psp->pr_sname = c; psp->pr_syscall = t->t_sysnum; psp->pr_pri = t->t_pri; psp->pr_start.tv_sec = t->t_start; psp->pr_start.tv_nsec = 0L; str_addr = (uintptr_t)gcore_sclass[t->t_cid].cl_name; if (mdb_readstr(psp->pr_clname, sizeof (psp->pr_clname) - 1, str_addr) == -1) { mdb_warn("Failed to read string from %p\n", str_addr); return (-1); } bzero(psp->pr_name, sizeof (psp->pr_name)); if (mdb_ctf_vread(&cpu, "struct cpu", "mdb_cpu_t", t->t_cpu, 0) == -1) { return (-1); } psp->pr_onpro = cpu.cpu_id; psp->pr_bindpro = t->t_bind_cpu; psp->pr_bindpset = t->t_bind_pset; if (mdb_ctf_vread(&lgrp, "lpl_t", "mdb_lpl_t", t->t_lpl, 0) == -1) { return (-1); } psp->pr_lgrp = lgrp.lpl_lgrpid; return (0); } /*ARGSUSED*/ static int gcore_lpsinfo_cb(mdb_proc_t *p, lwpent_t *lwent, void *data) { lwpsinfo_t *lpsinfo = data; uintptr_t t_addr = (uintptr_t)lwent->le_thread; mdb_kthread_t kthrd; if (t_addr != 0) { if (mdb_ctf_vread(&kthrd, "kthread_t", "mdb_kthread_t", t_addr, 0) == -1) { return (-1); } return (gcore_prgetlwpsinfo(t_addr, &kthrd, lpsinfo)); } bzero(lpsinfo, sizeof (*lpsinfo)); lpsinfo->pr_lwpid = lwent->le_lwpid; lpsinfo->pr_state = SZOMB; lpsinfo->pr_sname = 'Z'; lpsinfo->pr_start.tv_sec = lwent->le_start; lpsinfo->pr_bindpro = PBIND_NONE; lpsinfo->pr_bindpset = PS_NONE; return (0); } static void gcore_schedctl_finish_sigblock(mdb_kthread_t *t) { mdb_sc_shared_t td; mdb_sc_shared_t *tdp; if (t->t_schedctl == 0) { return; } if (mdb_ctf_vread(&td, "sc_shared_t", "mdb_sc_shared_t", t->t_schedctl, 0) == -1) { return; } tdp = &td; if (tdp->sc_sigblock) { t->t_hold.__sigbits[0] = FILLSET0 & ~CANTMASK0; t->t_hold.__sigbits[1] = FILLSET1 & ~CANTMASK1; t->t_hold.__sigbits[2] = FILLSET2 & ~CANTMASK2; tdp->sc_sigblock = 0; } } static void gcore_prgetaction(mdb_proc_t *p, user_t *up, uint_t sig, struct sigaction *sp) { int nsig = NSIG; bzero(sp, sizeof (*sp)); if (sig != 0 && (unsigned)sig < nsig) { sp->sa_handler = up->u_signal[sig-1]; prassignset(&sp->sa_mask, &up->u_sigmask[sig-1]); if (sigismember(&up->u_sigonstack, sig)) sp->sa_flags |= SA_ONSTACK; if (sigismember(&up->u_sigresethand, sig)) sp->sa_flags |= SA_RESETHAND; if (sigismember(&up->u_sigrestart, sig)) sp->sa_flags |= SA_RESTART; if (sigismember(&p->p_siginfo, sig)) sp->sa_flags |= SA_SIGINFO; if (sigismember(&up->u_signodefer, sig)) sp->sa_flags |= SA_NODEFER; if (sig == SIGCLD) { if (p->p_flag & SNOWAIT) sp->sa_flags |= SA_NOCLDWAIT; if ((p->p_flag & SJCTL) == 0) sp->sa_flags |= SA_NOCLDSTOP; } } } static void gcore_prgetprregs(mdb_klwp_t *lwp, prgregset_t prp) { gcore_getgregs(lwp, prp); } /* * Field not populated: * - pr_tstamp * - pr_utime * - pr_stime * - pr_syscall * - pr_syarg * - pr_nsysarg * - pr_fpreg */ /*ARGSUSED*/ static int gcore_prgetlwpstatus(mdb_proc_t *p, uintptr_t t_addr, mdb_kthread_t *t, lwpstatus_t *sp, zone_t *zp) { uintptr_t lwp_addr = ttolwp(t); mdb_klwp_t lw; mdb_klwp_t *lwp; ulong_t instr; int flags; uintptr_t str_addr; struct pid pid; if (mdb_ctf_vread(&lw, "klwp_t", "mdb_klwp_t", lwp_addr, 0) == -1) { return (-1); } lwp = &lw; bzero(sp, sizeof (*sp)); flags = 0L; if (t->t_state == TS_STOPPED) { flags |= PR_STOPPED; if ((t->t_schedflag & TS_PSTART) == 0) flags |= PR_ISTOP; } else if (VSTOPPED(t)) { flags |= PR_STOPPED|PR_ISTOP; } if (!(flags & PR_ISTOP) && (t->t_proc_flag & TP_PRSTOP)) flags |= PR_DSTOP; if (lwp->lwp_asleep) flags |= PR_ASLEEP; if (t_addr == p->p_agenttp) flags |= PR_AGENT; if (!(t->t_proc_flag & TP_TWAIT)) flags |= PR_DETACH; if (t->t_proc_flag & TP_DAEMON) flags |= PR_DAEMON; if (p->p_proc_flag & P_PR_FORK) flags |= PR_FORK; if (p->p_proc_flag & P_PR_RUNLCL) flags |= PR_RLC; if (p->p_proc_flag & P_PR_KILLCL) flags |= PR_KLC; if (p->p_proc_flag & P_PR_ASYNC) flags |= PR_ASYNC; if (p->p_proc_flag & P_PR_BPTADJ) flags |= PR_BPTADJ; if (p->p_proc_flag & P_PR_PTRACE) flags |= PR_PTRACE; if (p->p_flag & SMSACCT) flags |= PR_MSACCT; if (p->p_flag & SMSFORK) flags |= PR_MSFORK; if (p->p_flag & SVFWAIT) flags |= PR_VFORKP; if (mdb_vread(&pid, sizeof (struct pid), p->p_pgidp) != sizeof (pid)) { mdb_warn("Failed to read pid from %p\n", p->p_pgidp); return (-1); } if (pid.pid_pgorphaned) flags |= PR_ORPHAN; if (p->p_pidflag & CLDNOSIGCHLD) flags |= PR_NOSIGCHLD; if (p->p_pidflag & CLDWAITPID) flags |= PR_WAITPID; sp->pr_flags = flags; if (VSTOPPED(t)) { sp->pr_why = PR_REQUESTED; sp->pr_what = 0; } else { sp->pr_why = t->t_whystop; sp->pr_what = t->t_whatstop; } sp->pr_lwpid = t->t_tid; sp->pr_cursig = lwp->lwp_cursig; prassignset(&sp->pr_lwppend, &t->t_sig); gcore_schedctl_finish_sigblock(t); prassignset(&sp->pr_lwphold, &t->t_hold); if (t->t_whystop == PR_FAULTED) { bcopy(&lwp->lwp_siginfo, &sp->pr_info, sizeof (k_siginfo_t)); } else if (lwp->lwp_curinfo) { mdb_sigqueue_t sigq; if (mdb_ctf_vread(&sigq, "sigqueue_t", "mdb_sigqueue_t", lwp->lwp_curinfo, 0) == -1) { return (-1); } bcopy(&sigq.sq_info, &sp->pr_info, sizeof (k_siginfo_t)); } sp->pr_altstack = lwp->lwp_sigaltstack; gcore_prgetaction(p, PTOU(p), lwp->lwp_cursig, &sp->pr_action); sp->pr_oldcontext = lwp->lwp_oldcontext; sp->pr_ustack = lwp->lwp_ustack; str_addr = (uintptr_t)gcore_sclass[t->t_cid].cl_name; if (mdb_readstr(sp->pr_clname, sizeof (sp->pr_clname) - 1, str_addr) == -1) { mdb_warn("Failed to read string from %p\n", str_addr); return (-1); } /* * Fetch the current instruction, if not a system process. * We don't attempt this unless the lwp is stopped. */ if ((p->p_flag & SSYS) || p->p_as == gcore_kas) sp->pr_flags |= (PR_ISSYS|PR_PCINVAL); else if (!(flags & PR_STOPPED)) sp->pr_flags |= PR_PCINVAL; else if (!gcore_prfetchinstr(lwp, &instr)) sp->pr_flags |= PR_PCINVAL; else sp->pr_instr = instr; if (gcore_prisstep(lwp)) sp->pr_flags |= PR_STEP; gcore_prgetprregs(lwp, sp->pr_reg); if ((t->t_state == TS_STOPPED && t->t_whystop == PR_SYSEXIT) || (flags & PR_VFORKP)) { user_t *up; auxv_t *auxp; int i; sp->pr_errno = gcore_prgetrvals(lwp, &sp->pr_rval1, &sp->pr_rval2); if (sp->pr_errno == 0) sp->pr_errpriv = PRIV_NONE; else sp->pr_errpriv = lwp->lwp_badpriv; if (t->t_sysnum == SYS_execve) { up = PTOU(p); sp->pr_sysarg[0] = 0; sp->pr_sysarg[1] = (uintptr_t)up->u_argv; sp->pr_sysarg[2] = (uintptr_t)up->u_envp; sp->pr_sysarg[3] = 0; for (i = 0, auxp = up->u_auxv; i < sizeof (up->u_auxv) / sizeof (up->u_auxv[0]); i++, auxp++) { if (auxp->a_type == AT_SUN_EXECNAME) { sp->pr_sysarg[0] = (uintptr_t)auxp->a_un.a_ptr; break; } } } } return (0); } static int gcore_lstatus_cb(mdb_proc_t *p, lwpent_t *lwent, void *data) { lwpstatus_t *lstatus = data; uintptr_t t_addr = (uintptr_t)lwent->le_thread; mdb_kthread_t kthrd; if (t_addr == 0) { return (1); } if (mdb_ctf_vread(&kthrd, "kthread_t", "mdb_kthread_t", t_addr, 0) == -1) { return (-1); } return (gcore_prgetlwpstatus(p, t_addr, &kthrd, lstatus, NULL)); } static prheader_t * gcore_walk_lwps(mdb_proc_t *p, lwp_callback_t callback, int nlwp, size_t ent_size) { void *ent; prheader_t *php; lwpdir_t *ldp; lwpdir_t ld; lwpent_t lwent; int status; int i; php = calloc(1, sizeof (prheader_t) + nlwp * ent_size); if (php == NULL) { return (NULL); } php->pr_nent = nlwp; php->pr_entsize = ent_size; ent = php + 1; for (ldp = (lwpdir_t *)p->p_lwpdir, i = 0; i < p->p_lwpdir_sz; i++, ldp++) { if (mdb_vread(&ld, sizeof (ld), (uintptr_t)ldp) != sizeof (ld)) { mdb_warn("Failed to read lwpdir_t from %p\n", ldp); goto error; } if (ld.ld_entry == NULL) { continue; } if (mdb_vread(&lwent, sizeof (lwent), (uintptr_t)ld.ld_entry) != sizeof (lwent)) { mdb_warn("Failed to read lwpent_t from %p\n", ld.ld_entry); goto error; } status = callback(p, &lwent, ent); if (status == -1) { dprintf("lwp callback %p returned -1\n", callback); goto error; } if (status == 1) { dprintf("lwp callback %p returned 1\n", callback); continue; } ent = (caddr_t)ent + ent_size; } return (php); error: free(php); return (NULL); } /* * Misc helper functions. */ /* * convert code/data pair into old style wait status */ static int gcore_wstat(int code, int data) { int stat = (data & 0377); switch (code) { case CLD_EXITED: stat <<= 8; break; case CLD_DUMPED: stat |= WCOREFLG; break; case CLD_KILLED: break; case CLD_TRAPPED: case CLD_STOPPED: stat <<= 8; stat |= WSTOPFLG; break; case CLD_CONTINUED: stat = WCONTFLG; break; default: mdb_warn("wstat: bad code %d\n", code); } return (stat); } #if defined(__i386) || defined(__amd64) static void gcore_usd_to_ssd(user_desc_t *usd, struct ssd *ssd, selector_t sel) { ssd->bo = USEGD_GETBASE(usd); ssd->ls = USEGD_GETLIMIT(usd); ssd->sel = sel; /* * set type, dpl and present bits. */ ssd->acc1 = usd->usd_type; ssd->acc1 |= usd->usd_dpl << 5; ssd->acc1 |= usd->usd_p << (5 + 2); /* * set avl, DB and granularity bits. */ ssd->acc2 = usd->usd_avl; #if defined(__amd64) ssd->acc2 |= usd->usd_long << 1; #else ssd->acc2 |= usd->usd_reserved << 1; #endif ssd->acc2 |= usd->usd_def32 << (1 + 1); ssd->acc2 |= usd->usd_gran << (1 + 1 + 1); } #endif static priv_set_t * gcore_priv_getset(cred_t *cr, int set) { if ((CR_FLAGS(cr) & PRIV_AWARE) == 0) { switch (set) { case PRIV_EFFECTIVE: return (&CR_OEPRIV(cr)); case PRIV_PERMITTED: return (&CR_OPPRIV(cr)); } } return (&CR_PRIVS(cr)->crprivs[set]); } static void gcore_priv_getinfo(const cred_t *cr, void *buf) { struct priv_info_uint *ii; ii = buf; ii->val = CR_FLAGS(cr); ii->info.priv_info_size = (uint32_t)sizeof (*ii); ii->info.priv_info_type = PRIV_INFO_FLAGS; } static void map_list_free(prmap_node_t *n) { prmap_node_t *next; while (n != NULL) { next = n->next; mdb_free(n, sizeof (*n)); n = next; } } /* * Ops vector functions for ::gcore. */ /*ARGSUSED*/ static ssize_t Pread_gcore(struct ps_prochandle *P, void *buf, size_t n, uintptr_t addr, void *data) { mdb_proc_t *p = data; ssize_t ret; ret = mdb_aread(buf, n, addr, (void *)p->p_as); if (ret != n) { dprintf("%s: addr: %p len: %llx\n", __func__, addr, n); (void) memset(buf, 0, n); return (n); } return (ret); } /*ARGSUSED*/ static ssize_t Pwrite_gcore(struct ps_prochandle *P, const void *buf, size_t n, uintptr_t addr, void *data) { dprintf("%s: addr: %p len: %llx\n", __func__, addr, n); return (-1); } /*ARGSUSED*/ static int Pread_maps_gcore(struct ps_prochandle *P, prmap_t **Pmapp, ssize_t *nmapp, void *data) { mdb_proc_t *p = data; read_maps_cbarg_t cbarg; prmap_node_t *n; prmap_t *pmap; uintptr_t segtree_addr; int error; int i; cbarg.p = p; cbarg.brkseg = gcore_break_seg(p); cbarg.stkseg = gcore_as_segat(p->p_as, gcore_prgetstackbase(p)); (void) memset(&cbarg, 0, sizeof (cbarg)); segtree_addr = p->p_as + mdb_ctf_offsetof_by_name("struct as", "a_segtree"); error = avl_walk_mdb(segtree_addr, read_maps_cb, &cbarg); if (error != WALK_DONE) { return (-1); } /* Conver the linked list into an array */ pmap = malloc(cbarg.map_len * sizeof (*pmap)); if (pmap == NULL) { map_list_free(cbarg.map_head); return (-1); } for (i = 0, n = cbarg.map_head; i < cbarg.map_len; i++, n = n->next) { (void) memcpy(&pmap[i], &n->m, sizeof (prmap_t)); } map_list_free(cbarg.map_head); for (i = 0; i < cbarg.map_len; i++) { dprintf("pr_vaddr: %p pr_size: %llx, pr_name: %s " "pr_offset: %p pr_mflags: 0x%x\n", pmap[i].pr_vaddr, pmap[i].pr_size, pmap[i].pr_mapname, pmap[i].pr_offset, pmap[i].pr_mflags); } *Pmapp = pmap; *nmapp = cbarg.map_len; return (0); } /*ARGSUSED*/ static void Pread_aux_gcore(struct ps_prochandle *P, auxv_t **auxvp, int *nauxp, void *data) { mdb_proc_t *p = data; auxv_t *auxv; int naux; naux = __KERN_NAUXV_IMPL; auxv = calloc(naux + 1, sizeof (*auxv)); if (auxv == NULL) { *auxvp = NULL; *nauxp = 0; return; } (void) memcpy(auxv, p->p_user.u_auxv, naux * sizeof (*auxv)); *auxvp = auxv; *nauxp = naux; } /*ARGSUSED*/ static int Pcred_gcore(struct ps_prochandle *P, prcred_t *prcp, int ngroups, void *data) { mdb_proc_t *p = data; cred_t cr; credgrp_t crgrp; int i; if (mdb_vread(&cr, sizeof (cr), p->p_cred) != sizeof (cr)) { mdb_warn("Failed to read cred_t from %p\n", p->p_cred); return (-1); } prcp->pr_euid = cr.cr_uid; prcp->pr_ruid = cr.cr_ruid; prcp->pr_suid = cr.cr_suid; prcp->pr_egid = cr.cr_gid; prcp->pr_rgid = cr.cr_rgid; prcp->pr_sgid = cr.cr_sgid; if (cr.cr_grps == 0) { prcp->pr_ngroups = 0; return (0); } if (mdb_vread(&crgrp, sizeof (crgrp), (uintptr_t)cr.cr_grps) != sizeof (crgrp)) { mdb_warn("Failed to read credgrp_t from %p\n", cr.cr_grps); return (-1); } prcp->pr_ngroups = MIN(ngroups, crgrp.crg_ngroups); for (i = 0; i < prcp->pr_ngroups; i++) { prcp->pr_groups[i] = crgrp.crg_groups[i]; } return (0); } /*ARGSUSED*/ static int Ppriv_gcore(struct ps_prochandle *P, prpriv_t **pprv, void *data) { mdb_proc_t *p = data; prpriv_t *pp; cred_t cr; priv_set_t *psa; size_t pprv_size; int i; pprv_size = sizeof (prpriv_t) + PRIV_SETBYTES - sizeof (priv_chunk_t) + prinfo.priv_infosize; pp = malloc(pprv_size); if (pp == NULL) { return (-1); } if (mdb_vread(&cr, sizeof (cr), p->p_cred) != sizeof (cr)) { mdb_warn("Failed to read cred_t from %p\n", p->p_cred); free(pp); return (-1); } pp->pr_nsets = PRIV_NSET; pp->pr_setsize = PRIV_SETSIZE; pp->pr_infosize = prinfo.priv_infosize; psa = (priv_set_t *)pp->pr_sets; for (i = 0; i < PRIV_NSET; i++) { psa[i] = *gcore_priv_getset(&cr, i); } gcore_priv_getinfo(&cr, (char *)pp + PRIV_PRPRIV_INFO_OFFSET(pp)); *pprv = pp; return (0); } /* * Fields not filled populated: * - pr_utime * - pr_stkbase * - pr_cutime * - pr_cstime * - pr_agentid */ /*ARGSUSED*/ static void Pstatus_gcore(struct ps_prochandle *P, pstatus_t *sp, void *data) { mdb_proc_t *p = data; uintptr_t t_addr; mdb_kthread_t kthr; mdb_kthread_t *t; pcommon_t pc; t_addr = gcore_prchoose(p); if (t_addr != 0) { if (mdb_ctf_vread(&kthr, "kthread_t", "mdb_kthread_t", t_addr, 0) == -1) { return; } t = &kthr; } /* just bzero the process part, prgetlwpstatus() does the rest */ bzero(sp, sizeof (pstatus_t) - sizeof (lwpstatus_t)); if (pcommon_init(p, &pc) == -1) { return; } sp->pr_nlwp = pc.pc_nlwp; sp->pr_nzomb = pc.pc_nzomb; sp->pr_pid = pc.pc_pid; sp->pr_ppid = pc.pc_ppid; sp->pr_pgid = pc.pc_pgid; sp->pr_sid = pc.pc_sid; sp->pr_taskid = pc.pc_taskid; sp->pr_projid = pc.pc_projid; sp->pr_zoneid = pc.pc_zoneid; sp->pr_dmodel = pc.pc_dmodel; prassignset(&sp->pr_sigpend, &p->p_sig); sp->pr_brkbase = p->p_brkbase; sp->pr_brksize = p->p_brksize; sp->pr_stkbase = gcore_prgetstackbase(p); sp->pr_stksize = p->p_stksize; prassignset(&sp->pr_sigtrace, &p->p_sigmask); prassignset(&sp->pr_flttrace, &p->p_fltmask); prassignset(&sp->pr_sysentry, &PTOU(p)->u_entrymask); prassignset(&sp->pr_sysexit, &PTOU(p)->u_exitmask); /* get the chosen lwp's status */ gcore_prgetlwpstatus(p, t_addr, t, &sp->pr_lwp, NULL); /* replicate the flags */ sp->pr_flags = sp->pr_lwp.pr_flags; } /* * Fields not populated: * - pr_contract * - pr_addr * - pr_rtime * - pr_ctime * - pr_ttydev * - pr_pctcpu * - pr_size * - pr_rsize * - pr_pctmem */ /*ARGSUSED*/ static const psinfo_t * Ppsinfo_gcore(struct ps_prochandle *P, psinfo_t *psp, void *data) { mdb_proc_t *p = data; mdb_kthread_t *t; mdb_pool_t pool; cred_t cr; uintptr_t t_addr; pcommon_t pc; if ((t_addr = gcore_prchoose(p)) == 0) { bzero(psp, sizeof (*psp)); } else { bzero(psp, sizeof (*psp) - sizeof (psp->pr_lwp)); } if (pcommon_init(p, &pc) == -1) { return (NULL); } psp->pr_nlwp = pc.pc_nlwp; psp->pr_nzomb = pc.pc_nzomb; psp->pr_pid = pc.pc_pid; psp->pr_ppid = pc.pc_ppid; psp->pr_pgid = pc.pc_pgid; psp->pr_sid = pc.pc_sid; psp->pr_taskid = pc.pc_taskid; psp->pr_projid = pc.pc_projid; psp->pr_dmodel = pc.pc_dmodel; /* * only export SSYS and SMSACCT; everything else is off-limits to * userland apps. */ psp->pr_flag = p->p_flag & (SSYS | SMSACCT); if (mdb_vread(&cr, sizeof (cr), p->p_cred) != sizeof (cr)) { mdb_warn("Failed to read cred_t from %p\n", p->p_cred); return (NULL); } psp->pr_uid = cr.cr_ruid; psp->pr_euid = cr.cr_uid; psp->pr_gid = cr.cr_rgid; psp->pr_egid = cr.cr_gid; if (mdb_ctf_vread(&pool, "pool_t", "mdb_pool_t", p->p_pool, 0) == -1) { return (NULL); } psp->pr_poolid = pool.pool_id; if (t_addr == 0) { int wcode = p->p_wcode; if (wcode) psp->pr_wstat = gcore_wstat(wcode, p->p_wdata); psp->pr_ttydev = PRNODEV; psp->pr_lwp.pr_state = SZOMB; psp->pr_lwp.pr_sname = 'Z'; psp->pr_lwp.pr_bindpro = PBIND_NONE; psp->pr_lwp.pr_bindpset = PS_NONE; } else { mdb_kthread_t kthr; user_t *up = PTOU(p); psp->pr_start = up->u_start; bcopy(up->u_comm, psp->pr_fname, MIN(sizeof (up->u_comm), sizeof (psp->pr_fname)-1)); bcopy(up->u_psargs, psp->pr_psargs, MIN(PRARGSZ-1, PSARGSZ)); psp->pr_argc = up->u_argc; psp->pr_argv = up->u_argv; psp->pr_envp = up->u_envp; /* get the chosen lwp's lwpsinfo */ if (mdb_ctf_vread(&kthr, "kthread_t", "mdb_kthread_t", t_addr, 0) == -1) { return (NULL); } t = &kthr; gcore_prgetlwpsinfo(t_addr, t, &psp->pr_lwp); } return (NULL); } /*ARGSUSED*/ static prheader_t * Plstatus_gcore(struct ps_prochandle *P, void *data) { mdb_proc_t *p = data; int nlwp = p->p_lwpcnt; size_t ent_size = LSPAN(lwpstatus_t); return (gcore_walk_lwps(p, gcore_lstatus_cb, nlwp, ent_size)); } /*ARGSUSED*/ static prheader_t * Plpsinfo_gcore(struct ps_prochandle *P, void *data) { mdb_proc_t *p = data; int nlwp = p->p_lwpcnt + p->p_zombcnt; size_t ent_size = LSPAN(lwpsinfo_t); return (gcore_walk_lwps(p, gcore_lpsinfo_cb, nlwp, ent_size)); } /*ARGSUSED*/ static char * Pplatform_gcore(struct ps_prochandle *P, char *s, size_t n, void *data) { char platform[SYS_NMLN]; if (mdb_readvar(platform, "platform") == -1) { mdb_warn("failed to read platform!\n"); return (NULL); } dprintf("platform: %s\n", platform); (void) strncpy(s, platform, n); return (s); } /*ARGSUSED*/ static int Puname_gcore(struct ps_prochandle *P, struct utsname *u, void *data) { if (mdb_readvar(u, "utsname") != sizeof (*u)) { return (-1); } return (0); } /*ARGSUSED*/ static char * Pzonename_gcore(struct ps_prochandle *P, char *s, size_t n, void *data) { mdb_proc_t *p = data; mdb_zone_t zone; if (mdb_ctf_vread(&zone, "zone_t", "mdb_zone_t", p->p_zone, 0) == -1) { return (NULL); } if (mdb_readstr(s, n, zone.zone_name) == -1) { mdb_warn("Failed to read zone name from %p\n", zone.zone_name); return (NULL); } return (s); } /*ARGSUSED*/ static char * Pexecname_gcore(struct ps_prochandle *P, char *buf, size_t buflen, void *data) { mdb_proc_t *p = data; mdb_vnode_t vn; if (mdb_ctf_vread(&vn, "vnode_t", "mdb_vnode_t", p->p_exec, 0) == -1) { return (NULL); } if (mdb_readstr(buf, buflen, vn.v_path) == -1) { mdb_warn("Failed to read vnode path from %p\n", vn.v_path); return (NULL); } dprintf("execname: %s\n", buf); return (buf); } #if defined(__i386) || defined(__amd64) /*ARGSUSED*/ static int Pldt_gcore(struct ps_prochandle *P, struct ssd *pldt, int nldt, void *data) { mdb_proc_t *p = data; user_desc_t *udp; user_desc_t *ldts; size_t ldt_size; int i, limit; if (p->p_ldt == 0) { return (0); } limit = p->p_ldtlimit; /* Is this call just to query the size ? */ if (pldt == NULL || nldt == 0) { return (limit); } ldt_size = limit * sizeof (*ldts); ldts = malloc(ldt_size); if (ldts == NULL) { mdb_warn("Failed to malloc ldts (size %lld)n", ldt_size); return (-1); } if (mdb_vread(ldts, ldt_size, p->p_ldt) != ldt_size) { mdb_warn("Failed to read ldts from %p\n", p->p_ldt); free(ldts); return (-1); } for (i = LDT_UDBASE, udp = &ldts[i]; i <= limit; i++, udp++) { if (udp->usd_type != 0 || udp->usd_dpl != 0 || udp->usd_p != 0) { gcore_usd_to_ssd(udp, pldt++, SEL_LDT(i)); } } free(ldts); return (limit); } #endif static const ps_ops_t Pgcore_ops = { .pop_pread = Pread_gcore, .pop_pwrite = Pwrite_gcore, .pop_read_maps = Pread_maps_gcore, .pop_read_aux = Pread_aux_gcore, .pop_cred = Pcred_gcore, .pop_priv = Ppriv_gcore, .pop_psinfo = Ppsinfo_gcore, .pop_status = Pstatus_gcore, .pop_lstatus = Plstatus_gcore, .pop_lpsinfo = Plpsinfo_gcore, .pop_platform = Pplatform_gcore, .pop_uname = Puname_gcore, .pop_zonename = Pzonename_gcore, .pop_execname = Pexecname_gcore, #if defined(__i386) || defined(__amd64) .pop_ldt = Pldt_gcore #endif }; /*ARGSUSED*/ int gcore_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct ps_prochandle *P; char core_name[MAXNAMELEN]; mdb_proc_t p; mdb_pid_t pid; if (!gcore_initialized) { mdb_warn("gcore unavailable\n"); return (DCMD_ERR); } if (mdb_ctf_vread(&p, "proc_t", "mdb_proc_t", addr, 0) == -1) { return (DCMD_ERR); } if (p.p_flag & SSYS) { mdb_warn("'%s' is a system process\n", p.p_user.u_comm); return (DCMD_ERR); } if (mdb_ctf_vread(&pid, "struct pid", "mdb_pid_t", p.p_pidp, 0) == -1) { return (DCMD_ERR); } if ((P = Pgrab_ops(pid.pid_id, &p, &Pgcore_ops, PGRAB_INCORE)) == NULL) { mdb_warn("Failed to initialize proc handle"); return (DCMD_ERR); } (void) snprintf(core_name, sizeof (core_name), "core.%s.%d", p.p_user.u_comm, pid.pid_id); if (Pgcore(P, core_name, CC_CONTENT_DEFAULT) != 0) { mdb_warn("Failed to generate core file: %d", errno); Pfree(P); return (DCMD_ERR); } Pfree(P); mdb_printf("Created core file: %s\n", core_name); return (0); } void gcore_init(void) { GElf_Sym sym; uintptr_t priv_info_addr; if (mdb_lookup_by_name("segvn_ops", &sym) == -1) { mdb_warn("Failed to lookup symbol 'segvn_ops'\n"); return; } gcore_segvn_ops = sym.st_value; if (mdb_readvar(&priv_info_addr, "priv_info") == -1) { mdb_warn("Failed to read variable 'priv_info'\n"); return; } if (mdb_vread(&prinfo, sizeof (prinfo), priv_info_addr) == -1) { mdb_warn("Failed to read prinfo from %p\n", priv_info_addr); return; } if (mdb_lookup_by_name("sclass", &sym) == -1) { mdb_warn("Failed to lookup symbol 'segvn_ops'\n"); return; } gcore_sclass = mdb_zalloc(sym.st_size, UM_SLEEP); if (mdb_vread(gcore_sclass, sym.st_size, sym.st_value) != sym.st_size) { mdb_warn("Failed to read sclass' from %p\n", sym.st_value); return; } if (mdb_lookup_by_name("kas", &sym) == -1) { mdb_warn("Failed to lookup symbol 'kas'\n"); return; } gcore_kas = sym.st_value; gcore_initialized = B_TRUE; } #endif /* _KMDB */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2013 by Delphix. All rights reserved. */ #ifndef _MDB_GCORE_H #define _MDB_GCORE_H #ifdef __cplusplus extern "C" { #endif extern void gcore_init(void); extern int gcore_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_GCORE_H */ /* * 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 2011 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright (c) 2013 by Delphix. All rights reserved. * Copyright 2022 Garrett D'Amore * Copyright 2023 RackTop Systems, Inc. * Copyright 2026 Oxide Computer Company */ #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 #include #include #include #include #include "avl.h" #include "bio.h" #include "bitset.h" #include "combined.h" #include "contract.h" #include "cpupart_mdb.h" #include "cred.h" #include "ctxop.h" #include "cyclic.h" #include "damap.h" #include "ddi_periodic.h" #include "devinfo.h" #include "dnlc.h" #include "findstack.h" #include "fm.h" #include "gcore.h" #include "group.h" #include "irm.h" #include "kgrep.h" #include "kmem.h" #include "ldi.h" #include "leaky.h" #include "lgrp.h" #include "list.h" #include "log.h" #include "mdi.h" #include "memory.h" #include "modhash.h" #include "ndievents.h" #include "net.h" #include "netstack.h" #include "nvpair.h" #include "pci.h" #include "pg.h" #include "rctl.h" #include "sobj.h" #include "streams.h" #include "sysevent.h" #include "taskq.h" #include "thread.h" #include "tsd.h" #include "tsol.h" #include "typegraph.h" #include "vfs.h" #include "zone.h" #include "hotplug.h" /* * Surely this is defined somewhere... */ #define NINTR 16 #define KILOS 10 #define MEGS 20 #define GIGS 30 #ifndef STACK_BIAS #define STACK_BIAS 0 #endif static char pstat2ch(uchar_t state) { switch (state) { case SSLEEP: return ('S'); case SRUN: return ('R'); case SZOMB: return ('Z'); case SIDL: return ('I'); case SONPROC: return ('O'); case SSTOP: return ('T'); case SWAIT: return ('W'); default: return ('?'); } } #define PS_PRTTHREADS 0x1 #define PS_PRTLWPS 0x2 #define PS_PSARGS 0x4 #define PS_TASKS 0x8 #define PS_PROJECTS 0x10 #define PS_ZONES 0x20 #define PS_SERVICES 0x40 static int ps_threadprint(uintptr_t addr, const void *data, void *private) { const kthread_t *t = (const kthread_t *)data; uint_t prt_flags = *((uint_t *)private); static const mdb_bitmask_t t_state_bits[] = { { "TS_FREE", UINT_MAX, TS_FREE }, { "TS_SLEEP", TS_SLEEP, TS_SLEEP }, { "TS_RUN", TS_RUN, TS_RUN }, { "TS_ONPROC", TS_ONPROC, TS_ONPROC }, { "TS_ZOMB", TS_ZOMB, TS_ZOMB }, { "TS_STOPPED", TS_STOPPED, TS_STOPPED }, { "TS_WAIT", TS_WAIT, TS_WAIT }, { NULL, 0, 0 } }; if (prt_flags & PS_PRTTHREADS) mdb_printf("\tT %?a <%b>\n", addr, t->t_state, t_state_bits); if (prt_flags & PS_PRTLWPS) { char desc[128] = ""; (void) thread_getdesc(addr, B_FALSE, desc, sizeof (desc)); mdb_printf("\tL %?a ID: %s\n", t->t_lwp, desc); } return (WALK_NEXT); } typedef struct mdb_pflags_proc { struct pid *p_pidp; ushort_t p_pidflag; uint_t p_proc_flag; uint_t p_flag; } mdb_pflags_proc_t; static int pflags(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_pflags_proc_t pr; struct pid pid; static const mdb_bitmask_t p_flag_bits[] = { { "SSYS", SSYS, SSYS }, { "SEXITING", SEXITING, SEXITING }, { "SITBUSY", SITBUSY, SITBUSY }, { "SFORKING", SFORKING, SFORKING }, { "SWATCHOK", SWATCHOK, SWATCHOK }, { "SKILLED", SKILLED, SKILLED }, { "SSCONT", SSCONT, SSCONT }, { "SZONETOP", SZONETOP, SZONETOP }, { "SEXTKILLED", SEXTKILLED, SEXTKILLED }, { "SUGID", SUGID, SUGID }, { "SEXECED", SEXECED, SEXECED }, { "SJCTL", SJCTL, SJCTL }, { "SNOWAIT", SNOWAIT, SNOWAIT }, { "SVFORK", SVFORK, SVFORK }, { "SVFWAIT", SVFWAIT, SVFWAIT }, { "SEXITLWPS", SEXITLWPS, SEXITLWPS }, { "SHOLDFORK", SHOLDFORK, SHOLDFORK }, { "SHOLDFORK1", SHOLDFORK1, SHOLDFORK1 }, { "SCOREDUMP", SCOREDUMP, SCOREDUMP }, { "SMSACCT", SMSACCT, SMSACCT }, { "SLWPWRAP", SLWPWRAP, SLWPWRAP }, { "SAUTOLPG", SAUTOLPG, SAUTOLPG }, { "SNOCD", SNOCD, SNOCD }, { "SHOLDWATCH", SHOLDWATCH, SHOLDWATCH }, { "SMSFORK", SMSFORK, SMSFORK }, { "SDOCORE", SDOCORE, SDOCORE }, { NULL, 0, 0 } }; static const mdb_bitmask_t p_pidflag_bits[] = { { "CLDPEND", CLDPEND, CLDPEND }, { "CLDCONT", CLDCONT, CLDCONT }, { "CLDNOSIGCHLD", CLDNOSIGCHLD, CLDNOSIGCHLD }, { "CLDWAITPID", CLDWAITPID, CLDWAITPID }, { NULL, 0, 0 } }; static const mdb_bitmask_t p_proc_flag_bits[] = { { "P_PR_TRACE", P_PR_TRACE, P_PR_TRACE }, { "P_PR_PTRACE", P_PR_PTRACE, P_PR_PTRACE }, { "P_PR_FORK", P_PR_FORK, P_PR_FORK }, { "P_PR_LOCK", P_PR_LOCK, P_PR_LOCK }, { "P_PR_ASYNC", P_PR_ASYNC, P_PR_ASYNC }, { "P_PR_EXEC", P_PR_EXEC, P_PR_EXEC }, { "P_PR_BPTADJ", P_PR_BPTADJ, P_PR_BPTADJ }, { "P_PR_RUNLCL", P_PR_RUNLCL, P_PR_RUNLCL }, { "P_PR_KILLCL", P_PR_KILLCL, P_PR_KILLCL }, { NULL, 0, 0 } }; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("proc", "pflags", argc, argv) == -1) { mdb_warn("can't walk 'proc'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_ctf_vread(&pr, "proc_t", "mdb_pflags_proc_t", addr, 0) == -1 || mdb_vread(&pid, sizeof (pid), (uintptr_t)pr.p_pidp) == -1) { mdb_warn("cannot read proc_t or pid"); return (DCMD_ERR); } mdb_printf("%p [pid %d]:\n", addr, pid.pid_id); mdb_printf("\tp_flag: %08x <%b>\n", pr.p_flag, pr.p_flag, p_flag_bits); mdb_printf("\tp_pidflag: %08x <%b>\n", pr.p_pidflag, pr.p_pidflag, p_pidflag_bits); mdb_printf("\tp_proc_flag: %08x <%b>\n", pr.p_proc_flag, pr.p_proc_flag, p_proc_flag_bits); return (DCMD_OK); } typedef struct mdb_ps_proc { char p_stat; struct pid *p_pidp; struct pid *p_pgidp; struct cred *p_cred; struct sess *p_sessp; struct task *p_task; struct zone *p_zone; struct cont_process *p_ct_process; pid_t p_ppid; uint_t p_flag; struct { char u_comm[MAXCOMLEN + 1]; char u_psargs[PSARGSZ]; } p_user; } mdb_ps_proc_t; /* * A reasonable enough limit. Note that we purposefully let this column over-run * if needed. */ #define FMRI_LEN (128) int ps(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t prt_flags = 0; mdb_ps_proc_t pr; struct pid pid, pgid, sid; sess_t session; cred_t cred; task_t tk; kproject_t pj; zone_t zn; struct cont_process cp; char fmri[FMRI_LEN] = ""; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("proc", "ps", argc, argv) == -1) { mdb_warn("can't walk 'proc'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'f', MDB_OPT_SETBITS, PS_PSARGS, &prt_flags, 'l', MDB_OPT_SETBITS, PS_PRTLWPS, &prt_flags, 's', MDB_OPT_SETBITS, PS_SERVICES, &prt_flags, 'T', MDB_OPT_SETBITS, PS_TASKS, &prt_flags, 'P', MDB_OPT_SETBITS, PS_PROJECTS, &prt_flags, 'z', MDB_OPT_SETBITS, PS_ZONES, &prt_flags, 't', MDB_OPT_SETBITS, PS_PRTTHREADS, &prt_flags, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-1s %-6s %-6s %-6s %-6s ", "S", "PID", "PPID", "PGID", "SID"); if (prt_flags & PS_TASKS) mdb_printf("%-5s ", "TASK"); if (prt_flags & PS_PROJECTS) mdb_printf("%-5s ", "PROJ"); if (prt_flags & PS_ZONES) mdb_printf("%-5s ", "ZONE"); if (prt_flags & PS_SERVICES) mdb_printf("%-40s ", "SERVICE"); mdb_printf("%-6s %-10s %-?s %-s%\n", "UID", "FLAGS", "ADDR", "NAME"); } if (mdb_ctf_vread(&pr, "proc_t", "mdb_ps_proc_t", addr, 0) == -1) return (DCMD_ERR); mdb_vread(&pid, sizeof (pid), (uintptr_t)pr.p_pidp); mdb_vread(&pgid, sizeof (pgid), (uintptr_t)pr.p_pgidp); mdb_vread(&cred, sizeof (cred), (uintptr_t)pr.p_cred); mdb_vread(&session, sizeof (session), (uintptr_t)pr.p_sessp); mdb_vread(&sid, sizeof (sid), (uintptr_t)session.s_sidp); if (prt_flags & (PS_TASKS | PS_PROJECTS)) mdb_vread(&tk, sizeof (tk), (uintptr_t)pr.p_task); if (prt_flags & PS_PROJECTS) mdb_vread(&pj, sizeof (pj), (uintptr_t)tk.tk_proj); if (prt_flags & PS_ZONES) mdb_vread(&zn, sizeof (zn), (uintptr_t)pr.p_zone); if ((prt_flags & PS_SERVICES) && pr.p_ct_process != NULL) { mdb_vread(&cp, sizeof (cp), (uintptr_t)pr.p_ct_process); if (mdb_read_refstr((uintptr_t)cp.conp_svc_fmri, fmri, sizeof (fmri)) <= 0) (void) strlcpy(fmri, "?", sizeof (fmri)); /* Strip any standard prefix and suffix. */ if (strncmp(fmri, "svc:/", sizeof ("svc:/") - 1) == 0) { char *i = fmri; char *j = fmri + sizeof ("svc:/") - 1; for (; *j != '\0'; i++, j++) { if (strcmp(j, ":default") == 0) break; *i = *j; } *i = '\0'; } } mdb_printf("%-c %-6d %-6d %-6d %-6d ", pstat2ch(pr.p_stat), pid.pid_id, pr.p_ppid, pgid.pid_id, sid.pid_id); if (prt_flags & PS_TASKS) mdb_printf("%-5d ", tk.tk_tkid); if (prt_flags & PS_PROJECTS) mdb_printf("%-5d ", pj.kpj_id); if (prt_flags & PS_ZONES) mdb_printf("%-5d ", zn.zone_id); if (prt_flags & PS_SERVICES) mdb_printf("%-40s ", fmri); mdb_printf("%-6d 0x%08x %0?p %-s\n", cred.cr_uid, pr.p_flag, addr, (prt_flags & PS_PSARGS) ? pr.p_user.u_psargs : pr.p_user.u_comm); if (prt_flags & ~PS_PSARGS) (void) mdb_pwalk("thread", ps_threadprint, &prt_flags, addr); return (DCMD_OK); } static void ps_help(void) { mdb_printf("Display processes.\n\n" "Options:\n" " -f\tDisplay command arguments\n" " -l\tDisplay LWPs\n" " -T\tDisplay tasks\n" " -P\tDisplay projects\n" " -s\tDisplay SMF FMRI\n" " -z\tDisplay zones\n" " -t\tDisplay threads\n\n"); mdb_printf("The resulting output is a table of the processes on the " "system. The\n" "columns in the output consist of a combination of the " "following fields:\n\n"); mdb_printf("S\tProcess state. Possible states are:\n" "\tS\tSleeping (SSLEEP)\n" "\tR\tRunnable (SRUN)\n" "\tZ\tZombie (SZOMB)\n" "\tI\tIdle (SIDL)\n" "\tO\tOn Cpu (SONPROC)\n" "\tT\tStopped (SSTOP)\n" "\tW\tWaiting (SWAIT)\n"); mdb_printf("PID\tProcess id.\n"); mdb_printf("PPID\tParent process id.\n"); mdb_printf("PGID\tProcess group id.\n"); mdb_printf("SID\tProcess id of the session leader.\n"); mdb_printf("TASK\tThe task id of the process.\n"); mdb_printf("PROJ\tThe project id of the process.\n"); mdb_printf("ZONE\tThe zone id of the process.\n"); mdb_printf("SERVICE The SMF service FMRI of the process.\n"); mdb_printf("UID\tThe user id of the process.\n"); mdb_printf("FLAGS\tThe process flags (see ::pflags).\n"); mdb_printf("ADDR\tThe kernel address of the proc_t structure of the " "process\n"); mdb_printf("NAME\tThe name (p_user.u_comm field) of the process. If " "the -f flag\n" "\tis specified, the arguments of the process are displayed.\n"); } #define PG_NEWEST 0x0001 #define PG_OLDEST 0x0002 #define PG_PIPE_OUT 0x0004 #define PG_EXACT_MATCH 0x0008 typedef struct pgrep_data { uint_t pg_flags; uint_t pg_psflags; uintptr_t pg_xaddr; hrtime_t pg_xstart; const char *pg_pat; #ifndef _KMDB regex_t pg_reg; #endif } pgrep_data_t; typedef struct mdb_pgrep_proc { struct { timestruc_t u_start; char u_comm[MAXCOMLEN + 1]; } p_user; } mdb_pgrep_proc_t; /*ARGSUSED*/ static int pgrep_cb(uintptr_t addr, const void *ignored, void *data) { mdb_pgrep_proc_t p; pgrep_data_t *pgp = data; #ifndef _KMDB regmatch_t pmatch; #endif if (mdb_ctf_vread(&p, "proc_t", "mdb_pgrep_proc_t", addr, 0) == -1) return (WALK_ERR); /* * kmdb doesn't have access to the reg* functions, so we fall back * to strstr/strcmp. */ #ifdef _KMDB if ((pgp->pg_flags & PG_EXACT_MATCH) ? (strcmp(p.p_user.u_comm, pgp->pg_pat) != 0) : (strstr(p.p_user.u_comm, pgp->pg_pat) == NULL)) return (WALK_NEXT); #else if (regexec(&pgp->pg_reg, p.p_user.u_comm, 1, &pmatch, 0) != 0) return (WALK_NEXT); if ((pgp->pg_flags & PG_EXACT_MATCH) && (pmatch.rm_so != 0 || p.p_user.u_comm[pmatch.rm_eo] != '\0')) return (WALK_NEXT); #endif if (pgp->pg_flags & (PG_NEWEST | PG_OLDEST)) { hrtime_t start; start = (hrtime_t)p.p_user.u_start.tv_sec * NANOSEC + p.p_user.u_start.tv_nsec; if (pgp->pg_flags & PG_NEWEST) { if (pgp->pg_xaddr == 0 || start > pgp->pg_xstart) { pgp->pg_xaddr = addr; pgp->pg_xstart = start; } } else { if (pgp->pg_xaddr == 0 || start < pgp->pg_xstart) { pgp->pg_xaddr = addr; pgp->pg_xstart = start; } } } else if (pgp->pg_flags & PG_PIPE_OUT) { mdb_printf("%p\n", addr); } else { if (mdb_call_dcmd("ps", addr, pgp->pg_psflags, 0, NULL) != 0) { mdb_warn("can't invoke 'ps'"); return (WALK_DONE); } pgp->pg_psflags &= ~DCMD_LOOPFIRST; } return (WALK_NEXT); } /*ARGSUSED*/ int pgrep(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pgrep_data_t pg; int i; #ifndef _KMDB int err; #endif if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); pg.pg_flags = 0; pg.pg_xaddr = 0; i = mdb_getopts(argc, argv, 'n', MDB_OPT_SETBITS, PG_NEWEST, &pg.pg_flags, 'o', MDB_OPT_SETBITS, PG_OLDEST, &pg.pg_flags, 'x', MDB_OPT_SETBITS, PG_EXACT_MATCH, &pg.pg_flags, NULL); argc -= i; argv += i; if (argc != 1) return (DCMD_USAGE); /* * -n and -o are mutually exclusive. */ if ((pg.pg_flags & PG_NEWEST) && (pg.pg_flags & PG_OLDEST)) return (DCMD_USAGE); if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (flags & DCMD_PIPE_OUT) pg.pg_flags |= PG_PIPE_OUT; pg.pg_pat = argv->a_un.a_str; if (DCMD_HDRSPEC(flags)) pg.pg_psflags = DCMD_ADDRSPEC | DCMD_LOOP | DCMD_LOOPFIRST; else pg.pg_psflags = DCMD_ADDRSPEC | DCMD_LOOP; #ifndef _KMDB if ((err = regcomp(&pg.pg_reg, pg.pg_pat, REG_EXTENDED)) != 0) { size_t nbytes; char *buf; nbytes = regerror(err, &pg.pg_reg, NULL, 0); buf = mdb_alloc(nbytes + 1, UM_SLEEP | UM_GC); (void) regerror(err, &pg.pg_reg, buf, nbytes); mdb_warn("%s\n", buf); return (DCMD_ERR); } #endif if (mdb_walk("proc", pgrep_cb, &pg) != 0) { mdb_warn("can't walk 'proc'"); return (DCMD_ERR); } if (pg.pg_xaddr != 0 && (pg.pg_flags & (PG_NEWEST | PG_OLDEST))) { if (pg.pg_flags & PG_PIPE_OUT) { mdb_printf("%p\n", pg.pg_xaddr); } else { if (mdb_call_dcmd("ps", pg.pg_xaddr, pg.pg_psflags, 0, NULL) != 0) { mdb_warn("can't invoke 'ps'"); return (DCMD_ERR); } } } return (DCMD_OK); } int task(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { task_t tk; kproject_t pj; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("task_cache", "task", argc, argv) == -1) { mdb_warn("can't walk task_cache"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %6s %6s %6s %6s %10s%\n", "ADDR", "TASKID", "PROJID", "ZONEID", "REFCNT", "FLAGS"); } if (mdb_vread(&tk, sizeof (task_t), addr) == -1) { mdb_warn("can't read task_t structure at %p", addr); return (DCMD_ERR); } if (mdb_vread(&pj, sizeof (kproject_t), (uintptr_t)tk.tk_proj) == -1) { mdb_warn("can't read project_t structure at %p", addr); return (DCMD_ERR); } mdb_printf("%0?p %6d %6d %6d %6u 0x%08x\n", addr, tk.tk_tkid, pj.kpj_id, pj.kpj_zoneid, tk.tk_hold_count, tk.tk_flags); return (DCMD_OK); } int project(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kproject_t pj; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("projects", "project", argc, argv) == -1) { mdb_warn("can't walk projects"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %6s %6s %6s%\n", "ADDR", "PROJID", "ZONEID", "REFCNT"); } if (mdb_vread(&pj, sizeof (kproject_t), addr) == -1) { mdb_warn("can't read kproject_t structure at %p", addr); return (DCMD_ERR); } mdb_printf("%0?p %6d %6d %6u\n", addr, pj.kpj_id, pj.kpj_zoneid, pj.kpj_count); return (DCMD_OK); } /* walk callouts themselves, either by list or id hash. */ int callout_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("callout doesn't support global walk"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (callout_t), UM_SLEEP); return (WALK_NEXT); } #define CALLOUT_WALK_BYLIST 0 #define CALLOUT_WALK_BYID 1 /* the walker arg switches between walking by list (0) and walking by id (1). */ int callout_walk_step(mdb_walk_state_t *wsp) { int retval; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (callout_t), wsp->walk_addr) == -1) { mdb_warn("failed to read callout at %p", wsp->walk_addr); return (WALK_DONE); } retval = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); if ((ulong_t)wsp->walk_arg == CALLOUT_WALK_BYID) { wsp->walk_addr = (uintptr_t)(((callout_t *)wsp->walk_data)->c_idnext); } else { wsp->walk_addr = (uintptr_t)(((callout_t *)wsp->walk_data)->c_clnext); } return (retval); } void callout_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (callout_t)); } /* * walker for callout lists. This is different from hashes and callouts. * Thankfully, it's also simpler. */ int callout_list_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("callout list doesn't support global walk"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (callout_list_t), UM_SLEEP); return (WALK_NEXT); } int callout_list_walk_step(mdb_walk_state_t *wsp) { int retval; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (callout_list_t), wsp->walk_addr) != sizeof (callout_list_t)) { mdb_warn("failed to read callout_list at %p", wsp->walk_addr); return (WALK_ERR); } retval = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t) (((callout_list_t *)wsp->walk_data)->cl_next); return (retval); } void callout_list_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (callout_list_t)); } /* routines/structs to walk callout table(s) */ typedef struct cot_data { callout_table_t *ct0; callout_table_t ct; callout_hash_t cot_idhash[CALLOUT_BUCKETS]; callout_hash_t cot_clhash[CALLOUT_BUCKETS]; kstat_named_t ct_kstat_data[CALLOUT_NUM_STATS]; int cotndx; int cotsize; } cot_data_t; int callout_table_walk_init(mdb_walk_state_t *wsp) { int max_ncpus; cot_data_t *cot_walk_data; cot_walk_data = mdb_alloc(sizeof (cot_data_t), UM_SLEEP); if (wsp->walk_addr == 0) { if (mdb_readvar(&cot_walk_data->ct0, "callout_table") == -1) { mdb_warn("failed to read 'callout_table'"); return (WALK_ERR); } if (mdb_readvar(&max_ncpus, "max_ncpus") == -1) { mdb_warn("failed to get callout_table array size"); return (WALK_ERR); } cot_walk_data->cotsize = CALLOUT_NTYPES * max_ncpus; wsp->walk_addr = (uintptr_t)cot_walk_data->ct0; } else { /* not a global walk */ cot_walk_data->cotsize = 1; } cot_walk_data->cotndx = 0; wsp->walk_data = cot_walk_data; return (WALK_NEXT); } int callout_table_walk_step(mdb_walk_state_t *wsp) { int retval; cot_data_t *cotwd = (cot_data_t *)wsp->walk_data; size_t size; if (cotwd->cotndx >= cotwd->cotsize) { return (WALK_DONE); } if (mdb_vread(&(cotwd->ct), sizeof (callout_table_t), wsp->walk_addr) != sizeof (callout_table_t)) { mdb_warn("failed to read callout_table at %p", wsp->walk_addr); return (WALK_ERR); } size = sizeof (callout_hash_t) * CALLOUT_BUCKETS; if (cotwd->ct.ct_idhash != NULL) { if (mdb_vread(cotwd->cot_idhash, size, (uintptr_t)(cotwd->ct.ct_idhash)) != size) { mdb_warn("failed to read id_hash at %p", cotwd->ct.ct_idhash); return (WALK_ERR); } } if (cotwd->ct.ct_clhash != NULL) { if (mdb_vread(&(cotwd->cot_clhash), size, (uintptr_t)cotwd->ct.ct_clhash) == -1) { mdb_warn("failed to read cl_hash at %p", cotwd->ct.ct_clhash); return (WALK_ERR); } } size = sizeof (kstat_named_t) * CALLOUT_NUM_STATS; if (cotwd->ct.ct_kstat_data != NULL) { if (mdb_vread(&(cotwd->ct_kstat_data), size, (uintptr_t)cotwd->ct.ct_kstat_data) == -1) { mdb_warn("failed to read kstats at %p", cotwd->ct.ct_kstat_data); return (WALK_ERR); } } retval = wsp->walk_callback(wsp->walk_addr, (void *)cotwd, wsp->walk_cbdata); cotwd->cotndx++; if (cotwd->cotndx >= cotwd->cotsize) { return (WALK_DONE); } wsp->walk_addr = (uintptr_t)((char *)wsp->walk_addr + sizeof (callout_table_t)); return (retval); } void callout_table_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (cot_data_t)); } static const char *co_typenames[] = { "R", "N" }; #define CO_PLAIN_ID(xid) ((xid) & CALLOUT_ID_MASK) #define TABLE_TO_SEQID(x) ((x) >> CALLOUT_TYPE_BITS) /* callout flags, in no particular order */ #define COF_REAL 0x00000001 #define COF_NORM 0x00000002 #define COF_LONG 0x00000004 #define COF_SHORT 0x00000008 #define COF_EMPTY 0x00000010 #define COF_TIME 0x00000020 #define COF_BEFORE 0x00000040 #define COF_AFTER 0x00000080 #define COF_SEQID 0x00000100 #define COF_FUNC 0x00000200 #define COF_ADDR 0x00000400 #define COF_EXEC 0x00000800 #define COF_HIRES 0x00001000 #define COF_ABS 0x00002000 #define COF_TABLE 0x00004000 #define COF_BYIDH 0x00008000 #define COF_FREE 0x00010000 #define COF_LIST 0x00020000 #define COF_EXPREL 0x00040000 #define COF_HDR 0x00080000 #define COF_VERBOSE 0x00100000 #define COF_LONGLIST 0x00200000 #define COF_THDR 0x00400000 #define COF_LHDR 0x00800000 #define COF_CHDR 0x01000000 #define COF_PARAM 0x02000000 #define COF_DECODE 0x04000000 #define COF_HEAP 0x08000000 #define COF_QUEUE 0x10000000 /* show real and normal, short and long, expired and unexpired. */ #define COF_DEFAULT (COF_REAL | COF_NORM | COF_LONG | COF_SHORT) #define COF_LIST_FLAGS \ (CALLOUT_LIST_FLAG_HRESTIME | CALLOUT_LIST_FLAG_ABSOLUTE) /* private callout data for callback functions */ typedef struct callout_data { uint_t flags; /* COF_* */ cpu_t *cpu; /* cpu pointer if given */ int seqid; /* cpu seqid, or -1 */ hrtime_t time; /* expiration time value */ hrtime_t atime; /* expiration before value */ hrtime_t btime; /* expiration after value */ uintptr_t funcaddr; /* function address or NULL */ uintptr_t param; /* parameter to function or NULL */ hrtime_t now; /* current system time */ int nsec_per_tick; /* for conversions */ ulong_t ctbits; /* for decoding xid */ callout_table_t *co_table; /* top of callout table array */ int ndx; /* table index. */ int bucket; /* which list/id bucket are we in */ hrtime_t exp; /* expire time */ int list_flags; /* copy of cl_flags */ } callout_data_t; /* this callback does the actual callback itself (finally). */ /*ARGSUSED*/ static int callouts_cb(uintptr_t addr, const void *data, void *priv) { callout_data_t *coargs = (callout_data_t *)priv; callout_t *co = (callout_t *)data; int tableid, list_flags; callout_id_t coid; if ((coargs == NULL) || (co == NULL)) { return (WALK_ERR); } if ((coargs->flags & COF_FREE) && !(co->c_xid & CALLOUT_ID_FREE)) { /* * The callout must have been reallocated. No point in * walking any more. */ return (WALK_DONE); } if (!(coargs->flags & COF_FREE) && (co->c_xid & CALLOUT_ID_FREE)) { /* * The callout must have been freed. No point in * walking any more. */ return (WALK_DONE); } if ((coargs->flags & COF_FUNC) && (coargs->funcaddr != (uintptr_t)co->c_func)) { return (WALK_NEXT); } if ((coargs->flags & COF_PARAM) && (coargs->param != (uintptr_t)co->c_arg)) { return (WALK_NEXT); } if (!(coargs->flags & COF_LONG) && (co->c_xid & CALLOUT_LONGTERM)) { return (WALK_NEXT); } if (!(coargs->flags & COF_SHORT) && !(co->c_xid & CALLOUT_LONGTERM)) { return (WALK_NEXT); } if ((coargs->flags & COF_EXEC) && !(co->c_xid & CALLOUT_EXECUTING)) { return (WALK_NEXT); } /* it is possible we don't have the exp time or flags */ if (coargs->flags & COF_BYIDH) { if (!(coargs->flags & COF_FREE)) { /* we have to fetch the expire time ourselves. */ if (mdb_vread(&coargs->exp, sizeof (hrtime_t), (uintptr_t)co->c_list + offsetof(callout_list_t, cl_expiration)) == -1) { mdb_warn("failed to read expiration " "time from %p", co->c_list); coargs->exp = 0; } /* and flags. */ if (mdb_vread(&coargs->list_flags, sizeof (int), (uintptr_t)co->c_list + offsetof(callout_list_t, cl_flags)) == -1) { mdb_warn("failed to read list flags" "from %p", co->c_list); coargs->list_flags = 0; } } else { /* free callouts can't use list pointer. */ coargs->exp = 0; coargs->list_flags = 0; } if (coargs->exp != 0) { if ((coargs->flags & COF_TIME) && (coargs->exp != coargs->time)) { return (WALK_NEXT); } if ((coargs->flags & COF_BEFORE) && (coargs->exp > coargs->btime)) { return (WALK_NEXT); } if ((coargs->flags & COF_AFTER) && (coargs->exp < coargs->atime)) { return (WALK_NEXT); } } /* tricky part, since both HIRES and ABS can be set */ list_flags = coargs->list_flags; if ((coargs->flags & COF_HIRES) && (coargs->flags & COF_ABS)) { /* both flags are set, only skip "regular" ones */ if (! (list_flags & COF_LIST_FLAGS)) { return (WALK_NEXT); } } else { /* individual flags, or no flags */ if ((coargs->flags & COF_HIRES) && !(list_flags & CALLOUT_LIST_FLAG_HRESTIME)) { return (WALK_NEXT); } if ((coargs->flags & COF_ABS) && !(list_flags & CALLOUT_LIST_FLAG_ABSOLUTE)) { return (WALK_NEXT); } } /* * We do the checks for COF_HEAP and COF_QUEUE here only if we * are traversing BYIDH. If the traversal is by callout list, * we do this check in callout_list_cb() to be more * efficient. */ if ((coargs->flags & COF_HEAP) && !(list_flags & CALLOUT_LIST_FLAG_HEAPED)) { return (WALK_NEXT); } if ((coargs->flags & COF_QUEUE) && !(list_flags & CALLOUT_LIST_FLAG_QUEUED)) { return (WALK_NEXT); } } #define callout_table_mask ((1 << coargs->ctbits) - 1) tableid = CALLOUT_ID_TO_TABLE(co->c_xid); #undef callout_table_mask coid = CO_PLAIN_ID(co->c_xid); if ((coargs->flags & COF_CHDR) && !(coargs->flags & COF_ADDR)) { /* * We need to print the headers. If walking by id, then * the list header isn't printed, so we must include * that info here. */ if (!(coargs->flags & COF_VERBOSE)) { mdb_printf("%%3s %-1s %-14s %", "SEQ", "T", "EXP"); } else if (coargs->flags & COF_BYIDH) { mdb_printf("%%-14s %", "EXP"); } mdb_printf("%%-4s %-?s %-20s%", "XHAL", "XID", "FUNC(ARG)"); if (coargs->flags & COF_LONGLIST) { mdb_printf("% %-?s %-?s %-?s %-?s%", "PREVID", "NEXTID", "PREVL", "NEXTL"); mdb_printf("% %-?s %-4s %-?s%", "DONE", "UTOS", "THREAD"); } mdb_printf("\n"); coargs->flags &= ~COF_CHDR; coargs->flags |= (COF_THDR | COF_LHDR); } if (!(coargs->flags & COF_ADDR)) { if (!(coargs->flags & COF_VERBOSE)) { mdb_printf("%-3d %1s %-14llx ", TABLE_TO_SEQID(tableid), co_typenames[tableid & CALLOUT_TYPE_MASK], (coargs->flags & COF_EXPREL) ? coargs->exp - coargs->now : coargs->exp); } else if (coargs->flags & COF_BYIDH) { mdb_printf("%-14x ", (coargs->flags & COF_EXPREL) ? coargs->exp - coargs->now : coargs->exp); } list_flags = coargs->list_flags; mdb_printf("%1s%1s%1s%1s %-?llx %a(%p)", (co->c_xid & CALLOUT_EXECUTING) ? "X" : " ", (list_flags & CALLOUT_LIST_FLAG_HRESTIME) ? "H" : " ", (list_flags & CALLOUT_LIST_FLAG_ABSOLUTE) ? "A" : " ", (co->c_xid & CALLOUT_LONGTERM) ? "L" : " ", (long long)coid, co->c_func, co->c_arg); if (coargs->flags & COF_LONGLIST) { mdb_printf(" %-?p %-?p %-?p %-?p", co->c_idprev, co->c_idnext, co->c_clprev, co->c_clnext); mdb_printf(" %-?p %-4d %-0?p", co->c_done, co->c_waiting, co->c_executor); } } else { /* address only */ mdb_printf("%-0p", addr); } mdb_printf("\n"); return (WALK_NEXT); } /* this callback is for callout list handling. idhash is done by callout_t_cb */ /*ARGSUSED*/ static int callout_list_cb(uintptr_t addr, const void *data, void *priv) { callout_data_t *coargs = (callout_data_t *)priv; callout_list_t *cl = (callout_list_t *)data; callout_t *coptr; int list_flags; if ((coargs == NULL) || (cl == NULL)) { return (WALK_ERR); } coargs->exp = cl->cl_expiration; coargs->list_flags = cl->cl_flags; if ((coargs->flags & COF_FREE) && !(cl->cl_flags & CALLOUT_LIST_FLAG_FREE)) { /* * The callout list must have been reallocated. No point in * walking any more. */ return (WALK_DONE); } if (!(coargs->flags & COF_FREE) && (cl->cl_flags & CALLOUT_LIST_FLAG_FREE)) { /* * The callout list must have been freed. No point in * walking any more. */ return (WALK_DONE); } if ((coargs->flags & COF_TIME) && (cl->cl_expiration != coargs->time)) { return (WALK_NEXT); } if ((coargs->flags & COF_BEFORE) && (cl->cl_expiration > coargs->btime)) { return (WALK_NEXT); } if ((coargs->flags & COF_AFTER) && (cl->cl_expiration < coargs->atime)) { return (WALK_NEXT); } if (!(coargs->flags & COF_EMPTY) && (cl->cl_callouts.ch_head == NULL)) { return (WALK_NEXT); } /* FOUR cases, each different, !A!B, !AB, A!B, AB */ if ((coargs->flags & COF_HIRES) && (coargs->flags & COF_ABS)) { /* both flags are set, only skip "regular" ones */ if (! (cl->cl_flags & COF_LIST_FLAGS)) { return (WALK_NEXT); } } else { if ((coargs->flags & COF_HIRES) && !(cl->cl_flags & CALLOUT_LIST_FLAG_HRESTIME)) { return (WALK_NEXT); } if ((coargs->flags & COF_ABS) && !(cl->cl_flags & CALLOUT_LIST_FLAG_ABSOLUTE)) { return (WALK_NEXT); } } if ((coargs->flags & COF_HEAP) && !(coargs->list_flags & CALLOUT_LIST_FLAG_HEAPED)) { return (WALK_NEXT); } if ((coargs->flags & COF_QUEUE) && !(coargs->list_flags & CALLOUT_LIST_FLAG_QUEUED)) { return (WALK_NEXT); } if ((coargs->flags & COF_LHDR) && !(coargs->flags & COF_ADDR) && (coargs->flags & (COF_LIST | COF_VERBOSE))) { if (!(coargs->flags & COF_VERBOSE)) { /* don't be redundant again */ mdb_printf("%SEQ T %"); } mdb_printf("%EXP HA BUCKET " "CALLOUTS %"); if (coargs->flags & COF_LONGLIST) { mdb_printf("% %-?s %-?s%", "PREV", "NEXT"); } mdb_printf("\n"); coargs->flags &= ~COF_LHDR; coargs->flags |= (COF_THDR | COF_CHDR); } if (coargs->flags & (COF_LIST | COF_VERBOSE)) { if (!(coargs->flags & COF_ADDR)) { if (!(coargs->flags & COF_VERBOSE)) { mdb_printf("%3d %1s ", TABLE_TO_SEQID(coargs->ndx), co_typenames[coargs->ndx & CALLOUT_TYPE_MASK]); } list_flags = coargs->list_flags; mdb_printf("%-14llx %1s%1s %-6d %-0?p ", (coargs->flags & COF_EXPREL) ? coargs->exp - coargs->now : coargs->exp, (list_flags & CALLOUT_LIST_FLAG_HRESTIME) ? "H" : " ", (list_flags & CALLOUT_LIST_FLAG_ABSOLUTE) ? "A" : " ", coargs->bucket, cl->cl_callouts.ch_head); if (coargs->flags & COF_LONGLIST) { mdb_printf(" %-?p %-?p", cl->cl_prev, cl->cl_next); } } else { /* address only */ mdb_printf("%-0p", addr); } mdb_printf("\n"); if (coargs->flags & COF_LIST) { return (WALK_NEXT); } } /* yet another layer as we walk the actual callouts via list. */ if (cl->cl_callouts.ch_head == NULL) { return (WALK_NEXT); } /* free list structures do not have valid callouts off of them. */ if (coargs->flags & COF_FREE) { return (WALK_NEXT); } coptr = (callout_t *)cl->cl_callouts.ch_head; if (coargs->flags & COF_VERBOSE) { mdb_inc_indent(4); } /* * walk callouts using yet another callback routine. * we use callouts_bytime because id hash is handled via * the callout_t_cb callback. */ if (mdb_pwalk("callouts_bytime", callouts_cb, coargs, (uintptr_t)coptr) == -1) { mdb_warn("cannot walk callouts at %p", coptr); return (WALK_ERR); } if (coargs->flags & COF_VERBOSE) { mdb_dec_indent(4); } return (WALK_NEXT); } /* this callback handles the details of callout table walking. */ static int callout_t_cb(uintptr_t addr, const void *data, void *priv) { callout_data_t *coargs = (callout_data_t *)priv; cot_data_t *cotwd = (cot_data_t *)data; callout_table_t *ct = &(cotwd->ct); int index, seqid, cotype; int i; callout_list_t *clptr; callout_t *coptr; if ((coargs == NULL) || (ct == NULL) || (coargs->co_table == NULL)) { return (WALK_ERR); } index = ((char *)addr - (char *)coargs->co_table) / sizeof (callout_table_t); cotype = index & CALLOUT_TYPE_MASK; seqid = TABLE_TO_SEQID(index); if ((coargs->flags & COF_SEQID) && (coargs->seqid != seqid)) { return (WALK_NEXT); } if (!(coargs->flags & COF_REAL) && (cotype == CALLOUT_REALTIME)) { return (WALK_NEXT); } if (!(coargs->flags & COF_NORM) && (cotype == CALLOUT_NORMAL)) { return (WALK_NEXT); } if (!(coargs->flags & COF_EMPTY) && ( (ct->ct_heap == NULL) || (ct->ct_cyclic == 0))) { return (WALK_NEXT); } if ((coargs->flags & COF_THDR) && !(coargs->flags & COF_ADDR) && (coargs->flags & (COF_TABLE | COF_VERBOSE))) { /* print table hdr */ mdb_printf("%%-3s %-1s %-?s %-?s %-?s %-?s%", "SEQ", "T", "FREE", "LFREE", "CYCLIC", "HEAP"); coargs->flags &= ~COF_THDR; coargs->flags |= (COF_LHDR | COF_CHDR); if (coargs->flags & COF_LONGLIST) { /* more info! */ mdb_printf("% %-T%-7s %-7s %-?s %-?s %-?s" " %-?s %-?s %-?s%", "HEAPNUM", "HEAPMAX", "TASKQ", "EXPQ", "QUE", "PEND", "FREE", "LOCK"); } mdb_printf("\n"); } if (coargs->flags & (COF_TABLE | COF_VERBOSE)) { if (!(coargs->flags & COF_ADDR)) { mdb_printf("%-3d %-1s %-0?p %-0?p %-0?p %-?p", seqid, co_typenames[cotype], ct->ct_free, ct->ct_lfree, ct->ct_cyclic, ct->ct_heap); if (coargs->flags & COF_LONGLIST) { /* more info! */ mdb_printf(" %-7d %-7d %-?p %-?p %-?p" " %-?lld %-?lld %-?p", ct->ct_heap_num, ct->ct_heap_max, ct->ct_taskq, ct->ct_expired.ch_head, ct->ct_queue.ch_head, cotwd->ct_timeouts_pending, cotwd->ct_allocations - cotwd->ct_timeouts_pending, ct->ct_mutex); } } else { /* address only */ mdb_printf("%-0?p", addr); } mdb_printf("\n"); if (coargs->flags & COF_TABLE) { return (WALK_NEXT); } } coargs->ndx = index; if (coargs->flags & COF_VERBOSE) { mdb_inc_indent(4); } /* keep digging. */ if (!(coargs->flags & COF_BYIDH)) { /* walk the list hash table */ if (coargs->flags & COF_FREE) { clptr = ct->ct_lfree; coargs->bucket = 0; if (clptr == NULL) { return (WALK_NEXT); } if (mdb_pwalk("callout_list", callout_list_cb, coargs, (uintptr_t)clptr) == -1) { mdb_warn("cannot walk callout free list at %p", clptr); return (WALK_ERR); } } else { /* first print the expired list. */ clptr = (callout_list_t *)ct->ct_expired.ch_head; if (clptr != NULL) { coargs->bucket = -1; if (mdb_pwalk("callout_list", callout_list_cb, coargs, (uintptr_t)clptr) == -1) { mdb_warn("cannot walk callout_list" " at %p", clptr); return (WALK_ERR); } } /* then, print the callout queue */ clptr = (callout_list_t *)ct->ct_queue.ch_head; if (clptr != NULL) { coargs->bucket = -1; if (mdb_pwalk("callout_list", callout_list_cb, coargs, (uintptr_t)clptr) == -1) { mdb_warn("cannot walk callout_list" " at %p", clptr); return (WALK_ERR); } } for (i = 0; i < CALLOUT_BUCKETS; i++) { if (ct->ct_clhash == NULL) { /* nothing to do */ break; } if (cotwd->cot_clhash[i].ch_head == NULL) { continue; } clptr = (callout_list_t *) cotwd->cot_clhash[i].ch_head; coargs->bucket = i; /* walk list with callback routine. */ if (mdb_pwalk("callout_list", callout_list_cb, coargs, (uintptr_t)clptr) == -1) { mdb_warn("cannot walk callout_list" " at %p", clptr); return (WALK_ERR); } } } } else { /* walk the id hash table. */ if (coargs->flags & COF_FREE) { coptr = ct->ct_free; coargs->bucket = 0; if (coptr == NULL) { return (WALK_NEXT); } if (mdb_pwalk("callouts_byid", callouts_cb, coargs, (uintptr_t)coptr) == -1) { mdb_warn("cannot walk callout id free list" " at %p", coptr); return (WALK_ERR); } } else { for (i = 0; i < CALLOUT_BUCKETS; i++) { if (ct->ct_idhash == NULL) { break; } coptr = (callout_t *) cotwd->cot_idhash[i].ch_head; if (coptr == NULL) { continue; } coargs->bucket = i; /* * walk callouts directly by id. For id * chain, the callout list is just a header, * so there's no need to walk it. */ if (mdb_pwalk("callouts_byid", callouts_cb, coargs, (uintptr_t)coptr) == -1) { mdb_warn("cannot walk callouts at %p", coptr); return (WALK_ERR); } } } } if (coargs->flags & COF_VERBOSE) { mdb_dec_indent(4); } return (WALK_NEXT); } /* * initialize some common info for both callout dcmds. */ int callout_common_init(callout_data_t *coargs) { /* we need a couple of things */ if (mdb_readvar(&(coargs->co_table), "callout_table") == -1) { mdb_warn("failed to read 'callout_table'"); return (DCMD_ERR); } /* need to get now in nsecs. Approximate with hrtime vars */ if (mdb_readsym(&(coargs->now), sizeof (hrtime_t), "hrtime_last") != sizeof (hrtime_t)) { if (mdb_readsym(&(coargs->now), sizeof (hrtime_t), "hrtime_base") != sizeof (hrtime_t)) { mdb_warn("Could not determine current system time"); return (DCMD_ERR); } } if (mdb_readvar(&(coargs->ctbits), "callout_table_bits") == -1) { mdb_warn("failed to read 'callout_table_bits'"); return (DCMD_ERR); } if (mdb_readvar(&(coargs->nsec_per_tick), "nsec_per_tick") == -1) { mdb_warn("failed to read 'nsec_per_tick'"); return (DCMD_ERR); } return (DCMD_OK); } /* * dcmd to print callouts. Optional addr limits to specific table. * Parses lots of options that get passed to callbacks for walkers. * Has it's own help function. */ /*ARGSUSED*/ int callout(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { callout_data_t coargs; /* getopts doesn't help much with stuff like this */ boolean_t Sflag, Cflag, tflag, aflag, bflag, dflag, kflag; char *funcname = NULL; char *paramstr = NULL; uintptr_t Stmp, Ctmp; /* for getopt. */ int retval; coargs.flags = COF_DEFAULT; Sflag = Cflag = tflag = bflag = aflag = dflag = kflag = FALSE; coargs.seqid = -1; if (mdb_getopts(argc, argv, 'r', MDB_OPT_CLRBITS, COF_NORM, &coargs.flags, 'n', MDB_OPT_CLRBITS, COF_REAL, &coargs.flags, 'l', MDB_OPT_CLRBITS, COF_SHORT, &coargs.flags, 's', MDB_OPT_CLRBITS, COF_LONG, &coargs.flags, 'x', MDB_OPT_SETBITS, COF_EXEC, &coargs.flags, 'h', MDB_OPT_SETBITS, COF_HIRES, &coargs.flags, 'B', MDB_OPT_SETBITS, COF_ABS, &coargs.flags, 'E', MDB_OPT_SETBITS, COF_EMPTY, &coargs.flags, 'd', MDB_OPT_SETBITS, 1, &dflag, 'C', MDB_OPT_UINTPTR_SET, &Cflag, &Ctmp, 'S', MDB_OPT_UINTPTR_SET, &Sflag, &Stmp, 't', MDB_OPT_UINTPTR_SET, &tflag, (uintptr_t *)&coargs.time, 'a', MDB_OPT_UINTPTR_SET, &aflag, (uintptr_t *)&coargs.atime, 'b', MDB_OPT_UINTPTR_SET, &bflag, (uintptr_t *)&coargs.btime, 'k', MDB_OPT_SETBITS, 1, &kflag, 'f', MDB_OPT_STR, &funcname, 'p', MDB_OPT_STR, ¶mstr, 'T', MDB_OPT_SETBITS, COF_TABLE, &coargs.flags, 'D', MDB_OPT_SETBITS, COF_EXPREL, &coargs.flags, 'L', MDB_OPT_SETBITS, COF_LIST, &coargs.flags, 'V', MDB_OPT_SETBITS, COF_VERBOSE, &coargs.flags, 'v', MDB_OPT_SETBITS, COF_LONGLIST, &coargs.flags, 'i', MDB_OPT_SETBITS, COF_BYIDH, &coargs.flags, 'F', MDB_OPT_SETBITS, COF_FREE, &coargs.flags, 'H', MDB_OPT_SETBITS, COF_HEAP, &coargs.flags, 'Q', MDB_OPT_SETBITS, COF_QUEUE, &coargs.flags, 'A', MDB_OPT_SETBITS, COF_ADDR, &coargs.flags, NULL) != argc) { return (DCMD_USAGE); } /* initialize from kernel variables */ if ((retval = callout_common_init(&coargs)) != DCMD_OK) { return (retval); } /* do some option post-processing */ if (kflag) { coargs.time *= coargs.nsec_per_tick; coargs.atime *= coargs.nsec_per_tick; coargs.btime *= coargs.nsec_per_tick; } if (dflag) { coargs.time += coargs.now; coargs.atime += coargs.now; coargs.btime += coargs.now; } if (Sflag) { if (flags & DCMD_ADDRSPEC) { mdb_printf("-S option conflicts with explicit" " address\n"); return (DCMD_USAGE); } coargs.flags |= COF_SEQID; coargs.seqid = (int)Stmp; } if (Cflag) { if (flags & DCMD_ADDRSPEC) { mdb_printf("-C option conflicts with explicit" " address\n"); return (DCMD_USAGE); } if (coargs.flags & COF_SEQID) { mdb_printf("-C and -S are mutually exclusive\n"); return (DCMD_USAGE); } coargs.cpu = (cpu_t *)Ctmp; if (mdb_vread(&coargs.seqid, sizeof (processorid_t), (uintptr_t)&(coargs.cpu->cpu_seqid)) == -1) { mdb_warn("failed to read cpu_t at %p", Ctmp); return (DCMD_ERR); } coargs.flags |= COF_SEQID; } /* avoid null outputs. */ if (!(coargs.flags & (COF_REAL | COF_NORM))) { coargs.flags |= COF_REAL | COF_NORM; } if (!(coargs.flags & (COF_LONG | COF_SHORT))) { coargs.flags |= COF_LONG | COF_SHORT; } if (tflag) { if (aflag || bflag) { mdb_printf("-t and -a|b are mutually exclusive\n"); return (DCMD_USAGE); } coargs.flags |= COF_TIME; } if (aflag) { coargs.flags |= COF_AFTER; } if (bflag) { coargs.flags |= COF_BEFORE; } if ((aflag && bflag) && (coargs.btime <= coargs.atime)) { mdb_printf("value for -a must be earlier than the value" " for -b.\n"); return (DCMD_USAGE); } if ((coargs.flags & COF_HEAP) && (coargs.flags & COF_QUEUE)) { mdb_printf("-H and -Q are mutually exclusive\n"); return (DCMD_USAGE); } if (funcname != NULL) { GElf_Sym sym; if (mdb_lookup_by_name(funcname, &sym) != 0) { coargs.funcaddr = mdb_strtoull(funcname); } else { coargs.funcaddr = sym.st_value; } coargs.flags |= COF_FUNC; } if (paramstr != NULL) { GElf_Sym sym; if (mdb_lookup_by_name(paramstr, &sym) != 0) { coargs.param = mdb_strtoull(paramstr); } else { coargs.param = sym.st_value; } coargs.flags |= COF_PARAM; } if (!(flags & DCMD_ADDRSPEC)) { /* don't pass "dot" if no addr. */ addr = 0; } if (addr != 0) { /* * a callout table was specified. Ignore -r|n option * to avoid null output. */ coargs.flags |= (COF_REAL | COF_NORM); } if (DCMD_HDRSPEC(flags) || (coargs.flags & COF_VERBOSE)) { coargs.flags |= COF_THDR | COF_LHDR | COF_CHDR; } if (coargs.flags & COF_FREE) { coargs.flags |= COF_EMPTY; /* -F = free callouts, -FL = free lists */ if (!(coargs.flags & COF_LIST)) { coargs.flags |= COF_BYIDH; } } /* walk table, using specialized callback routine. */ if (mdb_pwalk("callout_table", callout_t_cb, &coargs, addr) == -1) { mdb_warn("cannot walk callout_table"); return (DCMD_ERR); } return (DCMD_OK); } /* * Given an extended callout id, dump its information. */ /*ARGSUSED*/ int calloutid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { callout_data_t coargs; callout_table_t *ctptr; callout_table_t ct; callout_id_t coid; callout_t *coptr; int tableid; callout_id_t xid; ulong_t idhash; int i, retval; const mdb_arg_t *arg; size_t size; callout_hash_t cot_idhash[CALLOUT_BUCKETS]; coargs.flags = COF_DEFAULT | COF_BYIDH; i = mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, COF_DECODE, &coargs.flags, 'v', MDB_OPT_SETBITS, COF_LONGLIST, &coargs.flags, NULL); argc -= i; argv += i; if (argc != 1) { return (DCMD_USAGE); } arg = &argv[0]; xid = (callout_id_t)mdb_argtoull(arg); if (DCMD_HDRSPEC(flags)) { coargs.flags |= COF_CHDR; } /* initialize from kernel variables */ if ((retval = callout_common_init(&coargs)) != DCMD_OK) { return (retval); } /* we must massage the environment so that the macros will play nice */ #define callout_table_mask ((1 << coargs.ctbits) - 1) #define callout_table_bits coargs.ctbits #define nsec_per_tick coargs.nsec_per_tick tableid = CALLOUT_ID_TO_TABLE(xid); idhash = CALLOUT_IDHASH(xid); #undef callouts_table_bits #undef callout_table_mask #undef nsec_per_tick coid = CO_PLAIN_ID(xid); if (flags & DCMD_ADDRSPEC) { mdb_printf("calloutid does not accept explicit address.\n"); return (DCMD_USAGE); } if (coargs.flags & COF_DECODE) { if (DCMD_HDRSPEC(flags)) { mdb_printf("%%3s %1s %2s %-?s %-6s %\n", "SEQ", "T", "XL", "XID", "IDHASH"); } mdb_printf("%-3d %1s %1s%1s %-?llx %-6d\n", TABLE_TO_SEQID(tableid), co_typenames[tableid & CALLOUT_TYPE_MASK], (xid & CALLOUT_EXECUTING) ? "X" : " ", (xid & CALLOUT_LONGTERM) ? "L" : " ", (long long)coid, idhash); return (DCMD_OK); } /* get our table. Note this relies on the types being correct */ ctptr = coargs.co_table + tableid; if (mdb_vread(&ct, sizeof (callout_table_t), (uintptr_t)ctptr) == -1) { mdb_warn("failed to read callout_table at %p", ctptr); return (DCMD_ERR); } size = sizeof (callout_hash_t) * CALLOUT_BUCKETS; if (ct.ct_idhash != NULL) { if (mdb_vread(&(cot_idhash), size, (uintptr_t)ct.ct_idhash) == -1) { mdb_warn("failed to read id_hash at %p", ct.ct_idhash); return (WALK_ERR); } } /* callout at beginning of hash chain */ if (ct.ct_idhash == NULL) { mdb_printf("id hash chain for this xid is empty\n"); return (DCMD_ERR); } coptr = (callout_t *)cot_idhash[idhash].ch_head; if (coptr == NULL) { mdb_printf("id hash chain for this xid is empty\n"); return (DCMD_ERR); } coargs.ndx = tableid; coargs.bucket = idhash; /* use the walker, luke */ if (mdb_pwalk("callouts_byid", callouts_cb, &coargs, (uintptr_t)coptr) == -1) { mdb_warn("cannot walk callouts at %p", coptr); return (WALK_ERR); } return (DCMD_OK); } void callout_help(void) { mdb_printf("callout: display callouts.\n" "Given a callout table address, display callouts from table.\n" "Without an address, display callouts from all tables.\n" "options:\n" " -r|n : limit display to (r)ealtime or (n)ormal type callouts\n" " -s|l : limit display to (s)hort-term ids or (l)ong-term ids\n" " -x : limit display to callouts which are executing\n" " -h : limit display to callouts based on hrestime\n" " -B : limit display to callouts based on absolute time\n" " -t|a|b nsec: limit display to callouts that expire a(t) time," " (a)fter time,\n or (b)efore time. Use -a and -b together " " to specify a range.\n For \"now\", use -d[t|a|b] 0.\n" " -d : interpret time option to -t|a|b as delta from current time\n" " -k : use ticks instead of nanoseconds as arguments to" " -t|a|b. Note that\n ticks are less accurate and may not" " match other tick times (ie: lbolt).\n" " -D : display exiration time as delta from current time\n" " -S seqid : limit display to callouts for this cpu sequence id\n" " -C addr : limit display to callouts for this cpu pointer\n" " -f name|addr : limit display to callouts with this function\n" " -p name|addr : limit display to callouts functions with this" " parameter\n" " -T : display the callout table itself, instead of callouts\n" " -L : display callout lists instead of callouts\n" " -E : with -T or L, display empty data structures.\n" " -i : traverse callouts by id hash instead of list hash\n" " -F : walk free callout list (free list with -i) instead\n" " -v : display more info for each item\n" " -V : show details of each level of info as it is traversed\n" " -H : limit display to callouts in the callout heap\n" " -Q : limit display to callouts in the callout queue\n" " -A : show only addresses. Useful for pipelines.\n"); } void calloutid_help(void) { mdb_printf("calloutid: display callout by id.\n" "Given an extended callout id, display the callout infomation.\n" "options:\n" " -d : do not dereference callout, just decode the id.\n" " -v : verbose display more info about the callout\n"); } /*ARGSUSED*/ int class(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { long num_classes, i; sclass_t *class_tbl; GElf_Sym g_sclass; char class_name[PC_CLNMSZ]; size_t tbl_size; if (mdb_lookup_by_name("sclass", &g_sclass) == -1) { mdb_warn("failed to find symbol sclass\n"); return (DCMD_ERR); } tbl_size = (size_t)g_sclass.st_size; num_classes = tbl_size / (sizeof (sclass_t)); class_tbl = mdb_alloc(tbl_size, UM_SLEEP | UM_GC); if (mdb_readsym(class_tbl, tbl_size, "sclass") == -1) { mdb_warn("failed to read sclass"); return (DCMD_ERR); } mdb_printf("%%4s %-10s %-24s %-24s%\n", "SLOT", "NAME", "INIT FCN", "CLASS FCN"); for (i = 0; i < num_classes; i++) { if (mdb_vread(class_name, sizeof (class_name), (uintptr_t)class_tbl[i].cl_name) == -1) (void) strcpy(class_name, "???"); mdb_printf("%4ld %-10s %-24a %-24a\n", i, class_name, class_tbl[i].cl_init, class_tbl[i].cl_funcs); } return (DCMD_OK); } #define FSNAMELEN 32 /* Max len of FS name we read from vnodeops */ int vnode2path(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t rootdir; vnode_t vn; char buf[MAXPATHLEN]; uint_t opt_F = FALSE; if (mdb_getopts(argc, argv, 'F', MDB_OPT_SETBITS, TRUE, &opt_F, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("expected explicit vnode_t address before ::\n"); return (DCMD_USAGE); } if (mdb_readvar(&rootdir, "rootdir") == -1) { mdb_warn("failed to read rootdir"); return (DCMD_ERR); } if (mdb_vnode2path(addr, buf, sizeof (buf)) == -1) return (DCMD_ERR); if (*buf == '\0') { mdb_printf("??\n"); return (DCMD_OK); } mdb_printf("%s", buf); if (opt_F && buf[strlen(buf)-1] != '/' && mdb_vread(&vn, sizeof (vn), addr) == sizeof (vn)) mdb_printf("%c", mdb_vtype2chr(vn.v_type, 0)); mdb_printf("\n"); return (DCMD_OK); } int ld_walk_init(mdb_walk_state_t *wsp) { wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int ld_walk_step(mdb_walk_state_t *wsp) { int status; lock_descriptor_t ld; if (mdb_vread(&ld, sizeof (lock_descriptor_t), wsp->walk_addr) == -1) { mdb_warn("couldn't read lock_descriptor_t at %p\n", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &ld, wsp->walk_cbdata); if (status == WALK_ERR) return (WALK_ERR); wsp->walk_addr = (uintptr_t)ld.l_next; if (wsp->walk_addr == (uintptr_t)wsp->walk_data) return (WALK_DONE); return (status); } int lg_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; if (mdb_lookup_by_name("lock_graph", &sym) == -1) { mdb_warn("failed to find symbol 'lock_graph'\n"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sym.st_value; wsp->walk_data = (void *)(uintptr_t)(sym.st_value + sym.st_size); return (WALK_NEXT); } typedef struct lg_walk_data { uintptr_t startaddr; mdb_walk_cb_t callback; void *data; } lg_walk_data_t; /* * We can't use ::walk lock_descriptor directly, because the head of each graph * is really a dummy lock. Rather than trying to dynamically determine if this * is a dummy node or not, we just filter out the initial element of the * list. */ static int lg_walk_cb(uintptr_t addr, const void *data, void *priv) { lg_walk_data_t *lw = priv; if (addr != lw->startaddr) return (lw->callback(addr, data, lw->data)); return (WALK_NEXT); } int lg_walk_step(mdb_walk_state_t *wsp) { graph_t *graph; lg_walk_data_t lw; if (wsp->walk_addr >= (uintptr_t)wsp->walk_data) return (WALK_DONE); if (mdb_vread(&graph, sizeof (graph), wsp->walk_addr) == -1) { mdb_warn("failed to read graph_t at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr += sizeof (graph); if (graph == NULL) return (WALK_NEXT); lw.callback = wsp->walk_callback; lw.data = wsp->walk_cbdata; lw.startaddr = (uintptr_t)&(graph->active_locks); if (mdb_pwalk("lock_descriptor", lg_walk_cb, &lw, lw.startaddr)) { mdb_warn("couldn't walk lock_descriptor at %p\n", lw.startaddr); return (WALK_ERR); } lw.startaddr = (uintptr_t)&(graph->sleeping_locks); if (mdb_pwalk("lock_descriptor", lg_walk_cb, &lw, lw.startaddr)) { mdb_warn("couldn't walk lock_descriptor at %p\n", lw.startaddr); return (WALK_ERR); } return (WALK_NEXT); } /* * The space available for the path corresponding to the locked vnode depends * on whether we are printing 32- or 64-bit addresses. */ #ifdef _LP64 #define LM_VNPATHLEN 20 #else #define LM_VNPATHLEN 30 #endif typedef struct mdb_lminfo_proc { struct { char u_comm[MAXCOMLEN + 1]; } p_user; } mdb_lminfo_proc_t; /*ARGSUSED*/ static int lminfo_cb(uintptr_t addr, const void *data, void *priv) { const lock_descriptor_t *ld = data; char buf[LM_VNPATHLEN]; mdb_lminfo_proc_t p; uintptr_t paddr = 0; if (ld->l_flock.l_pid != 0) paddr = mdb_pid2proc(ld->l_flock.l_pid, NULL); if (paddr != 0) mdb_ctf_vread(&p, "proc_t", "mdb_lminfo_proc_t", paddr, 0); mdb_printf("%-?p %2s %04x %6d %-16s %-?p ", addr, ld->l_type == F_RDLCK ? "RD" : ld->l_type == F_WRLCK ? "WR" : "??", ld->l_state, ld->l_flock.l_pid, ld->l_flock.l_pid == 0 ? "" : paddr == 0 ? "" : p.p_user.u_comm, ld->l_vnode); mdb_vnode2path((uintptr_t)ld->l_vnode, buf, sizeof (buf)); mdb_printf("%s\n", buf); return (WALK_NEXT); } /*ARGSUSED*/ int lminfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (DCMD_HDRSPEC(flags)) mdb_printf("%%-?s %2s %4s %6s %-16s %-?s %s%\n", "ADDR", "TP", "FLAG", "PID", "COMM", "VNODE", "PATH"); return (mdb_pwalk("lock_graph", lminfo_cb, NULL, 0)); } typedef struct mdb_whereopen { uint_t mwo_flags; uintptr_t mwo_target; boolean_t mwo_found; } mdb_whereopen_t; /*ARGSUSED*/ int whereopen_fwalk(uintptr_t addr, const void *farg, void *arg) { const struct file *f = farg; mdb_whereopen_t *mwo = arg; if ((uintptr_t)f->f_vnode == mwo->mwo_target) { if ((mwo->mwo_flags & DCMD_PIPE_OUT) == 0 && !mwo->mwo_found) { mdb_printf("file %p\n", addr); } mwo->mwo_found = B_TRUE; } return (WALK_NEXT); } /*ARGSUSED*/ int whereopen_pwalk(uintptr_t addr, const void *ignored, void *arg) { mdb_whereopen_t *mwo = arg; mwo->mwo_found = B_FALSE; if (mdb_pwalk("file", whereopen_fwalk, mwo, addr) == -1) { mdb_warn("couldn't file walk proc %p", addr); return (WALK_ERR); } if (mwo->mwo_found) { mdb_printf("%p\n", addr); } return (WALK_NEXT); } /*ARGSUSED*/ int whereopen(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_whereopen_t mwo; if (!(flags & DCMD_ADDRSPEC) || addr == 0) return (DCMD_USAGE); mwo.mwo_flags = flags; mwo.mwo_target = addr; mwo.mwo_found = B_FALSE; if (mdb_walk("proc", whereopen_pwalk, &mwo) == -1) { mdb_warn("can't proc walk"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct datafmt { char *hdr1; char *hdr2; char *dashes; char *fmt; } datafmt_t; static datafmt_t kmemfmt[] = { { "cache ", "name ", "-------------------------", "%-25s " }, { " buf", " size", "------", "%6u " }, { " buf", "in use", "------", "%6u " }, { " buf", " total", "------", "%6u " }, { " memory", " in use", "----------", "%10lu%c " }, { " alloc", " succeed", "---------", "%9u " }, { "alloc", " fail", "-----", "%5u " }, { NULL, NULL, NULL, NULL } }; static datafmt_t vmemfmt[] = { { "vmem ", "name ", "-------------------------", "%-*s " }, { " memory", " in use", "----------", "%9llu%c " }, { " memory", " total", "-----------", "%10llu%c " }, { " memory", " import", "----------", "%9llu%c " }, { " alloc", " succeed", "---------", "%9llu " }, { "alloc", " fail", "-----", "%5llu " }, { NULL, NULL, NULL, NULL } }; /*ARGSUSED*/ static int kmastat_cpu_avail(uintptr_t addr, const kmem_cpu_cache_t *ccp, int *avail) { short rounds, prounds; if (KMEM_DUMPCC(ccp)) { rounds = ccp->cc_dump_rounds; prounds = ccp->cc_dump_prounds; } else { rounds = ccp->cc_rounds; prounds = ccp->cc_prounds; } if (rounds > 0) *avail += rounds; if (prounds > 0) *avail += prounds; return (WALK_NEXT); } /*ARGSUSED*/ static int kmastat_cpu_alloc(uintptr_t addr, const kmem_cpu_cache_t *ccp, int *alloc) { *alloc += ccp->cc_alloc; return (WALK_NEXT); } /*ARGSUSED*/ static int kmastat_slab_avail(uintptr_t addr, const kmem_slab_t *sp, int *avail) { *avail += sp->slab_chunks - sp->slab_refcnt; return (WALK_NEXT); } typedef struct kmastat_vmem { uintptr_t kv_addr; struct kmastat_vmem *kv_next; size_t kv_meminuse; int kv_alloc; int kv_fail; } kmastat_vmem_t; typedef struct kmastat_args { kmastat_vmem_t **ka_kvpp; uint_t ka_shift; } kmastat_args_t; static int kmastat_cache(uintptr_t addr, const kmem_cache_t *cp, kmastat_args_t *kap) { kmastat_vmem_t **kvpp = kap->ka_kvpp; kmastat_vmem_t *kv; datafmt_t *dfp = kmemfmt; int magsize; int avail, alloc, total; size_t meminuse = (cp->cache_slab_create - cp->cache_slab_destroy) * cp->cache_slabsize; mdb_walk_cb_t cpu_avail = (mdb_walk_cb_t)kmastat_cpu_avail; mdb_walk_cb_t cpu_alloc = (mdb_walk_cb_t)kmastat_cpu_alloc; mdb_walk_cb_t slab_avail = (mdb_walk_cb_t)kmastat_slab_avail; magsize = kmem_get_magsize(cp); alloc = cp->cache_slab_alloc + cp->cache_full.ml_alloc; avail = cp->cache_full.ml_total * magsize; total = cp->cache_buftotal; (void) mdb_pwalk("kmem_cpu_cache", cpu_alloc, &alloc, addr); (void) mdb_pwalk("kmem_cpu_cache", cpu_avail, &avail, addr); (void) mdb_pwalk("kmem_slab_partial", slab_avail, &avail, addr); for (kv = *kvpp; kv != NULL; kv = kv->kv_next) { if (kv->kv_addr == (uintptr_t)cp->cache_arena) goto out; } kv = mdb_zalloc(sizeof (kmastat_vmem_t), UM_SLEEP | UM_GC); kv->kv_next = *kvpp; kv->kv_addr = (uintptr_t)cp->cache_arena; *kvpp = kv; out: kv->kv_meminuse += meminuse; kv->kv_alloc += alloc; kv->kv_fail += cp->cache_alloc_fail; mdb_printf((dfp++)->fmt, cp->cache_name); mdb_printf((dfp++)->fmt, cp->cache_bufsize); mdb_printf((dfp++)->fmt, total - avail); mdb_printf((dfp++)->fmt, total); mdb_printf((dfp++)->fmt, meminuse >> kap->ka_shift, kap->ka_shift == GIGS ? 'G' : kap->ka_shift == MEGS ? 'M' : kap->ka_shift == KILOS ? 'K' : 'B'); mdb_printf((dfp++)->fmt, alloc); mdb_printf((dfp++)->fmt, cp->cache_alloc_fail); mdb_printf("\n"); return (WALK_NEXT); } static int kmastat_vmem_totals(uintptr_t addr, const vmem_t *v, kmastat_args_t *kap) { kmastat_vmem_t *kv = *kap->ka_kvpp; size_t len; while (kv != NULL && kv->kv_addr != addr) kv = kv->kv_next; if (kv == NULL || kv->kv_alloc == 0) return (WALK_NEXT); len = MIN(17, strlen(v->vm_name)); mdb_printf("Total [%s]%*s %6s %6s %6s %10lu%c %9u %5u\n", v->vm_name, 17 - len, "", "", "", "", kv->kv_meminuse >> kap->ka_shift, kap->ka_shift == GIGS ? 'G' : kap->ka_shift == MEGS ? 'M' : kap->ka_shift == KILOS ? 'K' : 'B', kv->kv_alloc, kv->kv_fail); return (WALK_NEXT); } /*ARGSUSED*/ static int kmastat_vmem(uintptr_t addr, const vmem_t *v, const uint_t *shiftp) { datafmt_t *dfp = vmemfmt; const vmem_kstat_t *vkp = &v->vm_kstat; uintptr_t paddr; vmem_t parent; int ident = 0; for (paddr = (uintptr_t)v->vm_source; paddr != 0; ident += 4) { if (mdb_vread(&parent, sizeof (parent), paddr) == -1) { mdb_warn("couldn't trace %p's ancestry", addr); ident = 0; break; } paddr = (uintptr_t)parent.vm_source; } mdb_printf("%*s", ident, ""); mdb_printf((dfp++)->fmt, 25 - ident, v->vm_name); mdb_printf((dfp++)->fmt, vkp->vk_mem_inuse.value.ui64 >> *shiftp, *shiftp == GIGS ? 'G' : *shiftp == MEGS ? 'M' : *shiftp == KILOS ? 'K' : 'B'); mdb_printf((dfp++)->fmt, vkp->vk_mem_total.value.ui64 >> *shiftp, *shiftp == GIGS ? 'G' : *shiftp == MEGS ? 'M' : *shiftp == KILOS ? 'K' : 'B'); mdb_printf((dfp++)->fmt, vkp->vk_mem_import.value.ui64 >> *shiftp, *shiftp == GIGS ? 'G' : *shiftp == MEGS ? 'M' : *shiftp == KILOS ? 'K' : 'B'); mdb_printf((dfp++)->fmt, vkp->vk_alloc.value.ui64); mdb_printf((dfp++)->fmt, vkp->vk_fail.value.ui64); mdb_printf("\n"); return (WALK_NEXT); } /*ARGSUSED*/ int kmastat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kmastat_vmem_t *kv = NULL; datafmt_t *dfp; kmastat_args_t ka; ka.ka_shift = 0; if (mdb_getopts(argc, argv, 'k', MDB_OPT_SETBITS, KILOS, &ka.ka_shift, 'm', MDB_OPT_SETBITS, MEGS, &ka.ka_shift, 'g', MDB_OPT_SETBITS, GIGS, &ka.ka_shift, NULL) != argc) return (DCMD_USAGE); for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->hdr1); mdb_printf("\n"); for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->hdr2); mdb_printf("\n"); for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); ka.ka_kvpp = &kv; if (mdb_walk("kmem_cache", (mdb_walk_cb_t)kmastat_cache, &ka) == -1) { mdb_warn("can't walk 'kmem_cache'"); return (DCMD_ERR); } for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); if (mdb_walk("vmem", (mdb_walk_cb_t)kmastat_vmem_totals, &ka) == -1) { mdb_warn("can't walk 'vmem'"); return (DCMD_ERR); } for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); mdb_printf("\n"); for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->hdr1); mdb_printf("\n"); for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->hdr2); mdb_printf("\n"); for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); if (mdb_walk("vmem", (mdb_walk_cb_t)kmastat_vmem, &ka.ka_shift) == -1) { mdb_warn("can't walk 'vmem'"); return (DCMD_ERR); } for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); return (DCMD_OK); } /* * Our ::kgrep callback scans the entire kernel VA space (kas). kas is made * up of a set of 'struct seg's. We could just scan each seg en masse, but * unfortunately, a few of the segs are both large and sparse, so we could * spend quite a bit of time scanning VAs which have no backing pages. * * So for the few very sparse segs, we skip the segment itself, and scan * the allocated vmem_segs in the vmem arena which manages that part of kas. * Currently, we do this for: * * SEG VMEM ARENA * kvseg heap_arena * kvseg32 heap32_arena * kvseg_core heap_core_arena * * In addition, we skip the segkpm segment in its entirety, since it is very * sparse, and contains no new kernel data. */ typedef struct kgrep_walk_data { kgrep_cb_func *kg_cb; void *kg_cbdata; uintptr_t kg_kvseg; uintptr_t kg_kvseg32; uintptr_t kg_kvseg_core; uintptr_t kg_segkpm; uintptr_t kg_heap_lp_base; uintptr_t kg_heap_lp_end; } kgrep_walk_data_t; static int kgrep_walk_seg(uintptr_t addr, const struct seg *seg, kgrep_walk_data_t *kg) { uintptr_t base = (uintptr_t)seg->s_base; if (addr == kg->kg_kvseg || addr == kg->kg_kvseg32 || addr == kg->kg_kvseg_core) return (WALK_NEXT); if ((uintptr_t)seg->s_ops == kg->kg_segkpm) return (WALK_NEXT); return (kg->kg_cb(base, base + seg->s_size, kg->kg_cbdata)); } /*ARGSUSED*/ static int kgrep_walk_vseg(uintptr_t addr, const vmem_seg_t *seg, kgrep_walk_data_t *kg) { /* * skip large page heap address range - it is scanned by walking * allocated vmem_segs in the heap_lp_arena */ if (seg->vs_start == kg->kg_heap_lp_base && seg->vs_end == kg->kg_heap_lp_end) return (WALK_NEXT); return (kg->kg_cb(seg->vs_start, seg->vs_end, kg->kg_cbdata)); } /*ARGSUSED*/ static int kgrep_xwalk_vseg(uintptr_t addr, const vmem_seg_t *seg, kgrep_walk_data_t *kg) { return (kg->kg_cb(seg->vs_start, seg->vs_end, kg->kg_cbdata)); } static int kgrep_walk_vmem(uintptr_t addr, const vmem_t *vmem, kgrep_walk_data_t *kg) { mdb_walk_cb_t walk_vseg = (mdb_walk_cb_t)kgrep_walk_vseg; if (strcmp(vmem->vm_name, "heap") != 0 && strcmp(vmem->vm_name, "heap32") != 0 && strcmp(vmem->vm_name, "heap_core") != 0 && strcmp(vmem->vm_name, "heap_lp") != 0) return (WALK_NEXT); if (strcmp(vmem->vm_name, "heap_lp") == 0) walk_vseg = (mdb_walk_cb_t)kgrep_xwalk_vseg; if (mdb_pwalk("vmem_alloc", walk_vseg, kg, addr) == -1) { mdb_warn("couldn't walk vmem_alloc for vmem %p", addr); return (WALK_ERR); } return (WALK_NEXT); } int kgrep_subr(kgrep_cb_func *cb, void *cbdata) { GElf_Sym kas, kvseg, kvseg32, kvseg_core, segkpm; kgrep_walk_data_t kg; if (mdb_get_state() == MDB_STATE_RUNNING) { mdb_warn("kgrep can only be run on a system " "dump or under kmdb; see dumpadm(8)\n"); return (DCMD_ERR); } if (mdb_lookup_by_name("kas", &kas) == -1) { mdb_warn("failed to locate 'kas' symbol\n"); return (DCMD_ERR); } if (mdb_lookup_by_name("kvseg", &kvseg) == -1) { mdb_warn("failed to locate 'kvseg' symbol\n"); return (DCMD_ERR); } if (mdb_lookup_by_name("kvseg32", &kvseg32) == -1) { mdb_warn("failed to locate 'kvseg32' symbol\n"); return (DCMD_ERR); } if (mdb_lookup_by_name("kvseg_core", &kvseg_core) == -1) { mdb_warn("failed to locate 'kvseg_core' symbol\n"); return (DCMD_ERR); } if (mdb_lookup_by_name("segkpm_ops", &segkpm) == -1) { mdb_warn("failed to locate 'segkpm_ops' symbol\n"); return (DCMD_ERR); } if (mdb_readvar(&kg.kg_heap_lp_base, "heap_lp_base") == -1) { mdb_warn("failed to read 'heap_lp_base'\n"); return (DCMD_ERR); } if (mdb_readvar(&kg.kg_heap_lp_end, "heap_lp_end") == -1) { mdb_warn("failed to read 'heap_lp_end'\n"); return (DCMD_ERR); } kg.kg_cb = cb; kg.kg_cbdata = cbdata; kg.kg_kvseg = (uintptr_t)kvseg.st_value; kg.kg_kvseg32 = (uintptr_t)kvseg32.st_value; kg.kg_kvseg_core = (uintptr_t)kvseg_core.st_value; kg.kg_segkpm = (uintptr_t)segkpm.st_value; if (mdb_pwalk("seg", (mdb_walk_cb_t)kgrep_walk_seg, &kg, kas.st_value) == -1) { mdb_warn("failed to walk kas segments"); return (DCMD_ERR); } if (mdb_walk("vmem", (mdb_walk_cb_t)kgrep_walk_vmem, &kg) == -1) { mdb_warn("failed to walk heap/heap32 vmem arenas"); return (DCMD_ERR); } return (DCMD_OK); } size_t kgrep_subr_pagesize(void) { return (PAGESIZE); } typedef struct file_walk_data { struct uf_entry *fw_flist; int fw_flistsz; int fw_ndx; int fw_nofiles; } file_walk_data_t; typedef struct mdb_file_proc { struct { struct { int fi_nfiles; uf_entry_t *volatile fi_list; } u_finfo; } p_user; } mdb_file_proc_t; int file_walk_init(mdb_walk_state_t *wsp) { file_walk_data_t *fw; mdb_file_proc_t p; if (wsp->walk_addr == 0) { mdb_warn("file walk doesn't support global walks\n"); return (WALK_ERR); } fw = mdb_alloc(sizeof (file_walk_data_t), UM_SLEEP); if (mdb_ctf_vread(&p, "proc_t", "mdb_file_proc_t", wsp->walk_addr, 0) == -1) { mdb_free(fw, sizeof (file_walk_data_t)); mdb_warn("failed to read proc structure at %p", wsp->walk_addr); return (WALK_ERR); } if (p.p_user.u_finfo.fi_nfiles == 0) { mdb_free(fw, sizeof (file_walk_data_t)); return (WALK_DONE); } fw->fw_nofiles = p.p_user.u_finfo.fi_nfiles; fw->fw_flistsz = sizeof (struct uf_entry) * fw->fw_nofiles; fw->fw_flist = mdb_alloc(fw->fw_flistsz, UM_SLEEP); if (mdb_vread(fw->fw_flist, fw->fw_flistsz, (uintptr_t)p.p_user.u_finfo.fi_list) == -1) { mdb_warn("failed to read file array at %p", p.p_user.u_finfo.fi_list); mdb_free(fw->fw_flist, fw->fw_flistsz); mdb_free(fw, sizeof (file_walk_data_t)); return (WALK_ERR); } fw->fw_ndx = 0; wsp->walk_data = fw; return (WALK_NEXT); } int file_walk_step(mdb_walk_state_t *wsp) { file_walk_data_t *fw = (file_walk_data_t *)wsp->walk_data; struct file file; uintptr_t fp; again: if (fw->fw_ndx == fw->fw_nofiles) return (WALK_DONE); if ((fp = (uintptr_t)fw->fw_flist[fw->fw_ndx++].uf_file) == 0) goto again; (void) mdb_vread(&file, sizeof (file), (uintptr_t)fp); return (wsp->walk_callback(fp, &file, wsp->walk_cbdata)); } int allfile_walk_step(mdb_walk_state_t *wsp) { file_walk_data_t *fw = (file_walk_data_t *)wsp->walk_data; struct file file; uintptr_t fp; if (fw->fw_ndx == fw->fw_nofiles) return (WALK_DONE); if ((fp = (uintptr_t)fw->fw_flist[fw->fw_ndx++].uf_file) != 0) (void) mdb_vread(&file, sizeof (file), (uintptr_t)fp); else bzero(&file, sizeof (file)); return (wsp->walk_callback(fp, &file, wsp->walk_cbdata)); } void file_walk_fini(mdb_walk_state_t *wsp) { file_walk_data_t *fw = (file_walk_data_t *)wsp->walk_data; mdb_free(fw->fw_flist, fw->fw_flistsz); mdb_free(fw, sizeof (file_walk_data_t)); } int port_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("port walk doesn't support global walks\n"); return (WALK_ERR); } if (mdb_layered_walk("file", wsp) == -1) { mdb_warn("couldn't walk 'file'"); return (WALK_ERR); } return (WALK_NEXT); } int port_walk_step(mdb_walk_state_t *wsp) { struct vnode vn; uintptr_t vp; uintptr_t pp; struct port port; vp = (uintptr_t)((struct file *)wsp->walk_layer)->f_vnode; if (mdb_vread(&vn, sizeof (vn), vp) == -1) { mdb_warn("failed to read vnode_t at %p", vp); return (WALK_ERR); } if (vn.v_type != VPORT) return (WALK_NEXT); pp = (uintptr_t)vn.v_data; if (mdb_vread(&port, sizeof (port), pp) == -1) { mdb_warn("failed to read port_t at %p", pp); return (WALK_ERR); } return (wsp->walk_callback(pp, &port, wsp->walk_cbdata)); } typedef struct portev_walk_data { list_node_t *pev_node; list_node_t *pev_last; size_t pev_offset; } portev_walk_data_t; int portev_walk_init(mdb_walk_state_t *wsp) { portev_walk_data_t *pevd; struct port port; struct vnode vn; struct list *list; uintptr_t vp; if (wsp->walk_addr == 0) { mdb_warn("portev walk doesn't support global walks\n"); return (WALK_ERR); } pevd = mdb_alloc(sizeof (portev_walk_data_t), UM_SLEEP); if (mdb_vread(&port, sizeof (port), wsp->walk_addr) == -1) { mdb_free(pevd, sizeof (portev_walk_data_t)); mdb_warn("failed to read port structure at %p", wsp->walk_addr); return (WALK_ERR); } vp = (uintptr_t)port.port_vnode; if (mdb_vread(&vn, sizeof (vn), vp) == -1) { mdb_free(pevd, sizeof (portev_walk_data_t)); mdb_warn("failed to read vnode_t at %p", vp); return (WALK_ERR); } if (vn.v_type != VPORT) { mdb_free(pevd, sizeof (portev_walk_data_t)); mdb_warn("input address (%p) does not point to an event port", wsp->walk_addr); return (WALK_ERR); } if (port.port_queue.portq_nent == 0) { mdb_free(pevd, sizeof (portev_walk_data_t)); return (WALK_DONE); } list = &port.port_queue.portq_list; pevd->pev_offset = list->list_offset; pevd->pev_last = list->list_head.list_prev; pevd->pev_node = list->list_head.list_next; wsp->walk_data = pevd; return (WALK_NEXT); } int portev_walk_step(mdb_walk_state_t *wsp) { portev_walk_data_t *pevd; struct port_kevent ev; uintptr_t evp; pevd = (portev_walk_data_t *)wsp->walk_data; if (pevd->pev_last == NULL) return (WALK_DONE); if (pevd->pev_node == pevd->pev_last) pevd->pev_last = NULL; /* last round */ evp = ((uintptr_t)(((char *)pevd->pev_node) - pevd->pev_offset)); if (mdb_vread(&ev, sizeof (ev), evp) == -1) { mdb_warn("failed to read port_kevent at %p", evp); return (WALK_DONE); } pevd->pev_node = ev.portkev_node.list_next; return (wsp->walk_callback(evp, &ev, wsp->walk_cbdata)); } void portev_walk_fini(mdb_walk_state_t *wsp) { portev_walk_data_t *pevd = (portev_walk_data_t *)wsp->walk_data; if (pevd != NULL) mdb_free(pevd, sizeof (portev_walk_data_t)); } typedef struct proc_walk_data { uintptr_t *pw_stack; int pw_depth; int pw_max; } proc_walk_data_t; int proc_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; proc_walk_data_t *pw; if (wsp->walk_addr == 0) { if (mdb_lookup_by_name("p0", &sym) == -1) { mdb_warn("failed to read 'practive'"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sym.st_value; } pw = mdb_zalloc(sizeof (proc_walk_data_t), UM_SLEEP); if (mdb_readvar(&pw->pw_max, "nproc") == -1) { mdb_warn("failed to read 'nproc'"); mdb_free(pw, sizeof (pw)); return (WALK_ERR); } pw->pw_stack = mdb_alloc(pw->pw_max * sizeof (uintptr_t), UM_SLEEP); wsp->walk_data = pw; return (WALK_NEXT); } typedef struct mdb_walk_proc { struct proc *p_child; struct proc *p_sibling; } mdb_walk_proc_t; int proc_walk_step(mdb_walk_state_t *wsp) { proc_walk_data_t *pw = wsp->walk_data; uintptr_t addr = wsp->walk_addr; uintptr_t cld, sib; int status; mdb_walk_proc_t pr; if (mdb_ctf_vread(&pr, "proc_t", "mdb_walk_proc_t", addr, 0) == -1) { mdb_warn("failed to read proc at %p", addr); return (WALK_DONE); } cld = (uintptr_t)pr.p_child; sib = (uintptr_t)pr.p_sibling; if (pw->pw_depth > 0 && addr == pw->pw_stack[pw->pw_depth - 1]) { pw->pw_depth--; goto sib; } /* * Always pass NULL as the local copy pointer. Consumers * should use mdb_ctf_vread() to read their own minimal * version of proc_t. Thus minimizing the chance of breakage * with older crash dumps. */ status = wsp->walk_callback(addr, NULL, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); if ((wsp->walk_addr = cld) != 0) { if (mdb_ctf_vread(&pr, "proc_t", "mdb_walk_proc_t", cld, 0) == -1) { mdb_warn("proc %p has invalid p_child %p; skipping\n", addr, cld); goto sib; } pw->pw_stack[pw->pw_depth++] = addr; if (pw->pw_depth == pw->pw_max) { mdb_warn("depth %d exceeds max depth; try again\n", pw->pw_depth); return (WALK_DONE); } return (WALK_NEXT); } sib: /* * We know that p0 has no siblings, and if another starting proc * was given, we don't want to walk its siblings anyway. */ if (pw->pw_depth == 0) return (WALK_DONE); if (sib != 0 && mdb_ctf_vread(&pr, "proc_t", "mdb_walk_proc_t", sib, 0) == -1) { mdb_warn("proc %p has invalid p_sibling %p; skipping\n", addr, sib); sib = 0; } if ((wsp->walk_addr = sib) == 0) { if (pw->pw_depth > 0) { wsp->walk_addr = pw->pw_stack[pw->pw_depth - 1]; return (WALK_NEXT); } return (WALK_DONE); } return (WALK_NEXT); } void proc_walk_fini(mdb_walk_state_t *wsp) { proc_walk_data_t *pw = wsp->walk_data; mdb_free(pw->pw_stack, pw->pw_max * sizeof (uintptr_t)); mdb_free(pw, sizeof (proc_walk_data_t)); } int task_walk_init(mdb_walk_state_t *wsp) { task_t task; if (mdb_vread(&task, sizeof (task_t), wsp->walk_addr) == -1) { mdb_warn("failed to read task at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)task.tk_memb_list; wsp->walk_data = task.tk_memb_list; return (WALK_NEXT); } typedef struct mdb_task_proc { struct proc *p_tasknext; } mdb_task_proc_t; int task_walk_step(mdb_walk_state_t *wsp) { mdb_task_proc_t proc; int status; if (mdb_ctf_vread(&proc, "proc_t", "mdb_task_proc_t", wsp->walk_addr, 0) == -1) { mdb_warn("failed to read proc at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); if (proc.p_tasknext == wsp->walk_data) return (WALK_DONE); wsp->walk_addr = (uintptr_t)proc.p_tasknext; return (status); } int project_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { if (mdb_readvar(&wsp->walk_addr, "proj0p") == -1) { mdb_warn("failed to read 'proj0p'"); return (WALK_ERR); } } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int project_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; kproject_t pj; int status; if (mdb_vread(&pj, sizeof (kproject_t), addr) == -1) { mdb_warn("failed to read project at %p", addr); return (WALK_DONE); } status = wsp->walk_callback(addr, &pj, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); wsp->walk_addr = (uintptr_t)pj.kpj_next; if ((void *)wsp->walk_addr == wsp->walk_data) return (WALK_DONE); return (WALK_NEXT); } static int generic_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } static int cpu_walk_cmp(const void *l, const void *r) { uintptr_t lhs = *((uintptr_t *)l); uintptr_t rhs = *((uintptr_t *)r); cpu_t lcpu, rcpu; (void) mdb_vread(&lcpu, sizeof (lcpu), lhs); (void) mdb_vread(&rcpu, sizeof (rcpu), rhs); if (lcpu.cpu_id < rcpu.cpu_id) return (-1); if (lcpu.cpu_id > rcpu.cpu_id) return (1); return (0); } typedef struct cpu_walk { uintptr_t *cw_array; int cw_ndx; } cpu_walk_t; int cpu_walk_init(mdb_walk_state_t *wsp) { cpu_walk_t *cw; int max_ncpus, i = 0; uintptr_t current, first; cpu_t cpu, panic_cpu; uintptr_t panicstr, addr = 0; GElf_Sym sym; cw = mdb_zalloc(sizeof (cpu_walk_t), UM_SLEEP | UM_GC); if (mdb_readvar(&max_ncpus, "max_ncpus") == -1) { mdb_warn("failed to read 'max_ncpus'"); return (WALK_ERR); } if (mdb_readvar(&panicstr, "panicstr") == -1) { mdb_warn("failed to read 'panicstr'"); return (WALK_ERR); } if (panicstr != 0) { if (mdb_lookup_by_name("panic_cpu", &sym) == -1) { mdb_warn("failed to find 'panic_cpu'"); return (WALK_ERR); } addr = (uintptr_t)sym.st_value; if (mdb_vread(&panic_cpu, sizeof (cpu_t), addr) == -1) { mdb_warn("failed to read 'panic_cpu'"); return (WALK_ERR); } } /* * Unfortunately, there is no platform-independent way to walk * CPUs in ID order. We therefore loop through in cpu_next order, * building an array of CPU pointers which will subsequently be * sorted. */ cw->cw_array = mdb_zalloc((max_ncpus + 1) * sizeof (uintptr_t), UM_SLEEP | UM_GC); if (mdb_readvar(&first, "cpu_list") == -1) { mdb_warn("failed to read 'cpu_list'"); return (WALK_ERR); } current = first; do { if (mdb_vread(&cpu, sizeof (cpu), current) == -1) { mdb_warn("failed to read cpu at %p", current); return (WALK_ERR); } if (panicstr != 0 && panic_cpu.cpu_id == cpu.cpu_id) { cw->cw_array[i++] = addr; } else { cw->cw_array[i++] = current; } } while ((current = (uintptr_t)cpu.cpu_next) != first); qsort(cw->cw_array, i, sizeof (uintptr_t), cpu_walk_cmp); wsp->walk_data = cw; return (WALK_NEXT); } int cpu_walk_step(mdb_walk_state_t *wsp) { cpu_walk_t *cw = wsp->walk_data; cpu_t cpu; uintptr_t addr = cw->cw_array[cw->cw_ndx++]; if (addr == 0) return (WALK_DONE); if (mdb_vread(&cpu, sizeof (cpu), addr) == -1) { mdb_warn("failed to read cpu at %p", addr); return (WALK_DONE); } return (wsp->walk_callback(addr, &cpu, wsp->walk_cbdata)); } typedef struct cpuinfo_data { intptr_t cid_cpu; uintptr_t **cid_ithr; char cid_print_head; char cid_print_thr; char cid_print_ithr; char cid_print_flags; } cpuinfo_data_t; int cpuinfo_walk_ithread(uintptr_t addr, const kthread_t *thr, cpuinfo_data_t *cid) { cpu_t c; int id; uint8_t pil; if (!(thr->t_flag & T_INTR_THREAD) || thr->t_state == TS_FREE) return (WALK_NEXT); if (thr->t_bound_cpu == NULL) { mdb_warn("thr %p is intr thread w/out a CPU\n", addr); return (WALK_NEXT); } (void) mdb_vread(&c, sizeof (c), (uintptr_t)thr->t_bound_cpu); if ((id = c.cpu_id) >= NCPU) { mdb_warn("CPU %p has id (%d) greater than NCPU (%d)\n", thr->t_bound_cpu, id, NCPU); return (WALK_NEXT); } if ((pil = thr->t_pil) >= NINTR) { mdb_warn("thread %p has pil (%d) greater than %d\n", addr, pil, NINTR); return (WALK_NEXT); } if (cid->cid_ithr[id][pil] != 0) { mdb_warn("CPU %d has multiple threads at pil %d (at least " "%p and %p)\n", id, pil, addr, cid->cid_ithr[id][pil]); return (WALK_NEXT); } cid->cid_ithr[id][pil] = addr; return (WALK_NEXT); } #define CPUINFO_IDWIDTH 3 #define CPUINFO_FLAGWIDTH 9 #ifdef _LP64 #if defined(__amd64) #define CPUINFO_TWIDTH 16 #define CPUINFO_CPUWIDTH 16 #else #define CPUINFO_CPUWIDTH 11 #define CPUINFO_TWIDTH 11 #endif #else #define CPUINFO_CPUWIDTH 8 #define CPUINFO_TWIDTH 8 #endif #define CPUINFO_THRDELT (CPUINFO_IDWIDTH + CPUINFO_CPUWIDTH + 9) #define CPUINFO_FLAGDELT (CPUINFO_IDWIDTH + CPUINFO_CPUWIDTH + 4) #define CPUINFO_ITHRDELT 4 #define CPUINFO_INDENT mdb_printf("%*s", CPUINFO_THRDELT, \ flagline < nflaglines ? flagbuf[flagline++] : "") typedef struct mdb_cpuinfo_proc { struct { char u_comm[MAXCOMLEN + 1]; } p_user; } mdb_cpuinfo_proc_t; int cpuinfo_walk_cpu(uintptr_t addr, const cpu_t *cpu, cpuinfo_data_t *cid) { kthread_t t; disp_t disp; mdb_cpuinfo_proc_t p; uintptr_t pinned = 0; char **flagbuf; int nflaglines = 0, flagline = 0, bspl, rval = WALK_NEXT; const char *flags[] = { "RUNNING", "READY", "QUIESCED", "EXISTS", "ENABLE", "OFFLINE", "POWEROFF", "FROZEN", "SPARE", "FAULTED", "DISABLED", NULL }; if (cid->cid_cpu != -1) { if (addr != cid->cid_cpu && cpu->cpu_id != cid->cid_cpu) return (WALK_NEXT); /* * Set cid_cpu to -1 to indicate that we found a matching CPU. */ cid->cid_cpu = -1; rval = WALK_DONE; } if (cid->cid_print_head) { mdb_printf("%3s %-*s %3s %4s %4s %3s %4s %5s %-6s %-*s %s\n", "ID", CPUINFO_CPUWIDTH, "ADDR", "FLG", "NRUN", "BSPL", "PRI", "RNRN", "KRNRN", "SWITCH", CPUINFO_TWIDTH, "THREAD", "PROC"); cid->cid_print_head = FALSE; } bspl = cpu->cpu_base_spl; if (mdb_vread(&disp, sizeof (disp_t), (uintptr_t)cpu->cpu_disp) == -1) { mdb_warn("failed to read disp_t at %p", cpu->cpu_disp); return (WALK_ERR); } mdb_printf("%3d %0*p %3x %4d %4d ", cpu->cpu_id, CPUINFO_CPUWIDTH, addr, cpu->cpu_flags, disp.disp_nrunnable, bspl); if (mdb_vread(&t, sizeof (t), (uintptr_t)cpu->cpu_thread) != -1) { mdb_printf("%3d ", t.t_pri); } else { mdb_printf("%3s ", "-"); } mdb_printf("%4s %5s ", cpu->cpu_runrun ? "yes" : "no", cpu->cpu_kprunrun ? "yes" : "no"); if (cpu->cpu_last_swtch) { mdb_printf("t-%-4d ", (clock_t)mdb_get_lbolt() - cpu->cpu_last_swtch); } else { mdb_printf("%-6s ", "-"); } mdb_printf("%0*p", CPUINFO_TWIDTH, cpu->cpu_thread); if (cpu->cpu_thread == cpu->cpu_idle_thread) mdb_printf(" (idle)\n"); else if (cpu->cpu_thread == NULL) mdb_printf(" -\n"); else { if (mdb_ctf_vread(&p, "proc_t", "mdb_cpuinfo_proc_t", (uintptr_t)t.t_procp, 0) != -1) { mdb_printf(" %s\n", p.p_user.u_comm); } else { mdb_printf(" ?\n"); } } flagbuf = mdb_zalloc(sizeof (flags), UM_SLEEP | UM_GC); if (cid->cid_print_flags) { int first = 1, i, j, k; char *s; cid->cid_print_head = TRUE; for (i = 1, j = 0; flags[j] != NULL; i <<= 1, j++) { if (!(cpu->cpu_flags & i)) continue; if (first) { s = mdb_alloc(CPUINFO_THRDELT + 1, UM_GC | UM_SLEEP); (void) mdb_snprintf(s, CPUINFO_THRDELT + 1, "%*s|%*s", CPUINFO_FLAGDELT, "", CPUINFO_THRDELT - 1 - CPUINFO_FLAGDELT, ""); flagbuf[nflaglines++] = s; } s = mdb_alloc(CPUINFO_THRDELT + 1, UM_GC | UM_SLEEP); (void) mdb_snprintf(s, CPUINFO_THRDELT + 1, "%*s%*s %s", CPUINFO_IDWIDTH + CPUINFO_CPUWIDTH - CPUINFO_FLAGWIDTH, "", CPUINFO_FLAGWIDTH, flags[j], first ? "<--+" : ""); for (k = strlen(s); k < CPUINFO_THRDELT; k++) s[k] = ' '; s[k] = '\0'; flagbuf[nflaglines++] = s; first = 0; } } if (cid->cid_print_ithr) { int i, found_one = FALSE; int print_thr = disp.disp_nrunnable && cid->cid_print_thr; for (i = NINTR - 1; i >= 0; i--) { uintptr_t iaddr = cid->cid_ithr[cpu->cpu_id][i]; if (iaddr == 0) continue; if (!found_one) { found_one = TRUE; CPUINFO_INDENT; mdb_printf("%c%*s|\n", print_thr ? '|' : ' ', CPUINFO_ITHRDELT, ""); CPUINFO_INDENT; mdb_printf("%c%*s+--> %3s %s\n", print_thr ? '|' : ' ', CPUINFO_ITHRDELT, "", "PIL", "THREAD"); } if (mdb_vread(&t, sizeof (t), iaddr) == -1) { mdb_warn("failed to read kthread_t at %p", iaddr); return (WALK_ERR); } CPUINFO_INDENT; mdb_printf("%c%*s %3d %0*p\n", print_thr ? '|' : ' ', CPUINFO_ITHRDELT, "", t.t_pil, CPUINFO_TWIDTH, iaddr); pinned = (uintptr_t)t.t_intr; } if (found_one && pinned != 0) { cid->cid_print_head = TRUE; (void) strcpy(p.p_user.u_comm, "?"); if (mdb_vread(&t, sizeof (t), (uintptr_t)pinned) == -1) { mdb_warn("failed to read kthread_t at %p", pinned); return (WALK_ERR); } if (mdb_ctf_vread(&p, "proc_t", "mdb_cpuinfo_proc_t", (uintptr_t)t.t_procp, 0) == -1) { mdb_warn("failed to read proc_t at %p", t.t_procp); return (WALK_ERR); } CPUINFO_INDENT; mdb_printf("%c%*s %3s %0*p %s\n", print_thr ? '|' : ' ', CPUINFO_ITHRDELT, "", "-", CPUINFO_TWIDTH, pinned, pinned == (uintptr_t)cpu->cpu_idle_thread ? "(idle)" : p.p_user.u_comm); } } if (disp.disp_nrunnable && cid->cid_print_thr) { dispq_t *dq; int i, npri = disp.disp_npri; dq = mdb_alloc(sizeof (dispq_t) * npri, UM_SLEEP | UM_GC); if (mdb_vread(dq, sizeof (dispq_t) * npri, (uintptr_t)disp.disp_q) == -1) { mdb_warn("failed to read dispq_t at %p", disp.disp_q); return (WALK_ERR); } CPUINFO_INDENT; mdb_printf("|\n"); CPUINFO_INDENT; mdb_printf("+--> %3s %-*s %s\n", "PRI", CPUINFO_TWIDTH, "THREAD", "PROC"); for (i = npri - 1; i >= 0; i--) { uintptr_t taddr = (uintptr_t)dq[i].dq_first; while (taddr != 0) { if (mdb_vread(&t, sizeof (t), taddr) == -1) { mdb_warn("failed to read kthread_t " "at %p", taddr); return (WALK_ERR); } if (mdb_ctf_vread(&p, "proc_t", "mdb_cpuinfo_proc_t", (uintptr_t)t.t_procp, 0) == -1) { mdb_warn("failed to read proc_t at %p", t.t_procp); return (WALK_ERR); } CPUINFO_INDENT; mdb_printf(" %3d %0*p %s\n", t.t_pri, CPUINFO_TWIDTH, taddr, p.p_user.u_comm); taddr = (uintptr_t)t.t_link; } } cid->cid_print_head = TRUE; } while (flagline < nflaglines) mdb_printf("%s\n", flagbuf[flagline++]); if (cid->cid_print_head) mdb_printf("\n"); return (rval); } int cpuinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE; cpuinfo_data_t cid; cid.cid_print_ithr = FALSE; cid.cid_print_thr = FALSE; cid.cid_print_flags = FALSE; cid.cid_print_head = DCMD_HDRSPEC(flags) ? TRUE : FALSE; cid.cid_cpu = -1; if (flags & DCMD_ADDRSPEC) cid.cid_cpu = addr; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (verbose) { cid.cid_print_ithr = TRUE; cid.cid_print_thr = TRUE; cid.cid_print_flags = TRUE; cid.cid_print_head = TRUE; } if (cid.cid_print_ithr) { int i; cid.cid_ithr = mdb_alloc(sizeof (uintptr_t **) * NCPU, UM_SLEEP | UM_GC); for (i = 0; i < NCPU; i++) cid.cid_ithr[i] = mdb_zalloc(sizeof (uintptr_t *) * NINTR, UM_SLEEP | UM_GC); if (mdb_walk("thread", (mdb_walk_cb_t)cpuinfo_walk_ithread, &cid) == -1) { mdb_warn("couldn't walk thread"); return (DCMD_ERR); } } if (mdb_walk("cpu", (mdb_walk_cb_t)cpuinfo_walk_cpu, &cid) == -1) { mdb_warn("can't walk cpus"); return (DCMD_ERR); } if (cid.cid_cpu != -1) { /* * We didn't find this CPU when we walked through the CPUs * (i.e. the address specified doesn't show up in the "cpu" * walk). However, the specified address may still correspond * to a valid cpu_t (for example, if the specified address is * the actual panicking cpu_t and not the cached panic_cpu). * Point is: even if we didn't find it, we still want to try * to print the specified address as a cpu_t. */ cpu_t cpu; if (mdb_vread(&cpu, sizeof (cpu), cid.cid_cpu) == -1) { mdb_warn("%p is neither a valid CPU ID nor a " "valid cpu_t address\n", cid.cid_cpu); return (DCMD_ERR); } (void) cpuinfo_walk_cpu(cid.cid_cpu, &cpu, &cid); } return (DCMD_OK); } /*ARGSUSED*/ int flipone(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); for (i = 0; i < sizeof (addr) * NBBY; i++) mdb_printf("%p\n", addr ^ (1UL << i)); return (DCMD_OK); } typedef struct mdb_as2proc_proc { struct as *p_as; } mdb_as2proc_proc_t; /*ARGSUSED*/ int as2proc_walk(uintptr_t addr, const void *ignored, struct as **asp) { mdb_as2proc_proc_t p; mdb_ctf_vread(&p, "proc_t", "mdb_as2proc_proc_t", addr, 0); if (p.p_as == *asp) mdb_printf("%p\n", addr); return (WALK_NEXT); } /*ARGSUSED*/ int as2proc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_walk("proc", (mdb_walk_cb_t)as2proc_walk, &addr) == -1) { mdb_warn("failed to walk proc"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct mdb_ptree_proc { struct proc *p_parent; struct { char u_comm[MAXCOMLEN + 1]; } p_user; } mdb_ptree_proc_t; /*ARGSUSED*/ int ptree_walk(uintptr_t addr, const void *ignored, void *data) { mdb_ptree_proc_t proc; mdb_ptree_proc_t parent; int ident = 0; uintptr_t paddr; mdb_ctf_vread(&proc, "proc_t", "mdb_ptree_proc_t", addr, 0); for (paddr = (uintptr_t)proc.p_parent; paddr != 0; ident += 5) { mdb_ctf_vread(&parent, "proc_t", "mdb_ptree_proc_t", paddr, 0); paddr = (uintptr_t)parent.p_parent; } mdb_inc_indent(ident); mdb_printf("%0?p %s\n", addr, proc.p_user.u_comm); mdb_dec_indent(ident); return (WALK_NEXT); } void ptree_ancestors(uintptr_t addr, uintptr_t start) { mdb_ptree_proc_t p; if (mdb_ctf_vread(&p, "proc_t", "mdb_ptree_proc_t", addr, 0) == -1) { mdb_warn("couldn't read ancestor at %p", addr); return; } if (p.p_parent != NULL) ptree_ancestors((uintptr_t)p.p_parent, start); if (addr != start) (void) ptree_walk(addr, &p, NULL); } /*ARGSUSED*/ int ptree(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) addr = 0; else ptree_ancestors(addr, addr); if (mdb_pwalk("proc", (mdb_walk_cb_t)ptree_walk, NULL, addr) == -1) { mdb_warn("couldn't walk 'proc'"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct mdb_fd_proc { struct { struct { int fi_nfiles; uf_entry_t *volatile fi_list; } u_finfo; } p_user; } mdb_fd_proc_t; /*ARGSUSED*/ static int fd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int fdnum; const mdb_arg_t *argp = &argv[0]; mdb_fd_proc_t p; uf_entry_t uf; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn("fd doesn't give global information\n"); return (DCMD_ERR); } if (argc != 1) return (DCMD_USAGE); fdnum = (int)mdb_argtoull(argp); if (mdb_ctf_vread(&p, "proc_t", "mdb_fd_proc_t", addr, 0) == -1) { mdb_warn("couldn't read proc_t at %p", addr); return (DCMD_ERR); } if (fdnum > p.p_user.u_finfo.fi_nfiles) { mdb_warn("process %p only has %d files open.\n", addr, p.p_user.u_finfo.fi_nfiles); return (DCMD_ERR); } if (mdb_vread(&uf, sizeof (uf_entry_t), (uintptr_t)&p.p_user.u_finfo.fi_list[fdnum]) == -1) { mdb_warn("couldn't read uf_entry_t at %p", &p.p_user.u_finfo.fi_list[fdnum]); return (DCMD_ERR); } mdb_printf("%p\n", uf.uf_file); return (DCMD_OK); } /*ARGSUSED*/ static int pid2proc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pid_t pid = (pid_t)addr; if (argc != 0) return (DCMD_USAGE); if ((addr = mdb_pid2proc(pid, NULL)) == 0) { mdb_warn("PID 0t%d not found\n", pid); return (DCMD_ERR); } mdb_printf("%p\n", addr); return (DCMD_OK); } static char *sysfile_cmd[] = { "exclude:", "include:", "forceload:", "rootdev:", "rootfs:", "swapdev:", "swapfs:", "moddir:", "set", "unknown", }; static char *sysfile_ops[] = { "", "=", "&", "|" }; /*ARGSUSED*/ static int sysfile_vmem_seg(uintptr_t addr, const vmem_seg_t *vsp, void **target) { if (vsp->vs_type == VMEM_ALLOC && (void *)vsp->vs_start == *target) { *target = NULL; return (WALK_DONE); } return (WALK_NEXT); } /*ARGSUSED*/ static int sysfile(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct sysparam *sysp, sys; char var[256]; char modname[256]; char val[256]; char strval[256]; vmem_t *mod_sysfile_arena; void *straddr; if (mdb_readvar(&sysp, "sysparam_hd") == -1) { mdb_warn("failed to read sysparam_hd"); return (DCMD_ERR); } if (mdb_readvar(&mod_sysfile_arena, "mod_sysfile_arena") == -1) { mdb_warn("failed to read mod_sysfile_arena"); return (DCMD_ERR); } while (sysp != NULL) { var[0] = '\0'; val[0] = '\0'; modname[0] = '\0'; if (mdb_vread(&sys, sizeof (sys), (uintptr_t)sysp) == -1) { mdb_warn("couldn't read sysparam %p", sysp); return (DCMD_ERR); } if (sys.sys_modnam != NULL && mdb_readstr(modname, 256, (uintptr_t)sys.sys_modnam) == -1) { mdb_warn("couldn't read modname in %p", sysp); return (DCMD_ERR); } if (sys.sys_ptr != NULL && mdb_readstr(var, 256, (uintptr_t)sys.sys_ptr) == -1) { mdb_warn("couldn't read ptr in %p", sysp); return (DCMD_ERR); } if (sys.sys_op != SETOP_NONE) { /* * Is this an int or a string? We determine this * by checking whether straddr is contained in * mod_sysfile_arena. If so, the walker will set * straddr to NULL. */ straddr = (void *)(uintptr_t)sys.sys_info; if (sys.sys_op == SETOP_ASSIGN && sys.sys_info != 0 && mdb_pwalk("vmem_seg", (mdb_walk_cb_t)sysfile_vmem_seg, &straddr, (uintptr_t)mod_sysfile_arena) == 0 && straddr == NULL && mdb_readstr(strval, 256, (uintptr_t)sys.sys_info) != -1) { (void) mdb_snprintf(val, sizeof (val), "\"%s\"", strval); } else { (void) mdb_snprintf(val, sizeof (val), "0x%llx [0t%llu]", sys.sys_info, sys.sys_info); } } mdb_printf("%s %s%s%s%s%s\n", sysfile_cmd[sys.sys_type], modname, modname[0] == '\0' ? "" : ":", var, sysfile_ops[sys.sys_op], val); sysp = sys.sys_next; } return (DCMD_OK); } int didmatch(uintptr_t addr, const kthread_t *thr, kt_did_t *didp) { if (*didp == thr->t_did) { mdb_printf("%p\n", addr); return (WALK_DONE); } else return (WALK_NEXT); } /*ARGSUSED*/ int did2thread(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const mdb_arg_t *argp = &argv[0]; kt_did_t did; if (argc != 1) return (DCMD_USAGE); did = (kt_did_t)mdb_argtoull(argp); if (mdb_walk("thread", (mdb_walk_cb_t)didmatch, (void *)&did) == -1) { mdb_warn("failed to walk thread"); return (DCMD_ERR); } return (DCMD_OK); } static int errorq_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "errorq_list") == -1) { mdb_warn("failed to read errorq_list"); return (WALK_ERR); } return (WALK_NEXT); } static int errorq_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; errorq_t eq; if (addr == 0) return (WALK_DONE); if (mdb_vread(&eq, sizeof (eq), addr) == -1) { mdb_warn("failed to read errorq at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)eq.eq_next; return (wsp->walk_callback(addr, &eq, wsp->walk_cbdata)); } typedef struct eqd_walk_data { uintptr_t *eqd_stack; void *eqd_buf; ulong_t eqd_qpos; ulong_t eqd_qlen; size_t eqd_size; } eqd_walk_data_t; /* * In order to walk the list of pending error queue elements, we push the * addresses of the corresponding data buffers in to the eqd_stack array. * The error lists are in reverse chronological order when iterating using * eqe_prev, so we then pop things off the top in eqd_walk_step so that the * walker client gets addresses in order from oldest error to newest error. */ static void eqd_push_list(eqd_walk_data_t *eqdp, uintptr_t addr) { errorq_elem_t eqe; while (addr != 0) { if (mdb_vread(&eqe, sizeof (eqe), addr) != sizeof (eqe)) { mdb_warn("failed to read errorq element at %p", addr); break; } if (eqdp->eqd_qpos == eqdp->eqd_qlen) { mdb_warn("errorq is overfull -- more than %lu " "elems found\n", eqdp->eqd_qlen); break; } eqdp->eqd_stack[eqdp->eqd_qpos++] = (uintptr_t)eqe.eqe_data; addr = (uintptr_t)eqe.eqe_prev; } } static int eqd_walk_init(mdb_walk_state_t *wsp) { eqd_walk_data_t *eqdp; errorq_elem_t eqe, *addr; errorq_t eq; ulong_t i; if (mdb_vread(&eq, sizeof (eq), wsp->walk_addr) == -1) { mdb_warn("failed to read errorq at %p", wsp->walk_addr); return (WALK_ERR); } if (eq.eq_ptail != NULL && mdb_vread(&eqe, sizeof (eqe), (uintptr_t)eq.eq_ptail) == -1) { mdb_warn("failed to read errorq element at %p", eq.eq_ptail); return (WALK_ERR); } eqdp = mdb_alloc(sizeof (eqd_walk_data_t), UM_SLEEP); wsp->walk_data = eqdp; eqdp->eqd_stack = mdb_zalloc(sizeof (uintptr_t) * eq.eq_qlen, UM_SLEEP); eqdp->eqd_buf = mdb_alloc(eq.eq_size, UM_SLEEP); eqdp->eqd_qlen = eq.eq_qlen; eqdp->eqd_qpos = 0; eqdp->eqd_size = eq.eq_size; /* * The newest elements in the queue are on the pending list, so we * push those on to our stack first. */ eqd_push_list(eqdp, (uintptr_t)eq.eq_pend); /* * If eq_ptail is set, it may point to a subset of the errors on the * pending list in the event a atomic_cas_ptr() failed; if ptail's * data is already in our stack, NULL out eq_ptail and ignore it. */ if (eq.eq_ptail != NULL) { for (i = 0; i < eqdp->eqd_qpos; i++) { if (eqdp->eqd_stack[i] == (uintptr_t)eqe.eqe_data) { eq.eq_ptail = NULL; break; } } } /* * If eq_phead is set, it has the processing list in order from oldest * to newest. Use this to recompute eq_ptail as best we can and then * we nicely fall into eqd_push_list() of eq_ptail below. */ for (addr = eq.eq_phead; addr != NULL && mdb_vread(&eqe, sizeof (eqe), (uintptr_t)addr) == sizeof (eqe); addr = eqe.eqe_next) eq.eq_ptail = addr; /* * The oldest elements in the queue are on the processing list, subject * to machinations in the if-clauses above. Push any such elements. */ eqd_push_list(eqdp, (uintptr_t)eq.eq_ptail); return (WALK_NEXT); } static int eqd_walk_step(mdb_walk_state_t *wsp) { eqd_walk_data_t *eqdp = wsp->walk_data; uintptr_t addr; if (eqdp->eqd_qpos == 0) return (WALK_DONE); addr = eqdp->eqd_stack[--eqdp->eqd_qpos]; if (mdb_vread(eqdp->eqd_buf, eqdp->eqd_size, addr) != eqdp->eqd_size) { mdb_warn("failed to read errorq data at %p", addr); return (WALK_ERR); } return (wsp->walk_callback(addr, eqdp->eqd_buf, wsp->walk_cbdata)); } static void eqd_walk_fini(mdb_walk_state_t *wsp) { eqd_walk_data_t *eqdp = wsp->walk_data; mdb_free(eqdp->eqd_stack, sizeof (uintptr_t) * eqdp->eqd_qlen); mdb_free(eqdp->eqd_buf, eqdp->eqd_size); mdb_free(eqdp, sizeof (eqd_walk_data_t)); } #define EQKSVAL(eqv, what) (eqv.eq_kstat.what.value.ui64) static int errorq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; errorq_t eq; uint_t opt_v = FALSE; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("errorq", "errorq", argc, argv) == -1) { mdb_warn("can't walk 'errorq'"); return (DCMD_ERR); } return (DCMD_OK); } i = mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL); argc -= i; argv += i; if (argc != 0) return (DCMD_USAGE); if (opt_v || DCMD_HDRSPEC(flags)) { mdb_printf("%%-11s %-16s %1s %1s %1s ", "ADDR", "NAME", "S", "V", "N"); if (!opt_v) { mdb_printf("%7s %7s %7s%\n", "ACCEPT", "DROP", "LOG"); } else { mdb_printf("%5s %6s %6s %3s %16s%\n", "KSTAT", "QLEN", "SIZE", "IPL", "FUNC"); } } if (mdb_vread(&eq, sizeof (eq), addr) != sizeof (eq)) { mdb_warn("failed to read errorq at %p", addr); return (DCMD_ERR); } mdb_printf("%-11p %-16s %c %c %c ", addr, eq.eq_name, (eq.eq_flags & ERRORQ_ACTIVE) ? '+' : '-', (eq.eq_flags & ERRORQ_VITAL) ? '!' : ' ', (eq.eq_flags & ERRORQ_NVLIST) ? '*' : ' '); if (!opt_v) { mdb_printf("%7llu %7llu %7llu\n", EQKSVAL(eq, eqk_dispatched) + EQKSVAL(eq, eqk_committed), EQKSVAL(eq, eqk_dropped) + EQKSVAL(eq, eqk_reserve_fail) + EQKSVAL(eq, eqk_commit_fail), EQKSVAL(eq, eqk_logged)); } else { mdb_printf("%5s %6lu %6lu %3u %a\n", " | ", eq.eq_qlen, eq.eq_size, eq.eq_ipl, eq.eq_func); mdb_printf("%38s\n%41s" "%12s %llu\n" "%53s %llu\n" "%53s %llu\n" "%53s %llu\n" "%53s %llu\n" "%53s %llu\n" "%53s %llu\n" "%53s %llu\n\n", "|", "+-> ", "DISPATCHED", EQKSVAL(eq, eqk_dispatched), "DROPPED", EQKSVAL(eq, eqk_dropped), "LOGGED", EQKSVAL(eq, eqk_logged), "RESERVED", EQKSVAL(eq, eqk_reserved), "RESERVE FAIL", EQKSVAL(eq, eqk_reserve_fail), "COMMITTED", EQKSVAL(eq, eqk_committed), "COMMIT FAIL", EQKSVAL(eq, eqk_commit_fail), "CANCELLED", EQKSVAL(eq, eqk_cancelled)); } return (DCMD_OK); } /*ARGSUSED*/ static int panicinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { cpu_t panic_cpu; kthread_t *panic_thread; void *buf; panic_data_t *pd; int i, n; if (!mdb_prop_postmortem) { mdb_warn("panicinfo can only be run on a system " "dump; see dumpadm(8)\n"); return (DCMD_ERR); } if (flags & DCMD_ADDRSPEC || argc != 0) return (DCMD_USAGE); if (mdb_readsym(&panic_cpu, sizeof (cpu_t), "panic_cpu") == -1) mdb_warn("failed to read 'panic_cpu'"); else mdb_printf("%16s %?d\n", "cpu", panic_cpu.cpu_id); if (mdb_readvar(&panic_thread, "panic_thread") == -1) mdb_warn("failed to read 'panic_thread'"); else mdb_printf("%16s %?p\n", "thread", panic_thread); buf = mdb_alloc(PANICBUFSIZE, UM_SLEEP); pd = (panic_data_t *)buf; if (mdb_readsym(buf, PANICBUFSIZE, "panicbuf") == -1 || pd->pd_version != PANICBUFVERS) { mdb_warn("failed to read 'panicbuf'"); mdb_free(buf, PANICBUFSIZE); return (DCMD_ERR); } mdb_printf("%16s %s\n", "message", (char *)buf + pd->pd_msgoff); n = (pd->pd_msgoff - (sizeof (panic_data_t) - sizeof (panic_nv_t))) / sizeof (panic_nv_t); for (i = 0; i < n; i++) mdb_printf("%16s %?llx\n", pd->pd_nvdata[i].pnv_name, pd->pd_nvdata[i].pnv_value); mdb_free(buf, PANICBUFSIZE); return (DCMD_OK); } /* * ::time dcmd, which will print a hires timestamp of when we entered the * debugger, or the lbolt value if used with the -l option. * */ /*ARGSUSED*/ static int time(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_dec = FALSE; uint_t opt_lbolt = FALSE; uint_t opt_hex = FALSE; const char *fmt; hrtime_t result; if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &opt_dec, 'l', MDB_OPT_SETBITS, TRUE, &opt_lbolt, 'x', MDB_OPT_SETBITS, TRUE, &opt_hex, NULL) != argc) return (DCMD_USAGE); if (opt_dec && opt_hex) return (DCMD_USAGE); result = opt_lbolt ? mdb_get_lbolt() : mdb_gethrtime(); fmt = opt_hex ? "0x%llx\n" : opt_dec ? "0t%lld\n" : "%#llr\n"; mdb_printf(fmt, result); return (DCMD_OK); } void time_help(void) { mdb_printf("Prints the system time in nanoseconds.\n\n" "::time will return the timestamp at which we dropped into, \n" "if called from, kmdb(1); the core dump's high resolution \n" "time if inspecting one; or the running hires time if we're \n" "looking at a live system.\n\n" "Options:\n" " -d report times in decimal\n" " -l prints the number of clock ticks since system boot\n" " -x report times in hexadecimal\n"); } static void findstack_help(void) { mdb_printf( "Options:\n" " -n do not resolve addresses to names\n" " -s show the size of each stack frame to the left\n" " -t where CTF is present, show types for functions and " "arguments\n" " -v show function arguments\n" "\n" "If the optional %cnt% is given, no more than %cnt% " "arguments are shown\nfor each stack frame.\n"); } extern int cmd_refstr(uintptr_t, uint_t, int, const mdb_arg_t *); static const mdb_dcmd_t dcmds[] = { /* from genunix.c */ { "as2proc", ":", "convert as to proc_t address", as2proc }, { "binding_hash_entry", ":", "print driver names hash table entry", binding_hash_entry }, { "callout", "?[-r|n] [-s|l] [-xhB] [-t | -ab nsec [-dkD]]" " [-C addr | -S seqid] [-f name|addr] [-p name| addr] [-T|L [-E]]" " [-FivVA]", "display callouts", callout, callout_help }, { "calloutid", "[-d|v] xid", "print callout by extended id", calloutid, calloutid_help }, { "class", NULL, "print process scheduler classes", class }, { "cpuinfo", "?[-v]", "print CPUs and runnable threads", cpuinfo }, { "did2thread", "? kt_did", "find kernel thread for this id", did2thread }, { "errorq", "?[-v]", "display kernel error queues", errorq }, { "fd", ":[fd num]", "get a file pointer from an fd", fd }, { "flipone", ":", "the vik_rev_level 2 special", flipone }, { "lminfo", NULL, "print lock manager information", lminfo }, { "ndi_event_hdl", "?", "print ndi_event_hdl", ndi_event_hdl }, { "panicinfo", NULL, "print panic information", panicinfo }, { "pid2proc", "?", "convert PID to proc_t address", pid2proc }, { "project", NULL, "display kernel project(s)", project }, { "ps", "[-fltzTP]", "list processes (and associated thr,lwp)", ps, ps_help }, { "pflags", NULL, "display various proc_t flags", pflags }, { "pgrep", "[-x] [-n | -o] pattern", "pattern match against all processes", pgrep }, { "ptree", NULL, "print process tree", ptree }, { "refstr", NULL, "print string from a refstr_t", cmd_refstr, NULL }, { "sysevent", "?[-sv]", "print sysevent pending or sent queue", sysevent}, { "sysevent_channel", "?", "print sysevent channel database", sysevent_channel}, { "sysevent_class_list", ":", "print sysevent class list", sysevent_class_list}, { "sysevent_subclass_list", ":", "print sysevent subclass list", sysevent_subclass_list}, { "system", NULL, "print contents of /etc/system file", sysfile }, { "task", NULL, "display kernel task(s)", task }, { "time", "[-dlx]", "display system time", time, time_help }, { "vnode2path", ":[-F]", "vnode address to pathname", vnode2path }, { "whereopen", ":", "given a vnode, dumps procs which have it open", whereopen }, /* from bio.c */ { "bufpagefind", ":addr", "find page_t on buf_t list", bufpagefind }, /* from bitset.c */ { "bitset", ":", "display a bitset", bitset, bitset_help }, /* from contract.c */ { "contract", "?", "display a contract", cmd_contract }, { "ctevent", ":", "display a contract event", cmd_ctevent }, { "ctid", ":", "convert id to a contract pointer", cmd_ctid }, /* from cpupart.c */ { "cpupart", "?[-v]", "print cpu partition info", cpupart }, /* from cred.c */ { "cred", ":[-v]", "display a credential", cmd_cred }, { "credgrp", ":[-v]", "display cred_t groups", cmd_credgrp }, { "credsid", ":[-v]", "display a credsid_t", cmd_credsid }, { "ksidlist", ":[-v]", "display a ksidlist_t", cmd_ksidlist }, /* from cyclic.c */ { "cyccover", NULL, "dump cyclic coverage information", cyccover }, { "cycid", "?", "dump a cyclic id", cycid }, { "cycinfo", "?", "dump cyc_cpu info", cycinfo }, { "cyclic", ":", "developer information", cyclic }, { "cyctrace", "?", "dump cyclic trace buffer", cyctrace }, /* from damap.c */ { "damap", ":", "display a damap_t", damap, damap_help }, /* from ddi_periodic.c */ { "ddi_periodic", "?[-v]", "dump ddi_periodic_impl_t info", dprinfo }, /* from devinfo.c */ { "devbindings", "?[-qs] [device-name | major-num]", "print devinfo nodes bound to device-name or major-num", devbindings, devinfo_help }, { "devinfo", ":[-qsd] [-b bus]", "detailed devinfo of one node", devinfo, devinfo_help }, { "devinfo_audit", ":[-v]", "devinfo configuration audit record", devinfo_audit }, { "devinfo_audit_log", "?[-v]", "system wide devinfo configuration log", devinfo_audit_log }, { "devinfo_audit_node", ":[-v]", "devinfo node configuration history", devinfo_audit_node }, { "devinfo2driver", ":", "find driver name for this devinfo node", devinfo2driver }, { "devnames", "?[-vm] [num]", "print devnames array", devnames }, { "dev2major", "?", "convert dev_t to a major number", dev2major }, { "dev2minor", "?", "convert dev_t to a minor number", dev2minor }, { "devt", "?", "display a dev_t's major and minor numbers", devt }, { "major2name", "?", "convert major number to dev name", major2name }, { "minornodes", ":", "given a devinfo node, print its minor nodes", minornodes }, { "modctl2devinfo", ":", "given a modctl, list its devinfos", modctl2devinfo }, { "name2major", "", "convert dev name to major number", name2major }, { "prtconf", "?[-vpc] [-d driver] [-i inst]", "print devinfo tree", prtconf, prtconf_help }, { "softstate", ":", "retrieve soft-state pointer", softstate }, { "devinfo_fm", ":", "devinfo fault managment configuration", devinfo_fm }, { "devinfo_fmce", ":", "devinfo fault managment cache entry", devinfo_fmce}, /* from findstack.c */ { "findstack", ":[-nstv]", "find kernel thread stack", findstack, findstack_help }, { "findstack_debug", NULL, "toggle findstack debugging", findstack_debug }, { "stacks", "?[-afiv] [-c func] [-C func] [-m module] [-M module] " "[-s sobj | -S sobj] [-t tstate | -T tstate]", "print unique kernel thread stacks", stacks, stacks_help }, /* from fm.c */ { "ereport", "[-v]", "print ereports logged in dump", ereport }, /* from pci.c */ { "pcie_fatal_errors", "?[-v]", "display PCIe fabric scan info from fatal error", pcie_pf_impl_dcmd, pcie_pf_impl_help }, { "bdf", ":", "decode a PCIe BDF (Bus/Device/Function) value and " "print it as b/d/f", pcie_bdf_dcmd }, /* from group.c */ { "group", "?[-q]", "display a group", group}, /* from hotplug.c */ { "hotplug", "?[-p]", "display a registered hotplug attachment", hotplug, hotplug_help }, /* from irm.c */ { "irmpools", NULL, "display interrupt pools", irmpools_dcmd }, { "irmreqs", NULL, "display interrupt requests in an interrupt pool", irmreqs_dcmd }, { "irmreq", NULL, "display an interrupt request", irmreq_dcmd }, /* from kgrep.c + genunix.c */ { "kgrep", KGREP_USAGE, "search kernel as for a pointer", kgrep, kgrep_help }, /* from kmem.c */ { "allocdby", ":", "given a thread, print its allocated buffers", allocdby }, { "bufctl", ":[-vh] [-a addr] [-c caller] [-e earliest] [-l latest] " "[-t thd]", "print or filter a bufctl", bufctl, bufctl_help }, { "freedby", ":", "given a thread, print its freed buffers", freedby }, { "kmalog", "?[ fail | slab | zerosized ]", "display kmem transaction log and stack traces for specified type", kmalog }, { "kmastat", "[-kmg]", "kernel memory allocator stats", kmastat }, { "kmausers", "?[-ef] [cache ...]", "current medium and large users " "of the kmem allocator", kmausers, kmausers_help }, { "kmem_cache", "?[-n name]", "print kernel memory caches", kmem_cache, kmem_cache_help}, { "kmem_slabs", "?[-v] [-n cache] [-N cache] [-b maxbins] " "[-B minbinsize]", "display slab usage per kmem cache", kmem_slabs, kmem_slabs_help }, { "kmem_debug", NULL, "toggle kmem dcmd/walk debugging", kmem_debug }, { "kmem_log", "?[-b]", "dump kmem transaction log", kmem_log }, { "kmem_verify", "?", "check integrity of kmem-managed memory", kmem_verify }, { "vmem", "?", "print a vmem_t", vmem }, { "vmem_seg", ":[-sv] [-c caller] [-e earliest] [-l latest] " "[-m minsize] [-M maxsize] [-t thread] [-T type]", "print or filter a vmem_seg", vmem_seg, vmem_seg_help }, { "whatthread", ":[-v]", "print threads whose stack contains the " "given address", whatthread }, /* from ldi.c */ { "ldi_handle", "?[-i]", "display a layered driver handle", ldi_handle, ldi_handle_help }, { "ldi_ident", NULL, "display a layered driver identifier", ldi_ident, ldi_ident_help }, /* from leaky.c + leaky_subr.c */ { "findleaks", FINDLEAKS_USAGE, "search for potential kernel memory leaks", findleaks, findleaks_help }, /* from lgrp.c */ { "lgrp", "?[-q] [-p | -Pih]", "display an lgrp", lgrp}, { "lgrp_set", "", "display bitmask of lgroups as a list", lgrp_set}, /* from log.c */ { "msgbuf", "?[-tTv]", "print most recent console messages", msgbuf, msgbuf_help }, /* from mdi.c */ { "mdipi", NULL, "given a path, dump mdi_pathinfo " "and detailed pi_prop list", mdipi }, { "mdiprops", NULL, "given a pi_prop, dump the pi_prop list", mdiprops }, { "mdiphci", NULL, "given a phci, dump mdi_phci and " "list all paths", mdiphci }, { "mdivhci", NULL, "given a vhci, dump mdi_vhci and list " "all phcis", mdivhci }, { "mdiclient_paths", NULL, "given a path, walk mdi_pathinfo " "client links", mdiclient_paths }, { "mdiphci_paths", NULL, "given a path, walk through mdi_pathinfo " "phci links", mdiphci_paths }, { "mdiphcis", NULL, "given a phci, walk through mdi_phci ph_next links", mdiphcis }, /* from memory.c */ { "addr2smap", ":[offset]", "translate address to smap", addr2smap }, { "memlist", "?[-iav]", "display a struct memlist", memlist }, { "memstat", NULL, "display memory usage summary", memstat }, { "page", "?", "display a summarized page_t", page }, { "pagelookup", "?[-v vp] [-o offset]", "find the page_t with the name {vp, offset}", pagelookup, pagelookup_help }, { "page_num2pp", ":", "find the page_t for a given page frame number", page_num2pp }, { "pmap", ":[-q]", "print process memory map", pmap }, { "seg", ":", "print address space segment", seg }, { "swapinfo", "?", "display a struct swapinfo", swapinfof }, { "vnode2smap", ":[offset]", "translate vnode to smap", vnode2smap }, /* from modhash.c */ { "modhash", "?[-ceht] [-k key] [-v val] [-i index]", "display information about one or all mod_hash structures", modhash, modhash_help }, { "modent", ":[-k | -v | -t type]", "display information about a mod_hash_entry", modent, modent_help }, /* from net.c */ { "dladm", "? [flags]", "show data link information", dladm, dladm_help }, { "mi", ":[-p] [-d | -m]", "filter and display MI object or payload", mi }, { "netstat", "[-arv] [-f inet | inet6 | unix] [-P tcp | udp | icmp]", "show network statistics", netstat }, { "sonode", "?[-f inet | inet6 | unix | #] " "[-t stream | dgram | raw | #] [-p #]", "filter and display sonode", sonode }, /* from netstack.c */ { "netstack", "", "show stack instances", netstack }, { "netstackid2netstack", ":", "translate a netstack id to its netstack_t", netstackid2netstack }, /* from nvpair.c */ { NVPAIR_DCMD_NAME, NVPAIR_DCMD_USAGE, NVPAIR_DCMD_DESCR, nvpair_print }, { NVLIST_DCMD_NAME, NVLIST_DCMD_USAGE, NVLIST_DCMD_DESCR, print_nvlist }, /* from pg.c */ { "pg", "?[-q]", "display a pg", pg}, /* from rctl.c */ { "rctl_dict", "?", "print systemwide default rctl definitions", rctl_dict }, { "rctl_list", ":[handle]", "print rctls for the given proc", rctl_list }, { "rctl", ":[handle]", "print a rctl_t, only if it matches the handle", rctl }, { "rctl_validate", ":[-v] [-n #]", "test resource control value " "sequence", rctl_validate }, /* from sobj.c */ { "rwlock", ":", "dump out a readers/writer lock", rwlock }, { "mutex", ":[-f]", "dump out an adaptive or spin mutex", mutex, mutex_help }, { "sobj2ts", ":", "perform turnstile lookup on synch object", sobj2ts }, { "wchaninfo", "?[-v]", "dump condition variable", wchaninfo }, { "turnstile", "?", "display a turnstile", turnstile }, /* from stream.c */ { "mblk", ":[-q|v] [-f|F flag] [-t|T type] [-l|L|B len] [-d dbaddr]", "print an mblk", mblk_prt, mblk_help }, { "mblk_verify", "?", "verify integrity of an mblk", mblk_verify }, { "mblk2dblk", ":", "convert mblk_t address to dblk_t address", mblk2dblk }, { "q2otherq", ":", "print peer queue for a given queue", q2otherq }, { "q2rdq", ":", "print read queue for a given queue", q2rdq }, { "q2syncq", ":", "print syncq for a given queue", q2syncq }, { "q2stream", ":", "print stream pointer for a given queue", q2stream }, { "q2wrq", ":", "print write queue for a given queue", q2wrq }, { "queue", ":[-q|v] [-m mod] [-f flag] [-F flag] [-s syncq_addr]", "filter and display STREAM queue", queue, queue_help }, { "stdata", ":[-q|v] [-f flag] [-F flag]", "filter and display STREAM head", stdata, stdata_help }, { "str2mate", ":", "print mate of this stream", str2mate }, { "str2wrq", ":", "print write queue of this stream", str2wrq }, { "stream", ":", "display STREAM", stream }, { "strftevent", ":", "print STREAMS flow trace event", strftevent }, { "syncq", ":[-q|v] [-f flag] [-F flag] [-t type] [-T type]", "filter and display STREAM sync queue", syncq, syncq_help }, { "syncq2q", ":", "print queue for a given syncq", syncq2q }, /* from taskq.c */ { "taskq", ":[-atT] [-m min_maxq] [-n name]", "display a taskq", taskq, taskq_help }, { "taskq_entry", ":", "display a taskq_ent_t", taskq_ent }, /* from thread.c */ { "thread", "?[-bdfimps]", "display a summarized kthread_t", thread, thread_help }, { "threadlist", "?[-t] [-v [count]]", "display threads and associated C stack traces", threadlist, threadlist_help }, { "stackinfo", "?[-h|-a]", "display kthread_t stack usage", stackinfo, stackinfo_help }, /* from tsd.c */ { "tsd", ":-k key", "print tsd[key-1] for this thread", ttotsd }, { "tsdtot", ":", "find thread with this tsd", tsdtot }, /* * typegraph does not work under kmdb, as it requires too much memory * for its internal data structures. */ #ifndef _KMDB /* from typegraph.c */ { "findlocks", ":", "find locks held by specified thread", findlocks }, { "findfalse", "?[-v]", "find potentially falsely shared structures", findfalse }, { "typegraph", NULL, "build type graph", typegraph }, { "istype", ":type", "manually set object type", istype }, { "notype", ":", "manually clear object type", notype }, { "whattype", ":", "determine object type", whattype }, #endif /* from vfs.c */ { "fsinfo", "?[-v]", "print mounted filesystems", fsinfo }, { "pfiles", ":[-fp]", "print process file information", pfiles, pfiles_help }, /* from zone.c */ { "zid2zone", ":", "find the zone_t with the given zone id", zid2zone }, { "zone", "?[-r [-v]]", "display kernel zone(s)", zoneprt }, { "zsd", ":[-v] [zsd_key]", "display zone-specific-data entries for " "selected zones", zsd }, #ifndef _KMDB { "gcore", NULL, "generate a user core for the given process", gcore_dcmd }, #endif { NULL } }; static const mdb_walker_t walkers[] = { /* from genunix.c */ { "callouts_bytime", "walk callouts by list chain (expiration time)", callout_walk_init, callout_walk_step, callout_walk_fini, (void *)CALLOUT_WALK_BYLIST }, { "callouts_byid", "walk callouts by id hash chain", callout_walk_init, callout_walk_step, callout_walk_fini, (void *)CALLOUT_WALK_BYID }, { "callout_list", "walk a callout list", callout_list_walk_init, callout_list_walk_step, callout_list_walk_fini }, { "callout_table", "walk callout table array", callout_table_walk_init, callout_table_walk_step, callout_table_walk_fini }, { "cpu", "walk cpu structures", cpu_walk_init, cpu_walk_step }, { "dnlc", "walk dnlc entries", dnlc_walk_init, dnlc_walk_step, dnlc_walk_fini }, { "ereportq_dump", "walk list of ereports in dump error queue", ereportq_dump_walk_init, ereportq_dump_walk_step, NULL }, { "ereportq_pend", "walk list of ereports in pending error queue", ereportq_pend_walk_init, ereportq_pend_walk_step, NULL }, { "errorq", "walk list of system error queues", errorq_walk_init, errorq_walk_step, NULL }, { "errorq_data", "walk pending error queue data buffers", eqd_walk_init, eqd_walk_step, eqd_walk_fini }, { "allfile", "given a proc pointer, list all file pointers", file_walk_init, allfile_walk_step, file_walk_fini }, { "file", "given a proc pointer, list of open file pointers", file_walk_init, file_walk_step, file_walk_fini }, { "lock_descriptor", "walk lock_descriptor_t structures", ld_walk_init, ld_walk_step, NULL }, { "lock_graph", "walk lock graph", lg_walk_init, lg_walk_step, NULL }, { "port", "given a proc pointer, list of created event ports", port_walk_init, port_walk_step, NULL }, { "portev", "given a port pointer, list of events in the queue", portev_walk_init, portev_walk_step, portev_walk_fini }, { "proc", "list of active proc_t structures", proc_walk_init, proc_walk_step, proc_walk_fini }, { "projects", "walk a list of kernel projects", project_walk_init, project_walk_step, NULL }, { "sysevent_pend", "walk sysevent pending queue", sysevent_pend_walk_init, sysevent_walk_step, sysevent_walk_fini}, { "sysevent_sent", "walk sysevent sent queue", sysevent_sent_walk_init, sysevent_walk_step, sysevent_walk_fini}, { "sysevent_channel", "walk sysevent channel subscriptions", sysevent_channel_walk_init, sysevent_channel_walk_step, sysevent_channel_walk_fini}, { "sysevent_class_list", "walk sysevent subscription's class list", sysevent_class_list_walk_init, sysevent_class_list_walk_step, sysevent_class_list_walk_fini}, { "sysevent_subclass_list", "walk sysevent subscription's subclass list", sysevent_subclass_list_walk_init, sysevent_subclass_list_walk_step, sysevent_subclass_list_walk_fini}, { "task", "given a task pointer, walk its processes", task_walk_init, task_walk_step, NULL }, /* from avl.c */ { AVL_WALK_NAME, AVL_WALK_DESC, avl_walk_init, avl_walk_step, avl_walk_fini }, /* from bio.c */ { "buf", "walk the bio buf hash", buf_walk_init, buf_walk_step, buf_walk_fini }, /* from contract.c */ { "contract", "walk all contracts, or those of the specified type", ct_walk_init, generic_walk_step, NULL }, { "ct_event", "walk events on a contract event queue", ct_event_walk_init, generic_walk_step, NULL }, { "ct_listener", "walk contract event queue listeners", ct_listener_walk_init, generic_walk_step, NULL }, /* from cpupart.c */ { "cpupart_cpulist", "given an cpupart_t, walk cpus in partition", cpupart_cpulist_walk_init, cpupart_cpulist_walk_step, NULL }, { "cpupart_walk", "walk the set of cpu partitions", cpupart_walk_init, cpupart_walk_step, NULL }, /* from ctxop.c */ { "ctxop", "walk list of context ops on a thread", ctxop_walk_init, ctxop_walk_step, ctxop_walk_fini }, /* from cyclic.c */ { "cyccpu", "walk per-CPU cyc_cpu structures", cyccpu_walk_init, cyccpu_walk_step, NULL }, { "cycomni", "for an omnipresent cyclic, walk cyc_omni_cpu list", cycomni_walk_init, cycomni_walk_step, NULL }, { "cyctrace", "walk cyclic trace buffer", cyctrace_walk_init, cyctrace_walk_step, cyctrace_walk_fini }, /* from devinfo.c */ { "binding_hash", "walk all entries in binding hash table", binding_hash_walk_init, binding_hash_walk_step, NULL }, { "devinfo", "walk devinfo tree or subtree", devinfo_walk_init, devinfo_walk_step, devinfo_walk_fini }, { "devinfo_audit_log", "walk devinfo audit system-wide log", devinfo_audit_log_walk_init, devinfo_audit_log_walk_step, devinfo_audit_log_walk_fini}, { "devinfo_audit_node", "walk per-devinfo audit history", devinfo_audit_node_walk_init, devinfo_audit_node_walk_step, devinfo_audit_node_walk_fini}, { "devinfo_children", "walk children of devinfo node", devinfo_children_walk_init, devinfo_children_walk_step, devinfo_children_walk_fini }, { "devinfo_parents", "walk ancestors of devinfo node", devinfo_parents_walk_init, devinfo_parents_walk_step, devinfo_parents_walk_fini }, { "devinfo_siblings", "walk siblings of devinfo node", devinfo_siblings_walk_init, devinfo_siblings_walk_step, NULL }, { "devi_next", "walk devinfo list", NULL, devi_next_walk_step, NULL }, { "devnames", "walk devnames array", devnames_walk_init, devnames_walk_step, devnames_walk_fini }, { "minornode", "given a devinfo node, walk minor nodes", minornode_walk_init, minornode_walk_step, NULL }, { "softstate", "given an i_ddi_soft_state*, list all in-use driver stateps", soft_state_walk_init, soft_state_walk_step, NULL, NULL }, { "softstate_all", "given an i_ddi_soft_state*, list all driver stateps", soft_state_walk_init, soft_state_all_walk_step, NULL, NULL }, { "devinfo_fmc", "walk a fault management handle cache active list", devinfo_fmc_walk_init, devinfo_fmc_walk_step, NULL }, /* from group.c */ { "group", "walk all elements of a group", group_walk_init, group_walk_step, NULL }, /* from irm.c */ { "irmpools", "walk global list of interrupt pools", irmpools_walk_init, list_walk_step, list_walk_fini }, { "irmreqs", "walk list of interrupt requests in an interrupt pool", irmreqs_walk_init, list_walk_step, list_walk_fini }, /* from kmem.c */ { "allocdby", "given a thread, walk its allocated bufctls", allocdby_walk_init, allocdby_walk_step, allocdby_walk_fini }, { "bufctl", "walk a kmem cache's bufctls", bufctl_walk_init, kmem_walk_step, kmem_walk_fini }, { "bufctl_history", "walk the available history of a bufctl", bufctl_history_walk_init, bufctl_history_walk_step, bufctl_history_walk_fini }, { "freedby", "given a thread, walk its freed bufctls", freedby_walk_init, allocdby_walk_step, allocdby_walk_fini }, { "freectl", "walk a kmem cache's free bufctls", freectl_walk_init, kmem_walk_step, kmem_walk_fini }, { "freectl_constructed", "walk a kmem cache's constructed free bufctls", freectl_constructed_walk_init, kmem_walk_step, kmem_walk_fini }, { "freemem", "walk a kmem cache's free memory", freemem_walk_init, kmem_walk_step, kmem_walk_fini }, { "freemem_constructed", "walk a kmem cache's constructed free memory", freemem_constructed_walk_init, kmem_walk_step, kmem_walk_fini }, { "kmem", "walk a kmem cache", kmem_walk_init, kmem_walk_step, kmem_walk_fini }, { "kmem_cpu_cache", "given a kmem cache, walk its per-CPU caches", kmem_cpu_cache_walk_init, kmem_cpu_cache_walk_step, NULL }, { "kmem_hash", "given a kmem cache, walk its allocated hash table", kmem_hash_walk_init, kmem_hash_walk_step, kmem_hash_walk_fini }, { "kmem_log", "walk the kmem transaction log", kmem_log_walk_init, kmem_log_walk_step, kmem_log_walk_fini }, { "kmem_slab", "given a kmem cache, walk its slabs", kmem_slab_walk_init, combined_walk_step, combined_walk_fini }, { "kmem_slab_partial", "given a kmem cache, walk its partially allocated slabs (min 1)", kmem_slab_walk_partial_init, combined_walk_step, combined_walk_fini }, { "vmem", "walk vmem structures in pre-fix, depth-first order", vmem_walk_init, vmem_walk_step, vmem_walk_fini }, { "vmem_alloc", "given a vmem_t, walk its allocated vmem_segs", vmem_alloc_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini }, { "vmem_free", "given a vmem_t, walk its free vmem_segs", vmem_free_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini }, { "vmem_postfix", "walk vmem structures in post-fix, depth-first order", vmem_walk_init, vmem_postfix_walk_step, vmem_walk_fini }, { "vmem_seg", "given a vmem_t, walk all of its vmem_segs", vmem_seg_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini }, { "vmem_span", "given a vmem_t, walk its spanning vmem_segs", vmem_span_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini }, /* from ldi.c */ { "ldi_handle", "walk the layered driver handle hash", ldi_handle_walk_init, ldi_handle_walk_step, NULL }, { "ldi_ident", "walk the layered driver identifier hash", ldi_ident_walk_init, ldi_ident_walk_step, NULL }, /* from leaky.c + leaky_subr.c */ { "leak", "given a leaked bufctl or vmem_seg, find leaks w/ same " "stack trace", leaky_walk_init, leaky_walk_step, leaky_walk_fini }, { "leakbuf", "given a leaked bufctl or vmem_seg, walk buffers for " "leaks w/ same stack trace", leaky_walk_init, leaky_buf_walk_step, leaky_walk_fini }, /* from lgrp.c */ { "lgrp_cpulist", "walk CPUs in a given lgroup", lgrp_cpulist_walk_init, lgrp_cpulist_walk_step, NULL }, { "lgrptbl", "walk lgroup table", lgrp_walk_init, lgrp_walk_step, NULL }, { "lgrp_parents", "walk up lgroup lineage from given lgroup", lgrp_parents_walk_init, lgrp_parents_walk_step, NULL }, { "lgrp_rsrc_mem", "walk lgroup memory resources of given lgroup", lgrp_rsrc_mem_walk_init, lgrp_set_walk_step, NULL }, { "lgrp_rsrc_cpu", "walk lgroup CPU resources of given lgroup", lgrp_rsrc_cpu_walk_init, lgrp_set_walk_step, NULL }, /* from list.c */ { LIST_WALK_NAME, LIST_WALK_DESC, list_walk_init, list_walk_step, list_walk_fini }, /* from mdi.c */ { "mdipi_client_list", "Walker for mdi_pathinfo pi_client_link", mdi_pi_client_link_walk_init, mdi_pi_client_link_walk_step, mdi_pi_client_link_walk_fini }, { "mdipi_phci_list", "Walker for mdi_pathinfo pi_phci_link", mdi_pi_phci_link_walk_init, mdi_pi_phci_link_walk_step, mdi_pi_phci_link_walk_fini }, { "mdiphci_list", "Walker for mdi_phci ph_next link", mdi_phci_ph_next_walk_init, mdi_phci_ph_next_walk_step, mdi_phci_ph_next_walk_fini }, /* from memory.c */ { "allpages", "walk all pages, including free pages", allpages_walk_init, allpages_walk_step, allpages_walk_fini }, { "anon", "given an amp, list allocated anon structures", anon_walk_init, anon_walk_step, anon_walk_fini, ANON_WALK_ALLOC }, { "anon_all", "given an amp, list contents of all anon slots", anon_walk_init, anon_walk_step, anon_walk_fini, ANON_WALK_ALL }, { "memlist", "walk specified memlist", NULL, memlist_walk_step, NULL }, { "page", "walk all pages, or those from the specified vnode", page_walk_init, page_walk_step, page_walk_fini }, { "seg", "given an as, list of segments", seg_walk_init, avl_walk_step, avl_walk_fini }, { "segvn_anon", "given a struct segvn_data, list allocated anon structures", segvn_anon_walk_init, anon_walk_step, anon_walk_fini, ANON_WALK_ALLOC }, { "segvn_anon_all", "given a struct segvn_data, list contents of all anon slots", segvn_anon_walk_init, anon_walk_step, anon_walk_fini, ANON_WALK_ALL }, { "segvn_pages", "given a struct segvn_data, list resident pages in " "offset order", segvn_pages_walk_init, segvn_pages_walk_step, segvn_pages_walk_fini, SEGVN_PAGES_RESIDENT }, { "segvn_pages_all", "for each offset in a struct segvn_data, give page_t pointer " "(if resident), or NULL.", segvn_pages_walk_init, segvn_pages_walk_step, segvn_pages_walk_fini, SEGVN_PAGES_ALL }, { "swapinfo", "walk swapinfo structures", swap_walk_init, swap_walk_step, NULL }, /* from modhash.c */ { "modhash", "walk list of mod_hash structures", modhash_walk_init, modhash_walk_step, NULL }, { "modent", "walk list of entries in a given mod_hash", modent_walk_init, modent_walk_step, modent_walk_fini }, { "modchain", "walk list of entries in a given mod_hash_entry", NULL, modchain_walk_step, NULL }, /* from net.c */ { "icmp", "walk ICMP control structures using MI for all stacks", mi_payload_walk_init, mi_payload_walk_step, NULL, &mi_icmp_arg }, { "mi", "given a MI_O, walk the MI", mi_walk_init, mi_walk_step, mi_walk_fini, NULL }, { "sonode", "given a sonode, walk its children", sonode_walk_init, sonode_walk_step, sonode_walk_fini, NULL }, { "icmp_stacks", "walk all the icmp_stack_t", icmp_stacks_walk_init, icmp_stacks_walk_step, NULL }, { "tcp_stacks", "walk all the tcp_stack_t", tcp_stacks_walk_init, tcp_stacks_walk_step, NULL }, { "udp_stacks", "walk all the udp_stack_t", udp_stacks_walk_init, udp_stacks_walk_step, NULL }, /* from netstack.c */ { "netstack", "walk a list of kernel netstacks", netstack_walk_init, netstack_walk_step, NULL }, /* from nvpair.c */ { NVPAIR_WALKER_NAME, NVPAIR_WALKER_DESCR, nvpair_walk_init, nvpair_walk_step, NULL }, /* from pci.c */ { "pcie_bus", "walk all pcie_bus_t's", pcie_bus_walk_init, pcie_bus_walk_step, NULL }, /* from rctl.c */ { "rctl_dict_list", "walk all rctl_dict_entry_t's from rctl_lists", rctl_dict_walk_init, rctl_dict_walk_step, NULL }, { "rctl_set", "given a rctl_set, walk all rctls", rctl_set_walk_init, rctl_set_walk_step, NULL }, { "rctl_val", "given a rctl_t, walk all rctl_val entries associated", rctl_val_walk_init, rctl_val_walk_step }, /* from sobj.c */ { "blocked", "walk threads blocked on a given sobj", blocked_walk_init, blocked_walk_step, NULL }, { "wchan", "given a wchan, list of blocked threads", wchan_walk_init, wchan_walk_step, wchan_walk_fini }, /* from stream.c */ { "b_cont", "walk mblk_t list using b_cont", mblk_walk_init, b_cont_step, mblk_walk_fini }, { "b_next", "walk mblk_t list using b_next", mblk_walk_init, b_next_step, mblk_walk_fini }, { "qlink", "walk queue_t list using q_link", queue_walk_init, queue_link_step, queue_walk_fini }, { "qnext", "walk queue_t list using q_next", queue_walk_init, queue_next_step, queue_walk_fini }, { "strftblk", "given a dblk_t, walk STREAMS flow trace event list", strftblk_walk_init, strftblk_step, strftblk_walk_fini }, { "readq", "walk read queue side of stdata", str_walk_init, strr_walk_step, str_walk_fini }, { "writeq", "walk write queue side of stdata", str_walk_init, strw_walk_step, str_walk_fini }, /* from taskq.c */ { "taskq_thread", "given a taskq_t, list all of its threads", taskq_thread_walk_init, taskq_thread_walk_step, taskq_thread_walk_fini }, { "taskq_entry", "given a taskq_t*, list all taskq_ent_t in the list", taskq_ent_walk_init, taskq_ent_walk_step, NULL }, /* from thread.c */ { "deathrow", "walk threads on both lwp_ and thread_deathrow", deathrow_walk_init, deathrow_walk_step, NULL }, { "cpu_dispq", "given a cpu_t, walk threads in dispatcher queues", cpu_dispq_walk_init, dispq_walk_step, dispq_walk_fini }, { "cpupart_dispq", "given a cpupart_t, walk threads in dispatcher queues", cpupart_dispq_walk_init, dispq_walk_step, dispq_walk_fini }, { "lwp_deathrow", "walk lwp_deathrow", lwp_deathrow_walk_init, deathrow_walk_step, NULL }, { "thread", "global or per-process kthread_t structures", thread_walk_init, thread_walk_step, thread_walk_fini }, { "thread_deathrow", "walk threads on thread_deathrow", thread_deathrow_walk_init, deathrow_walk_step, NULL }, /* from tsd.c */ { "tsd", "walk list of thread-specific data", tsd_walk_init, tsd_walk_step, tsd_walk_fini }, /* from tsol.c */ { "tnrh", "walk remote host cache structures", tnrh_walk_init, tnrh_walk_step, tnrh_walk_fini }, { "tnrhtp", "walk remote host template structures", tnrhtp_walk_init, tnrhtp_walk_step, tnrhtp_walk_fini }, /* * typegraph does not work under kmdb, as it requires too much memory * for its internal data structures. */ #ifndef _KMDB /* from typegraph.c */ { "typeconflict", "walk buffers with conflicting type inferences", typegraph_walk_init, typeconflict_walk_step }, { "typeunknown", "walk buffers with unknown types", typegraph_walk_init, typeunknown_walk_step }, #endif /* from vfs.c */ { "vfs", "walk file system list", vfs_walk_init, vfs_walk_step }, /* from zone.c */ { "zone", "walk a list of kernel zones", zone_walk_init, zone_walk_step, NULL }, { "zsd", "walk list of zsd entries for a zone", zsd_walk_init, zsd_walk_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; /*ARGSUSED*/ static void genunix_statechange_cb(void *ignored) { /* * Force ::findleaks and ::stacks to let go any cached state. */ leaky_cleanup(1); stacks_cleanup(1); kmem_statechange(); /* notify kmem */ } const mdb_modinfo_t * _mdb_init(void) { kmem_init(); (void) mdb_callback_add(MDB_CALLBACK_STCHG, genunix_statechange_cb, NULL); #ifndef _KMDB gcore_init(); #endif return (&modinfo); } void _mdb_fini(void) { leaky_cleanup(1); stacks_cleanup(1); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Display group information and walk all elements of a group */ #include "group.h" #include #include /* * Display group information */ /* ARGSUSED */ int group(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { group_t group; int opt_q = 0; /* display only address. */ /* Should provide an address */ if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'q', MDB_OPT_SETBITS, TRUE, &opt_q, NULL) != argc) return (DCMD_USAGE); if (flags & DCMD_PIPE_OUT) opt_q = B_TRUE; if (DCMD_HDRSPEC(flags) && !opt_q) { mdb_printf("%?s %6s %9s %?s\n", "ADDR", "SIZE", "CAPACITY", "SET"); } if (mdb_vread(&group, sizeof (struct group), addr) == -1) { mdb_warn("unable to read 'group' at %p", addr); return (DCMD_ERR); } if (opt_q) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } mdb_printf("%?p %6d %9d %?p\n", addr, group.grp_size, group.grp_capacity, group.grp_set); return (DCMD_OK); } /* * Walk all elements in the group set. */ typedef struct group_walk { uintptr_t *gw_set; int gw_size; int gw_pos; int gw_initialized; } group_walk_t; /* * Initialize the walk structure with the copy of a group set, its size and the * initial pointer position. */ int group_walk_init(mdb_walk_state_t *wsp) { group_walk_t *gw; group_t group; gw = mdb_alloc(sizeof (group_walk_t), UM_SLEEP | UM_GC); if (mdb_vread(&group, sizeof (struct group), wsp->walk_addr) == -1) { mdb_warn("couldn't read 'group' at %p", wsp->walk_addr); return (WALK_ERR); } gw->gw_size = group.grp_size; gw->gw_initialized = 0; gw->gw_pos = 0; if (gw->gw_size < 0) { mdb_warn("invalid group at %p", wsp->walk_addr); return (WALK_ERR); } if (gw->gw_size == 0) return (WALK_DONE); /* * Allocate space for the set and copy all set entries. */ gw->gw_set = mdb_alloc(group.grp_size * sizeof (uintptr_t), UM_SLEEP | UM_GC); if (mdb_vread(gw->gw_set, group.grp_size * sizeof (uintptr_t), (uintptr_t)group.grp_set) == -1) { mdb_warn("couldn't read 'group set' at %p", group.grp_set); return (WALK_ERR); } wsp->walk_data = gw; wsp->walk_addr = gw->gw_set[0]; gw->gw_pos = 0; return (WALK_NEXT); } /* * Print element of the set and advance the pointer. */ int group_walk_step(mdb_walk_state_t *wsp) { group_walk_t *gw = (group_walk_t *)wsp->walk_data; int status; /* * Already visited all valid elements, nothing else to do. */ if (gw->gw_size < 0) return (WALK_DONE); /* * Print non-NULL elements */ status = wsp->walk_addr == 0 ? WALK_NEXT : wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); /* * Adjust walk_addr to point to the next element */ gw->gw_size--; if (gw->gw_size > 0) wsp->walk_addr = gw->gw_set[++gw->gw_pos]; else status = WALK_DONE; return (status); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_GROUP_H #define _MDB_GROUP_H /* * Block comment that describes the contents of this file. */ #ifdef __cplusplus extern "C" { #endif #include int group(uintptr_t, uint_t, int, const mdb_arg_t *); int group_walk_init(mdb_walk_state_t *); int group_walk_step(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _MDB_GROUP_H */ /* * 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 2019, Joyent, Inc. */ #include #include #include #include #include #include "devinfo.h" static char * ddihp_get_cn_state(ddi_hp_cn_state_t state) { switch (state) { case DDI_HP_CN_STATE_EMPTY: return ("Empty"); case DDI_HP_CN_STATE_PRESENT: return ("Present"); case DDI_HP_CN_STATE_POWERED: return ("Powered"); case DDI_HP_CN_STATE_ENABLED: return ("Enabled"); case DDI_HP_CN_STATE_PORT_EMPTY: return ("Port_Empty"); case DDI_HP_CN_STATE_PORT_PRESENT: return ("Port_Present"); case DDI_HP_CN_STATE_OFFLINE: return ("Offline"); case DDI_HP_CN_STATE_ATTACHED: return ("Attached"); case DDI_HP_CN_STATE_MAINTENANCE: return ("Maintenance"); case DDI_HP_CN_STATE_ONLINE: return ("Online"); default: return ("Unknown"); } } /*ARGSUSED*/ static int hotplug_print(uintptr_t addr, struct dev_info *dev, devinfo_cb_data_t *data) { ddi_hp_cn_handle_t hdl; uintptr_t hdlp = (uintptr_t)dev->devi_hp_hdlp; char cn_type[15]; char cn_name[15]; while (hdlp) { if (mdb_vread(&hdl, sizeof (ddi_hp_cn_handle_t), hdlp) == -1) { mdb_warn("Failed to read hdlp!\n"); return (DCMD_ERR); } if (!(data->di_flags & DEVINFO_HP_PHYSICAL) || hdl.cn_info.cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT) { if (mdb_readstr(cn_type, sizeof (cn_type), (uintptr_t)hdl.cn_info.cn_type_str) == -1) { mdb_warn("Failed to read cn_type!\n"); return (DCMD_ERR); } if (mdb_readstr(cn_name, sizeof (cn_name), (uintptr_t)hdl.cn_info.cn_name) == -1) { mdb_warn("Failed to read cn_name!\n"); return (DCMD_ERR); } mdb_printf("%?p %?p %-12s %-15s %-15s\n", hdl.cn_dip, hdlp, ddihp_get_cn_state(hdl.cn_info.cn_state), cn_type, cn_name); } hdlp = (uintptr_t)hdl.next; }; return (WALK_NEXT); } void hotplug_help(void) { mdb_printf("Switches:\n" " -p only print the physical hotplug connectors\n"); } int hotplug(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { devinfo_cb_data_t data; uintptr_t devinfo_root; /* Address of root of devinfo tree */ ddi_hp_cn_handle_t hdl; char cn_type[15]; char cn_name[15]; int status; data.di_flags = 0; data.di_filter = NULL; data.di_instance = UINT64_MAX; if (mdb_getopts(argc, argv, 'p', MDB_OPT_SETBITS, DEVINFO_HP_PHYSICAL, &data.di_flags, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %?s %-12s %-15s %-15s%\n", "PARENT_DEVINFO", "HANDLE", "STATE", "TYPE", "CN_NAME"); } if ((flags & DCMD_ADDRSPEC) == 0) { data.di_flags |= DEVINFO_PARENT | DEVINFO_CHILD; if (mdb_readvar(&devinfo_root, "top_devinfo") == -1) { mdb_warn("failed to read 'top_devinfo'"); return (0); } data.di_base = devinfo_root; status = mdb_pwalk("devinfo", (mdb_walk_cb_t)hotplug_print, &data, devinfo_root); if (status == -1) { mdb_warn("couldn't walk devinfo tree"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&hdl, sizeof (ddi_hp_cn_handle_t), (uintptr_t)addr) == -1) { mdb_warn("Failed to read hdlp!\n"); return (DCMD_ERR); } if (mdb_readstr(cn_type, sizeof (cn_type), (uintptr_t)hdl.cn_info.cn_type_str) == -1) { mdb_warn("Failed to read cn_type!\n"); return (DCMD_ERR); } if (mdb_readstr(cn_name, sizeof (cn_name), (uintptr_t)hdl.cn_info.cn_name) == -1) { mdb_warn("Failed to read cn_name!\n"); return (DCMD_ERR); } mdb_printf("%?p %?p %-12s %-15s %-15s\n", hdl.cn_dip, addr, ddihp_get_cn_state(hdl.cn_info.cn_state), cn_type, cn_name); return (DCMD_OK); } /* * 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. */ #ifndef _HOTPLUG_H #define _HOTPLUG_H #ifdef __cplusplus extern "C" { #endif extern int hotplug(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern void hotplug_help(void); #ifdef __cplusplus } #endif #endif /* _HOTPLUG_H */ /* * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include "list.h" extern int mdb_devinfo2driver(uintptr_t, char *, size_t); static char * irm_get_type(int type) { if (type == (DDI_INTR_TYPE_MSI | DDI_INTR_TYPE_MSIX)) return ("MSI/X"); switch (type) { case DDI_INTR_TYPE_FIXED: return ("Fixed"); case DDI_INTR_TYPE_MSI: return ("MSI"); case DDI_INTR_TYPE_MSIX: return ("MSI-X"); default: return ("Unknown"); } } static int check_irm_enabled(void) { GElf_Sym sym; uintptr_t addr; int value; if (mdb_lookup_by_name("irm_enable", &sym) == -1) { mdb_warn("couldn't find irm_enable"); return (0); } addr = (uintptr_t)sym.st_value; if (mdb_vread(&value, sizeof (value), addr) != sizeof (value)) { mdb_warn("couldn't read irm_enable at %p", addr); return (0); } return (value); } int irmpools_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; if (mdb_lookup_by_name("irm_pools_list", &sym) == -1) { mdb_warn("couldn't find irm_pools_list"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sym.st_value; return (list_walk_init_named(wsp, "interrupt pools", "pool")); } int irmreqs_walk_init(mdb_walk_state_t *wsp) { wsp->walk_addr = (uintptr_t)(wsp->walk_addr + offsetof(ddi_irm_pool_t, ipool_req_list)); return (list_walk_init_named(wsp, "interrupt requests", "request")); } int irmpools_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ddi_irm_pool_t pool; struct dev_info dev; char driver[MODMAXNAMELEN] = ""; char devname[MODMAXNAMELEN] = ""; if (argc != 0) return (DCMD_USAGE); if (check_irm_enabled() == 0) { mdb_warn("IRM is not enabled"); return (DCMD_ERR); } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("irmpools", "irmpools", argc, argv) == -1) { mdb_warn("can't walk interrupt pools"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %-18s %-8s %-6s %-9s %-8s%\n", "ADDR", "OWNER", "TYPE", "SIZE", "REQUESTED", "RESERVED"); } if (mdb_vread(&pool, sizeof (pool), addr) != sizeof (pool)) { mdb_warn("couldn't read interrupt pool at %p", addr); return (DCMD_ERR); } if (mdb_vread(&dev, sizeof (dev), (uintptr_t)pool.ipool_owner) != sizeof (dev)) { mdb_warn("couldn't read dev_info at %p", pool.ipool_owner); return (DCMD_ERR); } mdb_devinfo2driver((uintptr_t)pool.ipool_owner, driver, sizeof (driver)); /* * Include driver instance number only if the node has an * instance number assigned (i.e. instance != -1) to it. * This will cover cases like rootnex driver which doesn't * have instance number assigned to it. */ if (dev.devi_instance != -1) mdb_snprintf(devname, sizeof (devname), "%s#%d", driver, dev.devi_instance); else mdb_snprintf(devname, sizeof (devname), "%s", driver); mdb_printf("%0?p %-18s %-8s %-6d %-9d %-8d\n", addr, devname, irm_get_type(pool.ipool_types), pool.ipool_totsz, pool.ipool_reqno, pool.ipool_resno); return (DCMD_OK); } int irmreqs_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); if (check_irm_enabled() == 0) { mdb_warn("IRM is not enabled"); return (DCMD_ERR); } if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("can't perform global interrupt request walk"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("irmreqs", "irmreq", argc, argv, addr) == -1) { mdb_warn("can't walk interrupt requests"); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ int irmreq_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ddi_irm_req_t req; struct dev_info dev; struct devinfo_intr intr; char driver[MODMAXNAMELEN] = ""; char devname[MODMAXNAMELEN] = ""; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %-18s %-8s %-8s %-6s %-4s " "%-6s%\n", "ADDR", "OWNER", "TYPE", "CALLBACK", "NINTRS", "NREQ", "NAVAIL"); } if (mdb_vread(&req, sizeof (req), addr) != sizeof (req)) { mdb_warn("couldn't read interrupt request at %p", addr); return (DCMD_ERR); } if (mdb_vread(&dev, sizeof (dev), (uintptr_t)req.ireq_dip) != sizeof (dev)) { mdb_warn("couldn't read dev_info at %p", req.ireq_dip); return (DCMD_ERR); } if (mdb_vread(&intr, sizeof (intr), (uintptr_t)dev.devi_intr_p) != sizeof (intr)) { mdb_warn("couldn't read devinfo_intr at %p", dev.devi_intr_p); return (DCMD_ERR); } mdb_devinfo2driver((uintptr_t)req.ireq_dip, driver, sizeof (driver)); mdb_snprintf(devname, sizeof (devname), "%s#%d", driver, dev.devi_instance); mdb_printf("%0?p %-18s %-8s %-8s %-6d %-4d %-6d\n", addr, devname, irm_get_type(req.ireq_type), (req.ireq_flags & DDI_IRM_FLAG_CALLBACK) ? "Yes" : "No", intr.devi_intr_sup_nintrs, req.ireq_nreq, req.ireq_navail); return (DCMD_OK); } /* * 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. */ #ifndef _IRM_H #define _IRM_H #ifdef __cplusplus extern "C" { #endif #include extern int irmpools_walk_init(mdb_walk_state_t *); extern int irmreqs_walk_init(mdb_walk_state_t *); extern int irmpools_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int irmreqs_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int irmreq_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _IRM_H */ /* * 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. */ /* * Copyright 2011 Joyent, Inc. All rights reserved. */ /* * Generic memory walker, used by both the genunix and libumem dmods. */ #include #include #include "kgrep.h" #define KGREP_FULL_MASK (~(uintmax_t)0) typedef struct kgrep_data { uintmax_t kg_pattern; uintmax_t kg_mask; /* fancy only */ uintmax_t kg_dist; /* fancy only */ uintptr_t kg_minaddr; /* fancy only */ uintptr_t kg_maxaddr; /* fancy only */ void *kg_page; size_t kg_pagesize; char kg_cbtype; char kg_seen; } kgrep_data_t; #define KG_BASE 0 #define KG_VERBOSE 1 #define KG_PIPE 2 static void kgrep_cb(uintptr_t addr, uintmax_t *val, int type) { switch (type) { case KG_BASE: default: mdb_printf("%p\n", addr); break; case KG_VERBOSE: mdb_printf("%p:\t%llx\n", addr, *val); break; case KG_PIPE: mdb_printf("%#lr\n", addr); break; } } static int kgrep_range_basic(uintptr_t base, uintptr_t lim, void *kg_arg) { kgrep_data_t *kg = kg_arg; size_t pagesize = kg->kg_pagesize; uintptr_t pattern = kg->kg_pattern; uintptr_t *page = kg->kg_page; uintptr_t *page_end = &page[pagesize / sizeof (uintptr_t)]; uintptr_t *pos; uintptr_t addr, offset; int seen = 0; /* * page-align everything, to simplify the loop */ base = P2ALIGN(base, pagesize); lim = P2ROUNDUP(lim, pagesize); for (addr = base; addr < lim; addr += pagesize) { if (mdb_vread(page, pagesize, addr) == -1) continue; seen = 1; for (pos = page; pos < page_end; pos++) { if (*pos != pattern) continue; offset = (caddr_t)pos - (caddr_t)page; kgrep_cb(addr + offset, NULL, kg->kg_cbtype); } } if (seen) kg->kg_seen = 1; return (WALK_NEXT); } /* * Full-service template -- instantiated for each supported size. We support * the following options: * * addr in [minaddr, maxaddr), and * value in [pattern, pattern + dist) OR * mask matching: (value & mask) == (pattern & mask) */ #define KGREP_FANCY_TEMPLATE(kgrep_range_fancybits, uintbits_t) \ static int \ kgrep_range_fancybits(uintptr_t base, uintptr_t lim, void *kg_arg) \ { \ kgrep_data_t *kg = kg_arg; \ \ uintbits_t pattern = kg->kg_pattern; \ uintbits_t dist = kg->kg_dist; \ uintbits_t mask = kg->kg_mask; \ uintptr_t minaddr = kg->kg_minaddr; \ uintptr_t maxaddr = kg->kg_maxaddr; \ size_t pagesize = kg->kg_pagesize; \ uintbits_t *page = (uintbits_t *)kg->kg_page; \ uintbits_t *page_end; \ uintbits_t *pos; \ uintbits_t cur; \ uintmax_t out; \ \ uintptr_t addr, size, offset; \ int seen = 0; \ \ base = P2ROUNDUP(MAX(base, minaddr), sizeof (uintbits_t)); \ \ if (maxaddr != 0 && lim > maxaddr) \ lim = maxaddr; \ \ for (addr = base; addr < lim; addr += size) { \ /* P2END(...) computes the next page boundry */ \ size = MIN(lim, P2END(addr, pagesize)) - addr; \ \ if (mdb_vread(page, size, addr) == -1) \ continue; \ \ seen = 1; \ \ page_end = &page[size / sizeof (uintbits_t)]; \ for (pos = page; pos < page_end; pos++) { \ cur = *pos; \ \ /* \ * Due to C's (surprising) integral promotion \ * rules for unsigned types smaller than an \ * int, we need to explicitly cast the result \ * of cur minus pattern, below. \ */ \ if (((cur ^ pattern) & mask) != 0 && \ (uintbits_t)(cur - pattern) >= dist) \ continue; \ \ out = cur; \ offset = (caddr_t)pos - (caddr_t)page; \ kgrep_cb(addr + offset, &out, kg->kg_cbtype); \ } \ } \ if (seen) \ kg->kg_seen = 1; \ \ return (WALK_NEXT); \ } KGREP_FANCY_TEMPLATE(kgrep_range_fancy8, uint8_t) KGREP_FANCY_TEMPLATE(kgrep_range_fancy16, uint16_t) KGREP_FANCY_TEMPLATE(kgrep_range_fancy32, uint32_t) KGREP_FANCY_TEMPLATE(kgrep_range_fancy64, uint64_t) #undef KGREP_FANCY_TEMPLATE void kgrep_help(void) { mdb_printf( "\n" "Search the entire virtual address space for a particular pattern,\n" "%addr%. By default, a pointer-sized search for an exact match is\n" "done.\n\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf( " -v Report the value matched at each address\n" " -a minaddr\n" " Restrict the search to addresses >= minaddr\n" " -A maxaddr\n" " Restrict the search to addresses < maxaddr\n" " -d dist\n" " Search for values in [addr, addr + dist)\n" " -m mask\n" " Search for values where (value & mask) == addr\n" " -M invmask\n" " Search for values where (value & ~invmask) == addr\n" " -s size\n" " Instead of pointer-sized values, search for size-byte values.\n" " size must be 1, 2, 4, or 8.\n"); } /*ARGSUSED*/ int kgrep(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintmax_t pattern = mdb_get_dot(); uintmax_t mask = KGREP_FULL_MASK; uintmax_t invmask = 0; uintmax_t dist = 0; uintptr_t size = sizeof (uintptr_t); uintptr_t minaddr = 0; uintptr_t maxaddr = 0; size_t pagesize = kgrep_subr_pagesize(); int verbose = 0; int ret; int args = 0; kgrep_cb_func *func; kgrep_data_t kg; uintmax_t size_mask; if (mdb_getopts(argc, argv, 'a', MDB_OPT_UINTPTR, &minaddr, 'A', MDB_OPT_UINTPTR, &maxaddr, 'd', MDB_OPT_UINT64, &dist, 'm', MDB_OPT_UINT64, &mask, 'M', MDB_OPT_UINT64, &invmask, 's', MDB_OPT_UINTPTR, &size, 'v', MDB_OPT_SETBITS, B_TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (invmask != 0) args++; if (mask != KGREP_FULL_MASK) args++; if (dist != 0) args++; if (args > 1) { mdb_warn("only one of -d, -m and -M may be specified\n"); return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (invmask != 0) mask = ~invmask; if (pattern & ~mask) mdb_warn("warning: pattern does not match mask\n"); if (size > sizeof (uintmax_t)) { mdb_warn("sizes greater than %d not supported\n", sizeof (uintmax_t)); return (DCMD_ERR); } if (size == 0 || (size & (size - 1)) != 0) { mdb_warn("size must be a power of 2\n"); return (DCMD_ERR); } if (size == sizeof (uintmax_t)) size_mask = KGREP_FULL_MASK; else size_mask = (1ULL << (size * NBBY)) - 1ULL; if (pattern & ~size_mask) mdb_warn("warning: pattern %llx overflows requested size " "%d (max: %llx)\n", pattern, size, size_mask); if (dist > 0 && ((dist & ~size_mask) || size_mask + 1 - dist < pattern)) { mdb_warn("pattern %llx + distance %llx overflows size\n" "%d (max: %llx)\n", pattern, dist, size, size_mask); return (DCMD_ERR); } /* * All arguments have now been validated. */ (void) memset(&kg, '\0', sizeof (kg)); kg.kg_page = mdb_alloc(pagesize, UM_SLEEP | UM_GC); kg.kg_pagesize = pagesize; kg.kg_pattern = pattern; kg.kg_mask = mask; kg.kg_dist = dist; kg.kg_minaddr = minaddr; kg.kg_maxaddr = maxaddr; if (flags & DCMD_PIPE_OUT) { verbose = 0; kg.kg_cbtype = KG_PIPE; } else if (verbose) { kg.kg_cbtype = KG_VERBOSE; } else { kg.kg_cbtype = KG_BASE; } /* * kgrep_range_basic handles the common case (no arguments) * with dispatch. */ if (size == sizeof (uintptr_t) && !verbose && mask == KGREP_FULL_MASK && dist == 0 && minaddr == 0 && maxaddr == 0) func = kgrep_range_basic; else { switch (size) { case 1: func = kgrep_range_fancy8; break; case 2: func = kgrep_range_fancy16; break; case 4: func = kgrep_range_fancy32; break; case 8: func = kgrep_range_fancy64; break; default: mdb_warn("can't happen: non-recognized kgrep size\n"); return (DCMD_ERR); } } /* * Invoke the target, which should invoke func(start, end, &kg) for * every range [start, end) of vaddrs which might have backing. * Both start and end must be multiples of kgrep_subr_pagesize(). */ ret = kgrep_subr(func, &kg); if (ret == DCMD_OK && !kg.kg_seen) mdb_warn("warning: nothing searched\n"); return (ret); } /* * 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. */ #ifndef _KGREP_H #define _KGREP_H #include #ifdef __cplusplus extern "C" { #endif typedef int kgrep_cb_func(uintptr_t, uintptr_t, void *); #define KGREP_USAGE \ ":[-v] [-d dist|-m mask|-M invmask] [-a minad] [-A maxad] [-s sz]" extern int kgrep(uintptr_t, uint_t, int, const mdb_arg_t *); extern void kgrep_help(void); extern int kgrep_subr(kgrep_cb_func *, void *); extern size_t kgrep_subr_pagesize(void); #ifdef __cplusplus } #endif #endif /* _KGREP_H */ /* * 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 2018 Joyent, Inc. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "avl.h" #include "combined.h" #include "dist.h" #include "kmem.h" #include "list.h" #define dprintf(x) if (mdb_debug_level) { \ mdb_printf("kmem debug: "); \ /*CSTYLED*/\ mdb_printf x ;\ } #define KM_ALLOCATED 0x01 #define KM_FREE 0x02 #define KM_BUFCTL 0x04 #define KM_CONSTRUCTED 0x08 /* only constructed free buffers */ #define KM_HASH 0x10 static int mdb_debug_level = 0; /*ARGSUSED*/ static int kmem_init_walkers(uintptr_t addr, const kmem_cache_t *c, void *ignored) { mdb_walker_t w; char descr[64]; (void) mdb_snprintf(descr, sizeof (descr), "walk the %s cache", c->cache_name); w.walk_name = c->cache_name; w.walk_descr = descr; w.walk_init = kmem_walk_init; w.walk_step = kmem_walk_step; w.walk_fini = kmem_walk_fini; w.walk_init_arg = (void *)addr; if (mdb_add_walker(&w) == -1) mdb_warn("failed to add %s walker", c->cache_name); return (WALK_NEXT); } /*ARGSUSED*/ int kmem_debug(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_debug_level ^= 1; mdb_printf("kmem: debugging is now %s\n", mdb_debug_level ? "on" : "off"); return (DCMD_OK); } int kmem_cache_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; if (mdb_lookup_by_name("kmem_caches", &sym) == -1) { mdb_warn("couldn't find kmem_caches"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sym.st_value; return (list_walk_init_named(wsp, "cache list", "cache")); } int kmem_cpu_cache_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("kmem_cpu_cache doesn't support global walks"); return (WALK_ERR); } if (mdb_layered_walk("cpu", wsp) == -1) { mdb_warn("couldn't walk 'cpu'"); return (WALK_ERR); } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int kmem_cpu_cache_walk_step(mdb_walk_state_t *wsp) { uintptr_t caddr = (uintptr_t)wsp->walk_data; const cpu_t *cpu = wsp->walk_layer; kmem_cpu_cache_t cc; caddr += OFFSETOF(kmem_cache_t, cache_cpu[cpu->cpu_seqid]); if (mdb_vread(&cc, sizeof (kmem_cpu_cache_t), caddr) == -1) { mdb_warn("couldn't read kmem_cpu_cache at %p", caddr); return (WALK_ERR); } return (wsp->walk_callback(caddr, &cc, wsp->walk_cbdata)); } static int kmem_slab_check(void *p, uintptr_t saddr, void *arg) { kmem_slab_t *sp = p; uintptr_t caddr = (uintptr_t)arg; if ((uintptr_t)sp->slab_cache != caddr) { mdb_warn("slab %p isn't in cache %p (in cache %p)\n", saddr, caddr, sp->slab_cache); return (-1); } return (0); } static int kmem_partial_slab_check(void *p, uintptr_t saddr, void *arg) { kmem_slab_t *sp = p; int rc = kmem_slab_check(p, saddr, arg); if (rc != 0) { return (rc); } if (!KMEM_SLAB_IS_PARTIAL(sp)) { mdb_warn("slab %p is not a partial slab\n", saddr); return (-1); } return (0); } static int kmem_complete_slab_check(void *p, uintptr_t saddr, void *arg) { kmem_slab_t *sp = p; int rc = kmem_slab_check(p, saddr, arg); if (rc != 0) { return (rc); } if (!KMEM_SLAB_IS_ALL_USED(sp)) { mdb_warn("slab %p is not completely allocated\n", saddr); return (-1); } return (0); } typedef struct { uintptr_t kns_cache_addr; int kns_nslabs; } kmem_nth_slab_t; static int kmem_nth_slab_check(void *p, uintptr_t saddr, void *arg) { kmem_nth_slab_t *chkp = arg; int rc = kmem_slab_check(p, saddr, (void *)chkp->kns_cache_addr); if (rc != 0) { return (rc); } return (chkp->kns_nslabs-- == 0 ? 1 : 0); } static int kmem_complete_slab_walk_init(mdb_walk_state_t *wsp) { uintptr_t caddr = wsp->walk_addr; wsp->walk_addr = (uintptr_t)(caddr + offsetof(kmem_cache_t, cache_complete_slabs)); return (list_walk_init_checked(wsp, "slab list", "slab", kmem_complete_slab_check, (void *)caddr)); } static int kmem_partial_slab_walk_init(mdb_walk_state_t *wsp) { uintptr_t caddr = wsp->walk_addr; wsp->walk_addr = (uintptr_t)(caddr + offsetof(kmem_cache_t, cache_partial_slabs)); return (avl_walk_init_checked(wsp, "slab list", "slab", kmem_partial_slab_check, (void *)caddr)); } int kmem_slab_walk_init(mdb_walk_state_t *wsp) { uintptr_t caddr = wsp->walk_addr; if (caddr == 0) { mdb_warn("kmem_slab doesn't support global walks\n"); return (WALK_ERR); } combined_walk_init(wsp); combined_walk_add(wsp, kmem_complete_slab_walk_init, list_walk_step, list_walk_fini); combined_walk_add(wsp, kmem_partial_slab_walk_init, avl_walk_step, avl_walk_fini); return (WALK_NEXT); } static int kmem_first_complete_slab_walk_init(mdb_walk_state_t *wsp) { uintptr_t caddr = wsp->walk_addr; kmem_nth_slab_t *chk; chk = mdb_alloc(sizeof (kmem_nth_slab_t), UM_SLEEP | UM_GC); chk->kns_cache_addr = caddr; chk->kns_nslabs = 1; wsp->walk_addr = (uintptr_t)(caddr + offsetof(kmem_cache_t, cache_complete_slabs)); return (list_walk_init_checked(wsp, "slab list", "slab", kmem_nth_slab_check, chk)); } int kmem_slab_walk_partial_init(mdb_walk_state_t *wsp) { uintptr_t caddr = wsp->walk_addr; kmem_cache_t c; if (caddr == 0) { mdb_warn("kmem_slab_partial doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&c, sizeof (c), caddr) == -1) { mdb_warn("couldn't read kmem_cache at %p", caddr); return (WALK_ERR); } combined_walk_init(wsp); /* * Some consumers (umem_walk_step(), in particular) require at * least one callback if there are any buffers in the cache. So * if there are *no* partial slabs, report the first full slab, if * any. * * Yes, this is ugly, but it's cleaner than the other possibilities. */ if (c.cache_partial_slabs.avl_numnodes == 0) { combined_walk_add(wsp, kmem_first_complete_slab_walk_init, list_walk_step, list_walk_fini); } else { combined_walk_add(wsp, kmem_partial_slab_walk_init, avl_walk_step, avl_walk_fini); } return (WALK_NEXT); } int kmem_cache(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *argv) { kmem_cache_t c; const char *filter = NULL; if (mdb_getopts(ac, argv, 'n', MDB_OPT_STR, &filter, NULL) != ac) { return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("kmem_cache", "kmem_cache", ac, argv) == -1) { mdb_warn("can't walk kmem_cache"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%-?s %-25s %4s %6s %8s %8s\n", "ADDR", "NAME", "FLAG", "CFLAG", "BUFSIZE", "BUFTOTL"); if (mdb_vread(&c, sizeof (c), addr) == -1) { mdb_warn("couldn't read kmem_cache at %p", addr); return (DCMD_ERR); } if ((filter != NULL) && (strstr(c.cache_name, filter) == NULL)) return (DCMD_OK); mdb_printf("%0?p %-25s %04x %06x %8ld %8lld\n", addr, c.cache_name, c.cache_flags, c.cache_cflags, c.cache_bufsize, c.cache_buftotal); return (DCMD_OK); } void kmem_cache_help(void) { mdb_printf("%s", "Print kernel memory caches.\n\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -n name\n" " name of kmem cache (or matching partial name)\n" "\n" "Column\tDescription\n" "\n" "ADDR\t\taddress of kmem cache\n" "NAME\t\tname of kmem cache\n" "FLAG\t\tvarious cache state flags\n" "CFLAG\t\tcache creation flags\n" "BUFSIZE\tobject size in bytes\n" "BUFTOTL\tcurrent total buffers in cache (allocated and free)\n"); } #define LABEL_WIDTH 11 static void kmem_slabs_print_dist(uint_t *ks_bucket, size_t buffers_per_slab, size_t maxbuckets, size_t minbucketsize) { uint64_t total; int buckets; int i; const int *distarray; int complete[2]; buckets = buffers_per_slab; total = 0; for (i = 0; i <= buffers_per_slab; i++) total += ks_bucket[i]; if (maxbuckets > 1) buckets = MIN(buckets, maxbuckets); if (minbucketsize > 1) { /* * minbucketsize does not apply to the first bucket reserved * for completely allocated slabs */ buckets = MIN(buckets, 1 + ((buffers_per_slab - 1) / minbucketsize)); if ((buckets < 2) && (buffers_per_slab > 1)) { buckets = 2; minbucketsize = (buffers_per_slab - 1); } } /* * The first printed bucket is reserved for completely allocated slabs. * Passing (buckets - 1) excludes that bucket from the generated * distribution, since we're handling it as a special case. */ complete[0] = buffers_per_slab; complete[1] = buffers_per_slab + 1; distarray = dist_linear(buckets - 1, 1, buffers_per_slab - 1); mdb_printf("%*s\n", LABEL_WIDTH, "Allocated"); dist_print_header("Buffers", LABEL_WIDTH, "Slabs"); dist_print_bucket(complete, 0, ks_bucket, total, LABEL_WIDTH); /* * Print bucket ranges in descending order after the first bucket for * completely allocated slabs, so a person can see immediately whether * or not there is fragmentation without having to scan possibly * multiple screens of output. Starting at (buckets - 2) excludes the * extra terminating bucket. */ for (i = buckets - 2; i >= 0; i--) { dist_print_bucket(distarray, i, ks_bucket, total, LABEL_WIDTH); } mdb_printf("\n"); } #undef LABEL_WIDTH /*ARGSUSED*/ static int kmem_first_slab(uintptr_t addr, const kmem_slab_t *sp, boolean_t *is_slab) { *is_slab = B_TRUE; return (WALK_DONE); } /*ARGSUSED*/ static int kmem_first_partial_slab(uintptr_t addr, const kmem_slab_t *sp, boolean_t *is_slab) { /* * The "kmem_partial_slab" walker reports the first full slab if there * are no partial slabs (for the sake of consumers that require at least * one callback if there are any buffers in the cache). */ *is_slab = KMEM_SLAB_IS_PARTIAL(sp); return (WALK_DONE); } typedef struct kmem_slab_usage { int ksu_refcnt; /* count of allocated buffers on slab */ boolean_t ksu_nomove; /* slab marked non-reclaimable */ } kmem_slab_usage_t; typedef struct kmem_slab_stats { const kmem_cache_t *ks_cp; int ks_slabs; /* slabs in cache */ int ks_partial_slabs; /* partially allocated slabs in cache */ uint64_t ks_unused_buffers; /* total unused buffers in cache */ int ks_max_buffers_per_slab; /* max buffers per slab */ int ks_usage_len; /* ks_usage array length */ kmem_slab_usage_t *ks_usage; /* partial slab usage */ uint_t *ks_bucket; /* slab usage distribution */ } kmem_slab_stats_t; /*ARGSUSED*/ static int kmem_slablist_stat(uintptr_t addr, const kmem_slab_t *sp, kmem_slab_stats_t *ks) { kmem_slab_usage_t *ksu; long unused; ks->ks_slabs++; ks->ks_bucket[sp->slab_refcnt]++; unused = (sp->slab_chunks - sp->slab_refcnt); if (unused == 0) { return (WALK_NEXT); } ks->ks_partial_slabs++; ks->ks_unused_buffers += unused; if (ks->ks_partial_slabs > ks->ks_usage_len) { kmem_slab_usage_t *usage; int len = ks->ks_usage_len; len = (len == 0 ? 16 : len * 2); usage = mdb_zalloc(len * sizeof (kmem_slab_usage_t), UM_SLEEP); if (ks->ks_usage != NULL) { bcopy(ks->ks_usage, usage, ks->ks_usage_len * sizeof (kmem_slab_usage_t)); mdb_free(ks->ks_usage, ks->ks_usage_len * sizeof (kmem_slab_usage_t)); } ks->ks_usage = usage; ks->ks_usage_len = len; } ksu = &ks->ks_usage[ks->ks_partial_slabs - 1]; ksu->ksu_refcnt = sp->slab_refcnt; ksu->ksu_nomove = (sp->slab_flags & KMEM_SLAB_NOMOVE); return (WALK_NEXT); } static void kmem_slabs_header() { mdb_printf("%-25s %8s %8s %9s %9s %6s\n", "", "", "Partial", "", "Unused", ""); mdb_printf("%-25s %8s %8s %9s %9s %6s\n", "Cache Name", "Slabs", "Slabs", "Buffers", "Buffers", "Waste"); mdb_printf("%-25s %8s %8s %9s %9s %6s\n", "-------------------------", "--------", "--------", "---------", "---------", "------"); } int kmem_slabs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kmem_cache_t c; kmem_slab_stats_t stats; mdb_walk_cb_t cb; int pct; int tenths_pct; size_t maxbuckets = 1; size_t minbucketsize = 0; const char *filter = NULL; const char *name = NULL; uint_t opt_v = FALSE; boolean_t buckets = B_FALSE; boolean_t skip = B_FALSE; if (mdb_getopts(argc, argv, 'B', MDB_OPT_UINTPTR, &minbucketsize, 'b', MDB_OPT_UINTPTR, &maxbuckets, 'n', MDB_OPT_STR, &filter, 'N', MDB_OPT_STR, &name, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) { return (DCMD_USAGE); } if ((maxbuckets != 1) || (minbucketsize != 0)) { buckets = B_TRUE; } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("kmem_cache", "kmem_slabs", argc, argv) == -1) { mdb_warn("can't walk kmem_cache"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&c, sizeof (c), addr) == -1) { mdb_warn("couldn't read kmem_cache at %p", addr); return (DCMD_ERR); } if (name == NULL) { skip = ((filter != NULL) && (strstr(c.cache_name, filter) == NULL)); } else if (filter == NULL) { skip = (strcmp(c.cache_name, name) != 0); } else { /* match either -n or -N */ skip = ((strcmp(c.cache_name, name) != 0) && (strstr(c.cache_name, filter) == NULL)); } if (!(opt_v || buckets) && DCMD_HDRSPEC(flags)) { kmem_slabs_header(); } else if ((opt_v || buckets) && !skip) { if (DCMD_HDRSPEC(flags)) { kmem_slabs_header(); } else { boolean_t is_slab = B_FALSE; const char *walker_name; if (opt_v) { cb = (mdb_walk_cb_t)kmem_first_partial_slab; walker_name = "kmem_slab_partial"; } else { cb = (mdb_walk_cb_t)kmem_first_slab; walker_name = "kmem_slab"; } (void) mdb_pwalk(walker_name, cb, &is_slab, addr); if (is_slab) { kmem_slabs_header(); } } } if (skip) { return (DCMD_OK); } bzero(&stats, sizeof (kmem_slab_stats_t)); stats.ks_cp = &c; stats.ks_max_buffers_per_slab = c.cache_maxchunks; /* +1 to include a zero bucket */ stats.ks_bucket = mdb_zalloc((stats.ks_max_buffers_per_slab + 1) * sizeof (*stats.ks_bucket), UM_SLEEP); cb = (mdb_walk_cb_t)kmem_slablist_stat; (void) mdb_pwalk("kmem_slab", cb, &stats, addr); if (c.cache_buftotal == 0) { pct = 0; tenths_pct = 0; } else { uint64_t n = stats.ks_unused_buffers * 10000; pct = (int)(n / c.cache_buftotal); tenths_pct = pct - ((pct / 100) * 100); tenths_pct = (tenths_pct + 5) / 10; /* round nearest tenth */ if (tenths_pct == 10) { pct += 100; tenths_pct = 0; } } pct /= 100; mdb_printf("%-25s %8d %8d %9lld %9lld %3d.%1d%%\n", c.cache_name, stats.ks_slabs, stats.ks_partial_slabs, c.cache_buftotal, stats.ks_unused_buffers, pct, tenths_pct); if (maxbuckets == 0) { maxbuckets = stats.ks_max_buffers_per_slab; } if (((maxbuckets > 1) || (minbucketsize > 0)) && (stats.ks_slabs > 0)) { mdb_printf("\n"); kmem_slabs_print_dist(stats.ks_bucket, stats.ks_max_buffers_per_slab, maxbuckets, minbucketsize); } mdb_free(stats.ks_bucket, (stats.ks_max_buffers_per_slab + 1) * sizeof (*stats.ks_bucket)); if (!opt_v) { return (DCMD_OK); } if (opt_v && (stats.ks_partial_slabs > 0)) { int i; kmem_slab_usage_t *ksu; mdb_printf(" %d complete (%d), %d partial:", (stats.ks_slabs - stats.ks_partial_slabs), stats.ks_max_buffers_per_slab, stats.ks_partial_slabs); for (i = 0; i < stats.ks_partial_slabs; i++) { ksu = &stats.ks_usage[i]; mdb_printf(" %d%s", ksu->ksu_refcnt, (ksu->ksu_nomove ? "*" : "")); } mdb_printf("\n\n"); } if (stats.ks_usage_len > 0) { mdb_free(stats.ks_usage, stats.ks_usage_len * sizeof (kmem_slab_usage_t)); } return (DCMD_OK); } void kmem_slabs_help(void) { mdb_printf("%s", "Display slab usage per kmem cache.\n\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -n name\n" " name of kmem cache (or matching partial name)\n" " -N name\n" " exact name of kmem cache\n" " -b maxbins\n" " Print a distribution of allocated buffers per slab using at\n" " most maxbins bins. The first bin is reserved for completely\n" " allocated slabs. Setting maxbins to zero (-b 0) has the same\n" " effect as specifying the maximum allocated buffers per slab\n" " or setting minbinsize to 1 (-B 1).\n" " -B minbinsize\n" " Print a distribution of allocated buffers per slab, making\n" " all bins (except the first, reserved for completely allocated\n" " slabs) at least minbinsize buffers apart.\n" " -v verbose output: List the allocated buffer count of each partial\n" " slab on the free list in order from front to back to show how\n" " closely the slabs are ordered by usage. For example\n" "\n" " 10 complete, 3 partial (8): 7 3 1\n" "\n" " means there are thirteen slabs with eight buffers each, including\n" " three partially allocated slabs with less than all eight buffers\n" " allocated.\n" "\n" " Buffer allocations are always from the front of the partial slab\n" " list. When a buffer is freed from a completely used slab, that\n" " slab is added to the front of the partial slab list. Assuming\n" " that all buffers are equally likely to be freed soon, the\n" " desired order of partial slabs is most-used at the front of the\n" " list and least-used at the back (as in the example above).\n" " However, if a slab contains an allocated buffer that will not\n" " soon be freed, it would be better for that slab to be at the\n" " front where all of its buffers can be allocated. Taking a slab\n" " off the partial slab list (either with all buffers freed or all\n" " buffers allocated) reduces cache fragmentation.\n" "\n" " A slab's allocated buffer count representing a partial slab (9 in\n" " the example below) may be marked as follows:\n" "\n" " 9* An asterisk indicates that kmem has marked the slab non-\n" " reclaimable because the kmem client refused to move one of the\n" " slab's buffers. Since kmem does not expect to completely free the\n" " slab, it moves it to the front of the list in the hope of\n" " completely allocating it instead. A slab marked with an asterisk\n" " stays marked for as long as it remains on the partial slab list.\n" "\n" "Column\t\tDescription\n" "\n" "Cache Name\t\tname of kmem cache\n" "Slabs\t\t\ttotal slab count\n" "Partial Slabs\t\tcount of partially allocated slabs on the free list\n" "Buffers\t\ttotal buffer count (Slabs * (buffers per slab))\n" "Unused Buffers\tcount of unallocated buffers across all partial slabs\n" "Waste\t\t\t(Unused Buffers / Buffers) does not include space\n" "\t\t\t for accounting structures (debug mode), slab\n" "\t\t\t coloring (incremental small offsets to stagger\n" "\t\t\t buffer alignment), or the per-CPU magazine layer\n"); } static int addrcmp(const void *lhs, const void *rhs) { uintptr_t p1 = *((uintptr_t *)lhs); uintptr_t p2 = *((uintptr_t *)rhs); if (p1 < p2) return (-1); if (p1 > p2) return (1); return (0); } static int bufctlcmp(const kmem_bufctl_audit_t **lhs, const kmem_bufctl_audit_t **rhs) { const kmem_bufctl_audit_t *bcp1 = *lhs; const kmem_bufctl_audit_t *bcp2 = *rhs; if (bcp1->bc_timestamp > bcp2->bc_timestamp) return (-1); if (bcp1->bc_timestamp < bcp2->bc_timestamp) return (1); return (0); } typedef struct kmem_hash_walk { uintptr_t *kmhw_table; size_t kmhw_nelems; size_t kmhw_pos; kmem_bufctl_t kmhw_cur; } kmem_hash_walk_t; int kmem_hash_walk_init(mdb_walk_state_t *wsp) { kmem_hash_walk_t *kmhw; uintptr_t *hash; kmem_cache_t c; uintptr_t haddr, addr = wsp->walk_addr; size_t nelems; size_t hsize; if (addr == 0) { mdb_warn("kmem_hash doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&c, sizeof (c), addr) == -1) { mdb_warn("couldn't read cache at addr %p", addr); return (WALK_ERR); } if (!(c.cache_flags & KMF_HASH)) { mdb_warn("cache %p doesn't have a hash table\n", addr); return (WALK_DONE); /* nothing to do */ } kmhw = mdb_zalloc(sizeof (kmem_hash_walk_t), UM_SLEEP); kmhw->kmhw_cur.bc_next = NULL; kmhw->kmhw_pos = 0; kmhw->kmhw_nelems = nelems = c.cache_hash_mask + 1; hsize = nelems * sizeof (uintptr_t); haddr = (uintptr_t)c.cache_hash_table; kmhw->kmhw_table = hash = mdb_alloc(hsize, UM_SLEEP); if (mdb_vread(hash, hsize, haddr) == -1) { mdb_warn("failed to read hash table at %p", haddr); mdb_free(hash, hsize); mdb_free(kmhw, sizeof (kmem_hash_walk_t)); return (WALK_ERR); } wsp->walk_data = kmhw; return (WALK_NEXT); } int kmem_hash_walk_step(mdb_walk_state_t *wsp) { kmem_hash_walk_t *kmhw = wsp->walk_data; uintptr_t addr = 0; if ((addr = (uintptr_t)kmhw->kmhw_cur.bc_next) == 0) { while (kmhw->kmhw_pos < kmhw->kmhw_nelems) { if ((addr = kmhw->kmhw_table[kmhw->kmhw_pos++]) != 0) break; } } if (addr == 0) return (WALK_DONE); if (mdb_vread(&kmhw->kmhw_cur, sizeof (kmem_bufctl_t), addr) == -1) { mdb_warn("couldn't read kmem_bufctl_t at addr %p", addr); return (WALK_ERR); } return (wsp->walk_callback(addr, &kmhw->kmhw_cur, wsp->walk_cbdata)); } void kmem_hash_walk_fini(mdb_walk_state_t *wsp) { kmem_hash_walk_t *kmhw = wsp->walk_data; if (kmhw == NULL) return; mdb_free(kmhw->kmhw_table, kmhw->kmhw_nelems * sizeof (uintptr_t)); mdb_free(kmhw, sizeof (kmem_hash_walk_t)); } /* * Find the address of the bufctl structure for the address 'buf' in cache * 'cp', which is at address caddr, and place it in *out. */ static int kmem_hash_lookup(kmem_cache_t *cp, uintptr_t caddr, void *buf, uintptr_t *out) { uintptr_t bucket = (uintptr_t)KMEM_HASH(cp, buf); kmem_bufctl_t *bcp; kmem_bufctl_t bc; if (mdb_vread(&bcp, sizeof (kmem_bufctl_t *), bucket) == -1) { mdb_warn("unable to read hash bucket for %p in cache %p", buf, caddr); return (-1); } while (bcp != NULL) { if (mdb_vread(&bc, sizeof (kmem_bufctl_t), (uintptr_t)bcp) == -1) { mdb_warn("unable to read bufctl at %p", bcp); return (-1); } if (bc.bc_addr == buf) { *out = (uintptr_t)bcp; return (0); } bcp = bc.bc_next; } mdb_warn("unable to find bufctl for %p in cache %p\n", buf, caddr); return (-1); } int kmem_get_magsize(const kmem_cache_t *cp) { uintptr_t addr = (uintptr_t)cp->cache_magtype; GElf_Sym mt_sym; kmem_magtype_t mt; int res; /* * if cpu 0 has a non-zero magsize, it must be correct. caches * with KMF_NOMAGAZINE have disabled their magazine layers, so * it is okay to return 0 for them. */ if ((res = cp->cache_cpu[0].cc_magsize) != 0 || (cp->cache_flags & KMF_NOMAGAZINE)) return (res); if (mdb_lookup_by_name("kmem_magtype", &mt_sym) == -1) { mdb_warn("unable to read 'kmem_magtype'"); } else if (addr < mt_sym.st_value || addr + sizeof (mt) - 1 > mt_sym.st_value + mt_sym.st_size - 1 || ((addr - mt_sym.st_value) % sizeof (mt)) != 0) { mdb_warn("cache '%s' has invalid magtype pointer (%p)\n", cp->cache_name, addr); return (0); } if (mdb_vread(&mt, sizeof (mt), addr) == -1) { mdb_warn("unable to read magtype at %a", addr); return (0); } return (mt.mt_magsize); } /*ARGSUSED*/ static int kmem_estimate_slab(uintptr_t addr, const kmem_slab_t *sp, size_t *est) { *est -= (sp->slab_chunks - sp->slab_refcnt); return (WALK_NEXT); } /* * Returns an upper bound on the number of allocated buffers in a given * cache. */ size_t kmem_estimate_allocated(uintptr_t addr, const kmem_cache_t *cp) { int magsize; size_t cache_est; cache_est = cp->cache_buftotal; (void) mdb_pwalk("kmem_slab_partial", (mdb_walk_cb_t)kmem_estimate_slab, &cache_est, addr); if ((magsize = kmem_get_magsize(cp)) != 0) { size_t mag_est = cp->cache_full.ml_total * magsize; if (cache_est >= mag_est) { cache_est -= mag_est; } else { mdb_warn("cache %p's magazine layer holds more buffers " "than the slab layer.\n", addr); } } return (cache_est); } #define READMAG_ROUNDS(rounds) { \ if (mdb_vread(mp, magbsize, (uintptr_t)kmp) == -1) { \ mdb_warn("couldn't read magazine at %p", kmp); \ goto fail; \ } \ for (i = 0; i < rounds; i++) { \ maglist[magcnt++] = mp->mag_round[i]; \ if (magcnt == magmax) { \ mdb_warn("%d magazines exceeds fudge factor\n", \ magcnt); \ goto fail; \ } \ } \ } int kmem_read_magazines(kmem_cache_t *cp, uintptr_t addr, int ncpus, void ***maglistp, size_t *magcntp, size_t *magmaxp, int alloc_flags) { kmem_magazine_t *kmp, *mp; void **maglist = NULL; int i, cpu; size_t magsize, magmax, magbsize; size_t magcnt = 0; /* * Read the magtype out of the cache, after verifying the pointer's * correctness. */ magsize = kmem_get_magsize(cp); if (magsize == 0) { *maglistp = NULL; *magcntp = 0; *magmaxp = 0; return (WALK_NEXT); } /* * There are several places where we need to go buffer hunting: * the per-CPU loaded magazine, the per-CPU spare full magazine, * and the full magazine list in the depot. * * For an upper bound on the number of buffers in the magazine * layer, we have the number of magazines on the cache_full * list plus at most two magazines per CPU (the loaded and the * spare). Toss in 100 magazines as a fudge factor in case this * is live (the number "100" comes from the same fudge factor in * crash(8)). */ magmax = (cp->cache_full.ml_total + 2 * ncpus + 100) * magsize; magbsize = offsetof(kmem_magazine_t, mag_round[magsize]); if (magbsize >= PAGESIZE / 2) { mdb_warn("magazine size for cache %p unreasonable (%x)\n", addr, magbsize); return (WALK_ERR); } maglist = mdb_alloc(magmax * sizeof (void *), alloc_flags); mp = mdb_alloc(magbsize, alloc_flags); if (mp == NULL || maglist == NULL) goto fail; /* * First up: the magazines in the depot (i.e. on the cache_full list). */ for (kmp = cp->cache_full.ml_list; kmp != NULL; ) { READMAG_ROUNDS(magsize); kmp = mp->mag_next; if (kmp == cp->cache_full.ml_list) break; /* cache_full list loop detected */ } dprintf(("cache_full list done\n")); /* * Now whip through the CPUs, snagging the loaded magazines * and full spares. * * In order to prevent inconsistent dumps, rounds and prounds * are copied aside before dumping begins. */ for (cpu = 0; cpu < ncpus; cpu++) { kmem_cpu_cache_t *ccp = &cp->cache_cpu[cpu]; short rounds, prounds; if (KMEM_DUMPCC(ccp)) { rounds = ccp->cc_dump_rounds; prounds = ccp->cc_dump_prounds; } else { rounds = ccp->cc_rounds; prounds = ccp->cc_prounds; } dprintf(("reading cpu cache %p\n", (uintptr_t)ccp - (uintptr_t)cp + addr)); if (rounds > 0 && (kmp = ccp->cc_loaded) != NULL) { dprintf(("reading %d loaded rounds\n", rounds)); READMAG_ROUNDS(rounds); } if (prounds > 0 && (kmp = ccp->cc_ploaded) != NULL) { dprintf(("reading %d previously loaded rounds\n", prounds)); READMAG_ROUNDS(prounds); } } dprintf(("magazine layer: %d buffers\n", magcnt)); if (!(alloc_flags & UM_GC)) mdb_free(mp, magbsize); *maglistp = maglist; *magcntp = magcnt; *magmaxp = magmax; return (WALK_NEXT); fail: if (!(alloc_flags & UM_GC)) { if (mp) mdb_free(mp, magbsize); if (maglist) mdb_free(maglist, magmax * sizeof (void *)); } return (WALK_ERR); } static int kmem_walk_callback(mdb_walk_state_t *wsp, uintptr_t buf) { return (wsp->walk_callback(buf, NULL, wsp->walk_cbdata)); } static int bufctl_walk_callback(kmem_cache_t *cp, mdb_walk_state_t *wsp, uintptr_t buf) { kmem_bufctl_audit_t b; /* * if KMF_AUDIT is not set, we know that we're looking at a * kmem_bufctl_t. */ if (!(cp->cache_flags & KMF_AUDIT) || mdb_vread(&b, sizeof (kmem_bufctl_audit_t), buf) == -1) { (void) memset(&b, 0, sizeof (b)); if (mdb_vread(&b, sizeof (kmem_bufctl_t), buf) == -1) { mdb_warn("unable to read bufctl at %p", buf); return (WALK_ERR); } } return (wsp->walk_callback(buf, &b, wsp->walk_cbdata)); } typedef struct kmem_walk { int kmw_type; uintptr_t kmw_addr; /* cache address */ kmem_cache_t *kmw_cp; size_t kmw_csize; /* * magazine layer */ void **kmw_maglist; size_t kmw_max; size_t kmw_count; size_t kmw_pos; /* * slab layer */ char *kmw_valid; /* to keep track of freed buffers */ char *kmw_ubase; /* buffer for slab data */ } kmem_walk_t; static int kmem_walk_init_common(mdb_walk_state_t *wsp, int type) { kmem_walk_t *kmw; int ncpus, csize; kmem_cache_t *cp; size_t vm_quantum; size_t magmax, magcnt; void **maglist = NULL; uint_t chunksize = 1, slabsize = 1; int status = WALK_ERR; uintptr_t addr = wsp->walk_addr; const char *layered; type &= ~KM_HASH; if (addr == 0) { mdb_warn("kmem walk doesn't support global walks\n"); return (WALK_ERR); } dprintf(("walking %p\n", addr)); /* * First we need to figure out how many CPUs are configured in the * system to know how much to slurp out. */ mdb_readvar(&ncpus, "max_ncpus"); csize = KMEM_CACHE_SIZE(ncpus); cp = mdb_alloc(csize, UM_SLEEP); if (mdb_vread(cp, csize, addr) == -1) { mdb_warn("couldn't read cache at addr %p", addr); goto out2; } /* * It's easy for someone to hand us an invalid cache address. * Unfortunately, it is hard for this walker to survive an * invalid cache cleanly. So we make sure that: * * 1. the vmem arena for the cache is readable, * 2. the vmem arena's quantum is a power of 2, * 3. our slabsize is a multiple of the quantum, and * 4. our chunksize is >0 and less than our slabsize. */ if (mdb_vread(&vm_quantum, sizeof (vm_quantum), (uintptr_t)&cp->cache_arena->vm_quantum) == -1 || vm_quantum == 0 || (vm_quantum & (vm_quantum - 1)) != 0 || cp->cache_slabsize < vm_quantum || P2PHASE(cp->cache_slabsize, vm_quantum) != 0 || cp->cache_chunksize == 0 || cp->cache_chunksize > cp->cache_slabsize) { mdb_warn("%p is not a valid kmem_cache_t\n", addr); goto out2; } dprintf(("buf total is %d\n", cp->cache_buftotal)); if (cp->cache_buftotal == 0) { mdb_free(cp, csize); return (WALK_DONE); } /* * If they ask for bufctls, but it's a small-slab cache, * there is nothing to report. */ if ((type & KM_BUFCTL) && !(cp->cache_flags & KMF_HASH)) { dprintf(("bufctl requested, not KMF_HASH (flags: %p)\n", cp->cache_flags)); mdb_free(cp, csize); return (WALK_DONE); } /* * If they want constructed buffers, but there's no constructor or * the cache has DEADBEEF checking enabled, there is nothing to report. */ if ((type & KM_CONSTRUCTED) && (!(type & KM_FREE) || cp->cache_constructor == NULL || (cp->cache_flags & (KMF_DEADBEEF | KMF_LITE)) == KMF_DEADBEEF)) { mdb_free(cp, csize); return (WALK_DONE); } /* * Read in the contents of the magazine layer */ if (kmem_read_magazines(cp, addr, ncpus, &maglist, &magcnt, &magmax, UM_SLEEP) == WALK_ERR) goto out2; /* * We have all of the buffers from the magazines; if we are walking * allocated buffers, sort them so we can bsearch them later. */ if (type & KM_ALLOCATED) qsort(maglist, magcnt, sizeof (void *), addrcmp); wsp->walk_data = kmw = mdb_zalloc(sizeof (kmem_walk_t), UM_SLEEP); kmw->kmw_type = type; kmw->kmw_addr = addr; kmw->kmw_cp = cp; kmw->kmw_csize = csize; kmw->kmw_maglist = maglist; kmw->kmw_max = magmax; kmw->kmw_count = magcnt; kmw->kmw_pos = 0; /* * When walking allocated buffers in a KMF_HASH cache, we walk the * hash table instead of the slab layer. */ if ((cp->cache_flags & KMF_HASH) && (type & KM_ALLOCATED)) { layered = "kmem_hash"; kmw->kmw_type |= KM_HASH; } else { /* * If we are walking freed buffers, we only need the * magazine layer plus the partially allocated slabs. * To walk allocated buffers, we need all of the slabs. */ if (type & KM_ALLOCATED) layered = "kmem_slab"; else layered = "kmem_slab_partial"; /* * for small-slab caches, we read in the entire slab. For * freed buffers, we can just walk the freelist. For * allocated buffers, we use a 'valid' array to track * the freed buffers. */ if (!(cp->cache_flags & KMF_HASH)) { chunksize = cp->cache_chunksize; slabsize = cp->cache_slabsize; kmw->kmw_ubase = mdb_alloc(slabsize + sizeof (kmem_bufctl_t), UM_SLEEP); if (type & KM_ALLOCATED) kmw->kmw_valid = mdb_alloc(slabsize / chunksize, UM_SLEEP); } } status = WALK_NEXT; if (mdb_layered_walk(layered, wsp) == -1) { mdb_warn("unable to start layered '%s' walk", layered); status = WALK_ERR; } if (status == WALK_ERR) { if (kmw->kmw_valid) mdb_free(kmw->kmw_valid, slabsize / chunksize); if (kmw->kmw_ubase) mdb_free(kmw->kmw_ubase, slabsize + sizeof (kmem_bufctl_t)); if (kmw->kmw_maglist) mdb_free(kmw->kmw_maglist, kmw->kmw_max * sizeof (uintptr_t)); mdb_free(kmw, sizeof (kmem_walk_t)); wsp->walk_data = NULL; } out2: if (status == WALK_ERR) mdb_free(cp, csize); return (status); } int kmem_walk_step(mdb_walk_state_t *wsp) { kmem_walk_t *kmw = wsp->walk_data; int type = kmw->kmw_type; kmem_cache_t *cp = kmw->kmw_cp; void **maglist = kmw->kmw_maglist; int magcnt = kmw->kmw_count; uintptr_t chunksize, slabsize; uintptr_t addr; const kmem_slab_t *sp; const kmem_bufctl_t *bcp; kmem_bufctl_t bc; int chunks; char *kbase; void *buf; int i, ret; char *valid, *ubase; /* * first, handle the 'kmem_hash' layered walk case */ if (type & KM_HASH) { /* * We have a buffer which has been allocated out of the * global layer. We need to make sure that it's not * actually sitting in a magazine before we report it as * an allocated buffer. */ buf = ((const kmem_bufctl_t *)wsp->walk_layer)->bc_addr; if (magcnt > 0 && bsearch(&buf, maglist, magcnt, sizeof (void *), addrcmp) != NULL) return (WALK_NEXT); if (type & KM_BUFCTL) return (bufctl_walk_callback(cp, wsp, wsp->walk_addr)); return (kmem_walk_callback(wsp, (uintptr_t)buf)); } ret = WALK_NEXT; addr = kmw->kmw_addr; /* * If we're walking freed buffers, report everything in the * magazine layer before processing the first slab. */ if ((type & KM_FREE) && magcnt != 0) { kmw->kmw_count = 0; /* only do this once */ for (i = 0; i < magcnt; i++) { buf = maglist[i]; if (type & KM_BUFCTL) { uintptr_t out; if (cp->cache_flags & KMF_BUFTAG) { kmem_buftag_t *btp; kmem_buftag_t tag; /* LINTED - alignment */ btp = KMEM_BUFTAG(cp, buf); if (mdb_vread(&tag, sizeof (tag), (uintptr_t)btp) == -1) { mdb_warn("reading buftag for " "%p at %p", buf, btp); continue; } out = (uintptr_t)tag.bt_bufctl; } else { if (kmem_hash_lookup(cp, addr, buf, &out) == -1) continue; } ret = bufctl_walk_callback(cp, wsp, out); } else { ret = kmem_walk_callback(wsp, (uintptr_t)buf); } if (ret != WALK_NEXT) return (ret); } } /* * If they want constructed buffers, we're finished, since the * magazine layer holds them all. */ if (type & KM_CONSTRUCTED) return (WALK_DONE); /* * Handle the buffers in the current slab */ chunksize = cp->cache_chunksize; slabsize = cp->cache_slabsize; sp = wsp->walk_layer; chunks = sp->slab_chunks; kbase = sp->slab_base; dprintf(("kbase is %p\n", kbase)); if (!(cp->cache_flags & KMF_HASH)) { valid = kmw->kmw_valid; ubase = kmw->kmw_ubase; if (mdb_vread(ubase, chunks * chunksize, (uintptr_t)kbase) == -1) { mdb_warn("failed to read slab contents at %p", kbase); return (WALK_ERR); } /* * Set up the valid map as fully allocated -- we'll punch * out the freelist. */ if (type & KM_ALLOCATED) (void) memset(valid, 1, chunks); } else { valid = NULL; ubase = NULL; } /* * walk the slab's freelist */ bcp = sp->slab_head; dprintf(("refcnt is %d; chunks is %d\n", sp->slab_refcnt, chunks)); /* * since we could be in the middle of allocating a buffer, * our refcnt could be one higher than it aught. So we * check one further on the freelist than the count allows. */ for (i = sp->slab_refcnt; i <= chunks; i++) { uint_t ndx; dprintf(("bcp is %p\n", bcp)); if (bcp == NULL) { if (i == chunks) break; mdb_warn( "slab %p in cache %p freelist too short by %d\n", sp, addr, chunks - i); break; } if (cp->cache_flags & KMF_HASH) { if (mdb_vread(&bc, sizeof (bc), (uintptr_t)bcp) == -1) { mdb_warn("failed to read bufctl ptr at %p", bcp); break; } buf = bc.bc_addr; } else { /* * Otherwise the buffer is (or should be) in the slab * that we've read in; determine its offset in the * slab, validate that it's not corrupt, and add to * our base address to find the umem_bufctl_t. (Note * that we don't need to add the size of the bufctl * to our offset calculation because of the slop that's * allocated for the buffer at ubase.) */ uintptr_t offs = (uintptr_t)bcp - (uintptr_t)kbase; if (offs > chunks * chunksize) { mdb_warn("found corrupt bufctl ptr %p" " in slab %p in cache %p\n", bcp, wsp->walk_addr, addr); break; } bc = *((kmem_bufctl_t *)((uintptr_t)ubase + offs)); buf = KMEM_BUF(cp, bcp); } ndx = ((uintptr_t)buf - (uintptr_t)kbase) / chunksize; if (ndx > slabsize / cp->cache_bufsize) { /* * This is very wrong; we have managed to find * a buffer in the slab which shouldn't * actually be here. Emit a warning, and * try to continue. */ mdb_warn("buf %p is out of range for " "slab %p, cache %p\n", buf, sp, addr); } else if (type & KM_ALLOCATED) { /* * we have found a buffer on the slab's freelist; * clear its entry */ valid[ndx] = 0; } else { /* * Report this freed buffer */ if (type & KM_BUFCTL) { ret = bufctl_walk_callback(cp, wsp, (uintptr_t)bcp); } else { ret = kmem_walk_callback(wsp, (uintptr_t)buf); } if (ret != WALK_NEXT) return (ret); } bcp = bc.bc_next; } if (bcp != NULL) { dprintf(("slab %p in cache %p freelist too long (%p)\n", sp, addr, bcp)); } /* * If we are walking freed buffers, the loop above handled reporting * them. */ if (type & KM_FREE) return (WALK_NEXT); if (type & KM_BUFCTL) { mdb_warn("impossible situation: small-slab KM_BUFCTL walk for " "cache %p\n", addr); return (WALK_ERR); } /* * Report allocated buffers, skipping buffers in the magazine layer. * We only get this far for small-slab caches. */ for (i = 0; ret == WALK_NEXT && i < chunks; i++) { buf = (char *)kbase + i * chunksize; if (!valid[i]) continue; /* on slab freelist */ if (magcnt > 0 && bsearch(&buf, maglist, magcnt, sizeof (void *), addrcmp) != NULL) continue; /* in magazine layer */ ret = kmem_walk_callback(wsp, (uintptr_t)buf); } return (ret); } void kmem_walk_fini(mdb_walk_state_t *wsp) { kmem_walk_t *kmw = wsp->walk_data; uintptr_t chunksize; uintptr_t slabsize; if (kmw == NULL) return; if (kmw->kmw_maglist != NULL) mdb_free(kmw->kmw_maglist, kmw->kmw_max * sizeof (void *)); chunksize = kmw->kmw_cp->cache_chunksize; slabsize = kmw->kmw_cp->cache_slabsize; if (kmw->kmw_valid != NULL) mdb_free(kmw->kmw_valid, slabsize / chunksize); if (kmw->kmw_ubase != NULL) mdb_free(kmw->kmw_ubase, slabsize + sizeof (kmem_bufctl_t)); mdb_free(kmw->kmw_cp, kmw->kmw_csize); mdb_free(kmw, sizeof (kmem_walk_t)); } /*ARGSUSED*/ static int kmem_walk_all(uintptr_t addr, const kmem_cache_t *c, mdb_walk_state_t *wsp) { /* * Buffers allocated from NOTOUCH caches can also show up as freed * memory in other caches. This can be a little confusing, so we * don't walk NOTOUCH caches when walking all caches (thereby assuring * that "::walk kmem" and "::walk freemem" yield disjoint output). */ if (c->cache_cflags & KMC_NOTOUCH) return (WALK_NEXT); if (mdb_pwalk(wsp->walk_data, wsp->walk_callback, wsp->walk_cbdata, addr) == -1) return (WALK_DONE); return (WALK_NEXT); } #define KMEM_WALK_ALL(name, wsp) { \ wsp->walk_data = (name); \ if (mdb_walk("kmem_cache", (mdb_walk_cb_t)kmem_walk_all, wsp) == -1) \ return (WALK_ERR); \ return (WALK_DONE); \ } int kmem_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_arg != NULL) wsp->walk_addr = (uintptr_t)wsp->walk_arg; if (wsp->walk_addr == 0) KMEM_WALK_ALL("kmem", wsp); return (kmem_walk_init_common(wsp, KM_ALLOCATED)); } int bufctl_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) KMEM_WALK_ALL("bufctl", wsp); return (kmem_walk_init_common(wsp, KM_ALLOCATED | KM_BUFCTL)); } int freemem_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) KMEM_WALK_ALL("freemem", wsp); return (kmem_walk_init_common(wsp, KM_FREE)); } int freemem_constructed_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) KMEM_WALK_ALL("freemem_constructed", wsp); return (kmem_walk_init_common(wsp, KM_FREE | KM_CONSTRUCTED)); } int freectl_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) KMEM_WALK_ALL("freectl", wsp); return (kmem_walk_init_common(wsp, KM_FREE | KM_BUFCTL)); } int freectl_constructed_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) KMEM_WALK_ALL("freectl_constructed", wsp); return (kmem_walk_init_common(wsp, KM_FREE | KM_BUFCTL | KM_CONSTRUCTED)); } typedef struct bufctl_history_walk { void *bhw_next; kmem_cache_t *bhw_cache; kmem_slab_t *bhw_slab; hrtime_t bhw_timestamp; } bufctl_history_walk_t; int bufctl_history_walk_init(mdb_walk_state_t *wsp) { bufctl_history_walk_t *bhw; kmem_bufctl_audit_t bc; kmem_bufctl_audit_t bcn; if (wsp->walk_addr == 0) { mdb_warn("bufctl_history walk doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&bc, sizeof (bc), wsp->walk_addr) == -1) { mdb_warn("unable to read bufctl at %p", wsp->walk_addr); return (WALK_ERR); } bhw = mdb_zalloc(sizeof (*bhw), UM_SLEEP); bhw->bhw_timestamp = 0; bhw->bhw_cache = bc.bc_cache; bhw->bhw_slab = bc.bc_slab; /* * sometimes the first log entry matches the base bufctl; in that * case, skip the base bufctl. */ if (bc.bc_lastlog != NULL && mdb_vread(&bcn, sizeof (bcn), (uintptr_t)bc.bc_lastlog) != -1 && bc.bc_addr == bcn.bc_addr && bc.bc_cache == bcn.bc_cache && bc.bc_slab == bcn.bc_slab && bc.bc_timestamp == bcn.bc_timestamp && bc.bc_thread == bcn.bc_thread) bhw->bhw_next = bc.bc_lastlog; else bhw->bhw_next = (void *)wsp->walk_addr; wsp->walk_addr = (uintptr_t)bc.bc_addr; wsp->walk_data = bhw; return (WALK_NEXT); } int bufctl_history_walk_step(mdb_walk_state_t *wsp) { bufctl_history_walk_t *bhw = wsp->walk_data; uintptr_t addr = (uintptr_t)bhw->bhw_next; uintptr_t baseaddr = wsp->walk_addr; kmem_bufctl_audit_t bc; if (addr == 0) return (WALK_DONE); if (mdb_vread(&bc, sizeof (bc), addr) == -1) { mdb_warn("unable to read bufctl at %p", bhw->bhw_next); return (WALK_ERR); } /* * The bufctl is only valid if the address, cache, and slab are * correct. We also check that the timestamp is decreasing, to * prevent infinite loops. */ if ((uintptr_t)bc.bc_addr != baseaddr || bc.bc_cache != bhw->bhw_cache || bc.bc_slab != bhw->bhw_slab || (bhw->bhw_timestamp != 0 && bc.bc_timestamp >= bhw->bhw_timestamp)) return (WALK_DONE); bhw->bhw_next = bc.bc_lastlog; bhw->bhw_timestamp = bc.bc_timestamp; return (wsp->walk_callback(addr, &bc, wsp->walk_cbdata)); } void bufctl_history_walk_fini(mdb_walk_state_t *wsp) { bufctl_history_walk_t *bhw = wsp->walk_data; mdb_free(bhw, sizeof (*bhw)); } typedef struct kmem_log_walk { kmem_bufctl_audit_t *klw_base; kmem_bufctl_audit_t **klw_sorted; kmem_log_header_t klw_lh; size_t klw_size; size_t klw_maxndx; size_t klw_ndx; } kmem_log_walk_t; int kmem_log_walk_init(mdb_walk_state_t *wsp) { uintptr_t lp = wsp->walk_addr; kmem_log_walk_t *klw; kmem_log_header_t *lhp; int maxndx, i, j, k; /* * By default (global walk), walk the kmem_transaction_log. Otherwise * read the log whose kmem_log_header_t is stored at walk_addr. */ if (lp == 0 && mdb_readvar(&lp, "kmem_transaction_log") == -1) { mdb_warn("failed to read 'kmem_transaction_log'"); return (WALK_ERR); } if (lp == 0) { mdb_warn("log is disabled\n"); return (WALK_ERR); } klw = mdb_zalloc(sizeof (kmem_log_walk_t), UM_SLEEP); lhp = &klw->klw_lh; if (mdb_vread(lhp, sizeof (kmem_log_header_t), lp) == -1) { mdb_warn("failed to read log header at %p", lp); mdb_free(klw, sizeof (kmem_log_walk_t)); return (WALK_ERR); } klw->klw_size = lhp->lh_chunksize * lhp->lh_nchunks; klw->klw_base = mdb_alloc(klw->klw_size, UM_SLEEP); maxndx = lhp->lh_chunksize / sizeof (kmem_bufctl_audit_t) - 1; if (mdb_vread(klw->klw_base, klw->klw_size, (uintptr_t)lhp->lh_base) == -1) { mdb_warn("failed to read log at base %p", lhp->lh_base); mdb_free(klw->klw_base, klw->klw_size); mdb_free(klw, sizeof (kmem_log_walk_t)); return (WALK_ERR); } klw->klw_sorted = mdb_alloc(maxndx * lhp->lh_nchunks * sizeof (kmem_bufctl_audit_t *), UM_SLEEP); for (i = 0, k = 0; i < lhp->lh_nchunks; i++) { kmem_bufctl_audit_t *chunk = (kmem_bufctl_audit_t *) ((uintptr_t)klw->klw_base + i * lhp->lh_chunksize); for (j = 0; j < maxndx; j++) klw->klw_sorted[k++] = &chunk[j]; } qsort(klw->klw_sorted, k, sizeof (kmem_bufctl_audit_t *), (int(*)(const void *, const void *))bufctlcmp); klw->klw_maxndx = k; wsp->walk_data = klw; return (WALK_NEXT); } int kmem_log_walk_step(mdb_walk_state_t *wsp) { kmem_log_walk_t *klw = wsp->walk_data; kmem_bufctl_audit_t *bcp; if (klw->klw_ndx == klw->klw_maxndx) return (WALK_DONE); bcp = klw->klw_sorted[klw->klw_ndx++]; return (wsp->walk_callback((uintptr_t)bcp - (uintptr_t)klw->klw_base + (uintptr_t)klw->klw_lh.lh_base, bcp, wsp->walk_cbdata)); } void kmem_log_walk_fini(mdb_walk_state_t *wsp) { kmem_log_walk_t *klw = wsp->walk_data; mdb_free(klw->klw_base, klw->klw_size); mdb_free(klw->klw_sorted, klw->klw_maxndx * sizeof (kmem_bufctl_audit_t *)); mdb_free(klw, sizeof (kmem_log_walk_t)); } typedef struct allocdby_bufctl { uintptr_t abb_addr; hrtime_t abb_ts; } allocdby_bufctl_t; typedef struct allocdby_walk { const char *abw_walk; uintptr_t abw_thread; size_t abw_nbufs; size_t abw_size; allocdby_bufctl_t *abw_buf; size_t abw_ndx; } allocdby_walk_t; int allocdby_walk_bufctl(uintptr_t addr, const kmem_bufctl_audit_t *bcp, allocdby_walk_t *abw) { if ((uintptr_t)bcp->bc_thread != abw->abw_thread) return (WALK_NEXT); if (abw->abw_nbufs == abw->abw_size) { allocdby_bufctl_t *buf; size_t oldsize = sizeof (allocdby_bufctl_t) * abw->abw_size; buf = mdb_zalloc(oldsize << 1, UM_SLEEP); bcopy(abw->abw_buf, buf, oldsize); mdb_free(abw->abw_buf, oldsize); abw->abw_size <<= 1; abw->abw_buf = buf; } abw->abw_buf[abw->abw_nbufs].abb_addr = addr; abw->abw_buf[abw->abw_nbufs].abb_ts = bcp->bc_timestamp; abw->abw_nbufs++; return (WALK_NEXT); } /*ARGSUSED*/ int allocdby_walk_cache(uintptr_t addr, const kmem_cache_t *c, allocdby_walk_t *abw) { if (mdb_pwalk(abw->abw_walk, (mdb_walk_cb_t)allocdby_walk_bufctl, abw, addr) == -1) { mdb_warn("couldn't walk bufctl for cache %p", addr); return (WALK_DONE); } return (WALK_NEXT); } static int allocdby_cmp(const allocdby_bufctl_t *lhs, const allocdby_bufctl_t *rhs) { if (lhs->abb_ts < rhs->abb_ts) return (1); if (lhs->abb_ts > rhs->abb_ts) return (-1); return (0); } static int allocdby_walk_init_common(mdb_walk_state_t *wsp, const char *walk) { allocdby_walk_t *abw; if (wsp->walk_addr == 0) { mdb_warn("allocdby walk doesn't support global walks\n"); return (WALK_ERR); } abw = mdb_zalloc(sizeof (allocdby_walk_t), UM_SLEEP); abw->abw_thread = wsp->walk_addr; abw->abw_walk = walk; abw->abw_size = 128; /* something reasonable */ abw->abw_buf = mdb_zalloc(abw->abw_size * sizeof (allocdby_bufctl_t), UM_SLEEP); wsp->walk_data = abw; if (mdb_walk("kmem_cache", (mdb_walk_cb_t)allocdby_walk_cache, abw) == -1) { mdb_warn("couldn't walk kmem_cache"); allocdby_walk_fini(wsp); return (WALK_ERR); } qsort(abw->abw_buf, abw->abw_nbufs, sizeof (allocdby_bufctl_t), (int(*)(const void *, const void *))allocdby_cmp); return (WALK_NEXT); } int allocdby_walk_init(mdb_walk_state_t *wsp) { return (allocdby_walk_init_common(wsp, "bufctl")); } int freedby_walk_init(mdb_walk_state_t *wsp) { return (allocdby_walk_init_common(wsp, "freectl")); } int allocdby_walk_step(mdb_walk_state_t *wsp) { allocdby_walk_t *abw = wsp->walk_data; kmem_bufctl_audit_t bc; uintptr_t addr; if (abw->abw_ndx == abw->abw_nbufs) return (WALK_DONE); addr = abw->abw_buf[abw->abw_ndx++].abb_addr; if (mdb_vread(&bc, sizeof (bc), addr) == -1) { mdb_warn("couldn't read bufctl at %p", addr); return (WALK_DONE); } return (wsp->walk_callback(addr, &bc, wsp->walk_cbdata)); } void allocdby_walk_fini(mdb_walk_state_t *wsp) { allocdby_walk_t *abw = wsp->walk_data; mdb_free(abw->abw_buf, sizeof (allocdby_bufctl_t) * abw->abw_size); mdb_free(abw, sizeof (allocdby_walk_t)); } /*ARGSUSED*/ int allocdby_walk(uintptr_t addr, const kmem_bufctl_audit_t *bcp, void *ignored) { char c[MDB_SYM_NAMLEN]; GElf_Sym sym; int i; mdb_printf("%0?p %12llx ", addr, bcp->bc_timestamp); for (i = 0; i < bcp->bc_depth; i++) { if (mdb_lookup_by_addr(bcp->bc_stack[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) continue; if (strncmp(c, "kmem_", 5) == 0) continue; mdb_printf("%s+0x%lx", c, bcp->bc_stack[i] - (uintptr_t)sym.st_value); break; } mdb_printf("\n"); return (WALK_NEXT); } static int allocdby_common(uintptr_t addr, uint_t flags, const char *w) { if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); mdb_printf("%-?s %12s %s\n", "BUFCTL", "TIMESTAMP", "CALLER"); if (mdb_pwalk(w, (mdb_walk_cb_t)allocdby_walk, NULL, addr) == -1) { mdb_warn("can't walk '%s' for %p", w, addr); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ int allocdby(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (allocdby_common(addr, flags, "allocdby")); } /*ARGSUSED*/ int freedby(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (allocdby_common(addr, flags, "freedby")); } /* * Return a string describing the address in relation to the given thread's * stack. * * - If the thread state is TS_FREE, return " (inactive interrupt thread)". * * - If the address is above the stack pointer, return an empty string * signifying that the address is active. * * - If the address is below the stack pointer, and the thread is not on proc, * return " (below sp)". * * - If the address is below the stack pointer, and the thread is on proc, * return " (possibly below sp)". Depending on context, we may or may not * have an accurate t_sp. */ static const char * stack_active(const kthread_t *t, uintptr_t addr) { uintptr_t panicstk; GElf_Sym sym; if (t->t_state == TS_FREE) return (" (inactive interrupt thread)"); /* * Check to see if we're on the panic stack. If so, ignore t_sp, as it * no longer relates to the thread's real stack. */ if (mdb_lookup_by_name("panic_stack", &sym) == 0) { panicstk = (uintptr_t)sym.st_value; if (t->t_sp >= panicstk && t->t_sp < panicstk + PANICSTKSIZE) return (""); } if (addr >= t->t_sp + STACK_BIAS) return (""); if (t->t_state == TS_ONPROC) return (" (possibly below sp)"); return (" (below sp)"); } /* * Additional state for the kmem and vmem ::whatis handlers */ typedef struct whatis_info { mdb_whatis_t *wi_w; const kmem_cache_t *wi_cache; const vmem_t *wi_vmem; vmem_t *wi_msb_arena; size_t wi_slab_size; uint_t wi_slab_found; uint_t wi_kmem_lite_count; uint_t wi_freemem; } whatis_info_t; /* call one of our dcmd functions with "-v" and the provided address */ static void whatis_call_printer(mdb_dcmd_f *dcmd, uintptr_t addr) { mdb_arg_t a; a.a_type = MDB_TYPE_STRING; a.a_un.a_str = "-v"; mdb_printf(":\n"); (void) (*dcmd)(addr, DCMD_ADDRSPEC, 1, &a); } static void whatis_print_kmf_lite(uintptr_t btaddr, size_t count) { #define KMEM_LITE_MAX 16 pc_t callers[KMEM_LITE_MAX]; pc_t uninit = (pc_t)KMEM_UNINITIALIZED_PATTERN; kmem_buftag_t bt; intptr_t stat; const char *plural = ""; int i; /* validate our arguments and read in the buftag */ if (count == 0 || count > KMEM_LITE_MAX || mdb_vread(&bt, sizeof (bt), btaddr) == -1) return; /* validate the buffer state and read in the callers */ stat = (intptr_t)bt.bt_bufctl ^ bt.bt_bxstat; if (stat != KMEM_BUFTAG_ALLOC && stat != KMEM_BUFTAG_FREE) return; if (mdb_vread(callers, count * sizeof (pc_t), btaddr + offsetof(kmem_buftag_lite_t, bt_history)) == -1) return; /* If there aren't any filled in callers, bail */ if (callers[0] == uninit) return; plural = (callers[1] == uninit) ? "" : "s"; /* Everything's done and checked; print them out */ mdb_printf(":\n"); mdb_inc_indent(8); mdb_printf("recent caller%s: %a", plural, callers[0]); for (i = 1; i < count; i++) { if (callers[i] == uninit) break; mdb_printf(", %a", callers[i]); } mdb_dec_indent(8); } static void whatis_print_kmem(whatis_info_t *wi, uintptr_t maddr, uintptr_t addr, uintptr_t baddr) { mdb_whatis_t *w = wi->wi_w; const kmem_cache_t *cp = wi->wi_cache; /* LINTED pointer cast may result in improper alignment */ uintptr_t btaddr = (uintptr_t)KMEM_BUFTAG(cp, addr); int quiet = (mdb_whatis_flags(w) & WHATIS_QUIET); int call_printer = (!quiet && (cp->cache_flags & KMF_AUDIT)); mdb_whatis_report_object(w, maddr, addr, ""); if (baddr != 0 && !call_printer) mdb_printf("bufctl %p ", baddr); mdb_printf("%s from %s", (wi->wi_freemem == FALSE) ? "allocated" : "freed", cp->cache_name); if (baddr != 0 && call_printer) { whatis_call_printer(bufctl, baddr); return; } /* for KMF_LITE caches, try to print out the previous callers */ if (!quiet && (cp->cache_flags & KMF_LITE)) whatis_print_kmf_lite(btaddr, wi->wi_kmem_lite_count); mdb_printf("\n"); } /*ARGSUSED*/ static int whatis_walk_kmem(uintptr_t addr, void *ignored, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; uintptr_t cur; size_t size = wi->wi_cache->cache_bufsize; while (mdb_whatis_match(w, addr, size, &cur)) whatis_print_kmem(wi, cur, addr, 0); return (WHATIS_WALKRET(w)); } /*ARGSUSED*/ static int whatis_walk_bufctl(uintptr_t baddr, const kmem_bufctl_t *bcp, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; uintptr_t cur; uintptr_t addr = (uintptr_t)bcp->bc_addr; size_t size = wi->wi_cache->cache_bufsize; while (mdb_whatis_match(w, addr, size, &cur)) whatis_print_kmem(wi, cur, addr, baddr); return (WHATIS_WALKRET(w)); } static int whatis_walk_seg(uintptr_t addr, const vmem_seg_t *vs, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; size_t size = vs->vs_end - vs->vs_start; uintptr_t cur; /* We're not interested in anything but alloc and free segments */ if (vs->vs_type != VMEM_ALLOC && vs->vs_type != VMEM_FREE) return (WALK_NEXT); while (mdb_whatis_match(w, vs->vs_start, size, &cur)) { mdb_whatis_report_object(w, cur, vs->vs_start, ""); /* * If we're not printing it seperately, provide the vmem_seg * pointer if it has a stack trace. */ if ((mdb_whatis_flags(w) & WHATIS_QUIET) && (!(mdb_whatis_flags(w) & WHATIS_BUFCTL) || (vs->vs_type == VMEM_ALLOC && vs->vs_depth != 0))) { mdb_printf("vmem_seg %p ", addr); } mdb_printf("%s from the %s vmem arena", (vs->vs_type == VMEM_ALLOC) ? "allocated" : "freed", wi->wi_vmem->vm_name); if (!(mdb_whatis_flags(w) & WHATIS_QUIET)) whatis_call_printer(vmem_seg, addr); else mdb_printf("\n"); } return (WHATIS_WALKRET(w)); } static int whatis_walk_vmem(uintptr_t addr, const vmem_t *vmem, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; const char *nm = vmem->vm_name; int identifier = ((vmem->vm_cflags & VMC_IDENTIFIER) != 0); int idspace = ((mdb_whatis_flags(w) & WHATIS_IDSPACE) != 0); if (identifier != idspace) return (WALK_NEXT); wi->wi_vmem = vmem; if (mdb_whatis_flags(w) & WHATIS_VERBOSE) mdb_printf("Searching vmem arena %s...\n", nm); if (mdb_pwalk("vmem_seg", (mdb_walk_cb_t)whatis_walk_seg, wi, addr) == -1) { mdb_warn("can't walk vmem_seg for %p", addr); return (WALK_NEXT); } return (WHATIS_WALKRET(w)); } /*ARGSUSED*/ static int whatis_walk_slab(uintptr_t saddr, const kmem_slab_t *sp, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; /* It must overlap with the slab data, or it's not interesting */ if (mdb_whatis_overlaps(w, (uintptr_t)sp->slab_base, wi->wi_slab_size)) { wi->wi_slab_found++; return (WALK_DONE); } return (WALK_NEXT); } static int whatis_walk_cache(uintptr_t addr, const kmem_cache_t *c, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; char *walk, *freewalk; mdb_walk_cb_t func; int do_bufctl; int identifier = ((c->cache_flags & KMC_IDENTIFIER) != 0); int idspace = ((mdb_whatis_flags(w) & WHATIS_IDSPACE) != 0); if (identifier != idspace) return (WALK_NEXT); /* Override the '-b' flag as necessary */ if (!(c->cache_flags & KMF_HASH)) do_bufctl = FALSE; /* no bufctls to walk */ else if (c->cache_flags & KMF_AUDIT) do_bufctl = TRUE; /* we always want debugging info */ else do_bufctl = ((mdb_whatis_flags(w) & WHATIS_BUFCTL) != 0); if (do_bufctl) { walk = "bufctl"; freewalk = "freectl"; func = (mdb_walk_cb_t)whatis_walk_bufctl; } else { walk = "kmem"; freewalk = "freemem"; func = (mdb_walk_cb_t)whatis_walk_kmem; } wi->wi_cache = c; if (mdb_whatis_flags(w) & WHATIS_VERBOSE) mdb_printf("Searching %s...\n", c->cache_name); /* * If more then two buffers live on each slab, figure out if we're * interested in anything in any slab before doing the more expensive * kmem/freemem (bufctl/freectl) walkers. */ wi->wi_slab_size = c->cache_slabsize - c->cache_maxcolor; if (!(c->cache_flags & KMF_HASH)) wi->wi_slab_size -= sizeof (kmem_slab_t); if ((wi->wi_slab_size / c->cache_chunksize) > 2) { wi->wi_slab_found = 0; if (mdb_pwalk("kmem_slab", (mdb_walk_cb_t)whatis_walk_slab, wi, addr) == -1) { mdb_warn("can't find kmem_slab walker"); return (WALK_DONE); } if (wi->wi_slab_found == 0) return (WALK_NEXT); } wi->wi_freemem = FALSE; if (mdb_pwalk(walk, func, wi, addr) == -1) { mdb_warn("can't find %s walker", walk); return (WALK_DONE); } if (mdb_whatis_done(w)) return (WALK_DONE); /* * We have searched for allocated memory; now search for freed memory. */ if (mdb_whatis_flags(w) & WHATIS_VERBOSE) mdb_printf("Searching %s for free memory...\n", c->cache_name); wi->wi_freemem = TRUE; if (mdb_pwalk(freewalk, func, wi, addr) == -1) { mdb_warn("can't find %s walker", freewalk); return (WALK_DONE); } return (WHATIS_WALKRET(w)); } static int whatis_walk_touch(uintptr_t addr, const kmem_cache_t *c, whatis_info_t *wi) { if (c->cache_arena == wi->wi_msb_arena || (c->cache_cflags & KMC_NOTOUCH)) return (WALK_NEXT); return (whatis_walk_cache(addr, c, wi)); } static int whatis_walk_metadata(uintptr_t addr, const kmem_cache_t *c, whatis_info_t *wi) { if (c->cache_arena != wi->wi_msb_arena) return (WALK_NEXT); return (whatis_walk_cache(addr, c, wi)); } static int whatis_walk_notouch(uintptr_t addr, const kmem_cache_t *c, whatis_info_t *wi) { if (c->cache_arena == wi->wi_msb_arena || !(c->cache_cflags & KMC_NOTOUCH)) return (WALK_NEXT); return (whatis_walk_cache(addr, c, wi)); } static int whatis_walk_thread(uintptr_t addr, const kthread_t *t, mdb_whatis_t *w) { uintptr_t cur; uintptr_t saddr; size_t size; /* * Often, one calls ::whatis on an address from a thread structure. * We use this opportunity to short circuit this case... */ while (mdb_whatis_match(w, addr, sizeof (kthread_t), &cur)) mdb_whatis_report_object(w, cur, addr, "allocated as a thread structure\n"); /* * Now check the stack */ if (t->t_stkbase == NULL) return (WALK_NEXT); /* * This assumes that t_stk is the end of the stack, but it's really * only the initial stack pointer for the thread. Arguments to the * initial procedure, SA(MINFRAME), etc. are all after t_stk. So * that 't->t_stk::whatis' reports "part of t's stack", we include * t_stk in the range (the "+ 1", below), but the kernel should * really include the full stack bounds where we can find it. */ saddr = (uintptr_t)t->t_stkbase; size = (uintptr_t)t->t_stk - saddr + 1; while (mdb_whatis_match(w, saddr, size, &cur)) mdb_whatis_report_object(w, cur, cur, "in thread %p's stack%s\n", addr, stack_active(t, cur)); return (WHATIS_WALKRET(w)); } static void whatis_modctl_match(mdb_whatis_t *w, const char *name, uintptr_t base, size_t size, const char *where) { uintptr_t cur; /* * Since we're searching for addresses inside a module, we report * them as symbols. */ while (mdb_whatis_match(w, base, size, &cur)) mdb_whatis_report_address(w, cur, "in %s's %s\n", name, where); } struct kmem_ctf_module { Shdr *symhdr; char *symtbl; unsigned int nsyms; char *symspace; size_t symsize; char *text; char *data; uintptr_t bss; size_t text_size; size_t data_size; size_t bss_size; }; static int whatis_walk_modctl(uintptr_t addr, const struct modctl *m, mdb_whatis_t *w) { char name[MODMAXNAMELEN]; struct kmem_ctf_module mod; Shdr shdr; if (m->mod_mp == NULL) return (WALK_NEXT); if (mdb_ctf_vread(&mod, "struct module", "struct kmem_ctf_module", (uintptr_t)m->mod_mp, 0) == -1) { mdb_warn("couldn't read modctl %p's module", addr); return (WALK_NEXT); } if (mdb_readstr(name, sizeof (name), (uintptr_t)m->mod_modname) == -1) (void) mdb_snprintf(name, sizeof (name), "0x%p", addr); whatis_modctl_match(w, name, (uintptr_t)mod.text, mod.text_size, "text segment"); whatis_modctl_match(w, name, (uintptr_t)mod.data, mod.data_size, "data segment"); whatis_modctl_match(w, name, (uintptr_t)mod.bss, mod.bss_size, "bss segment"); if (mdb_vread(&shdr, sizeof (shdr), (uintptr_t)mod.symhdr) == -1) { mdb_warn("couldn't read symbol header for %p's module", addr); return (WALK_NEXT); } whatis_modctl_match(w, name, (uintptr_t)mod.symtbl, mod.nsyms * shdr.sh_entsize, "symtab"); whatis_modctl_match(w, name, (uintptr_t)mod.symspace, mod.symsize, "symtab"); return (WHATIS_WALKRET(w)); } /*ARGSUSED*/ static int whatis_walk_memseg(uintptr_t addr, const struct memseg *seg, mdb_whatis_t *w) { uintptr_t cur; uintptr_t base = (uintptr_t)seg->pages; size_t size = (uintptr_t)seg->epages - base; while (mdb_whatis_match(w, base, size, &cur)) { /* round our found pointer down to the page_t base. */ size_t offset = (cur - base) % sizeof (page_t); mdb_whatis_report_object(w, cur, cur - offset, "allocated as a page structure\n"); } return (WHATIS_WALKRET(w)); } /*ARGSUSED*/ static int whatis_run_modules(mdb_whatis_t *w, void *arg) { if (mdb_walk("modctl", (mdb_walk_cb_t)whatis_walk_modctl, w) == -1) { mdb_warn("couldn't find modctl walker"); return (1); } return (0); } /*ARGSUSED*/ static int whatis_run_threads(mdb_whatis_t *w, void *ignored) { /* * Now search all thread stacks. Yes, this is a little weak; we * can save a lot of work by first checking to see if the * address is in segkp vs. segkmem. But hey, computers are * fast. */ if (mdb_walk("thread", (mdb_walk_cb_t)whatis_walk_thread, w) == -1) { mdb_warn("couldn't find thread walker"); return (1); } return (0); } /*ARGSUSED*/ static int whatis_run_pages(mdb_whatis_t *w, void *ignored) { if (mdb_walk("memseg", (mdb_walk_cb_t)whatis_walk_memseg, w) == -1) { mdb_warn("couldn't find memseg walker"); return (1); } return (0); } /*ARGSUSED*/ static int whatis_run_kmem(mdb_whatis_t *w, void *ignored) { whatis_info_t wi; bzero(&wi, sizeof (wi)); wi.wi_w = w; if (mdb_readvar(&wi.wi_msb_arena, "kmem_msb_arena") == -1) mdb_warn("unable to readvar \"kmem_msb_arena\""); if (mdb_readvar(&wi.wi_kmem_lite_count, "kmem_lite_count") == -1 || wi.wi_kmem_lite_count > 16) wi.wi_kmem_lite_count = 0; /* * We process kmem caches in the following order: * * non-KMC_NOTOUCH, non-metadata (typically the most interesting) * metadata (can be huge with KMF_AUDIT) * KMC_NOTOUCH, non-metadata (see kmem_walk_all()) */ if (mdb_walk("kmem_cache", (mdb_walk_cb_t)whatis_walk_touch, &wi) == -1 || mdb_walk("kmem_cache", (mdb_walk_cb_t)whatis_walk_metadata, &wi) == -1 || mdb_walk("kmem_cache", (mdb_walk_cb_t)whatis_walk_notouch, &wi) == -1) { mdb_warn("couldn't find kmem_cache walker"); return (1); } return (0); } /*ARGSUSED*/ static int whatis_run_vmem(mdb_whatis_t *w, void *ignored) { whatis_info_t wi; bzero(&wi, sizeof (wi)); wi.wi_w = w; if (mdb_walk("vmem_postfix", (mdb_walk_cb_t)whatis_walk_vmem, &wi) == -1) { mdb_warn("couldn't find vmem_postfix walker"); return (1); } return (0); } typedef struct kmem_log_cpu { uintptr_t kmc_low; uintptr_t kmc_high; } kmem_log_cpu_t; typedef struct kmem_log_data { uintptr_t kmd_addr; kmem_log_cpu_t *kmd_cpu; } kmem_log_data_t; int kmem_log_walk(uintptr_t addr, const kmem_bufctl_audit_t *b, kmem_log_data_t *kmd) { int i; kmem_log_cpu_t *kmc = kmd->kmd_cpu; size_t bufsize; for (i = 0; i < NCPU; i++) { if (addr >= kmc[i].kmc_low && addr < kmc[i].kmc_high) break; } if (kmd->kmd_addr) { if (b->bc_cache == NULL) return (WALK_NEXT); if (mdb_vread(&bufsize, sizeof (bufsize), (uintptr_t)&b->bc_cache->cache_bufsize) == -1) { mdb_warn( "failed to read cache_bufsize for cache at %p", b->bc_cache); return (WALK_ERR); } if (kmd->kmd_addr < (uintptr_t)b->bc_addr || kmd->kmd_addr >= (uintptr_t)b->bc_addr + bufsize) return (WALK_NEXT); } if (i == NCPU) mdb_printf(" "); else mdb_printf("%3d", i); mdb_printf(" %0?p %0?p %16llx %0?p\n", addr, b->bc_addr, b->bc_timestamp, b->bc_thread); return (WALK_NEXT); } /*ARGSUSED*/ int kmem_log(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kmem_log_header_t lh; kmem_cpu_log_header_t clh; uintptr_t lhp, clhp; int ncpus; uintptr_t *cpu; GElf_Sym sym; kmem_log_cpu_t *kmc; int i; kmem_log_data_t kmd; uint_t opt_b = FALSE; if (mdb_getopts(argc, argv, 'b', MDB_OPT_SETBITS, TRUE, &opt_b, NULL) != argc) return (DCMD_USAGE); if (mdb_readvar(&lhp, "kmem_transaction_log") == -1) { mdb_warn("failed to read 'kmem_transaction_log'"); return (DCMD_ERR); } if (lhp == 0) { mdb_warn("no kmem transaction log\n"); return (DCMD_ERR); } mdb_readvar(&ncpus, "ncpus"); if (mdb_vread(&lh, sizeof (kmem_log_header_t), lhp) == -1) { mdb_warn("failed to read log header at %p", lhp); return (DCMD_ERR); } clhp = lhp + ((uintptr_t)&lh.lh_cpu[0] - (uintptr_t)&lh); cpu = mdb_alloc(sizeof (uintptr_t) * NCPU, UM_SLEEP | UM_GC); if (mdb_lookup_by_name("cpu", &sym) == -1) { mdb_warn("couldn't find 'cpu' array"); return (DCMD_ERR); } if (sym.st_size != NCPU * sizeof (uintptr_t)) { mdb_warn("expected 'cpu' to be of size %d; found %d\n", NCPU * sizeof (uintptr_t), sym.st_size); return (DCMD_ERR); } if (mdb_vread(cpu, sym.st_size, (uintptr_t)sym.st_value) == -1) { mdb_warn("failed to read cpu array at %p", sym.st_value); return (DCMD_ERR); } kmc = mdb_zalloc(sizeof (kmem_log_cpu_t) * NCPU, UM_SLEEP | UM_GC); kmd.kmd_addr = 0; kmd.kmd_cpu = kmc; for (i = 0; i < NCPU; i++) { if (cpu[i] == 0) continue; if (mdb_vread(&clh, sizeof (clh), clhp) == -1) { mdb_warn("cannot read cpu %d's log header at %p", i, clhp); return (DCMD_ERR); } kmc[i].kmc_low = clh.clh_chunk * lh.lh_chunksize + (uintptr_t)lh.lh_base; kmc[i].kmc_high = (uintptr_t)clh.clh_current; clhp += sizeof (kmem_cpu_log_header_t); } mdb_printf("%3s %-?s %-?s %16s %-?s\n", "CPU", "ADDR", "BUFADDR", "TIMESTAMP", "THREAD"); /* * If we have been passed an address, print out only log entries * corresponding to that address. If opt_b is specified, then interpret * the address as a bufctl. */ if (flags & DCMD_ADDRSPEC) { kmem_bufctl_audit_t b; if (opt_b) { kmd.kmd_addr = addr; } else { if (mdb_vread(&b, sizeof (kmem_bufctl_audit_t), addr) == -1) { mdb_warn("failed to read bufctl at %p", addr); return (DCMD_ERR); } (void) kmem_log_walk(addr, &b, &kmd); return (DCMD_OK); } } if (mdb_walk("kmem_log", (mdb_walk_cb_t)kmem_log_walk, &kmd) == -1) { mdb_warn("can't find kmem log walker"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct bufctl_history_cb { int bhc_flags; int bhc_argc; const mdb_arg_t *bhc_argv; int bhc_ret; } bufctl_history_cb_t; /*ARGSUSED*/ static int bufctl_history_callback(uintptr_t addr, const void *ign, void *arg) { bufctl_history_cb_t *bhc = arg; bhc->bhc_ret = bufctl(addr, bhc->bhc_flags, bhc->bhc_argc, bhc->bhc_argv); bhc->bhc_flags &= ~DCMD_LOOPFIRST; return ((bhc->bhc_ret == DCMD_OK)? WALK_NEXT : WALK_DONE); } void bufctl_help(void) { mdb_printf("%s", "Display the contents of kmem_bufctl_audit_ts, with optional filtering.\n\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -v Display the full content of the bufctl, including its stack trace\n" " -h retrieve the bufctl's transaction history, if available\n" " -a addr\n" " filter out bufctls not involving the buffer at addr\n" " -c caller\n" " filter out bufctls without the function/PC in their stack trace\n" " -e earliest\n" " filter out bufctls timestamped before earliest\n" " -l latest\n" " filter out bufctls timestamped after latest\n" " -t thread\n" " filter out bufctls not involving thread\n"); } int bufctl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kmem_bufctl_audit_t bc; uint_t verbose = FALSE; uint_t history = FALSE; uint_t in_history = FALSE; uintptr_t caller = 0, thread = 0; uintptr_t laddr, haddr, baddr = 0; hrtime_t earliest = 0, latest = 0; int i, depth; char c[MDB_SYM_NAMLEN]; GElf_Sym sym; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'h', MDB_OPT_SETBITS, TRUE, &history, 'H', MDB_OPT_SETBITS, TRUE, &in_history, /* internal */ 'c', MDB_OPT_UINTPTR, &caller, 't', MDB_OPT_UINTPTR, &thread, 'e', MDB_OPT_UINT64, &earliest, 'l', MDB_OPT_UINT64, &latest, 'a', MDB_OPT_UINTPTR, &baddr, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (in_history && !history) return (DCMD_USAGE); if (history && !in_history) { mdb_arg_t *nargv = mdb_zalloc(sizeof (*nargv) * (argc + 1), UM_SLEEP | UM_GC); bufctl_history_cb_t bhc; nargv[0].a_type = MDB_TYPE_STRING; nargv[0].a_un.a_str = "-H"; /* prevent recursion */ for (i = 0; i < argc; i++) nargv[i + 1] = argv[i]; /* * When in history mode, we treat each element as if it * were in a seperate loop, so that the headers group * bufctls with similar histories. */ bhc.bhc_flags = flags | DCMD_LOOP | DCMD_LOOPFIRST; bhc.bhc_argc = argc + 1; bhc.bhc_argv = nargv; bhc.bhc_ret = DCMD_OK; if (mdb_pwalk("bufctl_history", bufctl_history_callback, &bhc, addr) == -1) { mdb_warn("unable to walk bufctl_history"); return (DCMD_ERR); } if (bhc.bhc_ret == DCMD_OK && !(flags & DCMD_PIPE_OUT)) mdb_printf("\n"); return (bhc.bhc_ret); } if (DCMD_HDRSPEC(flags) && !(flags & DCMD_PIPE_OUT)) { if (verbose) { mdb_printf("%16s %16s %16s %16s\n" "%%16s %16s %16s %16s%\n", "ADDR", "BUFADDR", "TIMESTAMP", "THREAD", "", "CACHE", "LASTLOG", "CONTENTS"); } else { mdb_printf("%%-?s %-?s %-12s %-?s %s%\n", "ADDR", "BUFADDR", "TIMESTAMP", "THREAD", "CALLER"); } } if (mdb_vread(&bc, sizeof (bc), addr) == -1) { mdb_warn("couldn't read bufctl at %p", addr); return (DCMD_ERR); } /* * Guard against bogus bc_depth in case the bufctl is corrupt or * the address does not really refer to a bufctl. */ depth = MIN(bc.bc_depth, KMEM_STACK_DEPTH); if (caller != 0) { laddr = caller; haddr = caller + sizeof (caller); if (mdb_lookup_by_addr(caller, MDB_SYM_FUZZY, c, sizeof (c), &sym) != -1 && caller == (uintptr_t)sym.st_value) { /* * We were provided an exact symbol value; any * address in the function is valid. */ laddr = (uintptr_t)sym.st_value; haddr = (uintptr_t)sym.st_value + sym.st_size; } for (i = 0; i < depth; i++) if (bc.bc_stack[i] >= laddr && bc.bc_stack[i] < haddr) break; if (i == depth) return (DCMD_OK); } if (thread != 0 && (uintptr_t)bc.bc_thread != thread) return (DCMD_OK); if (earliest != 0 && bc.bc_timestamp < earliest) return (DCMD_OK); if (latest != 0 && bc.bc_timestamp > latest) return (DCMD_OK); if (baddr != 0 && (uintptr_t)bc.bc_addr != baddr) return (DCMD_OK); if (flags & DCMD_PIPE_OUT) { mdb_printf("%#lr\n", addr); return (DCMD_OK); } if (verbose) { mdb_printf( "%%16p% %16p %16llx %16p\n" "%16s %16p %16p %16p\n", addr, bc.bc_addr, bc.bc_timestamp, bc.bc_thread, "", bc.bc_cache, bc.bc_lastlog, bc.bc_contents); mdb_inc_indent(17); for (i = 0; i < depth; i++) mdb_printf("%a\n", bc.bc_stack[i]); mdb_dec_indent(17); mdb_printf("\n"); } else { mdb_printf("%0?p %0?p %12llx %0?p", addr, bc.bc_addr, bc.bc_timestamp, bc.bc_thread); for (i = 0; i < depth; i++) { if (mdb_lookup_by_addr(bc.bc_stack[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) continue; if (strncmp(c, "kmem_", 5) == 0) continue; mdb_printf(" %a\n", bc.bc_stack[i]); break; } if (i >= depth) mdb_printf("\n"); } return (DCMD_OK); } typedef struct kmem_verify { uint64_t *kmv_buf; /* buffer to read cache contents into */ size_t kmv_size; /* number of bytes in kmv_buf */ int kmv_corruption; /* > 0 if corruption found. */ uint_t kmv_flags; /* dcmd flags */ struct kmem_cache kmv_cache; /* the cache we're operating on */ } kmem_verify_t; /* * verify_pattern() * verify that buf is filled with the pattern pat. */ static int64_t verify_pattern(uint64_t *buf_arg, size_t size, uint64_t pat) { /*LINTED*/ uint64_t *bufend = (uint64_t *)((char *)buf_arg + size); uint64_t *buf; for (buf = buf_arg; buf < bufend; buf++) if (*buf != pat) return ((uintptr_t)buf - (uintptr_t)buf_arg); return (-1); } /* * verify_buftag() * verify that btp->bt_bxstat == (bcp ^ pat) */ static int verify_buftag(kmem_buftag_t *btp, uintptr_t pat) { return (btp->bt_bxstat == ((intptr_t)btp->bt_bufctl ^ pat) ? 0 : -1); } /* * verify_free() * verify the integrity of a free block of memory by checking * that it is filled with 0xdeadbeef and that its buftag is sane. */ /*ARGSUSED1*/ static int verify_free(uintptr_t addr, const void *data, void *private) { kmem_verify_t *kmv = (kmem_verify_t *)private; uint64_t *buf = kmv->kmv_buf; /* buf to validate */ int64_t corrupt; /* corruption offset */ kmem_buftag_t *buftagp; /* ptr to buftag */ kmem_cache_t *cp = &kmv->kmv_cache; boolean_t besilent = !!(kmv->kmv_flags & (DCMD_LOOP | DCMD_PIPE_OUT)); /*LINTED*/ buftagp = KMEM_BUFTAG(cp, buf); /* * Read the buffer to check. */ if (mdb_vread(buf, kmv->kmv_size, addr) == -1) { if (!besilent) mdb_warn("couldn't read %p", addr); return (WALK_NEXT); } if ((corrupt = verify_pattern(buf, cp->cache_verify, KMEM_FREE_PATTERN)) >= 0) { if (!besilent) mdb_printf("buffer %p (free) seems corrupted, at %p\n", addr, (uintptr_t)addr + corrupt); goto corrupt; } /* * When KMF_LITE is set, buftagp->bt_redzone is used to hold * the first bytes of the buffer, hence we cannot check for red * zone corruption. */ if ((cp->cache_flags & (KMF_HASH | KMF_LITE)) == KMF_HASH && buftagp->bt_redzone != KMEM_REDZONE_PATTERN) { if (!besilent) mdb_printf("buffer %p (free) seems to " "have a corrupt redzone pattern\n", addr); goto corrupt; } /* * confirm bufctl pointer integrity. */ if (verify_buftag(buftagp, KMEM_BUFTAG_FREE) == -1) { if (!besilent) mdb_printf("buffer %p (free) has a corrupt " "buftag\n", addr); goto corrupt; } return (WALK_NEXT); corrupt: if (kmv->kmv_flags & DCMD_PIPE_OUT) mdb_printf("%p\n", addr); kmv->kmv_corruption++; return (WALK_NEXT); } /* * verify_alloc() * Verify that the buftag of an allocated buffer makes sense with respect * to the buffer. */ /*ARGSUSED1*/ static int verify_alloc(uintptr_t addr, const void *data, void *private) { kmem_verify_t *kmv = (kmem_verify_t *)private; kmem_cache_t *cp = &kmv->kmv_cache; uint64_t *buf = kmv->kmv_buf; /* buf to validate */ /*LINTED*/ kmem_buftag_t *buftagp = KMEM_BUFTAG(cp, buf); uint32_t *ip = (uint32_t *)buftagp; uint8_t *bp = (uint8_t *)buf; int looks_ok = 0, size_ok = 1; /* flags for finding corruption */ boolean_t besilent = !!(kmv->kmv_flags & (DCMD_LOOP | DCMD_PIPE_OUT)); /* * Read the buffer to check. */ if (mdb_vread(buf, kmv->kmv_size, addr) == -1) { if (!besilent) mdb_warn("couldn't read %p", addr); return (WALK_NEXT); } /* * There are two cases to handle: * 1. If the buf was alloc'd using kmem_cache_alloc, it will have * 0xfeedfacefeedface at the end of it * 2. If the buf was alloc'd using kmem_alloc, it will have * 0xbb just past the end of the region in use. At the buftag, * it will have 0xfeedface (or, if the whole buffer is in use, * 0xfeedface & bb000000 or 0xfeedfacf & 000000bb depending on * endianness), followed by 32 bits containing the offset of the * 0xbb byte in the buffer. * * Finally, the two 32-bit words that comprise the second half of the * buftag should xor to KMEM_BUFTAG_ALLOC */ if (buftagp->bt_redzone == KMEM_REDZONE_PATTERN) looks_ok = 1; else if (!KMEM_SIZE_VALID(ip[1])) size_ok = 0; else if (bp[KMEM_SIZE_DECODE(ip[1])] == KMEM_REDZONE_BYTE) looks_ok = 1; else size_ok = 0; if (!size_ok) { if (!besilent) mdb_printf("buffer %p (allocated) has a corrupt " "redzone size encoding\n", addr); goto corrupt; } if (!looks_ok) { if (!besilent) mdb_printf("buffer %p (allocated) has a corrupt " "redzone signature\n", addr); goto corrupt; } if (verify_buftag(buftagp, KMEM_BUFTAG_ALLOC) == -1) { if (!besilent) mdb_printf("buffer %p (allocated) has a " "corrupt buftag\n", addr); goto corrupt; } return (WALK_NEXT); corrupt: if (kmv->kmv_flags & DCMD_PIPE_OUT) mdb_printf("%p\n", addr); kmv->kmv_corruption++; return (WALK_NEXT); } /*ARGSUSED2*/ int kmem_verify(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (flags & DCMD_ADDRSPEC) { int check_alloc = 0, check_free = 0; kmem_verify_t kmv; if (mdb_vread(&kmv.kmv_cache, sizeof (kmv.kmv_cache), addr) == -1) { mdb_warn("couldn't read kmem_cache %p", addr); return (DCMD_ERR); } if ((kmv.kmv_cache.cache_dump.kd_unsafe || kmv.kmv_cache.cache_dump.kd_alloc_fails) && !(flags & (DCMD_LOOP | DCMD_PIPE_OUT))) { mdb_warn("WARNING: cache was used during dump: " "corruption may be incorrectly reported\n"); } kmv.kmv_size = kmv.kmv_cache.cache_buftag + sizeof (kmem_buftag_t); kmv.kmv_buf = mdb_alloc(kmv.kmv_size, UM_SLEEP | UM_GC); kmv.kmv_corruption = 0; kmv.kmv_flags = flags; if ((kmv.kmv_cache.cache_flags & KMF_REDZONE)) { check_alloc = 1; if (kmv.kmv_cache.cache_flags & KMF_DEADBEEF) check_free = 1; } else { if (!(flags & DCMD_LOOP)) { mdb_warn("cache %p (%s) does not have " "redzone checking enabled\n", addr, kmv.kmv_cache.cache_name); } return (DCMD_ERR); } if (!(flags & (DCMD_LOOP | DCMD_PIPE_OUT))) { mdb_printf("Summary for cache '%s'\n", kmv.kmv_cache.cache_name); mdb_inc_indent(2); } if (check_alloc) (void) mdb_pwalk("kmem", verify_alloc, &kmv, addr); if (check_free) (void) mdb_pwalk("freemem", verify_free, &kmv, addr); if (!(flags & DCMD_PIPE_OUT)) { if (flags & DCMD_LOOP) { if (kmv.kmv_corruption == 0) { mdb_printf("%-*s %?p clean\n", KMEM_CACHE_NAMELEN, kmv.kmv_cache.cache_name, addr); } else { mdb_printf("%-*s %?p %d corrupt " "buffer%s\n", KMEM_CACHE_NAMELEN, kmv.kmv_cache.cache_name, addr, kmv.kmv_corruption, kmv.kmv_corruption > 1 ? "s" : ""); } } else { /* * This is the more verbose mode, when the user * typed addr::kmem_verify. If the cache was * clean, nothing will have yet been printed. So * say something. */ if (kmv.kmv_corruption == 0) mdb_printf("clean\n"); mdb_dec_indent(2); } } } else { /* * If the user didn't specify a cache to verify, we'll walk all * kmem_cache's, specifying ourself as a callback for each... * this is the equivalent of '::walk kmem_cache .::kmem_verify' */ if (!(flags & DCMD_PIPE_OUT)) { uintptr_t dump_curr; uintptr_t dump_end; if (mdb_readvar(&dump_curr, "kmem_dump_curr") != -1 && mdb_readvar(&dump_end, "kmem_dump_end") != -1 && dump_curr == dump_end) { mdb_warn("WARNING: exceeded kmem_dump_size; " "corruption may be incorrectly reported\n"); } mdb_printf("%%-*s %-?s %-20s%\n", KMEM_CACHE_NAMELEN, "Cache Name", "Addr", "Cache Integrity"); } (void) (mdb_walk_dcmd("kmem_cache", "kmem_verify", 0, NULL)); } return (DCMD_OK); } typedef struct vmem_node { struct vmem_node *vn_next; struct vmem_node *vn_parent; struct vmem_node *vn_sibling; struct vmem_node *vn_children; uintptr_t vn_addr; int vn_marked; vmem_t vn_vmem; } vmem_node_t; typedef struct vmem_walk { vmem_node_t *vw_root; vmem_node_t *vw_current; } vmem_walk_t; int vmem_walk_init(mdb_walk_state_t *wsp) { uintptr_t vaddr, paddr; vmem_node_t *head = NULL, *root = NULL, *current = NULL, *parent, *vp; vmem_walk_t *vw; if (mdb_readvar(&vaddr, "vmem_list") == -1) { mdb_warn("couldn't read 'vmem_list'"); return (WALK_ERR); } while (vaddr != 0) { vp = mdb_zalloc(sizeof (vmem_node_t), UM_SLEEP); vp->vn_addr = vaddr; vp->vn_next = head; head = vp; if (vaddr == wsp->walk_addr) current = vp; if (mdb_vread(&vp->vn_vmem, sizeof (vmem_t), vaddr) == -1) { mdb_warn("couldn't read vmem_t at %p", vaddr); goto err; } vaddr = (uintptr_t)vp->vn_vmem.vm_next; } for (vp = head; vp != NULL; vp = vp->vn_next) { if ((paddr = (uintptr_t)vp->vn_vmem.vm_source) == 0) { vp->vn_sibling = root; root = vp; continue; } for (parent = head; parent != NULL; parent = parent->vn_next) { if (parent->vn_addr != paddr) continue; vp->vn_sibling = parent->vn_children; parent->vn_children = vp; vp->vn_parent = parent; break; } if (parent == NULL) { mdb_warn("couldn't find %p's parent (%p)\n", vp->vn_addr, paddr); goto err; } } vw = mdb_zalloc(sizeof (vmem_walk_t), UM_SLEEP); vw->vw_root = root; if (current != NULL) vw->vw_current = current; else vw->vw_current = root; wsp->walk_data = vw; return (WALK_NEXT); err: for (vp = head; head != NULL; vp = head) { head = vp->vn_next; mdb_free(vp, sizeof (vmem_node_t)); } return (WALK_ERR); } int vmem_walk_step(mdb_walk_state_t *wsp) { vmem_walk_t *vw = wsp->walk_data; vmem_node_t *vp; int rval; if ((vp = vw->vw_current) == NULL) return (WALK_DONE); rval = wsp->walk_callback(vp->vn_addr, &vp->vn_vmem, wsp->walk_cbdata); if (vp->vn_children != NULL) { vw->vw_current = vp->vn_children; return (rval); } do { vw->vw_current = vp->vn_sibling; vp = vp->vn_parent; } while (vw->vw_current == NULL && vp != NULL); return (rval); } /* * The "vmem_postfix" walk walks the vmem arenas in post-fix order; all * children are visited before their parent. We perform the postfix walk * iteratively (rather than recursively) to allow mdb to regain control * after each callback. */ int vmem_postfix_walk_step(mdb_walk_state_t *wsp) { vmem_walk_t *vw = wsp->walk_data; vmem_node_t *vp = vw->vw_current; int rval; /* * If this node is marked, then we know that we have already visited * all of its children. If the node has any siblings, they need to * be visited next; otherwise, we need to visit the parent. Note * that vp->vn_marked will only be zero on the first invocation of * the step function. */ if (vp->vn_marked) { if (vp->vn_sibling != NULL) vp = vp->vn_sibling; else if (vp->vn_parent != NULL) vp = vp->vn_parent; else { /* * We have neither a parent, nor a sibling, and we * have already been visited; we're done. */ return (WALK_DONE); } } /* * Before we visit this node, visit its children. */ while (vp->vn_children != NULL && !vp->vn_children->vn_marked) vp = vp->vn_children; vp->vn_marked = 1; vw->vw_current = vp; rval = wsp->walk_callback(vp->vn_addr, &vp->vn_vmem, wsp->walk_cbdata); return (rval); } void vmem_walk_fini(mdb_walk_state_t *wsp) { vmem_walk_t *vw = wsp->walk_data; vmem_node_t *root = vw->vw_root; int done; if (root == NULL) return; if ((vw->vw_root = root->vn_children) != NULL) vmem_walk_fini(wsp); vw->vw_root = root->vn_sibling; done = (root->vn_sibling == NULL && root->vn_parent == NULL); mdb_free(root, sizeof (vmem_node_t)); if (done) { mdb_free(vw, sizeof (vmem_walk_t)); } else { vmem_walk_fini(wsp); } } typedef struct vmem_seg_walk { uint8_t vsw_type; uintptr_t vsw_start; uintptr_t vsw_current; } vmem_seg_walk_t; /*ARGSUSED*/ int vmem_seg_walk_common_init(mdb_walk_state_t *wsp, uint8_t type, char *name) { vmem_seg_walk_t *vsw; if (wsp->walk_addr == 0) { mdb_warn("vmem_%s does not support global walks\n", name); return (WALK_ERR); } wsp->walk_data = vsw = mdb_alloc(sizeof (vmem_seg_walk_t), UM_SLEEP); vsw->vsw_type = type; vsw->vsw_start = wsp->walk_addr + offsetof(vmem_t, vm_seg0); vsw->vsw_current = vsw->vsw_start; return (WALK_NEXT); } /* * vmem segments can't have type 0 (this should be added to vmem_impl.h). */ #define VMEM_NONE 0 int vmem_alloc_walk_init(mdb_walk_state_t *wsp) { return (vmem_seg_walk_common_init(wsp, VMEM_ALLOC, "alloc")); } int vmem_free_walk_init(mdb_walk_state_t *wsp) { return (vmem_seg_walk_common_init(wsp, VMEM_FREE, "free")); } int vmem_span_walk_init(mdb_walk_state_t *wsp) { return (vmem_seg_walk_common_init(wsp, VMEM_SPAN, "span")); } int vmem_seg_walk_init(mdb_walk_state_t *wsp) { return (vmem_seg_walk_common_init(wsp, VMEM_NONE, "seg")); } int vmem_seg_walk_step(mdb_walk_state_t *wsp) { vmem_seg_t seg; vmem_seg_walk_t *vsw = wsp->walk_data; uintptr_t addr = vsw->vsw_current; static size_t seg_size = 0; int rval; if (!seg_size) { if (mdb_readvar(&seg_size, "vmem_seg_size") == -1) { mdb_warn("failed to read 'vmem_seg_size'"); seg_size = sizeof (vmem_seg_t); } } if (seg_size < sizeof (seg)) bzero((caddr_t)&seg + seg_size, sizeof (seg) - seg_size); if (mdb_vread(&seg, seg_size, addr) == -1) { mdb_warn("couldn't read vmem_seg at %p", addr); return (WALK_ERR); } vsw->vsw_current = (uintptr_t)seg.vs_anext; if (vsw->vsw_type != VMEM_NONE && seg.vs_type != vsw->vsw_type) { rval = WALK_NEXT; } else { rval = wsp->walk_callback(addr, &seg, wsp->walk_cbdata); } if (vsw->vsw_current == vsw->vsw_start) return (WALK_DONE); return (rval); } void vmem_seg_walk_fini(mdb_walk_state_t *wsp) { vmem_seg_walk_t *vsw = wsp->walk_data; mdb_free(vsw, sizeof (vmem_seg_walk_t)); } #define VMEM_NAMEWIDTH 22 int vmem(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { vmem_t v, parent; vmem_kstat_t *vkp = &v.vm_kstat; uintptr_t paddr; int ident = 0; char c[VMEM_NAMEWIDTH]; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("vmem", "vmem", argc, argv) == -1) { mdb_warn("can't walk vmem"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%-?s %-*s %10s %12s %9s %5s\n", "ADDR", VMEM_NAMEWIDTH, "NAME", "INUSE", "TOTAL", "SUCCEED", "FAIL"); if (mdb_vread(&v, sizeof (v), addr) == -1) { mdb_warn("couldn't read vmem at %p", addr); return (DCMD_ERR); } for (paddr = (uintptr_t)v.vm_source; paddr != 0; ident += 2) { if (mdb_vread(&parent, sizeof (parent), paddr) == -1) { mdb_warn("couldn't trace %p's ancestry", addr); ident = 0; break; } paddr = (uintptr_t)parent.vm_source; } (void) mdb_snprintf(c, VMEM_NAMEWIDTH, "%*s%s", ident, "", v.vm_name); mdb_printf("%0?p %-*s %10llu %12llu %9llu %5llu\n", addr, VMEM_NAMEWIDTH, c, vkp->vk_mem_inuse.value.ui64, vkp->vk_mem_total.value.ui64, vkp->vk_alloc.value.ui64, vkp->vk_fail.value.ui64); return (DCMD_OK); } void vmem_seg_help(void) { mdb_printf("%s", "Display the contents of vmem_seg_ts, with optional filtering.\n\n" "\n" "A vmem_seg_t represents a range of addresses (or arbitrary numbers),\n" "representing a single chunk of data. Only ALLOC segments have debugging\n" "information.\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -v Display the full content of the vmem_seg, including its stack trace\n" " -s report the size of the segment, instead of the end address\n" " -c caller\n" " filter out segments without the function/PC in their stack trace\n" " -e earliest\n" " filter out segments timestamped before earliest\n" " -l latest\n" " filter out segments timestamped after latest\n" " -m minsize\n" " filer out segments smaller than minsize\n" " -M maxsize\n" " filer out segments larger than maxsize\n" " -t thread\n" " filter out segments not involving thread\n" " -T type\n" " filter out segments not of type 'type'\n" " type is one of: ALLOC/FREE/SPAN/ROTOR/WALKER\n"); } /*ARGSUSED*/ int vmem_seg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { vmem_seg_t vs; pc_t *stk = vs.vs_stack; uintptr_t sz; uint8_t t; const char *type = NULL; GElf_Sym sym; char c[MDB_SYM_NAMLEN]; int no_debug; int i; int depth; uintptr_t laddr, haddr; uintptr_t caller = 0, thread = 0; uintptr_t minsize = 0, maxsize = 0; hrtime_t earliest = 0, latest = 0; uint_t size = 0; uint_t verbose = 0; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'c', MDB_OPT_UINTPTR, &caller, 'e', MDB_OPT_UINT64, &earliest, 'l', MDB_OPT_UINT64, &latest, 's', MDB_OPT_SETBITS, TRUE, &size, 'm', MDB_OPT_UINTPTR, &minsize, 'M', MDB_OPT_UINTPTR, &maxsize, 't', MDB_OPT_UINTPTR, &thread, 'T', MDB_OPT_STR, &type, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags) && !(flags & DCMD_PIPE_OUT)) { if (verbose) { mdb_printf("%16s %4s %16s %16s %16s\n" "%%16s %4s %16s %16s %16s%\n", "ADDR", "TYPE", "START", "END", "SIZE", "", "", "THREAD", "TIMESTAMP", ""); } else { mdb_printf("%?s %4s %?s %?s %s\n", "ADDR", "TYPE", "START", size? "SIZE" : "END", "WHO"); } } if (mdb_vread(&vs, sizeof (vs), addr) == -1) { mdb_warn("couldn't read vmem_seg at %p", addr); return (DCMD_ERR); } if (type != NULL) { if (strcmp(type, "ALLC") == 0 || strcmp(type, "ALLOC") == 0) t = VMEM_ALLOC; else if (strcmp(type, "FREE") == 0) t = VMEM_FREE; else if (strcmp(type, "SPAN") == 0) t = VMEM_SPAN; else if (strcmp(type, "ROTR") == 0 || strcmp(type, "ROTOR") == 0) t = VMEM_ROTOR; else if (strcmp(type, "WLKR") == 0 || strcmp(type, "WALKER") == 0) t = VMEM_WALKER; else { mdb_warn("\"%s\" is not a recognized vmem_seg type\n", type); return (DCMD_ERR); } if (vs.vs_type != t) return (DCMD_OK); } sz = vs.vs_end - vs.vs_start; if (minsize != 0 && sz < minsize) return (DCMD_OK); if (maxsize != 0 && sz > maxsize) return (DCMD_OK); t = vs.vs_type; depth = vs.vs_depth; /* * debug info, when present, is only accurate for VMEM_ALLOC segments */ no_debug = (t != VMEM_ALLOC) || (depth == 0 || depth > VMEM_STACK_DEPTH); if (no_debug) { if (caller != 0 || thread != 0 || earliest != 0 || latest != 0) return (DCMD_OK); /* not enough info */ } else { if (caller != 0) { laddr = caller; haddr = caller + sizeof (caller); if (mdb_lookup_by_addr(caller, MDB_SYM_FUZZY, c, sizeof (c), &sym) != -1 && caller == (uintptr_t)sym.st_value) { /* * We were provided an exact symbol value; any * address in the function is valid. */ laddr = (uintptr_t)sym.st_value; haddr = (uintptr_t)sym.st_value + sym.st_size; } for (i = 0; i < depth; i++) if (vs.vs_stack[i] >= laddr && vs.vs_stack[i] < haddr) break; if (i == depth) return (DCMD_OK); } if (thread != 0 && (uintptr_t)vs.vs_thread != thread) return (DCMD_OK); if (earliest != 0 && vs.vs_timestamp < earliest) return (DCMD_OK); if (latest != 0 && vs.vs_timestamp > latest) return (DCMD_OK); } type = (t == VMEM_ALLOC ? "ALLC" : t == VMEM_FREE ? "FREE" : t == VMEM_SPAN ? "SPAN" : t == VMEM_ROTOR ? "ROTR" : t == VMEM_WALKER ? "WLKR" : "????"); if (flags & DCMD_PIPE_OUT) { mdb_printf("%#lr\n", addr); return (DCMD_OK); } if (verbose) { mdb_printf("%%16p% %4s %16p %16p %16ld\n", addr, type, vs.vs_start, vs.vs_end, sz); if (no_debug) return (DCMD_OK); mdb_printf("%16s %4s %16p %16llx\n", "", "", vs.vs_thread, vs.vs_timestamp); mdb_inc_indent(17); for (i = 0; i < depth; i++) { mdb_printf("%a\n", stk[i]); } mdb_dec_indent(17); mdb_printf("\n"); } else { mdb_printf("%0?p %4s %0?p %0?p", addr, type, vs.vs_start, size? sz : vs.vs_end); if (no_debug) { mdb_printf("\n"); return (DCMD_OK); } for (i = 0; i < depth; i++) { if (mdb_lookup_by_addr(stk[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) continue; if (strncmp(c, "vmem_", 5) == 0) continue; break; } mdb_printf(" %a\n", stk[i]); } return (DCMD_OK); } typedef struct kmalog_data { uintptr_t kma_addr; hrtime_t kma_newest; } kmalog_data_t; /*ARGSUSED*/ static int showbc(uintptr_t addr, const kmem_bufctl_audit_t *bcp, kmalog_data_t *kma) { char name[KMEM_CACHE_NAMELEN + 1]; hrtime_t delta; int i, depth; size_t bufsize; if (bcp->bc_timestamp == 0) return (WALK_DONE); if (kma->kma_newest == 0) kma->kma_newest = bcp->bc_timestamp; if (kma->kma_addr) { if (mdb_vread(&bufsize, sizeof (bufsize), (uintptr_t)&bcp->bc_cache->cache_bufsize) == -1) { mdb_warn( "failed to read cache_bufsize for cache at %p", bcp->bc_cache); return (WALK_ERR); } if (kma->kma_addr < (uintptr_t)bcp->bc_addr || kma->kma_addr >= (uintptr_t)bcp->bc_addr + bufsize) return (WALK_NEXT); } delta = kma->kma_newest - bcp->bc_timestamp; depth = MIN(bcp->bc_depth, KMEM_STACK_DEPTH); if (mdb_readstr(name, sizeof (name), (uintptr_t) &bcp->bc_cache->cache_name) <= 0) (void) mdb_snprintf(name, sizeof (name), "%a", bcp->bc_cache); mdb_printf("\nT-%lld.%09lld addr=%p %s\n", delta / NANOSEC, delta % NANOSEC, bcp->bc_addr, name); for (i = 0; i < depth; i++) mdb_printf("\t %a\n", bcp->bc_stack[i]); return (WALK_NEXT); } int kmalog(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *logname = "kmem_transaction_log"; kmalog_data_t kma; if (argc > 1) return (DCMD_USAGE); kma.kma_newest = 0; if (flags & DCMD_ADDRSPEC) kma.kma_addr = addr; else kma.kma_addr = 0; if (argc > 0) { if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (strcmp(argv->a_un.a_str, "fail") == 0) logname = "kmem_failure_log"; else if (strcmp(argv->a_un.a_str, "slab") == 0) logname = "kmem_slab_log"; else if (strcmp(argv->a_un.a_str, "zerosized") == 0) logname = "kmem_zerosized_log"; else return (DCMD_USAGE); } if (mdb_readvar(&addr, logname) == -1) { mdb_warn("failed to read %s log header pointer", logname); return (DCMD_ERR); } if (mdb_pwalk("kmem_log", (mdb_walk_cb_t)showbc, &kma, addr) == -1) { mdb_warn("failed to walk kmem log"); return (DCMD_ERR); } return (DCMD_OK); } /* * As the final lure for die-hard crash(8) users, we provide ::kmausers here. * The first piece is a structure which we use to accumulate kmem_cache_t * addresses of interest. The kmc_add is used as a callback for the kmem_cache * walker; we either add all caches, or ones named explicitly as arguments. */ typedef struct kmclist { const char *kmc_name; /* Name to match (or NULL) */ uintptr_t *kmc_caches; /* List of kmem_cache_t addrs */ int kmc_nelems; /* Num entries in kmc_caches */ int kmc_size; /* Size of kmc_caches array */ } kmclist_t; static int kmc_add(uintptr_t addr, const kmem_cache_t *cp, kmclist_t *kmc) { void *p; int s; if (kmc->kmc_name == NULL || strcmp(cp->cache_name, kmc->kmc_name) == 0) { /* * If we have a match, grow our array (if necessary), and then * add the virtual address of the matching cache to our list. */ if (kmc->kmc_nelems >= kmc->kmc_size) { s = kmc->kmc_size ? kmc->kmc_size * 2 : 256; p = mdb_alloc(sizeof (uintptr_t) * s, UM_SLEEP | UM_GC); bcopy(kmc->kmc_caches, p, sizeof (uintptr_t) * kmc->kmc_size); kmc->kmc_caches = p; kmc->kmc_size = s; } kmc->kmc_caches[kmc->kmc_nelems++] = addr; return (kmc->kmc_name ? WALK_DONE : WALK_NEXT); } return (WALK_NEXT); } /* * The second piece of ::kmausers is a hash table of allocations. Each * allocation owner is identified by its stack trace and data_size. We then * track the total bytes of all such allocations, and the number of allocations * to report at the end. Once we have a list of caches, we walk through the * allocated bufctls of each, and update our hash table accordingly. */ typedef struct kmowner { struct kmowner *kmo_head; /* First hash elt in bucket */ struct kmowner *kmo_next; /* Next hash elt in chain */ size_t kmo_signature; /* Hash table signature */ uint_t kmo_num; /* Number of allocations */ size_t kmo_data_size; /* Size of each allocation */ size_t kmo_total_size; /* Total bytes of allocation */ int kmo_depth; /* Depth of stack trace */ uintptr_t kmo_stack[KMEM_STACK_DEPTH]; /* Stack trace */ } kmowner_t; typedef struct kmusers { uintptr_t kmu_addr; /* address of interest */ const kmem_cache_t *kmu_cache; /* Current kmem cache */ kmowner_t *kmu_hash; /* Hash table of owners */ int kmu_nelems; /* Number of entries in use */ int kmu_size; /* Total number of entries */ } kmusers_t; static void kmu_add(kmusers_t *kmu, const kmem_bufctl_audit_t *bcp, size_t size, size_t data_size) { int i, depth = MIN(bcp->bc_depth, KMEM_STACK_DEPTH); size_t bucket, signature = data_size; kmowner_t *kmo, *kmoend; /* * If the hash table is full, double its size and rehash everything. */ if (kmu->kmu_nelems >= kmu->kmu_size) { int s = kmu->kmu_size ? kmu->kmu_size * 2 : 1024; kmo = mdb_alloc(sizeof (kmowner_t) * s, UM_SLEEP | UM_GC); bcopy(kmu->kmu_hash, kmo, sizeof (kmowner_t) * kmu->kmu_size); kmu->kmu_hash = kmo; kmu->kmu_size = s; kmoend = kmu->kmu_hash + kmu->kmu_size; for (kmo = kmu->kmu_hash; kmo < kmoend; kmo++) kmo->kmo_head = NULL; kmoend = kmu->kmu_hash + kmu->kmu_nelems; for (kmo = kmu->kmu_hash; kmo < kmoend; kmo++) { bucket = kmo->kmo_signature & (kmu->kmu_size - 1); kmo->kmo_next = kmu->kmu_hash[bucket].kmo_head; kmu->kmu_hash[bucket].kmo_head = kmo; } } /* * Finish computing the hash signature from the stack trace, and then * see if the owner is in the hash table. If so, update our stats. */ for (i = 0; i < depth; i++) signature += bcp->bc_stack[i]; bucket = signature & (kmu->kmu_size - 1); for (kmo = kmu->kmu_hash[bucket].kmo_head; kmo; kmo = kmo->kmo_next) { if (kmo->kmo_signature == signature) { size_t difference = 0; difference |= kmo->kmo_data_size - data_size; difference |= kmo->kmo_depth - depth; for (i = 0; i < depth; i++) { difference |= kmo->kmo_stack[i] - bcp->bc_stack[i]; } if (difference == 0) { kmo->kmo_total_size += size; kmo->kmo_num++; return; } } } /* * If the owner is not yet hashed, grab the next element and fill it * in based on the allocation information. */ kmo = &kmu->kmu_hash[kmu->kmu_nelems++]; kmo->kmo_next = kmu->kmu_hash[bucket].kmo_head; kmu->kmu_hash[bucket].kmo_head = kmo; kmo->kmo_signature = signature; kmo->kmo_num = 1; kmo->kmo_data_size = data_size; kmo->kmo_total_size = size; kmo->kmo_depth = depth; for (i = 0; i < depth; i++) kmo->kmo_stack[i] = bcp->bc_stack[i]; } /* * When ::kmausers is invoked without the -f flag, we simply update our hash * table with the information from each allocated bufctl. */ /*ARGSUSED*/ static int kmause1(uintptr_t addr, const kmem_bufctl_audit_t *bcp, kmusers_t *kmu) { const kmem_cache_t *cp = kmu->kmu_cache; kmu_add(kmu, bcp, cp->cache_bufsize, cp->cache_bufsize); return (WALK_NEXT); } /* * When ::kmausers is invoked with the -f flag, we print out the information * for each bufctl as well as updating the hash table. */ static int kmause2(uintptr_t addr, const kmem_bufctl_audit_t *bcp, kmusers_t *kmu) { int i, depth = MIN(bcp->bc_depth, KMEM_STACK_DEPTH); const kmem_cache_t *cp = kmu->kmu_cache; kmem_bufctl_t bufctl; if (kmu->kmu_addr) { if (mdb_vread(&bufctl, sizeof (bufctl), addr) == -1) mdb_warn("couldn't read bufctl at %p", addr); else if (kmu->kmu_addr < (uintptr_t)bufctl.bc_addr || kmu->kmu_addr >= (uintptr_t)bufctl.bc_addr + cp->cache_bufsize) return (WALK_NEXT); } mdb_printf("size %d, addr %p, thread %p, cache %s\n", cp->cache_bufsize, addr, bcp->bc_thread, cp->cache_name); for (i = 0; i < depth; i++) mdb_printf("\t %a\n", bcp->bc_stack[i]); kmu_add(kmu, bcp, cp->cache_bufsize, cp->cache_bufsize); return (WALK_NEXT); } /* * We sort our results by allocation size before printing them. */ static int kmownercmp(const void *lp, const void *rp) { const kmowner_t *lhs = lp; const kmowner_t *rhs = rp; return (rhs->kmo_total_size - lhs->kmo_total_size); } /* * The main engine of ::kmausers is relatively straightforward: First we * accumulate our list of kmem_cache_t addresses into the kmclist_t. Next we * iterate over the allocated bufctls of each cache in the list. Finally, * we sort and print our results. */ /*ARGSUSED*/ int kmausers(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int mem_threshold = 8192; /* Minimum # bytes for printing */ int cnt_threshold = 100; /* Minimum # blocks for printing */ int audited_caches = 0; /* Number of KMF_AUDIT caches found */ int do_all_caches = 1; /* Do all caches (no arguments) */ int opt_e = FALSE; /* Include "small" users */ int opt_f = FALSE; /* Print stack traces */ mdb_walk_cb_t callback = (mdb_walk_cb_t)kmause1; kmowner_t *kmo, *kmoend; int i, oelems; kmclist_t kmc; kmusers_t kmu; bzero(&kmc, sizeof (kmc)); bzero(&kmu, sizeof (kmu)); while ((i = mdb_getopts(argc, argv, 'e', MDB_OPT_SETBITS, TRUE, &opt_e, 'f', MDB_OPT_SETBITS, TRUE, &opt_f, NULL)) != argc) { argv += i; /* skip past options we just processed */ argc -= i; /* adjust argc */ if (argv->a_type != MDB_TYPE_STRING || *argv->a_un.a_str == '-') return (DCMD_USAGE); oelems = kmc.kmc_nelems; kmc.kmc_name = argv->a_un.a_str; (void) mdb_walk("kmem_cache", (mdb_walk_cb_t)kmc_add, &kmc); if (kmc.kmc_nelems == oelems) { mdb_warn("unknown kmem cache: %s\n", kmc.kmc_name); return (DCMD_ERR); } do_all_caches = 0; argv++; argc--; } if (flags & DCMD_ADDRSPEC) { opt_f = TRUE; kmu.kmu_addr = addr; } else { kmu.kmu_addr = 0; } if (opt_e) mem_threshold = cnt_threshold = 0; if (opt_f) callback = (mdb_walk_cb_t)kmause2; if (do_all_caches) { kmc.kmc_name = NULL; /* match all cache names */ (void) mdb_walk("kmem_cache", (mdb_walk_cb_t)kmc_add, &kmc); } for (i = 0; i < kmc.kmc_nelems; i++) { uintptr_t cp = kmc.kmc_caches[i]; kmem_cache_t c; if (mdb_vread(&c, sizeof (c), cp) == -1) { mdb_warn("failed to read cache at %p", cp); continue; } if (!(c.cache_flags & KMF_AUDIT)) { if (!do_all_caches) { mdb_warn("KMF_AUDIT is not enabled for %s\n", c.cache_name); } continue; } kmu.kmu_cache = &c; (void) mdb_pwalk("bufctl", callback, &kmu, cp); audited_caches++; } if (audited_caches == 0 && do_all_caches) { mdb_warn("KMF_AUDIT is not enabled for any caches\n"); return (DCMD_ERR); } qsort(kmu.kmu_hash, kmu.kmu_nelems, sizeof (kmowner_t), kmownercmp); kmoend = kmu.kmu_hash + kmu.kmu_nelems; for (kmo = kmu.kmu_hash; kmo < kmoend; kmo++) { if (kmo->kmo_total_size < mem_threshold && kmo->kmo_num < cnt_threshold) continue; mdb_printf("%lu bytes for %u allocations with data size %lu:\n", kmo->kmo_total_size, kmo->kmo_num, kmo->kmo_data_size); for (i = 0; i < kmo->kmo_depth; i++) mdb_printf("\t %a\n", kmo->kmo_stack[i]); } return (DCMD_OK); } void kmausers_help(void) { mdb_printf( "Displays the largest users of the kmem allocator, sorted by \n" "trace. If one or more caches is specified, only those caches\n" "will be searched. By default, all caches are searched. If an\n" "address is specified, then only those allocations which include\n" "the given address are displayed. Specifying an address implies\n" "-f.\n" "\n" "\t-e\tInclude all users, not just the largest\n" "\t-f\tDisplay individual allocations. By default, users are\n" "\t\tgrouped by stack\n"); } static int kmem_ready_check(void) { int ready; if (mdb_readvar(&ready, "kmem_ready") < 0) return (-1); /* errno is set for us */ return (ready); } void kmem_statechange(void) { static int been_ready = 0; if (been_ready) return; if (kmem_ready_check() <= 0) return; been_ready = 1; (void) mdb_walk("kmem_cache", (mdb_walk_cb_t)kmem_init_walkers, NULL); } void kmem_init(void) { mdb_walker_t w = { "kmem_cache", "walk list of kmem caches", kmem_cache_walk_init, list_walk_step, list_walk_fini }; /* * If kmem is ready, we'll need to invoke the kmem_cache walker * immediately. Walkers in the linkage structure won't be ready until * _mdb_init returns, so we'll need to add this one manually. If kmem * is ready, we'll use the walker to initialize the caches. If kmem * isn't ready, we'll register a callback that will allow us to defer * cache walking until it is. */ if (mdb_add_walker(&w) != 0) { mdb_warn("failed to add kmem_cache walker"); return; } kmem_statechange(); /* register our ::whatis handlers */ mdb_whatis_register("modules", whatis_run_modules, NULL, WHATIS_PRIO_EARLY, WHATIS_REG_NO_ID); mdb_whatis_register("threads", whatis_run_threads, NULL, WHATIS_PRIO_EARLY, WHATIS_REG_NO_ID); mdb_whatis_register("pages", whatis_run_pages, NULL, WHATIS_PRIO_EARLY, WHATIS_REG_NO_ID); mdb_whatis_register("kmem", whatis_run_kmem, NULL, WHATIS_PRIO_ALLOCATOR, 0); mdb_whatis_register("vmem", whatis_run_vmem, NULL, WHATIS_PRIO_ALLOCATOR, 0); } typedef struct whatthread { uintptr_t wt_target; int wt_verbose; } whatthread_t; static int whatthread_walk_thread(uintptr_t addr, const kthread_t *t, whatthread_t *w) { uintptr_t current, data; if (t->t_stkbase == NULL) return (WALK_NEXT); /* * Warn about swapped out threads, but drive on anyway */ if (!(t->t_schedflag & TS_LOAD)) { mdb_warn("thread %p's stack swapped out\n", addr); return (WALK_NEXT); } /* * Search the thread's stack for the given pointer. Note that it would * be more efficient to follow ::kgrep's lead and read in page-sized * chunks, but this routine is already fast and simple. */ for (current = (uintptr_t)t->t_stkbase; current < (uintptr_t)t->t_stk; current += sizeof (uintptr_t)) { if (mdb_vread(&data, sizeof (data), current) == -1) { mdb_warn("couldn't read thread %p's stack at %p", addr, current); return (WALK_ERR); } if (data == w->wt_target) { if (w->wt_verbose) { mdb_printf("%p in thread %p's stack%s\n", current, addr, stack_active(t, current)); } else { mdb_printf("%#lr\n", addr); return (WALK_NEXT); } } } return (WALK_NEXT); } int whatthread(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { whatthread_t w; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); w.wt_verbose = FALSE; w.wt_target = addr; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &w.wt_verbose, NULL) != argc) return (DCMD_USAGE); if (mdb_walk("thread", (mdb_walk_cb_t)whatthread_walk_thread, &w) == -1) { mdb_warn("couldn't walk threads"); return (DCMD_ERR); } return (DCMD_OK); } /* * 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. */ #ifndef _KMEM_H #define _KMEM_H #include #ifdef __cplusplus extern "C" { #endif extern int kmem_cache_walk_init(mdb_walk_state_t *); extern int kmem_cpu_cache_walk_init(mdb_walk_state_t *); extern int kmem_cpu_cache_walk_step(mdb_walk_state_t *); extern int kmem_slab_walk_init(mdb_walk_state_t *); extern int kmem_slab_walk_partial_init(mdb_walk_state_t *); extern int kmem_hash_walk_init(mdb_walk_state_t *wsp); extern int kmem_hash_walk_step(mdb_walk_state_t *wsp); extern void kmem_hash_walk_fini(mdb_walk_state_t *wsp); extern int kmem_walk_init(mdb_walk_state_t *); extern int bufctl_walk_init(mdb_walk_state_t *); extern int freemem_walk_init(mdb_walk_state_t *); extern int freemem_constructed_walk_init(mdb_walk_state_t *); extern int freectl_walk_init(mdb_walk_state_t *); extern int freectl_constructed_walk_init(mdb_walk_state_t *); extern int kmem_walk_step(mdb_walk_state_t *); extern void kmem_walk_fini(mdb_walk_state_t *); extern int bufctl_history_walk_init(mdb_walk_state_t *); extern int bufctl_history_walk_step(mdb_walk_state_t *); extern void bufctl_history_walk_fini(mdb_walk_state_t *); extern int kmem_log_walk_init(mdb_walk_state_t *); extern int kmem_log_walk_step(mdb_walk_state_t *); extern void kmem_log_walk_fini(mdb_walk_state_t *); extern int allocdby_walk_init(mdb_walk_state_t *); extern int allocdby_walk_step(mdb_walk_state_t *); extern void allocdby_walk_fini(mdb_walk_state_t *); extern int freedby_walk_init(mdb_walk_state_t *); extern int freedby_walk_step(mdb_walk_state_t *); extern void freedby_walk_fini(mdb_walk_state_t *); extern int vmem_walk_init(mdb_walk_state_t *); extern int vmem_walk_step(mdb_walk_state_t *); extern void vmem_walk_fini(mdb_walk_state_t *); extern int vmem_postfix_walk_step(mdb_walk_state_t *); extern int vmem_seg_walk_init(mdb_walk_state_t *); extern int vmem_seg_walk_step(mdb_walk_state_t *); extern void vmem_seg_walk_fini(mdb_walk_state_t *); extern int vmem_span_walk_init(mdb_walk_state_t *); extern int vmem_alloc_walk_init(mdb_walk_state_t *); extern int vmem_free_walk_init(mdb_walk_state_t *); extern int kmem_cache(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmem_slabs(uintptr_t, uint_t, int, const mdb_arg_t *); extern int allocdby(uintptr_t, uint_t, int, const mdb_arg_t *); extern int freedby(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmem_log(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmem_debug(uintptr_t, uint_t, int, const mdb_arg_t *); extern int bufctl(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmem_verify(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmem_verify_alloc(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmem_verify_free(uintptr_t, uint_t, int, const mdb_arg_t *); extern int vmem(uintptr_t, uint_t, int, const mdb_arg_t *); extern int vmem_seg(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmalog(uintptr_t, uint_t, int, const mdb_arg_t *); extern int kmausers(uintptr_t, uint_t, int, const mdb_arg_t *); extern void kmem_cache_help(void); extern void kmem_slabs_help(void); extern void bufctl_help(void); extern void vmem_seg_help(void); extern void kmausers_help(void); extern int whatthread(uintptr_t, uint_t, int, const mdb_arg_t *); /* * utility functions for the rest of genunix */ extern void kmem_init(void); extern void kmem_statechange(void); extern int kmem_get_magsize(const kmem_cache_t *); extern size_t kmem_estimate_allocated(uintptr_t, const kmem_cache_t *); #ifdef __cplusplus } #endif #endif /* _KMEM_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include "ldi.h" /* * ldi handle walker structure */ typedef struct lh_walk { struct ldi_handle **hash; /* current bucket pointer */ struct ldi_handle *lhp; /* ldi handle pointer */ size_t index; /* hash table index */ struct ldi_handle buf; /* buffer used for handle reads */ } lh_walk_t; /* * ldi identifier walker structure */ typedef struct li_walk { struct ldi_ident **hash; /* current bucket pointer */ struct ldi_ident *lip; /* ldi handle pointer */ size_t index; /* hash table index */ struct ldi_ident buf; /* buffer used for ident reads */ } li_walk_t; /* * Options for ldi_handles dcmd */ #define LH_IDENTINFO 0x1 /* * LDI walkers */ int ldi_handle_walk_init(mdb_walk_state_t *wsp) { lh_walk_t *lhwp; GElf_Sym sym; /* get the address of the hash table */ if (mdb_lookup_by_name("ldi_handle_hash", &sym) == -1) { mdb_warn("couldn't find ldi_handle_hash"); return (WALK_ERR); } lhwp = mdb_alloc(sizeof (lh_walk_t), UM_SLEEP|UM_GC); lhwp->hash = (struct ldi_handle **)(uintptr_t)sym.st_value; lhwp->index = 0; /* get the address of the first element in the first hash bucket */ if ((mdb_vread(&lhwp->lhp, sizeof (struct ldi_handle *), (uintptr_t)lhwp->hash)) == -1) { mdb_warn("couldn't read ldi handle hash at %p", lhwp->hash); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)lhwp->lhp; wsp->walk_data = lhwp; return (WALK_NEXT); } int ldi_handle_walk_step(mdb_walk_state_t *wsp) { lh_walk_t *lhwp = (lh_walk_t *)wsp->walk_data; int status; /* check if we need to go to the next hash bucket */ while (wsp->walk_addr == 0) { /* advance to the next bucket */ if (++(lhwp->index) >= LH_HASH_SZ) return (WALK_DONE); /* get handle address from the hash bucket */ if ((mdb_vread(&lhwp->lhp, sizeof (struct ldi_handle *), (uintptr_t)(lhwp->hash + lhwp->index))) == -1) { mdb_warn("couldn't read ldi handle hash at %p", (uintptr_t)lhwp->hash + lhwp->index); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)lhwp->lhp; } /* invoke the walker callback for this hash element */ status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); /* get a pointer to the next hash element */ if (mdb_vread(&lhwp->buf, sizeof (struct ldi_handle), wsp->walk_addr) == -1) { mdb_warn("couldn't read ldi handle at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)lhwp->buf.lh_next; return (WALK_NEXT); } int ldi_ident_walk_init(mdb_walk_state_t *wsp) { li_walk_t *liwp; GElf_Sym sym; /* get the address of the hash table */ if (mdb_lookup_by_name("ldi_ident_hash", &sym) == -1) { mdb_warn("couldn't find ldi_ident_hash"); return (WALK_ERR); } liwp = mdb_alloc(sizeof (li_walk_t), UM_SLEEP|UM_GC); liwp->hash = (struct ldi_ident **)(uintptr_t)sym.st_value; liwp->index = 0; /* get the address of the first element in the first hash bucket */ if ((mdb_vread(&liwp->lip, sizeof (struct ldi_ident *), (uintptr_t)liwp->hash)) == -1) { mdb_warn("couldn't read ldi ident hash at %p", liwp->hash); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)liwp->lip; wsp->walk_data = liwp; return (WALK_NEXT); } int ldi_ident_walk_step(mdb_walk_state_t *wsp) { li_walk_t *liwp = (li_walk_t *)wsp->walk_data; int status; /* check if we need to go to the next hash bucket */ while (wsp->walk_addr == 0) { /* advance to the next bucket */ if (++(liwp->index) >= LI_HASH_SZ) return (WALK_DONE); /* get handle address from the hash bucket */ if ((mdb_vread(&liwp->lip, sizeof (struct ldi_ident *), (uintptr_t)(liwp->hash + liwp->index))) == -1) { mdb_warn("couldn't read ldi ident hash at %p", (uintptr_t)liwp->hash + liwp->index); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)liwp->lip; } /* invoke the walker callback for this hash element */ status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); /* get a pointer to the next hash element */ if (mdb_vread(&liwp->buf, sizeof (struct ldi_ident), wsp->walk_addr) == -1) { mdb_warn("couldn't read ldi ident at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)liwp->buf.li_next; return (WALK_NEXT); } /* * LDI dcmds */ static void ldi_ident_header(int start, int refs) { if (start) { mdb_printf("%-?s ", "IDENT"); } else { mdb_printf("%?s ", "IDENT"); } if (refs) mdb_printf("%4s ", "REFS"); mdb_printf("%?s %5s %5s %s\n", "DIP", "MINOR", "MODID", "MODULE NAME"); } static int ldi_ident_print(uintptr_t addr, int refs) { struct ldi_ident li; /* read the ldi ident */ if (mdb_vread(&li, sizeof (struct ldi_ident), addr) == -1) { mdb_warn("couldn't read ldi ident at %p", addr); return (1); } /* display the ident address */ mdb_printf("%0?p ", addr); /* display the ref count */ if (refs) mdb_printf("%4u ", li.li_ref); /* display the dip (if any) */ if (li.li_dip != NULL) { mdb_printf("%0?p ", li.li_dip); } else { mdb_printf("%?s ", "-"); } /* display the minor node (if any) */ if (li.li_dev != DDI_DEV_T_NONE) { mdb_printf("%5u ", getminor(li.li_dev)); } else { mdb_printf("%5s ", "-"); } /* display the module info */ mdb_printf("%5d %s\n", li.li_modid, li.li_modname); return (0); } int ldi_ident(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int start = 1; int refs = 1; /* Determine if there is an ldi identifier address */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("ldi_ident", "ldi_ident", argc, argv) == -1) { mdb_warn("can't walk ldi idents"); return (DCMD_ERR); } return (DCMD_OK); } /* display the header line */ if (DCMD_HDRSPEC(flags)) ldi_ident_header(start, refs); /* display the ldi ident */ if (ldi_ident_print(addr, refs)) return (DCMD_ERR); return (DCMD_OK); } static void ldi_handle_header(int refs, int ident) { mdb_printf("%-?s ", "HANDLE"); if (refs) mdb_printf("%4s ", "REFS"); mdb_printf("%?s %10s %5s %?s ", "VNODE", "DRV", "MINOR", "EVENTS"); if (!ident) { mdb_printf("%?s\n", "IDENT"); } else { ldi_ident_header(0, 0); } } static int ldi_handle_print(uintptr_t addr, int ident, int refs) { vnode_t vnode; struct ldi_handle lh; const char *name; /* read in the ldi handle */ if (mdb_vread(&lh, sizeof (struct ldi_handle), addr) == -1) { mdb_warn("couldn't read ldi handle at %p", addr); return (DCMD_ERR); } /* display the handle address */ mdb_printf("%0?p ", addr); /* display the ref count */ if (refs) mdb_printf("%4u ", lh.lh_ref); /* display the vnode */ mdb_printf("%0?p ", lh.lh_vp); /* read in the vnode associated with the handle */ addr = (uintptr_t)lh.lh_vp; if (mdb_vread(&vnode, sizeof (vnode_t), addr) == -1) { mdb_warn("couldn't read vnode at %p", addr); return (1); } /* display the driver name */ if ((name = mdb_major_to_name(getmajor(vnode.v_rdev))) == NULL) { mdb_warn("failed to convert major number to name\n"); return (1); } mdb_printf("%10s ", name); /* display the minor number */ mdb_printf("%5d ", getminor(vnode.v_rdev)); /* display the event pointer (if any) */ if (lh.lh_events != NULL) { mdb_printf("%0?p ", lh.lh_events); } else { mdb_printf("%?s ", "-"); } if (!ident) { /* display the ident address */ mdb_printf("%0?p\n", lh.lh_ident); return (0); } /* display the entire ident */ return (ldi_ident_print((uintptr_t)lh.lh_ident, refs)); } int ldi_handle(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int ident = 0; int refs = 1; if (mdb_getopts(argc, argv, 'i', MDB_OPT_SETBITS, TRUE, &ident, NULL) != argc) return (DCMD_USAGE); if (ident) refs = 0; /* Determine if there is an ldi handle address */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("ldi_handle", "ldi_handle", argc, argv) == -1) { mdb_warn("can't walk ldi handles"); return (DCMD_ERR); } return (DCMD_OK); } /* display the header line */ if (DCMD_HDRSPEC(flags)) ldi_handle_header(refs, ident); /* display the ldi handle */ if (ldi_handle_print(addr, ident, refs)) return (DCMD_ERR); return (DCMD_OK); } void ldi_ident_help(void) { mdb_printf("Displays an ldi identifier.\n" "Without the address of an \"ldi_ident_t\", " "print all identifiers.\n" "With an address, print the specified identifier.\n"); } void ldi_handle_help(void) { mdb_printf("Displays an ldi handle.\n" "Without the address of an \"ldi_handle_t\", " "print all handles.\n" "With an address, print the specified handle.\n\n" "Switches:\n" " -i print the module identifier information\n"); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_LDI_H #define _MDB_LDI_H #ifdef __cplusplus extern "C" { #endif /* ldi handle walker routines */ extern int ldi_handle_walk_init(mdb_walk_state_t *); extern int ldi_handle_walk_step(mdb_walk_state_t *); /* ldi ident walker routines */ extern int ldi_ident_walk_init(mdb_walk_state_t *); extern int ldi_ident_walk_step(mdb_walk_state_t *); /* ::ldi_handle dcmd */ extern int ldi_handle(uintptr_t, uint_t, int, const mdb_arg_t *); extern void ldi_handle_help(void); /* ::ldi_identifier dcmd */ extern int ldi_ident(uintptr_t, uint_t, int, const mdb_arg_t *); extern void ldi_ident_help(void); #ifdef __cplusplus } #endif #endif /* _MDB_LDI_H */ /* * 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. */ /* * A generic memory leak detector. The target interface, defined in * , is implemented by the genunix and libumem dmods to fill * in the details of operation. */ #include #include "leaky.h" #include "leaky_impl.h" #define LK_BUFCTLHSIZE 127 /* * We re-use the low bit of the lkm_addr as the 'marked' bit. */ #define LK_MARKED(b) ((uintptr_t)(b) & 1) #define LK_MARK(b) ((b) |= 1) #define LK_ADDR(b) ((uintptr_t)(b) & ~1UL) /* * Possible values for lk_state. */ #define LK_CLEAN 0 /* No outstanding mdb_alloc()'s */ #define LK_SWEEPING 1 /* Potentially some outstanding mdb_alloc()'s */ #define LK_DONE 2 /* All mdb_alloc()'s complete */ #define LK_CLEANING 3 /* Currently cleaning prior mdb_alloc()'s */ static volatile int lk_state; #define LK_STATE_SIZE 10000 /* completely arbitrary */ typedef int leak_ndx_t; /* change if >2 billion buffers are needed */ typedef struct leak_state { struct leak_state *lks_next; leak_ndx_t lks_stack[LK_STATE_SIZE]; } leak_state_t; typedef struct leak_beans { int lkb_dups; int lkb_follows; int lkb_misses; int lkb_dismissals; int lkb_pushes; int lkb_deepest; } leak_beans_t; typedef struct leak_type { int lt_type; size_t lt_leaks; leak_bufctl_t **lt_sorted; } leak_type_t; typedef struct leak_walk { int lkw_ndx; leak_bufctl_t *lkw_current; leak_bufctl_t *lkw_hash_next; } leak_walk_t; #define LK_SCAN_BUFFER_SIZE 16384 static uintptr_t *lk_scan_buffer; static leak_mtab_t *lk_mtab; static leak_state_t *lk_free_state; static leak_ndx_t lk_nbuffers; static leak_beans_t lk_beans; static leak_bufctl_t *lk_bufctl[LK_BUFCTLHSIZE]; static leak_type_t lk_types[LK_NUM_TYPES]; static size_t lk_memusage; #ifndef _KMDB static hrtime_t lk_begin; static hrtime_t lk_vbegin; #endif static uint_t lk_verbose = FALSE; static void leaky_verbose(char *str, uint64_t stat) { if (lk_verbose == FALSE) return; mdb_printf("findleaks: "); if (str == NULL) { mdb_printf("\n"); return; } mdb_printf("%*s => %lld\n", 30, str, stat); } static void leaky_verbose_perc(char *str, uint64_t stat, uint64_t total) { uint_t perc = (stat * 100) / total; uint_t tenths = ((stat * 1000) / total) % 10; if (lk_verbose == FALSE) return; mdb_printf("findleaks: %*s => %-13lld (%2d.%1d%%)\n", 30, str, stat, perc, tenths); } static void leaky_verbose_begin(void) { /* kmdb can't tell time */ #ifndef _KMDB extern hrtime_t gethrvtime(void); lk_begin = gethrtime(); lk_vbegin = gethrvtime(); #endif lk_memusage = 0; } static void leaky_verbose_end(void) { /* kmdb can't tell time */ #ifndef _KMDB extern hrtime_t gethrvtime(void); hrtime_t ts = gethrtime() - lk_begin; hrtime_t sec = ts / (hrtime_t)NANOSEC; hrtime_t nsec = ts % (hrtime_t)NANOSEC; hrtime_t vts = gethrvtime() - lk_vbegin; hrtime_t vsec = vts / (hrtime_t)NANOSEC; hrtime_t vnsec = vts % (hrtime_t)NANOSEC; #endif if (lk_verbose == FALSE) return; mdb_printf("findleaks: %*s => %lu kB\n", 30, "peak memory usage", (lk_memusage + 1023)/1024); #ifndef _KMDB mdb_printf("findleaks: %*s => %lld.%lld seconds\n", 30, "elapsed CPU time", vsec, (vnsec * 10)/(hrtime_t)NANOSEC); mdb_printf("findleaks: %*s => %lld.%lld seconds\n", 30, "elapsed wall time", sec, (nsec * 10)/(hrtime_t)NANOSEC); #endif leaky_verbose(NULL, 0); } static void * leaky_alloc(size_t sz, uint_t flags) { void *buf = mdb_alloc(sz, flags); if (buf != NULL) lk_memusage += sz; return (buf); } static void * leaky_zalloc(size_t sz, uint_t flags) { void *buf = mdb_zalloc(sz, flags); if (buf != NULL) lk_memusage += sz; return (buf); } static int leaky_mtabcmp(const void *l, const void *r) { const leak_mtab_t *lhs = (const leak_mtab_t *)l; const leak_mtab_t *rhs = (const leak_mtab_t *)r; if (lhs->lkm_base < rhs->lkm_base) return (-1); if (lhs->lkm_base > rhs->lkm_base) return (1); return (0); } static leak_ndx_t leaky_search(uintptr_t addr) { leak_ndx_t left = 0, right = lk_nbuffers - 1, guess; while (right >= left) { guess = (right + left) >> 1; if (addr < LK_ADDR(lk_mtab[guess].lkm_base)) { right = guess - 1; continue; } if (addr >= lk_mtab[guess].lkm_limit) { left = guess + 1; continue; } return (guess); } return (-1); } void leaky_grep(uintptr_t addr, size_t size) { uintptr_t *buf, *cur, *end; size_t bytes, newsz, nptrs; leak_state_t *state = NULL, *new_state; uint_t state_idx; uintptr_t min = LK_ADDR(lk_mtab[0].lkm_base); uintptr_t max = lk_mtab[lk_nbuffers - 1].lkm_limit; int dups = 0, misses = 0, depth = 0, deepest = 0; int follows = 0, dismissals = 0, pushes = 0; leak_ndx_t mtab_ndx; leak_mtab_t *lmp; uintptr_t nbase; uintptr_t base; size_t base_size; const uintptr_t mask = sizeof (uintptr_t) - 1; if (addr == 0 || size == 0) return; state_idx = 0; /* * Our main loop, led by the 'pop' label: * 1) read in a buffer piece by piece, * 2) mark all unmarked mtab entries reachable from it, and * either scan them in-line or push them onto our stack of * unfinished work. * 3) pop the top mtab entry off the stack, and loop. */ pop: base = addr; base_size = size; /* * If our address isn't pointer-aligned, we need to align it and * whack the size appropriately. */ if (size < mask) { size = 0; } else if (addr & mask) { size -= (mask + 1) - (addr & mask); addr += (mask + 1) - (addr & mask); } size -= (size & mask); while (size > 0) { buf = lk_scan_buffer; end = &buf[LK_SCAN_BUFFER_SIZE / sizeof (uintptr_t)]; bytes = MIN(size, LK_SCAN_BUFFER_SIZE); cur = end - (bytes / sizeof (uintptr_t)); if (mdb_vread(cur, bytes, addr) == -1) { mdb_warn("[%p, %p): couldn't read %ld bytes at %p", base, base + base_size, bytes, addr); break; } addr += bytes; size -= bytes; /* * The buffer looks like: ('+'s are unscanned data) * * -----------------------------++++++++++++++++ * | | | * buf cur end * * cur scans forward. When we encounter a new buffer, and * it will fit behind "cur", we read it in and back up cur, * processing it immediately. */ while (cur < end) { uintptr_t ptr = *cur++; if (ptr < min || ptr > max) { dismissals++; continue; } if ((mtab_ndx = leaky_search(ptr)) == -1) { misses++; continue; } lmp = &lk_mtab[mtab_ndx]; if (LK_MARKED(lmp->lkm_base)) { dups++; /* already seen */ continue; } /* * Found an unmarked buffer. Mark it, then either * read it in, or add it to the stack of pending work. */ follows++; LK_MARK(lmp->lkm_base); nbase = LK_ADDR(lmp->lkm_base); newsz = lmp->lkm_limit - nbase; nptrs = newsz / sizeof (uintptr_t); newsz = nptrs * sizeof (uintptr_t); if ((nbase & mask) == 0 && nptrs <= (cur - buf) && mdb_vread(cur - nptrs, newsz, nbase) != -1) { cur -= nptrs; continue; } /* * couldn't process it in-place -- add it to the * stack. */ if (state == NULL || state_idx == LK_STATE_SIZE) { if ((new_state = lk_free_state) != NULL) lk_free_state = new_state->lks_next; else new_state = leaky_zalloc( sizeof (*state), UM_SLEEP | UM_GC); new_state->lks_next = state; state = new_state; state_idx = 0; } pushes++; state->lks_stack[state_idx++] = mtab_ndx; if (++depth > deepest) deepest = depth; } } /* * Retrieve the next mtab index, extract its info, and loop around * to process it. */ if (state_idx == 0 && state != NULL) { new_state = state->lks_next; state->lks_next = lk_free_state; lk_free_state = state; state = new_state; state_idx = LK_STATE_SIZE; } if (depth > 0) { mtab_ndx = state->lks_stack[--state_idx]; addr = LK_ADDR(lk_mtab[mtab_ndx].lkm_base); size = lk_mtab[mtab_ndx].lkm_limit - addr; depth--; goto pop; } /* * update the beans */ lk_beans.lkb_dups += dups; lk_beans.lkb_dismissals += dismissals; lk_beans.lkb_misses += misses; lk_beans.lkb_follows += follows; lk_beans.lkb_pushes += pushes; if (deepest > lk_beans.lkb_deepest) lk_beans.lkb_deepest = deepest; } static void leaky_do_grep_ptr(uintptr_t loc, int process) { leak_ndx_t ndx; leak_mtab_t *lkmp; size_t sz; if (loc < LK_ADDR(lk_mtab[0].lkm_base) || loc > lk_mtab[lk_nbuffers - 1].lkm_limit) { lk_beans.lkb_dismissals++; return; } if ((ndx = leaky_search(loc)) == -1) { lk_beans.lkb_misses++; return; } lkmp = &lk_mtab[ndx]; sz = lkmp->lkm_limit - lkmp->lkm_base; if (LK_MARKED(lkmp->lkm_base)) { lk_beans.lkb_dups++; } else { LK_MARK(lkmp->lkm_base); lk_beans.lkb_follows++; if (process) leaky_grep(lkmp->lkm_base, sz); } } void leaky_grep_ptr(uintptr_t loc) { leaky_do_grep_ptr(loc, 1); } void leaky_mark_ptr(uintptr_t loc) { leaky_do_grep_ptr(loc, 0); } /* * This may be used to manually process a marked buffer. */ int leaky_lookup_marked(uintptr_t loc, uintptr_t *addr_out, size_t *size_out) { leak_ndx_t ndx; leak_mtab_t *lkmp; if ((ndx = leaky_search(loc)) == -1) return (0); lkmp = &lk_mtab[ndx]; *addr_out = LK_ADDR(lkmp->lkm_base); *size_out = lkmp->lkm_limit - LK_ADDR(lkmp->lkm_base); return (1); } void leaky_add_leak(int type, uintptr_t addr, uintptr_t bufaddr, hrtime_t timestamp, leak_pc_t *stack, uint_t depth, uintptr_t cid, uintptr_t data) { leak_bufctl_t *nlkb, *lkb; uintptr_t total = 0; size_t ndx; int i; if (type < 0 || type >= LK_NUM_TYPES || depth != (uint8_t)depth) { mdb_warn("invalid arguments to leaky_add_leak()\n"); return; } nlkb = leaky_zalloc(LEAK_BUFCTL_SIZE(depth), UM_SLEEP); nlkb->lkb_type = type; nlkb->lkb_addr = addr; nlkb->lkb_bufaddr = bufaddr; nlkb->lkb_cid = cid; nlkb->lkb_data = data; nlkb->lkb_depth = depth; nlkb->lkb_timestamp = timestamp; total = type; for (i = 0; i < depth; i++) { total += stack[i]; nlkb->lkb_stack[i] = stack[i]; } ndx = total % LK_BUFCTLHSIZE; if ((lkb = lk_bufctl[ndx]) == NULL) { lk_types[type].lt_leaks++; lk_bufctl[ndx] = nlkb; return; } for (;;) { if (lkb->lkb_type != type || lkb->lkb_depth != depth || lkb->lkb_cid != cid) goto no_match; for (i = 0; i < depth; i++) if (lkb->lkb_stack[i] != stack[i]) goto no_match; /* * If we're here, we've found a matching stack; link it in. * Note that the volatile cast assures that these stores * will occur in program order (thus assuring that we can * take an interrupt and still be in a sane enough state to * throw away the data structure later, in leaky_cleanup()). */ ((volatile leak_bufctl_t *)nlkb)->lkb_next = lkb->lkb_next; ((volatile leak_bufctl_t *)lkb)->lkb_next = nlkb; lkb->lkb_dups++; /* * If we're older, swap places so that we are the * representative leak. */ if (timestamp < lkb->lkb_timestamp) { nlkb->lkb_addr = lkb->lkb_addr; nlkb->lkb_bufaddr = lkb->lkb_bufaddr; nlkb->lkb_data = lkb->lkb_data; nlkb->lkb_timestamp = lkb->lkb_timestamp; lkb->lkb_addr = addr; lkb->lkb_bufaddr = bufaddr; lkb->lkb_data = data; lkb->lkb_timestamp = timestamp; } break; no_match: if (lkb->lkb_hash_next == NULL) { lkb->lkb_hash_next = nlkb; lk_types[type].lt_leaks++; break; } lkb = lkb->lkb_hash_next; } } int leaky_ctlcmp(const void *l, const void *r) { const leak_bufctl_t *lhs = *((const leak_bufctl_t **)l); const leak_bufctl_t *rhs = *((const leak_bufctl_t **)r); return (leaky_subr_bufctl_cmp(lhs, rhs)); } void leaky_sort(void) { int type, i, j; leak_bufctl_t *lkb; leak_type_t *ltp; for (type = 0; type < LK_NUM_TYPES; type++) { ltp = &lk_types[type]; if (ltp->lt_leaks == 0) continue; ltp->lt_sorted = leaky_alloc(ltp->lt_leaks * sizeof (leak_bufctl_t *), UM_SLEEP); j = 0; for (i = 0; i < LK_BUFCTLHSIZE; i++) { for (lkb = lk_bufctl[i]; lkb != NULL; lkb = lkb->lkb_hash_next) { if (lkb->lkb_type == type) ltp->lt_sorted[j++] = lkb; } } if (j != ltp->lt_leaks) mdb_warn("expected %d leaks, got %d\n", ltp->lt_leaks, j); qsort(ltp->lt_sorted, ltp->lt_leaks, sizeof (leak_bufctl_t *), leaky_ctlcmp); } } void leaky_cleanup(int force) { int i; leak_bufctl_t *lkb, *l, *next; /* * State structures are allocated UM_GC, so we just need to nuke * the freelist pointer. */ lk_free_state = NULL; switch (lk_state) { case LK_CLEAN: return; /* nothing to do */ case LK_CLEANING: mdb_warn("interrupted during ::findleaks cleanup; some mdb " "memory will be leaked\n"); for (i = 0; i < LK_BUFCTLHSIZE; i++) lk_bufctl[i] = NULL; for (i = 0; i < LK_NUM_TYPES; i++) { lk_types[i].lt_leaks = 0; lk_types[i].lt_sorted = NULL; } bzero(&lk_beans, sizeof (lk_beans)); lk_state = LK_CLEAN; return; case LK_SWEEPING: break; /* must clean up */ case LK_DONE: default: if (!force) return; break; /* only clean up if forced */ } lk_state = LK_CLEANING; for (i = 0; i < LK_NUM_TYPES; i++) { if (lk_types[i].lt_sorted != NULL) { mdb_free(lk_types[i].lt_sorted, lk_types[i].lt_leaks * sizeof (leak_bufctl_t *)); lk_types[i].lt_sorted = NULL; } lk_types[i].lt_leaks = 0; } for (i = 0; i < LK_BUFCTLHSIZE; i++) { for (lkb = lk_bufctl[i]; lkb != NULL; lkb = next) { for (l = lkb->lkb_next; l != NULL; l = next) { next = l->lkb_next; mdb_free(l, LEAK_BUFCTL_SIZE(l->lkb_depth)); } next = lkb->lkb_hash_next; mdb_free(lkb, LEAK_BUFCTL_SIZE(lkb->lkb_depth)); } lk_bufctl[i] = NULL; } bzero(&lk_beans, sizeof (lk_beans)); lk_state = LK_CLEAN; } int leaky_filter(const leak_pc_t *stack, int depth, uintptr_t filter) { int i; GElf_Sym sym; char c; if (filter == 0) return (1); for (i = 0; i < depth; i++) { if (stack[i] == filter) return (1); if (mdb_lookup_by_addr(stack[i], MDB_SYM_FUZZY, &c, sizeof (c), &sym) == -1) continue; if ((uintptr_t)sym.st_value == filter) return (1); } return (0); } void leaky_dump(uintptr_t filter, uint_t dump_verbose) { int i; size_t leaks; leak_bufctl_t **sorted; leak_bufctl_t *lkb; int seen = 0; for (i = 0; i < LK_NUM_TYPES; i++) { leaks = lk_types[i].lt_leaks; sorted = lk_types[i].lt_sorted; leaky_subr_dump_start(i); while (leaks-- > 0) { lkb = *sorted++; if (!leaky_filter(lkb->lkb_stack, lkb->lkb_depth, filter)) continue; seen = 1; leaky_subr_dump(lkb, 0); } leaky_subr_dump_end(i); } if (!seen) { if (filter != 0) mdb_printf( "findleaks: no memory leaks matching %a found\n", filter); else mdb_printf( "findleaks: no memory leaks detected\n"); } if (!dump_verbose || !seen) return; mdb_printf("\n"); for (i = 0; i < LK_NUM_TYPES; i++) { leaks = lk_types[i].lt_leaks; sorted = lk_types[i].lt_sorted; while (leaks-- > 0) { lkb = *sorted++; if (!leaky_filter(lkb->lkb_stack, lkb->lkb_depth, filter)) continue; leaky_subr_dump(lkb, 1); } } } static const char *const findleaks_desc = "Does a conservative garbage collection of the heap in order to find\n" "potentially leaked buffers. Similar leaks are coalesced by stack\n" "trace, with the oldest leak picked as representative. The leak\n" "table is cached between invocations.\n" "\n" "addr, if provided, should be a function or PC location. Reported\n" "leaks will then be limited to those with that function or PC in\n" "their stack trace.\n" "\n" "The 'leak' and 'leakbuf' walkers can be used to retrieve coalesced\n" "leaks.\n"; static const char *const findleaks_args = " -d detail each representative leak (long)\n" " -f throw away cached state, and do a full run\n" " -v report verbose information about the findleaks run\n"; void findleaks_help(void) { mdb_printf("%s\n", findleaks_desc); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", findleaks_args); } #define LK_REPORT_BEAN(x) leaky_verbose_perc(#x, lk_beans.lkb_##x, total); /*ARGSUSED*/ int findleaks(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { size_t est = 0; leak_ndx_t i; leak_mtab_t *lmp; ssize_t total; uintptr_t filter = 0; uint_t dump = 0; uint_t force = 0; uint_t verbose = 0; int ret; if (flags & DCMD_ADDRSPEC) filter = addr; if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &dump, 'f', MDB_OPT_SETBITS, TRUE, &force, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (verbose || force) lk_verbose = verbose; /* * Clean any previous ::findleaks. */ leaky_cleanup(force); if (lk_state == LK_DONE) { if (lk_verbose) mdb_printf("findleaks: using cached results " "(use '-f' to force a full run)\n"); goto dump; } leaky_verbose_begin(); if ((ret = leaky_subr_estimate(&est)) != DCMD_OK) return (ret); leaky_verbose("maximum buffers", est); /* * Now we have an upper bound on the number of buffers. Allocate * our mtab array. */ lk_mtab = leaky_zalloc(est * sizeof (leak_mtab_t), UM_SLEEP | UM_GC); lmp = lk_mtab; if ((ret = leaky_subr_fill(&lmp)) != DCMD_OK) return (ret); lk_nbuffers = lmp - lk_mtab; qsort(lk_mtab, lk_nbuffers, sizeof (leak_mtab_t), leaky_mtabcmp); /* * validate the mtab table now that it is sorted */ for (i = 0; i < lk_nbuffers; i++) { if (lk_mtab[i].lkm_base >= lk_mtab[i].lkm_limit) { mdb_warn("[%p, %p): invalid mtab\n", lk_mtab[i].lkm_base, lk_mtab[i].lkm_limit); return (DCMD_ERR); } if (i < lk_nbuffers - 1 && lk_mtab[i].lkm_limit > lk_mtab[i + 1].lkm_base) { mdb_warn("[%p, %p) and [%p, %p): overlapping mtabs\n", lk_mtab[i].lkm_base, lk_mtab[i].lkm_limit, lk_mtab[i + 1].lkm_base, lk_mtab[i + 1].lkm_limit); return (DCMD_ERR); } } leaky_verbose("actual buffers", lk_nbuffers); lk_scan_buffer = leaky_zalloc(LK_SCAN_BUFFER_SIZE, UM_SLEEP | UM_GC); if ((ret = leaky_subr_run()) != DCMD_OK) return (ret); lk_state = LK_SWEEPING; for (i = 0; i < lk_nbuffers; i++) { if (LK_MARKED(lk_mtab[i].lkm_base)) continue; leaky_subr_add_leak(&lk_mtab[i]); } total = lk_beans.lkb_dismissals + lk_beans.lkb_misses + lk_beans.lkb_dups + lk_beans.lkb_follows; leaky_verbose(NULL, 0); leaky_verbose("potential pointers", total); LK_REPORT_BEAN(dismissals); LK_REPORT_BEAN(misses); LK_REPORT_BEAN(dups); LK_REPORT_BEAN(follows); leaky_verbose(NULL, 0); leaky_verbose_end(); leaky_sort(); lk_state = LK_DONE; dump: leaky_dump(filter, dump); return (DCMD_OK); } int leaky_walk_init(mdb_walk_state_t *wsp) { leak_walk_t *lw; leak_bufctl_t *lkb, *cur; uintptr_t addr; int i; if (lk_state != LK_DONE) { mdb_warn("::findleaks must be run %sbefore leaks can be" " walked\n", lk_state != LK_CLEAN ? "to completion " : ""); return (WALK_ERR); } if (wsp->walk_addr == 0) { lkb = NULL; goto found; } addr = wsp->walk_addr; /* * Search the representative leaks first, since that's what we * report in the table. If that fails, search everything. * * Note that we goto found with lkb as the head of desired dup list. */ for (i = 0; i < LK_BUFCTLHSIZE; i++) { for (lkb = lk_bufctl[i]; lkb != NULL; lkb = lkb->lkb_hash_next) if (lkb->lkb_addr == addr) goto found; } for (i = 0; i < LK_BUFCTLHSIZE; i++) { for (lkb = lk_bufctl[i]; lkb != NULL; lkb = lkb->lkb_hash_next) for (cur = lkb; cur != NULL; cur = cur->lkb_next) if (cur->lkb_addr == addr) goto found; } mdb_warn("%p is not a leaked ctl address\n", addr); return (WALK_ERR); found: wsp->walk_data = lw = mdb_zalloc(sizeof (*lw), UM_SLEEP); lw->lkw_ndx = 0; lw->lkw_current = lkb; lw->lkw_hash_next = NULL; return (WALK_NEXT); } leak_bufctl_t * leaky_walk_step_common(mdb_walk_state_t *wsp) { leak_walk_t *lw = wsp->walk_data; leak_bufctl_t *lk; if ((lk = lw->lkw_current) == NULL) { if ((lk = lw->lkw_hash_next) == NULL) { if (wsp->walk_addr) return (NULL); while (lk == NULL && lw->lkw_ndx < LK_BUFCTLHSIZE) lk = lk_bufctl[lw->lkw_ndx++]; if (lw->lkw_ndx == LK_BUFCTLHSIZE) return (NULL); } lw->lkw_hash_next = lk->lkb_hash_next; } lw->lkw_current = lk->lkb_next; return (lk); } int leaky_walk_step(mdb_walk_state_t *wsp) { leak_bufctl_t *lk; if ((lk = leaky_walk_step_common(wsp)) == NULL) return (WALK_DONE); return (leaky_subr_invoke_callback(lk, wsp->walk_callback, wsp->walk_cbdata)); } void leaky_walk_fini(mdb_walk_state_t *wsp) { leak_walk_t *lw = wsp->walk_data; mdb_free(lw, sizeof (leak_walk_t)); } int leaky_buf_walk_step(mdb_walk_state_t *wsp) { leak_bufctl_t *lk; if ((lk = leaky_walk_step_common(wsp)) == NULL) return (WALK_DONE); return (wsp->walk_callback(lk->lkb_bufaddr, NULL, wsp->walk_cbdata)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _LEAKY_H #define _LEAKY_H #include #ifdef __cplusplus extern "C" { #endif extern int leaky_walk_init(mdb_walk_state_t *); extern int leaky_walk_step(mdb_walk_state_t *); extern int leaky_buf_walk_step(mdb_walk_state_t *); extern void leaky_walk_fini(mdb_walk_state_t *); #define FINDLEAKS_USAGE "?[-dfv]" extern int findleaks(uintptr_t, uint_t, int, const mdb_arg_t *); extern void findleaks_help(void); extern void leaky_cleanup(int); #ifdef __cplusplus } #endif #endif /* _LEAKY_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _LEAKY_IMPL_H #define _LEAKY_IMPL_H #ifdef __cplusplus extern "C" { #endif #define LK_NUM_TYPES 8 /* arbitrary */ #ifdef _KERNEL typedef pc_t leak_pc_t; #else typedef uintptr_t leak_pc_t; #endif typedef struct leak_mtab { uintptr_t lkm_base; uintptr_t lkm_limit; uintptr_t lkm_bufctl; /* target-defined */ } leak_mtab_t; typedef struct leak_bufctl { struct leak_bufctl *lkb_hash_next; /* internal use only */ struct leak_bufctl *lkb_next; uintptr_t lkb_addr; /* should be unique */ uintptr_t lkb_bufaddr; uintptr_t lkb_data; uintptr_t lkb_cid; hrtime_t lkb_timestamp; int lkb_dups; uint8_t lkb_type; uint8_t lkb_depth; leak_pc_t lkb_stack[1]; /* actually lkb_depth */ } leak_bufctl_t; #define LEAK_BUFCTL_SIZE(d) (OFFSETOF(leak_bufctl_t, lkb_stack[(d)])) /* * callbacks for target to use */ extern void leaky_grep(uintptr_t, size_t); /* grep a vaddr range */ extern void leaky_grep_ptr(uintptr_t); /* grep a pointer */ extern void leaky_mark_ptr(uintptr_t); /* mark a pointer */ extern int leaky_lookup_marked(uintptr_t, uintptr_t *, size_t *); extern void leaky_add_leak(int, uintptr_t, uintptr_t, hrtime_t, leak_pc_t *, uint_t, uintptr_t, uintptr_t); /* * ::findleaks target interface * * int leaky_subr_estimate(estp) * Validate that any debugging options ::findleaks needs are active, * and store an upper bound on the number of buffers in the system into * estp. * * Returns DCMD_OK to proceed, DCMD_ERR to abort ::findleaks. * * int leaky_subr_fill(mtpp) * Passes a pointer to an mtab pointer, which points to the beginning * of the mtab array. Target should add an entry for each buffer in * the system to the array, and update the pointer to point at the end * of the table (i.e. one mtab beyond the last valid entry). * * The lkm_bufctl entry in each mtab is target-defined. * * Returns DCMD_OK to proceed, DCMD_ERR to abort ::findleaks. * * int leaky_subr_run(void) * Target should invoke leaky_grep() or one of its variants on the * root portions of the virtual address space. Any pointers which * are not reachable from those roots will be reported as leaks. * * Returns DCMD_OK to proceed, DCMD_ERR to abort ::findleaks. * * void leaky_subr_add_leak(mtp) * Invoked once for each leak. Target should call leaky_add_leak() * with the full details of the leak, which will be copied into a * leak_bufctl_t. That will be used in subsequent target invocations * to identify the buffer. * * leaky_add_leak() takes the following arguments: * type target-defined, 0 <= type < LK_NUM_TYPES. Leaks are * grouped by type. * * addr Address of the control structure for this leak. * Should be unique across all types -- ::walk leak and * ::walk leakbuf use this field to identify leaks. * * bufaddr Address of the beginning of the buffer -- reported by * ::walk leakbuf. * * timestamp * High-resolution timestamp, usually of the time of * allocation. Coalesced leaks are represented by * the leak with the earliest timestamp. * * stack, depth * The stack trace for this leak. Leaks with * identical stack traces will be coalesced. * * cid coalesce identifier -- leaks with differing * cids will not be coalesced. * * data target-defined data * * int leaky_subr_bufctl_cmp(lhs, rhs) * Target-defined display order for two leaks. Both leaks will have * the same lkb_type -- full display order is type (lowest-to-highest), * then whatever order this function defines. * * void leaky_subr_dump_start(type) * void leaky_subr_dump(lkb, verbose) * void leaky_subr_dump_end(type) * Used to dump the table of discovered leaks. invoked as: * * for i in 0 .. LK_NUM_TYPES * leaky_subr_dump_start(i) * for lkb in (possibly a subset of) the type i leaks * leaky_subr_dump(lkb, 0) * leaky_subr_dump_end(i) * * if (-d was passed to ::findleaks) * for i in 0 .. LK_NUM_TYPES * for lkb of type i, same subset/order as above * leaky_subr_dump(lkb, 1) * * leaky_subr_dump_start()/end() are always invoked for each type, even * if there are no leaks of that type. leaky_subr_dump() can use the * leaks chained off of lkb_next to access coalesced leaks. lkb_dups * is the length of the dup list. * * int leaky_subr_invoke_callback(lkb, cb, cbarg) * Underlying implementation of '::walk leak' walker -- target should * invoke cb for the passed in leak_bufctl_t. */ extern int leaky_subr_estimate(size_t *); extern int leaky_subr_fill(leak_mtab_t **); extern int leaky_subr_run(void); extern void leaky_subr_add_leak(leak_mtab_t *); extern int leaky_subr_bufctl_cmp(const leak_bufctl_t *, const leak_bufctl_t *); extern void leaky_subr_dump_start(int); extern void leaky_subr_dump(const leak_bufctl_t *, int verbose); extern void leaky_subr_dump_end(int); extern int leaky_subr_invoke_callback(const leak_bufctl_t *, mdb_walk_cb_t, void *); #ifdef __cplusplus } #endif #endif /* _LEAKY_IMPL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "kmem.h" #include "leaky_impl.h" /* * This file defines the genunix target for leaky.c. There are three types * of buffers in the kernel's heap: TYPE_VMEM, for kmem_oversize allocations, * TYPE_KMEM, for kmem_cache_alloc() allocations bufctl_audit_ts, and * TYPE_CACHE, for kmem_cache_alloc() allocation without bufctl_audit_ts. * * See "leaky_impl.h" for the target interface definition. */ #define TYPE_VMEM 0 /* lkb_data is the vmem_seg's size */ #define TYPE_CACHE 1 /* lkb_cid is the bufctl's cache */ #define TYPE_KMEM 2 /* lkb_cid is the bufctl's cache */ #define LKM_CTL_BUFCTL 0 /* normal allocation, PTR is bufctl */ #define LKM_CTL_VMSEG 1 /* oversize allocation, PTR is vmem_seg_t */ #define LKM_CTL_CACHE 2 /* normal alloc, non-debug, PTR is cache */ #define LKM_CTL_MASK 3L #define LKM_CTL(ptr, type) (LKM_CTLPTR(ptr) | (type)) #define LKM_CTLPTR(ctl) ((uintptr_t)(ctl) & ~(LKM_CTL_MASK)) #define LKM_CTLTYPE(ctl) ((uintptr_t)(ctl) & (LKM_CTL_MASK)) static int kmem_lite_count = 0; /* cache of the kernel's version */ /*ARGSUSED*/ static int leaky_mtab(uintptr_t addr, const kmem_bufctl_audit_t *bcp, leak_mtab_t **lmp) { leak_mtab_t *lm = (*lmp)++; lm->lkm_base = (uintptr_t)bcp->bc_addr; lm->lkm_bufctl = LKM_CTL(addr, LKM_CTL_BUFCTL); return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_mtab_addr(uintptr_t addr, void *ignored, leak_mtab_t **lmp) { leak_mtab_t *lm = (*lmp)++; lm->lkm_base = addr; return (WALK_NEXT); } static int leaky_seg(uintptr_t addr, const vmem_seg_t *seg, leak_mtab_t **lmp) { leak_mtab_t *lm = (*lmp)++; lm->lkm_base = seg->vs_start; lm->lkm_limit = seg->vs_end; lm->lkm_bufctl = LKM_CTL(addr, LKM_CTL_VMSEG); return (WALK_NEXT); } static int leaky_vmem_interested(const vmem_t *vmem) { if (strcmp(vmem->vm_name, "kmem_oversize") != 0 && strcmp(vmem->vm_name, "static_alloc") != 0) return (0); return (1); } static int leaky_vmem(uintptr_t addr, const vmem_t *vmem, leak_mtab_t **lmp) { if (!leaky_vmem_interested(vmem)) return (WALK_NEXT); if (mdb_pwalk("vmem_alloc", (mdb_walk_cb_t)leaky_seg, lmp, addr) == -1) mdb_warn("can't walk vmem_alloc for kmem_oversize (%p)", addr); return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_estimate_vmem(uintptr_t addr, const vmem_t *vmem, size_t *est) { if (!leaky_vmem_interested(vmem)) return (WALK_NEXT); *est += (int)(vmem->vm_kstat.vk_alloc.value.ui64 - vmem->vm_kstat.vk_free.value.ui64); return (WALK_NEXT); } static int leaky_interested(const kmem_cache_t *c) { vmem_t vmem; /* * ignore HAT-related caches that happen to derive from kmem_default */ if (strcmp(c->cache_name, "sfmmu1_cache") == 0 || strcmp(c->cache_name, "sf_hment_cache") == 0 || strcmp(c->cache_name, "pa_hment_cache") == 0) return (0); if (mdb_vread(&vmem, sizeof (vmem), (uintptr_t)c->cache_arena) == -1) { mdb_warn("cannot read arena %p for cache '%s'", (uintptr_t)c->cache_arena, c->cache_name); return (0); } /* * If this cache isn't allocating from the kmem_default, * kmem_firewall, or static vmem arenas, we're not interested. */ if (strcmp(vmem.vm_name, "kmem_default") != 0 && strcmp(vmem.vm_name, "kmem_firewall") != 0 && strcmp(vmem.vm_name, "static") != 0) return (0); return (1); } static int leaky_estimate(uintptr_t addr, const kmem_cache_t *c, size_t *est) { if (!leaky_interested(c)) return (WALK_NEXT); *est += kmem_estimate_allocated(addr, c); return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_cache(uintptr_t addr, const kmem_cache_t *c, leak_mtab_t **lmp) { leak_mtab_t *lm = *lmp; mdb_walk_cb_t cb; const char *walk; int audit = (c->cache_flags & KMF_AUDIT); if (!leaky_interested(c)) return (WALK_NEXT); if (audit) { walk = "bufctl"; cb = (mdb_walk_cb_t)leaky_mtab; } else { walk = "kmem"; cb = (mdb_walk_cb_t)leaky_mtab_addr; } if (mdb_pwalk(walk, cb, lmp, addr) == -1) { mdb_warn("can't walk kmem for cache %p (%s)", addr, c->cache_name); return (WALK_DONE); } for (; lm < *lmp; lm++) { lm->lkm_limit = lm->lkm_base + c->cache_bufsize; if (!audit) lm->lkm_bufctl = LKM_CTL(addr, LKM_CTL_CACHE); } return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_scan_buffer(uintptr_t addr, const void *ignored, const kmem_cache_t *c) { leaky_grep(addr, c->cache_bufsize); /* * free, constructed KMF_LITE buffers keep their first uint64_t in * their buftag's redzone. */ if (c->cache_flags & KMF_LITE) { /* LINTED alignment */ kmem_buftag_t *btp = KMEM_BUFTAG(c, addr); leaky_grep((uintptr_t)&btp->bt_redzone, sizeof (btp->bt_redzone)); } return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_scan_cache(uintptr_t addr, const kmem_cache_t *c, void *ignored) { if (!leaky_interested(c)) return (WALK_NEXT); /* * Scan all of the free, constructed buffers, since they may have * pointers to allocated objects. */ if (mdb_pwalk("freemem_constructed", (mdb_walk_cb_t)leaky_scan_buffer, (void *)c, addr) == -1) { mdb_warn("can't walk freemem_constructed for cache %p (%s)", addr, c->cache_name); return (WALK_DONE); } return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_modctl(uintptr_t addr, const struct modctl *m, int *ignored) { struct module mod; char name[MODMAXNAMELEN]; if (m->mod_mp == NULL) return (WALK_NEXT); if (mdb_vread(&mod, sizeof (mod), (uintptr_t)m->mod_mp) == -1) { mdb_warn("couldn't read modctl %p's module", addr); return (WALK_NEXT); } if (mdb_readstr(name, sizeof (name), (uintptr_t)m->mod_modname) == -1) (void) mdb_snprintf(name, sizeof (name), "0x%p", addr); leaky_grep((uintptr_t)m->mod_mp, sizeof (struct module)); leaky_grep((uintptr_t)mod.data, mod.data_size); leaky_grep((uintptr_t)mod.bss, mod.bss_size); return (WALK_NEXT); } static int leaky_thread(uintptr_t addr, const kthread_t *t, unsigned long *pagesize) { uintptr_t size, base = (uintptr_t)t->t_stkbase; uintptr_t stk = (uintptr_t)t->t_stk; /* * If this thread isn't in memory, we can't look at its stack. This * may result in false positives, so we print a warning. */ if (!(t->t_schedflag & TS_LOAD)) { mdb_printf("findleaks: thread %p's stack swapped out; " "false positives possible\n", addr); return (WALK_NEXT); } if (t->t_state != TS_FREE) leaky_grep(base, stk - base); /* * There is always gunk hanging out between t_stk and the page * boundary. If this thread structure wasn't kmem allocated, * this will include the thread structure itself. If the thread * _is_ kmem allocated, we'll be able to get to it via allthreads. */ size = *pagesize - (stk & (*pagesize - 1)); leaky_grep(stk, size); return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_kstat(uintptr_t addr, vmem_seg_t *seg, void *ignored) { leaky_grep(seg->vs_start, seg->vs_end - seg->vs_start); return (WALK_NEXT); } static void leaky_kludge(void) { GElf_Sym sym; mdb_ctf_id_t id, rid; int max_mem_nodes; uintptr_t *counters; size_t ncounters; ssize_t hwpm_size; int idx; /* * Because of DR, the page counters (which live in the kmem64 segment) * can point into kmem_alloc()ed memory. The "page_counters" array * is multi-dimensional, and each entry points to an array of * "hw_page_map_t"s which is "max_mem_nodes" in length. * * To keep this from having too much grotty knowledge of internals, * we use CTF data to get the size of the structure. For simplicity, * we treat the page_counters array as a flat array of pointers, and * use its size to determine how much to scan. Unused entries will * be NULL. */ if (mdb_lookup_by_name("page_counters", &sym) == -1) { mdb_warn("unable to lookup page_counters"); return; } if (mdb_readvar(&max_mem_nodes, "max_mem_nodes") == -1) { mdb_warn("unable to read max_mem_nodes"); return; } if (mdb_ctf_lookup_by_name("unix`hw_page_map_t", &id) == -1 || mdb_ctf_type_resolve(id, &rid) == -1 || (hwpm_size = mdb_ctf_type_size(rid)) < 0) { mdb_warn("unable to lookup unix`hw_page_map_t"); return; } counters = mdb_alloc(sym.st_size, UM_SLEEP | UM_GC); if (mdb_vread(counters, sym.st_size, (uintptr_t)sym.st_value) == -1) { mdb_warn("unable to read page_counters"); return; } ncounters = sym.st_size / sizeof (counters); for (idx = 0; idx < ncounters; idx++) { uintptr_t addr = counters[idx]; if (addr != 0) leaky_grep(addr, hwpm_size * max_mem_nodes); } } int leaky_subr_estimate(size_t *estp) { uintptr_t panicstr; int state; if ((state = mdb_get_state()) == MDB_STATE_RUNNING) { mdb_warn("findleaks: can only be run on a system " "dump or under kmdb; see dumpadm(8)\n"); return (DCMD_ERR); } if (mdb_readvar(&panicstr, "panicstr") == -1) { mdb_warn("can't read variable 'panicstr'"); return (DCMD_ERR); } if (state != MDB_STATE_STOPPED && panicstr == 0) { mdb_warn("findleaks: cannot be run on a live dump.\n"); return (DCMD_ERR); } if (mdb_walk("kmem_cache", (mdb_walk_cb_t)leaky_estimate, estp) == -1) { mdb_warn("couldn't walk 'kmem_cache'"); return (DCMD_ERR); } if (*estp == 0) { mdb_warn("findleaks: no buffers found\n"); return (DCMD_ERR); } if (mdb_walk("vmem", (mdb_walk_cb_t)leaky_estimate_vmem, estp) == -1) { mdb_warn("couldn't walk 'vmem'"); return (DCMD_ERR); } return (DCMD_OK); } int leaky_subr_fill(leak_mtab_t **lmpp) { if (mdb_walk("vmem", (mdb_walk_cb_t)leaky_vmem, lmpp) == -1) { mdb_warn("couldn't walk 'vmem'"); return (DCMD_ERR); } if (mdb_walk("kmem_cache", (mdb_walk_cb_t)leaky_cache, lmpp) == -1) { mdb_warn("couldn't walk 'kmem_cache'"); return (DCMD_ERR); } if (mdb_readvar(&kmem_lite_count, "kmem_lite_count") == -1) { mdb_warn("couldn't read 'kmem_lite_count'"); kmem_lite_count = 0; } else if (kmem_lite_count > 16) { mdb_warn("kmem_lite_count nonsensical, ignored\n"); kmem_lite_count = 0; } return (DCMD_OK); } int leaky_subr_run(void) { unsigned long ps = PAGESIZE; uintptr_t kstat_arena; uintptr_t dmods; leaky_kludge(); if (mdb_walk("kmem_cache", (mdb_walk_cb_t)leaky_scan_cache, NULL) == -1) { mdb_warn("couldn't walk 'kmem_cache'"); return (DCMD_ERR); } if (mdb_walk("modctl", (mdb_walk_cb_t)leaky_modctl, NULL) == -1) { mdb_warn("couldn't walk 'modctl'"); return (DCMD_ERR); } /* * If kmdb is loaded, we need to walk it's module list, since kmdb * modctl structures can reference kmem allocations. */ if ((mdb_readvar(&dmods, "kdi_dmods") != -1) && (dmods != 0)) (void) mdb_pwalk("modctl", (mdb_walk_cb_t)leaky_modctl, NULL, dmods); if (mdb_walk("thread", (mdb_walk_cb_t)leaky_thread, &ps) == -1) { mdb_warn("couldn't walk 'thread'"); return (DCMD_ERR); } if (mdb_walk("deathrow", (mdb_walk_cb_t)leaky_thread, &ps) == -1) { mdb_warn("couldn't walk 'deathrow'"); return (DCMD_ERR); } if (mdb_readvar(&kstat_arena, "kstat_arena") == -1) { mdb_warn("couldn't read 'kstat_arena'"); return (DCMD_ERR); } if (mdb_pwalk("vmem_alloc", (mdb_walk_cb_t)leaky_kstat, NULL, kstat_arena) == -1) { mdb_warn("couldn't walk kstat vmem arena"); return (DCMD_ERR); } return (DCMD_OK); } void leaky_subr_add_leak(leak_mtab_t *lmp) { uintptr_t addr = LKM_CTLPTR(lmp->lkm_bufctl); size_t depth; switch (LKM_CTLTYPE(lmp->lkm_bufctl)) { case LKM_CTL_VMSEG: { vmem_seg_t vs; if (mdb_vread(&vs, sizeof (vs), addr) == -1) { mdb_warn("couldn't read leaked vmem_seg at addr %p", addr); return; } depth = MIN(vs.vs_depth, VMEM_STACK_DEPTH); leaky_add_leak(TYPE_VMEM, addr, vs.vs_start, vs.vs_timestamp, vs.vs_stack, depth, 0, (vs.vs_end - vs.vs_start)); break; } case LKM_CTL_BUFCTL: { kmem_bufctl_audit_t bc; if (mdb_vread(&bc, sizeof (bc), addr) == -1) { mdb_warn("couldn't read leaked bufctl at addr %p", addr); return; } depth = MIN(bc.bc_depth, KMEM_STACK_DEPTH); /* * The top of the stack will be kmem_cache_alloc+offset. * Since the offset in kmem_cache_alloc() isn't interesting * we skip that frame for the purposes of uniquifying stacks. * * We also use the cache pointer as the leaks's cid, to * prevent the coalescing of leaks from different caches. */ if (depth > 0) depth--; leaky_add_leak(TYPE_KMEM, addr, (uintptr_t)bc.bc_addr, bc.bc_timestamp, bc.bc_stack + 1, depth, (uintptr_t)bc.bc_cache, 0); break; } case LKM_CTL_CACHE: { kmem_cache_t cache; kmem_buftag_lite_t bt; pc_t caller; int depth = 0; /* * For KMF_LITE caches, we can get the allocation PC * out of the buftag structure. */ if (mdb_vread(&cache, sizeof (cache), addr) != -1 && (cache.cache_flags & KMF_LITE) && kmem_lite_count > 0 && mdb_vread(&bt, sizeof (bt), /* LINTED alignment */ (uintptr_t)KMEM_BUFTAG(&cache, lmp->lkm_base)) != -1) { caller = bt.bt_history[0]; depth = 1; } leaky_add_leak(TYPE_CACHE, lmp->lkm_base, lmp->lkm_base, 0, &caller, depth, addr, addr); break; } default: mdb_warn("internal error: invalid leak_bufctl_t\n"); break; } } static void leaky_subr_caller(const pc_t *stack, uint_t depth, char *buf, uintptr_t *pcp) { int i; GElf_Sym sym; uintptr_t pc = 0; buf[0] = 0; for (i = 0; i < depth; i++) { pc = stack[i]; if (mdb_lookup_by_addr(pc, MDB_SYM_FUZZY, buf, MDB_SYM_NAMLEN, &sym) == -1) continue; if (strncmp(buf, "kmem_", 5) == 0) continue; if (strncmp(buf, "vmem_", 5) == 0) continue; *pcp = pc; return; } /* * We're only here if the entire call chain begins with "kmem_"; * this shouldn't happen, but we'll just use the last caller. */ *pcp = pc; } int leaky_subr_bufctl_cmp(const leak_bufctl_t *lhs, const leak_bufctl_t *rhs) { char lbuf[MDB_SYM_NAMLEN], rbuf[MDB_SYM_NAMLEN]; uintptr_t lcaller, rcaller; int rval; leaky_subr_caller(lhs->lkb_stack, lhs->lkb_depth, lbuf, &lcaller); leaky_subr_caller(rhs->lkb_stack, lhs->lkb_depth, rbuf, &rcaller); if (rval = strcmp(lbuf, rbuf)) return (rval); if (lcaller < rcaller) return (-1); if (lcaller > rcaller) return (1); if (lhs->lkb_data < rhs->lkb_data) return (-1); if (lhs->lkb_data > rhs->lkb_data) return (1); return (0); } /* * Global state variables used by the leaky_subr_dump_* routines. Note that * they are carefully cleared before use. */ static int lk_vmem_seen; static int lk_cache_seen; static int lk_kmem_seen; static size_t lk_ttl; static size_t lk_bytes; void leaky_subr_dump_start(int type) { switch (type) { case TYPE_VMEM: lk_vmem_seen = 0; break; case TYPE_CACHE: lk_cache_seen = 0; break; case TYPE_KMEM: lk_kmem_seen = 0; break; default: break; } lk_ttl = 0; lk_bytes = 0; } void leaky_subr_dump(const leak_bufctl_t *lkb, int verbose) { const leak_bufctl_t *cur; kmem_cache_t cache; size_t min, max, size; char sz[30]; char c[MDB_SYM_NAMLEN]; uintptr_t caller; if (verbose) { lk_ttl = 0; lk_bytes = 0; } switch (lkb->lkb_type) { case TYPE_VMEM: if (!verbose && !lk_vmem_seen) { lk_vmem_seen = 1; mdb_printf("%-16s %7s %?s %s\n", "BYTES", "LEAKED", "VMEM_SEG", "CALLER"); } min = max = lkb->lkb_data; for (cur = lkb; cur != NULL; cur = cur->lkb_next) { size = cur->lkb_data; if (size < min) min = size; if (size > max) max = size; lk_ttl++; lk_bytes += size; } if (min == max) (void) mdb_snprintf(sz, sizeof (sz), "%ld", min); else (void) mdb_snprintf(sz, sizeof (sz), "%ld-%ld", min, max); if (!verbose) { leaky_subr_caller(lkb->lkb_stack, lkb->lkb_depth, c, &caller); if (caller != 0) { (void) mdb_snprintf(c, sizeof (c), "%a", caller); } else { (void) mdb_snprintf(c, sizeof (c), "%s", "?"); } mdb_printf("%-16s %7d %?p %s\n", sz, lkb->lkb_dups + 1, lkb->lkb_addr, c); } else { mdb_arg_t v; if (lk_ttl == 1) mdb_printf("kmem_oversize leak: 1 vmem_seg, " "%ld bytes\n", lk_bytes); else mdb_printf("kmem_oversize leak: %d vmem_segs, " "%s bytes each, %ld bytes total\n", lk_ttl, sz, lk_bytes); v.a_type = MDB_TYPE_STRING; v.a_un.a_str = "-v"; if (mdb_call_dcmd("vmem_seg", lkb->lkb_addr, DCMD_ADDRSPEC, 1, &v) == -1) { mdb_warn("'%p::vmem_seg -v' failed", lkb->lkb_addr); } } return; case TYPE_CACHE: if (!verbose && !lk_cache_seen) { lk_cache_seen = 1; if (lk_vmem_seen) mdb_printf("\n"); mdb_printf("%-?s %7s %?s %s\n", "CACHE", "LEAKED", "BUFFER", "CALLER"); } if (mdb_vread(&cache, sizeof (cache), lkb->lkb_data) == -1) { /* * This _really_ shouldn't happen; we shouldn't * have been able to get this far if this * cache wasn't readable. */ mdb_warn("can't read cache %p for leaked " "buffer %p", lkb->lkb_data, lkb->lkb_addr); return; } lk_ttl += lkb->lkb_dups + 1; lk_bytes += (lkb->lkb_dups + 1) * cache.cache_bufsize; caller = (lkb->lkb_depth == 0) ? 0 : lkb->lkb_stack[0]; if (caller != 0) { (void) mdb_snprintf(c, sizeof (c), "%a", caller); } else { (void) mdb_snprintf(c, sizeof (c), "%s", (verbose) ? "" : "?"); } if (!verbose) { mdb_printf("%0?p %7d %0?p %s\n", lkb->lkb_cid, lkb->lkb_dups + 1, lkb->lkb_addr, c); } else { if (lk_ttl == 1) mdb_printf("%s leak: 1 buffer, %ld bytes,\n", cache.cache_name, lk_bytes); else mdb_printf("%s leak: %d buffers, " "%ld bytes each, %ld bytes total,\n", cache.cache_name, lk_ttl, cache.cache_bufsize, lk_bytes); mdb_printf(" sample addr %p%s%s\n", lkb->lkb_addr, (caller == 0) ? "" : ", caller ", c); } return; case TYPE_KMEM: if (!verbose && !lk_kmem_seen) { lk_kmem_seen = 1; if (lk_vmem_seen || lk_cache_seen) mdb_printf("\n"); mdb_printf("%-?s %7s %?s %s\n", "CACHE", "LEAKED", "BUFCTL", "CALLER"); } if (mdb_vread(&cache, sizeof (cache), lkb->lkb_cid) == -1) { /* * This _really_ shouldn't happen; we shouldn't * have been able to get this far if this * cache wasn't readable. */ mdb_warn("can't read cache %p for leaked " "bufctl %p", lkb->lkb_cid, lkb->lkb_addr); return; } lk_ttl += lkb->lkb_dups + 1; lk_bytes += (lkb->lkb_dups + 1) * cache.cache_bufsize; if (!verbose) { leaky_subr_caller(lkb->lkb_stack, lkb->lkb_depth, c, &caller); if (caller != 0) { (void) mdb_snprintf(c, sizeof (c), "%a", caller); } else { (void) mdb_snprintf(c, sizeof (c), "%s", "?"); } mdb_printf("%0?p %7d %0?p %s\n", lkb->lkb_cid, lkb->lkb_dups + 1, lkb->lkb_addr, c); } else { mdb_arg_t v; if (lk_ttl == 1) mdb_printf("%s leak: 1 buffer, %ld bytes\n", cache.cache_name, lk_bytes); else mdb_printf("%s leak: %d buffers, " "%ld bytes each, %ld bytes total\n", cache.cache_name, lk_ttl, cache.cache_bufsize, lk_bytes); v.a_type = MDB_TYPE_STRING; v.a_un.a_str = "-v"; if (mdb_call_dcmd("bufctl", lkb->lkb_addr, DCMD_ADDRSPEC, 1, &v) == -1) { mdb_warn("'%p::bufctl -v' failed", lkb->lkb_addr); } } return; default: return; } } void leaky_subr_dump_end(int type) { int i; int width; const char *leaks; switch (type) { case TYPE_VMEM: if (!lk_vmem_seen) return; width = 16; leaks = "kmem_oversize leak"; break; case TYPE_CACHE: if (!lk_cache_seen) return; width = sizeof (uintptr_t) * 2; leaks = "buffer"; break; case TYPE_KMEM: if (!lk_kmem_seen) return; width = sizeof (uintptr_t) * 2; leaks = "buffer"; break; default: return; } for (i = 0; i < 72; i++) mdb_printf("-"); mdb_printf("\n%*s %7ld %s%s, %ld byte%s\n", width, "Total", lk_ttl, leaks, (lk_ttl == 1) ? "" : "s", lk_bytes, (lk_bytes == 1) ? "" : "s"); } int leaky_subr_invoke_callback(const leak_bufctl_t *lkb, mdb_walk_cb_t cb, void *cbdata) { kmem_bufctl_audit_t bc; vmem_seg_t vs; switch (lkb->lkb_type) { case TYPE_VMEM: if (mdb_vread(&vs, sizeof (vs), lkb->lkb_addr) == -1) { mdb_warn("unable to read vmem_seg at %p", lkb->lkb_addr); return (WALK_NEXT); } return (cb(lkb->lkb_addr, &vs, cbdata)); case TYPE_CACHE: return (cb(lkb->lkb_addr, NULL, cbdata)); case TYPE_KMEM: if (mdb_vread(&bc, sizeof (bc), lkb->lkb_addr) == -1) { mdb_warn("unable to read bufctl at %p", lkb->lkb_addr); return (WALK_NEXT); } return (cb(lkb->lkb_addr, &bc, cbdata)); default: return (WALK_NEXT); } } /* * 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. */ #include "lgrp.h" #include #include #include #include int print_range(int start, int end, int separator) { int count; char tmp; char *format; if (start == end) { /* Unfortunately, mdb_printf returns void */ format = separator ? ", %d" : "%d"; mdb_printf(format, start); count = mdb_snprintf(&tmp, 1, format, start); } else { format = separator ? ", %d-%d" : "%d-%d"; mdb_printf(format, start, end); count = mdb_snprintf(&tmp, 1, format, start, end); } return (count); } void print_cpuset_range(ulong_t *cs, int words, int width) { int i, j; ulong_t m; int in = 0; int start; int end; int count = 0; int sep = 0; for (i = 0; i < words; i++) for (j = 0, m = 1; j < BT_NBIPUL; j++, m <<= 1) if (cs[i] & m) { if (in == 0) { start = i * BT_NBIPUL + j; in = 1; } } else { if (in == 1) { end = i * BT_NBIPUL + j - 1; count += print_range(start, end, sep); sep = 1; in = 0; } } if (in == 1) { end = i * BT_NBIPUL - 1; count += print_range(start, end, sep); } /* * print width - count spaces */ if (width > count) mdb_printf("%*s", width - count, ""); } typedef struct lgrp_cpu_walk { uintptr_t lcw_firstcpu; int lcw_cpusleft; } lgrp_cpu_walk_t; int lgrp_cpulist_walk_init(mdb_walk_state_t *wsp) { lgrp_cpu_walk_t *lcw; lgrp_t lgrp; lcw = mdb_alloc(sizeof (lgrp_cpu_walk_t), UM_SLEEP | UM_GC); if (mdb_vread(&lgrp, sizeof (struct lgrp), wsp->walk_addr) == -1) { mdb_warn("couldn't read 'lgrp' at %p", wsp->walk_addr); return (WALK_ERR); } lcw->lcw_firstcpu = (uintptr_t)lgrp.lgrp_cpu; lcw->lcw_cpusleft = lgrp.lgrp_cpucnt; wsp->walk_data = lcw; wsp->walk_addr = lcw->lcw_firstcpu; return (WALK_NEXT); } int lgrp_cpulist_walk_step(mdb_walk_state_t *wsp) { lgrp_cpu_walk_t *lcw = (lgrp_cpu_walk_t *)wsp->walk_data; uintptr_t addr = (uintptr_t)wsp->walk_addr; cpu_t cpu; int status; if (lcw->lcw_cpusleft-- == 0) return (WALK_DONE); if (mdb_vread(&cpu, sizeof (cpu_t), addr) == -1) { mdb_warn("couldn't read 'cpu' at %p", addr); return (WALK_ERR); } status = wsp->walk_callback(addr, &cpu, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); addr = (uintptr_t)cpu.cpu_next_lgrp; wsp->walk_addr = addr; if (lcw->lcw_cpusleft == 0 && addr != lcw->lcw_firstcpu) { mdb_warn("number of cpus in lgroup cpu != lgroup cpucnt\n"); return (WALK_ERR); } return (WALK_NEXT); } typedef struct lgrp_cpuwalk_cbdata { uint_t lcc_opt_p; uint_t lcc_count; uint_t lcc_used; uint_t *lcc_psrsetid; ulong_t **lcc_cpuset; uint_t *lcc_cpucnt; int *lcc_loadavg; } lgrp_cpuwalk_cbdata_t; /* ARGSUSED */ static int lgrp_cpuwalk_callback(uintptr_t addr, const void *arg, void *cb_data) { cpu_t *cpu = (cpu_t *)arg; lgrp_cpuwalk_cbdata_t *lcc = (lgrp_cpuwalk_cbdata_t *)cb_data; uint_t opt_p = lcc->lcc_opt_p; int offset = 0; /* * if opt_p is set, we're going to break up info for * each lgrp by processor set. */ if (opt_p != 0) { cpupartid_t cp_id; cpupart_t cpupart; lpl_t lpl; if (mdb_vread(&cpupart, sizeof (cpupart_t), (uintptr_t)cpu->cpu_part) == -1) { mdb_warn("cannot read cpu partition at %p", cpu->cpu_part); return (WALK_ERR); } cp_id = cpupart.cp_id; for (offset = 0; offset < lcc->lcc_used; offset++) if (cp_id == lcc->lcc_psrsetid[offset]) { goto found; } if (offset >= lcc->lcc_count) { mdb_warn( "number of cpu partitions changed during walk"); return (WALK_ERR); } lcc->lcc_psrsetid[offset] = cp_id; lcc->lcc_used++; if (mdb_vread(&lpl, sizeof (lpl_t), (uintptr_t)cpu->cpu_lpl) == -1) { mdb_warn("Cannot read lpl at %p", cpu->cpu_lpl); return (WALK_ERR); } lcc->lcc_loadavg[offset] = lpl.lpl_loadavg; } found: lcc->lcc_cpucnt[offset]++; BT_SET(lcc->lcc_cpuset[offset], cpu->cpu_id); return (WALK_NEXT); } /* ARGSUSED */ int lgrp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { lgrp_t lgrp; lgrp_cpuwalk_cbdata_t lcc; int cpusetsize; int lcpu; /* cpus in lgrp */ int _ncpu; int opt_p = 0; /* display partition fraction loads */ int opt_q = 0; /* display only address. */ int i; const char *s_index = NULL, *s_handle = NULL, *s_parent = NULL; uintptr_t index = 0; uintptr_t handle = 0; uintptr_t parent = 0; int filters = 0; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("lgrptbl", "lgrp", argc, argv) == -1) { mdb_warn("can't walk 'lgrps'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'p', MDB_OPT_SETBITS, TRUE, &opt_p, 'q', MDB_OPT_SETBITS, TRUE, &opt_q, 'P', MDB_OPT_STR, &s_parent, 'i', MDB_OPT_STR, &s_index, 'h', MDB_OPT_STR, &s_handle, NULL) != argc) return (DCMD_USAGE); if (s_index != NULL) filters++; if (s_handle != NULL) filters++; if (s_parent != NULL) filters++; if (flags & DCMD_PIPE_OUT) opt_q = B_TRUE; if (s_index != NULL) index = mdb_strtoull(s_index); if (s_parent != NULL) parent = mdb_strtoull(s_parent); if (s_handle != NULL) { if (strcmp(s_handle, "NULL") == 0) handle = (uintptr_t)LGRP_NULL_HANDLE; else if (strcmp(s_handle, "DEFAULT") == 0) handle = (uintptr_t)LGRP_DEFAULT_HANDLE; else handle = mdb_strtoull(s_handle); } if (DCMD_HDRSPEC(flags) && !opt_q) { if (opt_p == 0) mdb_printf("%9s %?s %?s %?s %9s %9s\n", "LGRPID", "ADDR", "PARENT", "PLATHAND", "#CPU", "CPUS"); else mdb_printf("%9s %9s %9s %9s %9s\n", "LGRPID", "PSRSETID", "LOAD", "#CPU", "CPUS"); } if (mdb_vread(&lgrp, sizeof (struct lgrp), addr) == -1) { mdb_warn("unable to read 'lgrp' at %p", addr); return (DCMD_ERR); } /* * Do not report free lgrp unless specifically asked for. */ if ((lgrp.lgrp_id == LGRP_NONE) && ((s_index == NULL) || ((int)index != LGRP_NONE))) return (DCMD_OK); /* * If lgrp doesn't pass filtering criteria, don't print anything and * just return. */ if (filters) { if ((s_parent != NULL) && parent != (uintptr_t)lgrp.lgrp_parent) return (DCMD_OK); if ((s_index != NULL) && index != (uintptr_t)lgrp.lgrp_id) return (DCMD_OK); if ((s_handle != NULL) && handle != (uintptr_t)lgrp.lgrp_plathand) return (DCMD_OK); } if (opt_q) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } /* * figure out what cpus we've got */ if (mdb_readsym(&_ncpu, sizeof (int), "_ncpu") == -1) { mdb_warn("symbol '_ncpu' not found"); return (DCMD_ERR); } /* * allocate enough space for set of longs to hold cpuid bitfield */ if (opt_p) lcpu = lgrp.lgrp_cpucnt; else lcpu = 1; cpusetsize = BT_BITOUL(_ncpu) * sizeof (uintptr_t); lcc.lcc_used = 0; lcc.lcc_cpucnt = mdb_zalloc(sizeof (uint_t) * lcpu, UM_SLEEP | UM_GC); lcc.lcc_psrsetid = mdb_zalloc(sizeof (uint_t) * lcpu, UM_SLEEP | UM_GC); lcc.lcc_cpuset = mdb_zalloc(sizeof (uintptr_t) * lcpu, UM_SLEEP | UM_GC); for (i = 0; i < lcpu; i++) lcc.lcc_cpuset[i] = mdb_zalloc(cpusetsize, UM_SLEEP | UM_GC); lcc.lcc_loadavg = mdb_zalloc(sizeof (int) * lcpu, UM_SLEEP | UM_GC); lcc.lcc_count = lcpu; lcc.lcc_opt_p = opt_p; if (mdb_pwalk("lgrp_cpulist", lgrp_cpuwalk_callback, &lcc, addr) == -1) { mdb_warn("unable to walk lgrp_cpulist"); } if (opt_p == 0) { if (lgrp.lgrp_plathand == LGRP_NULL_HANDLE) { mdb_printf("%9d %?p %?p %?s %9d ", lgrp.lgrp_id, addr, lgrp.lgrp_parent, "NULL", lgrp.lgrp_cpucnt); } else if (lgrp.lgrp_plathand == LGRP_DEFAULT_HANDLE) { mdb_printf("%9d %?p %?p %?s %9d ", lgrp.lgrp_id, addr, lgrp.lgrp_parent, "DEFAULT", lgrp.lgrp_cpucnt); } else { mdb_printf("%9d %?p %?p %?p %9d ", lgrp.lgrp_id, addr, lgrp.lgrp_parent, lgrp.lgrp_plathand, lgrp.lgrp_cpucnt); } if (lgrp.lgrp_cpucnt != 0) { print_cpuset_range(lcc.lcc_cpuset[0], cpusetsize/sizeof (ulong_t), 0); } mdb_printf("\n"); } else { for (i = 0; i < lcc.lcc_used; i++) { mdb_printf("%9d %9d %9d %9d ", lgrp.lgrp_id, lcc.lcc_psrsetid[i], lcc.lcc_loadavg[i], lcc.lcc_cpucnt[i]); if (lcc.lcc_cpucnt[i]) print_cpuset_range(lcc.lcc_cpuset[i], cpusetsize/sizeof (ulong_t), 0); mdb_printf("\n"); } } return (DCMD_OK); } typedef struct lgrp_walk_data { int lwd_nlgrps; uintptr_t *lwd_lgrp_tbl; int lwd_iter; } lgrp_walk_data_t; int lgrp_walk_init(mdb_walk_state_t *wsp) { lgrp_walk_data_t *lwd; GElf_Sym sym; lwd = mdb_zalloc(sizeof (lgrp_walk_data_t), UM_SLEEP | UM_GC); if (mdb_readsym(&lwd->lwd_nlgrps, sizeof (int), "lgrp_alloc_max") == -1) { mdb_warn("symbol 'lgrp_alloc_max' not found"); return (WALK_ERR); } if (lwd->lwd_nlgrps < 0) { mdb_warn("lgrp_alloc_max of bounds (%d)\n", lwd->lwd_nlgrps); return (WALK_ERR); } lwd->lwd_nlgrps++; if (mdb_lookup_by_name("lgrp_table", &sym) == -1) { mdb_warn("failed to find 'lgrp_table'"); return (WALK_ERR); } /* Get number of valid entries in lgrp_table */ if (sym.st_size < lwd->lwd_nlgrps * sizeof (lgrp_t *)) { mdb_warn("lgrp_table size inconsistent with lgrp_alloc_max"); return (WALK_ERR); } lwd->lwd_lgrp_tbl = mdb_alloc(sym.st_size, UM_SLEEP | UM_GC); if (mdb_readsym(lwd->lwd_lgrp_tbl, lwd->lwd_nlgrps * sizeof (lgrp_t *), "lgrp_table") == -1) { mdb_warn("unable to read lgrp_table"); return (WALK_ERR); } wsp->walk_data = lwd; wsp->walk_addr = lwd->lwd_lgrp_tbl[0]; return (WALK_NEXT); } /* * Common routine for several walkers. * Read lgroup from wsp->walk_addr and call wsp->walk_callback for it. * Normally returns the result of the callback. * Returns WALK_DONE if walk_addr is NULL and WALK_ERR if cannot read the * lgroup. */ static int lgrp_walk_step_common(mdb_walk_state_t *wsp) { lgrp_t lgrp; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&lgrp, sizeof (lgrp_t), wsp->walk_addr) == -1) { mdb_warn("unable to read lgrp at %p", wsp->walk_addr); return (WALK_ERR); } return (wsp->walk_callback(wsp->walk_addr, &lgrp, wsp->walk_cbdata)); } /* * Get one lgroup from the lgroup table and adjust lwd_iter to point to the next * one. */ int lgrp_walk_step(mdb_walk_state_t *wsp) { lgrp_walk_data_t *lwd = wsp->walk_data; int status = lgrp_walk_step_common(wsp); if (status == WALK_NEXT) { lwd->lwd_iter++; if (lwd->lwd_iter >= lwd->lwd_nlgrps) { status = WALK_DONE; } else { wsp->walk_addr = lwd->lwd_lgrp_tbl[lwd->lwd_iter]; if (wsp->walk_addr == 0) { mdb_warn("NULL lgrp pointer in lgrp_table[%d]", lwd->lwd_iter); return (WALK_ERR); } } } return (status); } /* * Initialize walker to traverse parents of lgroups. Nothing to do here. */ /* ARGSUSED */ int lgrp_parents_walk_init(mdb_walk_state_t *wsp) { return (WALK_NEXT); } /* * Call wsp callback on current lgroup in wsp and replace the lgroup with its * parent. */ int lgrp_parents_walk_step(mdb_walk_state_t *wsp) { lgrp_t lgrp; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&lgrp, sizeof (struct lgrp), wsp->walk_addr) == -1) { mdb_warn("couldn't read 'lgrp' at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &lgrp, wsp->walk_cbdata); if (status == WALK_NEXT) wsp->walk_addr = (uintptr_t)lgrp.lgrp_parent; return (status); } /* * Given the set return the ID of the first member of the set. * Returns LGRP_NONE if the set has no elements smaller than max_lgrp. */ static lgrp_id_t lgrp_set_get_first(klgrpset_t set, int max_lgrp) { lgrp_id_t id; klgrpset_t bit = 1; if (set == (klgrpset_t)0) return (LGRP_NONE); for (id = 0; (id < max_lgrp) && !(set & bit); id++, bit <<= 1) ; if (id >= max_lgrp) id = LGRP_NONE; return (id); } /* * lgrp_set_walk_data is used to walk lgroups specified by a set. * On every iteration one element is removed from the set. */ typedef struct lgrp_set_walk_data { int lswd_nlgrps; /* Number of lgroups */ uintptr_t *lwsd_lgrp_tbl; /* Full lgroup table */ klgrpset_t lwsd_set; /* Set of lgroups to walk */ } lgrp_set_walk_data_t; /* * Initialize iterator for walkers over a set of lgroups */ static int lgrp_set_walk_init(mdb_walk_state_t *wsp, klgrpset_t set) { lgrp_set_walk_data_t *lwsd; int nlgrps; lgrp_id_t id; GElf_Sym sym; /* Nothing to do if the set is empty */ if (set == (klgrpset_t)0) return (WALK_DONE); lwsd = mdb_zalloc(sizeof (lgrp_set_walk_data_t), UM_SLEEP | UM_GC); /* Get the total number of lgroups */ if (mdb_readsym(&nlgrps, sizeof (int), "lgrp_alloc_max") == -1) { mdb_warn("symbol 'lgrp_alloc_max' not found"); return (WALK_ERR); } if (nlgrps < 0) { mdb_warn("lgrp_alloc_max of bounds (%d)\n", nlgrps); return (WALK_ERR); } nlgrps++; /* Find ID of the first lgroup in the set */ if ((id = lgrp_set_get_first(set, nlgrps)) == LGRP_NONE) { mdb_warn("No set elements within %d lgroups\n", nlgrps); return (WALK_ERR); } /* Read lgroup_table and copy it to lwsd_lgrp_tbl */ if (mdb_lookup_by_name("lgrp_table", &sym) == -1) { mdb_warn("failed to find 'lgrp_table'"); return (WALK_ERR); } /* Get number of valid entries in lgrp_table */ if (sym.st_size < nlgrps * sizeof (lgrp_t *)) { mdb_warn("lgrp_table size inconsistent with lgrp_alloc_max"); return (WALK_ERR); } lwsd->lwsd_lgrp_tbl = mdb_alloc(sym.st_size, UM_SLEEP | UM_GC); lwsd->lswd_nlgrps = nlgrps; if (mdb_readsym(lwsd->lwsd_lgrp_tbl, nlgrps * sizeof (lgrp_t *), "lgrp_table") == -1) { mdb_warn("unable to read lgrp_table"); return (WALK_ERR); } wsp->walk_data = lwsd; /* Save the first lgroup from the set and remove it from the set */ wsp->walk_addr = lwsd->lwsd_lgrp_tbl[id]; lwsd->lwsd_set = set & ~(1 << id); return (WALK_NEXT); } /* * Get current lgroup and advance the lgroup to the next one in the lwsd_set. */ int lgrp_set_walk_step(mdb_walk_state_t *wsp) { lgrp_id_t id = 0; lgrp_set_walk_data_t *lwsd = wsp->walk_data; int status = lgrp_walk_step_common(wsp); if (status == WALK_NEXT) { id = lgrp_set_get_first(lwsd->lwsd_set, lwsd->lswd_nlgrps); if (id == LGRP_NONE) { status = WALK_DONE; } else { /* Move to the next lgroup in the set */ wsp->walk_addr = lwsd->lwsd_lgrp_tbl[id]; /* Remove id from the set */ lwsd->lwsd_set = lwsd->lwsd_set & ~(1 << id); } } return (status); } /* * Initialize resource walker for a given lgroup and resource. The lgroup * address is specified in walk_addr. */ static int lgrp_rsrc_walk_init(mdb_walk_state_t *wsp, int resource) { lgrp_t lgrp; if (mdb_vread(&lgrp, sizeof (struct lgrp), wsp->walk_addr) == -1) { mdb_warn("couldn't read 'lgrp' at %p", wsp->walk_addr); return (WALK_ERR); } return (lgrp_set_walk_init(wsp, lgrp.lgrp_set[resource])); } /* * Initialize CPU resource walker */ int lgrp_rsrc_cpu_walk_init(mdb_walk_state_t *wsp) { return (lgrp_rsrc_walk_init(wsp, LGRP_RSRC_CPU)); } /* * Initialize memory resource walker */ int lgrp_rsrc_mem_walk_init(mdb_walk_state_t *wsp) { return (lgrp_rsrc_walk_init(wsp, LGRP_RSRC_MEM)); } /* * Display bitmap as a list of integers */ /* ARGSUSED */ int lgrp_set(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint64_t set = (uint64_t)addr; uint64_t mask = 1; int i = 0; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (set == 0) return (DCMD_OK); for (; set != (uint64_t)0; i++, mask <<= 1) { if (set & mask) { mdb_printf("%d ", i); set &= ~mask; } } mdb_printf("\n"); return (DCMD_OK); } /* * 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. */ #ifndef _MDB_LGRP_H #define _MDB_LGRP_H #include #ifdef __cplusplus extern "C" { #endif extern int lgrp_cpulist_walk_init(mdb_walk_state_t *); extern int lgrp_cpulist_walk_step(mdb_walk_state_t *); extern int lgrp_walk_init(mdb_walk_state_t *); extern int lgrp_walk_step(mdb_walk_state_t *); extern int lgrp_parents_walk_init(mdb_walk_state_t *); extern int lgrp_parents_walk_step(mdb_walk_state_t *); extern int lgrp_rsrc_cpu_walk_init(mdb_walk_state_t *); extern int lgrp_rsrc_mem_walk_init(mdb_walk_state_t *); extern int lgrp_set_walk_step(mdb_walk_state_t *); extern int lgrp(uintptr_t, uint_t, int, const mdb_arg_t *); extern int lgrp_set(uintptr_t, uint_t, int, const mdb_arg_t *); extern int print_range(int start, int end, int separator); extern void print_cpuset_range(ulong_t *cs, int words, int width); #ifdef __cplusplus } #endif #endif /* _MDB_LGRP_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2013 Nexenta Systems, Inc. All rights reserved. */ #include #include typedef struct list_walk_data { uintptr_t lw_head; /* address of list head */ size_t lw_size; /* size of list element */ size_t lw_offset; /* list element linkage offset */ void *lw_obj; /* buffer of lw_size to hold list element */ uintptr_t lw_end; /* last node in specified range */ const char *lw_elem_name; int (*lw_elem_check)(void *, uintptr_t, void *); void *lw_elem_check_arg; } list_walk_data_t; /* * Initialize a forward walk through a list. * * begin and end optionally specify objects other than the first and last * objects in the list; either or both may be NULL (defaulting to first and * last). * * list_name and element_name specify command-specific labels other than * "list_t" and "list element" for use in error messages. * * element_check() returns -1, 1, or 0: abort the walk with an error, stop * without an error, or allow the normal callback; arg is an optional user * argument to element_check(). */ int list_walk_init_range(mdb_walk_state_t *wsp, uintptr_t begin, uintptr_t end, const char *list_name, const char *element_name, int (*element_check)(void *, uintptr_t, void *), void *arg) { list_walk_data_t *lwd; list_t list; if (list_name == NULL) list_name = "list_t"; if (element_name == NULL) element_name = "list element"; if (mdb_vread(&list, sizeof (list_t), wsp->walk_addr) == -1) { mdb_warn("failed to read %s at %#lx", list_name, wsp->walk_addr); return (WALK_ERR); } if (list.list_size < list.list_offset + sizeof (list_node_t)) { mdb_warn("invalid or uninitialized %s at %#lx\n", list_name, wsp->walk_addr); return (WALK_ERR); } lwd = mdb_alloc(sizeof (list_walk_data_t), UM_SLEEP); lwd->lw_size = list.list_size; lwd->lw_offset = list.list_offset; lwd->lw_obj = mdb_alloc(list.list_size, UM_SLEEP); lwd->lw_head = (uintptr_t)&((list_t *)wsp->walk_addr)->list_head; lwd->lw_end = (end == 0 ? 0 : end + lwd->lw_offset); lwd->lw_elem_name = element_name; lwd->lw_elem_check = element_check; lwd->lw_elem_check_arg = arg; wsp->walk_addr = (begin == 0 ? (uintptr_t)list.list_head.list_next : begin + lwd->lw_offset); wsp->walk_data = lwd; return (WALK_NEXT); } int list_walk_init(mdb_walk_state_t *wsp) { return (list_walk_init_range(wsp, 0, 0, NULL, NULL, NULL, NULL)); } int list_walk_init_named(mdb_walk_state_t *wsp, const char *list_name, const char *element_name) { return (list_walk_init_range(wsp, 0, 0, list_name, element_name, NULL, NULL)); } int list_walk_init_checked(mdb_walk_state_t *wsp, const char *list_name, const char *element_name, int (*element_check)(void *, uintptr_t, void *), void *arg) { return (list_walk_init_range(wsp, 0, 0, list_name, element_name, element_check, arg)); } int list_walk_step(mdb_walk_state_t *wsp) { list_walk_data_t *lwd = wsp->walk_data; uintptr_t addr = wsp->walk_addr - lwd->lw_offset; list_node_t *node; int status; if (wsp->walk_addr == lwd->lw_head) return (WALK_DONE); if (lwd->lw_end != 0 && wsp->walk_addr == lwd->lw_end) return (WALK_DONE); if (mdb_vread(lwd->lw_obj, lwd->lw_size, addr) == -1) { mdb_warn("failed to read %s at %#lx", lwd->lw_elem_name, addr); return (WALK_ERR); } if (lwd->lw_elem_check != NULL) { int rc = lwd->lw_elem_check(lwd->lw_obj, addr, lwd->lw_elem_check_arg); if (rc == -1) return (WALK_ERR); else if (rc == 1) return (WALK_DONE); } status = wsp->walk_callback(addr, lwd->lw_obj, wsp->walk_cbdata); node = (list_node_t *)((uintptr_t)lwd->lw_obj + lwd->lw_offset); wsp->walk_addr = (uintptr_t)node->list_next; return (status); } void list_walk_fini(mdb_walk_state_t *wsp) { list_walk_data_t *lwd = wsp->walk_data; mdb_free(lwd->lw_obj, lwd->lw_size); mdb_free(lwd, sizeof (list_walk_data_t)); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _LIST_H #define _LIST_H #include #ifdef __cplusplus extern "C" { #endif #define LIST_WALK_NAME "list" #define LIST_WALK_DESC "walk a linked list" extern int list_walk_init(mdb_walk_state_t *wsp); extern int list_walk_init_named(mdb_walk_state_t *wsp, const char *, const char *); extern int list_walk_init_checked(mdb_walk_state_t *wsp, const char *, const char *, int (*)(void *, uintptr_t, void *), void *); extern int list_walk_init_range(mdb_walk_state_t *wsp, uintptr_t, uintptr_t, const char *, const char *, int (*)(void *, uintptr_t, void *), void *); extern int list_walk_step(mdb_walk_state_t *wsp); extern void list_walk_fini(mdb_walk_state_t *wsp); #ifdef __cplusplus } #endif #endif /* _LIST_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2023 RackTop Systems, Inc. */ #include #include #include #include int msgbuf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { queue_t q; uintptr_t qp; mblk_t next; mblk_t cont; log_ctl_t lctl; char line[1024]; uint_t verbose = FALSE; uint_t delta = FALSE; uint_t abstime = FALSE; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_readsym(&qp, sizeof (qp), "log_recentq") == -1) { mdb_warn("failed to read log_recent"); return (DCMD_ERR); } if (mdb_vread(&q, sizeof (q), qp) == -1) { mdb_warn("failed to read queue_t at %p", qp); return (DCMD_ERR); } if (mdb_pwalk_dcmd("b_next", "msgbuf", argc, argv, (uintptr_t)q.q_first) == -1) { mdb_warn("can't walk 'b_next'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 't', MDB_OPT_SETBITS, TRUE, &delta, 'T', MDB_OPT_SETBITS, TRUE, &abstime, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); /* For backwards compatability, -v implies -T */ if (verbose) abstime = TRUE; if (DCMD_HDRSPEC(flags)) { int amt = 80; mdb_printf("%"); if (abstime) { mdb_printf("%-20s ", "TIMESTAMP"); amt -= 21; } if (delta) { mdb_printf("%-20s ", "DELTA"); amt -= 21; } if (verbose) { mdb_printf("%?s ", "LOGCTL"); amt -= 17; } mdb_printf("%-*s%\n", amt, "MESSAGE"); } if (mdb_vread(&next, sizeof (next), addr) == -1) { mdb_warn("failed to read msgb structure at %p", addr); return (DCMD_ERR); } if (mdb_vread(&lctl, sizeof (lctl), (uintptr_t)next.b_rptr) == -1) { mdb_warn("failed to read log_ctl_t at %p", next.b_rptr); return (DCMD_ERR); } if (mdb_vread(&cont, sizeof (cont), (uintptr_t)next.b_cont) == -1) { mdb_warn("failed to read msgb structure at %p", next.b_cont); return (DCMD_ERR); } if (mdb_readstr(line, sizeof (line), (uintptr_t)cont.b_rptr) == -1) { mdb_warn("failed to read string at %p", cont.b_rptr); return (DCMD_ERR); } if (abstime) mdb_printf("%Y ", lctl.ttime); if (delta) { timestruc_t hr_time; int64_t diff; char buf[32] = { 0 }; if (mdb_readvar(&hr_time, "panic_hrestime") == -1) { mdb_warn("failed to read panic_hrestime"); return (DCMD_ERR); } if (hr_time.tv_sec == 0 && mdb_readvar(&hr_time, "hrestime") == -1) { mdb_warn("failed to read hrestime"); return (DCMD_ERR); } diff = (int64_t)lctl.ttime - hr_time.tv_sec; mdb_nicetime(SEC2NSEC(diff), buf, sizeof (buf)); mdb_printf("%-20s ", buf); } if (verbose) mdb_printf("%?p ", next.b_rptr); /* skip leading CR to avoid extra lines */ if (line[0] == 0x0d) mdb_printf("%s", &line[1]); else mdb_printf("%s", &line[0]); return (DCMD_OK); } void msgbuf_help(void) { mdb_printf("Print the most recent console messages.\n\n" "%OPTIONS%\n" "\t-t\tInclude the age of the message from now.\n" "\t-T\tInclude the date/time of the message.\n" "\t-v\tInclude the date/time of the message as well as the address " "of its log_ctl_t.\n"); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _LOG_H #define _LOG_H #include #ifdef __cplusplus extern "C" { #endif int msgbuf(uintptr_t, uint_t, int, const mdb_arg_t *); void msgbuf_help(void); #ifdef __cplusplus } #endif #endif /* _LOG_H */ /* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdi.h" #define FT(var, typ) (*((typ *)(&(var)))) /* Utils */ static int get_mdbstr(uintptr_t addr, char *name); static void dump_flags(unsigned long long flags, char **strings); static void dump_mutex(kmutex_t m, char *name); static void dump_condvar(kcondvar_t c, char *name); static void dump_string(uintptr_t addr, char *name); static void dump_state_str(char *name, uintptr_t addr, char **strings); static int mpxio_walk_cb(uintptr_t addr, const void *data, void *cbdata); static char *client_lb_str[] = { "NONE", "RR", "LBA", NULL }; static char *mdi_pathinfo_states[] = { "MDI_PATHINFO_STATE_INIT", "MDI_PATHINFO_STATE_ONLINE", "MDI_PATHINFO_STATE_STANDBY", "MDI_PATHINFO_STATE_FAULT", "MDI_PATHINFO_STATE_OFFLINE", NULL }; static char *mdi_pathinfo_ext_states[] = { "MDI_PATHINFO_STATE_USER_DISABLE", "MDI_PATHINFO_STATE_DRV_DISABLE", "MDI_PATHINFO_STATE_DRV_DISABLE_TRANSIENT", NULL }; static char *mdi_phci_flags[] = { "MDI_PHCI_FLAGS_OFFLINE", "MDI_PHCI_FLAGS_SUSPEND", "MDI_PHCI_FLAGS_POWER_DOWN", "MDI_PHCI_FLAGS_DETACH", "MDI_PHCI_FLAGS_USER_DISABLE", "MDI_PHCI_FLAGS_D_DISABLE", "MDI_PHCI_FLAGS_D_DISABLE_TRANS", "MDI_PHCI_FLAGS_POWER_TRANSITION", NULL }; static uintptr_t firstaddr = 0; static char mdipathinfo_cb_str[] = "::print struct mdi_pathinfo"; static char mdiphci_cb_str[] = "::print struct mdi_phci"; /* * mdipi() * * Given a path, dump mdi_pathinfo struct and detailed pi_prop list. */ /* ARGSUSED */ int mdipi(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct mdi_pathinfo value; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("mdipi: requires an address"); return (DCMD_ERR); } if (mdb_vread(&value, sizeof (struct mdi_pathinfo), addr) != sizeof (struct mdi_pathinfo)) { mdb_warn("mdipi: Failed read on %l#r\n", addr); return (DCMD_ERR); } mdb_printf("------------- mdi_pathinfo @ %#lr ----------\n", addr); dump_string((uintptr_t)value.pi_addr, "PWWN,LUN (pi_addr)"); mdb_printf("\n"); mdb_printf("pi_client: %25l#r::print struct mdi_client\n", value.pi_client); mdb_printf("pi_phci: %27l#r::print struct mdi_phci\n", value.pi_phci); mdb_printf("pi_pprivate: %23l#r\n", value.pi_pprivate); mdb_printf("pi_client_link: %20l#r::print struct mdi_pathinfo\n", value.pi_client_link); mdb_printf("pi_phci_link: %22l#r::print struct mdi_pathinfo\n", value.pi_phci_link); mdb_printf("pi_prop: %27l#r::print struct nv_list\n", value.pi_prop); mdiprops((uintptr_t)value.pi_prop, flags, 0, NULL); mdb_printf("\n"); dump_state_str("Pathinfo State (pi_state) ", MDI_PI_STATE(&value), mdi_pathinfo_states); if (MDI_PI_IS_TRANSIENT(&value)) { mdb_printf("Pathinfo State is TRANSIENT\n"); } if (MDI_PI_EXT_STATE(&value)) { mdb_printf(" Extended (pi_state) : "); /* * Need to shift right 20 bits to match mdi_pathinfo_ext_states * array. */ dump_flags((unsigned long long)MDI_PI_EXT_STATE(&value) >> 20, mdi_pathinfo_ext_states); } dump_state_str("Old Pathinfo State (pi_old_state)", MDI_PI_OLD_STATE(&value), mdi_pathinfo_states); if (MDI_PI_OLD_EXT_STATE(&value)) { mdb_printf(" Extended (pi_old_state) : "); /* * Need to shift right 20 bits to match mdi_pathinfo_ext_states * array. */ dump_flags((unsigned long long)MDI_PI_OLD_EXT_STATE(&value) >> 20, mdi_pathinfo_ext_states); } dump_mutex(value.pi_mutex, "per-path mutex (pi_mutex):"); dump_condvar(value.pi_state_cv, "Path state (pi_state_cv)"); mdb_printf("\n"); mdb_printf("pi_ref_cnt: %d\n", value.pi_ref_cnt); dump_condvar(value.pi_ref_cv, "pi_ref_cv"); mdb_printf("\n"); mdb_printf("pi_kstats: %25l#r::print struct mdi_pi_kstats\n", value.pi_kstats); mdb_printf("pi_cprivate UNUSED: %16l#r \n", value.pi_cprivate); return (DCMD_OK); } /* * mdiprops() * * Given a pi_prop, dump the pi_prop list. */ /* ARGSUSED */ int mdiprops(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("mdiprops: requires an address"); return (DCMD_ERR); } mdb_printf("\tnvpairs @ %#lr:\n", addr); mdb_pwalk_dcmd("nvpair", "nvpair", argc, argv, addr); mdb_printf("\n"); return (DCMD_OK); } /* * mdiphci() * * Given a phci, dump mdi_phci struct. */ /* ARGSUSED */ int mdiphci(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct mdi_phci value; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("mdiphci: requires an address"); return (DCMD_ERR); } if (mdb_vread(&value, sizeof (struct mdi_phci), addr) != sizeof (struct mdi_phci)) { mdb_warn("mdiphci: Failed read on %l#r\n", addr); return (DCMD_ERR); } mdb_printf("---------------- mdi_phci @ %#lr ----------\n", addr); mdb_printf("ph_next: %27l#r::print struct mdi_phci\n", value.ph_next); mdb_printf("ph_prev: %27l#r::print struct mdi_phci\n", value.ph_prev); mdb_printf("ph_vhci: %27l#r::print struct mdi_vhci\n", value.ph_vhci); mdb_printf("ph_dip: %28l#r::print struct dev_info\n", value.ph_dip); mdb_printf("\nph_path_head: %22l#r::print struct mdi_pathinfo\n", value.ph_path_head); mdb_printf("ph_path_tail: %22l#r::print struct mdi_pathinfo\n", value.ph_path_tail); mdb_printf("ph_path_count: %21d\n", value.ph_path_count); mdb_printf("List of paths:\n"); mdb_pwalk("mdipi_phci_list", (mdb_walk_cb_t)mpxio_walk_cb, mdipathinfo_cb_str, (uintptr_t)value.ph_path_head); mdb_printf("\n"); mdb_printf("ph_flags: %26d\n", value.ph_flags); if (value.ph_flags) { dump_flags((unsigned long long)value.ph_flags, mdi_phci_flags); } dump_mutex(value.ph_mutex, "per-pHCI mutex (ph_mutex):"); dump_condvar(value.ph_unstable_cv, "Paths in transient state (ph_unstable_cv)"); mdb_printf("ph_unstable: %23d\n", value.ph_unstable); return (DCMD_OK); } /* * mdivhci() * * Given a vhci, dump mdi_vhci struct and list all phcis. */ /* ARGSUSED */ int mdivhci(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct mdi_vhci value; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("mdivhci: requires an address"); return (DCMD_ERR); } if (mdb_vread(&value, sizeof (struct mdi_vhci), addr) != sizeof (struct mdi_vhci)) { mdb_warn("mdivhci: Failed read on %l#r\n", addr); return (DCMD_ERR); } mdb_printf("----------------- mdi_vhci @ %#lr ----------\n", addr); dump_string((uintptr_t)value.vh_class, "Class name (vh_class)"); mdb_printf("vh_refcnt: %19d\n", value.vh_refcnt); mdb_printf("vh_dip: %28l#r::print struct dev_info\n", value.vh_dip); mdb_printf("vh_next: %27l#r::print struct mdi_vhci\n", value.vh_next); mdb_printf("vh_prev: %27l#r::print struct mdi_vhci\n", value.vh_prev); dump_state_str("Load Balance (vh_lb)", value.vh_lb, client_lb_str); mdb_printf("vh_ops: %28l#r::print struct mdi_vhci_ops\n", value.vh_ops); dump_mutex(value.vh_phci_mutex, "phci mutex (vh_phci_mutex):"); mdb_printf("vh_phci_count: %21d\n", value.vh_phci_count); mdb_printf("\nvh_phci_head: %22l#r::print struct mdi_phci\n", value.vh_phci_head); mdb_printf("vh_phci_tail: %22l#r::print struct mdi_phci\n", value.vh_phci_tail); dump_mutex(value.vh_phci_mutex, "client mutex (vh_client_mutex):"); mdb_printf("vh_client_count: %19d\n", value.vh_client_count); mdb_printf("vh_client_table: %19l#r::print struct client_hash\n", value.vh_client_table); mdb_printf("List of pHCIs:\n"); mdb_pwalk("mdiphci_list", (mdb_walk_cb_t)mpxio_walk_cb, mdiphci_cb_str, (uintptr_t)value.vh_phci_head); mdb_printf("\n"); return (DCMD_OK); } /* mdi_pathinfo client walker */ /* ARGUSED */ int mdi_pi_client_link_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("Address is required"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (struct mdi_pathinfo), UM_SLEEP); firstaddr = wsp->walk_addr; return (WALK_NEXT); } /* ARGUSED */ int mdi_pi_client_link_walk_step(mdb_walk_state_t *wsp) { int status = 0; static int counts = 0; if (firstaddr == wsp->walk_addr && counts != 0) { counts = 0; return (WALK_DONE); } if (wsp->walk_addr == 0) { counts = 0; return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (struct mdi_pathinfo), wsp->walk_addr) == -1) { mdb_warn("failed to read mdi_pathinfo at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t) (((struct mdi_pathinfo *)wsp->walk_data)->pi_client_link); counts++; return (status); } /* ARGUSED */ void mdi_pi_client_link_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct mdi_pathinfo)); } /* * mdiclient_paths() * * Given a path, walk through mdi_pathinfo client links. */ /* ARGUSED */ int mdiclient_paths(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int status; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("Address needs to be specified"); return (DCMD_ERR); } status = mdb_pwalk_dcmd("mdipi_client_list", "mdipi", argc, argv, addr); return (status); } /* mdi_pathinfo phci walker */ int mdi_pi_phci_link_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("Address is required"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (struct mdi_pathinfo), UM_SLEEP); firstaddr = wsp->walk_addr; return (WALK_NEXT); } int mdi_pi_phci_link_walk_step(mdb_walk_state_t *wsp) { int status; static int counts = 0; if (firstaddr == wsp->walk_addr && counts != 0) { counts = 0; return (WALK_DONE); } if (wsp->walk_addr == 0) { counts = 0; return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (struct mdi_pathinfo), wsp->walk_addr) == -1) { mdb_warn("failed to read mdi_pathinfo at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t) (((struct mdi_pathinfo *)wsp->walk_data)->pi_phci_link); counts++; return (status); } void mdi_pi_phci_link_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct mdi_pathinfo)); } /* * mdiphci_paths() * * Given a path, walk through mdi_pathinfo phci links. */ int mdiphci_paths(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int status; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("Address needs to be specified"); return (DCMD_ERR); } status = mdb_pwalk_dcmd("mdipi_phci_list", "mdipi", argc, argv, addr); return (status); } /* mdi_phci walker */ int mdi_phci_ph_next_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("Address is required"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (struct mdi_phci), UM_SLEEP); firstaddr = wsp->walk_addr; return (WALK_NEXT); } int mdi_phci_ph_next_walk_step(mdb_walk_state_t *wsp) { int status; static int counts = 0; if (firstaddr == wsp->walk_addr && counts != 0) { counts = 0; return (WALK_DONE); } if (wsp->walk_addr == 0) { counts = 0; return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (struct mdi_phci), wsp->walk_addr) == -1) { mdb_warn("failed to read mdi_phci at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t) (((struct mdi_phci *)wsp->walk_data)->ph_next); counts++; return (status); } void mdi_phci_ph_next_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct mdi_phci)); } /* * mdiphcis() * * Given a phci, walk through mdi_phci ph_next links. */ int mdiphcis(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int status; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("Address needs to be specified"); return (DCMD_ERR); } status = mdb_pwalk_dcmd("mdiphci_list", "mdiphci", argc, argv, addr); return (status); } /* * Print the flag name by comparing flags to the mask variable. */ static void dump_flags(unsigned long long flags, char **strings) { int i, linel = 8, first = 1; unsigned long long mask = 1; for (i = 0; i < 64; i++) { if (strings[i] == NULL) break; if (flags & mask) { if (!first) { mdb_printf(" | "); } else { first = 0; } /* make output pretty */ linel += strlen(strings[i]) + 3; if (linel > 80) { mdb_printf("\n\t"); linel = strlen(strings[i]) + 1 + 8; } mdb_printf("%s", strings[i]); } mask <<= 1; } mdb_printf("\n"); } static void dump_mutex(kmutex_t m, char *name) { mdb_printf("%s is%s held\n", name, FT(m, uint64_t) == 0 ? " not" : ""); } static void dump_condvar(kcondvar_t c, char *name) { mdb_printf("Threads sleeping on %s = %d\n", name, (int)FT(c, ushort_t)); } static int get_mdbstr(uintptr_t addr, char *string_val) { if (mdb_readstr(string_val, MAXNAMELEN, addr) == -1) { mdb_warn("Error Reading String from %l#r\n", addr); return (1); } return (0); } static void dump_string(uintptr_t addr, char *name) { char string_val[MAXNAMELEN]; if (get_mdbstr(addr, string_val)) { return; } mdb_printf("%s: %s (%l#r)\n", name, string_val, addr); } static void dump_state_str(char *name, uintptr_t addr, char **strings) { mdb_printf("%s: %s (%l#r)\n", name, strings[(unsigned long)addr], addr); } /* ARGSUSED */ static int mpxio_walk_cb(uintptr_t addr, const void *data, void *cbdata) { mdb_printf("%t%l#r%s\n", addr, (char *)cbdata); return (0); } /* * 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. */ #ifndef _MDI_H #define _MDI_H #include #ifdef __cplusplus extern "C" { #endif /* * dcmds */ extern int mdipi(uintptr_t, uint_t, int, const mdb_arg_t *); extern int mdiprops(uintptr_t, uint_t, int, const mdb_arg_t *); extern int mdiphci(uintptr_t, uint_t, int, const mdb_arg_t *); extern int mdivhci(uintptr_t, uint_t, int, const mdb_arg_t *); extern int mdiclient_paths(uintptr_t, uint_t, int, const mdb_arg_t *); extern int mdiphci_paths(uintptr_t, uint_t, int, const mdb_arg_t *); extern int mdiphcis(uintptr_t, uint_t, int, const mdb_arg_t *); /* * walkers */ /* mdi_pathinfo:pi_client_link */ extern int mdi_pi_client_link_walk_init(mdb_walk_state_t *); extern int mdi_pi_client_link_walk_step(mdb_walk_state_t *); extern void mdi_pi_client_link_walk_fini(mdb_walk_state_t *); /* mdi_pathinfo:pi_phci_link */ extern int mdi_pi_phci_link_walk_init(mdb_walk_state_t *); extern int mdi_pi_phci_link_walk_step(mdb_walk_state_t *); extern void mdi_pi_phci_link_walk_fini(mdb_walk_state_t *); /* mdi_phci:ph_next */ extern int mdi_phci_ph_next_walk_init(mdb_walk_state_t *); extern int mdi_phci_ph_next_walk_step(mdb_walk_state_t *); extern void mdi_phci_ph_next_walk_fini(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _MDI_H */ /* * 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 (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Hammerhead: Xen balloon support removed */ #include "avl.h" #include "memory.h" /* * Page walker. * By default, this will walk all pages in the system. If given an * address, it will walk all pages belonging to the vnode at that * address. */ /* * page_walk_data * * pw_hashleft is set to -1 when walking a vnode's pages, and holds the * number of hash locations remaining in the page hash table when * walking all pages. * * The astute reader will notice that pw_hashloc is only used when * reading all pages (to hold a pointer to our location in the page * hash table), and that pw_first is only used when reading the pages * belonging to a particular vnode (to hold a pointer to the first * page). While these could be combined to be a single pointer, they * are left separate for clarity. */ typedef struct page_walk_data { long pw_hashleft; void **pw_hashloc; uintptr_t pw_first; } page_walk_data_t; int page_walk_init(mdb_walk_state_t *wsp) { page_walk_data_t *pwd; void **ptr; size_t hashsz; vnode_t vn; if (wsp->walk_addr == 0) { /* * Walk all pages */ if ((mdb_readvar(&ptr, "page_hash") == -1) || (mdb_readvar(&hashsz, "page_hashsz") == -1) || (ptr == NULL) || (hashsz == 0)) { mdb_warn("page_hash, page_hashsz not found or invalid"); return (WALK_ERR); } /* * Since we are walking all pages, initialize hashleft * to be the remaining number of entries in the page * hash. hashloc is set the start of the page hash * table. Setting the walk address to 0 indicates that * we aren't currently following a hash chain, and that * we need to scan the page hash table for a page. */ pwd = mdb_alloc(sizeof (page_walk_data_t), UM_SLEEP); pwd->pw_hashleft = hashsz; pwd->pw_hashloc = ptr; wsp->walk_addr = 0; } else { /* * Walk just this vnode */ if (mdb_vread(&vn, sizeof (vnode_t), wsp->walk_addr) == -1) { mdb_warn("unable to read vnode_t at %#lx", wsp->walk_addr); return (WALK_ERR); } /* * We set hashleft to -1 to indicate that we are * walking a vnode, and initialize first to 0 (it is * used to terminate the walk, so it must not be set * until after we have walked the first page). The * walk address is set to the first page. */ pwd = mdb_alloc(sizeof (page_walk_data_t), UM_SLEEP); pwd->pw_hashleft = -1; pwd->pw_first = 0; wsp->walk_addr = (uintptr_t)vn.v_pages; } wsp->walk_data = pwd; return (WALK_NEXT); } int page_walk_step(mdb_walk_state_t *wsp) { page_walk_data_t *pwd = wsp->walk_data; page_t page; uintptr_t pp; pp = wsp->walk_addr; if (pwd->pw_hashleft < 0) { /* We're walking a vnode's pages */ /* * If we don't have any pages to walk, we have come * back around to the first one (we finished), or we * can't read the page we're looking at, we are done. */ if (pp == 0 || pp == pwd->pw_first) return (WALK_DONE); if (mdb_vread(&page, sizeof (page_t), pp) == -1) { mdb_warn("unable to read page_t at %#lx", pp); return (WALK_ERR); } /* * Set the walk address to the next page, and if the * first page hasn't been set yet (i.e. we are on the * first page), set it. */ wsp->walk_addr = (uintptr_t)page.p_vpnext; if (pwd->pw_first == 0) pwd->pw_first = pp; } else if (pwd->pw_hashleft > 0) { /* We're walking all pages */ /* * If pp (the walk address) is NULL, we scan through * the page hash table until we find a page. */ if (pp == 0) { /* * Iterate through the page hash table until we * find a page or reach the end. */ do { if (mdb_vread(&pp, sizeof (uintptr_t), (uintptr_t)pwd->pw_hashloc) == -1) { mdb_warn("unable to read from %#p", pwd->pw_hashloc); return (WALK_ERR); } pwd->pw_hashleft--; pwd->pw_hashloc++; } while (pwd->pw_hashleft && (pp == 0)); /* * We've reached the end; exit. */ if (pp == 0) return (WALK_DONE); } if (mdb_vread(&page, sizeof (page_t), pp) == -1) { mdb_warn("unable to read page_t at %#lx", pp); return (WALK_ERR); } /* * Set the walk address to the next page. */ wsp->walk_addr = (uintptr_t)page.p_hash; } else { /* We've finished walking all pages. */ return (WALK_DONE); } return (wsp->walk_callback(pp, &page, wsp->walk_cbdata)); } void page_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (page_walk_data_t)); } /* * allpages walks all pages in the system in order they appear in * the memseg structure */ #define PAGE_BUFFER 128 int allpages_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { mdb_warn("allpages only supports global walks.\n"); return (WALK_ERR); } if (mdb_layered_walk("memseg", wsp) == -1) { mdb_warn("couldn't walk 'memseg'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (page_t) * PAGE_BUFFER, UM_SLEEP); return (WALK_NEXT); } int allpages_walk_step(mdb_walk_state_t *wsp) { const struct memseg *msp = wsp->walk_layer; page_t *buf = wsp->walk_data; size_t pg_read, i; size_t pg_num = msp->pages_end - msp->pages_base; const page_t *pg_addr = msp->pages; while (pg_num > 0) { pg_read = MIN(pg_num, PAGE_BUFFER); if (mdb_vread(buf, pg_read * sizeof (page_t), (uintptr_t)pg_addr) == -1) { mdb_warn("can't read page_t's at %#lx", pg_addr); return (WALK_ERR); } for (i = 0; i < pg_read; i++) { int ret = wsp->walk_callback((uintptr_t)&pg_addr[i], &buf[i], wsp->walk_cbdata); if (ret != WALK_NEXT) return (ret); } pg_num -= pg_read; pg_addr += pg_read; } return (WALK_NEXT); } void allpages_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (page_t) * PAGE_BUFFER); } /* * Hash table + LRU queue. * This table is used to cache recently read vnodes for the memstat * command, to reduce the number of mdb_vread calls. This greatly * speeds the memstat command on on live, large CPU count systems. */ #define VN_SMALL 401 #define VN_LARGE 10007 #define VN_HTABLE_KEY(p, hp) ((p) % ((hp)->vn_htable_buckets)) struct vn_htable_list { uint_t vn_flag; /* v_flag from vnode */ uintptr_t vn_ptr; /* pointer to vnode */ struct vn_htable_list *vn_q_next; /* queue next pointer */ struct vn_htable_list *vn_q_prev; /* queue prev pointer */ struct vn_htable_list *vn_h_next; /* hash table pointer */ }; /* * vn_q_first -> points to to head of queue: the vnode that was most * recently used * vn_q_last -> points to the oldest used vnode, and is freed once a new * vnode is read. * vn_htable -> hash table * vn_htable_buf -> contains htable objects * vn_htable_size -> total number of items in the hash table * vn_htable_buckets -> number of buckets in the hash table */ typedef struct vn_htable { struct vn_htable_list *vn_q_first; struct vn_htable_list *vn_q_last; struct vn_htable_list **vn_htable; struct vn_htable_list *vn_htable_buf; int vn_htable_size; int vn_htable_buckets; } vn_htable_t; /* allocate memory, initilize hash table and LRU queue */ static void vn_htable_init(vn_htable_t *hp, size_t vn_size) { int i; int htable_size = MAX(vn_size, VN_LARGE); if ((hp->vn_htable_buf = mdb_zalloc(sizeof (struct vn_htable_list) * htable_size, UM_NOSLEEP|UM_GC)) == NULL) { htable_size = VN_SMALL; hp->vn_htable_buf = mdb_zalloc(sizeof (struct vn_htable_list) * htable_size, UM_SLEEP|UM_GC); } hp->vn_htable = mdb_zalloc(sizeof (struct vn_htable_list *) * htable_size, UM_SLEEP|UM_GC); hp->vn_q_first = &hp->vn_htable_buf[0]; hp->vn_q_last = &hp->vn_htable_buf[htable_size - 1]; hp->vn_q_first->vn_q_next = &hp->vn_htable_buf[1]; hp->vn_q_last->vn_q_prev = &hp->vn_htable_buf[htable_size - 2]; for (i = 1; i < (htable_size-1); i++) { hp->vn_htable_buf[i].vn_q_next = &hp->vn_htable_buf[i + 1]; hp->vn_htable_buf[i].vn_q_prev = &hp->vn_htable_buf[i - 1]; } hp->vn_htable_size = htable_size; hp->vn_htable_buckets = htable_size; } /* * Find the vnode whose address is ptr, and return its v_flag in vp->v_flag. * The function tries to find needed information in the following order: * * 1. check if ptr is the first in queue * 2. check if ptr is in hash table (if so move it to the top of queue) * 3. do mdb_vread, remove last queue item from queue and hash table. * Insert new information to freed object, and put this object in to the * top of the queue. */ static int vn_get(vn_htable_t *hp, struct vnode *vp, uintptr_t ptr) { int hkey; struct vn_htable_list *hent, **htmp, *q_next, *q_prev; struct vn_htable_list *q_first = hp->vn_q_first; /* 1. vnode ptr is the first in queue, just get v_flag and return */ if (q_first->vn_ptr == ptr) { vp->v_flag = q_first->vn_flag; return (0); } /* 2. search the hash table for this ptr */ hkey = VN_HTABLE_KEY(ptr, hp); hent = hp->vn_htable[hkey]; while (hent && (hent->vn_ptr != ptr)) hent = hent->vn_h_next; /* 3. if hent is NULL, we did not find in hash table, do mdb_vread */ if (hent == NULL) { struct vnode vn; if (mdb_vread(&vn, sizeof (vnode_t), ptr) == -1) { mdb_warn("unable to read vnode_t at %#lx", ptr); return (-1); } /* we will insert read data into the last element in queue */ hent = hp->vn_q_last; /* remove last hp->vn_q_last object from hash table */ if (hent->vn_ptr) { htmp = &hp->vn_htable[VN_HTABLE_KEY(hent->vn_ptr, hp)]; while (*htmp != hent) htmp = &(*htmp)->vn_h_next; *htmp = hent->vn_h_next; } /* insert data into new free object */ hent->vn_ptr = ptr; hent->vn_flag = vn.v_flag; /* insert new object into hash table */ hent->vn_h_next = hp->vn_htable[hkey]; hp->vn_htable[hkey] = hent; } /* Remove from queue. hent is not first, vn_q_prev is not NULL */ q_next = hent->vn_q_next; q_prev = hent->vn_q_prev; if (q_next == NULL) hp->vn_q_last = q_prev; else q_next->vn_q_prev = q_prev; q_prev->vn_q_next = q_next; /* Add to the front of queue */ hent->vn_q_prev = NULL; hent->vn_q_next = q_first; q_first->vn_q_prev = hent; hp->vn_q_first = hent; /* Set v_flag in vnode pointer from hent */ vp->v_flag = hent->vn_flag; return (0); } /* Summary statistics of pages */ typedef struct memstat { struct vnode *ms_unused_vp; /* Unused pages vnode pointer */ struct vnode *ms_kvps; /* Cached address of vnode array */ uint64_t ms_kmem; /* Pages of kernel memory */ uint64_t ms_zfs_data; /* Pages of zfs data */ uint64_t ms_vmm_mem; /* Pages of VMM mem */ uint64_t ms_anon; /* Pages of anonymous memory */ uint64_t ms_vnode; /* Pages of named (vnode) memory */ uint64_t ms_exec; /* Pages of exec/library memory */ uint64_t ms_cachelist; /* Pages on the cachelist (free) */ uint64_t ms_bootpages; /* Pages on the bootpages list */ uint64_t ms_total; /* Pages on page hash */ vn_htable_t *ms_vn_htable; /* Pointer to hash table */ struct vnode ms_vn; /* vnode buffer */ } memstat_t; #define MS_PP_ISTYPE(pp, stats, index) \ ((pp)->p_vnode == &(stats->ms_kvps[index])) /* * Summarize pages by type and update stat information */ /* ARGSUSED */ static int memstat_callback(page_t *page, page_t *pp, memstat_t *stats) { struct vnode *vp = &stats->ms_vn; if (PP_ISBOOTPAGES(pp)) stats->ms_bootpages++; else if (pp->p_vnode == NULL || pp->p_vnode == stats->ms_unused_vp) return (WALK_NEXT); else if (MS_PP_ISTYPE(pp, stats, KV_KVP)) stats->ms_kmem++; else if (MS_PP_ISTYPE(pp, stats, KV_ZVP)) stats->ms_zfs_data++; else if (MS_PP_ISTYPE(pp, stats, KV_VVP)) stats->ms_vmm_mem++; else if (PP_ISFREE(pp)) stats->ms_cachelist++; else if (vn_get(stats->ms_vn_htable, vp, (uintptr_t)pp->p_vnode)) return (WALK_ERR); else if (IS_SWAPFSVP(vp)) stats->ms_anon++; else if ((vp->v_flag & VVMEXEC) != 0) stats->ms_exec++; else stats->ms_vnode++; stats->ms_total++; return (WALK_NEXT); } /* ARGSUSED */ int memstat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pgcnt_t total_pages, physmem; ulong_t freemem; memstat_t stats; GElf_Sym sym; vn_htable_t ht; uintptr_t vn_size = 0; bzero(&stats, sizeof (memstat_t)); /* * -s size, is an internal option. It specifies the size of vn_htable. * Hash table size is set in the following order: * If user has specified the size that is larger than VN_LARGE: try it, * but if malloc failed default to VN_SMALL. Otherwise try VN_LARGE, if * failed to allocate default to VN_SMALL. * For a better efficiency of hash table it is highly recommended to * set size to a prime number. */ if ((flags & DCMD_ADDRSPEC) || mdb_getopts(argc, argv, 's', MDB_OPT_UINTPTR, &vn_size, NULL) != argc) return (DCMD_USAGE); /* Initialize vnode hash list and queue */ vn_htable_init(&ht, vn_size); stats.ms_vn_htable = &ht; /* Total physical memory */ if (mdb_readvar(&total_pages, "total_pages") == -1) { mdb_warn("unable to read total_pages"); return (DCMD_ERR); } /* Artificially limited memory */ if (mdb_readvar(&physmem, "physmem") == -1) { mdb_warn("unable to read physmem"); return (DCMD_ERR); } /* read kernel vnode array pointer */ if (mdb_lookup_by_obj(MDB_OBJ_EXEC, "kvps", (GElf_Sym *)&sym) == -1) { mdb_warn("unable to look up kvps"); return (DCMD_ERR); } stats.ms_kvps = (struct vnode *)(uintptr_t)sym.st_value; /* * If physmem != total_pages, then the administrator has limited the * number of pages available in the system. Excluded pages are * associated with the unused pages vnode. Read this vnode so the * pages can be excluded in the page accounting. */ if (mdb_lookup_by_obj(MDB_OBJ_EXEC, "unused_pages_vp", (GElf_Sym *)&sym) == -1) { mdb_warn("unable to read unused_pages_vp"); return (DCMD_ERR); } stats.ms_unused_vp = (struct vnode *)(uintptr_t)sym.st_value; /* walk all pages, collect statistics */ if (mdb_walk("allpages", (mdb_walk_cb_t)(uintptr_t)memstat_callback, &stats) == -1) { mdb_warn("can't walk memseg"); return (DCMD_ERR); } #define MS_PCT_TOTAL(x) ((ulong_t)((((5 * total_pages) + ((x) * 1000ull))) / \ ((physmem) * 10))) mdb_printf("Page Summary Pages MB" " %%Tot\n"); mdb_printf("------------ ---------------- ----------------" " ----\n"); mdb_printf("Kernel %16llu %16llu %3lu%%\n", stats.ms_kmem, (uint64_t)stats.ms_kmem * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_kmem)); if (stats.ms_bootpages != 0) { mdb_printf("Boot pages %16llu %16llu %3lu%%\n", stats.ms_bootpages, (uint64_t)stats.ms_bootpages * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_bootpages)); } if (stats.ms_zfs_data != 0) { mdb_printf("ZFS File Data %16llu %16llu %3lu%%\n", stats.ms_zfs_data, (uint64_t)stats.ms_zfs_data * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_zfs_data)); } if (stats.ms_vmm_mem != 0) { mdb_printf("VMM Memory %16llu %16llu %3lu%%\n", stats.ms_vmm_mem, (uint64_t)stats.ms_vmm_mem * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_vmm_mem)); } mdb_printf("Anon %16llu %16llu %3lu%%\n", stats.ms_anon, (uint64_t)stats.ms_anon * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_anon)); mdb_printf("Exec and libs %16llu %16llu %3lu%%\n", stats.ms_exec, (uint64_t)stats.ms_exec * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_exec)); mdb_printf("Page cache %16llu %16llu %3lu%%\n", stats.ms_vnode, (uint64_t)stats.ms_vnode * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_vnode)); mdb_printf("Free (cachelist) %16llu %16llu %3lu%%\n", stats.ms_cachelist, (uint64_t)stats.ms_cachelist * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_cachelist)); /* * occasionally, we double count pages above. To avoid printing * absurdly large values for freemem, we clamp it at zero. */ if (physmem > stats.ms_total) freemem = physmem - stats.ms_total; else freemem = 0; mdb_printf("Free (freelist) %16lu %16llu %3lu%%\n", freemem, (uint64_t)freemem * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(freemem)); mdb_printf("\nTotal %16lu %16lu\n", physmem, (uint64_t)physmem * PAGESIZE / (1024 * 1024)); if (physmem != total_pages) { mdb_printf("Physical %16lu %16lu\n", total_pages, (uint64_t)total_pages * PAGESIZE / (1024 * 1024)); } #undef MS_PCT_TOTAL return (DCMD_OK); } void pagelookup_help(void) { mdb_printf( "Finds the page with name { %vp%, %offset% }.\n" "\n" "Can be invoked three different ways:\n\n" " ::pagelookup -v %vp% -o %offset%\n" " %vp%::pagelookup -o %offset%\n" " %offset%::pagelookup -v %vp%\n" "\n" "The latter two forms are useful in pipelines.\n"); } int pagelookup(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t vp = -(uintptr_t)1; uint64_t offset = -(uint64_t)1; uintptr_t pageaddr; int hasaddr = (flags & DCMD_ADDRSPEC); int usedaddr = 0; if (mdb_getopts(argc, argv, 'v', MDB_OPT_UINTPTR, &vp, 'o', MDB_OPT_UINT64, &offset, NULL) != argc) { return (DCMD_USAGE); } if (vp == -(uintptr_t)1) { if (offset == -(uint64_t)1) { mdb_warn( "pagelookup: at least one of -v vp or -o offset " "required.\n"); return (DCMD_USAGE); } vp = addr; usedaddr = 1; } else if (offset == -(uint64_t)1) { offset = mdb_get_dot(); usedaddr = 1; } if (usedaddr && !hasaddr) { mdb_warn("pagelookup: address required\n"); return (DCMD_USAGE); } if (!usedaddr && hasaddr) { mdb_warn( "pagelookup: address specified when both -v and -o were " "passed"); return (DCMD_USAGE); } pageaddr = mdb_page_lookup(vp, offset); if (pageaddr == 0) { mdb_warn("pagelookup: no page for {vp = %p, offset = %llp)\n", vp, offset); return (DCMD_OK); } mdb_printf("%#lr\n", pageaddr); /* this is PIPE_OUT friendly */ return (DCMD_OK); } /*ARGSUSED*/ int page_num2pp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t pp; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } pp = mdb_pfn2page((pfn_t)addr); if (pp == 0) { return (DCMD_ERR); } if (flags & DCMD_PIPE_OUT) { mdb_printf("%#lr\n", pp); } else { mdb_printf("%lx has page_t at %#lx\n", (pfn_t)addr, pp); } return (DCMD_OK); } int page(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { page_t p; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("page", "page", argc, argv) == -1) { mdb_warn("can't walk pages"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %?s %16s %8s %3s %3s %2s %2s %2s%\n", "PAGE", "VNODE", "OFFSET", "SELOCK", "LCT", "COW", "IO", "FS", "ST"); } if (mdb_vread(&p, sizeof (page_t), addr) == -1) { mdb_warn("can't read page_t at %#lx", addr); return (DCMD_ERR); } mdb_printf("%0?lx %?p %16llx %8x %3d %3d %2x %2x %2x\n", addr, p.p_vnode, p.p_offset, p.p_selock, p.p_lckcnt, p.p_cowcnt, p.p_iolock_state, p.p_fsdata, p.p_state); return (DCMD_OK); } int swap_walk_init(mdb_walk_state_t *wsp) { void *ptr; if ((mdb_readvar(&ptr, "swapinfo") == -1) || ptr == NULL) { mdb_warn("swapinfo not found or invalid"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)ptr; return (WALK_NEXT); } int swap_walk_step(mdb_walk_state_t *wsp) { uintptr_t sip; struct swapinfo si; sip = wsp->walk_addr; if (sip == 0) return (WALK_DONE); if (mdb_vread(&si, sizeof (struct swapinfo), sip) == -1) { mdb_warn("unable to read swapinfo at %#lx", sip); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)si.si_next; return (wsp->walk_callback(sip, &si, wsp->walk_cbdata)); } int swapinfof(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct swapinfo si; char *name; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("swapinfo", "swapinfo", argc, argv) == -1) { mdb_warn("can't walk swapinfo"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %?s %9s %9s %s%\n", "ADDR", "VNODE", "PAGES", "FREE", "NAME"); } if (mdb_vread(&si, sizeof (struct swapinfo), addr) == -1) { mdb_warn("can't read swapinfo at %#lx", addr); return (DCMD_ERR); } name = mdb_alloc(si.si_pnamelen, UM_SLEEP | UM_GC); if (mdb_vread(name, si.si_pnamelen, (uintptr_t)si.si_pname) == -1) name = "*error*"; mdb_printf("%0?lx %?p %9d %9d %s\n", addr, si.si_vp, si.si_npgs, si.si_nfpgs, name); return (DCMD_OK); } int memlist_walk_step(mdb_walk_state_t *wsp) { uintptr_t mlp; struct memlist ml; mlp = wsp->walk_addr; if (mlp == 0) return (WALK_DONE); if (mdb_vread(&ml, sizeof (struct memlist), mlp) == -1) { mdb_warn("unable to read memlist at %#lx", mlp); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)ml.ml_next; return (wsp->walk_callback(mlp, &ml, wsp->walk_cbdata)); } int memlist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct memlist ml; if (!(flags & DCMD_ADDRSPEC)) { uintptr_t ptr; uint_t list = 0; int i; static const char *lists[] = { "phys_install", "phys_avail", "virt_avail" }; if (mdb_getopts(argc, argv, 'i', MDB_OPT_SETBITS, (1 << 0), &list, 'a', MDB_OPT_SETBITS, (1 << 1), &list, 'v', MDB_OPT_SETBITS, (1 << 2), &list, NULL) != argc) return (DCMD_USAGE); if (!list) list = 1; for (i = 0; list; i++, list >>= 1) { if (!(list & 1)) continue; if ((mdb_readvar(&ptr, lists[i]) == -1) || (ptr == 0)) { mdb_warn("%s not found or invalid", lists[i]); return (DCMD_ERR); } mdb_printf("%s:\n", lists[i]); if (mdb_pwalk_dcmd("memlist", "memlist", 0, NULL, ptr) == -1) { mdb_warn("can't walk memlist"); return (DCMD_ERR); } } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %16s %16s%\n", "ADDR", "BASE", "SIZE"); if (mdb_vread(&ml, sizeof (struct memlist), addr) == -1) { mdb_warn("can't read memlist at %#lx", addr); return (DCMD_ERR); } mdb_printf("%0?lx %16llx %16llx\n", addr, ml.ml_address, ml.ml_size); return (DCMD_OK); } int seg_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("seg walk must begin at struct as *\n"); return (WALK_ERR); } /* * this is really just a wrapper to AVL tree walk */ wsp->walk_addr = (uintptr_t)&((struct as *)wsp->walk_addr)->a_segtree; return (avl_walk_init(wsp)); } /*ARGSUSED*/ int seg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct seg s; if (argc != 0) return (DCMD_USAGE); if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s %?s %?s %?s %s%\n", "SEG", "BASE", "SIZE", "DATA", "OPS"); } if (mdb_vread(&s, sizeof (s), addr) == -1) { mdb_warn("failed to read seg at %p", addr); return (DCMD_ERR); } mdb_printf("%?p %?p %?lx %?p %a\n", addr, s.s_base, s.s_size, s.s_data, s.s_ops); return (DCMD_OK); } typedef struct pmap_walk_types { uintptr_t pwt_segvn; uintptr_t pwt_seghole; } pmap_walk_types_t; /*ARGSUSED*/ static int pmap_walk_count_pages(uintptr_t addr, const void *data, void *out) { pgcnt_t *nres = out; (*nres)++; return (WALK_NEXT); } static int pmap_walk_seg(uintptr_t addr, const struct seg *seg, const pmap_walk_types_t *types) { const uintptr_t ops = (uintptr_t)seg->s_ops; mdb_printf("%0?p %0?p %7dk", addr, seg->s_base, seg->s_size / 1024); if (ops == types->pwt_segvn && seg->s_data != NULL) { struct segvn_data svn; pgcnt_t nres = 0; svn.vp = NULL; (void) mdb_vread(&svn, sizeof (svn), (uintptr_t)seg->s_data); /* * Use the segvn_pages walker to find all of the in-core pages * for this mapping. */ if (mdb_pwalk("segvn_pages", pmap_walk_count_pages, &nres, (uintptr_t)seg->s_data) == -1) { mdb_warn("failed to walk segvn_pages (s_data=%p)", seg->s_data); } mdb_printf(" %7ldk", (nres * PAGESIZE) / 1024); if (svn.vp != NULL) { char buf[29]; mdb_vnode2path((uintptr_t)svn.vp, buf, sizeof (buf)); mdb_printf(" %s", buf); } else { mdb_printf(" [ anon ]"); } } else if (ops == types->pwt_seghole && seg->s_data != NULL) { seghole_data_t shd; char name[16]; (void) mdb_vread(&shd, sizeof (shd), (uintptr_t)seg->s_data); if (shd.shd_name == NULL || mdb_readstr(name, sizeof (name), (uintptr_t)shd.shd_name) == 0) { name[0] = '\0'; } mdb_printf(" %8s [ hole%s%s ]", "-", name[0] == '0' ? "" : ":", name); } else { mdb_printf(" %8s [ &%a ]", "?", seg->s_ops); } mdb_printf("\n"); return (WALK_NEXT); } static int pmap_walk_seg_quick(uintptr_t addr, const struct seg *seg, const pmap_walk_types_t *types) { const uintptr_t ops = (uintptr_t)seg->s_ops; mdb_printf("%0?p %0?p %7dk", addr, seg->s_base, seg->s_size / 1024); if (ops == types->pwt_segvn && seg->s_data != NULL) { struct segvn_data svn; svn.vp = NULL; (void) mdb_vread(&svn, sizeof (svn), (uintptr_t)seg->s_data); if (svn.vp != NULL) { mdb_printf(" %0?p", svn.vp); } else { mdb_printf(" [ anon ]"); } } else { mdb_printf(" [ &%a ]", seg->s_ops); } mdb_printf("\n"); return (WALK_NEXT); } /*ARGSUSED*/ int pmap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { proc_t proc; uint_t quick = FALSE; mdb_walk_cb_t cb = (mdb_walk_cb_t)pmap_walk_seg; pmap_walk_types_t wtypes = { 0 }; GElf_Sym sym; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'q', MDB_OPT_SETBITS, TRUE, &quick, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&proc, sizeof (proc), addr) == -1) { mdb_warn("failed to read proc at %p", addr); return (DCMD_ERR); } if (mdb_lookup_by_name("segvn_ops", &sym) == 0) wtypes.pwt_segvn = (uintptr_t)sym.st_value; if (mdb_lookup_by_name("seghole_ops", &sym) == 0) wtypes.pwt_seghole = (uintptr_t)sym.st_value; mdb_printf("%?s %?s %8s ", "SEG", "BASE", "SIZE"); if (quick) { mdb_printf("VNODE\n"); cb = (mdb_walk_cb_t)pmap_walk_seg_quick; } else { mdb_printf("%8s %s\n", "RES", "PATH"); } if (mdb_pwalk("seg", cb, (void *)&wtypes, (uintptr_t)proc.p_as) == -1) { mdb_warn("failed to walk segments of as %p", proc.p_as); return (DCMD_ERR); } return (DCMD_OK); } typedef struct anon_walk_data { uintptr_t *aw_levone; uintptr_t *aw_levtwo; size_t aw_minslot; size_t aw_maxslot; pgcnt_t aw_nlevone; pgcnt_t aw_levone_ndx; size_t aw_levtwo_ndx; struct anon_map *aw_ampp; struct anon_map aw_amp; struct anon_hdr aw_ahp; int aw_all; /* report all anon pointers, even NULLs */ } anon_walk_data_t; int anon_walk_init_common(mdb_walk_state_t *wsp, ulong_t minslot, ulong_t maxslot) { anon_walk_data_t *aw; if (wsp->walk_addr == 0) { mdb_warn("anon walk doesn't support global walks\n"); return (WALK_ERR); } aw = mdb_alloc(sizeof (anon_walk_data_t), UM_SLEEP); aw->aw_ampp = (struct anon_map *)wsp->walk_addr; if (mdb_vread(&aw->aw_amp, sizeof (aw->aw_amp), wsp->walk_addr) == -1) { mdb_warn("failed to read anon map at %p", wsp->walk_addr); mdb_free(aw, sizeof (anon_walk_data_t)); return (WALK_ERR); } if (mdb_vread(&aw->aw_ahp, sizeof (aw->aw_ahp), (uintptr_t)(aw->aw_amp.ahp)) == -1) { mdb_warn("failed to read anon hdr ptr at %p", aw->aw_amp.ahp); mdb_free(aw, sizeof (anon_walk_data_t)); return (WALK_ERR); } /* update min and maxslot with the given constraints */ maxslot = MIN(maxslot, aw->aw_ahp.size); minslot = MIN(minslot, maxslot); if (aw->aw_ahp.size <= ANON_CHUNK_SIZE || (aw->aw_ahp.flags & ANON_ALLOC_FORCE)) { aw->aw_nlevone = maxslot; aw->aw_levone_ndx = minslot; aw->aw_levtwo = NULL; } else { aw->aw_nlevone = (maxslot + ANON_CHUNK_OFF) >> ANON_CHUNK_SHIFT; aw->aw_levone_ndx = 0; aw->aw_levtwo = mdb_zalloc(ANON_CHUNK_SIZE * sizeof (uintptr_t), UM_SLEEP); } aw->aw_levone = mdb_alloc(aw->aw_nlevone * sizeof (uintptr_t), UM_SLEEP); aw->aw_all = (wsp->walk_arg == ANON_WALK_ALL); mdb_vread(aw->aw_levone, aw->aw_nlevone * sizeof (uintptr_t), (uintptr_t)aw->aw_ahp.array_chunk); aw->aw_levtwo_ndx = 0; aw->aw_minslot = minslot; aw->aw_maxslot = maxslot; wsp->walk_data = aw; return (0); } int anon_walk_step(mdb_walk_state_t *wsp) { anon_walk_data_t *aw = (anon_walk_data_t *)wsp->walk_data; struct anon anon; uintptr_t anonptr; ulong_t slot; /* * Once we've walked through level one, we're done. */ if (aw->aw_levone_ndx >= aw->aw_nlevone) { return (WALK_DONE); } if (aw->aw_levtwo == NULL) { anonptr = aw->aw_levone[aw->aw_levone_ndx]; aw->aw_levone_ndx++; } else { if (aw->aw_levtwo_ndx == 0) { uintptr_t levtwoptr; /* The first time through, skip to our first index. */ if (aw->aw_levone_ndx == 0) { aw->aw_levone_ndx = aw->aw_minslot / ANON_CHUNK_SIZE; aw->aw_levtwo_ndx = aw->aw_minslot % ANON_CHUNK_SIZE; } levtwoptr = (uintptr_t)aw->aw_levone[aw->aw_levone_ndx]; if (levtwoptr == 0) { if (!aw->aw_all) { aw->aw_levtwo_ndx = 0; aw->aw_levone_ndx++; return (WALK_NEXT); } bzero(aw->aw_levtwo, ANON_CHUNK_SIZE * sizeof (uintptr_t)); } else if (mdb_vread(aw->aw_levtwo, ANON_CHUNK_SIZE * sizeof (uintptr_t), levtwoptr) == -1) { mdb_warn("unable to read anon_map %p's " "second-level map %d at %p", aw->aw_ampp, aw->aw_levone_ndx, levtwoptr); return (WALK_ERR); } } slot = aw->aw_levone_ndx * ANON_CHUNK_SIZE + aw->aw_levtwo_ndx; anonptr = aw->aw_levtwo[aw->aw_levtwo_ndx]; /* update the indices for next time */ aw->aw_levtwo_ndx++; if (aw->aw_levtwo_ndx == ANON_CHUNK_SIZE) { aw->aw_levtwo_ndx = 0; aw->aw_levone_ndx++; } /* make sure the slot # is in the requested range */ if (slot >= aw->aw_maxslot) { return (WALK_DONE); } } if (anonptr != 0) { mdb_vread(&anon, sizeof (anon), anonptr); return (wsp->walk_callback(anonptr, &anon, wsp->walk_cbdata)); } if (aw->aw_all) { return (wsp->walk_callback(0, NULL, wsp->walk_cbdata)); } return (WALK_NEXT); } void anon_walk_fini(mdb_walk_state_t *wsp) { anon_walk_data_t *aw = (anon_walk_data_t *)wsp->walk_data; if (aw->aw_levtwo != NULL) mdb_free(aw->aw_levtwo, ANON_CHUNK_SIZE * sizeof (uintptr_t)); mdb_free(aw->aw_levone, aw->aw_nlevone * sizeof (uintptr_t)); mdb_free(aw, sizeof (anon_walk_data_t)); } int anon_walk_init(mdb_walk_state_t *wsp) { return (anon_walk_init_common(wsp, 0, ULONG_MAX)); } int segvn_anon_walk_init(mdb_walk_state_t *wsp) { const uintptr_t svd_addr = wsp->walk_addr; uintptr_t amp_addr; uintptr_t seg_addr; struct segvn_data svd; struct anon_map amp; struct seg seg; if (svd_addr == 0) { mdb_warn("segvn_anon walk doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&svd, sizeof (svd), svd_addr) == -1) { mdb_warn("segvn_anon walk: unable to read segvn_data at %p", svd_addr); return (WALK_ERR); } if (svd.amp == NULL) { mdb_warn("segvn_anon walk: segvn_data at %p has no anon map\n", svd_addr); return (WALK_ERR); } amp_addr = (uintptr_t)svd.amp; if (mdb_vread(&, sizeof (amp), amp_addr) == -1) { mdb_warn("segvn_anon walk: unable to read amp %p for " "segvn_data %p", amp_addr, svd_addr); return (WALK_ERR); } seg_addr = (uintptr_t)svd.seg; if (mdb_vread(&seg, sizeof (seg), seg_addr) == -1) { mdb_warn("segvn_anon walk: unable to read seg %p for " "segvn_data %p", seg_addr, svd_addr); return (WALK_ERR); } if ((seg.s_size + (svd.anon_index << PAGESHIFT)) > amp.size) { mdb_warn("anon map %p is too small for segment %p\n", amp_addr, seg_addr); return (WALK_ERR); } wsp->walk_addr = amp_addr; return (anon_walk_init_common(wsp, svd.anon_index, svd.anon_index + (seg.s_size >> PAGESHIFT))); } typedef struct { u_offset_t svs_offset; uintptr_t svs_page; } segvn_sparse_t; #define SEGVN_MAX_SPARSE ((128 * 1024) / sizeof (segvn_sparse_t)) typedef struct { uintptr_t svw_svdp; struct segvn_data svw_svd; struct seg svw_seg; size_t svw_walkoff; ulong_t svw_anonskip; segvn_sparse_t *svw_sparse; size_t svw_sparse_idx; size_t svw_sparse_count; size_t svw_sparse_size; uint8_t svw_sparse_overflow; uint8_t svw_all; } segvn_walk_data_t; static int segvn_sparse_fill(uintptr_t addr, const void *pp_arg, void *arg) { segvn_walk_data_t *const svw = arg; const page_t *const pp = pp_arg; const u_offset_t offset = pp->p_offset; segvn_sparse_t *const cur = &svw->svw_sparse[svw->svw_sparse_count]; /* See if the page is of interest */ if ((u_offset_t)(offset - svw->svw_svd.offset) >= svw->svw_seg.s_size) { return (WALK_NEXT); } /* See if we have space for the new entry, then add it. */ if (svw->svw_sparse_count >= svw->svw_sparse_size) { svw->svw_sparse_overflow = 1; return (WALK_DONE); } svw->svw_sparse_count++; cur->svs_offset = offset; cur->svs_page = addr; return (WALK_NEXT); } static int segvn_sparse_cmp(const void *lp, const void *rp) { const segvn_sparse_t *const l = lp; const segvn_sparse_t *const r = rp; if (l->svs_offset < r->svs_offset) { return (-1); } if (l->svs_offset > r->svs_offset) { return (1); } return (0); } /* * Builds on the "anon_all" walker to walk all resident pages in a segvn_data * structure. For segvn_datas without an anon structure, it just looks up * pages in the vnode. For segvn_datas with an anon structure, NULL slots * pass through to the vnode, and non-null slots are checked for residency. */ int segvn_pages_walk_init(mdb_walk_state_t *wsp) { segvn_walk_data_t *svw; struct segvn_data *svd; if (wsp->walk_addr == 0) { mdb_warn("segvn walk doesn't support global walks\n"); return (WALK_ERR); } svw = mdb_zalloc(sizeof (*svw), UM_SLEEP); svw->svw_svdp = wsp->walk_addr; svw->svw_anonskip = 0; svw->svw_sparse_idx = 0; svw->svw_walkoff = 0; svw->svw_all = (wsp->walk_arg == SEGVN_PAGES_ALL); if (mdb_vread(&svw->svw_svd, sizeof (svw->svw_svd), wsp->walk_addr) == -1) { mdb_warn("failed to read segvn_data at %p", wsp->walk_addr); mdb_free(svw, sizeof (*svw)); return (WALK_ERR); } svd = &svw->svw_svd; if (mdb_vread(&svw->svw_seg, sizeof (svw->svw_seg), (uintptr_t)svd->seg) == -1) { mdb_warn("failed to read seg at %p (from %p)", svd->seg, &((struct segvn_data *)(wsp->walk_addr))->seg); mdb_free(svw, sizeof (*svw)); return (WALK_ERR); } if (svd->amp == NULL && svd->vp == NULL) { /* make the walk terminate immediately; no pages */ svw->svw_walkoff = svw->svw_seg.s_size; } else if (svd->amp == NULL && (svw->svw_seg.s_size >> PAGESHIFT) >= SEGVN_MAX_SPARSE) { /* * If we don't have an anon pointer, and the segment is large, * we try to load the in-memory pages into a fixed-size array, * which is then sorted and reported directly. This is much * faster than doing a mdb_page_lookup() for each possible * offset. * * If the allocation fails, or there are too many pages * in-core, we fall back to looking up the pages individually. */ svw->svw_sparse = mdb_alloc( SEGVN_MAX_SPARSE * sizeof (*svw->svw_sparse), UM_NOSLEEP); if (svw->svw_sparse != NULL) { svw->svw_sparse_size = SEGVN_MAX_SPARSE; if (mdb_pwalk("page", segvn_sparse_fill, svw, (uintptr_t)svd->vp) == -1 || svw->svw_sparse_overflow) { mdb_free(svw->svw_sparse, SEGVN_MAX_SPARSE * sizeof (*svw->svw_sparse)); svw->svw_sparse = NULL; } else { qsort(svw->svw_sparse, svw->svw_sparse_count, sizeof (*svw->svw_sparse), segvn_sparse_cmp); } } } else if (svd->amp != NULL) { const char *const layer = (!svw->svw_all && svd->vp == NULL) ? "segvn_anon" : "segvn_anon_all"; /* * If we're not printing all offsets, and the segvn_data has * no backing VP, we can use the "segvn_anon" walker, which * efficiently skips NULL slots. * * Otherwise, we layer over the "segvn_anon_all" walker * (which reports all anon slots, even NULL ones), so that * segvn_pages_walk_step() knows the precise offset for each * element. It uses that offset information to look up the * backing pages for NULL anon slots. */ if (mdb_layered_walk(layer, wsp) == -1) { mdb_warn("segvn_pages: failed to layer \"%s\" " "for segvn_data %p", layer, svw->svw_svdp); mdb_free(svw, sizeof (*svw)); return (WALK_ERR); } } wsp->walk_data = svw; return (WALK_NEXT); } int segvn_pages_walk_step(mdb_walk_state_t *wsp) { segvn_walk_data_t *const svw = wsp->walk_data; struct seg *const seg = &svw->svw_seg; struct segvn_data *const svd = &svw->svw_svd; uintptr_t pp; page_t page; /* If we've walked off the end of the segment, we're done. */ if (svw->svw_walkoff >= seg->s_size) { return (WALK_DONE); } /* * If we've got a sparse page array, just send it directly. */ if (svw->svw_sparse != NULL) { u_offset_t off; if (svw->svw_sparse_idx >= svw->svw_sparse_count) { pp = 0; if (!svw->svw_all) { return (WALK_DONE); } } else { segvn_sparse_t *const svs = &svw->svw_sparse[svw->svw_sparse_idx]; off = svs->svs_offset - svd->offset; if (svw->svw_all && svw->svw_walkoff != off) { pp = 0; } else { pp = svs->svs_page; svw->svw_sparse_idx++; } } } else if (svd->amp == NULL || wsp->walk_addr == 0) { /* * If there's no anon, or the anon slot is NULL, look up * . */ if (svd->vp != NULL) { pp = mdb_page_lookup((uintptr_t)svd->vp, svd->offset + svw->svw_walkoff); } else { pp = 0; } } else { const struct anon *const anon = wsp->walk_layer; /* * We have a "struct anon"; if it's not swapped out, * look up the page. */ if (anon->an_vp != NULL || anon->an_off != 0) { pp = mdb_page_lookup((uintptr_t)anon->an_vp, anon->an_off); if (pp == 0 && mdb_get_state() != MDB_STATE_RUNNING) { mdb_warn("walk segvn_pages: segvn_data %p " "offset %ld, anon page <%p, %llx> not " "found.\n", svw->svw_svdp, svw->svw_walkoff, anon->an_vp, anon->an_off); } } else { if (anon->an_pvp == NULL) { mdb_warn("walk segvn_pages: useless struct " "anon at %p\n", wsp->walk_addr); } pp = 0; /* nothing at this offset */ } } svw->svw_walkoff += PAGESIZE; /* Update for the next call */ if (pp != 0) { if (mdb_vread(&page, sizeof (page_t), pp) == -1) { mdb_warn("unable to read page_t at %#lx", pp); return (WALK_ERR); } return (wsp->walk_callback(pp, &page, wsp->walk_cbdata)); } if (svw->svw_all) { return (wsp->walk_callback(0, NULL, wsp->walk_cbdata)); } return (WALK_NEXT); } void segvn_pages_walk_fini(mdb_walk_state_t *wsp) { segvn_walk_data_t *const svw = wsp->walk_data; if (svw->svw_sparse != NULL) { mdb_free(svw->svw_sparse, SEGVN_MAX_SPARSE * sizeof (*svw->svw_sparse)); } mdb_free(svw, sizeof (*svw)); } /* * Grumble, grumble. */ #define SMAP_HASHFUNC(vp, off) \ ((((uintptr_t)(vp) >> 6) + ((uintptr_t)(vp) >> 3) + \ ((off) >> MAXBSHIFT)) & smd_hashmsk) int vnode2smap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { long smd_hashmsk; int hash; uintptr_t offset = 0; struct smap smp; uintptr_t saddr, kaddr; uintptr_t smd_hash, smd_smap; struct seg seg; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_readvar(&smd_hashmsk, "smd_hashmsk") == -1) { mdb_warn("failed to read smd_hashmsk"); return (DCMD_ERR); } if (mdb_readvar(&smd_hash, "smd_hash") == -1) { mdb_warn("failed to read smd_hash"); return (DCMD_ERR); } if (mdb_readvar(&smd_smap, "smd_smap") == -1) { mdb_warn("failed to read smd_hash"); return (DCMD_ERR); } if (mdb_readvar(&kaddr, "segkmap") == -1) { mdb_warn("failed to read segkmap"); return (DCMD_ERR); } if (mdb_vread(&seg, sizeof (seg), kaddr) == -1) { mdb_warn("failed to read segkmap at %p", kaddr); return (DCMD_ERR); } if (argc != 0) { const mdb_arg_t *arg = &argv[0]; offset = (uintptr_t)mdb_argtoull(arg); } hash = SMAP_HASHFUNC(addr, offset); if (mdb_vread(&saddr, sizeof (saddr), smd_hash + hash * sizeof (uintptr_t)) == -1) { mdb_warn("couldn't read smap at %p", smd_hash + hash * sizeof (uintptr_t)); return (DCMD_ERR); } do { if (mdb_vread(&smp, sizeof (smp), saddr) == -1) { mdb_warn("couldn't read smap at %p", saddr); return (DCMD_ERR); } if ((uintptr_t)smp.sm_vp == addr && smp.sm_off == offset) { mdb_printf("vnode %p, offs %p is smap %p, vaddr %p\n", addr, offset, saddr, ((saddr - smd_smap) / sizeof (smp)) * MAXBSIZE + seg.s_base); return (DCMD_OK); } saddr = (uintptr_t)smp.sm_hash; } while (saddr != 0); mdb_printf("no smap for vnode %p, offs %p\n", addr, offset); return (DCMD_OK); } /*ARGSUSED*/ int addr2smap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t kaddr; struct seg seg; struct segmap_data sd; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_readvar(&kaddr, "segkmap") == -1) { mdb_warn("failed to read segkmap"); return (DCMD_ERR); } if (mdb_vread(&seg, sizeof (seg), kaddr) == -1) { mdb_warn("failed to read segkmap at %p", kaddr); return (DCMD_ERR); } if (mdb_vread(&sd, sizeof (sd), (uintptr_t)seg.s_data) == -1) { mdb_warn("failed to read segmap_data at %p", seg.s_data); return (DCMD_ERR); } mdb_printf("%p is smap %p\n", addr, ((addr - (uintptr_t)seg.s_base) >> MAXBSHIFT) * sizeof (struct smap) + (uintptr_t)sd.smd_sm); return (DCMD_OK); } /* * 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 (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _MEMORY_H #define _MEMORY_H #ifdef __cplusplus extern "C" { #endif int page_walk_init(mdb_walk_state_t *); int page_walk_step(mdb_walk_state_t *); void page_walk_fini(mdb_walk_state_t *); int page(uintptr_t, uint_t, int, const mdb_arg_t *); int allpages_walk_init(mdb_walk_state_t *); int allpages_walk_step(mdb_walk_state_t *); void allpages_walk_fini(mdb_walk_state_t *); int memstat(uintptr_t, uint_t, int, const mdb_arg_t *); int pagelookup(uintptr_t, uint_t, int, const mdb_arg_t *); void pagelookup_help(void); int page_num2pp(uintptr_t, uint_t, int, const mdb_arg_t *); int seg_walk_init(mdb_walk_state_t *); int seg(uintptr_t, uint_t, int, const mdb_arg_t *); #define SEGVN_PAGES_RESIDENT (void *)(uintptr_t)0 #define SEGVN_PAGES_ALL (void *)(uintptr_t)1 int segvn_pages_walk_init(mdb_walk_state_t *); int segvn_pages_walk_step(mdb_walk_state_t *); void segvn_pages_walk_fini(mdb_walk_state_t *); int vnode2smap(uintptr_t, uint_t, int, const mdb_arg_t *); int addr2smap(uintptr_t, uint_t, int, const mdb_arg_t *); #define ANON_WALK_ALLOC (void *)(uintptr_t)0 #define ANON_WALK_ALL (void *)(uintptr_t)1 int anon_walk_init(mdb_walk_state_t *); int segvn_anon_walk_init(mdb_walk_state_t *); int anon_walk_step(mdb_walk_state_t *); void anon_walk_fini(mdb_walk_state_t *); int pmap(uintptr_t, uint_t, int, const mdb_arg_t *); int swap_walk_init(mdb_walk_state_t *); int swap_walk_step(mdb_walk_state_t *); int swapinfof(uintptr_t, uint_t, int, const mdb_arg_t *); int memlist_walk_step(mdb_walk_state_t *); int memlist(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MEMORY_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ #include #include #include #include #include "modhash.h" /* This is passed to the modent callback; allows caller to get context */ typedef struct modent_step_data_s { struct mod_hash_entry msd_mhe; /* must be first */ int msd_hash_index; int msd_position; /* entry position in chain */ uintptr_t msd_first_addr; /* first address in chain */ } modent_step_data_t; /* Context for a walk over a modhash (variable length) */ typedef struct hash_walk_s { modent_step_data_t hwalk_msd; /* current entry data */ mod_hash_t hwalk_hash; /* always last (var. len) */ } hash_walk_t; /* Computes number of bytes to allocate for hash_walk_t structure. */ #define HW_SIZE(n) (sizeof (modent_step_data_t) + MH_SIZE(n)) /* Used for decoding hash keys for display */ typedef struct hash_type_entry_s { const char *hte_type; /* name of hash type for ::modent -t */ const char *hte_comparator; /* name of comparator function */ void (*hte_format)(const mod_hash_key_t, char *, size_t); } hash_type_entry_t; static void format_strhash(const mod_hash_key_t, char *, size_t); static void format_ptrhash(const mod_hash_key_t, char *, size_t); static void format_idhash(const mod_hash_key_t, char *, size_t); static void format_default(const mod_hash_key_t, char *, size_t); static const hash_type_entry_t hte_table[] = { { "str", "mod_hash_strkey_cmp", format_strhash }, { "ptr", "mod_hash_ptrkey_cmp", format_ptrhash }, { "id", "mod_hash_idkey_cmp", format_idhash }, { NULL, NULL, format_default } }; static int modent_print(uintptr_t, int, uint_t, const hash_type_entry_t *, boolean_t, uint_t, uint_t); /* The information used during a walk */ typedef struct mod_walk_data_s { const hash_type_entry_t *mwd_hte; /* pointer to entry type */ int mwd_main_flags; /* ::modhash flags */ int mwd_flags; /* DCMD_* flags for looping */ uint_t mwd_opt_e; /* call-modent mode */ uint_t mwd_opt_c; /* chain head only mode */ uint_t mwd_opt_h; /* hash index output */ boolean_t mwd_opt_k_set; /* key supplied */ boolean_t mwd_opt_v_set; /* value supplied */ uintptr_t mwd_opt_k; /* key */ uintptr_t mwd_opt_v; /* value */ int mwd_maxposn; /* len of longest chain - 1 */ int mwd_maxidx; /* hash idx of longest chain */ uintptr_t mwd_maxaddr; /* addr of 1st elem @ maxidx */ uintptr_t mwd_idxtoprint; /* desired hash pos to print */ uintptr_t mwd_addr; /* 1st elem addr @idxtoprint */ } mod_walk_data_t; /* * Initialize a walk over all the modhashes in the system. */ int modhash_walk_init(mdb_walk_state_t *wsp) { mod_hash_t *mh_head; if (mdb_readvar(&mh_head, "mh_head") == -1) { mdb_warn("failed to read mh_head"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)mh_head; return (WALK_NEXT); } /* * Step to the next modhash in the system. */ int modhash_walk_step(mdb_walk_state_t *wsp) { mod_hash_t mh; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&mh, sizeof (mh), wsp->walk_addr) == -1) { mdb_warn("failed to read mod_hash_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &mh, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)mh.mh_next; return (status); } /* * Initialize a walk over the entries in a given modhash. */ int modent_walk_init(mdb_walk_state_t *wsp) { mod_hash_t mh; hash_walk_t *hwp; int retv; if (wsp->walk_addr == 0) { mdb_warn("mod_hash_t address required\n"); return (WALK_ERR); } if (mdb_vread(&mh, sizeof (mh), wsp->walk_addr) == -1) { mdb_warn("failed to read mod_hash_t at %p", wsp->walk_addr); return (WALK_ERR); } if (mh.mh_nchains <= 1) { mdb_warn("impossible number of chains in mod_hash_t at %p", wsp->walk_addr); return (WALK_ERR); } /* * If the user presents us with a garbage pointer, and thus the number * of chains is just absurd, we don't want to bail out of mdb. Fail to * walk instead. */ hwp = mdb_alloc(HW_SIZE(mh.mh_nchains), UM_NOSLEEP); if (hwp == NULL) { mdb_warn("unable to allocate %#x bytes for mod_hash_t at %p", HW_SIZE(mh.mh_nchains), wsp->walk_addr); return (WALK_ERR); } (void) memcpy(&hwp->hwalk_hash, &mh, sizeof (hwp->hwalk_hash)); retv = mdb_vread(hwp->hwalk_hash.mh_entries + 1, (mh.mh_nchains - 1) * sizeof (struct mod_hash_entry *), wsp->walk_addr + sizeof (mh)); if (retv == -1) { mdb_free(hwp, HW_SIZE(mh.mh_nchains)); mdb_warn("failed to read %#x mod_hash_entry pointers at %p", mh.mh_nchains - 1, wsp->walk_addr + sizeof (mh)); return (WALK_ERR); } hwp->hwalk_msd.msd_hash_index = -1; hwp->hwalk_msd.msd_position = 0; hwp->hwalk_msd.msd_first_addr = 0; wsp->walk_addr = 0; wsp->walk_data = hwp; return (WALK_NEXT); } /* * Step to the next entry in the modhash. */ int modent_walk_step(mdb_walk_state_t *wsp) { hash_walk_t *hwp = wsp->walk_data; int status; while (wsp->walk_addr == 0) { hwp->hwalk_msd.msd_position = 0; if (++hwp->hwalk_msd.msd_hash_index >= hwp->hwalk_hash.mh_nchains) return (WALK_DONE); wsp->walk_addr = hwp->hwalk_msd.msd_first_addr = (uintptr_t)hwp->hwalk_hash.mh_entries[ hwp->hwalk_msd.msd_hash_index]; } if (mdb_vread(&hwp->hwalk_msd.msd_mhe, sizeof (hwp->hwalk_msd.msd_mhe), wsp->walk_addr) == -1) { mdb_warn("failed to read mod_hash_entry at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &hwp->hwalk_msd, wsp->walk_cbdata); hwp->hwalk_msd.msd_position++; wsp->walk_addr = (uintptr_t)hwp->hwalk_msd.msd_mhe.mhe_next; return (status); } /* * Clean up after walking the entries in a modhash. */ void modent_walk_fini(mdb_walk_state_t *wsp) { hash_walk_t *hwp = wsp->walk_data; mdb_free(hwp, HW_SIZE(hwp->hwalk_hash.mh_nchains)); wsp->walk_data = NULL; } /* * Step to next entry on a hash chain. */ int modchain_walk_step(mdb_walk_state_t *wsp) { struct mod_hash_entry mhe; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&mhe, sizeof (mhe), wsp->walk_addr) == -1) { mdb_warn("failed to read mod_hash_entry at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &mhe, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)mhe.mhe_next; return (status); } /* * This is called by ::modhash (via a callback) when gathering data about the * entries in a given modhash. It keeps track of the longest chain, finds a * specific entry (if the user requested one) and prints out a summary of the * entry or entries. */ static int modent_format(uintptr_t addr, const void *data, void *private) { const modent_step_data_t *msd = data; mod_walk_data_t *mwd = private; int retv = DCMD_OK; /* If this chain is longest seen, then save start of chain */ if (msd->msd_position > mwd->mwd_maxposn) { mwd->mwd_maxposn = msd->msd_position; mwd->mwd_maxidx = msd->msd_hash_index; mwd->mwd_maxaddr = msd->msd_first_addr; } /* If the user specified a particular chain, then ignore others */ if (mwd->mwd_idxtoprint != (uintptr_t)-1) { /* Save address of *first* entry */ if (mwd->mwd_idxtoprint == msd->msd_hash_index) mwd->mwd_addr = msd->msd_first_addr; else return (retv); } /* If the user specified a particular key, ignore others. */ if (mwd->mwd_opt_k_set && (uintptr_t)msd->msd_mhe.mhe_key != mwd->mwd_opt_k) return (retv); /* If the user specified a particular value, ignore others. */ if (mwd->mwd_opt_v_set && (uintptr_t)msd->msd_mhe.mhe_val != mwd->mwd_opt_v) return (retv); /* If the user just wants the chain heads, skip intermediate nodes. */ if (mwd->mwd_opt_c && msd->msd_position != 0) return (retv); /* If the user asked to have the entries printed, then do that. */ if (mwd->mwd_opt_e) { /* If the output is to a pipeline, just print addresses */ if (mwd->mwd_main_flags & DCMD_PIPE_OUT) mdb_printf("%p\n", addr); else retv = modent_print(addr, msd->msd_hash_index, mwd->mwd_flags, mwd->mwd_hte, mwd->mwd_opt_h, 0, 0); mwd->mwd_flags &= ~DCMD_LOOPFIRST; } return (retv); } void modhash_help(void) { mdb_printf("Prints information about one or all mod_hash_t databases " "in the system.\n" "This command has three basic forms, summarized below.\n\n" " ::modhash [-t]\n ::modhash\n" " ::modhash -e [-ch] [-k key] [-v val] [-i index]\n\n" "In the first form, no address is provided, and a summary of all " "registered\n" "hashes in the system is printed; adding the '-t' option shows" " the hash\n" "type instead of the limits. In the second form, the address of a" " mod_hash_t\n" "is provided, and the output is in a verbose format. The final " "form prints\n" "the elements of the hash, optionally selecting just those with a " "particular\n" "key, value, and/or hash index, or just the chain heads (-c). " "The -h option\n" "shows hash indices instead of addresses.\n"); } int modhash(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mod_hash_t mh; char name[256]; int len; mod_walk_data_t mwd; uint_t opt_s = FALSE; uint_t opt_t = FALSE; char kfunc[MDB_SYM_NAMLEN]; const hash_type_entry_t *htep; boolean_t elem_flags; (void) memset(&mwd, 0, sizeof (mwd)); mwd.mwd_main_flags = flags; mwd.mwd_flags = DCMD_ADDRSPEC | DCMD_LOOP | DCMD_LOOPFIRST; mwd.mwd_maxposn = -1; mwd.mwd_idxtoprint = (uintptr_t)-1; len = mdb_getopts(argc, argv, 's', MDB_OPT_SETBITS, TRUE, &opt_s, 't', MDB_OPT_SETBITS, TRUE, &opt_t, 'c', MDB_OPT_SETBITS, TRUE, &mwd.mwd_opt_c, 'e', MDB_OPT_SETBITS, TRUE, &mwd.mwd_opt_e, 'h', MDB_OPT_SETBITS, TRUE, &mwd.mwd_opt_h, 'i', MDB_OPT_UINTPTR, &mwd.mwd_idxtoprint, 'k', MDB_OPT_UINTPTR_SET, &mwd.mwd_opt_k_set, &mwd.mwd_opt_k, 'v', MDB_OPT_UINTPTR_SET, &mwd.mwd_opt_v_set, &mwd.mwd_opt_v, NULL); if (len < argc) { argv += len; if (argv->a_type == MDB_TYPE_STRING) mdb_warn("unexpected argument: %s\n", argv->a_un.a_str); else mdb_warn("unexpected argument(s)\n"); return (DCMD_USAGE); } /* true if any element-related flags are set */ elem_flags = mwd.mwd_opt_c || mwd.mwd_opt_e || mwd.mwd_opt_h || mwd.mwd_opt_k_set || mwd.mwd_opt_v_set || mwd.mwd_idxtoprint != (uintptr_t)-1; if (!(flags & DCMD_ADDRSPEC)) { mdb_arg_t new_argv[1]; if (elem_flags) { /* * This isn't allowed so that the output doesn't become * a confusing mix of hash table descriptions and * element entries. */ mdb_warn("printing elements from all hashes is not " "permitted\n"); return (DCMD_USAGE); } /* we force short mode here, no matter what it says */ new_argv[0].a_type = MDB_TYPE_STRING; new_argv[0].a_un.a_str = opt_t ? "-st" : "-s"; if (mdb_walk_dcmd("modhash", "modhash", 1, new_argv) == -1) { mdb_warn("can't walk mod_hash structures"); return (DCMD_ERR); } return (DCMD_OK); } if (mwd.mwd_opt_e) { if (opt_s | opt_t) { mdb_warn("hash summary options not permitted when " "displaying elements\n"); return (DCMD_USAGE); } } else { if (elem_flags) { /* * This isn't allowed so that the output doesn't become * a confusing mix of hash table description and * element entries. */ mdb_warn("printing elements requires -e\n"); return (DCMD_USAGE); } } if (mdb_vread(&mh, sizeof (mh), addr) == -1) { mdb_warn("failed to read mod_hash_t at %p", addr); return (DCMD_ERR); } if (mwd.mwd_idxtoprint != (uintptr_t)-1 && mwd.mwd_idxtoprint >= mh.mh_nchains) { mdb_warn("mod_hash chain index %x out of range 0..%x\n", mwd.mwd_idxtoprint, mh.mh_nchains - 1); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags) && opt_s) { if (opt_t != 0) { mdb_printf("%%?s %6s %5s %?s %s%\n", "ADDR", "CHAINS", "ELEMS", "TYPE", "NAME"); } else { mdb_printf("%%?s %6s %5s %6s %6s %s%\n", "ADDR", "CHAINS", "ELEMS", "MAXLEN", "MAXIDX", "NAME"); } } len = mdb_readstr(name, sizeof (name), (uintptr_t)mh.mh_name); if (len < 0) (void) strcpy(name, "??"); if (mdb_lookup_by_addr((uintptr_t)mh.mh_keycmp, MDB_SYM_EXACT, kfunc, sizeof (kfunc), NULL) == -1) kfunc[0] = '\0'; for (htep = hte_table; htep->hte_type != NULL; htep++) if (strcmp(kfunc, htep->hte_comparator) == 0) break; mwd.mwd_hte = htep; if (!mwd.mwd_opt_e && !opt_s) { mdb_printf("mod_hash_t %?p %s%s:\n", addr, name, len == sizeof (name) ? "..." : ""); mdb_printf("\tKey comparator: %?p %s\n", mh.mh_keycmp, kfunc); mdb_printf("\tType: %s\n", htep->hte_type == NULL ? "unknown" : htep->hte_type); mdb_printf("\tSleep flag = %s, alloc failed = %#x\n", mh.mh_sleep ? "true" : "false", mh.mh_stat.mhs_nomem); mdb_printf("\tNumber of chains = %#x, elements = %#x\n", mh.mh_nchains, mh.mh_stat.mhs_nelems); mdb_printf("\tHits = %#x, misses = %#x, dups = %#x\n", mh.mh_stat.mhs_hit, mh.mh_stat.mhs_miss, mh.mh_stat.mhs_coll); } if (mdb_pwalk("modent", modent_format, &mwd, addr) == -1) { mdb_warn("can't walk mod_hash entries"); return (DCMD_ERR); } if (opt_s) { const char *tname; char tbuf[64]; if (htep->hte_type == NULL) { (void) mdb_snprintf(tbuf, sizeof (tbuf), "%p", mh.mh_keycmp); tname = tbuf; } else { tname = htep->hte_type; } mdb_printf("%?p %6x %5x ", addr, mh.mh_nchains, mh.mh_stat.mhs_nelems); if (opt_t != 0) { mdb_printf("%?s", tname); } else { mdb_printf("%6x %6x", mwd.mwd_maxposn + 1, mwd.mwd_maxidx); } mdb_printf(" %s%s\n", name, len == sizeof (name) ? "..." : ""); } else if (!mwd.mwd_opt_e) { mdb_printf("\tMaximum chain length = %x (at index %x, first " "entry %p)\n", mwd.mwd_maxposn + 1, mwd.mwd_maxidx, mwd.mwd_maxaddr); } return (DCMD_OK); } static void format_strhash(const mod_hash_key_t key, char *keystr, size_t keystrlen) { int len; (void) mdb_snprintf(keystr, keystrlen, "%?p ", key); len = strlen(keystr); (void) mdb_readstr(keystr + len, keystrlen - len, (uintptr_t)key); } static void format_ptrhash(const mod_hash_key_t key, char *keystr, size_t keystrlen) { int len; (void) mdb_snprintf(keystr, keystrlen, "%?p ", key); len = strlen(keystr); (void) mdb_lookup_by_addr((uintptr_t)key, MDB_SYM_EXACT, keystr + len, keystrlen - len, NULL); } static void format_idhash(const mod_hash_key_t key, char *keystr, size_t keystrlen) { (void) mdb_snprintf(keystr, keystrlen, "%?x", (uint_t)(uintptr_t)key); } static void format_default(const mod_hash_key_t key, char *keystr, size_t keystrlen) { (void) mdb_snprintf(keystr, keystrlen, "%?p", key); } void modent_help(void) { mdb_printf("Options are mutually exclusive:\n" " -t print key in symbolic form; is one of str, " "ptr, or id\n" " -v print value pointer alone\n" " -k print key pointer alone\n"); } static int modent_print(uintptr_t addr, int hidx, uint_t flags, const hash_type_entry_t *htep, boolean_t prtidx, uint_t opt_k, uint_t opt_v) { char keystr[256]; struct mod_hash_entry mhe; if (DCMD_HDRSPEC(flags) && opt_k == 0 && opt_v == 0) { mdb_printf("%%?s %?s %?s%\n", prtidx ? "HASH_IDX" : "ADDR", "VAL", "KEY"); } if (mdb_vread(&mhe, sizeof (mhe), addr) == -1) { mdb_warn("failed to read mod_hash_entry at %p", addr); return (DCMD_ERR); } if (opt_k) { mdb_printf("%p\n", mhe.mhe_key); } else if (opt_v) { mdb_printf("%p\n", mhe.mhe_val); } else { htep->hte_format(mhe.mhe_key, keystr, sizeof (keystr)); if (prtidx) mdb_printf("%?x", hidx); else mdb_printf("%?p", addr); mdb_printf(" %?p %s\n", mhe.mhe_val, keystr); } return (DCMD_OK); } /* * This prints out a single mod_hash element, showing its value and its key. * The key is decoded based on the type of hash keys in use. */ int modent(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *opt_t = NULL; const hash_type_entry_t *htep; int len; uint_t opt_k = 0; uint_t opt_v = 0; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("address of mod_hash_entry must be specified\n"); return (DCMD_ERR); } len = mdb_getopts(argc, argv, 't', MDB_OPT_STR, &opt_t, 'k', MDB_OPT_SETBITS, 1, &opt_k, 'v', MDB_OPT_SETBITS, 1, &opt_v, NULL); /* options are mutually exclusive */ if ((opt_k && opt_v) || (opt_t != NULL && (opt_k || opt_v)) || len < argc) { return (DCMD_USAGE); } for (htep = hte_table; htep->hte_type != NULL; htep++) if (opt_t != NULL && strcmp(opt_t, htep->hte_type) == 0) break; if (opt_t != NULL && htep->hte_type == NULL) { mdb_warn("unknown hash type %s\n", opt_t); return (DCMD_ERR); } return (modent_print(addr, 0, flags, htep, FALSE, opt_k, opt_v)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_MODHASH_H #define _MDB_MODHASH_H #ifdef __cplusplus extern "C" { #endif #include /* walkers */ extern int modhash_walk_init(mdb_walk_state_t *); extern int modhash_walk_step(mdb_walk_state_t *); extern int modent_walk_init(mdb_walk_state_t *); extern int modent_walk_step(mdb_walk_state_t *); extern void modent_walk_fini(mdb_walk_state_t *); extern int modchain_walk_step(mdb_walk_state_t *); /* dcmds */ extern int modhash(uintptr_t, uint_t, int, const mdb_arg_t *); extern void modhash_help(void); extern int modent(uintptr_t, uint_t, int, const mdb_arg_t *); extern void modent_help(void); #ifdef __cplusplus } #endif #endif /* _MDB_MODHASH_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2018, Joyent, Inc. */ #include "ndievents.h" #include #include #include #include #include #include int dip_to_pathname(struct dev_info *device, char *path, int buflen) { char *bp; char *addr; char addr_str[32]; char nodename[MAXNAMELEN]; struct dev_info devi_parent; if (!device) { mdb_warn("Unable to access devinfo."); return (-1); } if (device->devi_parent == NULL) { if (mdb_readstr(nodename, sizeof (nodename), (uintptr_t)device->devi_node_name) == -1) { return (-1); } if (sizeof (nodename) > (buflen - strlen(path))) { return (-1); } strncpy(path, nodename, sizeof (nodename)); return (0); } if (mdb_vread(&devi_parent, sizeof (struct dev_info), (uintptr_t)device->devi_parent) == -1) { mdb_warn("Unable to access devi_parent at %p", (uintptr_t)device->devi_parent); return (-1); } if (dip_to_pathname(&devi_parent, path, buflen) == -1) { return (-1); } if (mdb_readstr(nodename, sizeof (nodename), (uintptr_t)device->devi_node_name) == -1) { return (-1); } if (device->devi_node_state < DS_INITIALIZED) { addr_str[0] = '\0'; } else { addr = device->devi_addr; if (mdb_readstr(addr_str, sizeof (addr_str), (uintptr_t)addr) == -1) { return (-1); } } bp = path + strlen(path); if (addr_str[0] == '\0') { (void) mdb_snprintf(bp, buflen - strlen(path), "/%s", nodename); } else { (void) mdb_snprintf(bp, buflen - strlen(path), "/%s@%s", nodename, addr_str); } return (0); } /*ARGSUSED*/ int ndi_callback_print(struct ndi_event_cookie *cookie, uint_t flags) { struct ndi_event_callbacks *callback_list; struct ndi_event_callbacks cb; char device_path[MAXPATHLEN]; struct dev_info devi; if (!cookie) { return (DCMD_ERR); } callback_list = cookie->callback_list; while (callback_list != NULL) { if (mdb_vread(&cb, sizeof (struct ndi_event_callbacks), (uintptr_t)callback_list) == -1) { mdb_warn("Could not read callback structure at" " %p", callback_list); return (DCMD_ERR); } if (mdb_vread(&devi, sizeof (struct dev_info), (uintptr_t)cb.ndi_evtcb_dip) == -1) { mdb_warn("Could not read devinfo structure at" " %p", cb.ndi_evtcb_dip); return (DCMD_ERR); } if (dip_to_pathname(&devi, device_path, sizeof (device_path)) == -1) { return (DCMD_ERR); } mdb_printf("\t\tCallback Registered By: %s\n", device_path); mdb_printf("\t\t Callback Address:\t%-?p\n" "\t\t Callback Function:\t%-p\n" "\t\t Callback Args:\t%-?p\n" "\t\t Callback Cookie:\t%-?p\n", callback_list, cb.ndi_evtcb_callback, cb.ndi_evtcb_arg, cb.ndi_evtcb_cookie); callback_list = cb.ndi_evtcb_next; } return (DCMD_OK); } int ndi_event_print(struct ndi_event_hdl *hdl, uint_t flags) { struct ndi_event_definition def; struct ndi_event_cookie cookie; struct ndi_event_cookie *cookie_list; char ndi_event_name[256]; if (!hdl) return (DCMD_ERR); cookie_list = hdl->ndi_evthdl_cookie_list; if (cookie_list == NULL) { mdb_printf("\tNo cookies defined for this handle.\n"); return (DCMD_OK); } while (cookie_list != NULL) { if (mdb_vread(&cookie, sizeof (struct ndi_event_cookie), (uintptr_t)cookie_list) == -1) { mdb_warn("Unable to access cookie list"); return (DCMD_ERR); } if (mdb_vread(&def, sizeof (struct ndi_event_definition), (uintptr_t)cookie.definition) == -1) { mdb_warn("Unable to access definition at %p", cookie.definition); return (DCMD_ERR); } if (mdb_readstr(ndi_event_name, sizeof (ndi_event_name), (uintptr_t)def.ndi_event_name) == -1) { mdb_warn("Unable to read cookie name."); return (DCMD_ERR); } mdb_printf("\tCookie(%s %p) :Plevel(%d)\n\tddip(%p)" " : Attr(%d)\n", ndi_event_name, cookie_list, def.ndi_event_plevel, cookie.ddip, def.ndi_event_attributes); ndi_callback_print(&cookie, flags); cookie_list = cookie.next_cookie; } return (0); } /*ARGSUSED*/ int ndi_event_hdl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct dev_info devi; struct ndi_event_hdl handle; char path[MAXPATHLEN]; int done; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_vread(&handle, sizeof (struct ndi_event_hdl), addr) == -1) { mdb_warn("failed to read ndi_event_hdl at %p", addr); return (DCMD_ERR); } if (mdb_vread(&devi, sizeof (struct dev_info), (uintptr_t)handle.ndi_evthdl_dip) == -1) { mdb_warn("failed to read devinfo node at %p", handle.ndi_evthdl_dip); return (DCMD_ERR); } if (dip_to_pathname(&devi, path, sizeof (path)) == -1) { return (DCMD_ERR); } done = 0; while (!done) { mdb_printf("%Handle% (%p) :% Path% (%s) : %" "dip %(%p) \n", addr, path, handle.ndi_evthdl_dip); mdb_printf("mutexes: handle(%p) callback(%p)\n", handle.ndi_evthdl_mutex, handle.ndi_evthdl_cb_mutex); ndi_event_print(&handle, flags); if (handle.ndi_next_hdl == NULL) { done = 1; } else { addr = (uintptr_t)handle.ndi_next_hdl; if (mdb_vread(&handle, sizeof (struct ndi_event_hdl), (uintptr_t)addr) == -1) { mdb_warn("failed to read ndi_event_hdl at %p", addr); break; } } } return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NDIEVENTS_H #define _NDIEVENTS_H #include #include #ifdef __cplusplus extern "C" { #endif extern int ndi_event_hdl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); #ifdef __cplusplus } #endif #endif /* _NDIEVENTS_H */ /* * 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 2024 MNX Cloud, Inc. */ #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 #define ADDR_V6_WIDTH 23 #define ADDR_V4_WIDTH 15 #define NETSTAT_ALL 0x01 #define NETSTAT_VERBOSE 0x02 #define NETSTAT_ROUTE 0x04 #define NETSTAT_V4 0x08 #define NETSTAT_V6 0x10 #define NETSTAT_UNIX 0x20 #define NETSTAT_FIRST 0x80000000u typedef struct netstat_cb_data_s { uint_t opts; conn_t conn; int af; union { tcp_t tcp; udp_t udp; icmp_t icmp; } cb_proto; } netstat_cb_data_t; int icmp_stacks_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("netstack", wsp) == -1) { mdb_warn("can't walk 'netstack'"); return (WALK_ERR); } return (WALK_NEXT); } int icmp_stacks_walk_step(mdb_walk_state_t *wsp) { uintptr_t kaddr; netstack_t nss; if (mdb_vread(&nss, sizeof (nss), wsp->walk_addr) == -1) { mdb_warn("can't read netstack at %p", wsp->walk_addr); return (WALK_ERR); } kaddr = (uintptr_t)nss.netstack_modules[NS_ICMP]; return (wsp->walk_callback(kaddr, wsp->walk_layer, wsp->walk_cbdata)); } int tcp_stacks_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("netstack", wsp) == -1) { mdb_warn("can't walk 'netstack'"); return (WALK_ERR); } return (WALK_NEXT); } int tcp_stacks_walk_step(mdb_walk_state_t *wsp) { uintptr_t kaddr; netstack_t nss; if (mdb_vread(&nss, sizeof (nss), wsp->walk_addr) == -1) { mdb_warn("can't read netstack at %p", wsp->walk_addr); return (WALK_ERR); } kaddr = (uintptr_t)nss.netstack_modules[NS_TCP]; return (wsp->walk_callback(kaddr, wsp->walk_layer, wsp->walk_cbdata)); } int udp_stacks_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("netstack", wsp) == -1) { mdb_warn("can't walk 'netstack'"); return (WALK_ERR); } return (WALK_NEXT); } int udp_stacks_walk_step(mdb_walk_state_t *wsp) { uintptr_t kaddr; netstack_t nss; if (mdb_vread(&nss, sizeof (nss), wsp->walk_addr) == -1) { mdb_warn("can't read netstack at %p", wsp->walk_addr); return (WALK_ERR); } kaddr = (uintptr_t)nss.netstack_modules[NS_UDP]; return (wsp->walk_callback(kaddr, wsp->walk_layer, wsp->walk_cbdata)); } /* * Print an IPv4 address and port number in a compact and easy to read format * The arguments are in network byte order */ static void net_ipv4addrport_pr(const in6_addr_t *nipv6addr, in_port_t nport) { uint32_t naddr = V4_PART_OF_V6((*nipv6addr)); mdb_nhconvert(&nport, &nport, sizeof (nport)); mdb_printf("%*I.%-5hu", ADDR_V4_WIDTH, naddr, nport); } /* * Print an IPv6 address and port number in a compact and easy to read format * The arguments are in network byte order */ static void net_ipv6addrport_pr(const in6_addr_t *naddr, in_port_t nport) { mdb_nhconvert(&nport, &nport, sizeof (nport)); mdb_printf("%*N.%-5hu", ADDR_V6_WIDTH, naddr, nport); } static int net_tcp_active(const tcp_t *tcp) { return (tcp->tcp_state >= TCPS_ESTABLISHED); } static int net_tcp_ipv4(const tcp_t *tcp) { return ((tcp->tcp_connp->conn_ipversion == IPV4_VERSION) || (IN6_IS_ADDR_UNSPECIFIED(&tcp->tcp_connp->conn_laddr_v6) && (tcp->tcp_state <= TCPS_LISTEN))); } static int net_tcp_ipv6(const tcp_t *tcp) { return (tcp->tcp_connp->conn_ipversion == IPV6_VERSION); } static int net_udp_active(const udp_t *udp) { return ((udp->udp_state == TS_IDLE) || (udp->udp_state == TS_DATA_XFER)); } static int net_udp_ipv4(const udp_t *udp) { return ((udp->udp_connp->conn_ipversion == IPV4_VERSION) || (IN6_IS_ADDR_UNSPECIFIED(&udp->udp_connp->conn_laddr_v6) && (udp->udp_state <= TS_IDLE))); } static int net_udp_ipv6(const udp_t *udp) { return (udp->udp_connp->conn_ipversion == IPV6_VERSION); } int sonode_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { GElf_Sym sym; struct socklist *slp; if (mdb_lookup_by_obj("sockfs", "socklist", &sym) == -1) { mdb_warn("failed to lookup sockfs`socklist"); return (WALK_ERR); } slp = (struct socklist *)(uintptr_t)sym.st_value; if (mdb_vread(&wsp->walk_addr, sizeof (wsp->walk_addr), (uintptr_t)&slp->sl_list) == -1) { mdb_warn("failed to read address of initial sonode " "at %p", &slp->sl_list); return (WALK_ERR); } } wsp->walk_data = mdb_alloc(sizeof (struct sotpi_sonode), UM_SLEEP); return (WALK_NEXT); } int sonode_walk_step(mdb_walk_state_t *wsp) { int status; struct sotpi_sonode *stp; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct sotpi_sonode), wsp->walk_addr) == -1) { mdb_warn("failed to read sonode at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); stp = wsp->walk_data; wsp->walk_addr = (uintptr_t)stp->st_info.sti_next_so; return (status); } void sonode_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct sotpi_sonode)); } struct mi_walk_data { uintptr_t mi_wd_miofirst; MI_O mi_wd_miodata; }; int mi_walk_init(mdb_walk_state_t *wsp) { struct mi_walk_data *wdp; if (wsp->walk_addr == 0) { mdb_warn("mi doesn't support global walks\n"); return (WALK_ERR); } wdp = mdb_alloc(sizeof (struct mi_walk_data), UM_SLEEP); /* So that we do not immediately return WALK_DONE below */ wdp->mi_wd_miofirst = 0; wsp->walk_data = wdp; return (WALK_NEXT); } int mi_walk_step(mdb_walk_state_t *wsp) { struct mi_walk_data *wdp = wsp->walk_data; MI_OP miop = &wdp->mi_wd_miodata; int status; /* Always false in the first iteration */ if ((wsp->walk_addr == (uintptr_t)NULL) || (wsp->walk_addr == wdp->mi_wd_miofirst)) { return (WALK_DONE); } if (mdb_vread(miop, sizeof (MI_O), wsp->walk_addr) == -1) { mdb_warn("failed to read MI object at %p", wsp->walk_addr); return (WALK_ERR); } /* Only true in the first iteration */ if (wdp->mi_wd_miofirst == 0) { wdp->mi_wd_miofirst = wsp->walk_addr; status = WALK_NEXT; } else { status = wsp->walk_callback(wsp->walk_addr + sizeof (MI_O), &miop[1], wsp->walk_cbdata); } wsp->walk_addr = (uintptr_t)miop->mi_o_next; return (status); } void mi_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct mi_walk_data)); } typedef struct mi_payload_walk_arg_s { const char *mi_pwa_walker; /* Underlying walker */ const off_t mi_pwa_head_off; /* Offset for mi_o_head_t * in stack */ const size_t mi_pwa_size; /* size of mi payload */ const uint_t mi_pwa_flags; /* device and/or module */ } mi_payload_walk_arg_t; #define MI_PAYLOAD_DEVICE 0x1 #define MI_PAYLOAD_MODULE 0x2 int mi_payload_walk_init(mdb_walk_state_t *wsp) { const mi_payload_walk_arg_t *arg = wsp->walk_arg; if (mdb_layered_walk(arg->mi_pwa_walker, wsp) == -1) { mdb_warn("can't walk '%s'", arg->mi_pwa_walker); return (WALK_ERR); } return (WALK_NEXT); } int mi_payload_walk_step(mdb_walk_state_t *wsp) { const mi_payload_walk_arg_t *arg = wsp->walk_arg; uintptr_t kaddr; kaddr = wsp->walk_addr + arg->mi_pwa_head_off; if (mdb_vread(&kaddr, sizeof (kaddr), kaddr) == -1) { mdb_warn("can't read address of mi head at %p for %s", kaddr, arg->mi_pwa_walker); return (WALK_ERR); } if (kaddr == 0) { /* Empty list */ return (WALK_DONE); } if (mdb_pwalk("genunix`mi", wsp->walk_callback, wsp->walk_cbdata, kaddr) == -1) { mdb_warn("failed to walk genunix`mi"); return (WALK_ERR); } return (WALK_NEXT); } const mi_payload_walk_arg_t mi_icmp_arg = { "icmp_stacks", OFFSETOF(icmp_stack_t, is_head), sizeof (icmp_t), MI_PAYLOAD_DEVICE | MI_PAYLOAD_MODULE }; int sonode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *optf = NULL; const char *optt = NULL; const char *optp = NULL; int family = AF_UNSPEC, type = 0, proto = 0; int filter = 0; struct sonode so; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("genunix`sonode", "genunix`sonode", argc, argv) == -1) { mdb_warn("failed to walk sonode"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'f', MDB_OPT_STR, &optf, 't', MDB_OPT_STR, &optt, 'p', MDB_OPT_STR, &optp, NULL) != argc) return (DCMD_USAGE); if (optf != NULL) { if (strcmp("inet", optf) == 0) family = AF_INET; else if (strcmp("inet6", optf) == 0) family = AF_INET6; else if (strcmp("unix", optf) == 0) family = AF_UNIX; else family = mdb_strtoull(optf); filter = 1; } if (optt != NULL) { if (strcmp("stream", optt) == 0) type = SOCK_STREAM; else if (strcmp("dgram", optt) == 0) type = SOCK_DGRAM; else if (strcmp("raw", optt) == 0) type = SOCK_RAW; else type = mdb_strtoull(optt); filter = 1; } if (optp != NULL) { proto = mdb_strtoull(optp); filter = 1; } if (DCMD_HDRSPEC(flags) && !filter) { mdb_printf("%%-?s Family Type Proto State Mode Flag " "AccessVP%\n", "Sonode:"); } if (mdb_vread(&so, sizeof (so), addr) == -1) { mdb_warn("failed to read sonode at %p", addr); return (DCMD_ERR); } if ((optf != NULL) && (so.so_family != family)) return (DCMD_OK); if ((optt != NULL) && (so.so_type != type)) return (DCMD_OK); if ((optp != NULL) && (so.so_protocol != proto)) return (DCMD_OK); if (filter) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } mdb_printf("%0?p ", addr); switch (so.so_family) { case AF_UNIX: mdb_printf("unix "); break; case AF_INET: mdb_printf("inet "); break; case AF_INET6: mdb_printf("inet6 "); break; default: mdb_printf("%6hi", so.so_family); } switch (so.so_type) { case SOCK_STREAM: mdb_printf(" strm"); break; case SOCK_DGRAM: mdb_printf(" dgrm"); break; case SOCK_RAW: mdb_printf(" raw "); break; default: mdb_printf(" %4hi", so.so_type); } mdb_printf(" %5hi %05x %04x %04hx\n", so.so_protocol, so.so_state, so.so_mode, so.so_flag); return (DCMD_OK); } #define MI_PAYLOAD 0x1 #define MI_DEVICE 0x2 #define MI_MODULE 0x4 int mi(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts = 0; MI_O mio; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'p', MDB_OPT_SETBITS, MI_PAYLOAD, &opts, 'd', MDB_OPT_SETBITS, MI_DEVICE, &opts, 'm', MDB_OPT_SETBITS, MI_MODULE, &opts, NULL) != argc) return (DCMD_USAGE); if ((opts & (MI_DEVICE | MI_MODULE)) == (MI_DEVICE | MI_MODULE)) { mdb_warn("at most one filter, d for devices or m " "for modules, may be specified\n"); return (DCMD_USAGE); } if ((opts == 0) && (DCMD_HDRSPEC(flags))) { mdb_printf("%%-?s %-?s %-?s IsDev Dev%\n", "MI_O", "Next", "Prev"); } if (mdb_vread(&mio, sizeof (mio), addr) == -1) { mdb_warn("failed to read mi object MI_O at %p", addr); return (DCMD_ERR); } if (opts != 0) { if (mio.mi_o_isdev == B_FALSE) { /* mio is a module */ if (!(opts & MI_MODULE) && (opts & MI_DEVICE)) return (DCMD_OK); } else { /* mio is a device */ if (!(opts & MI_DEVICE) && (opts & MI_MODULE)) return (DCMD_OK); } if (opts & MI_PAYLOAD) mdb_printf("%p\n", addr + sizeof (MI_O)); else mdb_printf("%p\n", addr); return (DCMD_OK); } mdb_printf("%0?p %0?p %0?p ", addr, mio.mi_o_next, mio.mi_o_prev); if (mio.mi_o_isdev == B_FALSE) mdb_printf("FALSE"); else mdb_printf("TRUE "); mdb_printf(" %0?p\n", mio.mi_o_dev); return (DCMD_OK); } static int ns_to_stackid(uintptr_t kaddr) { netstack_t nss; if (mdb_vread(&nss, sizeof (nss), kaddr) == -1) { mdb_warn("failed to read netstack_t %p", kaddr); return (0); } return (nss.netstack_stackid); } static void netstat_tcp_verbose_pr(const tcp_t *tcp) { mdb_printf(" %5i %08x %08x %5i %08x %08x %5li %5i\n", tcp->tcp_swnd, tcp->tcp_snxt, tcp->tcp_suna, tcp->tcp_rwnd, tcp->tcp_rack, tcp->tcp_rnxt, tcp->tcp_rto, tcp->tcp_mss); } static int netstat_tcp_cb(uintptr_t kaddr, const void *walk_data __unused, void *cb_data) { netstat_cb_data_t *ncb = cb_data; uint_t opts = ncb->opts; int af = ncb->af; uintptr_t tcp_kaddr; conn_t *connp = &ncb->conn; tcp_t *tcp = &ncb->cb_proto.tcp; if (mdb_vread(connp, sizeof (conn_t), kaddr) == -1) { mdb_warn("failed to read conn_t at %p", kaddr); return (WALK_ERR); } tcp_kaddr = (uintptr_t)connp->conn_tcp; if (mdb_vread(tcp, sizeof (tcp_t), tcp_kaddr) == -1) { mdb_warn("failed to read tcp_t at %p", tcp_kaddr); return (WALK_ERR); } connp->conn_tcp = tcp; tcp->tcp_connp = connp; if (!((opts & NETSTAT_ALL) || net_tcp_active(tcp)) || (af == AF_INET && !net_tcp_ipv4(tcp)) || (af == AF_INET6 && !net_tcp_ipv6(tcp))) { return (WALK_NEXT); } mdb_printf("%0?p %2i ", tcp_kaddr, tcp->tcp_state); if (af == AF_INET) { net_ipv4addrport_pr(&connp->conn_laddr_v6, connp->conn_lport); mdb_printf(" "); net_ipv4addrport_pr(&connp->conn_faddr_v6, connp->conn_fport); } else if (af == AF_INET6) { net_ipv6addrport_pr(&connp->conn_laddr_v6, connp->conn_lport); mdb_printf(" "); net_ipv6addrport_pr(&connp->conn_faddr_v6, connp->conn_fport); } mdb_printf(" %5i", ns_to_stackid((uintptr_t)connp->conn_netstack)); mdb_printf(" %4i\n", connp->conn_zoneid); if (opts & NETSTAT_VERBOSE) netstat_tcp_verbose_pr(tcp); return (WALK_NEXT); } static int netstat_udp_cb(uintptr_t kaddr, const void *walk_data __unused, void *cb_data) { netstat_cb_data_t *ncb = cb_data; uint_t opts = ncb->opts; int af = ncb->af; udp_t *udp = &ncb->cb_proto.udp; conn_t *connp = &ncb->conn; char *state; uintptr_t udp_kaddr; if (mdb_vread(connp, sizeof (conn_t), kaddr) == -1) { mdb_warn("failed to read conn_t at %p", kaddr); return (WALK_ERR); } udp_kaddr = (uintptr_t)connp->conn_udp; if (mdb_vread(udp, sizeof (udp_t), udp_kaddr) == -1) { mdb_warn("failed to read conn_udp at %p", udp_kaddr); return (WALK_ERR); } /* Need to do these reassignments for the net_udp_*() routines below. */ connp->conn_udp = udp; udp->udp_connp = connp; if (!((opts & NETSTAT_ALL) || net_udp_active(udp)) || (af == AF_INET && !net_udp_ipv4(udp)) || (af == AF_INET6 && !net_udp_ipv6(udp))) { return (WALK_NEXT); } if (udp->udp_state == TS_UNBND) state = "UNBOUND"; else if (udp->udp_state == TS_IDLE) state = "IDLE"; else if (udp->udp_state == TS_DATA_XFER) state = "CONNECTED"; else state = "UNKNOWN"; mdb_printf("%0?p %10s ", udp_kaddr, state); if (af == AF_INET) { net_ipv4addrport_pr(&connp->conn_laddr_v6, connp->conn_lport); mdb_printf(" "); net_ipv4addrport_pr(&connp->conn_faddr_v6, connp->conn_fport); } else if (af == AF_INET6) { net_ipv6addrport_pr(&connp->conn_laddr_v6, connp->conn_lport); mdb_printf(" "); net_ipv6addrport_pr(&connp->conn_faddr_v6, connp->conn_fport); } mdb_printf(" %5i", ns_to_stackid((uintptr_t)connp->conn_netstack)); mdb_printf(" %4i\n", connp->conn_zoneid); return (WALK_NEXT); } static int netstat_icmp_cb(uintptr_t kaddr, const void *walk_data __unused, void *cb_data) { netstat_cb_data_t *ncb = cb_data; int af = ncb->af; icmp_t *icmp = &ncb->cb_proto.icmp; conn_t *connp = &ncb->conn; char *state; if (mdb_vread(connp, sizeof (conn_t), kaddr) == -1) { mdb_warn("failed to read conn_t at %p", kaddr); return (WALK_ERR); } if (mdb_vread(icmp, sizeof (icmp_t), (uintptr_t)connp->conn_icmp) == -1) { mdb_warn("failed to read conn_icmp at %p", (uintptr_t)connp->conn_icmp); return (WALK_ERR); } connp->conn_icmp = icmp; icmp->icmp_connp = connp; if ((af == AF_INET && connp->conn_ipversion != IPV4_VERSION) || (af == AF_INET6 && connp->conn_ipversion != IPV6_VERSION)) { return (WALK_NEXT); } if (icmp->icmp_state == TS_UNBND) state = "UNBOUND"; else if (icmp->icmp_state == TS_IDLE) state = "IDLE"; else if (icmp->icmp_state == TS_DATA_XFER) state = "CONNECTED"; else state = "UNKNOWN"; mdb_printf("%0?p %10s ", (uintptr_t)connp->conn_icmp, state); if (af == AF_INET) { net_ipv4addrport_pr(&connp->conn_laddr_v6, connp->conn_lport); mdb_printf(" "); net_ipv4addrport_pr(&connp->conn_faddr_v6, connp->conn_fport); } else if (af == AF_INET6) { net_ipv6addrport_pr(&connp->conn_laddr_v6, connp->conn_lport); mdb_printf(" "); net_ipv6addrport_pr(&connp->conn_faddr_v6, connp->conn_fport); } mdb_printf(" %5i", ns_to_stackid((uintptr_t)connp->conn_netstack)); mdb_printf(" %4i\n", connp->conn_zoneid); return (WALK_NEXT); } /* * print the address of a unix domain socket * * so is the address of a AF_UNIX struct sonode in mdb's address space * soa is the address of the struct soaddr to print * * returns 0 on success, -1 otherwise */ static int netstat_unix_name_pr(const struct sotpi_sonode *st, const struct soaddr *soa) { const struct sonode *so = &st->st_sonode; const char none[] = " (none)"; if ((so->so_state & SS_ISBOUND) && (soa->soa_len != 0)) { if (st->st_info.sti_faddr_noxlate) { mdb_printf("%-14s ", " (socketpair)"); } else { if (soa->soa_len > sizeof (sa_family_t)) { char addr[MAXPATHLEN + 1]; if (mdb_readstr(addr, sizeof (addr), (uintptr_t)&soa->soa_sa->sa_data) == -1) { mdb_warn("failed to read unix address " "at %p", &soa->soa_sa->sa_data); return (-1); } mdb_printf("%-14s ", addr); } else { mdb_printf("%-14s ", none); } } } else { mdb_printf("%-14s ", none); } return (0); } /* based on sockfs_snapshot */ /*ARGSUSED*/ static int netstat_unix_cb(uintptr_t kaddr, const void *walk_data, void *cb_data) { const struct sotpi_sonode *st = walk_data; const struct sonode *so = &st->st_sonode; const struct sotpi_info *sti = &st->st_info; if (so->so_count == 0) return (WALK_NEXT); if (so->so_family != AF_UNIX) { mdb_warn("sonode of family %hi at %p\n", so->so_family, kaddr); return (WALK_ERR); } mdb_printf("%-?p ", kaddr); switch (sti->sti_serv_type) { case T_CLTS: mdb_printf("%-10s ", "dgram"); break; case T_COTS: mdb_printf("%-10s ", "stream"); break; case T_COTS_ORD: mdb_printf("%-10s ", "stream-ord"); break; default: mdb_printf("%-10i ", sti->sti_serv_type); } if ((so->so_state & SS_ISBOUND) && (sti->sti_ux_laddr.soua_magic == SOU_MAGIC_EXPLICIT)) { mdb_printf("%0?p ", sti->sti_ux_laddr.soua_vp); } else { mdb_printf("%0?p ", NULL); } if ((so->so_state & SS_ISCONNECTED) && (sti->sti_ux_faddr.soua_magic == SOU_MAGIC_EXPLICIT)) { mdb_printf("%0?p ", sti->sti_ux_faddr.soua_vp); } else { mdb_printf("%0?p ", NULL); } if (netstat_unix_name_pr(st, &sti->sti_laddr) == -1) return (WALK_ERR); if (netstat_unix_name_pr(st, &sti->sti_faddr) == -1) return (WALK_ERR); mdb_printf("%4i\n", so->so_zoneid); return (WALK_NEXT); } static void netstat_tcp_verbose_header_pr(void) { mdb_printf(" %%-5s %-8s %-8s %-5s %-8s %-8s %5s %5s%\n", "Swind", "Snext", "Suna", "Rwind", "Rack", "Rnext", "Rto", "Mss"); } static void get_ifname(const ire_t *ire, char *intf) { ill_t ill; *intf = '\0'; if (ire->ire_ill != NULL) { if (mdb_vread(&ill, sizeof (ill), (uintptr_t)ire->ire_ill) == -1) return; (void) mdb_readstr(intf, MIN(LIFNAMSIZ, ill.ill_name_length), (uintptr_t)ill.ill_name); } } const in6_addr_t ipv6_all_ones = { 0xffffffffU, 0xffffffffU, 0xffffffffU, 0xffffffffU }; static void get_ireflags(const ire_t *ire, char *flags) { (void) strcpy(flags, "U"); /* RTF_INDIRECT wins over RTF_GATEWAY - don't display both */ if (ire->ire_flags & RTF_INDIRECT) (void) strcat(flags, "I"); else if (ire->ire_type & IRE_OFFLINK) (void) strcat(flags, "G"); /* IRE_IF_CLONE wins over RTF_HOST - don't display both */ if (ire->ire_type & IRE_IF_CLONE) (void) strcat(flags, "C"); else if (ire->ire_ipversion == IPV4_VERSION) { if (ire->ire_mask == IP_HOST_MASK) (void) strcat(flags, "H"); } else { if (IN6_ARE_ADDR_EQUAL(&ire->ire_mask_v6, &ipv6_all_ones)) (void) strcat(flags, "H"); } if (ire->ire_flags & RTF_DYNAMIC) (void) strcat(flags, "D"); if (ire->ire_type == IRE_BROADCAST) (void) strcat(flags, "b"); if (ire->ire_type == IRE_MULTICAST) (void) strcat(flags, "m"); if (ire->ire_type == IRE_LOCAL) (void) strcat(flags, "L"); if (ire->ire_type == IRE_NOROUTE) (void) strcat(flags, "N"); if (ire->ire_flags & RTF_MULTIRT) (void) strcat(flags, "M"); if (ire->ire_flags & RTF_SETSRC) (void) strcat(flags, "S"); if (ire->ire_flags & RTF_REJECT) (void) strcat(flags, "R"); if (ire->ire_flags & RTF_BLACKHOLE) (void) strcat(flags, "B"); } static int netstat_irev4_cb(uintptr_t kaddr, const void *walk_data, void *cb_data) { const ire_t *ire = walk_data; uint_t *opts = cb_data; ipaddr_t gate; char flags[10], intf[LIFNAMSIZ + 1]; if (ire->ire_ipversion != IPV4_VERSION) return (WALK_NEXT); /* Skip certain IREs by default */ if (!(*opts & NETSTAT_ALL) && (ire->ire_type & (IRE_BROADCAST|IRE_LOCAL|IRE_MULTICAST|IRE_NOROUTE|IRE_IF_CLONE))) return (WALK_NEXT); if (*opts & NETSTAT_FIRST) { *opts &= ~NETSTAT_FIRST; mdb_printf("%%s Table: IPv4%\n", (*opts & NETSTAT_VERBOSE) ? "IRE" : "Routing"); if (*opts & NETSTAT_VERBOSE) { mdb_printf("%%-?s %-*s %-*s %-*s Device Mxfrg Rtt " " Ref Flg Out In/Fwd%\n", "Address", ADDR_V4_WIDTH, "Destination", ADDR_V4_WIDTH, "Mask", ADDR_V4_WIDTH, "Gateway"); } else { mdb_printf("%%-?s %-*s %-*s Flags Ref Use " "Interface%\n", "Address", ADDR_V4_WIDTH, "Destination", ADDR_V4_WIDTH, "Gateway"); } } gate = ire->ire_gateway_addr; get_ireflags(ire, flags); get_ifname(ire, intf); if (*opts & NETSTAT_VERBOSE) { mdb_printf("%?p %-*I %-*I %-*I %-6s %5u%c %4u %3u %-3s %5u " "%u\n", kaddr, ADDR_V4_WIDTH, ire->ire_addr, ADDR_V4_WIDTH, ire->ire_mask, ADDR_V4_WIDTH, gate, intf, 0, ' ', ire->ire_metrics.iulp_rtt, ire->ire_refcnt, flags, ire->ire_ob_pkt_count, ire->ire_ib_pkt_count); } else { mdb_printf("%?p %-*I %-*I %-5s %4u %5u %s\n", kaddr, ADDR_V4_WIDTH, ire->ire_addr, ADDR_V4_WIDTH, gate, flags, ire->ire_refcnt, ire->ire_ob_pkt_count + ire->ire_ib_pkt_count, intf); } return (WALK_NEXT); } int ip_mask_to_plen_v6(const in6_addr_t *v6mask) { int plen; int i; uint32_t val; for (i = 3; i >= 0; i--) if (v6mask->s6_addr32[i] != 0) break; if (i < 0) return (0); plen = 32 + 32 * i; val = v6mask->s6_addr32[i]; while (!(val & 1)) { val >>= 1; plen--; } return (plen); } static int netstat_irev6_cb(uintptr_t kaddr, const void *walk_data, void *cb_data) { const ire_t *ire = walk_data; uint_t *opts = cb_data; const in6_addr_t *gatep; char deststr[ADDR_V6_WIDTH + 5]; char flags[10], intf[LIFNAMSIZ + 1]; int masklen; if (ire->ire_ipversion != IPV6_VERSION) return (WALK_NEXT); /* Skip certain IREs by default */ if (!(*opts & NETSTAT_ALL) && (ire->ire_type & (IRE_BROADCAST|IRE_LOCAL|IRE_MULTICAST|IRE_NOROUTE|IRE_IF_CLONE))) return (WALK_NEXT); if (*opts & NETSTAT_FIRST) { *opts &= ~NETSTAT_FIRST; mdb_printf("\n%%s Table: IPv6%\n", (*opts & NETSTAT_VERBOSE) ? "IRE" : "Routing"); if (*opts & NETSTAT_VERBOSE) { mdb_printf("%%-?s %-*s %-*s If PMTU Rtt Ref " "Flags Out In/Fwd%\n", "Address", ADDR_V6_WIDTH+4, "Destination/Mask", ADDR_V6_WIDTH, "Gateway"); } else { mdb_printf("%%-?s %-*s %-*s Flags Ref Use If" "%\n", "Address", ADDR_V6_WIDTH+4, "Destination/Mask", ADDR_V6_WIDTH, "Gateway"); } } gatep = &ire->ire_gateway_addr_v6; masklen = ip_mask_to_plen_v6(&ire->ire_mask_v6); (void) mdb_snprintf(deststr, sizeof (deststr), "%N/%d", &ire->ire_addr_v6, masklen); get_ireflags(ire, flags); get_ifname(ire, intf); if (*opts & NETSTAT_VERBOSE) { mdb_printf("%?p %-*s %-*N %-5s %5u%c %5u %3u %-5s %6u %u\n", kaddr, ADDR_V6_WIDTH+4, deststr, ADDR_V6_WIDTH, gatep, intf, 0, ' ', ire->ire_metrics.iulp_rtt, ire->ire_refcnt, flags, ire->ire_ob_pkt_count, ire->ire_ib_pkt_count); } else { mdb_printf("%?p %-*s %-*N %-5s %3u %6u %s\n", kaddr, ADDR_V6_WIDTH+4, deststr, ADDR_V6_WIDTH, gatep, flags, ire->ire_refcnt, ire->ire_ob_pkt_count + ire->ire_ib_pkt_count, intf); } return (WALK_NEXT); } static void netstat_header_v4(int proto) { if (proto == IPPROTO_TCP) mdb_printf("%%-?s ", "TCPv4"); else if (proto == IPPROTO_UDP) mdb_printf("%%-?s ", "UDPv4"); else if (proto == IPPROTO_ICMP) mdb_printf("%%-?s ", "ICMPv4"); mdb_printf("State %6s%*s %6s%*s %-5s %-4s%\n", "", ADDR_V4_WIDTH, "Local Address", "", ADDR_V4_WIDTH, "Remote Address", "Stack", "Zone"); } static void netstat_header_v6(int proto) { if (proto == IPPROTO_TCP) mdb_printf("%%-?s ", "TCPv6"); else if (proto == IPPROTO_UDP) mdb_printf("%%-?s ", "UDPv6"); else if (proto == IPPROTO_ICMP) mdb_printf("%%-?s ", "ICMPv6"); mdb_printf("State %6s%*s %6s%*s %-5s %-4s%\n", "", ADDR_V6_WIDTH, "Local Address", "", ADDR_V6_WIDTH, "Remote Address", "Stack", "Zone"); } static int netstat_print_conn(const char *cache, int proto, mdb_walk_cb_t cbfunc, void *cbdata) { netstat_cb_data_t *ncb = cbdata; if ((ncb->opts & NETSTAT_VERBOSE) && proto == IPPROTO_TCP) netstat_tcp_verbose_header_pr(); if (mdb_walk(cache, cbfunc, cbdata) == -1) { mdb_warn("failed to walk %s", cache); return (DCMD_ERR); } return (DCMD_OK); } static int netstat_print_common(const char *cache, int proto, mdb_walk_cb_t cbfunc, void *cbdata) { netstat_cb_data_t *ncb = cbdata; int af = ncb->af; int status = DCMD_OK; if (af != AF_INET6) { ncb->af = AF_INET; netstat_header_v4(proto); status = netstat_print_conn(cache, proto, cbfunc, cbdata); } if (status == DCMD_OK && af != AF_INET) { ncb->af = AF_INET6; netstat_header_v6(proto); status = netstat_print_conn(cache, proto, cbfunc, cbdata); } ncb->af = af; return (status); } /*ARGSUSED*/ int netstat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts = 0; const char *optf = NULL; const char *optP = NULL; netstat_cb_data_t *cbdata; int status; int af = 0; if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, NETSTAT_ALL, &opts, 'f', MDB_OPT_STR, &optf, 'P', MDB_OPT_STR, &optP, 'r', MDB_OPT_SETBITS, NETSTAT_ROUTE, &opts, 'v', MDB_OPT_SETBITS, NETSTAT_VERBOSE, &opts, NULL) != argc) return (DCMD_USAGE); if (optP != NULL) { if ((strcmp("tcp", optP) != 0) && (strcmp("udp", optP) != 0) && (strcmp("icmp", optP) != 0)) return (DCMD_USAGE); if (opts & NETSTAT_ROUTE) return (DCMD_USAGE); } if (optf == NULL) opts |= NETSTAT_V4 | NETSTAT_V6 | NETSTAT_UNIX; else if (strcmp("inet", optf) == 0) opts |= NETSTAT_V4; else if (strcmp("inet6", optf) == 0) opts |= NETSTAT_V6; else if (strcmp("unix", optf) == 0) opts |= NETSTAT_UNIX; else return (DCMD_USAGE); if (opts & NETSTAT_ROUTE) { if (!(opts & (NETSTAT_V4|NETSTAT_V6))) return (DCMD_USAGE); if (opts & NETSTAT_V4) { opts |= NETSTAT_FIRST; if (mdb_walk("ip`ire", netstat_irev4_cb, &opts) == -1) { mdb_warn("failed to walk ip`ire"); return (DCMD_ERR); } } if (opts & NETSTAT_V6) { opts |= NETSTAT_FIRST; if (mdb_walk("ip`ire", netstat_irev6_cb, &opts) == -1) { mdb_warn("failed to walk ip`ire"); return (DCMD_ERR); } } return (DCMD_OK); } if ((opts & NETSTAT_UNIX) && (optP == NULL)) { /* Print Unix Domain Sockets */ mdb_printf("%%-?s %-10s %-?s %-?s %-14s %-14s %s%\n", "AF_UNIX", "Type", "Vnode", "Conn", "Local Addr", "Remote Addr", "Zone"); if (mdb_walk("genunix`sonode", netstat_unix_cb, NULL) == -1) { mdb_warn("failed to walk genunix`sonode"); return (DCMD_ERR); } if (!(opts & (NETSTAT_V4 | NETSTAT_V6))) return (DCMD_OK); } cbdata = mdb_alloc(sizeof (netstat_cb_data_t), UM_SLEEP); cbdata->opts = opts; if ((optf != NULL) && (opts & NETSTAT_V4)) af = AF_INET; else if ((optf != NULL) && (opts & NETSTAT_V6)) af = AF_INET6; cbdata->af = af; if ((optP == NULL) || (strcmp("tcp", optP) == 0)) { status = netstat_print_common("tcp_conn_cache", IPPROTO_TCP, netstat_tcp_cb, cbdata); if (status != DCMD_OK) goto out; } if ((optP == NULL) || (strcmp("udp", optP) == 0)) { status = netstat_print_common("udp_conn_cache", IPPROTO_UDP, netstat_udp_cb, cbdata); if (status != DCMD_OK) goto out; } if ((optP == NULL) || (strcmp("icmp", optP) == 0)) { status = netstat_print_common("rawip_conn_cache", IPPROTO_ICMP, netstat_icmp_cb, cbdata); if (status != DCMD_OK) goto out; } out: mdb_free(cbdata, sizeof (netstat_cb_data_t)); return (status); } /* * "::dladm show-bridge" support */ typedef struct { uint_t opt_l; uint_t opt_f; uint_t opt_t; const char *name; clock_t lbolt; boolean_t found; uint_t nlinks; uint_t nfwd; /* * These structures are kept inside the 'args' for allocation reasons. * They're all large data structures (over 1K), and may cause the stack * to explode. mdb and kmdb will fail in these cases, and thus we * allocate them from the heap. */ trill_inst_t ti; bridge_link_t bl; mac_impl_t mi; } show_bridge_args_t; static void show_vlans(const uint8_t *vlans) { int i, bit; uint8_t val; int rstart = -1, rnext = -1; for (i = 0; i < BRIDGE_VLAN_ARR_SIZE; i++) { val = vlans[i]; if (i == 0) val &= ~1; while ((bit = mdb_ffs(val)) != 0) { bit--; val &= ~(1 << bit); bit += i * sizeof (*vlans) * NBBY; if (bit != rnext) { if (rnext != -1 && rstart + 1 != rnext) mdb_printf("-%d", rnext - 1); if (rstart != -1) mdb_printf(","); mdb_printf("%d", bit); rstart = bit; } rnext = bit + 1; } } if (rnext != -1 && rstart + 1 != rnext) mdb_printf("-%d", rnext - 1); mdb_printf("\n"); } /* * This callback is invoked by a walk of the links attached to a bridge. If * we're showing link details, then they're printed here. If not, then we just * count up the links for the bridge summary. */ static int do_bridge_links(uintptr_t addr, const void *data, void *ptr) { show_bridge_args_t *args = ptr; const bridge_link_t *blp = data; char macaddr[ETHERADDRL * 3]; const char *name; args->nlinks++; if (!args->opt_l) return (WALK_NEXT); if (mdb_vread(&args->mi, sizeof (args->mi), (uintptr_t)blp->bl_mh) == -1) { mdb_warn("cannot read mac data at %p", blp->bl_mh); name = "?"; } else { name = args->mi.mi_name; } mdb_mac_addr(blp->bl_local_mac, ETHERADDRL, macaddr, sizeof (macaddr)); mdb_printf("%-?p %-16s %-17s %03X %-4d ", addr, name, macaddr, blp->bl_flags, blp->bl_pvid); if (blp->bl_trilldata == NULL) { switch (blp->bl_state) { case BLS_BLOCKLISTEN: name = "BLOCK"; break; case BLS_LEARNING: name = "LEARN"; break; case BLS_FORWARDING: name = "FWD"; break; default: name = "?"; } mdb_printf("%-5s ", name); show_vlans(blp->bl_vlans); } else { show_vlans(blp->bl_afs); } return (WALK_NEXT); } /* * It seems a shame to duplicate this code, but merging it with the link * printing code above is more trouble than it would be worth. */ static void print_link_name(show_bridge_args_t *args, uintptr_t addr, char sep) { const char *name; if (mdb_vread(&args->bl, sizeof (args->bl), addr) == -1) { mdb_warn("cannot read bridge link at %p", addr); return; } if (mdb_vread(&args->mi, sizeof (args->mi), (uintptr_t)args->bl.bl_mh) == -1) { name = "?"; } else { name = args->mi.mi_name; } mdb_printf("%s%c", name, sep); } static int do_bridge_fwd(uintptr_t addr, const void *data, void *ptr) { show_bridge_args_t *args = ptr; const bridge_fwd_t *bfp = data; char macaddr[ETHERADDRL * 3]; int i; #define MAX_FWD_LINKS 16 bridge_link_t *links[MAX_FWD_LINKS]; uint_t nlinks; args->nfwd++; if (!args->opt_f) return (WALK_NEXT); if ((nlinks = bfp->bf_nlinks) > MAX_FWD_LINKS) nlinks = MAX_FWD_LINKS; if (mdb_vread(links, sizeof (links[0]) * nlinks, (uintptr_t)bfp->bf_links) == -1) { mdb_warn("cannot read bridge forwarding links at %p", bfp->bf_links); return (WALK_ERR); } mdb_mac_addr(bfp->bf_dest, ETHERADDRL, macaddr, sizeof (macaddr)); mdb_printf("%-?p %-17s ", addr, macaddr); if (bfp->bf_flags & BFF_LOCALADDR) mdb_printf("%-7s", "[self]"); else mdb_printf("t-%-5d", args->lbolt - bfp->bf_lastheard); mdb_printf(" %-7u ", bfp->bf_refs); if (bfp->bf_trill_nick != 0) { mdb_printf("%d\n", bfp->bf_trill_nick); } else { for (i = 0; i < bfp->bf_nlinks; i++) { print_link_name(args, (uintptr_t)links[i], i == bfp->bf_nlinks - 1 ? '\n' : ' '); } } return (WALK_NEXT); } static int do_show_bridge(uintptr_t addr, const void *data, void *ptr) { show_bridge_args_t *args = ptr; bridge_inst_t bi; const bridge_inst_t *bip; trill_node_t tn; trill_sock_t tsp; trill_nickinfo_t tni; char bname[MAXLINKNAMELEN]; char macaddr[ETHERADDRL * 3]; uint_t nnicks; int i; if (data != NULL) { bip = data; } else { if (mdb_vread(&bi, sizeof (bi), addr) == -1) { mdb_warn("cannot read bridge instance at %p", addr); return (WALK_ERR); } bip = &bi; } (void) strncpy(bname, bip->bi_name, sizeof (bname) - 1); bname[MAXLINKNAMELEN - 1] = '\0'; i = strlen(bname); if (i > 1 && bname[i - 1] == '0') bname[i - 1] = '\0'; if (args->name != NULL && strcmp(args->name, bname) != 0) return (WALK_NEXT); args->found = B_TRUE; args->nlinks = args->nfwd = 0; if (args->opt_l) { mdb_printf("%-?s %-16s %-17s %3s %-4s ", "ADDR", "LINK", "MAC-ADDR", "FLG", "PVID"); if (bip->bi_trilldata == NULL) mdb_printf("%-5s %s\n", "STATE", "VLANS"); else mdb_printf("%s\n", "FWD-VLANS"); } if (!args->opt_f && !args->opt_t && mdb_pwalk("list", do_bridge_links, args, addr + offsetof(bridge_inst_t, bi_links)) != DCMD_OK) return (WALK_ERR); if (args->opt_f) mdb_printf("%-?s %-17s %-7s %-7s %s\n", "ADDR", "DEST", "TIME", "REFS", "OUTPUT"); if (!args->opt_l && !args->opt_t && mdb_pwalk("avl", do_bridge_fwd, args, addr + offsetof(bridge_inst_t, bi_fwd)) != DCMD_OK) return (WALK_ERR); nnicks = 0; if (bip->bi_trilldata != NULL && !args->opt_l && !args->opt_f) { if (mdb_vread(&args->ti, sizeof (args->ti), (uintptr_t)bip->bi_trilldata) == -1) { mdb_warn("cannot read trill instance at %p", bip->bi_trilldata); return (WALK_ERR); } if (args->opt_t) mdb_printf("%-?s %-5s %-17s %s\n", "ADDR", "NICK", "NEXT-HOP", "LINK"); for (i = 0; i < RBRIDGE_NICKNAME_MAX; i++) { if (args->ti.ti_nodes[i] == NULL) continue; if (args->opt_t) { if (mdb_vread(&tn, sizeof (tn), (uintptr_t)args->ti.ti_nodes[i]) == -1) { mdb_warn("cannot read trill node %d at " "%p", i, args->ti.ti_nodes[i]); return (WALK_ERR); } if (mdb_vread(&tni, sizeof (tni), (uintptr_t)tn.tn_ni) == -1) { mdb_warn("cannot read trill node info " "%d at %p", i, tn.tn_ni); return (WALK_ERR); } mdb_mac_addr(tni.tni_adjsnpa, ETHERADDRL, macaddr, sizeof (macaddr)); if (tni.tni_nick == args->ti.ti_nick) { (void) strcpy(macaddr, "[self]"); } mdb_printf("%-?p %-5u %-17s ", args->ti.ti_nodes[i], tni.tni_nick, macaddr); if (tn.tn_tsp != NULL) { if (mdb_vread(&tsp, sizeof (tsp), (uintptr_t)tn.tn_tsp) == -1) { mdb_warn("cannot read trill " "socket info at %p", tn.tn_tsp); return (WALK_ERR); } if (tsp.ts_link != NULL) { print_link_name(args, (uintptr_t)tsp.ts_link, '\n'); continue; } } mdb_printf("--\n"); } else { nnicks++; } } } else { if (args->opt_t) mdb_printf("bridge is not running TRILL\n"); } if (!args->opt_l && !args->opt_f && !args->opt_t) { mdb_printf("%-?p %-7s %-16s %-7u %-7u", addr, bip->bi_trilldata == NULL ? "stp" : "trill", bname, args->nlinks, args->nfwd); if (bip->bi_trilldata != NULL) mdb_printf(" %-7u %u\n", nnicks, args->ti.ti_nick); else mdb_printf(" %-7s %s\n", "--", "--"); } return (WALK_NEXT); } static int dladm_show_bridge(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { show_bridge_args_t *args; GElf_Sym sym; int i; args = mdb_zalloc(sizeof (*args), UM_SLEEP); i = mdb_getopts(argc, argv, 'l', MDB_OPT_SETBITS, 1, &args->opt_l, 'f', MDB_OPT_SETBITS, 1, &args->opt_f, 't', MDB_OPT_SETBITS, 1, &args->opt_t, NULL); argc -= i; argv += i; if (argc > 1 || (argc == 1 && argv[0].a_type != MDB_TYPE_STRING)) { mdb_free(args, sizeof (*args)); return (DCMD_USAGE); } if (argc == 1) args->name = argv[0].a_un.a_str; if ((args->lbolt = mdb_get_lbolt()) == -1) { mdb_warn("failed to read lbolt"); goto err; } if (flags & DCMD_ADDRSPEC) { if (args->name != NULL) { mdb_printf("bridge name and address are mutually " "exclusive\n"); goto err; } if (!args->opt_l && !args->opt_f && !args->opt_t) mdb_printf("%-?s %-7s %-16s %-7s %-7s\n", "ADDR", "PROTECT", "NAME", "NLINKS", "NFWD"); if (do_show_bridge(addr, NULL, args) != WALK_NEXT) goto err; mdb_free(args, sizeof (*args)); return (DCMD_OK); } else { if ((args->opt_l || args->opt_f || args->opt_t) && args->name == NULL) { mdb_printf("need bridge name or address with -[lft]\n"); goto err; } if (mdb_lookup_by_obj("bridge", "inst_list", &sym) == -1) { mdb_warn("failed to find 'bridge`inst_list'"); goto err; } if (!args->opt_l && !args->opt_f && !args->opt_t) mdb_printf("%-?s %-7s %-16s %-7s %-7s %-7s %s\n", "ADDR", "PROTECT", "NAME", "NLINKS", "NFWD", "NNICKS", "NICK"); if (mdb_pwalk("list", do_show_bridge, args, (uintptr_t)sym.st_value) != DCMD_OK) goto err; if (!args->found && args->name != NULL) { mdb_printf("bridge instance %s not found\n", args->name); goto err; } mdb_free(args, sizeof (*args)); return (DCMD_OK); } err: mdb_free(args, sizeof (*args)); return (DCMD_ERR); } /* * Support for the "::dladm" dcmd */ int dladm(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc < 1 || argv[0].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); /* * This could be a bit more elaborate, once we support more of the * dladm show-* subcommands. */ argc--; argv++; if (strcmp(argv[-1].a_un.a_str, "show-bridge") == 0) return (dladm_show_bridge(addr, flags, argc, argv)); return (DCMD_USAGE); } void dladm_help(void) { mdb_printf("Subcommands:\n" " show-bridge [-flt] []\n" "\t Show bridge information; -l for links and -f for " "forwarding\n" "\t entries, and -t for TRILL nicknames. Address is required " "if name\n" "\t is not specified.\n"); } /* * 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. */ #ifndef _NET_H #define _NET_H #ifdef __cplusplus extern "C" { #endif extern struct mi_payload_walk_arg_s mi_icmp_arg; extern struct mi_payload_walk_arg_s mi_ill_arg; extern int sonode_walk_init(mdb_walk_state_t *); extern int sonode_walk_step(mdb_walk_state_t *); extern void sonode_walk_fini(mdb_walk_state_t *); extern int mi_walk_init(mdb_walk_state_t *); extern int mi_walk_step(mdb_walk_state_t *); extern void mi_walk_fini(mdb_walk_state_t *); extern int mi_payload_walk_init(mdb_walk_state_t *); extern int mi_payload_walk_step(mdb_walk_state_t *); extern int icmp_stacks_walk_init(mdb_walk_state_t *); extern int icmp_stacks_walk_step(mdb_walk_state_t *); extern int tcp_stacks_walk_init(mdb_walk_state_t *); extern int tcp_stacks_walk_step(mdb_walk_state_t *); extern int udp_stacks_walk_init(mdb_walk_state_t *); extern int udp_stacks_walk_step(mdb_walk_state_t *); extern int sonode(uintptr_t, uint_t, int, const mdb_arg_t *); extern int mi(uintptr_t, uint_t, int, const mdb_arg_t *); extern int netstat(uintptr_t, uint_t, int, const mdb_arg_t *); extern int dladm(uintptr_t, uint_t, int, const mdb_arg_t *); extern void dladm_help(void); #ifdef __cplusplus } #endif #endif /* _NET_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2012, Joyent, Inc. All rights reserved. */ #include #include #include #include #include int netstack_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; uintptr_t addr; if (mdb_lookup_by_name("netstack_head", &sym) == -1) { mdb_warn("couldn't find netstack_head"); return (WALK_ERR); } addr = (uintptr_t)sym.st_value; if (mdb_vread(&wsp->walk_addr, sizeof (wsp->walk_addr), addr) == -1) { mdb_warn("failed to read address of initial netstack " "at %p", addr); return (WALK_ERR); } return (WALK_NEXT); } int netstack_walk_step(mdb_walk_state_t *wsp) { int status; netstack_t nss; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&nss, sizeof (netstack_t), wsp->walk_addr) == -1) { mdb_warn("failed to read netstack at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &nss, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); wsp->walk_addr = (uintptr_t)nss.netstack_next; return (status); } /*ARGSUSED*/ int netstack(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { netstack_t nss; uint_t quiet = FALSE; uint_t verbose = FALSE; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("genunix`netstack", "genunix`netstack", argc, argv) == -1) { mdb_warn("failed to walk netstack"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'q', MDB_OPT_SETBITS, TRUE, &quiet, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags) && !quiet) { mdb_printf("%?s %-7s %6s\n", "ADDR", "STACKID", "FLAGS"); } if (mdb_vread(&nss, sizeof (nss), addr) == -1) { mdb_warn("couldn't read netstack at %p", addr); return (DCMD_ERR); } /* * Options are specified for filtering, so If any option is specified on * the command line, just print address and exit. */ if (quiet) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } mdb_printf("%0?p %6d %06x\n", addr, nss.netstack_stackid, nss.netstack_flags); return (DCMD_OK); } static int netstackid_lookup_cb(uintptr_t addr, const netstack_t *ns, void *arg) { netstackid_t nid = *(uintptr_t *)arg; if (ns->netstack_stackid == nid) mdb_printf("%p\n", addr); return (WALK_NEXT); } /*ARGSUSED*/ int netstackid2netstack(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_walk("netstack", (mdb_walk_cb_t)netstackid_lookup_cb, &addr) == -1) { mdb_warn("failed to walk zone"); return (DCMD_ERR); } return (DCMD_OK); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NETSTACK_H #define _NETSTACK_H #include #ifdef __cplusplus extern "C" { #endif int netstack_walk_init(mdb_walk_state_t *); int netstack_walk_step(mdb_walk_state_t *); int netstack(uintptr_t, uint_t, int, const mdb_arg_t *); int netstackid2netstack(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _NETSTACK_H */ /* * 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. */ #include #include #include #include #include #include "nvpair.h" #define NVPAIR_VALUE_INDENT 4 #define NELEM(a) (sizeof (a) / sizeof ((a)[0])) /* * nvpair walker */ int nvpair_walk_init(mdb_walk_state_t *wsp) { nvlist_t nvlist; nvpriv_t nvpriv; i_nvp_t *tmp; if (wsp->walk_addr == 0) { mdb_warn("nvpair does not support global walks\n"); return (WALK_ERR); } if (mdb_vread(&nvlist, sizeof (nvlist), wsp->walk_addr) == -1) { mdb_warn("failed to read nvlist at %p", wsp->walk_addr); return (WALK_ERR); } if (mdb_vread(&nvpriv, sizeof (nvpriv), nvlist.nvl_priv) == -1) { mdb_warn("failed to read nvpriv at %p", nvlist.nvl_priv); return (WALK_ERR); } tmp = (i_nvp_t *)nvpriv.nvp_list; wsp->walk_addr = (uintptr_t)tmp; return (WALK_NEXT); } int nvpair_walk_step(mdb_walk_state_t *wsp) { int status; nvpair_t *nvpair; i_nvp_t i_nvp, *tmp; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&i_nvp, sizeof (i_nvp), wsp->walk_addr) == -1) { mdb_warn("failed to read i_nvp at %p", wsp->walk_addr); return (WALK_ERR); } nvpair = &((i_nvp_t *)wsp->walk_addr)->nvi_nvp; status = wsp->walk_callback((uintptr_t)nvpair, NULL, wsp->walk_cbdata); tmp = i_nvp.nvi_next; wsp->walk_addr = (uintptr_t)tmp; return (status); } /* * ::nvlist [-v] * * Print out an entire nvlist. This is shorthand for '::walk nvpair | * ::nvpair -rq'. The '-v' option invokes '::nvpair' without the "-q" option. */ /*ARGSUSED*/ int print_nvlist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int verbose = B_FALSE; mdb_arg_t v; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); v.a_type = MDB_TYPE_STRING; if (verbose) v.a_un.a_str = "-r"; else v.a_un.a_str = "-rq"; return (mdb_pwalk_dcmd("nvpair", "nvpair", 1, &v, addr)); } /* * ::nvpair [-rq] * * -r Recursively print any nvlist elements * -q Quiet mode; print members only as "name=value" * * Prints out a single nvpair. By default, all information is printed. When * given the '-q' option, the type of elements is hidden, and elements are * instead printed simply as 'name=value'. */ typedef struct { data_type_t type; int elem_size; char *type_name; } nvpair_info_t; nvpair_info_t nvpair_info[] = { { DATA_TYPE_BOOLEAN, 1, "boolean" }, { DATA_TYPE_BOOLEAN_VALUE, 4, "boolean_value" }, { DATA_TYPE_BYTE, 1, "byte" }, { DATA_TYPE_INT8, 1, "int8" }, { DATA_TYPE_UINT8, 1, "uint8" }, { DATA_TYPE_INT16, 2, "int16" }, { DATA_TYPE_UINT16, 2, "uint16" }, { DATA_TYPE_INT32, 4, "int32" }, { DATA_TYPE_UINT32, 4, "uint32" }, { DATA_TYPE_INT64, 8, "int64" }, { DATA_TYPE_UINT64, 8, "uint64" }, { DATA_TYPE_STRING, 0, "string" }, { DATA_TYPE_NVLIST, 0, "nvpair_list" }, { DATA_TYPE_HRTIME, 8, "hrtime" }, { DATA_TYPE_BOOLEAN_ARRAY, 4, "boolean_array" }, { DATA_TYPE_BYTE_ARRAY, 1, "byte_array" }, { DATA_TYPE_INT8_ARRAY, 1, "int8_array" }, { DATA_TYPE_UINT8_ARRAY, 1, "uint8_array" }, { DATA_TYPE_INT16_ARRAY, 2, "int16_array" }, { DATA_TYPE_UINT16_ARRAY, 2, "uint16_array" }, { DATA_TYPE_INT32_ARRAY, 4, "int32_array" }, { DATA_TYPE_UINT32_ARRAY, 4, "uint32_array" }, { DATA_TYPE_INT64_ARRAY, 8, "int64_array" }, { DATA_TYPE_UINT64_ARRAY, 8, "uint64_array" }, { DATA_TYPE_STRING_ARRAY, 0, "string_array" }, { DATA_TYPE_NVLIST_ARRAY, 0, "nvpair list_array" } }; static void nvpair_print_value(char *data, int32_t elem_size, int32_t nelem, data_type_t type) { int32_t i; if (elem_size == 0) { char *p = data; /* print out all the strings */ for (i = 0; i < nelem - 1; i++) { mdb_printf("'%s' + ", p); p += strlen(p) + 1; } mdb_printf("'%s'", p); } else if (type == DATA_TYPE_BOOLEAN_VALUE || type == DATA_TYPE_BOOLEAN_ARRAY) { /* LINTED - pointer alignment */ boolean_t *p = (boolean_t *)data; for (i = 0; i < nelem; i++) { if (i > 0) mdb_printf("."); mdb_printf("%d", p[i]); } } else { unsigned char *p = (unsigned char *)data; int size = elem_size * nelem; /* * if elem_size != 0 then we are printing out an array * where each element is of elem_size */ mdb_nhconvert(p, p, elem_size); mdb_printf("%02x", *p); for (i = 1; i < size; i++) { if ((i % elem_size) == 0) { mdb_nhconvert(&p[i], &p[i], elem_size); mdb_printf("."); } mdb_printf("%02x", p[i]); } } mdb_printf("\n"); } /*ARGSUSED*/ int nvpair_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { nvpair_t nvpair_tmp, *nvpair; int32_t i, size, nelem, elem_size = 0; char *data = NULL, *data_end = NULL; char *type_name = NULL; data_type_t type = DATA_TYPE_UNKNOWN; int quiet = FALSE; int recurse = FALSE; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'r', MDB_OPT_SETBITS, TRUE, &recurse, 'q', MDB_OPT_SETBITS, TRUE, &quiet, NULL) != argc) return (DCMD_USAGE); /* read in the nvpair header so we can get the size */ if (mdb_vread(&nvpair_tmp, sizeof (nvpair), addr) == -1) { mdb_warn("failed to read nvpair at %p", addr); return (DCMD_ERR); } size = NVP_SIZE(&nvpair_tmp); if (size == 0) { mdb_warn("nvpair of size zero at %p", addr); return (DCMD_OK); } /* read in the entire nvpair */ nvpair = mdb_alloc(size, UM_SLEEP | UM_GC); if (mdb_vread(nvpair, size, addr) == -1) { mdb_warn("failed to read nvpair and data at %p", addr); return (DCMD_ERR); } /* lookup type decoding information for this nvpair */ type = NVP_TYPE(nvpair); nelem = NVP_NELEM(nvpair); for (i = 0; i < NELEM(nvpair_info); i++) { if (nvpair_info[i].type == type) { elem_size = nvpair_info[i].elem_size; type_name = nvpair_info[i].type_name; break; } } if (quiet) { mdb_printf("%s", NVP_NAME(nvpair)); } else { /* print out the first line of nvpair info */ mdb_printf("name='%s'", NVP_NAME(nvpair)); if (type_name != NULL) { mdb_printf(" type=%s", type_name); } else { /* * If the nvpair type is unknown we print the type * number */ mdb_printf(" type=0x%x", type); } mdb_printf(" items=%d\n", nelem); } /* if there is no data and the type is known then we're done */ if ((nelem == 0) && (type_name != NULL)) { if (quiet) mdb_printf("(unknown)\n"); return (DCMD_OK); } /* get pointers to the data to print out */ data = (char *)NVP_VALUE(nvpair); data_end = (char *)nvpair + NVP_SIZE(nvpair); /* * The value of the name-value pair for a single embedded * list is the nvlist_t structure for the embedded list. * So we print that address out (computed as an offset from * the nvpair address we received as addr). * * The value of the name-value pair for an array of embedded * lists is nelem pointers to nvlist_t structures followed * by the structures themselves. We display the list * of pointers as the pair's value. */ if (type == DATA_TYPE_NVLIST) { char *p = (char *)addr + (data - (char *)nvpair); if (recurse) { if (quiet) mdb_printf("\n"); mdb_inc_indent(NVPAIR_VALUE_INDENT); if (mdb_pwalk_dcmd("nvpair", "nvpair", argc, argv, (uintptr_t)p) != DCMD_OK) return (DCMD_ERR); mdb_dec_indent(NVPAIR_VALUE_INDENT); } else { if (!quiet) { mdb_inc_indent(NVPAIR_VALUE_INDENT); mdb_printf("value", p); } mdb_printf("=%p\n", p); if (!quiet) mdb_dec_indent(NVPAIR_VALUE_INDENT); } return (DCMD_OK); } else if (type == DATA_TYPE_NVLIST_ARRAY) { if (recurse) { for (i = 0; i < nelem; i++, data += sizeof (nvlist_t *)) { nvlist_t **nl = (nvlist_t **)(void *)data; if (quiet && i != 0) mdb_printf("%s", NVP_NAME(nvpair)); mdb_printf("[%d]\n", i); mdb_inc_indent(NVPAIR_VALUE_INDENT); if (mdb_pwalk_dcmd("nvpair", "nvpair", argc, argv, (uintptr_t)*nl) != DCMD_OK) return (DCMD_ERR); mdb_dec_indent(NVPAIR_VALUE_INDENT); } } else { if (!quiet) { mdb_inc_indent(NVPAIR_VALUE_INDENT); mdb_printf("value"); } mdb_printf("="); for (i = 0; i < nelem; i++, data += sizeof (nvlist_t *)) { nvlist_t **nl = (nvlist_t **)(void *)data; mdb_printf("%c%p", " "[i == 0], *nl); } mdb_printf("\n"); if (!quiet) mdb_dec_indent(NVPAIR_VALUE_INDENT); } return (DCMD_OK); } /* if it's a string array, skip the index pointers */ if (type == DATA_TYPE_STRING_ARRAY) data += (sizeof (int64_t) * nelem); /* if the type is unknown, treat the data as a byte array */ if (type_name == NULL) { elem_size = 1; nelem = data_end - data; } /* * if the type is of strings, make sure they are printable * otherwise print them out as byte arrays */ if (elem_size == 0) { int32_t count = 0; i = 0; while ((&data[i] < data_end) && (count < nelem)) { if (data[i] == '\0') count++; else if (!isprint(data[i])) break; i++; } if (count != nelem) { /* there is unprintable data, output as byte array */ elem_size = 1; nelem = data_end - data; } } if (!quiet) { mdb_inc_indent(NVPAIR_VALUE_INDENT); mdb_printf("value="); } else { mdb_printf("="); } nvpair_print_value(data, elem_size, nelem, type); if (!quiet) mdb_dec_indent(NVPAIR_VALUE_INDENT); return (DCMD_OK); } /* * 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. */ #ifndef _NVPAIR_H #define _NVPAIR_H #ifdef __cplusplus extern "C" { #endif #define NVPAIR_DCMD_NAME "nvpair" #define NVPAIR_DCMD_USAGE ":[-rq]" #define NVPAIR_DCMD_DESCR "print out an nvpair" #define NVLIST_DCMD_NAME "nvlist" #define NVLIST_DCMD_USAGE ":[-v]" #define NVLIST_DCMD_DESCR "print out an nvlist" #define NVPAIR_WALKER_NAME "nvpair" #define NVPAIR_WALKER_DESCR "walk through the nvpairs in an unpacked nvlist" #ifdef _KERNEL #define NVPAIR_MODULE "genunix" #else /* _KERNEL */ #define NVPAIR_MODULE "libnvpair" #endif /* _KERNEL */ #define NVPAIR_DCMD_FQNAME NVPAIR_MODULE"`"NVPAIR_DCMD_NAME #define NVPAIR_WALKER_FQNAME NVPAIR_MODULE"`"NVPAIR_WALKER_NAME extern int nvpair_walk_init(mdb_walk_state_t *wsp); extern int nvpair_walk_step(mdb_walk_state_t *wsp); extern int nvpair_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int print_nvlist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); #ifdef __cplusplus } #endif #endif /* _NVPAIR_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2019, Joyent, Inc. * Copyright 2026 Oxide Computer Company */ /* * PCIe related dcmds */ #include #include #include #include #include #include boolean_t pcie_bus_match(const struct dev_info *devi, uintptr_t *bus_p) { if (devi->devi_bus.port_up.info.port.type == DEVI_PORT_TYPE_PCI) { *bus_p = (uintptr_t)devi->devi_bus.port_up.priv_p; } else if (devi->devi_bus.port_down.info.port.type == DEVI_PORT_TYPE_PCI) { *bus_p = (uintptr_t)devi->devi_bus.port_down.priv_p; } else { return (B_FALSE); } return (B_TRUE); } int pcie_bus_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { mdb_warn("pcie_bus walker doesn't support non-global walks\n"); return (WALK_ERR); } if (mdb_layered_walk("devinfo", wsp) == -1) { mdb_warn("couldn't walk \"devinfo\""); return (WALK_ERR); } return (WALK_NEXT); } int pcie_bus_walk_step(mdb_walk_state_t *wsp) { const struct dev_info *devi; uintptr_t bus_addr; struct pcie_bus bus; if (wsp->walk_layer == NULL) { mdb_warn("missing layered walk info\n"); return (WALK_ERR); } devi = wsp->walk_layer; if (!pcie_bus_match(devi, &bus_addr)) { return (WALK_NEXT); } if (mdb_vread(&bus, sizeof (bus), bus_addr) == -1) { mdb_warn("failed to read pcie_bus_t at %p", bus_addr); return (WALK_NEXT); } return (wsp->walk_callback(bus_addr, &bus, wsp->walk_cbdata)); } /* * Decode a BDF (Bus/Device/Function) value. */ /* The maximum size of a string produced by pcie_bdf() */ #define PCIE_BDF_BUFSZ sizeof ("XX/XX/X") static const char * pcie_bdf(pcie_req_id_t bdf, char *buf, size_t len) { if (bdf == PCIE_INVALID_BDF) { (void) strlcpy(buf, "INVBDF", len); } else { uint_t bus, dev, func; bus = (bdf & PCIE_REQ_ID_BUS_MASK) >> PCIE_REQ_ID_BUS_SHIFT; dev = (bdf & PCIE_REQ_ID_DEV_MASK) >> PCIE_REQ_ID_DEV_SHIFT; func = (bdf & PCIE_REQ_ID_FUNC_MASK) >> PCIE_REQ_ID_FUNC_SHIFT; mdb_snprintf(buf, len, "%r/%r/%r", bus, dev, func); } return (buf); } static const mdb_bitmask_t pf_affected_bits[] = { { "ROOT", PF_AFFECTED_ROOT, PF_AFFECTED_ROOT }, { "SELF", PF_AFFECTED_SELF, PF_AFFECTED_SELF }, { "PARENT", PF_AFFECTED_PARENT, PF_AFFECTED_PARENT }, { "CHILDREN", PF_AFFECTED_CHILDREN, PF_AFFECTED_CHILDREN }, { "BDF", PF_AFFECTED_BDF, PF_AFFECTED_BDF }, { "AER", PF_AFFECTED_AER, PF_AFFECTED_AER }, { "SAER", PF_AFFECTED_SAER, PF_AFFECTED_SAER }, { "ADDR", PF_AFFECTED_ADDR, PF_AFFECTED_ADDR }, { NULL, 0, 0 } }; static const mdb_bitmask_t pf_severity_bits[] = { { "NO_ERROR", PF_ERR_NO_ERROR, PF_ERR_NO_ERROR }, { "CE", PF_ERR_CE, PF_ERR_CE }, { "NO_PANIC", PF_ERR_NO_PANIC, PF_ERR_NO_PANIC }, { "MATCHED_DEVICE", PF_ERR_MATCHED_DEVICE, PF_ERR_MATCHED_DEVICE }, { "MATCHED_RC", PF_ERR_MATCHED_RC, PF_ERR_MATCHED_RC }, { "MATCHED_PARENT", PF_ERR_MATCHED_PARENT, PF_ERR_MATCHED_PARENT }, { "PANIC", PF_ERR_PANIC, PF_ERR_PANIC }, { "PANIC_DEADLOCK", PF_ERR_PANIC_DEADLOCK, PF_ERR_PANIC_DEADLOCK }, { "BAD_RESPONSE", PF_ERR_BAD_RESPONSE, PF_ERR_BAD_RESPONSE }, { "MATCH_DOM", PF_ERR_MATCH_DOM, PF_ERR_MATCH_DOM }, { NULL, 0, 0 } }; static const mdb_bitmask_t pcie_devsts_bits[] = { { "CE", PCIE_DEVSTS_CE_DETECTED, PCIE_DEVSTS_CE_DETECTED }, { "NFE", PCIE_DEVSTS_NFE_DETECTED, PCIE_DEVSTS_NFE_DETECTED }, { "FE", PCIE_DEVSTS_FE_DETECTED, PCIE_DEVSTS_FE_DETECTED }, { "UR", PCIE_DEVSTS_UR_DETECTED, PCIE_DEVSTS_UR_DETECTED }, { NULL, 0, 0 } }; static const mdb_bitmask_t pcie_aer_uce_bits[] = { { "TRAINING", PCIE_AER_UCE_TRAINING, PCIE_AER_UCE_TRAINING }, { "DLP", PCIE_AER_UCE_DLP, PCIE_AER_UCE_DLP }, { "SD", PCIE_AER_UCE_SD, PCIE_AER_UCE_SD }, { "PTLP", PCIE_AER_UCE_PTLP, PCIE_AER_UCE_PTLP }, { "FCP", PCIE_AER_UCE_FCP, PCIE_AER_UCE_FCP }, { "TO", PCIE_AER_UCE_TO, PCIE_AER_UCE_TO }, { "CA", PCIE_AER_UCE_CA, PCIE_AER_UCE_CA }, { "UC", PCIE_AER_UCE_UC, PCIE_AER_UCE_UC }, { "RO", PCIE_AER_UCE_RO, PCIE_AER_UCE_RO }, { "MTLP", PCIE_AER_UCE_MTLP, PCIE_AER_UCE_MTLP }, { "ECRC", PCIE_AER_UCE_ECRC, PCIE_AER_UCE_ECRC }, { "UR", PCIE_AER_UCE_UR, PCIE_AER_UCE_UR }, { NULL, 0, 0 } }; static const mdb_bitmask_t pcie_aer_ce_bits[] = { { "RX_ERR", PCIE_AER_CE_RECEIVER_ERR, PCIE_AER_CE_RECEIVER_ERR }, { "BAD_TLP", PCIE_AER_CE_BAD_TLP, PCIE_AER_CE_BAD_TLP }, { "BAD_DLLP", PCIE_AER_CE_BAD_DLLP, PCIE_AER_CE_BAD_DLLP }, { "REPLAY_RO", PCIE_AER_CE_REPLAY_ROLLOVER, PCIE_AER_CE_REPLAY_ROLLOVER }, { "REPLAY_TO", PCIE_AER_CE_REPLAY_TO, PCIE_AER_CE_REPLAY_TO }, { "AD_NFE", PCIE_AER_CE_AD_NFE, PCIE_AER_CE_AD_NFE }, { NULL, 0, 0 } }; /* * Shadow structures for CTF reading. These contain only the fields we need * from the target structures, allowing compatibility across kernel versions. */ typedef struct { void *pf_derr; void *pf_fault; void *pf_dq_head_p; void *pf_dq_tail_p; uint32_t pf_total; } mdb_pf_impl_t; typedef struct { uint16_t pcie_err_status; void *pcie_adv_regs; } mdb_pf_pcie_err_regs_t; typedef struct { uint32_t pcie_ue_status; uint32_t pcie_ce_status; } mdb_pf_pcie_adv_err_regs_t; typedef struct { boolean_t pe_valid; uint32_t pe_severity_flags; uint32_t pe_orig_severity_flags; uint32_t pe_severity_mask; void *pe_affected_dev; void *pe_bus_p; union { void *pe_pcie_regs; } pe_ext; void *pe_next; } mdb_pf_data_t; typedef struct { pcie_req_id_t scan_bdf; uint64_t scan_addr; boolean_t full_scan; } mdb_pf_root_fault_t; typedef struct { pcie_req_id_t bus_bdf; uint16_t bus_dev_type; void *bus_dip; void *bus_rp_dip; } mdb_pcie_bus_t; typedef struct { uint16_t pe_affected_flags; } mdb_pf_affected_dev_t; void pcie_pf_impl_help(void) { mdb_printf( "Display PCIe fabric error scan results from a pf_impl_t structure.\n" "\n" "This dcmd is used to analyze PCIe fatal errors in crash dumps. It displays\n" "the error data queue and decoded PCIe error registers from a fabric scan.\n" "\n" "When called without an address, the dcmd uses the cached pcie_faulty_pf_impl\n" "global variable, which is automatically populated when pf_scan_fabric()\n" "detects a fatal error (PF_ERR_FATAL_FLAGS). This cache is specifically\n" "designed for post-mortem debugging, as ereports may be lost if errorq_dump\n" "overflows.\n" "\n" "When called with an address, the dcmd analyzes the pf_impl_t structure at\n" "that address. This is useful for examining old crash dumps where the address\n" "of the pf_impl_t is known, or for analyzing non-fatal error scenarios.\n" "\n" "%OPTIONS%\n" " -v Verbose mode. Display additional per-device information including\n" " pe_valid status, original severity flags, severity mask, and device\n" " info pointers.\n" "\n" "%EXAMPLES%\n" " ::pcie_fatal_errors Display cached fatal error info\n" " ::pcie_fatal_errors -v Display with verbose details\n" " addr::pcie_fatal_errors Analyze pf_impl_t at specific address\n"); } int pcie_pf_impl_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_pf_impl_t impl; mdb_pf_data_t pfd; uintptr_t pfd_addr; bool opt_v = false; int count = 0; char bdf[PCIE_BDF_BUFSZ]; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, true, &opt_v, NULL) != argc) { return (DCMD_USAGE); } /* * If no address is provided, use the cached global pcie_faulty_pf_impl */ if ((flags & DCMD_ADDRSPEC) == 0) { GElf_Sym sym; if (mdb_lookup_by_name("pcie_faulty_pf_impl", &sym) != 0) { mdb_warn("failed to lookup pcie_faulty_pf_impl " "symbol"); return (DCMD_ERR); } addr = (uintptr_t)sym.st_value; } if ((flags & DCMD_PIPE_OUT) != 0) { mdb_printf("%lr", addr); return (DCMD_OK); } if (mdb_ctf_vread(&impl, "pf_impl_t", "mdb_pf_impl_t", addr, 0) == -1) { mdb_warn("failed to read pf_impl_t at %p", addr); return (DCMD_ERR); } /* * Check if the structure has been populated. If pf_dq_head_p is NULL, * no fatal error was recorded. */ if (impl.pf_dq_head_p == NULL && impl.pf_derr == NULL) { mdb_printf("No fatal PCIe errors recorded.\n"); return (DCMD_OK); } mdb_printf("pf_impl_t (%p) summary:\n", addr); mdb_printf(" pf_derr: %p\n", impl.pf_derr); mdb_printf(" pf_fault: %p\n", impl.pf_fault); mdb_printf(" pf_dq_head_p: %p\n", impl.pf_dq_head_p); mdb_printf(" pf_dq_tail_p: %p\n", impl.pf_dq_tail_p); mdb_printf(" pf_total: %r\n", impl.pf_total); if (impl.pf_fault != NULL) { mdb_pf_root_fault_t fault; if (mdb_ctf_vread(&fault, "pf_root_fault_t", "mdb_pf_root_fault_t", (uintptr_t)impl.pf_fault, 0) == -1) { mdb_warn("failed to read pf_root_fault_t at %p", impl.pf_fault); } else { mdb_printf("\nRoot Fault Information:\n"); mdb_printf(" scan_bdf: %04r (%s)\n", fault.scan_bdf, pcie_bdf(fault.scan_bdf, bdf, sizeof (bdf))); mdb_printf(" scan_addr: %016r\n", fault.scan_addr); mdb_printf(" full_scan: %s\n", fault.full_scan ? "true" : "false"); } } mdb_printf("\nError Data Queue:\n"); mdb_printf("%%-4s %-16s %-17s %-7s %-16s %-16s%\n", "#", "pf_data_t", "BDF", "DevType", "Severity", "Affected"); for (pfd_addr = (uintptr_t)impl.pf_dq_head_p; pfd_addr != 0; pfd_addr = (uintptr_t)pfd.pe_next) { mdb_pf_affected_dev_t affected; mdb_pcie_bus_t bus; uint16_t affected_flags = 0; if (mdb_ctf_vread(&pfd, "pf_data_t", "mdb_pf_data_t", pfd_addr, 0) == -1) { mdb_warn("failed to read pf_data_t at %p", pfd_addr); break; } if (pfd.pe_bus_p != NULL && mdb_ctf_vread(&bus, "pcie_bus_t", "mdb_pcie_bus_t", (uintptr_t)pfd.pe_bus_p, 0) != -1) { mdb_printf("%-4r %016p %05r (%8s) %8r ", count, pfd_addr, bus.bus_bdf, pcie_bdf(bus.bus_bdf, bdf, sizeof (bdf)), bus.bus_dev_type); } else { mdb_printf("%-4r %016p %-15s %-8s ", count, pfd_addr, "????", "????"); } count++; mdb_printf("%b ", pfd.pe_severity_flags, pf_severity_bits); if (pfd.pe_affected_dev != NULL && mdb_ctf_vread(&affected, "pf_affected_dev_t", "mdb_pf_affected_dev_t", (uintptr_t)pfd.pe_affected_dev, 0) != -1) { affected_flags = affected.pe_affected_flags; } mdb_printf("%hb\n", affected_flags, pf_affected_bits); if (opt_v == 0) continue; if (pfd.pe_bus_p != NULL) { mdb_printf(" pe_valid: %s\n", pfd.pe_valid ? "true" : "false"); mdb_printf(" pe_affected: %08r <%hb>\n", affected_flags, affected_flags, pf_affected_bits); mdb_printf(" pe_severity: %08r <%b>\n", pfd.pe_severity_flags, pfd.pe_severity_flags, pf_severity_bits); mdb_printf(" pe_orig_severity: %08r <%b>\n", pfd.pe_orig_severity_flags, pfd.pe_orig_severity_flags, pf_severity_bits); mdb_printf(" pe_severity_mask: %08r <%b>\n", pfd.pe_severity_mask, pfd.pe_severity_mask, pf_severity_bits); mdb_printf(" bus_dip: %p\n", bus.bus_dip); mdb_printf(" bus_rp_dip: %p\n", bus.bus_rp_dip); } /* Display PCIe-specific error information if available */ if (pfd.pe_ext.pe_pcie_regs != NULL) { mdb_pf_pcie_err_regs_t pcie_regs; mdb_pf_pcie_adv_err_regs_t adv_regs; if (mdb_ctf_vread(&pcie_regs, "pf_pcie_err_regs_t", "mdb_pf_pcie_err_regs_t", (uintptr_t)pfd.pe_ext.pe_pcie_regs, 0) != -1) { if (pcie_regs.pcie_err_status != 0) { mdb_printf(" pcie_err_status: " " %08r <%hb>\n", pcie_regs.pcie_err_status, pcie_regs.pcie_err_status, pcie_devsts_bits); } if (pcie_regs.pcie_adv_regs != NULL && mdb_ctf_vread(&adv_regs, "pf_pcie_adv_err_regs_t", "mdb_pf_pcie_adv_err_regs_t", (uintptr_t)pcie_regs.pcie_adv_regs, 0) != -1) { if (adv_regs.pcie_ue_status != 0) { mdb_printf(" AER UCE: " " %08r <%b>\n", adv_regs.pcie_ue_status, adv_regs.pcie_ue_status, pcie_aer_uce_bits); } if (adv_regs.pcie_ce_status != 0) { mdb_printf(" AER CE: " " %08r <%b>\n", adv_regs.pcie_ce_status, adv_regs.pcie_ce_status, pcie_aer_ce_bits); } } } } } mdb_printf("\nTotal errors in queue: %r\n", count); return (DCMD_OK); } int pcie_bdf_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char buf[PCIE_BDF_BUFSZ]; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (addr > UINT16_MAX) { mdb_warn("bdf value too large, range [0,%r]\n", UINT16_MAX); return (DCMD_ERR); } mdb_printf("%s\n", pcie_bdf((pcie_req_id_t)addr, buf, sizeof (buf))); return (DCMD_OK); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2019, Joyent, Inc. * Copyright 2026 Oxide Computer Company */ #ifndef _MDB_PCI_H #define _MDB_PCI_H /* * genunix PCI dcmds and walkers. */ #include #include #include #ifdef __cplusplus extern "C" { #endif extern int pcie_bus_walk_init(mdb_walk_state_t *); extern int pcie_bus_walk_step(mdb_walk_state_t *); extern boolean_t pcie_bus_match(const struct dev_info *, uintptr_t *); extern int pcie_pf_impl_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void pcie_pf_impl_help(void); extern int pcie_bdf_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_PCI_H */ /* * 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. */ /* * Display processor group information */ #include "pg.h" #include #include #include /* * PG hardware types indexed by hardware ID */ char *pg_hw_names[] = { "hw", "ipipe", "cache", "fpu", "mpipe", "chip", "memory", "active_pwr", "idle_pwr", }; #define A_CNT(arr) (sizeof (arr) / sizeof (arr[0])) #define NHW A_CNT(pg_hw_names) /* * Convert HW id to symbolic name */ static char * pg_hw_name(int hw) { return ((hw < 0 || hw > NHW) ? "UNKNOWN" : pg_hw_names[hw]); } /* * Display processor group. */ /* ARGSUSED */ int pg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pg_t pg; pghw_t pghw; pg_cmt_t pg_cmt; pg_class_t pg_class; int opt_q = 0; /* display only address. */ int is_cmt = 0; /* This is CMT pg */ /* Should provide an address */ if (! (flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'q', MDB_OPT_SETBITS, TRUE, &opt_q, NULL) != argc) return (DCMD_USAGE); if (flags & DCMD_PIPE_OUT) opt_q = B_TRUE; if (DCMD_HDRSPEC(flags) && !opt_q) { mdb_printf("%6s %?s %6s %7s %11s %5s %5s\n", "PGID", "ADDR", "PHYSID", "CLASS", "HARDWARE", "#CPUs", "LOAD"); } /* * Read pg at specified address */ if (mdb_vread(&pg, sizeof (struct pg), addr) == -1) { mdb_warn("unable to read 'pg' at %p", addr); return (DCMD_ERR); } /* * In quiet mode just print pg address */ if (opt_q) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } if (mdb_vread(&pg_class, sizeof (struct pg_class), (uintptr_t)pg.pg_class) == -1) { mdb_warn("unable to read 'pg_class' at %p", pg.pg_class); return (DCMD_ERR); } if (strcmp(pg_class.pgc_name, "cmt") == 0) { if (mdb_vread(&pg_cmt, sizeof (pg_cmt_t), addr) == -1) { mdb_warn("unable to read 'cmt pg' at %p", addr); return (DCMD_ERR); } is_cmt = 1; } if (pg.pg_relation == PGR_PHYSICAL) { if (mdb_vread(&pghw, sizeof (struct pghw), addr) == -1) { mdb_warn("unable to read 'pghw' at %p", addr); return (DCMD_ERR); } /* * Display the physical PG info. */ mdb_printf("%6d %?p %6d %7s %11s %5d %5d\n", pg.pg_id, addr, pghw.pghw_instance, pg_class.pgc_name, pg_hw_name(pghw.pghw_hw), pg.pg_cpus.grp_size, is_cmt ? pg_cmt.cmt_utilization : 0); } else { /* * Display the basic PG info. */ mdb_printf("%6d %?p %7s %5d\n", pg.pg_id, addr, pg_class.pgc_name, pg.pg_cpus.grp_size); } return (DCMD_OK); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_PG_H #define _MDB_PG_H /* * Block comment that describes the contents of this file. */ #ifdef __cplusplus extern "C" { #endif #include int pg(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_PG_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include static int print_val(uintptr_t addr, rctl_val_t *val, uintptr_t *enforced) { char *priv; static const mdb_bitmask_t val_localflag_bits[] = { { "SIGNAL", RCTL_LOCAL_SIGNAL, RCTL_LOCAL_SIGNAL }, { "DENY", RCTL_LOCAL_DENY, RCTL_LOCAL_DENY }, { "MAX", RCTL_LOCAL_MAXIMAL, RCTL_LOCAL_MAXIMAL }, { NULL, 0, 0 } }; switch (val->rcv_privilege) { case (RCPRIV_BASIC): priv = "basic"; break; case (RCPRIV_PRIVILEGED): priv = "privileged"; break; case (RCPRIV_SYSTEM): priv = "system"; break; default: priv = "???"; break; }; mdb_printf("\t%s ", addr == *enforced ? "(cur)": " "); mdb_printf("%-#18llx %11s\tflags=<%b>\n", val->rcv_value, priv, val->rcv_flagaction, val_localflag_bits); return (WALK_NEXT); } int rctl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rctl_t rctl; rctl_dict_entry_t dict; char name[256]; rctl_hndl_t hndl; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&rctl, sizeof (rctl_t), addr) == -1) { mdb_warn("failed to read rctl_t structure at %p", addr); return (DCMD_ERR); } if (argc != 0) { const mdb_arg_t *argp = &argv[0]; hndl = (rctl_hndl_t)mdb_argtoull(argp); if (rctl.rc_id != hndl) return (DCMD_OK); } if (mdb_vread(&dict, sizeof (rctl_dict_entry_t), (uintptr_t)rctl.rc_dict_entry) == -1) { mdb_warn("failed to read dict entry for rctl_t %p at %p", addr, rctl.rc_dict_entry); return (DCMD_ERR); } if (mdb_readstr(name, 256, (uintptr_t)(dict.rcd_name)) == -1) { mdb_warn("failed to read name for rctl_t %p", addr); return (DCMD_ERR); } mdb_printf("%0?p\t%3d : %s\n", addr, rctl.rc_id, name); if (mdb_pwalk("rctl_val", (mdb_walk_cb_t)print_val, &(rctl.rc_cursor), addr) == -1) { mdb_warn("failed to walk all values for rctl_t %p", addr); return (DCMD_ERR); } return (DCMD_OK); } int rctl_dict(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rctl_dict_entry_t dict; char name[256], *type = NULL; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("rctl_dict_list", "rctl_dict", argc, argv) == -1) { mdb_warn("failed to walk 'rctl_dict_list'"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%2s %-27s %?s %7s %s%\n", "ID", "NAME", "ADDR", "TYPE", "GLOBAL_FLAGS"); if (mdb_vread(&dict, sizeof (dict), addr) == -1) { mdb_warn("failed to read rctl_dict at %p", addr); return (DCMD_ERR); } if (mdb_readstr(name, 256, (uintptr_t)(dict.rcd_name)) == -1) { mdb_warn("failed to read rctl_dict name for %p", addr); return (DCMD_ERR); } switch (dict.rcd_entity) { case RCENTITY_PROCESS: type = "process"; break; case RCENTITY_TASK: type = "task"; break; case RCENTITY_PROJECT: type = "project"; break; case RCENTITY_ZONE: type = "zone"; break; default: type = "unknown"; break; } mdb_printf("%2d %-27s %0?p %7s 0x%08x", dict.rcd_id, name, addr, type, dict.rcd_flagaction); return (DCMD_OK); } typedef struct dict_data { rctl_hndl_t hndl; uintptr_t dict_addr; rctl_entity_t type; } dict_data_t; static int hndl2dict(uintptr_t addr, rctl_dict_entry_t *entry, dict_data_t *data) { if (data->hndl == entry->rcd_id) { data->dict_addr = addr; data->type = entry->rcd_entity; return (WALK_DONE); } return (WALK_NEXT); } /* * Print out all project, task, and process rctls for a given process. * If a handle is specified, print only the rctl matching that handle * for the process. */ int rctl_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { proc_t proc; uintptr_t set; task_t task; kproject_t proj; zone_t zone; dict_data_t rdict; int i; rdict.dict_addr = 0; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (argc == 0) rdict.hndl = 0; else if (argc == 1) { /* * User specified a handle. Go find the rctl_dict_entity_t * structure so we know what type of rctl to look for. */ const mdb_arg_t *argp = &argv[0]; rdict.hndl = (rctl_hndl_t)mdb_argtoull(argp); if (mdb_walk("rctl_dict_list", (mdb_walk_cb_t)hndl2dict, &rdict) == -1) { mdb_warn("failed to walk rctl_dict_list"); return (DCMD_ERR); } /* Couldn't find a rctl_dict_entry_t for this handle */ if (rdict.dict_addr == 0) return (DCMD_ERR); } else return (DCMD_USAGE); if (mdb_vread(&proc, sizeof (proc_t), addr) == -1) { mdb_warn("failed to read proc at %p", addr); return (DCMD_ERR); } if (mdb_vread(&zone, sizeof (zone_t), (uintptr_t)proc.p_zone) == -1) { mdb_warn("failed to read zone at %p", proc.p_zone); return (DCMD_ERR); } if (mdb_vread(&task, sizeof (task_t), (uintptr_t)proc.p_task) == -1) { mdb_warn("failed to read task at %p", proc.p_task); return (DCMD_ERR); } if (mdb_vread(&proj, sizeof (kproject_t), (uintptr_t)task.tk_proj) == -1) { mdb_warn("failed to read proj at %p", task.tk_proj); return (DCMD_ERR); } for (i = 0; i <= RC_MAX_ENTITY; i++) { /* * If user didn't specify a handle, print rctls for all * types. Otherwise, we can walk the rctl_set for only the * entity specified by the handle. */ if (rdict.hndl != 0 && rdict.type != i) continue; switch (i) { case (RCENTITY_PROCESS): set = (uintptr_t)proc.p_rctls; break; case (RCENTITY_TASK): set = (uintptr_t)task.tk_rctls; break; case (RCENTITY_PROJECT): set = (uintptr_t)proj.kpj_rctls; break; case (RCENTITY_ZONE): set = (uintptr_t)zone.zone_rctls; break; default: mdb_warn("Unknown rctl type %d", i); return (DCMD_ERR); } if (mdb_pwalk_dcmd("rctl_set", "rctl", argc, argv, set) == -1) { mdb_warn("failed to walk rctls in set %p", set); return (DCMD_ERR); } } return (DCMD_OK); } typedef struct dict_walk_data { int num_dicts; int num_cur; rctl_dict_entry_t **curdict; } dict_walk_data_t; int rctl_dict_walk_init(mdb_walk_state_t *wsp) { uintptr_t ptr; int nlists; GElf_Sym sym; rctl_dict_entry_t **dicts; dict_walk_data_t *dwd; if (mdb_lookup_by_name("rctl_lists", &sym) == -1) { mdb_warn("failed to find 'rctl_lists'\n"); return (WALK_ERR); } nlists = sym.st_size / sizeof (rctl_dict_entry_t *); ptr = (uintptr_t)sym.st_value; dicts = mdb_alloc(nlists * sizeof (rctl_dict_entry_t *), UM_SLEEP); mdb_vread(dicts, sym.st_size, ptr); dwd = mdb_alloc(sizeof (dict_walk_data_t), UM_SLEEP); dwd->num_dicts = nlists; dwd->num_cur = 0; dwd->curdict = dicts; wsp->walk_addr = 0; wsp->walk_data = dwd; return (WALK_NEXT); } int rctl_dict_walk_step(mdb_walk_state_t *wsp) { dict_walk_data_t *dwd = wsp->walk_data; uintptr_t dp; rctl_dict_entry_t entry; int status; dp = (uintptr_t)((dwd->curdict)[dwd->num_cur]); while (dp != 0) { if (mdb_vread(&entry, sizeof (rctl_dict_entry_t), dp) == -1) { mdb_warn("failed to read rctl_dict_entry_t structure " "at %p", dp); return (WALK_ERR); } status = wsp->walk_callback(dp, &entry, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); dp = (uintptr_t)entry.rcd_next; } dwd->num_cur++; if (dwd->num_cur == dwd->num_dicts) return (WALK_DONE); return (WALK_NEXT); } void rctl_dict_walk_fini(mdb_walk_state_t *wsp) { dict_walk_data_t *wd = wsp->walk_data; mdb_free(wd->curdict, wd->num_dicts * sizeof (rctl_dict_entry_t *)); mdb_free(wd, sizeof (dict_walk_data_t)); } typedef struct set_walk_data { uint_t hashsize; int hashcur; void **hashloc; } set_walk_data_t; int rctl_set_walk_init(mdb_walk_state_t *wsp) { rctl_set_t rset; uint_t hashsz; set_walk_data_t *swd; rctl_t **rctls; if (mdb_vread(&rset, sizeof (rctl_set_t), wsp->walk_addr) == -1) { mdb_warn("failed to read rset at %p", wsp->walk_addr); return (WALK_ERR); } if (mdb_readvar(&hashsz, "rctl_set_size") == -1 || hashsz == 0) { mdb_warn("rctl_set_size not found or invalid"); return (WALK_ERR); } rctls = mdb_alloc(hashsz * sizeof (rctl_t *), UM_SLEEP); if (mdb_vread(rctls, hashsz * sizeof (rctl_t *), (uintptr_t)rset.rcs_ctls) == -1) { mdb_warn("cannot read rctl hash at %p", rset.rcs_ctls); mdb_free(rctls, hashsz * sizeof (rctl_t *)); return (WALK_ERR); } swd = mdb_alloc(sizeof (set_walk_data_t), UM_SLEEP); swd->hashsize = hashsz; swd->hashcur = 0; swd->hashloc = (void **)rctls; wsp->walk_addr = 0; wsp->walk_data = swd; return (WALK_NEXT); } int rctl_set_walk_step(mdb_walk_state_t *wsp) { set_walk_data_t *swd = wsp->walk_data; rctl_t rctl; void **rhash = swd->hashloc; int status; if (swd->hashcur >= swd->hashsize) return (WALK_DONE); if (wsp->walk_addr == 0) { while (swd->hashcur < swd->hashsize) { if (rhash[swd->hashcur] != NULL) { break; } swd->hashcur++; } if (rhash[swd->hashcur] == NULL || swd->hashcur >= swd->hashsize) return (WALK_DONE); wsp->walk_addr = (uintptr_t)rhash[swd->hashcur]; swd->hashcur++; } if (mdb_vread(&rctl, sizeof (rctl_t), wsp->walk_addr) == -1) { wsp->walk_addr = 0; mdb_warn("unable to read from %#p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &rctl, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)rctl.rc_next; return (status); } void rctl_set_walk_fini(mdb_walk_state_t *wsp) { set_walk_data_t *sd = wsp->walk_data; mdb_free(sd->hashloc, sd->hashsize * sizeof (rctl_t *)); mdb_free(sd, sizeof (set_walk_data_t)); } int rctl_val_walk_init(mdb_walk_state_t *wsp) { rctl_t rctl; if (mdb_vread(&rctl, sizeof (rctl_t), wsp->walk_addr) == -1) { mdb_warn("failed to read rctl at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)rctl.rc_values; wsp->walk_data = rctl.rc_values; return (WALK_NEXT); } int rctl_val_walk_step(mdb_walk_state_t *wsp) { rctl_val_t val; int status; if (mdb_vread(&val, sizeof (rctl_val_t), wsp->walk_addr) == -1) { mdb_warn("failed to read rctl_val at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, &val, wsp->walk_cbdata); if ((wsp->walk_addr = (uintptr_t)val.rcv_next) == 0) return (WALK_DONE); return (status); } typedef struct rctl_val_seen { uintptr_t s_ptr; rctl_qty_t s_val; } rctl_val_seen_t; typedef struct rctl_validate_data { uintptr_t v_rctl_addr; rctl_val_t *v_cursor; uint_t v_flags; int v_bad_rctl; int v_cursor_valid; int v_circularity_detected; uint_t v_seen_size; uint_t v_seen_cnt; rctl_val_seen_t *v_seen; } rctl_validate_data_t; #define RCV_VERBOSE 0x1 /* * rctl_val_validate() * Do validation on an individual rctl_val_t. This function is called * as part of the rctl_val walker, and helps perform the checks described * in the ::rctl_validate dcmd. */ static int rctl_val_validate(uintptr_t addr, rctl_val_t *val, rctl_validate_data_t *data) { int i; data->v_seen[data->v_seen_cnt].s_ptr = addr; if (addr == (uintptr_t)data->v_cursor) data->v_cursor_valid++; data->v_seen[data->v_seen_cnt].s_val = val->rcv_value; if (val->rcv_prev == (void *)0xbaddcafe || val->rcv_next == (void *)0xbaddcafe || val->rcv_prev == (void *)0xdeadbeef || val->rcv_next == (void *)0xdeadbeef) { if (data->v_bad_rctl++ == 0) mdb_printf("%p ", data->v_rctl_addr); if (data->v_flags & RCV_VERBOSE) mdb_printf("/ uninitialized or previously " "freed link at %p ", addr); } if (data->v_seen_cnt == 0) { if (val->rcv_prev != NULL) { if (data->v_bad_rctl++ == 0) mdb_printf("%p ", data->v_rctl_addr); if (data->v_flags & RCV_VERBOSE) mdb_printf("/ bad prev pointer at " "head "); } } else { if ((uintptr_t)val->rcv_prev != data->v_seen[data->v_seen_cnt - 1].s_ptr) { if (data->v_bad_rctl++ == 0) mdb_printf("%p ", data->v_rctl_addr); if (data->v_flags & RCV_VERBOSE) mdb_printf("/ bad prev pointer at %p ", addr); } if (data->v_seen[data->v_seen_cnt].s_val < data->v_seen[data->v_seen_cnt - 1].s_val) { if (data->v_bad_rctl++ == 0) mdb_printf("%p ", data->v_rctl_addr); if (data->v_flags & RCV_VERBOSE) mdb_printf("/ ordering error at %p ", addr); } } for (i = data->v_seen_cnt; i >= 0; i--) { if (data->v_seen[i].s_ptr == (uintptr_t)val->rcv_next) { if (data->v_bad_rctl++ == 0) mdb_printf("%p ", data->v_rctl_addr); if (data->v_flags & RCV_VERBOSE) mdb_printf("/ circular next pointer " "at %p ", addr); data->v_circularity_detected++; break; } } if (data->v_circularity_detected) return (WALK_DONE); data->v_seen_cnt++; if (data->v_seen_cnt >= data->v_seen_size) { uint_t new_seen_size = data->v_seen_size * 2; rctl_val_seen_t *tseen = mdb_zalloc(new_seen_size * sizeof (rctl_val_seen_t), UM_SLEEP | UM_GC); bcopy(data->v_seen, tseen, data->v_seen_size * sizeof (rctl_val_seen_t)); data->v_seen = tseen; data->v_seen_size = new_seen_size; } return (WALK_NEXT); } /* * Validate a rctl pointer by checking: * - rctl_val_t's for that rctl form an ordered, non-circular list * - the cursor points to a rctl_val_t within that list * - there are no more than UINT64_MAX (or # specified by -n) * rctl_val_t's in the list */ int rctl_validate(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rctl_validate_data_t data; rctl_t r; uint64_t long_threshold = UINT64_MAX; /* Initialize validate data structure */ data.v_rctl_addr = addr; data.v_flags = 0; data.v_bad_rctl = 0; data.v_seen_cnt = 0; data.v_cursor_valid = 0; data.v_circularity_detected = 0; data.v_seen_size = 1; data.v_seen = mdb_zalloc(data.v_seen_size * sizeof (rctl_val_seen_t), UM_SLEEP | UM_GC); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, RCV_VERBOSE, &data.v_flags, 'n', MDB_OPT_UINT64, &long_threshold, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&r, sizeof (rctl_t), addr) != sizeof (rctl_t)) { mdb_warn("failed to read rctl structure at %p", addr); return (DCMD_ERR); } data.v_cursor = r.rc_cursor; if (data.v_cursor == NULL) { if (data.v_bad_rctl++ == 0) mdb_printf("%p ", addr); if (data.v_flags & RCV_VERBOSE) mdb_printf("/ NULL cursor seen "); } else if (data.v_cursor == (rctl_val_t *)0xbaddcafe) { if (data.v_bad_rctl++ == 0) mdb_printf("%p ", addr); if (data.v_flags & RCV_VERBOSE) mdb_printf("/ uninitialized cursor seen "); } /* Walk through each val in this rctl for individual validation. */ if (mdb_pwalk("rctl_val", (mdb_walk_cb_t)rctl_val_validate, &data, addr) == -1) { mdb_warn("failed to walk all values for rctl_t %p", addr); return (DCMD_ERR); } if (data.v_seen_cnt >= long_threshold) { if (data.v_bad_rctl++ == 0) mdb_printf("%p ", addr); if (data.v_flags & RCV_VERBOSE) mdb_printf("/ sequence length = %d ", data.v_seen_cnt); } if (!data.v_cursor_valid) { if (data.v_bad_rctl++ == 0) mdb_printf("%p ", addr); if (data.v_flags & RCV_VERBOSE) mdb_printf("/ cursor outside sequence"); } if (data.v_bad_rctl) mdb_printf("\n"); if (data.v_circularity_detected) mdb_warn("circular list implies possible memory leak; " "recommend invoking ::findleaks"); return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_RCTL_H #define _MDB_RCTL_H #include #ifdef __cplusplus extern "C" { #endif int rctl_validate(uintptr_t, uint_t, int, const mdb_arg_t *); int rctl(uintptr_t, uint_t, int, const mdb_arg_t *); int rctl_dict(uintptr_t, uint_t, int, const mdb_arg_t *); int rctl_list(uintptr_t, uint_t, int, const mdb_arg_t *); int rctl_dict_walk_init(mdb_walk_state_t *); int rctl_dict_walk_step(mdb_walk_state_t *); void rctl_dict_walk_fini(mdb_walk_state_t *); int rctl_set_walk_init(mdb_walk_state_t *); int rctl_set_walk_step(mdb_walk_state_t *); void rctl_set_walk_fini(mdb_walk_state_t *); int rctl_val_walk_init(mdb_walk_state_t *); int rctl_val_walk_step(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _MDB_RCTL_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2019 Joyent, Inc. */ #include #include #include #define REFSTR_LEN (1024) int cmd_refstr(uintptr_t addr, uint_t flags __unused, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("address is required\n"); return (DCMD_ERR); } if (mdb_getopts(argc, argv, NULL) != argc) return (DCMD_USAGE); char *buf = mdb_alloc(REFSTR_LEN, UM_SLEEP | UM_GC); if (mdb_read_refstr(addr, buf, REFSTR_LEN) < 0) { mdb_warn("couldn't read refstr from %p", addr); return (DCMD_ERR); } mdb_printf("%s\n", buf); return (DCMD_OK); } /* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include struct sobj_type_info { int sobj_type; const char *sobj_name; const char *sobj_ops_name; } sobj_types[] = { { SOBJ_MUTEX, "mutex", "mutex_sobj_ops" }, { SOBJ_RWLOCK, "rwlock", "rw_sobj_ops" }, { SOBJ_CV, "cv", "cv_sobj_ops" }, { SOBJ_SEMA, "sema", "sema_sobj_ops" }, { SOBJ_USER, "user", "lwp_sobj_ops" }, { SOBJ_USER_PI, "user_pi", "lwp_sobj_pi_ops" }, { SOBJ_SHUTTLE, "shuttle", "shuttle_sobj_ops" } }; #define NUM_SOBJ_TYPES (sizeof (sobj_types) / sizeof (*sobj_types)) void sobj_type_to_text(int type, char *out, size_t sz) { int idx; if (type == SOBJ_NONE) { mdb_snprintf(out, sz, ""); return; } for (idx = 0; idx < NUM_SOBJ_TYPES; idx++) { struct sobj_type_info *info = &sobj_types[idx]; if (info->sobj_type == type) { mdb_snprintf(out, sz, "%s", sobj_types[idx].sobj_name); return; } } mdb_snprintf(out, sz, "", type); } void sobj_ops_to_text(uintptr_t addr, char *out, size_t sz) { sobj_ops_t ops; if (addr == 0) { mdb_snprintf(out, sz, ""); return; } if (mdb_vread(&ops, sizeof (ops), addr) == -1) { mdb_snprintf(out, sz, "??", ops.sobj_type); return; } sobj_type_to_text(ops.sobj_type, out, sz); } int sobj_text_to_ops(const char *name, uintptr_t *sobj_ops_out) { int idx; GElf_Sym sym; for (idx = 0; idx < NUM_SOBJ_TYPES; idx++) { struct sobj_type_info *info = &sobj_types[idx]; if (strcasecmp(info->sobj_name, name) == 0) { if (mdb_lookup_by_name(info->sobj_ops_name, &sym) == -1) { mdb_warn("unable to find symbol \"%s\"", info->sobj_ops_name); return (-1); } *sobj_ops_out = (uintptr_t)sym.st_value; return (0); } } mdb_warn("sobj type \"%s\" unknown\n", name); return (-1); } void sobj_type_walk(void (*cbfunc)(int, const char *, const char *, void *), void *cbarg) { int idx; for (idx = 0; idx < NUM_SOBJ_TYPES; idx++) { struct sobj_type_info *info = &sobj_types[idx]; cbfunc(info->sobj_type, info->sobj_name, info->sobj_ops_name, cbarg); } } typedef struct wchan_walk_data { caddr_t *ww_seen; int ww_seen_size; int ww_seen_ndx; uintptr_t ww_thr; sleepq_head_t ww_sleepq[NSLEEPQ]; int ww_sleepq_ndx; uintptr_t ww_compare; } wchan_walk_data_t; int wchan_walk_init(mdb_walk_state_t *wsp) { wchan_walk_data_t *ww = mdb_zalloc(sizeof (wchan_walk_data_t), UM_SLEEP); if (mdb_readvar(&ww->ww_sleepq[0], "sleepq_head") == -1) { mdb_warn("failed to read sleepq"); mdb_free(ww, sizeof (wchan_walk_data_t)); return (WALK_ERR); } if ((ww->ww_compare = wsp->walk_addr) == 0) { if (mdb_readvar(&ww->ww_seen_size, "nthread") == -1) { mdb_warn("failed to read nthread"); mdb_free(ww, sizeof (wchan_walk_data_t)); return (WALK_ERR); } ww->ww_seen = mdb_alloc(ww->ww_seen_size * sizeof (caddr_t), UM_SLEEP); } else { ww->ww_sleepq_ndx = SQHASHINDEX(wsp->walk_addr); } wsp->walk_data = ww; return (WALK_NEXT); } int wchan_walk_step(mdb_walk_state_t *wsp) { wchan_walk_data_t *ww = wsp->walk_data; sleepq_head_t *sq; kthread_t thr; uintptr_t t; int i; again: /* * Get the address of the first thread on the next sleepq in the * sleepq hash. If ww_compare is set, ww_sleepq_ndx is already * set to the appropriate sleepq index for the desired cv. */ for (t = ww->ww_thr; t == 0; ) { if (ww->ww_sleepq_ndx == NSLEEPQ) return (WALK_DONE); sq = &ww->ww_sleepq[ww->ww_sleepq_ndx++]; t = (uintptr_t)sq->sq_queue.sq_first; /* * If we were looking for a specific cv and we're at the end * of its sleepq, we're done walking. */ if (t == 0 && ww->ww_compare != 0) return (WALK_DONE); } /* * Read in the thread. If it's t_wchan pointer is NULL, the thread has * woken up since we took a snapshot of the sleepq (i.e. we are probably * being applied to a live system); we can't believe the t_link pointer * anymore either, so just skip to the next sleepq index. */ if (mdb_vread(&thr, sizeof (thr), t) != sizeof (thr)) { mdb_warn("failed to read thread at %p", t); return (WALK_ERR); } if (thr.t_wchan == NULL) { ww->ww_thr = 0; goto again; } /* * Set ww_thr to the address of the next thread in the sleepq list. */ ww->ww_thr = (uintptr_t)thr.t_link; /* * If we're walking a specific cv, invoke the callback if we've * found a match, or loop back to the top and read the next thread. */ if (ww->ww_compare != 0) { if (ww->ww_compare == (uintptr_t)thr.t_wchan) return (wsp->walk_callback(t, &thr, wsp->walk_cbdata)); if (ww->ww_thr == 0) return (WALK_DONE); goto again; } /* * If we're walking all cvs, seen if we've already encountered this one * on the current sleepq. If we have, skip to the next thread. */ for (i = 0; i < ww->ww_seen_ndx; i++) { if (ww->ww_seen[i] == thr.t_wchan) goto again; } /* * If we're not at the end of a sleepq, save t_wchan; otherwise reset * the seen index so our array is empty at the start of the next sleepq. * If we hit seen_size this is a live kernel and nthread is now larger, * cope by replacing the final element in our memory. */ if (ww->ww_thr != 0) { if (ww->ww_seen_ndx < ww->ww_seen_size) ww->ww_seen[ww->ww_seen_ndx++] = thr.t_wchan; else ww->ww_seen[ww->ww_seen_size - 1] = thr.t_wchan; } else ww->ww_seen_ndx = 0; return (wsp->walk_callback((uintptr_t)thr.t_wchan, NULL, wsp->walk_cbdata)); } void wchan_walk_fini(mdb_walk_state_t *wsp) { wchan_walk_data_t *ww = wsp->walk_data; mdb_free(ww->ww_seen, ww->ww_seen_size * sizeof (uintptr_t)); mdb_free(ww, sizeof (wchan_walk_data_t)); } struct wcdata { sobj_ops_t sobj; int nwaiters; }; /*ARGSUSED*/ static int wchaninfo_twalk(uintptr_t addr, const kthread_t *t, struct wcdata *wc) { if (wc->sobj.sobj_type == SOBJ_NONE) { (void) mdb_vread(&wc->sobj, sizeof (sobj_ops_t), (uintptr_t)t->t_sobj_ops); } wc->nwaiters++; return (WALK_NEXT); } static int wchaninfo_vtwalk(uintptr_t addr, const kthread_t *t, int *first) { proc_t p; (void) mdb_vread(&p, sizeof (p), (uintptr_t)t->t_procp); if (*first) { *first = 0; mdb_printf(": %0?p %s\n", addr, p.p_user.u_comm); } else { mdb_printf("%*s%0?p %s\n", (int)(sizeof (uintptr_t) * 2 + 17), "", addr, p.p_user.u_comm); } return (WALK_NEXT); } /*ARGSUSED*/ static int wchaninfo_walk(uintptr_t addr, void *ignored, uint_t *verbose) { struct wcdata wc; int first = 1; bzero(&wc, sizeof (wc)); wc.sobj.sobj_type = SOBJ_NONE; if (mdb_pwalk("wchan", (mdb_walk_cb_t)wchaninfo_twalk, &wc, addr) < 0) { mdb_warn("failed to walk wchan %p", addr); return (WALK_NEXT); } mdb_printf("%0?p %4s %8d%s", addr, wc.sobj.sobj_type == SOBJ_CV ? "cond" : wc.sobj.sobj_type == SOBJ_SEMA ? "sema" : "??", wc.nwaiters, (*verbose) ? "" : "\n"); if (*verbose != 0 && wc.nwaiters != 0 && mdb_pwalk("wchan", (mdb_walk_cb_t)wchaninfo_vtwalk, &first, addr) == -1) { mdb_warn("failed to walk waiters for wchan %p", addr); mdb_printf("\n"); } return (WALK_NEXT); } int wchaninfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &v, NULL) != argc) return (DCMD_USAGE); if (v == TRUE) { mdb_printf("%-?s %-4s %8s %-?s %s\n", "ADDR", "TYPE", "NWAITERS", "THREAD", "PROC"); } else mdb_printf("%-?s %-4s %8s\n", "ADDR", "TYPE", "NWAITERS"); if (flags & DCMD_ADDRSPEC) { if (wchaninfo_walk(addr, NULL, &v) == WALK_ERR) return (DCMD_ERR); } else if (mdb_walk("wchan", (mdb_walk_cb_t)wchaninfo_walk, &v) == -1) { mdb_warn("failed to walk wchans"); return (DCMD_ERR); } return (DCMD_OK); } int blocked_walk_init(mdb_walk_state_t *wsp) { if ((wsp->walk_data = (void *)wsp->walk_addr) == NULL) { mdb_warn("must specify a sobj * for blocked walk"); return (WALK_ERR); } wsp->walk_addr = 0; if (mdb_layered_walk("thread", wsp) == -1) { mdb_warn("couldn't walk 'thread'"); return (WALK_ERR); } return (WALK_NEXT); } int blocked_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = (uintptr_t)((const kthread_t *)wsp->walk_layer)->t_ts; uintptr_t taddr = wsp->walk_addr; turnstile_t ts; if (mdb_vread(&ts, sizeof (ts), addr) == -1) { mdb_warn("couldn't read %p's turnstile at %p", taddr, addr); return (WALK_ERR); } if (ts.ts_waiters == 0 || ts.ts_sobj != wsp->walk_data) return (WALK_NEXT); return (wsp->walk_callback(taddr, wsp->walk_layer, wsp->walk_cbdata)); } typedef struct rwlock_block { struct rwlock_block *rw_next; int rw_qnum; uintptr_t rw_thread; } rwlock_block_t; static int rwlock_walk(uintptr_t taddr, const kthread_t *t, rwlock_block_t **rwp) { turnstile_t ts; uintptr_t addr = (uintptr_t)t->t_ts; rwlock_block_t *rw; int state, i; if (mdb_vread(&ts, sizeof (ts), addr) == -1) { mdb_warn("couldn't read %p's turnstile at %p", taddr, addr); return (WALK_ERR); } for (i = 0; i < TS_NUM_Q; i++) { if ((uintptr_t)t->t_sleepq == (uintptr_t)&ts.ts_sleepq[i] - (uintptr_t)&ts + addr) break; } if (i == TS_NUM_Q) { if ((state = mdb_get_state()) == MDB_STATE_DEAD || state == MDB_STATE_STOPPED) { /* * This shouldn't happen post-mortem or under kmdb; * the blocked walk returned a thread which wasn't * actually blocked on its turnstile. This may happen * in-situ if the thread wakes up during the ::rwlock. */ mdb_warn("thread %p isn't blocked on ts %p\n", taddr, addr); return (WALK_ERR); } return (WALK_NEXT); } rw = mdb_alloc(sizeof (rwlock_block_t), UM_SLEEP | UM_GC); rw->rw_next = *rwp; rw->rw_qnum = i; rw->rw_thread = taddr; *rwp = rw; return (WALK_NEXT); } /* * > rwd_rwlock::rwlock * ADDR OWNER/COUNT FLAGS WAITERS * 7835dee8 READERS=1 B011 30004393d20 (W) * || * WRITE_WANTED -------+| * HAS_WAITERS --------+ * * |--ADDR_WIDTH--| |--OWNR_WIDTH--| * |--LBL_OFFSET--||-LBL_WIDTH| * |--------------LONG-------------| * |------------WAITER_OFFSET------------| */ #ifdef _LP64 #define RW_ADDR_WIDTH 16 #define RW_OWNR_WIDTH 16 #else #define RW_ADDR_WIDTH 8 #define RW_OWNR_WIDTH 11 #endif #define RW_LONG (RW_ADDR_WIDTH + 1 + RW_OWNR_WIDTH) #define RW_LBL_WIDTH 12 #define RW_LBL_OFFSET (RW_ADDR_WIDTH + RW_OWNR_WIDTH - 3 - RW_LBL_WIDTH) #define RW_WAITER_OFFSET (RW_LONG + 6) /* Access rwlock bits */ #define RW_BIT(n, offon) (wwwh & (1 << (n)) ? offon[1] : offon[0]) #define RW_BIT_SET(n) (wwwh & (1 << (n))) /* Print a waiter (if any) and a newline */ #define RW_NEWLINE \ if (rw != NULL) { \ int q = rw->rw_qnum; \ mdb_printf(" %?p (%s)", rw->rw_thread, \ q == TS_READER_Q ? "R" : q == TS_WRITER_Q ? "W" : "?"); \ rw = rw->rw_next; \ } \ mdb_printf("\n"); /*ARGSUSED*/ int rwlock(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rwlock_impl_t lock; rwlock_block_t *rw = NULL; uintptr_t wwwh; if (!(flags & DCMD_ADDRSPEC) || addr == 0 || argc != 0) return (DCMD_USAGE); if (mdb_vread(&lock, sizeof (lock), addr) == -1) { mdb_warn("failed to read rwlock at 0x%p", addr); return (DCMD_ERR); } if (mdb_pwalk("blocked", (mdb_walk_cb_t)rwlock_walk, &rw, addr) == -1) { mdb_warn("couldn't walk 'blocked' for sobj %p", addr); return (WALK_ERR); } mdb_printf("%?s %*s %5s %?s\n", "ADDR", RW_OWNR_WIDTH, "OWNER/COUNT", "FLAGS", "WAITERS"); mdb_printf("%?p ", addr); if (((wwwh = lock.rw_wwwh) & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK) mdb_printf("%*s", RW_OWNR_WIDTH, "1"); else if ((wwwh = lock.rw_wwwh) & RW_WRITE_LOCKED) mdb_printf("%*p", RW_OWNR_WIDTH, wwwh & RW_OWNER); else { uintptr_t count = (wwwh & RW_HOLD_COUNT) >> RW_HOLD_COUNT_SHIFT; char c[20]; mdb_snprintf(c, 20, "READERS=%ld", count); mdb_printf("%*s", RW_OWNR_WIDTH, count ? c : "-"); } mdb_printf(" B%c%c%c", RW_BIT(2, "01"), RW_BIT(1, "01"), RW_BIT(0, "01")); RW_NEWLINE; mdb_printf("%*s%c %c%c%c", RW_LONG - 1, "", " |"[(wwwh & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK], RW_BIT(2, " |"), RW_BIT(1, " |"), RW_BIT(0, " |")); RW_NEWLINE; if ((wwwh & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK) { mdb_printf("%*s%*s --+---+", RW_LBL_OFFSET, "", RW_LBL_WIDTH, "DESTROYED"); goto no_zero; } if (!RW_BIT_SET(2)) goto no_two; mdb_printf("%*s%*s ------+%c%c", RW_LBL_OFFSET, "", RW_LBL_WIDTH, "WRITE_LOCKED", RW_BIT(1, " |"), RW_BIT(0, " |")); RW_NEWLINE; no_two: if (!RW_BIT_SET(1)) goto no_one; mdb_printf("%*s%*s -------+%c", RW_LBL_OFFSET, "", RW_LBL_WIDTH, "WRITE_WANTED", RW_BIT(0, " |")); RW_NEWLINE; no_one: if (!RW_BIT_SET(0)) goto no_zero; mdb_printf("%*s%*s --------+", RW_LBL_OFFSET, "", RW_LBL_WIDTH, "HAS_WAITERS"); RW_NEWLINE; no_zero: while (rw != NULL) { mdb_printf("%*s", RW_WAITER_OFFSET, ""); RW_NEWLINE; } return (DCMD_OK); } int mutex(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mutex_impl_t lock; uint_t force = FALSE; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'f', MDB_OPT_SETBITS, TRUE, &force, NULL) != argc) { return (DCMD_USAGE); } if (mdb_vread(&lock, sizeof (lock), addr) == -1) { mdb_warn("failed to read mutex at 0x%0?p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %5s %?s %6s %6s %7s%\n", "ADDR", "TYPE", "HELD", "MINSPL", "OLDSPL", "WAITERS"); } if (MUTEX_TYPE_SPIN(&lock)) { struct spin_mutex *sp = &lock.m_spin; if (!force && (sp->m_filler != 0 || sp->m_minspl > PIL_MAX || sp->m_oldspl > PIL_MAX || (sp->m_spinlock != 0 && sp->m_spinlock != 0xff))) { mdb_warn("%a: invalid spin lock " "(-f to dump anyway)\n", addr); return (DCMD_ERR); } if (sp->m_spinlock == 0xff) { mdb_printf("%0?p %5s %?s %6d %6d %7s\n", addr, "spin", "yes", sp->m_minspl, sp->m_oldspl, "-"); } else { mdb_printf("%0?p %5s %?s %6d %6s %7s\n", addr, "spin", "no", sp->m_minspl, "-", "-"); } } else { kthread_t *owner = MUTEX_OWNER(&lock); char *waiters = MUTEX_HAS_WAITERS(&lock) ? "yes" : "no"; if (!force && (!MUTEX_TYPE_ADAPTIVE(&lock) || (owner == NULL && MUTEX_HAS_WAITERS(&lock)))) { mdb_warn("%a: invalid adaptive mutex " "(-f to dump anyway)\n", addr); return (DCMD_ERR); } if (owner != NULL) { mdb_printf("%0?p %5s %?p %6s %6s %7s\n", addr, "adapt", owner, "-", "-", waiters); } else { mdb_printf("%0?p %5s %?s %6s %6s %7s\n", addr, "adapt", "no", "-", "-", waiters); } } return (DCMD_OK); } void mutex_help(void) { mdb_printf("Options:\n" " -f force printing even if the data seems to be" " inconsistent\n"); } int turnstile(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { turnstile_t t; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("turnstile_cache", "turnstile", argc, argv) == -1) { mdb_warn("can't walk turnstiles"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %?s %5s %4s %?s %?s%\n", "ADDR", "SOBJ", "WTRS", "EPRI", "ITOR", "PRIOINV"); if (mdb_vread(&t, sizeof (turnstile_t), addr) == -1) { mdb_warn("can't read turnstile_t at %p", addr); return (DCMD_ERR); } mdb_printf("%0?p %?p %5d %4d %?p %?p\n", addr, t.ts_sobj, t.ts_waiters, t.ts_epri, t.ts_inheritor, t.ts_prioinv); return (DCMD_OK); } /* * Macros and structure definition copied from turnstile.c. * This is unfortunate, but half the macros we need aren't usable from * within mdb anyway. */ #define TURNSTILE_HASH_SIZE 128 /* must be power of 2 */ #define TURNSTILE_HASH_MASK (TURNSTILE_HASH_SIZE - 1) #define TURNSTILE_SOBJ_HASH(sobj) \ ((((int)sobj >> 2) + ((int)sobj >> 9)) & TURNSTILE_HASH_MASK) typedef struct turnstile_chain { turnstile_t *tc_first; /* first turnstile on hash chain */ disp_lock_t tc_lock; /* lock for this hash chain */ } turnstile_chain_t; /* * Given the address of a blocked-upon synchronization object, return * the address of its turnstile. */ /*ARGSUSED*/ int sobj2ts(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { GElf_Sym sym; int isupi; int ttoff; uintptr_t ttable; turnstile_t ts, *tsp; turnstile_chain_t tc; if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_lookup_by_name("upimutextab", &sym) == -1) { mdb_warn("unable to reference upimutextab\n"); return (DCMD_ERR); } isupi = addr - (uintptr_t)sym.st_value < sym.st_size; ttoff = (isupi ? 0 : TURNSTILE_HASH_SIZE) + TURNSTILE_SOBJ_HASH(addr); if (mdb_lookup_by_name("turnstile_table", &sym) == -1) { mdb_warn("unable to reference turnstile_table"); return (DCMD_ERR); } ttable = (uintptr_t)sym.st_value + sizeof (turnstile_chain_t) * ttoff; if (mdb_vread(&tc, sizeof (turnstile_chain_t), ttable) == -1) { mdb_warn("unable to read turnstile_chain_t at %#lx", ttable); return (DCMD_ERR); } for (tsp = tc.tc_first; tsp != NULL; tsp = ts.ts_next) { if (mdb_vread(&ts, sizeof (turnstile_t), (uintptr_t)tsp) == -1) { mdb_warn("unable to read turnstile_t at %#p", tsp); return (DCMD_ERR); } if ((uintptr_t)ts.ts_sobj == addr) { mdb_printf("%p\n", tsp); break; } } return (DCMD_OK); } /* * 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. */ #ifndef _SOBJ_H #define _SOBJ_H #ifdef __cplusplus extern "C" { #endif int wchan_walk_init(mdb_walk_state_t *); int wchan_walk_step(mdb_walk_state_t *); void wchan_walk_fini(mdb_walk_state_t *); int wchaninfo(uintptr_t, uint_t, int, const mdb_arg_t *); int blocked_walk_init(mdb_walk_state_t *); int blocked_walk_step(mdb_walk_state_t *); int rwlock(uintptr_t, uint_t, int, const mdb_arg_t *); int mutex(uintptr_t, uint_t, int, const mdb_arg_t *); int turnstile(uintptr_t, uint_t, int, const mdb_arg_t *); int sobj2ts(uintptr_t, uint_t, int, const mdb_arg_t *); void mutex_help(void); void sobj_ops_to_text(uintptr_t, char *, size_t); void sobj_type_to_text(int, char *, size_t); int sobj_text_to_ops(const char *, uintptr_t *); void sobj_type_walk(void (*)(int, const char *, const char *, void *), void *); #ifdef __cplusplus } #endif #endif /* _SOBJ_H */ /* * 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 2020 OmniOS Community Edition (OmniOSce) Association. */ #include #include #include #include #include #include #include #include #include #include #include "streams.h" typedef struct str_flags { uint_t strf_flag; const char *strf_name; const char *strf_descr; } strflags_t; typedef struct str_types { const char *strt_name; int strt_value; const char *strt_descr; } strtypes_t; typedef struct ftblk_data { ftblk_t ft_data; /* Copy of ftblk */ int ft_ix; /* Index in event list */ boolean_t ft_in_evlist; /* Iterating through evlist */ } ftblkdata_t; typedef void qprint_func(queue_t *, queue_t *); typedef void sdprint_func(stdata_t *, stdata_t *); #define SF(flag) flag, #flag /* * Queue flags */ static const strflags_t qf[] = { { SF(QENAB), "Queue is already enabled to run" }, { SF(QWANTR), "Someone wants to read Q" }, { SF(QWANTW), "Someone wants to write Q" }, { SF(QFULL), "Q is considered full" }, { SF(QREADR), "This is the reader (first) Q" }, { SF(QUSE), "This queue in use (allocation)" }, { SF(QNOENB), "Don't enable Q via putq" }, { SF(QWANTRMQSYNC), "Want to remove sync stream Q" }, { SF(QBACK), "queue has been back-enabled" }, { SF(0x00000200), "unused (was QHLIST)" }, { SF(0x00000400), "unused (was QUNSAFE)" }, { SF(QPAIR), "per queue-pair syncq" }, { SF(QPERQ), "per queue-instance syncq" }, { SF(QPERMOD), "per module syncq" }, { SF(QMTSAFE), "stream module is MT-safe" }, { SF(QMTOUTPERIM), "Has outer perimeter" }, { SF(QINSERVICE), "service routine executing" }, { SF(QWCLOSE), "will not be enabled" }, { SF(QEND), "last queue in stream" }, { SF(QWANTWSYNC), "Streamhead wants to write Q" }, { SF(QSYNCSTR), "Q supports Synchronous STREAMS" }, { SF(QISDRV), "the Queue is attached to a driver" }, { SF(0x00400000), "unused (was QHOT)" }, { SF(0x00800000), "unused (was QNEXTHOT)" }, { SF(0x01000000), "unused (was _QNEXTLESS)" }, { SF(0x02000000), "unused" }, { SF(_QINSERTING), "module is inserted with _I_INSERT" }, { SF(_QREMOVING) "module is removed with _I_REMOVE" }, { SF(_QASSOCIATED), "queue is associated with a device" }, { 0, NULL, NULL } }; /* * Syncq flags */ static const struct str_flags sqf[] = { { SF(SQ_EXCL), "Exclusive access to inner perimeter" }, { SF(SQ_BLOCKED), "qprocsoff in progress" }, { SF(SQ_FROZEN), "freezestr in progress" }, { SF(SQ_WRITER), "qwriter(OUTER) pending or running" }, { SF(SQ_MESSAGES), "There are messages on syncq" }, { SF(SQ_WANTWAKEUP) "Thread waiting on sq_wait" }, { SF(SQ_WANTEXWAKEUP), "Thread waiting on sq_exwait" }, { SF(SQ_EVENTS), "There are events on syncq" }, { 0, NULL, NULL } }; /* * Syncq types */ static const struct str_flags sqt[] = { { SF(SQ_CIPUT), "Concurrent inner put procedure" }, { SF(SQ_CISVC), "Concurrent inner svc procedure" }, { SF(SQ_CIOC), "Concurrent inner open/close" }, { SF(SQ_CICB), "Concurrent inner callback" }, { SF(SQ_COPUT), "Concurrent outer put procedure" }, { SF(SQ_COSVC), "Concurrent outer svc procedure" }, { SF(SQ_COOC), "Concurrent outer open/close" }, { SF(SQ_COCB), "Concurrent outer callback" }, { 0, NULL, NULL } }; /* * Stdata flags */ static const struct str_flags stdf[] = { { SF(IOCWAIT), "someone is doing an ioctl" }, { SF(RSLEEP), "someone wants to read/recv msg" }, { SF(WSLEEP), "someone wants to write" }, { SF(STRPRI), "an M_PCPROTO is at stream head" }, { SF(STRHUP), "device has vanished" }, { SF(STWOPEN), "waiting for 1st open" }, { SF(STPLEX), "stream is being multiplexed" }, { SF(STRISTTY), "stream is a terminal" }, { SF(STRGETINPROG), "(k)strgetmsg is running" }, { SF(IOCWAITNE), "STR_NOERROR ioctl running" }, { SF(STRDERR), "fatal read error from M_ERROR" }, { SF(STWRERR), "fatal write error from M_ERROR" }, { SF(STRDERRNONPERSIST), "nonpersistent read errors" }, { SF(STWRERRNONPERSIST), "nonpersistent write errors" }, { SF(STRCLOSE), "wait for a close to complete" }, { SF(SNDMREAD), "used for read notification" }, { SF(OLDNDELAY), "use old NDELAY TTY semantics" }, { SF(STRXPG4TTY), "Use XPG4 TTY semantics" }, { SF(0x00040000), "unused" }, { SF(STRTOSTOP), "block background writes" }, { SF(STRCMDWAIT), "someone is doing an _I_CMD" }, { SF(0x00200000), "unused" }, { SF(STRMOUNT), "stream is mounted" }, { SF(STRNOTATMARK), "Not at mark (when empty read q)" }, { SF(STRDELIM), "generate delimited messages" }, { SF(STRATMARK), "at mark (due to MSGMARKNEXT)" }, { SF(STZCNOTIFY), "wait for zerocopy mblk to be acked" }, { SF(STRPLUMB), "stream plumbing changes in progress" }, { SF(STREOF), "End-of-file indication" }, { SF(STREOPENFAIL), "re-open has failed" }, { SF(STRMATE), "this stream is a mate" }, { SF(STRHASLINKS), "there are I_LINKs under this stream" }, { 0, NULL, NULL } }; static const struct str_flags mbf[] = { { SF(MSGMARK), "last byte of message is marked" }, { SF(MSGNOLOOP), "don't loop message to write side" }, { SF(MSGDELIM), "message is delimited" }, { SF(0x08), "unused" }, { SF(MSGMARKNEXT), "Private: b_next's first byte marked" }, { SF(MSGNOTMARKNEXT), "Private: ... not marked" }, { 0, NULL, NULL } }; #define M_DATA_T 0xff static const strtypes_t mbt[] = { { "M_DATA", M_DATA_T, "regular data" }, { "M_PROTO", M_PROTO, "protocol control" }, { "M_MULTIDATA", M_MULTIDATA, "multidata" }, { "M_BREAK", M_BREAK, "line break" }, { "M_PASSFP", M_PASSFP, "pass file pointer" }, { "M_EVENT", M_EVENT, "Obsoleted: do not use" }, { "M_SIG", M_SIG, "generate process signal" }, { "M_DELAY", M_DELAY, "real-time xmit delay" }, { "M_CTL", M_CTL, "device-specific control message" }, { "M_IOCTL", M_IOCTL, "ioctl; set/get params" }, { "M_SETOPTS", M_SETOPTS, "set stream head options" }, { "M_RSE", M_RSE, "reserved for RSE use only" }, { "M_IOCACK", M_IOCACK, "acknowledge ioctl" }, { "M_IOCNAK", M_IOCNAK, "negative ioctl acknowledge" }, { "M_PCPROTO", M_PCPROTO, "priority proto message" }, { "M_PCSIG", M_PCSIG, "generate process signal" }, { "M_READ", M_READ, "generate read notification" }, { "M_FLUSH", M_FLUSH, "flush your queues" }, { "M_STOP", M_STOP, "stop transmission immediately" }, { "M_START", M_START, "restart transmission after stop" }, { "M_HANGUP", M_HANGUP, "line disconnect" }, { "M_ERROR", M_ERROR, "send error to stream head" }, { "M_COPYIN", M_COPYIN, "request to copyin data" }, { "M_COPYOUT", M_COPYOUT, "request to copyout data" }, { "M_IOCDATA", M_IOCDATA, "response to M_COPYIN and M_COPYOUT" }, { "M_PCRSE", M_PCRSE, "reserved for RSE use only" }, { "M_STOPI", M_STOPI, "stop reception immediately" }, { "M_STARTI", M_STARTI, "restart reception after stop" }, { "M_PCEVENT", M_PCEVENT, "Obsoleted: do not use" }, { "M_UNHANGUP", M_UNHANGUP, "line reconnect" }, { "M_CMD", M_CMD, "out-of-band ioctl command" }, { NULL, 0, NULL } }; /* Allocation flow trace events, starting from 0 */ static const char *ftev_alloc[] = { /* 0 */ "allocb", /* 1 */ "esballoc", /* 2 */ "desballoc", /* 3 */ "esballoca", /* 4 */ "desballoca", /* 5 */ "allocbig", /* 6 */ "allocbw", /* 7 */ "bcallocb", /* 8 */ "freeb", /* 9 */ "dupb", /* A */ "copyb", }; #define FTEV_PROC_START FTEV_PUT /* Procedures recorded by flow tracing, starting from 0x100 */ static const char *ftev_proc[] = { /* 100 */ "put", /* 101 */ "0x101", /* 102 */ "0x102", /* 103 */ "0x103", /* 104 */ "0x104", /* 105 */ "putq", /* 106 */ "getq", /* 107 */ "rmvq", /* 108 */ "insq", /* 109 */ "putbq", /* 10A */ "flushq", /* 10B */ "0x10b", /* 10C */ "0x10c", /* 10D */ "putnext", /* 10E */ "rwnext", }; static const char *db_control_types[] = { /* 00 */ "data", /* 01 */ "proto", /* 02 */ "multidata", /* 03 */ "0x03", /* 04 */ "0x04", /* 05 */ "0x05", /* 06 */ "0x06", /* 07 */ "0x07", /* 08 */ "break", /* 09 */ "passfp", /* 0a */ "event", /* 0b */ "sig", /* 0c */ "delay", /* 0d */ "ctl", /* 0e */ "ioctl", /* 0f */ "unused", /* 10 */ "setopts", /* 11 */ "rse", }; static const char *db_control_hipri_types[] = { /* 81 */ "iocack", /* 82 */ "iocnak", /* 83 */ "pcproto", /* 84 */ "pcsig", /* 85 */ "read", /* 86 */ "flush", /* 87 */ "stop", /* 88 */ "start", /* 89 */ "hangup", /* 8a */ "error", /* 8b */ "copyin", /* 8c */ "copyout", /* 8d */ "iocdata", /* 8e */ "pcrse", /* 8f */ "stopi", /* 90 */ "starti", /* 91 */ "pcevent", /* 92 */ "unhangup", /* 93 */ "cmd", }; #define A_SIZE(a) (sizeof (a) / sizeof (a[0])) static void ft_printevent(ushort_t); static int streams_parse_flag(const strflags_t ftable[], const char *arg, uint32_t *flag) { int i; for (i = 0; ftable[i].strf_name != NULL; i++) { if (strcasecmp(arg, ftable[i].strf_name) == 0) { *flag |= (1 << i); return (0); } } return (-1); } static void streams_flag_usage(const strflags_t ftable[]) { int i; for (i = 0; ftable[i].strf_name != NULL; i++) mdb_printf("%-14s %s\n", ftable[i].strf_name, ftable[i].strf_descr); } static int streams_parse_type(const strtypes_t ftable[], const char *arg, uint32_t *flag) { int i; for (i = 0; ftable[i].strt_name != NULL; i++) { if (strcasecmp(arg, ftable[i].strt_name) == 0) { *flag = ftable[i].strt_value; return (0); } } return (-1); } static void streams_type_usage(const strtypes_t ftable[]) { int i; for (i = 0; ftable[i].strt_name != NULL; i++) mdb_printf("%-12s %s\n", ftable[i].strt_name, ftable[i].strt_descr); } int queue(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const int QUEUE_FLGDELT = (int)(sizeof (uintptr_t) * 2 + 15); char name[MODMAXNAMELEN]; int nblks = 0; uintptr_t maddr; mblk_t mblk; queue_t q; const char *mod = NULL, *flag = NULL, *not_flag = NULL; uint_t quiet = FALSE; uint_t verbose = FALSE; uint32_t mask = 0, not_mask = 0; uintptr_t syncq = 0; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("genunix`queue_cache", "genunix`queue", argc, argv) == -1) { mdb_warn("failed to walk queue cache"); return (DCMD_ERR); } return (DCMD_OK); } if (flags & DCMD_PIPE_OUT) quiet = TRUE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'q', MDB_OPT_SETBITS, TRUE, &quiet, 'm', MDB_OPT_STR, &mod, 'f', MDB_OPT_STR, &flag, 'F', MDB_OPT_STR, ¬_flag, 's', MDB_OPT_UINTPTR, &syncq, NULL) != argc) return (DCMD_USAGE); /* * If any of the filtering flags is specified, don't print anything * except the matching pointer. */ if (flag != NULL || not_flag != NULL || mod != NULL || syncq != 0) quiet = TRUE; if (DCMD_HDRSPEC(flags) && !quiet) { mdb_printf("%?s %-13s %6s %4s\n", "ADDR", "MODULE", "FLAGS", "NBLK"); } if (flag != NULL && streams_parse_flag(qf, flag, &mask) == -1) { mdb_warn("unrecognized queue flag '%s'\n", flag); streams_flag_usage(qf); return (DCMD_USAGE); } if (not_flag != NULL && streams_parse_flag(qf, not_flag, ¬_mask) == -1) { mdb_warn("unrecognized queue flag '%s'\n", flag); streams_flag_usage(qf); return (DCMD_USAGE); } if (mdb_vread(&q, sizeof (q), addr) == -1) { mdb_warn("couldn't read queue at %p", addr); return (DCMD_ERR); } for (maddr = (uintptr_t)q.q_first; maddr != 0; nblks++) { if (mdb_vread(&mblk, sizeof (mblk), maddr) == -1) { mdb_warn("couldn't read mblk %p for queue %p", maddr, addr); break; } maddr = (uintptr_t)mblk.b_next; } (void) mdb_qname(&q, name, sizeof (name)); /* * If queue doesn't pass filtering criteria, don't print anything and * just return. */ if (mod != NULL && strcmp(mod, name) != 0) return (DCMD_OK); if (mask != 0 && !(q.q_flag & mask)) return (DCMD_OK); if (not_mask != 0 && (q.q_flag & not_mask)) return (DCMD_OK); if (syncq != 0 && q.q_syncq != (syncq_t *)syncq) return (DCMD_OK); /* * Options are specified for filtering, so If any option is specified on * the command line, just print address and exit. */ if (quiet) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } mdb_printf("%0?p %-13s %06x %4d %0?p\n", addr, name, q.q_flag, nblks, q.q_first); if (verbose) { int i, arm = 0; for (i = 0; qf[i].strf_name != NULL; i++) { if (!(q.q_flag & (1 << i))) continue; if (!arm) { mdb_printf("%*s|\n%*s+--> ", QUEUE_FLGDELT, "", QUEUE_FLGDELT, ""); arm = 1; } else mdb_printf("%*s ", QUEUE_FLGDELT, ""); mdb_printf("%-12s %s\n", qf[i].strf_name, qf[i].strf_descr); } } return (DCMD_OK); } int syncq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const int SYNC_FLGDELT = (int)(sizeof (uintptr_t) * 2 + 1); const int SYNC_TYPDELT = (int)(sizeof (uintptr_t) * 2 + 5); syncq_t sq; const char *flag = NULL, *not_flag = NULL; const char *typ = NULL, *not_typ = NULL; uint_t verbose = FALSE; uint_t quiet = FALSE; uint32_t mask = 0, not_mask = 0; uint32_t tmask = 0, not_tmask = 0; uint8_t sqtype = 0; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("genunix`syncq_cache", "genunix`syncq", argc, argv) == -1) { mdb_warn("failed to walk syncq cache"); return (DCMD_ERR); } return (DCMD_OK); } if (flags & DCMD_PIPE_OUT) quiet = TRUE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'q', MDB_OPT_SETBITS, TRUE, &quiet, 'f', MDB_OPT_STR, &flag, 'F', MDB_OPT_STR, ¬_flag, 't', MDB_OPT_STR, &typ, 'T', MDB_OPT_STR, ¬_typ, NULL) != argc) return (DCMD_USAGE); /* * If any of the filtering flags is specified, don't print anything * except the matching pointer. */ if (flag != NULL || not_flag != NULL || typ != NULL || not_typ != NULL) quiet = TRUE; if (DCMD_HDRSPEC(flags) && !quiet) { mdb_printf("%?s %s %s %s %s %?s %s %s\n", "ADDR", "FLG", "TYP", "CNT", "NQS", "OUTER", "SF", "PRI"); } if (flag != NULL && streams_parse_flag(sqf, flag, &mask) == -1) { mdb_warn("unrecognized syncq flag '%s'\n", flag); streams_flag_usage(sqf); return (DCMD_USAGE); } if (typ != NULL && streams_parse_flag(sqt, typ, &tmask) == -1) { mdb_warn("unrecognized syncq type '%s'\n", typ); streams_flag_usage(sqt); return (DCMD_USAGE); } if (not_flag != NULL && streams_parse_flag(sqf, not_flag, ¬_mask) == -1) { mdb_warn("unrecognized syncq flag '%s'\n", not_flag); streams_flag_usage(sqf); return (DCMD_USAGE); } if (not_typ != NULL && streams_parse_flag(sqt, not_typ, ¬_tmask) == -1) { mdb_warn("unrecognized syncq type '%s'\n", not_typ); streams_flag_usage(sqt); return (DCMD_USAGE); } if (mdb_vread(&sq, sizeof (sq), addr) == -1) { mdb_warn("couldn't read syncq at %p", addr); return (DCMD_ERR); } if (mask != 0 && !(sq.sq_flags & mask)) return (DCMD_OK); if (not_mask != 0 && (sq.sq_flags & not_mask)) return (DCMD_OK); sqtype = (sq.sq_type >> 8) & 0xff; if (tmask != 0 && !(sqtype & tmask)) return (DCMD_OK); if (not_tmask != 0 && (sqtype & not_tmask)) return (DCMD_OK); /* * Options are specified for filtering, so If any option is specified on * the command line, just print address and exit. */ if (quiet) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } mdb_printf("%0?p %02x %02x %-3u %-3u %0?p %1x %-3d\n", addr, sq.sq_flags & 0xff, sqtype, sq.sq_count, sq.sq_nqueues, sq.sq_outer, sq.sq_svcflags, sq.sq_pri); if (verbose) { int i, arm = 0; for (i = 0; sqf[i].strf_name != NULL; i++) { if (!(sq.sq_flags & (1 << i))) continue; if (!arm) { mdb_printf("%*s|\n%*s+--> ", SYNC_FLGDELT, "", SYNC_FLGDELT, ""); arm = 1; } else mdb_printf("%*s ", SYNC_FLGDELT, ""); mdb_printf("%-12s %s\n", sqf[i].strf_name, sqf[i].strf_descr); } for (i = 0; sqt[i].strf_name != NULL; i++) { if (!(sqtype & (1 << i))) continue; if (!arm) { mdb_printf("%*s|\n%*s+--> ", SYNC_TYPDELT, "", SYNC_TYPDELT, ""); arm = 1; } else mdb_printf("%*s ", SYNC_TYPDELT, ""); mdb_printf("%-12s %s\n", sqt[i].strf_name, sqt[i].strf_descr); } } return (DCMD_OK); } int stdata(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const int STREAM_FLGDELT = (int)(sizeof (uintptr_t) * 2 + 10); stdata_t sd; const char *flag = NULL, *not_flag = NULL; uint_t verbose = FALSE; uint_t quiet = FALSE; uint32_t mask = 0, not_mask = 0; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("genunix`stream_head_cache", "genunix`stdata", argc, argv) == -1) { mdb_warn("failed to walk stream head cache"); return (DCMD_ERR); } return (DCMD_OK); } if (flags & DCMD_PIPE_OUT) quiet = TRUE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'q', MDB_OPT_SETBITS, TRUE, &quiet, 'f', MDB_OPT_STR, &flag, 'F', MDB_OPT_STR, ¬_flag, NULL) != argc) return (DCMD_USAGE); /* * If any of the filtering flags is specified, don't print anything * except the matching pointer. */ if (flag != NULL || not_flag != NULL) quiet = TRUE; if (DCMD_HDRSPEC(flags) && !quiet) { mdb_printf("%?s %?s %8s %?s %s %s\n", "ADDR", "WRQ", "FLAGS", "VNODE", "N/A", "REF"); } if (flag != NULL && streams_parse_flag(stdf, flag, &mask) == -1) { mdb_warn("unrecognized stream flag '%s'\n", flag); streams_flag_usage(stdf); return (DCMD_USAGE); } if (not_flag != NULL && streams_parse_flag(stdf, not_flag, ¬_mask) == -1) { mdb_warn("unrecognized stream flag '%s'\n", flag); streams_flag_usage(stdf); return (DCMD_USAGE); } if (mdb_vread(&sd, sizeof (sd), addr) == -1) { mdb_warn("couldn't read stdata at %p", addr); return (DCMD_ERR); } /* * If stream doesn't pass filtering criteria, don't print anything and * just return. */ if (mask != 0 && !(sd.sd_flag & mask)) return (DCMD_OK); if (not_mask != 0 && (sd.sd_flag & not_mask)) return (DCMD_OK); /* * Options are specified for filtering, so If any option is specified on * the command line, just print address and exit. */ if (quiet) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } mdb_printf("%0?p %0?p %08x %0?p %d/%d %d\n", addr, sd.sd_wrq, sd.sd_flag, sd.sd_vnode, sd.sd_pushcnt, sd.sd_anchor, sd.sd_refcnt); if (verbose) { int i, arm = 0; for (i = 0; stdf[i].strf_name != NULL; i++) { if (!(sd.sd_flag & (1 << i))) continue; if (!arm) { mdb_printf("%*s|\n%*s+--> ", STREAM_FLGDELT, "", STREAM_FLGDELT, ""); arm = 1; } else mdb_printf("%*s ", STREAM_FLGDELT, ""); mdb_printf("%-12s %s\n", stdf[i].strf_name, stdf[i].strf_descr); } } return (DCMD_OK); } /*ARGSUSED*/ static void qprint_syncq(queue_t *addr, queue_t *q) { mdb_printf("%p\n", q->q_syncq); } /*ARGSUSED*/ static void qprint_stream(queue_t *addr, queue_t *q) { mdb_printf("%p\n", q->q_stream); } static void qprint_wrq(queue_t *addr, queue_t *q) { mdb_printf("%p\n", ((q)->q_flag & QREADR? (addr)+1: (addr))); } static void qprint_rdq(queue_t *addr, queue_t *q) { mdb_printf("%p\n", ((q)->q_flag & QREADR? (addr): (addr)-1)); } static void qprint_otherq(queue_t *addr, queue_t *q) { mdb_printf("%p\n", ((q)->q_flag & QREADR? (addr)+1: (addr)-1)); } static int q2x(uintptr_t addr, int argc, qprint_func prfunc) { queue_t q; if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&q, sizeof (q), addr) == -1) { mdb_warn("couldn't read queue at %p", addr); return (DCMD_ERR); } prfunc((queue_t *)addr, &q); return (DCMD_OK); } /*ARGSUSED*/ int q2syncq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (q2x(addr, argc, qprint_syncq)); } /*ARGSUSED*/ int q2stream(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (q2x(addr, argc, qprint_stream)); } /*ARGSUSED*/ int q2rdq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (q2x(addr, argc, qprint_rdq)); } /*ARGSUSED*/ int q2wrq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (q2x(addr, argc, qprint_wrq)); } /*ARGSUSED*/ int q2otherq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (q2x(addr, argc, qprint_otherq)); } static int sd2x(uintptr_t addr, int argc, sdprint_func prfunc) { stdata_t sd; if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&sd, sizeof (sd), addr) == -1) { mdb_warn("couldn't read stream head at %p", addr); return (DCMD_ERR); } prfunc((stdata_t *)addr, &sd); return (DCMD_OK); } /*ARGSUSED*/ static void sdprint_wrq(stdata_t *addr, stdata_t *sd) { mdb_printf("%p\n", sd->sd_wrq); } static void sdprint_mate(stdata_t *addr, stdata_t *sd) { mdb_printf("%p\n", sd->sd_mate ? sd->sd_mate : addr); } /*ARGSUSED*/ int str2mate(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (sd2x(addr, argc, sdprint_mate)); } /*ARGSUSED*/ int str2wrq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (sd2x(addr, argc, sdprint_wrq)); } /* * If this syncq is a part of the queue pair structure, find the queue for it. */ /*ARGSUSED*/ int syncq2q(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { syncq_t sq; queue_t q; queue_t *qp; if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&sq, sizeof (sq), addr) == -1) { mdb_warn("couldn't read syncq at %p", addr); return (DCMD_ERR); } /* Try to find its queue */ qp = (queue_t *)addr - 2; if ((mdb_vread(&q, sizeof (q), (uintptr_t)qp) == -1) || (q.q_syncq != (syncq_t *)addr)) { mdb_warn("syncq2q: %p is not part of any queue\n", addr); return (DCMD_ERR); } else mdb_printf("%p\n", qp); return (DCMD_OK); } int queue_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "qhead") == -1) { mdb_warn("failed to read 'qhead'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (queue_t), UM_SLEEP); return (WALK_NEXT); } int queue_link_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (queue_t), wsp->walk_addr) == -1) { mdb_warn("failed to read queue at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((queue_t *)wsp->walk_data)->q_link); return (status); } int queue_next_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (queue_t), wsp->walk_addr) == -1) { mdb_warn("failed to read queue at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((queue_t *)wsp->walk_data)->q_next); return (status); } void queue_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (queue_t)); } int str_walk_init(mdb_walk_state_t *wsp) { stdata_t s; if (wsp->walk_addr == 0) { mdb_warn("walk must begin at address of stdata_t\n"); return (WALK_ERR); } if (mdb_vread(&s, sizeof (s), wsp->walk_addr) == -1) { mdb_warn("failed to read stdata at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)s.sd_wrq; wsp->walk_data = mdb_alloc(sizeof (queue_t) * 2, UM_SLEEP); return (WALK_NEXT); } int strr_walk_step(mdb_walk_state_t *wsp) { queue_t *rq = wsp->walk_data, *wq = rq + 1; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (queue_t) * 2, wsp->walk_addr - sizeof (queue_t)) == -1) { mdb_warn("failed to read queue pair at %p", wsp->walk_addr - sizeof (queue_t)); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr - sizeof (queue_t), rq, wsp->walk_cbdata); if (wq->q_next != NULL) wsp->walk_addr = (uintptr_t)wq->q_next; else wsp->walk_addr = mdb_qwnext(wq); return (status); } int strw_walk_step(mdb_walk_state_t *wsp) { queue_t *rq = wsp->walk_data, *wq = rq + 1; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (queue_t) * 2, wsp->walk_addr - sizeof (queue_t)) == -1) { mdb_warn("failed to read queue pair at %p", wsp->walk_addr - sizeof (queue_t)); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wq, wsp->walk_cbdata); if (wq->q_next != NULL) wsp->walk_addr = (uintptr_t)wq->q_next; else wsp->walk_addr = mdb_qwnext(wq); return (status); } void str_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (queue_t) * 2); } static int print_qpair(uintptr_t addr, const queue_t *q, uint_t *depth) { static const char box_lid[] = "+-----------------------+-----------------------+\n"; static const char box_sep[] = "| | |\n"; char wname[32], rname[32], info1[256], *info2; if (*depth != 0) { mdb_printf(" | ^\n"); mdb_printf(" v |\n"); } else mdb_printf("\n"); (void) mdb_qname(_WR(q), wname, sizeof (wname)); (void) mdb_qname(_RD(q), rname, sizeof (rname)); mdb_qinfo(_WR(q), info1, sizeof (info1)); if ((info2 = strchr(info1, '\n')) != NULL) *info2++ = '\0'; else info2 = ""; mdb_printf(box_lid); mdb_printf("| 0x%-19p | 0x%-19p | %s\n", addr, addr - sizeof (queue_t), info1); mdb_printf("| %%-21s% | %%-21s% |", wname, rname); mdb_flush(); /* Account for buffered terminal sequences */ mdb_printf(" %s\n", info2); mdb_printf(box_sep); mdb_qinfo(_RD(q), info1, sizeof (info1)); if ((info2 = strchr(info1, '\n')) != NULL) *info2++ = '\0'; else info2 = ""; mdb_printf("| cnt = 0t%-13lu | cnt = 0t%-13lu | %s\n", _WR(q)->q_count, _RD(q)->q_count, info1); mdb_printf("| flg = 0x%08x | flg = 0x%08x | %s\n", _WR(q)->q_flag, _RD(q)->q_flag, info2); mdb_printf(box_lid); *depth += 1; return (0); } /*ARGSUSED*/ int stream(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t d = 0; /* Depth counter for print_qpair */ if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_pwalk("writeq", (mdb_walk_cb_t)print_qpair, &d, addr) == -1) { mdb_warn("failed to walk writeq"); return (DCMD_ERR); } return (DCMD_OK); } int mblk_walk_init(mdb_walk_state_t *wsp) { wsp->walk_data = mdb_alloc(sizeof (mblk_t), UM_SLEEP); return (WALK_NEXT); } int b_cont_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (mblk_t), wsp->walk_addr) == -1) { mdb_warn("failed to read mblk at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((mblk_t *)wsp->walk_data)->b_cont); return (status); } int b_next_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (mblk_t), wsp->walk_addr) == -1) { mdb_warn("failed to read mblk at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((mblk_t *)wsp->walk_data)->b_next); return (status); } void mblk_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (mblk_t)); } /* ARGSUSED */ int mblk2dblk(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mblk_t mb; if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&mb, sizeof (mb), addr) == -1) { mdb_warn("couldn't read mblk at %p", addr); return (DCMD_ERR); } mdb_printf("%p\n", mb.b_datap); return (DCMD_OK); } static void mblk_error(int *error, uintptr_t addr, char *message) { if (!*error) mdb_printf("%?lx: ", addr); else mdb_printf(", "); mdb_printf("%s", message); *error = 1; } int mblk_verify(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mblk_t mb; dblk_t db; int error = 0; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("streams_mblk", "mblk_verify", argc, argv) == -1) { mdb_warn("can't walk mblk cache"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&mb, sizeof (mblk_t), addr) == -1) { mdb_warn("can't read mblk_t at 0x%lx", addr); return (DCMD_ERR); } if (mdb_vread(&db, sizeof (dblk_t), (uintptr_t)mb.b_datap) == -1) { mdb_warn("%?lx: invalid b_datap pointer\n", addr); return (DCMD_ERR); } if (mb.b_rptr < db.db_base || mb.b_rptr > db.db_lim) mblk_error(&error, addr, "b_rptr out of range"); if (mb.b_wptr < db.db_base || mb.b_wptr > db.db_lim) mblk_error(&error, addr, "b_wptr out of range"); if (error) mdb_printf("\n"); return (error ? DCMD_ERR : DCMD_OK); } int mblk_prt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const int MBLK_FLGDELT = (int)(sizeof (uintptr_t) * 2 + 15); mblk_t mblk; dblk_t dblk; int b_flag; int db_type; int mblklen; uint64_t len = ~0UL; uint64_t glen = ~0UL; uint64_t llen = ~0UL; uint64_t blen = ~0UL; const char *dbtype; const char *flag = NULL, *not_flag = NULL; const char *typ = NULL, *not_typ = NULL; uintptr_t dbaddr = 0; uint32_t tmask = 0, not_tmask = 0; uint32_t mask = 0, not_mask = 0; uint_t quiet = FALSE; uint_t verbose = FALSE; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("genunix`streams_mblk", "genunix`mblk", argc, argv) == -1) { mdb_warn("failed to walk mblk cache"); return (DCMD_ERR); } return (DCMD_OK); } if (flags & DCMD_PIPE_OUT) quiet = TRUE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'q', MDB_OPT_SETBITS, TRUE, &quiet, 'f', MDB_OPT_STR, &flag, 'F', MDB_OPT_STR, ¬_flag, 't', MDB_OPT_STR, &typ, 'T', MDB_OPT_STR, ¬_typ, 'l', MDB_OPT_UINT64, &len, 'L', MDB_OPT_UINT64, &llen, 'G', MDB_OPT_UINT64, &glen, 'b', MDB_OPT_UINT64, &blen, 'd', MDB_OPT_UINTPTR, &dbaddr, NULL) != argc) return (DCMD_USAGE); /* * If any of the filtering flags is specified, don't print anything * except the matching pointer. */ if ((flag != NULL) || (not_flag != NULL) || (typ != NULL) || (not_typ != NULL) || (len != ~0UL) || (glen != ~0UL) || (llen != ~0UL) || (blen != ~0UL) || (dbaddr != 0)) quiet = TRUE; if (flag != NULL && streams_parse_flag(mbf, flag, &mask) == -1) { mdb_warn("unrecognized mblk flag '%s'\n", flag); streams_flag_usage(mbf); return (DCMD_USAGE); } if (not_flag != NULL && streams_parse_flag(mbf, not_flag, ¬_mask) == -1) { mdb_warn("unrecognized mblk flag '%s'\n", flag); streams_flag_usage(mbf); return (DCMD_USAGE); } if (typ != NULL && streams_parse_type(mbt, typ, &tmask) == -1) { mdb_warn("unrecognized dblk type '%s'\n", typ); streams_type_usage(mbt); return (DCMD_USAGE); } if (not_typ != NULL && streams_parse_type(mbt, not_typ, ¬_tmask) == -1) { mdb_warn("unrecognized dblk type '%s'\n", not_typ); streams_type_usage(mbt); return (DCMD_USAGE); } if (DCMD_HDRSPEC(flags) && !quiet) { mdb_printf("%?s %2s %-7s %-5s %-5s %?s %?s\n", "ADDR", "FL", "TYPE", "LEN", "BLEN", "RPTR", "DBLK"); } if (mdb_vread(&mblk, sizeof (mblk), addr) == -1) { mdb_warn("couldn't read mblk at %p", addr); return (DCMD_ERR); } b_flag = mblk.b_flag; if (mask != 0 && !(b_flag & mask)) return (DCMD_OK); if (not_mask != 0 && (b_flag & not_mask)) return (DCMD_OK); if (mdb_vread(&dblk, sizeof (dblk), (uintptr_t)(mblk.b_datap)) == -1) { mdb_warn("couldn't read dblk at %p/%p", addr, mblk.b_datap); return (DCMD_ERR); } db_type = dblk.db_type; /* M_DATA is 0, so tmask has special value 0xff for it */ if (tmask != 0) { if ((tmask == M_DATA_T && db_type != M_DATA) || (tmask != M_DATA_T && db_type != tmask)) return (DCMD_OK); } if (not_tmask != 0) { if ((not_tmask == M_DATA_T && db_type == M_DATA) || (db_type == not_tmask)) return (DCMD_OK); } if (dbaddr != 0 && (uintptr_t)mblk.b_datap != dbaddr) return (DCMD_OK); mblklen = MBLKL(&mblk); if ((len != ~0UL) && (len != mblklen)) return (DCMD_OK); if ((llen != ~0Ul) && (mblklen > (int)llen)) return (DCMD_OK); if ((glen != ~0Ul) && (mblklen < (int)glen)) return (DCMD_OK); if ((blen != ~0UL) && (blen != (dblk.db_lim - dblk.db_base))) return (DCMD_OK); /* * Options are specified for filtering, so If any option is specified on * the command line, just print address and exit. */ if (quiet) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } /* Figure out symbolic DB_TYPE */ if (db_type < A_SIZE(db_control_types)) { dbtype = db_control_types[db_type]; } else { /* * Must be a high-priority message -- adjust so that * "QPCTL + 1" corresponds to db_control_hipri_types[0] */ db_type -= (QPCTL + 1); if (db_type >= 0 && db_type < A_SIZE(db_control_hipri_types)) dbtype = db_control_hipri_types[db_type]; else dbtype = "UNKNOWN"; } mdb_printf("%0?p %-2x %-7s %-5d %-5d %0?p %0?p\n", addr, b_flag, dbtype, mblklen, dblk.db_lim - dblk.db_base, mblk.b_rptr, mblk.b_datap); if (verbose) { int i, arm = 0; for (i = 0; mbf[i].strf_name != NULL; i++) { if (!(b_flag & (1 << i))) continue; if (!arm) { mdb_printf("%*s|\n%*s+--> ", MBLK_FLGDELT, "", MBLK_FLGDELT, ""); arm = 1; } else mdb_printf("%*s ", MBLK_FLGDELT, ""); mdb_printf("%-12s %s\n", mbf[i].strf_name, mbf[i].strf_descr); } } return (DCMD_OK); } /* * Streams flow trace walkers. */ int strftblk_walk_init(mdb_walk_state_t *wsp) { ftblkdata_t *ftd; dblk_t db; /* Get the dblock from the address */ if (mdb_vread(&db, sizeof (dblk_t), wsp->walk_addr) == -1) { mdb_warn("failed to read dblk at %p", wsp->walk_addr); return (WALK_ERR); } /* Is there any flow trace data? */ if (db.db_fthdr == NULL) { return (WALK_DONE); } wsp->walk_addr = (uintptr_t)((char *)db.db_fthdr + offsetof(fthdr_t, first)); ftd = mdb_alloc(sizeof (ftblkdata_t), UM_SLEEP); ftd->ft_ix = 0; ftd->ft_in_evlist = B_FALSE; wsp->walk_data = ftd; return (WALK_NEXT); } int strftblk_step(mdb_walk_state_t *wsp) { ftblkdata_t *ftd; ftblk_t *ftbp; int status = WALK_NEXT; if (wsp->walk_addr == 0) return (WALK_DONE); ftd = (ftblkdata_t *)wsp->walk_data; ftbp = &(ftd->ft_data); if (! ftd->ft_in_evlist) { /* Read a new ft block */ if (mdb_vread(ftbp, sizeof (ftblk_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ftblk at %p", wsp->walk_addr); return (WALK_ERR); } /* * Check correctness of the index field. */ if (ftbp->ix < 0 || ftbp->ix > FTBLK_EVNTS) { mdb_warn("ftblk: incorrect index value %i\n", ftbp->ix); return (WALK_ERR); } ftd->ft_ix = 1; ftd->ft_in_evlist = B_TRUE; } if (ftd->ft_ix > ftbp->ix) { ftd->ft_in_evlist = B_FALSE; /* End of event list reached - move to the next event block */ wsp->walk_addr = (uintptr_t)ftbp->nxt; } else { /* Print event address */ status = wsp->walk_callback((uintptr_t)((char *)wsp->walk_addr + offsetof(ftblk_t, ev) + (ftd->ft_ix - 1) * sizeof (struct ftevnt)), wsp->walk_data, wsp->walk_cbdata); ftd->ft_ix++; } return (status); } void strftblk_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (ftblkdata_t)); } static const char * getqname(const void *nameptr, char *buf, uint_t bufsize) { char *cp; if (mdb_readstr(buf, bufsize, (uintptr_t)nameptr) == -1) goto fail; /* * Sanity-check the name we read. This is needed because the pointer * value may have been recycled for some other purpose in the kernel * (e.g., if the STREAMS module was unloaded). */ for (cp = buf; *cp != '\0'; cp++) { if (!isprint(*cp)) goto fail; } return (buf); fail: return (strncpy(buf, "?", bufsize)); } /*ARGSUSED*/ int strftevent(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; struct ftstk stk; struct ftevnt ev; char name[FMNAMESZ + 1]; boolean_t havestk = B_FALSE; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-18s %-9s %-18s %4s %s\n", "ADDR", "Q/CALLER", "QNEXT", "STACK", "DATA", "EVENT"); } if (mdb_vread(&ev, sizeof (ev), addr) == -1) { mdb_warn("couldn't read struct ftevnt at %p", addr); return (DCMD_ERR); } mdb_printf("%0?p", addr); if (ev.evnt & FTEV_QMASK) mdb_printf(" %-18s", getqname(ev.mid, name, sizeof (name))); else mdb_printf(" %-18a", ev.mid); if ((ev.evnt & FTEV_MASK) == FTEV_PUTNEXT) mdb_printf(" %-9s", getqname(ev.midnext, name, sizeof (name))); else mdb_printf(" %-9s", "--"); if (ev.stk == NULL) { mdb_printf(" %-18s", "--"); } else if (mdb_vread(&stk, sizeof (stk), (uintptr_t)ev.stk) == -1) { mdb_printf(" %-18s", "?"); } else { mdb_printf(" %-18a", stk.fs_stk[0]); havestk = B_TRUE; } mdb_printf(" %4x", ev.data); ft_printevent(ev.evnt); mdb_printf("\n"); if (havestk) { for (i = 1; i < stk.fs_depth; i++) { mdb_printf("%?s %-18s %-9s %-18a\n", "", "", "", stk.fs_stk[i]); } } return (DCMD_OK); } static void ft_printevent(ushort_t ev) { ushort_t proc_ev = (ev & (FTEV_PROC_START | 0xFF)) - FTEV_PROC_START; ushort_t alloc_ev = ev & FTEV_CALLER; /* Get event class first */ if (ev & FTEV_PROC_START) { if (proc_ev >= A_SIZE(ftev_proc)) mdb_printf(" undefined"); else mdb_printf(" %s", ftev_proc[proc_ev]); } else if (alloc_ev >= A_SIZE(ftev_alloc)) { mdb_printf(" undefined"); } else { mdb_printf(" %s", ftev_alloc[alloc_ev]); } /* Print event modifiers, if any */ if (ev & (FTEV_PS | FTEV_CS | FTEV_ISWR)) { mdb_printf("|"); if (ev & FTEV_ISWR) mdb_printf("W"); if (ev & FTEV_CS) mdb_printf("C"); if (ev & FTEV_PS) mdb_printf("P"); } } /* * Help functions for STREAMS debugging facilities. */ void queue_help(void) { mdb_printf("Print queue information for a given queue pointer.\n" "\nWithout the address of a \"queue_t\" structure given, print " "information about all\n" "queues in the \"queue_cache\".\n\n" "Options:\n" " -v:\t\tbe verbose - print symbolic flags falues\n" " -q:\t\tbe quiet - print queue pointer only\n" " -f flag:\tprint only queues with flag set\n" " -F flag:\tprint only queues with flag NOT set\n" " -m modname:\tprint only queues with specified module name\n" " -s syncq_addr:\tprint only queues which use specified syncq\n\n" "Available conversions:\n" " q2rdq: given a queue addr print read queue pointer\n" " q2wrq: given a queue addr print write queue pointer\n" " q2otherq: given a queue addr print other queue pointer\n" " q2syncq: given a queue addr print syncq pointer" " (::help syncq)\n" " q2stream: given a queue addr print its stream pointer\n" "\t\t(see ::help stream and ::help stdata)\n\n" "To walk q_next pointer of the queue use\n" " queue_addr::walk qnext\n"); } void syncq_help(void) { mdb_printf("Print syncq information for a given syncq pointer.\n" "\nWithout the address of a \"syncq_t\" structure given, print " "information about all\n" "syncqs in the \"syncq_cache\".\n\n" "Options:\n" " -v:\t\tbe verbose - print symbolic flags falues\n" " -q:\t\tbe quiet - print syncq pointer only\n" " -f flag:\tprint only syncqs with flag set\n" " -F flag:\tprint only syncqs with flag NOT set\n" " -t type:\tprint only syncqs with specified type\n" " -T type:\tprint only syncqs with do NOT have specified type\n\n" "Available conversions:\n" " syncq2q:\tgiven a syncq addr print queue address of the\n" "\t\t\tenclosing queue, if it is part of a queue\n\n" "See also: \"::help queue\" and \"::help stdata\"\n"); } void stdata_help(void) { mdb_printf("Print stdata information for a given stdata pointer.\n" "\nWithout the address of a \"stdata_t\" structure given, print " "information about all\n" "stream head pointers from the \"stream_head_cache\".\n\n" "Fields printed:\n" " ADDR:\tstream head address\n" " WRQ:\twrite queue pointer\n" " FLAGS:\tstream head flags (use -v to show in symbolic form)\n" " VNODE:\tstream vnode pointer\n" " N/A:\tpushcount and anchor positions\n" " REF:\tstream head reference counter\n\n" "Options:\n" " -v:\t\tbe verbose - print symbolic flags falues\n" " -q:\t\tbe quiet - print stdata pointer only\n" " -f flag:\tprint only stdatas with flag set\n" " -F flag:\tprint only stdatas with flag NOT set\n\n" "Available conversions:\n" " str2mate:\tgiven a stream head addr print its mate\n" " str2wrq:\tgiven a stream head addr print its write queue\n\n" "See also: \"::help queue\" and \"::help syncq\"\n"); } void mblk_help(void) { mdb_printf("Print mblock information for a given mblk pointer.\n" "Without the address, print information about all mblocks.\n\n" "Fields printed:\n" " ADDR:\tmblk address\n" " FL:\tFlags\n" " TYPE:\tType of corresponding dblock\n" " LEN:\tData length as b_wptr - b_rptr\n" " BLEN:\tDblock space as db_lim - db_base\n" " RPTR:\tRead pointer\n" " DBLK:\tDblock pointer\n\n" "Options:\n" " -v:\t\tbe verbose - print symbolic flags falues\n" " -q:\t\tbe quiet - print mblk pointer only\n" " -d dbaddr:\t\tprint mblks with specified dblk address\n" " -f flag:\tprint only mblks with flag set\n" " -F flag:\tprint only mblks with flag NOT set\n" " -t type:\tprint only mblks of specified db_type\n" " -T type:\tprint only mblks other then the specified db_type\n" " -l len:\t\ttprint only mblks with MBLKL == len\n" " -L len:\t\tprint only mblks with MBLKL <= len \n" " -G len:\t\tprint only mblks with MBLKL >= len \n" " -b len:\t\tprint only mblks with db_lim - db_base == len\n" "\n"); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 1998-2001, 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _STREAMS_H #define _STREAMS_H #include #ifdef __cplusplus extern "C" { #endif int queue_walk_init(mdb_walk_state_t *); int queue_link_step(mdb_walk_state_t *); int queue_next_step(mdb_walk_state_t *); void queue_walk_fini(mdb_walk_state_t *); int str_walk_init(mdb_walk_state_t *); int strr_walk_step(mdb_walk_state_t *); int strw_walk_step(mdb_walk_state_t *); void str_walk_fini(mdb_walk_state_t *); int mblk_walk_init(mdb_walk_state_t *); int b_cont_step(mdb_walk_state_t *); int b_next_step(mdb_walk_state_t *); void mblk_walk_fini(mdb_walk_state_t *); int strftblk_walk_init(mdb_walk_state_t *); int strftblk_step(mdb_walk_state_t *); void strftblk_walk_fini(mdb_walk_state_t *); int stream(uintptr_t, uint_t, int, const mdb_arg_t *); int queue(uintptr_t, uint_t, int, const mdb_arg_t *); int q2syncq(uintptr_t, uint_t, int, const mdb_arg_t *); int q2stream(uintptr_t, uint_t, int, const mdb_arg_t *); int q2rdq(uintptr_t, uint_t, int, const mdb_arg_t *); int q2wrq(uintptr_t, uint_t, int, const mdb_arg_t *); int q2otherq(uintptr_t, uint_t, int, const mdb_arg_t *); int stdata(uintptr_t, uint_t, int, const mdb_arg_t *); int str2mate(uintptr_t, uint_t, int, const mdb_arg_t *); int str2wrq(uintptr_t, uint_t, int, const mdb_arg_t *); int syncq(uintptr_t, uint_t, int, const mdb_arg_t *); int syncq2q(uintptr_t, uint_t, int, const mdb_arg_t *); int strftevent(uintptr_t, uint_t, int, const mdb_arg_t *); int mblk_prt(uintptr_t, uint_t, int, const mdb_arg_t *); int mblk2dblk(uintptr_t, uint_t, int, const mdb_arg_t *); int mblk_verify(uintptr_t, uint_t, int, const mdb_arg_t *); void queue_help(void); void syncq_help(void); void stdata_help(void); void mblk_help(void); #ifdef __cplusplus } #endif #endif /* _STREAMS_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "sysevent.h" int sysevent_buf(uintptr_t addr, uint_t flags, uint_t opt_flags) { sysevent_hdr_t evh; sysevent_impl_t *ev; int size; if (DCMD_HDRSPEC(flags)) { if ((opt_flags & SYSEVENT_VERBOSE) == 0) { mdb_printf("%%-?s %-16s %-9s %-10s " "%-?s%\n", "ADDRESS", "SEQUENCE ID", "CLASS", "SUBCLASS", "NVPAIR BUF ADDR"); } } /* * Read in the sysevent buffer header first. After extracting * the size of the buffer, re-read the buffer in its entirety. */ if (mdb_vread(&evh, sizeof (sysevent_hdr_t), addr) == -1) { mdb_warn("failed to read event header at %p", addr); return (DCMD_ERR); } size = SE_SIZE((sysevent_impl_t *)&evh); ev = mdb_alloc(size, UM_SLEEP | UM_GC); if (mdb_vread(ev, size, addr) == -1) { mdb_warn("can not read sysevent at %p", addr); return (DCMD_ERR); } if ((opt_flags & SYSEVENT_VERBOSE) == 0) { char ev_class[CLASS_FIELD_MAX]; char ev_subclass[SUBCLASS_FIELD_MAX]; if (mdb_snprintf(ev_class, CLASS_FIELD_MAX, "%s", SE_CLASS_NAME(ev)) >= CLASS_FIELD_MAX - 1) (void) strcpy(&ev_class[CLASS_FIELD_MAX - 4], "..."); if (mdb_snprintf(ev_subclass, SUBCLASS_FIELD_MAX, "%s", SE_SUBCLASS_NAME(ev)) >= SUBCLASS_FIELD_MAX - 1) (void) strcpy(&ev_subclass[SUBCLASS_FIELD_MAX - 4], "..."); mdb_printf("%-?p %-16llu %-9s %-10s %-?p%\n", addr, SE_SEQ(ev), ev_class, ev_subclass, addr + SE_ATTR_OFF(ev)); } else { mdb_printf("%Sequence ID\t : %llu%\n", SE_SEQ(ev)); mdb_printf("%16s : %s\n", "publisher", SE_PUB_NAME(ev)); mdb_printf("%16s : %p\n", "event address", (caddr_t)addr); mdb_printf("%16s : %s\n", "class", SE_CLASS_NAME(ev)); mdb_printf("%16s : %s\n", "subclass", SE_SUBCLASS_NAME(ev)); mdb_printf("%16s : %llu\n", "time stamp", SE_TIME(ev)); mdb_printf("%16s : %p\n", "nvpair buf addr", addr + SE_ATTR_OFF(ev)); } return (DCMD_OK); } int sysevent_subclass_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int subclass_name_sz; char subclass_name[CLASS_LIST_FIELD_MAX]; subclass_lst_t sclist; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if ((flags & DCMD_LOOP) == 0) { if (mdb_pwalk_dcmd("sysevent_subclass_list", "sysevent_subclass_list", argc, argv, addr) == -1) { mdb_warn("can't walk sysevent subclass list"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-?s %-24s %-?s%\n", "ADDR", "NAME", "SUBSCRIBER DATA ADDR"); } if (mdb_vread(&sclist, sizeof (sclist), (uintptr_t)addr) == -1) { mdb_warn("failed to read subclass list at %p", addr); return (DCMD_ERR); } if ((subclass_name_sz = mdb_readstr(subclass_name, CLASS_LIST_FIELD_MAX, (uintptr_t)sclist.sl_name)) == -1) { mdb_warn("failed to read class name at %p", sclist.sl_name); return (DCMD_ERR); } if (subclass_name_sz >= CLASS_LIST_FIELD_MAX - 1) (void) strcpy(&subclass_name[CLASS_LIST_FIELD_MAX - 4], "..."); mdb_printf("%-?p %-24s %-?p\n", addr, subclass_name, addr + offsetof(subclass_lst_t, sl_num)); return (DCMD_OK); } int sysevent_class_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int class_name_sz; char class_name[CLASS_LIST_FIELD_MAX]; class_lst_t clist; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if ((flags & DCMD_LOOP) == 0) { if (mdb_pwalk_dcmd("sysevent_class_list", "sysevent_class_list", argc, argv, addr) == -1) { mdb_warn("can't walk sysevent class list"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%-?s %-24s %-?s%\n", "ADDR", "NAME", "SUBCLASS LIST ADDR"); if (mdb_vread(&clist, sizeof (clist), (uintptr_t)addr) == -1) { mdb_warn("failed to read class clist at %p", addr); return (DCMD_ERR); } if ((class_name_sz = mdb_readstr(class_name, CLASS_LIST_FIELD_MAX, (uintptr_t)clist.cl_name)) == -1) { mdb_warn("failed to read class name at %p", clist.cl_name); return (DCMD_ERR); } if (class_name_sz >= CLASS_LIST_FIELD_MAX - 1) (void) strcpy(&class_name[CLASS_LIST_FIELD_MAX - 4], "..."); mdb_printf("%-?p %-24s %-?p\n", addr, class_name, clist.cl_subclass_list); return (DCMD_OK); } int sysevent_subclass_list_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("sysevent_subclass_list does not support global " "walks"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (subclass_lst_t), UM_SLEEP); return (WALK_NEXT); } int sysevent_subclass_list_walk_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (subclass_lst_t), wsp->walk_addr) == -1) { mdb_warn("failed to read class list at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((subclass_lst_t *)wsp->walk_data)->sl_next); return (status); } void sysevent_subclass_list_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (subclass_lst_t)); } typedef struct class_walk_data { int hash_index; class_lst_t *hash_tbl[CLASS_HASH_SZ + 1]; } class_walk_data_t; int sysevent_class_list_walk_init(mdb_walk_state_t *wsp) { class_walk_data_t *cl_walker; if (wsp->walk_addr == 0) { mdb_warn("sysevent_class_list does not support global walks"); return (WALK_ERR); } cl_walker = mdb_zalloc(sizeof (class_walk_data_t), UM_SLEEP); if (mdb_vread(cl_walker->hash_tbl, sizeof (cl_walker->hash_tbl), wsp->walk_addr) == -1) { mdb_warn("failed to read class hash table at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)cl_walker->hash_tbl[0]; wsp->walk_data = cl_walker; return (WALK_NEXT); } int sysevent_class_list_walk_step(mdb_walk_state_t *wsp) { int status = WALK_NEXT; class_walk_data_t *cl_walker; class_lst_t clist; cl_walker = (class_walk_data_t *)wsp->walk_data; /* Skip over empty class table entries */ if (wsp->walk_addr != 0) { if (mdb_vread(&clist, sizeof (class_lst_t), wsp->walk_addr) == -1) { mdb_warn("failed to read class list at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)clist.cl_next; } else { if (cl_walker->hash_index > CLASS_HASH_SZ) { return (WALK_DONE); } else { wsp->walk_addr = (uintptr_t) cl_walker->hash_tbl[cl_walker->hash_index]; cl_walker->hash_index++; } } return (status); } void sysevent_class_list_walk_fini(mdb_walk_state_t *wsp) { class_walk_data_t *cl_walker = wsp->walk_data; mdb_free(cl_walker, sizeof (cl_walker)); } #ifdef _KERNEL int sysevent(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t sys_flags = FALSE; if (mdb_getopts(argc, argv, 's', MDB_OPT_SETBITS, SYSEVENT_SENTQ, &sys_flags, 'v', MDB_OPT_SETBITS, SYSEVENT_VERBOSE, &sys_flags, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { if (sys_flags & SYSEVENT_SENTQ) { if (mdb_walk_dcmd("sysevent_sent", "sysevent", argc, argv) == -1) { mdb_warn("can not walk sent queue"); return (DCMD_ERR); } } else { if (mdb_walk_dcmd("sysevent_pend", "sysevent", argc, argv) == -1) { mdb_warn("can not walk pending queue"); return (DCMD_ERR); } } return (DCMD_OK); } return (sysevent_buf(addr, flags, sys_flags)); } int sysevent_channel(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ssize_t channel_name_sz; char channel_name[CHAN_FIELD_MAX]; sysevent_channel_descriptor_t chan_tbl; if (argc != 0) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { if (mdb_walk_dcmd("sysevent_channel", "sysevent_channel", argc, argv) == -1) { mdb_warn("can't walk sysevent channel"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%-?s %-16s %-8s %-?s%\n", "ADDR", "NAME", "REF CNT", "CLASS LST ADDR"); if (mdb_vread(&chan_tbl, sizeof (chan_tbl), (uintptr_t)addr) == -1) { mdb_warn("failed to read channel table at %p", addr); return (DCMD_ERR); } if ((channel_name_sz = mdb_readstr(channel_name, CHAN_FIELD_MAX, (uintptr_t)chan_tbl.scd_channel_name)) == -1) { mdb_warn("failed to read channel name at %p", chan_tbl.scd_channel_name); return (DCMD_ERR); } if (channel_name_sz >= CHAN_FIELD_MAX - 1) (void) strcpy(&channel_name[CHAN_FIELD_MAX - 4], "..."); mdb_printf("%-?p %-16s %-8lu %-?p\n", addr, channel_name, chan_tbl.scd_ref_cnt, addr + offsetof(sysevent_channel_descriptor_t, scd_class_list_tbl)); return (DCMD_OK); } typedef struct channel_walk_data { int hash_index; sysevent_channel_descriptor_t *hash_tbl[CHAN_HASH_SZ]; } channel_walk_data_t; int sysevent_channel_walk_init(mdb_walk_state_t *wsp) { channel_walk_data_t *ch_walker; if (wsp->walk_addr != 0) { mdb_warn("sysevent_channel supports only global walks"); return (WALK_ERR); } ch_walker = mdb_zalloc(sizeof (channel_walk_data_t), UM_SLEEP); if (mdb_readvar(ch_walker->hash_tbl, "registered_channels") == -1) { mdb_warn("failed to read 'registered_channels'"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)ch_walker->hash_tbl[0]; wsp->walk_data = ch_walker; return (WALK_NEXT); } int sysevent_channel_walk_step(mdb_walk_state_t *wsp) { int status = WALK_NEXT; channel_walk_data_t *ch_walker; sysevent_channel_descriptor_t scd; ch_walker = (channel_walk_data_t *)wsp->walk_data; /* Skip over empty hash table entries */ if (wsp->walk_addr != 0) { if (mdb_vread(&scd, sizeof (sysevent_channel_descriptor_t), wsp->walk_addr) == -1) { mdb_warn("failed to read channel at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)scd.scd_next; } else { if (ch_walker->hash_index == CHAN_HASH_SZ) { return (WALK_DONE); } else { wsp->walk_addr = (uintptr_t) ch_walker->hash_tbl[ch_walker->hash_index]; ch_walker->hash_index++; } } return (status); } void sysevent_channel_walk_fini(mdb_walk_state_t *wsp) { channel_walk_data_t *ch_walker = wsp->walk_data; mdb_free(ch_walker, sizeof (ch_walker)); } int sysevent_pend_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { if (mdb_readvar(&wsp->walk_addr, "log_eventq_head") == -1) { mdb_warn("failed to read 'log_eventq_head'"); return (WALK_ERR); } } wsp->walk_data = mdb_alloc(sizeof (log_eventq_t), UM_SLEEP); return (WALK_NEXT); } int sysevent_walk_step(mdb_walk_state_t *wsp) { int status; uintptr_t ev_arg_addr; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (log_eventq_t), wsp->walk_addr) == -1) { mdb_warn("failed to read event queue at %p", wsp->walk_addr); return (WALK_ERR); } ev_arg_addr = wsp->walk_addr + offsetof(log_eventq_t, arg.buf); status = wsp->walk_callback(ev_arg_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((log_eventq_t *)wsp->walk_data)->next); return (status); } int sysevent_sent_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { if (mdb_readvar(&wsp->walk_addr, "log_eventq_sent") == -1) { mdb_warn("failed to read 'log_eventq_sent'"); return (WALK_ERR); } } wsp->walk_data = mdb_alloc(sizeof (log_eventq_t), UM_SLEEP); return (WALK_NEXT); } void sysevent_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (log_eventq_t)); } #endif /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYSEVENT_H #define _SYSEVENT_H #ifdef __cplusplus extern "C" { #endif #ifdef _KERNEL #include #else #include #endif #include #include #include #include #include #include #define SYSEVENT_SENTQ 0x1 #define SYSEVENT_VERBOSE 0x2 #define CLASS_FIELD_MAX 9 #define CHAN_FIELD_MAX 14 #define CLASS_LIST_FIELD_MAX 24 #define SUBCLASS_FIELD_MAX 10 extern int sysevent_buf(uintptr_t addr, uint_t flags, uint_t opt_flags); extern int sysevent(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int sysevent_channel(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int sysevent_class_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int sysevent_subclass_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int sysevent_pend_walk_init(mdb_walk_state_t *wsp); extern int sysevent_walk_step(mdb_walk_state_t *wsp); extern int sysevent_sent_walk_init(mdb_walk_state_t *wsp); extern void sysevent_walk_fini(mdb_walk_state_t *wsp); extern int sysevent_channel_walk_init(mdb_walk_state_t *wsp); extern int sysevent_channel_walk_step(mdb_walk_state_t *wsp); extern void sysevent_channel_walk_fini(mdb_walk_state_t *wsp); extern int sysevent_class_list_walk_init(mdb_walk_state_t *wsp); extern int sysevent_class_list_walk_step(mdb_walk_state_t *wsp); extern void sysevent_class_list_walk_fini(mdb_walk_state_t *wsp); extern int sysevent_subclass_list_walk_init(mdb_walk_state_t *wsp); extern int sysevent_subclass_list_walk_step(mdb_walk_state_t *wsp); extern void sysevent_subclass_list_walk_fini(mdb_walk_state_t *wsp); #ifdef __cplusplus } #endif #endif /* _SYSEVENT_H */ /* * 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 2023-2024 RackTop Systems, Inc. */ #include #include #include #include #include #include "taskq.h" typedef struct tqarray_ent { uintptr_t tq_addr; char tq_name[TASKQ_NAMELEN + 1]; int tq_instance; uint_t tq_flags; } tqarray_ent_t; typedef struct tq_info { tqarray_ent_t *tqi_array; size_t tqi_count; size_t tqi_size; } tq_info_t; /* * We sort taskqs as follows: * * DYNAMIC last * NOINSTANCE first * within NOINSTANCE, sort by order of creation (instance #) * within non-NOINSTANCE, sort by name (case-insensitive) then instance # */ int tqcmp(const void *lhs, const void *rhs) { const tqarray_ent_t *l = lhs; const tqarray_ent_t *r = rhs; uint_t lflags = l->tq_flags; uint_t rflags = r->tq_flags; int ret; if ((lflags & TASKQ_DYNAMIC) && !(rflags & TASKQ_DYNAMIC)) return (1); if (!(lflags & TASKQ_DYNAMIC) && (rflags & TASKQ_DYNAMIC)) return (-1); if ((lflags & TASKQ_NOINSTANCE) && !(rflags & TASKQ_NOINSTANCE)) return (-1); if (!(lflags & TASKQ_NOINSTANCE) && (rflags & TASKQ_NOINSTANCE)) return (1); if (!(lflags & TASKQ_NOINSTANCE) && (ret = strcasecmp(l->tq_name, r->tq_name)) != 0) return (ret); if (l->tq_instance < r->tq_instance) return (-1); if (l->tq_instance > r->tq_instance) return (1); return (0); } /*ARGSUSED*/ int tq_count(uintptr_t addr, const void *ignored, void *arg) { tq_info_t *ti = arg; ti->tqi_size++; return (WALK_NEXT); } /*ARGSUSED*/ int tq_fill(uintptr_t addr, const void *ignored, tq_info_t *ti) { int idx = ti->tqi_count; taskq_t tq; tqarray_ent_t *tqe = &ti->tqi_array[idx]; if (idx == ti->tqi_size) { mdb_warn("taskq: inadequate slop\n"); return (WALK_ERR); } if (mdb_vread(&tq, sizeof (tq), addr) == -1) { mdb_warn("unable to read taskq_t at %p", addr); return (WALK_NEXT); } ti->tqi_count++; tqe->tq_addr = addr; strncpy(tqe->tq_name, tq.tq_name, TASKQ_NAMELEN); tqe->tq_instance = tq.tq_instance; tqe->tq_flags = tq.tq_flags; return (WALK_NEXT); } /* * Dcmd: taskq * ::taskq :[-atT] [-m min_maxq] [-n name] * With addr, display taskq details * Without addr, list all, filtered */ void taskq_help(void) { mdb_printf("%s", " -a Only show taskqs with active threads.\n" " -t Display active thread stacks in each taskq.\n" " -T Display all thread stacks in each taskq.\n" " -m min_maxq\n" " Only show Dynamic taskqs and taskqs with a MAXQ of at\n" " least min_maxq.\n" " -n name\n" " Only show taskqs which contain name somewhere in their\n" " name.\n"); } int taskq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { taskq_t tq; const char *name = NULL; uintptr_t minmaxq = 0; uint_t active = FALSE; uint_t print_threads = FALSE; uint_t print_threads_all = FALSE; size_t tact, tcount, queued, maxq; if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &active, 'm', MDB_OPT_UINTPTR, &minmaxq, 'n', MDB_OPT_STR, &name, 't', MDB_OPT_SETBITS, TRUE, &print_threads, 'T', MDB_OPT_SETBITS, TRUE, &print_threads_all, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { size_t idx; tq_info_t tqi; bzero(&tqi, sizeof (tqi)); if (mdb_walk("taskq_cache", tq_count, &tqi) == -1) { mdb_warn("unable to walk taskq_cache"); return (DCMD_ERR); } tqi.tqi_size += 10; /* slop */ tqi.tqi_array = mdb_zalloc( sizeof (*tqi.tqi_array) * tqi.tqi_size, UM_SLEEP|UM_GC); if (mdb_walk("taskq_cache", (mdb_walk_cb_t)tq_fill, &tqi) == -1) { mdb_warn("unable to walk taskq_cache"); return (DCMD_ERR); } qsort(tqi.tqi_array, tqi.tqi_count, sizeof (*tqi.tqi_array), tqcmp); flags &= ~DCMD_PIPE; flags |= DCMD_LOOP | DCMD_LOOPFIRST | DCMD_ADDRSPEC; for (idx = 0; idx < tqi.tqi_count; idx++) { int ret = taskq(tqi.tqi_array[idx].tq_addr, flags, argc, argv); if (ret != DCMD_OK) return (ret); flags &= ~DCMD_LOOPFIRST; } return (DCMD_OK); } if (DCMD_HDRSPEC(flags) && !(flags & DCMD_PIPE_OUT)) { mdb_printf("%%-?s %-31s %4s/%4s %4s %5s %4s%\n", "ADDR", "NAME", "ACT", "THDS", "Q'ED", "MAXQ", "INST"); } if (mdb_vread(&tq, sizeof (tq), addr) == -1) { mdb_warn("failed to read taskq_t at %p", addr); return (DCMD_ERR); } /* terminate the name, just in case */ tq.tq_name[sizeof (tq.tq_name) - 1] = 0; tact = tq.tq_active; tcount = tq.tq_nthreads; queued = tq.tq_tasks - tq.tq_executed; maxq = tq.tq_maxtasks; if (tq.tq_flags & TASKQ_DYNAMIC) { size_t bsize = tq.tq_nbuckets * sizeof (*tq.tq_buckets); size_t idx; taskq_bucket_t *b = mdb_zalloc(bsize, UM_SLEEP | UM_GC); if (mdb_vread(b, bsize, (uintptr_t)tq.tq_buckets) == -1) { mdb_warn("unable to read buckets for taskq %p", addr); return (DCMD_ERR); } tcount += tq.tq_dnthreads; /* * There are actually (tq.tq_nbuckets + 1) buckets now, * with the + 1 used as the "idle bucket". That never * has nalloc or nbacklog, so ignoring it here. */ for (idx = 0; idx < tq.tq_nbuckets; idx++) { tact += b[idx].tqbucket_nalloc; queued += b[idx].tqbucket_nbacklog; } } /* filter out taskqs that aren't of interest. */ if (name != NULL && strstr(tq.tq_name, name) == NULL) return (DCMD_OK); if (active && tact == 0 && queued == 0) return (DCMD_OK); if (!(tq.tq_flags & TASKQ_DYNAMIC) && maxq < minmaxq) return (DCMD_OK); if (flags & DCMD_PIPE_OUT) { mdb_printf("%#lr\n", addr); return (DCMD_OK); } mdb_printf("%?p %-31s %4d/%4d %4d ", addr, tq.tq_name, tact, tcount, queued); if (tq.tq_flags & TASKQ_DYNAMIC) mdb_printf("%5s ", "-"); else mdb_printf("%5d ", maxq); if (tq.tq_flags & TASKQ_NOINSTANCE) mdb_printf("%4s", "-"); else mdb_printf("%4x", tq.tq_instance); mdb_printf("\n"); if (print_threads || print_threads_all) { int ret; char strbuf[128]; const char *arg = print_threads_all ? "" : "-C \"taskq_thread_wait\""; /* * We can't use mdb_pwalk_dcmd() here, because ::stacks needs * to get the full pipeline. */ mdb_snprintf(strbuf, sizeof (strbuf), "%p::walk taskq_thread | ::stacks -a %s", addr, arg); (void) mdb_inc_indent(4); ret = mdb_eval(strbuf); (void) mdb_dec_indent(4); /* abort, since they could have control-Ced the eval */ if (ret == -1) return (DCMD_ABORT); } return (DCMD_OK); } /* * Dcmd: "taskq_entry" * Dump a taskq_ent_t given its address. */ /*ARGSUSED*/ int taskq_ent(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { taskq_ent_t taskq_ent; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_vread(&taskq_ent, sizeof (taskq_ent_t), addr) == -1) { mdb_warn("failed to read taskq_ent_t at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-?s %-?s %-s%\n", "ENTRY", "ARG", "FUNCTION"); } mdb_printf("%-?p %-?p %a\n", addr, taskq_ent.tqent_arg, taskq_ent.tqent_func); return (DCMD_OK); } /* * Walker: "taskq_entry"; taskq_ent_walk_{init,step} * * Given the address of the (taskq_t) task queue head, walk the queue listing * the address of every taskq_ent_t. On dynamic taskq, enum. backlog. */ struct mdb_tqe_walk_data { taskq_ent_t tq_ent; /* working buffer */ uintptr_t tq_addr; /* taskq ptr, for debug */ uint_t tq_nbuckets; /* from taskq.tq_nbuckets */ int tq_bidx; /* index of next bucket */ uintptr_t tq_bucket; /* next bucket we'll emum. */ uintptr_t tqent_head; /* ptr to list head we're on */ uintptr_t tqent_cur; /* ptr to current list element */ }; int taskq_ent_walk_init(mdb_walk_state_t *wsp) { taskq_t tq; struct mdb_tqe_walk_data *wd; if (wsp->walk_addr == 0) { mdb_warn("start address required (taskq_t)\n"); return (WALK_ERR); } /* * Get our walk state storage. Auto-GC */ wd = mdb_zalloc(sizeof (*wd), UM_SLEEP | UM_GC); wsp->walk_data = wd; /* * Read in taskq head, setup walk data. */ if (mdb_vread((void *)&tq, sizeof (taskq_t), wsp->walk_addr) == -1) { mdb_warn("failed to read taskq_t at %p", wsp->walk_addr); return (WALK_ERR); } wd->tq_addr = wsp->walk_addr; wd->tq_nbuckets = tq.tq_nbuckets; wd->tq_bucket = (uintptr_t)tq.tq_buckets; if (wd->tq_bucket == 0) wd->tq_nbuckets = 0; wd->tq_bidx = -1; /* for tq_task */ return (WALK_NEXT); } int taskq_ent_walk_step(mdb_walk_state_t *wsp) { struct mdb_tqe_walk_data *wd; int status; wd = wsp->walk_data; /* * If done in the current bucket, * move to next bucket's list head. */ while (wd->tqent_cur == wd->tqent_head) { /* Terminate when no more buckets. */ if (wd->tq_bidx == wd->tq_nbuckets) return (WALK_DONE); /* * Setup next list head. First bidx is -1 which * means enumerate in the tq.tq_task list. * Then bidx >= 0 are the taskq buckets. */ if (wd->tq_bidx == -1) { /* enum. in tq_task */ wd->tqent_head = wd->tq_addr + OFFSETOF(taskq_t, tq_task); /* next is bucket zero */ wd->tq_bidx = 0; } else { /* enum in tq_bucket.tqbucket_backlog */ wd->tqent_head = wd->tq_bucket + OFFSETOF(taskq_bucket_t, tqbucket_backlog); /* next bucket */ wd->tq_bucket += sizeof (taskq_bucket_t); wd->tq_bidx++; } /* read the list head, get next pointer */ if (mdb_vread(&wd->tq_ent, sizeof (taskq_ent_t), wd->tqent_head) == -1) { mdb_warn("failed to read taskq_ent_t at %p", wd->tqent_head); wd->tqent_cur = wd->tqent_head; } else { /* Moved to a new list head */ wd->tqent_cur = (uintptr_t)wd->tq_ent.tqent_next; } } wsp->walk_addr = wd->tqent_cur; /* read the entry, do callback */ if (mdb_vread(&wd->tq_ent, sizeof (taskq_ent_t), wd->tqent_cur) == -1) { mdb_warn("failed to read taskq_ent_t at %p", wd->tqent_cur); status = WALK_NEXT; /* finish with this bucket */ wd->tqent_cur = wd->tqent_head; } else { status = wsp->walk_callback(wd->tqent_cur, &wd->tq_ent, wsp->walk_cbdata); /* next entry in this bucket */ wd->tqent_cur = (uintptr_t)wd->tq_ent.tqent_next; } return (status); } /* * Walker: "taskq_thread"; taskq_thread_walk_{init,step,fini} * given a taskq_t, list all of its threads */ typedef struct taskq_thread_info { uintptr_t tti_addr; uintptr_t *tti_tlist; size_t tti_nthreads; size_t tti_idx; kthread_t tti_thread; } taskq_thread_info_t; int taskq_thread_walk_init(mdb_walk_state_t *wsp) { taskq_thread_info_t *tti; taskq_t tq; uintptr_t *tlist; size_t nthreads; tti = wsp->walk_data = mdb_zalloc(sizeof (*tti), UM_SLEEP); tti->tti_addr = wsp->walk_addr; if (wsp->walk_addr != 0 && mdb_vread(&tq, sizeof (tq), wsp->walk_addr) != -1 && !(tq.tq_flags & TASKQ_DYNAMIC)) { nthreads = tq.tq_nthreads; tlist = mdb_alloc(nthreads * sizeof (*tlist), UM_SLEEP); if (tq.tq_nthreads_max == 1) { tlist[0] = (uintptr_t)tq.tq_thread; } else if (mdb_vread(tlist, nthreads * sizeof (*tlist), (uintptr_t)tq.tq_threadlist) == -1) { mdb_warn("unable to read threadlist for taskq_t %p", wsp->walk_addr); mdb_free(tlist, nthreads * sizeof (*tlist)); return (WALK_ERR); } tti->tti_tlist = tlist; tti->tti_nthreads = nthreads; return (WALK_NEXT); } wsp->walk_addr = 0; if (mdb_layered_walk("thread", wsp) == -1) { mdb_warn("can't walk \"thread\""); return (WALK_ERR); } return (0); } int taskq_thread_walk_step(mdb_walk_state_t *wsp) { taskq_thread_info_t *tti = wsp->walk_data; const kthread_t *kt = wsp->walk_layer; taskq_t *tq = (taskq_t *)tti->tti_addr; if (kt == NULL) { uintptr_t addr; if (tti->tti_idx >= tti->tti_nthreads) return (WALK_DONE); addr = tti->tti_tlist[tti->tti_idx]; tti->tti_idx++; if (addr == 0) return (WALK_NEXT); if (mdb_vread(&tti->tti_thread, sizeof (kthread_t), addr) == -1) { mdb_warn("unable to read kthread_t at %p", addr); return (WALK_ERR); } return (wsp->walk_callback(addr, &tti->tti_thread, wsp->walk_cbdata)); } if (kt->t_taskq == NULL) return (WALK_NEXT); if (tq != NULL && kt->t_taskq != tq) return (WALK_NEXT); return (wsp->walk_callback(wsp->walk_addr, kt, wsp->walk_cbdata)); } void taskq_thread_walk_fini(mdb_walk_state_t *wsp) { taskq_thread_info_t *tti = wsp->walk_data; if (tti->tti_nthreads > 0) { mdb_free(tti->tti_tlist, tti->tti_nthreads * sizeof (*tti->tti_tlist)); } mdb_free(tti, sizeof (*tti)); } /* * 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. */ #ifndef _TASKQ_H #define _TASKQ_H #ifdef __cplusplus extern "C" { #endif extern int taskq(uintptr_t, uint_t, int, const mdb_arg_t *); extern void taskq_help(void); extern int taskq_ent(uintptr_t, uint_t, int, const mdb_arg_t *); extern int taskq_ent_walk_init(mdb_walk_state_t *); extern int taskq_ent_walk_step(mdb_walk_state_t *); extern int taskq_thread_walk_init(mdb_walk_state_t *); extern int taskq_thread_walk_step(mdb_walk_state_t *); extern void taskq_thread_walk_fini(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _TASKQ_H */ /* * 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 2013 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2018, Joyent, Inc. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include "thread.h" #ifndef STACK_BIAS #define STACK_BIAS 0 #endif typedef struct thread_walk { kthread_t *tw_thread; uintptr_t tw_last; uint_t tw_inproc; uint_t tw_step; } thread_walk_t; int thread_walk_init(mdb_walk_state_t *wsp) { thread_walk_t *twp = mdb_alloc(sizeof (thread_walk_t), UM_SLEEP); if (wsp->walk_addr == 0) { if (mdb_readvar(&wsp->walk_addr, "allthreads") == -1) { mdb_warn("failed to read 'allthreads'"); mdb_free(twp, sizeof (thread_walk_t)); return (WALK_ERR); } twp->tw_inproc = FALSE; } else { proc_t pr; if (mdb_vread(&pr, sizeof (proc_t), wsp->walk_addr) == -1) { mdb_warn("failed to read proc at %p", wsp->walk_addr); mdb_free(twp, sizeof (thread_walk_t)); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)pr.p_tlist; twp->tw_inproc = TRUE; } twp->tw_thread = mdb_alloc(sizeof (kthread_t), UM_SLEEP); twp->tw_last = wsp->walk_addr; twp->tw_step = FALSE; wsp->walk_data = twp; return (WALK_NEXT); } int thread_walk_step(mdb_walk_state_t *wsp) { thread_walk_t *twp = (thread_walk_t *)wsp->walk_data; int status; if (wsp->walk_addr == 0) return (WALK_DONE); /* Proc has 0 threads or allthreads = 0 */ if (twp->tw_step && wsp->walk_addr == twp->tw_last) return (WALK_DONE); /* We've wrapped around */ if (mdb_vread(twp->tw_thread, sizeof (kthread_t), wsp->walk_addr) == -1) { mdb_warn("failed to read thread at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, twp->tw_thread, wsp->walk_cbdata); if (twp->tw_inproc) wsp->walk_addr = (uintptr_t)twp->tw_thread->t_forw; else wsp->walk_addr = (uintptr_t)twp->tw_thread->t_next; twp->tw_step = TRUE; return (status); } void thread_walk_fini(mdb_walk_state_t *wsp) { thread_walk_t *twp = (thread_walk_t *)wsp->walk_data; mdb_free(twp->tw_thread, sizeof (kthread_t)); mdb_free(twp, sizeof (thread_walk_t)); } int deathrow_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("thread_deathrow", wsp) == -1) { mdb_warn("couldn't walk 'thread_deathrow'"); return (WALK_ERR); } if (mdb_layered_walk("lwp_deathrow", wsp) == -1) { mdb_warn("couldn't walk 'lwp_deathrow'"); return (WALK_ERR); } return (WALK_NEXT); } int deathrow_walk_step(mdb_walk_state_t *wsp) { kthread_t t; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&t, sizeof (t), addr) == -1) { mdb_warn("couldn't read deathrow thread at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)t.t_forw; return (wsp->walk_callback(addr, &t, wsp->walk_cbdata)); } int thread_deathrow_walk_init(mdb_walk_state_t *wsp) { if (mdb_readvar(&wsp->walk_addr, "thread_deathrow") == -1) { mdb_warn("couldn't read symbol 'thread_deathrow'"); return (WALK_ERR); } return (WALK_NEXT); } int lwp_deathrow_walk_init(mdb_walk_state_t *wsp) { if (mdb_readvar(&wsp->walk_addr, "lwp_deathrow") == -1) { mdb_warn("couldn't read symbol 'lwp_deathrow'"); return (WALK_ERR); } return (WALK_NEXT); } typedef struct dispq_walk { int dw_npri; uintptr_t dw_dispq; uintptr_t dw_last; } dispq_walk_t; int cpu_dispq_walk_init(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; dispq_walk_t *dw; cpu_t cpu; dispq_t dispq; disp_t disp; if (addr == 0) { mdb_warn("cpu_dispq walk needs a cpu_t address\n"); return (WALK_ERR); } if (mdb_vread(&cpu, sizeof (cpu_t), addr) == -1) { mdb_warn("failed to read cpu_t at %p", addr); return (WALK_ERR); } if (mdb_vread(&disp, sizeof (disp_t), (uintptr_t)cpu.cpu_disp) == -1) { mdb_warn("failed to read disp_t at %p", cpu.cpu_disp); return (WALK_ERR); } if (mdb_vread(&dispq, sizeof (dispq_t), (uintptr_t)disp.disp_q) == -1) { mdb_warn("failed to read dispq_t at %p", disp.disp_q); return (WALK_ERR); } dw = mdb_alloc(sizeof (dispq_walk_t), UM_SLEEP); dw->dw_npri = disp.disp_npri; dw->dw_dispq = (uintptr_t)disp.disp_q; dw->dw_last = (uintptr_t)dispq.dq_last; wsp->walk_addr = (uintptr_t)dispq.dq_first; wsp->walk_data = dw; return (WALK_NEXT); } int cpupart_dispq_walk_init(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; dispq_walk_t *dw; cpupart_t cpupart; dispq_t dispq; if (addr == 0) { mdb_warn("cpupart_dispq walk needs a cpupart_t address\n"); return (WALK_ERR); } if (mdb_vread(&cpupart, sizeof (cpupart_t), addr) == -1) { mdb_warn("failed to read cpupart_t at %p", addr); return (WALK_ERR); } if (mdb_vread(&dispq, sizeof (dispq_t), (uintptr_t)cpupart.cp_kp_queue.disp_q) == -1) { mdb_warn("failed to read dispq_t at %p", cpupart.cp_kp_queue.disp_q); return (WALK_ERR); } dw = mdb_alloc(sizeof (dispq_walk_t), UM_SLEEP); dw->dw_npri = cpupart.cp_kp_queue.disp_npri; dw->dw_dispq = (uintptr_t)cpupart.cp_kp_queue.disp_q; dw->dw_last = (uintptr_t)dispq.dq_last; wsp->walk_addr = (uintptr_t)dispq.dq_first; wsp->walk_data = dw; return (WALK_NEXT); } int dispq_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; dispq_walk_t *dw = wsp->walk_data; dispq_t dispq; kthread_t t; while (addr == 0) { if (--dw->dw_npri == 0) return (WALK_DONE); dw->dw_dispq += sizeof (dispq_t); if (mdb_vread(&dispq, sizeof (dispq_t), dw->dw_dispq) == -1) { mdb_warn("failed to read dispq_t at %p", dw->dw_dispq); return (WALK_ERR); } dw->dw_last = (uintptr_t)dispq.dq_last; addr = (uintptr_t)dispq.dq_first; } if (mdb_vread(&t, sizeof (kthread_t), addr) == -1) { mdb_warn("failed to read kthread_t at %p", addr); return (WALK_ERR); } if (addr == dw->dw_last) wsp->walk_addr = 0; else wsp->walk_addr = (uintptr_t)t.t_link; return (wsp->walk_callback(addr, &t, wsp->walk_cbdata)); } void dispq_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (dispq_walk_t)); } struct thread_state { uint_t ts_state; const char *ts_name; } thread_states[] = { { TS_FREE, "free" }, { TS_SLEEP, "sleep" }, { TS_RUN, "run" }, { TS_ONPROC, "onproc" }, { TS_ZOMB, "zomb" }, { TS_STOPPED, "stopped" }, { TS_WAIT, "wait" } }; #define NUM_THREAD_STATES (sizeof (thread_states) / sizeof (*thread_states)) void thread_state_to_text(uint_t state, char *out, size_t out_sz) { int idx; for (idx = 0; idx < NUM_THREAD_STATES; idx++) { struct thread_state *tsp = &thread_states[idx]; if (tsp->ts_state == state) { mdb_snprintf(out, out_sz, "%s", tsp->ts_name); return; } } mdb_snprintf(out, out_sz, "inval/%02x", state); } int thread_text_to_state(const char *state, uint_t *out) { int idx; for (idx = 0; idx < NUM_THREAD_STATES; idx++) { struct thread_state *tsp = &thread_states[idx]; if (strcasecmp(tsp->ts_name, state) == 0) { *out = tsp->ts_state; return (0); } } return (-1); } void thread_walk_states(void (*cbfunc)(uint_t, const char *, void *), void *cbarg) { int idx; for (idx = 0; idx < NUM_THREAD_STATES; idx++) { struct thread_state *tsp = &thread_states[idx]; cbfunc(tsp->ts_state, tsp->ts_name, cbarg); } } #define TF_INTR 0x01 #define TF_PROC 0x02 #define TF_BLOCK 0x04 #define TF_SIG 0x08 #define TF_DISP 0x10 #define TF_MERGE 0x20 /* * Display a kthread_t. * This is a little complicated, as there is a lot of information that * the user could be interested in. The flags "ipbsd" are used to * indicate which subset of the thread's members are to be displayed * ('i' is the default). If multiple options are specified, multiple * sets of data will be displayed in a vaguely readable format. If the * 'm' option is specified, all the selected sets will be merged onto a * single line for the benefit of those using wider-than-normal * terminals. Having a generic mechanism for doing this would be * really useful, but is a project best left to another day. */ int thread(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kthread_t t; uint_t oflags = 0; uint_t fflag = FALSE; int first; char stbuf[20]; /* * "Gracefully" handle printing a boatload of stuff to the * screen. If we are not printing our first set of data, and * we haven't been instructed to merge sets together, output a * newline and indent such that the thread addresses form a * column of their own. */ #define SPACER() \ if (first) { \ first = FALSE; \ } else if (!(oflags & TF_MERGE)) { \ mdb_printf("\n%?s", ""); \ } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("thread", "thread", argc, argv) == -1) { mdb_warn("can't walk threads"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'f', MDB_OPT_SETBITS, TRUE, &fflag, 'i', MDB_OPT_SETBITS, TF_INTR, &oflags, 'p', MDB_OPT_SETBITS, TF_PROC, &oflags, 'b', MDB_OPT_SETBITS, TF_BLOCK, &oflags, 's', MDB_OPT_SETBITS, TF_SIG, &oflags, 'd', MDB_OPT_SETBITS, TF_DISP, &oflags, 'm', MDB_OPT_SETBITS, TF_MERGE, &oflags, NULL) != argc) return (DCMD_USAGE); /* * If no sets were specified, choose the 'i' set. */ if (!(oflags & ~TF_MERGE)) #ifdef _LP64 oflags = TF_INTR; #else oflags = TF_INTR | TF_DISP | TF_MERGE; #endif /* * Print the relevant headers; note use of SPACER(). */ if (DCMD_HDRSPEC(flags)) { first = TRUE; mdb_printf("%%?s%", "ADDR"); mdb_flush(); if (oflags & TF_PROC) { SPACER(); mdb_printf("% %?s %?s %?s%", "PROC", "LWP", "CRED"); } if (oflags & TF_INTR) { SPACER(); mdb_printf("% %8s %4s %4s %4s %5s %5s %3s %?s%", "STATE", "FLG", "PFLG", "SFLG", "PRI", "EPRI", "PIL", "INTR"); } if (oflags & TF_BLOCK) { SPACER(); mdb_printf("% %?s %?s %?s %11s%", "WCHAN", "TS", "PITS", "SOBJ OPS"); } if (oflags & TF_SIG) { SPACER(); mdb_printf("% %?s %16s %16s%", "SIGQUEUE", "SIG PEND", "SIG HELD"); } if (oflags & TF_DISP) { SPACER(); mdb_printf("% %?s %5s %2s %-6s%", "DISPTIME", "BOUND", "PR", "SWITCH"); } mdb_printf("\n"); } if (mdb_vread(&t, sizeof (kthread_t), addr) == -1) { mdb_warn("can't read kthread_t at %#lx", addr); return (DCMD_ERR); } if (fflag && (t.t_state == TS_FREE)) return (DCMD_OK); first = TRUE; mdb_printf("%0?lx", addr); /* process information */ if (oflags & TF_PROC) { SPACER(); mdb_printf(" %?p %?p %?p", t.t_procp, t.t_lwp, t.t_cred); } /* priority/interrupt information */ if (oflags & TF_INTR) { SPACER(); thread_state_to_text(t.t_state, stbuf, sizeof (stbuf)); if (t.t_intr == NULL) { mdb_printf(" %-8s %4x %4x %4x %5d %5d %3d %?s", stbuf, t.t_flag, t.t_proc_flag, t.t_schedflag, t.t_pri, t.t_epri, t.t_pil, "n/a"); } else { mdb_printf(" %-8s %4x %4x %4x %5d %5d %3d %?p", stbuf, t.t_flag, t.t_proc_flag, t.t_schedflag, t.t_pri, t.t_epri, t.t_pil, t.t_intr); } } /* blocking information */ if (oflags & TF_BLOCK) { SPACER(); (void) mdb_snprintf(stbuf, 20, "%a", t.t_sobj_ops); stbuf[11] = '\0'; mdb_printf(" %?p %?p %?p %11s", t.t_wchan, t.t_ts, t.t_prioinv, stbuf); } /* signal information */ if (oflags & TF_SIG) { SPACER(); mdb_printf(" %?p %016llx %016llx", t.t_sigqueue, t.t_sig, t.t_hold); } /* dispatcher stuff */ if (oflags & TF_DISP) { SPACER(); mdb_printf(" %?lx %5d %2d ", t.t_disp_time, t.t_bind_cpu, t.t_preempt); if (t.t_disp_time != 0) mdb_printf("t-%-4d", (clock_t)mdb_get_lbolt() - t.t_disp_time); else mdb_printf("%-6s", "-"); } mdb_printf("\n"); #undef SPACER return (DCMD_OK); } void thread_help(void) { mdb_printf( "The flags -ipbsd control which information is displayed. When\n" "combined, the fields are displayed on separate lines unless the\n" "-m option is given.\n" "\n" "\t-b\tprint blocked thread state\n" "\t-d\tprint dispatcher state\n" "\t-f\tignore freed threads\n" "\t-i\tprint basic thread state (default)\n" "\t-m\tdisplay results on a single line\n" "\t-p\tprint process and lwp state\n" "\t-s\tprint signal state\n"); } /* * Return a string description of the thread, including the ID and the thread * name. * * If ->t_name is NULL, and we're a system thread, we'll do a little more * spelunking to find a useful string to return. */ int thread_getdesc(uintptr_t addr, boolean_t include_comm, char *buf, size_t bufsize) { char name[THREAD_NAME_MAX] = ""; kthread_t t; proc_t p; bzero(buf, bufsize); if (mdb_vread(&t, sizeof (kthread_t), addr) == -1) { mdb_warn("failed to read kthread_t at %p", addr); return (-1); } if (t.t_tid == 0) { taskq_t tq; if (mdb_vread(&tq, sizeof (taskq_t), (uintptr_t)t.t_taskq) == -1) tq.tq_name[0] = '\0'; if (t.t_name != NULL) { if (mdb_readstr(buf, bufsize, (uintptr_t)t.t_name) == -1) { mdb_warn("error reading thread name"); } } else if (tq.tq_name[0] != '\0') { (void) mdb_snprintf(buf, bufsize, "tq:%s", tq.tq_name); } else { mdb_snprintf(buf, bufsize, "%a()", t.t_startpc); } return (buf[0] == '\0' ? -1 : 0); } if (include_comm && mdb_vread(&p, sizeof (proc_t), (uintptr_t)t.t_procp) == -1) { mdb_warn("failed to read proc at %p", t.t_procp); return (-1); } if (t.t_name != NULL) { if (mdb_readstr(name, sizeof (name), (uintptr_t)t.t_name) == -1) mdb_warn("error reading thread name"); /* * Just to be safe -- if mdb_readstr() succeeds, it always NUL * terminates the output, but is unclear what it does on * failure. In that case we attempt to show any partial content * w/ the warning in case it's useful, but explicitly * NUL-terminate to be safe. */ buf[bufsize - 1] = '\0'; } if (name[0] != '\0') { if (include_comm) { (void) mdb_snprintf(buf, bufsize, "%s/%u [%s]", p.p_user.u_comm, t.t_tid, name); } else { (void) mdb_snprintf(buf, bufsize, "%u [%s]", t.t_tid, name); } } else { if (include_comm) { (void) mdb_snprintf(buf, bufsize, "%s/%u", p.p_user.u_comm, t.t_tid); } else { (void) mdb_snprintf(buf, bufsize, "%u", t.t_tid); } } return (buf[0] == '\0' ? -1 : 0); } /* * List a combination of kthread_t and proc_t. Add stack traces in verbose mode. */ int threadlist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; uint_t count = 0; uint_t verbose = FALSE; uint_t notaskq = FALSE; kthread_t t; char cmd[80]; mdb_arg_t cmdarg; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("thread", "threadlist", argc, argv) == -1) { mdb_warn("can't walk threads"); return (DCMD_ERR); } return (DCMD_OK); } i = mdb_getopts(argc, argv, 't', MDB_OPT_SETBITS, TRUE, ¬askq, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL); if (i != argc) { if (i != argc - 1 || !verbose) return (DCMD_USAGE); count = (uint_t)mdb_argtoull(&argv[i]); } if (DCMD_HDRSPEC(flags)) { if (verbose) mdb_printf("%%?s %?s %?s %3s %3s %?s%\n", "ADDR", "PROC", "LWP", "CLS", "PRI", "WCHAN"); else mdb_printf("%%?s %?s %?s %s/%s%\n", "ADDR", "PROC", "LWP", "CMD", "LWPID"); } if (mdb_vread(&t, sizeof (kthread_t), addr) == -1) { mdb_warn("failed to read kthread_t at %p", addr); return (DCMD_ERR); } if (notaskq && t.t_taskq != NULL) return (DCMD_OK); if (t.t_state == TS_FREE) return (DCMD_OK); if (!verbose) { char desc[128]; if (thread_getdesc(addr, B_TRUE, desc, sizeof (desc)) == -1) return (DCMD_ERR); mdb_printf("%0?p %?p %?p %s\n", addr, t.t_procp, t.t_lwp, desc); return (DCMD_OK); } mdb_printf("%0?p %?p %?p %3u %3d %?p\n", addr, t.t_procp, t.t_lwp, t.t_cid, t.t_pri, t.t_wchan); mdb_inc_indent(2); mdb_printf("PC: %a\n", t.t_pc); mdb_snprintf(cmd, sizeof (cmd), "<.$c%d", count); cmdarg.a_type = MDB_TYPE_STRING; cmdarg.a_un.a_str = cmd; (void) mdb_call_dcmd("findstack", addr, flags, 1, &cmdarg); mdb_dec_indent(2); mdb_printf("\n"); return (DCMD_OK); } void threadlist_help(void) { mdb_printf( " -v print verbose output including C stack trace\n" " -t skip threads belonging to a taskq\n" " count print no more than count arguments (default 0)\n"); } static size_t stk_compute_percent(caddr_t t_stk, caddr_t t_stkbase, caddr_t sp) { size_t percent; size_t s; if (t_stk > t_stkbase) { /* stack grows down */ if (sp > t_stk) { return (0); } if (sp < t_stkbase) { return (100); } percent = t_stk - sp + 1; s = t_stk - t_stkbase + 1; } else { /* stack grows up */ if (sp < t_stk) { return (0); } if (sp > t_stkbase) { return (100); } percent = sp - t_stk + 1; s = t_stkbase - t_stk + 1; } percent = ((100 * percent) / s) + 1; if (percent > 100) { percent = 100; } return (percent); } /* * Display kthread stack infos. */ int stackinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kthread_t t; uint64_t *ptr; /* pattern pointer */ caddr_t start; /* kernel stack start */ caddr_t end; /* kernel stack end */ caddr_t ustack; /* userland copy of kernel stack */ size_t usize; /* userland copy of kernel stack size */ caddr_t ustart; /* userland copy of kernel stack, aligned start */ caddr_t uend; /* userland copy of kernel stack, aligned end */ size_t percent = 0; uint_t all = FALSE; /* don't show TS_FREE kthread by default */ uint_t history = FALSE; int i = 0; unsigned int ukmem_stackinfo; uintptr_t allthreads; char tdesc[128] = ""; /* handle options */ if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &all, 'h', MDB_OPT_SETBITS, TRUE, &history, NULL) != argc) { return (DCMD_USAGE); } /* walk all kthread if needed */ if ((history == FALSE) && !(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("thread", "stackinfo", argc, argv) == -1) { mdb_warn("can't walk threads"); return (DCMD_ERR); } return (DCMD_OK); } /* read 'kmem_stackinfo' */ if (mdb_readsym(&ukmem_stackinfo, sizeof (ukmem_stackinfo), "kmem_stackinfo") == -1) { mdb_warn("failed to read 'kmem_stackinfo'\n"); ukmem_stackinfo = 0; } /* read 'allthreads' */ if (mdb_readsym(&allthreads, sizeof (kthread_t *), "allthreads") == -1) { mdb_warn("failed to read 'allthreads'\n"); allthreads = 0; } if (history == TRUE) { kmem_stkinfo_t *log; uintptr_t kaddr; mdb_printf("Dead kthreads stack usage history:\n"); if (ukmem_stackinfo == 0) { mdb_printf("Tunable kmem_stackinfo is unset, history "); mdb_printf("feature is off.\nUse ::help stackinfo "); mdb_printf("for more details.\n"); return (DCMD_OK); } mdb_printf("%%?s%", "THREAD"); mdb_printf(" %%?s%", "STACK"); mdb_printf("%%s%", " SIZE MAX LWP"); mdb_printf("\n"); usize = KMEM_STKINFO_LOG_SIZE * sizeof (kmem_stkinfo_t); log = (kmem_stkinfo_t *)mdb_alloc(usize, UM_SLEEP); if (mdb_readsym(&kaddr, sizeof (kaddr), "kmem_stkinfo_log") == -1) { mdb_free((void *)log, usize); mdb_warn("failed to read 'kmem_stkinfo_log'\n"); return (DCMD_ERR); } if (kaddr == 0) { mdb_free((void *)log, usize); return (DCMD_OK); } if (mdb_vread(log, usize, kaddr) == -1) { mdb_free((void *)log, usize); mdb_warn("failed to read %p\n", kaddr); return (DCMD_ERR); } for (i = 0; i < KMEM_STKINFO_LOG_SIZE; i++) { if (log[i].kthread == NULL) { continue; } (void) thread_getdesc((uintptr_t)log[i].kthread, B_TRUE, tdesc, sizeof (tdesc)); mdb_printf("%0?p %0?p %6x %3d%% %s\n", log[i].kthread, log[i].start, (uint_t)log[i].stksz, (int)log[i].percent, tdesc); } mdb_free((void *)log, usize); return (DCMD_OK); } /* display header */ if (DCMD_HDRSPEC(flags)) { if (ukmem_stackinfo == 0) { mdb_printf("Tunable kmem_stackinfo is unset, "); mdb_printf("MAX value is not available.\n"); mdb_printf("Use ::help stackinfo for more details.\n"); } mdb_printf("%%?s%", "THREAD"); mdb_printf(" %%?s%", "STACK"); mdb_printf("%%s%", " SIZE CUR MAX LWP"); mdb_printf("\n"); } /* read kthread */ if (mdb_vread(&t, sizeof (kthread_t), addr) == -1) { mdb_warn("can't read kthread_t at %#lx\n", addr); return (DCMD_ERR); } if (t.t_state == TS_FREE && all == FALSE) { return (DCMD_OK); } /* * Stack grows up or down, see thread_create(), * compute stack memory aera start and end (start < end). */ if (t.t_stk > t.t_stkbase) { /* stack grows down */ start = t.t_stkbase; end = t.t_stk; } else { /* stack grows up */ start = t.t_stk; end = t.t_stkbase; } /* display stack info */ mdb_printf("%0?p %0?p", addr, start); /* (end - start), kernel stack size as found in kthread_t */ if ((end <= start) || ((end - start) > (1024 * 1024))) { /* negative or stack size > 1 meg, assume bogus */ mdb_warn(" t_stk/t_stkbase problem\n"); return (DCMD_ERR); } /* display stack size */ mdb_printf(" %6x", end - start); /* display current stack usage */ percent = stk_compute_percent(t.t_stk, t.t_stkbase, (caddr_t)t.t_sp + STACK_BIAS); mdb_printf(" %3d%%", percent); percent = 0; (void) thread_getdesc(addr, B_TRUE, tdesc, sizeof (tdesc)); if (ukmem_stackinfo == 0) { mdb_printf(" n/a %s\n", tdesc); return (DCMD_OK); } if ((((uintptr_t)start) & 0x7) != 0) { start = (caddr_t)((((uintptr_t)start) & (~0x7)) + 8); } end = (caddr_t)(((uintptr_t)end) & (~0x7)); /* size to scan in userland copy of kernel stack */ usize = end - start; /* is a multiple of 8 bytes */ /* * Stackinfo pattern size is 8 bytes. Ensure proper 8 bytes * alignement for ustart and uend, in boundaries. */ ustart = ustack = (caddr_t)mdb_alloc(usize + 8, UM_SLEEP); if ((((uintptr_t)ustart) & 0x7) != 0) { ustart = (caddr_t)((((uintptr_t)ustart) & (~0x7)) + 8); } uend = ustart + usize; /* read the kernel stack */ if (mdb_vread(ustart, usize, (uintptr_t)start) != usize) { mdb_free((void *)ustack, usize + 8); mdb_printf("\n"); mdb_warn("couldn't read entire stack\n"); return (DCMD_ERR); } /* scan the stack */ if (t.t_stk > t.t_stkbase) { /* stack grows down */ #if defined(__i386) || defined(__amd64) /* * 6 longs are pushed on stack, see thread_load(). Skip * them, so if kthread has never run, percent is zero. * 8 bytes alignement is preserved for a 32 bit kernel, * 6 x 4 = 24, 24 is a multiple of 8. */ uend -= (6 * sizeof (long)); #endif ptr = (uint64_t *)((void *)ustart); while (ptr < (uint64_t *)((void *)uend)) { if (*ptr != KMEM_STKINFO_PATTERN) { percent = stk_compute_percent(uend, ustart, (caddr_t)ptr); break; } ptr++; } } else { /* stack grows up */ ptr = (uint64_t *)((void *)uend); ptr--; while (ptr >= (uint64_t *)((void *)ustart)) { if (*ptr != KMEM_STKINFO_PATTERN) { percent = stk_compute_percent(ustart, uend, (caddr_t)ptr); break; } ptr--; } } /* thread 't0' stack is not created by thread_create() */ if (addr == allthreads) { percent = 0; } if (percent != 0) { mdb_printf(" %3d%%", percent); } else { mdb_printf(" n/a"); } mdb_printf(" %s\n", tdesc); mdb_free((void *)ustack, usize + 8); return (DCMD_OK); } void stackinfo_help(void) { mdb_printf( "Shows kernel stacks real utilization, if /etc/system " "kmem_stackinfo tunable\n"); mdb_printf( "(an unsigned integer) is non zero at kthread creation time. "); mdb_printf("For example:\n"); mdb_printf( " THREAD STACK SIZE CUR MAX LWP\n"); mdb_printf( "ffffff014f5f2c20 ffffff0004153000 4f00 4%% 43%% init/1\n"); mdb_printf( "The stack size utilization for this kthread is at 4%%" " of its maximum size,\n"); mdb_printf( "but has already used up to 43%%, stack size is 4f00 bytes.\n"); mdb_printf( "MAX value can be shown as n/a (not available):\n"); mdb_printf( " - for the very first kthread (sched/1)\n"); mdb_printf( " - kmem_stackinfo was zero at kthread creation time\n"); mdb_printf( " - kthread has not yet run\n"); mdb_printf("\n"); mdb_printf("Options:\n"); mdb_printf( "-a shows also TS_FREE kthreads (interrupt kthreads)\n"); mdb_printf( "-h shows history, dead kthreads that used their " "kernel stack the most\n"); mdb_printf( "\nSee illumos Modular Debugger Guide for detailed usage.\n"); mdb_flush(); } /* * 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) 2018, Joyent, Inc. */ #ifndef _THREAD_H #define _THREAD_H #include #ifdef __cplusplus extern "C" { #endif int thread_walk_init(mdb_walk_state_t *); int thread_walk_step(mdb_walk_state_t *); void thread_walk_fini(mdb_walk_state_t *); int deathrow_walk_init(mdb_walk_state_t *); int deathrow_walk_step(mdb_walk_state_t *); int thread_deathrow_walk_init(mdb_walk_state_t *); int lwp_deathrow_walk_init(mdb_walk_state_t *); int cpu_dispq_walk_init(mdb_walk_state_t *); int cpupart_dispq_walk_init(mdb_walk_state_t *); int dispq_walk_step(mdb_walk_state_t *); void dispq_walk_fini(mdb_walk_state_t *); int thread(uintptr_t, uint_t, int, const mdb_arg_t *); void thread_help(void); int threadlist(uintptr_t, uint_t, int, const mdb_arg_t *); void threadlist_help(void); int stackinfo(uintptr_t, uint_t, int, const mdb_arg_t *); void stackinfo_help(void); void thread_state_to_text(uint_t, char *, size_t); int thread_text_to_state(const char *, uint_t *); void thread_walk_states(void (*)(uint_t, const char *, void *), void *); int thread_getdesc(uintptr_t, boolean_t, char *, size_t); #ifdef __cplusplus } #endif #endif /* _THREAD_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ /* * Copyright (c) 2012 by Delphix. All rights reserved. */ #include #include "tsd.h" /* * Initialize the tsd walker by either using the given starting address, * or reading the value of the kernel's tsd_list pointer. */ int tsd_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "tsd_list") == -1) { mdb_warn("failed to read 'tsd_list'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (struct tsd_thread), UM_SLEEP); return (WALK_NEXT); } int tsd_walk_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct tsd_thread), wsp->walk_addr) == -1) { mdb_warn("failed to read tsd at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct tsd_thread *)wsp->walk_data)->ts_next); return (status); } void tsd_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct tsd_thread)); } /* * Map from thread pointer to tsd pointer for given key */ int ttotsd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kthread_t thread, *t = &thread; struct tsd_thread tsdata, *ts = &tsdata; uintptr_t key = 0; uintptr_t eladdr; void *element = NULL; if (mdb_getopts(argc, argv, 'k', MDB_OPT_UINTPTR, &key, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC) || key == 0) return (DCMD_USAGE); if (mdb_vread(t, sizeof (*t), addr) == -1) { mdb_warn("failed to read thread at %p", addr); return (DCMD_ERR); } if (t->t_tsd == NULL) goto out; if (mdb_vread(ts, sizeof (*ts), (uintptr_t)t->t_tsd) == -1) { mdb_warn("failed to read tsd at %p", t->t_tsd); return (DCMD_ERR); } if (key > ts->ts_nkeys) goto out; eladdr = (uintptr_t)(ts->ts_value + key - 1); if (mdb_vread(&element, sizeof (element), eladdr) == -1) { mdb_warn("failed to read t->t_tsd[%d] at %p", key - 1, eladdr); return (DCMD_ERR); } out: if (element == NULL && (flags & DCMD_PIPE)) return (DCMD_OK); mdb_printf("%p\n", element); return (DCMD_OK); } static int tsdthr_match(uintptr_t addr, const kthread_t *t, uintptr_t tsdaddr) { /* * Allow for multiple matches, even though that "can't happen." */ if (tsdaddr == (uintptr_t)t->t_tsd) mdb_printf("%p\n", addr); return (WALK_NEXT); } /* * Given a tsd pointer, find the owning thread */ /*ARGSUSED*/ int tsdtot(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (addr == 0 || argc != 0) return (DCMD_USAGE); if (mdb_walk("thread", (mdb_walk_cb_t)(uintptr_t)tsdthr_match, (void *)addr) == -1) return (DCMD_ERR); return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2000 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MDB_TSD_H #define _MDB_TSD_H #include #ifdef __cplusplus extern "C" { #endif extern int tsd_walk_init(mdb_walk_state_t *); extern int tsd_walk_step(mdb_walk_state_t *); extern void tsd_walk_fini(mdb_walk_state_t *); extern int tsdtot(uintptr_t, uint_t, int, const mdb_arg_t *); extern int ttotsd(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _MDB_TSD_H */ /* * 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. */ #include #include #include #include #include #include "tsol.h" #include "modhash.h" /* ****************** tnrh ****************** */ typedef struct tnrh_walk_s { tnrhc_hash_t **hptr; int idx; tnrhc_hash_t *tnrhc_table[TSOL_MASK_TABLE_SIZE]; tnrhc_hash_t *tnrhc_table_v6[TSOL_MASK_TABLE_SIZE_V6]; } tnrh_walk_t; /* * Free the mdb storage pointed to by the given per-prefix table. */ static void free_table(tnrhc_hash_t **table, int ntable) { while (--ntable >= 0) { if (*table != NULL) mdb_free(*table, TNRHC_SIZE * sizeof (**table)); table++; } } /* * Read in a list of per-prefix-length hash tables. Allocate storage for the * hashes that are present. On successful return, the table will contain * pointers to mdb-resident storage, not kernel addresses. On failure, the * contents will not point to any mdb storage. */ static int read_table(const char *symname, tnrhc_hash_t **table, int ntable) { GElf_Sym tnrhc_hash; tnrhc_hash_t **hp; uintptr_t addr; if (mdb_lookup_by_name(symname, &tnrhc_hash) == -1) { mdb_warn("failed to read %s", symname); return (-1); } if (mdb_vread(table, ntable * sizeof (*table), tnrhc_hash.st_value) == -1) { mdb_warn("can't read %s at %p", symname, tnrhc_hash.st_value); return (-1); } for (hp = table; hp < table + ntable; hp++) { if ((addr = (uintptr_t)*hp) != 0) { *hp = mdb_alloc(TNRHC_SIZE * sizeof (**hp), UM_SLEEP); if (mdb_vread(*hp, TNRHC_SIZE * sizeof (**hp), addr) == -1) { mdb_warn("can't read %s[%d] at %p", symname, hp - table, addr); free_table(table, (hp - table) + 1); return (-1); } } } return (0); } int tnrh_walk_init(mdb_walk_state_t *wsp) { tnrh_walk_t *twp; twp = mdb_alloc(sizeof (*twp), UM_SLEEP); if (read_table("tnrhc_table", twp->tnrhc_table, TSOL_MASK_TABLE_SIZE) == -1) { mdb_free(twp, sizeof (*twp)); return (WALK_ERR); } if (read_table("tnrhc_table_v6", twp->tnrhc_table_v6, TSOL_MASK_TABLE_SIZE_V6) == -1) { free_table(twp->tnrhc_table, TSOL_MASK_TABLE_SIZE); mdb_free(twp, sizeof (*twp)); return (WALK_ERR); } twp->hptr = twp->tnrhc_table; twp->idx = 0; wsp->walk_addr = 0; wsp->walk_data = twp; return (WALK_NEXT); } int tnrh_walk_step(mdb_walk_state_t *wsp) { tnrh_walk_t *twp = wsp->walk_data; tsol_tnrhc_t tnrhc; int status; while (wsp->walk_addr == 0) { if (*twp->hptr == NULL || twp->idx >= TNRHC_SIZE) { twp->hptr++; if (twp->hptr == twp->tnrhc_table + TSOL_MASK_TABLE_SIZE) twp->hptr = twp->tnrhc_table_v6; else if (twp->hptr == twp->tnrhc_table_v6 + TSOL_MASK_TABLE_SIZE_V6) return (WALK_DONE); twp->idx = 0; } else { wsp->walk_addr = (uintptr_t)(*twp->hptr)[twp->idx++]. tnrh_list; } } if (mdb_vread(&tnrhc, sizeof (tnrhc), wsp->walk_addr) == -1) { mdb_warn("can't read tsol_tnrhc_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &tnrhc, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)tnrhc.rhc_next; return (status); } void tnrh_walk_fini(mdb_walk_state_t *wsp) { tnrh_walk_t *twp = wsp->walk_data; free_table(twp->tnrhc_table, TSOL_MASK_TABLE_SIZE); free_table(twp->tnrhc_table_v6, TSOL_MASK_TABLE_SIZE_V6); mdb_free(twp, sizeof (*twp)); } /* ****************** tnrhtp ****************** */ typedef struct tnrhtp_walk_data_s { int (*old_callback)(uintptr_t, const void *, void *); void *old_cbdata; } tnrhtp_walk_data_t; /* ARGSUSED */ static int tnrhtp_walk_callback(uintptr_t addr, const void *data, void *private) { const struct mod_hash_entry *mhe = data; tnrhtp_walk_data_t *twd = private; tsol_tpc_t tpc; if (mdb_vread(&tpc, sizeof (tpc), (uintptr_t)mhe->mhe_val) == -1) { mdb_warn("failed to read tsol_tpc_t at %p", mhe->mhe_val); return (WALK_ERR); } else { return (twd->old_callback((uintptr_t)mhe->mhe_val, &tpc, twd->old_cbdata)); } } int tnrhtp_walk_init(mdb_walk_state_t *wsp) { mod_hash_t *tpc_name_hash; if (mdb_readvar(&tpc_name_hash, "tpc_name_hash") == -1) { mdb_warn("failed to read tpc_name_hash"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)tpc_name_hash; return (modent_walk_init(wsp)); } int tnrhtp_walk_step(mdb_walk_state_t *wsp) { tnrhtp_walk_data_t twd; int retv; twd.old_callback = wsp->walk_callback; twd.old_cbdata = wsp->walk_cbdata; wsp->walk_callback = tnrhtp_walk_callback; wsp->walk_cbdata = &twd; retv = modent_walk_step(wsp); wsp->walk_callback = twd.old_callback; wsp->walk_cbdata = twd.old_cbdata; return (retv); } void tnrhtp_walk_fini(mdb_walk_state_t *wsp) { modent_walk_fini(wsp); } /* * 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. */ #ifndef _TSOL_H #define _TSOL_H #ifdef __cplusplus extern "C" { #endif extern int tnrh_walk_init(mdb_walk_state_t *); extern int tnrh_walk_step(mdb_walk_state_t *); extern void tnrh_walk_fini(mdb_walk_state_t *); extern int tnrhtp_walk_init(mdb_walk_state_t *); extern int tnrhtp_walk_step(mdb_walk_state_t *); extern void tnrhtp_walk_fini(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _TSOL_H */ /* * 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 2025 Oxide Computer Company */ /* * Postmortem type identification * ------------------------------ * * When debugging kernel memory corruption problems, one often determines that * the corrupted buffer has been erroneously written to by a user of an * adjacent buffer -- determining the specifics of the adjacent buffer can * therefore offer insight into the cause of the corruption. To determine the * type of an arbitrary memory buffer, however, one has historically been * forced to use dcmds ::kgrep and ::whatis in alternating succession; when an * object of known type is finally reached, types can be back-propagated to * determine the type of the unknown object. * * This process is labor-intensive and error-prone. Using CTF data and a * collection of heuristics, we would like to both automate this process and * improve on it. * * We start by constructing the pointer graph. Each node in the graph is * a memory object (either a static object from module data, or a dynamically * allocated memory object); the node's outgoing edges represent pointers from * the object to other memory objects in the system. * * Once the graph is constructed, we start at nodes of known type, and use the * type information to determine the type of each pointer represented by an * outgoing edge. Determining the pointer type allows us to determine the * type of the edge's destination node, and therefore to iteratively continue * the process of type identification. This process works as long as all * pointed-to objects are exactly the size of their inferred types. * * Unfortunately, pointed-to objects are often _not_ the size of the pointed-to * type. This is largely due to three phenomena: * * (a) C makes no distinction between a pointer to a single object and a * pointer to some number of objects of like type. * * (b) C performs no bounds checking on array indexing, allowing declarations * of structures that are implicitly followed by arrays of the type of the * structure's last member. These declarations most often look like: * * typedef struct foo { * int foo_bar; * int foo_baz; * mumble_t foo_mumble[1]; * } foo_t; * * When a foo_t is allocated, the size of n - 1 mumble_t's is added to the * size of a foo_t to derive the size of the allocation; this allows for * the n trailing mumble_t's to be referenced from the allocated foo_t * using C's convenient array syntax -- without requiring an additional * memory dereference. ISO C99 calls the last member in such a structure * the "flexible array member" (FAM); we adhere to this terminology. * * (c) It is not uncommon for structures to embed smaller structures, and * to pass pointers to these smaller structures to routines that track * the structures only by the smaller type. This can be thought of as * a sort of crude-but-efficient polymorphism; see e.g., struct seg and * its embedded avl_node_t. It is less common (but by no means unheard * of) for the smaller structures to be used as place holders in data * structures consisting of the larger structure. That is, instead of an * instance of the larger structure being pointed to by the smaller * structure pointer, an instance of the smaller structure is pointed to * the larger structure pointer; see e.g., struct buf and struct hbuf or * struct seg_pcache and struct seg_phash. This construct is particularly * important to identify when the smaller structures are in a contiguous * array (as they are in each of the two examples provided): by examining * only the data structure of larger structures, one would erroneously * assume that the array of the smaller structure is actually an array of * the larger structure. * * Taken together, these three phenomena imply that if we have a pointer to * an object that is larger than the pointed-to type, we don't know if the * object is an array of objects of the pointed-to type, the pointed-to type * followed by an array of that type's last member, or some other larger type * that we haven't yet discovered. * * Differentiating these three situations is the focus of many of the * type graph heuristics. Type graph processing is performed in an initial * pass, four type-determining passes, and a final, post-pass: * * Initial: Graph construction * * The initial pass constructs the nodes from the kmem caches and module data, * and constructs the edges by propagating out from module data. Nodes that * are in module data or known kmem caches (see tg_cachetab[], below) are * marked with their known type. This pass takes the longest amount of * wall-clock time, for it frequently induces I/O to read the postmortem image * into memory from permanent storage. * * pass1: Conservative propagation * * In pass1, we propagate types out from the known nodes, adding types to * nodes' tgn_typelists as they are inferred. Nodes are marked as they are * processed to guarantee halting. We proceed as conservatively as possible * in this pass; if we discover that a node is larger than twice its inferred * type (that is, we've run into one of the three phenomena described above), * we add the inferred type to the node's tgn_typelist, but we don't descend. * * pass2: Array determination * * In pass2, we visit those nodes through which we refused to descend in pass1. * If we find one (and only one) structural interpretation for the object, we * have determined that -- to the best of our knowledge -- we are not seeing * phenomenon (c). To further differentiate (a) from (b), we check if the * structure ends with an array of size one; if it does, we assume that it has * a flexible array member. Otherwise, we perform an additional check: we * calculate the size of the object modulo the size of the inferred type and * subtract it from the size of the object. If this value is less than or * equal to the size of the next-smaller kmem cache, we know that it's not an * array of the inferred type -- if it were an array of the inferred type, it * would have been instead allocated out of the next-smaller cache. * * In either case (FAM or no FAM), we iterate through each element of the * hypothesised array, checking that each pointer member points to either NULL * or valid memory. If pointer members do not satisfy these criteria, it is * assumed that we have not satisfactorily determined that the given object is * an array of the inferred type, and we abort processing of the node. Note * that uninitialized pointers can potentially prevent an otherwise valid * array from being interpreted as such. Because array misinterpretation * can induce substantial cascading type misinterpretation, it is preferred to * be conservative and accurate in such cases -- even if it means a lower type * recognition rate. * * pass3: Type coalescence * * pass3 coalesces type possibilities by preferring structural possibilities * over non-structural ones. For example, if an object is either of type * "char" (pointed to by a caddr_t) or type "struct frotz", the possibilities * will be coalesced into just "struct frotz." * * pass4: Non-array type inference * * pass4 is the least conservative: it is assumed that phenomenon (c) has been * completely ferreted out by prior passes. All unknown types are visited, and * incoming edges are checked. If there is only one possible structural * inference for the unknown type, the node is inferred to be of that type, and * the type is propagated. This pass picks up those nodes that are larger than * their inferred type, but for which the inferred type is likely accurate. * (struct dcentry, with its FAM of characters, is an example type that is * frequently determined by this pass.) * * Post-pass: Greatest unknown reach * * If recognition rate is low (or, from a more practical perspective, if the * object of interest is not automatically identified), it can be useful * to know which node is the greatest impediment to further recognition. * If the user can -- by hook or by crook -- determine the true type of this * node (and set it with ::istype), much more type identification should be * possible. To facilitate this, we therefore define the _reach_ of a node to * be the number of unknown nodes that could potentially be identified were the * node's type better known. We determine the reach by performing a * depth-first pass through the graph. The node of greatest reach (along with * the reach itself) are reported upon completion of the post-pass. */ #include #include #include #include #include #include #include #include #include #include "kmem.h" struct tg_node; typedef struct tg_edge { struct tg_node *tge_src; /* source node */ struct tg_node *tge_dest; /* destination node */ uintptr_t tge_srcoffs; /* offset in source node */ uintptr_t tge_destoffs; /* offset in destination node */ struct tg_edge *tge_nextin; /* next incoming edge */ struct tg_edge *tge_nextout; /* next outgoing edge */ int tge_marked; /* mark */ } tg_edge_t; typedef struct tg_type { mdb_ctf_id_t tgt_type; /* CTF type */ mdb_ctf_id_t tgt_utype; /* unresolved CTF type */ mdb_ctf_id_t tgt_rtype; /* referring type */ size_t tgt_roffs; /* referring offset */ const char *tgt_rmember; /* referring member */ tg_edge_t *tgt_redge; /* referring edge */ struct tg_type *tgt_next; /* next type */ int tgt_flags; /* flags */ } tg_type_t; #define TG_TYPE_ARRAY 0x0001 #define TG_TYPE_NOTARRAY 0x0002 #define TG_TYPE_HASFAM 0x0004 typedef struct tg_node { uintptr_t tgn_base; /* address base of object */ uintptr_t tgn_limit; /* address limit of object */ tg_edge_t *tgn_incoming; /* incoming edges */ tg_edge_t *tgn_outgoing; /* outgoing edges */ tg_type_t *tgn_typelist; /* conjectured typelist */ tg_type_t *tgn_fraglist; /* type fragment list */ char tgn_marked; /* marked */ char tgn_postmarked; /* marked in postpass */ int tgn_smaller; /* size of next-smaller cache */ int tgn_reach; /* number of reachable unknown nodes */ mdb_ctf_id_t tgn_type; /* known type */ } tg_node_t; #define TG_NODE_SIZE(n) ((n)->tgn_limit - (n)->tgn_base) typedef struct tg_stats { size_t tgs_buffers; size_t tgs_nodes; size_t tgs_unmarked; size_t tgs_known; size_t tgs_typed; size_t tgs_conflicts; size_t tgs_frag; size_t tgs_candidates; } tg_stats_t; typedef struct tg_typeoffs { mdb_ctf_id_t tgto_type; /* found type */ ulong_t tgto_offs; /* offset of interest */ const char **tgto_memberp; /* referring member name */ tg_edge_t *tgto_edge; /* outbound edge */ } tg_typeoffs_t; typedef struct tg_buildstate { uintptr_t tgbs_addr; /* address of region */ uintptr_t *tgbs_buf; /* in-core copy of region */ size_t tgbs_ndx; /* current pointer index */ size_t tgbs_nptrs; /* number of pointers */ tg_node_t *tgbs_src; /* corresponding node */ struct tg_buildstate *tgbs_next; /* next stacked or free */ } tg_buildstate_t; typedef struct tg_poststate { tg_node_t *tgps_node; /* current node */ tg_edge_t *tgps_edge; /* current edge */ size_t tgps_total; /* current total */ struct tg_poststate *tgps_next; /* next stacked or free */ } tg_poststate_t; typedef struct tg_todo { tg_node_t *tgtd_node; /* node to process */ uintptr_t tgtd_offs; /* offset within node */ mdb_ctf_id_t tgtd_type; /* conjectured type */ struct tg_todo *tgtd_next; /* next todo */ } tg_todo_t; typedef struct tg_nodedata { tg_node_t *tgd_next; /* next node to fill in */ size_t tgd_size; /* size of this node */ } tg_nodedata_t; /* * Some caches can be pretty arduous to identify (or are rife with conflicts). * To assist type identification, specific caches are identified with the * types of their contents. Each cache need _not_ be listed here; in general, * a cache should only be added to the tg_cachetab[] if the identification rate * for the cache is less than 95%Every . (The identification rate for a * specific cache can be quickly determined by specifying the cache to * ::typegraph.) */ struct { char *tgc_name; char *tgc_type; } tg_cachetab[] = { { "streams_mblk", "mblk_t" }, { "seg_cache", "struct seg" }, { "segvn_cache", "struct segvn_data" }, { "anon_cache", "struct anon" }, { "ufs_inode_cache", "inode_t" }, { "hme_cache", "struct hment" }, { "queue_cache", "queinfo_t" }, { "sock_cache", "struct sonode" }, { "ire_cache", "ire_t" }, { NULL, NULL } }; /* * Some types are only known by their opaque handles. While this is a good way * to keep interface clients from eating the Forbidden Fruit, it can make type * identification difficult -- which can be especially important for big * structures like dev_info_t. To assist type identification, we keep a table * to translate from opaque handles to their underlying structures. A * translation should only be added to the tg_typetab[] if the lack of * translation is preventing substantial type identification. (This can be * determined by using the "typeunknown" walker on a dump with bufctl auditing * enabled, and using "::whatis -b" to determine the types of unknown buffers; * if many of these unknown types are structures behind an opaque handle, a * new entry in tg_typetab[] is likely warranted.) */ struct { char *tgt_type_name; /* filled in statically */ char *tgt_actual_name; /* filled in statically */ mdb_ctf_id_t tgt_type; /* determined dynamically */ mdb_ctf_id_t tgt_actual_type; /* determined dynamically */ } tg_typetab[] = { { "dev_info_t", "struct dev_info" }, { "ddi_dma_handle_t", "ddi_dma_impl_t *" }, { NULL, NULL } }; static enum { TG_PASS1 = 1, TG_PASS2, TG_PASS3, TG_PASS4 } tg_pass; static size_t tg_nnodes; /* number of nodes */ static size_t tg_nanchored; /* number of anchored nodes */ static tg_node_t *tg_node; /* array of nodes */ static tg_node_t **tg_sorted; /* sorted array of pointers into tg_node */ static size_t tg_nsorted; /* number of pointers in tg_sorted */ static int *tg_sizes; /* copy of kmem_alloc_sizes[] array */ static int tg_nsizes; /* number of sizes in tg_sizes */ static hrtime_t tg_start; /* start time */ static int tg_improved; /* flag indicating that we have improved */ static int tg_built; /* flag indicating that type graph is built */ static uint_t tg_verbose; /* flag to increase verbosity */ struct typegraph_ctf_module { unsigned int nsyms; char *data; uintptr_t bss; size_t data_size; size_t bss_size; }; static mdb_ctf_id_t typegraph_type_offset(mdb_ctf_id_t, size_t, tg_edge_t *, const char **); static void typegraph_typetab_init(void) { int i; for (i = 0; tg_typetab[i].tgt_type_name != NULL; i++) { if (mdb_ctf_lookup_by_name(tg_typetab[i].tgt_type_name, &tg_typetab[i].tgt_type) == -1) { mdb_warn("can't find type '%s'\n", tg_typetab[i].tgt_type_name); mdb_ctf_type_invalidate(&tg_typetab[i].tgt_type); continue; } if (mdb_ctf_lookup_by_name(tg_typetab[i].tgt_actual_name, &tg_typetab[i].tgt_actual_type) == -1) { mdb_warn("can't find type '%s'\n", tg_typetab[i].tgt_actual_name); mdb_ctf_type_invalidate(&tg_typetab[i].tgt_actual_type); } } } /* * A wrapper around mdb_ctf_type_resolve() that first checks the type * translation table. */ static mdb_ctf_id_t typegraph_resolve(mdb_ctf_id_t type) { int i; mdb_ctf_id_t ret; /* * This could be _much_ more efficient... */ for (i = 0; tg_typetab[i].tgt_type_name != NULL; i++) { if (mdb_ctf_type_cmp(type, tg_typetab[i].tgt_type) == 0) { type = tg_typetab[i].tgt_actual_type; break; } } (void) mdb_ctf_type_resolve(type, &ret); return (ret); } /* * A wrapper around mdb_ctf_type_name() that deals with anonymous structures. * Anonymous structures are those that have no name associated with them. * Nearly always, these structures are referred to by a typedef (e.g. * "typedef struct { int bar } foo_t"); we expect the unresolved type to * be passed as utype. */ static char * typegraph_type_name(mdb_ctf_id_t type, mdb_ctf_id_t utype) { static char buf[MDB_SYM_NAMLEN]; if (mdb_ctf_type_name(type, buf, sizeof (buf)) == NULL) { (void) strcpy(buf, ""); } else { /* * Perhaps a CTF interface would be preferable to this kludgey * strcmp()? Perhaps. */ if (strcmp(buf, "struct ") == 0) (void) mdb_ctf_type_name(utype, buf, sizeof (buf)); } return (buf); } /* * A wrapper around mdb_ctf_type_size() that accurately accounts for arrays. */ static ssize_t typegraph_size(mdb_ctf_id_t type) { mdb_ctf_arinfo_t arr; ssize_t size; if (!mdb_ctf_type_valid(type)) return (-1); if (mdb_ctf_type_kind(type) != CTF_K_ARRAY) return (mdb_ctf_type_size(type)); if (mdb_ctf_array_info(type, &arr) == -1) return (-1); type = typegraph_resolve(arr.mta_contents); if (!mdb_ctf_type_valid(type)) return (-1); if ((size = mdb_ctf_type_size(type)) == -1) return (-1); return (size * arr.mta_nelems); } /* * The mdb_ctf_member_iter() callback for typegraph_type_offset(). */ static int typegraph_offiter(const char *name, mdb_ctf_id_t type, ulong_t off, tg_typeoffs_t *toffs) { int kind; ssize_t size; mdb_ctf_arinfo_t arr; off /= NBBY; if (off > toffs->tgto_offs) { /* * We went past it; return failure. */ return (1); } if (!mdb_ctf_type_valid(type = typegraph_resolve(type))) return (0); if ((size = mdb_ctf_type_size(type)) == -1) return (0); if (off < toffs->tgto_offs && size != 0 && off + size <= toffs->tgto_offs) { /* * Haven't reached it yet; continue looking. */ return (0); } /* * If the base type is not a structure, an array or a union, and * the offset equals the desired offset, we have our type. */ if ((kind = mdb_ctf_type_kind(type)) != CTF_K_STRUCT && kind != CTF_K_UNION && kind != CTF_K_ARRAY) { if (off == toffs->tgto_offs) toffs->tgto_type = type; if (toffs->tgto_memberp != NULL) *(toffs->tgto_memberp) = name; return (1); } /* * If the type is an array, see if we fall within the bounds. */ if (kind == CTF_K_ARRAY) { if (mdb_ctf_array_info(type, &arr) == -1) return (0); type = typegraph_resolve(arr.mta_contents); if (!mdb_ctf_type_valid(type)) return (0); size = mdb_ctf_type_size(type) * arr.mta_nelems; if (off < toffs->tgto_offs && off + size <= toffs->tgto_offs) { /* * Nope, haven't found it yet; continue looking. */ return (0); } } toffs->tgto_type = typegraph_type_offset(type, toffs->tgto_offs - off, toffs->tgto_edge, toffs->tgto_memberp); return (1); } /* * The mdb_ctf_member_iter() callback for typegraph_type_offset() when the type * is found to be of kind CTF_K_UNION. With unions, we attempt to do better * than just completely punting: if all but one of the members is impossible * (due to, say, size constraints on the destination node), we can propagate * the valid member. */ static int typegraph_union(const char *name, mdb_ctf_id_t type, ulong_t off, tg_typeoffs_t *toffs) { const char *member = name; tg_edge_t *e = toffs->tgto_edge; mdb_ctf_id_t rtype; size_t rsize; int kind; if (!mdb_ctf_type_valid(type = typegraph_resolve(type))) return (0); kind = mdb_ctf_type_kind(type); if (kind == CTF_K_STRUCT || kind != CTF_K_UNION || kind != CTF_K_ARRAY) { type = typegraph_type_offset(type, toffs->tgto_offs - off, e, &member); } if (!mdb_ctf_type_valid(type)) return (0); if (mdb_ctf_type_kind(type) != CTF_K_POINTER) return (0); /* * Now figure out what exactly we're pointing to. */ if (mdb_ctf_type_reference(type, &rtype) == -1) return (0); if (!mdb_ctf_type_valid(rtype = typegraph_resolve(rtype))) return (0); rsize = mdb_ctf_type_size(rtype); /* * Compare this size to the size of the thing we're pointing to -- * if it's larger than the node that we're pointing to, we know that * the alleged pointer type must be an invalid interpretation of the * union. */ if (rsize > TG_NODE_SIZE(e->tge_dest) - e->tge_destoffs) { /* * We're in luck -- it's not possibly this pointer. */ return (0); } /* * This looks like it could be legit. If the type hasn't been * specified, we could be in business. */ if (mdb_ctf_type_valid(toffs->tgto_type)) { /* * There are two potentially valid interpretations for this * union. Invalidate the type. */ mdb_ctf_type_invalidate(&toffs->tgto_type); return (1); } toffs->tgto_type = type; if (toffs->tgto_memberp != NULL) *(toffs->tgto_memberp) = member; return (0); } /*ARGSUSED*/ static int typegraph_lastmember(const char *name, mdb_ctf_id_t type, ulong_t off, void *last) { *((mdb_ctf_id_t *)last) = type; return (0); } /* * To determine if a structure is has a flexible array member, we iterate over * the members; if the structure has more than one member, and the last member * is an array of size 1, we're going to assume that this structure has a * flexible array member. Yes, this heuristic is a little sloppy -- but cut me * some slack: why the hell else would you have an array of size 1? (Don't * answer that.) */ static int typegraph_hasfam(mdb_ctf_id_t type, mdb_ctf_id_t *atype) { mdb_ctf_arinfo_t arr; mdb_ctf_id_t last; int kind; if (!mdb_ctf_type_valid(type)) return (0); if ((kind = mdb_ctf_type_kind(type)) != CTF_K_STRUCT) return (0); mdb_ctf_type_invalidate(&last); mdb_ctf_member_iter(type, typegraph_lastmember, &last, 0); if (!mdb_ctf_type_valid(last)) return (0); if ((kind = mdb_ctf_type_kind(last)) == CTF_K_STRUCT) return (typegraph_hasfam(last, atype)); if (kind != CTF_K_ARRAY) return (0); if (typegraph_size(last) == typegraph_size(type)) { /* * This structure has only one member; even if that member is * an array of size 1, we'll assume that there is something * stranger going on than a run-of-the-mill FAM (e.g., a * kmutex_t). */ return (0); } if (mdb_ctf_array_info(last, &arr) == -1) return (0); if (arr.mta_nelems != 1) return (0); if (atype != NULL) *atype = typegraph_resolve(arr.mta_contents); return (1); } /* * This routine takes a type and offset, and returns the type at the specified * offset. It additionally takes an optional edge to help bust unions, and * an optional address of a character pointer to set to the name of the member * found at the specified offset. */ static mdb_ctf_id_t typegraph_type_offset(mdb_ctf_id_t type, size_t offset, tg_edge_t *e, const char **member) { mdb_ctf_arinfo_t arr; uint_t kind; mdb_ctf_id_t last; ssize_t size; ssize_t lsize; tg_typeoffs_t toffs; mdb_ctf_id_t inval; mdb_ctf_type_invalidate(&inval); if (member != NULL) *member = NULL; /* * Resolve type to its base type. */ type = typegraph_resolve(type); kind = mdb_ctf_type_kind(type); switch (kind) { case CTF_K_ARRAY: /* * If this is an array, we need to figure out what it's an * array _of_. We must then figure out the size of the array * structure, and then determine our offset within that type. * From there, we can recurse. */ if (mdb_ctf_array_info(type, &arr) == -1) return (inval); type = typegraph_resolve(arr.mta_contents); if (!mdb_ctf_type_valid(type)) return (inval); /* * If the type is not a structure/union, then check that the * offset doesn't point to the middle of the base type and * return it. */ kind = mdb_ctf_type_kind(type); size = mdb_ctf_type_size(type); if (kind != CTF_K_STRUCT && kind != CTF_K_UNION) { if (offset % size) { /* * The offset is pointing to the middle of a * type; return failure. */ return (inval); } return (type); } return (typegraph_type_offset(type, offset % size, e, member)); case CTF_K_STRUCT: /* * If the offset is larger than the size, we need to figure * out what exactly we're looking at. There are several * possibilities: * * (a) A structure that has this type as its first member. * * (b) An array of structures of this type. * * (c) A structure has a flexible array member. * * The differentiation between (a) and (b) has hopefully * happened before entering this function. To differentiate * between (b) and (c), we call typegraph_hasfam(). */ size = mdb_ctf_type_size(type); if (offset >= size) { if (typegraph_hasfam(type, &last)) { /* * We have a live one. Take the size, subtract * the size of the last element, and recurse. */ if (!mdb_ctf_type_valid(last)) return (inval); lsize = mdb_ctf_type_size(last); return (typegraph_type_offset(last, offset - size - lsize, e, member)); } offset %= size; } toffs.tgto_offs = offset; toffs.tgto_memberp = member; toffs.tgto_edge = e; mdb_ctf_type_invalidate(&toffs.tgto_type); mdb_ctf_member_iter(type, (mdb_ctf_member_f *)typegraph_offiter, &toffs, 0); return (toffs.tgto_type); case CTF_K_POINTER: if (!mdb_ctf_type_valid(type = typegraph_resolve(type))) return (inval); size = mdb_ctf_type_size(type); if (offset % size) { /* * The offset is pointing to the middle of a type; * return failure. */ return (inval); } return (type); case CTF_K_UNION: if (e == NULL) { /* * We've been given no outbound edge -- we have no way * of figuring out what the hell this union is. */ return (inval); } toffs.tgto_offs = offset; toffs.tgto_memberp = member; toffs.tgto_edge = e; mdb_ctf_type_invalidate(&toffs.tgto_type); /* * Try to bust the union... */ if (mdb_ctf_member_iter(type, (mdb_ctf_member_f *)typegraph_union, &toffs, 0) != 0) { /* * There was at least one valid pointer in there. * Return "void *". */ (void) mdb_ctf_lookup_by_name("void *", &type); return (type); } return (toffs.tgto_type); default: /* * If the offset is anything other than zero, no dice. */ if (offset != 0) return (inval); return (type); } } /* * This routine takes an address and a type, and determines if the memory * pointed to by the specified address could be of the specified type. * This could become significantly more sophisticated, but for now it's pretty * simple: this is _not_ of the specified type if it's a pointer, and either: * * (a) The alignment is not correct given the type that is pointed to. * * (b) The memory pointed to is invalid. Note that structures that have * uninitialized pointers may cause us to erroneously fail -- but these * structures are a bug anyway (uninitialized pointers can confuse many * analysis tools, including ::findleaks). */ static int typegraph_couldbe(uintptr_t addr, mdb_ctf_id_t type) { int rkind; mdb_ctf_id_t rtype; uintptr_t val, throwaway; size_t rsize; char buf[MDB_SYM_NAMLEN]; if (mdb_ctf_type_kind(type) != CTF_K_POINTER) return (1); if (mdb_ctf_type_reference(type, &rtype) == -1) return (1); if (!mdb_ctf_type_valid(rtype = typegraph_resolve(rtype))) return (1); if (mdb_vread(&val, sizeof (val), addr) == -1) { /* * This is definitely unexpected. We should not be getting * back an error on a node that was successfully read in. * Lacking something better to do, we'll print an error * and return. */ mdb_warn("failed to evaluate pointer type at address %p", addr); return (1); } rkind = mdb_ctf_type_kind(rtype); if (rkind == CTF_K_STRUCT || rkind == CTF_K_UNION) { /* * If it's a pointer to a structure or union, it must be * aligned to sizeof (uintptr_t). */ if (val & (sizeof (uintptr_t) - 1)) { if (tg_verbose) { mdb_printf("typegraph: pass %d: rejecting " "*%p (%p) as %s: misaligned pointer\n", tg_pass, addr, val, mdb_ctf_type_name(type, buf, sizeof (buf))); } return (0); } } rsize = mdb_ctf_type_size(rtype); if (val == 0 || rsize == 0) return (1); /* * For our speculative read, we're going to clamp the referenced size * at the size of a pointer. */ if (rsize > sizeof (uintptr_t)) rsize = sizeof (uintptr_t); if (mdb_vread(&throwaway, rsize, val) == -1) { if (tg_verbose) { mdb_printf("typegraph: pass %d: rejecting *%p (%p) as" " %s: bad pointer\n", tg_pass, addr, val, mdb_ctf_type_name(type, buf, sizeof (buf))); } return (0); } return (1); } static int typegraph_nodecmp(const void *l, const void *r) { tg_node_t *lhs = *(tg_node_t **)l; tg_node_t *rhs = *(tg_node_t **)r; if (lhs->tgn_base < rhs->tgn_base) return (-1); if (lhs->tgn_base > rhs->tgn_base) return (1); return (0); } static tg_node_t * typegraph_search(uintptr_t addr) { ssize_t left = 0, right = tg_nnodes - 1, guess; while (right >= left) { guess = (right + left) >> 1; if (addr < tg_sorted[guess]->tgn_base) { right = guess - 1; continue; } if (addr >= tg_sorted[guess]->tgn_limit) { left = guess + 1; continue; } return (tg_sorted[guess]); } return (NULL); } static int typegraph_interested(const kmem_cache_t *c) { vmem_t vmem; if (mdb_vread(&vmem, sizeof (vmem), (uintptr_t)c->cache_arena) == -1) { mdb_warn("cannot read arena %p for cache '%s'", (uintptr_t)c->cache_arena, c->cache_name); return (0); } /* * If this cache isn't allocating from the kmem_default or the * kmem_firewall vmem arena, we're not interested. */ if (strcmp(vmem.vm_name, "kmem_default") != 0 && strcmp(vmem.vm_name, "kmem_firewall") != 0) return (0); return (1); } static int typegraph_estimate(uintptr_t addr, const kmem_cache_t *c, size_t *est) { if (!typegraph_interested(c)) return (WALK_NEXT); *est += kmem_estimate_allocated(addr, c); return (WALK_NEXT); } static int typegraph_estimate_modctl(uintptr_t addr, const struct modctl *m, size_t *est) { struct typegraph_ctf_module mod; if (m->mod_mp == NULL) return (WALK_NEXT); if (mdb_ctf_vread(&mod, "struct module", "struct typegraph_ctf_module", (uintptr_t)m->mod_mp, 0) == -1) { mdb_warn("couldn't read modctl %p's module", addr); return (WALK_NEXT); } (*est) += mod.nsyms; return (WALK_NEXT); } /*ARGSUSED*/ static int typegraph_estimate_vmem(uintptr_t addr, const vmem_t *vmem, size_t *est) { if (strcmp(vmem->vm_name, "kmem_oversize") != 0) return (WALK_NEXT); *est += (size_t)(vmem->vm_kstat.vk_alloc.value.ui64 - vmem->vm_kstat.vk_free.value.ui64); return (WALK_NEXT); } /*ARGSUSED*/ static int typegraph_buf(uintptr_t addr, void *ignored, tg_nodedata_t *tgd) { tg_node_t *node = tgd->tgd_next++; uintptr_t limit = addr + tgd->tgd_size; node->tgn_base = addr; node->tgn_limit = limit; return (WALK_NEXT); } /*ARGSUSED*/ static int typegraph_kmem(uintptr_t addr, const kmem_cache_t *c, tg_node_t **tgp) { tg_node_t *node = *tgp; tg_nodedata_t tgd; mdb_ctf_id_t type; int i, smaller; mdb_ctf_type_invalidate(&type); if (!typegraph_interested(c)) return (WALK_NEXT); tgd.tgd_size = c->cache_bufsize; tgd.tgd_next = *tgp; if (mdb_pwalk("kmem", (mdb_walk_cb_t)typegraph_buf, &tgd, addr) == -1) { mdb_warn("can't walk kmem for cache %p (%s)", addr, c->cache_name); return (WALK_DONE); } *tgp = tgd.tgd_next; for (i = 0; tg_cachetab[i].tgc_name != NULL; i++) { if (strcmp(tg_cachetab[i].tgc_name, c->cache_name) != 0) continue; if (mdb_ctf_lookup_by_name(tg_cachetab[i].tgc_type, &type) == -1) { mdb_warn("could not find type '%s', allegedly type " "for cache %s", tg_cachetab[i].tgc_type, c->cache_name); break; } break; } /* * If this is a named cache (i.e., not from a kmem_alloc_[n] cache), * the nextsize is 0. */ if (strncmp(c->cache_name, "kmem_alloc_", strlen("kmem_alloc_")) == 0) { GElf_Sym sym; GElf_Sym sym2; if (tg_sizes == NULL) { size_t nsizes = 0; size_t nsizes_reg = 0; size_t nsizes_big = 0; if (mdb_lookup_by_name("kmem_alloc_sizes", &sym) == -1) { mdb_warn("failed to find 'kmem_alloc_sizes'"); return (WALK_ERR); } nsizes_reg = sym.st_size / sizeof (int); if (mdb_lookup_by_name("kmem_big_alloc_sizes", &sym2) != -1) { nsizes_big = sym2.st_size / sizeof (int); } nsizes = nsizes_reg + nsizes_big; tg_sizes = mdb_zalloc(nsizes * sizeof (int), UM_SLEEP); tg_nsizes = nsizes; if (mdb_vread(tg_sizes, sym.st_size, (uintptr_t)sym.st_value) == -1) { mdb_warn("failed to read kmem_alloc_sizes"); return (WALK_ERR); } if (nsizes_big > 0 && mdb_vread(&tg_sizes[nsizes_reg], sym2.st_size, (uintptr_t)sym2.st_value) == -1) { mdb_warn("failed to read kmem_big_alloc_sizes"); return (WALK_ERR); } } /* * Yes, this is a linear search -- but we're talking about * a pretty small array (38 elements as of this writing), and * only executed a handful of times (for each sized kmem * cache). */ for (i = 0; i < tg_nsizes; i++) { if (tg_sizes[i] == c->cache_bufsize) break; } if (i == tg_nsizes) { /* * Something is wrong -- this appears to be a sized * kmem cache, but we can't find its size in the * kmem_alloc_sizes array. Emit a warning and return * failure. */ mdb_warn("couldn't find buffer size for %s (%d)" " in kmem_alloc_sizes array\n", c->cache_name, c->cache_bufsize); return (WALK_ERR); } if (i == 0) { smaller = 1; } else { smaller = tg_sizes[i - 1]; } } else { smaller = 0; } for (; node < *tgp; node++) { node->tgn_type = type; node->tgn_smaller = smaller; } *tgp = tgd.tgd_next; return (WALK_NEXT); } /*ARGSUSED*/ static int typegraph_seg(uintptr_t addr, const vmem_seg_t *seg, tg_node_t **tgp) { tg_nodedata_t tgd; tgd.tgd_next = *tgp; tgd.tgd_size = seg->vs_end - seg->vs_start; typegraph_buf(seg->vs_start, NULL, &tgd); *tgp = tgd.tgd_next; return (WALK_NEXT); } static int typegraph_vmem(uintptr_t addr, const vmem_t *vmem, tg_node_t **tgp) { if (strcmp(vmem->vm_name, "kmem_oversize") != 0) return (WALK_NEXT); if (mdb_pwalk("vmem_alloc", (mdb_walk_cb_t)typegraph_seg, tgp, addr) == -1) mdb_warn("can't walk vmem for arena %p", addr); return (WALK_NEXT); } static void typegraph_build_anchored(uintptr_t addr, size_t size, mdb_ctf_id_t type) { uintptr_t *buf; tg_buildstate_t *state = NULL, *new_state, *free = NULL; tg_node_t *node, *src; tg_edge_t *edge; size_t nptrs, ndx; uintptr_t min = tg_sorted[0]->tgn_base; uintptr_t max = tg_sorted[tg_nnodes - 1]->tgn_limit; ssize_t rval; int mask = sizeof (uintptr_t) - 1; if (addr == 0 || size < sizeof (uintptr_t)) return; /* * Add an anchored node. */ src = &tg_node[tg_nnodes + tg_nanchored++]; src->tgn_base = addr; src->tgn_limit = addr + size; src->tgn_type = type; push: /* * If our address isn't pointer-aligned, we need to align it and * whack the size appropriately. */ if (addr & mask) { if ((mask + 1) - (addr & mask) >= size) goto out; size -= (mask + 1) - (addr & mask); addr += (mask + 1) - (addr & mask); } nptrs = size / sizeof (uintptr_t); buf = mdb_alloc(size, UM_SLEEP); ndx = 0; if ((rval = mdb_vread(buf, size, addr)) != size) { mdb_warn("couldn't read ptr at %p (size %ld); rval is %d", addr, size, rval); goto out; } pop: for (; ndx < nptrs; ndx++) { uintptr_t ptr = buf[ndx]; if (ptr < min || ptr >= max) continue; if ((node = typegraph_search(ptr)) == NULL) continue; /* * We need to record an edge to us. */ edge = mdb_zalloc(sizeof (tg_edge_t), UM_SLEEP); edge->tge_src = src; edge->tge_dest = node; edge->tge_nextout = src->tgn_outgoing; src->tgn_outgoing = edge; edge->tge_srcoffs += ndx * sizeof (uintptr_t); edge->tge_destoffs = ptr - node->tgn_base; edge->tge_nextin = node->tgn_incoming; node->tgn_incoming = edge; /* * If this node is marked, we don't need to descend. */ if (node->tgn_marked) continue; /* * We need to descend. To minimize the resource consumption * of type graph construction, we avoid recursing. */ node->tgn_marked = 1; if (free != NULL) { new_state = free; free = free->tgbs_next; } else { new_state = mdb_zalloc(sizeof (tg_buildstate_t), UM_SLEEP); } new_state->tgbs_src = src; src = node; new_state->tgbs_addr = addr; addr = node->tgn_base; size = node->tgn_limit - addr; new_state->tgbs_next = state; new_state->tgbs_buf = buf; new_state->tgbs_ndx = ndx + 1; new_state->tgbs_nptrs = nptrs; state = new_state; goto push; } /* * If we're here, then we have completed this region. We need to * free our buffer, and update our "resident" counter accordingly. */ mdb_free(buf, size); out: /* * If we have pushed state, we need to pop it. */ if (state != NULL) { buf = state->tgbs_buf; ndx = state->tgbs_ndx; src = state->tgbs_src; nptrs = state->tgbs_nptrs; addr = state->tgbs_addr; size = nptrs * sizeof (uintptr_t); new_state = state->tgbs_next; state->tgbs_next = free; free = state; state = new_state; goto pop; } while (free != NULL) { state = free; free = free->tgbs_next; mdb_free(state, sizeof (tg_buildstate_t)); } } static void typegraph_build(uintptr_t addr, size_t size) { uintptr_t limit = addr + size; char name[MDB_SYM_NAMLEN]; GElf_Sym sym; mdb_ctf_id_t type; do { if (mdb_lookup_by_addr(addr, MDB_SYM_EXACT, name, sizeof (name), &sym) == -1) { addr++; continue; } if (sym.st_size == 0) { addr++; continue; } if (strcmp(name, "kstat_initial") == 0) { /* * Yes, this is a kludge. "kstat_initial" ends up * backing the kstat vmem arena -- so we don't want * to include it as an anchor node. */ addr += sym.st_size; continue; } /* * We have the symbol; now get its type. */ if (mdb_ctf_lookup_by_addr(addr, &type) == -1) { addr += sym.st_size; continue; } if (!mdb_ctf_type_valid(type)) { addr += sym.st_size; continue; } if (!mdb_ctf_type_valid(type = typegraph_resolve(type))) { addr += sym.st_size; continue; } typegraph_build_anchored(addr, (size_t)sym.st_size, type); addr += sym.st_size; } while (addr < limit); } /*ARGSUSED*/ static int typegraph_thread(uintptr_t addr, const kthread_t *t, mdb_ctf_id_t *type) { /* * If this thread isn't already a node, add it as an anchor. And * regardless, set its type to be the specified type. */ tg_node_t *node; if ((node = typegraph_search(addr)) == NULL) { typegraph_build_anchored(addr, mdb_ctf_type_size(*type), *type); } else { node->tgn_type = *type; } return (WALK_NEXT); } /*ARGSUSED*/ static int typegraph_kstat(uintptr_t addr, const vmem_seg_t *seg, mdb_ctf_id_t *type) { size_t size = mdb_ctf_type_size(*type); typegraph_build_anchored(seg->vs_start, size, *type); return (WALK_NEXT); } static void typegraph_node_addtype(tg_node_t *node, tg_edge_t *edge, mdb_ctf_id_t rtype, const char *rmember, size_t roffs, mdb_ctf_id_t utype, mdb_ctf_id_t type) { tg_type_t *tp; tg_type_t **list; if (edge->tge_destoffs == 0) { list = &node->tgn_typelist; } else { list = &node->tgn_fraglist; } /* * First, search for this type in the type list. */ for (tp = *list; tp != NULL; tp = tp->tgt_next) { if (mdb_ctf_type_cmp(tp->tgt_type, type) == 0) return; } tp = mdb_zalloc(sizeof (tg_type_t), UM_SLEEP); tp->tgt_next = *list; tp->tgt_type = type; tp->tgt_rtype = rtype; tp->tgt_utype = utype; tp->tgt_redge = edge; tp->tgt_roffs = roffs; tp->tgt_rmember = rmember; *list = tp; tg_improved = 1; } static void typegraph_stats_node(tg_node_t *node, tg_stats_t *stats) { tg_edge_t *e; stats->tgs_nodes++; if (!node->tgn_marked) stats->tgs_unmarked++; if (mdb_ctf_type_valid(node->tgn_type)) { stats->tgs_known++; return; } if (node->tgn_typelist != NULL) { stats->tgs_typed++; if (node->tgn_typelist->tgt_next) stats->tgs_conflicts++; return; } if (node->tgn_fraglist != NULL) { stats->tgs_frag++; return; } /* * This node is not typed -- but check if any of its outgoing edges * were successfully typed; such nodes represent candidates for * an exhaustive type search. */ for (e = node->tgn_outgoing; e != NULL; e = e->tge_nextout) { if (e->tge_dest->tgn_typelist) { stats->tgs_candidates++; break; } } } /*ARGSUSED*/ static int typegraph_stats_buffer(uintptr_t addr, void *ignored, tg_stats_t *stats) { tg_node_t *node; stats->tgs_buffers++; if ((node = typegraph_search(addr)) == NULL) { return (WALK_NEXT); } typegraph_stats_node(node, stats); return (WALK_NEXT); } /*ARGSUSED*/ void typegraph_stat_print(char *name, size_t stat) { mdb_printf("typegraph: "); if (name == NULL) { mdb_printf("\n"); return; } mdb_printf("%30s => %ld\n", name, stat); } static void typegraph_stat_str(char *name, char *str) { mdb_printf("typegraph: %30s => %s\n", name, str); } static void typegraph_stat_perc(char *name, size_t stat, size_t total) { int perc = (stat * 100) / total; int tenths = ((stat * 1000) / total) % 10; mdb_printf("typegraph: %30s => %-13ld (%2d.%1d%%)\n", name, stat, perc, tenths); } static void typegraph_stat_time(int last) { static hrtime_t ts; hrtime_t pass; if (ts == 0) { pass = (ts = gethrtime()) - tg_start; } else { hrtime_t now = gethrtime(); pass = now - ts; ts = now; } mdb_printf("typegraph: %30s => %lld seconds\n", "time elapsed, this pass", pass / NANOSEC); mdb_printf("typegraph: %30s => %lld seconds\n", "time elapsed, total", (ts - tg_start) / NANOSEC); mdb_printf("typegraph:\n"); if (last) ts = 0; } static void typegraph_stats(void) { size_t i, n; tg_stats_t stats; bzero(&stats, sizeof (stats)); for (i = 0; i < tg_nnodes - tg_nanchored; i++) typegraph_stats_node(&tg_node[i], &stats); n = stats.tgs_nodes; typegraph_stat_print("pass", tg_pass); typegraph_stat_print("nodes", n); typegraph_stat_perc("unmarked", stats.tgs_unmarked, n); typegraph_stat_perc("known", stats.tgs_known, n); typegraph_stat_perc("conjectured", stats.tgs_typed, n); typegraph_stat_perc("conjectured fragments", stats.tgs_frag, n); typegraph_stat_perc("known or conjectured", stats.tgs_known + stats.tgs_typed + stats.tgs_frag, n); typegraph_stat_print("conflicts", stats.tgs_conflicts); typegraph_stat_print("candidates", stats.tgs_candidates); typegraph_stat_time(0); } /* * This is called both in pass1 and in subsequent passes (to propagate new type * inferences). */ static void typegraph_pass1_node(tg_node_t *node, mdb_ctf_id_t type) { tg_todo_t *first = NULL, *last = NULL, *free = NULL, *this = NULL; tg_todo_t *todo; tg_edge_t *e; uintptr_t offs = 0; size_t size; const char *member; if (!mdb_ctf_type_valid(type)) return; again: /* * For each of the nodes corresponding to our outgoing edges, * determine their type. */ size = typegraph_size(type); for (e = node->tgn_outgoing; e != NULL; e = e->tge_nextout) { mdb_ctf_id_t ntype, rtype; size_t nsize; int kind; /* * If we're being called in pass1, we're very conservative: * * (a) If the outgoing edge is beyond the size of the type * (and the current node is not the root), we refuse to * descend. This situation isn't actually hopeless -- we * could be looking at array of the projected type -- but * we'll allow a later phase to pass in that node and its * conjectured type as the root. * * (b) If the outgoing edge has a destination offset of * something other than 0, we'll descend, but we won't * add the type to the type list of the destination node. * This allows us to gather information that can later be * used to perform a more constrained search. */ if (tg_pass == TG_PASS1 && e->tge_srcoffs - offs > size) continue; if (offs >= typegraph_size(type)) continue; if (e->tge_srcoffs < offs) continue; if (e->tge_marked) continue; ntype = typegraph_type_offset(type, e->tge_srcoffs - offs, e, &member); if (!mdb_ctf_type_valid(ntype)) continue; if ((kind = mdb_ctf_type_kind(ntype)) != CTF_K_POINTER) continue; if (mdb_ctf_type_reference(ntype, &rtype) == -1) continue; if (!mdb_ctf_type_valid(ntype = typegraph_resolve(rtype))) continue; kind = mdb_ctf_type_kind(ntype); nsize = mdb_ctf_type_size(ntype); if (nsize > TG_NODE_SIZE(e->tge_dest) - e->tge_destoffs) continue; typegraph_node_addtype(e->tge_dest, e, type, member, e->tge_srcoffs - offs, rtype, ntype); if (e->tge_dest->tgn_marked) continue; /* * If our destination offset is 0 and the type that we marked * it with is useful, mark the node that we're * going to visit. And regardless, mark the edge. */ if (e->tge_destoffs == 0 && kind == CTF_K_STRUCT) e->tge_dest->tgn_marked = 1; e->tge_marked = 1; /* * If this isn't a structure, it's pointless to descend. */ if (kind != CTF_K_STRUCT) continue; if (nsize <= TG_NODE_SIZE(e->tge_dest) / 2) { tg_node_t *dest = e->tge_dest; /* * If the conjectured type is less than half of the * size of the object, we might be dealing with a * polymorphic type. It's dangerous to descend in * this case -- if our conjectured type is larger than * the actual type, we will mispropagate. (See the * description for phenomenon (c) in the block comment * for how this condition can arise.) We therefore * only descend if we are in pass4 and there is only * one inference for this node. */ if (tg_pass < TG_PASS4) continue; if (dest->tgn_typelist == NULL || dest->tgn_typelist->tgt_next != NULL) { /* * There is either no inference for this node, * or more than one -- in either case, chicken * out. */ continue; } } if (free != NULL) { todo = free; free = free->tgtd_next; } else { todo = mdb_alloc(sizeof (tg_todo_t), UM_SLEEP); } todo->tgtd_node = e->tge_dest; todo->tgtd_type = ntype; todo->tgtd_offs = e->tge_destoffs; todo->tgtd_next = NULL; if (last == NULL) { first = last = todo; } else { last->tgtd_next = todo; last = todo; } } /* * If this was from a to-do list, it needs to be freed. */ if (this != NULL) { this->tgtd_next = free; free = this; } /* * Now peel something off of the to-do list. */ if (first != NULL) { this = first; first = first->tgtd_next; if (first == NULL) last = NULL; node = this->tgtd_node; offs = this->tgtd_offs; type = this->tgtd_type; goto again; } /* * Nothing more to do -- free the to-do list. */ while (free != NULL) { this = free->tgtd_next; mdb_free(free, sizeof (tg_todo_t)); free = this; } } static void typegraph_pass1(void) { int i; tg_pass = TG_PASS1; for (i = 0; i < tg_nnodes; i++) typegraph_pass1_node(&tg_node[i], tg_node[i].tgn_type); } static void typegraph_pass2_node(tg_node_t *node) { mdb_ctf_id_t type, ntype; size_t tsize, nsize, rem, offs, limit; uintptr_t base, addr; int fam, kind; tg_type_t *tp, *found = NULL; if (mdb_ctf_type_valid(node->tgn_type)) return; for (tp = node->tgn_typelist; tp != NULL; tp = tp->tgt_next) { if ((kind = mdb_ctf_type_kind(tp->tgt_type)) == CTF_K_UNION) { /* * Fucking unions... */ found = NULL; break; } if (kind == CTF_K_POINTER || kind == CTF_K_STRUCT) { if (found != NULL) { /* * There's more than one interpretation for * this structure; for now, we punt. */ found = NULL; break; } found = tp; } } if (found == NULL || (found->tgt_flags & (TG_TYPE_ARRAY | TG_TYPE_NOTARRAY))) return; fam = typegraph_hasfam(type = found->tgt_type, &ntype); if (fam) { /* * If this structure has a flexible array member, and the * FAM type isn't a struct or pointer, we're going to treat * it as if it did not have a FAM. */ kind = mdb_ctf_type_kind(ntype); if (kind != CTF_K_POINTER && kind != CTF_K_STRUCT) fam = 0; } tsize = typegraph_size(type); nsize = TG_NODE_SIZE(node); if (!fam) { /* * If this doesn't have a flexible array member, and our * preferred type is greater than half the size of the node, we * won't try to treat it as an array. */ if (tsize > nsize / 2) return; if ((rem = (nsize % tsize)) != 0) { /* * If the next-smaller cache size is zero, we were * expecting the type size to evenly divide the node * size -- we must not have the right type. */ if (node->tgn_smaller == 0) return; if (nsize - rem <= node->tgn_smaller) { /* * If this were really an array of this type, * we would have allocated it out of a smaller * cache -- it's either not an array (e.g., * it's a bigger, unknown structure) or it's an * array of some other type. In either case, * we punt. */ return; } } } /* * So far, this looks like it might be an array. */ if (node->tgn_smaller != 0) { limit = node->tgn_smaller; } else { limit = TG_NODE_SIZE(node); } base = node->tgn_base; if (fam) { found->tgt_flags |= TG_TYPE_HASFAM; for (offs = 0; offs < limit; ) { ntype = typegraph_type_offset(type, offs, NULL, NULL); if (!mdb_ctf_type_valid(ntype)) { offs++; continue; } if (!typegraph_couldbe(base + offs, ntype)) { found->tgt_flags |= TG_TYPE_NOTARRAY; return; } offs += mdb_ctf_type_size(ntype); } } else { for (offs = 0; offs < tsize; ) { ntype = typegraph_type_offset(type, offs, NULL, NULL); if (!mdb_ctf_type_valid(ntype)) { offs++; continue; } for (addr = base + offs; addr < base + limit; addr += tsize) { if (typegraph_couldbe(addr, ntype)) continue; found->tgt_flags |= TG_TYPE_NOTARRAY; return; } offs += mdb_ctf_type_size(ntype); continue; } } /* * Now mark this type as an array, and reattempt pass1 from this node. */ found->tgt_flags |= TG_TYPE_ARRAY; typegraph_pass1_node(node, type); } static void typegraph_pass2(void) { int i; tg_pass = TG_PASS2; do { tg_improved = 0; for (i = 0; i < tg_nnodes; i++) typegraph_pass2_node(&tg_node[i]); } while (tg_improved); } static void typegraph_pass3(void) { tg_node_t *node; tg_type_t *tp; size_t i; uintptr_t loffs; tg_pass = TG_PASS3; loffs = offsetof(tg_node_t, tgn_typelist); again: /* * In this pass, we're going to coalesce types. We're looking for * nodes where one possible type is a structure, and another is * either a CTF_K_INTEGER variant (e.g. "char", "void") or a * CTF_K_FORWARD (an opaque forward definition). * * N.B. It might appear to be beneficial to coalesce types when * the possibilities include two structures, and one is contained * within the other (e.g., "door_node" contains a "vnode" as its * first member; "vnode" could be smooshed, leaving just "door_node"). * This optimization is overly aggressive, however: there are too * many places where we pull stunts with structures such that they're * actually polymorphic (e.g., "nc_cache" and "ncache"). Performing * this optimization would run the risk of false propagation -- * which we want to avoid if at all possible. */ for (i = 0; i < tg_nnodes; i++) { tg_type_t **list; list = (tg_type_t **)((uintptr_t)(node = &tg_node[i]) + loffs); if (mdb_ctf_type_valid(node->tgn_type)) continue; if (*list == NULL) continue; /* * First, scan for type CTF_K_STRUCT. If we find it, eliminate * everything that's a CTF_K_INTEGER or CTF_K_FORWARD. */ for (tp = *list; tp != NULL; tp = tp->tgt_next) { if (mdb_ctf_type_kind(tp->tgt_type) == CTF_K_STRUCT) break; } if (tp != NULL) { tg_type_t *prev = NULL, *next; for (tp = *list; tp != NULL; tp = next) { int kind = mdb_ctf_type_kind(tp->tgt_type); next = tp->tgt_next; if (kind == CTF_K_INTEGER || kind == CTF_K_FORWARD) { if (prev == NULL) { *list = next; } else { prev->tgt_next = next; } mdb_free(tp, sizeof (tg_type_t)); } else { prev = tp; } } } } if (loffs == offsetof(tg_node_t, tgn_typelist)) { loffs = offsetof(tg_node_t, tgn_fraglist); goto again; } } static void typegraph_pass4_node(tg_node_t *node) { tg_edge_t *e; mdb_ctf_id_t type, ntype; tg_node_t *src = NULL; int kind; if (mdb_ctf_type_valid(node->tgn_type)) return; if (node->tgn_typelist != NULL) return; mdb_ctf_type_invalidate(&type); /* * Now we want to iterate over all incoming edges. If we can find an * incoming edge pointing to offset 0 from a node of known or * conjectured type, check the types of the referring node. */ for (e = node->tgn_incoming; e != NULL; e = e->tge_nextin) { tg_node_t *n = e->tge_src; if (e->tge_destoffs != 0) continue; if (!mdb_ctf_type_valid(ntype = n->tgn_type)) { if (n->tgn_typelist != NULL && n->tgn_typelist->tgt_next == NULL) { ntype = n->tgn_typelist->tgt_type; } if (!mdb_ctf_type_valid(ntype)) continue; } kind = mdb_ctf_type_kind(ntype); if (kind != CTF_K_STRUCT && kind != CTF_K_POINTER) continue; if (src != NULL && mdb_ctf_type_cmp(type, ntype) != 0) { /* * We have two valid, potentially conflicting * interpretations for this node -- chicken out. */ src = NULL; break; } src = n; type = ntype; } if (src != NULL) typegraph_pass1_node(src, type); } static void typegraph_pass4(void) { size_t i, conjectured[2], gen = 0; conjectured[1] = tg_nnodes; tg_pass = TG_PASS4; do { conjectured[gen] = 0; for (i = 0; i < tg_nnodes; i++) { if (tg_node[i].tgn_typelist != NULL) conjectured[gen]++; typegraph_pass4_node(&tg_node[i]); } /* * Perform another pass3 to coalesce any new conflicts. */ typegraph_pass3(); tg_pass = TG_PASS4; gen ^= 1; } while (conjectured[gen ^ 1] < conjectured[gen]); } static void typegraph_postpass_node(tg_node_t *node) { size_t total = 0; tg_edge_t *e, *edge = node->tgn_outgoing; tg_poststate_t *free = NULL, *stack = NULL, *state; tg_node_t *dest; if (node->tgn_postmarked) return; push: node->tgn_postmarked = 1; node->tgn_reach = 0; pop: for (e = edge; e != NULL; e = e->tge_nextout) { dest = e->tge_dest; if (dest->tgn_postmarked) continue; /* * Add a new element and descend. */ if (free == NULL) { state = mdb_alloc(sizeof (tg_poststate_t), UM_SLEEP); } else { state = free; free = free->tgps_next; } state->tgps_node = node; state->tgps_edge = e; state->tgps_total = total; state->tgps_next = stack; stack = state; node = dest; edge = dest->tgn_outgoing; goto push; } if (!mdb_ctf_type_valid(node->tgn_type) && node->tgn_typelist == NULL && node->tgn_fraglist == NULL) { /* * We are an unknown node; our count must reflect this. */ node->tgn_reach++; } /* * Now we need to check for state to pop. */ if ((state = stack) != NULL) { edge = state->tgps_edge; node = state->tgps_node; total = state->tgps_total; dest = edge->tge_dest; stack = state->tgps_next; state->tgps_next = free; free = state; if (!mdb_ctf_type_valid(dest->tgn_type) && dest->tgn_typelist == NULL && dest->tgn_fraglist == NULL) { /* * We only sum our child's reach into our own if * that child is of unknown type. This prevents long * chains of non-increasing reach. */ node->tgn_reach += dest->tgn_reach; } edge = edge->tge_nextout; goto pop; } /* * We need to free our freelist. */ while (free != NULL) { state = free; free = free->tgps_next; mdb_free(state, sizeof (tg_poststate_t)); } } static void typegraph_postpass(void) { int i, max = 0; tg_node_t *node, *maxnode = NULL; char c[256]; for (i = 0; i < tg_nnodes; i++) tg_node[i].tgn_postmarked = 0; /* * From those nodes with unknown type and no outgoing edges, we want * to eminate towards the root. */ for (i = tg_nnodes - tg_nanchored; i < tg_nnodes; i++) { node = &tg_node[i]; typegraph_postpass_node(node); } for (i = 0; i < tg_nnodes - tg_nanchored; i++) { node = &tg_node[i]; if (mdb_ctf_type_valid(node->tgn_type)) continue; if (node->tgn_reach < max) continue; maxnode = node; max = node->tgn_reach; } typegraph_stat_str("pass", "post"); if (maxnode != NULL) { mdb_snprintf(c, sizeof (c), "%p", maxnode->tgn_base, maxnode->tgn_reach); } else { strcpy(c, "-"); } typegraph_stat_print("nodes", tg_nnodes - tg_nanchored); typegraph_stat_str("greatest unknown node reach", c); typegraph_stat_perc("reachable unknown nodes", max, tg_nnodes - tg_nanchored); typegraph_stat_time(1); } static void typegraph_allpass(int first) { size_t i; tg_edge_t *e; if (!first) tg_start = gethrtime(); for (i = 0; i < tg_nnodes; i++) { tg_node[i].tgn_marked = 0; tg_node[i].tgn_postmarked = 0; for (e = tg_node[i].tgn_incoming; e != NULL; e = e->tge_nextin) e->tge_marked = 0; } typegraph_pass1(); typegraph_stats(); typegraph_pass2(); typegraph_stats(); typegraph_pass3(); typegraph_stats(); typegraph_pass4(); typegraph_stats(); typegraph_postpass(); } /*ARGSUSED*/ static int typegraph_modctl(uintptr_t addr, const struct modctl *m, int *ignored) { struct typegraph_ctf_module mod; tg_node_t *node; mdb_ctf_id_t type; if (m->mod_mp == NULL) return (WALK_NEXT); if (mdb_ctf_vread(&mod, "struct module", "struct typegraph_ctf_module", (uintptr_t)m->mod_mp, 0) == -1) { mdb_warn("couldn't read modctl %p's module", addr); return (WALK_NEXT); } /* * As long as we're here, we're going to mark the address pointed * to by mod_mp as a "struct module" (mod_mp is defined to be a * void *). Yes, this is a horrible kludge -- but it's not like * this code isn't already depending on the fact that mod_mp is * actually a pointer to "struct module" (see the mdb_vread(), above). * Without this, we can't identify any of the objects allocated by * krtld. */ if ((node = typegraph_search((uintptr_t)m->mod_mp)) != NULL) { if (mdb_ctf_lookup_by_name("struct module", &type) != -1) node->tgn_type = type; } typegraph_build((uintptr_t)mod.data, mod.data_size); typegraph_build((uintptr_t)mod.bss, mod.bss_size); return (WALK_NEXT); } static void typegraph_sort(void) { size_t i; if (tg_sorted) mdb_free(tg_sorted, tg_nsorted * sizeof (tg_node_t *)); tg_nsorted = tg_nnodes; tg_sorted = mdb_alloc(tg_nsorted * sizeof (tg_node_t *), UM_SLEEP); for (i = 0; i < tg_nsorted; i++) tg_sorted[i] = &tg_node[i]; qsort(tg_sorted, tg_nsorted, sizeof (tg_node_t *), typegraph_nodecmp); } static void typegraph_known_node(uintptr_t addr, const char *typename) { tg_node_t *node; mdb_ctf_id_t type; if ((node = typegraph_search(addr)) == NULL) { mdb_warn("couldn't find node corresponding to " "%s at %p\n", typename, addr); return; } if (mdb_ctf_lookup_by_name(typename, &type) == -1) { mdb_warn("couldn't find type for '%s'", typename); return; } node->tgn_type = type; } /* * There are a few important nodes that are impossible to figure out without * some carnal knowledge. */ static void typegraph_known_nodes(void) { uintptr_t segkp; if (mdb_readvar(&segkp, "segkp") == -1) { mdb_warn("couldn't read 'segkp'"); } else { struct seg seg; if (mdb_vread(&seg, sizeof (seg), segkp) == -1) { mdb_warn("couldn't read seg at %p", segkp); } else { typegraph_known_node((uintptr_t)seg.s_data, "struct segkp_segdata"); } } } /*ARGSUSED*/ int typegraph(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { size_t est = 0; tg_node_t *tgp; kmem_cache_t c; tg_stats_t stats; mdb_ctf_id_t type; int wasbuilt = tg_built; uintptr_t kstat_arena; uint_t perc; int i; if (!mdb_prop_postmortem) { mdb_warn("typegraph: can only be run on a system " "dump; see dumpadm(8)\n"); return (DCMD_ERR); } tg_verbose = 0; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &tg_verbose, NULL) != argc) return (DCMD_USAGE); if (tg_built) goto trace; tg_start = gethrtime(); typegraph_stat_str("pass", "initial"); typegraph_typetab_init(); /* * First, we need an estimate on the number of buffers. */ if (mdb_walk("kmem_cache", (mdb_walk_cb_t)typegraph_estimate, &est) == -1) { mdb_warn("couldn't walk 'kmem_cache'"); return (DCMD_ERR); } if (mdb_walk("modctl", (mdb_walk_cb_t)typegraph_estimate_modctl, &est) == -1) { mdb_warn("couldn't walk 'modctl'"); return (DCMD_ERR); } if (mdb_walk("vmem", (mdb_walk_cb_t)typegraph_estimate_vmem, &est) == -1) { mdb_warn("couldn't walk 'vmem'"); return (DCMD_ERR); } typegraph_stat_print("maximum nodes", est); tgp = tg_node = mdb_zalloc(sizeof (tg_node_t) * est, UM_SLEEP); for (i = 0; i < est; i++) mdb_ctf_type_invalidate(&tg_node[i].tgn_type); if (mdb_walk("vmem", (mdb_walk_cb_t)typegraph_vmem, &tgp) == -1) { mdb_warn("couldn't walk 'vmem'"); return (DCMD_ERR); } if (mdb_walk("kmem_cache", (mdb_walk_cb_t)typegraph_kmem, &tgp) == -1) { mdb_warn("couldn't walk 'kmem_cache'"); return (DCMD_ERR); } tg_nnodes = tgp - tg_node; typegraph_stat_print("actual nodes", tg_nnodes); typegraph_sort(); if (mdb_ctf_lookup_by_name("kthread_t", &type) == -1) { mdb_warn("couldn't find 'kthread_t'"); return (DCMD_ERR); } if (mdb_walk("thread", (mdb_walk_cb_t)typegraph_thread, &type) == -1) { mdb_warn("couldn't walk 'thread'"); return (DCMD_ERR); } if (mdb_ctf_lookup_by_name("ekstat_t", &type) == -1) { mdb_warn("couldn't find 'ekstat_t'"); return (DCMD_ERR); } if (mdb_readvar(&kstat_arena, "kstat_arena") == -1) { mdb_warn("couldn't read 'kstat_arena'"); return (DCMD_ERR); } if (mdb_pwalk("vmem_alloc", (mdb_walk_cb_t)typegraph_kstat, &type, kstat_arena) == -1) { mdb_warn("couldn't walk kstat vmem arena"); return (DCMD_ERR); } if (mdb_walk("modctl", (mdb_walk_cb_t)typegraph_modctl, NULL) == -1) { mdb_warn("couldn't walk 'modctl'"); return (DCMD_ERR); } typegraph_stat_print("anchored nodes", tg_nanchored); tg_nnodes += tg_nanchored; typegraph_sort(); typegraph_known_nodes(); typegraph_stat_time(0); tg_built = 1; trace: if (!wasbuilt || !(flags & DCMD_ADDRSPEC)) { typegraph_allpass(!wasbuilt); return (DCMD_OK); } bzero(&stats, sizeof (stats)); /* * If we've been given an address, it's a kmem cache. */ if (mdb_vread(&c, sizeof (c), addr) == -1) { mdb_warn("couldn't read kmem_cache at %p", addr); return (DCMD_ERR); } if (mdb_pwalk("kmem", (mdb_walk_cb_t)typegraph_stats_buffer, &stats, addr) == -1) { mdb_warn("can't walk kmem for cache %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) mdb_printf("%-25s %7s %7s %7s %7s %7s %7s %5s\n", "NAME", "BUFS", "NODES", "UNMRK", "KNOWN", "INFER", "FRAG", "HIT%"); if (stats.tgs_nodes) { perc = ((stats.tgs_known + stats.tgs_typed + stats.tgs_frag) * 1000) / stats.tgs_nodes; } else { perc = 0; } mdb_printf("%-25s %7ld %7ld %7ld %7ld %7ld %7ld %3d.%1d\n", c.cache_name, stats.tgs_buffers, stats.tgs_nodes, stats.tgs_unmarked, stats.tgs_known, stats.tgs_typed, stats.tgs_frag, perc / 10, perc % 10); return (DCMD_OK); } int typegraph_built(void) { if (!tg_built) { mdb_warn("type graph not yet built; run ::typegraph.\n"); return (0); } return (1); } int whattype(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { tg_node_t *node; tg_edge_t *e; char buf[MDB_SYM_NAMLEN]; tg_type_t *tp; int verbose = 0; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (!typegraph_built()) return (DCMD_ABORT); if ((node = typegraph_search(addr)) == NULL) { mdb_warn("%p does not correspond to a node.\n", addr); return (DCMD_OK); } if (!verbose) { mdb_printf("%p is %p+%p, ", addr, node->tgn_base, addr - node->tgn_base); if (mdb_ctf_type_valid(node->tgn_type)) { mdb_printf("%s\n", mdb_ctf_type_name(node->tgn_type, buf, sizeof (buf))); return (DCMD_OK); } if ((tp = node->tgn_typelist) == NULL) { if ((tp = node->tgn_fraglist) == NULL) { mdb_printf("unknown type\n"); return (DCMD_OK); } } if (tp->tgt_next == NULL && mdb_ctf_type_valid(tp->tgt_type)) { int kind = mdb_ctf_type_kind(tp->tgt_type); size_t offs = tp->tgt_redge->tge_destoffs; mdb_printf("possibly %s%s ", tp->tgt_flags & TG_TYPE_ARRAY ? "array of " : "", typegraph_type_name(tp->tgt_type, tp->tgt_utype)); if (kind != CTF_K_STRUCT && kind != CTF_K_UNION && mdb_ctf_type_valid(tp->tgt_rtype) && tp->tgt_rmember != NULL) { mdb_printf("(%s.%s) ", mdb_ctf_type_name(tp->tgt_rtype, buf, sizeof (buf)), tp->tgt_rmember); } if (offs != 0) mdb_printf("at %p", node->tgn_base + offs); mdb_printf("\n"); return (DCMD_OK); } mdb_printf("possibly one of the following:\n"); for (; tp != NULL; tp = tp->tgt_next) { size_t offs = tp->tgt_redge->tge_destoffs; mdb_printf(" %s%s ", tp->tgt_flags & TG_TYPE_ARRAY ? "array of " : "", typegraph_type_name(tp->tgt_type, tp->tgt_utype)); if (offs != 0) mdb_printf("at %p ", node->tgn_base + offs); mdb_printf("(from %p+%p, type %s)\n", tp->tgt_redge->tge_src->tgn_base, tp->tgt_redge->tge_srcoffs, mdb_ctf_type_name(tp->tgt_rtype, buf, sizeof (buf)) != NULL ? buf : ""); } mdb_printf("\n"); return (DCMD_OK); } mdb_printf("%-?s %-?s %-29s %5s %5s %s\n", "BASE", "LIMIT", "TYPE", "SIZE", "REACH", "MRK"); mdb_printf("%-?p %-?p %-29s %5d %5d %s\n", node->tgn_base, node->tgn_limit, mdb_ctf_type_name(node->tgn_type, buf, sizeof (buf)) != NULL ? buf : "", typegraph_size(node->tgn_type), node->tgn_reach, node->tgn_marked ? "yes" : "no"); mdb_printf("\n"); mdb_printf(" %-20s %?s %8s %-20s %s\n", "INFERENCE", "FROM", "SRCOFFS", "REFTYPE", "REFMEMBER"); for (tp = node->tgn_typelist; tp != NULL; tp = tp->tgt_next) { mdb_printf(" %-20s %?p %8p %-20s %s\n", typegraph_type_name(tp->tgt_type, tp->tgt_utype), tp->tgt_redge->tge_src->tgn_base, tp->tgt_redge->tge_srcoffs, mdb_ctf_type_name(tp->tgt_rtype, buf, sizeof (buf)) != NULL ? buf : "", tp->tgt_rmember != NULL ? tp->tgt_rmember : "-"); } mdb_printf("\n"); mdb_printf(" %-20s %?s %8s %-20s %s\n", "FRAGMENT", "FROM", "SRCOFFS", "REFTYPE", "REFMEMBER"); for (tp = node->tgn_fraglist; tp != NULL; tp = tp->tgt_next) { mdb_printf(" %-20s %?p %8p %-20s %s\n", typegraph_type_name(tp->tgt_type, tp->tgt_utype), tp->tgt_redge->tge_src->tgn_base, tp->tgt_redge->tge_srcoffs, mdb_ctf_type_name(tp->tgt_rtype, buf, sizeof (buf)) != NULL ? buf : "", tp->tgt_rmember != NULL ? tp->tgt_rmember : "-"); } mdb_printf("\n"); mdb_printf(" %?s %8s %8s %6s %6s %5s\n", "FROM", "SRCOFFS", "DESTOFFS", "MARKED", "STATUS", "REACH"); for (e = node->tgn_incoming; e != NULL; e = e->tge_nextin) { tg_node_t *n = e->tge_src; mdb_printf(" %?p %8p %8p %6s %6s %ld\n", n->tgn_base, e->tge_srcoffs, e->tge_destoffs, e->tge_marked ? "yes" : "no", mdb_ctf_type_valid(n->tgn_type) ? "known" : n->tgn_typelist != NULL ? "inferd" : n->tgn_fraglist != NULL ? "frgmnt" : "unknwn", n->tgn_reach); } mdb_printf("\n %?s %8s %8s %6s %6s %5s\n", "TO", "SRCOFFS", "DESTOFFS", "MARKED", "STATUS", "REACH"); for (e = node->tgn_outgoing; e != NULL; e = e->tge_nextout) { tg_node_t *n = e->tge_dest; mdb_printf(" %?p %8p %8p %6s %6s %ld\n", n->tgn_base, e->tge_srcoffs, e->tge_destoffs, e->tge_marked ? "yes" : "no", mdb_ctf_type_valid(n->tgn_type) ? "known" : n->tgn_typelist != NULL ? "inferd" : n->tgn_fraglist != NULL ? "frgmnt" : "unknwn", n->tgn_reach); } mdb_printf("\n"); return (DCMD_OK); } int istype(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { tg_node_t *node; mdb_ctf_id_t type; if (!(flags & DCMD_ADDRSPEC) || argc != 1 || argv[0].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (!typegraph_built()) return (DCMD_ABORT); /* * Determine the node corresponding to the passed address. */ if ((node = typegraph_search(addr)) == NULL) { mdb_warn("%p not found\n", addr); return (DCMD_ERR); } /* * Now look up the specified type. */ if (mdb_ctf_lookup_by_name(argv[0].a_un.a_str, &type) == -1) { mdb_warn("could not find type %s", argv[0].a_un.a_str); return (DCMD_ERR); } node->tgn_type = type; typegraph_allpass(0); return (DCMD_OK); } /*ARGSUSED*/ int notype(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { tg_node_t *node; if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (!typegraph_built()) return (DCMD_ABORT); if ((node = typegraph_search(addr)) == NULL) { mdb_warn("%p not found\n", addr); return (DCMD_ERR); } mdb_ctf_type_invalidate(&node->tgn_type); typegraph_allpass(0); return (DCMD_OK); } int typegraph_walk_init(mdb_walk_state_t *wsp) { wsp->walk_data = (void *)0; return (WALK_NEXT); } int typeconflict_walk_step(mdb_walk_state_t *wsp) { size_t ndx; tg_node_t *node = NULL; for (ndx = (size_t)wsp->walk_data; ndx < tg_nnodes; ndx++) { node = &tg_node[ndx]; if (mdb_ctf_type_valid(node->tgn_type)) continue; if (node->tgn_typelist == NULL) continue; if (node->tgn_typelist->tgt_next == NULL) continue; break; } if (ndx == tg_nnodes) return (WALK_DONE); wsp->walk_data = (void *)++ndx; return (wsp->walk_callback(node->tgn_base, NULL, wsp->walk_cbdata)); } int typeunknown_walk_step(mdb_walk_state_t *wsp) { size_t ndx; tg_node_t *node = NULL; for (ndx = (size_t)wsp->walk_data; ndx < tg_nnodes; ndx++) { node = &tg_node[ndx]; if (mdb_ctf_type_valid(node->tgn_type)) continue; if (node->tgn_typelist != NULL) continue; if (node->tgn_fraglist != NULL) continue; break; } if (ndx == tg_nnodes) return (WALK_DONE); wsp->walk_data = (void *)++ndx; return (wsp->walk_callback(node->tgn_base, NULL, wsp->walk_cbdata)); } #define FINDLOCKS_DEPTH 32 typedef struct foundlock { uintptr_t fnd_addr; uintptr_t fnd_owner; const char *fnd_member[FINDLOCKS_DEPTH]; mdb_ctf_id_t fnd_parent; tg_node_t *fnd_node; } foundlock_t; typedef struct findlocks { uintptr_t fl_addr; uintptr_t fl_thread; size_t fl_ndx; size_t fl_nlocks; foundlock_t *fl_locks; mdb_ctf_id_t fl_parent; tg_node_t *fl_node; const char *fl_member[FINDLOCKS_DEPTH - 1]; int fl_depth; } findlocks_t; /*ARGSUSED*/ static int findlocks_owner(uintptr_t addr, const void *data, void *owner) { *((uintptr_t *)owner) = addr; return (WALK_NEXT); } static int findlocks_findmutex(const char *name, mdb_ctf_id_t type, ulong_t offs, findlocks_t *fl) { static int called = 0; static mdb_ctf_id_t mutex; static mdb_ctf_id_t thread; mdb_ctf_id_t parent = fl->fl_parent; uintptr_t addr = fl->fl_addr; int kind, depth = fl->fl_depth, i; foundlock_t *found; offs /= NBBY; if (!called) { if (mdb_ctf_lookup_by_name("kmutex_t", &mutex) == -1) { mdb_warn("can't find 'kmutex_t' type"); return (1); } if (!mdb_ctf_type_valid(mutex = typegraph_resolve(mutex))) { mdb_warn("can't resolve 'kmutex_t' type"); return (1); } if (mdb_ctf_lookup_by_name("kthread_t", &thread) == -1) { mdb_warn("can't find 'kthread_t' type"); return (1); } if (!mdb_ctf_type_valid(thread = typegraph_resolve(thread))) { mdb_warn("can't resolve 'kthread_t' type"); return (1); } called = 1; } if (!mdb_ctf_type_valid(type)) return (0); type = typegraph_resolve(type); kind = mdb_ctf_type_kind(type); if (!mdb_ctf_type_valid(type)) return (0); if (kind == CTF_K_ARRAY) { mdb_ctf_arinfo_t arr; ssize_t size; if (mdb_ctf_array_info(type, &arr) == -1) return (0); type = typegraph_resolve(arr.mta_contents); if (!mdb_ctf_type_valid(type)) return (0); /* * Small optimization: don't bother running through the array * if we know that we can't process the type. */ kind = mdb_ctf_type_kind(type); size = mdb_ctf_type_size(type); if (kind == CTF_K_POINTER || kind == CTF_K_INTEGER) return (0); for (i = 0; i < arr.mta_nelems; i++) { fl->fl_addr = addr + offs + (i * size); findlocks_findmutex(name, type, 0, fl); } fl->fl_addr = addr; return (0); } if (kind != CTF_K_STRUCT) return (0); if (mdb_ctf_type_cmp(type, mutex) == 0) { mdb_ctf_id_t ttype; uintptr_t owner = 0; tg_node_t *node; if (mdb_pwalk("mutex_owner", findlocks_owner, &owner, addr + offs) == -1) { return (0); } /* * Check to see if the owner is a thread. */ if (owner == 0 || (node = typegraph_search(owner)) == NULL) return (0); if (!mdb_ctf_type_valid(node->tgn_type)) return (0); ttype = typegraph_resolve(node->tgn_type); if (!mdb_ctf_type_valid(ttype)) return (0); if (mdb_ctf_type_cmp(ttype, thread) != 0) return (0); if (fl->fl_thread != 0 && owner != fl->fl_thread) return (0); if (fl->fl_ndx >= fl->fl_nlocks) { size_t nlocks, osize, size; foundlock_t *locks; if ((nlocks = (fl->fl_nlocks << 1)) == 0) nlocks = 1; osize = fl->fl_nlocks * sizeof (foundlock_t); size = nlocks * sizeof (foundlock_t); locks = mdb_zalloc(size, UM_SLEEP); if (fl->fl_locks) { bcopy(fl->fl_locks, locks, osize); mdb_free(fl->fl_locks, osize); } fl->fl_locks = locks; fl->fl_nlocks = nlocks; } found = &fl->fl_locks[fl->fl_ndx++]; found->fnd_addr = (uintptr_t)addr + offs; found->fnd_owner = owner; for (i = 0; i < fl->fl_depth; i++) found->fnd_member[i] = fl->fl_member[i]; found->fnd_member[i] = name; found->fnd_parent = fl->fl_parent; found->fnd_node = fl->fl_node; return (0); } fl->fl_addr = (uintptr_t)addr + offs; if (name == NULL) { fl->fl_parent = type; } else if (depth < FINDLOCKS_DEPTH - 1) { fl->fl_member[depth] = name; fl->fl_depth++; } mdb_ctf_member_iter(type, (mdb_ctf_member_f *)findlocks_findmutex, fl, 0); fl->fl_addr = addr; fl->fl_parent = parent; fl->fl_depth = depth; return (0); } static void findlocks_node(tg_node_t *node, findlocks_t *fl) { mdb_ctf_id_t type = node->tgn_type, ntype; int kind; tg_type_t *tp, *found = NULL; if (!mdb_ctf_type_valid(type)) { mdb_ctf_type_invalidate(&type); for (tp = node->tgn_typelist; tp != NULL; tp = tp->tgt_next) { kind = mdb_ctf_type_kind(ntype = tp->tgt_type); if (kind == CTF_K_UNION) { /* * Insert disparaging comment about unions here. */ return; } if (kind != CTF_K_STRUCT && kind != CTF_K_ARRAY) continue; if (found != NULL) { /* * There are multiple interpretations for this * node; we have to punt. */ return; } found = tp; } } if (found != NULL) type = found->tgt_type; fl->fl_parent = type; fl->fl_node = node; /* * We have our type. Now iterate for locks. Note that we don't yet * deal with locks in flexible array members. */ if (found != NULL && (found->tgt_flags & TG_TYPE_ARRAY) && !(found->tgt_flags & TG_TYPE_HASFAM)) { uintptr_t base, limit = node->tgn_limit; size_t size = mdb_ctf_type_size(found->tgt_type); for (base = node->tgn_base; base < limit; base += size) { fl->fl_addr = base; findlocks_findmutex(NULL, type, 0, fl); } } else { fl->fl_addr = node->tgn_base; findlocks_findmutex(NULL, type, 0, fl); } if (mdb_ctf_type_valid(type)) return; for (tp = node->tgn_fraglist; tp != NULL; tp = tp->tgt_next) { kind = mdb_ctf_type_kind(ntype = tp->tgt_type); if (kind != CTF_K_STRUCT && kind != CTF_K_ARRAY) continue; fl->fl_addr = node->tgn_base + tp->tgt_redge->tge_destoffs; fl->fl_parent = ntype; findlocks_findmutex(NULL, ntype, 0, fl); } } /*ARGSUSED*/ int findlocks(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { size_t i, j; findlocks_t fl; if (argc != 0) return (DCMD_USAGE); if (!typegraph_built()) return (DCMD_ABORT); if (!(flags & DCMD_ADDRSPEC)) addr = 0; bzero(&fl, sizeof (fl)); fl.fl_thread = addr; for (i = 0; i < tg_nnodes; i++) { findlocks_node(&tg_node[i], &fl); } for (i = 0; i < fl.fl_ndx; i++) { foundlock_t *found = &fl.fl_locks[i]; char buf[MDB_SYM_NAMLEN]; if (found->fnd_member[0] != NULL) { mdb_printf("%p (%s", found->fnd_addr, mdb_ctf_type_name(found->fnd_parent, buf, sizeof (buf))); for (j = 0; found->fnd_member[j] != NULL; j++) mdb_printf(".%s", found->fnd_member[j]); mdb_printf(") is owned by %p\n", found->fnd_owner); } else { if (found->fnd_node->tgn_incoming == NULL) { mdb_printf("%p (%a) is owned by %p\n", found->fnd_addr, found->fnd_addr, found->fnd_owner); } else { mdb_printf("%p is owned by %p\n", found->fnd_addr, found->fnd_owner); } } } mdb_printf("findlocks: nota bene: %slocks may be held", fl.fl_nlocks ? "other " : ""); if (addr == 0) { mdb_printf("\n"); } else { mdb_printf(" by %p\n", addr); } if (fl.fl_nlocks) mdb_free(fl.fl_locks, fl.fl_nlocks * sizeof (foundlock_t)); return (DCMD_OK); } /* * ::findfalse: Using type knowledge to detect potential false sharing * * In caching SMP systems, memory is kept coherent through bus-based snooping * protocols. Under these protocols, only a single cache may have a given line * of memory in a dirty state. If a different cache wishes to write to the * dirty line, the new cache must first read-to-own the dirty line from the * owning cache. The size of the line used for coherence (the coherence * granularity) has an immediate ramification for parallel software: because * only one cache may own a line at a given time, one wishes to avoid a * situation where two or more small, disjoint data structures are both * (a) contained within a single line and (b) accessed in parallel on disjoint * CPUs. This situation -- so-called "false sharing" -- can induce suboptimal * scalability in otherwise scalable software. * * Historically, one has been able to find false sharing only with some * combination of keen intuition and good luck. And where false sharing has * been discovered, it has almost always been after having induced suboptimal * scaling; one has historically not been able to detect false sharing before * the fact. * * Building on the mechanism for postmortem type information, however, we * can -- from a system crash dump -- detect the the potentially most egregious * cases of false sharing. Specifically, after having run through the type * identification passes described above, we can iterate over all nodes, * looking for nodes that satisfy the following criteria: * * (a) The node is an array. That is, the node was either determined to * be of type CTF_K_ARRAY, or the node was inferred to be an array in * pass2 of type identification (described above). * * (b) Each element of the array is a structure that is smaller than the * coherence granularity. * * (c) The total size of the array is greater than the coherence granularity. * * (d) Each element of the array is a structure that contains within it a * synchronization primitive (mutex, readers/writer lock, condition * variable or semaphore). We use the presence of a synchronization * primitive as a crude indicator that the disjoint elements of the * array are accessed in parallel. * * Any node satisfying these criteria is identified as an object that could * potentially suffer from false sharing, and the node's address, symbolic * name (if any), type, type size and total size are provided as output. * * While there are some instances of false sharing that do not meet the * above criteria (e.g., if the synchronization for each element is handled * in a separate structure, or if the elements are only manipulated with * atomic memory operations), these criteria yield many examples of false * sharing without swamping the user with false positives. */ #define FINDFALSE_COHERENCE_SIZE 64 /*ARGSUSED*/ static int findfalse_findsync(const char *name, mdb_ctf_id_t type, ulong_t offs, void *ignored) { int i, kind; static int called = 0; static struct { char *name; mdb_ctf_id_t type; } sync[] = { { "kmutex_t" }, { "krwlock_t" }, { "kcondvar_t" }, { "ksema_t" }, { NULL } }; if (!called) { char *name; called = 1; for (i = 0; (name = sync[i].name) != NULL; i++) { if (mdb_ctf_lookup_by_name(name, &sync[i].type) == -1) { mdb_warn("can't find '%s' type", name); return (0); } sync[i].type = typegraph_resolve(sync[i].type); if (!mdb_ctf_type_valid(sync[i].type)) { mdb_warn("can't resolve '%s' type", name); return (0); } } } /* * See if this type is any of the synchronization primitives. */ if (!mdb_ctf_type_valid(type)) return (0); type = typegraph_resolve(type); for (i = 0; sync[i].name != NULL; i++) { if (mdb_ctf_type_cmp(type, sync[i].type) == 0) { /* * We have a winner! */ return (1); } } if ((kind = mdb_ctf_type_kind(type)) == CTF_K_ARRAY) { mdb_ctf_arinfo_t arr; if (mdb_ctf_array_info(type, &arr) == -1) return (0); type = typegraph_resolve(arr.mta_contents); return (findfalse_findsync(name, type, 0, NULL)); } if (kind != CTF_K_STRUCT) return (0); if (mdb_ctf_member_iter(type, (mdb_ctf_member_f *)findfalse_findsync, NULL, 0) != 0) return (1); return (0); } static void findfalse_node(tg_node_t *node) { mdb_ctf_id_t type = node->tgn_type; tg_type_t *tp, *found = NULL; ssize_t size; int kind; char buf[MDB_SYM_NAMLEN + 1]; GElf_Sym sym; if (!mdb_ctf_type_valid(type)) { mdb_ctf_type_invalidate(&type); for (tp = node->tgn_typelist; tp != NULL; tp = tp->tgt_next) { kind = mdb_ctf_type_kind(tp->tgt_type); if (kind == CTF_K_UNION) { /* * Once again, the unions impede progress... */ return; } if (kind != CTF_K_STRUCT && kind != CTF_K_ARRAY) continue; if (found != NULL) { /* * There are multiple interpretations for this * node; we have to punt. */ return; } found = tp; } } if (found != NULL) type = found->tgt_type; if (!mdb_ctf_type_valid(type)) return; kind = mdb_ctf_type_kind(type); /* * If this isn't an array (or treated as one), it can't induce false * sharing. (Or at least, we can't detect it.) */ if (found != NULL) { if (!(found->tgt_flags & TG_TYPE_ARRAY)) return; if (found->tgt_flags & TG_TYPE_HASFAM) return; } else { if (kind != CTF_K_ARRAY) return; } if (kind == CTF_K_ARRAY) { mdb_ctf_arinfo_t arr; if (mdb_ctf_array_info(type, &arr) == -1) return; type = typegraph_resolve(arr.mta_contents); if (!mdb_ctf_type_valid(type)) return; } size = mdb_ctf_type_size(type); /* * If the size is greater than or equal to the cache line size, it's * not false sharing. (Or at least, the false sharing is benign.) */ if (size >= FINDFALSE_COHERENCE_SIZE) return; if (TG_NODE_SIZE(node) <= FINDFALSE_COHERENCE_SIZE) return; /* * This looks like it could be a falsely shared structure. If this * type contains a mutex, rwlock, semaphore or condition variable, * we're going to report it. */ if (!findfalse_findsync(NULL, type, 0, NULL)) return; mdb_printf("%?p ", node->tgn_base); if (mdb_lookup_by_addr(node->tgn_base, MDB_SYM_EXACT, buf, sizeof (buf), &sym) != -1) { mdb_printf("%-28s ", buf); } else { mdb_printf("%-28s ", "-"); } mdb_printf("%-22s %2d %7ld\n", mdb_ctf_type_name(type, buf, sizeof (buf)), size, TG_NODE_SIZE(node)); } /*ARGSUSED*/ int findfalse(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ssize_t i; if (argc != 0 || (flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (!typegraph_built()) return (DCMD_ABORT); mdb_printf("%?s %-28s %-22s %2s %7s\n", "ADDR", "SYMBOL", "TYPE", "SZ", "TOTSIZE"); /* * We go from the back of the bus and move forward to report false * sharing in named symbols before reporting false sharing in dynamic * structures. */ for (i = tg_nnodes - 1; i >= 0; i--) findfalse_node(&tg_node[i]); return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MDB_TYPEGRAPH_H #define _MDB_TYPEGRAPH_H #ifdef __cplusplus extern "C" { #endif extern int typegraph(uintptr_t, uint_t, int, const mdb_arg_t *); extern int istype(uintptr_t, uint_t, int, const mdb_arg_t *); extern int notype(uintptr_t, uint_t, int, const mdb_arg_t *); extern int whattype(uintptr_t, uint_t, int, const mdb_arg_t *); extern int findfalse(uintptr_t, uint_t, int, const mdb_arg_t *); extern int findlocks(uintptr_t, uint_t, int, const mdb_arg_t *); extern int typegraph_walk_init(mdb_walk_state_t *); extern int typeconflict_walk_step(mdb_walk_state_t *); extern int typeunknown_walk_step(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _MDB_TYPEGRAPH_H */ /* * 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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. */ #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 int vfs_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "rootvfs") == -1) { mdb_warn("failed to read 'rootvfs'"); return (WALK_ERR); } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int vfs_walk_step(mdb_walk_state_t *wsp) { vfs_t vfs; int status; if (mdb_vread(&vfs, sizeof (vfs), wsp->walk_addr) == -1) { mdb_warn("failed to read vfs_t at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, &vfs, wsp->walk_cbdata); if (vfs.vfs_next == wsp->walk_data) return (WALK_DONE); wsp->walk_addr = (uintptr_t)vfs.vfs_next; return (status); } /* * Utility routine to read in a filesystem name given a vfs pointer. If * no vfssw entry for the vfs is available (as is the case with some pseudo- * filesystems), we check against some known problem fs's: doorfs and * portfs. If that fails, we try to guess the filesystem name using * symbol names. fsname should be a buffer of size _ST_FSTYPSZ. */ static int read_fsname(uintptr_t vfsp, char *fsname) { vfs_t vfs; struct vfssw vfssw_entry; GElf_Sym vfssw_sym, test_sym; char testname[MDB_SYM_NAMLEN]; if (mdb_vread(&vfs, sizeof (vfs), vfsp) == -1) { mdb_warn("failed to read vfs %p", vfsp); return (-1); } if (mdb_lookup_by_name("vfssw", &vfssw_sym) == -1) { mdb_warn("failed to find vfssw"); return (-1); } /* * vfssw is an array; we need vfssw[vfs.vfs_fstype]. */ if (mdb_vread(&vfssw_entry, sizeof (vfssw_entry), vfssw_sym.st_value + (sizeof (struct vfssw) * vfs.vfs_fstype)) == -1) { mdb_warn("failed to read vfssw index %d", vfs.vfs_fstype); return (-1); } if (vfs.vfs_fstype != 0) { if (mdb_readstr(fsname, _ST_FSTYPSZ, (uintptr_t)vfssw_entry.vsw_name) == -1) { mdb_warn("failed to find fs name %p", vfssw_entry.vsw_name); return (-1); } return (0); } /* * Do precise detection for certain filesystem types that we * know do not appear in vfssw[], and that we depend upon in other * parts of the code: doorfs and portfs. */ if (mdb_lookup_by_name("door_vfs", &test_sym) != -1) { if (test_sym.st_value == vfsp) { strcpy(fsname, "doorfs"); return (0); } } if (mdb_lookup_by_name("port_vfs", &test_sym) != -1) { if (test_sym.st_value == vfsp) { strcpy(fsname, "portfs"); return (0); } } /* * Heuristic detection for other filesystems that don't have a * vfssw[] entry. These tend to be named _vfs, so we do a * lookup_by_addr and see if we find a symbol of that name. */ if (mdb_lookup_by_addr(vfsp, MDB_SYM_EXACT, testname, sizeof (testname), &test_sym) != -1) { if ((strlen(testname) > 4) && (strcmp(testname + strlen(testname) - 4, "_vfs") == 0)) { testname[strlen(testname) - 4] = '\0'; strncpy(fsname, testname, _ST_FSTYPSZ); return (0); } } mdb_warn("unknown filesystem type for vfs %p", vfsp); return (-1); } /* * Column widths for mount point display in ::fsinfo output. */ #ifdef _LP64 #define FSINFO_MNTLEN 48 #else #define FSINFO_MNTLEN 56 #endif /* ARGSUSED */ int fsinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { vfs_t vfs; int len; int opt_v = 0; char buf[MAXPATHLEN]; char fsname[_ST_FSTYPSZ]; mntopt_t *mntopts; size_t size; int i; int first = 1; char opt[MAX_MNTOPT_STR]; uintptr_t global_zone; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("vfs", "fsinfo", argc, argv) == -1) { mdb_warn("failed to walk file system list"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %-15s %s%\n", "VFSP", "FS", "MOUNT"); if (mdb_vread(&vfs, sizeof (vfs), addr) == -1) { mdb_warn("failed to read vfs_t %p", addr); return (DCMD_ERR); } if ((len = mdb_read_refstr((uintptr_t)vfs.vfs_mntpt, buf, sizeof (buf))) <= 0) strcpy(buf, "??"); else if (!opt_v && (len >= FSINFO_MNTLEN)) /* * In normal mode, we truncate the path to keep the output * clean. In -v mode, we just print the full path. */ strcpy(&buf[FSINFO_MNTLEN - 4], "..."); if (read_fsname(addr, fsname) == -1) return (DCMD_ERR); mdb_printf("%0?p %-15s %s\n", addr, fsname, buf); if (!opt_v) return (DCMD_OK); /* * Print 'resource' string; this shows what we're mounted upon. */ if (mdb_read_refstr((uintptr_t)vfs.vfs_resource, buf, MAXPATHLEN) <= 0) strcpy(buf, "??"); mdb_printf("%?s %s\n", "R:", buf); /* * Print mount options array; it sucks to be a mimic, but we copy * the same logic as in mntvnops.c for adding zone= tags, and we * don't bother with the obsolete dev= option. */ size = vfs.vfs_mntopts.mo_count * sizeof (mntopt_t); mntopts = mdb_alloc(size, UM_SLEEP | UM_GC); if (mdb_vread(mntopts, size, (uintptr_t)vfs.vfs_mntopts.mo_list) == -1) { mdb_warn("failed to read mntopts %p", vfs.vfs_mntopts.mo_list); return (DCMD_ERR); } for (i = 0; i < vfs.vfs_mntopts.mo_count; i++) { if (mntopts[i].mo_flags & MO_SET) { if (mdb_readstr(opt, sizeof (opt), (uintptr_t)mntopts[i].mo_name) == -1) { mdb_warn("failed to read mntopt name %p", mntopts[i].mo_name); return (DCMD_ERR); } if (first) { mdb_printf("%?s ", "O:"); first = 0; } else { mdb_printf(","); } mdb_printf("%s", opt); if (mntopts[i].mo_flags & MO_HASVALUE) { if (mdb_readstr(opt, sizeof (opt), (uintptr_t)mntopts[i].mo_arg) == -1) { mdb_warn("failed to read mntopt " "value %p", mntopts[i].mo_arg); return (DCMD_ERR); } mdb_printf("=%s", opt); } } } if (mdb_readvar(&global_zone, "global_zone") == -1) { mdb_warn("failed to locate global_zone"); return (DCMD_ERR); } if ((vfs.vfs_zone != NULL) && ((uintptr_t)vfs.vfs_zone != global_zone)) { zone_t z; if (mdb_vread(&z, sizeof (z), (uintptr_t)vfs.vfs_zone) == -1) { mdb_warn("failed to read zone"); return (DCMD_ERR); } /* * zone names are much shorter than MAX_MNTOPT_STR */ if (mdb_readstr(opt, sizeof (opt), (uintptr_t)z.zone_name) == -1) { mdb_warn("failed to read zone name"); return (DCMD_ERR); } if (first) { mdb_printf("%?s ", "O:"); } else { mdb_printf(","); } mdb_printf("zone=%s", opt); } return (DCMD_OK); } #define REALVP_DONE 0 #define REALVP_ERR 1 #define REALVP_CONTINUE 2 static int next_realvp(uintptr_t invp, struct vnode *outvn, uintptr_t *outvp) { char fsname[_ST_FSTYPSZ]; *outvp = invp; if (mdb_vread(outvn, sizeof (struct vnode), invp) == -1) { mdb_warn("failed to read vnode at %p", invp); return (REALVP_ERR); } if (read_fsname((uintptr_t)outvn->v_vfsp, fsname) == -1) return (REALVP_ERR); /* * We know how to do 'realvp' for as many filesystems as possible; * for all other filesystems, we assume that the vp we are given * is the realvp. In the kernel, a realvp operation will sometimes * dig through multiple layers. Here, we only fetch the pointer * to the next layer down. This allows dcmds to print out the * various layers. */ if (strcmp(fsname, "fifofs") == 0) { fifonode_t fn; if (mdb_vread(&fn, sizeof (fn), (uintptr_t)outvn->v_data) == -1) { mdb_warn("failed to read fifonode"); return (REALVP_ERR); } *outvp = (uintptr_t)fn.fn_realvp; } else if (strcmp(fsname, "namefs") == 0) { struct namenode nn; if (mdb_vread(&nn, sizeof (nn), (uintptr_t)outvn->v_data) == -1) { mdb_warn("failed to read namenode"); return (REALVP_ERR); } *outvp = (uintptr_t)nn.nm_filevp; } else if (outvn->v_type == VSOCK && outvn->v_stream != NULL) { struct stdata stream; /* * Sockets have a strange and different layering scheme; we * hop over into the sockfs vnode (accessible via the stream * head) if possible. */ if (mdb_vread(&stream, sizeof (stream), (uintptr_t)outvn->v_stream) == -1) { mdb_warn("failed to read stream data"); return (REALVP_ERR); } *outvp = (uintptr_t)stream.sd_vnode; } if (*outvp == invp || *outvp == 0) return (REALVP_DONE); return (REALVP_CONTINUE); } static void pfiles_print_addr(struct sockaddr *addr) { struct sockaddr_in *s_in; struct sockaddr_un *s_un; struct sockaddr_in6 *s_in6; in_port_t port; switch (addr->sa_family) { case AF_INET: /* LINTED: alignment */ s_in = (struct sockaddr_in *)addr; mdb_nhconvert(&port, &s_in->sin_port, sizeof (port)); mdb_printf("AF_INET %I %d ", s_in->sin_addr.s_addr, port); break; case AF_INET6: /* LINTED: alignment */ s_in6 = (struct sockaddr_in6 *)addr; mdb_nhconvert(&port, &s_in6->sin6_port, sizeof (port)); mdb_printf("AF_INET6 %N %d ", &(s_in6->sin6_addr), port); break; case AF_UNIX: s_un = (struct sockaddr_un *)addr; mdb_printf("AF_UNIX %s ", s_un->sun_path); break; default: mdb_printf("AF_?? (%d) ", addr->sa_family); break; } } static int pfiles_get_sonode(vnode_t *v_sock, struct sonode *sonode) { if (mdb_vread(sonode, sizeof (struct sonode), (uintptr_t)v_sock->v_data) == -1) { mdb_warn("failed to read sonode"); return (-1); } return (0); } static int pfiles_get_tpi_sonode(vnode_t *v_sock, sotpi_sonode_t *sotpi_sonode) { struct stdata stream; if (mdb_vread(&stream, sizeof (stream), (uintptr_t)v_sock->v_stream) == -1) { mdb_warn("failed to read stream data"); return (-1); } if (mdb_vread(v_sock, sizeof (vnode_t), (uintptr_t)stream.sd_vnode) == -1) { mdb_warn("failed to read stream vnode"); return (-1); } if (mdb_vread(sotpi_sonode, sizeof (sotpi_sonode_t), (uintptr_t)v_sock->v_data) == -1) { mdb_warn("failed to read sotpi_sonode"); return (-1); } return (0); } /* * Do some digging to get a reasonable pathname for this vnode. 'path' * should point at a buffer of MAXPATHLEN in size. */ static int pfiles_dig_pathname(uintptr_t vp, char *path) { vnode_t v; bzero(path, MAXPATHLEN); if (mdb_vread(&v, sizeof (v), vp) == -1) { mdb_warn("failed to read vnode"); return (-1); } if (v.v_path == NULL) { /* * fifo's and doors are special. Some have pathnames, and * some do not. And for these, it is pointless to go off to * mdb_vnode2path, which is very slow. * * Event ports never have a pathname. */ if (v.v_type == VFIFO || v.v_type == VDOOR || v.v_type == VPORT) return (0); /* * For sockets, we won't find a path unless we print the path * associated with transport's STREAM device. */ if (v.v_type == VSOCK) { struct sonode sonode; struct sockparams sockparams; if (pfiles_get_sonode(&v, &sonode) == -1) { return (-1); } if (mdb_vread(&sockparams, sizeof (sockparams), (uintptr_t)sonode.so_sockparams) == -1) { mdb_warn("failed to read sockparams"); return (-1); } if (!SOCK_IS_NONSTR(&sonode)) { vp = (uintptr_t) sockparams.sp_sdev_info.sd_vnode; } else { vp = 0; } } } /* * mdb_vnode2path will print an error for us as needed, but not * finding a pathname is not really an error, so we plow on. */ (void) mdb_vnode2path(vp, path, MAXPATHLEN); /* * A common problem is that device pathnames are prefixed with * /dev/../devices/. We just clean those up slightly: * /dev/../devices/ --> /devices/ * /dev/pts/../../devices/ --> /devices/ */ if (strncmp("/dev/../devices/", path, strlen("/dev/../devices/")) == 0) strcpy(path, path + 7); if (strncmp("/dev/pts/../../devices/", path, strlen("/dev/pts/../../devices/")) == 0) strcpy(path, path + 14); return (0); } const struct fs_type { vtype_t type; const char *name; } fs_types[] = { { VNON, "NON" }, { VREG, "REG" }, { VDIR, "DIR" }, { VBLK, "BLK" }, { VCHR, "CHR" }, { VLNK, "LNK" }, { VFIFO, "FIFO" }, { VDOOR, "DOOR" }, { VPROC, "PROC" }, { VSOCK, "SOCK" }, { VPORT, "PORT" }, { VBAD, "BAD" } }; #define NUM_FS_TYPES (sizeof (fs_types) / sizeof (struct fs_type)) struct pfiles_cbdata { int opt_p; int fd; }; #define list_d2l(a, obj) ((list_node_t *)(((char *)obj) + (a)->list_offset)) #define list_object(a, node) ((void *)(((char *)node) - (a)->list_offset)) /* * SCTP interface for geting the first source address of a sctp_t. */ int sctp_getsockaddr(sctp_t *sctp, struct sockaddr *addr) { int err = -1; int i; int l; sctp_saddr_ipif_t *pobj; sctp_saddr_ipif_t obj; size_t added = 0; sin6_t *sin6; sin_t *sin4; int scanned = 0; boolean_t skip_lback = B_FALSE; conn_t *connp = sctp->sctp_connp; addr->sa_family = connp->conn_family; if (sctp->sctp_nsaddrs == 0) goto done; /* * Skip loopback addresses for non-loopback assoc. */ if (sctp->sctp_state >= SCTPS_ESTABLISHED && !sctp->sctp_loopback) { skip_lback = B_TRUE; } for (i = 0; i < SCTP_IPIF_HASH; i++) { if (sctp->sctp_saddrs[i].ipif_count == 0) continue; pobj = list_object(&sctp->sctp_saddrs[i].sctp_ipif_list, sctp->sctp_saddrs[i].sctp_ipif_list.list_head.list_next); if (mdb_vread(&obj, sizeof (sctp_saddr_ipif_t), (uintptr_t)pobj) == -1) { mdb_warn("failed to read sctp_saddr_ipif_t"); return (err); } for (l = 0; l < sctp->sctp_saddrs[i].ipif_count; l++) { sctp_ipif_t ipif; in6_addr_t laddr; list_node_t *pnode; list_node_t node; if (mdb_vread(&ipif, sizeof (sctp_ipif_t), (uintptr_t)obj.saddr_ipifp) == -1) { mdb_warn("failed to read sctp_ipif_t"); return (err); } laddr = ipif.sctp_ipif_saddr; scanned++; if ((ipif.sctp_ipif_state == SCTP_IPIFS_CONDEMNED) || SCTP_DONT_SRC(&obj) || (ipif.sctp_ipif_ill->sctp_ill_flags & PHYI_LOOPBACK) && skip_lback) { if (scanned >= sctp->sctp_nsaddrs) goto done; /* LINTED: alignment */ pnode = list_d2l(&sctp->sctp_saddrs[i]. sctp_ipif_list, pobj); if (mdb_vread(&node, sizeof (list_node_t), (uintptr_t)pnode) == -1) { mdb_warn("failed to read list_node_t"); return (err); } pobj = list_object(&sctp->sctp_saddrs[i]. sctp_ipif_list, node.list_next); if (mdb_vread(&obj, sizeof (sctp_saddr_ipif_t), (uintptr_t)pobj) == -1) { mdb_warn("failed to read " "sctp_saddr_ipif_t"); return (err); } continue; } switch (connp->conn_family) { case AF_INET: /* LINTED: alignment */ sin4 = (sin_t *)addr; if ((sctp->sctp_state <= SCTPS_LISTEN) && sctp->sctp_bound_to_all) { sin4->sin_addr.s_addr = INADDR_ANY; sin4->sin_port = connp->conn_lport; } else { sin4 += added; sin4->sin_family = AF_INET; sin4->sin_port = connp->conn_lport; IN6_V4MAPPED_TO_INADDR(&laddr, &sin4->sin_addr); } break; case AF_INET6: /* LINTED: alignment */ sin6 = (sin6_t *)addr; if ((sctp->sctp_state <= SCTPS_LISTEN) && sctp->sctp_bound_to_all) { bzero(&sin6->sin6_addr, sizeof (sin6->sin6_addr)); sin6->sin6_port = connp->conn_lport; } else { sin6 += added; sin6->sin6_family = AF_INET6; sin6->sin6_port = connp->conn_lport; sin6->sin6_addr = laddr; } sin6->sin6_flowinfo = connp->conn_flowinfo; sin6->sin6_scope_id = 0; sin6->__sin6_src_id = 0; break; } added++; if (added >= 1) { err = 0; goto done; } if (scanned >= sctp->sctp_nsaddrs) goto done; /* LINTED: alignment */ pnode = list_d2l(&sctp->sctp_saddrs[i].sctp_ipif_list, pobj); if (mdb_vread(&node, sizeof (list_node_t), (uintptr_t)pnode) == -1) { mdb_warn("failed to read list_node_t"); return (err); } pobj = list_object(&sctp->sctp_saddrs[i]. sctp_ipif_list, node.list_next); if (mdb_vread(&obj, sizeof (sctp_saddr_ipif_t), (uintptr_t)pobj) == -1) { mdb_warn("failed to read sctp_saddr_ipif_t"); return (err); } } } done: return (err); } /* * SCTP interface for geting the primary peer address of a sctp_t. */ static int sctp_getpeeraddr(sctp_t *sctp, struct sockaddr *addr) { struct sockaddr_in *sin4; struct sockaddr_in6 *sin6; sctp_faddr_t sctp_primary; in6_addr_t faddr; conn_t *connp = sctp->sctp_connp; if (sctp->sctp_faddrs == NULL) return (-1); addr->sa_family = connp->conn_family; if (mdb_vread(&sctp_primary, sizeof (sctp_faddr_t), (uintptr_t)sctp->sctp_primary) == -1) { mdb_warn("failed to read sctp primary faddr"); return (-1); } faddr = sctp_primary.sf_faddr; switch (connp->conn_family) { case AF_INET: /* LINTED: alignment */ sin4 = (struct sockaddr_in *)addr; IN6_V4MAPPED_TO_INADDR(&faddr, &sin4->sin_addr); sin4->sin_port = connp->conn_fport; sin4->sin_family = AF_INET; break; case AF_INET6: /* LINTED: alignment */ sin6 = (struct sockaddr_in6 *)addr; sin6->sin6_addr = faddr; sin6->sin6_port = connp->conn_fport; sin6->sin6_family = AF_INET6; sin6->sin6_flowinfo = 0; sin6->sin6_scope_id = 0; sin6->__sin6_src_id = 0; break; } return (0); } static int tpi_sock_print(sotpi_sonode_t *sotpi_sonode) { if (sotpi_sonode->st_info.sti_laddr_valid == 1) { struct sockaddr *laddr = mdb_alloc(sotpi_sonode->st_info.sti_laddr_len, UM_SLEEP); if (mdb_vread(laddr, sotpi_sonode->st_info.sti_laddr_len, (uintptr_t)sotpi_sonode->st_info.sti_laddr_sa) == -1) { mdb_warn("failed to read sotpi_sonode socket addr"); return (-1); } mdb_printf("socket: "); pfiles_print_addr(laddr); } if (sotpi_sonode->st_info.sti_faddr_valid == 1) { struct sockaddr *faddr = mdb_alloc(sotpi_sonode->st_info.sti_faddr_len, UM_SLEEP); if (mdb_vread(faddr, sotpi_sonode->st_info.sti_faddr_len, (uintptr_t)sotpi_sonode->st_info.sti_faddr_sa) == -1) { mdb_warn("failed to read sotpi_sonode remote addr"); return (-1); } mdb_printf("remote: "); pfiles_print_addr(faddr); } return (0); } static int tcpip_sock_print(struct sonode *socknode) { switch (socknode->so_family) { case AF_INET: { conn_t conn_t; in_port_t port; if (mdb_vread(&conn_t, sizeof (conn_t), (uintptr_t)socknode->so_proto_handle) == -1) { mdb_warn("failed to read conn_t V4"); return (-1); } mdb_printf("socket: "); mdb_nhconvert(&port, &conn_t.conn_lport, sizeof (port)); mdb_printf("AF_INET %I %d ", conn_t.conn_laddr_v4, port); /* * If this is a listening socket, we don't print * the remote address. */ if (IPCL_IS_TCP(&conn_t) && IPCL_IS_BOUND(&conn_t) == 0 || IPCL_IS_UDP(&conn_t) && IPCL_IS_CONNECTED(&conn_t)) { mdb_printf("remote: "); mdb_nhconvert(&port, &conn_t.conn_fport, sizeof (port)); mdb_printf("AF_INET %I %d ", conn_t.conn_faddr_v4, port); } break; } case AF_INET6: { conn_t conn_t; in_port_t port; if (mdb_vread(&conn_t, sizeof (conn_t), (uintptr_t)socknode->so_proto_handle) == -1) { mdb_warn("failed to read conn_t V6"); return (-1); } mdb_printf("socket: "); mdb_nhconvert(&port, &conn_t.conn_lport, sizeof (port)); mdb_printf("AF_INET6 %N %d ", &conn_t.conn_laddr_v4, port); /* * If this is a listening socket, we don't print * the remote address. */ if (IPCL_IS_TCP(&conn_t) && IPCL_IS_BOUND(&conn_t) == 0 || IPCL_IS_UDP(&conn_t) && IPCL_IS_CONNECTED(&conn_t)) { mdb_printf("remote: "); mdb_nhconvert(&port, &conn_t.conn_fport, sizeof (port)); mdb_printf("AF_INET6 %N %d ", &conn_t.conn_faddr_v6, port); } break; } default: mdb_printf("AF_?? (%d)", socknode->so_family); break; } return (0); } static int sctp_sock_print(struct sonode *socknode) { sctp_t sctp_t; conn_t conns; struct sockaddr *laddr = mdb_alloc(sizeof (struct sockaddr), UM_SLEEP); struct sockaddr *faddr = mdb_alloc(sizeof (struct sockaddr), UM_SLEEP); if (mdb_vread(&sctp_t, sizeof (sctp_t), (uintptr_t)socknode->so_proto_handle) == -1) { mdb_warn("failed to read sctp_t"); return (-1); } if (mdb_vread(&conns, sizeof (conn_t), (uintptr_t)sctp_t.sctp_connp) == -1) { mdb_warn("failed to read conn_t at %p", (uintptr_t)sctp_t.sctp_connp); return (-1); } sctp_t.sctp_connp = &conns; if (sctp_getsockaddr(&sctp_t, laddr) == 0) { mdb_printf("socket:"); pfiles_print_addr(laddr); } if (sctp_getpeeraddr(&sctp_t, faddr) == 0) { mdb_printf("remote:"); pfiles_print_addr(faddr); } return (0); } /* ARGSUSED */ static int sdp_sock_print(struct sonode *socknode) { return (0); } struct sock_print { int family; int type; int pro; int (*print)(struct sonode *socknode); } sock_prints[] = { { 2, 2, 0, tcpip_sock_print }, /* /dev/tcp */ { 2, 2, 6, tcpip_sock_print }, /* /dev/tcp */ { 26, 2, 0, tcpip_sock_print }, /* /dev/tcp6 */ { 26, 2, 6, tcpip_sock_print }, /* /dev/tcp6 */ { 2, 1, 0, tcpip_sock_print }, /* /dev/udp */ { 2, 1, 17, tcpip_sock_print }, /* /dev/udp */ { 26, 1, 0, tcpip_sock_print }, /* /dev/udp6 */ { 26, 1, 17, tcpip_sock_print }, /* /dev/udp6 */ { 2, 4, 0, tcpip_sock_print }, /* /dev/rawip */ { 26, 4, 0, tcpip_sock_print }, /* /dev/rawip6 */ { 2, 2, 132, sctp_sock_print }, /* /dev/sctp */ { 26, 2, 132, sctp_sock_print }, /* /dev/sctp6 */ { 2, 6, 132, sctp_sock_print }, /* /dev/sctp */ { 26, 6, 132, sctp_sock_print }, /* /dev/sctp6 */ { 24, 4, 0, tcpip_sock_print }, /* /dev/rts */ { 2, 2, 257, sdp_sock_print }, /* /dev/sdp */ { 26, 2, 257, sdp_sock_print }, /* /dev/sdp */ }; #define NUM_SOCK_PRINTS \ (sizeof (sock_prints) / sizeof (struct sock_print)) static int pfile_callback(uintptr_t addr, const struct file *f, struct pfiles_cbdata *cb) { vnode_t v, layer_vn; int myfd = cb->fd; const char *type; char path[MAXPATHLEN]; uintptr_t top_vnodep, realvpp; char fsname[_ST_FSTYPSZ]; int err, i; cb->fd++; if (addr == 0) { return (WALK_NEXT); } top_vnodep = realvpp = (uintptr_t)f->f_vnode; if (mdb_vread(&v, sizeof (v), realvpp) == -1) { mdb_warn("failed to read vnode"); return (DCMD_ERR); } type = "?"; for (i = 0; i < NUM_FS_TYPES; i++) { if (fs_types[i].type == v.v_type) { type = fs_types[i].name; break; } } do { uintptr_t next_realvpp; err = next_realvp(realvpp, &layer_vn, &next_realvpp); if (next_realvpp != 0) realvpp = next_realvpp; } while (err == REALVP_CONTINUE); if (err == REALVP_ERR) { mdb_warn("failed to do realvp() for %p", realvpp); return (DCMD_ERR); } if (read_fsname((uintptr_t)layer_vn.v_vfsp, fsname) == -1) return (DCMD_ERR); mdb_printf("%4d %4s %?0p ", myfd, type, top_vnodep); if (cb->opt_p) { if (pfiles_dig_pathname(top_vnodep, path) == -1) return (DCMD_ERR); mdb_printf("%s\n", path); return (DCMD_OK); } /* * Sockets generally don't have interesting pathnames; we only * show those in the '-p' view. */ path[0] = '\0'; if (v.v_type != VSOCK) { if (pfiles_dig_pathname(top_vnodep, path) == -1) return (DCMD_ERR); } mdb_printf("%s%s", path, path[0] == '\0' ? "" : " "); switch (v.v_type) { case VDOOR: { door_node_t doornode; proc_t pr; if (mdb_vread(&doornode, sizeof (doornode), (uintptr_t)layer_vn.v_data) == -1) { mdb_warn("failed to read door_node"); return (DCMD_ERR); } if (mdb_vread(&pr, sizeof (pr), (uintptr_t)doornode.door_target) == -1) { mdb_warn("failed to read door server process %p", doornode.door_target); return (DCMD_ERR); } mdb_printf("[door to '%s' (proc=%p)]", pr.p_user.u_comm, doornode.door_target); break; } case VSOCK: { vnode_t v_sock; struct sonode so; if (mdb_vread(&v_sock, sizeof (v_sock), realvpp) == -1) { mdb_warn("failed to read socket vnode"); return (DCMD_ERR); } /* * Sockets can be non-stream or stream, they have to be dealed * with differently. */ if (v_sock.v_stream == NULL) { if (pfiles_get_sonode(&v_sock, &so) == -1) return (DCMD_ERR); /* Pick the proper methods. */ for (i = 0; i <= NUM_SOCK_PRINTS; i++) { if ((sock_prints[i].family == so.so_family && sock_prints[i].type == so.so_type && sock_prints[i].pro == so.so_protocol) || (sock_prints[i].family == so.so_family && sock_prints[i].type == so.so_type && so.so_type == SOCK_RAW)) { if ((*sock_prints[i].print)(&so) == -1) return (DCMD_ERR); } } } else { sotpi_sonode_t sotpi_sonode; if (pfiles_get_sonode(&v_sock, &so) == -1) return (DCMD_ERR); /* * If the socket is a fallback socket, read its related * information separately; otherwise, read it as a whole * tpi socket. */ if (so.so_state & SS_FALLBACK_COMP) { sotpi_sonode.st_sonode = so; if (mdb_vread(&(sotpi_sonode.st_info), sizeof (sotpi_info_t), (uintptr_t)so.so_priv) == -1) return (DCMD_ERR); } else { if (pfiles_get_tpi_sonode(&v_sock, &sotpi_sonode) == -1) return (DCMD_ERR); } if (tpi_sock_print(&sotpi_sonode) == -1) return (DCMD_ERR); } break; } case VPORT: mdb_printf("[event port (port=%p)]", v.v_data); break; case VPROC: { prnode_t prnode; prcommon_t prcommon; if (mdb_vread(&prnode, sizeof (prnode), (uintptr_t)layer_vn.v_data) == -1) { mdb_warn("failed to read prnode"); return (DCMD_ERR); } if (mdb_vread(&prcommon, sizeof (prcommon), (uintptr_t)prnode.pr_common) == -1) { mdb_warn("failed to read prcommon %p", prnode.pr_common); return (DCMD_ERR); } mdb_printf("(proc=%p)", prcommon.prc_proc); break; } default: break; } mdb_printf("\n"); return (WALK_NEXT); } static int file_t_callback(uintptr_t addr, const struct file *f, struct pfiles_cbdata *cb) { int myfd = cb->fd; cb->fd++; if (addr == 0) { return (WALK_NEXT); } /* * We really need 20 digits to print a 64-bit offset_t, but this * is exceedingly rare, so we cheat and assume a column width of 10 * digits, in order to fit everything cleanly into 80 columns. */ mdb_printf("%?0p %4d %8x %?0p %10lld %?0p %4d\n", addr, myfd, f->f_flag, f->f_vnode, f->f_offset, f->f_cred, f->f_count); return (WALK_NEXT); } int pfiles(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int opt_f = 0; struct pfiles_cbdata cb; bzero(&cb, sizeof (cb)); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'p', MDB_OPT_SETBITS, TRUE, &cb.opt_p, 'f', MDB_OPT_SETBITS, TRUE, &opt_f, NULL) != argc) return (DCMD_USAGE); if (opt_f) { mdb_printf("%%?s %4s %8s %?s %10s %?s %4s%\n", "FILE", "FD", "FLAG", "VNODE", "OFFSET", "CRED", "CNT"); if (mdb_pwalk("allfile", (mdb_walk_cb_t)file_t_callback, &cb, addr) == -1) { mdb_warn("failed to walk 'allfile'"); return (DCMD_ERR); } } else { mdb_printf("%%-4s %4s %?s ", "FD", "TYPE", "VNODE"); if (cb.opt_p) mdb_printf("PATH"); else mdb_printf("INFO"); mdb_printf("%\n"); if (mdb_pwalk("allfile", (mdb_walk_cb_t)pfile_callback, &cb, addr) == -1) { mdb_warn("failed to walk 'allfile'"); return (DCMD_ERR); } } return (DCMD_OK); } void pfiles_help(void) { mdb_printf( "Given the address of a process, print information about files\n" "which the process has open. By default, this includes decoded\n" "information about the file depending on file and filesystem type\n" "\n" "\t-p\tPathnames; omit decoded information. Only display " "pathnames\n" "\t-f\tfile_t view; show the file_t structure corresponding to " "the fd\n"); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _VFS_H #define _VFS_H #include #ifdef __cplusplus extern "C" { #endif int vfs_walk_init(mdb_walk_state_t *); int vfs_walk_step(mdb_walk_state_t *); int fsinfo(uintptr_t, uint_t, int, const mdb_arg_t *); int pfiles(uintptr_t, uint_t, int, const mdb_arg_t *); void pfiles_help(void); #ifdef __cplusplus } #endif #endif /* _VFS_H */ /* * 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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, Joyent, Inc. All rights reserved. * Copyright 2025 Oxide Computer Company */ #include #include #include #include "zone.h" #include #include #define ZONE_NAMELEN 20 #ifdef _LP64 #define ZONE_PATHLEN 32 #else #define ZONE_PATHLEN 40 #endif /* * Names corresponding to zone_status_t values in sys/zone.h */ char *zone_status_names[] = { "uninitialized", /* ZONE_IS_UNINITIALIZED */ "initialized", /* ZONE_IS_INITIALIZED */ "ready", /* ZONE_IS_READY */ "booting", /* ZONE_IS_BOOTING */ "running", /* ZONE_IS_RUNNING */ "shutting_down", /* ZONE_IS_SHUTTING_DOWN */ "empty", /* ZONE_IS_EMPTY */ "down", /* ZONE_IS_DOWN */ "dying", /* ZONE_IS_DYING */ "dead" /* ZONE_IS_DEAD */ }; static int zid_lookup_cb(uintptr_t addr, const zone_t *zone, void *arg) { zoneid_t zid = *(uintptr_t *)arg; if (zone->zone_id == zid) mdb_printf("%p\n", addr); return (WALK_NEXT); } /*ARGSUSED*/ int zid2zone(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_walk("zone", (mdb_walk_cb_t)zid_lookup_cb, &addr) == -1) { mdb_warn("failed to walk zone"); return (DCMD_ERR); } return (DCMD_OK); } int zoneprt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { zone_t zn; char name[ZONE_NAMELEN]; char path[ZONE_PATHLEN]; int len; uint_t vopt_given; uint_t ropt_given; if (argc > 2) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("zone", "zone", argc, argv) == -1) { mdb_warn("can't walk zones"); return (DCMD_ERR); } return (DCMD_OK); } /* * Get the optional -r (reference counts) and -v (verbose output) * arguments. */ vopt_given = FALSE; ropt_given = FALSE; if (argc > 0 && mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &vopt_given, 'r', MDB_OPT_SETBITS, TRUE, &ropt_given, NULL) != argc) return (DCMD_USAGE); /* * -v can only be specified with -r. */ if (vopt_given == TRUE && ropt_given == FALSE) return (DCMD_USAGE); /* * Print a table header, if necessary. */ if (DCMD_HDRSPEC(flags)) { if (ropt_given == FALSE) mdb_printf("%%?s %6s %-13s %-20s %-s%\n", "ADDR", "ID", "STATUS", "NAME", "PATH"); else mdb_printf("%%?s %6s %10s %10s %-20s%\n", "ADDR", "ID", "REFS", "CREFS", "NAME"); } /* * Read the zone_t structure at the given address and read its name. */ if (mdb_vread(&zn, sizeof (zone_t), addr) == -1) { mdb_warn("can't read zone_t structure at %p", addr); return (DCMD_ERR); } len = mdb_readstr(name, ZONE_NAMELEN, (uintptr_t)zn.zone_name); if (len > 0) { if (len == ZONE_NAMELEN) (void) strcpy(&name[len - 4], "..."); } else { (void) strcpy(name, "??"); } if (ropt_given == FALSE) { char *statusp; /* * Default display * Fetch the zone's path and print the results. */ len = mdb_readstr(path, ZONE_PATHLEN, (uintptr_t)zn.zone_rootpath); if (len > 0) { if (len == ZONE_PATHLEN) (void) strcpy(&path[len - 4], "..."); } else { (void) strcpy(path, "??"); } if (zn.zone_status >= ZONE_IS_UNINITIALIZED && zn.zone_status <= ZONE_IS_DEAD) statusp = zone_status_names[zn.zone_status]; else statusp = "???"; mdb_printf("%0?p %6d %-13s %-20s %s\n", addr, zn.zone_id, statusp, name, path); } else { /* * Display the zone's reference counts. * Display the zone's subsystem-specific reference counts if * the user specified the '-v' option. */ mdb_printf("%0?p %6d %10u %10u %-20s\n", addr, zn.zone_id, zn.zone_ref, zn.zone_cred_ref, name); if (vopt_given == TRUE) { GElf_Sym subsys_names_sym; uintptr_t **zone_ref_subsys_names; uint_t num_subsys; uint_t n; /* * Read zone_ref_subsys_names from the kernel image. */ if (mdb_lookup_by_name("zone_ref_subsys_names", &subsys_names_sym) != 0) { mdb_warn("can't find zone_ref_subsys_names"); return (DCMD_ERR); } if (subsys_names_sym.st_size != ZONE_REF_NUM_SUBSYS * sizeof (char *)) { mdb_warn("number of subsystems in target " "differs from what mdb expects (mismatched" " kernel versions?)"); if (subsys_names_sym.st_size < ZONE_REF_NUM_SUBSYS * sizeof (char *)) num_subsys = subsys_names_sym.st_size / sizeof (char *); else num_subsys = ZONE_REF_NUM_SUBSYS; } else { num_subsys = ZONE_REF_NUM_SUBSYS; } if ((zone_ref_subsys_names = mdb_alloc( subsys_names_sym.st_size, UM_GC)) == NULL) { mdb_warn("out of memory"); return (DCMD_ERR); } if (mdb_readvar(zone_ref_subsys_names, "zone_ref_subsys_names") == -1) { mdb_warn("can't find zone_ref_subsys_names"); return (DCMD_ERR); } /* * Display each subsystem's reference count if it's * nonzero. */ mdb_inc_indent(7); for (n = 0; n < num_subsys; ++n) { char subsys_name[16]; /* * Skip subsystems lacking outstanding * references. */ if (zn.zone_subsys_ref[n] == 0) continue; /* * Each subsystem's name must be read from * the target's image. */ if (mdb_readstr(subsys_name, sizeof (subsys_name), (uintptr_t)zone_ref_subsys_names[n]) == -1) { mdb_warn("unable to read subsystem name" " from zone_ref_subsys_names[%u]", n); return (DCMD_ERR); } mdb_printf("%15s: %10u\n", subsys_name, zn.zone_subsys_ref[n]); } mdb_dec_indent(7); } } return (DCMD_OK); } int zone_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; if (wsp->walk_addr == 0) { if (mdb_lookup_by_name("zone_active", &sym) == -1) { mdb_warn("failed to find 'zone_active'"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sym.st_value; } if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("couldn't walk 'list'"); return (WALK_ERR); } return (WALK_NEXT); } int zone_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } int zsd_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("global walk not supported\n"); return (WALK_ERR); } wsp->walk_addr += offsetof(struct zone, zone_zsd); if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("couldn't walk 'list'"); return (WALK_ERR); } return (WALK_NEXT); } int zsd_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * Helper structure used when walking ZSD entries via zsd(). */ struct zsd_cb_data { uint_t keygiven; /* Was a key specified (are we */ /* searching for a specific ZSD */ /* entry)? */ zone_key_t key; /* Key of ZSD for which we're looking */ uint_t found; /* Was the specific ZSD entry found? */ uint_t voptgiven; /* Display verbose information? */ }; /* * Helper function for zsd() that displays information from a single ZSD struct. * 'datap' must point to a valid zsd_cb_data struct. */ /* ARGSUSED */ static int zsd_print(uintptr_t addrp, const void * datap, void * privatep) { struct zsd_entry entry; struct zsd_cb_data *cbdp; if (mdb_vread(&entry, sizeof (entry), addrp) == -1) { mdb_warn("couldn't read zsd_entry at %p", addrp); return (WALK_ERR); } cbdp = (struct zsd_cb_data *)privatep; /* * Are we looking for a single entry specified by a key? Then make sure * that the current ZSD's key is what we're looking for. */ if (cbdp->keygiven == TRUE && cbdp->key != entry.zsd_key) return (WALK_NEXT); mdb_printf("%?x %0?p %8x\n", entry.zsd_key, entry.zsd_data, entry.zsd_flags); if (cbdp->voptgiven == TRUE) mdb_printf(" Create CB: %a\n Shutdown CB: %a\n" " Destroy CB: %a\n", entry.zsd_create, entry.zsd_shutdown, entry.zsd_destroy); if (cbdp->keygiven == TRUE) { cbdp->found = TRUE; return (WALK_DONE); } return (WALK_NEXT); } int zsd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { zone_t zone; const mdb_arg_t *argp; int argcindex; struct zsd_cb_data cbd; char name[ZONE_NAMELEN]; int len; /* * Walk all zones if necessary. */ if (argc > 2) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { if (mdb_walk_dcmd("zone", "zsd", argc, argv) == -1) { mdb_warn("failed to walk zone\n"); return (DCMD_ERR); } return (DCMD_OK); } /* * Make sure a zone_t can be read from the specified address. */ if (mdb_vread(&zone, sizeof (zone), addr) == -1) { mdb_warn("couldn't read zone_t at %p", (void *)addr); return (DCMD_ERR); } /* * Get the optional arguments (key or -v or both). Note that * mdb_getopts() will not parse a key argument because it is not * preceded by an option letter. We'll get around this by requiring * that all options precede the optional key argument. */ cbd.keygiven = FALSE; cbd.voptgiven = FALSE; if (argc > 0 && (argcindex = mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &cbd.voptgiven, NULL)) != argc) { /* * No options may appear after the key. */ if (argcindex != argc - 1) return (DCMD_USAGE); /* * The missed argument should be a key. */ argp = &argv[argcindex]; cbd.key = (zone_key_t)mdb_argtoull(argp); cbd.keygiven = TRUE; cbd.found = FALSE; } /* * Prepare to output the specified zone's ZSD information. */ if (DCMD_HDRSPEC(flags)) mdb_printf("%%-20s %?s %?s %8s%\n", "ZONE", "KEY", "VALUE", "FLAGS"); len = mdb_readstr(name, ZONE_NAMELEN, (uintptr_t)zone.zone_name); if (len > 0) { if (len == ZONE_NAMELEN) (void) strcpy(&name[len - 4], "..."); } else { (void) strcpy(name, "??"); } mdb_printf("%-20s ", name); /* * Display the requested ZSD entries. */ mdb_inc_indent(21); if (mdb_pwalk("zsd", zsd_print, &cbd, addr) != 0) { mdb_warn("failed to walk zsd\n"); mdb_dec_indent(21); return (DCMD_ERR); } if (cbd.keygiven == TRUE && cbd.found == FALSE) { mdb_printf("no corresponding ZSD entry found\n"); mdb_dec_indent(21); return (DCMD_ERR); } mdb_dec_indent(21); return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _ZONE_H #define _ZONE_H #include #ifdef __cplusplus extern "C" { #endif extern int zid2zone(uintptr_t, uint_t, int argc, const mdb_arg_t *); extern int zoneprt(uintptr_t, uint_t, int argc, const mdb_arg_t *); extern int zone_walk_init(mdb_walk_state_t *); extern int zone_walk_step(mdb_walk_state_t *); extern int zsd_walk_init(mdb_walk_state_t *); extern int zsd_walk_step(mdb_walk_state_t *); extern int zsd(uintptr_t, uint_t, int, const mdb_arg_t *); #ifdef __cplusplus } #endif #endif /* _ZONE_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #define MAX_LENGTH 64 /* * List pfhooks hook list information. */ /*ARGSUSED*/ int hooklist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { hook_event_int_t hr; hook_int_t hl, *hlp; char hrstr[MAX_LENGTH]; GElf_Sym sym; char buf[MDB_SYM_NAMLEN + 1]; char *hintname; hook_t *h; if (argc) return (DCMD_USAGE); if (mdb_vread((void *)&hr, sizeof (hr), (uintptr_t)addr) == -1) { mdb_warn("couldn't read hook register at %p", addr); return (DCMD_ERR); } mdb_printf("%%?s %8s %20s %4s %24s %24s%\n", "ADDR", "FLAG", "FUNC", "HINT", "NAME", "HINTVALUE"); h = &hl.hi_hook; hlp = TAILQ_FIRST(&hr.hei_head); while (hlp) { if (mdb_vread((void *)&hl, sizeof (hl), (uintptr_t)hlp) == -1) { mdb_warn("couldn't read hook list at %p", hlp); return (DCMD_ERR); } if (!h->h_name) { mdb_warn("hook list at %p has null role", h); return (DCMD_ERR); } if (mdb_readstr((char *)hrstr, sizeof (hrstr), (uintptr_t)h->h_name) == -1) { mdb_warn("couldn't read list role at %p", h->h_name); return (DCMD_ERR); } switch (h->h_hint) { case HH_BEFORE : case HH_AFTER : hintname = h->h_hintvalue ? (char *)h->h_hintvalue : ""; break; default : hintname = ""; break; } if (mdb_lookup_by_addr((uintptr_t)h->h_func, MDB_SYM_EXACT, buf, sizeof (buf), &sym) == -1) mdb_printf("%0?p %8x %0?p %4d %24s %24s\n", hlp, h->h_flags, h->h_func, h->h_hint, hrstr, hintname); else mdb_printf("%0?p %8x %20s %4d %24s %24s\n", hlp, h->h_flags, buf, h->h_hint, hrstr, hintname); hlp = TAILQ_NEXT(&hl, hi_entry); } return (DCMD_OK); } /* * List pfhooks event information. * List the hooks information in verbose mode as well. */ /*ARGSUSED*/ int hookeventlist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { hook_family_int_t hf; hook_event_int_t hr, *hrp; hook_event_t hp; char hprstr[MAX_LENGTH]; if (argc) return (DCMD_USAGE); if (mdb_vread((void *)&hf, sizeof (hf), (uintptr_t)addr) == -1) { mdb_warn("couldn't read hook family at %p", addr); return (DCMD_ERR); } mdb_printf("%%?s %10s %20s%\n", "ADDR", "FLAG", "NAME"); hrp = SLIST_FIRST(&hf.hfi_head); while (hrp) { if (mdb_vread((void *)&hr, sizeof (hr), (uintptr_t)hrp) == -1) { mdb_warn("couldn't read hook register at %p", hrp); return (DCMD_ERR); } if (!hr.hei_event) { mdb_warn("hook register at %p has no hook provider", hrp); return (DCMD_ERR); } if (mdb_vread((void *)&hp, sizeof (hp), (uintptr_t)hr.hei_event) == -1) { mdb_warn("hook provider at %p has null role", hr.hei_event); return (DCMD_ERR); } if (!hp.he_name) { mdb_warn("hook provider at %p has null role", hr.hei_event); return (DCMD_ERR); } if (mdb_readstr((char *)hprstr, sizeof (hprstr), (uintptr_t)hp.he_name) == -1) { mdb_warn("couldn't read provider role at %p", hp.he_name); return (DCMD_ERR); } mdb_printf("%0?p %10x %20s\n", hrp, hp.he_flags, hprstr); hrp = SLIST_NEXT(&hr, hei_entry); } return (DCMD_OK); } /* * List pfhooks family information. */ /*ARGSUSED*/ int hookrootlist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct hook_stack *hks; hook_family_int_head_t hfh; hook_family_int_t hf, *hfp; char hrrstr[MAX_LENGTH]; if (argc) return (DCMD_USAGE); if (mdb_vread((void *)&hks, sizeof (hks), (uintptr_t)(addr + OFFSETOF(netstack_t, netstack_hook))) == -1) { mdb_warn("couldn't read netstack_hook"); return (DCMD_ERR); } if (mdb_vread((void *)&hfh, sizeof (hfh), (uintptr_t)((uintptr_t)hks + OFFSETOF(hook_stack_t, hks_familylist))) == -1) { mdb_warn("couldn't read hook family head"); return (DCMD_ERR); } mdb_printf("%%?s %10s%\n", "ADDR", "FAMILY"); hfp = SLIST_FIRST(&hfh); while (hfp) { if (mdb_vread((void *)&hf, sizeof (hf), (uintptr_t)hfp) == -1) { mdb_warn("couldn't read hook family at %p", hfp); return (DCMD_ERR); } if (!hf.hfi_family.hf_name) { mdb_warn("hook root at %p has null role", hf.hfi_family); return (DCMD_ERR); } if (mdb_readstr((char *)hrrstr, sizeof (hrrstr), (uintptr_t)hf.hfi_family.hf_name) == -1) { mdb_warn("couldn't read root role at %p", hf.hfi_family.hf_name); return (DCMD_ERR); } mdb_printf("%0?p %10s\n", hfp, hrrstr); hfp = SLIST_NEXT(&hf, hfi_entry); } return (DCMD_OK); } static int hookevent_stack_walk_init(mdb_walk_state_t *wsp) { hook_family_int_t hf; if (wsp->walk_addr == 0) { mdb_warn("global walk not supported\n"); return (WALK_ERR); } if (mdb_vread((void *)&hf, sizeof (hf), (uintptr_t)wsp->walk_addr) == -1) { mdb_warn("couldn't read hook family at %p", wsp->walk_addr); return (DCMD_ERR); } wsp->walk_addr = (uintptr_t)SLIST_FIRST(&hf.hfi_head); return (wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata)); } static int hookevent_stack_walk_step(mdb_walk_state_t *wsp) { hook_event_int_t hr; if (mdb_vread((void *)&hr, sizeof (hr), (uintptr_t)wsp->walk_addr) == -1) { mdb_warn("couldn't read hook event at %p", wsp->walk_addr); return (DCMD_ERR); } wsp->walk_addr = (uintptr_t)SLIST_NEXT(&hr, hei_entry); if (wsp->walk_addr == 0) return (WALK_DONE); return (wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata)); } static const mdb_dcmd_t dcmds[] = { { "hookrootlist", "", "display hook family information", hookrootlist }, { "hookeventlist", "", "display hook event information", hookeventlist, NULL }, { "hooklist", "", "display hooks", hooklist }, { NULL } }; static const mdb_walker_t walkers[] = { { "hookevent_stack", "walk list of hooks", hookevent_stack_walk_init, hookevent_stack_walk_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include /* networking stuff */ #include /* networking stuff */ #include #include #include #include #include #define IDM_CONN_SM_STRINGS #define IDM_TASK_SM_STRINGS #define ISCSIT_TGT_SM_STRINGS #define ISCSIT_SESS_SM_STRINGS #define ISCSIT_LOGIN_SM_STRINGS #define ISCSI_SESS_SM_STRINGS #define ISCSI_CMD_SM_STRINGS #define ISCSI_ICS_NAMES #define ISCSI_LOGIN_STATE_NAMES #define IDM_CN_NOTIFY_STRINGS #include #include #include #include /* * We want to be able to print multiple levels of object hierarchy with a * single dcmd information, and preferably also exclude intermediate * levels if desired. For example some of the target objects have the * following relationship: * * target --> session --> connection --> task * * The session dcmd should allow the printing of all associated tasks for the * sessions without printing all the associated connections. To accomplish * this the following structure contains a bit for each object type. Dcmds * should invoke the functions for child objects if any bits are set * in iscsi_dcmd_ctrl_t but the functions for the child object should only * print data if their associated bit is set. Each object type should print * a header for its first occurrence or if it is being printed as a child * object for the first occurrence under each parent. For the model to follow * see how idc->idc_header is handled in iscsi_sess_impl. * * Each dcmd should provide an external interface with the standard MDB API * and an internal interface that accepts iscsi_dcmd_ctrl_t. To display * child objects the dcmd calls the internal interface for the child object * directly. Dcmds invoked from the command line will, of course, call the * external interface. See iscsi_conn() and iscsi_conn_impl(). */ typedef struct { union { uint32_t idc_children; struct { uint32_t idc_tgt:1, idc_tpg:1, idc_tpgt:1, idc_portal:1, idc_sess:1, idc_conn:1, idc_svc:1, idc_print_ip:1, idc_task:1, idc_buffer:1, idc_states:1, idc_rc_audit:1, idc_lun:1, idc_hba:1, idc_cmd:1; } child; } u; boolean_t idc_ini; boolean_t idc_tgt; boolean_t idc_verbose; boolean_t idc_header; /* * Our connection dcmd code works off the global connection lists * in IDM since we want to know about connections even when they * have not progressed to the point that they have an associated * session. If we use "::iscsi_sess [-c]" then we only want to * see connections associated with particular session. To avoid * writing a separate set of code to print session-specific connection * the session code should set the sessions kernel address in the * following field. The connection code will then only print * connections that match. */ uintptr_t idc_assoc_session; } iscsi_dcmd_ctrl_t; typedef struct idm_hba_walk_info { void **array; int n_elements; int cur_element; void *data; } idm_hba_walk_info_t; static int iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc); static int iscsi_walk_all_conn(iscsi_dcmd_ctrl_t *idc); static int iscsi_tgt_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void); static int iscsi_tpgt_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void); static int iscsi_tpg_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void); static int iscsi_portal_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void); static int iscsi_sess_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void); static int iscsi_conn_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void); static int iscsi_buffer_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void); static int iscsi_svc_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void); static int iscsi_ini_hba_walk_cb(uintptr_t addr, const void *vhba, void *idc_void); static int iscsi_ini_sess_walk_cb(uintptr_t addr, const void *vsess, void *idc); static int iscsi_ini_conn_walk_cb(uintptr_t addr, const void *vconn, void *idc_void); static int iscsi_ini_lun_walk_cb(uintptr_t addr, const void *vlun, void *idc_void); static int iscsi_ini_cmd_walk_cb(uintptr_t addr, const void *vcmd, void *idc); static int iscsi_tgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static int iscsi_tpgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static int iscsi_tpg_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static int iscsi_portal_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static int iscsi_sess_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static int iscsi_conn_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static void iscsi_print_iscsit_conn_data(idm_conn_t *ict); static void iscsi_print_ini_conn_data(idm_conn_t *ict); static void iscsi_print_idm_conn_data(idm_conn_t *ict); static int iscsi_task_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static void iscsi_print_iscsit_task_data(idm_task_t *idt); static int iscsi_buffer_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static idm_conn_type_t idm_conn_type(uintptr_t addr); static int iscsi_i_task_impl(idm_task_t *idt, uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static int iscsi_refcnt_impl(uintptr_t addr); static int iscsi_sm_audit_impl(uintptr_t addr); static int iscsi_isns(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); static int iscsi_svc_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static int iscsi_ini_hba_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); static int iscsi_print_ini_sess(uintptr_t addr, iscsi_sess_t *sess, iscsi_dcmd_ctrl_t *idc); static int iscsi_print_ini_lun(uintptr_t addr, const iscsi_lun_t *lun, iscsi_dcmd_ctrl_t *idc); static int iscsi_print_ini_cmd(uintptr_t addr, const iscsi_cmd_t *cmd, iscsi_dcmd_ctrl_t *idc); static int iscsi_ini_sess_walk_init(mdb_walk_state_t *wsp); static int iscsi_ini_sess_step(mdb_walk_state_t *wsp); static int iscsi_ini_conn_walk_init(mdb_walk_state_t *wsp); static int iscsi_ini_conn_step(mdb_walk_state_t *wsp); static int iscsi_ini_lun_walk_init(mdb_walk_state_t *wsp); static int iscsi_ini_lun_step(mdb_walk_state_t *wsp); static int iscsi_ini_cmd_walk_init(mdb_walk_state_t *wsp); static int iscsi_ini_cmd_step(mdb_walk_state_t *wsp); static const char *iscsi_idm_conn_event(unsigned int event); static const char *iscsi_iscsit_tgt_event(unsigned int event); static const char *iscsi_iscsit_sess_event(unsigned int event); static const char *iscsi_iscsit_login_event(unsigned int event); static const char *iscsi_iscsi_cmd_event(unsigned int event); static const char *iscsi_iscsi_sess_event(unsigned int event); static const char *iscsi_idm_conn_state(unsigned int state); static const char *iscsi_idm_task_state(unsigned int state); static const char *iscsi_iscsit_tgt_state(unsigned int state); static const char *iscsi_iscsit_sess_state(unsigned int state); static const char *iscsi_iscsit_login_state(unsigned int state); static const char *iscsi_iscsi_cmd_state(unsigned int state); static const char *iscsi_iscsi_sess_state(unsigned int state); static const char *iscsi_iscsi_conn_state(unsigned int state); static const char *iscsi_iscsi_conn_event(unsigned int event); static const char *iscsi_iscsi_login_state(unsigned int state); static void iscsi_format_timestamp(char *ts_str, int strlen, timespec_t *ts); static char *iscsi_inet_ntop(int af, const void *addr, char *buf, int addrlen); static void convert2ascii(char *, const in6_addr_t *); static int sa_to_str(struct sockaddr_storage *sa, char *addr); static int iscsi_isns_esi_cb(uintptr_t addr, const void *walker_data, void *data); static int iscsi_isns_portal_cb(uintptr_t addr, const void *walker_data, void *data); #define PORTAL_STR_LEN (INET6_ADDRSTRLEN + 7) /* * ::iscsi_tgt [-scatgpbSRv] * * iscsi_tgt - Print out information associated with an iscsit target instance * * s Print associated session information * c Print associated connection information * a Print IP addresses with connection information * t Print associated task information * g Print associated TPG information * p Print portals with TPG information * b Print associated buffer information * S Print recent state events and transitions * R Print reference count audit data * v Verbose output about the connection */ /*ARGSUSED*/ static int iscsi_tgt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; int buffer = 0, task = 0, print_ip = 0; int tpgt = 0, conn = 0, sess = 0, portal = 0; int states = 0, rc_audit = 0; uintptr_t iscsit_global_addr, avl_addr, list_addr; GElf_Sym sym; bzero(&idc, sizeof (idc)); if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &print_ip, 'g', MDB_OPT_SETBITS, TRUE, &tpgt, 's', MDB_OPT_SETBITS, TRUE, &sess, 'c', MDB_OPT_SETBITS, TRUE, &conn, 't', MDB_OPT_SETBITS, TRUE, &task, 'b', MDB_OPT_SETBITS, TRUE, &buffer, 'p', MDB_OPT_SETBITS, TRUE, &portal, 'S', MDB_OPT_SETBITS, TRUE, &states, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, NULL) != argc) return (DCMD_USAGE); idc.u.child.idc_tgt = 1; idc.u.child.idc_print_ip = print_ip; idc.u.child.idc_tpgt = tpgt; idc.u.child.idc_portal = portal; idc.u.child.idc_sess = sess; idc.u.child.idc_conn = conn; idc.u.child.idc_task = task; idc.u.child.idc_buffer = buffer; idc.u.child.idc_states = states; idc.u.child.idc_rc_audit = rc_audit; if (DCMD_HDRSPEC(flags)) idc.idc_header = 1; /* * If no address was specified on the command line, we * print out all tgtions */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { mdb_warn("failed to find symbol 'iscsit_global'"); return (DCMD_ERR); } iscsit_global_addr = (uintptr_t)sym.st_value; avl_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_target_list); if (mdb_pwalk("avl", iscsi_tgt_walk_cb, &idc, avl_addr) == -1) { mdb_warn("avl walk failed for global target tree"); return (DCMD_ERR); } list_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_deleted_target_list); if (mdb_pwalk("list", iscsi_tgt_walk_cb, &idc, list_addr) == -1) { mdb_warn("list walk failed for deleted target list"); return (DCMD_ERR); } return (DCMD_OK); } return (iscsi_tgt_impl(addr, &idc)); } static int iscsi_tpg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; uintptr_t iscsit_global_addr, avl_addr; GElf_Sym sym; int rc_audit = 0; bzero(&idc, sizeof (idc)); if (mdb_getopts(argc, argv, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, NULL) != argc) return (DCMD_USAGE); /* Always print tpgs and portals */ idc.u.child.idc_tpg = 1; idc.u.child.idc_portal = 1; idc.u.child.idc_rc_audit = rc_audit; if (DCMD_HDRSPEC(flags)) idc.idc_header = 1; /* * If no address was specified on the command line, we * print out all tgtions */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { mdb_warn("failed to find symbol 'iscsit_global'"); return (DCMD_ERR); } iscsit_global_addr = (uintptr_t)sym.st_value; avl_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_tpg_list); if (mdb_pwalk("avl", iscsi_tpg_walk_cb, &idc, avl_addr) == -1) { mdb_warn("avl walk failed for global target tree"); return (DCMD_ERR); } return (DCMD_OK); } return (iscsi_tpg_impl(addr, &idc)); } /* * ::iscsi_tpgt [-pR] * * Print tpgt information. * R Print reference count audit data * p Print portal data */ static int iscsi_tpgt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; uintptr_t iscsit_global_addr, avl_addr, list_addr; GElf_Sym sym; int rc_audit = 0, portal = 0; bzero(&idc, sizeof (idc)); if (mdb_getopts(argc, argv, 'p', MDB_OPT_SETBITS, TRUE, &portal, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, NULL) != argc) return (DCMD_USAGE); idc.u.child.idc_tpgt = 1; idc.u.child.idc_portal = portal; idc.u.child.idc_rc_audit = rc_audit; if (DCMD_HDRSPEC(flags)) idc.idc_header = 1; /* * If no address was specified on the command line, * print out all tpgts */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { mdb_warn("failed to find symbol 'iscsit_global'"); return (DCMD_ERR); } iscsit_global_addr = (uintptr_t)sym.st_value; avl_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_target_list); if (mdb_pwalk("avl", iscsi_tgt_walk_cb, &idc, avl_addr) == -1) { mdb_warn("avl walk failed for global target tree"); return (DCMD_ERR); } list_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_deleted_target_list); if (mdb_pwalk("list", iscsi_tgt_walk_cb, &idc, list_addr) == -1) { mdb_warn("list walk failed for deleted target list"); return (DCMD_ERR); } return (DCMD_OK); } return (iscsi_tpgt_impl(addr, &idc)); } /* * ::iscsi_sess [-ablmtvcSRIT] * * iscsi_sess - Print out information associated with an iSCSI session * * I Print only initiator sessions * T Print only target sessions * c Print associated connection information * a Print IP addresses with connection information * t Print associated task information * l Print associated lun information (with -I) * m Print associated initiator command information (with -I) * b Print associated buffer information * S Print recent state events and transitions * R Print reference count audit data * v Verbose output about the connection */ /*ARGSUSED*/ static int iscsi_sess(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; int buffer = 0, task = 0, conn = 0, print_ip = 0; int states = 0, rc_audit = 0, commands = 0; int luns = 0; bzero(&idc, sizeof (idc)); if (mdb_getopts(argc, argv, 'I', MDB_OPT_SETBITS, TRUE, &idc.idc_ini, 'T', MDB_OPT_SETBITS, TRUE, &idc.idc_tgt, 'a', MDB_OPT_SETBITS, TRUE, &print_ip, 'c', MDB_OPT_SETBITS, TRUE, &conn, 't', MDB_OPT_SETBITS, TRUE, &task, 'l', MDB_OPT_SETBITS, TRUE, &luns, 'm', MDB_OPT_SETBITS, TRUE, &commands, 'b', MDB_OPT_SETBITS, TRUE, &buffer, 'S', MDB_OPT_SETBITS, TRUE, &states, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, NULL) != argc) return (DCMD_USAGE); idc.u.child.idc_sess = 1; idc.u.child.idc_print_ip = print_ip; idc.u.child.idc_conn = conn; idc.u.child.idc_task = task; idc.u.child.idc_cmd = commands; idc.u.child.idc_lun = luns; idc.u.child.idc_buffer = buffer; idc.u.child.idc_states = states; idc.u.child.idc_rc_audit = rc_audit; if (DCMD_HDRSPEC(flags)) idc.idc_header = 1; /* * If no address was specified on the command line, we * print out all sessions */ if (!(flags & DCMD_ADDRSPEC)) { return (iscsi_walk_all_sess(&idc)); } return (iscsi_sess_impl(addr, &idc)); } /* * ::iscsi_conn [-abmtvSRIT] * * iscsi_conn - Print out information associated with an iSCSI connection * * I Print only initiator connections * T Print only target connections * a Print IP addresses with connection information * t Print associated task information * b Print associated buffer information * m Print associated initiator commands (with -I) * S Print recent state events and transitions * R Print reference count audit data * v Verbose output about the connection */ /*ARGSUSED*/ static int iscsi_conn(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; int buffer = 0, task = 0, print_ip = 0; int states = 0, rc_audit = 0, commands = 0; bzero(&idc, sizeof (idc)); if (mdb_getopts(argc, argv, 'I', MDB_OPT_SETBITS, TRUE, &idc.idc_ini, 'T', MDB_OPT_SETBITS, TRUE, &idc.idc_tgt, 'a', MDB_OPT_SETBITS, TRUE, &print_ip, 't', MDB_OPT_SETBITS, TRUE, &task, 'b', MDB_OPT_SETBITS, TRUE, &buffer, 'm', MDB_OPT_SETBITS, TRUE, &commands, 'S', MDB_OPT_SETBITS, TRUE, &states, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, NULL) != argc) return (DCMD_USAGE); idc.u.child.idc_conn = 1; idc.u.child.idc_print_ip = print_ip; idc.u.child.idc_task = task; idc.u.child.idc_buffer = buffer; idc.u.child.idc_cmd = commands; idc.u.child.idc_states = states; idc.u.child.idc_rc_audit = rc_audit; if (DCMD_HDRSPEC(flags)) idc.idc_header = 1; /* * If no address was specified on the command line, we * print out all connections */ if (!(flags & DCMD_ADDRSPEC)) { return (iscsi_walk_all_conn(&idc)); } return (iscsi_conn_impl(addr, &idc)); } /* * ::iscsi_svc [-vR] * * iscsi_svc - Print out information associated with an iSCSI svc * * R Print reference count audit data * v Verbose output about the service */ static int iscsi_svc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; GElf_Sym sym; uintptr_t idm_addr; uintptr_t svc_list_addr; int rc_audit = 0; bzero(&idc, sizeof (iscsi_dcmd_ctrl_t)); if (mdb_getopts(argc, argv, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, NULL) != argc) return (DCMD_USAGE); idc.u.child.idc_svc = 1; idc.u.child.idc_rc_audit = rc_audit; if (DCMD_HDRSPEC(flags)) { idc.idc_header = 1; } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_lookup_by_name("idm", &sym) == -1) { mdb_warn("failed to find symbol 'idm'"); return (DCMD_ERR); } idm_addr = (uintptr_t)sym.st_value; svc_list_addr = idm_addr + offsetof(idm_global_t, idm_tgt_svc_list); if (mdb_pwalk("list", iscsi_svc_walk_cb, &idc, svc_list_addr) == -1) { mdb_warn("list walk failed for idm services"); return (DCMD_ERR); } return (DCMD_OK); } return (iscsi_svc_impl(addr, &idc)); } /* * ::iscsi_portal -R * * iscsi_portal - Print out information associated with an iSCSI portal * * R Print reference count audit data */ static int iscsi_portal(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; GElf_Sym sym; iscsit_global_t iscsit_global; uintptr_t iscsit_global_addr; uintptr_t tpg_avl_addr; int rc_audit = 0; bzero(&idc, sizeof (iscsi_dcmd_ctrl_t)); if (mdb_getopts(argc, argv, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, NULL) != argc) return (DCMD_USAGE); idc.u.child.idc_rc_audit = rc_audit; idc.u.child.idc_portal = 1; if (DCMD_HDRSPEC(flags)) { idc.idc_header = 1; } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { mdb_warn("failed to find symbol 'iscsit_global'"); return (DCMD_ERR); } iscsit_global_addr = (uintptr_t)sym.st_value; /* get and print the global default tpg */ if (mdb_vread(&iscsit_global, sizeof (iscsit_global_t), iscsit_global_addr) != sizeof (iscsit_global_t)) { mdb_warn("failed to read iscsit_global_t"); return (DCMD_ERR); } if (iscsi_tpg_impl((uintptr_t)iscsit_global.global_default_tpg, &idc) != DCMD_OK) { return (DCMD_ERR); } /* Walk the tpgs for the rest of the portals */ tpg_avl_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_tpg_list); if (mdb_pwalk("avl", iscsi_tpg_walk_cb, &idc, tpg_avl_addr) == -1) { mdb_warn("list walk failed for global tpg tree"); return (DCMD_ERR); } return (DCMD_OK); } return (iscsi_portal_impl(addr, &idc)); } /* * ::iscsi_cmd -S * * iscsi_cmd - Print out information associated with an iSCSI cmd * * S Print state audit data */ static int iscsi_cmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; iscsi_cmd_t cmd; int states = 0; bzero(&idc, sizeof (iscsi_dcmd_ctrl_t)); if (mdb_getopts(argc, argv, 'S', MDB_OPT_SETBITS, TRUE, &states, NULL) != argc) return (DCMD_USAGE); idc.u.child.idc_states = states; idc.u.child.idc_cmd = 1; idc.idc_ini = 1; if (DCMD_HDRSPEC(flags)) { idc.idc_header = 1; } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_pwalk("iscsi_ini_hba", iscsi_ini_hba_walk_cb, &idc, 0) == -1) { mdb_warn("iscsi cmd hba list walk failed"); return (DCMD_ERR); } } else { if (mdb_vread(&cmd, sizeof (iscsi_cmd_t), addr) != sizeof (iscsi_cmd_t)) { return (DCMD_ERR); } return (iscsi_print_ini_cmd(addr, &cmd, &idc)); } return (DCMD_OK); } static int iscsi_ini_hba_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { iscsi_hba_t ih; if (mdb_vread(&ih, sizeof (ih), addr) != sizeof (ih)) { mdb_warn("Invalid HBA\n"); return (DCMD_ERR); } if (idc->u.child.idc_hba) { mdb_printf("iscsi_hba %p sessions: \n", addr); } if (mdb_pwalk("iscsi_ini_sess", iscsi_ini_sess_walk_cb, idc, (uintptr_t)ih.hba_sess_list) == -1) { mdb_warn("iscsi_sess_t walk failed"); return (DCMD_ERR); } return (DCMD_OK); } /* * ::iscsi_task [-bv] * * iscsi_task - Print out information associated with an iSCSI task * * b Print associated buffer information * S Print recent state events and transitions * R Print reference count audit data * v Verbose output about the connection */ /*ARGSUSED*/ static int iscsi_task(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; int buffer = 0; int states = 0, rc_audit = 0; bzero(&idc, sizeof (idc)); if (mdb_getopts(argc, argv, 'b', MDB_OPT_SETBITS, TRUE, &buffer, 'S', MDB_OPT_SETBITS, TRUE, &states, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, NULL) != argc) return (DCMD_USAGE); idc.u.child.idc_conn = 0; idc.u.child.idc_task = 1; idc.u.child.idc_buffer = buffer; idc.u.child.idc_states = states; idc.u.child.idc_rc_audit = rc_audit; if (DCMD_HDRSPEC(flags)) idc.idc_header = 1; /* * If no address was specified on the command line, we * print out all connections */ if (!(flags & DCMD_ADDRSPEC)) { return (iscsi_walk_all_conn(&idc)); } return (iscsi_task_impl(addr, &idc)); } /* * ::iscsi_refcnt * * iscsi_refcnt - Dump an idm_refcnt_t structure * */ /*ARGSUSED*/ static int iscsi_refcnt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_ERR); } return (iscsi_refcnt_impl(addr)); } /* * ::iscsi_states * * iscsi_states - Dump events and state transitions recoreded in an * idm_sm_audit_t structure * */ /*ARGSUSED*/ static int iscsi_states(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_ERR); } return (iscsi_sm_audit_impl(addr)); } static int iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc) { uintptr_t iscsit_global_addr; uintptr_t avl_addr; uintptr_t list_addr; GElf_Sym sym; /* Initiator sessions */ if (idc->idc_ini) { /* Always print hba info on this path */ idc->u.child.idc_hba = 1; if (mdb_pwalk("iscsi_ini_hba", iscsi_ini_hba_walk_cb, idc, 0) == -1) { mdb_warn("iscsi cmd hba list walk failed"); return (DCMD_ERR); } return (DCMD_OK); } /* Target sessions */ /* Walk discovery sessions */ if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { mdb_warn("failed to find symbol 'iscsit_global'"); return (DCMD_ERR); } iscsit_global_addr = (uintptr_t)sym.st_value; avl_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_discovery_sessions); if (mdb_pwalk("avl", iscsi_sess_walk_cb, idc, avl_addr) == -1) { mdb_warn("avl walk failed for discovery sessions"); return (DCMD_ERR); } /* Walk targets printing all session info */ avl_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_target_list); if (mdb_pwalk("avl", iscsi_tgt_walk_cb, idc, avl_addr) == -1) { mdb_warn("avl walk failed for target/session tree"); return (DCMD_ERR); } /* Walk deleting targets printing all session info */ list_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_deleted_target_list); if (mdb_pwalk("list", iscsi_tgt_walk_cb, idc, list_addr) == -1) { mdb_warn("list walk failed for deleted target list"); return (DCMD_ERR); } return (DCMD_OK); } static int iscsi_walk_all_conn(iscsi_dcmd_ctrl_t *idc) { uintptr_t idm_global_addr; uintptr_t list_addr; GElf_Sym sym; /* Walk initiator connections */ if (mdb_lookup_by_name("idm", &sym) == -1) { mdb_warn("failed to find symbol 'idm'"); return (DCMD_ERR); } idm_global_addr = (uintptr_t)sym.st_value; /* Walk connection list associated with the initiator */ list_addr = idm_global_addr + offsetof(idm_global_t, idm_ini_conn_list); if (mdb_pwalk("list", iscsi_conn_walk_cb, idc, list_addr) == -1) { mdb_warn("list walk failed for initiator connections"); return (DCMD_ERR); } /* Walk connection list associated with the target */ list_addr = idm_global_addr + offsetof(idm_global_t, idm_tgt_conn_list); if (mdb_pwalk("list", iscsi_conn_walk_cb, idc, list_addr) == -1) { mdb_warn("list walk failed for target service instances"); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ static int iscsi_tpg_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { /* We don't particularly care about the list walker data */ iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_tpg_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_tgt_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { /* We don't particularly care about the list walker data */ iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_tgt_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_tpgt_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { /* We don't particularly care about the list walker data */ iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_tpgt_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_portal_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { /* We don't particularly care about the list walker data */ iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_portal_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_sess_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { /* We don't particularly care about the list walker data */ iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_sess_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_sess_conn_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { /* We don't particularly care about the list walker data */ iscsi_dcmd_ctrl_t *idc = idc_void; iscsit_conn_t ict; int rc; /* * This function is different from iscsi_conn_walk_cb because * we get an iscsit_conn_t instead of an idm_conn_t * * Read iscsit_conn_t, use to get idm_conn_t pointer */ if (mdb_vread(&ict, sizeof (iscsit_conn_t), addr) != sizeof (iscsit_conn_t)) { return (DCMD_ERR); } rc = iscsi_conn_impl((uintptr_t)ict.ict_ic, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_conn_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { /* We don't particularly care about the list walker data */ iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_conn_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_buffer_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { /* We don't particularly care about the list walker data */ iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_buffer_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_svc_walk_cb(uintptr_t addr, const void *list_walker_data, void *idc_void) { iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_svc_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_ini_hba_walk_cb(uintptr_t addr, const void *vhba, void *idc_void) { iscsi_dcmd_ctrl_t *idc = idc_void; int rc; rc = iscsi_ini_hba_impl(addr, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } static int iscsi_ini_sess_walk_cb(uintptr_t addr, const void *vsess, void *idc_void) { int rc; if (vsess == NULL) { return (WALK_ERR); } rc = iscsi_print_ini_sess(addr, (iscsi_sess_t *)vsess, (iscsi_dcmd_ctrl_t *)idc_void); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } /*ARGSUSED*/ static int iscsi_ini_conn_walk_cb(uintptr_t addr, const void *vconn, void *idc_void) { const iscsi_conn_t *ict = vconn; int rc; if (vconn == NULL) { return (WALK_ERR); } /* * Look up the idm_conn_t in the iscsi_conn_t and call the general * connection handler. */ rc = iscsi_conn_impl((uintptr_t)ict->conn_ic, (iscsi_dcmd_ctrl_t *)idc_void); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } static int iscsi_ini_lun_walk_cb(uintptr_t addr, const void *vlun, void *idc_void) { int rc; if (vlun == NULL) { return (WALK_ERR); } rc = iscsi_print_ini_lun(addr, (iscsi_lun_t *)vlun, (iscsi_dcmd_ctrl_t *)idc_void); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } static int iscsi_tgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { iscsit_tgt_t tgt; uintptr_t avl_addr, rc_addr, states_addr; char tgt_name[MAX_ISCSI_NODENAMELEN]; int verbose, states, rc_audit; /* * Read iscsit_tgt_t */ if (mdb_vread(&tgt, sizeof (iscsit_tgt_t), addr) != sizeof (iscsit_tgt_t)) { return (DCMD_ERR); } /* * Read target name if available */ if ((tgt.target_name == NULL) || (mdb_readstr(tgt_name, sizeof (tgt_name), (uintptr_t)tgt.target_name) == -1)) { strcpy(tgt_name, "N/A"); } /* * Brief output * * iscsit_tgt_t pointer * iscsit_tgt_t.target_stmf_state * iscsit_tgt_t.target_sess_list.avl_numnodes (session count) * iscsit_tgt_t.target_name; */ verbose = idc->idc_verbose; states = idc->u.child.idc_states; rc_audit = idc->u.child.idc_rc_audit; /* For now we will ignore the verbose flag */ if (idc->u.child.idc_tgt) { /* Print target data */ if (idc->idc_header) { mdb_printf("%%-19s %-4s %-8s%\n", "iscsit_tgt_t", "Sess", "State"); } mdb_printf("%-19p %-4d %-8d\n", addr, tgt.target_sess_list.avl_numnodes, tgt.target_state); mdb_printf(" %s\n", tgt_name); /* Indent and disable verbose for any child structures */ mdb_inc_indent(4); idc->idc_verbose = 0; } /* * Print states if requested */ if (idc->u.child.idc_tgt && states) { states_addr = addr + offsetof(iscsit_tgt_t, target_state_audit); mdb_printf("State History(target_state_audit):\n"); if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) return (DCMD_ERR); idc->u.child.idc_states = 0; } /* * Print refcnt audit data if requested */ if (idc->u.child.idc_tgt && rc_audit) { mdb_printf("Reference History(target_sess_refcnt):\n"); rc_addr = addr + offsetof(iscsit_tgt_t, target_sess_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) return (DCMD_ERR); mdb_printf("Reference History(target_refcnt):\n"); rc_addr = addr + offsetof(iscsit_tgt_t, target_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) return (DCMD_ERR); idc->u.child.idc_rc_audit = 0; } /* Any child objects to walk? */ if (idc->u.child.idc_tpgt || idc->u.child.idc_portal) { if (idc->u.child.idc_tgt) { idc->idc_header = 1; } /* Walk TPGT tree */ avl_addr = addr + offsetof(iscsit_tgt_t, target_tpgt_list); if (mdb_pwalk("avl", iscsi_tpgt_walk_cb, idc, avl_addr) == -1) { mdb_warn("target tpgt list walk failed"); (void) mdb_dec_indent(4); return (DCMD_ERR); } } if (idc->u.child.idc_sess || idc->u.child.idc_conn || idc->u.child.idc_task || idc->u.child.idc_buffer) { if (idc->u.child.idc_tgt || idc->u.child.idc_tpgt || idc->u.child.idc_portal) { idc->idc_header = 1; } /* Walk sess tree */ avl_addr = addr + offsetof(iscsit_tgt_t, target_sess_list); if (mdb_pwalk("avl", iscsi_sess_walk_cb, idc, avl_addr) == -1) { mdb_warn("target sess list walk failed"); (void) mdb_dec_indent(4); return (DCMD_ERR); } } /* If tgts were handled decrease indent and reset header */ if (idc->u.child.idc_tgt) { idc->idc_header = 0; mdb_dec_indent(4); } idc->idc_verbose = verbose; idc->u.child.idc_states = states; idc->u.child.idc_rc_audit = rc_audit; return (DCMD_OK); } static int iscsi_tpgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { iscsit_tpgt_t tpgt; iscsit_tpg_t tpg; uintptr_t avl_addr, tpg_addr, rc_addr; int rc_audit; /* * Read iscsit_tpgt_t */ if (mdb_vread(&tpgt, sizeof (iscsit_tpgt_t), addr) != sizeof (iscsit_tpgt_t)) { return (DCMD_ERR); } tpg_addr = (uintptr_t)tpgt.tpgt_tpg; /* * Read iscsit_tpg_t */ if (mdb_vread(&tpg, sizeof (iscsit_tpg_t), tpg_addr) != sizeof (iscsit_tpg_t)) { return (DCMD_ERR); } rc_audit = idc->u.child.idc_rc_audit; /* * Brief output * * iscsit_tpgt_t pointer * iscsit_tpg_t pointer * iscsit_tpg_t.tpg_name * iscsit_tpgt_t.tpgt_tag; */ /* For now we will ignore the verbose flag */ if (idc->u.child.idc_tpgt) { /* Print target data */ if (idc->idc_header) { mdb_printf("%%-?s %-?s %-18s %-6s%\n", "iscsit_tpgt_t", "iscsit_tpg_t", "Name", "Tag"); } mdb_printf("%?p %?p %-18s 0x%04x\n", addr, tpgt.tpgt_tpg, tpg.tpg_name, tpgt.tpgt_tag); if (rc_audit) { (void) mdb_inc_indent(4); mdb_printf("Reference History(tpgt_refcnt):\n"); rc_addr = addr + offsetof(iscsit_tpgt_t, tpgt_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) return (DCMD_ERR); idc->u.child.idc_rc_audit = 0; (void) mdb_dec_indent(4); } } /* * Assume for now that anyone interested in TPGT wants to see the * portals as well. Enable idc_header for the portals. */ idc->idc_header = 1; (void) mdb_inc_indent(4); avl_addr = tpg_addr + offsetof(iscsit_tpg_t, tpg_portal_list); if (mdb_pwalk("avl", iscsi_portal_walk_cb, idc, avl_addr) == -1) { mdb_warn("portal list walk failed"); (void) mdb_dec_indent(4); return (DCMD_ERR); } (void) mdb_dec_indent(4); idc->idc_header = 0; idc->u.child.idc_rc_audit = rc_audit; return (DCMD_OK); } static int iscsi_tpg_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { iscsit_tpg_t tpg; uintptr_t avl_addr, rc_addr; int rc_audit = 0; rc_audit = idc->u.child.idc_rc_audit; /* * Read iscsit_tpg_t */ if (mdb_vread(&tpg, sizeof (iscsit_tpg_t), addr) != sizeof (iscsit_tpg_t)) { return (DCMD_ERR); } /* * Brief output * * iscsit_tpgt_t pointer * iscsit_tpg_t pointer * iscsit_tpg_t.tpg_name * iscsit_tpgt_t.tpgt_tag; */ /* Print tpg data */ if (idc->u.child.idc_tpg) { if (idc->idc_header) { mdb_printf("%%-?s %-18s%\n", "iscsit_tpg_t", "Name"); } mdb_printf("%?p %-18s\n", addr, tpg.tpg_name); (void) mdb_inc_indent(4); if (rc_audit) { mdb_printf("Reference History(tpg_refcnt):\n"); rc_addr = addr + offsetof(iscsit_tpg_t, tpg_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) { return (DCMD_ERR); } idc->u.child.idc_rc_audit = 0; } } if (idc->u.child.idc_portal) { if (idc->u.child.idc_tpg) { idc->idc_header = 1; } avl_addr = addr + offsetof(iscsit_tpg_t, tpg_portal_list); if (mdb_pwalk("avl", iscsi_portal_walk_cb, idc, avl_addr) == -1) { mdb_warn("portal list walk failed"); if (idc->u.child.idc_tpg) { (void) mdb_dec_indent(4); } return (DCMD_ERR); } } if (idc->u.child.idc_tpg) { (void) mdb_dec_indent(4); idc->idc_header = 0; } idc->u.child.idc_rc_audit = rc_audit; return (DCMD_OK); } static int iscsi_portal_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { iscsit_portal_t portal; char portal_addr[PORTAL_STR_LEN]; uintptr_t rc_addr; if (idc->u.child.idc_portal) { /* * Read iscsit_portal_t */ if (mdb_vread(&portal, sizeof (iscsit_portal_t), addr) != sizeof (iscsit_portal_t)) { return (DCMD_ERR); } /* Print portal data */ if (idc->idc_header) { mdb_printf("%%-?s %-?s %-30s%\n", "iscsit_portal_t", "idm_svc_t", "IP:Port"); idc->idc_header = 0; } sa_to_str(&portal.portal_addr, portal_addr); mdb_printf("%?p %?p %s\n", addr, portal.portal_svc, portal.portal_default ? "(Default)" : portal_addr); if (idc->u.child.idc_rc_audit) { (void) mdb_inc_indent(4); mdb_printf("Reference History(portal_refcnt):\n"); rc_addr = addr + offsetof(iscsit_portal_t, portal_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) { return (DCMD_ERR); } (void) mdb_dec_indent(4); } } return (DCMD_OK); } static int iscsi_sess_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { iscsit_sess_t ist; iscsi_sess_t ini_sess; uintptr_t list_addr, states_addr, rc_addr; char ini_name[80]; char tgt_name[80]; int verbose, states, rc_audit; if (idc->idc_ini) { if ((mdb_vread(&ini_sess, sizeof (iscsi_sess_t), (uintptr_t)addr)) != sizeof (iscsi_sess_t)) { mdb_warn("Failed to read initiator session\n"); return (DCMD_ERR); } if (iscsi_print_ini_sess(addr, &ini_sess, idc) != DCMD_OK) { return (DCMD_ERR); } return (DCMD_OK); } /* * Read iscsit_sess_t */ if (mdb_vread(&ist, sizeof (iscsit_sess_t), addr) != sizeof (iscsit_sess_t)) { return (DCMD_ERR); } /* * Brief output * * iscsit_sess_t pointer * iscsit_sess_t.ist_state/iscsit_sess_t.ist_ffp_conn_count * iscsit_sess_t.ist_tsih * iscsit_sess_t.ist_initiator_name */ verbose = idc->idc_verbose; states = idc->u.child.idc_states; rc_audit = idc->u.child.idc_rc_audit; if (idc->u.child.idc_sess) { if (verbose) { /* * Read initiator name if available */ if ((ist.ist_initiator_name == NULL) || (mdb_readstr(ini_name, sizeof (ini_name), (uintptr_t)ist.ist_initiator_name) == -1)) { strcpy(ini_name, "N/A"); } /* * Read target name if available */ if ((ist.ist_target_name == NULL) || (mdb_readstr(tgt_name, sizeof (tgt_name), (uintptr_t)ist.ist_target_name) == -1)) { strcpy(tgt_name, "N/A"); } mdb_printf("Session %p\n", addr); mdb_printf("%16s: %d\n", "State", ist.ist_state); mdb_printf("%16s: %d\n", "Last State", ist.ist_last_state); mdb_printf("%16s: %d\n", "FFP Connections", ist.ist_ffp_conn_count); mdb_printf("%16s: %02x%02x%02x%02x%02x%02x\n", "ISID", ist.ist_isid[0], ist.ist_isid[1], ist.ist_isid[2], ist.ist_isid[3], ist.ist_isid[4], ist.ist_isid[5]); mdb_printf("%16s: 0x%04x\n", "TSIH", ist.ist_tsih); mdb_printf("%16s: %s\n", "Initiator IQN", ini_name); mdb_printf("%16s: %s\n", "Target IQN", tgt_name); mdb_printf("%16s: %08x\n", "ExpCmdSN", ist.ist_expcmdsn); mdb_printf("%16s: %08x\n", "MaxCmdSN", ist.ist_maxcmdsn); idc->idc_verbose = 0; } else { /* Print session data */ if (idc->idc_header) { mdb_printf("%%-?s %10s %-12s %-6s%\n", "iscsit_sess_t", "State/Conn", "ISID", "TSIH"); } mdb_printf("%?p %4d/%-4d %02x%02x%02x%02x%02x%02x " "0x%04x\n", addr, ist.ist_state, ist.ist_ffp_conn_count, ist.ist_isid[0], ist.ist_isid[1], ist.ist_isid[2], ist.ist_isid[3], ist.ist_isid[4], ist.ist_isid[5], ist.ist_tsih); } /* * Indent for any child structures */ (void) mdb_inc_indent(4); } /* * Print states if requested */ if (idc->u.child.idc_sess && states) { states_addr = addr + offsetof(iscsit_sess_t, ist_state_audit); mdb_printf("State History(ist_state_audit):\n"); if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) return (DCMD_ERR); /* Don't print state history for child objects */ idc->u.child.idc_states = 0; } /* * Print refcnt audit data if requested */ if (idc->u.child.idc_sess && rc_audit) { mdb_printf("Reference History(ist_refcnt):\n"); rc_addr = addr + offsetof(iscsit_sess_t, ist_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) return (DCMD_ERR); /* Don't print audit data for child objects */ idc->u.child.idc_rc_audit = 0; } /* Any child objects to walk? */ if (idc->u.child.idc_conn || idc->u.child.idc_task || idc->u.child.idc_buffer) { /* * If a session has been printed enable headers for * any child structs. */ if (idc->u.child.idc_sess) { idc->idc_header = 1; } /* Walk conn list */ list_addr = addr + offsetof(iscsit_sess_t, ist_conn_list); if (mdb_pwalk("list", iscsi_sess_conn_walk_cb, idc, list_addr) == -1) { mdb_warn("session conn list walk failed"); (void) mdb_dec_indent(4); return (DCMD_ERR); } } /* If a session was handled decrease indent and reset header. */ if (idc->u.child.idc_sess) { idc->idc_header = 0; mdb_dec_indent(4); } idc->idc_verbose = verbose; idc->u.child.idc_states = states; idc->u.child.idc_rc_audit = rc_audit; return (DCMD_OK); } static int iscsi_print_ini_sess(uintptr_t addr, iscsi_sess_t *sess, iscsi_dcmd_ctrl_t *idc) { int verbose, states; uintptr_t states_addr; verbose = idc->idc_verbose; states = idc->u.child.idc_states; if (idc->u.child.idc_sess) { if (!idc->idc_verbose) { if (idc->idc_header) { mdb_printf("%%-?s %-4s %-8s%\n", "iscsi_sess_t", "Type", "State"); } mdb_printf("%-19p %-4d %-8d\n", addr, sess->sess_type, sess->sess_state); } else { mdb_printf("Session %p\n", addr); mdb_printf("%22s: %d\n", "State", sess->sess_state); mdb_printf("%22s: %d\n", "Last State", sess->sess_prev_state); mdb_printf("%22s: %s\n", "Session Name", sess->sess_name); mdb_printf("%22s: %s\n", "Alias", sess->sess_alias); mdb_printf("%22s: %08x\n", "CmdSN", sess->sess_cmdsn); mdb_printf("%22s: %08x\n", "ExpCmdSN", sess->sess_expcmdsn); mdb_printf("%22s: %08x\n", "MaxCmdSN", sess->sess_maxcmdsn); mdb_printf("%22s: %p\n", "Pending Queue Head", sess->sess_queue_pending.head); mdb_printf("%22s: %p\n", "Completion Queue Head", sess->sess_queue_completion.head); mdb_printf("%22s: %p\n", "Connnection List Head", sess->sess_conn_list); idc->idc_verbose = 0; } /* Indent for any child structures */ mdb_inc_indent(4); if (idc->u.child.idc_states) { states_addr = (uintptr_t)addr + offsetof(iscsi_sess_t, sess_state_audit); mdb_printf("State History(sess_state_audit):\n"); if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) { (void) mdb_dec_indent(4); return (DCMD_ERR); } idc->u.child.idc_states = 0; } } if (idc->u.child.idc_lun && sess->sess_lun_list) { if (idc->u.child.idc_sess) { idc->idc_header = 1; } if (mdb_pwalk("iscsi_ini_lun", iscsi_ini_lun_walk_cb, idc, (uintptr_t)sess->sess_lun_list) == -1) { mdb_warn("iscsi_ini_lun walk failed"); (void) mdb_dec_indent(4); return (DCMD_ERR); } } /* If requested print the cmds in the session queue */ if (idc->u.child.idc_cmd) { /* If any other structs printed enable header */ if (idc->u.child.idc_sess || idc->u.child.idc_lun) { idc->idc_header = 1; } if (sess->sess_queue_pending.head) { if (mdb_pwalk("iscsi_ini_cmd", iscsi_ini_cmd_walk_cb, idc, (uintptr_t)sess->sess_queue_pending.head) == -1) { mdb_warn("list walk failed for iscsi cmds"); } } if (sess->sess_queue_completion.head) { if (mdb_pwalk("iscsi_ini_cmd", iscsi_ini_cmd_walk_cb, idc, (uintptr_t)sess->sess_queue_completion.head) == -1) { mdb_warn("list walk failed for iscsi cmds"); } } } /* If connections or cmds requested walk the connections */ if (idc->u.child.idc_conn || idc->u.child.idc_cmd) { /* * If idc_conn is not set don't enable header or the * commands may get extraneous headers. */ if (idc->u.child.idc_conn) { idc->idc_header = 1; } if (mdb_pwalk("iscsi_ini_conn", iscsi_ini_conn_walk_cb, idc, (uintptr_t)sess->sess_conn_list) == -1) { mdb_warn("iscsi_ini_conn walk failed"); return (DCMD_ERR); } } /* If sessions were handled decrease indent and reset header */ if (idc->u.child.idc_sess) { idc->idc_header = 0; mdb_dec_indent(4); } idc->u.child.idc_states = states; idc->idc_verbose = verbose; return (DCMD_OK); } static int iscsi_conn_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { uintptr_t idm_global_addr, states_addr, rc_addr; uintptr_t task_addr, task_ptr; GElf_Sym sym; idm_task_t idt; idm_conn_t ic; iscsit_conn_t ict; iscsi_conn_t ini_conn; char *conn_type; int task_idx; char laddr[PORTAL_STR_LEN]; char raddr[PORTAL_STR_LEN]; int verbose, states, rc_audit; /* * Get pointer to task table */ if (mdb_lookup_by_name("idm", &sym) == -1) { mdb_warn("failed to find symbol 'idm'"); return (DCMD_ERR); } idm_global_addr = (uintptr_t)sym.st_value; if (mdb_vread(&task_ptr, sizeof (uintptr_t), idm_global_addr + offsetof(idm_global_t, idm_taskid_table)) != sizeof (uintptr_t)) { mdb_warn("Failed to read address of task table"); return (DCMD_ERR); } /* * Read idm_conn_t */ if (mdb_vread(&ic, sizeof (idm_conn_t), addr) != sizeof (idm_conn_t)) { return (DCMD_ERR); } /* * If filter bits are set to only print targets or only initiators * skip entries of the other type. */ if (!(idc->idc_ini && idc->idc_tgt) && ((idc->idc_ini && (ic.ic_conn_type != CONN_TYPE_INI)) || (idc->idc_tgt && (ic.ic_conn_type != CONN_TYPE_TGT)))) { return (DCMD_OK); } conn_type = (ic.ic_conn_type == CONN_TYPE_INI) ? "Ini" : (ic.ic_conn_type == CONN_TYPE_TGT) ? "Tgt" : "Unk"; /* * Brief output * * idm_conn_t pointer * idm_conn_t.ic_conn_type * idm_conn_t.ic_statet+idm_conn_t.ic_ffp */ verbose = idc->idc_verbose; states = idc->u.child.idc_states; rc_audit = idc->u.child.idc_rc_audit; /* * If targets(-T) and/or initiators (-I) are specifically requested, * fetch the iscsit_conn_t and/or iscsi_conn_t struct as a sanity * check and for use below. */ if (idc->idc_tgt && IDM_CONN_ISTGT(&ic)) { if (mdb_vread(&ict, sizeof (iscsit_conn_t), (uintptr_t)ic.ic_handle) != sizeof (iscsit_conn_t)) { mdb_printf("Failed to read target connection " "handle data\n"); return (DCMD_ERR); } } if (idc->idc_ini && IDM_CONN_ISINI(&ic)) { if (mdb_vread(&ini_conn, sizeof (iscsi_conn_t), (uintptr_t)ic.ic_handle) != sizeof (iscsi_conn_t)) { mdb_printf("Failed to read initiator " "connection handle data\n"); return (DCMD_ERR); } } if (idc->u.child.idc_conn) { if (idc->idc_verbose) { mdb_printf("IDM Conn %p\n", addr); if (ic.ic_conn_type == CONN_TYPE_TGT) { iscsi_print_iscsit_conn_data(&ic); } else { iscsi_print_ini_conn_data(&ic); } idc->idc_verbose = 0; } else { /* Print connection data */ if (idc->idc_header) { mdb_printf("%%-?s %-6s %-10s %12s%\n", "idm_conn_t", "Type", "Transport", "State/FFP"); } mdb_printf("%?p %-6s %-10s %6d/%-6d\n", addr, conn_type, (ic.ic_transport_type == IDM_TRANSPORT_TYPE_ISER) ? "ISER_IB" : (ic.ic_transport_type == IDM_TRANSPORT_TYPE_SOCKETS) ? "SOCKETS" : "N/A", ic.ic_state, ic.ic_ffp); if (idc->u.child.idc_print_ip) { sa_to_str(&ic.ic_laddr, laddr); sa_to_str(&ic.ic_raddr, raddr); mdb_printf(" L%s R%s\n", laddr, raddr); } } /* Indent for any child structs */ mdb_inc_indent(4); } /* * Print states if requested */ if (idc->u.child.idc_conn && states) { states_addr = addr + offsetof(idm_conn_t, ic_state_audit); mdb_printf("State History(ic_state_audit):\n"); if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) return (DCMD_ERR); /* * If targets are specifically requested show the * state audit for the target specific connection struct */ if (idc->idc_tgt && IDM_CONN_ISTGT(&ic)) { states_addr = (uintptr_t)ic.ic_handle + offsetof(iscsit_conn_t, ict_login_sm) + offsetof(iscsit_conn_login_t, icl_state_audit); mdb_printf("State History(icl_state_audit):\n"); if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) { return (DCMD_ERR); } } /* * If initiators are specifically requested show the * state audit for the initiator specific connection struct */ if (idc->idc_ini && IDM_CONN_ISINI(&ic)) { states_addr = (uintptr_t)ic.ic_handle + offsetof(iscsi_conn_t, conn_state_audit); mdb_printf("State History(iscsi_conn_t " "conn_state_audit):\n"); if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) { return (DCMD_ERR); } } /* Don't print state history for child objects */ idc->u.child.idc_states = 0; } /* * Print refcnt audit data for the connection struct if requested. */ if (idc->u.child.idc_conn && rc_audit) { mdb_printf("Reference History(ic_refcnt):\n"); rc_addr = addr + offsetof(idm_conn_t, ic_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) return (DCMD_ERR); /* * If targets are specifically requested show the * Refcounts for the target specific connection struct */ if (idc->idc_tgt && IDM_CONN_ISTGT(&ic)) { mdb_printf("Reference History(ict_refcnt):\n"); rc_addr = (uintptr_t)ic.ic_handle + offsetof(iscsit_conn_t, ict_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) { return (DCMD_ERR); } mdb_printf("Reference History(ict_dispatch_refcnt):\n"); rc_addr = (uintptr_t)ic.ic_handle + offsetof(iscsit_conn_t, ict_dispatch_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) { return (DCMD_ERR); } } /* Don't print audit data for child objects */ idc->u.child.idc_rc_audit = 0; } task_idx = 0; if (idc->u.child.idc_task || idc->u.child.idc_buffer) { if (idc->u.child.idc_conn) { idc->idc_header = 1; } while (task_idx < IDM_TASKIDS_MAX) { /* * Read the next idm_task_t */ if (mdb_vread(&task_addr, sizeof (uintptr_t), task_ptr) != sizeof (uintptr_t)) { mdb_warn("Failed to read task pointer"); return (DCMD_ERR); } if (task_addr == 0) { task_ptr += sizeof (uintptr_t); task_idx++; continue; } if (mdb_vread(&idt, sizeof (idm_task_t), task_addr) != sizeof (idm_task_t)) { mdb_warn("Failed to read task pointer"); return (DCMD_ERR); } if (((uintptr_t)idt.idt_ic == addr) && (idt.idt_state != TASK_IDLE)) { if (iscsi_i_task_impl(&idt, task_addr, idc) == -1) { mdb_warn("Failed to walk connection " "task tree"); return (DCMD_ERR); } } task_ptr += sizeof (uintptr_t); task_idx++; } } if (idc->idc_ini && IDM_CONN_ISINI(&ic) && idc->u.child.idc_cmd) { if (idc->u.child.idc_conn || idc->u.child.idc_task) { idc->idc_header = 1; } if (ini_conn.conn_queue_active.head && (mdb_pwalk("iscsi_ini_cmd", iscsi_ini_cmd_walk_cb, idc, (uintptr_t)ini_conn.conn_queue_active.head) == -1)) { mdb_warn("list walk failed for iscsi cmds"); } if (ini_conn.conn_queue_idm_aborting.head && (mdb_pwalk("iscsi_ini_cmd", iscsi_ini_cmd_walk_cb, idc, (uintptr_t)ini_conn.conn_queue_idm_aborting.head) == -1)) { mdb_warn("list walk failed for iscsi cmds"); } } /* * If connection information was handled unset header and * decrease indent */ if (idc->u.child.idc_conn) { idc->idc_header = 0; mdb_dec_indent(4); } idc->idc_verbose = verbose; idc->u.child.idc_states = states; idc->u.child.idc_rc_audit = rc_audit; return (DCMD_OK); } static int iscsi_svc_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { idm_svc_t svc; uintptr_t rc_addr; if (mdb_vread(&svc, sizeof (idm_svc_t), addr) != sizeof (idm_svc_t)) { return (DCMD_ERR); } if (idc->u.child.idc_svc) { if (idc->idc_verbose) { mdb_printf("Service %p\n", addr); mdb_printf("%20s: %d\n", "Port", svc.is_svc_req.sr_port); mdb_printf("%20s: %d\n", "Online", svc.is_online); mdb_printf("%20s: %p\n", "Socket Service", svc.is_so_svc); mdb_printf("%20s: %p\n", "iSER Service", svc.is_iser_svc); } else { if (idc->idc_header) { mdb_printf("%%-?s %-8s %-8s%\n", "idm_svc_t", "Port", "Online"); idc->idc_header = 0; } mdb_printf("%?p %-8d %-8d\n", addr, svc.is_svc_req.sr_port, svc.is_online); } if (idc->u.child.idc_rc_audit) { (void) mdb_inc_indent(4); mdb_printf("Reference History(is_refcnt):\n"); rc_addr = addr + offsetof(idm_svc_t, is_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) { (void) mdb_dec_indent(4); return (DCMD_ERR); } (void) mdb_dec_indent(4); } } return (DCMD_OK); } static void iscsi_print_iscsit_conn_data(idm_conn_t *ic) { iscsit_conn_t ict; char *csg; char *nsg; iscsi_print_idm_conn_data(ic); if (mdb_vread(&ict, sizeof (iscsit_conn_t), (uintptr_t)ic->ic_handle) != sizeof (iscsit_conn_t)) { mdb_printf("**Failed to read conn private data\n"); return; } mdb_printf("%20s: %p\n", "iSCSIT TGT Conn", ic->ic_handle); if (ict.ict_login_sm.icl_login_state != ILS_LOGIN_DONE) { switch (ict.ict_login_sm.icl_login_csg) { case ISCSI_SECURITY_NEGOTIATION_STAGE: csg = "Security"; break; case ISCSI_OP_PARMS_NEGOTIATION_STAGE: csg = "Operational"; break; case ISCSI_FULL_FEATURE_PHASE: csg = "FFP"; break; default: csg = "Unknown"; } switch (ict.ict_login_sm.icl_login_nsg) { case ISCSI_SECURITY_NEGOTIATION_STAGE: nsg = "Security"; break; case ISCSI_OP_PARMS_NEGOTIATION_STAGE: nsg = "Operational"; break; case ISCSI_FULL_FEATURE_PHASE: nsg = "FFP"; break; default: nsg = "Unknown"; } mdb_printf("%20s: %d\n", "Login State", ict.ict_login_sm.icl_login_state); mdb_printf("%20s: %d\n", "Login Last State", ict.ict_login_sm.icl_login_last_state); mdb_printf("%20s: %s\n", "CSG", csg); mdb_printf("%20s: %s\n", "NSG", nsg); mdb_printf("%20s: %d\n", "Transit", ict.ict_login_sm.icl_login_transit >> 7); mdb_printf("%20s: %p\n", "Request nvlist", ict.ict_login_sm.icl_request_nvlist); mdb_printf("%20s: %p\n", "Response nvlist", ict.ict_login_sm.icl_response_nvlist); mdb_printf("%20s: %p\n", "Negotiated nvlist", ict.ict_login_sm.icl_negotiated_values); if (ict.ict_login_sm.icl_login_state == ILS_LOGIN_ERROR) { mdb_printf("%20s: 0x%02x\n", "Error Class", ict.ict_login_sm.icl_login_resp_err_class); mdb_printf("%20s: 0x%02x\n", "Error Detail", ict.ict_login_sm.icl_login_resp_err_detail); } } mdb_printf("%20s: 0x%04x\n", "CID", ict.ict_cid); mdb_printf("%20s: 0x%08x\n", "StatSN", ict.ict_statsn); } static void iscsi_print_ini_conn_data(idm_conn_t *ic) { iscsi_conn_t ini_conn; iscsi_print_idm_conn_data(ic); if (mdb_vread(&ini_conn, sizeof (iscsi_conn_t), (uintptr_t)ic->ic_handle) != sizeof (iscsi_conn_t)) { mdb_printf("Failed to read conn private data\n"); return; } mdb_printf("%20s: %p\n", "iSCSI Ini Conn", ic->ic_handle); mdb_printf("%20s: %p\n", "Parent Session", ini_conn.conn_sess); mdb_printf("%20s: %d\n", "Conn State", ini_conn.conn_state); mdb_printf("%20s: %d\n", "Last Conn State", ini_conn.conn_prev_state); mdb_printf("%20s: %d\n", "Login Stage", ini_conn.conn_current_stage); mdb_printf("%20s: %d\n", "Next Login Stage", ini_conn.conn_next_stage); mdb_printf("%20s: 0x%08x\n", "Expected StatSN", ini_conn.conn_expstatsn); mdb_printf("%20s: %p\n", "Active Queue Head", ini_conn.conn_queue_active.head); mdb_printf("%20s: %d\n", "Abort Queue Head", ini_conn.conn_queue_idm_aborting.head); } static void iscsi_print_idm_conn_data(idm_conn_t *ic) { char laddr[PORTAL_STR_LEN]; char raddr[PORTAL_STR_LEN]; sa_to_str(&ic->ic_laddr, laddr); sa_to_str(&ic->ic_raddr, raddr); mdb_printf("%20s: %s\n", "Conn Type", ((ic->ic_conn_type == CONN_TYPE_TGT) ? "Target" : ((ic->ic_conn_type == CONN_TYPE_INI) ? "Initiator" : "Unknown"))); if (ic->ic_conn_type == CONN_TYPE_TGT) { mdb_printf("%20s: %p\n", "Svc. Binding", ic->ic_svc_binding); } mdb_printf("%20s: %s\n", "Transport", (ic->ic_transport_type == IDM_TRANSPORT_TYPE_ISER) ? "ISER_IB" : (ic->ic_transport_type == IDM_TRANSPORT_TYPE_SOCKETS) ? "SOCKETS" : "N/A"); mdb_printf("%20s: %s\n", "Local IP", laddr); mdb_printf("%20s: %s\n", "Remote IP", raddr); mdb_printf("%20s: %d\n", "State", ic->ic_state); mdb_printf("%20s: %d\n", "Last State", ic->ic_last_state); mdb_printf("%20s: %d %s\n", "Refcount", ic->ic_refcnt.ir_refcnt, (ic->ic_refcnt.ir_waiting == REF_NOWAIT) ? "" : ((ic->ic_refcnt.ir_waiting == REF_WAIT_SYNC) ? "REF_WAIT_SYNC" : ((ic->ic_refcnt.ir_waiting == REF_WAIT_ASYNC) ? "REF_WAIT_ASYNC" : "UNKNOWN"))); } static int iscsi_i_task_impl(idm_task_t *idt, uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { uintptr_t list_addr, rc_addr; idm_conn_type_t conn_type; int verbose, states, rc_audit; conn_type = idm_conn_type((uintptr_t)idt->idt_ic); verbose = idc->idc_verbose; states = idc->u.child.idc_states; rc_audit = idc->u.child.idc_rc_audit; if (idc->u.child.idc_task) { if (verbose) { mdb_printf("Task %p\n", addr); (void) mdb_inc_indent(2); if (conn_type == CONN_TYPE_TGT) { iscsi_print_iscsit_task_data(idt); } (void) mdb_dec_indent(2); } else { /* Print task data */ if (idc->idc_header) { mdb_printf( "%%-?s %-16s %-4s %-8s %-8s%\n", "Tasks:", "State", "Ref", (conn_type == CONN_TYPE_TGT ? "TTT" : (conn_type == CONN_TYPE_INI ? "ITT" : "TT")), "Handle"); } mdb_printf("%?p %-16s %04x %08x %08x\n", addr, idm_ts_name[idt->idt_state], idt->idt_refcnt.ir_refcnt, idt->idt_tt, idt->idt_client_handle); } } idc->idc_header = 0; idc->idc_verbose = 0; /* * Print states if requested */ #if 0 if (states) { states_addr = addr + offsetof(idm_task_t, idt_state_audit); (void) mdb_inc_indent(4); mdb_printf("State History(idt_state_audit):\n"); if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) return (DCMD_ERR); /* Don't print state history for child objects */ idc->u.child.idc_states = 0; (void) mdb_dec_indent(4); } #endif /* * Print refcnt audit data if requested */ if (rc_audit) { (void) mdb_inc_indent(4); mdb_printf("Reference History(idt_refcnt):\n"); rc_addr = addr + offsetof(idm_task_t, idt_refcnt); if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) return (DCMD_ERR); /* Don't print audit data for child objects */ idc->u.child.idc_rc_audit = 0; (void) mdb_dec_indent(4); } /* * Buffers are leaf objects and always get headers so the * user can discern between in and out buffers. */ if (idc->u.child.idc_buffer) { /* Walk in buffer list */ (void) mdb_inc_indent(2); mdb_printf("In buffers:\n"); idc->idc_header = 1; (void) mdb_inc_indent(2); list_addr = addr + offsetof(idm_task_t, idt_inbufv); if (mdb_pwalk("list", iscsi_buffer_walk_cb, idc, list_addr) == -1) { mdb_warn("list walk failed for task in buffers"); (void) mdb_dec_indent(4); return (DCMD_ERR); } (void) mdb_dec_indent(2); /* Walk out buffer list */ mdb_printf("Out buffers:\n"); idc->idc_header = 1; (void) mdb_inc_indent(2); list_addr = addr + offsetof(idm_task_t, idt_outbufv); if (mdb_pwalk("list", iscsi_buffer_walk_cb, idc, list_addr) == -1) { mdb_warn("list walk failed for task out buffers\n"); (void) mdb_dec_indent(2); return (DCMD_ERR); } (void) mdb_dec_indent(4); } idc->idc_verbose = verbose; idc->u.child.idc_states = states; idc->u.child.idc_rc_audit = rc_audit; return (DCMD_OK); } static int iscsi_task_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { idm_task_t idt; /* * Read idm_conn_t */ if (mdb_vread(&idt, sizeof (idm_task_t), addr) != sizeof (idm_task_t)) { return (DCMD_ERR); } return (iscsi_i_task_impl(&idt, addr, idc)); } #define ISCSI_CDB_INDENT 16 static void iscsi_print_iscsit_task_data(idm_task_t *idt) { iscsit_task_t itask; boolean_t good_scsi_task = B_TRUE; scsi_task_t scsi_task; if (mdb_vread(&itask, sizeof (iscsit_task_t), (uintptr_t)idt->idt_private) != sizeof (iscsit_task_t)) { mdb_printf("**Failed to read idt_private data\n"); return; } if (mdb_vread(&scsi_task, sizeof (scsi_task_t), (uintptr_t)itask.it_stmf_task) != sizeof (scsi_task_t)) { good_scsi_task = B_FALSE; } mdb_printf("%20s: %s(%d)\n", "State", idt->idt_state > TASK_MAX_STATE ? "UNKNOWN" : idm_ts_name[idt->idt_state], idt->idt_state); mdb_printf("%20s: %d/%d\n", "STMF abort/IDM aborted", itask.it_stmf_abort, itask.it_aborted); mdb_printf("%20s: %p/%p/%p%s\n", "iscsit/STMF/LU", idt->idt_private, itask.it_stmf_task, good_scsi_task ? scsi_task.task_lu_private : 0, good_scsi_task ? "" : "**"); if (good_scsi_task) { mdb_printf("%20s: %08x/%08x\n", "ITT/TTT", itask.it_itt, itask.it_ttt); mdb_printf("%20s: %08x\n", "CmdSN", itask.it_cmdsn); mdb_printf("%20s: %02x %02x %02x %02x %02x %02x %02x %02x\n", "LU number", scsi_task.task_lun_no[0], scsi_task.task_lun_no[1], scsi_task.task_lun_no[2], scsi_task.task_lun_no[3], scsi_task.task_lun_no[4], scsi_task.task_lun_no[5], scsi_task.task_lun_no[6], scsi_task.task_lun_no[7]); mdb_printf(" CDB (%d bytes):\n", scsi_task.task_cdb_length); (void) mdb_inc_indent(ISCSI_CDB_INDENT); if (mdb_dumpptr((uintptr_t)scsi_task.task_cdb, scsi_task.task_cdb_length, MDB_DUMP_RELATIVE | MDB_DUMP_TRIM | MDB_DUMP_GROUP(1), NULL, NULL)) { mdb_printf("** Invalid CDB addr (%p)\n", scsi_task.task_cdb); } (void) mdb_dec_indent(ISCSI_CDB_INDENT); mdb_printf("%20s: %d/%d\n", "STMF cur/max bufs", scsi_task.task_cur_nbufs, scsi_task.task_max_nbufs); mdb_printf("%20s: 0x%08x/0x%08x/0x%08x\n", "Bytes Exp/Cmd/Done", scsi_task.task_expected_xfer_length, scsi_task.task_cmd_xfer_length, scsi_task.task_nbytes_transferred); mdb_printf("%20s: 0x%x/0x%x\n", "TX-ini start/done", idt->idt_tx_to_ini_start, idt->idt_tx_to_ini_done); mdb_printf("%20s: 0x%x/0x%x\n", "RX-ini start/done", idt->idt_rx_from_ini_start, idt->idt_rx_from_ini_done); } } static int iscsi_print_ini_lun(uintptr_t addr, const iscsi_lun_t *lun, iscsi_dcmd_ctrl_t *idc) { if (idc->u.child.idc_lun) { if (idc->idc_header) { mdb_printf("%%-?s %-5s %-10s%\n", "iscsi_lun_t", "State", "Lun Number"); idc->idc_header = 0; } mdb_printf("%?p %-5d %-10d\n", addr, lun->lun_state, lun->lun_num); } return (DCMD_OK); } static int iscsi_print_ini_cmd(uintptr_t addr, const iscsi_cmd_t *cmd, iscsi_dcmd_ctrl_t *idc) { uintptr_t states_addr; if (idc->idc_header) { mdb_printf("%%-?s %-?s %4s %6s/%-6s %-?s%\n", "iscsi_cmd_t", "idm_task_t", "Type", "State", "Prev", "iscsi_lun_t"); idc->idc_header = 0; } mdb_printf("%?p %?p %4d %6d/%-6d %?p\n", addr, cmd->cmd_itp, cmd->cmd_type, cmd->cmd_state, cmd->cmd_prev_state, cmd->cmd_lun); /* * Print states if requested */ if (idc->u.child.idc_states) { states_addr = addr + offsetof(iscsi_cmd_t, cmd_state_audit); (void) mdb_inc_indent(4); mdb_printf("State History(cmd_state_audit):\n"); if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) return (DCMD_ERR); idc->u.child.idc_states = 0; (void) mdb_dec_indent(4); } return (DCMD_OK); } static int iscsi_buffer_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) { idm_buf_t idb; /* * Read idm_buf_t */ if (mdb_vread(&idb, sizeof (idm_buf_t), addr) != sizeof (idm_buf_t)) { return (DCMD_ERR); } if (idc->idc_header) { mdb_printf("%%-?s %?s/%-8s %8s %8s %8s%\n", "idm_buf_t", "Mem Rgn", "Length", "Rel Off", "Xfer Len", "Exp. Off"); idc->idc_header = 0; } /* Print buffer data */ mdb_printf("%?p %?p/%08x %8x %8x %08x\n", addr, idb.idb_buf, idb.idb_buflen, idb.idb_bufoffset, idb.idb_xfer_len, idb.idb_exp_offset); /* Buffers are leaf objects */ return (DCMD_OK); } static int iscsi_refcnt_impl(uintptr_t addr) { idm_refcnt_t refcnt; refcnt_audit_buf_t *anb; int ctr; /* * Print refcnt info */ if (mdb_vread(&refcnt, sizeof (idm_refcnt_t), addr) != sizeof (idm_refcnt_t)) { mdb_warn("read refcnt failed"); return (DCMD_ERR); } anb = &refcnt.ir_audit_buf; ctr = anb->anb_max_index + 1; anb->anb_index--; anb->anb_index &= anb->anb_max_index; while (ctr) { refcnt_audit_record_t *anr; anr = anb->anb_records + anb->anb_index; if (anr->anr_depth) { char c[MDB_SYM_NAMLEN]; GElf_Sym sym; int i; mdb_printf("\nRefCnt: %u\t", anr->anr_refcnt); for (i = 0; i < anr->anr_depth; i++) { if (mdb_lookup_by_addr(anr->anr_stack[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) { continue; } mdb_printf("%s+0x%1x", c, anr->anr_stack[i] - (uintptr_t)sym.st_value); ++i; break; } while (i < anr->anr_depth) { if (mdb_lookup_by_addr(anr->anr_stack[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) { ++i; continue; } mdb_printf("\n\t\t%s+0x%1x", c, anr->anr_stack[i] - (uintptr_t)sym.st_value); ++i; } mdb_printf("\n"); } anb->anb_index--; anb->anb_index &= anb->anb_max_index; ctr--; } return (DCMD_OK); } static int iscsi_sm_audit_impl(uintptr_t addr) { sm_audit_buf_t audit_buf; int ctr; const char *event_name; const char *state_name; const char *new_state_name; char ts_string[40]; /* * Print refcnt info */ if (mdb_vread(&audit_buf, sizeof (sm_audit_buf_t), addr) != sizeof (sm_audit_buf_t)) { mdb_warn("failed to read audit buf"); return (DCMD_ERR); } ctr = audit_buf.sab_max_index + 1; audit_buf.sab_index++; audit_buf.sab_index &= audit_buf.sab_max_index; while (ctr) { sm_audit_record_t *sar; sar = audit_buf.sab_records + audit_buf.sab_index; iscsi_format_timestamp(ts_string, 40, &sar->sar_timestamp); switch (sar->sar_type) { case SAR_STATE_EVENT: switch (sar->sar_sm_type) { case SAS_IDM_CONN: state_name = iscsi_idm_conn_state(sar->sar_state); event_name = iscsi_idm_conn_event(sar->sar_event); break; case SAS_ISCSIT_TGT: state_name = iscsi_iscsit_tgt_state(sar->sar_state); event_name = iscsi_iscsit_tgt_event(sar->sar_event); break; case SAS_ISCSIT_SESS: state_name = iscsi_iscsit_sess_state(sar->sar_state); event_name = iscsi_iscsit_sess_event(sar->sar_event); break; case SAS_ISCSIT_LOGIN: state_name = iscsi_iscsit_login_state(sar->sar_state); event_name = iscsi_iscsit_login_event(sar->sar_event); break; case SAS_ISCSI_CMD: state_name = iscsi_iscsi_cmd_state(sar->sar_state); event_name= iscsi_iscsi_cmd_event(sar->sar_event); break; case SAS_ISCSI_SESS: state_name = iscsi_iscsi_sess_state(sar->sar_state); event_name= iscsi_iscsi_sess_event(sar->sar_event); break; case SAS_ISCSI_CONN: state_name = iscsi_iscsi_conn_state(sar->sar_state); event_name= iscsi_iscsi_conn_event(sar->sar_event); break; default: state_name = event_name = "N/A"; break; } mdb_printf("%s|%s (%d)\n\t%9s %s (%d) %p\n", ts_string, state_name, sar->sar_state, "Event", event_name, sar->sar_event, sar->sar_event_info); break; case SAR_STATE_CHANGE: switch (sar->sar_sm_type) { case SAS_IDM_CONN: state_name = iscsi_idm_conn_state(sar->sar_state); new_state_name = iscsi_idm_conn_state(sar->sar_new_state); break; case SAS_IDM_TASK: state_name = iscsi_idm_task_state(sar->sar_state); new_state_name = iscsi_idm_task_state(sar->sar_new_state); break; case SAS_ISCSIT_TGT: state_name = iscsi_iscsit_tgt_state(sar->sar_state); new_state_name = iscsi_iscsit_tgt_state(sar->sar_new_state); break; case SAS_ISCSIT_SESS: state_name = iscsi_iscsit_sess_state(sar->sar_state); new_state_name = iscsi_iscsit_sess_state(sar->sar_new_state); break; case SAS_ISCSIT_LOGIN: state_name = iscsi_iscsit_login_state(sar->sar_state); new_state_name = iscsi_iscsit_login_state( sar->sar_new_state); break; case SAS_ISCSI_CMD: state_name = iscsi_iscsi_cmd_state(sar->sar_state); new_state_name= iscsi_iscsi_cmd_state(sar->sar_new_state); break; case SAS_ISCSI_SESS: state_name = iscsi_iscsi_sess_state(sar->sar_state); new_state_name= iscsi_iscsi_sess_state(sar->sar_new_state); break; case SAS_ISCSI_CONN: state_name = iscsi_iscsi_conn_state(sar->sar_state); new_state_name= iscsi_iscsi_conn_state(sar->sar_new_state); break; case SAS_ISCSI_LOGIN: state_name = iscsi_iscsi_login_state(sar->sar_state); new_state_name= iscsi_iscsi_login_state(sar->sar_new_state); break; default: state_name = new_state_name = "N/A"; break; } mdb_printf("%s|%s (%d)\n\t%9s %s (%d)\n", ts_string, state_name, sar->sar_state, "New State", new_state_name, sar->sar_new_state); break; default: break; } audit_buf.sab_index++; audit_buf.sab_index &= audit_buf.sab_max_index; ctr--; } return (DCMD_OK); } static const char * iscsi_idm_conn_event(unsigned int event) { return ((event < CE_MAX_EVENT) ? idm_ce_name[event] : "N/A"); } static const char * iscsi_iscsit_tgt_event(unsigned int event) { return ((event < TE_MAX_EVENT) ? iscsit_te_name[event] : "N/A"); } static const char * iscsi_iscsit_sess_event(unsigned int event) { return ((event < SE_MAX_EVENT) ? iscsit_se_name[event] : "N/A"); } static const char * iscsi_iscsit_login_event(unsigned int event) { return ((event < ILE_MAX_EVENT) ? iscsit_ile_name[event] : "N/A"); } static const char * iscsi_iscsi_cmd_event(unsigned int event) { return ((event < ISCSI_CMD_EVENT_MAX) ? iscsi_cmd_event_names[event] : "N/A"); } static const char * iscsi_iscsi_sess_event(unsigned int event) { return ((event < ISCSI_SESS_EVENT_MAX) ? iscsi_sess_event_names[event] : "N/A"); } static const char * iscsi_idm_conn_state(unsigned int state) { return ((state < CS_MAX_STATE) ? idm_cs_name[state] : "N/A"); } static const char * iscsi_iscsi_conn_event(unsigned int event) { return ((event < CN_MAX) ? idm_cn_strings[event] : "N/A"); } /*ARGSUSED*/ static const char * iscsi_idm_task_state(unsigned int state) { return ("N/A"); } static const char * iscsi_iscsit_tgt_state(unsigned int state) { return ((state < TS_MAX_STATE) ? iscsit_ts_name[state] : "N/A"); } static const char * iscsi_iscsit_sess_state(unsigned int state) { return ((state < SS_MAX_STATE) ? iscsit_ss_name[state] : "N/A"); } static const char * iscsi_iscsit_login_state(unsigned int state) { return ((state < ILS_MAX_STATE) ? iscsit_ils_name[state] : "N/A"); } static const char * iscsi_iscsi_cmd_state(unsigned int state) { return ((state < ISCSI_CMD_STATE_MAX) ? iscsi_cmd_state_names[state] : "N/A"); } static const char * iscsi_iscsi_sess_state(unsigned int state) { return ((state < ISCSI_SESS_STATE_MAX) ? iscsi_sess_state_names[state] : "N/A"); } static const char * iscsi_iscsi_conn_state(unsigned int state) { return ((state < ISCSI_CONN_STATE_MAX) ? iscsi_ics_name[state] : "N/A"); } static const char * iscsi_iscsi_login_state(unsigned int state) { return ((state < LOGIN_MAX) ? iscsi_login_state_names[state] : "N/A"); } /* * Retrieve connection type given a kernel address */ static idm_conn_type_t idm_conn_type(uintptr_t addr) { idm_conn_type_t result = 0; /* Unknown */ uintptr_t idm_conn_type_addr; idm_conn_type_addr = addr + offsetof(idm_conn_t, ic_conn_type); (void) mdb_vread(&result, sizeof (result), idm_conn_type_addr); return (result); } /* * Convert a sockaddr to the string representation, suitable for * storing in an nvlist or printing out in a list. */ static int sa_to_str(struct sockaddr_storage *sa, char *buf) { char pbuf[7]; const char *bufp; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; uint16_t port; if (!sa || !buf) { return (EINVAL); } buf[0] = '\0'; if (sa->ss_family == AF_INET) { sin = (struct sockaddr_in *)sa; bufp = iscsi_inet_ntop(AF_INET, (const void *)&(sin->sin_addr.s_addr), buf, PORTAL_STR_LEN); if (bufp == NULL) { return (-1); } mdb_nhconvert(&port, &sin->sin_port, sizeof (uint16_t)); } else if (sa->ss_family == AF_INET6) { strlcat(buf, "[", sizeof (buf)); sin6 = (struct sockaddr_in6 *)sa; bufp = iscsi_inet_ntop(AF_INET6, (const void *)&sin6->sin6_addr.s6_addr, &buf[1], PORTAL_STR_LEN - 1); if (bufp == NULL) { return (-1); } strlcat(buf, "]", PORTAL_STR_LEN); mdb_nhconvert(&port, &sin6->sin6_port, sizeof (uint16_t)); } else { return (EINVAL); } mdb_snprintf(pbuf, sizeof (pbuf), ":%u", port); strlcat(buf, pbuf, PORTAL_STR_LEN); return (0); } static void iscsi_format_timestamp(char *ts_str, int strlen, timespec_t *ts) { mdb_snprintf(ts_str, strlen, "%Y:%03d:%03d:%03d", ts->tv_sec, (ts->tv_nsec / 1000000) % 1000, (ts->tv_nsec / 1000) % 1000, ts->tv_nsec % 1000); } /* * Help information for the iscsi_isns dcmd */ static void iscsi_isns_help(void) { mdb_printf("iscsi_isns:\n"); mdb_inc_indent(4); mdb_printf("-e: Print ESI information\n"); mdb_printf("-p: Print portal information\n"); mdb_printf("-s: Print iSNS server information\n"); mdb_printf("-t: Print target information\n"); mdb_printf("-v: Add verbosity to the other options' output\n"); mdb_printf("-R: Add Refcount information to '-t' output\n"); mdb_dec_indent(4); } /* ARGSUSED */ static int iscsi_isns_esi_cb(uintptr_t addr, const void *walker_data, void *data) { isns_esi_tinfo_t tinfo; if (mdb_vread(&tinfo, sizeof (isns_esi_tinfo_t), addr) != sizeof (isns_esi_tinfo_t)) { return (WALK_ERR); } mdb_printf("ESI thread/thr did : 0x%p / %d\n", tinfo.esi_thread, tinfo.esi_thread_did); mdb_printf("ESI sonode : 0x%p\n", tinfo.esi_so); mdb_printf("ESI port : %d\n", tinfo.esi_port); mdb_printf("ESI thread running : %s\n", (tinfo.esi_thread_running) ? "Yes" : "No"); return (WALK_NEXT); } static int iscsi_isns_esi(iscsi_dcmd_ctrl_t *idc) { GElf_Sym sym; uintptr_t addr; if (mdb_lookup_by_name("esi", &sym) == -1) { mdb_warn("failed to find symbol 'esi_list'"); return (DCMD_ERR); } addr = (uintptr_t)sym.st_value; idc->idc_header = 1; (void) iscsi_isns_esi_cb(addr, NULL, idc); return (0); } /* ARGSUSED */ static int iscsi_isns_portal_cb(uintptr_t addr, const void *walker_data, void *data) { iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data; isns_portal_t portal; char portal_addr[PORTAL_STR_LEN]; struct sockaddr_storage *ss; char ts_string[40]; if (mdb_vread(&portal, sizeof (isns_portal_t), addr) != sizeof (isns_portal_t)) { return (WALK_ERR); } ss = &portal.portal_addr; sa_to_str(ss, portal_addr); mdb_printf("Portal IP address "); if (ss->ss_family == AF_INET) { mdb_printf("(v4): %s", portal_addr); } else { mdb_printf("(v6): %s", portal_addr); } if (portal.portal_default == B_TRUE) { mdb_printf(" (Default portal)\n"); } else { mdb_printf("\n"); } if (portal.portal_iscsit != NULL) { mdb_printf("(Part of TPG: 0x%p)\n", portal.portal_iscsit); } iscsi_format_timestamp(ts_string, 40, &portal.portal_esi_timestamp); mdb_printf("Portal ESI timestamp: %s\n\n", ts_string); if ((portal.portal_iscsit != NULL) && (idc->idc_verbose)) { mdb_inc_indent(4); iscsi_portal_impl((uintptr_t)portal.portal_iscsit, idc); mdb_dec_indent(4); } return (WALK_NEXT); } static int iscsi_isns_portals(iscsi_dcmd_ctrl_t *idc) { GElf_Sym sym; uintptr_t portal_list; mdb_printf("All Active Portals:\n"); if (mdb_lookup_by_name("isns_all_portals", &sym) == -1) { mdb_warn("failed to find symbol 'isns_all_portals'"); return (DCMD_ERR); } portal_list = (uintptr_t)sym.st_value; idc->idc_header = 1; if (mdb_pwalk("avl", iscsi_isns_portal_cb, idc, portal_list) == -1) { mdb_warn("avl walk failed for isns_all_portals"); return (DCMD_ERR); } mdb_printf("\nPortals from TPGs:\n"); if (mdb_lookup_by_name("isns_tpg_portals", &sym) == -1) { mdb_warn("failed to find symbol 'isns_tpg_portals'"); return (DCMD_ERR); } portal_list = (uintptr_t)sym.st_value; idc->idc_header = 1; if (mdb_pwalk("avl", iscsi_isns_portal_cb, idc, portal_list) == -1) { mdb_warn("avl walk failed for isns_tpg_portals"); return (DCMD_ERR); } return (0); } /* ARGSUSED */ static int iscsi_isns_targets_cb(uintptr_t addr, const void *walker_data, void *data) { iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data; isns_target_t itarget; int rc = 0; int rc_audit = 0; uintptr_t rc_addr; if (mdb_vread(&itarget, sizeof (isns_target_t), addr) != sizeof (isns_target_t)) { return (WALK_ERR); } idc->idc_header = 1; rc_audit = idc->u.child.idc_rc_audit; mdb_printf("Target: %p\n", addr); mdb_inc_indent(4); mdb_printf("Registered: %s\n", (itarget.target_registered) ? "Yes" : "No"); mdb_printf("Update needed: %s\n", (itarget.target_update_needed) ? "Yes" : "No"); mdb_printf("Target Info: %p\n", itarget.target_info); /* Prevent target refcounts from showing through this path */ idc->u.child.idc_rc_audit = 0; rc = iscsi_tgt_impl((uintptr_t)itarget.target, idc); idc->u.child.idc_rc_audit = rc_audit; if (idc->u.child.idc_rc_audit) { rc_addr = (uintptr_t)itarget.target_info + offsetof(isns_target_info_t, ti_refcnt); mdb_printf("Reference History(isns_target_info ti_refcnt):\n"); if (iscsi_refcnt_impl(rc_addr) != 0) { return (WALK_ERR); } } mdb_dec_indent(4); if (rc == DCMD_OK) { return (WALK_NEXT); } return (WALK_ERR); } static int iscsi_isns_targets(iscsi_dcmd_ctrl_t *idc) { GElf_Sym sym; uintptr_t isns_target_list; if (mdb_lookup_by_name("isns_target_list", &sym) == -1) { mdb_warn("failed to find symbol 'isns_target_list'"); return (DCMD_ERR); } isns_target_list = (uintptr_t)sym.st_value; idc->idc_header = 1; idc->u.child.idc_tgt = 1; if (mdb_pwalk("avl", iscsi_isns_targets_cb, idc, isns_target_list) == -1) { mdb_warn("avl walk failed for isns_target_list"); return (DCMD_ERR); } return (0); } /* ARGSUSED */ static int iscsi_isns_servers_cb(uintptr_t addr, const void *walker_data, void *data) { iscsit_isns_svr_t server; char server_addr[PORTAL_STR_LEN]; struct sockaddr_storage *ss; clock_t lbolt; iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data; uintptr_t avl_addr; if (mdb_vread(&server, sizeof (iscsit_isns_svr_t), addr) != sizeof (iscsit_isns_svr_t)) { return (WALK_ERR); } if ((lbolt = (clock_t)mdb_get_lbolt()) == -1) return (WALK_ERR); mdb_printf("iSNS server %p:\n", addr); mdb_inc_indent(4); ss = &server.svr_sa; sa_to_str(ss, server_addr); mdb_printf("IP address "); if (ss->ss_family == AF_INET) { mdb_printf("(v4): %s\n", server_addr); } else { mdb_printf("(v6): %s\n", server_addr); } mdb_printf("ESI Interval: %d seconds\n", server.svr_esi_interval); mdb_printf("Last message: %d seconds ago\n", ((lbolt - server.svr_last_msg) / 100)); mdb_printf("Client registered: %s\n", (server.svr_registered) ? "Yes" : "No"); mdb_printf("Retry Count: %d\n", server.svr_retry_count); mdb_printf("Targets Changes Pending: %s\n", (server.svr_targets_changed) ? "Yes" : "No"); mdb_printf("Delete Pending: %s\n", (server.svr_delete_needed) ? "Yes" : "No"); mdb_printf("Replace-All Needed: %s\n", (server.svr_reset_needed) ? "Yes" : "No"); if (idc->idc_verbose) { idc->idc_header = 1; idc->u.child.idc_tgt = 1; mdb_inc_indent(2); avl_addr = addr + offsetof(iscsit_isns_svr_t, svr_target_list); if (mdb_pwalk("avl", iscsi_isns_targets_cb, idc, avl_addr) == -1) { mdb_warn("avl walk failed for svr_target_list"); return (WALK_ERR); } mdb_dec_indent(2); } mdb_dec_indent(4); return (WALK_NEXT); } static int iscsi_isns_servers(iscsi_dcmd_ctrl_t *idc) { uintptr_t iscsit_global_addr; uintptr_t list_addr; GElf_Sym sym; if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { mdb_warn("failed to find symbol 'iscsit_global'"); return (DCMD_ERR); } iscsit_global_addr = (uintptr_t)sym.st_value; idc->idc_header = 1; list_addr = iscsit_global_addr + offsetof(iscsit_global_t, global_isns_cfg.isns_svrs); if (mdb_pwalk("list", iscsi_isns_servers_cb, idc, list_addr) == -1) { mdb_warn("list walk failed for iSNS servers"); return (DCMD_ERR); } return (0); } /* ARGSUSED */ static int iscsi_isns(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { iscsi_dcmd_ctrl_t idc; int portals = 0, esi = 0, targets = 0, verbose = 0, servers = 0; int rc_audit = 0; if (flags & DCMD_ADDRSPEC) { mdb_warn("iscsi_isns is only a global dcmd."); return (DCMD_ERR); } bzero(&idc, sizeof (idc)); if (mdb_getopts(argc, argv, 'e', MDB_OPT_SETBITS, TRUE, &esi, 'p', MDB_OPT_SETBITS, TRUE, &portals, 's', MDB_OPT_SETBITS, TRUE, &servers, 't', MDB_OPT_SETBITS, TRUE, &targets, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, NULL) != argc) return (DCMD_USAGE); if ((esi + portals + targets + servers) > 1) { mdb_printf("Only one of e, p, s, and t must be provided"); return (DCMD_ERR); } if ((esi | portals | targets | servers) == 0) { mdb_printf("Exactly one of e, p, s, or t must be provided"); return (DCMD_ERR); } idc.idc_verbose = verbose; idc.u.child.idc_rc_audit = rc_audit; if (esi) { return (iscsi_isns_esi(&idc)); } if (portals) { return (iscsi_isns_portals(&idc)); } if (servers) { return (iscsi_isns_servers(&idc)); } return (iscsi_isns_targets(&idc)); } static int iscsi_ini_sess_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("::walk iscsi_ini_sess"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (iscsi_sess_t), UM_SLEEP|UM_GC); if (!wsp->walk_data) { mdb_warn("iscsi_ini_sess walk failed"); return (WALK_ERR); } return (WALK_NEXT); } static int iscsi_ini_sess_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (iscsi_sess_t), wsp->walk_addr) != sizeof (iscsi_sess_t)) { mdb_warn("failed to read iscsi_sess_t at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((iscsi_sess_t *)wsp->walk_data)->sess_next); return (status); } static int iscsi_ini_conn_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("::walk iscsi_ini_conn"); return (WALK_DONE); } wsp->walk_data = mdb_alloc(sizeof (iscsi_conn_t), UM_SLEEP|UM_GC); if (!wsp->walk_data) { mdb_warn("iscsi_ini_conn walk failed"); return (WALK_ERR); } return (WALK_NEXT); } static int iscsi_ini_conn_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (iscsi_conn_t), wsp->walk_addr) != sizeof (iscsi_conn_t)) { mdb_warn("failed to read iscsi_conn_t at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((iscsi_conn_t *)wsp->walk_data)->conn_next); return (status); } static int iscsi_ini_lun_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("::walk iscsi_ini_lun"); return (WALK_DONE); } wsp->walk_data = mdb_alloc(sizeof (iscsi_lun_t), UM_SLEEP|UM_GC); if (!wsp->walk_data) { return (WALK_ERR); } return (WALK_NEXT); } static int iscsi_ini_lun_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (iscsi_lun_t), wsp->walk_addr) != sizeof (iscsi_lun_t)) { mdb_warn("failed to read iscsi_lun_t at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((iscsi_lun_t *)wsp->walk_data)->lun_next); return (status); } static int iscsi_ini_cmd_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("::walk iscsi_ini_cmd"); return (WALK_DONE); } wsp->walk_data = mdb_alloc(sizeof (iscsi_cmd_t), UM_SLEEP|UM_GC); if (!wsp->walk_data) { return (WALK_ERR); } return (WALK_NEXT); } static int iscsi_ini_cmd_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (iscsi_cmd_t), wsp->walk_addr) != sizeof (iscsi_cmd_t)) { mdb_warn("failed to read iscsi_cmd_t at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((iscsi_cmd_t *)wsp->walk_data)->cmd_next); return (status); } static int iscsi_ini_cmd_walk_cb(uintptr_t addr, const void *vcmd, void *vidc) { const iscsi_cmd_t *cmd = vcmd; iscsi_dcmd_ctrl_t *idc = vidc; int rc; if (cmd == NULL) { mdb_warn("list walk failed. Null cmd"); return (WALK_ERR); } rc = iscsi_print_ini_cmd(addr, cmd, idc); return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); } static int iscsi_ini_hba_walk_init(mdb_walk_state_t *wsp) { uintptr_t state_addr, array_addr; int array_size; struct i_ddi_soft_state *ss; idm_hba_walk_info_t *hwi; hwi = (idm_hba_walk_info_t *)mdb_zalloc( sizeof (idm_hba_walk_info_t), UM_SLEEP|UM_GC); if (!hwi) { mdb_warn("unable to allocate storage for iscsi_ini_hba walk"); return (WALK_ERR); } if (wsp->walk_addr != 0) { mdb_warn("iscsi_ini_hba only supports global walk"); return (WALK_ERR); } else { /* * Read in the array and setup the walk struct. */ if (mdb_readvar(&state_addr, "iscsi_state") == -1) { mdb_warn("state variable iscsi_state not found.\n"); mdb_warn("Is the driver loaded ?\n"); return (WALK_ERR); } ss = (struct i_ddi_soft_state *)mdb_alloc(sizeof (*ss), UM_SLEEP|UM_GC); if (mdb_vread(ss, sizeof (*ss), state_addr) != sizeof (*ss)) { mdb_warn("Cannot read softstate struct " "(Invalid pointer?).\n"); return (WALK_ERR); } /* Where to get the data */ array_size = ss->n_items * (sizeof (void *)); array_addr = (uintptr_t)ss->array; /* Where to put the data */ hwi->n_elements = ss->n_items; hwi->array = mdb_alloc(array_size, UM_SLEEP|UM_GC); if (!hwi->array) { mdb_warn("list walk failed"); return (WALK_ERR); } if (mdb_vread(hwi->array, array_size, array_addr) != array_size) { mdb_warn("Corrupted softstate struct.\n"); return (WALK_ERR); } hwi->cur_element = 0; wsp->walk_data = hwi; } return (WALK_NEXT); } static int iscsi_ini_hba_step(mdb_walk_state_t *wsp) { int status; idm_hba_walk_info_t *hwi = (idm_hba_walk_info_t *)wsp->walk_data; for (; hwi->cur_element < hwi->n_elements; hwi->cur_element++) { if (hwi->array[hwi->cur_element] != NULL) { break; } } if (hwi->cur_element >= hwi->n_elements) { return (WALK_DONE); } hwi->data = (iscsi_hba_t *)mdb_alloc(sizeof (iscsi_hba_t), UM_SLEEP|UM_GC); if (mdb_vread(hwi->data, sizeof (iscsi_hba_t), (uintptr_t)hwi->array[hwi->cur_element]) != sizeof (iscsi_hba_t)) { mdb_warn("failed to read iscsi_sess_t at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback((uintptr_t)hwi->array[hwi->cur_element], hwi->data, wsp->walk_cbdata); /* Increment cur_element for next iteration */ hwi->cur_element++; return (status); } /* * iscsi_inet_ntop -- Convert an IPv4 or IPv6 address in binary form into * printable form, and return a pointer to that string. Caller should * provide a buffer of correct length to store string into. * Note: this routine is kernel version of inet_ntop. It has similar * format as iscsi_inet_ntop() defined in rfc2553. But it does not do * error handling operations exactly as rfc2553 defines. This function * is used by kernel inet directory routines only for debugging. * This iscsi_inet_ntop() function, does not return NULL if third argument * is NULL. The reason is simple that we don't want kernel to panic * as the output of this function is directly fed to ipdbg macro. * Instead it uses a local buffer for destination address for * those calls which purposely pass NULL ptr for the destination * buffer. This function is thread-safe when the caller passes a non- * null buffer with the third argument. */ /* ARGSUSED */ #define OK_16PTR(p) (!((uintptr_t)(p) & 0x1)) #if defined(__x86) #define OK_32PTR(p) OK_16PTR(p) #else #define OK_32PTR(p) (!((uintptr_t)(p) & 0x3)) #endif char * iscsi_inet_ntop(int af, const void *addr, char *buf, int addrlen) { static char local_buf[PORTAL_STR_LEN]; static char *err_buf1 = ""; static char *err_buf2 = ""; in6_addr_t *v6addr; uchar_t *v4addr; char *caddr; /* * We don't allow thread unsafe iscsi_inet_ntop calls, they * must pass a non-null buffer pointer. For DEBUG mode * we use the ASSERT() and for non-debug kernel it will * silently allow it for now. Someday we should remove * the static buffer from this function. */ ASSERT(buf != NULL); if (buf == NULL) buf = local_buf; buf[0] = '\0'; /* Let user know politely not to send NULL or unaligned addr */ if (addr == NULL || !(OK_32PTR(addr))) { return (err_buf1); } #define UC(b) (((int)b) & 0xff) switch (af) { case AF_INET: ASSERT(addrlen >= INET_ADDRSTRLEN); v4addr = (uchar_t *)addr; (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, "%03d.%03d.%03d.%03d", UC(v4addr[0]), UC(v4addr[1]), UC(v4addr[2]), UC(v4addr[3])); return (buf); case AF_INET6: ASSERT(addrlen >= INET6_ADDRSTRLEN); v6addr = (in6_addr_t *)addr; if (IN6_IS_ADDR_V4MAPPED(v6addr)) { caddr = (char *)addr; (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, "::ffff:%d.%d.%d.%d", UC(caddr[12]), UC(caddr[13]), UC(caddr[14]), UC(caddr[15])); } else if (IN6_IS_ADDR_V4COMPAT(v6addr)) { caddr = (char *)addr; (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, "::%d.%d.%d.%d", UC(caddr[12]), UC(caddr[13]), UC(caddr[14]), UC(caddr[15])); } else if (IN6_IS_ADDR_UNSPECIFIED(v6addr)) { (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, "::"); } else { convert2ascii(buf, v6addr); } return (buf); default: return (err_buf2); } #undef UC } /* * * v6 formats supported * General format xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx * The short hand notation :: is used for COMPAT addr * Other forms : fe80::xxxx:xxxx:xxxx:xxxx */ static void convert2ascii(char *buf, const in6_addr_t *addr) { int hexdigits; int head_zero = 0; int tail_zero = 0; /* tempbuf must be big enough to hold ffff:\0 */ char tempbuf[6]; char *ptr; uint16_t out_addr_component; uint16_t *addr_component; size_t len; boolean_t first = B_FALSE; boolean_t med_zero = B_FALSE; boolean_t end_zero = B_FALSE; addr_component = (uint16_t *)addr; ptr = buf; /* First count if trailing zeroes higher in number */ for (hexdigits = 0; hexdigits < 8; hexdigits++) { if (*addr_component == 0) { if (hexdigits < 4) head_zero++; else tail_zero++; } addr_component++; } addr_component = (uint16_t *)addr; if (tail_zero > head_zero && (head_zero + tail_zero) != 7) end_zero = B_TRUE; for (hexdigits = 0; hexdigits < 8; hexdigits++) { /* if entry is a 0 */ if (*addr_component == 0) { if (!first && *(addr_component + 1) == 0) { if (end_zero && (hexdigits < 4)) { *ptr++ = '0'; *ptr++ = ':'; } else { /* * address starts with 0s .. * stick in leading ':' of pair */ if (hexdigits == 0) *ptr++ = ':'; /* add another */ *ptr++ = ':'; first = B_TRUE; med_zero = B_TRUE; } } else if (first && med_zero) { if (hexdigits == 7) *ptr++ = ':'; addr_component++; continue; } else { *ptr++ = '0'; *ptr++ = ':'; } addr_component++; continue; } if (med_zero) med_zero = B_FALSE; tempbuf[0] = '\0'; mdb_nhconvert(&out_addr_component, addr_component, sizeof (uint16_t)); (void) mdb_snprintf(tempbuf, 6, "%x:", out_addr_component); len = strlen(tempbuf); bcopy(tempbuf, ptr, len); ptr = ptr + len; addr_component++; } *--ptr = '\0'; } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, a list of structures * describing our walkers and a function named _mdb_init to return a pointer * to our module information. */ static const mdb_dcmd_t dcmds[] = { { "iscsi_tgt", "[-agscptbSRv]", "iSCSI target information", iscsi_tgt }, { "iscsi_tpgt", "[-R]", "iSCSI target portal group tag information", iscsi_tpgt }, { "iscsi_tpg", "[-R]", "iSCSI target portal group information", iscsi_tpg }, { "iscsi_sess", "[-ablmtvcSRIT]", "iSCSI session information", iscsi_sess }, { "iscsi_conn", "[-abmtvSRIT]", "iSCSI connection information", iscsi_conn }, { "iscsi_task", "[-bSRv]", "iSCSI task information", iscsi_task }, { "iscsi_refcnt", "", "print audit informtion for idm_refcnt_t", iscsi_refcnt }, { "iscsi_states", "", "dump events and state transitions recorded in an\t" "\t\tidm_sm_audit_t structure", iscsi_states }, { "iscsi_isns", "[-epstvR]", "print iscsit iSNS information", iscsi_isns, iscsi_isns_help }, { "iscsi_svc", "[-vR]", "iSCSI service information", iscsi_svc }, { "iscsi_portal", "[-R]", "iSCSI portal information", iscsi_portal }, { "iscsi_cmd", "[-S]", "iSCSI command information (initiator only)", iscsi_cmd }, { NULL } }; /* * Basic walkers for the initiator linked lists */ static const mdb_walker_t walkers[] = { { "iscsi_ini_hba", "global walk of the initiator iscsi_hba_t " "list", iscsi_ini_hba_walk_init, iscsi_ini_hba_step, NULL}, { "iscsi_ini_sess", "walk list of initiator iscsi_sess_t structures", iscsi_ini_sess_walk_init, iscsi_ini_sess_step, NULL }, { "iscsi_ini_conn", "walk list of initiator iscsi_conn_t structures", iscsi_ini_conn_walk_init, iscsi_ini_conn_step, NULL }, { "iscsi_ini_lun", "walk list of initiator iscsi_lun_t structures", iscsi_ini_lun_walk_init, iscsi_ini_lun_step, NULL }, { "iscsi_ini_cmd", "walk list of initiator iscsi_cmd_t structures", iscsi_ini_cmd_walk_init, iscsi_ini_cmd_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. * Copyright 2024 Oxide Computer Company */ #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 #define ADDR_WIDTH 11 #define L2MAXADDRSTRLEN 255 #define MAX_SAP_LEN 255 #define DEFCOLS 80 typedef struct { const char *bit_name; /* name of bit */ const char *bit_descr; /* description of bit's purpose */ } bitname_t; static const bitname_t squeue_states[] = { { "SQS_PROC", "being processed" }, { "SQS_WORKER", "... by a worker thread" }, { "SQS_ENTER", "... by an squeue_enter() thread" }, { "SQS_FAST", "... in fast-path mode" }, { "SQS_USER", "A non interrupt user" }, { "SQS_BOUND", "worker thread bound to CPU" }, { "SQS_PROFILE", "profiling enabled" }, { "SQS_REENTER", "re-entered thred" }, { NULL } }; typedef struct illif_walk_data { ill_g_head_t ill_g_heads[MAX_G_HEADS]; int ill_list; ill_if_t ill_if; } illif_walk_data_t; typedef struct ncec_walk_data_s { struct ndp_g_s ncec_ip_ndp; int ncec_hash_tbl_index; ncec_t ncec; } ncec_walk_data_t; typedef struct ncec_cbdata_s { uintptr_t ncec_addr; int ncec_ipversion; } ncec_cbdata_t; typedef struct nce_cbdata_s { int nce_ipversion; char nce_ill_name[LIFNAMSIZ]; } nce_cbdata_t; typedef struct ire_cbdata_s { int ire_ipversion; boolean_t verbose; } ire_cbdata_t; typedef struct zi_cbdata_s { const char *zone_name; ip_stack_t *ipst; boolean_t shared_ip_zone; } zi_cbdata_t; typedef struct th_walk_data { uint_t thw_non_zero_only; boolean_t thw_match; uintptr_t thw_matchkey; uintptr_t thw_ipst; clock_t thw_lbolt; } th_walk_data_t; typedef struct ipcl_hash_walk_data_s { conn_t *conn; int connf_tbl_index; uintptr_t hash_tbl; int hash_tbl_size; } ipcl_hash_walk_data_t; typedef struct ill_walk_data_s { ill_t ill; } ill_walk_data_t; typedef struct ill_cbdata_s { uintptr_t ill_addr; int ill_ipversion; ip_stack_t *ill_ipst; boolean_t verbose; } ill_cbdata_t; typedef struct ipif_walk_data_s { ipif_t ipif; } ipif_walk_data_t; typedef struct ipif_cbdata_s { ill_t ill; int ipif_ipversion; boolean_t verbose; } ipif_cbdata_t; typedef struct hash_walk_arg_s { off_t tbl_off; off_t size_off; } hash_walk_arg_t; static hash_walk_arg_t udp_hash_arg = { OFFSETOF(ip_stack_t, ips_ipcl_udp_fanout), OFFSETOF(ip_stack_t, ips_ipcl_udp_fanout_size) }; static hash_walk_arg_t conn_hash_arg = { OFFSETOF(ip_stack_t, ips_ipcl_conn_fanout), OFFSETOF(ip_stack_t, ips_ipcl_conn_fanout_size) }; static hash_walk_arg_t bind_hash_arg = { OFFSETOF(ip_stack_t, ips_ipcl_bind_fanout), OFFSETOF(ip_stack_t, ips_ipcl_bind_fanout_size) }; static hash_walk_arg_t proto_hash_arg = { OFFSETOF(ip_stack_t, ips_ipcl_proto_fanout_v4), 0 }; static hash_walk_arg_t proto_v6_hash_arg = { OFFSETOF(ip_stack_t, ips_ipcl_proto_fanout_v6), 0 }; typedef struct ip_list_walk_data_s { off_t nextoff; } ip_list_walk_data_t; typedef struct ip_list_walk_arg_s { off_t off; size_t size; off_t nextp_off; } ip_list_walk_arg_t; static ip_list_walk_arg_t ipif_walk_arg = { OFFSETOF(ill_t, ill_ipif), sizeof (ipif_t), OFFSETOF(ipif_t, ipif_next) }; static ip_list_walk_arg_t srcid_walk_arg = { OFFSETOF(ip_stack_t, ips_srcid_head), sizeof (srcid_map_t), OFFSETOF(srcid_map_t, sm_next) }; static int iphdr(uintptr_t, uint_t, int, const mdb_arg_t *); static int ip6hdr(uintptr_t, uint_t, int, const mdb_arg_t *); static int ill(uintptr_t, uint_t, int, const mdb_arg_t *); static void ill_help(void); static int ill_walk_init(mdb_walk_state_t *); static int ill_walk_step(mdb_walk_state_t *); static int ill_format(uintptr_t, const void *, void *); static void ill_header(boolean_t); static int ipif(uintptr_t, uint_t, int, const mdb_arg_t *); static void ipif_help(void); static int ipif_walk_init(mdb_walk_state_t *); static int ipif_walk_step(mdb_walk_state_t *); static int ipif_format(uintptr_t, const void *, void *); static void ipif_header(boolean_t); static int ip_list_walk_init(mdb_walk_state_t *); static int ip_list_walk_step(mdb_walk_state_t *); static void ip_list_walk_fini(mdb_walk_state_t *); static int srcid_walk_step(mdb_walk_state_t *); static int ire_format(uintptr_t addr, const void *, void *); static int ncec_format(uintptr_t addr, const ncec_t *ncec, int ipversion); static int ncec(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); static int ncec_walk_step(mdb_walk_state_t *wsp); static int ncec_stack_walk_init(mdb_walk_state_t *wsp); static int ncec_stack_walk_step(mdb_walk_state_t *wsp); static void ncec_stack_walk_fini(mdb_walk_state_t *wsp); static int ncec_cb(uintptr_t addr, const ncec_walk_data_t *iw, ncec_cbdata_t *id); static char *nce_l2_addr(const nce_t *, const ill_t *); static int ipcl_hash_walk_init(mdb_walk_state_t *); static int ipcl_hash_walk_step(mdb_walk_state_t *); static void ipcl_hash_walk_fini(mdb_walk_state_t *); static int conn_status_walk_step(mdb_walk_state_t *); static int conn_status(uintptr_t, uint_t, int, const mdb_arg_t *); static void conn_status_help(void); static int srcid_status(uintptr_t, uint_t, int, const mdb_arg_t *); static int ilb_stacks_walk_step(mdb_walk_state_t *); static int ilb_rules_walk_init(mdb_walk_state_t *); static int ilb_rules_walk_step(mdb_walk_state_t *); static int ilb_servers_walk_init(mdb_walk_state_t *); static int ilb_servers_walk_step(mdb_walk_state_t *); static int ilb_nat_src_walk_init(mdb_walk_state_t *); static int ilb_nat_src_walk_step(mdb_walk_state_t *); static int ilb_conn_walk_init(mdb_walk_state_t *); static int ilb_conn_walk_step(mdb_walk_state_t *); static int ilb_sticky_walk_init(mdb_walk_state_t *); static int ilb_sticky_walk_step(mdb_walk_state_t *); static void ilb_common_walk_fini(mdb_walk_state_t *); /* * Given the kernel address of an ip_stack_t, return the stackid */ static int ips_to_stackid(uintptr_t kaddr) { ip_stack_t ipss; netstack_t nss; if (mdb_vread(&ipss, sizeof (ipss), kaddr) == -1) { mdb_warn("failed to read ip_stack_t %p", kaddr); return (0); } kaddr = (uintptr_t)ipss.ips_netstack; if (mdb_vread(&nss, sizeof (nss), kaddr) == -1) { mdb_warn("failed to read netstack_t %p", kaddr); return (0); } return (nss.netstack_stackid); } /* ARGSUSED */ static int zone_to_ips_cb(uintptr_t addr, const void *zi_arg, void *zi_cb_arg) { zi_cbdata_t *zi_cb = zi_cb_arg; zone_t zone; char zone_name[ZONENAME_MAX]; netstack_t ns; if (mdb_vread(&zone, sizeof (zone_t), addr) == -1) { mdb_warn("can't read zone at %p", addr); return (WALK_ERR); } (void) mdb_readstr(zone_name, ZONENAME_MAX, (uintptr_t)zone.zone_name); if (strcmp(zi_cb->zone_name, zone_name) != 0) return (WALK_NEXT); zi_cb->shared_ip_zone = (!(zone.zone_flags & ZF_NET_EXCL) && (strcmp(zone_name, "global") != 0)); if (mdb_vread(&ns, sizeof (netstack_t), (uintptr_t)zone.zone_netstack) == -1) { mdb_warn("can't read netstack at %p", zone.zone_netstack); return (WALK_ERR); } zi_cb->ipst = ns.netstack_ip; return (WALK_DONE); } static ip_stack_t * zone_to_ips(const char *zone_name) { zi_cbdata_t zi_cb; if (zone_name == NULL) return (NULL); zi_cb.zone_name = zone_name; zi_cb.ipst = NULL; zi_cb.shared_ip_zone = B_FALSE; if (mdb_walk("zone", (mdb_walk_cb_t)zone_to_ips_cb, &zi_cb) == -1) { mdb_warn("failed to walk zone"); return (NULL); } if (zi_cb.shared_ip_zone) { mdb_warn("%s is a Shared-IP zone, try '-s global' instead\n", zone_name); return (NULL); } if (zi_cb.ipst == NULL) { mdb_warn("failed to find zone %s\n", zone_name); return (NULL); } return (zi_cb.ipst); } /* * Generic network stack walker initialization function. It is used by all * other netwrok stack walkers. */ int ns_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("netstack", wsp) == -1) { mdb_warn("can't walk 'netstack'"); return (WALK_ERR); } return (WALK_NEXT); } /* * Generic network stack walker stepping function. It is used by all other * network stack walkers. The which parameter differentiates the different * walkers. */ int ns_walk_step(mdb_walk_state_t *wsp, int which) { uintptr_t kaddr; netstack_t nss; if (mdb_vread(&nss, sizeof (nss), wsp->walk_addr) == -1) { mdb_warn("can't read netstack at %p", wsp->walk_addr); return (WALK_ERR); } kaddr = (uintptr_t)nss.netstack_modules[which]; return (wsp->walk_callback(kaddr, wsp->walk_layer, wsp->walk_cbdata)); } /* * IP network stack walker stepping function. */ int ip_stacks_walk_step(mdb_walk_state_t *wsp) { return (ns_walk_step(wsp, NS_IP)); } /* * TCP network stack walker stepping function. */ int tcp_stacks_walk_step(mdb_walk_state_t *wsp) { return (ns_walk_step(wsp, NS_TCP)); } /* * SCTP network stack walker stepping function. */ int sctp_stacks_walk_step(mdb_walk_state_t *wsp) { return (ns_walk_step(wsp, NS_SCTP)); } /* * UDP network stack walker stepping function. */ int udp_stacks_walk_step(mdb_walk_state_t *wsp) { return (ns_walk_step(wsp, NS_UDP)); } /* * Initialization function for the per CPU TCP stats counter walker of a given * TCP stack. */ int tcps_sc_walk_init(mdb_walk_state_t *wsp) { tcp_stack_t tcps; if (wsp->walk_addr == 0) return (WALK_ERR); if (mdb_vread(&tcps, sizeof (tcps), wsp->walk_addr) == -1) { mdb_warn("failed to read tcp_stack_t at %p", wsp->walk_addr); return (WALK_ERR); } if (tcps.tcps_sc_cnt == 0) return (WALK_DONE); /* * Store the tcp_stack_t pointer in walk_data. The stepping function * used it to calculate if the end of the counter has reached. */ wsp->walk_data = (void *)wsp->walk_addr; wsp->walk_addr = (uintptr_t)tcps.tcps_sc; return (WALK_NEXT); } /* * Stepping function for the per CPU TCP stats counterwalker. */ int tcps_sc_walk_step(mdb_walk_state_t *wsp) { int status; tcp_stack_t tcps; tcp_stats_cpu_t *stats; char *next, *end; if (mdb_vread(&tcps, sizeof (tcps), (uintptr_t)wsp->walk_data) == -1) { mdb_warn("failed to read tcp_stack_t at %p", wsp->walk_addr); return (WALK_ERR); } if (mdb_vread(&stats, sizeof (stats), wsp->walk_addr) == -1) { mdb_warn("failed ot read tcp_stats_cpu_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback((uintptr_t)stats, &stats, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); next = (char *)wsp->walk_addr + sizeof (tcp_stats_cpu_t *); end = (char *)tcps.tcps_sc + tcps.tcps_sc_cnt * sizeof (tcp_stats_cpu_t *); if (next >= end) return (WALK_DONE); wsp->walk_addr = (uintptr_t)next; return (WALK_NEXT); } int th_hash_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; list_node_t *next; if (wsp->walk_addr == 0) { if (mdb_lookup_by_obj("ip", "ip_thread_list", &sym) == 0) { wsp->walk_addr = sym.st_value; } else { mdb_warn("unable to locate ip_thread_list\n"); return (WALK_ERR); } } if (mdb_vread(&next, sizeof (next), wsp->walk_addr + offsetof(list_t, list_head) + offsetof(list_node_t, list_next)) == -1 || next == NULL) { mdb_warn("non-DEBUG image; cannot walk th_hash list\n"); return (WALK_ERR); } if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("can't walk 'list'"); return (WALK_ERR); } else { return (WALK_NEXT); } } int th_hash_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * Called with walk_addr being the address of ips_ill_g_heads */ int illif_stack_walk_init(mdb_walk_state_t *wsp) { illif_walk_data_t *iw; if (wsp->walk_addr == 0) { mdb_warn("illif_stack supports only local walks\n"); return (WALK_ERR); } iw = mdb_alloc(sizeof (illif_walk_data_t), UM_SLEEP); if (mdb_vread(iw->ill_g_heads, MAX_G_HEADS * sizeof (ill_g_head_t), wsp->walk_addr) == -1) { mdb_warn("failed to read 'ips_ill_g_heads' at %p", wsp->walk_addr); mdb_free(iw, sizeof (illif_walk_data_t)); return (WALK_ERR); } iw->ill_list = 0; wsp->walk_addr = (uintptr_t)iw->ill_g_heads[0].ill_g_list_head; wsp->walk_data = iw; return (WALK_NEXT); } int illif_stack_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; illif_walk_data_t *iw = wsp->walk_data; int list = iw->ill_list; if (mdb_vread(&iw->ill_if, sizeof (ill_if_t), addr) == -1) { mdb_warn("failed to read ill_if_t at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)iw->ill_if.illif_next; if (wsp->walk_addr == (uintptr_t)iw->ill_g_heads[list].ill_g_list_head) { if (++list >= MAX_G_HEADS) return (WALK_DONE); iw->ill_list = list; wsp->walk_addr = (uintptr_t)iw->ill_g_heads[list].ill_g_list_head; return (WALK_NEXT); } return (wsp->walk_callback(addr, iw, wsp->walk_cbdata)); } void illif_stack_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (illif_walk_data_t)); } typedef struct illif_cbdata { uint_t ill_flags; uintptr_t ill_addr; int ill_printlist; /* list to be printed (MAX_G_HEADS for all) */ boolean_t ill_printed; } illif_cbdata_t; static int illif_cb(uintptr_t addr, const illif_walk_data_t *iw, illif_cbdata_t *id) { const char *version; if (id->ill_printlist < MAX_G_HEADS && id->ill_printlist != iw->ill_list) return (WALK_NEXT); if (id->ill_flags & DCMD_ADDRSPEC && id->ill_addr != addr) return (WALK_NEXT); if (id->ill_flags & DCMD_PIPE_OUT) { mdb_printf("%p\n", addr); return (WALK_NEXT); } switch (iw->ill_list) { case IP_V4_G_HEAD: version = "v4"; break; case IP_V6_G_HEAD: version = "v6"; break; default: version = "??"; break; } mdb_printf("%?p %2s %?p %10d %?p %s\n", addr, version, addr + offsetof(ill_if_t, illif_avl_by_ppa), iw->ill_if.illif_avl_by_ppa.avl_numnodes, iw->ill_if.illif_ppa_arena, iw->ill_if.illif_name); id->ill_printed = TRUE; return (WALK_NEXT); } int ip_stacks_common_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("ip_stacks", wsp) == -1) { mdb_warn("can't walk 'ip_stacks'"); return (WALK_ERR); } return (WALK_NEXT); } int illif_walk_step(mdb_walk_state_t *wsp) { uintptr_t kaddr; kaddr = wsp->walk_addr + OFFSETOF(ip_stack_t, ips_ill_g_heads); if (mdb_vread(&kaddr, sizeof (kaddr), kaddr) == -1) { mdb_warn("can't read ips_ip_cache_table at %p", kaddr); return (WALK_ERR); } if (mdb_pwalk("illif_stack", wsp->walk_callback, wsp->walk_cbdata, kaddr) == -1) { mdb_warn("couldn't walk 'illif_stack' for ips_ill_g_heads %p", kaddr); return (WALK_ERR); } return (WALK_NEXT); } int illif(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { illif_cbdata_t id; ill_if_t ill_if; const char *opt_P = NULL; int printlist = MAX_G_HEADS; if (mdb_getopts(argc, argv, 'P', MDB_OPT_STR, &opt_P, NULL) != argc) return (DCMD_USAGE); if (opt_P != NULL) { if (strcmp("v4", opt_P) == 0) { printlist = IP_V4_G_HEAD; } else if (strcmp("v6", opt_P) == 0) { printlist = IP_V6_G_HEAD; } else { mdb_warn("invalid protocol '%s'\n", opt_P); return (DCMD_USAGE); } } if (DCMD_HDRSPEC(flags) && (flags & DCMD_PIPE_OUT) == 0) { mdb_printf("%%?s %2s %?s %10s %?s %-10s%\n", "ADDR", "IP", "AVLADDR", "NUMNODES", "ARENA", "NAME"); } id.ill_flags = flags; id.ill_addr = addr; id.ill_printlist = printlist; id.ill_printed = FALSE; if (mdb_walk("illif", (mdb_walk_cb_t)illif_cb, &id) == -1) { mdb_warn("can't walk ill_if_t structures"); return (DCMD_ERR); } if (!(flags & DCMD_ADDRSPEC) || opt_P != NULL || id.ill_printed) return (DCMD_OK); /* * If an address is specified and the walk doesn't find it, * print it anyway. */ if (mdb_vread(&ill_if, sizeof (ill_if_t), addr) == -1) { mdb_warn("failed to read ill_if_t at %p", addr); return (DCMD_ERR); } mdb_printf("%?p %2s %?p %10d %?p %s\n", addr, "??", addr + offsetof(ill_if_t, illif_avl_by_ppa), ill_if.illif_avl_by_ppa.avl_numnodes, ill_if.illif_ppa_arena, ill_if.illif_name); return (DCMD_OK); } static void illif_help(void) { mdb_printf("Options:\n"); mdb_printf("\t-P v4 | v6" "\tfilter interface structures for the specified protocol\n"); } int nce_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("nce_cache", wsp) == -1) { mdb_warn("can't walk 'nce_cache'"); return (WALK_ERR); } return (WALK_NEXT); } int nce_walk_step(mdb_walk_state_t *wsp) { nce_t nce; if (mdb_vread(&nce, sizeof (nce), wsp->walk_addr) == -1) { mdb_warn("can't read nce at %p", wsp->walk_addr); return (WALK_ERR); } return (wsp->walk_callback(wsp->walk_addr, &nce, wsp->walk_cbdata)); } static int nce_format(uintptr_t addr, const nce_t *ncep, void *nce_cb_arg) { nce_cbdata_t *nce_cb = nce_cb_arg; ill_t ill; char ill_name[LIFNAMSIZ]; ncec_t ncec; if (mdb_vread(&ncec, sizeof (ncec), (uintptr_t)ncep->nce_common) == -1) { mdb_warn("can't read ncec at %p", ncep->nce_common); return (WALK_NEXT); } if (nce_cb->nce_ipversion != 0 && ncec.ncec_ipversion != nce_cb->nce_ipversion) return (WALK_NEXT); if (mdb_vread(&ill, sizeof (ill), (uintptr_t)ncep->nce_ill) == -1) { mdb_snprintf(ill_name, sizeof (ill_name), "--"); } else { (void) mdb_readstr(ill_name, MIN(LIFNAMSIZ, ill.ill_name_length), (uintptr_t)ill.ill_name); } if (nce_cb->nce_ill_name[0] != '\0' && strncmp(nce_cb->nce_ill_name, ill_name, LIFNAMSIZ) != 0) return (WALK_NEXT); if (ncec.ncec_ipversion == IPV6_VERSION) { mdb_printf("%?p %5s %-18s %?p %6d %N\n", addr, ill_name, nce_l2_addr(ncep, &ill), ncep->nce_fp_mp, ncep->nce_refcnt, &ncep->nce_addr); } else { struct in_addr nceaddr; IN6_V4MAPPED_TO_INADDR(&ncep->nce_addr, &nceaddr); mdb_printf("%?p %5s %-18s %?p %6d %I\n", addr, ill_name, nce_l2_addr(ncep, &ill), ncep->nce_fp_mp, ncep->nce_refcnt, nceaddr.s_addr); } return (WALK_NEXT); } int dce_walk_init(mdb_walk_state_t *wsp) { wsp->walk_data = (void *)wsp->walk_addr; if (mdb_layered_walk("dce_cache", wsp) == -1) { mdb_warn("can't walk 'dce_cache'"); return (WALK_ERR); } return (WALK_NEXT); } int dce_walk_step(mdb_walk_state_t *wsp) { dce_t dce; if (mdb_vread(&dce, sizeof (dce), wsp->walk_addr) == -1) { mdb_warn("can't read dce at %p", wsp->walk_addr); return (WALK_ERR); } /* If ip_stack_t is specified, skip DCEs that don't belong to it. */ if ((wsp->walk_data != NULL) && (wsp->walk_data != dce.dce_ipst)) return (WALK_NEXT); return (wsp->walk_callback(wsp->walk_addr, &dce, wsp->walk_cbdata)); } int ire_walk_init(mdb_walk_state_t *wsp) { wsp->walk_data = (void *)wsp->walk_addr; if (mdb_layered_walk("ire_cache", wsp) == -1) { mdb_warn("can't walk 'ire_cache'"); return (WALK_ERR); } return (WALK_NEXT); } int ire_walk_step(mdb_walk_state_t *wsp) { ire_t ire; if (mdb_vread(&ire, sizeof (ire), wsp->walk_addr) == -1) { mdb_warn("can't read ire at %p", wsp->walk_addr); return (WALK_ERR); } /* If ip_stack_t is specified, skip IREs that don't belong to it. */ if ((wsp->walk_data != NULL) && (wsp->walk_data != ire.ire_ipst)) return (WALK_NEXT); return (wsp->walk_callback(wsp->walk_addr, &ire, wsp->walk_cbdata)); } /* ARGSUSED */ int ire_next_walk_init(mdb_walk_state_t *wsp) { return (WALK_NEXT); } int ire_next_walk_step(mdb_walk_state_t *wsp) { ire_t ire; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&ire, sizeof (ire), wsp->walk_addr) == -1) { mdb_warn("can't read ire at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &ire, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); wsp->walk_addr = (uintptr_t)ire.ire_next; return (status); } static int ire_format(uintptr_t addr, const void *ire_arg, void *ire_cb_arg) { const ire_t *irep = ire_arg; ire_cbdata_t *ire_cb = ire_cb_arg; boolean_t verbose = ire_cb->verbose; ill_t ill; char ill_name[LIFNAMSIZ]; boolean_t condemned = irep->ire_generation == IRE_GENERATION_CONDEMNED; static const mdb_bitmask_t tmasks[] = { { "BROADCAST", IRE_BROADCAST, IRE_BROADCAST }, { "DEFAULT", IRE_DEFAULT, IRE_DEFAULT }, { "LOCAL", IRE_LOCAL, IRE_LOCAL }, { "LOOPBACK", IRE_LOOPBACK, IRE_LOOPBACK }, { "PREFIX", IRE_PREFIX, IRE_PREFIX }, { "MULTICAST", IRE_MULTICAST, IRE_MULTICAST }, { "NOROUTE", IRE_NOROUTE, IRE_NOROUTE }, { "IF_NORESOLVER", IRE_IF_NORESOLVER, IRE_IF_NORESOLVER }, { "IF_RESOLVER", IRE_IF_RESOLVER, IRE_IF_RESOLVER }, { "IF_CLONE", IRE_IF_CLONE, IRE_IF_CLONE }, { "HOST", IRE_HOST, IRE_HOST }, { NULL, 0, 0 } }; static const mdb_bitmask_t fmasks[] = { { "UP", RTF_UP, RTF_UP }, { "GATEWAY", RTF_GATEWAY, RTF_GATEWAY }, { "HOST", RTF_HOST, RTF_HOST }, { "REJECT", RTF_REJECT, RTF_REJECT }, { "DYNAMIC", RTF_DYNAMIC, RTF_DYNAMIC }, { "MODIFIED", RTF_MODIFIED, RTF_MODIFIED }, { "DONE", RTF_DONE, RTF_DONE }, { "MASK", RTF_MASK, RTF_MASK }, { "CLONING", RTF_CLONING, RTF_CLONING }, { "XRESOLVE", RTF_XRESOLVE, RTF_XRESOLVE }, { "LLINFO", RTF_LLINFO, RTF_LLINFO }, { "STATIC", RTF_STATIC, RTF_STATIC }, { "BLACKHOLE", RTF_BLACKHOLE, RTF_BLACKHOLE }, { "PRIVATE", RTF_PRIVATE, RTF_PRIVATE }, { "PROTO2", RTF_PROTO2, RTF_PROTO2 }, { "PROTO1", RTF_PROTO1, RTF_PROTO1 }, { "MULTIRT", RTF_MULTIRT, RTF_MULTIRT }, { "SETSRC", RTF_SETSRC, RTF_SETSRC }, { "INDIRECT", RTF_INDIRECT, RTF_INDIRECT }, { NULL, 0, 0 } }; if (ire_cb->ire_ipversion != 0 && irep->ire_ipversion != ire_cb->ire_ipversion) return (WALK_NEXT); if (mdb_vread(&ill, sizeof (ill), (uintptr_t)irep->ire_ill) == -1) { mdb_snprintf(ill_name, sizeof (ill_name), "--"); } else { (void) mdb_readstr(ill_name, MIN(LIFNAMSIZ, ill.ill_name_length), (uintptr_t)ill.ill_name); } if (irep->ire_ipversion == IPV6_VERSION && verbose) { mdb_printf("%%?p%%3s %40N <%hb%s>\n" "%?s %40N\n" "%?s %40d %4d <%hb> %s\n", addr, condemned ? "(C)" : "", &irep->ire_setsrc_addr_v6, irep->ire_type, tmasks, (irep->ire_testhidden ? ", HIDDEN" : ""), "", &irep->ire_addr_v6, "", ips_to_stackid((uintptr_t)irep->ire_ipst), irep->ire_zoneid, irep->ire_flags, fmasks, ill_name); } else if (irep->ire_ipversion == IPV6_VERSION) { mdb_printf("%?p%3s %30N %30N %5d %4d %s\n", addr, condemned ? "(C)" : "", &irep->ire_setsrc_addr_v6, &irep->ire_addr_v6, ips_to_stackid((uintptr_t)irep->ire_ipst), irep->ire_zoneid, ill_name); } else if (verbose) { mdb_printf("%%?p%%3s %40I <%hb%s>\n" "%?s %40I\n" "%?s %40d %4d <%hb> %s\n", addr, condemned ? "(C)" : "", irep->ire_setsrc_addr, irep->ire_type, tmasks, (irep->ire_testhidden ? ", HIDDEN" : ""), "", irep->ire_addr, "", ips_to_stackid((uintptr_t)irep->ire_ipst), irep->ire_zoneid, irep->ire_flags, fmasks, ill_name); } else { mdb_printf("%?p%3s %30I %30I %5d %4d %s\n", addr, condemned ? "(C)" : "", irep->ire_setsrc_addr, irep->ire_addr, ips_to_stackid((uintptr_t)irep->ire_ipst), irep->ire_zoneid, ill_name); } return (WALK_NEXT); } /* * There are faster ways to do this. Given the interactive nature of this * use I don't think its worth much effort. */ static unsigned short ipcksum(void *p, int len) { int32_t sum = 0; while (len > 1) { /* alignment */ sum += *(uint16_t *)p; p = (char *)p + sizeof (uint16_t); if (sum & 0x80000000) sum = (sum & 0xFFFF) + (sum >> 16); len -= 2; } if (len) sum += (uint16_t)*(unsigned char *)p; while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); return (~sum); } static const mdb_bitmask_t tcp_flags[] = { { "SYN", TH_SYN, TH_SYN }, { "ACK", TH_ACK, TH_ACK }, { "FIN", TH_FIN, TH_FIN }, { "RST", TH_RST, TH_RST }, { "PSH", TH_PUSH, TH_PUSH }, { "ECE", TH_ECE, TH_ECE }, { "CWR", TH_CWR, TH_CWR }, { NULL, 0, 0 } }; /* TCP option length */ #define TCPOPT_HEADER_LEN 2 #define TCPOPT_MAXSEG_LEN 4 #define TCPOPT_WS_LEN 3 #define TCPOPT_TSTAMP_LEN 10 #define TCPOPT_SACK_OK_LEN 2 #define TCPOPT_MD5_LEN 18 static void tcphdr_print_options(uint8_t *opts, uint32_t opts_len) { uint8_t *endp; uint32_t len, val; mdb_printf("%Options:%"); endp = opts + opts_len; while (opts < endp) { len = endp - opts; switch (*opts) { case TCPOPT_EOL: mdb_printf(" EOL"); opts++; break; case TCPOPT_NOP: mdb_printf(" NOP"); opts++; break; case TCPOPT_MAXSEG: { uint16_t mss; if (len < TCPOPT_MAXSEG_LEN || opts[1] != TCPOPT_MAXSEG_LEN) { mdb_printf(" \n"); return; } mdb_nhconvert(&mss, opts + TCPOPT_HEADER_LEN, sizeof (mss)); mdb_printf(" MSS=%u", mss); opts += TCPOPT_MAXSEG_LEN; break; } case TCPOPT_WSCALE: if (len < TCPOPT_WS_LEN || opts[1] != TCPOPT_WS_LEN) { mdb_printf(" \n"); return; } mdb_printf(" WS=%u", opts[2]); opts += TCPOPT_WS_LEN; break; case TCPOPT_TSTAMP: { if (len < TCPOPT_TSTAMP_LEN || opts[1] != TCPOPT_TSTAMP_LEN) { mdb_printf(" \n"); return; } opts += TCPOPT_HEADER_LEN; mdb_nhconvert(&val, opts, sizeof (val)); mdb_printf(" TS_VAL=%u,", val); opts += sizeof (val); mdb_nhconvert(&val, opts, sizeof (val)); mdb_printf("TS_ECHO=%u", val); opts += sizeof (val); break; } case TCPOPT_SACK_PERMITTED: if (len < TCPOPT_SACK_OK_LEN || opts[1] != TCPOPT_SACK_OK_LEN) { mdb_printf(" \n"); return; } mdb_printf(" SACK_OK"); opts += TCPOPT_SACK_OK_LEN; break; case TCPOPT_SACK: { uint32_t sack_len; if (len <= TCPOPT_HEADER_LEN || len < opts[1] || opts[1] <= TCPOPT_HEADER_LEN) { mdb_printf(" \n"); return; } sack_len = opts[1] - TCPOPT_HEADER_LEN; opts += TCPOPT_HEADER_LEN; mdb_printf(" SACK="); while (sack_len > 0) { if (opts + 2 * sizeof (val) > endp) { mdb_printf("\n"); opts = endp; break; } mdb_nhconvert(&val, opts, sizeof (val)); mdb_printf("<%u,", val); opts += sizeof (val); mdb_nhconvert(&val, opts, sizeof (val)); mdb_printf("%u>", val); opts += sizeof (val); sack_len -= 2 * sizeof (val); } break; } case TCPOPT_MD5: { uint_t i; if (len < TCPOPT_MD5_LEN || len < opts[1] || opts[1] < TCPOPT_MD5_LEN) { mdb_printf("\n"); return; } if (opts[1] > TCPOPT_MD5_LEN) { mdb_printf("\n"); return; } mdb_printf(" MD5=0x"); for (i = 2; i < TCPOPT_MD5_LEN - 2; i++) mdb_printf("%02x", opts[i]); opts += TCPOPT_MD5_LEN; break; } default: mdb_printf(" Opts=", *opts, opts[1]); opts += opts[1]; break; } } mdb_printf("\n"); } static void tcphdr_print(struct tcphdr *tcph) { in_port_t sport, dport; tcp_seq seq, ack; uint16_t win, urp; mdb_printf("%TCP header%\n"); mdb_nhconvert(&sport, &tcph->th_sport, sizeof (sport)); mdb_nhconvert(&dport, &tcph->th_dport, sizeof (dport)); mdb_nhconvert(&seq, &tcph->th_seq, sizeof (seq)); mdb_nhconvert(&ack, &tcph->th_ack, sizeof (ack)); mdb_nhconvert(&win, &tcph->th_win, sizeof (win)); mdb_nhconvert(&urp, &tcph->th_urp, sizeof (urp)); mdb_printf("%%6s %6s %10s %10s %4s %5s %5s %5s %-15s%\n", "SPORT", "DPORT", "SEQ", "ACK", "HLEN", "WIN", "CSUM", "URP", "FLAGS"); mdb_printf("%6hu %6hu %10u %10u %4d %5hu %5hu %5hu <%b>\n", sport, dport, seq, ack, tcph->th_off << 2, win, tcph->th_sum, urp, tcph->th_flags, tcp_flags); mdb_printf("0x%04x 0x%04x 0x%08x 0x%08x\n\n", sport, dport, seq, ack); } /* ARGSUSED */ static int tcphdr(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { struct tcphdr tcph; uint32_t opt_len; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&tcph, sizeof (tcph), addr) == -1) { mdb_warn("failed to read TCP header at %p", addr); return (DCMD_ERR); } tcphdr_print(&tcph); /* If there are options, print them out also. */ opt_len = (tcph.th_off << 2) - TCP_MIN_HEADER_LENGTH; if (opt_len > 0) { uint8_t *opts, *opt_buf; opt_buf = mdb_alloc(opt_len, UM_SLEEP); opts = (uint8_t *)addr + sizeof (tcph); if (mdb_vread(opt_buf, opt_len, (uintptr_t)opts) == -1) { mdb_warn("failed to read TCP options at %p", opts); return (DCMD_ERR); } tcphdr_print_options(opt_buf, opt_len); mdb_free(opt_buf, opt_len); } return (DCMD_OK); } static void udphdr_print(struct udphdr *udph) { in_port_t sport, dport; uint16_t hlen; mdb_printf("%UDP header%\n"); mdb_nhconvert(&sport, &udph->uh_sport, sizeof (sport)); mdb_nhconvert(&dport, &udph->uh_dport, sizeof (dport)); mdb_nhconvert(&hlen, &udph->uh_ulen, sizeof (hlen)); mdb_printf("%%14s %14s %5s %6s%\n", "SPORT", "DPORT", "LEN", "CSUM"); mdb_printf("%5hu (0x%04x) %5hu (0x%04x) %5hu 0x%04hx\n\n", sport, sport, dport, dport, hlen, udph->uh_sum); } /* ARGSUSED */ static int udphdr(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { struct udphdr udph; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&udph, sizeof (udph), addr) == -1) { mdb_warn("failed to read UDP header at %p", addr); return (DCMD_ERR); } udphdr_print(&udph); return (DCMD_OK); } static void sctphdr_print(sctp_hdr_t *sctph) { in_port_t sport, dport; mdb_printf("%SCTP header%\n"); mdb_nhconvert(&sport, &sctph->sh_sport, sizeof (sport)); mdb_nhconvert(&dport, &sctph->sh_dport, sizeof (dport)); mdb_printf("%%14s %14s %10s %10s%\n", "SPORT", "DPORT", "VTAG", "CHKSUM"); mdb_printf("%5hu (0x%04x) %5hu (0x%04x) %10u 0x%08x\n\n", sport, sport, dport, dport, sctph->sh_verf, sctph->sh_chksum); } /* ARGSUSED */ static int sctphdr(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { sctp_hdr_t sctph; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&sctph, sizeof (sctph), addr) == -1) { mdb_warn("failed to read SCTP header at %p", addr); return (DCMD_ERR); } sctphdr_print(&sctph); return (DCMD_OK); } static int transport_hdr(int proto, uintptr_t addr) { mdb_printf("\n"); switch (proto) { case IPPROTO_TCP: { struct tcphdr tcph; if (mdb_vread(&tcph, sizeof (tcph), addr) == -1) { mdb_warn("failed to read TCP header at %p", addr); return (DCMD_ERR); } tcphdr_print(&tcph); break; } case IPPROTO_UDP: { struct udphdr udph; if (mdb_vread(&udph, sizeof (udph), addr) == -1) { mdb_warn("failed to read UDP header at %p", addr); return (DCMD_ERR); } udphdr_print(&udph); break; } case IPPROTO_SCTP: { sctp_hdr_t sctph; if (mdb_vread(&sctph, sizeof (sctph), addr) == -1) { mdb_warn("failed to read SCTP header at %p", addr); return (DCMD_ERR); } sctphdr_print(&sctph); break; } default: break; } return (DCMD_OK); } static const mdb_bitmask_t ip_flags[] = { { "DF", IPH_DF, IPH_DF }, { "MF", IPH_MF, IPH_MF }, { NULL, 0, 0 } }; /* ARGSUSED */ static int iphdr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE, force = FALSE; ipha_t iph[1]; uint16_t ver, totlen, hdrlen, ipid, off, csum; uintptr_t nxt_proto; char exp_csum[8]; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'f', MDB_OPT_SETBITS, TRUE, &force, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(iph, sizeof (*iph), addr) == -1) { mdb_warn("failed to read IPv4 header at %p", addr); return (DCMD_ERR); } ver = (iph->ipha_version_and_hdr_length & 0xf0) >> 4; if (ver != IPV4_VERSION) { if (ver == IPV6_VERSION) { return (ip6hdr(addr, flags, argc, argv)); } else if (!force) { mdb_warn("unknown IP version: %d\n", ver); return (DCMD_ERR); } } mdb_printf("%IPv4 header%\n"); mdb_printf("%-34s %-34s\n" "%%-4s %-4s %-5s %-5s %-6s %-5s %-5s %-6s %-8s %-6s%\n", "SRC", "DST", "HLEN", "TOS", "LEN", "ID", "OFFSET", "TTL", "PROTO", "CHKSUM", "EXP-CSUM", "FLGS"); hdrlen = (iph->ipha_version_and_hdr_length & 0x0f) << 2; mdb_nhconvert(&totlen, &iph->ipha_length, sizeof (totlen)); mdb_nhconvert(&ipid, &iph->ipha_ident, sizeof (ipid)); mdb_nhconvert(&off, &iph->ipha_fragment_offset_and_flags, sizeof (off)); if (hdrlen == IP_SIMPLE_HDR_LENGTH) { if ((csum = ipcksum(iph, sizeof (*iph))) != 0) csum = ~(~csum + ~iph->ipha_hdr_checksum); else csum = iph->ipha_hdr_checksum; mdb_snprintf(exp_csum, 8, "%u", csum); } else { mdb_snprintf(exp_csum, 8, ""); } mdb_printf("%-34I %-34I%\n" "%-4d %-4d %-5hu %-5hu %-6hu %-5hu %-5hu %-6u %-8s <%5hb>\n", iph->ipha_src, iph->ipha_dst, hdrlen, iph->ipha_type_of_service, totlen, ipid, (off << 3) & 0xffff, iph->ipha_ttl, iph->ipha_protocol, iph->ipha_hdr_checksum, exp_csum, off, ip_flags); if (verbose) { nxt_proto = addr + hdrlen; return (transport_hdr(iph->ipha_protocol, nxt_proto)); } else { return (DCMD_OK); } } /* ARGSUSED */ static int ip6hdr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE, force = FALSE; ip6_t iph[1]; int ver, class, flow; uint16_t plen; uintptr_t nxt_proto; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'f', MDB_OPT_SETBITS, TRUE, &force, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(iph, sizeof (*iph), addr) == -1) { mdb_warn("failed to read IPv6 header at %p", addr); return (DCMD_ERR); } ver = (iph->ip6_vfc & 0xf0) >> 4; if (ver != IPV6_VERSION) { if (ver == IPV4_VERSION) { return (iphdr(addr, flags, argc, argv)); } else if (!force) { mdb_warn("unknown IP version: %d\n", ver); return (DCMD_ERR); } } mdb_printf("%IPv6 header%\n"); mdb_printf("%%-26s %-26s %4s %7s %5s %3s %3s%\n", "SRC", "DST", "TCLS", "FLOW-ID", "PLEN", "NXT", "HOP"); class = iph->ip6_vcf & IPV6_FLOWINFO_TCLASS; mdb_nhconvert(&class, &class, sizeof (class)); class >>= 20; flow = iph->ip6_vcf & IPV6_FLOWINFO_FLOWLABEL; mdb_nhconvert(&flow, &flow, sizeof (flow)); mdb_nhconvert(&plen, &iph->ip6_plen, sizeof (plen)); mdb_printf("%-26N %-26N %4d %7d %5hu %3d %3d\n", &iph->ip6_src, &iph->ip6_dst, class, flow, plen, iph->ip6_nxt, iph->ip6_hlim); if (verbose) { nxt_proto = addr + sizeof (ip6_t); return (transport_hdr(iph->ip6_nxt, nxt_proto)); } else { return (DCMD_OK); } } int nce(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { nce_t nce; nce_cbdata_t nce_cb; int ipversion = 0; const char *opt_P = NULL, *opt_ill = NULL; if (mdb_getopts(argc, argv, 'i', MDB_OPT_STR, &opt_ill, 'P', MDB_OPT_STR, &opt_P, NULL) != argc) return (DCMD_USAGE); if (opt_P != NULL) { if (strcmp("v4", opt_P) == 0) { ipversion = IPV4_VERSION; } else if (strcmp("v6", opt_P) == 0) { ipversion = IPV6_VERSION; } else { mdb_warn("invalid protocol '%s'\n", opt_P); return (DCMD_USAGE); } } if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s %5s %18s %?s %s %s %\n", "ADDR", "INTF", "LLADDR", "FP_MP", "REFCNT", "NCE_ADDR"); } bzero(&nce_cb, sizeof (nce_cb)); if (opt_ill != NULL) { strcpy(nce_cb.nce_ill_name, opt_ill); } nce_cb.nce_ipversion = ipversion; if (flags & DCMD_ADDRSPEC) { (void) mdb_vread(&nce, sizeof (nce_t), addr); (void) nce_format(addr, &nce, &nce_cb); } else if (mdb_walk("nce", (mdb_walk_cb_t)nce_format, &nce_cb) == -1) { mdb_warn("failed to walk ire table"); return (DCMD_ERR); } return (DCMD_OK); } /* ARGSUSED */ static int dce_format(uintptr_t addr, const dce_t *dcep, void *dce_cb_arg) { static const mdb_bitmask_t dmasks[] = { { "D", DCEF_DEFAULT, DCEF_DEFAULT }, { "P", DCEF_PMTU, DCEF_PMTU }, { "U", DCEF_UINFO, DCEF_UINFO }, { "S", DCEF_TOO_SMALL_PMTU, DCEF_TOO_SMALL_PMTU }, { NULL, 0, 0 } }; char flagsbuf[2 * A_CNT(dmasks)]; int ipversion = *(int *)dce_cb_arg; boolean_t condemned = dcep->dce_generation == DCE_GENERATION_CONDEMNED; if (ipversion != 0 && ipversion != dcep->dce_ipversion) return (WALK_NEXT); mdb_snprintf(flagsbuf, sizeof (flagsbuf), "%b", dcep->dce_flags, dmasks); switch (dcep->dce_ipversion) { case IPV4_VERSION: mdb_printf("%%?p%3s %8s %8d %30I %\n", addr, condemned ? "(C)" : "", flagsbuf, dcep->dce_pmtu, &dcep->dce_v4addr); break; case IPV6_VERSION: mdb_printf("%%?p%3s %8s %8d %30N %\n", addr, condemned ? "(C)" : "", flagsbuf, dcep->dce_pmtu, &dcep->dce_v6addr); break; default: mdb_printf("%%?p%3s %8s %8d %30s %\n", addr, condemned ? "(C)" : "", flagsbuf, dcep->dce_pmtu, ""); } return (WALK_NEXT); } int dce(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dce_t dce; const char *opt_P = NULL; const char *zone_name = NULL; ip_stack_t *ipst = NULL; int ipversion = 0; if (mdb_getopts(argc, argv, 's', MDB_OPT_STR, &zone_name, 'P', MDB_OPT_STR, &opt_P, NULL) != argc) return (DCMD_USAGE); /* Follow the specified zone name to find a ip_stack_t*. */ if (zone_name != NULL) { ipst = zone_to_ips(zone_name); if (ipst == NULL) return (DCMD_USAGE); } if (opt_P != NULL) { if (strcmp("v4", opt_P) == 0) { ipversion = IPV4_VERSION; } else if (strcmp("v6", opt_P) == 0) { ipversion = IPV6_VERSION; } else { mdb_warn("invalid protocol '%s'\n", opt_P); return (DCMD_USAGE); } } if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s%3s %8s %8s %30s %\n", "ADDR", "", "FLAGS", "PMTU", "DST_ADDR"); } if (flags & DCMD_ADDRSPEC) { (void) mdb_vread(&dce, sizeof (dce_t), addr); (void) dce_format(addr, &dce, &ipversion); } else if (mdb_pwalk("dce", (mdb_walk_cb_t)dce_format, &ipversion, (uintptr_t)ipst) == -1) { mdb_warn("failed to walk dce cache"); return (DCMD_ERR); } return (DCMD_OK); } int ire(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE; ire_t ire; ire_cbdata_t ire_cb; int ipversion = 0; const char *opt_P = NULL; const char *zone_name = NULL; ip_stack_t *ipst = NULL; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 's', MDB_OPT_STR, &zone_name, 'P', MDB_OPT_STR, &opt_P, NULL) != argc) return (DCMD_USAGE); /* Follow the specified zone name to find a ip_stack_t*. */ if (zone_name != NULL) { ipst = zone_to_ips(zone_name); if (ipst == NULL) return (DCMD_USAGE); } if (opt_P != NULL) { if (strcmp("v4", opt_P) == 0) { ipversion = IPV4_VERSION; } else if (strcmp("v6", opt_P) == 0) { ipversion = IPV6_VERSION; } else { mdb_warn("invalid protocol '%s'\n", opt_P); return (DCMD_USAGE); } } if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { if (verbose) { mdb_printf("%?s %40s %-20s%\n" "%?s %40s %-20s%\n" "%%?s %40s %4s %-20s %s%\n", "ADDR", "SRC", "TYPE", "", "DST", "MARKS", "", "STACK", "ZONE", "FLAGS", "INTF"); } else { mdb_printf("%%?s %30s %30s %5s %4s %s%\n", "ADDR", "SRC", "DST", "STACK", "ZONE", "INTF"); } } ire_cb.verbose = (verbose == TRUE); ire_cb.ire_ipversion = ipversion; if (flags & DCMD_ADDRSPEC) { (void) mdb_vread(&ire, sizeof (ire_t), addr); (void) ire_format(addr, &ire, &ire_cb); } else if (mdb_pwalk("ire", (mdb_walk_cb_t)ire_format, &ire_cb, (uintptr_t)ipst) == -1) { mdb_warn("failed to walk ire table"); return (DCMD_ERR); } return (DCMD_OK); } static size_t mi_osize(const queue_t *q) { /* * The code in common/inet/mi.c allocates an extra word to store the * size of the allocation. An mi_o_s is thus a size_t plus an mi_o_s. */ struct mi_block { size_t mi_nbytes; struct mi_o_s mi_o; } m; if (mdb_vread(&m, sizeof (m), (uintptr_t)q->q_ptr - sizeof (m)) == sizeof (m)) return (m.mi_nbytes - sizeof (m)); return (0); } static void ip_ill_qinfo(const queue_t *q, char *buf, size_t nbytes) { char name[32]; ill_t ill; if (mdb_vread(&ill, sizeof (ill), (uintptr_t)q->q_ptr) == sizeof (ill) && mdb_readstr(name, sizeof (name), (uintptr_t)ill.ill_name) > 0) (void) mdb_snprintf(buf, nbytes, "if: %s", name); } void ip_qinfo(const queue_t *q, char *buf, size_t nbytes) { size_t size = mi_osize(q); if (size == sizeof (ill_t)) ip_ill_qinfo(q, buf, nbytes); } uintptr_t ip_rnext(const queue_t *q) { size_t size = mi_osize(q); ill_t ill; if (size == sizeof (ill_t) && mdb_vread(&ill, sizeof (ill), (uintptr_t)q->q_ptr) == sizeof (ill)) return ((uintptr_t)ill.ill_rq); return (0); } uintptr_t ip_wnext(const queue_t *q) { size_t size = mi_osize(q); ill_t ill; if (size == sizeof (ill_t) && mdb_vread(&ill, sizeof (ill), (uintptr_t)q->q_ptr) == sizeof (ill)) return ((uintptr_t)ill.ill_wq); return (0); } /* * Print the core fields in an squeue_t. With the "-v" argument, * provide more verbose output. */ static int squeue(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { unsigned int i; unsigned int verbose = FALSE; const int SQUEUE_STATEDELT = (int)(sizeof (uintptr_t) + 9); boolean_t arm; squeue_t squeue; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("genunix`squeue_cache", "ip`squeue", argc, argv) == -1) { mdb_warn("failed to walk squeue cache"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (!DCMD_HDRSPEC(flags) && verbose) mdb_printf("\n\n"); if (DCMD_HDRSPEC(flags) || verbose) { mdb_printf("%?s %-5s %-3s %?s %?s %?s\n", "ADDR", "STATE", "CPU", "FIRST", "LAST", "WORKER"); } if (mdb_vread(&squeue, sizeof (squeue_t), addr) == -1) { mdb_warn("cannot read squeue_t at %p", addr); return (DCMD_ERR); } mdb_printf("%0?p %05x %3d %0?p %0?p %0?p\n", addr, squeue.sq_state, squeue.sq_bind, squeue.sq_first, squeue.sq_last, squeue.sq_worker); if (!verbose) return (DCMD_OK); arm = B_TRUE; for (i = 0; squeue_states[i].bit_name != NULL; i++) { if (((squeue.sq_state) & (1 << i)) == 0) continue; if (arm) { mdb_printf("%*s|\n", SQUEUE_STATEDELT, ""); mdb_printf("%*s+--> ", SQUEUE_STATEDELT, ""); arm = B_FALSE; } else mdb_printf("%*s ", SQUEUE_STATEDELT, ""); mdb_printf("%-12s %s\n", squeue_states[i].bit_name, squeue_states[i].bit_descr); } return (DCMD_OK); } static void ip_squeue_help(void) { mdb_printf("Print the core information for a given NCA squeue_t.\n\n"); mdb_printf("Options:\n"); mdb_printf("\t-v\tbe verbose (more descriptive)\n"); } /* * This is called by ::th_trace (via a callback) when walking the th_hash * list. It calls modent to find the entries. */ /* ARGSUSED */ static int modent_summary(uintptr_t addr, const void *data, void *private) { th_walk_data_t *thw = private; const struct mod_hash_entry *mhe = data; th_trace_t th; if (mdb_vread(&th, sizeof (th), (uintptr_t)mhe->mhe_val) == -1) { mdb_warn("failed to read th_trace_t %p", mhe->mhe_val); return (WALK_ERR); } if (th.th_refcnt == 0 && thw->thw_non_zero_only) return (WALK_NEXT); if (!thw->thw_match) { mdb_printf("%?p %?p %?p %8d %?p\n", thw->thw_ipst, mhe->mhe_key, mhe->mhe_val, th.th_refcnt, th.th_id); } else if (thw->thw_matchkey == (uintptr_t)mhe->mhe_key) { int i, j, k; tr_buf_t *tr; mdb_printf("Object %p in IP stack %p:\n", mhe->mhe_key, thw->thw_ipst); i = th.th_trace_lastref; mdb_printf("\tThread %p refcnt %d:\n", th.th_id, th.th_refcnt); for (j = TR_BUF_MAX; j > 0; j--) { tr = th.th_trbuf + i; if (tr->tr_depth == 0 || tr->tr_depth > TR_STACK_DEPTH) break; mdb_printf("\t T%+ld:\n", tr->tr_time - thw->thw_lbolt); for (k = 0; k < tr->tr_depth; k++) mdb_printf("\t\t%a\n", tr->tr_stack[k]); if (--i < 0) i = TR_BUF_MAX - 1; } } return (WALK_NEXT); } /* * This is called by ::th_trace (via a callback) when walking the th_hash * list. It calls modent to find the entries. */ /* ARGSUSED */ static int th_hash_summary(uintptr_t addr, const void *data, void *private) { const th_hash_t *thh = data; th_walk_data_t *thw = private; thw->thw_ipst = (uintptr_t)thh->thh_ipst; return (mdb_pwalk("modent", modent_summary, private, (uintptr_t)thh->thh_hash)); } /* * Print or summarize the th_trace_t structures. */ static int th_trace(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { th_walk_data_t thw; (void) memset(&thw, 0, sizeof (thw)); if (mdb_getopts(argc, argv, 'n', MDB_OPT_SETBITS, TRUE, &thw.thw_non_zero_only, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { /* * No address specified. Walk all of the th_hash_t in the * system, and summarize the th_trace_t entries in each. */ mdb_printf("%?s %?s %?s %8s %?s\n", "IPSTACK", "OBJECT", "TRACE", "REFCNT", "THREAD"); thw.thw_match = B_FALSE; } else { thw.thw_match = B_TRUE; thw.thw_matchkey = addr; if ((thw.thw_lbolt = (clock_t)mdb_get_lbolt()) == -1) { mdb_warn("failed to read lbolt"); return (DCMD_ERR); } } if (mdb_pwalk("th_hash", th_hash_summary, &thw, 0) == -1) { mdb_warn("can't walk th_hash entries"); return (DCMD_ERR); } return (DCMD_OK); } static void th_trace_help(void) { mdb_printf("If given an address of an ill_t, ipif_t, ire_t, or ncec_t, " "print the\n" "corresponding th_trace_t structure in detail. Otherwise, if no " "address is\n" "given, then summarize all th_trace_t structures.\n\n"); mdb_printf("Options:\n" "\t-n\tdisplay only entries with non-zero th_refcnt\n"); } static const mdb_dcmd_t dcmds[] = { { "conn_status", ":", "display connection structures from ipcl hash tables", conn_status, conn_status_help }, { "srcid_status", ":", "display connection structures from ipcl hash tables", srcid_status }, { "ill", "?[-v] [-P v4 | v6] [-s exclusive-ip-zone-name]", "display ill_t structures", ill, ill_help }, { "illif", "?[-P v4 | v6]", "display or filter IP Lower Level InterFace structures", illif, illif_help }, { "iphdr", ":[-vf]", "display an IPv4 header", iphdr }, { "ip6hdr", ":[-vf]", "display an IPv6 header", ip6hdr }, { "ipif", "?[-v] [-P v4 | v6]", "display ipif structures", ipif, ipif_help }, { "ire", "?[-v] [-P v4|v6] [-s exclusive-ip-zone-name]", "display Internet Route Entry structures", ire }, { "nce", "?[-P v4|v6] [-i ]", "display interface-specific Neighbor Cache structures", nce }, { "ncec", "?[-P v4 | v6]", "display Neighbor Cache Entry structures", ncec }, { "dce", "?[-P v4|v6] [-s exclusive-ip-zone-name]", "display Destination Cache Entry structures", dce }, { "squeue", ":[-v]", "print core squeue_t info", squeue, ip_squeue_help }, { "tcphdr", ":", "display a TCP header", tcphdr }, { "udphdr", ":", "display an UDP header", udphdr }, { "sctphdr", ":", "display an SCTP header", sctphdr }, { "th_trace", "?[-n]", "display th_trace_t structures", th_trace, th_trace_help }, { NULL } }; static const mdb_walker_t walkers[] = { { "conn_status", "walk list of conn_t structures", ip_stacks_common_walk_init, conn_status_walk_step, NULL }, { "illif", "walk list of ill interface types for all stacks", ip_stacks_common_walk_init, illif_walk_step, NULL }, { "illif_stack", "walk list of ill interface types", illif_stack_walk_init, illif_stack_walk_step, illif_stack_walk_fini }, { "ill", "walk active ill_t structures for all stacks", ill_walk_init, ill_walk_step, NULL }, { "ipif", "walk list of ipif structures for all stacks", ipif_walk_init, ipif_walk_step, NULL }, { "ipif_list", "walk the linked list of ipif structures " "for a given ill", ip_list_walk_init, ip_list_walk_step, ip_list_walk_fini, &ipif_walk_arg }, { "srcid", "walk list of srcid_map structures for all stacks", ip_stacks_common_walk_init, srcid_walk_step, NULL }, { "srcid_list", "walk list of srcid_map structures for a stack", ip_list_walk_init, ip_list_walk_step, ip_list_walk_fini, &srcid_walk_arg }, { "ire", "walk active ire_t structures", ire_walk_init, ire_walk_step, NULL }, { "ire_next", "walk ire_t structures in the ctable", ire_next_walk_init, ire_next_walk_step, NULL }, { "nce", "walk active nce_t structures", nce_walk_init, nce_walk_step, NULL }, { "dce", "walk active dce_t structures", dce_walk_init, dce_walk_step, NULL }, { "ip_stacks", "walk all the ip_stack_t", ns_walk_init, ip_stacks_walk_step, NULL }, { "tcp_stacks", "walk all the tcp_stack_t", ns_walk_init, tcp_stacks_walk_step, NULL }, { "sctp_stacks", "walk all the sctp_stack_t", ns_walk_init, sctp_stacks_walk_step, NULL }, { "udp_stacks", "walk all the udp_stack_t", ns_walk_init, udp_stacks_walk_step, NULL }, { "th_hash", "walk all the th_hash_t entries", th_hash_walk_init, th_hash_walk_step, NULL }, { "ncec", "walk list of ncec structures for all stacks", ip_stacks_common_walk_init, ncec_walk_step, NULL }, { "ncec_stack", "walk list of ncec structures", ncec_stack_walk_init, ncec_stack_walk_step, ncec_stack_walk_fini}, { "udp_hash", "walk list of conn_t structures in ips_ipcl_udp_fanout", ipcl_hash_walk_init, ipcl_hash_walk_step, ipcl_hash_walk_fini, &udp_hash_arg}, { "conn_hash", "walk list of conn_t structures in ips_ipcl_conn_fanout", ipcl_hash_walk_init, ipcl_hash_walk_step, ipcl_hash_walk_fini, &conn_hash_arg}, { "bind_hash", "walk list of conn_t structures in ips_ipcl_bind_fanout", ipcl_hash_walk_init, ipcl_hash_walk_step, ipcl_hash_walk_fini, &bind_hash_arg}, { "proto_hash", "walk list of conn_t structures in " "ips_ipcl_proto_fanout", ipcl_hash_walk_init, ipcl_hash_walk_step, ipcl_hash_walk_fini, &proto_hash_arg}, { "proto_v6_hash", "walk list of conn_t structures in " "ips_ipcl_proto_fanout_v6", ipcl_hash_walk_init, ipcl_hash_walk_step, ipcl_hash_walk_fini, &proto_v6_hash_arg}, { "ilb_stacks", "walk all ilb_stack_t", ns_walk_init, ilb_stacks_walk_step, NULL }, { "ilb_rules", "walk ilb rules in a given ilb_stack_t", ilb_rules_walk_init, ilb_rules_walk_step, NULL }, { "ilb_servers", "walk server in a given ilb_rule_t", ilb_servers_walk_init, ilb_servers_walk_step, NULL }, { "ilb_nat_src", "walk NAT source table of a given ilb_stack_t", ilb_nat_src_walk_init, ilb_nat_src_walk_step, ilb_common_walk_fini }, { "ilb_conns", "walk NAT table of a given ilb_stack_t", ilb_conn_walk_init, ilb_conn_walk_step, ilb_common_walk_fini }, { "ilb_stickys", "walk sticky table of a given ilb_stack_t", ilb_sticky_walk_init, ilb_sticky_walk_step, ilb_common_walk_fini }, { "tcps_sc", "walk all the per CPU stats counters of a tcp_stack_t", tcps_sc_walk_init, tcps_sc_walk_step, NULL }, { NULL } }; static const mdb_qops_t ip_qops = { .q_info = ip_qinfo, .q_rnext = ip_rnext, .q_wnext = ip_wnext }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_obj("ip", "ipwinit", &sym) == 0) mdb_qops_install(&ip_qops, (uintptr_t)sym.st_value); return (&modinfo); } void _mdb_fini(void) { GElf_Sym sym; if (mdb_lookup_by_obj("ip", "ipwinit", &sym) == 0) mdb_qops_remove(&ip_qops, (uintptr_t)sym.st_value); } static char * ncec_state(int ncec_state) { switch (ncec_state) { case ND_UNCHANGED: return ("unchanged"); case ND_INCOMPLETE: return ("incomplete"); case ND_REACHABLE: return ("reachable"); case ND_STALE: return ("stale"); case ND_DELAY: return ("delay"); case ND_PROBE: return ("probe"); case ND_UNREACHABLE: return ("unreach"); case ND_INITIAL: return ("initial"); default: return ("??"); } } static char * ncec_l2_addr(const ncec_t *ncec, const ill_t *ill) { uchar_t *h; static char addr_buf[L2MAXADDRSTRLEN]; if (ncec->ncec_lladdr == NULL) { return ("None"); } if (ill->ill_net_type == IRE_IF_RESOLVER) { if (ill->ill_phys_addr_length == 0) return ("None"); h = mdb_zalloc(ill->ill_phys_addr_length, UM_SLEEP); if (mdb_vread(h, ill->ill_phys_addr_length, (uintptr_t)ncec->ncec_lladdr) == -1) { mdb_warn("failed to read hwaddr at %p", ncec->ncec_lladdr); return ("Unknown"); } mdb_mac_addr(h, ill->ill_phys_addr_length, addr_buf, sizeof (addr_buf)); } else { return ("None"); } mdb_free(h, ill->ill_phys_addr_length); return (addr_buf); } static char * nce_l2_addr(const nce_t *nce, const ill_t *ill) { uchar_t *h; static char addr_buf[L2MAXADDRSTRLEN]; mblk_t mp; size_t mblen; if (nce->nce_dlur_mp == NULL) return ("None"); if (ill->ill_net_type == IRE_IF_RESOLVER) { if (mdb_vread(&mp, sizeof (mblk_t), (uintptr_t)nce->nce_dlur_mp) == -1) { mdb_warn("failed to read nce_dlur_mp at %p", nce->nce_dlur_mp); return ("None"); } if (ill->ill_phys_addr_length == 0) return ("None"); mblen = mp.b_wptr - mp.b_rptr; if (mblen > (sizeof (dl_unitdata_req_t) + MAX_SAP_LEN) || ill->ill_phys_addr_length > MAX_SAP_LEN || (NCE_LL_ADDR_OFFSET(ill) + ill->ill_phys_addr_length) > mblen) { return ("Unknown"); } h = mdb_zalloc(mblen, UM_SLEEP); if (mdb_vread(h, mblen, (uintptr_t)(mp.b_rptr)) == -1) { mdb_warn("failed to read hwaddr at %p", mp.b_rptr + NCE_LL_ADDR_OFFSET(ill)); return ("Unknown"); } mdb_mac_addr(h + NCE_LL_ADDR_OFFSET(ill), ill->ill_phys_addr_length, addr_buf, sizeof (addr_buf)); } else { return ("None"); } mdb_free(h, mblen); return (addr_buf); } static void ncec_header(uint_t flags) { if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s %-20s %-10s %-8s %-5s %s%\n", "ADDR", "HW_ADDR", "STATE", "FLAGS", "ILL", "IP ADDR"); } } int ncec(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ncec_t ncec; ncec_cbdata_t id; int ipversion = 0; const char *opt_P = NULL; if (mdb_getopts(argc, argv, 'P', MDB_OPT_STR, &opt_P, NULL) != argc) return (DCMD_USAGE); if (opt_P != NULL) { if (strcmp("v4", opt_P) == 0) { ipversion = IPV4_VERSION; } else if (strcmp("v6", opt_P) == 0) { ipversion = IPV6_VERSION; } else { mdb_warn("invalid protocol '%s'\n", opt_P); return (DCMD_USAGE); } } if (flags & DCMD_ADDRSPEC) { if (mdb_vread(&ncec, sizeof (ncec_t), addr) == -1) { mdb_warn("failed to read ncec at %p\n", addr); return (DCMD_ERR); } if (ipversion != 0 && ncec.ncec_ipversion != ipversion) { mdb_printf("IP Version mismatch\n"); return (DCMD_ERR); } ncec_header(flags); return (ncec_format(addr, &ncec, ipversion)); } else { id.ncec_addr = addr; id.ncec_ipversion = ipversion; ncec_header(flags); if (mdb_walk("ncec", (mdb_walk_cb_t)ncec_cb, &id) == -1) { mdb_warn("failed to walk ncec table\n"); return (DCMD_ERR); } } return (DCMD_OK); } static int ncec_format(uintptr_t addr, const ncec_t *ncec, int ipversion) { static const mdb_bitmask_t ncec_flags[] = { { "P", NCE_F_NONUD, NCE_F_NONUD }, { "R", NCE_F_ISROUTER, NCE_F_ISROUTER }, { "N", NCE_F_NONUD, NCE_F_NONUD }, { "A", NCE_F_ANYCAST, NCE_F_ANYCAST }, { "C", NCE_F_CONDEMNED, NCE_F_CONDEMNED }, { "U", NCE_F_UNSOL_ADV, NCE_F_UNSOL_ADV }, { "B", NCE_F_BCAST, NCE_F_BCAST }, { NULL, 0, 0 } }; #define NCE_MAX_FLAGS (sizeof (ncec_flags) / sizeof (mdb_bitmask_t)) struct in_addr nceaddr; ill_t ill; char ill_name[LIFNAMSIZ]; char flagsbuf[NCE_MAX_FLAGS]; if (mdb_vread(&ill, sizeof (ill), (uintptr_t)ncec->ncec_ill) == -1) { mdb_warn("failed to read ncec_ill at %p", ncec->ncec_ill); return (DCMD_ERR); } (void) mdb_readstr(ill_name, MIN(LIFNAMSIZ, ill.ill_name_length), (uintptr_t)ill.ill_name); mdb_snprintf(flagsbuf, sizeof (flagsbuf), "%hb", ncec->ncec_flags, ncec_flags); if (ipversion != 0 && ncec->ncec_ipversion != ipversion) return (DCMD_OK); if (ncec->ncec_ipversion == IPV4_VERSION) { IN6_V4MAPPED_TO_INADDR(&ncec->ncec_addr, &nceaddr); mdb_printf("%?p %-20s %-10s " "%-8s " "%-5s %I\n", addr, ncec_l2_addr(ncec, &ill), ncec_state(ncec->ncec_state), flagsbuf, ill_name, nceaddr.s_addr); } else { mdb_printf("%?p %-20s %-10s %-8s %-5s %N\n", addr, ncec_l2_addr(ncec, &ill), ncec_state(ncec->ncec_state), flagsbuf, ill_name, &ncec->ncec_addr); } return (DCMD_OK); } static uintptr_t ncec_get_next_hash_tbl(uintptr_t start, int *index, struct ndp_g_s ndp) { uintptr_t addr = start; int i = *index; while (addr == 0) { if (++i >= NCE_TABLE_SIZE) break; addr = (uintptr_t)ndp.nce_hash_tbl[i]; } *index = i; return (addr); } static int ncec_walk_step(mdb_walk_state_t *wsp) { uintptr_t kaddr4, kaddr6; kaddr4 = wsp->walk_addr + OFFSETOF(ip_stack_t, ips_ndp4); kaddr6 = wsp->walk_addr + OFFSETOF(ip_stack_t, ips_ndp6); if (mdb_vread(&kaddr4, sizeof (kaddr4), kaddr4) == -1) { mdb_warn("can't read ips_ip_cache_table at %p", kaddr4); return (WALK_ERR); } if (mdb_vread(&kaddr6, sizeof (kaddr6), kaddr6) == -1) { mdb_warn("can't read ips_ip_cache_table at %p", kaddr6); return (WALK_ERR); } if (mdb_pwalk("ncec_stack", wsp->walk_callback, wsp->walk_cbdata, kaddr4) == -1) { mdb_warn("couldn't walk 'ncec_stack' for ips_ndp4 %p", kaddr4); return (WALK_ERR); } if (mdb_pwalk("ncec_stack", wsp->walk_callback, wsp->walk_cbdata, kaddr6) == -1) { mdb_warn("couldn't walk 'ncec_stack' for ips_ndp6 %p", kaddr6); return (WALK_ERR); } return (WALK_NEXT); } static uintptr_t ipcl_hash_get_next_connf_tbl(ipcl_hash_walk_data_t *iw) { struct connf_s connf; uintptr_t addr = 0, next; int index = iw->connf_tbl_index; do { next = iw->hash_tbl + index * sizeof (struct connf_s); if (++index >= iw->hash_tbl_size) { addr = 0; break; } if (mdb_vread(&connf, sizeof (struct connf_s), next) == -1) { mdb_warn("failed to read conn_t at %p", next); return (0); } addr = (uintptr_t)connf.connf_head; } while (addr == 0); iw->connf_tbl_index = index; return (addr); } static int ipcl_hash_walk_init(mdb_walk_state_t *wsp) { const hash_walk_arg_t *arg = wsp->walk_arg; ipcl_hash_walk_data_t *iw; uintptr_t tbladdr; uintptr_t sizeaddr; iw = mdb_alloc(sizeof (ipcl_hash_walk_data_t), UM_SLEEP); iw->conn = mdb_alloc(sizeof (conn_t), UM_SLEEP); tbladdr = wsp->walk_addr + arg->tbl_off; sizeaddr = wsp->walk_addr + arg->size_off; if (mdb_vread(&iw->hash_tbl, sizeof (uintptr_t), tbladdr) == -1) { mdb_warn("can't read fanout table addr at %p", tbladdr); mdb_free(iw->conn, sizeof (conn_t)); mdb_free(iw, sizeof (ipcl_hash_walk_data_t)); return (WALK_ERR); } if (arg->tbl_off == OFFSETOF(ip_stack_t, ips_ipcl_proto_fanout_v4) || arg->tbl_off == OFFSETOF(ip_stack_t, ips_ipcl_proto_fanout_v6)) { iw->hash_tbl_size = IPPROTO_MAX; } else { if (mdb_vread(&iw->hash_tbl_size, sizeof (int), sizeaddr) == -1) { mdb_warn("can't read fanout table size addr at %p", sizeaddr); mdb_free(iw->conn, sizeof (conn_t)); mdb_free(iw, sizeof (ipcl_hash_walk_data_t)); return (WALK_ERR); } } iw->connf_tbl_index = 0; wsp->walk_addr = ipcl_hash_get_next_connf_tbl(iw); wsp->walk_data = iw; if (wsp->walk_addr != 0) return (WALK_NEXT); else return (WALK_DONE); } static int ipcl_hash_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; ipcl_hash_walk_data_t *iw = wsp->walk_data; conn_t *conn = iw->conn; int ret = WALK_DONE; while (addr != 0) { if (mdb_vread(conn, sizeof (conn_t), addr) == -1) { mdb_warn("failed to read conn_t at %p", addr); return (WALK_ERR); } ret = wsp->walk_callback(addr, iw, wsp->walk_cbdata); if (ret != WALK_NEXT) break; addr = (uintptr_t)conn->conn_next; } if (ret == WALK_NEXT) { wsp->walk_addr = ipcl_hash_get_next_connf_tbl(iw); if (wsp->walk_addr != 0) return (WALK_NEXT); else return (WALK_DONE); } return (ret); } static void ipcl_hash_walk_fini(mdb_walk_state_t *wsp) { ipcl_hash_walk_data_t *iw = wsp->walk_data; mdb_free(iw->conn, sizeof (conn_t)); mdb_free(iw, sizeof (ipcl_hash_walk_data_t)); } /* * Called with walk_addr being the address of ips_ndp{4,6} */ static int ncec_stack_walk_init(mdb_walk_state_t *wsp) { ncec_walk_data_t *nw; if (wsp->walk_addr == 0) { mdb_warn("ncec_stack requires ndp_g_s address\n"); return (WALK_ERR); } nw = mdb_alloc(sizeof (ncec_walk_data_t), UM_SLEEP); if (mdb_vread(&nw->ncec_ip_ndp, sizeof (struct ndp_g_s), wsp->walk_addr) == -1) { mdb_warn("failed to read 'ip_ndp' at %p", wsp->walk_addr); mdb_free(nw, sizeof (ncec_walk_data_t)); return (WALK_ERR); } /* * ncec_get_next_hash_tbl() starts at ++i , so initialize index to -1 */ nw->ncec_hash_tbl_index = -1; wsp->walk_addr = ncec_get_next_hash_tbl(0, &nw->ncec_hash_tbl_index, nw->ncec_ip_ndp); wsp->walk_data = nw; return (WALK_NEXT); } static int ncec_stack_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; ncec_walk_data_t *nw = wsp->walk_data; if (addr == 0) return (WALK_DONE); if (mdb_vread(&nw->ncec, sizeof (ncec_t), addr) == -1) { mdb_warn("failed to read ncec_t at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)nw->ncec.ncec_next; wsp->walk_addr = ncec_get_next_hash_tbl(wsp->walk_addr, &nw->ncec_hash_tbl_index, nw->ncec_ip_ndp); return (wsp->walk_callback(addr, nw, wsp->walk_cbdata)); } static void ncec_stack_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (ncec_walk_data_t)); } /* ARGSUSED */ static int ncec_cb(uintptr_t addr, const ncec_walk_data_t *iw, ncec_cbdata_t *id) { ncec_t ncec; if (mdb_vread(&ncec, sizeof (ncec_t), addr) == -1) { mdb_warn("failed to read ncec at %p", addr); return (WALK_NEXT); } (void) ncec_format(addr, &ncec, id->ncec_ipversion); return (WALK_NEXT); } static int ill_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("illif", wsp) == -1) { mdb_warn("can't walk 'illif'"); return (WALK_ERR); } return (WALK_NEXT); } static int ill_walk_step(mdb_walk_state_t *wsp) { ill_if_t ill_if; if (mdb_vread(&ill_if, sizeof (ill_if_t), wsp->walk_addr) == -1) { mdb_warn("can't read ill_if_t at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(wsp->walk_addr + offsetof(ill_if_t, illif_avl_by_ppa)); if (mdb_pwalk("avl", wsp->walk_callback, wsp->walk_cbdata, wsp->walk_addr) == -1) { mdb_warn("can't walk 'avl'"); return (WALK_ERR); } return (WALK_NEXT); } /* ARGSUSED */ static int ill_cb(uintptr_t addr, const ill_walk_data_t *iw, ill_cbdata_t *id) { ill_t ill; if (mdb_vread(&ill, sizeof (ill_t), (uintptr_t)addr) == -1) { mdb_warn("failed to read ill at %p", addr); return (WALK_NEXT); } /* If ip_stack_t is specified, skip ILLs that don't belong to it. */ if (id->ill_ipst != NULL && ill.ill_ipst != id->ill_ipst) return (WALK_NEXT); return (ill_format((uintptr_t)addr, &ill, id)); } static void ill_header(boolean_t verbose) { if (verbose) { mdb_printf("%-?s %-8s %3s %-10s %-?s %-?s %-10s%\n", "ADDR", "NAME", "VER", "TYPE", "WQ", "IPST", "FLAGS"); mdb_printf("%-?s %4s%4s %-?s\n", "PHYINT", "CNT", "", "GROUP"); mdb_printf("%%80s%\n", ""); } else { mdb_printf("%%-?s %-8s %-3s %-10s %4s %-?s %-10s%\n", "ADDR", "NAME", "VER", "TYPE", "CNT", "WQ", "FLAGS"); } } static int ill_format(uintptr_t addr, const void *illptr, void *ill_cb_arg) { ill_t *ill = (ill_t *)illptr; ill_cbdata_t *illcb = ill_cb_arg; boolean_t verbose = illcb->verbose; phyint_t phyi; static const mdb_bitmask_t fmasks[] = { { "R", PHYI_RUNNING, PHYI_RUNNING }, { "P", PHYI_PROMISC, PHYI_PROMISC }, { "V", PHYI_VIRTUAL, PHYI_VIRTUAL }, { "I", PHYI_IPMP, PHYI_IPMP }, { "f", PHYI_FAILED, PHYI_FAILED }, { "S", PHYI_STANDBY, PHYI_STANDBY }, { "i", PHYI_INACTIVE, PHYI_INACTIVE }, { "O", PHYI_OFFLINE, PHYI_OFFLINE }, { "T", ILLF_NOTRAILERS, ILLF_NOTRAILERS }, { "A", ILLF_NOARP, ILLF_NOARP }, { "M", ILLF_MULTICAST, ILLF_MULTICAST }, { "F", ILLF_ROUTER, ILLF_ROUTER }, { "D", ILLF_NONUD, ILLF_NONUD }, { "X", ILLF_NORTEXCH, ILLF_NORTEXCH }, { NULL, 0, 0 } }; static const mdb_bitmask_t v_fmasks[] = { { "RUNNING", PHYI_RUNNING, PHYI_RUNNING }, { "PROMISC", PHYI_PROMISC, PHYI_PROMISC }, { "VIRTUAL", PHYI_VIRTUAL, PHYI_VIRTUAL }, { "IPMP", PHYI_IPMP, PHYI_IPMP }, { "FAILED", PHYI_FAILED, PHYI_FAILED }, { "STANDBY", PHYI_STANDBY, PHYI_STANDBY }, { "INACTIVE", PHYI_INACTIVE, PHYI_INACTIVE }, { "OFFLINE", PHYI_OFFLINE, PHYI_OFFLINE }, { "NOTRAILER", ILLF_NOTRAILERS, ILLF_NOTRAILERS }, { "NOARP", ILLF_NOARP, ILLF_NOARP }, { "MULTICAST", ILLF_MULTICAST, ILLF_MULTICAST }, { "ROUTER", ILLF_ROUTER, ILLF_ROUTER }, { "NONUD", ILLF_NONUD, ILLF_NONUD }, { "NORTEXCH", ILLF_NORTEXCH, ILLF_NORTEXCH }, { NULL, 0, 0 } }; char ill_name[LIFNAMSIZ]; int cnt; char *typebuf; char sbuf[DEFCOLS]; int ipver = illcb->ill_ipversion; if (ipver != 0) { if ((ipver == IPV4_VERSION && ill->ill_isv6) || (ipver == IPV6_VERSION && !ill->ill_isv6)) { return (WALK_NEXT); } } if (mdb_vread(&phyi, sizeof (phyint_t), (uintptr_t)ill->ill_phyint) == -1) { mdb_warn("failed to read ill_phyint at %p", (uintptr_t)ill->ill_phyint); return (WALK_NEXT); } (void) mdb_readstr(ill_name, MIN(LIFNAMSIZ, ill->ill_name_length), (uintptr_t)ill->ill_name); switch (ill->ill_type) { case 0: typebuf = "LOOPBACK"; break; case IFT_ETHER: typebuf = "ETHER"; break; case IFT_OTHER: typebuf = "OTHER"; break; default: typebuf = NULL; break; } cnt = ill->ill_refcnt + ill->ill_ire_cnt + ill->ill_nce_cnt + ill->ill_ilm_cnt + ill->ill_ncec_cnt; mdb_printf("%-?p %-8s %-3s ", addr, ill_name, ill->ill_isv6 ? "v6" : "v4"); if (typebuf != NULL) mdb_printf("%-10s ", typebuf); else mdb_printf("%-10x ", ill->ill_type); if (verbose) { mdb_printf("%-?p %-?p %-llb\n", ill->ill_wq, ill->ill_ipst, ill->ill_flags | phyi.phyint_flags, v_fmasks); mdb_printf("%-?p %4d%4s %-?p\n", ill->ill_phyint, cnt, "", ill->ill_grp); mdb_snprintf(sbuf, sizeof (sbuf), "%*s %3s", sizeof (uintptr_t) * 2, "", ""); mdb_printf("%s|\n%s+--> %3d %-18s " "references from active threads\n", sbuf, sbuf, ill->ill_refcnt, "ill_refcnt"); mdb_printf("%*s %7d %-18s ires referencing this ill\n", strlen(sbuf), "", ill->ill_ire_cnt, "ill_ire_cnt"); mdb_printf("%*s %7d %-18s nces referencing this ill\n", strlen(sbuf), "", ill->ill_nce_cnt, "ill_nce_cnt"); mdb_printf("%*s %7d %-18s ncecs referencing this ill\n", strlen(sbuf), "", ill->ill_ncec_cnt, "ill_ncec_cnt"); mdb_printf("%*s %7d %-18s ilms referencing this ill\n", strlen(sbuf), "", ill->ill_ilm_cnt, "ill_ilm_cnt"); } else { mdb_printf("%4d %-?p %-llb\n", cnt, ill->ill_wq, ill->ill_flags | phyi.phyint_flags, fmasks); } return (WALK_NEXT); } static int ill(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ill_t ill_data; ill_cbdata_t id; int ipversion = 0; const char *zone_name = NULL; const char *opt_P = NULL; uint_t verbose = FALSE; ip_stack_t *ipst = NULL; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 's', MDB_OPT_STR, &zone_name, 'P', MDB_OPT_STR, &opt_P, NULL) != argc) return (DCMD_USAGE); /* Follow the specified zone name to find a ip_stack_t*. */ if (zone_name != NULL) { ipst = zone_to_ips(zone_name); if (ipst == NULL) return (DCMD_USAGE); } if (opt_P != NULL) { if (strcmp("v4", opt_P) == 0) { ipversion = IPV4_VERSION; } else if (strcmp("v6", opt_P) == 0) { ipversion = IPV6_VERSION; } else { mdb_warn("invalid protocol '%s'\n", opt_P); return (DCMD_USAGE); } } id.verbose = verbose; id.ill_addr = addr; id.ill_ipversion = ipversion; id.ill_ipst = ipst; ill_header(verbose); if (flags & DCMD_ADDRSPEC) { if (mdb_vread(&ill_data, sizeof (ill_t), addr) == -1) { mdb_warn("failed to read ill at %p\n", addr); return (DCMD_ERR); } (void) ill_format(addr, &ill_data, &id); } else { if (mdb_walk("ill", (mdb_walk_cb_t)ill_cb, &id) == -1) { mdb_warn("failed to walk ills\n"); return (DCMD_ERR); } } return (DCMD_OK); } static void ill_help(void) { mdb_printf("Prints the following fields: ill ptr, name, " "IP version, count, ill type and ill flags.\n" "The count field is a sum of individual refcnts and is expanded " "with the -v option.\n\n"); mdb_printf("Options:\n"); mdb_printf("\t-P v4 | v6" "\tfilter ill structures for the specified protocol\n"); } static int ip_list_walk_init(mdb_walk_state_t *wsp) { const ip_list_walk_arg_t *arg = wsp->walk_arg; ip_list_walk_data_t *iw; uintptr_t addr = (uintptr_t)(wsp->walk_addr + arg->off); if (wsp->walk_addr == 0) { mdb_warn("only local walks supported\n"); return (WALK_ERR); } if (mdb_vread(&wsp->walk_addr, sizeof (uintptr_t), addr) == -1) { mdb_warn("failed to read list head at %p", addr); return (WALK_ERR); } iw = mdb_alloc(sizeof (ip_list_walk_data_t), UM_SLEEP); iw->nextoff = arg->nextp_off; wsp->walk_data = iw; return (WALK_NEXT); } static int ip_list_walk_step(mdb_walk_state_t *wsp) { ip_list_walk_data_t *iw = wsp->walk_data; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); wsp->walk_addr = addr + iw->nextoff; if (mdb_vread(&wsp->walk_addr, sizeof (uintptr_t), wsp->walk_addr) == -1) { mdb_warn("failed to read list node at %p", addr); return (WALK_ERR); } return (wsp->walk_callback(addr, iw, wsp->walk_cbdata)); } static void ip_list_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (ip_list_walk_data_t)); } static int ipif_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("ill", wsp) == -1) { mdb_warn("can't walk 'ills'"); return (WALK_ERR); } return (WALK_NEXT); } static int ipif_walk_step(mdb_walk_state_t *wsp) { if (mdb_pwalk("ipif_list", wsp->walk_callback, wsp->walk_cbdata, wsp->walk_addr) == -1) { mdb_warn("can't walk 'ipif_list'"); return (WALK_ERR); } return (WALK_NEXT); } /* ARGSUSED */ static int ipif_cb(uintptr_t addr, const ipif_walk_data_t *iw, ipif_cbdata_t *id) { ipif_t ipif; if (mdb_vread(&ipif, sizeof (ipif_t), (uintptr_t)addr) == -1) { mdb_warn("failed to read ipif at %p", addr); return (WALK_NEXT); } if (mdb_vread(&id->ill, sizeof (ill_t), (uintptr_t)ipif.ipif_ill) == -1) { mdb_warn("failed to read ill at %p", ipif.ipif_ill); return (WALK_NEXT); } (void) ipif_format((uintptr_t)addr, &ipif, id); return (WALK_NEXT); } static void ipif_header(boolean_t verbose) { if (verbose) { mdb_printf("%-?s %-10s %-3s %-?s %-8s %-30s\n", "ADDR", "NAME", "CNT", "ILL", "STFLAGS", "FLAGS"); mdb_printf("%s\n%s\n", "LCLADDR", "BROADCAST"); mdb_printf("%%80s%\n", ""); } else { mdb_printf("%-?s %-10s %6s %-?s %-8s %-30s\n", "ADDR", "NAME", "CNT", "ILL", "STFLAGS", "FLAGS"); mdb_printf("%s\n%%80s%\n", "LCLADDR", ""); } } #ifdef _BIG_ENDIAN #define ip_ntohl_32(x) ((x) & 0xffffffff) #else #define ip_ntohl_32(x) (((uint32_t)(x) << 24) | \ (((uint32_t)(x) << 8) & 0xff0000) | \ (((uint32_t)(x) >> 8) & 0xff00) | \ ((uint32_t)(x) >> 24)) #endif int mask_to_prefixlen(int af, const in6_addr_t *addr) { int len = 0; int i; uint_t mask = 0; if (af == AF_INET6) { for (i = 0; i < 4; i++) { if (addr->s6_addr32[i] == 0xffffffff) { len += 32; } else { mask = addr->s6_addr32[i]; break; } } } else { mask = V4_PART_OF_V6((*addr)); } if (mask > 0) len += (33 - mdb_ffs(ip_ntohl_32(mask))); return (len); } static int ipif_format(uintptr_t addr, const void *ipifptr, void *ipif_cb_arg) { const ipif_t *ipif = ipifptr; ipif_cbdata_t *ipifcb = ipif_cb_arg; boolean_t verbose = ipifcb->verbose; char ill_name[LIFNAMSIZ]; char buf[LIFNAMSIZ]; int cnt; static const mdb_bitmask_t sfmasks[] = { { "CO", IPIF_CONDEMNED, IPIF_CONDEMNED}, { "CH", IPIF_CHANGING, IPIF_CHANGING}, { "SL", IPIF_SET_LINKLOCAL, IPIF_SET_LINKLOCAL}, { NULL, 0, 0 } }; static const mdb_bitmask_t fmasks[] = { { "UP", IPIF_UP, IPIF_UP }, { "UNN", IPIF_UNNUMBERED, IPIF_UNNUMBERED}, { "DHCP", IPIF_DHCPRUNNING, IPIF_DHCPRUNNING}, { "PRIV", IPIF_PRIVATE, IPIF_PRIVATE}, { "NOXMT", IPIF_NOXMIT, IPIF_NOXMIT}, { "NOLCL", IPIF_NOLOCAL, IPIF_NOLOCAL}, { "DEPR", IPIF_DEPRECATED, IPIF_DEPRECATED}, { "PREF", IPIF_PREFERRED, IPIF_PREFERRED}, { "TEMP", IPIF_TEMPORARY, IPIF_TEMPORARY}, { "ACONF", IPIF_ADDRCONF, IPIF_ADDRCONF}, { "ANY", IPIF_ANYCAST, IPIF_ANYCAST}, { "NFAIL", IPIF_NOFAILOVER, IPIF_NOFAILOVER}, { NULL, 0, 0 } }; char flagsbuf[2 * A_CNT(fmasks)]; char bitfields[A_CNT(fmasks)]; char sflagsbuf[A_CNT(sfmasks)]; char sbuf[DEFCOLS], addrstr[INET6_ADDRSTRLEN]; int ipver = ipifcb->ipif_ipversion; int af; if (ipver != 0) { if ((ipver == IPV4_VERSION && ipifcb->ill.ill_isv6) || (ipver == IPV6_VERSION && !ipifcb->ill.ill_isv6)) { return (WALK_NEXT); } } if ((mdb_readstr(ill_name, MIN(LIFNAMSIZ, ipifcb->ill.ill_name_length), (uintptr_t)ipifcb->ill.ill_name)) == -1) { mdb_warn("failed to read ill_name of ill %p\n", ipifcb->ill); return (WALK_NEXT); } if (ipif->ipif_id != 0) { mdb_snprintf(buf, LIFNAMSIZ, "%s:%d", ill_name, ipif->ipif_id); } else { mdb_snprintf(buf, LIFNAMSIZ, "%s", ill_name); } mdb_snprintf(bitfields, sizeof (bitfields), "%s", ipif->ipif_addr_ready ? ",ADR" : "", ipif->ipif_was_up ? ",WU" : "", ipif->ipif_was_dup ? ",WD" : ""); mdb_snprintf(flagsbuf, sizeof (flagsbuf), "%llb%s", ipif->ipif_flags, fmasks, bitfields); mdb_snprintf(sflagsbuf, sizeof (sflagsbuf), "%b", ipif->ipif_state_flags, sfmasks); cnt = ipif->ipif_refcnt; if (ipifcb->ill.ill_isv6) { mdb_snprintf(addrstr, sizeof (addrstr), "%N", &ipif->ipif_v6lcl_addr); af = AF_INET6; } else { mdb_snprintf(addrstr, sizeof (addrstr), "%I", V4_PART_OF_V6((ipif->ipif_v6lcl_addr))); af = AF_INET; } if (verbose) { mdb_printf("%-?p %-10s %3d %-?p %-8s %-30s\n", addr, buf, cnt, ipif->ipif_ill, sflagsbuf, flagsbuf); mdb_snprintf(sbuf, sizeof (sbuf), "%*s %12s", sizeof (uintptr_t) * 2, "", ""); mdb_printf("%s |\n%s +---> %4d %-15s " "Active consistent reader cnt\n", sbuf, sbuf, ipif->ipif_refcnt, "ipif_refcnt"); mdb_printf("%-s/%d\n", addrstr, mask_to_prefixlen(af, &ipif->ipif_v6net_mask)); if (ipifcb->ill.ill_isv6) { mdb_printf("%-N\n", &ipif->ipif_v6brd_addr); } else { mdb_printf("%-I\n", V4_PART_OF_V6((ipif->ipif_v6brd_addr))); } } else { mdb_printf("%-?p %-10s %6d %-?p %-8s %-30s\n", addr, buf, cnt, ipif->ipif_ill, sflagsbuf, flagsbuf); mdb_printf("%-s/%d\n", addrstr, mask_to_prefixlen(af, &ipif->ipif_v6net_mask)); } return (WALK_NEXT); } static int ipif(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ipif_t ipif; ipif_cbdata_t id; int ipversion = 0; const char *opt_P = NULL; uint_t verbose = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'P', MDB_OPT_STR, &opt_P, NULL) != argc) return (DCMD_USAGE); if (opt_P != NULL) { if (strcmp("v4", opt_P) == 0) { ipversion = IPV4_VERSION; } else if (strcmp("v6", opt_P) == 0) { ipversion = IPV6_VERSION; } else { mdb_warn("invalid protocol '%s'\n", opt_P); return (DCMD_USAGE); } } id.verbose = verbose; id.ipif_ipversion = ipversion; if (flags & DCMD_ADDRSPEC) { if (mdb_vread(&ipif, sizeof (ipif_t), addr) == -1) { mdb_warn("failed to read ipif at %p\n", addr); return (DCMD_ERR); } ipif_header(verbose); if (mdb_vread(&id.ill, sizeof (ill_t), (uintptr_t)ipif.ipif_ill) == -1) { mdb_warn("failed to read ill at %p", ipif.ipif_ill); return (WALK_NEXT); } return (ipif_format(addr, &ipif, &id)); } else { ipif_header(verbose); if (mdb_walk("ipif", (mdb_walk_cb_t)ipif_cb, &id) == -1) { mdb_warn("failed to walk ipifs\n"); return (DCMD_ERR); } } return (DCMD_OK); } static void ipif_help(void) { mdb_printf("Prints the following fields: ipif ptr, name, " "count, ill ptr, state flags and ipif flags.\n" "The count field is a sum of individual refcnts and is expanded " "with the -v option.\n" "The flags field shows the following:" "\n\tUNN -> UNNUMBERED, DHCP -> DHCPRUNNING, PRIV -> PRIVATE, " "\n\tNOXMT -> NOXMIT, NOLCL -> NOLOCAL, DEPR -> DEPRECATED, " "\n\tPREF -> PREFERRED, TEMP -> TEMPORARY, ACONF -> ADDRCONF, " "\n\tANY -> ANYCAST, NFAIL -> NOFAILOVER, " "\n\tADR -> ipif_addr_ready, MU -> ipif_multicast_up, " "\n\tWU -> ipif_was_up, WD -> ipif_was_dup, " "JA -> ipif_joined_allhosts.\n\n"); mdb_printf("Options:\n"); mdb_printf("\t-P v4 | v6" "\tfilter ipif structures on ills for the specified protocol\n"); } static int conn_status_walk_fanout(uintptr_t addr, mdb_walk_state_t *wsp, const char *walkname) { if (mdb_pwalk(walkname, wsp->walk_callback, wsp->walk_cbdata, addr) == -1) { mdb_warn("couldn't walk '%s' at %p", walkname, addr); return (WALK_ERR); } return (WALK_NEXT); } static int conn_status_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; (void) conn_status_walk_fanout(addr, wsp, "udp_hash"); (void) conn_status_walk_fanout(addr, wsp, "conn_hash"); (void) conn_status_walk_fanout(addr, wsp, "bind_hash"); (void) conn_status_walk_fanout(addr, wsp, "proto_hash"); (void) conn_status_walk_fanout(addr, wsp, "proto_v6_hash"); return (WALK_NEXT); } /* ARGSUSED */ static int conn_status_cb(uintptr_t addr, const void *walk_data, void *private) { netstack_t nss; char src_addrstr[INET6_ADDRSTRLEN]; char rem_addrstr[INET6_ADDRSTRLEN]; const ipcl_hash_walk_data_t *iw = walk_data; conn_t c, *conn = &c; in_port_t lport, fport; if (iw != NULL) conn = iw->conn; else if (mdb_vread(conn, sizeof (conn_t), addr) == -1) { mdb_warn("failed to read conn_t at %p", addr); return (WALK_ERR); } if (mdb_vread(&nss, sizeof (nss), (uintptr_t)conn->conn_netstack) == -1) { mdb_warn("failed to read netstack_t %p", conn->conn_netstack); return (WALK_ERR); } mdb_printf("%-?p %-?p %?d %?d\n", addr, conn->conn_wq, nss.netstack_stackid, conn->conn_zoneid); if (conn->conn_family == AF_INET6) { mdb_snprintf(src_addrstr, sizeof (rem_addrstr), "%N", &conn->conn_laddr_v6); mdb_snprintf(rem_addrstr, sizeof (rem_addrstr), "%N", &conn->conn_faddr_v6); } else { mdb_snprintf(src_addrstr, sizeof (src_addrstr), "%I", V4_PART_OF_V6((conn->conn_laddr_v6))); mdb_snprintf(rem_addrstr, sizeof (rem_addrstr), "%I", V4_PART_OF_V6((conn->conn_faddr_v6))); } mdb_nhconvert(&lport, &conn->conn_lport, sizeof (lport)); mdb_nhconvert(&fport, &conn->conn_fport, sizeof (fport)); mdb_printf("%s:%-5d\n%s:%-5d\n", src_addrstr, lport, rem_addrstr, fport); return (WALK_NEXT); } static void conn_header(void) { mdb_printf("%-?s %-?s %?s %?s\n%s\n%s\n", "ADDR", "WQ", "STACK", "ZONE", "SRC:PORT", "DEST:PORT"); mdb_printf("%%80s%\n", ""); } /*ARGSUSED*/ static int conn_status(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { conn_header(); if (flags & DCMD_ADDRSPEC) { (void) conn_status_cb(addr, NULL, NULL); } else { if (mdb_walk("conn_status", (mdb_walk_cb_t)conn_status_cb, NULL) == -1) { mdb_warn("failed to walk conn_fanout"); return (DCMD_ERR); } } return (DCMD_OK); } static void conn_status_help(void) { mdb_printf("Prints conn_t structures from the following hash tables: " "\n\tips_ipcl_udp_fanout\n\tips_ipcl_bind_fanout" "\n\tips_ipcl_conn_fanout\n\tips_ipcl_proto_fanout_v4" "\n\tips_ipcl_proto_fanout_v6\n"); } static int srcid_walk_step(mdb_walk_state_t *wsp) { if (mdb_pwalk("srcid_list", wsp->walk_callback, wsp->walk_cbdata, wsp->walk_addr) == -1) { mdb_warn("can't walk 'srcid_list'"); return (WALK_ERR); } return (WALK_NEXT); } /* ARGSUSED */ static int srcid_status_cb(uintptr_t addr, const void *walk_data, void *private) { srcid_map_t smp; if (mdb_vread(&smp, sizeof (srcid_map_t), addr) == -1) { mdb_warn("failed to read srcid_map at %p", addr); return (WALK_ERR); } mdb_printf("%-?p %3d %4d %6d %N\n", addr, smp.sm_srcid, smp.sm_zoneid, smp.sm_refcnt, &smp.sm_addr); return (WALK_NEXT); } static void srcid_header(void) { mdb_printf("%-?s %3s %4s %6s %s\n", "ADDR", "ID", "ZONE", "REFCNT", "IPADDR"); mdb_printf("%%80s%\n", ""); } /*ARGSUSED*/ static int srcid_status(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { srcid_header(); if (flags & DCMD_ADDRSPEC) { (void) srcid_status_cb(addr, NULL, NULL); } else { if (mdb_walk("srcid", (mdb_walk_cb_t)srcid_status_cb, NULL) == -1) { mdb_warn("failed to walk srcid_map"); return (DCMD_ERR); } } return (DCMD_OK); } static int ilb_stacks_walk_step(mdb_walk_state_t *wsp) { return (ns_walk_step(wsp, NS_ILB)); } static int ilb_rules_walk_init(mdb_walk_state_t *wsp) { ilb_stack_t ilbs; if (wsp->walk_addr == 0) return (WALK_ERR); if (mdb_vread(&ilbs, sizeof (ilbs), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_stack_t at %p", wsp->walk_addr); return (WALK_ERR); } if ((wsp->walk_addr = (uintptr_t)ilbs.ilbs_rule_head) != 0) return (WALK_NEXT); else return (WALK_DONE); } static int ilb_rules_walk_step(mdb_walk_state_t *wsp) { ilb_rule_t rule; int status; if (mdb_vread(&rule, sizeof (rule), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_rule_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &rule, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); if ((wsp->walk_addr = (uintptr_t)rule.ir_next) == 0) return (WALK_DONE); else return (WALK_NEXT); } static int ilb_servers_walk_init(mdb_walk_state_t *wsp) { ilb_rule_t rule; if (wsp->walk_addr == 0) return (WALK_ERR); if (mdb_vread(&rule, sizeof (rule), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_rule_t at %p", wsp->walk_addr); return (WALK_ERR); } if ((wsp->walk_addr = (uintptr_t)rule.ir_servers) != 0) return (WALK_NEXT); else return (WALK_DONE); } static int ilb_servers_walk_step(mdb_walk_state_t *wsp) { ilb_server_t server; int status; if (mdb_vread(&server, sizeof (server), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_server_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &server, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); if ((wsp->walk_addr = (uintptr_t)server.iser_next) == 0) return (WALK_DONE); else return (WALK_NEXT); } /* * Helper structure for ilb_nat_src walker. It stores the current index of the * nat src table. */ typedef struct { ilb_stack_t ilbs; int idx; } ilb_walk_t; /* Copy from list.c */ #define list_object(a, node) ((void *)(((char *)node) - (a)->list_offset)) static int ilb_nat_src_walk_init(mdb_walk_state_t *wsp) { int i; ilb_walk_t *ns_walk; ilb_nat_src_entry_t *entry = NULL; if (wsp->walk_addr == 0) return (WALK_ERR); ns_walk = mdb_alloc(sizeof (ilb_walk_t), UM_SLEEP); if (mdb_vread(&ns_walk->ilbs, sizeof (ns_walk->ilbs), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_stack_t at %p", wsp->walk_addr); mdb_free(ns_walk, sizeof (ilb_walk_t)); return (WALK_ERR); } if (ns_walk->ilbs.ilbs_nat_src == NULL) { mdb_free(ns_walk, sizeof (ilb_walk_t)); return (WALK_DONE); } wsp->walk_data = ns_walk; for (i = 0; i < ns_walk->ilbs.ilbs_nat_src_hash_size; i++) { list_t head; char *khead; /* Read in the nsh_head in the i-th element of the array. */ khead = (char *)ns_walk->ilbs.ilbs_nat_src + i * sizeof (ilb_nat_src_hash_t); if (mdb_vread(&head, sizeof (list_t), (uintptr_t)khead) == -1) { mdb_warn("failed to read ilbs_nat_src at %p\n", khead); return (WALK_ERR); } /* * Note that list_next points to a kernel address and we need * to compare list_next with the kernel address of the list * head. So we need to calculate the address manually. */ if ((char *)head.list_head.list_next != khead + offsetof(list_t, list_head)) { entry = list_object(&head, head.list_head.list_next); break; } } if (entry == NULL) return (WALK_DONE); wsp->walk_addr = (uintptr_t)entry; ns_walk->idx = i; return (WALK_NEXT); } static int ilb_nat_src_walk_step(mdb_walk_state_t *wsp) { int status; ilb_nat_src_entry_t entry, *next_entry; ilb_walk_t *ns_walk; ilb_stack_t *ilbs; list_t head; char *khead; int i; if (mdb_vread(&entry, sizeof (ilb_nat_src_entry_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_nat_src_entry_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &entry, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); ns_walk = (ilb_walk_t *)wsp->walk_data; ilbs = &ns_walk->ilbs; i = ns_walk->idx; /* Read in the nsh_head in the i-th element of the array. */ khead = (char *)ilbs->ilbs_nat_src + i * sizeof (ilb_nat_src_hash_t); if (mdb_vread(&head, sizeof (list_t), (uintptr_t)khead) == -1) { mdb_warn("failed to read ilbs_nat_src at %p\n", khead); return (WALK_ERR); } /* * Check if there is still entry in the current list. * * Note that list_next points to a kernel address and we need to * compare list_next with the kernel address of the list head. * So we need to calculate the address manually. */ if ((char *)entry.nse_link.list_next != khead + offsetof(list_t, list_head)) { wsp->walk_addr = (uintptr_t)list_object(&head, entry.nse_link.list_next); return (WALK_NEXT); } /* Start with the next bucket in the array. */ next_entry = NULL; for (i++; i < ilbs->ilbs_nat_src_hash_size; i++) { khead = (char *)ilbs->ilbs_nat_src + i * sizeof (ilb_nat_src_hash_t); if (mdb_vread(&head, sizeof (list_t), (uintptr_t)khead) == -1) { mdb_warn("failed to read ilbs_nat_src at %p\n", khead); return (WALK_ERR); } if ((char *)head.list_head.list_next != khead + offsetof(list_t, list_head)) { next_entry = list_object(&head, head.list_head.list_next); break; } } if (next_entry == NULL) return (WALK_DONE); wsp->walk_addr = (uintptr_t)next_entry; ns_walk->idx = i; return (WALK_NEXT); } static void ilb_common_walk_fini(mdb_walk_state_t *wsp) { ilb_walk_t *walk; walk = (ilb_walk_t *)wsp->walk_data; if (walk == NULL) return; mdb_free(walk, sizeof (ilb_walk_t *)); } static int ilb_conn_walk_init(mdb_walk_state_t *wsp) { int i; ilb_walk_t *conn_walk; ilb_conn_hash_t head; if (wsp->walk_addr == 0) return (WALK_ERR); conn_walk = mdb_alloc(sizeof (ilb_walk_t), UM_SLEEP); if (mdb_vread(&conn_walk->ilbs, sizeof (conn_walk->ilbs), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_stack_t at %p", wsp->walk_addr); mdb_free(conn_walk, sizeof (ilb_walk_t)); return (WALK_ERR); } if (conn_walk->ilbs.ilbs_c2s_conn_hash == NULL) { mdb_free(conn_walk, sizeof (ilb_walk_t)); return (WALK_DONE); } wsp->walk_data = conn_walk; for (i = 0; i < conn_walk->ilbs.ilbs_conn_hash_size; i++) { char *khead; /* Read in the nsh_head in the i-th element of the array. */ khead = (char *)conn_walk->ilbs.ilbs_c2s_conn_hash + i * sizeof (ilb_conn_hash_t); if (mdb_vread(&head, sizeof (ilb_conn_hash_t), (uintptr_t)khead) == -1) { mdb_warn("failed to read ilbs_c2s_conn_hash at %p\n", khead); return (WALK_ERR); } if (head.ilb_connp != NULL) break; } if (head.ilb_connp == NULL) return (WALK_DONE); wsp->walk_addr = (uintptr_t)head.ilb_connp; conn_walk->idx = i; return (WALK_NEXT); } static int ilb_conn_walk_step(mdb_walk_state_t *wsp) { int status; ilb_conn_t conn; ilb_walk_t *conn_walk; ilb_stack_t *ilbs; ilb_conn_hash_t head; char *khead; int i; if (mdb_vread(&conn, sizeof (ilb_conn_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_conn_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &conn, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); conn_walk = (ilb_walk_t *)wsp->walk_data; ilbs = &conn_walk->ilbs; i = conn_walk->idx; /* Check if there is still entry in the current list. */ if (conn.conn_c2s_next != NULL) { wsp->walk_addr = (uintptr_t)conn.conn_c2s_next; return (WALK_NEXT); } /* Start with the next bucket in the array. */ for (i++; i < ilbs->ilbs_conn_hash_size; i++) { khead = (char *)ilbs->ilbs_c2s_conn_hash + i * sizeof (ilb_conn_hash_t); if (mdb_vread(&head, sizeof (ilb_conn_hash_t), (uintptr_t)khead) == -1) { mdb_warn("failed to read ilbs_c2s_conn_hash at %p\n", khead); return (WALK_ERR); } if (head.ilb_connp != NULL) break; } if (head.ilb_connp == NULL) return (WALK_DONE); wsp->walk_addr = (uintptr_t)head.ilb_connp; conn_walk->idx = i; return (WALK_NEXT); } static int ilb_sticky_walk_init(mdb_walk_state_t *wsp) { int i; ilb_walk_t *sticky_walk; ilb_sticky_t *st = NULL; if (wsp->walk_addr == 0) return (WALK_ERR); sticky_walk = mdb_alloc(sizeof (ilb_walk_t), UM_SLEEP); if (mdb_vread(&sticky_walk->ilbs, sizeof (sticky_walk->ilbs), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_stack_t at %p", wsp->walk_addr); mdb_free(sticky_walk, sizeof (ilb_walk_t)); return (WALK_ERR); } if (sticky_walk->ilbs.ilbs_sticky_hash == NULL) { mdb_free(sticky_walk, sizeof (ilb_walk_t)); return (WALK_DONE); } wsp->walk_data = sticky_walk; for (i = 0; i < sticky_walk->ilbs.ilbs_sticky_hash_size; i++) { list_t head; char *khead; /* Read in the nsh_head in the i-th element of the array. */ khead = (char *)sticky_walk->ilbs.ilbs_sticky_hash + i * sizeof (ilb_sticky_hash_t); if (mdb_vread(&head, sizeof (list_t), (uintptr_t)khead) == -1) { mdb_warn("failed to read ilbs_sticky_hash at %p\n", khead); return (WALK_ERR); } /* * Note that list_next points to a kernel address and we need * to compare list_next with the kernel address of the list * head. So we need to calculate the address manually. */ if ((char *)head.list_head.list_next != khead + offsetof(list_t, list_head)) { st = list_object(&head, head.list_head.list_next); break; } } if (st == NULL) return (WALK_DONE); wsp->walk_addr = (uintptr_t)st; sticky_walk->idx = i; return (WALK_NEXT); } static int ilb_sticky_walk_step(mdb_walk_state_t *wsp) { int status; ilb_sticky_t st, *st_next; ilb_walk_t *sticky_walk; ilb_stack_t *ilbs; list_t head; char *khead; int i; if (mdb_vread(&st, sizeof (ilb_sticky_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ilb_sticky_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &st, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); sticky_walk = (ilb_walk_t *)wsp->walk_data; ilbs = &sticky_walk->ilbs; i = sticky_walk->idx; /* Read in the nsh_head in the i-th element of the array. */ khead = (char *)ilbs->ilbs_sticky_hash + i * sizeof (ilb_sticky_hash_t); if (mdb_vread(&head, sizeof (list_t), (uintptr_t)khead) == -1) { mdb_warn("failed to read ilbs_sticky_hash at %p\n", khead); return (WALK_ERR); } /* * Check if there is still entry in the current list. * * Note that list_next points to a kernel address and we need to * compare list_next with the kernel address of the list head. * So we need to calculate the address manually. */ if ((char *)st.list.list_next != khead + offsetof(list_t, list_head)) { wsp->walk_addr = (uintptr_t)list_object(&head, st.list.list_next); return (WALK_NEXT); } /* Start with the next bucket in the array. */ st_next = NULL; for (i++; i < ilbs->ilbs_nat_src_hash_size; i++) { khead = (char *)ilbs->ilbs_sticky_hash + i * sizeof (ilb_sticky_hash_t); if (mdb_vread(&head, sizeof (list_t), (uintptr_t)khead) == -1) { mdb_warn("failed to read ilbs_sticky_hash at %p\n", khead); return (WALK_ERR); } if ((char *)head.list_head.list_next != khead + offsetof(list_t, list_head)) { st_next = list_object(&head, head.list_head.list_next); break; } } if (st_next == NULL) return (WALK_DONE); wsp->walk_addr = (uintptr_t)st_next; sticky_walk->idx = i; return (WALK_NEXT); } /* * 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. */ #include #include #include #include #include #include #include #include #include #include #define CMN_HDR_START "%" #define CMN_HDR_END "%\n" #define CMN_INDENT (4) #define CMN_INACTIVE "%s facility inactive.\n" /* * Bitmap data for page protection flags suitable for use with %b. */ const mdb_bitmask_t prot_flag_bits[] = { { "PROT_READ", PROT_READ, PROT_READ }, { "PROT_WRITE", PROT_WRITE, PROT_WRITE }, { "PROT_EXEC", PROT_EXEC, PROT_EXEC }, { "PROT_USER", PROT_USER, PROT_USER }, { NULL, 0, 0 } }; static void printtime_nice(const char *str, time_t time) { if (time) mdb_printf("%s%Y\n", str, time); else mdb_printf("%sn/a\n", str); } /* * Print header common to all IPC types. */ static void ipcperm_header() { mdb_printf(CMN_HDR_START "%?s %5s %5s %8s %5s %5s %6s %5s %5s %5s %5s" CMN_HDR_END, "ADDR", "REF", "ID", "KEY", "MODE", "PRJID", "ZONEID", "OWNER", "GROUP", "CREAT", "CGRP"); } /* * Print data common to all IPC types. */ static void ipcperm_print(uintptr_t addr, kipc_perm_t *perm) { kproject_t proj; int res; res = mdb_vread(&proj, sizeof (kproject_t), (uintptr_t)perm->ipc_proj); if (res == -1) mdb_warn("failed to read kproject_t at %#p", perm->ipc_proj); mdb_printf("%0?p %5d %5d", addr, perm->ipc_ref, perm->ipc_id); if (perm->ipc_key) mdb_printf(" %8x", perm->ipc_key); else mdb_printf(" %8s", "private"); mdb_printf(" %5#o", perm->ipc_mode & 07777); if (res == -1) mdb_printf(" %5s %5s", "", ""); else mdb_printf(" %5d %6d", proj.kpj_id, proj.kpj_zoneid); mdb_printf(" %5d %5d %5d %5d\n", perm->ipc_uid, perm->ipc_gid, perm->ipc_cuid, perm->ipc_cgid); } /*ARGSUSED*/ static int ipcperm(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kipc_perm_t perm; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) ipcperm_header(); if (mdb_vread(&perm, sizeof (kipc_perm_t), addr) == -1) { mdb_warn("failed to read kipc_perm_t at %#lx", addr); return (DCMD_ERR); } ipcperm_print(addr, &perm); return (DCMD_OK); } #define MSG_SND_SIZE 0x1 static int msgq_check_for_waiters(list_t *walk_this, int min, int max, int copy_wait, uintptr_t addr, int flag) { int found = 0; int ii; msgq_wakeup_t *walker, next; uintptr_t head; for (ii = min; ii < max; ii++) { head = ((ulong_t)addr) + sizeof (list_t)*ii + sizeof (list_node_t); if (head != (uintptr_t)walk_this[ii].list_head.list_next) { walker = (msgq_wakeup_t *)walk_this[ii].list_head.list_next; while (head != (uintptr_t)walker) { if (mdb_vread(&next, sizeof (msgq_wakeup_t), (uintptr_t)walker) == -1) { mdb_warn( "Failed to read message queue\n"); return (found); } if (flag & MSG_SND_SIZE) { mdb_printf("%15lx\t%6d\t%15lx\t%15d\n", next.msgw_thrd, next.msgw_type, walker + (uintptr_t) OFFSETOF(msgq_wakeup_t, msgw_wake_cv), next.msgw_snd_size); } else { mdb_printf("%15lx\t%6d\t%15lx\t%15s\n", next.msgw_thrd, next.msgw_type, walker + (uintptr_t) OFFSETOF(msgq_wakeup_t, msgw_wake_cv), (copy_wait ? "yes":"no")); } found++; walker = (msgq_wakeup_t *)next.msgw_list.list_next; } } } return (found); } static void msq_print(kmsqid_t *msqid, uintptr_t addr) { int total = 0; mdb_printf("&list: %-?p\n", addr + OFFSETOF(kmsqid_t, msg_list)); mdb_printf("cbytes: 0t%lu qnum: 0t%lu qbytes: 0t%lu" " qmax: 0t%lu\n", msqid->msg_cbytes, msqid->msg_qnum, msqid->msg_qbytes, msqid->msg_qmax); mdb_printf("lspid: 0t%d lrpid: 0t%d\n", (int)msqid->msg_lspid, (int)msqid->msg_lrpid); printtime_nice("stime: ", msqid->msg_stime); printtime_nice("rtime: ", msqid->msg_rtime); printtime_nice("ctime: ", msqid->msg_ctime); mdb_printf("snd_cnt: 0t%lld snd_cv: %hd (%p)\n", msqid->msg_snd_cnt, msqid->msg_snd_cv._opaque, addr + (uintptr_t)OFFSETOF(kmsqid_t, msg_snd_cv)); mdb_printf("Blocked recievers\n"); mdb_printf("%15s\t%6s\t%15s\t%15s\n", "Thread Addr", "Type", "cv addr", "copyout-wait?"); total += msgq_check_for_waiters(&msqid->msg_cpy_block, 0, 1, 1, addr + OFFSETOF(kmsqid_t, msg_cpy_block), 0); total += msgq_check_for_waiters(msqid->msg_wait_snd_ngt, 0, MSG_MAX_QNUM + 1, 0, addr + OFFSETOF(kmsqid_t, msg_wait_snd_ngt), 0); mdb_printf("Blocked senders\n"); total += msgq_check_for_waiters(&msqid->msg_wait_rcv, 0, 1, 1, addr + OFFSETOF(kmsqid_t, msg_wait_rcv), MSG_SND_SIZE); mdb_printf("%15s\t%6s\t%15s\t%15s\n", "Thread Addr", "Type", "cv addr", "Msg Size"); total += msgq_check_for_waiters(msqid->msg_wait_snd, 0, MSG_MAX_QNUM + 1, 0, addr + OFFSETOF(kmsqid_t, msg_wait_snd), 0); mdb_printf("Total number of waiters: %d\n", total); } /*ARGSUSED1*/ static void shm_print(kshmid_t *shmid, uintptr_t addr) { shmatt_t nattch; nattch = shmid->shm_perm.ipc_ref - (IPC_FREE(&shmid->shm_perm) ? 0 : 1); mdb_printf(CMN_HDR_START "%10s %?s %5s %7s %7s %7s %7s" CMN_HDR_END, "SEGSZ", "AMP", "LKCNT", "LPID", "CPID", "NATTCH", "CNATTCH"); mdb_printf("%10#lx %?p %5u %7d %7d %7lu %7lu\n", shmid->shm_segsz, shmid->shm_amp, shmid->shm_lkcnt, (int)shmid->shm_lpid, (int)shmid->shm_cpid, nattch, shmid->shm_ismattch); printtime_nice("atime: ", shmid->shm_atime); printtime_nice("dtime: ", shmid->shm_dtime); printtime_nice("ctime: ", shmid->shm_ctime); mdb_printf("sptinfo: %-?p sptseg: %-?p\n", shmid->shm_sptinfo, shmid->shm_sptseg); mdb_printf("sptprot: <%lb>\n", shmid->shm_sptprot, prot_flag_bits); } /*ARGSUSED1*/ static void sem_print(ksemid_t *semid, uintptr_t addr) { mdb_printf("base: %-?p nsems: 0t%u\n", semid->sem_base, semid->sem_nsems); printtime_nice("otime: ", semid->sem_otime); printtime_nice("ctime: ", semid->sem_ctime); mdb_printf("binary: %s\n", semid->sem_binary ? "yes" : "no"); } typedef struct ipc_ops_vec { char *iv_wcmd; /* walker name */ char *iv_ocmd; /* output dcmd */ char *iv_service; /* service pointer */ void (*iv_print)(void *, uintptr_t); /* output callback */ size_t iv_idsize; } ipc_ops_vec_t; ipc_ops_vec_t msq_ops_vec = { "msq", "kmsqid", "msq_svc", (void(*)(void *, uintptr_t))msq_print, sizeof (kmsqid_t) }; ipc_ops_vec_t shm_ops_vec = { "shm", "kshmid", "shm_svc", (void(*)(void *, uintptr_t))shm_print, sizeof (kshmid_t) }; ipc_ops_vec_t sem_ops_vec = { "sem", "ksemid", "sem_svc", (void(*)(void *, uintptr_t))sem_print, sizeof (ksemid_t) }; /* * Generic IPC data structure display code */ static int ds_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, ipc_ops_vec_t *iv) { void *iddata; if (!(flags & DCMD_ADDRSPEC)) { uint_t oflags = 0; if (mdb_getopts(argc, argv, 'l', MDB_OPT_SETBITS, 1, &oflags, NULL) != argc) return (DCMD_USAGE); if (mdb_walk_dcmd(iv->iv_wcmd, oflags ? iv->iv_ocmd : "ipcperm", argc, argv) == -1) { mdb_warn("can't walk '%s'", iv->iv_wcmd); return (DCMD_ERR); } return (DCMD_OK); } iddata = mdb_alloc(iv->iv_idsize, UM_SLEEP | UM_GC); if (mdb_vread(iddata, iv->iv_idsize, addr) == -1) { mdb_warn("failed to read %s at %#lx", iv->iv_ocmd, addr); return (DCMD_ERR); } if (!DCMD_HDRSPEC(flags) && iv->iv_print) mdb_printf("\n"); if (DCMD_HDRSPEC(flags) || iv->iv_print) ipcperm_header(); ipcperm_print(addr, (struct kipc_perm *)iddata); if (iv->iv_print) { mdb_inc_indent(CMN_INDENT); iv->iv_print(iddata, addr); mdb_dec_indent(CMN_INDENT); } return (DCMD_OK); } /* * Stubs to call ds_print with the appropriate ops vector */ static int cmd_kshmid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (ds_print(addr, flags, argc, argv, &shm_ops_vec)); } static int cmd_kmsqid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (ds_print(addr, flags, argc, argv, &msq_ops_vec)); } static int cmd_ksemid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (ds_print(addr, flags, argc, argv, &sem_ops_vec)); } /* * Generic IPC walker */ static int ds_walk_init(mdb_walk_state_t *wsp) { ipc_ops_vec_t *iv = wsp->walk_arg; if (wsp->walk_arg != NULL && wsp->walk_addr != 0) mdb_printf("ignoring provided address\n"); if (wsp->walk_arg) if (mdb_readvar(&wsp->walk_addr, iv->iv_service) == -1) { mdb_printf("failed to read '%s'; module not present\n", iv->iv_service); return (WALK_DONE); } else wsp->walk_addr = wsp->walk_addr + OFFSETOF(ipc_service_t, ipcs_usedids); if (mdb_layered_walk("list", wsp) == -1) return (WALK_ERR); return (WALK_NEXT); } static int ds_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * Generic IPC ID/key to pointer code */ static int ipcid_impl(uintptr_t svcptr, uintptr_t id, uintptr_t *addr) { ipc_service_t service; kipc_perm_t perm; ipc_slot_t slot; uintptr_t slotptr; uint_t index; if (id > INT_MAX) { mdb_warn("id out of range\n"); return (DCMD_ERR); } if (mdb_vread(&service, sizeof (ipc_service_t), svcptr) == -1) { mdb_warn("failed to read ipc_service_t at %#lx", svcptr); return (DCMD_ERR); } index = (uint_t)id & (service.ipcs_tabsz - 1); slotptr = (uintptr_t)(service.ipcs_table + index); if (mdb_vread(&slot, sizeof (ipc_slot_t), slotptr) == -1) { mdb_warn("failed to read ipc_slot_t at %#lx", slotptr); return (DCMD_ERR); } if (slot.ipct_data == NULL) return (DCMD_ERR); if (mdb_vread(&perm, sizeof (kipc_perm_t), (uintptr_t)slot.ipct_data) == -1) { mdb_warn("failed to read kipc_perm_t at %#p", slot.ipct_data); return (DCMD_ERR); } if (perm.ipc_id != (uint_t)id) return (DCMD_ERR); *addr = (uintptr_t)slot.ipct_data; return (DCMD_OK); } typedef struct findkey_data { key_t fk_key; uintptr_t fk_addr; boolean_t fk_found; } findkey_data_t; static int findkey(uintptr_t addr, kipc_perm_t *perm, findkey_data_t *arg) { if (perm->ipc_key == arg->fk_key) { arg->fk_found = B_TRUE; arg->fk_addr = addr; return (WALK_DONE); } return (WALK_NEXT); } static int ipckey_impl(uintptr_t svcptr, uintptr_t key, uintptr_t *addr) { ipc_service_t service; findkey_data_t fkdata; if ((key == IPC_PRIVATE) || (key > INT_MAX)) { mdb_warn("key out of range\n"); return (DCMD_ERR); } if (mdb_vread(&service, sizeof (ipc_service_t), svcptr) == -1) { mdb_warn("failed to read ipc_service_t at %#lx", svcptr); return (DCMD_ERR); } fkdata.fk_key = (key_t)key; fkdata.fk_found = B_FALSE; if ((mdb_pwalk("avl", (mdb_walk_cb_t)findkey, &fkdata, svcptr + OFFSETOF(ipc_service_t, ipcs_keys)) == -1) || !fkdata.fk_found) return (DCMD_ERR); *addr = fkdata.fk_addr; return (DCMD_OK); } static int ipckeyid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, int(*fp)(uintptr_t, uintptr_t, uintptr_t *)) { uintmax_t val; uintptr_t raddr; int result; if (!(flags & DCMD_ADDRSPEC) || (argc != 1)) return (DCMD_USAGE); if (argv[0].a_type == MDB_TYPE_IMMEDIATE) val = argv[0].a_un.a_val; else if (argv[0].a_type == MDB_TYPE_STRING) val = mdb_strtoull(argv[0].a_un.a_str); else return (DCMD_USAGE); result = fp(addr, val, &raddr); if (result == DCMD_OK) mdb_printf("%lx", raddr); return (result); } static int ipckey(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (ipckeyid(addr, flags, argc, argv, ipckey_impl)); } static int ipcid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (ipckeyid(addr, flags, argc, argv, ipcid_impl)); } static int ds_ptr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, ipc_ops_vec_t *iv) { uint_t kflag = FALSE; uintptr_t svcptr, raddr; int result; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'k', MDB_OPT_SETBITS, TRUE, &kflag, NULL) != argc) return (DCMD_USAGE); if (mdb_readvar(&svcptr, iv->iv_service) == -1) { mdb_warn("failed to read '%s'; module not present\n", iv->iv_service); return (DCMD_ERR); } result = kflag ? ipckey_impl(svcptr, addr, &raddr) : ipcid_impl(svcptr, addr, &raddr); if (result == DCMD_OK) mdb_printf("%lx", raddr); return (result); } /* * Stubs to call ds_ptr with the appropriate ops vector */ static int id2shm(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (ds_ptr(addr, flags, argc, argv, &shm_ops_vec)); } static int id2msq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (ds_ptr(addr, flags, argc, argv, &msq_ops_vec)); } static int id2sem(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (ds_ptr(addr, flags, argc, argv, &sem_ops_vec)); } /* * The message queue contents walker */ static int msg_walk_init(mdb_walk_state_t *wsp) { wsp->walk_addr += OFFSETOF(kmsqid_t, msg_list); if (mdb_layered_walk("list", wsp) == -1) return (WALK_ERR); return (WALK_NEXT); } static int msg_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * The "::ipcs" command itself. Just walks each IPC type in turn. */ /*ARGSUSED*/ static int ipcs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t oflags = 0; if ((flags & DCMD_ADDRSPEC) || mdb_getopts(argc, argv, 'l', MDB_OPT_SETBITS, 1, &oflags, NULL) != argc) return (DCMD_USAGE); mdb_printf("Message queues:\n"); if (mdb_walk_dcmd("msq", oflags ? "kmsqid" : "ipcperm", argc, argv) == -1) { mdb_warn("can't walk 'msq'"); return (DCMD_ERR); } mdb_printf("\nShared memory:\n"); if (mdb_walk_dcmd("shm", oflags ? "kshmid" : "ipcperm", argc, argv) == -1) { mdb_warn("can't walk 'shm'"); return (DCMD_ERR); } mdb_printf("\nSemaphores:\n"); if (mdb_walk_dcmd("sem", oflags ? "ksemid" : "ipcperm", argc, argv) == -1) { mdb_warn("can't walk 'sem'"); return (DCMD_ERR); } return (DCMD_OK); } static int msgprint(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct msg message; uint_t lflag = FALSE; long type = 0; char *tflag = NULL; if (!(flags & DCMD_ADDRSPEC) || (mdb_getopts(argc, argv, 'l', MDB_OPT_SETBITS, TRUE, &lflag, 't', MDB_OPT_STR, &tflag, NULL) != argc)) return (DCMD_USAGE); /* * Handle negative values. */ if (tflag != NULL) { if (*tflag == '-') { tflag++; type = -1; } else { type = 1; } type *= mdb_strtoull(tflag); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %?s %8s %8s %8s%\n", "ADDR", "TEXT", "SIZE", "TYPE", "REF"); if (mdb_vread(&message, sizeof (struct msg), addr) == -1) { mdb_warn("failed to read msg at %#lx", addr); return (DCMD_ERR); } /* * If we are meeting our type contraints, display the message. * If -l was specified, we will also display the message * contents. */ if ((type == 0) || (type > 0 && message.msg_type == type) || (type < 0 && message.msg_type <= -type)) { if (lflag && !DCMD_HDRSPEC(flags)) mdb_printf("\n"); mdb_printf("%0?lx %?p %8ld %8ld %8ld\n", addr, message.msg_addr, message.msg_size, message.msg_type, message.msg_copycnt); if (lflag) { mdb_printf("\n"); mdb_inc_indent(CMN_INDENT); if (mdb_dumpptr( (uintptr_t)message.msg_addr, message.msg_size, MDB_DUMP_RELATIVE | MDB_DUMP_TRIM | MDB_DUMP_ASCII | MDB_DUMP_HEADER | MDB_DUMP_GROUP(4), NULL, NULL)) { mdb_dec_indent(CMN_INDENT); return (DCMD_ERR); } mdb_dec_indent(CMN_INDENT); } } return (DCMD_OK); } /* * MDB module linkage */ static const mdb_dcmd_t dcmds[] = { /* Generic routines */ { "ipcperm", ":", "display an IPC perm structure", ipcperm }, { "ipcid", ":id", "perform an IPC id lookup", ipcid }, { "ipckey", ":key", "perform an IPC key lookup", ipckey }, /* Specific routines */ { "kshmid", "?[-l]", "display a struct kshmid", cmd_kshmid }, { "kmsqid", "?[-l]", "display a struct kmsqid", cmd_kmsqid }, { "ksemid", "?[-l]", "display a struct ksemid", cmd_ksemid }, { "msg", ":[-l] [-t type]", "display contents of a message", msgprint }, /* Convenience routines */ { "id2shm", ":[-k]", "convert shared memory ID to pointer", id2shm }, { "id2msq", ":[-k]", "convert message queue ID to pointer", id2msq }, { "id2sem", ":[-k]", "convert semaphore ID to pointer", id2sem }, { "ipcs", "[-l]", "display System V IPC information", ipcs }, { NULL } }; static const mdb_walker_t walkers[] = { { "ipcsvc", "walk a System V IPC service", ds_walk_init, ds_walk_step }, { "shm", "walk the active shmid_ds structures", ds_walk_init, ds_walk_step, NULL, &shm_ops_vec }, { "msq", "walk the active msqid_ds structures", ds_walk_init, ds_walk_step, NULL, &msq_ops_vec }, { "sem", "walk the active semid_ds structures", ds_walk_init, ds_walk_step, NULL, &sem_ops_vec }, { "msgqueue", "walk messages on a message queue", msg_walk_init, msg_walk_step }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2001-2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include static uintptr_t ipp_mod_byid; static uintptr_t ipp_action_byid; static int byid_walk_init(mdb_walk_state_t *); static int byid_walk_step(mdb_walk_state_t *); static void byid_walk_fini(mdb_walk_state_t *); static int action(uintptr_t, uint_t, int, const mdb_arg_t *); static int action_format(uintptr_t, const void *, void *); static int action_dump(uintptr_t, ipp_action_t *, boolean_t); static int action_summary(uintptr_t, ipp_action_t *, boolean_t); static int cfglock(uintptr_t, uint_t, int, const mdb_arg_t *); static int mod(uintptr_t, uint_t, int, const mdb_arg_t *); static int mod_format(uintptr_t, const void *, void *); static int mod_dump(uintptr_t, ipp_mod_t *, boolean_t); static int mod_summary(uintptr_t, ipp_mod_t *, boolean_t); static int cfglock(uintptr_t, uint_t, int, const mdb_arg_t *); static int ippops(uintptr_t, uint_t, int, const mdb_arg_t *); static int packet(uintptr_t, uint_t, int, const mdb_arg_t *); static void dump_classes(uintptr_t, uint_t); static void dump_log(uintptr_t, uint_t); static void aid2aname(ipp_action_id_t, char *); static int ref_walk_init(mdb_walk_state_t *); static int ref_walk_step(mdb_walk_state_t *); static void ref_walk_fini(mdb_walk_state_t *); typedef struct afdata { boolean_t af_banner; uint_t af_flags; } afdata_t; #define AF_VERBOSE 1 typedef struct mfdata { boolean_t mf_banner; uint_t mf_flags; } mfdata_t; #define MF_VERBOSE 1 /* * walker. Skips entries that are NULL. */ static int byid_walk_init( mdb_walk_state_t *wsp) { uintptr_t start; if (mdb_vread(&start, sizeof (uintptr_t), wsp->walk_addr) == -1) { mdb_warn("failed to read from address %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = start; return (WALK_NEXT); } static int byid_walk_step( mdb_walk_state_t *wsp) { int status; void *ptr; if (mdb_vread(&ptr, sizeof (void *), wsp->walk_addr) == -1) { mdb_warn("failed to read from address %p", wsp->walk_addr); return (WALK_ERR); } if (ptr == (void *)-1) { status = WALK_DONE; } else if (ptr == NULL) { status = WALK_NEXT; } else { status = wsp->walk_callback((uintptr_t)ptr, NULL, wsp->walk_cbdata); } wsp->walk_addr += sizeof (void *); return (status); } /*ARGSUSED*/ static void byid_walk_fini( mdb_walk_state_t *wsp) { } /*ARGSUSED*/ static int action( uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int status; int rc = DCMD_OK; afdata_t *afp; afp = mdb_zalloc(sizeof (afdata_t), UM_SLEEP); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, AF_VERBOSE, &afp->af_flags, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) afp->af_banner = B_TRUE; if (flags & DCMD_ADDRSPEC) { status = action_format(addr, NULL, afp); rc = (status == WALK_NEXT) ? DCMD_OK : DCMD_ERR; goto cleanup; } if (mdb_pwalk("ipp_byid", action_format, afp, ipp_action_byid) == -1) { mdb_warn("failed to execute ipp_byid walk"); rc = DCMD_ERR; } cleanup: mdb_free(afp, sizeof (afdata_t)); return (rc); } /*ARGSUSED*/ static int action_format( uintptr_t addr, const void *data, void *arg) { afdata_t *afp = (afdata_t *)arg; ipp_action_t *ap; int rc; ap = mdb_alloc(sizeof (ipp_action_t), UM_SLEEP); if (mdb_vread(ap, sizeof (ipp_action_t), addr) == -1) { mdb_warn("failed to read ipp_action_t at %p", addr); rc = WALK_ERR; goto done; } if (afp->af_flags & AF_VERBOSE) rc = action_dump(addr, ap, afp->af_banner); else rc = action_summary(addr, ap, afp->af_banner); afp->af_banner = B_FALSE; done: mdb_free(ap, sizeof (ipp_action_t)); return (rc); } /*ARGSUSED*/ static int action_dump( uintptr_t addr, ipp_action_t *ap, boolean_t banner) { mdb_printf("%?p: %20s = %d\n", addr, "id", ap->ippa_id); if (!ap->ippa_nameless) { mdb_printf("%?s %20s = %s\n", "", "name", ap->ippa_name); } mdb_printf("%?s %20s = 0x%p\n", "", "mod", ap->ippa_mod); mdb_printf("%?s %20s = 0x%p\n", "", "ref", ap->ippa_ref); mdb_printf("%?s %20s = 0x%p\n", "", "refby", ap->ippa_refby); mdb_printf("%?s %20s = 0x%p\n", "", "ptr", ap->ippa_ptr); mdb_printf("%?s %20s = ", "", "state"); switch (ap->ippa_state) { case IPP_ASTATE_PROTO: mdb_printf("%s\n", "PROTO"); break; case IPP_ASTATE_CONFIG_PENDING: mdb_printf("%s\n", "CONFIG_PENDING"); break; case IPP_ASTATE_AVAILABLE: mdb_printf("%s\n", "AVAILABLE"); break; default: mdb_printf("%s\n", ""); break; } mdb_printf("%?s %20s = %d\n", "", "packets", ap->ippa_packets); mdb_printf("%?s %20s = %d\n", "", "hold_count", ap->ippa_hold_count); mdb_printf("%?s %20s = %s\n", "", "destruct_pending", (ap->ippa_destruct_pending) ? "TRUE" : "FALSE"); mdb_printf("%?s %20s = 0x%p\n", "", "lock", addr + ((uintptr_t)ap->ippa_lock - (uintptr_t)ap)); mdb_printf("%?s %20s = 0x%p\n", "", "config_lock", addr + ((uintptr_t)ap->ippa_config_lock - (uintptr_t)ap)); mdb_printf("\n"); return (WALK_NEXT); } static int action_summary( uintptr_t addr, ipp_action_t *ap, boolean_t banner) { ipp_mod_t *imp; uintptr_t ptr; if (banner) mdb_printf("%?s %%20s %5s %20s%\n", "", "NAME", "ID", "MODNAME"); imp = mdb_alloc(sizeof (ipp_mod_t), UM_SLEEP); ptr = (uintptr_t)ap->ippa_mod; if (mdb_vread(imp, sizeof (ipp_mod_t), ptr) == -1) { mdb_warn("failed to read ipp_mod_t at %p", ptr); mdb_free(imp, sizeof (ipp_mod_t)); return (WALK_ERR); } mdb_printf("%?p:%20s %5d %20s\n", addr, ap->ippa_name, ap->ippa_id, imp->ippm_name); mdb_free(imp, sizeof (ipp_mod_t)); return (WALK_NEXT); } /*ARGSUSED*/ static int cfglock( uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { cfglock_t *clp; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_ERR); clp = mdb_alloc(sizeof (cfglock_t), UM_SLEEP); if (mdb_vread(clp, sizeof (cfglock_t), addr) == -1) { mdb_warn("failed to read cfglock_t at %p", addr); mdb_free(clp, sizeof (cfglock_t)); return (WALK_ERR); } mdb_printf("%?p: %20s = %p\n", addr, "owner", clp->cl_owner); mdb_printf("%?s %20s = %s\n", "", "reader", clp->cl_reader ? "TRUE" : "FALSE"); mdb_printf("%?s %20s = %d\n", "", "writers", clp->cl_writers); mdb_printf("%?s %20s = 0x%p\n", "", "mutex", addr + ((uintptr_t)clp->cl_mutex - (uintptr_t)clp)); mdb_printf("%?s %20s = 0x%p\n", "", "cv", addr + ((uintptr_t)clp->cl_cv - (uintptr_t)clp)); mdb_printf("\n"); mdb_free(clp, sizeof (cfglock_t)); return (DCMD_OK); } /*ARGSUSED*/ static int mod( uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int status; int rc = DCMD_OK; mfdata_t *mfp; mfp = mdb_zalloc(sizeof (mfdata_t), UM_SLEEP); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, MF_VERBOSE, &mfp->mf_flags, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) mfp->mf_banner = B_TRUE; if (flags & DCMD_ADDRSPEC) { status = mod_format(addr, NULL, mfp); rc = (status == WALK_NEXT) ? DCMD_OK : DCMD_ERR; goto cleanup; } if (mdb_pwalk("ipp_byid", mod_format, mfp, ipp_mod_byid) == -1) { mdb_warn("failed to execute ipp_byid walk"); rc = DCMD_ERR; } cleanup: mdb_free(mfp, sizeof (mfdata_t)); return (rc); } /*ARGSUSED*/ static int mod_format( uintptr_t addr, const void *data, void *arg) { mfdata_t *mfp = (mfdata_t *)arg; ipp_mod_t *imp; int rc; imp = mdb_alloc(sizeof (ipp_mod_t), UM_SLEEP); if (mdb_vread(imp, sizeof (ipp_mod_t), addr) == -1) { mdb_warn("failed to read ipp_mod_t at %p", addr); rc = WALK_ERR; goto done; } if (mfp->mf_flags & MF_VERBOSE) rc = mod_dump(addr, imp, mfp->mf_banner); else rc = mod_summary(addr, imp, mfp->mf_banner); mfp->mf_banner = B_FALSE; done: mdb_free(imp, sizeof (ipp_mod_t)); return (rc); } /*ARGSUSED*/ static int mod_dump( uintptr_t addr, ipp_mod_t *imp, boolean_t banner) { mdb_printf("%?p: %20s = %d\n", addr, "id", imp->ippm_id); mdb_printf("%?s %20s = %s\n", "", "name", imp->ippm_name); mdb_printf("%?s %20s = 0x%p\n", "", "ops", imp->ippm_ops); mdb_printf("%?s %20s = 0x%p\n", "", "action", imp->ippm_action); mdb_printf("%?s %20s = ", "", "state"); switch (imp->ippm_state) { case IPP_MODSTATE_PROTO: mdb_printf("%s\n", "PROTO"); break; case IPP_MODSTATE_AVAILABLE: mdb_printf("%s\n", "AVAILABLE"); break; default: mdb_printf("%s\n", ""); break; } mdb_printf("%?s %20s = %d\n", "", "hold_count", imp->ippm_hold_count); mdb_printf("%?s %20s = %s\n", "", "destruct_pending", (imp->ippm_destruct_pending) ? "TRUE" : "FALSE"); mdb_printf("%?s %20s = 0x%p\n", "", "lock", addr + ((uintptr_t)imp->ippm_lock - (uintptr_t)imp)); mdb_printf("\n"); return (WALK_NEXT); } static int mod_summary( uintptr_t addr, ipp_mod_t *imp, boolean_t banner) { if (banner) mdb_printf("%?s %%20s %5s%\n", "", "NAME", "ID"); mdb_printf("%?p:%20s %5d\n", addr, imp->ippm_name, imp->ippm_id); return (WALK_NEXT); } /*ARGSUSED*/ static int ippops( uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ipp_ops_t *ippo; GElf_Sym sym; char buf[MDB_SYM_NAMLEN]; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_ERR); ippo = mdb_alloc(sizeof (ipp_ops_t), UM_SLEEP); if (mdb_vread(ippo, sizeof (ipp_ops_t), addr) == -1) { mdb_warn("failed to read ipp_ops_t at %p", addr); mdb_free(ippo, sizeof (ipp_ops_t)); return (DCMD_ERR); } mdb_printf("%?p: %20s = %d\n", addr, "rev", ippo->ippo_rev); if (mdb_lookup_by_addr((uintptr_t)ippo->ippo_action_create, MDB_SYM_EXACT, buf, MDB_SYM_NAMLEN, &sym) == 0) mdb_printf("%?s %20s = %s\n", "", "action_create", buf); else mdb_printf("%?s %20s = 0x%p\n", "", "action_create", ippo->ippo_action_create); if (mdb_lookup_by_addr((uintptr_t)ippo->ippo_action_modify, MDB_SYM_EXACT, buf, MDB_SYM_NAMLEN, &sym) == 0) mdb_printf("%?s %20s = %s\n", "", "action_modify", buf); else mdb_printf("%?s %20s = 0x%p\n", "", "action_modify", ippo->ippo_action_modify); if (mdb_lookup_by_addr((uintptr_t)ippo->ippo_action_destroy, MDB_SYM_EXACT, buf, MDB_SYM_NAMLEN, &sym) == 0) mdb_printf("%?s %20s = %s\n", "", "action_destroy", buf); else mdb_printf("%?s %20s = 0x%p\n", "", "action_destroy", ippo->ippo_action_destroy); if (mdb_lookup_by_addr((uintptr_t)ippo->ippo_action_info, MDB_SYM_EXACT, buf, MDB_SYM_NAMLEN, &sym) == 0) mdb_printf("%?s %20s = %s\n", "", "action_info", buf); else mdb_printf("%?s %20s = 0x%p\n", "", "action_info", ippo->ippo_action_info); if (mdb_lookup_by_addr((uintptr_t)ippo->ippo_action_invoke, MDB_SYM_EXACT, buf, MDB_SYM_NAMLEN, &sym) == 0) mdb_printf("%?s %20s = %s\n", "", "action_invoke", buf); else mdb_printf("%?s %20s = 0x%p\n", "", "action_invoke", ippo->ippo_action_invoke); mdb_printf("\n"); mdb_free(ippo, sizeof (ipp_ops_t)); return (DCMD_OK); } static int ref_walk_init( mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) return (WALK_DONE); return (WALK_NEXT); } static int ref_walk_step( mdb_walk_state_t *wsp) { ipp_ref_t *rp; int status; if (wsp->walk_addr == 0) return (WALK_DONE); rp = mdb_alloc(sizeof (ipp_ref_t), UM_SLEEP); if (mdb_vread(rp, sizeof (ipp_ref_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ipp_ref_t at %p", wsp->walk_addr); mdb_free(rp, sizeof (ipp_ref_t)); return (WALK_ERR); } status = wsp->walk_callback((uintptr_t)rp->ippr_ptr, NULL, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(rp->ippr_nextp); mdb_free(rp, sizeof (ipp_ref_t)); return (status); } /*ARGSUSED*/ static void ref_walk_fini( mdb_walk_state_t *wsp) { } /*ARGSUSED*/ static int packet( uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ipp_packet_t *pp; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_ERR); pp = mdb_alloc(sizeof (ipp_packet_t), UM_SLEEP); if (mdb_vread(pp, sizeof (ipp_packet_t), addr) == -1) { mdb_warn("failed to read ipp_packet_t at %p", addr); mdb_free(pp, sizeof (ipp_packet_t)); return (DCMD_ERR); } mdb_printf("%?p: %20s = 0x%p\n", addr, "data", pp->ippp_data); mdb_printf("%?s %20s = 0x%p\n", "", "private", pp->ippp_private); dump_classes((uintptr_t)pp->ippp_class_array, pp->ippp_class_windex); dump_log((uintptr_t)pp->ippp_log, pp->ippp_log_windex); mdb_free(pp, sizeof (ipp_packet_t)); return (DCMD_OK); } static void dump_classes( uintptr_t ptr, uint_t nelt) { ipp_class_t *array; ipp_class_t *cp = NULL; uint_t i; boolean_t first_time = B_TRUE; char buf[MAXNAMELEN]; array = mdb_alloc(sizeof (ipp_class_t) * nelt, UM_SLEEP); if (mdb_vread(array, sizeof (ipp_class_t) * nelt, ptr) == -1) { mdb_warn("failed to read ipp_class_t array at %p", ptr); return; } for (i = 0; i < nelt; i++) { if (first_time) { mdb_printf("%20s %?s %%15s %15s%\n", "", "classes", "NAME", "ACTION"); first_time = B_FALSE; } cp = &(array[i]); aid2aname(cp->ippc_aid, buf); mdb_printf("%20s %?p: %15s %15s%\n", "", ptr + (i * sizeof (ipp_class_t)), cp->ippc_name, buf); } mdb_free(cp, sizeof (ipp_class_t) * nelt); } static void dump_log( uintptr_t ptr, uint_t nelt) { ipp_log_t *array; ipp_log_t *lp = NULL; uint_t i; boolean_t first_time = B_TRUE; char buf[MAXNAMELEN]; array = mdb_alloc(sizeof (ipp_log_t) * nelt, UM_SLEEP); if (mdb_vread(array, sizeof (ipp_log_t) * nelt, ptr) == -1) { mdb_warn("failed to read ipp_log_t array at %p", ptr); return; } for (i = 0; i < nelt; i++) { if (first_time) { mdb_printf("%20s %?s %%15s %15s%\n", "", "log", "CLASS NAME", "ACTION"); first_time = B_FALSE; } lp = &(array[i]); aid2aname(lp->ippl_aid, buf); mdb_printf("%20s %?p: %15s %15s\n", "", ptr + (i * sizeof (ipp_class_t)), lp->ippl_name, buf); } mdb_free(lp, sizeof (ipp_log_t) * nelt); } static void aid2aname( ipp_action_id_t aid, char *buf) { uintptr_t addr; uintptr_t ptr; ipp_action_t *ap; switch (aid) { case IPP_ACTION_INVAL: strcpy(buf, "invalid"); break; case IPP_ACTION_CONT: strcpy(buf, "continue"); break; case IPP_ACTION_DEFER: strcpy(buf, "defer"); break; case IPP_ACTION_DROP: strcpy(buf, "drop"); break; default: if (mdb_vread(&addr, sizeof (uintptr_t), ipp_action_byid) == -1) { mdb_warn("failed to read from address %p", ipp_action_byid); strcpy(buf, "???"); break; } addr += ((int32_t)aid * sizeof (void *)); if (mdb_vread(&ptr, sizeof (uintptr_t), addr) == -1) { mdb_warn("failed to read from address %p", addr); strcpy(buf, "???"); break; } ap = mdb_alloc(sizeof (ipp_action_t), UM_SLEEP); if (mdb_vread(ap, sizeof (ipp_action_t), ptr) == -1) { mdb_warn("failed to read ipp_action_t at %p", ptr); mdb_free(ap, sizeof (ipp_action_t)); strcpy(buf, "???"); break; } if (ap->ippa_id != aid) { mdb_warn("corrupt action at %p", ptr); mdb_free(ap, sizeof (ipp_action_t)); strcpy(buf, "???"); break; } strcpy(buf, ap->ippa_name); } } static const mdb_dcmd_t dcmds[] = { { "ipp_action", "?[-v]", "display ipp_action structure", action }, { "ipp_mod", "?[-v]", "display ipp_mod structure", mod }, { "cfglock", ":", "display cfglock structure", cfglock }, { "ippops", ":", "display ipp_ops structure", ippops }, { "ipp_packet", ":", "display ipp_packet structure", packet }, { NULL } }; static const mdb_walker_t walkers[] = { { "ipp_byid", "walk byid array", byid_walk_init, byid_walk_step, byid_walk_fini }, { "ipp_ref", "walk reference list", ref_walk_init, ref_walk_step, ref_walk_fini }, { NULL } }; static const mdb_modinfo_t ipp_modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_name("ipp_action_byid", &sym) == -1) { mdb_warn("failed to lookup 'ipp_action_byid'"); return (NULL); } ipp_action_byid = (uintptr_t)sym.st_value; if (mdb_lookup_by_name("ipp_mod_byid", &sym) == -1) { mdb_warn("failed to lookup 'ipp_mod_byid'"); return (NULL); } ipp_mod_byid = (uintptr_t)sym.st_value; return (&ipp_modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "klm/nlm_impl.h" #define NLM_MAXNAMELEN 256 #define NLM_MAXADDRSTR 64 /* * **************************************************************** * Helper functions */ /* * Helper to get printable IP address into a buffer. * Used by nlm_host_dcmd */ static int nlm_netbuf_str(char *buf, size_t bufsz, const struct netbuf *nb) { struct sockaddr_storage sa; struct sockaddr_in *s_in; struct sockaddr_in6 *s_in6; uint_t salen = nb->len; in_port_t port; if (salen < sizeof (sa_family_t)) return (-1); if (salen > sizeof (sa)) salen = sizeof (sa); if (mdb_vread(&sa, salen, (uintptr_t)nb->buf) < 0) return (-1); switch (sa.ss_family) { case AF_INET: s_in = (struct sockaddr_in *)(void *)&sa; mdb_nhconvert(&port, &s_in->sin_port, sizeof (port)); mdb_snprintf(buf, bufsz, "%I/%d", s_in->sin_addr.s_addr, port); break; case AF_INET6: s_in6 = (struct sockaddr_in6 *)(void *)&sa; mdb_nhconvert(&port, &s_in6->sin6_port, sizeof (port)); mdb_snprintf(buf, bufsz, "%N/%d", &(s_in6->sin6_addr), port); break; default: mdb_printf("AF_%d", sa.ss_family); break; } return (0); } /* * Get the name for an enum value */ static void get_enum(char *obuf, size_t size, const char *type_str, int val, const char *prefix) { mdb_ctf_id_t type_id; const char *cp; if (mdb_ctf_lookup_by_name(type_str, &type_id) != 0) goto errout; if (mdb_ctf_type_resolve(type_id, &type_id) != 0) goto errout; if ((cp = mdb_ctf_enum_name(type_id, val)) == NULL) goto errout; if (prefix != NULL) { size_t len = strlen(prefix); if (strncmp(cp, prefix, len) == 0) cp += len; } (void) strlcpy(obuf, cp, size); return; errout: mdb_snprintf(obuf, size, "? (%d)", val); } static const mdb_bitmask_t host_flag_bits[] = { { "MONITORED", NLM_NH_MONITORED, NLM_NH_MONITORED }, { "RECLAIM", NLM_NH_RECLAIM, NLM_NH_RECLAIM }, { "INIDLE", NLM_NH_INIDLE, NLM_NH_INIDLE }, { "SUSPEND", NLM_NH_SUSPEND, NLM_NH_SUSPEND }, { NULL, 0, 0 } }; /* * **************************************************************** * NLM zones (top level) */ /* * nlm_zone walker implementation */ int nlm_zone_walk_init(mdb_walk_state_t *wsp) { /* * Technically, this is "cheating" with the knowledge that * the TAILQ_HEAD link is at the beginning of this object. */ if (wsp->walk_addr == 0 && mdb_readsym(&wsp->walk_addr, sizeof (wsp->walk_addr), "nlm_zones_list") == -1) { mdb_warn("failed to read 'nlm_zones_list'"); return (WALK_ERR); } return (WALK_NEXT); } int nlm_zone_walk_step(mdb_walk_state_t *wsp) { struct nlm_globals g; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&g, sizeof (g), addr) < 0) { mdb_warn("failed to read nlm_globals at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)TAILQ_NEXT(&g, nlm_link); return (wsp->walk_callback(addr, &g, wsp->walk_cbdata)); } /* * nlm_zone dcmd implementation */ static void nlm_zone_print(uintptr_t, const struct nlm_globals *, uint_t); void nlm_zone_help(void) { mdb_printf("-v verbose information\n"); } int nlm_zone_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct nlm_globals g; char enum_val[32]; uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn("requires addr of nlm_zone"); return (DCMD_ERR); } if (mdb_vread(&g, sizeof (g), addr) == -1) { mdb_warn("failed to read nlm_globals at %p", addr); return (DCMD_ERR); } if (opt_v == FALSE) { nlm_zone_print(addr, &g, flags); return (DCMD_OK); } /* * Print verbose format */ mdb_printf("%%NLM zone globals (%p):%%\n", addr); mdb_printf(" Lockd PID: %u\n", g.lockd_pid); get_enum(enum_val, sizeof (enum_val), "nlm_run_status_t", g.run_status, "NLM_S_"); mdb_printf("Run status: %d (%s)\n", g.run_status, enum_val); mdb_printf(" NSM state: %d\n", g.nsm_state); return (DCMD_OK); } /* * Shared by nlm_zone_dcmd and nlm_list_zone_cb * Print a zone (nlm_globals) summary line. */ static void nlm_zone_print(uintptr_t addr, const struct nlm_globals *g, uint_t flags) { if (DCMD_HDRSPEC(flags)) { mdb_printf( "%%%?-s %-16s %%\n", "nlm_globals", "pid"); } mdb_printf("%-?p %6d\n", addr, (int)g->lockd_pid); } /* * **************************************************************** * NLM hosts (under zones) */ /* * nlm_host walker implementation */ int nlm_host_walk_init(mdb_walk_state_t *wsp) { static int avl_off = -1; if (wsp->walk_addr == 0) { mdb_printf("requires address of struct nlm_globals\n"); return (WALK_ERR); } /* * Need the address of the nlm_hosts_tree AVL head * within the nlm_globals, for the AVL walker. */ if (avl_off < 0) { avl_off = mdb_ctf_offsetof_by_name( "struct nlm_globals", "nlm_hosts_tree"); } if (avl_off < 0) { mdb_warn("cannot lookup: nlm_globals .nlm_hosts_tree"); return (WALK_ERR); } wsp->walk_addr += avl_off; if (mdb_layered_walk("avl", wsp) == -1) { mdb_warn("failed to walk nlm_globals .nlm_hosts_tree"); return (WALK_ERR); } return (WALK_NEXT); } int nlm_host_walk_step(mdb_walk_state_t *wsp) { struct nlm_host nh; uintptr_t addr = wsp->walk_addr; if (mdb_vread(&nh, sizeof (nh), addr) < 0) { mdb_warn("failed to read nlm_host at %p", addr); return (WALK_ERR); } /* layered walk avl */ return (wsp->walk_callback(wsp->walk_addr, &nh, wsp->walk_cbdata)); } /* * nlm_host dcmd implementation */ static void nlm_host_print(uintptr_t, const struct nlm_host *, char *, char *, uint_t); void nlm_host_help(void) { mdb_printf("-v verbose information\n"); } int nlm_host_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct nlm_host nh; char hname[NLM_MAXNAMELEN]; char haddr[NLM_MAXADDRSTR]; uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn("requires addr of nlm_host"); return (DCMD_ERR); } /* Get the nlm_host */ if (mdb_vread(&nh, sizeof (nh), addr) == -1) { mdb_warn("failed to read nlm_host at %p", addr); return (DCMD_ERR); } /* Get its name and address */ if (mdb_readstr(hname, sizeof (hname), (uintptr_t)nh.nh_name) < 0) strlcpy(hname, "?", sizeof (hname)); if (nlm_netbuf_str(haddr, sizeof (haddr), &nh.nh_addr) < 0) strlcpy(haddr, "?", sizeof (haddr)); if (opt_v == FALSE) { nlm_host_print(addr, &nh, hname, haddr, flags); return (DCMD_OK); } /* * Print verbose format */ mdb_printf("%%NLM host (%p):%%\n", addr); mdb_printf("Refcnt: %u\n", nh.nh_refs); mdb_printf(" Sysid: %d\n", (int)nh.nh_sysid); mdb_printf(" Name: %s\n", hname); mdb_printf(" Addr: %s\n", haddr); mdb_printf(" State: %d\n", nh.nh_state); mdb_printf(" Flags: 0x%x <%b>\n", nh.nh_flags, nh.nh_flags, host_flag_bits); mdb_printf("Vholds: %?p\n", nh.nh_vholds_list.tqh_first); return (DCMD_OK); } /* * Shared by nlm_host_dcmd and nlm_list_host_cb * Print an nlm_host summary line. */ static void nlm_host_print(uintptr_t addr, const struct nlm_host *nh, char *hname, char *haddr, uint_t flags) { int hname_width = 20; if (DCMD_HDRSPEC(flags)) { mdb_printf("%%%-?s %-*s%10s %6s ", "nlm_host", hname_width, "name", "refs", "sysid"); mdb_printf("%s%%\n", "net_addr"); } mdb_printf("%?p %-*s%10i %6hi %s\n", addr, hname_width, hname, nh->nh_refs, nh->nh_sysid, haddr); } /* * **************************************************************** * NLM vholds (under hosts) */ /* * nlm_vhold walker implementation */ int nlm_vhold_walk_init(mdb_walk_state_t *wsp) { struct nlm_vhold_list head; uintptr_t addr; static int head_off = -1; if (wsp->walk_addr == 0) { mdb_printf("requires address of struct nlm_host\n"); return (WALK_ERR); } /* Get offset of the list head and read it. */ if (head_off < 0) { head_off = mdb_ctf_offsetof_by_name( "struct nlm_host", "nh_vholds_list"); } if (head_off < 0) { mdb_warn("cannot lookup: nlm_host .nh_vholds_list"); return (WALK_ERR); } addr = wsp->walk_addr + head_off; if (mdb_vread(&head, sizeof (head), addr) < 0) { mdb_warn("cannot read nlm_host at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)head.tqh_first; return (WALK_NEXT); } int nlm_vhold_walk_step(mdb_walk_state_t *wsp) { struct nlm_vhold nv; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&nv, sizeof (nv), addr) < 0) { mdb_warn("failed to read nlm_vhold at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)nv.nv_link.tqe_next; return (wsp->walk_callback(addr, &nv, wsp->walk_cbdata)); } /* * nlm_vhold dcmd implementation */ static void nlm_vhold_print(uintptr_t, const struct nlm_vhold *, uint_t); void nlm_vhold_help(void) { mdb_printf("-v verbose information\n"); } int nlm_vhold_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct nlm_vhold nv; char path_buf[MAXPATHLEN]; uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn("requires addr of nlm_vhold"); return (DCMD_ERR); } if (mdb_vread(&nv, sizeof (nv), addr) == -1) { mdb_warn("failed to read nlm_vhold at %p", addr); return (DCMD_ERR); } if (opt_v == FALSE) { nlm_vhold_print(addr, &nv, flags); return (DCMD_OK); } /* * Print verbose format */ if (nv.nv_vp == NULL || mdb_vnode2path((uintptr_t)nv.nv_vp, path_buf, sizeof (path_buf)) != 0) strlcpy(path_buf, "?", sizeof (path_buf)); mdb_printf("%%NLM vhold (%p):%%\n", addr); mdb_printf("Refcnt: %u\n", nv.nv_refcnt); mdb_printf(" Vnode: %?p (%s)\n", nv.nv_vp, path_buf); mdb_printf(" Slreq: %?p\n", nv.nv_slreqs.tqh_first); return (DCMD_OK); } /* * Shared by nlm_vhold_dcmd and nlm_list_vnode_cb * Print an nlm_vhold summary line. */ static void nlm_vhold_print(uintptr_t addr, const struct nlm_vhold *nv, uint_t flags) { if (DCMD_HDRSPEC(flags)) { mdb_printf("%%%-?s %10s %-?s %-?s%%\n", "nlm_vhold", "refcnt", "vnode", "slreq"); } mdb_printf("%?p %10i %?p %?-p\n", addr, nv->nv_refcnt, nv->nv_vp, nv->nv_slreqs.tqh_first); } /* * **************************************************************** * NLM slreqs (under vhold) */ /* * nlm_slreq walker implementation */ int nlm_slreq_walk_init(mdb_walk_state_t *wsp) { struct nlm_slreq_list head; uintptr_t addr; static int head_off = -1; if (wsp->walk_addr == 0) { mdb_printf("requires address of struct nlm_vhold\n"); return (WALK_ERR); } /* Get offset of the list head and read it. */ if (head_off < 0) { head_off = mdb_ctf_offsetof_by_name( "struct nlm_vhold", "nv_slreqs"); } if (head_off < 0) { mdb_warn("cannot lookup: nlm_vhold .nv_slreqs"); return (WALK_ERR); } addr = wsp->walk_addr + head_off; if (mdb_vread(&head, sizeof (head), addr) < 0) { mdb_warn("cannot read nlm_vhold at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)head.tqh_first; return (WALK_NEXT); } int nlm_slreq_walk_step(mdb_walk_state_t *wsp) { struct nlm_slreq nsr; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&nsr, sizeof (nsr), addr) < 0) { mdb_warn("failed to read nlm_slreq at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)nsr.nsr_link.tqe_next; return (wsp->walk_callback(addr, &nsr, wsp->walk_cbdata)); } /* * nlm_slreq dcmd implementation */ static void nlm_slreq_print(uintptr_t, const struct nlm_slreq *, uint_t); void nlm_slreq_help(void) { mdb_printf("-v verbose information\n"); } int nlm_slreq_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct nlm_slreq nsr; uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn("requires addr of nlm_slreq"); return (DCMD_ERR); } if (mdb_vread(&nsr, sizeof (nsr), addr) == -1) { mdb_warn("failed to read nlm_slreq at %p", addr); return (DCMD_ERR); } if (opt_v == FALSE) { nlm_slreq_print(addr, &nsr, flags); return (DCMD_OK); } /* * Print verbose format */ mdb_printf("%%NLM slreq (%p):%%\n", addr); mdb_printf(" type: %d (%s)\n", nsr.nsr_fl.l_type, (nsr.nsr_fl.l_type == F_RDLCK) ? "RD" : (nsr.nsr_fl.l_type == F_WRLCK) ? "WR" : "??"); mdb_printf("sysid: %d\n", nsr.nsr_fl.l_sysid); mdb_printf(" pid: %d\n", nsr.nsr_fl.l_pid); mdb_printf("start: %lld\n", nsr.nsr_fl.l_start); mdb_printf(" len: %lld\n", nsr.nsr_fl.l_len); return (DCMD_OK); } /* * Shared by nlm_slreq_dcmd and nlm_list_slreq_cb * Print an nlm_slreq summary line. */ static void nlm_slreq_print(uintptr_t addr, const struct nlm_slreq *nsr, uint_t flags) { if (DCMD_HDRSPEC(flags)) { mdb_printf("%%%-?s %4s %5s %3s %6s %6s%%\n", "nlm_slreq", "type", "sysid", "pid", "start", "len"); } mdb_printf( "%?p %4d %5d %3d %6lld %6lld\n", addr, nsr->nsr_fl.l_type, nsr->nsr_fl.l_sysid, nsr->nsr_fl.l_pid, nsr->nsr_fl.l_start, nsr->nsr_fl.l_len); } /* * **************************************************************** */ /* * nlm_list dcmd implementation * * This is a fancy command command to walk the whole NLM * data hierarchy, skipping uninteresting elements. */ #define NLM_LIST_DEPTH_HOSTS 1 /* just hosts */ #define NLM_LIST_DEPTH_VHOLDS 2 /* host and vholds */ #define NLM_LIST_DEPTH_SLREQS 3 /* sleeping lock requests */ #define NLM_LIST_DEPTH_DEFAULT 3 /* default: show all */ struct nlm_list_arg { uint_t opt_v; uint_t opt_a; uint_t depth; int sysid; char *host; uint_t zone_flags; uint_t host_flags; uint_t vhold_flags; uint_t slreq_flags; char namebuf[NLM_MAXNAMELEN]; char addrbuf[NLM_MAXADDRSTR]; }; static int nlm_list_zone_cb(uintptr_t, const void *, void *); static int nlm_list_host_cb(uintptr_t, const void *, void *); static int nlm_list_vhold_cb(uintptr_t, const void *, void *); static int nlm_list_slreq_cb(uintptr_t, const void *, void *); void nlm_list_help(void) { mdb_printf("-v verbose information\n"); mdb_printf("-a include idle hosts\n"); mdb_printf("-d depth recursion depth (zones, hosts, ...)\n"); mdb_printf("-h host filter by host name\n"); mdb_printf("-s sysid filter by sysid (0tnnn for decimal)\n"); } int nlm_list_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct nlm_list_arg *arg; uintptr_t depth = NLM_LIST_DEPTH_DEFAULT; char *host = NULL; char *sysid = NULL; if ((flags & DCMD_ADDRSPEC) != 0) return (DCMD_USAGE); arg = mdb_zalloc(sizeof (*arg), UM_SLEEP | UM_GC); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &arg->opt_v, 'a', MDB_OPT_SETBITS, TRUE, &arg->opt_a, 'd', MDB_OPT_UINTPTR, &depth, 'h', MDB_OPT_STR, &host, 's', MDB_OPT_STR, &sysid, NULL) != argc) return (DCMD_USAGE); arg->depth = (uint_t)depth; arg->sysid = -1; if (host != NULL) arg->host = host; if (sysid != NULL) { arg->sysid = (int)mdb_strtoull(sysid); if (arg->sysid < 1) { mdb_warn("invalid sysid"); arg->sysid = -1; } } /* Specifying host or sysid id implies -a */ if (arg->host != NULL || arg->sysid >= 0) arg->opt_a = TRUE; arg->zone_flags = (DCMD_LOOP | DCMD_LOOPFIRST); if (mdb_pwalk("nlm_zone", nlm_list_zone_cb, arg, 0)) { mdb_warn("cannot walk nlm_zone list"); return (DCMD_ERR); } return (DCMD_OK); } /* Called for each zone's nlm_globals */ static int nlm_list_zone_cb(uintptr_t addr, const void *data, void *cb_data) { struct nlm_list_arg *arg = cb_data; const struct nlm_globals *g = data; /* Add zone filtering? */ /* * Summary line for a struct nlm_globals */ nlm_zone_print(addr, g, 0); arg->zone_flags &= ~DCMD_LOOPFIRST; if (arg->depth >= NLM_LIST_DEPTH_HOSTS) { (void) mdb_inc_indent(2); arg->host_flags = (DCMD_LOOP | DCMD_LOOPFIRST); if (mdb_pwalk("nlm_host", nlm_list_host_cb, arg, addr) != 0) { mdb_warn("failed to walk hosts for zone %p", addr); /* keep going */ } (void) mdb_dec_indent(2); } return (WALK_NEXT); } /* Called for each nlm_host */ static int nlm_list_host_cb(uintptr_t addr, const void *data, void *cb_data) { struct nlm_list_arg *arg = cb_data; const struct nlm_host *nh = data; /* Get the host name and net addr. */ if (mdb_readstr(arg->namebuf, NLM_MAXNAMELEN, (uintptr_t)nh->nh_name) < 0) (void) strlcpy(arg->namebuf, "?", sizeof (char)); if (nlm_netbuf_str(arg->addrbuf, NLM_MAXADDRSTR, &nh->nh_addr) < 0) (void) strlcpy(arg->addrbuf, "?", sizeof (char)); /* Filter out uninteresting hosts */ if (arg->opt_a == 0 && nh->nh_refs == 0) return (WALK_NEXT); if (arg->sysid != -1 && arg->sysid != (nh->nh_sysid & LM_SYSID_MAX)) return (WALK_NEXT); if (arg->host != NULL && strcmp(arg->host, arg->namebuf) != 0) return (WALK_NEXT); /* * Summary line for struct nlm_host */ nlm_host_print(addr, nh, arg->namebuf, arg->addrbuf, arg->host_flags); arg->host_flags &= ~DCMD_LOOPFIRST; if (arg->depth >= NLM_LIST_DEPTH_VHOLDS) { (void) mdb_inc_indent(2); arg->vhold_flags = (DCMD_LOOP | DCMD_LOOPFIRST); if (mdb_pwalk("nlm_vhold", nlm_list_vhold_cb, arg, addr)) { mdb_warn("failed to walk vholds for host %p", addr); /* keep going */ } (void) mdb_dec_indent(2); } /* * We printed some hosts, so tell nlm_list_zone_cb to * print its header line again. */ arg->zone_flags |= DCMD_LOOPFIRST; return (WALK_NEXT); } /* Called for each nlm_vhold */ static int nlm_list_vhold_cb(uintptr_t addr, const void *data, void *cb_data) { struct nlm_list_arg *arg = cb_data; const struct nlm_vhold *nv = data; /* Filter out uninteresting vholds */ if (arg->opt_a == 0 && nv->nv_refcnt == 0) return (WALK_NEXT); /* * Summary line for struct nlm_vhold */ nlm_vhold_print(addr, nv, arg->vhold_flags); arg->vhold_flags &= ~DCMD_LOOPFIRST; if (arg->depth >= NLM_LIST_DEPTH_SLREQS) { (void) mdb_inc_indent(2); arg->slreq_flags = (DCMD_LOOP | DCMD_LOOPFIRST); if (mdb_pwalk("nlm_slreq", nlm_list_slreq_cb, arg, addr)) { mdb_warn("failed to walk slreqs for vhold %p", addr); /* keep going */ } (void) mdb_dec_indent(2); } /* * We printed some vholds, so tell nlm_list_host_cb to * print its header line again. */ arg->host_flags |= DCMD_LOOPFIRST; return (WALK_NEXT); } /* Called for each nlm_slreq */ static int nlm_list_slreq_cb(uintptr_t addr, const void *data, void *cb_data) { struct nlm_list_arg *arg = cb_data; const struct nlm_slreq *nv = data; /* * Summary line for struct nlm_slreq */ nlm_slreq_print(addr, nv, arg->slreq_flags); arg->slreq_flags &= ~DCMD_LOOPFIRST; /* * We printed some slreqs, so tell nlm_list_vhold_cb to * print its header line again. */ arg->vhold_flags |= DCMD_LOOPFIRST; return (WALK_NEXT); } /* * **************************************************************** */ /* * nlm_lockson dcmd implementation * Walk the lock_graph, filtered by sysid */ struct nlm_locks_arg { /* dcmd options */ uint_t opt_v; int sysid; char *host; /* callback vars */ uint_t flags; int lg_sysid; char namebuf[NLM_MAXNAMELEN]; char addrbuf[NLM_MAXADDRSTR]; char pathbuf[PATH_MAX]; }; static int nlm_locks_zone_cb(uintptr_t, const void *, void *); static int nlm_locks_host_cb(uintptr_t, const void *, void *); static int nlm_lockson_cb(uintptr_t, const void *, void *c); void nlm_lockson_help(void) { mdb_printf("-v verbose information\n"); mdb_printf("-h host filter by host name\n"); mdb_printf("-s sysid filter by sysid (0tnnn for decimal)\n"); } int nlm_lockson_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct nlm_locks_arg *arg; char *host = NULL; char *sysid = NULL; if ((flags & DCMD_ADDRSPEC) != 0) return (DCMD_USAGE); arg = mdb_zalloc(sizeof (*arg), UM_SLEEP | UM_GC); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &arg->opt_v, 'h', MDB_OPT_STR, &host, 's', MDB_OPT_STR, &sysid, NULL) != argc) return (DCMD_USAGE); arg->sysid = -1; if (host != NULL) arg->host = host; if (sysid != NULL) { arg->sysid = (int)mdb_strtoull(sysid); if (arg->sysid < 1) { mdb_warn("invalid sysid"); arg->sysid = -1; } } if (mdb_pwalk("nlm_zone", nlm_locks_zone_cb, arg, 0)) { mdb_warn("cannot walk nlm_zone list"); return (DCMD_ERR); } return (DCMD_OK); } /* Called for each zone's nlm_globals */ static int nlm_locks_zone_cb(uintptr_t addr, const void *data, void *cb_data) { struct nlm_locks_arg *arg = cb_data; (void) data; /* * No filtering here. Don't even print zone addr. * Just run the host list walker. */ if (mdb_pwalk("nlm_host", nlm_locks_host_cb, arg, addr) != 0) { mdb_warn("failed to walk hosts for zone %p", addr); /* keep going */ } return (WALK_NEXT); } /* Called for each nlm_host */ static int nlm_locks_host_cb(uintptr_t addr, const void *data, void *cb_data) { struct nlm_locks_arg *arg = cb_data; const struct nlm_host *nh = data; /* Get the host name and net addr. */ if (mdb_readstr(arg->namebuf, NLM_MAXNAMELEN, (uintptr_t)nh->nh_name) < 0) (void) strlcpy(arg->namebuf, "?", sizeof (char)); if (nlm_netbuf_str(arg->addrbuf, NLM_MAXADDRSTR, &nh->nh_addr) < 0) (void) strlcpy(arg->addrbuf, "?", sizeof (char)); /* Filter out uninteresting hosts */ if (arg->sysid != -1 && arg->sysid != (nh->nh_sysid & LM_SYSID_MAX)) return (WALK_NEXT); if (arg->host != NULL && strcmp(arg->host, arg->namebuf) != 0) return (WALK_NEXT); /* * Summary line for struct nlm_host */ nlm_host_print(addr, nh, arg->namebuf, arg->addrbuf, 0); /* * We run the lock_graph walker for every sysid, and the callback * uses arg->lg_sysid to filter graph elements. Set that from * the host we're visiting now. */ arg->lg_sysid = (int)nh->nh_sysid; arg->flags = (DCMD_LOOP | DCMD_LOOPFIRST); if (mdb_pwalk("lock_graph", nlm_lockson_cb, arg, 0) < 0) { mdb_warn("failed to walk lock_graph"); return (WALK_ERR); } return (WALK_NEXT); } static int nlm_lockson_cb(uintptr_t addr, const void *data, void *cb_data) { struct nlm_locks_arg *arg = cb_data; const lock_descriptor_t *ld = data; proc_t p; int local, sysid; int host_width = 16; char *s; local = ld->l_flock.l_sysid & LM_SYSID_CLIENT; sysid = ld->l_flock.l_sysid & LM_SYSID_MAX; if (arg->lg_sysid != sysid) return (WALK_NEXT); if (DCMD_HDRSPEC(arg->flags)) { mdb_printf("%%%-?s %-*s %5s(x) %-?s %-6s %-*s %-*s type", "lock_addr", host_width, "host", "sysid", "vnode", "pid", MAXCOMLEN, "cmd", arg->opt_v ? 9 : 5, "state"); if (arg->opt_v) mdb_printf("%-11s srvstat %-10s", "(width)", "path"); mdb_printf("%%\n"); } arg->flags &= ~DCMD_LOOPFIRST; mdb_printf("%?p %-*s %5hi(%c) %?p %-6i %-*s ", addr, host_width, arg->namebuf, sysid, local ? 'L' : 'R', ld->l_vnode, ld->l_flock.l_pid, MAXCOMLEN, ld->l_flock.l_pid == 0 ? "" : !local ? "" : mdb_pid2proc(ld->l_flock.l_pid, &p) == 0 ? "" : p.p_user.u_comm); if (arg->opt_v) { switch (ld->l_status) { case FLK_INITIAL_STATE: s = "init"; break; case FLK_START_STATE: s = "execute"; break; case FLK_ACTIVE_STATE: s = "active"; break; case FLK_SLEEPING_STATE: s = "blocked"; break; case FLK_GRANTED_STATE: s = "granted"; break; case FLK_INTERRUPTED_STATE: s = "interrupt"; break; case FLK_CANCELLED_STATE: s = "cancel"; break; case FLK_DEAD_STATE: s = "done"; break; default: s = "??"; break; } mdb_printf("%-9s", s); } else { mdb_printf("%-5i", ld->l_status); } mdb_printf(" %-2s", ld->l_type == F_RDLCK ? "RD" : ld->l_type == F_WRLCK ? "WR" : "??"); if (!arg->opt_v) { mdb_printf("\n"); return (WALK_NEXT); } switch (GET_NLM_STATE(ld)) { case FLK_NLM_UP: s = "up"; break; case FLK_NLM_SHUTTING_DOWN: s = "halting"; break; case FLK_NLM_DOWN: s = "down"; break; case FLK_NLM_UNKNOWN: s = "unknown"; break; default: s = "??"; break; } mdb_printf("(%5i:%-5i) %-7s ", ld->l_start, ld->l_len, s); if (mdb_vnode2path((uintptr_t)ld->l_vnode, arg->pathbuf, PATH_MAX) == -1) strlcpy(arg->pathbuf, "??", PATH_MAX); mdb_printf("%s\n", arg->pathbuf); return (WALK_NEXT); } static const mdb_walker_t walkers[] = { { "nlm_zone", "nlm_zone walker", nlm_zone_walk_init, nlm_zone_walk_step }, { "nlm_host", "nlm_host walker", nlm_host_walk_init, nlm_host_walk_step }, { "nlm_vhold", "nlm_vhold walker", nlm_vhold_walk_init, nlm_vhold_walk_step }, { "nlm_slreq", "nlm_slreq walker", nlm_slreq_walk_init, nlm_slreq_walk_step }, {NULL, NULL, NULL, NULL} }; static const mdb_dcmd_t dcmds[] = { { "nlm_zone", "?[-v]", "dump per-zone nlm_globals", nlm_zone_dcmd, nlm_zone_help }, { "nlm_host", "?[-v]", "dump nlm_host structures (hosts/sysids)", nlm_host_dcmd, nlm_host_help }, { "nlm_vhold", "?[-v]", "dump nlm_vhold structures (vnode holds)", nlm_vhold_dcmd, nlm_vhold_help }, { "nlm_slreq", "?[-v]", "dump nlm_slreq structures (sleeping lock requests)", nlm_slreq_dcmd, nlm_slreq_help }, { "nlm_list", "[-v][-a][-d depth][-h host][-s 0tSysID]", "list all zones, optionally filter hosts ", nlm_list_dcmd, nlm_list_help }, { "nlm_lockson", "[-v] [-h host] [-s 0tSysID]", "dump NLM locks from host (or sysid)", nlm_lockson_dcmd, nlm_lockson_help }, {NULL, NULL, NULL, NULL} }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include static uintptr_t module_head; /* Head of kernel modctl list */ struct krtld_ctf_module { Ehdr hdr; char *shdrs; size_t text_size; size_t data_size; char *text; char *ctfdata; size_t ctfsize; }; struct modctl_walk_data { uintptr_t mwd_head; struct modctl mwd_modctl; }; static int modctl_walk_init(mdb_walk_state_t *wsp) { struct modctl_walk_data *mwd = mdb_alloc( sizeof (struct modctl_walk_data), UM_SLEEP); mwd->mwd_head = (wsp->walk_addr == 0 ? module_head : wsp->walk_addr); wsp->walk_data = mwd; wsp->walk_addr = 0; return (WALK_NEXT); } static int modctl_walk_step(mdb_walk_state_t *wsp) { struct modctl_walk_data *mwd = wsp->walk_data; int status; if (wsp->walk_addr == mwd->mwd_head) return (WALK_DONE); if (wsp->walk_addr == 0) wsp->walk_addr = mwd->mwd_head; if (mdb_vread(&mwd->mwd_modctl, sizeof (struct modctl), wsp->walk_addr) == -1) { mdb_warn("failed to read modctl at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &mwd->mwd_modctl, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)mwd->mwd_modctl.mod_next; return (status); } static void modctl_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct modctl_walk_data)); } /*ARGSUSED*/ static int modctl_format(uintptr_t addr, const void *data, void *private) { const struct modctl *mcp = (const struct modctl *)data; char name[MAXPATHLEN], bits[6], *bp = &bits[0]; if (mdb_readstr(name, sizeof (name), (uintptr_t)mcp->mod_filename) == -1) (void) strcpy(name, "???"); if (mcp->mod_busy) *bp++ = 'b'; if (mcp->mod_want) *bp++ = 'w'; if (mcp->mod_prim) *bp++ = 'p'; if (mcp->mod_loaded) *bp++ = 'l'; if (mcp->mod_installed) *bp++ = 'i'; *bp = '\0'; mdb_printf("%?p %?p %6s 0x%02x %3d %s\n", (uintptr_t)addr, (uintptr_t)mcp->mod_mp, bits, mcp->mod_loadflags, mcp->mod_ref, name); return (WALK_NEXT); } /*ARGSUSED*/ static int modctls(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s %?s %6s %4s %3s %s%\n", "MODCTL", "MODULE", "BITS", "FLAGS", "REF", "FILE"); } if (flags & DCMD_ADDRSPEC) { struct modctl mc; (void) mdb_vread(&mc, sizeof (mc), addr); return (modctl_format(addr, &mc, NULL)); } if (mdb_walk("modctl", modctl_format, NULL) == -1) return (DCMD_ERR); return (DCMD_OK); } static void dump_ehdr(const Ehdr *ehdr) { mdb_printf("\nELF Header\n"); mdb_printf(" ei_magic: { 0x%02x, %c, %c, %c }\n", ehdr->e_ident[EI_MAG0], ehdr->e_ident[EI_MAG1], ehdr->e_ident[EI_MAG2], ehdr->e_ident[EI_MAG3]); mdb_printf(" ei_class: %-18u ei_data: %-16u\n", ehdr->e_ident[EI_CLASS], ehdr->e_ident[EI_DATA]); mdb_printf(" e_machine: %-18hu e_version: %-16u\n", ehdr->e_machine, ehdr->e_version); mdb_printf(" e_type: %-18hu\n", ehdr->e_type); mdb_printf(" e_flags: %-18u\n", ehdr->e_flags); mdb_printf(" e_entry: 0x%16lx e_ehsize: %8hu e_shstrndx: %hu\n", ehdr->e_entry, ehdr->e_ehsize, ehdr->e_shstrndx); mdb_printf(" e_shoff: 0x%16lx e_shentsize: %8hu e_shnum: %hu\n", ehdr->e_shoff, ehdr->e_shentsize, ehdr->e_shnum); mdb_printf(" e_phoff: 0x%16lx e_phentsize: %8hu e_phnum: %hu\n", ehdr->e_phoff, ehdr->e_phentsize, ehdr->e_phnum); } static void dump_shdr(const Shdr *shdr, int i) { static const mdb_bitmask_t sh_type_masks[] = { { "SHT_NULL", 0xffffffff, SHT_NULL }, { "SHT_PROGBITS", 0xffffffff, SHT_PROGBITS }, { "SHT_SYMTAB", 0xffffffff, SHT_SYMTAB }, { "SHT_STRTAB", 0xffffffff, SHT_STRTAB }, { "SHT_RELA", 0xffffffff, SHT_RELA }, { "SHT_HASH", 0xffffffff, SHT_HASH }, { "SHT_DYNAMIC", 0xffffffff, SHT_DYNAMIC }, { "SHT_NOTE", 0xffffffff, SHT_NOTE }, { "SHT_NOBITS", 0xffffffff, SHT_NOBITS }, { "SHT_REL", 0xffffffff, SHT_REL }, { "SHT_SHLIB", 0xffffffff, SHT_SHLIB }, { "SHT_DYNSYM", 0xffffffff, SHT_DYNSYM }, { "SHT_LOSUNW", 0xffffffff, SHT_LOSUNW }, { "SHT_SUNW_COMDAT", 0xffffffff, SHT_SUNW_COMDAT }, { "SHT_SUNW_syminfo", 0xffffffff, SHT_SUNW_syminfo }, { "SHT_SUNW_verdef", 0xffffffff, SHT_SUNW_verdef }, { "SHT_SUNW_verneed", 0xffffffff, SHT_SUNW_verneed }, { "SHT_SUNW_versym", 0xffffffff, SHT_SUNW_versym }, { "SHT_HISUNW", 0xffffffff, SHT_HISUNW }, { "SHT_LOPROC", 0xffffffff, SHT_LOPROC }, { "SHT_HIPROC", 0xffffffff, SHT_HIPROC }, { "SHT_LOUSER", 0xffffffff, SHT_LOUSER }, { "SHT_HIUSER", 0xffffffff, SHT_HIUSER }, { NULL, 0, 0 } }; static const mdb_bitmask_t sh_flag_masks[] = { { "SHF_WRITE", SHF_WRITE, SHF_WRITE }, { "SHF_ALLOC", SHF_ALLOC, SHF_ALLOC }, { "SHF_EXECINSTR", SHF_EXECINSTR, SHF_EXECINSTR }, { "SHF_MASKPROC", SHF_MASKPROC, SHF_MASKPROC }, { NULL, 0, 0 } }; mdb_printf("\nSection Header[%d]:\n", i); mdb_printf(" sh_addr: 0x%-16lx sh_flags: [ %#lb ]\n", shdr->sh_addr, shdr->sh_flags, sh_flag_masks); mdb_printf(" sh_size: 0x%-16lx sh_type: [ %#lb ]\n", shdr->sh_size, shdr->sh_type, sh_type_masks); mdb_printf(" sh_offset: 0x%-16lx sh_entsize: 0x%lx\n", shdr->sh_offset, shdr->sh_entsize); mdb_printf(" sh_link: 0x%-16lx sh_info: 0x%lx\n", shdr->sh_link, shdr->sh_info); mdb_printf(" sh_addralign: 0x%-16lx\n", shdr->sh_addralign); } /*ARGSUSED*/ static int modhdrs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct modctl ctl; struct krtld_ctf_module mod; Shdr *shdrs; size_t nbytes; int i; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("expected address of struct modctl before ::\n"); return (DCMD_USAGE); } if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&ctl, sizeof (struct modctl), addr) == -1) { mdb_warn("failed to read modctl at %p\n", addr); return (DCMD_ERR); } if (mdb_ctf_vread(&mod, "struct module", "struct krtld_ctf_module", (uintptr_t)ctl.mod_mp, 0) == -1) { mdb_warn("failed to read module at %p for modctl %p\n", ctl.mod_mp, addr); return (DCMD_ERR); } dump_ehdr(&mod.hdr); nbytes = sizeof (Shdr) * mod.hdr.e_shnum; shdrs = mdb_alloc(nbytes, UM_SLEEP | UM_GC); mdb_vread(shdrs, nbytes, (uintptr_t)(void *)mod.shdrs); for (i = 0; i < mod.hdr.e_shnum; i++) dump_shdr(&shdrs[i], i); return (DCMD_OK); } /*ARGSUSED*/ static int modinfo_format(uintptr_t addr, const void *data, void *private) { const struct modctl *mcp = (const struct modctl *)data; struct modlinkage linkage; struct modlmisc lmisc; struct krtld_ctf_module mod; char info[MODMAXLINKINFOLEN]; char name[MODMAXNAMELEN]; mod.text_size = 0; mod.data_size = 0; mod.text = NULL; linkage.ml_rev = 0; info[0] = '\0'; if (mcp->mod_mp != NULL) { if (mdb_ctf_vread(&mod, "struct module", "struct krtld_ctf_module", (uintptr_t)mcp->mod_mp, 0) == -1) { mdb_warn("failed to read module at %p\n", mcp->mod_mp); return (WALK_NEXT); } } if (mcp->mod_linkage != NULL) { (void) mdb_vread(&linkage, sizeof (linkage), (uintptr_t)mcp->mod_linkage); if (linkage.ml_linkage[0] != NULL) { (void) mdb_vread(&lmisc, sizeof (lmisc), (uintptr_t)linkage.ml_linkage[0]); mdb_readstr(info, sizeof (info), (uintptr_t)lmisc.misc_linkinfo); } } if (mdb_readstr(name, sizeof (name), (uintptr_t)mcp->mod_modname) == -1) (void) strcpy(name, "???"); mdb_printf("%3d %?p %8lx %3d %s (%s)\n", mcp->mod_id, mod.text, mod.text_size + mod.data_size, linkage.ml_rev, name, info[0] != '\0' ? info : "?"); return (WALK_NEXT); } /*ARGSUSED*/ static int modinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%3s %?s %8s %3s %s%\n", "ID", "LOADADDR", "SIZE", "REV", "MODULE NAME"); } if (flags & DCMD_ADDRSPEC) { struct modctl mc; (void) mdb_vread(&mc, sizeof (mc), addr); return (modinfo_format(addr, &mc, NULL)); } if (mdb_walk("modctl", modinfo_format, NULL) == -1) return (DCMD_ERR); return (DCMD_OK); } /*ARGSUSED*/ static int ctfinfo_format(uintptr_t addr, const struct modctl *mcp, void *private) { char name[MODMAXNAMELEN]; struct krtld_ctf_module mod; if (mcp->mod_mp == NULL) return (WALK_NEXT); /* module is not loaded */ if (mdb_ctf_vread(&mod, "struct module", "struct krtld_ctf_module", (uintptr_t)mcp->mod_mp, 0) == -1) { mdb_warn("failed to read module at %p for modctl %p\n", mcp->mod_mp, addr); return (WALK_NEXT); } if (mdb_readstr(name, sizeof (name), (uintptr_t)mcp->mod_modname) == -1) (void) mdb_snprintf(name, sizeof (name), "%a", mcp->mod_mp); mdb_printf("%-30s %?p %lu\n", name, mod.ctfdata, (ulong_t)mod.ctfsize); return (WALK_NEXT); } /*ARGSUSED*/ static int ctfinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); mdb_printf("%%-30s %?s %s%\n", "MODULE", "CTFDATA", "CTFSIZE"); if (mdb_walk("modctl", (mdb_walk_cb_t)ctfinfo_format, NULL) == -1) return (DCMD_ERR); return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "modctl", NULL, "list modctl structures", modctls }, { "modhdrs", ":", "given modctl, dump module ehdr and shdrs", modhdrs }, { "modinfo", NULL, "list module information", modinfo }, { "ctfinfo", NULL, "list module CTF information", ctfinfo }, { NULL } }; static const mdb_walker_t walkers[] = { { "modctl", "list of modctl structures", modctl_walk_init, modctl_walk_step, modctl_walk_fini }, { NULL } }; static const mdb_modinfo_t krtld_modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_name("modules", &sym) == -1) { mdb_warn("failed to lookup 'modules'"); return (NULL); } module_head = (uintptr_t)sym.st_value; return (&krtld_modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include "../genunix/avl.h" static const mdb_walker_t walkers[] = { { AVL_WALK_NAME, AVL_WALK_DESC, avl_walk_init, avl_walk_step, avl_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, NULL, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include "findstack.h" #if defined(__i386) || defined(__amd64) struct rwindow { uintptr_t rw_fp; uintptr_t rw_rtn; }; #endif #ifndef STACK_BIAS #define STACK_BIAS 0 #endif #ifdef __amd64 #define STACKS_REGS_FP "rbp" #define STACKS_REGS_RC "rip" #else #ifdef __i386 #define STACKS_REGS_FP "ebp" #define STACKS_REGS_RC "eip" #else #define STACKS_REGS_FP "fp" #define STACKS_REGS_RC "pc" #endif #endif #define STACKS_SOBJ_MX (uintptr_t)"MX" #define STACKS_SOBJ_CV (uintptr_t)"CV" int thread_text_to_state(const char *state, uint_t *out) { if (strcmp(state, "PARKED") == 0) { *out = B_TRUE; } else if (strcmp(state, "UNPARKED") == 0) { *out = B_FALSE; } else if (strcmp(state, "FREE") == 0) { /* * When run with "-i", ::stacks filters out "FREE" threads. * We therefore need to recognize "FREE", and set it to a * value that will never match fsi_tstate. */ *out = UINT_MAX; } else { return (-1); } return (0); } void thread_state_to_text(uint_t state, char *out, size_t out_sz) { (void) snprintf(out, out_sz, state ? "PARKED" : "UNPARKED"); } int sobj_text_to_ops(const char *name, uintptr_t *sobj_ops_out) { if (strcmp(name, "MX") == 0) { *sobj_ops_out = STACKS_SOBJ_MX; } else if (strcmp(name, "CV") == 0) { *sobj_ops_out = STACKS_SOBJ_CV; } else { mdb_warn("sobj \"%s\" not recognized\n", name); return (-1); } return (0); } void sobj_ops_to_text(uintptr_t addr, char *out, size_t sz) { (void) snprintf(out, sz, "%s", addr == 0 ? "" : (char *)addr); } static int stacks_module_callback(mdb_object_t *obj, void *arg) { stacks_module_t *smp = arg; boolean_t match = (strcmp(obj->obj_name, smp->sm_name) == 0); char *suffix = ".so"; const char *s, *next; size_t len; if (smp->sm_size != 0) return (0); /* * It doesn't match the name, but -- for convenience -- we want to * allow matches before ".so.[suffix]". An aside: why doesn't * strrstr() exist? (Don't google that. I'm serious, don't do it. * If you do, and you read the thread of "why doesn't strrstr() exist?" * circa 2005 you will see things that you will NEVER be able to unsee!) */ if (!match && (s = strstr(obj->obj_name, suffix)) != NULL) { while ((next = strstr(s + 1, suffix)) != NULL) { s = next; continue; } len = s - obj->obj_name; match = (strncmp(smp->sm_name, obj->obj_name, len) == 0 && smp->sm_name[len] == '\0'); } /* * If we have a library that has the libc directory in the path, we * want to match against anything that would match libc.so.1. (This * is necessary to be able to easily deal with libc implementations * that have alternate hardware capabilities.) */ if (!match && strstr(obj->obj_fullname, "/libc/") != NULL) { mdb_object_t libc = *obj; libc.obj_name = "libc.so.1"; libc.obj_fullname = ""; return (stacks_module_callback(&libc, arg)); } if (match) { smp->sm_text = obj->obj_base; smp->sm_size = obj->obj_size; } return (0); } int stacks_module(stacks_module_t *smp) { if (mdb_object_iter(stacks_module_callback, smp) != 0) return (-1); return (0); } typedef struct stacks_ulwp { avl_node_t sulwp_node; lwpid_t sulwp_id; uintptr_t sulwp_addr; } stacks_ulwp_t; boolean_t stacks_ulwp_initialized; avl_tree_t stacks_ulwp_byid; /*ARGSUSED*/ int stacks_ulwp_walk(uintptr_t addr, ulwp_t *ulwp, void *ignored) { stacks_ulwp_t *sulwp = mdb_alloc(sizeof (stacks_ulwp_t), UM_SLEEP); sulwp->sulwp_id = ulwp->ul_lwpid; sulwp->sulwp_addr = addr; if (avl_find(&stacks_ulwp_byid, sulwp, NULL) != NULL) { mdb_warn("found multiple LWPs with ID %d!", ulwp->ul_lwpid); return (WALK_ERR); } avl_add(&stacks_ulwp_byid, sulwp); return (WALK_NEXT); } static int stacks_ulwp_compare(const void *l, const void *r) { const stacks_ulwp_t *lhs = l; const stacks_ulwp_t *rhs = r; if (lhs->sulwp_id > rhs->sulwp_id) return (1); if (lhs->sulwp_id < rhs->sulwp_id) return (-1); return (0); } /*ARGSUSED*/ int stacks_findstack(uintptr_t addr, findstack_info_t *fsip, uint_t print_warnings) { mdb_reg_t reg; uintptr_t fp; struct rwindow frame; avl_tree_t *tree = &stacks_ulwp_byid; stacks_ulwp_t *sulwp, cmp; ulwp_t ulwp; fsip->fsi_failed = 0; fsip->fsi_pc = 0; fsip->fsi_sp = 0; fsip->fsi_depth = 0; fsip->fsi_overflow = 0; if (!stacks_ulwp_initialized) { avl_create(tree, stacks_ulwp_compare, sizeof (stacks_ulwp_t), offsetof(stacks_ulwp_t, sulwp_node)); if (mdb_walk("ulwp", (mdb_walk_cb_t)stacks_ulwp_walk, NULL) != 0) { mdb_warn("couldn't walk 'ulwp'"); return (-1); } stacks_ulwp_initialized = B_TRUE; } bzero(&cmp, sizeof (cmp)); cmp.sulwp_id = (lwpid_t)addr; if ((sulwp = avl_find(tree, &cmp, NULL)) == NULL) { mdb_warn("couldn't find ulwp_t for tid %d\n", cmp.sulwp_id); return (-1); } if (mdb_vread(&ulwp, sizeof (ulwp), sulwp->sulwp_addr) == -1) { mdb_warn("couldn't read ulwp_t for tid %d at %p", cmp.sulwp_id, sulwp->sulwp_addr); return (-1); } fsip->fsi_tstate = ulwp.ul_sleepq != NULL; fsip->fsi_sobj_ops = (uintptr_t)(ulwp.ul_sleepq == NULL ? 0 : (ulwp.ul_qtype == MX ? STACKS_SOBJ_MX : STACKS_SOBJ_CV)); if (mdb_getareg(addr, STACKS_REGS_FP, ®) != 0) { mdb_warn("couldn't read frame pointer for thread 0x%p", addr); return (-1); } fsip->fsi_sp = fp = (uintptr_t)reg; #if !defined(__i386) if (mdb_getareg(addr, STACKS_REGS_RC, ®) != 0) { mdb_warn("couldn't read program counter for thread 0x%p", addr); return (-1); } fsip->fsi_pc = (uintptr_t)reg; #endif while (fp != 0) { if (mdb_vread(&frame, sizeof (frame), fp) == -1) { mdb_warn("couldn't read frame for thread 0x%p at %p", addr, fp); return (-1); } if (frame.rw_rtn == 0) break; if (fsip->fsi_depth < fsip->fsi_max_depth) { fsip->fsi_stack[fsip->fsi_depth++] = frame.rw_rtn; } else { fsip->fsi_overflow = 1; break; } fp = frame.rw_fp + STACK_BIAS; } return (0); } void stacks_findstack_cleanup() { avl_tree_t *tree = &stacks_ulwp_byid; void *cookie = NULL; stacks_ulwp_t *sulwp; if (!stacks_ulwp_initialized) return; while ((sulwp = avl_destroy_nodes(tree, &cookie)) != NULL) mdb_free(sulwp, sizeof (stacks_ulwp_t)); bzero(tree, sizeof (*tree)); stacks_ulwp_initialized = B_FALSE; } void stacks_help(void) { mdb_printf( "::stacks processes all of the thread stacks in the process, grouping\n" "together threads which have the same:\n" "\n" " * Thread state,\n" " * Sync object type, and\n" " * PCs in their stack trace.\n" "\n" "The default output (no address or options) is just a dump of the thread\n" "groups in the process. For a view of active threads, use \"::stacks -i\",\n" "which filters out threads sleeping on a CV. More general filtering options\n" "are described below, in the \"FILTERS\" section.\n" "\n" "::stacks can be used in a pipeline. The input to ::stacks is one or more\n" "thread IDs. When output into a pipe, ::stacks prints all of the threads \n" "input, filtered by the given filtering options. This means that multiple\n" "::stacks invocations can be piped together to achieve more complicated\n" "filters. For example, to get threads which have both '__door_return' and\n" "'mutex_lock' in their stack trace, you could do:\n" "\n" " ::stacks -c __door_return | ::stacks -c mutex_lock\n" "\n" "To get the full list of threads in each group, use the '-a' flag:\n" "\n" " ::stacks -a\n" "\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -a Print all of the grouped threads, instead of just a count.\n" " -f Force a re-run of the thread stack gathering.\n" " -v Be verbose about thread stack gathering.\n" "\n"); mdb_dec_indent(2); mdb_printf("%FILTERS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -i Show active threads; equivalent to '-S CV'.\n" " -c func[+offset]\n" " Only print threads whose stacks contain func/func+offset.\n" " -C func[+offset]\n" " Only print threads whose stacks do not contain func/func+offset.\n" " -m module\n" " Only print threads whose stacks contain functions from module.\n" " -M module\n" " Only print threads whose stacks do not contain functions from\n" " module.\n" " -s {type | ALL}\n" " Only print threads which are on a 'type' synchronization object\n" " (SOBJ).\n" " -S {type | ALL}\n" " Only print threads which are not on a 'type' SOBJ.\n" " -t tstate\n" " Only print threads which are in thread state 'tstate'.\n" " -T tstate\n" " Only print threads which are not in thread state 'tstate'.\n" "\n"); } /* * 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 (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright (c) 2019 Carlos Neira * Copyright 2019 OmniOS Community Edition (OmniOSce) Association. * Copyright 2019 Joyent, Inc. * Copyright 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include "findstack.h" #include static const char * stack_flags(const stack_t *sp) { static char buf[32]; if (sp->ss_flags == 0) (void) strcpy(buf, " 0"); else if (sp->ss_flags & ~(SS_ONSTACK | SS_DISABLE)) (void) mdb_snprintf(buf, sizeof (buf), " 0x%x", sp->ss_flags); else { buf[0] = '\0'; if (sp->ss_flags & SS_ONSTACK) (void) strcat(buf, "|ONSTACK"); if (sp->ss_flags & SS_DISABLE) (void) strcat(buf, "|DISABLE"); } return (buf + 1); } /*ARGSUSED*/ static int d_jmp_buf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { jmp_buf jb; const ulong_t *b = (const ulong_t *)jb; if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&jb, sizeof (jb), addr) != sizeof (jb)) { mdb_warn("failed to read jmp_buf at %p", addr); return (DCMD_ERR); } #if defined(__sparc) mdb_printf(" %%sp = 0x%lx\n", b[1]); mdb_printf(" %%pc = 0x%lx %lA\n", b[2], b[2]); mdb_printf(" %%fp = 0x%lx\n", b[3]); mdb_printf(" %%i7 = 0x%lx %lA\n", b[4], b[4]); #elif defined(__amd64) mdb_printf(" %%rbx = 0x%lx\n", b[0]); mdb_printf(" %%r12 = 0x%lx\n", b[1]); mdb_printf(" %%r13 = 0x%lx\n", b[2]); mdb_printf(" %%r14 = 0x%lx\n", b[3]); mdb_printf(" %%r15 = 0x%lx\n", b[4]); mdb_printf(" %%rbp = 0x%lx\n", b[5]); mdb_printf(" %%rsp = 0x%lx\n", b[6]); mdb_printf(" %%rip = 0x%lx %lA\n", b[7], b[7]); #elif defined(__i386) mdb_printf(" %%ebx = 0x%lx\n", b[0]); mdb_printf(" %%esi = 0x%lx\n", b[1]); mdb_printf(" %%edi = 0x%lx\n", b[2]); mdb_printf(" %%ebp = 0x%lx\n", b[3]); mdb_printf(" %%esp = 0x%lx\n", b[4]); mdb_printf(" %%eip = 0x%lx %lA\n", b[5], b[5]); #endif return (DCMD_OK); } const mdb_bitmask_t uc_flags_bits[] = { { "UC_SIGMASK", UC_SIGMASK, UC_SIGMASK }, { "UC_STACK", UC_STACK, UC_STACK }, { "UC_CPU", UC_CPU, UC_CPU }, { "UC_FPU", UC_FPU, UC_FPU }, #if defined(UC_INTR) { "UC_INTR", UC_INTR, UC_INTR }, #endif #if defined(UC_ASR) { "UC_ASR", UC_ASR, UC_ASR }, #endif { NULL, 0, 0 } }; /*ARGSUSED*/ static int d_ucontext(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ucontext_t uc; if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&uc, sizeof (uc), addr) != sizeof (uc)) { mdb_warn("failed to read ucontext at %p", addr); return (DCMD_ERR); } mdb_printf(" flags = 0x%lx <%b>\n", uc.uc_flags, (uint_t)uc.uc_flags, uc_flags_bits); mdb_printf(" link = 0x%p\n", uc.uc_link); mdb_printf(" sigmask = 0x%08x 0x%08x 0x%08x 0x%08x\n", uc.uc_sigmask.__sigbits[0], uc.uc_sigmask.__sigbits[1], uc.uc_sigmask.__sigbits[2], uc.uc_sigmask.__sigbits[3]); mdb_printf(" stack = sp 0x%p size 0x%lx flags %s\n", uc.uc_stack.ss_sp, uc.uc_stack.ss_size, stack_flags(&uc.uc_stack)); mdb_printf(" mcontext = 0x%p\n", addr + OFFSETOF(ucontext_t, uc_mcontext)); return (DCMD_OK); } /*ARGSUSED*/ static int d_sigjmp_buf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { #if defined(__sparc) struct { int sjs_flags; greg_t sjs_sp; greg_t sjs_pc; greg_t sjs_fp; greg_t sjs_i7; ucontext_t *sjs_uclink; ulong_t sjs_pad[_JBLEN - 6]; sigset_t sjs_sigmask; #if defined(_LP64) greg_t sjs_asi; greg_t sjs_fprs; #endif stack_t sjs_stack; } s; if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&s, sizeof (s), addr) != sizeof (s)) { mdb_warn("failed to read sigjmp_buf at %p", addr); return (DCMD_ERR); } mdb_printf(" flags = 0x%x\n", s.sjs_flags); mdb_printf(" %%sp = 0x%lx %lA\n", s.sjs_sp, s.sjs_sp); mdb_printf(" %%pc = 0x%lx %lA\n", s.sjs_pc, s.sjs_pc); mdb_printf(" %%fp = 0x%lx %lA\n", s.sjs_fp, s.sjs_fp); mdb_printf(" %%i7 = 0x%lx %lA\n", s.sjs_i7, s.sjs_i7); mdb_printf(" uclink = %p\n", s.sjs_uclink); mdb_printf(" sigset = 0x%08x 0x%08x 0x%08x 0x%08x\n", s.sjs_sigmask.__sigbits[0], s.sjs_sigmask.__sigbits[1], s.sjs_sigmask.__sigbits[2], s.sjs_sigmask.__sigbits[3]); #if defined(_LP64) mdb_printf(" %%asi = 0x%lx\n", s.sjs_asi); mdb_printf(" %%fprs = 0x%lx\n", s.sjs_fprs); #endif mdb_printf(" stack = sp 0x%p size 0x%lx flags %s\n", s.sjs_stack.ss_sp, s.sjs_stack.ss_size, stack_flags(&s.sjs_stack)); return (DCMD_OK); #elif defined(__i386) || defined(__amd64) return (d_ucontext(addr, flags, argc, argv)); #endif } /*ARGSUSED*/ static int d_siginfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { static const char *const msname[] = { "USER", "SYSTEM", "TRAP", "TFAULT", "DFAULT", "KFAULT", "USER_LOCK", "SLEEP", "WAIT_CPU", "STOPPED" }; char signame[SIG2STR_MAX]; siginfo_t si; int i; if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&si, sizeof (si), addr) != sizeof (si)) { mdb_warn("failed to read siginfo at %p", addr); return (DCMD_ERR); } if (sig2str(si.si_signo, signame) == -1) (void) strcpy(signame, "unknown"); mdb_printf(" signal %5d (%s)\n", si.si_signo, signame); mdb_printf(" code %5d (", si.si_code); switch (si.si_code) { case SI_NOINFO: mdb_printf("no info"); break; case SI_DTRACE: mdb_printf("from DTrace raise() action"); break; case SI_RCTL: mdb_printf("from rctl action"); break; case SI_USER: mdb_printf("user generated via kill"); break; case SI_LWP: mdb_printf("user generated via lwp_kill"); break; case SI_QUEUE: mdb_printf("user generated via sigqueue"); break; case SI_TIMER: mdb_printf("from timer expiration"); break; case SI_ASYNCIO: mdb_printf("from async i/o completion"); break; case SI_MESGQ: mdb_printf("from message arrival"); break; default: if (SI_FROMUSER(&si)) mdb_printf("from user process"); else mdb_printf("from kernel"); } mdb_printf(")\n errno %5d (%s)\n", si.si_errno, strerror(si.si_errno)); if (si.si_code == SI_USER || si.si_code == SI_QUEUE) { mdb_printf(" signal sent from PID %d (uid %d)\n", si.si_pid, si.si_uid); } if (si.si_code == SI_QUEUE) { mdb_printf(" signal value = 0t%d / %p\n", si.si_value.sival_int, si.si_value.sival_ptr); } switch (si.si_signo) { case SIGCLD: mdb_printf(" signal sent from child PID %d (uid %d)\n", si.si_pid, si.si_uid); mdb_printf(" usr time = 0t%ld ticks, sys time = 0t%ld ticks\n", si.si_utime, si.si_stime); mdb_printf(" wait status = 0x%x\n", si.si_status); break; case SIGSEGV: case SIGBUS: case SIGILL: case SIGTRAP: case SIGFPE: mdb_printf(" fault address = 0x%p\n trapno = %d\n", si.si_addr, si.si_trapno); mdb_printf(" instruction address = 0x%p %lA\n", si.si_pc, si.si_pc); break; case SIGPOLL: case SIGXFSZ: mdb_printf(" fd = %d band = 0x%lx\n", si.si_fd, si.si_band); break; case SIGPROF: mdb_printf(" last fault address = 0x%p fault type = %d\n", si.si_faddr, si.si_fault); mdb_printf(" timestamp = 0t%ld sec 0t%ld nsec\n", si.si_tstamp.tv_sec, si.si_tstamp.tv_nsec); if (si.__data.__prof.__syscall != 0) { mdb_printf(" system call %d (", si.si_syscall); if (si.si_nsysarg > 0) { mdb_printf("%lx", si.si_sysarg[0]); for (i = 1; i < si.si_nsysarg; i++) mdb_printf(", %lx", si.si_sysarg[i]); } mdb_printf(" )\n"); } for (i = 0; i < sizeof (msname) / sizeof (msname[0]); i++) { mdb_printf(" mstate[\"%s\"] = %d\n", msname[i], si.si_mstate[i]); } break; } return (DCMD_OK); } static int uc_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; ucontext_t uc; if (addr == 0) return (WALK_DONE); if (mdb_vread(&uc, sizeof (uc), addr) != sizeof (uc)) { mdb_warn("failed to read ucontext at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)uc.uc_link; return (wsp->walk_callback(addr, &uc, wsp->walk_cbdata)); } static int oldc_walk_init(mdb_walk_state_t *wsp) { ssize_t nbytes = mdb_get_xdata("lwpstatus", NULL, 0); if (nbytes <= 0) { mdb_warn("lwpstatus information not available"); return (WALK_ERR); } if (wsp->walk_addr != 0) { mdb_warn("walker only supports global walk\n"); return (WALK_ERR); } wsp->walk_addr = nbytes; /* Use walk_addr to track size */ wsp->walk_data = mdb_alloc(nbytes, UM_SLEEP); if (mdb_get_xdata("lwpstatus", wsp->walk_data, nbytes) != nbytes) { mdb_warn("failed to read lwpstatus information"); mdb_free(wsp->walk_data, nbytes); return (WALK_ERR); } wsp->walk_arg = wsp->walk_data; /* Use walk_arg to track pointer */ return (WALK_NEXT); } static int oldc_walk_step(mdb_walk_state_t *wsp) { const lwpstatus_t *lsp, *end; end = (const lwpstatus_t *)((uintptr_t)wsp->walk_data + wsp->walk_addr); lsp = wsp->walk_arg; wsp->walk_arg = (void *)(lsp + 1); if (lsp < end) { uintptr_t addr = lsp->pr_oldcontext; ucontext_t uc; if (addr == 0) return (WALK_NEXT); if (mdb_vread(&uc, sizeof (uc), addr) != sizeof (uc)) { mdb_warn("failed to read ucontext at %p", addr); return (WALK_NEXT); } return (wsp->walk_callback(addr, &uc, wsp->walk_cbdata)); } return (WALK_DONE); } static void oldc_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, wsp->walk_addr); /* walk_addr has size */ } /* * ==================== threads ========================== * These are the interfaces that used to require libthread. * Now, libthread has been folded into libc. * ======================================================= */ /* * prt_addr() is called up to three times to generate arguments for * one call to mdb_printf(). We must return at least three different * pointers to static storage for consecutive calls to prt_addr(). */ static const char * prt_addr(void *addr, int pad) { static char buffer[4][24]; static int ix = 0; char *buf; if (ix == 4) /* use buffers in sequence: 0, 1, 2, 3 */ ix = 0; buf = buffer[ix++]; if (addr == NULL) return (pad? " " : ""); else { #ifdef _LP64 (void) mdb_snprintf(buf, sizeof (buffer[0]), "0x%016lx", addr); if (pad) (void) strcpy(buf + 18, " "); #else (void) mdb_snprintf(buf, sizeof (buffer[0]), "0x%08lx", addr); if (pad) (void) strcpy(buf + 10, " "); #endif /* _LP64 */ return (buf); } } #define HD(str) mdb_printf(" " str "\n") #define OFFSTR "+0x%-7lx " #define OFFSET(member) ((size_t)OFFSETOF(ulwp_t, member)) /*ARGSUSED*/ static int d_ulwp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ulwp_t ulwp; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&ulwp, sizeof (ulwp), addr) != sizeof (ulwp) && (bzero(&ulwp, sizeof (ulwp)), mdb_vread(&ulwp, REPLACEMENT_SIZE, addr)) != REPLACEMENT_SIZE) { mdb_warn("failed to read ulwp at 0x%p", addr); return (DCMD_ERR); } mdb_printf("%#a\n", addr); HD("self uberdata"); mdb_printf(OFFSTR "%s %s\n", OFFSET(ul_self), prt_addr(ulwp.ul_self, 1), prt_addr(ulwp.ul_uberdata, 0)); HD("tlsent ntlsent"); mdb_printf(OFFSTR "%s %ld\n", OFFSET(ul_tlsent), prt_addr(ulwp.ul_tlsent, 1), ulwp.ul_ntlsent); HD("forw back next"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(ul_forw), prt_addr(ulwp.ul_forw, 1), prt_addr(ulwp.ul_back, 1), prt_addr(ulwp.ul_next, 0)); HD("hash rval stk"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(ul_hash), prt_addr(ulwp.ul_hash, 1), prt_addr(ulwp.ul_rval, 1), prt_addr(ulwp.ul_stk, 0)); HD("mapsiz guardsize stktop stksiz"); mdb_printf(OFFSTR "%-10ld %-10ld %s %ld\n", OFFSET(ul_mapsiz), ulwp.ul_mapsiz, ulwp.ul_guardsize, prt_addr((void *)ulwp.ul_stktop, 1), ulwp.ul_stksiz); HD("ustack.ss_sp ustack.ss_size ustack.ss_flags"); mdb_printf(OFFSTR "%s %-21ld %s\n", OFFSET(ul_ustack.ss_sp), prt_addr(ulwp.ul_ustack.ss_sp, 1), ulwp.ul_ustack.ss_size, stack_flags(&ulwp.ul_ustack)); HD("ix lwpid pri epri policy cid"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %-10d %d\n", OFFSET(ul_ix), ulwp.ul_ix, ulwp.ul_lwpid, ulwp.ul_pri, ulwp.ul_epri, ulwp.ul_policy, ulwp.ul_cid); HD("cursig pleasestop stop signalled dead unwind"); mdb_printf(OFFSTR "%-10d ", OFFSET(ul_cursig), ulwp.ul_cursig); mdb_printf(ulwp.ul_pleasestop? "0x%-8x " : "%-10d ", ulwp.ul_pleasestop); mdb_printf(ulwp.ul_stop? "0x%-8x " : "%-10d ", ulwp.ul_stop); mdb_printf("%-10d %-10d %d\n", ulwp.ul_signalled, ulwp.ul_dead, ulwp.ul_unwind); HD("detached writer stopping can'prolog preempt savpreempt"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %-10d %d\n", OFFSET(ul_detached), ulwp.ul_detached, ulwp.ul_writer, ulwp.ul_stopping, ulwp.ul_cancel_prologue, ulwp.ul_preempt, ulwp.ul_savpreempt); HD("sigsuspend main fork primarymap m'spinners d'noreserv"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %-10d %d\n", OFFSET(ul_sigsuspend), ulwp.ul_sigsuspend, ulwp.ul_main, ulwp.ul_fork, ulwp.ul_primarymap, ulwp.ul_max_spinners, ulwp.ul_door_noreserve); HD("queue_fifo c'w'defer e'detect' async_safe rt rtqueued"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %-10d %d\n", OFFSET(ul_queue_fifo), ulwp.ul_queue_fifo, ulwp.ul_cond_wait_defer, ulwp.ul_error_detection, ulwp.ul_async_safe, ulwp.ul_rt, ulwp.ul_rtqueued); HD("misaligned adapt'spin queue_spin critical sigdefer vfork"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %-10d %d\n", OFFSET(ul_misaligned), ulwp.ul_misaligned, ulwp.ul_adaptive_spin, ulwp.ul_queue_spin, ulwp.ul_critical, ulwp.ul_sigdefer, ulwp.ul_vfork); HD("cancelable c'pending c'disabled c'async save_async mutator"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %-10d %d\n", OFFSET(ul_cancelable), ulwp.ul_cancelable, ulwp.ul_cancel_pending, ulwp.ul_cancel_disabled, ulwp.ul_cancel_async, ulwp.ul_save_async, ulwp.ul_mutator); HD("created replace nocancel errno errnop"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %s\n", OFFSET(ul_created), ulwp.ul_created, ulwp.ul_replace, ulwp.ul_nocancel, ulwp.ul_errno, prt_addr(ulwp.ul_errnop, 0)); HD("clnup_hdr schedctl_called schedctl"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(ul_clnup_hdr), prt_addr(ulwp.ul_clnup_hdr, 1), prt_addr(ulwp.ul_schedctl_called, 1), prt_addr((void *)ulwp.ul_schedctl, 0)); HD("bindflags libc_locks stsd &ftsd"); mdb_printf(OFFSTR, OFFSET(ul_bindflags)); mdb_printf(ulwp.ul_bindflags? "0x%-8x " : "%-10d ", ulwp.ul_bindflags); mdb_printf("%-10d ", ulwp.ul_libc_locks); mdb_printf("%s %s\n", prt_addr(ulwp.ul_stsd, 1), prt_addr((void *)(addr + OFFSET(ul_ftsd[0])), 0)); HD("eventmask[0..1] eventnum eventdata"); mdb_printf(OFFSTR "0x%08x 0x%08x %-21d %s\n", OFFSET(ul_td_evbuf.eventmask.event_bits[0]), ulwp.ul_td_evbuf.eventmask.event_bits[0], ulwp.ul_td_evbuf.eventmask.event_bits[1], ulwp.ul_td_evbuf.eventnum, prt_addr(ulwp.ul_td_evbuf.eventdata, 0)); HD("td'enable sync'reg qtype cv_wake rtld usropts"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %-10d ", OFFSET(ul_td_events_enable), ulwp.ul_td_events_enable, ulwp.ul_sync_obj_reg, ulwp.ul_qtype, ulwp.ul_cv_wake, ulwp.ul_rtld); mdb_printf(ulwp.ul_usropts? "0x%x\n" : "%d\n", ulwp.ul_usropts); HD("startpc startarg wchan"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(ul_startpc), prt_addr((void *)ulwp.ul_startpc, 1), prt_addr(ulwp.ul_startarg, 1), prt_addr(ulwp.ul_wchan, 0)); HD("link sleepq cvmutex"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(ul_link), prt_addr(ulwp.ul_link, 1), prt_addr(ulwp.ul_sleepq, 1), prt_addr(ulwp.ul_cvmutex, 0)); HD("mxchain save_state"); mdb_printf(OFFSTR "%s %d\n", OFFSET(ul_mxchain), prt_addr(ulwp.ul_mxchain, 1), ulwp.ul_save_state); HD("rdlockcnt rd_rwlock rd_count"); mdb_printf(OFFSTR "%-21d %s %d\n", OFFSET(ul_rdlockcnt), ulwp.ul_rdlockcnt, prt_addr(ulwp.ul_readlock.single.rd_rwlock, 1), ulwp.ul_readlock.single.rd_count); HD("heldlockcnt heldlocks tpdp"); mdb_printf(OFFSTR "%-21d %s %s\n", OFFSET(ul_heldlockcnt), ulwp.ul_heldlockcnt, prt_addr(ulwp.ul_heldlocks.single, 1), prt_addr(ulwp.ul_tpdp, 0)); HD("siglink s'l'spin s'l'spin2 s'l'sleep s'l'wakeup"); mdb_printf(OFFSTR "%s %-10d %-10d %-10d %d\n", OFFSET(ul_siglink), prt_addr(ulwp.ul_siglink, 1), ulwp.ul_spin_lock_spin, ulwp.ul_spin_lock_spin2, ulwp.ul_spin_lock_sleep, ulwp.ul_spin_lock_wakeup); HD("&queue_root rtclassid pilocks"); mdb_printf(OFFSTR "%s %-10d %d\n", OFFSET(ul_queue_root), prt_addr((void *)(addr + OFFSET(ul_queue_root)), 1), ulwp.ul_rtclassid, ulwp.ul_pilocks); /* * The remainder of the ulwp_t structure * is invalid if this is a replacement. */ if (ulwp.ul_replace) return (DCMD_OK); HD("sigmask[0..3]"); mdb_printf(OFFSTR "0x%08x 0x%08x 0x%08x 0x%08x\n", OFFSET(ul_sigmask.__sigbits[0]), ulwp.ul_sigmask.__sigbits[0], ulwp.ul_sigmask.__sigbits[1], ulwp.ul_sigmask.__sigbits[2], ulwp.ul_sigmask.__sigbits[3]); HD("tmpmask[0..3]"); mdb_printf(OFFSTR "0x%08x 0x%08x 0x%08x 0x%08x\n", OFFSET(ul_tmpmask.__sigbits[0]), ulwp.ul_tmpmask.__sigbits[0], ulwp.ul_tmpmask.__sigbits[1], ulwp.ul_tmpmask.__sigbits[2], ulwp.ul_tmpmask.__sigbits[3]); HD("&siginfo &spinlock &fpuenv"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(ul_siginfo), prt_addr((void *)(addr + OFFSET(ul_siginfo)), 1), prt_addr((void *)(addr + OFFSET(ul_spinlock)), 1), prt_addr((void *)(addr + OFFSET(ul_fpuenv)), 0)); HD("tmem.size &tmem.roots"); mdb_printf(OFFSTR "%-21H %s\n", OFFSET(ul_tmem), ulwp.ul_tmem.tm_size, prt_addr((void *)(addr + OFFSET(ul_tmem) + sizeof (size_t)), 0)); return (DCMD_OK); } /* * Get the address of the unique uberdata_t structure. */ static uintptr_t uberdata_addr(void) { uintptr_t uaddr; uintptr_t addr; GElf_Sym sym; if (mdb_lookup_by_obj("libc.so.1", "_tdb_bootstrap", &sym) != 0) { mdb_warn("cannot find libc.so.1`_tdb_bootstrap"); return (0); } if (mdb_vread(&addr, sizeof (addr), sym.st_value) == sizeof (addr) && addr != 0 && mdb_vread(&uaddr, sizeof (uaddr), addr) == sizeof (uaddr) && uaddr != 0) { return (uaddr); } if (mdb_lookup_by_obj("libc.so.1", "_uberdata", &sym) != 0) { mdb_warn("cannot find libc.so.1`_uberdata"); return (0); } return ((uintptr_t)sym.st_value); } #undef OFFSET #define OFFSET(member) ((size_t)OFFSETOF(uberdata_t, member)) /*ARGSUSED*/ static int d_uberdata(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uberdata_t uberdata; int i; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC) && (addr = uberdata_addr()) == 0) return (DCMD_ERR); if (mdb_vread(&uberdata, sizeof (uberdata), addr) != sizeof (uberdata)) { mdb_warn("failed to read uberdata at 0x%p", addr); return (DCMD_ERR); } mdb_printf("%#a\n", addr); HD("&link_lock &ld_lock &fork_lock"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(link_lock), prt_addr((void *)(addr + OFFSET(link_lock)), 1), prt_addr((void *)(addr + OFFSET(ld_lock)), 1), prt_addr((void *)(addr + OFFSET(fork_lock)), 0)); HD("&atfork_lock &callout_lock &tdb_hash_lock"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(atfork_lock), prt_addr((void *)(addr + OFFSET(atfork_lock)), 1), prt_addr((void *)(addr + OFFSET(callout_lock)), 1), prt_addr((void *)(addr + OFFSET(tdb_hash_lock)), 0)); HD("&tdb_hash_lock_stats &siguaction[0]"); mdb_printf(OFFSTR "%s %s\n", OFFSET(tdb_hash_lock_stats), prt_addr((void *)(addr + OFFSET(tdb_hash_lock_stats)), 1), prt_addr((void *)(addr + OFFSET(siguaction)), 0)); HD("&bucket free_list chunks"); for (i = 0; i < NBUCKETS; i++) { mdb_printf(OFFSTR "%s %s %ld\n", OFFSET(bucket[i]), prt_addr((void *)(addr + OFFSET(bucket[i])), 1), prt_addr(uberdata.bucket[i].free_list, 1), uberdata.bucket[i].chunks); } HD("&atexit_root head exit_frame_monitor"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(atexit_root), prt_addr((void *)(addr + OFFSET(atexit_root.exitfns_lock)), 1), prt_addr(uberdata.atexit_root.head, 1), prt_addr(uberdata.atexit_root.exit_frame_monitor, 0)); HD("&quickexit_root head"); mdb_printf(OFFSTR "%s %s\n", OFFSET(quickexit_root), prt_addr((void *)(addr + OFFSET(quickexit_root.exitfns_lock)), 1), prt_addr(uberdata.quickexit_root.head, 0)); HD("&tsd_metadata tsdm_nkeys tsdm_nused tsdm_destro"); mdb_printf(OFFSTR "%s %-10d %-10d %s\n", OFFSET(tsd_metadata), prt_addr((void *)(addr + OFFSET(tsd_metadata.tsdm_lock)), 1), uberdata.tsd_metadata.tsdm_nkeys, uberdata.tsd_metadata.tsdm_nused, prt_addr((void *)uberdata.tsd_metadata.tsdm_destro, 0)); HD("&tls_metadata tls_modinfo.data tls_modinfo.size"); mdb_printf(OFFSTR "%s %s %ld\n", OFFSET(tls_metadata), prt_addr((void *)(addr + OFFSET(tls_metadata.tls_lock)), 1), prt_addr(uberdata.tls_metadata.tls_modinfo.tls_data, 1), uberdata.tls_metadata.tls_modinfo.tls_size); HD(" static_tls.data static_tls.size"); mdb_printf(OFFSTR "%s %s %ld\n", OFFSET(tls_metadata.static_tls), " ", prt_addr(uberdata.tls_metadata.static_tls.tls_data, 1), uberdata.tls_metadata.static_tls.tls_size); HD("primary_ma bucket_ini uflags.mt uflags.pad uflags.trs uflags.ted"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %-10d %d\n", OFFSET(primary_map), uberdata.primary_map, uberdata.bucket_init, uberdata.uberflags.uf_x.x_mt, uberdata.uberflags.uf_x.x_pad, uberdata.uberflags.uf_x.x_tdb_register_sync, uberdata.uberflags.uf_x.x_thread_error_detection); HD("queue_head thr_hash_table hash_size hash_mask"); mdb_printf(OFFSTR "%s %s %-10d 0x%x\n", OFFSET(queue_head), prt_addr(uberdata.queue_head, 1), prt_addr(uberdata.thr_hash_table, 1), uberdata.hash_size, uberdata.hash_mask); HD("ulwp_one all_lwps all_zombies"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(ulwp_one), prt_addr(uberdata.ulwp_one, 1), prt_addr(uberdata.all_lwps, 1), prt_addr(uberdata.all_zombies, 0)); HD("nthreads nzombies ndaemons pid sigacthandler"); mdb_printf(OFFSTR "%-10d %-10d %-10d %-10d %s\n", OFFSET(nthreads), uberdata.nthreads, uberdata.nzombies, uberdata.ndaemons, (int)uberdata.pid, prt_addr((void *)uberdata.sigacthandler, 0)); HD("lwp_stacks lwp_laststack nfreestack stk_cache"); mdb_printf(OFFSTR "%s %s %-10d %d\n", OFFSET(lwp_stacks), prt_addr(uberdata.lwp_stacks, 1), prt_addr(uberdata.lwp_laststack, 1), uberdata.nfreestack, uberdata.thread_stack_cache); HD("ulwp_freelist ulwp_lastfree ulwp_replace_free"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(ulwp_freelist), prt_addr(uberdata.ulwp_freelist, 1), prt_addr(uberdata.ulwp_lastfree, 1), prt_addr(uberdata.ulwp_replace_free, 0)); HD("ulwp_replace_last atforklist"); mdb_printf(OFFSTR "%s %s\n", OFFSET(ulwp_replace_last), prt_addr(uberdata.ulwp_replace_last, 1), prt_addr(uberdata.atforklist, 0)); HD("robustlocks robustlist progname"); mdb_printf(OFFSTR "%s %s %s\n", OFFSET(robustlocks), prt_addr(uberdata.robustlocks, 1), prt_addr(uberdata.robustlist, 1), prt_addr(uberdata.progname, 0)); HD("tdb_bootstrap tdb_sync_addr_hash tdb_'count tdb_'fail"); mdb_printf(OFFSTR "%s %s %-10d %d\n", OFFSET(tdb_bootstrap), prt_addr(uberdata.tdb_bootstrap, 1), prt_addr(uberdata.tdb.tdb_sync_addr_hash, 1), uberdata.tdb.tdb_register_count, uberdata.tdb.tdb_hash_alloc_failed); HD("tdb_sync_addr_free tdb_sync_addr_last tdb_sync_alloc"); mdb_printf(OFFSTR "%s %s %ld\n", OFFSET(tdb.tdb_sync_addr_free), prt_addr(uberdata.tdb.tdb_sync_addr_free, 1), prt_addr(uberdata.tdb.tdb_sync_addr_last, 1), uberdata.tdb.tdb_sync_alloc); HD("tdb_ev_global_mask tdb_events"); mdb_printf(OFFSTR "0x%08x 0x%08x %s\n", OFFSET(tdb.tdb_ev_global_mask), uberdata.tdb.tdb_ev_global_mask.event_bits[0], uberdata.tdb.tdb_ev_global_mask.event_bits[1], prt_addr((void *)uberdata.tdb.tdb_events, 0)); return (DCMD_OK); } static int ulwp_walk_init(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; uintptr_t uber_addr; int offset; offset = mdb_ctf_offsetof_by_name("uberdata_t", "all_lwps"); if (offset == -1) { offset = OFFSETOF(uberdata_t, all_lwps); mdb_warn("CTF data is missing for uberdata_t; using current " "platform's offset for uberdata.all_lwps"); } if (addr == 0 && ((uber_addr = uberdata_addr()) == 0 || mdb_vread(&addr, sizeof (addr), uber_addr + offset) != sizeof (addr))) { mdb_warn("cannot find 'uberdata.all_lwps'"); return (WALK_ERR); } if (addr == 0) return (WALK_DONE); wsp->walk_addr = addr; wsp->walk_data = (void *)addr; return (WALK_NEXT); } static int ulwp_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; ulwp_t ulwp; if (addr == 0) return (WALK_DONE); if (mdb_vread(&ulwp, sizeof (ulwp), addr) != sizeof (ulwp) && (bzero(&ulwp, sizeof (ulwp)), mdb_vread(&ulwp, REPLACEMENT_SIZE, addr)) != REPLACEMENT_SIZE) { mdb_warn("failed to read ulwp at 0x%p", addr); return (WALK_ERR); } /* * If we have looped around to the beginning * of the circular linked list, we are done. */ if ((wsp->walk_addr = (uintptr_t)ulwp.ul_forw) == (uintptr_t)wsp->walk_data) wsp->walk_addr = 0; return (wsp->walk_callback(addr, &ulwp, wsp->walk_cbdata)); } typedef struct lwp_wchan { uint8_t wchan_flag[4]; uint16_t wchan_type; uint16_t wchan_magic; } lwp_wchan_t; static int wchan_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { mdb_warn("wchan walk only supports global walks"); return (WALK_ERR); } if (mdb_layered_walk("ulwp", wsp) == -1) { mdb_warn("couldn't walk ulwp"); return (WALK_ERR); } return (WALK_NEXT); } static int wchan_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = (uintptr_t)(((ulwp_t *)wsp->walk_layer)->ul_wchan); lwp_wchan_t wchan; if (addr == (uintptr_t)NULL) { return (WALK_NEXT); } if (mdb_vread(&wchan, sizeof (wchan), addr) != sizeof (wchan)) { mdb_warn("failed to read wchan at 0x%p", addr); return (WALK_ERR); } return (wsp->walk_callback(addr, &wchan, wsp->walk_cbdata)); } /* Avoid classifying NULL pointers as part of the main stack on x86 */ #define MIN_STACK_ADDR (0x10000ul) static int whatis_walk_ulwp(uintptr_t addr, const ulwp_t *ulwp, mdb_whatis_t *w) { uintptr_t cur; lwpid_t id = ulwp->ul_lwpid; uintptr_t top, base, size; while (mdb_whatis_match(w, addr, sizeof (ulwp_t), &cur)) mdb_whatis_report_object(w, cur, addr, "allocated as thread %#r's ulwp_t\n", id); top = (uintptr_t)ulwp->ul_stktop; size = ulwp->ul_stksiz; /* * The main stack ends up being a little weird, especially if * the stack ulimit is unlimited. This tries to take that into * account. */ if (size > top) size = top; if (top > MIN_STACK_ADDR && top - size < MIN_STACK_ADDR) size = top - MIN_STACK_ADDR; base = top - size; while (mdb_whatis_match(w, base, size, &cur)) mdb_whatis_report_address(w, cur, "in [ stack tid=%#r ]\n", id); if (ulwp->ul_ustack.ss_flags & SS_ONSTACK) { base = (uintptr_t)ulwp->ul_ustack.ss_sp; size = ulwp->ul_ustack.ss_size; while (mdb_whatis_match(w, base, size, &cur)) mdb_whatis_report_address(w, cur, "in [ altstack tid=%#r ]\n", id); } return (WHATIS_WALKRET(w)); } /*ARGSUSED*/ static int whatis_run_ulwps(mdb_whatis_t *w, void *arg) { if (mdb_walk("ulwps", (mdb_walk_cb_t)whatis_walk_ulwp, w) == -1) { mdb_warn("couldn't find ulwps walker"); return (1); } return (0); } /* * ======================================================= * End of thread (previously libthread) interfaces. * ==================== threads ========================== */ int stacks_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int rval = stacks(addr, flags, argc, argv); /* * For the user-level variant of ::stacks, we don't bother caching * state, as even a very large program is unlikely to compare to the * kernel in terms of number of threads. (And if you find yourself * here in anger, frustrated about how long ::stacks is running on * your galactically complicated zillion-thread program, hopefully * you will find some solace in the irony. Okay, probably not...) */ stacks_cleanup(B_TRUE); return (rval); } typedef struct tid2ulwp_walk { lwpid_t t2u_tid; uintptr_t t2u_lwp; boolean_t t2u_found; } tid2ulwp_walk_t; /*ARGSUSED*/ static int tid2ulwp_walk(uintptr_t addr, ulwp_t *ulwp, tid2ulwp_walk_t *t2u) { if (ulwp->ul_lwpid == t2u->t2u_tid) { t2u->t2u_lwp = addr; t2u->t2u_found = B_TRUE; return (WALK_DONE); } return (WALK_NEXT); } static int tid2ulwp_impl(uintptr_t tid_addr, uintptr_t *ulwp_addrp) { tid2ulwp_walk_t t2u; bzero(&t2u, sizeof (t2u)); t2u.t2u_tid = (lwpid_t)tid_addr; if (mdb_walk("ulwp", (mdb_walk_cb_t)tid2ulwp_walk, &t2u) != 0) { mdb_warn("can't walk 'ulwp'"); return (DCMD_ERR); } if (!t2u.t2u_found) { mdb_warn("thread ID %d not found", t2u.t2u_tid); return (DCMD_ERR); } *ulwp_addrp = t2u.t2u_lwp; return (DCMD_OK); } /*ARGSUSED*/ static int tid2ulwp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t ulwp_addr; int error; if (argc != 0) return (DCMD_USAGE); error = tid2ulwp_impl(addr, &ulwp_addr); if (error == DCMD_OK) mdb_printf("%p\n", ulwp_addr); return (error); } /* * This is used by both d_tsd and d_errno, and contains the sum of all * members used by both commands. */ typedef struct mdb_libc_ulwp { void *ul_ftsd[TSD_NFAST]; tsd_t *ul_stsd; int *ul_errnop; } mdb_libc_ulwp_t; /* * Map from thread pointer to tsd for given key */ static int d_tsd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_libc_ulwp_t u; uintptr_t ulwp_addr; uintptr_t key = 0; void *element = NULL; if (mdb_getopts(argc, argv, 'k', MDB_OPT_UINTPTR, &key, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC) || key == 0) return (DCMD_USAGE); if (tid2ulwp_impl(addr, &ulwp_addr) != DCMD_OK) return (DCMD_ERR); if (mdb_ctf_vread(&u, "ulwp_t", "mdb_libc_ulwp_t", ulwp_addr, 0) == -1) return (DCMD_ERR); if (key < TSD_NFAST) { element = u.ul_ftsd[key]; } else if (u.ul_stsd != NULL) { uint_t nalloc; /* tsd_t is a union, so we can't use ctf_vread() on it. */ if (mdb_vread(&nalloc, sizeof (nalloc), (uintptr_t)&u.ul_stsd->tsd_nalloc) == -1) { mdb_warn("failed to read tsd_t at %p", u.ul_stsd); return (DCMD_ERR); } if (key < nalloc) { if (mdb_vread(&element, sizeof (element), (uintptr_t)&u.ul_stsd->tsd_data[key]) == -1) { mdb_warn("failed to read tsd_t at %p", u.ul_stsd); return (DCMD_ERR); } } } if (element == NULL && (flags & DCMD_PIPE)) return (DCMD_OK); mdb_printf("%p\n", element); return (DCMD_OK); } /* * Print percent from 16-bit binary fraction [0 .. 1] * Round up .01 to .1 to indicate some small percentage (the 0x7000 below). * * Note: This routine was copied from elfdump/common/corenote.c and modified. * */ static uint_t pct_value(ushort_t pct) { uint_t value = pct; value = ((value * 1000) + 0x7000) >> 15; /* [0 .. 1000] */ if (value >= 1000) value = 999; return (value); } static void psinfo_raw(psinfo_t psinfo) { const int minspaces = 2; const int spbcols = 20; char sysname[SYS2STR_MAX]; uint_t cpu, mem; char buff[32]; int bufflen; mdb_printf("[ NT_PRPSINFO ]\n"); mdb_printf("\tpr_state: %d\t\t\tpr_sname: %c\n", psinfo.pr_lwp.pr_state, psinfo.pr_lwp.pr_sname); mdb_printf("\tpr_zomb: %d\t\t\tpr_nice: %d\n", psinfo.pr_nzomb, psinfo.pr_lwp.pr_nice); mdb_printf("\tpr_uid: %u\t\t\tpr_gid: %u\n", psinfo.pr_uid, psinfo.pr_gid); mdb_snprintf(buff, sizeof (buff), "%d", psinfo.pr_pid); bufflen = strlen(buff); mdb_printf("\tpr_pid: %s%*spr_ppid: %d\n", buff, strlen(buff) > spbcols ? minspaces : (spbcols - bufflen), " ", psinfo.pr_ppid); mdb_printf("\tpr_pgid: %u\t\t\tpr_sid: %d\n", psinfo.pr_gid, psinfo.pr_sid); mdb_snprintf(buff, sizeof (buff), "0x%lx", (ulong_t)psinfo.pr_addr); bufflen = strlen(buff); mdb_printf("\tpr_addr: %s%*spr_size: %#x\n", buff, strlen(buff) > spbcols ? minspaces : (spbcols - bufflen), " ", (ulong_t)psinfo.pr_size); mdb_printf("\tpr_rssize: %#lx\t\tpr_wchan: %#lx\n", (ulong_t)psinfo.pr_rssize, (ulong_t)psinfo.pr_lwp.pr_wchan); mdb_printf("\tpr_start:\n\t tv_sec: %ld\t\ttv_nsec: %ld\n", psinfo.pr_start.tv_sec, psinfo.pr_start.tv_nsec); mdb_printf("\tpr_time:\n\t tv_sec: %ld\t\t\ttv_nsec: %ld\n", psinfo.pr_time.tv_sec, psinfo.pr_time.tv_nsec); mdb_printf("\tpr_pri: %d\t\t\tpr_oldpri: %d\n", psinfo.pr_lwp.pr_pri, psinfo.pr_lwp.pr_oldpri); mdb_printf("\tpr_cpu: %d\n", psinfo.pr_lwp.pr_cpu); mdb_printf("\tpr_clname: %s\n", psinfo.pr_lwp.pr_clname); mdb_printf("\tpr_fname: %s\n", psinfo.pr_fname); mdb_printf("\tpr_psargs: %s\n", psinfo.pr_psargs); mdb_printf("\tpr_syscall: [ %s ]\n", proc_sysname(psinfo.pr_lwp.pr_syscall, sysname, sizeof (sysname))); mdb_printf("\tpr_ctime:\n\t tv_sec: %ld\t\t\ttv_nsec: %ld\n", psinfo.pr_ctime.tv_sec, psinfo.pr_ctime.tv_nsec); mdb_printf("\tpr_argc: %d\t\t\tpr_argv: 0x%lx\n", psinfo.pr_argc, (ulong_t)psinfo.pr_argv); mdb_snprintf(buff, sizeof (buff), "0x%lx", (ulong_t)psinfo.pr_envp); bufflen = strlen(buff); mdb_printf("\tpr_envp: %s%*spr_wstat: %d\n", buff, strlen(buff) > spbcols ? minspaces : (spbcols - bufflen), " ", psinfo.pr_wstat); cpu = pct_value(psinfo.pr_pctcpu); mem = pct_value(psinfo.pr_pctmem); mdb_printf("\tpr_pctcpu: %u.%u%%\t\tpr_pctmem: %u.%u%%\n", cpu / 10, cpu % 10, mem / 10, mem % 10); mdb_printf("\tpr_euid: %u\t\t\tpr_egid: %u\n", psinfo.pr_euid, psinfo.pr_egid); mdb_printf("\tpr_dmodel: [%s]\n", proc_dmodelname(psinfo.pr_dmodel, buff, sizeof (buff))); } static void psinfo_sum(psinfo_t psinfo) { const int minspaces = 2; const int spbcols = 23; char buff[64]; int bufflen; int ms; mdb_printf("PID: %6d (process id)\t\t" "UID: %4u (real user id)\n", psinfo.pr_pid, psinfo.pr_uid); mdb_printf("PPID: %6d (parent process id)\tEUID: %4d" " (effective user id)\n", psinfo.pr_ppid, psinfo.pr_euid); mdb_printf("PGID: %6d (process group id)\tGID: %4u" " (real group id)\n", psinfo.pr_pgid, psinfo.pr_gid); mdb_printf("SID: %6d (session id)\t\tEGID: %4u" " (effective group id)\n", psinfo.pr_sid, psinfo.pr_egid); mdb_printf("ZONEID: %6d\t\t\t\tCONTRACT:%4d\n", psinfo.pr_zoneid, psinfo.pr_contract); mdb_printf("PROJECT:%6d \t\t\t\tTASK: %4d\n\n", psinfo.pr_projid, psinfo.pr_taskid); mdb_printf("START: %Y (wall timestamp when the process started)\n", psinfo.pr_start); ms = NSEC2MSEC(psinfo.pr_time.tv_nsec); mdb_snprintf(buff, sizeof (buff), "%ld.%d seconds", psinfo.pr_time.tv_sec, ms); bufflen = strlen(buff); mdb_printf("TIME: %s%*s" "(CPU time used by this process)\n", buff, bufflen > spbcols ? minspaces : (spbcols - bufflen), " "); ms = NSEC2MSEC(psinfo.pr_ctime.tv_nsec); mdb_snprintf(buff, sizeof (buff), "%ld.%d seconds", psinfo.pr_ctime.tv_sec, ms); mdb_printf("CTIME: %s%*s" "(CPU time used by child processes)\n", buff, bufflen > spbcols ? minspaces : (spbcols - bufflen), " "); mdb_snprintf(buff, sizeof (buff), "%s", psinfo.pr_fname); bufflen = strlen(buff); mdb_printf("FNAME: %s%*s(name of the program executed)\n", buff, bufflen > spbcols ? minspaces : (spbcols - bufflen), " "); mdb_printf("PSARGS: \"%s\"\n", psinfo.pr_psargs); } void d_psinfo_dcmd_help(void) { mdb_printf( "Prints relevant fields from psinfo_t data and\n" "most fields from NT_PRPSINFO note section\n\n" "Usage: ::psinfo [-v]\n" "Options:\n" " -v verbose output\n"); } static int d_psinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { psinfo_t psinfo; uint_t opt_v = FALSE; ssize_t nbytes; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); nbytes = mdb_get_xdata("psinfo", NULL, 0); if (nbytes <= 0) { mdb_warn("information not available for analysis"); return (DCMD_ERR); } if (mdb_get_xdata("psinfo", &psinfo, nbytes) != nbytes) { mdb_warn("failed to read psinfo information"); return (DCMD_ERR); } if (opt_v) { psinfo_raw(psinfo); } else { psinfo_sum(psinfo); } return (DCMD_OK); } typedef struct d_mutex_output { char *mo_output; struct d_mutex_output *mo_next; } d_mutex_output_t; static void d_mutex_output_push(d_mutex_output_t **head, const char *out) { d_mutex_output_t *new; size_t len = strlen(out) + 1; new = mdb_alloc(sizeof (d_mutex_output_t), UM_SLEEP | UM_GC); new->mo_next = *head; new->mo_output = mdb_alloc(len, UM_SLEEP | UM_GC); bcopy(out, new->mo_output, len); *head = new; } void d_mutex_output_reverse(d_mutex_output_t **head) { d_mutex_output_t *current, *next, *last = NULL; for (current = *head; current != NULL; current = next) { next = current->mo_next; current->mo_next = last; last = current; if (next == NULL) { break; } } *head = current; } typedef struct d_mutex_walkdata { uintptr_t mow_target; d_mutex_output_t *mow_output; } d_mutex_walkdata_t; int d_mutex_walk(uintptr_t addr, const ulwp_t *ulwp, d_mutex_walkdata_t *wd) { char buf[256]; if ((uintptr_t)ulwp->ul_wchan != wd->mow_target) return (WALK_NEXT); if (mdb_thread_name(ulwp->ul_lwpid, buf, sizeof (buf)) != 0) { mdb_snprintf(buf, sizeof (buf), "0x%p", addr); } d_mutex_output_push(&wd->mow_output, buf); return (WALK_NEXT); } static void d_mutex_help(void) { mdb_printf("%s\n", "Dump a mutex, optionally decoding flags and displaying waiters.\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -v Dump verbosely, decoding type and flags and showing waiters\n" " -f force printing as a mutex, even if it doesn't appear to be one\n"); } static int d_mutex(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { lwp_mutex_t mutex; uintptr_t owner; char buf[256]; uint_t opt_v = FALSE, opt_f = FALSE, warn; d_mutex_walkdata_t wd; d_mutex_output_t *toutput = NULL, *foutput = NULL; size_t i; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, 'f', MDB_OPT_SETBITS, TRUE, &opt_f, NULL) != argc) { return (DCMD_USAGE); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%-16s %4s %4s %4s %s\n", "ADDR", "TYPE", "FLAG", "WTRS", "OWNER"); } /* * If we aren't in a loop or a pipe, we will warn explicitly when * we can't make sense of a mutex. */ warn = (flags & (DCMD_LOOP | DCMD_PIPE)) ? FALSE : TRUE; if (mdb_vread(&mutex, sizeof (mutex), addr) != sizeof (mutex)) { if (warn) mdb_warn("failed to read mutex at 0x%p", addr); return (DCMD_ERR); } /* * It's legal to have a zero'd mutex_t in BSS -- so we can only * rely on the magic to disambiguate a mutex if it is non-zero. */ if (!opt_f && mutex.flags.magic != 0 && mutex.flags.magic != MUTEX_MAGIC) { if (!warn) return (DCMD_ERR); if (mutex.flags.magic == COND_MAGIC) { mdb_warn("0x%p is not a mutex (appears to be a " "condition variable)\n", addr); } else if (mutex.flags.magic == SEMA_MAGIC) { mdb_warn("0x%p is not a mutex (appears to be a " "semaphore)\n", addr); } else if (mutex.flags.magic == RWL_MAGIC) { mdb_warn("0x%p is not a mutex (appears to be a " "readers/writer lock)\n", addr); } else { mdb_warn("0x%p does not appear to be a mutex (expected " "0x%x, found 0x%x)\n", addr, MUTEX_MAGIC, mutex.flags.magic); } return (DCMD_ERR); } if (!opt_f) { /* * Sanity check that if we have an owner, it at least isn't * obviously not a ulwp_t. */ uintptr_t owner = mutex.mutex_owner; ulwp_t u; if (owner == (uintptr_t)NULL) { if (mutex.mutex_waiters) { if (!warn) return (DCMD_ERR); mdb_warn("0x%p does not appear to be a mutex " "(waiters, but no owner?)\n", addr); return (DCMD_ERR); } } else if (mdb_vread(&u, sizeof (u), owner) != sizeof (u) || (uintptr_t)u.ul_self != owner) { if (!warn) return (DCMD_ERR); mdb_warn("0x%p does not appear to be mutex " "(owner 0x%p does not appear to be a ulwp_t)\n", addr, owner); return (DCMD_ERR); } } mdb_printf("%-16p %4x %4x %4s ", addr, mutex.mutex_type, mutex.mutex_flag, mutex.mutex_waiters ? "yes" : "no"); if ((owner = mutex.mutex_owner) == (uintptr_t)NULL) { mdb_printf("-\n"); } else { ulwp_t u; if (mdb_vread(&u, sizeof (u), owner) != sizeof (u) || mdb_thread_name(u.ul_lwpid, buf, sizeof (buf)) != 0) { mdb_snprintf(buf, sizeof (buf), "%d", u.ul_lwpid); } mdb_printf("%p %s\n", owner, buf); } if (!opt_v) return (DCMD_OK); static struct { int val; char *str; } tvals[] = { { 0x01, "LOCK_SHARED" }, { 0x02, "LOCK_ERRORCHECK" }, { 0x04, "LOCK_RECURSIVE" }, { 0x10, "LOCK_PRIO_INHERIT" }, { 0x20, "LOCK_PRIO_PROTECT" }, { 0x40, "LOCK_ROBUST" }, /* * This is a defunct type, but an ancient (or corrupt) mutex * might have it set; indicate it if we see it. */ { 0x08, "PROCESS_ROBUST" }, { 0, "" } }; if (!(mutex.mutex_type & LOCK_SHARED)) { d_mutex_output_push(&toutput, "LOCK_NORMAL"); } for (i = 0; tvals[i].val != 0; i++) { if ((mutex.mutex_type & tvals[i].val) != 0) { d_mutex_output_push(&toutput, tvals[i].str); } } static struct { int val; char *str; } fvals[] = { { 0x1, "LOCK_OWNERDEAD" }, { 0x2, "LOCK_NOTRECOVERABLE" }, { 0x4, "LOCK_INITED" }, { 0x8, "LOCK_UNMAPPED" }, { 0x10, "LOCK_DEADLOCK" }, { 0, "" }, }; for (i = 0; fvals[i].val != 0; i++) { if ((mutex.mutex_flag & fvals[i].val) != 0) { d_mutex_output_push(&foutput, fvals[i].str); } } wd.mow_target = addr; wd.mow_output = NULL; if (mdb_walk("ulwp", (mdb_walk_cb_t)d_mutex_walk, &wd) != 0) { mdb_warn("can't walk \"ulwp\""); return (DCMD_ERR); } d_mutex_output_t *ooutput = wd.mow_output; d_mutex_output_reverse(&toutput); d_mutex_output_reverse(&foutput); d_mutex_output_reverse(&ooutput); d_mutex_output_t *thead = toutput; d_mutex_output_t *fhead = foutput; d_mutex_output_t *ohead = ooutput; mdb_printf("%21s", toutput != NULL ? "|" : ""); mdb_printf("%5s", foutput != NULL ? "|" : ""); mdb_printf("%3s\n", ooutput != NULL ? "|" : ""); boolean_t needblank = ooutput != NULL; while (toutput != NULL || foutput != NULL || ooutput != NULL) { if (toutput != NULL) { mdb_printf("%17s %s", toutput->mo_output, toutput == thead ? "<-+" : " "); toutput = toutput->mo_next; } else { mdb_printf("%21s", ""); } if (foutput != NULL) { if (ooutput != NULL) { mdb_printf("%5s", "|"); } else { if (needblank) { mdb_printf("%5s", "|"); needblank = B_FALSE; } else { mdb_printf("%7s %s", foutput == fhead ? "+->" : "", foutput->mo_output); foutput = foutput->mo_next; } } } else { mdb_printf("%5s", ""); } if (ooutput != NULL) { mdb_printf("%5s %s", ooutput == ohead ? "+->" : "", ooutput->mo_output); ooutput = ooutput->mo_next; } mdb_printf("\n"); } return (DCMD_OK); } static int d_errno(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_libc_ulwp_t u; uintptr_t ulwp_addr; int error, errval; if (argc != 0 || (flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); error = tid2ulwp_impl(addr, &ulwp_addr); if (error != DCMD_OK) return (error); /* * For historical compatibility, thread 1's errno value is stored in * a libc global variable 'errno', while each additional thread's * errno value is stored in ulwp_t->ul_errno. In addition, * ulwp_t->ul_errnop is set to the address of the thread's errno value, * (i.e. for tid 1, curthead->ul_errnop = &errno, for tid > 1, * curthread->ul_errnop = &curthread->ul_errno). * * Since errno itself uses *curthread->ul_errnop (see ___errno()) to * return the thread's current errno value, we do the same. */ if (mdb_ctf_vread(&u, "ulwp_t", "mdb_libc_ulwp_t", ulwp_addr, 0) == -1) return (DCMD_ERR); if (mdb_vread(&errval, sizeof (errval), (uintptr_t)u.ul_errnop) == -1) { mdb_warn("cannot read error value at 0x%p", u.ul_errnop); return (DCMD_ERR); } mdb_printf("%d\n", errval); return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "errno", "?", "print errno of a given TID", d_errno, NULL }, { "jmp_buf", ":", "print jmp_buf contents", d_jmp_buf, NULL }, { "mutex", ":[-f|v]", "dump out a mutex", d_mutex, d_mutex_help }, { "psinfo", "[-v]", "prints relevant psinfo_t data", d_psinfo, d_psinfo_dcmd_help }, { "siginfo", ":", "print siginfo_t structure", d_siginfo, NULL }, { "sigjmp_buf", ":", "print sigjmp_buf contents", d_sigjmp_buf, NULL }, { "stacks", "?[-afiv] [-c func] [-C func] [-m module] [-M module] ", "print unique thread stacks", stacks_dcmd, stacks_help }, { "tid2ulwp", "?", "convert TID to ulwp_t address", tid2ulwp }, { "tsd", ":-k key", "print tsd for this thread", d_tsd, NULL }, { "ucontext", ":", "print ucontext_t structure", d_ucontext, NULL }, { "ulwp", ":", "print ulwp_t structure", d_ulwp, NULL }, { "uberdata", ":", "print uberdata_t structure", d_uberdata, NULL }, { NULL } }; static const mdb_walker_t walkers[] = { { "ucontext", "walk ucontext_t uc_link list", NULL, uc_walk_step, NULL, NULL }, { "oldcontext", "walk per-lwp oldcontext pointers", oldc_walk_init, oldc_walk_step, oldc_walk_fini, NULL }, { "ulwps", "walk list of ulwp_t pointers", ulwp_walk_init, ulwp_walk_step, NULL, NULL }, { "ulwp", "walk list of ulwp_t pointers", ulwp_walk_init, ulwp_walk_step, NULL, NULL }, { "wchan", "walk wait channels", wchan_walk_init, wchan_walk_step, NULL, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { mdb_whatis_register("threads", whatis_run_ulwps, NULL, WHATIS_PRIO_EARLY, WHATIS_REG_NO_ID); return (&modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* * mdb module for libmlsvc, which contains interesting data structures * including: the share cache */ #include #include #include #include #include #include #include #define MLSVC_OBJNAME "libmlsvc.so.1" #define MLSVC_SCOPE MLSVC_OBJNAME "`" #define AFLAG 1 #define VFLAG 2 typedef struct dump_shr_args { uint_t dsa_opts; uintptr_t dsa_hdl; smb_share_t dsa_shr; } dump_shr_args_t; /*ARGSUSED*/ static int dump_shr_cb(uintptr_t addr, const void *data, void *varg) { dump_shr_args_t *args = varg; const HT_ITEM *hi = data; smb_share_t *shr = &args->dsa_shr; if (hi->hi_data == NULL) return (WALK_NEXT); if ((hi->hi_flags & HT_DELETE) != 0 && (args->dsa_opts & AFLAG) == 0) return (WALK_NEXT); if (args->dsa_opts & VFLAG) { mdb_arg_t argv; int flags = DCMD_ADDRSPEC; argv.a_type = MDB_TYPE_STRING; argv.a_un.a_str = MLSVC_SCOPE "smb_share_t"; /* Don't fail the walk if this fails. */ mdb_printf("%-?p ", hi->hi_data); mdb_call_dcmd("print", (uintptr_t)hi->hi_data, flags, 1, &argv); } else { if (mdb_vread(shr, sizeof (*shr), (uintptr_t)hi->hi_data) == -1) { mdb_warn("failed to read %s at %p", "smb_share_t", hi->hi_data); return (WALK_NEXT); } mdb_printf("%-?p ", hi->hi_data); mdb_printf("name=%s path=%s desc=\"%s\"\n", shr->shr_name, shr->shr_path, shr->shr_cmnt); } return (WALK_NEXT); } /* * *************************** Top level dcmds **************************** */ typedef struct mdb_smb_shr_cache { HT_HANDLE *sc_cache; rwlock_t sc_cache_lck; mutex_t sc_mtx; cond_t sc_cv; uint32_t sc_state; uint32_t sc_nops; } mdb_smb_shr_cache_t; static void smb_shr_cache_help(void) { mdb_printf( "Displays the list of shares in the smbd smb_shr_cache.\n" "With -a, also show deleted entries.\n" "With -v, print full smb_share_t objects.\n\n"); } /* * ::smb_shr_cache */ /*ARGSUSED*/ static int smb_shr_cache_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dump_shr_args_t *args; mdb_smb_shr_cache_t *ssc; args = mdb_zalloc(sizeof (*args), UM_SLEEP | UM_GC); if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, AFLAG, &args->dsa_opts, 'v', MDB_OPT_SETBITS, VFLAG, &args->dsa_opts, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { GElf_Sym sym; /* Locate the shr hash head. */ if (mdb_lookup_by_obj(MLSVC_OBJNAME, "smb_shr_cache", &sym)) { mdb_warn("failed to lookup `smb_shr_cache'\n"); return (DCMD_ERR); } addr = sym.st_value; } ssc = mdb_zalloc(sizeof (*ssc), UM_SLEEP | UM_GC); if (mdb_ctf_vread(ssc, MLSVC_SCOPE "smb_shr_cache_t", "mdb_smb_shr_cache_t", addr, 0) < 0) { mdb_warn("failed to read smb_shr_cache at %p", addr); return (DCMD_ERR); } /* Now walk HT_HANDLE *sc_cache */ args->dsa_hdl = (uintptr_t)ssc->sc_cache; if (mdb_pwalk(MLSVC_SCOPE "smb_ht_walker", dump_shr_cb, args, args->dsa_hdl) == -1) { mdb_warn("cannot walk smb_shr_cache list"); return (DCMD_ERR); } return (DCMD_OK); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, a list of structures * describing our walkers and a function named _mdb_init to return a pointer * to our module information. */ static const mdb_dcmd_t dcmds[] = { /* Avoiding name conflict with smbsrv`smb_shr_cache */ { "smbd_shr_cache", "[-av]", "print SMB share cache", smb_shr_cache_dcmd, smb_shr_cache_help }, { NULL } }; int smb_ht_walk_init(mdb_walk_state_t *wsp); int smb_ht_walk_step(mdb_walk_state_t *wsp); static const mdb_walker_t walkers[] = { { "smb_ht_walker", "walk an smb_hash_t structure", smb_ht_walk_init, smb_ht_walk_step, NULL, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* * walker for libsmb : smb_ht.c (hash tables) */ #include #include #include #include /* smb_ht_walk info */ struct hw_info { HT_HANDLE hw_handle; /* struct ht_handle being walked */ HT_TABLE_ENTRY hw_tblent; HT_ITEM hw_item; int hw_idx; }; /* * Walker for libsmb/smb_ht.c code. Calls the call-back function with * each HT_ITEM object. Top-level is HT_HANDLE, passed to _walk_init. */ int smb_ht_walk_init(mdb_walk_state_t *wsp) { struct hw_info *hw; uintptr_t addr = wsp->walk_addr; HT_HANDLE *ht; if (addr == 0) { mdb_printf("require address of an HT_HANDLE\n"); return (WALK_ERR); } /* * allocate the AVL walk data */ wsp->walk_data = hw = mdb_zalloc(sizeof (*hw), UM_GC|UM_SLEEP); /* * get an mdb copy of the HT_HANDLE being walked */ ht = &hw->hw_handle; if (mdb_vread(ht, sizeof (*ht), wsp->walk_addr) == -1) { mdb_warn("failed to read %s at %#lx", "HT_HANDLE", wsp->walk_addr); return (WALK_ERR); } hw->hw_idx = -1; wsp->walk_addr = 0; wsp->walk_data = hw; return (WALK_NEXT); } int smb_ht_walk_step(mdb_walk_state_t *wsp) { struct hw_info *hw = wsp->walk_data; HT_TABLE_ENTRY *he = &hw->hw_tblent; HT_ITEM *hi = &hw->hw_item; uintptr_t he_addr; int rv; while (wsp->walk_addr == 0) { if (++hw->hw_idx >= hw->hw_handle.ht_table_size) return (WALK_DONE); he_addr = (uintptr_t)hw->hw_handle.ht_table + (hw->hw_idx * sizeof (HT_TABLE_ENTRY)); if (mdb_vread(he, sizeof (*he), he_addr) == -1) { mdb_warn("failed to read %s at %p", "HT_TABLE_ENTRY", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)he->he_head; } if (mdb_vread(hi, sizeof (*hi), wsp->walk_addr) == -1) { mdb_warn("failed to read %s at %p", "HT_ITEM", wsp->walk_addr); return (WALK_ERR); } rv = wsp->walk_callback(wsp->walk_addr, hi, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)hi->hi_next; return (rv); } /* * 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. */ #include #include "../genunix/nvpair.h" static const mdb_dcmd_t dcmds[] = { { NVPAIR_DCMD_NAME, NVPAIR_DCMD_USAGE, NVPAIR_DCMD_DESCR, nvpair_print }, { NVLIST_DCMD_NAME, NVLIST_DCMD_USAGE, NVLIST_DCMD_DESCR, print_nvlist }, { NULL } }; static const mdb_walker_t walkers[] = { { NVPAIR_WALKER_NAME, NVPAIR_WALKER_DESCR, nvpair_walk_init, nvpair_walk_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2025 Oxide Computer Company */ #include #include #include #include typedef struct ps_prochandle ps_prochandle_t; /* * addr::pr_symtab [-a | n] * * -a Sort symbols by address * -n Sort symbols by name * * Given a sym_tbl_t, dump its contents in tabular form. When given '-a' or * '-n', we use the sorted tables 'sym_byaddr' or 'sym_byname', respectively. */ static int pr_symtab(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { sym_tbl_t symtab; Elf_Data data_pri; Elf_Data data_aux; Elf_Data *data; #ifdef _LP64 Elf64_Sym sym; int width = 16; #else Elf32_Sym sym; int width = 8; #endif int i, idx, count; char name[128]; int byaddr = FALSE; int byname = FALSE; uint_t *symlist; size_t symlistsz; if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &byaddr, 'n', MDB_OPT_SETBITS, TRUE, &byname, NULL) != argc) return (DCMD_USAGE); if (byaddr && byname) { mdb_warn("only one of '-a' or '-n' can be specified\n"); return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&symtab, sizeof (sym_tbl_t), addr) == -1) { mdb_warn("failed to read sym_tbl_t at %p", addr); return (DCMD_ERR); } if (symtab.sym_count == 0) { mdb_warn("no symbols present\n"); return (DCMD_ERR); } /* * As described in the libproc header Pcontrol.h, a sym_tbl_t * contains a primary and an optional auxiliary symbol table. * We treat the combination as a single table, with the auxiliary * values coming before the primary ones. * * Read the primary and auxiliary Elf_Data structs. */ if (mdb_vread(&data_pri, sizeof (Elf_Data), (uintptr_t)symtab.sym_data_pri) == -1) { mdb_warn("failed to read primary Elf_Data at %p", symtab.sym_data_pri); return (DCMD_ERR); } if ((symtab.sym_symn_aux > 0) && (mdb_vread(&data_aux, sizeof (Elf_Data), (uintptr_t)symtab.sym_data_aux) == -1)) { mdb_warn("failed to read auxiliary Elf_Data at %p", symtab.sym_data_aux); return (DCMD_ERR); } symlist = NULL; if (byaddr || byname) { uintptr_t src = byaddr ? (uintptr_t)symtab.sym_byaddr : (uintptr_t)symtab.sym_byname; symlistsz = symtab.sym_count * sizeof (uint_t); symlist = mdb_alloc(symlistsz, UM_SLEEP); if (mdb_vread(symlist, symlistsz, src) == -1) { mdb_warn("failed to read sorted symbols at %p", src); return (DCMD_ERR); } count = symtab.sym_count; } else { count = symtab.sym_symn; } mdb_printf("%%*s %*s %s%\n", width, "ADDRESS", width, "SIZE", "NAME"); for (i = 0; i < count; i++) { if (byaddr | byname) idx = symlist[i]; else idx = i; /* If index is in range of primary symtab, look it up there */ if (idx >= symtab.sym_symn_aux) { data = &data_pri; idx -= symtab.sym_symn_aux; } else { /* Look it up in the auxiliary symtab */ data = &data_aux; } if (mdb_vread(&sym, sizeof (sym), (uintptr_t)data->d_buf + idx * sizeof (sym)) == -1) { mdb_warn("failed to read symbol at %p", (uintptr_t)data->d_buf + idx * sizeof (sym)); if (symlist) mdb_free(symlist, symlistsz); return (DCMD_ERR); } if (mdb_readstr(name, sizeof (name), (uintptr_t)symtab.sym_strs + sym.st_name) == -1) { mdb_warn("failed to read symbol name at %p", symtab.sym_strs + sym.st_name); name[0] = '\0'; } mdb_printf("%0?p %0?p %s\n", sym.st_value, sym.st_size, name); } if (symlist) mdb_free(symlist, symlistsz); return (DCMD_OK); } /* * addr::pr_addr2map search * * Given a ps_prochandle_t, convert the given address to the corresponding * map_info_t. Functionally equivalent to Paddr2mptr(). */ static int pr_addr2map(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t search; ps_prochandle_t psp; map_info_t *mp; int lo, hi, mid; if (!(flags & DCMD_ADDRSPEC) || argc != 1) return (DCMD_USAGE); search = (uintptr_t)mdb_argtoull(&argv[0]); if (mdb_vread(&psp, sizeof (ps_prochandle_t), addr) == -1) { mdb_warn("failed to read ps_prochandle at %p", addr); return (DCMD_ERR); } lo = 0; hi = psp.map_count; while (lo <= hi) { mid = (lo + hi) / 2; mp = &psp.mappings[mid]; if ((addr - mp->map_pmap.pr_vaddr) < mp->map_pmap.pr_size) { mdb_printf("%#lr\n", addr + offsetof(ps_prochandle_t, mappings) + (mp - psp.mappings) * sizeof (map_info_t)); return (DCMD_OK); } if (addr < mp->map_pmap.pr_vaddr) hi = mid - 1; else lo = mid + 1; } mdb_warn("no corresponding map for %p\n", search); return (DCMD_ERR); } /* * ::walk pr_file_info * * Given a ps_prochandle_t, walk all its file_info_t structures. */ static int pr_file_info_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("pr_file_info doesn't support global walks\n"); return (WALK_ERR); } wsp->walk_addr += offsetof(ps_prochandle_t, file_head); if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("failed to walk layered 'list'"); return (WALK_ERR); } return (WALK_NEXT); } static int pr_file_info_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * ::walk pr_map_info * * Given a ps_prochandle_t, walk all its map_info_t structures. */ typedef struct { uintptr_t miw_next; int miw_count; int miw_current; } map_info_walk_t; static int pr_map_info_walk_init(mdb_walk_state_t *wsp) { ps_prochandle_t psp; map_info_walk_t *miw; if (wsp->walk_addr == 0) { mdb_warn("pr_map_info doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&psp, sizeof (ps_prochandle_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ps_prochandle at %p", wsp->walk_addr); return (WALK_ERR); } miw = mdb_alloc(sizeof (map_info_walk_t), UM_SLEEP); miw->miw_next = (uintptr_t)psp.mappings; miw->miw_count = psp.map_count; miw->miw_current = 0; wsp->walk_data = miw; return (WALK_NEXT); } static int pr_map_info_walk_step(mdb_walk_state_t *wsp) { map_info_walk_t *miw = wsp->walk_data; map_info_t m; int status; if (miw->miw_current == miw->miw_count) return (WALK_DONE); if (mdb_vread(&m, sizeof (map_info_t), miw->miw_next) == -1) { mdb_warn("failed to read map_info_t at %p", miw->miw_next); return (WALK_DONE); } status = wsp->walk_callback(miw->miw_next, &m, wsp->walk_cbdata); miw->miw_current++; miw->miw_next += sizeof (map_info_t); return (status); } static void pr_map_info_walk_fini(mdb_walk_state_t *wsp) { map_info_walk_t *miw = wsp->walk_data; mdb_free(miw, sizeof (map_info_walk_t)); } static const mdb_dcmd_t dcmds[] = { { "pr_addr2map", ":addr", "convert an adress into a map_info_t", pr_addr2map }, { "pr_symtab", ":[-a | -n]", "print the contents of a sym_tbl_t", pr_symtab }, { NULL } }; static const mdb_walker_t walkers[] = { { "pr_file_info", "given a ps_prochandle, walk its file_info " "structures", pr_file_info_walk_init, pr_file_info_walk_step }, { "pr_map_info", "given a ps_prochandle, walk its map_info structures", pr_map_info_walk_init, pr_map_info_walk_step, pr_map_info_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2002-2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include "../genunix/sysevent.h" int sysevent(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if ((flags & DCMD_LOOP) == 0) { if (mdb_pwalk_dcmd("sysevent", "sysevent", argc, argv, addr) == -1) { mdb_warn("can't walk sysevent queue"); return (DCMD_ERR); } return (DCMD_OK); } return (sysevent_buf(addr, flags, 0)); } /*ARGSUSED*/ int sysevent_handle(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ssize_t channel_name_sz; char channel_name[CLASS_LIST_FIELD_MAX]; subscriber_priv_t sub; sysevent_impl_hdl_t sysevent_hdl; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&sysevent_hdl, sizeof (sysevent_hdl), (uintptr_t)addr) == -1) { mdb_warn("failed to read sysevent handle at %p", addr); return (DCMD_ERR); } if ((channel_name_sz = mdb_readstr(channel_name, CLASS_LIST_FIELD_MAX, (uintptr_t)sysevent_hdl.sh_channel_name)) == -1) { mdb_warn("failed to read channel name at %p", sysevent_hdl.sh_channel_name); return (DCMD_ERR); } if (channel_name_sz >= CLASS_LIST_FIELD_MAX - 1) (void) strcpy(&channel_name[CLASS_LIST_FIELD_MAX - 4], "..."); if (sysevent_hdl.sh_type == SUBSCRIBER) { if (mdb_vread(&sub, sizeof (sub), (uintptr_t)sysevent_hdl.sh_priv_data) == -1) { mdb_warn("failed to read sysevent handle at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%-?s %-24s %-13s %-5s %-?s" "%\n", "ADDR", "NAME", "TYPE", "ID", "EVENT QUEUE ADDR"); mdb_printf("%-?p %-24s %-13s %-5lu %-?p\n", addr, channel_name, "SUBSCRIBER", sysevent_hdl.sh_id, (uintptr_t)sub.sp_evq_head); } else { if (DCMD_HDRSPEC(flags)) mdb_printf("%%-?s %-24s %-13s %-5s %-?s" "%\n", "ADDR", "NAME", "TYPE", "ID", "CLASS LIST ADDR"); mdb_printf("%-?p %-24s %-13s %-5lu %-?p\n", addr, channel_name, "PUBLISHER", sysevent_hdl.sh_id, (uintptr_t)sysevent_hdl.sh_priv_data + offsetof(publisher_priv_t, pp_class_hash)); } return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "sysevent", "?[-v]", "print the contents of a sysevent queue", sysevent}, { "sysevent_handle", ":", "print sysevent subscriber/publisher handle", sysevent_handle}, { "sysevent_class_list", ":", "print sysevent class list", sysevent_class_list }, { "sysevent_subclass_list", ":", "print sysevent subclass list", sysevent_subclass_list }, { NULL } }; int sysevent_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("sysevent does not support global walks"); return (WALK_ERR); } return (WALK_NEXT); } int sysevent_walk_step(mdb_walk_state_t *wsp) { int status; sysevent_queue_t se_q; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&se_q, sizeof (se_q), wsp->walk_addr) == -1) { mdb_warn("failed to read sysevent queue at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback((uintptr_t)se_q.sq_ev, NULL, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)se_q.sq_next; return (status); } static const mdb_walker_t walkers[] = { { "sysevent", "walk sysevent buffer queue", sysevent_walk_init, sysevent_walk_step, NULL }, { "sysevent_class_list", "walk sysevent subsccription class list", sysevent_class_list_walk_init, sysevent_class_list_walk_step, sysevent_class_list_walk_fini }, { "sysevent_subclass_list", "walk sysevent subsccription subclass list", sysevent_subclass_list_walk_init, sysevent_subclass_list_walk_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2017, Joyent, Inc. */ #include #include #include #include #include #include #include /* * We use this to keep track of which bucket we're in while walking * the modhash and we also cache the length of the hash */ static topo_modhash_t tmh; static uint_t hash_idx; static uintptr_t curr_pg; static uint_t is_root; static uint_t verbose; static char *pgrp; static char *tgt_scheme; static char parent[255]; /* * This structure is used by the topo_nodehash walker instances to * keep track of where they're at in the node hash */ typedef struct tnwalk_state { uint_t hash_idx; topo_nodehash_t hash; topo_nodehash_t *curr_hash; } tnwalk_state_t; static char *stab_lvls[] = {"Internal", "", "Private", "Obsolete", "External", "Unstable", "Evolving", "Stable", "Standard", "Max"}; /*ARGSUSED*/ static int topo_handle(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char uuid[36], root[36], plat[36], isa[36], machine[36], product[36]; topo_hdl_t th; /* * Read in the structure and then read in all of the string fields from * the target's addr space */ if (mdb_vread(&th, sizeof (th), addr) != sizeof (th)) { mdb_warn("failed to read topo_hdl_t at %p", addr); return (DCMD_ERR); } if (mdb_readstr(uuid, sizeof (uuid), (uintptr_t)th.th_uuid) < 0) { (void) mdb_snprintf(uuid, sizeof (uuid), "<%p>", th.th_uuid); } if (mdb_readstr(root, sizeof (root), (uintptr_t)th.th_rootdir) < 0) { (void) mdb_snprintf(root, sizeof (root), "<%p>", th.th_rootdir); } if (mdb_readstr(plat, sizeof (plat), (uintptr_t)th.th_platform) < 0) { (void) mdb_snprintf(plat, sizeof (plat), "<%p>", th.th_platform); } if (mdb_readstr(isa, sizeof (isa), (uintptr_t)th.th_isa) < 0) { (void) mdb_snprintf(isa, sizeof (isa), "<%p>", th.th_isa); } if (mdb_readstr(machine, sizeof (machine), (uintptr_t)th.th_machine) < 0) { (void) mdb_snprintf(machine, sizeof (machine), "<%p>", th.th_machine); } if (mdb_readstr(product, sizeof (product), (uintptr_t)th.th_product) < 0) { (void) mdb_snprintf(product, sizeof (product), "<%p>", th.th_product); } /* * Dump it all out in a nice pretty format and keep it to 80 chars wide */ if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-12s %-36s %-30s%\n", "FIELD", "VALUE", "DESCR"); } mdb_printf("%-12s 0x%-34p %-30s\n", "th_lock", addr + offsetof(topo_hdl_t, th_lock), "Mutex lock protecting handle"); mdb_printf("%-12s %-36s %-30s\n", "th_uuid", uuid, "UUID of the topology snapshot"); mdb_printf("%-12s %-36s %-30s\n", "th_rootdir", root, "Root directory of plugin paths"); mdb_printf("%-12s %-36s %-30s\n", "th_platform", plat, "Platform name"); mdb_printf("%-12s %-36s %-30s\n", "th_isa", isa, "ISA name"); mdb_printf("%-12s %-36s %-30s\n", "th_machine", machine, "Machine name"); mdb_printf("%-12s %-36s %-30s\n", "th_product", product, "Product name"); mdb_printf("%-12s 0x%-34p %-30s\n", "th_di", th.th_di, "Handle to the root of the devinfo tree"); mdb_printf("%-12s 0x%-34p %-30s\n", "th_pi", th.th_pi, "Handle to the root of the PROM tree"); mdb_printf("%-12s 0x%-34p %-30s\n", "th_modhash", th.th_modhash, "Module hash"); mdb_printf("%-12s %-36s %-30s\n", "th_trees", "", "Scheme-specific topo tree list"); mdb_printf(" %-12s 0x%-34p %-30s\n", "l_prev", th.th_trees.l_prev, ""); mdb_printf(" %-12s 0x%-34p %-30s\n", "l_next", th.th_trees.l_next, ""); mdb_printf("%-12s 0x%-34p %-30s\n", "th_alloc", th.th_alloc, "Allocators"); mdb_printf("%-12s %-36d %-30s\n", "tm_ernno", th.th_errno, "errno"); mdb_printf("%-12s %-36d %-30s\n", "tm_debug", th.th_debug, "Debug mask"); mdb_printf("%-12s %-36d %-30s\n", "tm_dbout", th.th_dbout, "Debug channel"); return (DCMD_OK); } /*ARGSUSED*/ static int topo_module(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char name[36], path[36], root[36]; topo_mod_t tm; /* * Read in the structure and then read in all of the string fields from * the target's addr space */ if (mdb_vread(&tm, sizeof (tm), addr) != sizeof (tm)) { mdb_warn("failed to read topo_mod_t at %p", addr); return (DCMD_ERR); } if (mdb_readstr(name, sizeof (name), (uintptr_t)tm.tm_name) < 0) { (void) mdb_snprintf(name, sizeof (name), "<%p>", tm.tm_name); } if (mdb_readstr(path, sizeof (path), (uintptr_t)tm.tm_path) < 0) { (void) mdb_snprintf(path, sizeof (path), "<%p>", tm.tm_path); } if (mdb_readstr(root, sizeof (root), (uintptr_t)tm.tm_rootdir) < 0) { (void) mdb_snprintf(root, sizeof (root), "<%p>", tm.tm_rootdir); } /* * Dump it all out in a nice pretty format and keep it to 80 chars wide */ if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-12s %-36s %-30s%\n", "FIELD", "VALUE", "DESCR"); } mdb_printf("%-12s 0x%-34p %-30s\n", "tm_lock", addr + offsetof(topo_mod_t, tm_lock), "Lock for tm_cv/owner/flags/refs"); mdb_printf("%-12s 0x%-34p %-30s\n", "tm_cv", addr + offsetof(topo_mod_t, tm_cv), "Module condition variable"); if (tm.tm_busy) mdb_printf("%-12s %-36s %-30s\n", "tm_busy", "TRUE", "Busy indicator"); else mdb_printf("%-12s %-36s %-30s\n", "tm_busy", "FALSE", "Busy indicator"); mdb_printf("%-12s 0x%-34p %-30s\n", "tm_next", tm.tm_next, "Next module in hash chain"); mdb_printf("%-12s 0x%-34p %-30s\n", "tm_hdl", tm.tm_hdl, "Topo handle for this module"); mdb_printf("%-12s 0x%-34p %-30s\n", "tm_alloc", tm.tm_alloc, "Allocators"); mdb_printf("%-12s %-36s %-30s\n", "tm_name", name, "Basename of module"); mdb_printf("%-12s %-36s %-30s\n", "tm_path", path, "Full pathname of module"); mdb_printf("%-12s %-36s %-30s\n", "tm_rootdir", root, "Relative root directory of module"); mdb_printf("%-12s %-36u %-30s\n", "tm_refs", tm.tm_refs, "Module reference count"); mdb_printf("%-12s %-36u %-30s\n", "tm_flags", tm.tm_flags, "Module flags"); if (TOPO_MOD_INIT & tm.tm_flags) { mdb_printf("%-12s %-36s %-30s\n", "", "TOPO_MOD_INIT", "Module init completed"); } if (TOPO_MOD_FINI & tm.tm_flags) { mdb_printf("%-12s %-36s %-30s\n", "", "TOPO_MOD_FINI", "Module fini completed"); } if (TOPO_MOD_REG & tm.tm_flags) { mdb_printf("%-12s %-36s %-30s\n", "", "TOPO_MOD_REG", "Module registered"); } if (TOPO_MOD_UNREG & tm.tm_flags) { mdb_printf("%-12s %-36s %-30s\n", "", "TOPO_MOD_UNREG", "Module unregistered"); } mdb_printf("%-12s %-36u %-30s\n", "tm_debug", tm.tm_debug, "Debug printf mask"); mdb_printf("%-12s 0x%-34p %-30s\n", "tm_data", tm.tm_data, "Private rtld/builtin data"); mdb_printf("%-12s 0x%-34p %-30s\n", "tm_mops", tm.tm_mops, "Module class ops vector"); mdb_printf("%-12s 0x%-34p %-30s\n", "tm_info", tm.tm_info, "Module info registered with handle"); mdb_printf("%-12s %-36d %-30s\n", "tm_ernno", tm.tm_errno, "Module errno"); return (DCMD_OK); } /*ARGSUSED*/ static int topo_node(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char name[36]; tnode_t tn; if (!addr) return (DCMD_ERR); /* * Read in the structure and then read in all of the string fields from * the target's addr space */ if (mdb_vread(&tn, sizeof (tn), addr) != sizeof (tn)) { mdb_warn("failed to read tnode_t at %p", addr); return (DCMD_ERR); } if (mdb_readstr(name, sizeof (name), (uintptr_t)tn.tn_name) < 0) { (void) mdb_snprintf(name, sizeof (name), "<%p>", tn.tn_name); } /* * Dump it all out in a nice pretty format and keep it to 80 chars wide */ if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-12s %-36s %-30s%\n", "FIELD", "VALUE", "DESCR"); } mdb_printf("%-12s 0x%-34p %-30s\n", "tn_lock", addr + offsetof(tnode_t, tn_lock), "Lock protecting node members"); mdb_printf("%-12s %-36s %-30s\n", "tn_name", name, "Node name"); mdb_printf("%-12s %-36d %-30s\n", "tn_instance", tn.tn_instance, "Node instance"); mdb_printf("%-12s %-36d %-30s\n", "tn_state", tn.tn_state, "Node state"); if (TOPO_NODE_INIT & tn.tn_state) { mdb_printf("%-12s %-36s %-30s\n", "", "TOPO_NODE_INIT", ""); } if (TOPO_NODE_ROOT & tn.tn_state) { mdb_printf("%-12s %-36s %-30s\n", "", "TOPO_NODE_ROOT", ""); } if (TOPO_NODE_BOUND & tn.tn_state) { mdb_printf("%-12s %-36s %-30s\n", "", "TOPO_NODE_BOUND", ""); } if (TOPO_NODE_LINKED & tn.tn_state) { mdb_printf("%-12s %-36s %-30s\n", "", "TOPO_NODE_LINKED", ""); } mdb_printf("%-12s %-36d %-30s\n", "tn_fflags", tn.tn_fflags, "FMRI flags"); mdb_printf("%-12s 0x%-34p %-30s\n", "tn_parent", tn.tn_parent, "Node parent"); mdb_printf("%-12s 0x%-34p %-30s\n", "tn_phash", tn.tn_phash, "Parent hash bucket"); mdb_printf("%-12s 0x%-34p %-30s\n", "tn_hdl", tn.tn_hdl, "Topo handle"); mdb_printf("%-12s 0x%-34p %-30s\n", "tn_enum", tn.tn_enum, "Enumerator module"); mdb_printf("%-12s %-36s %-30s\n", "tn_children", "", "Hash table of child nodes"); mdb_printf(" %-12s 0x%-34p\n", "l_prev", tn.tn_children.l_prev); mdb_printf(" %-12s 0x%-34p\n", "l_next", tn.tn_children.l_next); mdb_printf("%-12s 0x%-34p %-30s\n", "tn_pgroups", &(tn.tn_pgroups), "Property group list"); mdb_printf("%-12s 0x%-34p %-30s\n", "tn_methods", &(tn.tn_methods), "Registered method list"); mdb_printf("%-12s 0x%-34p %-30s\n", "tn_priv", tn.tn_priv, "Private enumerator data"); mdb_printf("%-12s %-36d %-30s\n", "tn_refs", tn.tn_refs, "Node reference count"); return (DCMD_OK); } /*ARGSUSED*/ static int find_tree_root(uintptr_t addr, const void *data, void *arg) { ttree_t *tree = (ttree_t *)data; char scheme[36]; if (mdb_readstr(scheme, sizeof (scheme), (uintptr_t)tree->tt_scheme) < 0) { (void) mdb_snprintf(scheme, sizeof (scheme), "<%p>", tree->tt_scheme); } if (strncmp(tgt_scheme, scheme, 36) == 0) { *((tnode_t **)arg) = tree->tt_root; return (WALK_DONE); } return (WALK_NEXT); } static void dump_propmethod(uintptr_t addr) { topo_propmethod_t pm; char mname[32]; if (mdb_vread(&pm, sizeof (pm), addr) != sizeof (pm)) { mdb_warn("failed to read topo_propmethod at %p", addr); return; } if (mdb_readstr(mname, sizeof (mname), (uintptr_t)pm.tpm_name) < 0) { (void) mdb_snprintf(mname, sizeof (mname), "<%p>", pm.tpm_name); } mdb_printf(" method: %-32s version: %-16d args: %p\n", mname, pm.tpm_version, pm.tpm_args); } /* * Dump the given property value. For the actual property values * we dump a pointer to the nvlist which can be decoded using the ::nvlist * dcmd from the libnvpair MDB module */ /*ARGSUSED*/ static int dump_propval(uintptr_t addr, const void *data, void *arg) { topo_proplist_t *plistp = (topo_proplist_t *)data; topo_propval_t pval; char name[32], *type; if (mdb_vread(&pval, sizeof (pval), (uintptr_t)plistp->tp_pval) != sizeof (pval)) { mdb_warn("failed to read topo_propval_t at %p", plistp->tp_pval); return (WALK_ERR); } if (mdb_readstr(name, sizeof (name), (uintptr_t)pval.tp_name) < 0) { (void) mdb_snprintf(name, sizeof (name), "<%p>", pval.tp_name); } switch (pval.tp_type) { case TOPO_TYPE_BOOLEAN: type = "boolean"; break; case TOPO_TYPE_INT32: type = "int32"; break; case TOPO_TYPE_UINT32: type = "uint32"; break; case TOPO_TYPE_INT64: type = "int64"; break; case TOPO_TYPE_UINT64: type = "uint64"; break; case TOPO_TYPE_STRING: type = "string"; break; case TOPO_TYPE_FMRI: type = "fmri"; break; case TOPO_TYPE_INT32_ARRAY: type = "int32[]"; break; case TOPO_TYPE_UINT32_ARRAY: type = "uint32[]"; break; case TOPO_TYPE_INT64_ARRAY: type = "int64[]"; break; case TOPO_TYPE_UINT64_ARRAY: type = "uint64[]"; break; case TOPO_TYPE_STRING_ARRAY: type = "string[]"; break; case TOPO_TYPE_FMRI_ARRAY: type = "fmri[]"; break; default: type = "unknown type"; } mdb_printf(" %-32s %-16s value: %p\n", name, type, pval.tp_val); if (pval.tp_method != NULL) dump_propmethod((uintptr_t)pval.tp_method); return (WALK_NEXT); } /* * Dumps the contents of the property group. */ /*ARGSUSED*/ static int dump_pgroup(uintptr_t addr, const void *data, void *arg) { topo_pgroup_t *pgp = (topo_pgroup_t *)data; topo_ipgroup_info_t ipg; char buf[32]; if (mdb_vread(&ipg, sizeof (ipg), (uintptr_t)pgp->tpg_info) != sizeof (ipg)) { mdb_warn("failed to read topo_ipgroup_info_t at %p", pgp->tpg_info); return (WALK_ERR); } if (mdb_readstr(buf, sizeof (buf), (uintptr_t)ipg.tpi_name) < 0) { mdb_warn("failed to read string at %p", ipg.tpi_name); return (WALK_ERR); } /* * If this property group is the one we're interested in or if the user * specified the "all" property group, we'll dump it */ if ((strncmp(pgrp, buf, sizeof (buf)) == 0) || (strncmp(pgrp, "all", sizeof (buf)) == 0)) { mdb_printf(" group: %-32s version: %d, stability: %s/%s\n", buf, ipg.tpi_version, stab_lvls[ipg.tpi_namestab], stab_lvls[ipg.tpi_datastab]); (void) mdb_pwalk("topo_proplist", dump_propval, NULL, curr_pg); } return (WALK_NEXT); } /* * Recursive function to dump the specified node and all of it's children */ /*ARGSUSED*/ static int dump_tnode(uintptr_t addr, const void *data, void *arg) { tnode_t node; char pname[255], buf[80], old_pname[255]; if (!addr) { return (WALK_NEXT); } if (mdb_vread(&node, sizeof (node), addr) != sizeof (node)) { mdb_warn("failed to read tnode_t at %p", addr); return (WALK_ERR); } if (mdb_readstr(buf, sizeof (buf), (uintptr_t)node.tn_name) < 0) { (void) mdb_snprintf(buf, sizeof (buf), "<%p>", node.tn_name); } if (is_root) { mdb_snprintf(pname, sizeof (pname), "%s", parent); is_root = 0; } else { mdb_snprintf(pname, sizeof (pname), "%s/%s=%u", parent, buf, node.tn_instance); if (verbose) mdb_printf("%s\n tnode_t: %p\n", pname, addr); else mdb_printf("%s\n", pname); } mdb_snprintf(old_pname, sizeof (old_pname), "%s", parent); mdb_snprintf(parent, sizeof (parent), "%s", pname); if (pgrp) (void) mdb_pwalk("topo_pgroup", dump_pgroup, NULL, addr); (void) mdb_pwalk("topo_nodehash", dump_tnode, NULL, addr); mdb_snprintf(parent, sizeof (parent), "%s", old_pname); return (WALK_NEXT); } /* * Given a topo_hdl_t *, the topo dcmd dumps the topo tree. The format of the * output is modeled after fmtopo. Like fmtopo, by default, we'll dump the * "hc" scheme tree. The user can optionally specify a different tree via the * "-s " option. * * Specifying the "-v" option provides more verbose output. Currently it * outputs the tnode_t * addr for each node, which is useful if you want to * dump it with the topo_node dcmd. * * The functionality of the "-P" option is similar to fmtopo. */ /*ARGSUSED*/ static int fmtopo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char product[36], *opt_s = NULL, *opt_P = NULL; topo_hdl_t th; tnode_t *tree_root; uint_t opt_v = FALSE; char *def_scheme = "hc"; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, 's', MDB_OPT_STR, &opt_s, 'P', MDB_OPT_STR, &opt_P, NULL) != argc) { return (DCMD_USAGE); } if (opt_s) { tgt_scheme = opt_s; } else { tgt_scheme = def_scheme; } pgrp = opt_P; verbose = opt_v; is_root = 1; /* * Read in the topo_handle and some of its string fields from * the target's addr space */ if (mdb_vread(&th, sizeof (th), addr) != sizeof (th)) { mdb_warn("failed to read topo_hdl_t at %p", addr); return (DCMD_ERR); } if (mdb_readstr(product, sizeof (product), (uintptr_t)th.th_product) < 0) { (void) mdb_snprintf(product, sizeof (product), "<%p>", th.th_product); } mdb_snprintf(parent, sizeof (parent), "%s://:product-id=%s", tgt_scheme, product); /* * Walk the list of topo trees, looking for the one that is for the * scheme we're interested in. */ tree_root = NULL; mdb_pwalk("topo_tree", find_tree_root, &tree_root, addr); if (! tree_root) { mdb_warn("failed to find a topo tree for scheme %s\n", tgt_scheme); return (DCMD_ERR); } return (dump_tnode((uintptr_t)tree_root, NULL, NULL)); } static int ttree_walk_init(mdb_walk_state_t *wsp) { topo_hdl_t th; if (wsp->walk_addr == 0) { mdb_warn("NULL topo_hdl_t passed in"); return (WALK_ERR); } if (mdb_vread(&th, sizeof (th), wsp->walk_addr) != sizeof (th)) { mdb_warn("failed to read topo_hdl_t at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)th.th_trees.l_next; wsp->walk_data = mdb_alloc(sizeof (ttree_t), UM_SLEEP); return (WALK_NEXT); } static int ttree_walk_step(mdb_walk_state_t *wsp) { int rv; ttree_t *tree; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (ttree_t), wsp->walk_addr) != sizeof (ttree_t)) { mdb_warn("failed to read ttree_t at %p", wsp->walk_addr); return (WALK_ERR); } rv = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); tree = (ttree_t *)wsp->walk_data; wsp->walk_addr = (uintptr_t)tree->tt_list.l_next; return (rv); } static void ttree_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (ttree_t)); } static int tmod_walk_init(mdb_walk_state_t *wsp) { topo_hdl_t th; if (wsp->walk_addr == 0) { mdb_warn("NULL topo_hdl_t passed in"); return (WALK_ERR); } if (mdb_vread(&th, sizeof (th), wsp->walk_addr) != sizeof (th)) { mdb_warn("failed to read topo_hdl_t at %p", wsp->walk_addr); return (WALK_ERR); } if (mdb_vread(&tmh, sizeof (topo_modhash_t), (uintptr_t)th.th_modhash) == -1) { mdb_warn("failed to read topo_modhash_t at %p", wsp->walk_addr); return (WALK_DONE); } hash_idx = 0; if (mdb_vread(&(wsp->walk_addr), sizeof (uintptr_t *), (uintptr_t)(tmh.mh_hash)) != sizeof (tnode_t *)) { mdb_warn("failed to read %u bytes at %p", sizeof (tnode_t *), tmh.mh_hash); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (topo_mod_t), UM_SLEEP); return (WALK_NEXT); } static int tmod_walk_step(mdb_walk_state_t *wsp) { int rv; topo_mod_t *tm; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (topo_mod_t), wsp->walk_addr) == -1) { mdb_warn("failed to read topo_mod_t at %p", wsp->walk_addr); return (WALK_DONE); } rv = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); tm = (topo_mod_t *)wsp->walk_data; if (tm->tm_next) wsp->walk_addr = (uintptr_t)tm->tm_next; else if (++hash_idx < tmh.mh_hashlen) if (mdb_vread(&(wsp->walk_addr), sizeof (uintptr_t *), (uintptr_t)(tmh.mh_hash+hash_idx)) != sizeof (tnode_t *)) { mdb_warn("failed to read %u bytes at %p", sizeof (tnode_t *), tmh.mh_hash+hash_idx); return (DCMD_ERR); } else wsp->walk_addr = 0; return (rv); } static void tmod_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (topo_mod_t)); } static int tpg_walk_init(mdb_walk_state_t *wsp) { tnode_t node; if (wsp->walk_addr == 0) { mdb_warn("NULL tnode_t passed in"); return (WALK_ERR); } if (mdb_vread(&node, sizeof (node), wsp->walk_addr) != sizeof (node)) { mdb_warn("failed to read tnode_t at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)node.tn_pgroups.l_next; wsp->walk_data = mdb_alloc(sizeof (topo_pgroup_t), UM_SLEEP); return (WALK_NEXT); } static int tpg_walk_step(mdb_walk_state_t *wsp) { int rv; topo_pgroup_t *tpgp; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (topo_pgroup_t), wsp->walk_addr) == -1) { mdb_warn("failed to read topo_pgroup_t at %p", wsp->walk_addr); return (WALK_DONE); } curr_pg = wsp->walk_addr; rv = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); tpgp = (topo_pgroup_t *)wsp->walk_data; wsp->walk_addr = (uintptr_t)tpgp->tpg_list.l_next; return (rv); } static void tpg_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (topo_pgroup_t)); } static int tpl_walk_init(mdb_walk_state_t *wsp) { topo_pgroup_t pg; if (wsp->walk_addr == 0) { mdb_warn("NULL topo_pgroup_t passed in"); return (WALK_ERR); } if (mdb_vread(&pg, sizeof (pg), wsp->walk_addr) != sizeof (pg)) { mdb_warn("failed to read topo_pgroup_t at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)pg.tpg_pvals.l_next; wsp->walk_data = mdb_alloc(sizeof (topo_proplist_t), UM_SLEEP); return (WALK_NEXT); } static int tpl_walk_step(mdb_walk_state_t *wsp) { int rv; topo_proplist_t *plp; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (topo_proplist_t), wsp->walk_addr) == -1) { mdb_warn("failed to read topo_proplist_t at %p", wsp->walk_addr); return (WALK_DONE); } plp = (topo_proplist_t *)wsp->walk_data; rv = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)plp->tp_list.l_next; return (rv); } static void tpl_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (topo_proplist_t)); } static int tnh_walk_init(mdb_walk_state_t *wsp) { tnode_t node; tnwalk_state_t *state; if (wsp->walk_addr == 0) { mdb_warn("NULL tnode_t passed in"); return (WALK_ERR); } if (mdb_vread(&node, sizeof (node), wsp->walk_addr) != sizeof (node)) { mdb_warn("failed to read tnode_t at %p", wsp->walk_addr); return (WALK_ERR); } state = mdb_zalloc(sizeof (tnwalk_state_t), UM_SLEEP); state->curr_hash = (topo_nodehash_t *)node.tn_children.l_next; state->hash_idx = 0; wsp->walk_data = state; return (WALK_NEXT); } static int tnh_walk_step(mdb_walk_state_t *wsp) { tnwalk_state_t *state = wsp->walk_data; int rv, i = state->hash_idx++; tnode_t *npp; if (state->curr_hash == NULL) return (WALK_DONE); if (mdb_vread(&(state->hash), sizeof (topo_nodehash_t), (uintptr_t)state->curr_hash) != sizeof (topo_nodehash_t)) { mdb_warn("failed to read topo_nodehash_t at %p", (uintptr_t)state->curr_hash); return (WALK_ERR); } if (mdb_vread(&npp, sizeof (tnode_t *), (uintptr_t)(state->hash.th_nodearr+i)) != sizeof (tnode_t *)) { mdb_warn("failed to read %u bytes at %p", sizeof (tnode_t *), state->hash.th_nodearr+i); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)npp; rv = wsp->walk_callback(wsp->walk_addr, state, wsp->walk_cbdata); if (state->hash_idx >= state->hash.th_arrlen) { /* * move on to the next child hash bucket */ state->curr_hash = (topo_nodehash_t *)(state->hash.th_list.l_next); state->hash_idx = 0; } return (rv); } static void tnh_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (tnwalk_state_t)); } static int tlist_walk_init(mdb_walk_state_t *wsp) { topo_list_t tl; if (wsp->walk_addr == 0) { mdb_warn("NULL topo_list_t passed in\n"); return (WALK_ERR); } if (mdb_vread(&tl, sizeof (tl), wsp->walk_addr) == -1) { mdb_warn("failed to read topo_list_t at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)tl.l_next; wsp->walk_data = mdb_alloc(sizeof (topo_list_t), UM_SLEEP | UM_GC); return (WALK_NEXT); } static int tlist_walk_step(mdb_walk_state_t *wsp) { int rv; topo_list_t *tl; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (topo_list_t), wsp->walk_addr) == -1) { mdb_warn("failed to read topo_list_t at %p", wsp->walk_addr); return (WALK_DONE); } tl = (topo_list_t *)wsp->walk_data; rv = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)tl->l_next; return (rv); } static const mdb_dcmd_t dcmds[] = { { "topo_handle", "", "print contents of a topo handle", topo_handle, NULL }, { "topo_module", "", "print contents of a topo module handle", topo_module, NULL }, { "topo_node", "", "print contents of a topo node", topo_node, NULL }, { "fmtopo", "[-P ][-s ][-v]", "print topology of the given handle", fmtopo, NULL }, { NULL } }; static const mdb_walker_t walkers[] = { { "topo_tree", "walk the tree list for a given topo handle", ttree_walk_init, ttree_walk_step, ttree_walk_fini, NULL }, { "topo_module", "walk the module hash for a given topo handle", tmod_walk_init, tmod_walk_step, tmod_walk_fini, NULL }, { "topo_pgroup", "walk the property groups for a given topo node", tpg_walk_init, tpg_walk_step, tpg_walk_fini, NULL }, { "topo_proplist", "walk the property list for a given property group", tpl_walk_init, tpl_walk_step, tpl_walk_fini, NULL }, { "topo_nodehash", "walk the child nodehash for a given topo node", tnh_walk_init, tnh_walk_step, tnh_walk_fini, NULL }, { "topo_list", "walk a topo_list_t linked list", tlist_walk_init, tlist_walk_step, NULL, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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. */ #include "umem.h" #include #include #include #include #include #include #include #include "leaky_impl.h" #include "misc.h" #include "proc_kludges.h" #include "umem_pagesize.h" /* * This file defines the libumem target for ../genunix/leaky.c. * * See ../genunix/leaky_impl.h for the target interface definition. */ /* * leaky_subr_dump_start()/_end() depend on the ordering of TYPE_VMEM, * TYPE_MMAP and TYPE_SBRK. */ #define TYPE_MMAP 0 /* lkb_data is the size */ #define TYPE_SBRK 1 /* lkb_data is the size */ #define TYPE_VMEM 2 /* lkb_data is the vmem_seg's size */ #define TYPE_CACHE 3 /* lkb_cid is the bufctl's cache */ #define TYPE_UMEM 4 /* lkb_cid is the bufctl's cache */ #define LKM_CTL_BUFCTL 0 /* normal allocation, PTR is bufctl */ #define LKM_CTL_VMSEG 1 /* oversize allocation, PTR is vmem_seg_t */ #define LKM_CTL_MEMORY 2 /* non-umem mmap or brk, PTR is region start */ #define LKM_CTL_CACHE 3 /* normal alloc, non-debug, PTR is cache */ #define LKM_CTL_MASK 3L /* * create a lkm_bufctl from a pointer and a type */ #define LKM_CTL(ptr, type) (LKM_CTLPTR(ptr) | (type)) #define LKM_CTLPTR(ctl) ((uintptr_t)(ctl) & ~(LKM_CTL_MASK)) #define LKM_CTLTYPE(ctl) ((uintptr_t)(ctl) & (LKM_CTL_MASK)) static uintptr_t leak_brkbase; static uintptr_t leak_brksize; #define LEAKY_INBRK(ptr) \ (((uintptr_t)(ptr) - leak_brkbase) < leak_brksize) typedef struct leaky_seg_info { uintptr_t ls_start; uintptr_t ls_end; } leaky_seg_info_t; typedef struct leaky_maps { leaky_seg_info_t *lm_segs; uintptr_t lm_seg_count; uintptr_t lm_seg_max; pstatus_t *lm_pstatus; leak_mtab_t **lm_lmp; } leaky_maps_t; /*ARGSUSED*/ static int leaky_mtab(uintptr_t addr, const umem_bufctl_audit_t *bcp, leak_mtab_t **lmp) { leak_mtab_t *lm = (*lmp)++; lm->lkm_base = (uintptr_t)bcp->bc_addr; lm->lkm_bufctl = LKM_CTL(addr, LKM_CTL_BUFCTL); return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_mtab_addr(uintptr_t addr, void *ignored, leak_mtab_t **lmp) { leak_mtab_t *lm = (*lmp)++; lm->lkm_base = addr; return (WALK_NEXT); } static int leaky_seg(uintptr_t addr, const vmem_seg_t *seg, leak_mtab_t **lmp) { leak_mtab_t *lm = (*lmp)++; lm->lkm_base = seg->vs_start; lm->lkm_limit = seg->vs_end; lm->lkm_bufctl = LKM_CTL(addr, LKM_CTL_VMSEG); return (WALK_NEXT); } static int leaky_vmem(uintptr_t addr, const vmem_t *vmem, leak_mtab_t **lmp) { if (strcmp(vmem->vm_name, "umem_oversize") != 0 && strcmp(vmem->vm_name, "umem_memalign") != 0) return (WALK_NEXT); if (mdb_pwalk("vmem_alloc", (mdb_walk_cb_t)leaky_seg, lmp, addr) == -1) mdb_warn("can't walk vmem_alloc for %s (%p)", vmem->vm_name, addr); return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_estimate_vmem(uintptr_t addr, const vmem_t *vmem, size_t *est) { if (strcmp(vmem->vm_name, "umem_oversize") != 0 && strcmp(vmem->vm_name, "umem_memalign") != 0) return (WALK_NEXT); *est += (int)(vmem->vm_kstat.vk_alloc - vmem->vm_kstat.vk_free); return (WALK_NEXT); } static int leaky_seg_cmp(const void *l, const void *r) { const leaky_seg_info_t *lhs = (const leaky_seg_info_t *)l; const leaky_seg_info_t *rhs = (const leaky_seg_info_t *)r; if (lhs->ls_start < rhs->ls_start) return (-1); if (lhs->ls_start > rhs->ls_start) return (1); return (0); } static ssize_t leaky_seg_search(uintptr_t addr, leaky_seg_info_t *listp, unsigned count) { ssize_t left = 0, right = count - 1, guess; while (right >= left) { guess = (right + left) >> 1; if (addr < listp[guess].ls_start) { right = guess - 1; continue; } if (addr >= listp[guess].ls_end) { left = guess + 1; continue; } return (guess); } return (-1); } /*ARGSUSED*/ static int leaky_count(uintptr_t addr, void *unused, size_t *total) { ++*total; return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_read_segs(uintptr_t addr, const vmem_seg_t *seg, leaky_maps_t *lmp) { leaky_seg_info_t *my_si = lmp->lm_segs + lmp->lm_seg_count; if (seg->vs_start == seg->vs_end && seg->vs_start == 0) return (WALK_NEXT); if (lmp->lm_seg_count++ >= lmp->lm_seg_max) return (WALK_ERR); my_si->ls_start = seg->vs_start; my_si->ls_end = seg->vs_end; return (WALK_NEXT); } /* ARGSUSED */ static int leaky_process_anon_mappings(uintptr_t ignored, const prmap_t *pmp, leaky_maps_t *lmp) { uintptr_t start = pmp->pr_vaddr; uintptr_t end = pmp->pr_vaddr + pmp->pr_size; leak_mtab_t *lm; pstatus_t *Psp = lmp->lm_pstatus; uintptr_t brk_start = Psp->pr_brkbase; uintptr_t brk_end = Psp->pr_brkbase + Psp->pr_brksize; int has_brk = 0; int in_vmem = 0; /* * This checks if there is any overlap between the segment and the brk. */ if (end > brk_start && start < brk_end) has_brk = 1; if (leaky_seg_search(start, lmp->lm_segs, lmp->lm_seg_count) != -1) in_vmem = 1; /* * We only want anonymous, mmaped memory. That means: * * 1. Must be read-write * 2. Cannot be shared * 3. Cannot have backing * 4. Cannot be in the brk * 5. Cannot be part of the vmem heap. */ if ((pmp->pr_mflags & (MA_READ | MA_WRITE)) == (MA_READ | MA_WRITE) && (pmp->pr_mflags & MA_SHARED) == 0 && (pmp->pr_mapname[0] == 0) && !has_brk && !in_vmem) { dprintf(("mmaped region: [%p, %p)\n", start, end)); lm = (*lmp->lm_lmp)++; lm->lkm_base = start; lm->lkm_limit = end; lm->lkm_bufctl = LKM_CTL(pmp->pr_vaddr, LKM_CTL_MEMORY); } return (WALK_NEXT); } static void leaky_handle_sbrk(leaky_maps_t *lmp) { uintptr_t brkbase = lmp->lm_pstatus->pr_brkbase; uintptr_t brkend = brkbase + lmp->lm_pstatus->pr_brksize; leak_mtab_t *lm; leaky_seg_info_t *segs = lmp->lm_segs; int x, first = -1, last = -1; dprintf(("brk: [%p, %p)\n", brkbase, brkend)); for (x = 0; x < lmp->lm_seg_count; x++) { if (segs[x].ls_start >= brkbase && segs[x].ls_end <= brkend) { if (first == -1) first = x; last = x; } } if (brkbase == brkend) { dprintf(("empty brk -- do nothing\n")); } else if (first == -1) { dprintf(("adding [%p, %p) whole brk\n", brkbase, brkend)); lm = (*lmp->lm_lmp)++; lm->lkm_base = brkbase; lm->lkm_limit = brkend; lm->lkm_bufctl = LKM_CTL(brkbase, LKM_CTL_MEMORY); } else { uintptr_t curbrk = P2ROUNDUP(brkbase, umem_pagesize); if (curbrk != segs[first].ls_start) { dprintf(("adding [%p, %p) in brk, before first seg\n", brkbase, segs[first].ls_start)); lm = (*lmp->lm_lmp)++; lm->lkm_base = brkbase; lm->lkm_limit = segs[first].ls_start; lm->lkm_bufctl = LKM_CTL(brkbase, LKM_CTL_MEMORY); curbrk = segs[first].ls_start; } else if (curbrk != brkbase) { dprintf(("ignore [%p, %p) -- realign\n", brkbase, curbrk)); } for (x = first; x <= last; x++) { if (curbrk < segs[x].ls_start) { dprintf(("adding [%p, %p) in brk\n", curbrk, segs[x].ls_start)); lm = (*lmp->lm_lmp)++; lm->lkm_base = curbrk; lm->lkm_limit = segs[x].ls_start; lm->lkm_bufctl = LKM_CTL(curbrk, LKM_CTL_MEMORY); } curbrk = segs[x].ls_end; } if (curbrk < brkend) { dprintf(("adding [%p, %p) in brk, after last seg\n", curbrk, brkend)); lm = (*lmp->lm_lmp)++; lm->lkm_base = curbrk; lm->lkm_limit = brkend; lm->lkm_bufctl = LKM_CTL(curbrk, LKM_CTL_MEMORY); } } } static int leaky_handle_anon_mappings(leak_mtab_t **lmp) { leaky_maps_t lm; vmem_t *heap_arena; vmem_t *vm_next; vmem_t *heap_top; vmem_t vmem; pstatus_t Ps; if (mdb_get_xdata("pstatus", &Ps, sizeof (Ps)) == -1) { mdb_warn("couldn't read pstatus xdata"); return (DCMD_ERR); } lm.lm_pstatus = &Ps; leak_brkbase = Ps.pr_brkbase; leak_brksize = Ps.pr_brksize; if (umem_readvar(&heap_arena, "heap_arena") == -1) { mdb_warn("couldn't read heap_arena"); return (DCMD_ERR); } if (heap_arena == NULL) { mdb_warn("heap_arena is NULL.\n"); return (DCMD_ERR); } for (vm_next = heap_arena; vm_next != NULL; vm_next = vmem.vm_source) { if (mdb_vread(&vmem, sizeof (vmem), (uintptr_t)vm_next) == -1) { mdb_warn("couldn't read vmem at %p", vm_next); return (DCMD_ERR); } heap_top = vm_next; } lm.lm_seg_count = 0; lm.lm_seg_max = 0; if (mdb_pwalk("vmem_span", (mdb_walk_cb_t)leaky_count, &lm.lm_seg_max, (uintptr_t)heap_top) == -1) { mdb_warn("couldn't walk vmem_span for vmem %p", heap_top); return (DCMD_ERR); } lm.lm_segs = mdb_alloc(lm.lm_seg_max * sizeof (*lm.lm_segs), UM_SLEEP | UM_GC); if (mdb_pwalk("vmem_span", (mdb_walk_cb_t)leaky_read_segs, &lm, (uintptr_t)heap_top) == -1) { mdb_warn("couldn't walk vmem_span for vmem %p", heap_top); return (DCMD_ERR); } if (lm.lm_seg_count > lm.lm_seg_max) { mdb_warn("segment list for vmem %p grew\n", heap_top); return (DCMD_ERR); } qsort(lm.lm_segs, lm.lm_seg_count, sizeof (*lm.lm_segs), leaky_seg_cmp); lm.lm_lmp = lmp; prockludge_add_walkers(); if (mdb_walk(KLUDGE_MAPWALK_NAME, (mdb_walk_cb_t)leaky_process_anon_mappings, &lm) == -1) { mdb_warn("Couldn't walk "KLUDGE_MAPWALK_NAME); prockludge_remove_walkers(); return (DCMD_ERR); } prockludge_remove_walkers(); leaky_handle_sbrk(&lm); return (DCMD_OK); } static int leaky_interested(const umem_cache_t *c) { vmem_t vmem; if (mdb_vread(&vmem, sizeof (vmem), (uintptr_t)c->cache_arena) == -1) { mdb_warn("cannot read arena %p for cache '%s'", (uintptr_t)c->cache_arena, c->cache_name); return (0); } /* * If this cache isn't allocating from either the umem_default or * umem_firewall vmem arena, we're not interested. */ if (strcmp(vmem.vm_name, "umem_default") != 0 && strcmp(vmem.vm_name, "umem_firewall") != 0) { dprintf(("Skipping cache '%s' with arena '%s'\n", c->cache_name, vmem.vm_name)); return (0); } return (1); } /*ARGSUSED*/ static int leaky_estimate(uintptr_t addr, const umem_cache_t *c, size_t *est) { if (!leaky_interested(c)) return (WALK_NEXT); *est += umem_estimate_allocated(addr, c); return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_cache(uintptr_t addr, const umem_cache_t *c, leak_mtab_t **lmp) { leak_mtab_t *lm = *lmp; mdb_walk_cb_t cb; const char *walk; int audit = (c->cache_flags & UMF_AUDIT); if (!leaky_interested(c)) return (WALK_NEXT); if (audit) { walk = "bufctl"; cb = (mdb_walk_cb_t)leaky_mtab; } else { walk = "umem"; cb = (mdb_walk_cb_t)leaky_mtab_addr; } if (mdb_pwalk(walk, cb, lmp, addr) == -1) { mdb_warn("can't walk umem for cache %p (%s)", addr, c->cache_name); return (WALK_DONE); } for (; lm < *lmp; lm++) { lm->lkm_limit = lm->lkm_base + c->cache_bufsize; if (!audit) lm->lkm_bufctl = LKM_CTL(addr, LKM_CTL_CACHE); } return (WALK_NEXT); } static char *map_head = "%-?s %?s %-10s used reason\n"; static char *map_fmt = "[%?p,%?p) %-10s "; #define BACKING_LEN 10 /* must match the third field's width in map_fmt */ static void leaky_mappings_header(void) { dprintf((map_head, "mapping", "", "backing")); } /* ARGSUSED */ static int leaky_grep_mappings(uintptr_t ignored, const prmap_t *pmp, const pstatus_t *Psp) { const char *map_libname_ptr; char db_mp_name[BACKING_LEN+1]; map_libname_ptr = strrchr(pmp->pr_mapname, '/'); if (map_libname_ptr != NULL) map_libname_ptr++; else map_libname_ptr = pmp->pr_mapname; strlcpy(db_mp_name, map_libname_ptr, sizeof (db_mp_name)); dprintf((map_fmt, pmp->pr_vaddr, (char *)pmp->pr_vaddr + pmp->pr_size, db_mp_name)); #define USE(rsn) dprintf_cont(("yes %s\n", (rsn))) #define IGNORE(rsn) dprintf_cont(("no %s\n", (rsn))) if (!(pmp->pr_mflags & MA_WRITE) || !(pmp->pr_mflags & MA_READ)) { IGNORE("read-only"); } else if (pmp->pr_vaddr <= Psp->pr_brkbase && pmp->pr_vaddr + pmp->pr_size > Psp->pr_brkbase) { USE("bss"); /* grab up to brkbase */ leaky_grep(pmp->pr_vaddr, Psp->pr_brkbase - pmp->pr_vaddr); } else if (pmp->pr_vaddr >= Psp->pr_brkbase && pmp->pr_vaddr < Psp->pr_brkbase + Psp->pr_brksize) { IGNORE("in brk"); } else if (pmp->pr_vaddr == Psp->pr_stkbase && pmp->pr_size == Psp->pr_stksize) { IGNORE("stack"); } else if (0 == strcmp(map_libname_ptr, "a.out")) { USE("a.out data"); leaky_grep(pmp->pr_vaddr, pmp->pr_size); } else if (0 == strncmp(map_libname_ptr, "libumem.so", 10)) { IGNORE("part of umem"); } else if (pmp->pr_mapname[0] != 0) { USE("lib data"); /* library data/bss */ leaky_grep(pmp->pr_vaddr, pmp->pr_size); } else if ((pmp->pr_mflags & MA_ANON) && pmp->pr_mapname[0] == 0) { IGNORE("anon"); } else { IGNORE(""); /* default to ignoring */ } #undef USE #undef IGNORE return (WALK_NEXT); } /*ARGSUSED*/ static int leaky_mark_lwp(void *ignored, const lwpstatus_t *lwp) { leaky_mark_ptr(lwp->pr_reg[R_SP] + STACK_BIAS); return (0); } /*ARGSUSED*/ static int leaky_process_lwp(void *ignored, const lwpstatus_t *lwp) { const uintptr_t *regs = (const uintptr_t *)&lwp->pr_reg; int i; uintptr_t sp; uintptr_t addr; size_t size; for (i = 0; i < R_SP; i++) leaky_grep_ptr(regs[i]); sp = regs[i++] + STACK_BIAS; if (leaky_lookup_marked(sp, &addr, &size)) leaky_grep(sp, size - (sp - addr)); for (; i < NPRGREG; i++) leaky_grep_ptr(regs[i]); return (0); } /* * Handles processing various proc-related things: * 1. calls leaky_process_lwp on each the LWP * 2. leaky_greps the bss/data of libraries and a.out, and the a.out stack. */ static int leaky_process_proc(void) { pstatus_t Ps; struct ps_prochandle *Pr; if (mdb_get_xdata("pstatus", &Ps, sizeof (Ps)) == -1) { mdb_warn("couldn't read pstatus xdata"); return (DCMD_ERR); } dprintf(("pstatus says:\n")); dprintf(("\tbrk: base %p size %p\n", Ps.pr_brkbase, Ps.pr_brksize)); dprintf(("\tstk: base %p size %p\n", Ps.pr_stkbase, Ps.pr_stksize)); if (mdb_get_xdata("pshandle", &Pr, sizeof (Pr)) == -1) { mdb_warn("couldn't read pshandle xdata"); return (DCMD_ERR); } if (Plwp_iter(Pr, leaky_mark_lwp, NULL) != 0) { mdb_warn("findleaks: Failed to iterate lwps\n"); return (DCMD_ERR); } if (Plwp_iter(Pr, leaky_process_lwp, NULL) != 0) { mdb_warn("findleaks: Failed to iterate lwps\n"); return (DCMD_ERR); } prockludge_add_walkers(); leaky_mappings_header(); if (mdb_walk(KLUDGE_MAPWALK_NAME, (mdb_walk_cb_t)leaky_grep_mappings, &Ps) == -1) { mdb_warn("Couldn't walk "KLUDGE_MAPWALK_NAME); prockludge_remove_walkers(); return (-1); } prockludge_remove_walkers(); return (0); } static void leaky_subr_caller(const uintptr_t *stack, uint_t depth, char *buf, uintptr_t *pcp) { int i; GElf_Sym sym; uintptr_t pc = 0; buf[0] = 0; for (i = 0; i < depth; i++) { pc = stack[i]; if (mdb_lookup_by_addr(pc, MDB_SYM_FUZZY, buf, MDB_SYM_NAMLEN, &sym) == -1) continue; if (strncmp(buf, "libumem.so", 10) == 0) continue; *pcp = pc; return; } /* * We're only here if the entire call chain is in libumem.so; * this shouldn't happen, but we'll just use the last caller. */ *pcp = pc; } int leaky_subr_bufctl_cmp(const leak_bufctl_t *lhs, const leak_bufctl_t *rhs) { char lbuf[MDB_SYM_NAMLEN], rbuf[MDB_SYM_NAMLEN]; uintptr_t lcaller, rcaller; int rval; leaky_subr_caller(lhs->lkb_stack, lhs->lkb_depth, lbuf, &lcaller); leaky_subr_caller(rhs->lkb_stack, lhs->lkb_depth, rbuf, &rcaller); if (rval = strcmp(lbuf, rbuf)) return (rval); if (lcaller < rcaller) return (-1); if (lcaller > rcaller) return (1); if (lhs->lkb_data < rhs->lkb_data) return (-1); if (lhs->lkb_data > rhs->lkb_data) return (1); return (0); } /*ARGSUSED*/ int leaky_subr_estimate(size_t *estp) { if (umem_ready == 0) { mdb_warn( "findleaks: umem is not loaded in the address space\n"); return (DCMD_ERR); } if (umem_ready == UMEM_READY_INIT_FAILED) { mdb_warn("findleaks: umem initialization failed -- no " "possible leaks.\n"); return (DCMD_ERR); } if (umem_ready != UMEM_READY) { mdb_warn("findleaks: No allocations have occured -- no " "possible leaks.\n"); return (DCMD_ERR); } if (mdb_walk("umem_cache", (mdb_walk_cb_t)leaky_estimate, estp) == -1) { mdb_warn("couldn't walk 'umem_cache'"); return (DCMD_ERR); } if (mdb_walk("vmem", (mdb_walk_cb_t)leaky_estimate_vmem, estp) == -1) { mdb_warn("couldn't walk 'vmem'"); return (DCMD_ERR); } if (*estp == 0) { mdb_warn("findleaks: No allocated buffers found.\n"); return (DCMD_ERR); } prockludge_add_walkers(); if (mdb_walk(KLUDGE_MAPWALK_NAME, (mdb_walk_cb_t)leaky_count, estp) == -1) { mdb_warn("Couldn't walk "KLUDGE_MAPWALK_NAME); prockludge_remove_walkers(); return (DCMD_ERR); } prockludge_remove_walkers(); return (DCMD_OK); } int leaky_subr_fill(leak_mtab_t **lmpp) { if (leaky_handle_anon_mappings(lmpp) != DCMD_OK) { mdb_warn("unable to process mappings\n"); return (DCMD_ERR); } if (mdb_walk("vmem", (mdb_walk_cb_t)leaky_vmem, lmpp) == -1) { mdb_warn("couldn't walk 'vmem'"); return (DCMD_ERR); } if (mdb_walk("umem_cache", (mdb_walk_cb_t)leaky_cache, lmpp) == -1) { mdb_warn("couldn't walk 'umem_cache'"); return (DCMD_ERR); } return (DCMD_OK); } int leaky_subr_run(void) { if (leaky_process_proc() == DCMD_ERR) { mdb_warn("failed to process proc"); return (DCMD_ERR); } return (DCMD_OK); } void leaky_subr_add_leak(leak_mtab_t *lmp) { uintptr_t addr = LKM_CTLPTR(lmp->lkm_bufctl); uint_t depth; vmem_seg_t vs; umem_bufctl_audit_t *bcp; UMEM_LOCAL_BUFCTL_AUDIT(&bcp); switch (LKM_CTLTYPE(lmp->lkm_bufctl)) { case LKM_CTL_BUFCTL: if (mdb_vread(bcp, UMEM_BUFCTL_AUDIT_SIZE, addr) == -1) { mdb_warn("couldn't read leaked bufctl at addr %p", addr); return; } depth = MIN(bcp->bc_depth, umem_stack_depth); /* * The top of the stack will be in umem_cache_alloc(). * Since the offset in umem_cache_alloc() isn't interesting * we skip that frame for the purposes of uniquifying stacks. * * Also, we use the cache pointer as the leaks's cid, to * prevent the coalescing of leaks from different caches. */ if (depth > 0) depth--; leaky_add_leak(TYPE_UMEM, addr, (uintptr_t)bcp->bc_addr, bcp->bc_timestamp, bcp->bc_stack + 1, depth, (uintptr_t)bcp->bc_cache, (uintptr_t)bcp->bc_cache); break; case LKM_CTL_VMSEG: if (mdb_vread(&vs, sizeof (vs), addr) == -1) { mdb_warn("couldn't read leaked vmem_seg at addr %p", addr); return; } depth = MIN(vs.vs_depth, VMEM_STACK_DEPTH); leaky_add_leak(TYPE_VMEM, addr, vs.vs_start, vs.vs_timestamp, vs.vs_stack, depth, 0, (vs.vs_end - vs.vs_start)); break; case LKM_CTL_MEMORY: if (LEAKY_INBRK(addr)) leaky_add_leak(TYPE_SBRK, addr, addr, 0, NULL, 0, 0, lmp->lkm_limit - addr); else leaky_add_leak(TYPE_MMAP, addr, addr, 0, NULL, 0, 0, lmp->lkm_limit - addr); break; case LKM_CTL_CACHE: leaky_add_leak(TYPE_CACHE, lmp->lkm_base, lmp->lkm_base, 0, NULL, 0, addr, addr); break; default: mdb_warn("internal error: invalid leak_bufctl_t\n"); break; } } static int lk_vmem_seen; static int lk_cache_seen; static int lk_umem_seen; static size_t lk_ttl; static size_t lk_bytes; void leaky_subr_dump_start(int type) { switch (type) { case TYPE_MMAP: lk_vmem_seen = 0; break; case TYPE_SBRK: case TYPE_VMEM: return; /* don't zero counts */ case TYPE_CACHE: lk_cache_seen = 0; break; case TYPE_UMEM: lk_umem_seen = 0; break; default: break; } lk_ttl = 0; lk_bytes = 0; } void leaky_subr_dump(const leak_bufctl_t *lkb, int verbose) { const leak_bufctl_t *cur; umem_cache_t cache; size_t min, max, size; char sz[30]; char c[MDB_SYM_NAMLEN]; uintptr_t caller; const char *nm, *nm_lc; uint8_t type = lkb->lkb_type; if (verbose) { lk_ttl = 0; lk_bytes = 0; } else if (!lk_vmem_seen && (type == TYPE_VMEM || type == TYPE_MMAP || type == TYPE_SBRK)) { lk_vmem_seen = 1; mdb_printf("%-16s %7s %?s %s\n", "BYTES", "LEAKED", "VMEM_SEG", "CALLER"); } switch (lkb->lkb_type) { case TYPE_MMAP: case TYPE_SBRK: nm = (lkb->lkb_type == TYPE_MMAP) ? "MMAP" : "SBRK"; nm_lc = (lkb->lkb_type == TYPE_MMAP) ? "mmap(2)" : "sbrk(2)"; for (; lkb != NULL; lkb = lkb->lkb_next) { if (!verbose) mdb_printf("%-16d %7d %?p %s\n", lkb->lkb_data, lkb->lkb_dups + 1, lkb->lkb_addr, nm); else mdb_printf("%s leak: [%p, %p), %ld bytes\n", nm_lc, lkb->lkb_addr, lkb->lkb_addr + lkb->lkb_data, lkb->lkb_data); lk_ttl++; lk_bytes += lkb->lkb_data; } return; case TYPE_VMEM: min = max = lkb->lkb_data; for (cur = lkb; cur != NULL; cur = cur->lkb_next) { size = cur->lkb_data; if (size < min) min = size; if (size > max) max = size; lk_ttl++; lk_bytes += size; } if (min == max) (void) mdb_snprintf(sz, sizeof (sz), "%ld", min); else (void) mdb_snprintf(sz, sizeof (sz), "%ld-%ld", min, max); if (!verbose) { leaky_subr_caller(lkb->lkb_stack, lkb->lkb_depth, c, &caller); mdb_printf("%-16s %7d %?p %a\n", sz, lkb->lkb_dups + 1, lkb->lkb_addr, caller); } else { mdb_arg_t v; if (lk_ttl == 1) mdb_printf("umem_oversize leak: 1 vmem_seg, " "%ld bytes\n", lk_bytes); else mdb_printf("umem_oversize leak: %d vmem_segs, " "%s bytes each, %ld bytes total\n", lk_ttl, sz, lk_bytes); v.a_type = MDB_TYPE_STRING; v.a_un.a_str = "-v"; if (mdb_call_dcmd("vmem_seg", lkb->lkb_addr, DCMD_ADDRSPEC, 1, &v) == -1) { mdb_warn("'%p::vmem_seg -v' failed", lkb->lkb_addr); } } return; case TYPE_CACHE: if (!lk_cache_seen) { lk_cache_seen = 1; if (lk_vmem_seen) mdb_printf("\n"); mdb_printf("%-?s %7s %?s %s\n", "CACHE", "LEAKED", "BUFFER", "CALLER"); } if (mdb_vread(&cache, sizeof (cache), lkb->lkb_data) == -1) { /* * This _really_ shouldn't happen; we shouldn't * have been able to get this far if this * cache wasn't readable. */ mdb_warn("can't read cache %p for leaked " "buffer %p", lkb->lkb_data, lkb->lkb_addr); return; } lk_ttl += lkb->lkb_dups + 1; lk_bytes += (lkb->lkb_dups + 1) * cache.cache_bufsize; caller = (lkb->lkb_depth == 0) ? 0 : lkb->lkb_stack[0]; if (caller != 0) { (void) mdb_snprintf(c, sizeof (c), "%a", caller); } else { (void) mdb_snprintf(c, sizeof (c), "%s", (verbose) ? "" : "?"); } if (!verbose) { mdb_printf("%0?p %7d %0?p %s\n", lkb->lkb_cid, lkb->lkb_dups + 1, lkb->lkb_addr, c); } else { if (lk_ttl == 1) mdb_printf("%s leak: 1 buffer, %ld bytes,\n", cache.cache_name, lk_bytes); else mdb_printf("%s leak: %d buffers, " "%ld bytes each, %ld bytes total,\n", cache.cache_name, lk_ttl, cache.cache_bufsize, lk_bytes); mdb_printf(" %s%s%ssample addr %p\n", (caller == 0) ? "" : "caller ", c, (caller == 0) ? "" : ", ", lkb->lkb_addr); } return; case TYPE_UMEM: if (!lk_umem_seen) { lk_umem_seen = 1; if (lk_vmem_seen || lk_cache_seen) mdb_printf("\n"); mdb_printf("%-?s %7s %?s %s\n", "CACHE", "LEAKED", "BUFCTL", "CALLER"); } if (mdb_vread(&cache, sizeof (cache), lkb->lkb_data) == -1) { /* * This _really_ shouldn't happen; we shouldn't * have been able to get this far if this * cache wasn't readable. */ mdb_warn("can't read cache %p for leaked " "bufctl %p", lkb->lkb_data, lkb->lkb_addr); return; } lk_ttl += lkb->lkb_dups + 1; lk_bytes += (lkb->lkb_dups + 1) * cache.cache_bufsize; if (!verbose) { leaky_subr_caller(lkb->lkb_stack, lkb->lkb_depth, c, &caller); mdb_printf("%0?p %7d %0?p %a\n", lkb->lkb_data, lkb->lkb_dups + 1, lkb->lkb_addr, caller); } else { mdb_arg_t v; if (lk_ttl == 1) mdb_printf("%s leak: 1 buffer, %ld bytes\n", cache.cache_name, lk_bytes); else mdb_printf("%s leak: %d buffers, " "%ld bytes each, %ld bytes total\n", cache.cache_name, lk_ttl, cache.cache_bufsize, lk_bytes); v.a_type = MDB_TYPE_STRING; v.a_un.a_str = "-v"; if (mdb_call_dcmd("bufctl", lkb->lkb_addr, DCMD_ADDRSPEC, 1, &v) == -1) { mdb_warn("'%p::bufctl -v' failed", lkb->lkb_addr); } } return; default: return; } } void leaky_subr_dump_end(int type) { int i; int width; const char *leak; switch (type) { case TYPE_VMEM: if (!lk_vmem_seen) return; width = 16; leak = "oversized leak"; break; case TYPE_CACHE: if (!lk_cache_seen) return; width = sizeof (uintptr_t) * 2; leak = "buffer"; break; case TYPE_UMEM: if (!lk_umem_seen) return; width = sizeof (uintptr_t) * 2; leak = "buffer"; break; default: return; } for (i = 0; i < 72; i++) mdb_printf("-"); mdb_printf("\n%*s %7ld %s%s, %ld byte%s\n", width, "Total", lk_ttl, leak, (lk_ttl == 1) ? "" : "s", lk_bytes, (lk_bytes == 1) ? "" : "s"); } int leaky_subr_invoke_callback(const leak_bufctl_t *lkb, mdb_walk_cb_t cb, void *cbdata) { vmem_seg_t vs; umem_bufctl_audit_t *bcp; UMEM_LOCAL_BUFCTL_AUDIT(&bcp); switch (lkb->lkb_type) { case TYPE_VMEM: if (mdb_vread(&vs, sizeof (vs), lkb->lkb_addr) == -1) { mdb_warn("unable to read vmem_seg at %p", lkb->lkb_addr); return (WALK_NEXT); } return (cb(lkb->lkb_addr, &vs, cbdata)); case TYPE_UMEM: if (mdb_vread(bcp, UMEM_BUFCTL_AUDIT_SIZE, lkb->lkb_addr) == -1) { mdb_warn("unable to read bufctl at %p", lkb->lkb_addr); return (WALK_NEXT); } return (cb(lkb->lkb_addr, bcp, cbdata)); default: return (cb(lkb->lkb_addr, NULL, cbdata)); } } /* * 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, Joyent, Inc. All rights reserved. */ #include "umem.h" #include #include #include "kgrep.h" #include "leaky.h" #include "misc.h" #include "proc_kludges.h" #include #include #include #include "umem_pagesize.h" typedef struct datafmt { char *hdr1; char *hdr2; char *dashes; char *fmt; } datafmt_t; static datafmt_t ptcfmt[] = { { " ", "tid", "---", "%3u " }, { " memory", " cached", "-------", "%7lH " }, { " %", "cap", "---", "%3u " }, { " %", NULL, "---", "%3u " }, { NULL, NULL, NULL, NULL } }; static datafmt_t umemfmt[] = { { "cache ", "name ", "-------------------------", "%-25s " }, { " buf", " size", "------", "%6u " }, { " buf", " in use", "-------", "%7u " }, { " buf", " in ptc", "-------", "%7s " }, { " buf", " total", "-------", "%7u " }, { " memory", " in use", "-------", "%7H " }, { " alloc", " succeed", "---------", "%9u " }, { "alloc", " fail", "-----", "%5llu" }, { NULL, NULL, NULL, NULL } }; static datafmt_t vmemfmt[] = { { "vmem ", "name ", "-------------------------", "%-*s " }, { " memory", " in use", "---------", "%9H " }, { " memory", " total", "----------", "%10H " }, { " memory", " import", "---------", "%9H " }, { " alloc", " succeed", "---------", "%9llu " }, { "alloc", " fail", "-----", "%5llu " }, { NULL, NULL, NULL, NULL } }; /*ARGSUSED*/ static int umastat_cpu_avail(uintptr_t addr, const umem_cpu_cache_t *ccp, int *avail) { if (ccp->cc_rounds > 0) *avail += ccp->cc_rounds; if (ccp->cc_prounds > 0) *avail += ccp->cc_prounds; return (WALK_NEXT); } /*ARGSUSED*/ static int umastat_cpu_alloc(uintptr_t addr, const umem_cpu_cache_t *ccp, int *alloc) { *alloc += ccp->cc_alloc; return (WALK_NEXT); } /*ARGSUSED*/ static int umastat_slab_avail(uintptr_t addr, const umem_slab_t *sp, int *avail) { *avail += sp->slab_chunks - sp->slab_refcnt; return (WALK_NEXT); } typedef struct umastat_vmem { uintptr_t kv_addr; struct umastat_vmem *kv_next; int kv_meminuse; int kv_alloc; int kv_fail; } umastat_vmem_t; /*ARGSUSED*/ static int umastat_cache_nptc(uintptr_t addr, const umem_cache_t *cp, int *nptc) { if (!(cp->cache_flags & UMF_PTC)) return (WALK_NEXT); (*nptc)++; return (WALK_NEXT); } /*ARGSUSED*/ static int umastat_cache_hdr(uintptr_t addr, const umem_cache_t *cp, void *ignored) { if (!(cp->cache_flags & UMF_PTC)) return (WALK_NEXT); mdb_printf("%3d ", cp->cache_bufsize); return (WALK_NEXT); } /*ARGSUSED*/ static int umastat_lwp_ptc(uintptr_t addr, void *buf, int *nbufs) { (*nbufs)++; return (WALK_NEXT); } /*ARGSUSED*/ static int umastat_lwp_cache(uintptr_t addr, const umem_cache_t *cp, ulwp_t *ulwp) { char walk[60]; int nbufs = 0; if (!(cp->cache_flags & UMF_PTC)) return (WALK_NEXT); (void) mdb_snprintf(walk, sizeof (walk), "umem_ptc_%d", cp->cache_bufsize); if (mdb_pwalk(walk, (mdb_walk_cb_t)umastat_lwp_ptc, &nbufs, (uintptr_t)ulwp->ul_self) == -1) { mdb_warn("unable to walk '%s'", walk); return (WALK_ERR); } mdb_printf("%3d ", ulwp->ul_tmem.tm_size ? (nbufs * cp->cache_bufsize * 100) / ulwp->ul_tmem.tm_size : 0); return (WALK_NEXT); } /*ARGSUSED*/ static int umastat_lwp(uintptr_t addr, const ulwp_t *ulwp, void *ignored) { size_t size; datafmt_t *dfp = ptcfmt; mdb_printf((dfp++)->fmt, ulwp->ul_lwpid); mdb_printf((dfp++)->fmt, ulwp->ul_tmem.tm_size); if (umem_readvar(&size, "umem_ptc_size") == -1) { mdb_warn("unable to read 'umem_ptc_size'"); return (WALK_ERR); } mdb_printf((dfp++)->fmt, (ulwp->ul_tmem.tm_size * 100) / size); if (mdb_walk("umem_cache", (mdb_walk_cb_t)umastat_lwp_cache, (void *)ulwp) == -1) { mdb_warn("can't walk 'umem_cache'"); return (WALK_ERR); } mdb_printf("\n"); return (WALK_NEXT); } /*ARGSUSED*/ static int umastat_cache_ptc(uintptr_t addr, const void *ignored, int *nptc) { (*nptc)++; return (WALK_NEXT); } static int umastat_cache(uintptr_t addr, const umem_cache_t *cp, umastat_vmem_t **kvp) { umastat_vmem_t *kv; datafmt_t *dfp = umemfmt; char buf[10]; int magsize; int avail, alloc, total, nptc = 0; size_t meminuse = (cp->cache_slab_create - cp->cache_slab_destroy) * cp->cache_slabsize; mdb_walk_cb_t cpu_avail = (mdb_walk_cb_t)umastat_cpu_avail; mdb_walk_cb_t cpu_alloc = (mdb_walk_cb_t)umastat_cpu_alloc; mdb_walk_cb_t slab_avail = (mdb_walk_cb_t)umastat_slab_avail; magsize = umem_get_magsize(cp); alloc = cp->cache_slab_alloc + cp->cache_full.ml_alloc; avail = cp->cache_full.ml_total * magsize; total = cp->cache_buftotal; (void) mdb_pwalk("umem_cpu_cache", cpu_alloc, &alloc, addr); (void) mdb_pwalk("umem_cpu_cache", cpu_avail, &avail, addr); (void) mdb_pwalk("umem_slab_partial", slab_avail, &avail, addr); if (cp->cache_flags & UMF_PTC) { char walk[60]; (void) mdb_snprintf(walk, sizeof (walk), "umem_ptc_%d", cp->cache_bufsize); if (mdb_walk(walk, (mdb_walk_cb_t)umastat_cache_ptc, &nptc) == -1) { mdb_warn("unable to walk '%s'", walk); return (WALK_ERR); } (void) mdb_snprintf(buf, sizeof (buf), "%d", nptc); } for (kv = *kvp; kv != NULL; kv = kv->kv_next) { if (kv->kv_addr == (uintptr_t)cp->cache_arena) goto out; } kv = mdb_zalloc(sizeof (umastat_vmem_t), UM_SLEEP | UM_GC); kv->kv_next = *kvp; kv->kv_addr = (uintptr_t)cp->cache_arena; *kvp = kv; out: kv->kv_meminuse += meminuse; kv->kv_alloc += alloc; kv->kv_fail += cp->cache_alloc_fail; mdb_printf((dfp++)->fmt, cp->cache_name); mdb_printf((dfp++)->fmt, cp->cache_bufsize); mdb_printf((dfp++)->fmt, total - avail); mdb_printf((dfp++)->fmt, cp->cache_flags & UMF_PTC ? buf : "-"); mdb_printf((dfp++)->fmt, total); mdb_printf((dfp++)->fmt, meminuse); mdb_printf((dfp++)->fmt, alloc); mdb_printf((dfp++)->fmt, cp->cache_alloc_fail); mdb_printf("\n"); return (WALK_NEXT); } static int umastat_vmem_totals(uintptr_t addr, const vmem_t *v, umastat_vmem_t *kv) { while (kv != NULL && kv->kv_addr != addr) kv = kv->kv_next; if (kv == NULL || kv->kv_alloc == 0) return (WALK_NEXT); mdb_printf("Total [%s]%*s %6s %7s %7s %7s %7H %9u %5u\n", v->vm_name, 17 - strlen(v->vm_name), "", "", "", "", "", kv->kv_meminuse, kv->kv_alloc, kv->kv_fail); return (WALK_NEXT); } /*ARGSUSED*/ static int umastat_vmem(uintptr_t addr, const vmem_t *v, void *ignored) { datafmt_t *dfp = vmemfmt; uintptr_t paddr; vmem_t parent; int ident = 0; for (paddr = (uintptr_t)v->vm_source; paddr != 0; ident += 4) { if (mdb_vread(&parent, sizeof (parent), paddr) == -1) { mdb_warn("couldn't trace %p's ancestry", addr); ident = 0; break; } paddr = (uintptr_t)parent.vm_source; } mdb_printf("%*s", ident, ""); mdb_printf((dfp++)->fmt, 25 - ident, v->vm_name); mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_inuse); mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_total); mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_import); mdb_printf((dfp++)->fmt, v->vm_kstat.vk_alloc); mdb_printf((dfp++)->fmt, v->vm_kstat.vk_fail); mdb_printf("\n"); return (WALK_NEXT); } /*ARGSUSED*/ int umastat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { umastat_vmem_t *kv = NULL; datafmt_t *dfp; int nptc = 0, i; if (argc != 0) return (DCMD_USAGE); /* * We need to determine if we have any caches that have per-thread * caching enabled. */ if (mdb_walk("umem_cache", (mdb_walk_cb_t)umastat_cache_nptc, &nptc) == -1) { mdb_warn("can't walk 'umem_cache'"); return (DCMD_ERR); } if (nptc) { for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++) mdb_printf("%s ", dfp->hdr1); for (i = 0; i < nptc; i++) mdb_printf("%s ", dfp->hdr1); mdb_printf("\n"); for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++) mdb_printf("%s ", dfp->hdr2); if (mdb_walk("umem_cache", (mdb_walk_cb_t)umastat_cache_hdr, NULL) == -1) { mdb_warn("can't walk 'umem_cache'"); return (DCMD_ERR); } mdb_printf("\n"); for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); for (i = 0; i < nptc; i++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); if (mdb_walk("ulwp", (mdb_walk_cb_t)umastat_lwp, NULL) == -1) { mdb_warn("can't walk 'ulwp'"); return (DCMD_ERR); } mdb_printf("\n"); } for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->hdr1); mdb_printf("\n"); for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->hdr2); mdb_printf("\n"); for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->dashes); mdb_printf("\n"); if (mdb_walk("umem_cache", (mdb_walk_cb_t)umastat_cache, &kv) == -1) { mdb_warn("can't walk 'umem_cache'"); return (DCMD_ERR); } for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->dashes); mdb_printf("\n"); if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem_totals, kv) == -1) { mdb_warn("can't walk 'vmem'"); return (DCMD_ERR); } for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); mdb_printf("\n"); for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->hdr1); mdb_printf("\n"); for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->hdr2); mdb_printf("\n"); for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem, NULL) == -1) { mdb_warn("can't walk 'vmem'"); return (DCMD_ERR); } for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) mdb_printf("%s ", dfp->dashes); mdb_printf("\n"); return (DCMD_OK); } /* * kmdb doesn't use libproc, and thus doesn't have any prmap_t's to walk. * We have other ways to grep kmdb's address range. */ #ifndef _KMDB typedef struct ugrep_walk_data { kgrep_cb_func *ug_cb; void *ug_cbdata; } ugrep_walk_data_t; /*ARGSUSED*/ int ugrep_mapping_cb(uintptr_t addr, const void *prm_arg, void *data) { ugrep_walk_data_t *ug = data; const prmap_t *prm = prm_arg; return (ug->ug_cb(prm->pr_vaddr, prm->pr_vaddr + prm->pr_size, ug->ug_cbdata)); } int kgrep_subr(kgrep_cb_func *cb, void *cbdata) { ugrep_walk_data_t ug; prockludge_add_walkers(); ug.ug_cb = cb; ug.ug_cbdata = cbdata; if (mdb_walk(KLUDGE_MAPWALK_NAME, ugrep_mapping_cb, &ug) == -1) { mdb_warn("Unable to walk "KLUDGE_MAPWALK_NAME); return (DCMD_ERR); } prockludge_remove_walkers(); return (DCMD_OK); } size_t kgrep_subr_pagesize(void) { return (PAGESIZE); } #endif /* !_KMDB */ static const mdb_dcmd_t dcmds[] = { /* from libumem.c */ { "umastat", NULL, "umem allocator stats", umastat }, /* from misc.c */ { "umem_debug", NULL, "toggle umem dcmd/walk debugging", umem_debug}, /* from umem.c */ { "umem_status", NULL, "Print umem status and message buffer", umem_status }, { "allocdby", ":", "given a thread, print its allocated buffers", allocdby }, { "bufctl", ":[-vh] [-a addr] [-c caller] [-e earliest] [-l latest] " "[-t thd]", "print or filter a bufctl", bufctl, bufctl_help }, { "bufctl_audit", ":", "print a bufctl_audit", bufctl_audit }, { "freedby", ":", "given a thread, print its freed buffers", freedby }, { "umalog", "[ fail | slab ]", "display umem transaction log and stack traces", umalog }, { "umausers", "[-ef] [cache ...]", "display current medium and large " "users of the umem allocator", umausers }, { "umem_cache", "?", "print a umem cache", umem_cache }, { "umem_log", "?", "dump umem transaction log", umem_log }, { "umem_malloc_dist", "[-dg] [-b maxbins] [-B minbinsize]", "report distribution of outstanding malloc()s", umem_malloc_dist, umem_malloc_dist_help }, { "umem_malloc_info", "?[-dg] [-b maxbins] [-B minbinsize]", "report information about malloc()s by cache", umem_malloc_info, umem_malloc_info_help }, { "umem_verify", "?", "check integrity of umem-managed memory", umem_verify }, { "vmem", "?", "print a vmem_t", vmem }, { "vmem_seg", ":[-sv] [-c caller] [-e earliest] [-l latest] " "[-m minsize] [-M maxsize] [-t thread] [-T type]", "print or filter a vmem_seg", vmem_seg, vmem_seg_help }, #ifndef _KMDB /* from ../genunix/kgrep.c + libumem.c */ { "ugrep", KGREP_USAGE, "search user address space for a pointer", kgrep, kgrep_help }, /* from ../genunix/leaky.c + leaky_subr.c */ { "findleaks", FINDLEAKS_USAGE, "search for potential memory leaks", findleaks, findleaks_help }, #endif { NULL } }; static const mdb_walker_t walkers[] = { /* from umem.c */ { "allocdby", "given a thread, walk its allocated bufctls", allocdby_walk_init, allocdby_walk_step, allocdby_walk_fini }, { "bufctl", "walk a umem cache's bufctls", bufctl_walk_init, umem_walk_step, umem_walk_fini }, { "bufctl_history", "walk the available history of a bufctl", bufctl_history_walk_init, bufctl_history_walk_step, bufctl_history_walk_fini }, { "freectl", "walk a umem cache's free bufctls", freectl_walk_init, umem_walk_step, umem_walk_fini }, { "freedby", "given a thread, walk its freed bufctls", freedby_walk_init, allocdby_walk_step, allocdby_walk_fini }, { "freemem", "walk a umem cache's free memory", freemem_walk_init, umem_walk_step, umem_walk_fini }, { "umem", "walk a umem cache", umem_walk_init, umem_walk_step, umem_walk_fini }, { "umem_cpu", "walk the umem CPU structures", umem_cpu_walk_init, umem_cpu_walk_step, umem_cpu_walk_fini }, { "umem_cpu_cache", "given a umem cache, walk its per-CPU caches", umem_cpu_cache_walk_init, umem_cpu_cache_walk_step, NULL }, { "umem_hash", "given a umem cache, walk its allocated hash table", umem_hash_walk_init, umem_hash_walk_step, umem_hash_walk_fini }, { "umem_log", "walk the umem transaction log", umem_log_walk_init, umem_log_walk_step, umem_log_walk_fini }, { "umem_slab", "given a umem cache, walk its slabs", umem_slab_walk_init, umem_slab_walk_step, NULL }, { "umem_slab_partial", "given a umem cache, walk its partially allocated slabs (min 1)", umem_slab_walk_partial_init, umem_slab_walk_step, NULL }, { "vmem", "walk vmem structures in pre-fix, depth-first order", vmem_walk_init, vmem_walk_step, vmem_walk_fini }, { "vmem_alloc", "given a vmem_t, walk its allocated vmem_segs", vmem_alloc_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini }, { "vmem_free", "given a vmem_t, walk its free vmem_segs", vmem_free_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini }, { "vmem_postfix", "walk vmem structures in post-fix, depth-first order", vmem_walk_init, vmem_postfix_walk_step, vmem_walk_fini }, { "vmem_seg", "given a vmem_t, walk all of its vmem_segs", vmem_seg_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini }, { "vmem_span", "given a vmem_t, walk its spanning vmem_segs", vmem_span_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini }, #ifndef _KMDB /* from ../genunix/leaky.c + leaky_subr.c */ { "leak", "given a leak ctl, walk other leaks w/ that stacktrace", leaky_walk_init, leaky_walk_step, leaky_walk_fini }, { "leakbuf", "given a leak ctl, walk addr of leaks w/ that stacktrace", leaky_walk_init, leaky_buf_walk_step, leaky_walk_fini }, #endif { NULL } }; static const mdb_modinfo_t modinfo = {MDB_API_VERSION, dcmds, walkers}; const mdb_modinfo_t * _mdb_init(void) { if (umem_init() != 0) return (NULL); return (&modinfo); } void _mdb_fini(void) { #ifndef _KMDB leaky_cleanup(1); #endif } /* * 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. */ #include "misc.h" #define UMEM_OBJNAME "libumem.so.1" int umem_debug_level = 0; int umem_is_standalone = 0; /*ARGSUSED*/ int umem_debug(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { umem_debug_level ^= 1; mdb_printf("umem: debugging is now %s\n", umem_debug_level ? "on" : "off"); return (DCMD_OK); } /* * To further confuse the issue, this dmod can run against either * libumem.so.1 *or* the libstandumem.so linked into kmdb(1). To figure * out which one we are working against, we look up "umem_alloc" in both * libumem.so and the executable. * * A further wrinkle is that libumem.so may not yet be loaded into the * process' address space. That can lead to either the lookup failing, or * being unable to read from the data segment. We treat either case as * an error. */ int umem_set_standalone(void) { GElf_Sym sym; int ready; if (mdb_lookup_by_obj(UMEM_OBJNAME, "umem_alloc", &sym) == 0) umem_is_standalone = 0; else if (mdb_lookup_by_obj(MDB_OBJ_EXEC, "umem_alloc", &sym) == 0) umem_is_standalone = 1; else return (-1); /* * now that we know where things should be, make sure we can actually * read things out. */ if (umem_readvar(&ready, "umem_ready") == -1) return (-1); return (0); } ssize_t umem_lookup_by_name(const char *name, GElf_Sym *sym) { return (mdb_lookup_by_obj((umem_is_standalone ? MDB_OBJ_EXEC : UMEM_OBJNAME), name, sym)); } /* This is like mdb_readvar, only for libumem.so's symbols */ ssize_t umem_readvar(void *buf, const char *name) { GElf_Sym sym; if (umem_lookup_by_name(name, &sym)) return (-1); if (mdb_vread(buf, sym.st_size, (uintptr_t)sym.st_value) == sym.st_size) return ((ssize_t)sym.st_size); return (-1); } int is_umem_sym(const char *sym, const char *prefix) { char *tick_p = strrchr(sym, '`'); return (strncmp(sym, "libumem", 7) == 0 && tick_p != NULL && strncmp(tick_p + 1, prefix, strlen(prefix)) == 0); } /* * 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. */ #ifndef _MDBMOD_MISC_H #define _MDBMOD_MISC_H #include #include #ifdef __cplusplus extern "C" { #endif extern int umem_debug(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_set_standalone(void); extern ssize_t umem_lookup_by_name(const char *, GElf_Sym *); extern ssize_t umem_readvar(void *, const char *); /* * Returns non-zero if sym matches libumem*`prefix* */ int is_umem_sym(const char *, const char *); #define dprintf(x) if (umem_debug_level) { \ mdb_printf("umem debug: "); \ /*CSTYLED*/\ mdb_printf x ;\ } #define dprintf_cont(x) if (umem_debug_level) { \ /*CSTYLED*/\ mdb_printf x ;\ } extern int umem_debug_level; #ifdef __cplusplus } #endif #endif /* _MDBMOD_MISC_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2000-2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include "proc_kludges.h" typedef struct prockuldge_mappings { struct ps_prochandle *pkm_Pr; uint_t pkm_idx; uint_t pkm_count; uint_t pkm_max; prmap_t *pkm_mappings; uint_t pkm_old_max; prmap_t *pkm_old_mappings; } prockludge_mappings_t; /* ARGSUSED */ static int prockludge_mappings_iter(prockludge_mappings_t *pkm, const prmap_t *pmp, const char *object_name) { if (pkm->pkm_count >= pkm->pkm_max) { int s = pkm->pkm_max ? pkm->pkm_max * 2 : 16; pkm->pkm_old_max = pkm->pkm_max; pkm->pkm_old_mappings = pkm->pkm_mappings; pkm->pkm_max = s; pkm->pkm_mappings = mdb_alloc(sizeof (prmap_t) * s, UM_SLEEP); bcopy(pkm->pkm_old_mappings, pkm->pkm_mappings, sizeof (prmap_t) * pkm->pkm_old_max); mdb_free(pkm->pkm_old_mappings, sizeof (prmap_t) * pkm->pkm_old_max); pkm->pkm_old_mappings = NULL; pkm->pkm_old_max = 0; } bcopy(pmp, &pkm->pkm_mappings[pkm->pkm_count++], sizeof (prmap_t)); return (0); } int prockludge_mappings_walk_init(mdb_walk_state_t *mws) { struct ps_prochandle *Pr; int rc; prockludge_mappings_t *pkm; if (mdb_get_xdata("pshandle", &Pr, sizeof (Pr)) == -1) { mdb_warn("couldn't read pshandle xdata"); return (WALK_ERR); } pkm = mdb_zalloc(sizeof (prockludge_mappings_t), UM_SLEEP); pkm->pkm_Pr = Pr; mws->walk_data = pkm; rc = Pmapping_iter(Pr, (proc_map_f *)prockludge_mappings_iter, pkm); if (rc != 0) { mdb_warn("Pmapping_iter failed"); /* clean up */ prockludge_mappings_walk_fini(mws); return (WALK_ERR); } return (WALK_NEXT); } int prockludge_mappings_walk_step(mdb_walk_state_t *wsp) { prockludge_mappings_t *pkm = wsp->walk_data; int status; if (pkm->pkm_idx >= pkm->pkm_count) return (WALK_DONE); status = wsp->walk_callback(0, &pkm->pkm_mappings[pkm->pkm_idx++], wsp->walk_cbdata); return (status); } void prockludge_mappings_walk_fini(mdb_walk_state_t *wsp) { prockludge_mappings_t *pkm = wsp->walk_data; if (pkm != NULL) { if (pkm->pkm_old_mappings != NULL) { mdb_free(pkm->pkm_old_mappings, sizeof (prmap_t) * pkm->pkm_old_max); } if (pkm->pkm_mappings && pkm->pkm_mappings != pkm->pkm_old_mappings) { mdb_free(pkm->pkm_mappings, sizeof (prmap_t) * pkm->pkm_max); } mdb_free(pkm, sizeof (prockludge_mappings_t)); } } static int add_count = 0; void prockludge_add_walkers(void) { mdb_walker_t w; if (add_count++ == 0) { w.walk_name = KLUDGE_MAPWALK_NAME; w.walk_descr = "kludge: walk the process' prmap_ts"; w.walk_init = prockludge_mappings_walk_init; w.walk_step = prockludge_mappings_walk_step; w.walk_fini = prockludge_mappings_walk_fini; w.walk_init_arg = NULL; if (mdb_add_walker(&w) == -1) { mdb_warn("unable to add walker "KLUDGE_MAPWALK_NAME); } } } void prockludge_remove_walkers(void) { if (--add_count == 0) { mdb_remove_walker(KLUDGE_MAPWALK_NAME); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2000-2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _PROC_KLUDGES_H #define _PROC_KLUDGES_H #ifdef __cplusplus extern "C" { #endif #define KLUDGE_MAPWALK_NAME "__prockludge_mappings" extern int prockludge_mappings_walk_init(mdb_walk_state_t *); extern int prockludge_mappings_walk_step(mdb_walk_state_t *); extern void prockludge_mappings_walk_fini(mdb_walk_state_t *); extern void prockludge_add_walkers(void); extern void prockludge_remove_walkers(void); #ifdef __cplusplus } #endif #endif /* _PROC_KLUDGES_H */ /* * 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 2019 Joyent, Inc. * Copyright (c) 2013, 2015 by Delphix. All rights reserved. */ #include "umem.h" #include #include #include #include #include #include #include "misc.h" #include "leaky.h" #include "dist.h" #include "umem_pagesize.h" #define UM_ALLOCATED 0x1 #define UM_FREE 0x2 #define UM_BUFCTL 0x4 #define UM_HASH 0x8 int umem_ready; static int umem_stack_depth_warned; static uint32_t umem_max_ncpus; uint32_t umem_stack_depth; size_t umem_pagesize; #define UMEM_READVAR(var) \ (umem_readvar(&(var), #var) == -1 && \ (mdb_warn("failed to read "#var), 1)) int umem_update_variables(void) { size_t pagesize; /* * Figure out which type of umem is being used; if it's not there * yet, succeed quietly. */ if (umem_set_standalone() == -1) { umem_ready = 0; return (0); /* umem not there yet */ } /* * Solaris 9 used a different name for umem_max_ncpus. It's * cheap backwards compatibility to check for both names. */ if (umem_readvar(&umem_max_ncpus, "umem_max_ncpus") == -1 && umem_readvar(&umem_max_ncpus, "max_ncpus") == -1) { mdb_warn("unable to read umem_max_ncpus or max_ncpus"); return (-1); } if (UMEM_READVAR(umem_ready)) return (-1); if (UMEM_READVAR(umem_stack_depth)) return (-1); if (UMEM_READVAR(pagesize)) return (-1); if (umem_stack_depth > UMEM_MAX_STACK_DEPTH) { if (umem_stack_depth_warned == 0) { mdb_warn("umem_stack_depth corrupted (%d > %d)\n", umem_stack_depth, UMEM_MAX_STACK_DEPTH); umem_stack_depth_warned = 1; } umem_stack_depth = 0; } umem_pagesize = pagesize; return (0); } static int umem_ptc_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { if (mdb_layered_walk("ulwp", wsp) == -1) { mdb_warn("couldn't walk 'ulwp'"); return (WALK_ERR); } } return (WALK_NEXT); } static int umem_ptc_walk_step(mdb_walk_state_t *wsp) { uintptr_t this; int rval; if (wsp->walk_layer != NULL) { this = (uintptr_t)((ulwp_t *)wsp->walk_layer)->ul_self + (uintptr_t)wsp->walk_arg; } else { this = wsp->walk_addr + (uintptr_t)wsp->walk_arg; } for (;;) { if (mdb_vread(&this, sizeof (void *), this) == -1) { mdb_warn("couldn't read ptc buffer at %p", this); return (WALK_ERR); } if (this == 0) break; rval = wsp->walk_callback(this, &this, wsp->walk_cbdata); if (rval != WALK_NEXT) return (rval); } return (wsp->walk_layer != NULL ? WALK_NEXT : WALK_DONE); } /*ARGSUSED*/ static int umem_init_walkers(uintptr_t addr, const umem_cache_t *c, int *sizes) { mdb_walker_t w; char descr[64]; char name[64]; int i; (void) mdb_snprintf(descr, sizeof (descr), "walk the %s cache", c->cache_name); w.walk_name = c->cache_name; w.walk_descr = descr; w.walk_init = umem_walk_init; w.walk_step = umem_walk_step; w.walk_fini = umem_walk_fini; w.walk_init_arg = (void *)addr; if (mdb_add_walker(&w) == -1) mdb_warn("failed to add %s walker", c->cache_name); if (!(c->cache_flags & UMF_PTC)) return (WALK_NEXT); /* * For the per-thread cache walker, the address is the offset in the * tm_roots[] array of the ulwp_t. */ for (i = 0; sizes[i] != 0; i++) { if (sizes[i] == c->cache_bufsize) break; } if (sizes[i] == 0) { mdb_warn("cache %s is cached per-thread, but could not find " "size in umem_alloc_sizes\n", c->cache_name); return (WALK_NEXT); } if (i >= NTMEMBASE) { mdb_warn("index for %s (%d) exceeds root slots (%d)\n", c->cache_name, i, NTMEMBASE); return (WALK_NEXT); } (void) mdb_snprintf(name, sizeof (name), "umem_ptc_%d", c->cache_bufsize); (void) mdb_snprintf(descr, sizeof (descr), "walk the per-thread cache for %s", c->cache_name); w.walk_name = name; w.walk_descr = descr; w.walk_init = umem_ptc_walk_init; w.walk_step = umem_ptc_walk_step; w.walk_fini = NULL; w.walk_init_arg = (void *)offsetof(ulwp_t, ul_tmem.tm_roots[i]); if (mdb_add_walker(&w) == -1) mdb_warn("failed to add %s walker", w.walk_name); return (WALK_NEXT); } /*ARGSUSED*/ static void umem_statechange_cb(void *arg) { static int been_ready = 0; GElf_Sym sym; int *sizes; #ifndef _KMDB leaky_cleanup(1); /* state changes invalidate leaky state */ #endif if (umem_update_variables() == -1) return; if (been_ready) return; if (umem_ready != UMEM_READY) return; been_ready = 1; /* * In order to determine the tm_roots offset of any cache that is * cached per-thread, we need to have the umem_alloc_sizes array. * Read this, assuring that it is zero-terminated. */ if (umem_lookup_by_name("umem_alloc_sizes", &sym) == -1) { mdb_warn("unable to lookup 'umem_alloc_sizes'"); return; } sizes = mdb_zalloc(sym.st_size + sizeof (int), UM_SLEEP | UM_GC); if (mdb_vread(sizes, sym.st_size, (uintptr_t)sym.st_value) == -1) { mdb_warn("couldn't read 'umem_alloc_sizes'"); return; } (void) mdb_walk("umem_cache", (mdb_walk_cb_t)umem_init_walkers, sizes); } int umem_abort_messages(void) { char *umem_error_buffer; uint_t umem_error_begin; GElf_Sym sym; size_t bufsize; if (UMEM_READVAR(umem_error_begin)) return (DCMD_ERR); if (umem_lookup_by_name("umem_error_buffer", &sym) == -1) { mdb_warn("unable to look up umem_error_buffer"); return (DCMD_ERR); } bufsize = (size_t)sym.st_size; umem_error_buffer = mdb_alloc(bufsize+1, UM_SLEEP | UM_GC); if (mdb_vread(umem_error_buffer, bufsize, (uintptr_t)sym.st_value) != bufsize) { mdb_warn("unable to read umem_error_buffer"); return (DCMD_ERR); } /* put a zero after the end of the buffer to simplify printing */ umem_error_buffer[bufsize] = 0; if ((umem_error_begin % bufsize) == 0) mdb_printf("%s\n", umem_error_buffer); else { umem_error_buffer[(umem_error_begin % bufsize) - 1] = 0; mdb_printf("%s%s\n", &umem_error_buffer[umem_error_begin % bufsize], umem_error_buffer); } return (DCMD_OK); } static void umem_log_status(const char *name, umem_log_header_t *val) { umem_log_header_t my_lh; uintptr_t pos = (uintptr_t)val; size_t size; if (pos == 0) return; if (mdb_vread(&my_lh, sizeof (umem_log_header_t), pos) == -1) { mdb_warn("\nunable to read umem_%s_log pointer %p", name, pos); return; } size = my_lh.lh_chunksize * my_lh.lh_nchunks; if (size % (1024 * 1024) == 0) mdb_printf("%s=%dm ", name, size / (1024 * 1024)); else if (size % 1024 == 0) mdb_printf("%s=%dk ", name, size / 1024); else mdb_printf("%s=%d ", name, size); } typedef struct umem_debug_flags { const char *udf_name; uint_t udf_flags; uint_t udf_clear; /* if 0, uses udf_flags */ } umem_debug_flags_t; umem_debug_flags_t umem_status_flags[] = { { "random", UMF_RANDOMIZE, UMF_RANDOM }, { "default", UMF_AUDIT | UMF_DEADBEEF | UMF_REDZONE | UMF_CONTENTS }, { "audit", UMF_AUDIT }, { "guards", UMF_DEADBEEF | UMF_REDZONE }, { "nosignal", UMF_CHECKSIGNAL }, { "firewall", UMF_FIREWALL }, { "lite", UMF_LITE }, { "checknull", UMF_CHECKNULL }, { NULL } }; /*ARGSUSED*/ int umem_status(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *argv) { int umem_logging; umem_log_header_t *umem_transaction_log; umem_log_header_t *umem_content_log; umem_log_header_t *umem_failure_log; umem_log_header_t *umem_slab_log; mdb_printf("Status:\t\t%s\n", umem_ready == UMEM_READY_INIT_FAILED ? "initialization failed" : umem_ready == UMEM_READY_STARTUP ? "uninitialized" : umem_ready == UMEM_READY_INITING ? "initialization in process" : umem_ready == UMEM_READY ? "ready and active" : umem_ready == 0 ? "not loaded into address space" : "unknown (umem_ready invalid)"); if (umem_ready == 0) return (DCMD_OK); mdb_printf("Concurrency:\t%d\n", umem_max_ncpus); if (UMEM_READVAR(umem_logging)) goto err; if (UMEM_READVAR(umem_transaction_log)) goto err; if (UMEM_READVAR(umem_content_log)) goto err; if (UMEM_READVAR(umem_failure_log)) goto err; if (UMEM_READVAR(umem_slab_log)) goto err; mdb_printf("Logs:\t\t"); umem_log_status("transaction", umem_transaction_log); umem_log_status("content", umem_content_log); umem_log_status("fail", umem_failure_log); umem_log_status("slab", umem_slab_log); if (!umem_logging) mdb_printf("(inactive)"); mdb_printf("\n"); mdb_printf("Message buffer:\n"); return (umem_abort_messages()); err: mdb_printf("Message buffer:\n"); (void) umem_abort_messages(); return (DCMD_ERR); } typedef struct { uintptr_t ucw_first; uintptr_t ucw_current; } umem_cache_walk_t; int umem_cache_walk_init(mdb_walk_state_t *wsp) { umem_cache_walk_t *ucw; umem_cache_t c; uintptr_t cp; GElf_Sym sym; if (umem_lookup_by_name("umem_null_cache", &sym) == -1) { mdb_warn("couldn't find umem_null_cache"); return (WALK_ERR); } cp = (uintptr_t)sym.st_value; if (mdb_vread(&c, sizeof (umem_cache_t), cp) == -1) { mdb_warn("couldn't read cache at %p", cp); return (WALK_ERR); } ucw = mdb_alloc(sizeof (umem_cache_walk_t), UM_SLEEP); ucw->ucw_first = cp; ucw->ucw_current = (uintptr_t)c.cache_next; wsp->walk_data = ucw; return (WALK_NEXT); } int umem_cache_walk_step(mdb_walk_state_t *wsp) { umem_cache_walk_t *ucw = wsp->walk_data; umem_cache_t c; int status; if (mdb_vread(&c, sizeof (umem_cache_t), ucw->ucw_current) == -1) { mdb_warn("couldn't read cache at %p", ucw->ucw_current); return (WALK_DONE); } status = wsp->walk_callback(ucw->ucw_current, &c, wsp->walk_cbdata); if ((ucw->ucw_current = (uintptr_t)c.cache_next) == ucw->ucw_first) return (WALK_DONE); return (status); } void umem_cache_walk_fini(mdb_walk_state_t *wsp) { umem_cache_walk_t *ucw = wsp->walk_data; mdb_free(ucw, sizeof (umem_cache_walk_t)); } typedef struct { umem_cpu_t *ucw_cpus; uint32_t ucw_current; uint32_t ucw_max; } umem_cpu_walk_state_t; int umem_cpu_walk_init(mdb_walk_state_t *wsp) { umem_cpu_t *umem_cpus; umem_cpu_walk_state_t *ucw; if (umem_readvar(&umem_cpus, "umem_cpus") == -1) { mdb_warn("failed to read 'umem_cpus'"); return (WALK_ERR); } ucw = mdb_alloc(sizeof (*ucw), UM_SLEEP); ucw->ucw_cpus = umem_cpus; ucw->ucw_current = 0; ucw->ucw_max = umem_max_ncpus; wsp->walk_data = ucw; return (WALK_NEXT); } int umem_cpu_walk_step(mdb_walk_state_t *wsp) { umem_cpu_t cpu; umem_cpu_walk_state_t *ucw = wsp->walk_data; uintptr_t caddr; if (ucw->ucw_current >= ucw->ucw_max) return (WALK_DONE); caddr = (uintptr_t)&(ucw->ucw_cpus[ucw->ucw_current]); if (mdb_vread(&cpu, sizeof (umem_cpu_t), caddr) == -1) { mdb_warn("failed to read cpu %d", ucw->ucw_current); return (WALK_ERR); } ucw->ucw_current++; return (wsp->walk_callback(caddr, &cpu, wsp->walk_cbdata)); } void umem_cpu_walk_fini(mdb_walk_state_t *wsp) { umem_cpu_walk_state_t *ucw = wsp->walk_data; mdb_free(ucw, sizeof (*ucw)); } int umem_cpu_cache_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("umem_cpu_cache doesn't support global walks"); return (WALK_ERR); } if (mdb_layered_walk("umem_cpu", wsp) == -1) { mdb_warn("couldn't walk 'umem_cpu'"); return (WALK_ERR); } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int umem_cpu_cache_walk_step(mdb_walk_state_t *wsp) { uintptr_t caddr = (uintptr_t)wsp->walk_data; const umem_cpu_t *cpu = wsp->walk_layer; umem_cpu_cache_t cc; caddr += cpu->cpu_cache_offset; if (mdb_vread(&cc, sizeof (umem_cpu_cache_t), caddr) == -1) { mdb_warn("couldn't read umem_cpu_cache at %p", caddr); return (WALK_ERR); } return (wsp->walk_callback(caddr, &cc, wsp->walk_cbdata)); } int umem_slab_walk_init(mdb_walk_state_t *wsp) { uintptr_t caddr = wsp->walk_addr; umem_cache_t c; if (caddr == 0) { mdb_warn("umem_slab doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&c, sizeof (c), caddr) == -1) { mdb_warn("couldn't read umem_cache at %p", caddr); return (WALK_ERR); } wsp->walk_data = (void *)(caddr + offsetof(umem_cache_t, cache_nullslab)); wsp->walk_addr = (uintptr_t)c.cache_nullslab.slab_next; return (WALK_NEXT); } int umem_slab_walk_partial_init(mdb_walk_state_t *wsp) { uintptr_t caddr = wsp->walk_addr; umem_cache_t c; if (caddr == 0) { mdb_warn("umem_slab_partial doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&c, sizeof (c), caddr) == -1) { mdb_warn("couldn't read umem_cache at %p", caddr); return (WALK_ERR); } wsp->walk_data = (void *)(caddr + offsetof(umem_cache_t, cache_nullslab)); wsp->walk_addr = (uintptr_t)c.cache_freelist; /* * Some consumers (umem_walk_step(), in particular) require at * least one callback if there are any buffers in the cache. So * if there are *no* partial slabs, report the last full slab, if * any. * * Yes, this is ugly, but it's cleaner than the other possibilities. */ if ((uintptr_t)wsp->walk_data == wsp->walk_addr) wsp->walk_addr = (uintptr_t)c.cache_nullslab.slab_prev; return (WALK_NEXT); } int umem_slab_walk_step(mdb_walk_state_t *wsp) { umem_slab_t s; uintptr_t addr = wsp->walk_addr; uintptr_t saddr = (uintptr_t)wsp->walk_data; uintptr_t caddr = saddr - offsetof(umem_cache_t, cache_nullslab); if (addr == saddr) return (WALK_DONE); if (mdb_vread(&s, sizeof (s), addr) == -1) { mdb_warn("failed to read slab at %p", wsp->walk_addr); return (WALK_ERR); } if ((uintptr_t)s.slab_cache != caddr) { mdb_warn("slab %p isn't in cache %p (in cache %p)\n", addr, caddr, s.slab_cache); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)s.slab_next; return (wsp->walk_callback(addr, &s, wsp->walk_cbdata)); } int umem_cache(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *argv) { umem_cache_t c; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("umem_cache", "umem_cache", ac, argv) == -1) { mdb_warn("can't walk umem_cache"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%-?s %-25s %4s %8s %8s %8s\n", "ADDR", "NAME", "FLAG", "CFLAG", "BUFSIZE", "BUFTOTL"); if (mdb_vread(&c, sizeof (c), addr) == -1) { mdb_warn("couldn't read umem_cache at %p", addr); return (DCMD_ERR); } mdb_printf("%0?p %-25s %04x %08x %8ld %8lld\n", addr, c.cache_name, c.cache_flags, c.cache_cflags, c.cache_bufsize, c.cache_buftotal); return (DCMD_OK); } static int addrcmp(const void *lhs, const void *rhs) { uintptr_t p1 = *((uintptr_t *)lhs); uintptr_t p2 = *((uintptr_t *)rhs); if (p1 < p2) return (-1); if (p1 > p2) return (1); return (0); } static int bufctlcmp(const umem_bufctl_audit_t **lhs, const umem_bufctl_audit_t **rhs) { const umem_bufctl_audit_t *bcp1 = *lhs; const umem_bufctl_audit_t *bcp2 = *rhs; if (bcp1->bc_timestamp > bcp2->bc_timestamp) return (-1); if (bcp1->bc_timestamp < bcp2->bc_timestamp) return (1); return (0); } typedef struct umem_hash_walk { uintptr_t *umhw_table; size_t umhw_nelems; size_t umhw_pos; umem_bufctl_t umhw_cur; } umem_hash_walk_t; int umem_hash_walk_init(mdb_walk_state_t *wsp) { umem_hash_walk_t *umhw; uintptr_t *hash; umem_cache_t c; uintptr_t haddr, addr = wsp->walk_addr; size_t nelems; size_t hsize; if (addr == 0) { mdb_warn("umem_hash doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&c, sizeof (c), addr) == -1) { mdb_warn("couldn't read cache at addr %p", addr); return (WALK_ERR); } if (!(c.cache_flags & UMF_HASH)) { mdb_warn("cache %p doesn't have a hash table\n", addr); return (WALK_DONE); /* nothing to do */ } umhw = mdb_zalloc(sizeof (umem_hash_walk_t), UM_SLEEP); umhw->umhw_cur.bc_next = NULL; umhw->umhw_pos = 0; umhw->umhw_nelems = nelems = c.cache_hash_mask + 1; hsize = nelems * sizeof (uintptr_t); haddr = (uintptr_t)c.cache_hash_table; umhw->umhw_table = hash = mdb_alloc(hsize, UM_SLEEP); if (mdb_vread(hash, hsize, haddr) == -1) { mdb_warn("failed to read hash table at %p", haddr); mdb_free(hash, hsize); mdb_free(umhw, sizeof (umem_hash_walk_t)); return (WALK_ERR); } wsp->walk_data = umhw; return (WALK_NEXT); } int umem_hash_walk_step(mdb_walk_state_t *wsp) { umem_hash_walk_t *umhw = wsp->walk_data; uintptr_t addr = 0; if ((addr = (uintptr_t)umhw->umhw_cur.bc_next) == 0) { while (umhw->umhw_pos < umhw->umhw_nelems) { if ((addr = umhw->umhw_table[umhw->umhw_pos++]) != 0) break; } } if (addr == 0) return (WALK_DONE); if (mdb_vread(&umhw->umhw_cur, sizeof (umem_bufctl_t), addr) == -1) { mdb_warn("couldn't read umem_bufctl_t at addr %p", addr); return (WALK_ERR); } return (wsp->walk_callback(addr, &umhw->umhw_cur, wsp->walk_cbdata)); } void umem_hash_walk_fini(mdb_walk_state_t *wsp) { umem_hash_walk_t *umhw = wsp->walk_data; if (umhw == NULL) return; mdb_free(umhw->umhw_table, umhw->umhw_nelems * sizeof (uintptr_t)); mdb_free(umhw, sizeof (umem_hash_walk_t)); } /* * Find the address of the bufctl structure for the address 'buf' in cache * 'cp', which is at address caddr, and place it in *out. */ static int umem_hash_lookup(umem_cache_t *cp, uintptr_t caddr, void *buf, uintptr_t *out) { uintptr_t bucket = (uintptr_t)UMEM_HASH(cp, buf); umem_bufctl_t *bcp; umem_bufctl_t bc; if (mdb_vread(&bcp, sizeof (umem_bufctl_t *), bucket) == -1) { mdb_warn("unable to read hash bucket for %p in cache %p", buf, caddr); return (-1); } while (bcp != NULL) { if (mdb_vread(&bc, sizeof (umem_bufctl_t), (uintptr_t)bcp) == -1) { mdb_warn("unable to read bufctl at %p", bcp); return (-1); } if (bc.bc_addr == buf) { *out = (uintptr_t)bcp; return (0); } bcp = bc.bc_next; } mdb_warn("unable to find bufctl for %p in cache %p\n", buf, caddr); return (-1); } int umem_get_magsize(const umem_cache_t *cp) { uintptr_t addr = (uintptr_t)cp->cache_magtype; GElf_Sym mt_sym; umem_magtype_t mt; int res; /* * if cpu 0 has a non-zero magsize, it must be correct. caches * with UMF_NOMAGAZINE have disabled their magazine layers, so * it is okay to return 0 for them. */ if ((res = cp->cache_cpu[0].cc_magsize) != 0 || (cp->cache_flags & UMF_NOMAGAZINE)) return (res); if (umem_lookup_by_name("umem_magtype", &mt_sym) == -1) { mdb_warn("unable to read 'umem_magtype'"); } else if (addr < mt_sym.st_value || addr + sizeof (mt) - 1 > mt_sym.st_value + mt_sym.st_size - 1 || ((addr - mt_sym.st_value) % sizeof (mt)) != 0) { mdb_warn("cache '%s' has invalid magtype pointer (%p)\n", cp->cache_name, addr); return (0); } if (mdb_vread(&mt, sizeof (mt), addr) == -1) { mdb_warn("unable to read magtype at %a", addr); return (0); } return (mt.mt_magsize); } /*ARGSUSED*/ static int umem_estimate_slab(uintptr_t addr, const umem_slab_t *sp, size_t *est) { *est -= (sp->slab_chunks - sp->slab_refcnt); return (WALK_NEXT); } /* * Returns an upper bound on the number of allocated buffers in a given * cache. */ size_t umem_estimate_allocated(uintptr_t addr, const umem_cache_t *cp) { int magsize; size_t cache_est; cache_est = cp->cache_buftotal; (void) mdb_pwalk("umem_slab_partial", (mdb_walk_cb_t)umem_estimate_slab, &cache_est, addr); if ((magsize = umem_get_magsize(cp)) != 0) { size_t mag_est = cp->cache_full.ml_total * magsize; if (cache_est >= mag_est) { cache_est -= mag_est; } else { mdb_warn("cache %p's magazine layer holds more buffers " "than the slab layer.\n", addr); } } return (cache_est); } #define READMAG_ROUNDS(rounds) { \ if (mdb_vread(mp, magbsize, (uintptr_t)ump) == -1) { \ mdb_warn("couldn't read magazine at %p", ump); \ goto fail; \ } \ for (i = 0; i < rounds; i++) { \ maglist[magcnt++] = mp->mag_round[i]; \ if (magcnt == magmax) { \ mdb_warn("%d magazines exceeds fudge factor\n", \ magcnt); \ goto fail; \ } \ } \ } static int umem_read_magazines(umem_cache_t *cp, uintptr_t addr, void ***maglistp, size_t *magcntp, size_t *magmaxp) { umem_magazine_t *ump, *mp; void **maglist = NULL; int i, cpu; size_t magsize, magmax, magbsize; size_t magcnt = 0; /* * Read the magtype out of the cache, after verifying the pointer's * correctness. */ magsize = umem_get_magsize(cp); if (magsize == 0) { *maglistp = NULL; *magcntp = 0; *magmaxp = 0; return (0); } /* * There are several places where we need to go buffer hunting: * the per-CPU loaded magazine, the per-CPU spare full magazine, * and the full magazine list in the depot. * * For an upper bound on the number of buffers in the magazine * layer, we have the number of magazines on the cache_full * list plus at most two magazines per CPU (the loaded and the * spare). Toss in 100 magazines as a fudge factor in case this * is live (the number "100" comes from the same fudge factor in * crash(8)). */ magmax = (cp->cache_full.ml_total + 2 * umem_max_ncpus + 100) * magsize; magbsize = offsetof(umem_magazine_t, mag_round[magsize]); if (magbsize >= PAGESIZE / 2) { mdb_warn("magazine size for cache %p unreasonable (%x)\n", addr, magbsize); return (-1); } maglist = mdb_alloc(magmax * sizeof (void *), UM_SLEEP); mp = mdb_alloc(magbsize, UM_SLEEP); if (mp == NULL || maglist == NULL) goto fail; /* * First up: the magazines in the depot (i.e. on the cache_full list). */ for (ump = cp->cache_full.ml_list; ump != NULL; ) { READMAG_ROUNDS(magsize); ump = mp->mag_next; if (ump == cp->cache_full.ml_list) break; /* cache_full list loop detected */ } dprintf(("cache_full list done\n")); /* * Now whip through the CPUs, snagging the loaded magazines * and full spares. */ for (cpu = 0; cpu < umem_max_ncpus; cpu++) { umem_cpu_cache_t *ccp = &cp->cache_cpu[cpu]; dprintf(("reading cpu cache %p\n", (uintptr_t)ccp - (uintptr_t)cp + addr)); if (ccp->cc_rounds > 0 && (ump = ccp->cc_loaded) != NULL) { dprintf(("reading %d loaded rounds\n", ccp->cc_rounds)); READMAG_ROUNDS(ccp->cc_rounds); } if (ccp->cc_prounds > 0 && (ump = ccp->cc_ploaded) != NULL) { dprintf(("reading %d previously loaded rounds\n", ccp->cc_prounds)); READMAG_ROUNDS(ccp->cc_prounds); } } dprintf(("magazine layer: %d buffers\n", magcnt)); mdb_free(mp, magbsize); *maglistp = maglist; *magcntp = magcnt; *magmaxp = magmax; return (0); fail: if (mp) mdb_free(mp, magbsize); if (maglist) mdb_free(maglist, magmax * sizeof (void *)); return (-1); } typedef struct umem_read_ptc_walk { void **urpw_buf; size_t urpw_cnt; size_t urpw_max; } umem_read_ptc_walk_t; /*ARGSUSED*/ static int umem_read_ptc_walk_buf(uintptr_t addr, const void *ignored, umem_read_ptc_walk_t *urpw) { if (urpw->urpw_cnt == urpw->urpw_max) { size_t nmax = urpw->urpw_max ? (urpw->urpw_max << 1) : 1; void **new = mdb_zalloc(nmax * sizeof (void *), UM_SLEEP); if (nmax > 1) { size_t osize = urpw->urpw_max * sizeof (void *); bcopy(urpw->urpw_buf, new, osize); mdb_free(urpw->urpw_buf, osize); } urpw->urpw_buf = new; urpw->urpw_max = nmax; } urpw->urpw_buf[urpw->urpw_cnt++] = (void *)addr; return (WALK_NEXT); } static int umem_read_ptc(umem_cache_t *cp, void ***buflistp, size_t *bufcntp, size_t *bufmaxp) { umem_read_ptc_walk_t urpw; char walk[60]; int rval; if (!(cp->cache_flags & UMF_PTC)) return (0); (void) mdb_snprintf(walk, sizeof (walk), "umem_ptc_%d", cp->cache_bufsize); urpw.urpw_buf = *buflistp; urpw.urpw_cnt = *bufcntp; urpw.urpw_max = *bufmaxp; if ((rval = mdb_walk(walk, (mdb_walk_cb_t)umem_read_ptc_walk_buf, &urpw)) == -1) { mdb_warn("couldn't walk %s", walk); } *buflistp = urpw.urpw_buf; *bufcntp = urpw.urpw_cnt; *bufmaxp = urpw.urpw_max; return (rval); } static int umem_walk_callback(mdb_walk_state_t *wsp, uintptr_t buf) { return (wsp->walk_callback(buf, NULL, wsp->walk_cbdata)); } static int bufctl_walk_callback(umem_cache_t *cp, mdb_walk_state_t *wsp, uintptr_t buf) { umem_bufctl_audit_t *b; UMEM_LOCAL_BUFCTL_AUDIT(&b); /* * if UMF_AUDIT is not set, we know that we're looking at a * umem_bufctl_t. */ if (!(cp->cache_flags & UMF_AUDIT) || mdb_vread(b, UMEM_BUFCTL_AUDIT_SIZE, buf) == -1) { (void) memset(b, 0, UMEM_BUFCTL_AUDIT_SIZE); if (mdb_vread(b, sizeof (umem_bufctl_t), buf) == -1) { mdb_warn("unable to read bufctl at %p", buf); return (WALK_ERR); } } return (wsp->walk_callback(buf, b, wsp->walk_cbdata)); } typedef struct umem_walk { int umw_type; uintptr_t umw_addr; /* cache address */ umem_cache_t *umw_cp; size_t umw_csize; /* * magazine layer */ void **umw_maglist; size_t umw_max; size_t umw_count; size_t umw_pos; /* * slab layer */ char *umw_valid; /* to keep track of freed buffers */ char *umw_ubase; /* buffer for slab data */ } umem_walk_t; static int umem_walk_init_common(mdb_walk_state_t *wsp, int type) { umem_walk_t *umw; int csize; umem_cache_t *cp; size_t vm_quantum; size_t magmax, magcnt; void **maglist = NULL; uint_t chunksize = 1, slabsize = 1; int status = WALK_ERR; uintptr_t addr = wsp->walk_addr; const char *layered; type &= ~UM_HASH; if (addr == 0) { mdb_warn("umem walk doesn't support global walks\n"); return (WALK_ERR); } dprintf(("walking %p\n", addr)); /* * The number of "cpus" determines how large the cache is. */ csize = UMEM_CACHE_SIZE(umem_max_ncpus); cp = mdb_alloc(csize, UM_SLEEP); if (mdb_vread(cp, csize, addr) == -1) { mdb_warn("couldn't read cache at addr %p", addr); goto out2; } /* * It's easy for someone to hand us an invalid cache address. * Unfortunately, it is hard for this walker to survive an * invalid cache cleanly. So we make sure that: * * 1. the vmem arena for the cache is readable, * 2. the vmem arena's quantum is a power of 2, * 3. our slabsize is a multiple of the quantum, and * 4. our chunksize is >0 and less than our slabsize. */ if (mdb_vread(&vm_quantum, sizeof (vm_quantum), (uintptr_t)&cp->cache_arena->vm_quantum) == -1 || vm_quantum == 0 || (vm_quantum & (vm_quantum - 1)) != 0 || cp->cache_slabsize < vm_quantum || P2PHASE(cp->cache_slabsize, vm_quantum) != 0 || cp->cache_chunksize == 0 || cp->cache_chunksize > cp->cache_slabsize) { mdb_warn("%p is not a valid umem_cache_t\n", addr); goto out2; } dprintf(("buf total is %d\n", cp->cache_buftotal)); if (cp->cache_buftotal == 0) { mdb_free(cp, csize); return (WALK_DONE); } /* * If they ask for bufctls, but it's a small-slab cache, * there is nothing to report. */ if ((type & UM_BUFCTL) && !(cp->cache_flags & UMF_HASH)) { dprintf(("bufctl requested, not UMF_HASH (flags: %p)\n", cp->cache_flags)); mdb_free(cp, csize); return (WALK_DONE); } /* * Read in the contents of the magazine layer */ if (umem_read_magazines(cp, addr, &maglist, &magcnt, &magmax) != 0) goto out2; /* * Read in the contents of the per-thread caches, if any */ if (umem_read_ptc(cp, &maglist, &magcnt, &magmax) != 0) goto out2; /* * We have all of the buffers from the magazines and from the * per-thread cache (if any); if we are walking allocated buffers, * sort them so we can bsearch them later. */ if (type & UM_ALLOCATED) qsort(maglist, magcnt, sizeof (void *), addrcmp); wsp->walk_data = umw = mdb_zalloc(sizeof (umem_walk_t), UM_SLEEP); umw->umw_type = type; umw->umw_addr = addr; umw->umw_cp = cp; umw->umw_csize = csize; umw->umw_maglist = maglist; umw->umw_max = magmax; umw->umw_count = magcnt; umw->umw_pos = 0; /* * When walking allocated buffers in a UMF_HASH cache, we walk the * hash table instead of the slab layer. */ if ((cp->cache_flags & UMF_HASH) && (type & UM_ALLOCATED)) { layered = "umem_hash"; umw->umw_type |= UM_HASH; } else { /* * If we are walking freed buffers, we only need the * magazine layer plus the partially allocated slabs. * To walk allocated buffers, we need all of the slabs. */ if (type & UM_ALLOCATED) layered = "umem_slab"; else layered = "umem_slab_partial"; /* * for small-slab caches, we read in the entire slab. For * freed buffers, we can just walk the freelist. For * allocated buffers, we use a 'valid' array to track * the freed buffers. */ if (!(cp->cache_flags & UMF_HASH)) { chunksize = cp->cache_chunksize; slabsize = cp->cache_slabsize; umw->umw_ubase = mdb_alloc(slabsize + sizeof (umem_bufctl_t), UM_SLEEP); if (type & UM_ALLOCATED) umw->umw_valid = mdb_alloc(slabsize / chunksize, UM_SLEEP); } } status = WALK_NEXT; if (mdb_layered_walk(layered, wsp) == -1) { mdb_warn("unable to start layered '%s' walk", layered); status = WALK_ERR; } if (status == WALK_ERR) { if (umw->umw_valid) mdb_free(umw->umw_valid, slabsize / chunksize); if (umw->umw_ubase) mdb_free(umw->umw_ubase, slabsize + sizeof (umem_bufctl_t)); if (umw->umw_maglist) mdb_free(umw->umw_maglist, umw->umw_max * sizeof (uintptr_t)); mdb_free(umw, sizeof (umem_walk_t)); wsp->walk_data = NULL; } out2: if (status == WALK_ERR) mdb_free(cp, csize); return (status); } int umem_walk_step(mdb_walk_state_t *wsp) { umem_walk_t *umw = wsp->walk_data; int type = umw->umw_type; umem_cache_t *cp = umw->umw_cp; void **maglist = umw->umw_maglist; int magcnt = umw->umw_count; uintptr_t chunksize, slabsize; uintptr_t addr; const umem_slab_t *sp; const umem_bufctl_t *bcp; umem_bufctl_t bc; int chunks; char *kbase; void *buf; int i, ret; char *valid, *ubase; /* * first, handle the 'umem_hash' layered walk case */ if (type & UM_HASH) { /* * We have a buffer which has been allocated out of the * global layer. We need to make sure that it's not * actually sitting in a magazine before we report it as * an allocated buffer. */ buf = ((const umem_bufctl_t *)wsp->walk_layer)->bc_addr; if (magcnt > 0 && bsearch(&buf, maglist, magcnt, sizeof (void *), addrcmp) != NULL) return (WALK_NEXT); if (type & UM_BUFCTL) return (bufctl_walk_callback(cp, wsp, wsp->walk_addr)); return (umem_walk_callback(wsp, (uintptr_t)buf)); } ret = WALK_NEXT; addr = umw->umw_addr; /* * If we're walking freed buffers, report everything in the * magazine layer before processing the first slab. */ if ((type & UM_FREE) && magcnt != 0) { umw->umw_count = 0; /* only do this once */ for (i = 0; i < magcnt; i++) { buf = maglist[i]; if (type & UM_BUFCTL) { uintptr_t out; if (cp->cache_flags & UMF_BUFTAG) { umem_buftag_t *btp; umem_buftag_t tag; /* LINTED - alignment */ btp = UMEM_BUFTAG(cp, buf); if (mdb_vread(&tag, sizeof (tag), (uintptr_t)btp) == -1) { mdb_warn("reading buftag for " "%p at %p", buf, btp); continue; } out = (uintptr_t)tag.bt_bufctl; } else { if (umem_hash_lookup(cp, addr, buf, &out) == -1) continue; } ret = bufctl_walk_callback(cp, wsp, out); } else { ret = umem_walk_callback(wsp, (uintptr_t)buf); } if (ret != WALK_NEXT) return (ret); } } /* * Handle the buffers in the current slab */ chunksize = cp->cache_chunksize; slabsize = cp->cache_slabsize; sp = wsp->walk_layer; chunks = sp->slab_chunks; kbase = sp->slab_base; dprintf(("kbase is %p\n", kbase)); if (!(cp->cache_flags & UMF_HASH)) { valid = umw->umw_valid; ubase = umw->umw_ubase; if (mdb_vread(ubase, chunks * chunksize, (uintptr_t)kbase) == -1) { mdb_warn("failed to read slab contents at %p", kbase); return (WALK_ERR); } /* * Set up the valid map as fully allocated -- we'll punch * out the freelist. */ if (type & UM_ALLOCATED) (void) memset(valid, 1, chunks); } else { valid = NULL; ubase = NULL; } /* * walk the slab's freelist */ bcp = sp->slab_head; dprintf(("refcnt is %d; chunks is %d\n", sp->slab_refcnt, chunks)); /* * since we could be in the middle of allocating a buffer, * our refcnt could be one higher than it aught. So we * check one further on the freelist than the count allows. */ for (i = sp->slab_refcnt; i <= chunks; i++) { uint_t ndx; dprintf(("bcp is %p\n", bcp)); if (bcp == NULL) { if (i == chunks) break; mdb_warn( "slab %p in cache %p freelist too short by %d\n", sp, addr, chunks - i); break; } if (cp->cache_flags & UMF_HASH) { if (mdb_vread(&bc, sizeof (bc), (uintptr_t)bcp) == -1) { mdb_warn("failed to read bufctl ptr at %p", bcp); break; } buf = bc.bc_addr; } else { /* * Otherwise the buffer is (or should be) in the slab * that we've read in; determine its offset in the * slab, validate that it's not corrupt, and add to * our base address to find the umem_bufctl_t. (Note * that we don't need to add the size of the bufctl * to our offset calculation because of the slop that's * allocated for the buffer at ubase.) */ uintptr_t offs = (uintptr_t)bcp - (uintptr_t)kbase; if (offs > chunks * chunksize) { mdb_warn("found corrupt bufctl ptr %p" " in slab %p in cache %p\n", bcp, wsp->walk_addr, addr); break; } bc = *((umem_bufctl_t *)((uintptr_t)ubase + offs)); buf = UMEM_BUF(cp, bcp); } ndx = ((uintptr_t)buf - (uintptr_t)kbase) / chunksize; if (ndx > slabsize / cp->cache_bufsize) { /* * This is very wrong; we have managed to find * a buffer in the slab which shouldn't * actually be here. Emit a warning, and * try to continue. */ mdb_warn("buf %p is out of range for " "slab %p, cache %p\n", buf, sp, addr); } else if (type & UM_ALLOCATED) { /* * we have found a buffer on the slab's freelist; * clear its entry */ valid[ndx] = 0; } else { /* * Report this freed buffer */ if (type & UM_BUFCTL) { ret = bufctl_walk_callback(cp, wsp, (uintptr_t)bcp); } else { ret = umem_walk_callback(wsp, (uintptr_t)buf); } if (ret != WALK_NEXT) return (ret); } bcp = bc.bc_next; } if (bcp != NULL) { dprintf(("slab %p in cache %p freelist too long (%p)\n", sp, addr, bcp)); } /* * If we are walking freed buffers, the loop above handled reporting * them. */ if (type & UM_FREE) return (WALK_NEXT); if (type & UM_BUFCTL) { mdb_warn("impossible situation: small-slab UM_BUFCTL walk for " "cache %p\n", addr); return (WALK_ERR); } /* * Report allocated buffers, skipping buffers in the magazine layer. * We only get this far for small-slab caches. */ for (i = 0; ret == WALK_NEXT && i < chunks; i++) { buf = (char *)kbase + i * chunksize; if (!valid[i]) continue; /* on slab freelist */ if (magcnt > 0 && bsearch(&buf, maglist, magcnt, sizeof (void *), addrcmp) != NULL) continue; /* in magazine layer */ ret = umem_walk_callback(wsp, (uintptr_t)buf); } return (ret); } void umem_walk_fini(mdb_walk_state_t *wsp) { umem_walk_t *umw = wsp->walk_data; uintptr_t chunksize; uintptr_t slabsize; if (umw == NULL) return; if (umw->umw_maglist != NULL) mdb_free(umw->umw_maglist, umw->umw_max * sizeof (void *)); chunksize = umw->umw_cp->cache_chunksize; slabsize = umw->umw_cp->cache_slabsize; if (umw->umw_valid != NULL) mdb_free(umw->umw_valid, slabsize / chunksize); if (umw->umw_ubase != NULL) mdb_free(umw->umw_ubase, slabsize + sizeof (umem_bufctl_t)); mdb_free(umw->umw_cp, umw->umw_csize); mdb_free(umw, sizeof (umem_walk_t)); } /*ARGSUSED*/ static int umem_walk_all(uintptr_t addr, const umem_cache_t *c, mdb_walk_state_t *wsp) { /* * Buffers allocated from NOTOUCH caches can also show up as freed * memory in other caches. This can be a little confusing, so we * don't walk NOTOUCH caches when walking all caches (thereby assuring * that "::walk umem" and "::walk freemem" yield disjoint output). */ if (c->cache_cflags & UMC_NOTOUCH) return (WALK_NEXT); if (mdb_pwalk(wsp->walk_data, wsp->walk_callback, wsp->walk_cbdata, addr) == -1) return (WALK_DONE); return (WALK_NEXT); } #define UMEM_WALK_ALL(name, wsp) { \ wsp->walk_data = (name); \ if (mdb_walk("umem_cache", (mdb_walk_cb_t)umem_walk_all, wsp) == -1) \ return (WALK_ERR); \ return (WALK_DONE); \ } int umem_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_arg != NULL) wsp->walk_addr = (uintptr_t)wsp->walk_arg; if (wsp->walk_addr == 0) UMEM_WALK_ALL("umem", wsp); return (umem_walk_init_common(wsp, UM_ALLOCATED)); } int bufctl_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) UMEM_WALK_ALL("bufctl", wsp); return (umem_walk_init_common(wsp, UM_ALLOCATED | UM_BUFCTL)); } int freemem_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) UMEM_WALK_ALL("freemem", wsp); return (umem_walk_init_common(wsp, UM_FREE)); } int freectl_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) UMEM_WALK_ALL("freectl", wsp); return (umem_walk_init_common(wsp, UM_FREE | UM_BUFCTL)); } typedef struct bufctl_history_walk { void *bhw_next; umem_cache_t *bhw_cache; umem_slab_t *bhw_slab; hrtime_t bhw_timestamp; } bufctl_history_walk_t; int bufctl_history_walk_init(mdb_walk_state_t *wsp) { bufctl_history_walk_t *bhw; umem_bufctl_audit_t bc; umem_bufctl_audit_t bcn; if (wsp->walk_addr == 0) { mdb_warn("bufctl_history walk doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&bc, sizeof (bc), wsp->walk_addr) == -1) { mdb_warn("unable to read bufctl at %p", wsp->walk_addr); return (WALK_ERR); } bhw = mdb_zalloc(sizeof (*bhw), UM_SLEEP); bhw->bhw_timestamp = 0; bhw->bhw_cache = bc.bc_cache; bhw->bhw_slab = bc.bc_slab; /* * sometimes the first log entry matches the base bufctl; in that * case, skip the base bufctl. */ if (bc.bc_lastlog != NULL && mdb_vread(&bcn, sizeof (bcn), (uintptr_t)bc.bc_lastlog) != -1 && bc.bc_addr == bcn.bc_addr && bc.bc_cache == bcn.bc_cache && bc.bc_slab == bcn.bc_slab && bc.bc_timestamp == bcn.bc_timestamp && bc.bc_thread == bcn.bc_thread) bhw->bhw_next = bc.bc_lastlog; else bhw->bhw_next = (void *)wsp->walk_addr; wsp->walk_addr = (uintptr_t)bc.bc_addr; wsp->walk_data = bhw; return (WALK_NEXT); } int bufctl_history_walk_step(mdb_walk_state_t *wsp) { bufctl_history_walk_t *bhw = wsp->walk_data; uintptr_t addr = (uintptr_t)bhw->bhw_next; uintptr_t baseaddr = wsp->walk_addr; umem_bufctl_audit_t *b; UMEM_LOCAL_BUFCTL_AUDIT(&b); if (addr == 0) return (WALK_DONE); if (mdb_vread(b, UMEM_BUFCTL_AUDIT_SIZE, addr) == -1) { mdb_warn("unable to read bufctl at %p", bhw->bhw_next); return (WALK_ERR); } /* * The bufctl is only valid if the address, cache, and slab are * correct. We also check that the timestamp is decreasing, to * prevent infinite loops. */ if ((uintptr_t)b->bc_addr != baseaddr || b->bc_cache != bhw->bhw_cache || b->bc_slab != bhw->bhw_slab || (bhw->bhw_timestamp != 0 && b->bc_timestamp >= bhw->bhw_timestamp)) return (WALK_DONE); bhw->bhw_next = b->bc_lastlog; bhw->bhw_timestamp = b->bc_timestamp; return (wsp->walk_callback(addr, b, wsp->walk_cbdata)); } void bufctl_history_walk_fini(mdb_walk_state_t *wsp) { bufctl_history_walk_t *bhw = wsp->walk_data; mdb_free(bhw, sizeof (*bhw)); } typedef struct umem_log_walk { umem_bufctl_audit_t *ulw_base; umem_bufctl_audit_t **ulw_sorted; umem_log_header_t ulw_lh; size_t ulw_size; size_t ulw_maxndx; size_t ulw_ndx; } umem_log_walk_t; int umem_log_walk_init(mdb_walk_state_t *wsp) { uintptr_t lp = wsp->walk_addr; umem_log_walk_t *ulw; umem_log_header_t *lhp; int maxndx, i, j, k; /* * By default (global walk), walk the umem_transaction_log. Otherwise * read the log whose umem_log_header_t is stored at walk_addr. */ if (lp == 0 && umem_readvar(&lp, "umem_transaction_log") == -1) { mdb_warn("failed to read 'umem_transaction_log'"); return (WALK_ERR); } if (lp == 0) { mdb_warn("log is disabled\n"); return (WALK_ERR); } ulw = mdb_zalloc(sizeof (umem_log_walk_t), UM_SLEEP); lhp = &ulw->ulw_lh; if (mdb_vread(lhp, sizeof (umem_log_header_t), lp) == -1) { mdb_warn("failed to read log header at %p", lp); mdb_free(ulw, sizeof (umem_log_walk_t)); return (WALK_ERR); } ulw->ulw_size = lhp->lh_chunksize * lhp->lh_nchunks; ulw->ulw_base = mdb_alloc(ulw->ulw_size, UM_SLEEP); maxndx = lhp->lh_chunksize / UMEM_BUFCTL_AUDIT_SIZE - 1; if (mdb_vread(ulw->ulw_base, ulw->ulw_size, (uintptr_t)lhp->lh_base) == -1) { mdb_warn("failed to read log at base %p", lhp->lh_base); mdb_free(ulw->ulw_base, ulw->ulw_size); mdb_free(ulw, sizeof (umem_log_walk_t)); return (WALK_ERR); } ulw->ulw_sorted = mdb_alloc(maxndx * lhp->lh_nchunks * sizeof (umem_bufctl_audit_t *), UM_SLEEP); for (i = 0, k = 0; i < lhp->lh_nchunks; i++) { caddr_t chunk = (caddr_t) ((uintptr_t)ulw->ulw_base + i * lhp->lh_chunksize); for (j = 0; j < maxndx; j++) { /* LINTED align */ ulw->ulw_sorted[k++] = (umem_bufctl_audit_t *)chunk; chunk += UMEM_BUFCTL_AUDIT_SIZE; } } qsort(ulw->ulw_sorted, k, sizeof (umem_bufctl_audit_t *), (int(*)(const void *, const void *))bufctlcmp); ulw->ulw_maxndx = k; wsp->walk_data = ulw; return (WALK_NEXT); } int umem_log_walk_step(mdb_walk_state_t *wsp) { umem_log_walk_t *ulw = wsp->walk_data; umem_bufctl_audit_t *bcp; if (ulw->ulw_ndx == ulw->ulw_maxndx) return (WALK_DONE); bcp = ulw->ulw_sorted[ulw->ulw_ndx++]; return (wsp->walk_callback((uintptr_t)bcp - (uintptr_t)ulw->ulw_base + (uintptr_t)ulw->ulw_lh.lh_base, bcp, wsp->walk_cbdata)); } void umem_log_walk_fini(mdb_walk_state_t *wsp) { umem_log_walk_t *ulw = wsp->walk_data; mdb_free(ulw->ulw_base, ulw->ulw_size); mdb_free(ulw->ulw_sorted, ulw->ulw_maxndx * sizeof (umem_bufctl_audit_t *)); mdb_free(ulw, sizeof (umem_log_walk_t)); } typedef struct allocdby_bufctl { uintptr_t abb_addr; hrtime_t abb_ts; } allocdby_bufctl_t; typedef struct allocdby_walk { const char *abw_walk; uintptr_t abw_thread; size_t abw_nbufs; size_t abw_size; allocdby_bufctl_t *abw_buf; size_t abw_ndx; } allocdby_walk_t; int allocdby_walk_bufctl(uintptr_t addr, const umem_bufctl_audit_t *bcp, allocdby_walk_t *abw) { if ((uintptr_t)bcp->bc_thread != abw->abw_thread) return (WALK_NEXT); if (abw->abw_nbufs == abw->abw_size) { allocdby_bufctl_t *buf; size_t oldsize = sizeof (allocdby_bufctl_t) * abw->abw_size; buf = mdb_zalloc(oldsize << 1, UM_SLEEP); bcopy(abw->abw_buf, buf, oldsize); mdb_free(abw->abw_buf, oldsize); abw->abw_size <<= 1; abw->abw_buf = buf; } abw->abw_buf[abw->abw_nbufs].abb_addr = addr; abw->abw_buf[abw->abw_nbufs].abb_ts = bcp->bc_timestamp; abw->abw_nbufs++; return (WALK_NEXT); } /*ARGSUSED*/ int allocdby_walk_cache(uintptr_t addr, const umem_cache_t *c, allocdby_walk_t *abw) { if (mdb_pwalk(abw->abw_walk, (mdb_walk_cb_t)allocdby_walk_bufctl, abw, addr) == -1) { mdb_warn("couldn't walk bufctl for cache %p", addr); return (WALK_DONE); } return (WALK_NEXT); } static int allocdby_cmp(const allocdby_bufctl_t *lhs, const allocdby_bufctl_t *rhs) { if (lhs->abb_ts < rhs->abb_ts) return (1); if (lhs->abb_ts > rhs->abb_ts) return (-1); return (0); } static int allocdby_walk_init_common(mdb_walk_state_t *wsp, const char *walk) { allocdby_walk_t *abw; if (wsp->walk_addr == 0) { mdb_warn("allocdby walk doesn't support global walks\n"); return (WALK_ERR); } abw = mdb_zalloc(sizeof (allocdby_walk_t), UM_SLEEP); abw->abw_thread = wsp->walk_addr; abw->abw_walk = walk; abw->abw_size = 128; /* something reasonable */ abw->abw_buf = mdb_zalloc(abw->abw_size * sizeof (allocdby_bufctl_t), UM_SLEEP); wsp->walk_data = abw; if (mdb_walk("umem_cache", (mdb_walk_cb_t)allocdby_walk_cache, abw) == -1) { mdb_warn("couldn't walk umem_cache"); allocdby_walk_fini(wsp); return (WALK_ERR); } qsort(abw->abw_buf, abw->abw_nbufs, sizeof (allocdby_bufctl_t), (int(*)(const void *, const void *))allocdby_cmp); return (WALK_NEXT); } int allocdby_walk_init(mdb_walk_state_t *wsp) { return (allocdby_walk_init_common(wsp, "bufctl")); } int freedby_walk_init(mdb_walk_state_t *wsp) { return (allocdby_walk_init_common(wsp, "freectl")); } int allocdby_walk_step(mdb_walk_state_t *wsp) { allocdby_walk_t *abw = wsp->walk_data; uintptr_t addr; umem_bufctl_audit_t *bcp; UMEM_LOCAL_BUFCTL_AUDIT(&bcp); if (abw->abw_ndx == abw->abw_nbufs) return (WALK_DONE); addr = abw->abw_buf[abw->abw_ndx++].abb_addr; if (mdb_vread(bcp, UMEM_BUFCTL_AUDIT_SIZE, addr) == -1) { mdb_warn("couldn't read bufctl at %p", addr); return (WALK_DONE); } return (wsp->walk_callback(addr, bcp, wsp->walk_cbdata)); } void allocdby_walk_fini(mdb_walk_state_t *wsp) { allocdby_walk_t *abw = wsp->walk_data; mdb_free(abw->abw_buf, sizeof (allocdby_bufctl_t) * abw->abw_size); mdb_free(abw, sizeof (allocdby_walk_t)); } /*ARGSUSED*/ int allocdby_walk(uintptr_t addr, const umem_bufctl_audit_t *bcp, void *ignored) { char c[MDB_SYM_NAMLEN]; GElf_Sym sym; int i; mdb_printf("%0?p %12llx ", addr, bcp->bc_timestamp); for (i = 0; i < bcp->bc_depth; i++) { if (mdb_lookup_by_addr(bcp->bc_stack[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) continue; if (is_umem_sym(c, "umem_")) continue; mdb_printf("%s+0x%lx", c, bcp->bc_stack[i] - (uintptr_t)sym.st_value); break; } mdb_printf("\n"); return (WALK_NEXT); } static int allocdby_common(uintptr_t addr, uint_t flags, const char *w) { if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); mdb_printf("%-?s %12s %s\n", "BUFCTL", "TIMESTAMP", "CALLER"); if (mdb_pwalk(w, (mdb_walk_cb_t)allocdby_walk, NULL, addr) == -1) { mdb_warn("can't walk '%s' for %p", w, addr); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ int allocdby(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (allocdby_common(addr, flags, "allocdby")); } /*ARGSUSED*/ int freedby(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (allocdby_common(addr, flags, "freedby")); } typedef struct whatis_info { mdb_whatis_t *wi_w; const umem_cache_t *wi_cache; const vmem_t *wi_vmem; vmem_t *wi_msb_arena; size_t wi_slab_size; int wi_slab_found; uint_t wi_freemem; } whatis_info_t; /* call one of our dcmd functions with "-v" and the provided address */ static void whatis_call_printer(mdb_dcmd_f *dcmd, uintptr_t addr) { mdb_arg_t a; a.a_type = MDB_TYPE_STRING; a.a_un.a_str = "-v"; mdb_printf(":\n"); (void) (*dcmd)(addr, DCMD_ADDRSPEC, 1, &a); } static void whatis_print_umem(whatis_info_t *wi, uintptr_t maddr, uintptr_t addr, uintptr_t baddr) { mdb_whatis_t *w = wi->wi_w; const umem_cache_t *cp = wi->wi_cache; int quiet = (mdb_whatis_flags(w) & WHATIS_QUIET); int call_printer = (!quiet && (cp->cache_flags & UMF_AUDIT)); mdb_whatis_report_object(w, maddr, addr, ""); if (baddr != 0 && !call_printer) mdb_printf("bufctl %p ", baddr); mdb_printf("%s from %s", (wi->wi_freemem == FALSE) ? "allocated" : "freed", cp->cache_name); if (call_printer && baddr != 0) { whatis_call_printer(bufctl, baddr); return; } mdb_printf("\n"); } /*ARGSUSED*/ static int whatis_walk_umem(uintptr_t addr, void *ignored, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; uintptr_t cur; size_t size = wi->wi_cache->cache_bufsize; while (mdb_whatis_match(w, addr, size, &cur)) whatis_print_umem(wi, cur, addr, 0); return (WHATIS_WALKRET(w)); } /*ARGSUSED*/ static int whatis_walk_bufctl(uintptr_t baddr, const umem_bufctl_t *bcp, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; uintptr_t cur; uintptr_t addr = (uintptr_t)bcp->bc_addr; size_t size = wi->wi_cache->cache_bufsize; while (mdb_whatis_match(w, addr, size, &cur)) whatis_print_umem(wi, cur, addr, baddr); return (WHATIS_WALKRET(w)); } static int whatis_walk_seg(uintptr_t addr, const vmem_seg_t *vs, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; size_t size = vs->vs_end - vs->vs_start; uintptr_t cur; /* We're not interested in anything but alloc and free segments */ if (vs->vs_type != VMEM_ALLOC && vs->vs_type != VMEM_FREE) return (WALK_NEXT); while (mdb_whatis_match(w, vs->vs_start, size, &cur)) { mdb_whatis_report_object(w, cur, vs->vs_start, ""); /* * If we're not printing it seperately, provide the vmem_seg * pointer if it has a stack trace. */ if ((mdb_whatis_flags(w) & WHATIS_QUIET) && ((mdb_whatis_flags(w) & WHATIS_BUFCTL) != 0 || (vs->vs_type == VMEM_ALLOC && vs->vs_depth != 0))) { mdb_printf("vmem_seg %p ", addr); } mdb_printf("%s from %s vmem arena", (vs->vs_type == VMEM_ALLOC) ? "allocated" : "freed", wi->wi_vmem->vm_name); if (!(mdb_whatis_flags(w) & WHATIS_QUIET)) whatis_call_printer(vmem_seg, addr); else mdb_printf("\n"); } return (WHATIS_WALKRET(w)); } static int whatis_walk_vmem(uintptr_t addr, const vmem_t *vmem, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; const char *nm = vmem->vm_name; wi->wi_vmem = vmem; if (mdb_whatis_flags(w) & WHATIS_VERBOSE) mdb_printf("Searching vmem arena %s...\n", nm); if (mdb_pwalk("vmem_seg", (mdb_walk_cb_t)whatis_walk_seg, wi, addr) == -1) { mdb_warn("can't walk vmem seg for %p", addr); return (WALK_NEXT); } return (WHATIS_WALKRET(w)); } /*ARGSUSED*/ static int whatis_walk_slab(uintptr_t saddr, const umem_slab_t *sp, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; /* It must overlap with the slab data, or it's not interesting */ if (mdb_whatis_overlaps(w, (uintptr_t)sp->slab_base, wi->wi_slab_size)) { wi->wi_slab_found++; return (WALK_DONE); } return (WALK_NEXT); } static int whatis_walk_cache(uintptr_t addr, const umem_cache_t *c, whatis_info_t *wi) { mdb_whatis_t *w = wi->wi_w; char *walk, *freewalk; mdb_walk_cb_t func; int do_bufctl; /* Override the '-b' flag as necessary */ if (!(c->cache_flags & UMF_HASH)) do_bufctl = FALSE; /* no bufctls to walk */ else if (c->cache_flags & UMF_AUDIT) do_bufctl = TRUE; /* we always want debugging info */ else do_bufctl = ((mdb_whatis_flags(w) & WHATIS_BUFCTL) != 0); if (do_bufctl) { walk = "bufctl"; freewalk = "freectl"; func = (mdb_walk_cb_t)whatis_walk_bufctl; } else { walk = "umem"; freewalk = "freemem"; func = (mdb_walk_cb_t)whatis_walk_umem; } wi->wi_cache = c; if (mdb_whatis_flags(w) & WHATIS_VERBOSE) mdb_printf("Searching %s...\n", c->cache_name); /* * If more then two buffers live on each slab, figure out if we're * interested in anything in any slab before doing the more expensive * umem/freemem (bufctl/freectl) walkers. */ wi->wi_slab_size = c->cache_slabsize - c->cache_maxcolor; if (!(c->cache_flags & UMF_HASH)) wi->wi_slab_size -= sizeof (umem_slab_t); if ((wi->wi_slab_size / c->cache_chunksize) > 2) { wi->wi_slab_found = 0; if (mdb_pwalk("umem_slab", (mdb_walk_cb_t)whatis_walk_slab, wi, addr) == -1) { mdb_warn("can't find umem_slab walker"); return (WALK_DONE); } if (wi->wi_slab_found == 0) return (WALK_NEXT); } wi->wi_freemem = FALSE; if (mdb_pwalk(walk, func, wi, addr) == -1) { mdb_warn("can't find %s walker", walk); return (WALK_DONE); } if (mdb_whatis_done(w)) return (WALK_DONE); /* * We have searched for allocated memory; now search for freed memory. */ if (mdb_whatis_flags(w) & WHATIS_VERBOSE) mdb_printf("Searching %s for free memory...\n", c->cache_name); wi->wi_freemem = TRUE; if (mdb_pwalk(freewalk, func, wi, addr) == -1) { mdb_warn("can't find %s walker", freewalk); return (WALK_DONE); } return (WHATIS_WALKRET(w)); } static int whatis_walk_touch(uintptr_t addr, const umem_cache_t *c, whatis_info_t *wi) { if (c->cache_arena == wi->wi_msb_arena || (c->cache_cflags & UMC_NOTOUCH)) return (WALK_NEXT); return (whatis_walk_cache(addr, c, wi)); } static int whatis_walk_metadata(uintptr_t addr, const umem_cache_t *c, whatis_info_t *wi) { if (c->cache_arena != wi->wi_msb_arena) return (WALK_NEXT); return (whatis_walk_cache(addr, c, wi)); } static int whatis_walk_notouch(uintptr_t addr, const umem_cache_t *c, whatis_info_t *wi) { if (c->cache_arena == wi->wi_msb_arena || !(c->cache_cflags & UMC_NOTOUCH)) return (WALK_NEXT); return (whatis_walk_cache(addr, c, wi)); } /*ARGSUSED*/ static int whatis_run_umem(mdb_whatis_t *w, void *ignored) { whatis_info_t wi; bzero(&wi, sizeof (wi)); wi.wi_w = w; /* umem's metadata is allocated from the umem_internal_arena */ if (umem_readvar(&wi.wi_msb_arena, "umem_internal_arena") == -1) mdb_warn("unable to readvar \"umem_internal_arena\""); /* * We process umem caches in the following order: * * non-UMC_NOTOUCH, non-metadata (typically the most interesting) * metadata (can be huge with UMF_AUDIT) * UMC_NOTOUCH, non-metadata (see umem_walk_all()) */ if (mdb_walk("umem_cache", (mdb_walk_cb_t)whatis_walk_touch, &wi) == -1 || mdb_walk("umem_cache", (mdb_walk_cb_t)whatis_walk_metadata, &wi) == -1 || mdb_walk("umem_cache", (mdb_walk_cb_t)whatis_walk_notouch, &wi) == -1) { mdb_warn("couldn't find umem_cache walker"); return (1); } return (0); } /*ARGSUSED*/ static int whatis_run_vmem(mdb_whatis_t *w, void *ignored) { whatis_info_t wi; bzero(&wi, sizeof (wi)); wi.wi_w = w; if (mdb_walk("vmem_postfix", (mdb_walk_cb_t)whatis_walk_vmem, &wi) == -1) { mdb_warn("couldn't find vmem_postfix walker"); return (1); } return (0); } int umem_init(void) { mdb_walker_t w = { "umem_cache", "walk list of umem caches", umem_cache_walk_init, umem_cache_walk_step, umem_cache_walk_fini }; if (mdb_add_walker(&w) == -1) { mdb_warn("failed to add umem_cache walker"); return (-1); } if (umem_update_variables() == -1) return (-1); /* install a callback so that our variables are always up-to-date */ (void) mdb_callback_add(MDB_CALLBACK_STCHG, umem_statechange_cb, NULL); umem_statechange_cb(NULL); /* * Register our ::whatis callbacks. */ mdb_whatis_register("umem", whatis_run_umem, NULL, WHATIS_PRIO_ALLOCATOR, WHATIS_REG_NO_ID); mdb_whatis_register("vmem", whatis_run_vmem, NULL, WHATIS_PRIO_ALLOCATOR, WHATIS_REG_NO_ID); return (0); } typedef struct umem_log_cpu { uintptr_t umc_low; uintptr_t umc_high; } umem_log_cpu_t; int umem_log_walk(uintptr_t addr, const umem_bufctl_audit_t *b, umem_log_cpu_t *umc) { int i; for (i = 0; i < umem_max_ncpus; i++) { if (addr >= umc[i].umc_low && addr < umc[i].umc_high) break; } if (i == umem_max_ncpus) mdb_printf(" "); else mdb_printf("%3d", i); mdb_printf(" %0?p %0?p %16llx %0?p\n", addr, b->bc_addr, b->bc_timestamp, b->bc_thread); return (WALK_NEXT); } /*ARGSUSED*/ int umem_log(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { umem_log_header_t lh; umem_cpu_log_header_t clh; uintptr_t lhp, clhp; umem_log_cpu_t *umc; int i; if (umem_readvar(&lhp, "umem_transaction_log") == -1) { mdb_warn("failed to read 'umem_transaction_log'"); return (DCMD_ERR); } if (lhp == 0) { mdb_warn("no umem transaction log\n"); return (DCMD_ERR); } if (mdb_vread(&lh, sizeof (umem_log_header_t), lhp) == -1) { mdb_warn("failed to read log header at %p", lhp); return (DCMD_ERR); } clhp = lhp + ((uintptr_t)&lh.lh_cpu[0] - (uintptr_t)&lh); umc = mdb_zalloc(sizeof (umem_log_cpu_t) * umem_max_ncpus, UM_SLEEP | UM_GC); for (i = 0; i < umem_max_ncpus; i++) { if (mdb_vread(&clh, sizeof (clh), clhp) == -1) { mdb_warn("cannot read cpu %d's log header at %p", i, clhp); return (DCMD_ERR); } umc[i].umc_low = clh.clh_chunk * lh.lh_chunksize + (uintptr_t)lh.lh_base; umc[i].umc_high = (uintptr_t)clh.clh_current; clhp += sizeof (umem_cpu_log_header_t); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%3s %-?s %-?s %16s %-?s\n", "CPU", "ADDR", "BUFADDR", "TIMESTAMP", "THREAD"); } /* * If we have been passed an address, we'll just print out that * log entry. */ if (flags & DCMD_ADDRSPEC) { umem_bufctl_audit_t *bp; UMEM_LOCAL_BUFCTL_AUDIT(&bp); if (mdb_vread(bp, UMEM_BUFCTL_AUDIT_SIZE, addr) == -1) { mdb_warn("failed to read bufctl at %p", addr); return (DCMD_ERR); } (void) umem_log_walk(addr, bp, umc); return (DCMD_OK); } if (mdb_walk("umem_log", (mdb_walk_cb_t)umem_log_walk, umc) == -1) { mdb_warn("can't find umem log walker"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct bufctl_history_cb { int bhc_flags; int bhc_argc; const mdb_arg_t *bhc_argv; int bhc_ret; } bufctl_history_cb_t; /*ARGSUSED*/ static int bufctl_history_callback(uintptr_t addr, const void *ign, void *arg) { bufctl_history_cb_t *bhc = arg; bhc->bhc_ret = bufctl(addr, bhc->bhc_flags, bhc->bhc_argc, bhc->bhc_argv); bhc->bhc_flags &= ~DCMD_LOOPFIRST; return ((bhc->bhc_ret == DCMD_OK)? WALK_NEXT : WALK_DONE); } void bufctl_help(void) { mdb_printf("%s\n", "Display the contents of umem_bufctl_audit_ts, with optional filtering.\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -v Display the full content of the bufctl, including its stack trace\n" " -h retrieve the bufctl's transaction history, if available\n" " -a addr\n" " filter out bufctls not involving the buffer at addr\n" " -c caller\n" " filter out bufctls without the function/PC in their stack trace\n" " -e earliest\n" " filter out bufctls timestamped before earliest\n" " -l latest\n" " filter out bufctls timestamped after latest\n" " -t thread\n" " filter out bufctls not involving thread\n"); } int bufctl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE; uint_t history = FALSE; uint_t in_history = FALSE; uintptr_t caller = 0, thread = 0; uintptr_t laddr, haddr, baddr = 0; hrtime_t earliest = 0, latest = 0; int i, depth; char c[MDB_SYM_NAMLEN]; GElf_Sym sym; umem_bufctl_audit_t *bcp; UMEM_LOCAL_BUFCTL_AUDIT(&bcp); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'h', MDB_OPT_SETBITS, TRUE, &history, 'H', MDB_OPT_SETBITS, TRUE, &in_history, /* internal */ 'c', MDB_OPT_UINTPTR, &caller, 't', MDB_OPT_UINTPTR, &thread, 'e', MDB_OPT_UINT64, &earliest, 'l', MDB_OPT_UINT64, &latest, 'a', MDB_OPT_UINTPTR, &baddr, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (in_history && !history) return (DCMD_USAGE); if (history && !in_history) { mdb_arg_t *nargv = mdb_zalloc(sizeof (*nargv) * (argc + 1), UM_SLEEP | UM_GC); bufctl_history_cb_t bhc; nargv[0].a_type = MDB_TYPE_STRING; nargv[0].a_un.a_str = "-H"; /* prevent recursion */ for (i = 0; i < argc; i++) nargv[i + 1] = argv[i]; /* * When in history mode, we treat each element as if it * were in a seperate loop, so that the headers group * bufctls with similar histories. */ bhc.bhc_flags = flags | DCMD_LOOP | DCMD_LOOPFIRST; bhc.bhc_argc = argc + 1; bhc.bhc_argv = nargv; bhc.bhc_ret = DCMD_OK; if (mdb_pwalk("bufctl_history", bufctl_history_callback, &bhc, addr) == -1) { mdb_warn("unable to walk bufctl_history"); return (DCMD_ERR); } if (bhc.bhc_ret == DCMD_OK && !(flags & DCMD_PIPE_OUT)) mdb_printf("\n"); return (bhc.bhc_ret); } if (DCMD_HDRSPEC(flags) && !(flags & DCMD_PIPE_OUT)) { if (verbose) { mdb_printf("%16s %16s %16s %16s\n" "%%16s %16s %16s %16s%\n", "ADDR", "BUFADDR", "TIMESTAMP", "THREAD", "", "CACHE", "LASTLOG", "CONTENTS"); } else { mdb_printf("%%-?s %-?s %-12s %5s %s%\n", "ADDR", "BUFADDR", "TIMESTAMP", "THRD", "CALLER"); } } if (mdb_vread(bcp, UMEM_BUFCTL_AUDIT_SIZE, addr) == -1) { mdb_warn("couldn't read bufctl at %p", addr); return (DCMD_ERR); } /* * Guard against bogus bc_depth in case the bufctl is corrupt or * the address does not really refer to a bufctl. */ depth = MIN(bcp->bc_depth, umem_stack_depth); if (caller != 0) { laddr = caller; haddr = caller + sizeof (caller); if (mdb_lookup_by_addr(caller, MDB_SYM_FUZZY, c, sizeof (c), &sym) != -1 && caller == (uintptr_t)sym.st_value) { /* * We were provided an exact symbol value; any * address in the function is valid. */ laddr = (uintptr_t)sym.st_value; haddr = (uintptr_t)sym.st_value + sym.st_size; } for (i = 0; i < depth; i++) if (bcp->bc_stack[i] >= laddr && bcp->bc_stack[i] < haddr) break; if (i == depth) return (DCMD_OK); } if (thread != 0 && (uintptr_t)bcp->bc_thread != thread) return (DCMD_OK); if (earliest != 0 && bcp->bc_timestamp < earliest) return (DCMD_OK); if (latest != 0 && bcp->bc_timestamp > latest) return (DCMD_OK); if (baddr != 0 && (uintptr_t)bcp->bc_addr != baddr) return (DCMD_OK); if (flags & DCMD_PIPE_OUT) { mdb_printf("%#r\n", addr); return (DCMD_OK); } if (verbose) { mdb_printf( "%%16p% %16p %16llx %16d\n" "%16s %16p %16p %16p\n", addr, bcp->bc_addr, bcp->bc_timestamp, bcp->bc_thread, "", bcp->bc_cache, bcp->bc_lastlog, bcp->bc_contents); mdb_inc_indent(17); for (i = 0; i < depth; i++) mdb_printf("%a\n", bcp->bc_stack[i]); mdb_dec_indent(17); mdb_printf("\n"); } else { mdb_printf("%0?p %0?p %12llx %5d", addr, bcp->bc_addr, bcp->bc_timestamp, bcp->bc_thread); for (i = 0; i < depth; i++) { if (mdb_lookup_by_addr(bcp->bc_stack[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) continue; if (is_umem_sym(c, "umem_")) continue; mdb_printf(" %a\n", bcp->bc_stack[i]); break; } if (i >= depth) mdb_printf("\n"); } return (DCMD_OK); } /*ARGSUSED*/ int bufctl_audit(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_arg_t a; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (argc != 0) return (DCMD_USAGE); a.a_type = MDB_TYPE_STRING; a.a_un.a_str = "-v"; return (bufctl(addr, flags, 1, &a)); } typedef struct umem_verify { uint64_t *umv_buf; /* buffer to read cache contents into */ size_t umv_size; /* number of bytes in umv_buf */ int umv_corruption; /* > 0 if corruption found. */ int umv_besilent; /* report actual corruption sites */ struct umem_cache umv_cache; /* the cache we're operating on */ } umem_verify_t; /* * verify_pattern() * verify that buf is filled with the pattern pat. */ static int64_t verify_pattern(uint64_t *buf_arg, size_t size, uint64_t pat) { /*LINTED*/ uint64_t *bufend = (uint64_t *)((char *)buf_arg + size); uint64_t *buf; for (buf = buf_arg; buf < bufend; buf++) if (*buf != pat) return ((uintptr_t)buf - (uintptr_t)buf_arg); return (-1); } /* * verify_buftag() * verify that btp->bt_bxstat == (bcp ^ pat) */ static int verify_buftag(umem_buftag_t *btp, uintptr_t pat) { return (btp->bt_bxstat == ((intptr_t)btp->bt_bufctl ^ pat) ? 0 : -1); } /* * verify_free() * verify the integrity of a free block of memory by checking * that it is filled with 0xdeadbeef and that its buftag is sane. */ /*ARGSUSED1*/ static int verify_free(uintptr_t addr, const void *data, void *private) { umem_verify_t *umv = (umem_verify_t *)private; uint64_t *buf = umv->umv_buf; /* buf to validate */ int64_t corrupt; /* corruption offset */ umem_buftag_t *buftagp; /* ptr to buftag */ umem_cache_t *cp = &umv->umv_cache; int besilent = umv->umv_besilent; /*LINTED*/ buftagp = UMEM_BUFTAG(cp, buf); /* * Read the buffer to check. */ if (mdb_vread(buf, umv->umv_size, addr) == -1) { if (!besilent) mdb_warn("couldn't read %p", addr); return (WALK_NEXT); } if ((corrupt = verify_pattern(buf, cp->cache_verify, UMEM_FREE_PATTERN)) >= 0) { if (!besilent) mdb_printf("buffer %p (free) seems corrupted, at %p\n", addr, (uintptr_t)addr + corrupt); goto corrupt; } if ((cp->cache_flags & UMF_HASH) && buftagp->bt_redzone != UMEM_REDZONE_PATTERN) { if (!besilent) mdb_printf("buffer %p (free) seems to " "have a corrupt redzone pattern\n", addr); goto corrupt; } /* * confirm bufctl pointer integrity. */ if (verify_buftag(buftagp, UMEM_BUFTAG_FREE) == -1) { if (!besilent) mdb_printf("buffer %p (free) has a corrupt " "buftag\n", addr); goto corrupt; } return (WALK_NEXT); corrupt: umv->umv_corruption++; return (WALK_NEXT); } /* * verify_alloc() * Verify that the buftag of an allocated buffer makes sense with respect * to the buffer. */ /*ARGSUSED1*/ static int verify_alloc(uintptr_t addr, const void *data, void *private) { umem_verify_t *umv = (umem_verify_t *)private; umem_cache_t *cp = &umv->umv_cache; uint64_t *buf = umv->umv_buf; /* buf to validate */ /*LINTED*/ umem_buftag_t *buftagp = UMEM_BUFTAG(cp, buf); uint32_t *ip = (uint32_t *)buftagp; uint8_t *bp = (uint8_t *)buf; int looks_ok = 0, size_ok = 1; /* flags for finding corruption */ int besilent = umv->umv_besilent; /* * Read the buffer to check. */ if (mdb_vread(buf, umv->umv_size, addr) == -1) { if (!besilent) mdb_warn("couldn't read %p", addr); return (WALK_NEXT); } /* * There are two cases to handle: * 1. If the buf was alloc'd using umem_cache_alloc, it will have * 0xfeedfacefeedface at the end of it * 2. If the buf was alloc'd using umem_alloc, it will have * 0xbb just past the end of the region in use. At the buftag, * it will have 0xfeedface (or, if the whole buffer is in use, * 0xfeedface & bb000000 or 0xfeedfacf & 000000bb depending on * endianness), followed by 32 bits containing the offset of the * 0xbb byte in the buffer. * * Finally, the two 32-bit words that comprise the second half of the * buftag should xor to UMEM_BUFTAG_ALLOC */ if (buftagp->bt_redzone == UMEM_REDZONE_PATTERN) looks_ok = 1; else if (!UMEM_SIZE_VALID(ip[1])) size_ok = 0; else if (bp[UMEM_SIZE_DECODE(ip[1])] == UMEM_REDZONE_BYTE) looks_ok = 1; else size_ok = 0; if (!size_ok) { if (!besilent) mdb_printf("buffer %p (allocated) has a corrupt " "redzone size encoding\n", addr); goto corrupt; } if (!looks_ok) { if (!besilent) mdb_printf("buffer %p (allocated) has a corrupt " "redzone signature\n", addr); goto corrupt; } if (verify_buftag(buftagp, UMEM_BUFTAG_ALLOC) == -1) { if (!besilent) mdb_printf("buffer %p (allocated) has a " "corrupt buftag\n", addr); goto corrupt; } return (WALK_NEXT); corrupt: umv->umv_corruption++; return (WALK_NEXT); } /*ARGSUSED2*/ int umem_verify(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (flags & DCMD_ADDRSPEC) { int check_alloc = 0, check_free = 0; umem_verify_t umv; if (mdb_vread(&umv.umv_cache, sizeof (umv.umv_cache), addr) == -1) { mdb_warn("couldn't read umem_cache %p", addr); return (DCMD_ERR); } umv.umv_size = umv.umv_cache.cache_buftag + sizeof (umem_buftag_t); umv.umv_buf = mdb_alloc(umv.umv_size, UM_SLEEP | UM_GC); umv.umv_corruption = 0; if ((umv.umv_cache.cache_flags & UMF_REDZONE)) { check_alloc = 1; if (umv.umv_cache.cache_flags & UMF_DEADBEEF) check_free = 1; } else { if (!(flags & DCMD_LOOP)) { mdb_warn("cache %p (%s) does not have " "redzone checking enabled\n", addr, umv.umv_cache.cache_name); } return (DCMD_ERR); } if (flags & DCMD_LOOP) { /* * table mode, don't print out every corrupt buffer */ umv.umv_besilent = 1; } else { mdb_printf("Summary for cache '%s'\n", umv.umv_cache.cache_name); mdb_inc_indent(2); umv.umv_besilent = 0; } if (check_alloc) (void) mdb_pwalk("umem", verify_alloc, &umv, addr); if (check_free) (void) mdb_pwalk("freemem", verify_free, &umv, addr); if (flags & DCMD_LOOP) { if (umv.umv_corruption == 0) { mdb_printf("%-*s %?p clean\n", UMEM_CACHE_NAMELEN, umv.umv_cache.cache_name, addr); } else { char *s = ""; /* optional s in "buffer[s]" */ if (umv.umv_corruption > 1) s = "s"; mdb_printf("%-*s %?p %d corrupt buffer%s\n", UMEM_CACHE_NAMELEN, umv.umv_cache.cache_name, addr, umv.umv_corruption, s); } } else { /* * This is the more verbose mode, when the user has * type addr::umem_verify. If the cache was clean, * nothing will have yet been printed. So say something. */ if (umv.umv_corruption == 0) mdb_printf("clean\n"); mdb_dec_indent(2); } } else { /* * If the user didn't specify a cache to verify, we'll walk all * umem_cache's, specifying ourself as a callback for each... * this is the equivalent of '::walk umem_cache .::umem_verify' */ mdb_printf("%%-*s %-?s %-20s%\n", UMEM_CACHE_NAMELEN, "Cache Name", "Addr", "Cache Integrity"); (void) (mdb_walk_dcmd("umem_cache", "umem_verify", 0, NULL)); } return (DCMD_OK); } typedef struct vmem_node { struct vmem_node *vn_next; struct vmem_node *vn_parent; struct vmem_node *vn_sibling; struct vmem_node *vn_children; uintptr_t vn_addr; int vn_marked; vmem_t vn_vmem; } vmem_node_t; typedef struct vmem_walk { vmem_node_t *vw_root; vmem_node_t *vw_current; } vmem_walk_t; int vmem_walk_init(mdb_walk_state_t *wsp) { uintptr_t vaddr, paddr; vmem_node_t *head = NULL, *root = NULL, *current = NULL, *parent, *vp; vmem_walk_t *vw; if (umem_readvar(&vaddr, "vmem_list") == -1) { mdb_warn("couldn't read 'vmem_list'"); return (WALK_ERR); } while (vaddr != 0) { vp = mdb_zalloc(sizeof (vmem_node_t), UM_SLEEP); vp->vn_addr = vaddr; vp->vn_next = head; head = vp; if (vaddr == wsp->walk_addr) current = vp; if (mdb_vread(&vp->vn_vmem, sizeof (vmem_t), vaddr) == -1) { mdb_warn("couldn't read vmem_t at %p", vaddr); goto err; } vaddr = (uintptr_t)vp->vn_vmem.vm_next; } for (vp = head; vp != NULL; vp = vp->vn_next) { if ((paddr = (uintptr_t)vp->vn_vmem.vm_source) == 0) { vp->vn_sibling = root; root = vp; continue; } for (parent = head; parent != NULL; parent = parent->vn_next) { if (parent->vn_addr != paddr) continue; vp->vn_sibling = parent->vn_children; parent->vn_children = vp; vp->vn_parent = parent; break; } if (parent == NULL) { mdb_warn("couldn't find %p's parent (%p)\n", vp->vn_addr, paddr); goto err; } } vw = mdb_zalloc(sizeof (vmem_walk_t), UM_SLEEP); vw->vw_root = root; if (current != NULL) vw->vw_current = current; else vw->vw_current = root; wsp->walk_data = vw; return (WALK_NEXT); err: for (vp = head; head != NULL; vp = head) { head = vp->vn_next; mdb_free(vp, sizeof (vmem_node_t)); } return (WALK_ERR); } int vmem_walk_step(mdb_walk_state_t *wsp) { vmem_walk_t *vw = wsp->walk_data; vmem_node_t *vp; int rval; if ((vp = vw->vw_current) == NULL) return (WALK_DONE); rval = wsp->walk_callback(vp->vn_addr, &vp->vn_vmem, wsp->walk_cbdata); if (vp->vn_children != NULL) { vw->vw_current = vp->vn_children; return (rval); } do { vw->vw_current = vp->vn_sibling; vp = vp->vn_parent; } while (vw->vw_current == NULL && vp != NULL); return (rval); } /* * The "vmem_postfix" walk walks the vmem arenas in post-fix order; all * children are visited before their parent. We perform the postfix walk * iteratively (rather than recursively) to allow mdb to regain control * after each callback. */ int vmem_postfix_walk_step(mdb_walk_state_t *wsp) { vmem_walk_t *vw = wsp->walk_data; vmem_node_t *vp = vw->vw_current; int rval; /* * If this node is marked, then we know that we have already visited * all of its children. If the node has any siblings, they need to * be visited next; otherwise, we need to visit the parent. Note * that vp->vn_marked will only be zero on the first invocation of * the step function. */ if (vp->vn_marked) { if (vp->vn_sibling != NULL) vp = vp->vn_sibling; else if (vp->vn_parent != NULL) vp = vp->vn_parent; else { /* * We have neither a parent, nor a sibling, and we * have already been visited; we're done. */ return (WALK_DONE); } } /* * Before we visit this node, visit its children. */ while (vp->vn_children != NULL && !vp->vn_children->vn_marked) vp = vp->vn_children; vp->vn_marked = 1; vw->vw_current = vp; rval = wsp->walk_callback(vp->vn_addr, &vp->vn_vmem, wsp->walk_cbdata); return (rval); } void vmem_walk_fini(mdb_walk_state_t *wsp) { vmem_walk_t *vw = wsp->walk_data; vmem_node_t *root = vw->vw_root; int done; if (root == NULL) return; if ((vw->vw_root = root->vn_children) != NULL) vmem_walk_fini(wsp); vw->vw_root = root->vn_sibling; done = (root->vn_sibling == NULL && root->vn_parent == NULL); mdb_free(root, sizeof (vmem_node_t)); if (done) { mdb_free(vw, sizeof (vmem_walk_t)); } else { vmem_walk_fini(wsp); } } typedef struct vmem_seg_walk { uint8_t vsw_type; uintptr_t vsw_start; uintptr_t vsw_current; } vmem_seg_walk_t; /*ARGSUSED*/ int vmem_seg_walk_common_init(mdb_walk_state_t *wsp, uint8_t type, char *name) { vmem_seg_walk_t *vsw; if (wsp->walk_addr == 0) { mdb_warn("vmem_%s does not support global walks\n", name); return (WALK_ERR); } wsp->walk_data = vsw = mdb_alloc(sizeof (vmem_seg_walk_t), UM_SLEEP); vsw->vsw_type = type; vsw->vsw_start = wsp->walk_addr + OFFSETOF(vmem_t, vm_seg0); vsw->vsw_current = vsw->vsw_start; return (WALK_NEXT); } /* * vmem segments can't have type 0 (this should be added to vmem_impl.h). */ #define VMEM_NONE 0 int vmem_alloc_walk_init(mdb_walk_state_t *wsp) { return (vmem_seg_walk_common_init(wsp, VMEM_ALLOC, "alloc")); } int vmem_free_walk_init(mdb_walk_state_t *wsp) { return (vmem_seg_walk_common_init(wsp, VMEM_FREE, "free")); } int vmem_span_walk_init(mdb_walk_state_t *wsp) { return (vmem_seg_walk_common_init(wsp, VMEM_SPAN, "span")); } int vmem_seg_walk_init(mdb_walk_state_t *wsp) { return (vmem_seg_walk_common_init(wsp, VMEM_NONE, "seg")); } int vmem_seg_walk_step(mdb_walk_state_t *wsp) { vmem_seg_t seg; vmem_seg_walk_t *vsw = wsp->walk_data; uintptr_t addr = vsw->vsw_current; static size_t seg_size = 0; int rval; if (!seg_size) { if (umem_readvar(&seg_size, "vmem_seg_size") == -1) { mdb_warn("failed to read 'vmem_seg_size'"); seg_size = sizeof (vmem_seg_t); } } if (seg_size < sizeof (seg)) bzero((caddr_t)&seg + seg_size, sizeof (seg) - seg_size); if (mdb_vread(&seg, seg_size, addr) == -1) { mdb_warn("couldn't read vmem_seg at %p", addr); return (WALK_ERR); } vsw->vsw_current = (uintptr_t)seg.vs_anext; if (vsw->vsw_type != VMEM_NONE && seg.vs_type != vsw->vsw_type) { rval = WALK_NEXT; } else { rval = wsp->walk_callback(addr, &seg, wsp->walk_cbdata); } if (vsw->vsw_current == vsw->vsw_start) return (WALK_DONE); return (rval); } void vmem_seg_walk_fini(mdb_walk_state_t *wsp) { vmem_seg_walk_t *vsw = wsp->walk_data; mdb_free(vsw, sizeof (vmem_seg_walk_t)); } #define VMEM_NAMEWIDTH 22 int vmem(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { vmem_t v, parent; uintptr_t paddr; int ident = 0; char c[VMEM_NAMEWIDTH]; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("vmem", "vmem", argc, argv) == -1) { mdb_warn("can't walk vmem"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%-?s %-*s %10s %12s %9s %5s\n", "ADDR", VMEM_NAMEWIDTH, "NAME", "INUSE", "TOTAL", "SUCCEED", "FAIL"); if (mdb_vread(&v, sizeof (v), addr) == -1) { mdb_warn("couldn't read vmem at %p", addr); return (DCMD_ERR); } for (paddr = (uintptr_t)v.vm_source; paddr != 0; ident += 2) { if (mdb_vread(&parent, sizeof (parent), paddr) == -1) { mdb_warn("couldn't trace %p's ancestry", addr); ident = 0; break; } paddr = (uintptr_t)parent.vm_source; } (void) mdb_snprintf(c, VMEM_NAMEWIDTH, "%*s%s", ident, "", v.vm_name); mdb_printf("%0?p %-*s %10llu %12llu %9llu %5llu\n", addr, VMEM_NAMEWIDTH, c, v.vm_kstat.vk_mem_inuse, v.vm_kstat.vk_mem_total, v.vm_kstat.vk_alloc, v.vm_kstat.vk_fail); return (DCMD_OK); } void vmem_seg_help(void) { mdb_printf("%s\n", "Display the contents of vmem_seg_ts, with optional filtering.\n" "\n" "A vmem_seg_t represents a range of addresses (or arbitrary numbers),\n" "representing a single chunk of data. Only ALLOC segments have debugging\n" "information.\n"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -v Display the full content of the vmem_seg, including its stack trace\n" " -s report the size of the segment, instead of the end address\n" " -c caller\n" " filter out segments without the function/PC in their stack trace\n" " -e earliest\n" " filter out segments timestamped before earliest\n" " -l latest\n" " filter out segments timestamped after latest\n" " -m minsize\n" " filer out segments smaller than minsize\n" " -M maxsize\n" " filer out segments larger than maxsize\n" " -t thread\n" " filter out segments not involving thread\n" " -T type\n" " filter out segments not of type 'type'\n" " type is one of: ALLOC/FREE/SPAN/ROTOR/WALKER\n"); } /*ARGSUSED*/ int vmem_seg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { vmem_seg_t vs; uintptr_t *stk = vs.vs_stack; uintptr_t sz; uint8_t t; const char *type = NULL; GElf_Sym sym; char c[MDB_SYM_NAMLEN]; int no_debug; int i; int depth; uintptr_t laddr, haddr; uintptr_t caller = 0, thread = 0; uintptr_t minsize = 0, maxsize = 0; hrtime_t earliest = 0, latest = 0; uint_t size = 0; uint_t verbose = 0; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'c', MDB_OPT_UINTPTR, &caller, 'e', MDB_OPT_UINT64, &earliest, 'l', MDB_OPT_UINT64, &latest, 's', MDB_OPT_SETBITS, TRUE, &size, 'm', MDB_OPT_UINTPTR, &minsize, 'M', MDB_OPT_UINTPTR, &maxsize, 't', MDB_OPT_UINTPTR, &thread, 'T', MDB_OPT_STR, &type, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags) && !(flags & DCMD_PIPE_OUT)) { if (verbose) { mdb_printf("%16s %4s %16s %16s %16s\n" "%%16s %4s %16s %16s %16s%\n", "ADDR", "TYPE", "START", "END", "SIZE", "", "", "THREAD", "TIMESTAMP", ""); } else { mdb_printf("%?s %4s %?s %?s %s\n", "ADDR", "TYPE", "START", size? "SIZE" : "END", "WHO"); } } if (mdb_vread(&vs, sizeof (vs), addr) == -1) { mdb_warn("couldn't read vmem_seg at %p", addr); return (DCMD_ERR); } if (type != NULL) { if (strcmp(type, "ALLC") == 0 || strcmp(type, "ALLOC") == 0) t = VMEM_ALLOC; else if (strcmp(type, "FREE") == 0) t = VMEM_FREE; else if (strcmp(type, "SPAN") == 0) t = VMEM_SPAN; else if (strcmp(type, "ROTR") == 0 || strcmp(type, "ROTOR") == 0) t = VMEM_ROTOR; else if (strcmp(type, "WLKR") == 0 || strcmp(type, "WALKER") == 0) t = VMEM_WALKER; else { mdb_warn("\"%s\" is not a recognized vmem_seg type\n", type); return (DCMD_ERR); } if (vs.vs_type != t) return (DCMD_OK); } sz = vs.vs_end - vs.vs_start; if (minsize != 0 && sz < minsize) return (DCMD_OK); if (maxsize != 0 && sz > maxsize) return (DCMD_OK); t = vs.vs_type; depth = vs.vs_depth; /* * debug info, when present, is only accurate for VMEM_ALLOC segments */ no_debug = (t != VMEM_ALLOC) || (depth == 0 || depth > VMEM_STACK_DEPTH); if (no_debug) { if (caller != 0 || thread != 0 || earliest != 0 || latest != 0) return (DCMD_OK); /* not enough info */ } else { if (caller != 0) { laddr = caller; haddr = caller + sizeof (caller); if (mdb_lookup_by_addr(caller, MDB_SYM_FUZZY, c, sizeof (c), &sym) != -1 && caller == (uintptr_t)sym.st_value) { /* * We were provided an exact symbol value; any * address in the function is valid. */ laddr = (uintptr_t)sym.st_value; haddr = (uintptr_t)sym.st_value + sym.st_size; } for (i = 0; i < depth; i++) if (vs.vs_stack[i] >= laddr && vs.vs_stack[i] < haddr) break; if (i == depth) return (DCMD_OK); } if (thread != 0 && (uintptr_t)vs.vs_thread != thread) return (DCMD_OK); if (earliest != 0 && vs.vs_timestamp < earliest) return (DCMD_OK); if (latest != 0 && vs.vs_timestamp > latest) return (DCMD_OK); } type = (t == VMEM_ALLOC ? "ALLC" : t == VMEM_FREE ? "FREE" : t == VMEM_SPAN ? "SPAN" : t == VMEM_ROTOR ? "ROTR" : t == VMEM_WALKER ? "WLKR" : "????"); if (flags & DCMD_PIPE_OUT) { mdb_printf("%#r\n", addr); return (DCMD_OK); } if (verbose) { mdb_printf("%%16p% %4s %16p %16p %16d\n", addr, type, vs.vs_start, vs.vs_end, sz); if (no_debug) return (DCMD_OK); mdb_printf("%16s %4s %16d %16llx\n", "", "", vs.vs_thread, vs.vs_timestamp); mdb_inc_indent(17); for (i = 0; i < depth; i++) { mdb_printf("%a\n", stk[i]); } mdb_dec_indent(17); mdb_printf("\n"); } else { mdb_printf("%0?p %4s %0?p %0?p", addr, type, vs.vs_start, size? sz : vs.vs_end); if (no_debug) { mdb_printf("\n"); return (DCMD_OK); } for (i = 0; i < depth; i++) { if (mdb_lookup_by_addr(stk[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) continue; if (is_umem_sym(c, "vmem_")) continue; break; } mdb_printf(" %a\n", stk[i]); } return (DCMD_OK); } /*ARGSUSED*/ static int showbc(uintptr_t addr, const umem_bufctl_audit_t *bcp, hrtime_t *newest) { char name[UMEM_CACHE_NAMELEN + 1]; hrtime_t delta; int i, depth; if (bcp->bc_timestamp == 0) return (WALK_DONE); if (*newest == 0) *newest = bcp->bc_timestamp; delta = *newest - bcp->bc_timestamp; depth = MIN(bcp->bc_depth, umem_stack_depth); if (mdb_readstr(name, sizeof (name), (uintptr_t) &bcp->bc_cache->cache_name) <= 0) (void) mdb_snprintf(name, sizeof (name), "%a", bcp->bc_cache); mdb_printf("\nT-%lld.%09lld addr=%p %s\n", delta / NANOSEC, delta % NANOSEC, bcp->bc_addr, name); for (i = 0; i < depth; i++) mdb_printf("\t %a\n", bcp->bc_stack[i]); return (WALK_NEXT); } int umalog(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *logname = "umem_transaction_log"; hrtime_t newest = 0; if ((flags & DCMD_ADDRSPEC) || argc > 1) return (DCMD_USAGE); if (argc > 0) { if (argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (strcmp(argv->a_un.a_str, "fail") == 0) logname = "umem_failure_log"; else if (strcmp(argv->a_un.a_str, "slab") == 0) logname = "umem_slab_log"; else return (DCMD_USAGE); } if (umem_readvar(&addr, logname) == -1) { mdb_warn("failed to read %s log header pointer", logname); return (DCMD_ERR); } if (mdb_pwalk("umem_log", (mdb_walk_cb_t)showbc, &newest, addr) == -1) { mdb_warn("failed to walk umem log"); return (DCMD_ERR); } return (DCMD_OK); } /* * As the final lure for die-hard crash(8) users, we provide ::umausers here. * The first piece is a structure which we use to accumulate umem_cache_t * addresses of interest. The umc_add is used as a callback for the umem_cache * walker; we either add all caches, or ones named explicitly as arguments. */ typedef struct umclist { const char *umc_name; /* Name to match (or NULL) */ uintptr_t *umc_caches; /* List of umem_cache_t addrs */ int umc_nelems; /* Num entries in umc_caches */ int umc_size; /* Size of umc_caches array */ } umclist_t; static int umc_add(uintptr_t addr, const umem_cache_t *cp, umclist_t *umc) { void *p; int s; if (umc->umc_name == NULL || strcmp(cp->cache_name, umc->umc_name) == 0) { /* * If we have a match, grow our array (if necessary), and then * add the virtual address of the matching cache to our list. */ if (umc->umc_nelems >= umc->umc_size) { s = umc->umc_size ? umc->umc_size * 2 : 256; p = mdb_alloc(sizeof (uintptr_t) * s, UM_SLEEP | UM_GC); bcopy(umc->umc_caches, p, sizeof (uintptr_t) * umc->umc_size); umc->umc_caches = p; umc->umc_size = s; } umc->umc_caches[umc->umc_nelems++] = addr; return (umc->umc_name ? WALK_DONE : WALK_NEXT); } return (WALK_NEXT); } /* * The second piece of ::umausers is a hash table of allocations. Each * allocation owner is identified by its stack trace and data_size. We then * track the total bytes of all such allocations, and the number of allocations * to report at the end. Once we have a list of caches, we walk through the * allocated bufctls of each, and update our hash table accordingly. */ typedef struct umowner { struct umowner *umo_head; /* First hash elt in bucket */ struct umowner *umo_next; /* Next hash elt in chain */ size_t umo_signature; /* Hash table signature */ uint_t umo_num; /* Number of allocations */ size_t umo_data_size; /* Size of each allocation */ size_t umo_total_size; /* Total bytes of allocation */ int umo_depth; /* Depth of stack trace */ uintptr_t *umo_stack; /* Stack trace */ } umowner_t; typedef struct umusers { const umem_cache_t *umu_cache; /* Current umem cache */ umowner_t *umu_hash; /* Hash table of owners */ uintptr_t *umu_stacks; /* stacks for owners */ int umu_nelems; /* Number of entries in use */ int umu_size; /* Total number of entries */ } umusers_t; static void umu_add(umusers_t *umu, const umem_bufctl_audit_t *bcp, size_t size, size_t data_size) { int i, depth = MIN(bcp->bc_depth, umem_stack_depth); size_t bucket, signature = data_size; umowner_t *umo, *umoend; /* * If the hash table is full, double its size and rehash everything. */ if (umu->umu_nelems >= umu->umu_size) { int s = umu->umu_size ? umu->umu_size * 2 : 1024; size_t umowner_size = sizeof (umowner_t); size_t trace_size = umem_stack_depth * sizeof (uintptr_t); uintptr_t *new_stacks; umo = mdb_alloc(umowner_size * s, UM_SLEEP | UM_GC); new_stacks = mdb_alloc(trace_size * s, UM_SLEEP | UM_GC); bcopy(umu->umu_hash, umo, umowner_size * umu->umu_size); bcopy(umu->umu_stacks, new_stacks, trace_size * umu->umu_size); umu->umu_hash = umo; umu->umu_stacks = new_stacks; umu->umu_size = s; umoend = umu->umu_hash + umu->umu_size; for (umo = umu->umu_hash; umo < umoend; umo++) { umo->umo_head = NULL; umo->umo_stack = &umu->umu_stacks[ umem_stack_depth * (umo - umu->umu_hash)]; } umoend = umu->umu_hash + umu->umu_nelems; for (umo = umu->umu_hash; umo < umoend; umo++) { bucket = umo->umo_signature & (umu->umu_size - 1); umo->umo_next = umu->umu_hash[bucket].umo_head; umu->umu_hash[bucket].umo_head = umo; } } /* * Finish computing the hash signature from the stack trace, and then * see if the owner is in the hash table. If so, update our stats. */ for (i = 0; i < depth; i++) signature += bcp->bc_stack[i]; bucket = signature & (umu->umu_size - 1); for (umo = umu->umu_hash[bucket].umo_head; umo; umo = umo->umo_next) { if (umo->umo_signature == signature) { size_t difference = 0; difference |= umo->umo_data_size - data_size; difference |= umo->umo_depth - depth; for (i = 0; i < depth; i++) { difference |= umo->umo_stack[i] - bcp->bc_stack[i]; } if (difference == 0) { umo->umo_total_size += size; umo->umo_num++; return; } } } /* * If the owner is not yet hashed, grab the next element and fill it * in based on the allocation information. */ umo = &umu->umu_hash[umu->umu_nelems++]; umo->umo_next = umu->umu_hash[bucket].umo_head; umu->umu_hash[bucket].umo_head = umo; umo->umo_signature = signature; umo->umo_num = 1; umo->umo_data_size = data_size; umo->umo_total_size = size; umo->umo_depth = depth; for (i = 0; i < depth; i++) umo->umo_stack[i] = bcp->bc_stack[i]; } /* * When ::umausers is invoked without the -f flag, we simply update our hash * table with the information from each allocated bufctl. */ /*ARGSUSED*/ static int umause1(uintptr_t addr, const umem_bufctl_audit_t *bcp, umusers_t *umu) { const umem_cache_t *cp = umu->umu_cache; umu_add(umu, bcp, cp->cache_bufsize, cp->cache_bufsize); return (WALK_NEXT); } /* * When ::umausers is invoked with the -f flag, we print out the information * for each bufctl as well as updating the hash table. */ static int umause2(uintptr_t addr, const umem_bufctl_audit_t *bcp, umusers_t *umu) { int i, depth = MIN(bcp->bc_depth, umem_stack_depth); const umem_cache_t *cp = umu->umu_cache; mdb_printf("size %d, addr %p, thread %p, cache %s\n", cp->cache_bufsize, addr, bcp->bc_thread, cp->cache_name); for (i = 0; i < depth; i++) mdb_printf("\t %a\n", bcp->bc_stack[i]); umu_add(umu, bcp, cp->cache_bufsize, cp->cache_bufsize); return (WALK_NEXT); } /* * We sort our results by allocation size before printing them. */ static int umownercmp(const void *lp, const void *rp) { const umowner_t *lhs = lp; const umowner_t *rhs = rp; return (rhs->umo_total_size - lhs->umo_total_size); } /* * The main engine of ::umausers is relatively straightforward: First we * accumulate our list of umem_cache_t addresses into the umclist_t. Next we * iterate over the allocated bufctls of each cache in the list. Finally, * we sort and print our results. */ /*ARGSUSED*/ int umausers(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int mem_threshold = 8192; /* Minimum # bytes for printing */ int cnt_threshold = 100; /* Minimum # blocks for printing */ int audited_caches = 0; /* Number of UMF_AUDIT caches found */ int do_all_caches = 1; /* Do all caches (no arguments) */ int opt_e = FALSE; /* Include "small" users */ int opt_f = FALSE; /* Print stack traces */ mdb_walk_cb_t callback = (mdb_walk_cb_t)umause1; umowner_t *umo, *umoend; int i, oelems; umclist_t umc; umusers_t umu; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); bzero(&umc, sizeof (umc)); bzero(&umu, sizeof (umu)); while ((i = mdb_getopts(argc, argv, 'e', MDB_OPT_SETBITS, TRUE, &opt_e, 'f', MDB_OPT_SETBITS, TRUE, &opt_f, NULL)) != argc) { argv += i; /* skip past options we just processed */ argc -= i; /* adjust argc */ if (argv->a_type != MDB_TYPE_STRING || *argv->a_un.a_str == '-') return (DCMD_USAGE); oelems = umc.umc_nelems; umc.umc_name = argv->a_un.a_str; (void) mdb_walk("umem_cache", (mdb_walk_cb_t)umc_add, &umc); if (umc.umc_nelems == oelems) { mdb_warn("unknown umem cache: %s\n", umc.umc_name); return (DCMD_ERR); } do_all_caches = 0; argv++; argc--; } if (opt_e) mem_threshold = cnt_threshold = 0; if (opt_f) callback = (mdb_walk_cb_t)umause2; if (do_all_caches) { umc.umc_name = NULL; /* match all cache names */ (void) mdb_walk("umem_cache", (mdb_walk_cb_t)umc_add, &umc); } for (i = 0; i < umc.umc_nelems; i++) { uintptr_t cp = umc.umc_caches[i]; umem_cache_t c; if (mdb_vread(&c, sizeof (c), cp) == -1) { mdb_warn("failed to read cache at %p", cp); continue; } if (!(c.cache_flags & UMF_AUDIT)) { if (!do_all_caches) { mdb_warn("UMF_AUDIT is not enabled for %s\n", c.cache_name); } continue; } umu.umu_cache = &c; (void) mdb_pwalk("bufctl", callback, &umu, cp); audited_caches++; } if (audited_caches == 0 && do_all_caches) { mdb_warn("UMF_AUDIT is not enabled for any caches\n"); return (DCMD_ERR); } qsort(umu.umu_hash, umu.umu_nelems, sizeof (umowner_t), umownercmp); umoend = umu.umu_hash + umu.umu_nelems; for (umo = umu.umu_hash; umo < umoend; umo++) { if (umo->umo_total_size < mem_threshold && umo->umo_num < cnt_threshold) continue; mdb_printf("%lu bytes for %u allocations with data size %lu:\n", umo->umo_total_size, umo->umo_num, umo->umo_data_size); for (i = 0; i < umo->umo_depth; i++) mdb_printf("\t %a\n", umo->umo_stack[i]); } return (DCMD_OK); } struct malloc_data { uint32_t malloc_size; uint32_t malloc_stat; /* == UMEM_MALLOC_ENCODE(state, malloc_size) */ }; #ifdef _LP64 #define UMI_MAX_BUCKET (UMEM_MAXBUF - 2*sizeof (struct malloc_data)) #else #define UMI_MAX_BUCKET (UMEM_MAXBUF - sizeof (struct malloc_data)) #endif typedef struct umem_malloc_info { size_t um_total; /* total allocated buffers */ size_t um_malloc; /* malloc buffers */ size_t um_malloc_size; /* sum of malloc buffer sizes */ size_t um_malloc_overhead; /* sum of in-chunk overheads */ umem_cache_t *um_cp; uint_t *um_bucket; } umem_malloc_info_t; static void umem_malloc_print_dist(uint_t *um_bucket, size_t minmalloc, size_t maxmalloc, size_t maxbuckets, size_t minbucketsize, int geometric) { uint64_t um_malloc; int minb = -1; int maxb = -1; int buckets; int nbucks; int i; int b; const int *distarray; minb = (int)minmalloc; maxb = (int)maxmalloc; nbucks = buckets = maxb - minb + 1; um_malloc = 0; for (b = minb; b <= maxb; b++) um_malloc += um_bucket[b]; if (maxbuckets != 0) buckets = MIN(buckets, maxbuckets); if (minbucketsize > 1) { buckets = MIN(buckets, nbucks/minbucketsize); if (buckets == 0) { buckets = 1; minbucketsize = nbucks; } } if (geometric) distarray = dist_geometric(buckets, minb, maxb, minbucketsize); else distarray = dist_linear(buckets, minb, maxb); dist_print_header("malloc size", 11, "count"); for (i = 0; i < buckets; i++) { dist_print_bucket(distarray, i, um_bucket, um_malloc, 11); } mdb_printf("\n"); } /* * A malloc()ed buffer looks like: * * <----------- mi.malloc_size ---> * <----------- cp.cache_bufsize ------------------> * <----------- cp.cache_chunksize --------------------------------> * +-------+-----------------------+---------------+---------------+ * |/tag///| mallocsz |/round-off/////|/debug info////| * +-------+---------------------------------------+---------------+ * <-- usable space ------> * * mallocsz is the argument to malloc(3C). * mi.malloc_size is the actual size passed to umem_alloc(), which * is rounded up to the smallest available cache size, which is * cache_bufsize. If there is debugging or alignment overhead in * the cache, that is reflected in a larger cache_chunksize. * * The tag at the beginning of the buffer is either 8-bytes or 16-bytes, * depending upon the ISA's alignment requirements. For 32-bit allocations, * it is always a 8-byte tag. For 64-bit allocations larger than 8 bytes, * the tag has 8 bytes of padding before it. * * 32-byte, 64-byte buffers <= 8 bytes: * +-------+-------+--------- ... * |/size//|/stat//| mallocsz ... * +-------+-------+--------- ... * ^ * pointer returned from malloc(3C) * * 64-byte buffers > 8 bytes: * +---------------+-------+-------+--------- ... * |/padding///////|/size//|/stat//| mallocsz ... * +---------------+-------+-------+--------- ... * ^ * pointer returned from malloc(3C) * * The "size" field is "malloc_size", which is mallocsz + the padding. * The "stat" field is derived from malloc_size, and functions as a * validation that this buffer is actually from malloc(3C). */ /*ARGSUSED*/ static int um_umem_buffer_cb(uintptr_t addr, void *buf, umem_malloc_info_t *ump) { struct malloc_data md; size_t m_addr = addr; size_t overhead = sizeof (md); size_t mallocsz; ump->um_total++; #ifdef _LP64 if (ump->um_cp->cache_bufsize > UMEM_SECOND_ALIGN) { m_addr += overhead; overhead += sizeof (md); } #endif if (mdb_vread(&md, sizeof (md), m_addr) == -1) { mdb_warn("unable to read malloc header at %p", m_addr); return (WALK_NEXT); } switch (UMEM_MALLOC_DECODE(md.malloc_stat, md.malloc_size)) { case MALLOC_MAGIC: #ifdef _LP64 case MALLOC_SECOND_MAGIC: #endif mallocsz = md.malloc_size - overhead; ump->um_malloc++; ump->um_malloc_size += mallocsz; ump->um_malloc_overhead += overhead; /* include round-off and debug overhead */ ump->um_malloc_overhead += ump->um_cp->cache_chunksize - md.malloc_size; if (ump->um_bucket != NULL && mallocsz <= UMI_MAX_BUCKET) ump->um_bucket[mallocsz]++; break; default: break; } return (WALK_NEXT); } int get_umem_alloc_sizes(int **out, size_t *out_num) { GElf_Sym sym; if (umem_lookup_by_name("umem_alloc_sizes", &sym) == -1) { mdb_warn("unable to look up umem_alloc_sizes"); return (-1); } *out = mdb_alloc(sym.st_size, UM_SLEEP | UM_GC); *out_num = sym.st_size / sizeof (int); if (mdb_vread(*out, sym.st_size, sym.st_value) == -1) { mdb_warn("unable to read umem_alloc_sizes (%p)", sym.st_value); *out = NULL; return (-1); } return (0); } static int um_umem_cache_cb(uintptr_t addr, umem_cache_t *cp, umem_malloc_info_t *ump) { if (strncmp(cp->cache_name, "umem_alloc_", strlen("umem_alloc_")) != 0) return (WALK_NEXT); ump->um_cp = cp; if (mdb_pwalk("umem", (mdb_walk_cb_t)um_umem_buffer_cb, ump, addr) == -1) { mdb_warn("can't walk 'umem' for cache %p", addr); return (WALK_ERR); } return (WALK_NEXT); } void umem_malloc_dist_help(void) { mdb_printf("%s\n", "report distribution of outstanding malloc()s"); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -b maxbins\n" " Use at most maxbins bins for the data\n" " -B minbinsize\n" " Make the bins at least minbinsize bytes apart\n" " -d dump the raw data out, without binning\n" " -g use geometric binning instead of linear binning\n"); } /*ARGSUSED*/ int umem_malloc_dist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { umem_malloc_info_t mi; uint_t geometric = 0; uint_t dump = 0; size_t maxbuckets = 0; size_t minbucketsize = 0; size_t minalloc = 0; size_t maxalloc = UMI_MAX_BUCKET; if (flags & DCMD_ADDRSPEC) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &dump, 'g', MDB_OPT_SETBITS, TRUE, &geometric, 'b', MDB_OPT_UINTPTR, &maxbuckets, 'B', MDB_OPT_UINTPTR, &minbucketsize, NULL) != argc) return (DCMD_USAGE); bzero(&mi, sizeof (mi)); mi.um_bucket = mdb_zalloc((UMI_MAX_BUCKET + 1) * sizeof (*mi.um_bucket), UM_SLEEP | UM_GC); if (mdb_walk("umem_cache", (mdb_walk_cb_t)um_umem_cache_cb, &mi) == -1) { mdb_warn("unable to walk 'umem_cache'"); return (DCMD_ERR); } if (dump) { int i; for (i = minalloc; i <= maxalloc; i++) mdb_printf("%d\t%d\n", i, mi.um_bucket[i]); return (DCMD_OK); } umem_malloc_print_dist(mi.um_bucket, minalloc, maxalloc, maxbuckets, minbucketsize, geometric); return (DCMD_OK); } void umem_malloc_info_help(void) { mdb_printf("%s\n", "report information about malloc()s by cache. "); mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); mdb_inc_indent(2); mdb_printf("%s", " -b maxbins\n" " Use at most maxbins bins for the data\n" " -B minbinsize\n" " Make the bins at least minbinsize bytes apart\n" " -d dump the raw distribution data without binning\n" #ifndef _KMDB " -g use geometric binning instead of linear binning\n" #endif ""); } int umem_malloc_info(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { umem_cache_t c; umem_malloc_info_t mi; int skip = 0; size_t maxmalloc; size_t overhead; size_t allocated; size_t avg_malloc; size_t overhead_pct; /* 1000 * overhead_percent */ uint_t verbose = 0; uint_t dump = 0; uint_t geometric = 0; size_t maxbuckets = 0; size_t minbucketsize = 0; int *alloc_sizes; int idx; size_t num; size_t minmalloc; if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &dump, 'g', MDB_OPT_SETBITS, TRUE, &geometric, 'b', MDB_OPT_UINTPTR, &maxbuckets, 'B', MDB_OPT_UINTPTR, &minbucketsize, NULL) != argc) return (DCMD_USAGE); if (dump || geometric || (maxbuckets != 0) || (minbucketsize != 0)) verbose = 1; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("umem_cache", "umem_malloc_info", argc, argv) == -1) { mdb_warn("can't walk umem_cache"); return (DCMD_ERR); } return (DCMD_OK); } if (!mdb_vread(&c, sizeof (c), addr)) { mdb_warn("unable to read cache at %p", addr); return (DCMD_ERR); } if (strncmp(c.cache_name, "umem_alloc_", strlen("umem_alloc_")) != 0) { if (!(flags & DCMD_LOOP)) mdb_warn("umem_malloc_info: cache \"%s\" is not used " "by malloc()\n", c.cache_name); skip = 1; } /* * normally, print the header only the first time. In verbose mode, * print the header on every non-skipped buffer */ if ((!verbose && DCMD_HDRSPEC(flags)) || (verbose && !skip)) mdb_printf("%
    %-?s %6s %6s %8s %8s %10s %10s %6s%
\n", "CACHE", "BUFSZ", "MAXMAL", "BUFMALLC", "AVG_MAL", "MALLOCED", "OVERHEAD", "%OVER"); if (skip) return (DCMD_OK); maxmalloc = c.cache_bufsize - sizeof (struct malloc_data); #ifdef _LP64 if (c.cache_bufsize > UMEM_SECOND_ALIGN) maxmalloc -= sizeof (struct malloc_data); #endif bzero(&mi, sizeof (mi)); mi.um_cp = &c; if (verbose) mi.um_bucket = mdb_zalloc((UMI_MAX_BUCKET + 1) * sizeof (*mi.um_bucket), UM_SLEEP | UM_GC); if (mdb_pwalk("umem", (mdb_walk_cb_t)um_umem_buffer_cb, &mi, addr) == -1) { mdb_warn("can't walk 'umem'"); return (DCMD_ERR); } overhead = mi.um_malloc_overhead; allocated = mi.um_malloc_size; /* do integer round off for the average */ if (mi.um_malloc != 0) avg_malloc = (allocated + (mi.um_malloc - 1)/2) / mi.um_malloc; else avg_malloc = 0; /* * include per-slab overhead * * Each slab in a given cache is the same size, and has the same * number of chunks in it; we read in the first slab on the * slab list to get the number of chunks for all slabs. To * compute the per-slab overhead, we just subtract the chunk usage * from the slabsize: * * +------------+-------+-------+ ... --+-------+-------+-------+ * |////////////| | | ... | |///////|///////| * |////color///| chunk | chunk | ... | chunk |/color/|/slab//| * |////////////| | | ... | |///////|///////| * +------------+-------+-------+ ... --+-------+-------+-------+ * | \_______chunksize * chunks_____/ | * \__________________________slabsize__________________________/ * * For UMF_HASH caches, there is an additional source of overhead; * the external umem_slab_t and per-chunk bufctl structures. We * include those in our per-slab overhead. * * Once we have a number for the per-slab overhead, we estimate * the actual overhead by treating the malloc()ed buffers as if * they were densely packed: * * additional overhead = (# mallocs) * (per-slab) / (chunks); * * carefully ordering the multiply before the divide, to avoid * round-off error. */ if (mi.um_malloc != 0) { umem_slab_t slab; uintptr_t saddr = (uintptr_t)c.cache_nullslab.slab_next; if (mdb_vread(&slab, sizeof (slab), saddr) == -1) { mdb_warn("unable to read slab at %p\n", saddr); } else { long chunks = slab.slab_chunks; if (chunks != 0 && c.cache_chunksize != 0 && chunks <= c.cache_slabsize / c.cache_chunksize) { uintmax_t perslab = c.cache_slabsize - (c.cache_chunksize * chunks); if (c.cache_flags & UMF_HASH) { perslab += sizeof (umem_slab_t) + chunks * ((c.cache_flags & UMF_AUDIT) ? sizeof (umem_bufctl_audit_t) : sizeof (umem_bufctl_t)); } overhead += (perslab * (uintmax_t)mi.um_malloc)/chunks; } else { mdb_warn("invalid #chunks (%d) in slab %p\n", chunks, saddr); } } } if (allocated != 0) overhead_pct = (1000ULL * overhead) / allocated; else overhead_pct = 0; mdb_printf("%0?p %6ld %6ld %8ld %8ld %10ld %10ld %3ld.%01ld%%\n", addr, c.cache_bufsize, maxmalloc, mi.um_malloc, avg_malloc, allocated, overhead, overhead_pct / 10, overhead_pct % 10); if (!verbose) return (DCMD_OK); if (!dump) mdb_printf("\n"); if (get_umem_alloc_sizes(&alloc_sizes, &num) == -1) return (DCMD_ERR); for (idx = 0; idx < num; idx++) { if (alloc_sizes[idx] == c.cache_bufsize) break; if (alloc_sizes[idx] == 0) { idx = num; /* 0-terminated array */ break; } } if (idx == num) { mdb_warn( "cache %p's size (%d) not in umem_alloc_sizes\n", addr, c.cache_bufsize); return (DCMD_ERR); } minmalloc = (idx == 0)? 0 : alloc_sizes[idx - 1]; if (minmalloc > 0) { #ifdef _LP64 if (minmalloc > UMEM_SECOND_ALIGN) minmalloc -= sizeof (struct malloc_data); #endif minmalloc -= sizeof (struct malloc_data); minmalloc += 1; } if (dump) { for (idx = minmalloc; idx <= maxmalloc; idx++) mdb_printf("%d\t%d\n", idx, mi.um_bucket[idx]); mdb_printf("\n"); } else { umem_malloc_print_dist(mi.um_bucket, minmalloc, maxmalloc, maxbuckets, minbucketsize, geometric); } return (DCMD_OK); } /* * 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. */ #ifndef _MDBMOD_UMEM_H #define _MDBMOD_UMEM_H #include #include #ifdef __cplusplus extern "C" { #endif extern int umem_ready; extern uint32_t umem_stack_depth; extern int umem_cache_walk_init(mdb_walk_state_t *); extern int umem_cache_walk_step(mdb_walk_state_t *); extern void umem_cache_walk_fini(mdb_walk_state_t *); extern int umem_cpu_walk_init(mdb_walk_state_t *); extern int umem_cpu_walk_step(mdb_walk_state_t *); extern void umem_cpu_walk_fini(mdb_walk_state_t *); extern int umem_cpu_cache_walk_init(mdb_walk_state_t *); extern int umem_cpu_cache_walk_step(mdb_walk_state_t *); extern int umem_slab_walk_init(mdb_walk_state_t *); extern int umem_slab_walk_partial_init(mdb_walk_state_t *); extern int umem_slab_walk_step(mdb_walk_state_t *); extern int umem_hash_walk_init(mdb_walk_state_t *wsp); extern int umem_hash_walk_step(mdb_walk_state_t *wsp); extern void umem_hash_walk_fini(mdb_walk_state_t *wsp); extern int umem_walk_init(mdb_walk_state_t *); extern int bufctl_walk_init(mdb_walk_state_t *); extern int freemem_walk_init(mdb_walk_state_t *); extern int freectl_walk_init(mdb_walk_state_t *); extern int umem_walk_step(mdb_walk_state_t *); extern void umem_walk_fini(mdb_walk_state_t *); extern int bufctl_history_walk_init(mdb_walk_state_t *); extern int bufctl_history_walk_step(mdb_walk_state_t *); extern void bufctl_history_walk_fini(mdb_walk_state_t *); extern int allocdby_walk_init(mdb_walk_state_t *); extern int allocdby_walk_step(mdb_walk_state_t *); extern void allocdby_walk_fini(mdb_walk_state_t *); extern int freedby_walk_init(mdb_walk_state_t *); extern int freedby_walk_step(mdb_walk_state_t *); extern void freedby_walk_fini(mdb_walk_state_t *); extern int umem_log_walk_init(mdb_walk_state_t *); extern int umem_log_walk_step(mdb_walk_state_t *); extern void umem_log_walk_fini(mdb_walk_state_t *); extern int allocdby_walk_init(mdb_walk_state_t *); extern int allocdby_walk_step(mdb_walk_state_t *); extern void allocdby_walk_fini(mdb_walk_state_t *); extern int freedby_walk_init(mdb_walk_state_t *); extern int freedby_walk_step(mdb_walk_state_t *); extern void freedby_walk_fini(mdb_walk_state_t *); extern int vmem_walk_init(mdb_walk_state_t *); extern int vmem_walk_step(mdb_walk_state_t *); extern void vmem_walk_fini(mdb_walk_state_t *); extern int vmem_postfix_walk_step(mdb_walk_state_t *); extern int vmem_seg_walk_init(mdb_walk_state_t *); extern int vmem_seg_walk_step(mdb_walk_state_t *); extern void vmem_seg_walk_fini(mdb_walk_state_t *); extern int vmem_span_walk_init(mdb_walk_state_t *); extern int vmem_alloc_walk_init(mdb_walk_state_t *); extern int vmem_free_walk_init(mdb_walk_state_t *); extern int allocdby(uintptr_t, uint_t, int, const mdb_arg_t *); extern int bufctl(uintptr_t, uint_t, int, const mdb_arg_t *); extern int bufctl_audit(uintptr_t, uint_t, int, const mdb_arg_t *); extern int freedby(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umalog(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umausers(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_cache(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_log(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_malloc_dist(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_malloc_info(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_status(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_verify(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_verify_alloc(uintptr_t, uint_t, int, const mdb_arg_t *); extern int umem_verify_free(uintptr_t, uint_t, int, const mdb_arg_t *); extern int vmem(uintptr_t, uint_t, int, const mdb_arg_t *); extern int vmem_seg(uintptr_t, uint_t, int, const mdb_arg_t *); extern int whatis(uintptr_t, uint_t, int, const mdb_arg_t *); extern void bufctl_help(void); extern void umem_malloc_dist_help(void); extern void umem_malloc_info_help(void); extern void vmem_seg_help(void); /* * utility functions for the rest of libumem */ extern int umem_init(void); extern int umem_get_magsize(const umem_cache_t *); extern size_t umem_estimate_allocated(uintptr_t, const umem_cache_t *); #ifdef __cplusplus } #endif #endif /* _MDBMOD_UMEM_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _UMEM_PAGESIZE_H #define _UMEM_PAGESIZE_H #ifdef __cplusplus extern "C" { #endif extern size_t umem_pagesize; #undef PAGESIZE #define PAGESIZE (umem_pagesize) #ifdef __cplusplus } #endif #endif /* _UMEM_PAGESIZE_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include /*ARGSUSED*/ static int uutil_status(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pthread_t uu_panic_thread = 0; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_readvar(&uu_panic_thread, "uu_panic_thread") == -1) { mdb_warn("unable to read uu_panic_thread"); } if (uu_panic_thread != 0) { mdb_printf("thread %d uu_panicked\n", uu_panic_thread); } return (DCMD_OK); } /*ARGSUSED*/ static int uutil_listpool(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uu_list_pool_t ulp; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("uu_list_pool", "uu_list_pool", argc, argv) == -1) { mdb_warn("can't walk uu_list_pool"); return (DCMD_ERR); } return (DCMD_OK); } if (argc != 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%-?s %-30s %?s %5s\n", "ADDR", "NAME", "COMPARE", "FLAGS"); if (mdb_vread(&ulp, sizeof (uu_list_pool_t), addr) == -1) { mdb_warn("failed to read uu_list_pool\n"); return (DCMD_ERR); } mdb_printf("%0?p %-30s %08x %c\n", addr, ulp.ulp_name, ulp.ulp_cmp, ulp.ulp_debug ? 'D' : ' '); return (DCMD_OK); } /*ARGSUSED*/ static int uutil_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uu_list_t ul; if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_vread(&ul, sizeof (uu_list_t), addr) == -1) { mdb_warn("failed to read uu_list\n"); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) mdb_printf("%-?s %-?s %-?s %6s %5s\n", "ADDR", "POOL", "PARENT", "NODES", "FLAGS"); mdb_printf("%0?p %0?p %0?p %6u %c%c\n", addr, ul.ul_pool, UU_PTR_DECODE(ul.ul_parent_enc), ul.ul_numnodes, ul.ul_sorted ? 'S' : ' ', ul.ul_debug? 'D' : ' '); return (DCMD_OK); } typedef struct uutil_listpool_walk { uintptr_t ulpw_final; uintptr_t ulpw_current; } uutil_listpool_walk_t; int uutil_listpool_walk_init(mdb_walk_state_t *wsp) { uu_list_pool_t null_lpool; uutil_listpool_walk_t *ulpw; GElf_Sym sym; bzero(&null_lpool, sizeof (uu_list_pool_t)); if (mdb_lookup_by_obj("libuutil.so.1", "uu_null_lpool", &sym) == -1) { mdb_warn("failed to find 'uu_null_lpool'\n"); return (WALK_ERR); } if (mdb_vread(&null_lpool, sym.st_size, (uintptr_t)sym.st_value) == -1) { mdb_warn("failed to read data from 'uu_null_lpool' address\n"); return (WALK_ERR); } ulpw = mdb_alloc(sizeof (uutil_listpool_walk_t), UM_SLEEP); ulpw->ulpw_final = (uintptr_t)null_lpool.ulp_prev; ulpw->ulpw_current = (uintptr_t)null_lpool.ulp_next; wsp->walk_data = ulpw; return (WALK_NEXT); } int uutil_listpool_walk_step(mdb_walk_state_t *wsp) { uu_list_pool_t ulp; uutil_listpool_walk_t *ulpw = wsp->walk_data; int status; if (mdb_vread(&ulp, sizeof (uu_list_pool_t), ulpw->ulpw_current) == -1) { mdb_warn("failed to read uu_list_pool %x", ulpw->ulpw_current); return (WALK_ERR); } status = wsp->walk_callback(ulpw->ulpw_current, &ulp, wsp->walk_cbdata); if (ulpw->ulpw_current == ulpw->ulpw_final) return (WALK_DONE); ulpw->ulpw_current = (uintptr_t)ulp.ulp_next; return (status); } void uutil_listpool_walk_fini(mdb_walk_state_t *wsp) { uutil_listpool_walk_t *ulpw = wsp->walk_data; mdb_free(ulpw, sizeof (uutil_listpool_walk_t)); } typedef struct uutil_list_walk { uintptr_t ulw_final; uintptr_t ulw_current; } uutil_list_walk_t; int uutil_list_walk_init(mdb_walk_state_t *wsp) { uutil_list_walk_t *ulw; uu_list_pool_t ulp; if (mdb_vread(&ulp, sizeof (uu_list_pool_t), wsp->walk_addr) == -1) { mdb_warn("failed to read uu_list_pool_t at given address\n"); return (WALK_ERR); } if (UU_LIST_PTR(ulp.ulp_null_list.ul_next_enc) == &((uu_list_pool_t *)wsp->walk_addr)->ulp_null_list) return (WALK_DONE); ulw = mdb_alloc(sizeof (uutil_list_walk_t), UM_SLEEP); ulw->ulw_final = (uintptr_t)UU_LIST_PTR(ulp.ulp_null_list.ul_prev_enc); ulw->ulw_current = (uintptr_t)UU_LIST_PTR(ulp.ulp_null_list.ul_next_enc); wsp->walk_data = ulw; return (WALK_NEXT); } int uutil_list_walk_step(mdb_walk_state_t *wsp) { uu_list_t ul; uutil_list_walk_t *ulw = wsp->walk_data; int status; if (mdb_vread(&ul, sizeof (uu_list_t), ulw->ulw_current) == -1) { mdb_warn("failed to read uu_list %x", ulw->ulw_current); return (WALK_ERR); } status = wsp->walk_callback(ulw->ulw_current, &ul, wsp->walk_cbdata); if (ulw->ulw_current == ulw->ulw_final) return (WALK_DONE); ulw->ulw_current = (uintptr_t)UU_LIST_PTR(ul.ul_next_enc); return (status); } void uutil_list_walk_fini(mdb_walk_state_t *wsp) { uutil_list_walk_t *ulw = wsp->walk_data; mdb_free(ulw, sizeof (uutil_list_walk_t)); } typedef struct uutil_list_node_walk { size_t ulnw_offset; uintptr_t ulnw_final; uintptr_t ulnw_current; void *ulnw_buf; size_t ulnw_bufsz; } uutil_list_node_walk_t; int uutil_list_node_walk_init(mdb_walk_state_t *wsp) { uutil_list_node_walk_t *ulnw; uu_list_t ul; uu_list_pool_t ulp; if (mdb_vread(&ul, sizeof (uu_list_t), wsp->walk_addr) == -1) { mdb_warn("failed to read uu_list_t at given address\n"); return (WALK_ERR); } if (mdb_vread(&ulp, sizeof (uu_list_pool_t), (uintptr_t)ul.ul_pool) == -1) { mdb_warn("failed to read supporting uu_list_pool_t\n"); return (WALK_ERR); } ulnw = mdb_alloc(sizeof (uutil_list_node_walk_t), UM_SLEEP); ulnw->ulnw_offset = ul.ul_offset; ulnw->ulnw_final = wsp->walk_addr + offsetof(uu_list_t, ul_null_node); ulnw->ulnw_current = (uintptr_t)ul.ul_null_node.uln_next; ulnw->ulnw_buf = mdb_alloc(ulp.ulp_objsize, UM_SLEEP); ulnw->ulnw_bufsz = ulp.ulp_objsize; wsp->walk_data = ulnw; return (WALK_NEXT); } int uutil_list_node_walk_step(mdb_walk_state_t *wsp) { uu_list_node_impl_t uln; uutil_list_node_walk_t *ulnw = wsp->walk_data; int status; uintptr_t diff; if (ulnw->ulnw_current == ulnw->ulnw_final) return (WALK_DONE); if (mdb_vread(&uln, sizeof (uu_list_node_impl_t), ulnw->ulnw_current) == -1) { mdb_warn("failed to read uu_list_node %x", ulnw->ulnw_current); return (WALK_ERR); } diff = ulnw->ulnw_current - ulnw->ulnw_offset; if (mdb_vread(ulnw->ulnw_buf, ulnw->ulnw_bufsz, diff) == -1) { mdb_warn("failed to read enclosing structure %x", diff); return (WALK_ERR); } /* * Correct for offset; we return the address of the included structure. */ status = wsp->walk_callback(diff, ulnw->ulnw_buf, wsp->walk_cbdata); ulnw->ulnw_current = (uintptr_t)uln.uln_next; return (status); } void uutil_list_node_walk_fini(mdb_walk_state_t *wsp) { uutil_list_node_walk_t *ulnw = wsp->walk_data; mdb_free(ulnw->ulnw_buf, ulnw->ulnw_bufsz); mdb_free(ulnw, sizeof (uutil_list_node_walk_t)); } static const mdb_dcmd_t dcmds[] = { { "uutil_status", NULL, "libuutil status summary", uutil_status }, { "uu_list_pool", NULL, "display uu_list_pool information", uutil_listpool }, { "uu_list", NULL, "display uu_list information", uutil_list }, { NULL } }; static const mdb_walker_t walkers[] = { { "uu_list_pool", "walk uu_list_pools", uutil_listpool_walk_init, uutil_listpool_walk_step, uutil_listpool_walk_fini }, { "uu_list", "given a uu_list_pool, walk its uu_lists", uutil_list_walk_init, uutil_list_walk_step, uutil_list_walk_fini }, { "uu_list_node", "given a uu_list, walk its nodes, returning container addr", uutil_list_node_walk_init, uutil_list_node_walk_step, uutil_list_node_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include "../genunix/list.h" static const mdb_walker_t walkers[] = { { LIST_WALK_NAME, LIST_WALK_DESC, list_walk_init, list_walk_step, list_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, NULL, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include typedef struct lnode_walk { struct lobucket *lw_table; /* Snapshot of hash table */ uint_t lw_tabsz; /* Size of hash table */ uint_t lw_tabi; /* Current table index */ lnode_t *lw_lnode; /* Current buffer */ } lnode_walk_t; int lnode_walk_init(mdb_walk_state_t *wsp) { lnode_walk_t *lwp; int lofsfstype; struct vfs vfs; struct loinfo loinfo; if (mdb_readvar(&lofsfstype, "lofsfstype") == -1) { mdb_warn("failed to read 'lofsfstype' symbol\n"); return (WALK_ERR); } if (wsp->walk_addr == 0) { uintptr_t rootvfsp, vfsp; uint_t htsize; lwp = mdb_alloc(sizeof (lnode_walk_t), UM_SLEEP); retry: lwp->lw_tabsz = 0; if (mdb_readvar(&rootvfsp, "rootvfs") == -1) { mdb_warn("failed to read 'rootvfs' symbol\n"); mdb_free(lwp, sizeof (lnode_walk_t)); return (WALK_ERR); } vfsp = rootvfsp; do { (void) mdb_vread(&vfs, sizeof (vfs), vfsp); if (lofsfstype != vfs.vfs_fstype) { vfsp = (uintptr_t)vfs.vfs_next; continue; } (void) mdb_vread(&loinfo, sizeof (struct loinfo), (uintptr_t)vfs.vfs_data); lwp->lw_tabsz += loinfo.li_htsize; vfsp = (uintptr_t)vfs.vfs_next; } while (vfsp != rootvfsp); if (lwp->lw_tabsz == 0) { /* * No lofs filesystems mounted. */ mdb_free(lwp, sizeof (lnode_walk_t)); return (WALK_DONE); } lwp->lw_table = mdb_alloc(lwp->lw_tabsz * sizeof (struct lobucket), UM_SLEEP); htsize = 0; vfsp = rootvfsp; do { (void) mdb_vread(&vfs, sizeof (vfs), vfsp); if (lofsfstype != vfs.vfs_fstype) { vfsp = (uintptr_t)vfs.vfs_next; continue; } (void) mdb_vread(&loinfo, sizeof (struct loinfo), (uintptr_t)vfs.vfs_data); if (htsize + loinfo.li_htsize > lwp->lw_tabsz) { /* * Something must have resized. */ mdb_free(lwp->lw_table, lwp->lw_tabsz * sizeof (struct lobucket)); goto retry; } (void) mdb_vread(lwp->lw_table + htsize, loinfo.li_htsize * sizeof (struct lobucket), (uintptr_t)loinfo.li_hashtable); htsize += loinfo.li_htsize; vfsp = (uintptr_t)vfs.vfs_next; } while (vfsp != rootvfsp); } else { if (mdb_vread(&vfs, sizeof (vfs_t), wsp->walk_addr) == -1) { mdb_warn("failed to read from '%p'\n", wsp->walk_addr); return (WALK_ERR); } if (lofsfstype != vfs.vfs_fstype) { mdb_warn("%p does not point to a lofs mount vfs\n", wsp->walk_addr); return (WALK_ERR); } if (mdb_vread(&loinfo, sizeof (loinfo), (uintptr_t)vfs.vfs_data) == -1) { mdb_warn("failed to read struct loinfo from '%p'\n", vfs.vfs_data); return (WALK_ERR); } lwp = mdb_alloc(sizeof (lnode_walk_t), UM_SLEEP); lwp->lw_tabsz = loinfo.li_htsize; lwp->lw_table = mdb_alloc(lwp->lw_tabsz * sizeof (struct lobucket), UM_SLEEP); (void) mdb_vread(lwp->lw_table, lwp->lw_tabsz * sizeof (struct lobucket), (uintptr_t)loinfo.li_hashtable); } lwp->lw_tabi = 0; lwp->lw_lnode = mdb_alloc(sizeof (lnode_t), UM_SLEEP); wsp->walk_addr = (uintptr_t)lwp->lw_table[0].lh_chain; wsp->walk_data = lwp; return (WALK_NEXT); } int lnode_walk_step(mdb_walk_state_t *wsp) { lnode_walk_t *lwp = wsp->walk_data; uintptr_t addr; /* * If the next lnode_t address we want is NULL, advance to the next * hash bucket. When we reach lw_tabsz, we're done. */ while (wsp->walk_addr == 0) { if (++lwp->lw_tabi < lwp->lw_tabsz) wsp->walk_addr = (uintptr_t)lwp->lw_table[lwp->lw_tabi].lh_chain; else return (WALK_DONE); } /* * When we have an lnode_t address, read the lnode and invoke the * walk callback. Keep the next lnode_t address in wsp->walk_addr. */ addr = wsp->walk_addr; (void) mdb_vread(lwp->lw_lnode, sizeof (lnode_t), addr); wsp->walk_addr = (uintptr_t)lwp->lw_lnode->lo_next; return (wsp->walk_callback(addr, lwp->lw_lnode, wsp->walk_cbdata)); } void lnode_walk_fini(mdb_walk_state_t *wsp) { lnode_walk_t *lwp = wsp->walk_data; mdb_free(lwp->lw_table, lwp->lw_tabsz * sizeof (struct lobucket)); mdb_free(lwp->lw_lnode, sizeof (lnode_t)); mdb_free(lwp, sizeof (lnode_walk_t)); } /*ARGSUSED*/ static int lnode_format(uintptr_t addr, const void *data, void *private) { const lnode_t *lop = data; mdb_printf("%?p %?p %?p\n", addr, lop->lo_vnode, lop->lo_vp); return (DCMD_OK); } /*ARGSUSED*/ int lnode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %?s %?s%\n", "LNODE", "VNODE", "REALVP"); } if (flags & DCMD_ADDRSPEC) { lnode_t lo; (void) mdb_vread(&lo, sizeof (lo), addr); return (lnode_format(addr, &lo, NULL)); } if (mdb_walk("lnode", lnode_format, NULL) == -1) return (DCMD_ERR); return (DCMD_OK); } /*ARGSUSED*/ int lnode2dev(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { lnode_t lo; vnode_t vno; vfs_t vfs; if (argc != 0) return (DCMD_ERR); (void) mdb_vread(&lo, sizeof (lo), addr); (void) mdb_vread(&vno, sizeof (vno), (uintptr_t)lo.lo_vnode); (void) mdb_vread(&vfs, sizeof (vfs), (uintptr_t)vno.v_vfsp); mdb_printf("lnode %p vfs_dev %0?lx\n", addr, vfs.vfs_dev); return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "lnode", NULL, "print lnode structure(s)", lnode }, { "lnode2dev", ":", "print vfs_dev given lnode", lnode2dev }, { NULL } }; static const mdb_walker_t walkers[] = { { "lnode", "hash of lnode structures", lnode_walk_init, lnode_walk_step, lnode_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include /* * Print our peer's upper queue pointer, and our lower queue pointer. */ void logdmux_uqinfo(const queue_t *q, char *buf, size_t nbytes) { struct tmx tmx; uintptr_t peer, lower; queue_t lq; /* * First, get the pointer to our lower write queue. */ (void) mdb_vread(&tmx, sizeof (tmx), (uintptr_t)q->q_ptr); lower = (uintptr_t)tmx.muxq; /* * Now read in the lower's peer, and follow that to up to our peer. */ (void) mdb_vread(&lq, sizeof (lq), (uintptr_t)tmx.peerq); (void) mdb_vread(&tmx, sizeof (tmx), (uintptr_t)lq.q_ptr); peer = (uintptr_t)tmx.rdq; (void) mdb_snprintf(buf, nbytes, "peer rq : %p\nlower wq : %p", peer, lower); } /* * Print our peer's lower queue pointer, and our upper queue pointer. */ void logdmux_lqinfo(const queue_t *q, char *buf, size_t nbytes) { struct tmx tmx; (void) mdb_vread(&tmx, sizeof (tmx), (uintptr_t)q->q_ptr); (void) mdb_snprintf(buf, nbytes, "peer wq : %p\nupper rq : %p", (uintptr_t)tmx.peerq, (uintptr_t)tmx.rdq); } uintptr_t logdmux_lrnext(const queue_t *q) { struct tmx tmx; (void) mdb_vread(&tmx, sizeof (tmx), (uintptr_t)q->q_ptr); return ((uintptr_t)tmx.rdq); } uintptr_t logdmux_uwnext(const queue_t *q) { struct tmx tmx; (void) mdb_vread(&tmx, sizeof (tmx), (uintptr_t)q->q_ptr); return ((uintptr_t)tmx.muxq); } static const mdb_qops_t logdmux_uqops = { .q_info = logdmux_uqinfo, .q_rnext = mdb_qrnext_default, .q_wnext = logdmux_uwnext, }; static const mdb_qops_t logdmux_lqops = { .q_info = logdmux_lqinfo, .q_rnext = logdmux_lrnext, .q_wnext = mdb_qwnext_default }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_obj("logindmux", "logdmuxuwinit", &sym) == 0) mdb_qops_install(&logdmux_uqops, (uintptr_t)sym.st_value); if (mdb_lookup_by_obj("logindmux", "logdmuxlwinit", &sym) == 0) mdb_qops_install(&logdmux_lqops, (uintptr_t)sym.st_value); return (&modinfo); } void _mdb_fini(void) { GElf_Sym sym; if (mdb_lookup_by_obj("logindmux", "logdmuxuwinit", &sym) == 0) mdb_qops_remove(&logdmux_uqops, (uintptr_t)sym.st_value); if (mdb_lookup_by_obj("logindmux", "logdmuxlwinit", &sym) == 0) mdb_qops_remove(&logdmux_lqops, (uintptr_t)sym.st_value); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2018 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #define STRSIZE 64 #define MAC_RX_SRS_SIZE (MAX_RINGS_PER_GROUP * sizeof (uintptr_t)) #define LAYERED_WALKER_FOR_FLOW "flow_entry_cache" #define LAYERED_WALKER_FOR_SRS "mac_srs_cache" #define LAYERED_WALKER_FOR_RING "mac_ring_cache" #define LAYERED_WALKER_FOR_GROUP "mac_impl_cache" /* arguments passed to mac_flow dee-command */ #define MAC_FLOW_NONE 0x01 #define MAC_FLOW_ATTR 0x02 #define MAC_FLOW_PROP 0x04 #define MAC_FLOW_RX 0x08 #define MAC_FLOW_TX 0x10 #define MAC_FLOW_USER 0x20 #define MAC_FLOW_STATS 0x40 #define MAC_FLOW_MISC 0x80 /* arguments passed to mac_srs dee-command */ #define MAC_SRS_NONE 0x00 #define MAC_SRS_RX 0x01 #define MAC_SRS_TX 0x02 #define MAC_SRS_STAT 0x04 #define MAC_SRS_CPU 0x08 #define MAC_SRS_VERBOSE 0x10 #define MAC_SRS_INTR 0x20 #define MAC_SRS_RXSTAT (MAC_SRS_RX|MAC_SRS_STAT) #define MAC_SRS_TXSTAT (MAC_SRS_TX|MAC_SRS_STAT) #define MAC_SRS_RXCPU (MAC_SRS_RX|MAC_SRS_CPU) #define MAC_SRS_TXCPU (MAC_SRS_TX|MAC_SRS_CPU) #define MAC_SRS_RXCPUVERBOSE (MAC_SRS_RXCPU|MAC_SRS_VERBOSE) #define MAC_SRS_TXCPUVERBOSE (MAC_SRS_TXCPU|MAC_SRS_VERBOSE) #define MAC_SRS_RXINTR (MAC_SRS_RX|MAC_SRS_INTR) #define MAC_SRS_TXINTR (MAC_SRS_TX|MAC_SRS_INTR) /* arguments passed to mac_group dcmd */ #define MAC_GROUP_NONE 0x00 #define MAC_GROUP_RX 0x01 #define MAC_GROUP_TX 0x02 #define MAC_GROUP_UNINIT 0x04 static char * mac_flow_proto2str(uint8_t protocol) { switch (protocol) { case IPPROTO_TCP: return ("tcp"); case IPPROTO_UDP: return ("udp"); case IPPROTO_SCTP: return ("sctp"); case IPPROTO_ICMP: return ("icmp"); case IPPROTO_ICMPV6: return ("icmpv6"); default: return ("--"); } } static char * mac_flow_priority2str(mac_priority_level_t prio) { switch (prio) { case MPL_LOW: return ("low"); case MPL_MEDIUM: return ("medium"); case MPL_HIGH: return ("high"); case MPL_RESET: return ("reset"); default: return ("--"); } } /* * Convert bandwidth in bps to a string in Mbps. */ static char * mac_flow_bw2str(uint64_t bw, char *buf, ssize_t len) { int kbps, mbps; kbps = (bw % 1000000)/1000; mbps = bw/1000000; if ((mbps == 0) && (kbps != 0)) mdb_snprintf(buf, len, "0.%03u", kbps); else mdb_snprintf(buf, len, "%5u", mbps); return (buf); } static void mac_flow_print_header(uint_t args) { switch (args) { case MAC_FLOW_NONE: mdb_printf("%?s %-20s %4s %?s %?s %-16s\n", "", "", "LINK", "", "", "MIP"); mdb_printf("%%?s %-20s %4s %?s %?s %-16s%\n", "ADDR", "FLOW NAME", "ID", "MCIP", "MIP", "NAME"); break; case MAC_FLOW_ATTR: mdb_printf("%%?s %-32s %-7s %6s " "%-9s %s%\n", "ADDR", "FLOW NAME", "PROTO", "PORT", "DSFLD:MSK", "IPADDR"); break; case MAC_FLOW_PROP: mdb_printf("%%?s %-32s %8s %9s%\n", "ADDR", "FLOW NAME", "MAXBW(M)", "PRIORITY"); break; case MAC_FLOW_MISC: mdb_printf("%%?s %-24s %10s %10s " "%20s %4s%\n", "ADDR", "FLOW NAME", "TYPE", "FLAGS", "MATCH_FN", "ZONE"); break; case MAC_FLOW_RX: mdb_printf("%?s %-24s %3s %s\n", "", "", "SRS", "RX"); mdb_printf("%%?s %-24s %3s %s%\n", "ADDR", "FLOW NAME", "CNT", "SRS"); break; case MAC_FLOW_TX: mdb_printf("%%?s %-32s %?s %\n", "ADDR", "FLOW NAME", "TX_SRS"); break; case MAC_FLOW_STATS: mdb_printf("%%?s %-32s %16s %16s%\n", "ADDR", "FLOW NAME", "RBYTES", "OBYTES"); break; } } /* * Display selected fields of the flow_entry_t structure */ static int mac_flow_dcmd_output(uintptr_t addr, uint_t flags, uint_t args) { static const mdb_bitmask_t flow_type_bits[] = { {"P", FLOW_PRIMARY_MAC, FLOW_PRIMARY_MAC}, {"V", FLOW_VNIC_MAC, FLOW_VNIC_MAC}, {"M", FLOW_MCAST, FLOW_MCAST}, {"O", FLOW_OTHER, FLOW_OTHER}, {"U", FLOW_USER, FLOW_USER}, {"V", FLOW_VNIC, FLOW_VNIC}, {"NS", FLOW_NO_STATS, FLOW_NO_STATS}, { NULL, 0, 0 } }; #define FLOW_MAX_TYPE (sizeof (flow_type_bits) / sizeof (mdb_bitmask_t)) static const mdb_bitmask_t flow_flag_bits[] = { {"Q", FE_QUIESCE, FE_QUIESCE}, {"W", FE_WAITER, FE_WAITER}, {"T", FE_FLOW_TAB, FE_FLOW_TAB}, {"G", FE_G_FLOW_HASH, FE_G_FLOW_HASH}, {"I", FE_INCIPIENT, FE_INCIPIENT}, {"C", FE_CONDEMNED, FE_CONDEMNED}, {"NU", FE_UF_NO_DATAPATH, FE_UF_NO_DATAPATH}, {"NC", FE_MC_NO_DATAPATH, FE_MC_NO_DATAPATH}, { NULL, 0, 0 } }; #define FLOW_MAX_FLAGS (sizeof (flow_flag_bits) / sizeof (mdb_bitmask_t)) flow_entry_t fe; mac_client_impl_t mcip; mac_impl_t mip; if (mdb_vread(&fe, sizeof (fe), addr) == -1) { mdb_warn("failed to read struct flow_entry_s at %p", addr); return (DCMD_ERR); } if (args & MAC_FLOW_USER) { args &= ~MAC_FLOW_USER; if (fe.fe_type & FLOW_MCAST) { if (DCMD_HDRSPEC(flags)) mac_flow_print_header(args); return (DCMD_OK); } } if (DCMD_HDRSPEC(flags)) mac_flow_print_header(args); bzero(&mcip, sizeof (mcip)); bzero(&mip, sizeof (mip)); if (fe.fe_mcip != NULL && mdb_vread(&mcip, sizeof (mcip), (uintptr_t)fe.fe_mcip) == sizeof (mcip)) { (void) mdb_vread(&mip, sizeof (mip), (uintptr_t)mcip.mci_mip); } switch (args) { case MAC_FLOW_NONE: { mdb_printf("%?p %-20s %4d %?p " "%?p %-16s\n", addr, fe.fe_flow_name, fe.fe_link_id, fe.fe_mcip, mcip.mci_mip, mip.mi_name); break; } case MAC_FLOW_ATTR: { struct in_addr in4; uintptr_t desc_addr; flow_desc_t fdesc; desc_addr = addr + OFFSETOF(flow_entry_t, fe_flow_desc); if (mdb_vread(&fdesc, sizeof (fdesc), desc_addr) == -1) { mdb_warn("failed to read struct flow_description at %p", desc_addr); return (DCMD_ERR); } mdb_printf("%?p %-32s " "%-7s %6d " "%4d:%-4d ", addr, fe.fe_flow_name, mac_flow_proto2str(fdesc.fd_protocol), fdesc.fd_local_port, fdesc.fd_dsfield, fdesc.fd_dsfield_mask); if (fdesc.fd_ipversion == IPV4_VERSION) { IN6_V4MAPPED_TO_INADDR(&fdesc.fd_local_addr, &in4); mdb_printf("%I", in4.s_addr); } else if (fdesc.fd_ipversion == IPV6_VERSION) { mdb_printf("%N", &fdesc.fd_local_addr); } else { mdb_printf("%s", "--"); } mdb_printf("\n"); break; } case MAC_FLOW_PROP: { uintptr_t prop_addr; char bwstr[STRSIZE]; mac_resource_props_t fprop; prop_addr = addr + OFFSETOF(flow_entry_t, fe_resource_props); if (mdb_vread(&fprop, sizeof (fprop), prop_addr) == -1) { mdb_warn("failed to read struct mac_resoource_props " "at %p", prop_addr); return (DCMD_ERR); } mdb_printf("%?p %-32s " "%8s %9s\n", addr, fe.fe_flow_name, mac_flow_bw2str(fprop.mrp_maxbw, bwstr, STRSIZE), mac_flow_priority2str(fprop.mrp_priority)); break; } case MAC_FLOW_MISC: { char flow_flags[2 * FLOW_MAX_FLAGS]; char flow_type[2 * FLOW_MAX_TYPE]; GElf_Sym sym; char func_name[MDB_SYM_NAMLEN] = ""; uintptr_t func, match_addr; match_addr = addr + OFFSETOF(flow_entry_t, fe_match); (void) mdb_vread(&func, sizeof (func), match_addr); (void) mdb_lookup_by_addr(func, MDB_SYM_EXACT, func_name, MDB_SYM_NAMLEN, &sym); mdb_snprintf(flow_flags, 2 * FLOW_MAX_FLAGS, "%hb", fe.fe_flags, flow_flag_bits); mdb_snprintf(flow_type, 2 * FLOW_MAX_TYPE, "%hb", fe.fe_type, flow_type_bits); mdb_printf("%?p %-24s %10s %10s %20s\n", addr, fe.fe_flow_name, flow_type, flow_flags, func_name); break; } case MAC_FLOW_RX: { uintptr_t rxaddr, rx_srs[MAX_RINGS_PER_GROUP] = {0}; int i; rxaddr = addr + OFFSETOF(flow_entry_t, fe_rx_srs); (void) mdb_vread(rx_srs, MAC_RX_SRS_SIZE, rxaddr); mdb_printf("%?p %-24s %3d ", addr, fe.fe_flow_name, fe.fe_rx_srs_cnt); for (i = 0; i < MAX_RINGS_PER_GROUP; i++) { if (rx_srs[i] == 0) continue; mdb_printf("%p ", rx_srs[i]); } mdb_printf("\n"); break; } case MAC_FLOW_TX: { uintptr_t tx_srs = 0, txaddr; txaddr = addr + OFFSETOF(flow_entry_t, fe_tx_srs); (void) mdb_vread(&tx_srs, sizeof (uintptr_t), txaddr); mdb_printf("%?p %-32s %?p\n", addr, fe.fe_flow_name, fe.fe_tx_srs); break; } case MAC_FLOW_STATS: { uint64_t totibytes = 0; uint64_t totobytes = 0; mac_soft_ring_set_t *mac_srs; mac_rx_stats_t mac_rx_stat; mac_tx_stats_t mac_tx_stat; int i; /* * Sum bytes for all Rx SRS. */ for (i = 0; i < fe.fe_rx_srs_cnt; i++) { mac_srs = (mac_soft_ring_set_t *)(fe.fe_rx_srs[i]); if (mdb_vread(&mac_rx_stat, sizeof (mac_rx_stats_t), (uintptr_t)&mac_srs->srs_rx.sr_stat) == -1) { mdb_warn("failed to read mac_rx_stats_t at %p", &mac_srs->srs_rx.sr_stat); return (DCMD_ERR); } totibytes += mac_rx_stat.mrs_intrbytes + mac_rx_stat.mrs_pollbytes + mac_rx_stat.mrs_lclbytes; } /* * Sum bytes for Tx SRS. */ mac_srs = (mac_soft_ring_set_t *)(fe.fe_tx_srs); if (mac_srs != NULL) { if (mdb_vread(&mac_tx_stat, sizeof (mac_tx_stats_t), (uintptr_t)&mac_srs->srs_tx.st_stat) == -1) { mdb_warn("failed to read max_tx_stats_t at %p", &mac_srs->srs_tx.st_stat); return (DCMD_ERR); } totobytes = mac_tx_stat.mts_obytes; } mdb_printf("%?p %-32s %16llu %16llu\n", addr, fe.fe_flow_name, totibytes, totobytes); break; } } return (DCMD_OK); } /* * Parse the arguments passed to the dcmd and print all or one flow_entry_t * structures */ static int mac_flow_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t args = 0; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("mac_flow", "mac_flow", argc, argv) == -1) { mdb_warn("failed to walk 'mac_flow'"); return (DCMD_ERR); } return (DCMD_OK); } if ((mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, MAC_FLOW_ATTR, &args, 'p', MDB_OPT_SETBITS, MAC_FLOW_PROP, &args, 'm', MDB_OPT_SETBITS, MAC_FLOW_MISC, &args, 'r', MDB_OPT_SETBITS, MAC_FLOW_RX, &args, 't', MDB_OPT_SETBITS, MAC_FLOW_TX, &args, 's', MDB_OPT_SETBITS, MAC_FLOW_STATS, &args, 'u', MDB_OPT_SETBITS, MAC_FLOW_USER, &args, NULL) != argc)) { return (DCMD_USAGE); } if (argc > 2 || (argc == 2 && !(args & MAC_FLOW_USER))) return (DCMD_USAGE); /* * If no arguments was specified or just "-u" was specified then * we default to printing basic information of flows. */ if (args == 0 || args == MAC_FLOW_USER) args |= MAC_FLOW_NONE; return (mac_flow_dcmd_output(addr, flags, args)); } static void mac_flow_help(void) { mdb_printf("If an address is specified, then flow_entry structure at " "that address is printed. Otherwise all the flows in the system " "are printed.\n"); mdb_printf("Options:\n" "\t-u\tdisplay user defined link & vnic flows.\n" "\t-a\tdisplay flow attributes\n" "\t-p\tdisplay flow properties\n" "\t-r\tdisplay rx side information\n" "\t-t\tdisplay tx side information\n" "\t-s\tdisplay flow statistics\n" "\t-m\tdisplay miscellaneous flow information\n\n"); mdb_printf("%Interpreting Flow type and Flow flags output.%\n"); mdb_printf("Flow Types:\n"); mdb_printf("\t P --> FLOW_PRIMARY_MAC\n"); mdb_printf("\t V --> FLOW_VNIC_MAC\n"); mdb_printf("\t M --> FLOW_MCAST\n"); mdb_printf("\t O --> FLOW_OTHER\n"); mdb_printf("\t U --> FLOW_USER\n"); mdb_printf("\t NS --> FLOW_NO_STATS\n\n"); mdb_printf("Flow Flags:\n"); mdb_printf("\t Q --> FE_QUIESCE\n"); mdb_printf("\t W --> FE_WAITER\n"); mdb_printf("\t T --> FE_FLOW_TAB\n"); mdb_printf("\t G --> FE_G_FLOW_HASH\n"); mdb_printf("\t I --> FE_INCIPIENT\n"); mdb_printf("\t C --> FE_CONDEMNED\n"); mdb_printf("\t NU --> FE_UF_NO_DATAPATH\n"); mdb_printf("\t NC --> FE_MC_NO_DATAPATH\n"); } /* * called once by the debugger when the mac_flow walk begins. */ static int mac_flow_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk(LAYERED_WALKER_FOR_FLOW, wsp) == -1) { mdb_warn("failed to walk 'mac_flow'"); return (WALK_ERR); } return (WALK_NEXT); } /* * Common walker step funciton for flow_entry_t, mac_soft_ring_set_t and * mac_ring_t. * * Steps through each flow_entry_t and calls the callback function. If the * user executed ::walk mac_flow, it just prints the address or if the user * executed ::mac_flow it displays selected fields of flow_entry_t structure * by calling "mac_flow_dcmd" */ static int mac_common_walk_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) return (WALK_DONE); status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); return (status); } static char * mac_srs_txmode2str(mac_tx_srs_mode_t mode) { switch (mode) { case SRS_TX_DEFAULT: return ("DEF"); case SRS_TX_SERIALIZE: return ("SER"); case SRS_TX_FANOUT: return ("FO"); case SRS_TX_BW: return ("BW"); case SRS_TX_BW_FANOUT: return ("BWFO"); case SRS_TX_AGGR: return ("AG"); case SRS_TX_BW_AGGR: return ("BWAG"); } return ("--"); } static void mac_srs_help(void) { mdb_printf("If an address is specified, then mac_soft_ring_set " "structure at that address is printed. Otherwise all the " "SRS in the system are printed.\n"); mdb_printf("Options:\n" "\t-r\tdisplay recieve side SRS structures\n" "\t-t\tdisplay transmit side SRS structures\n" "\t-s\tdisplay statistics for RX or TX side\n" "\t-c\tdisplay CPU binding for RX or TX side\n" "\t-v\tverbose flag for CPU binding to list cpus\n" "\t-i\tdisplay mac_ring_t and interrupt information\n" "Note: use -r or -t (to specify RX or TX side respectively) along " "with -c or -s\n"); mdb_printf("\n%Interpreting TX Modes%\n"); mdb_printf("\t DEF --> Default\n"); mdb_printf("\t SER --> Serialize\n"); mdb_printf("\t FO --> Fanout\n"); mdb_printf("\t BW --> Bandwidth\n"); mdb_printf("\tBWFO --> Bandwidth Fanout\n"); mdb_printf("\t AG --> Aggr\n"); mdb_printf("\tBWAG --> Bandwidth Aggr\n"); } /* * In verbose mode "::mac_srs -rcv or ::mac_srs -tcv", we print the CPUs * assigned to a link and CPUS assigned to the soft rings. * 'len' is used for formatting the output and represents the number of * spaces between CPU list and Fanout CPU list in the output. */ static boolean_t mac_srs_print_cpu(int *i, uint32_t cnt, uint32_t *cpu_list, int *len) { int num = 0; if (*i == 0) mdb_printf("("); else mdb_printf(" "); while (*i < cnt) { /* We print 6 CPU's at a time to keep display within 80 cols */ if (((num + 1) % 7) == 0) { if (len != NULL) *len = 2; return (B_FALSE); } mdb_printf("%02x%c", cpu_list[*i], ((*i == cnt - 1)?')':',')); ++*i; ++num; } if (len != NULL) *len = (7 - num) * 3; return (B_TRUE); } static int mac_srs_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t args = MAC_SRS_NONE; mac_soft_ring_set_t srs; mac_client_impl_t mci; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("mac_srs", "mac_srs", argc, argv) == -1) { mdb_warn("failed to walk 'mac_srs'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'r', MDB_OPT_SETBITS, MAC_SRS_RX, &args, 't', MDB_OPT_SETBITS, MAC_SRS_TX, &args, 'c', MDB_OPT_SETBITS, MAC_SRS_CPU, &args, 'v', MDB_OPT_SETBITS, MAC_SRS_VERBOSE, &args, 'i', MDB_OPT_SETBITS, MAC_SRS_INTR, &args, 's', MDB_OPT_SETBITS, MAC_SRS_STAT, &args, NULL) != argc) { return (DCMD_USAGE); } if (argc > 2) return (DCMD_USAGE); if (mdb_vread(&srs, sizeof (srs), addr) == -1) { mdb_warn("failed to read struct mac_soft_ring_set_s at %p", addr); return (DCMD_ERR); } if (mdb_vread(&mci, sizeof (mci), (uintptr_t)srs.srs_mcip) == -1) { mdb_warn("failed to read struct mac_client_impl_t at %p " "for SRS %p", srs.srs_mcip, addr); return (DCMD_ERR); } switch (args) { case MAC_SRS_RX: { if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-20s %-8s %-8s %8s " "%8s %3s\n", "", "", "", "", "MBLK", "Q", "SR"); mdb_printf("%%?s %-20s %-8s %-8s %8s " "%8s %3s%\n", "ADDR", "LINK_NAME", "STATE", "TYPE", "CNT", "BYTES", "CNT"); } if (srs.srs_type & SRST_TX) return (DCMD_OK); mdb_printf("%?p %-20s %08x %08x " "%8d %8d %3d\n", addr, mci.mci_name, srs.srs_state, srs.srs_type, srs.srs_count, srs.srs_size, srs.srs_soft_ring_count); break; } case MAC_SRS_TX: { if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-16s %-4s %-8s " "%-8s %8s %8s %3s\n", "", "", "TX", "", "", "MBLK", "Q", "SR"); mdb_printf("%%?s %-16s %-4s %-8s " "%-8s %8s %8s %3s%\n", "ADDR", "LINK_NAME", "MODE", "STATE", "TYPE", "CNT", "BYTES", "CNT"); } if (!(srs.srs_type & SRST_TX)) return (DCMD_OK); mdb_printf("%?p %-16s %-4s " "%08x %08x %8d %8d %3d\n", addr, mci.mci_name, mac_srs_txmode2str(srs.srs_tx.st_mode), srs.srs_state, srs.srs_type, srs.srs_count, srs.srs_size, srs.srs_tx_ring_count); break; } case MAC_SRS_RXCPU: { mac_cpus_t mc = srs.srs_cpu; if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-20s %-4s %-4s " "%-6s %-4s %-7s\n", "", "", "NUM", "POLL", "WORKER", "INTR", "FANOUT"); mdb_printf("%%?s %-20s %-4s %-4s " "%-6s %-4s %-7s%\n", "ADDR", "LINK_NAME", "CPUS", "CPU", "CPU", "CPU", "CPU_CNT"); } if ((args & MAC_SRS_RX) && (srs.srs_type & SRST_TX)) return (DCMD_OK); mdb_printf("%?p %-20s %-4d %-4d " "%-6d %-4d %-7d\n", addr, mci.mci_name, mc.mc_ncpus, mc.mc_rx_pollid, mc.mc_rx_workerid, mc.mc_rx_intr_cpu, mc.mc_rx_fanout_cnt); break; } case MAC_SRS_TXCPU: { mac_cpus_t mc = srs.srs_cpu; mac_soft_ring_t *s_ringp, s_ring; boolean_t first = B_TRUE; int i; if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-12s %?s %8s %8s %8s\n", "", "", "SOFT", "WORKER", "INTR", "RETARGETED"); mdb_printf("%%?s %-12s %?s %8s %8s %8s%\n", "ADDR", "LINK_NAME", "RING", "CPU", "CPU", "CPU"); } if (!(srs.srs_type & SRST_TX)) return (DCMD_OK); mdb_printf("%?p %-12s ", addr, mci.mci_name); /* * Case of no soft rings, print the info from * mac_srs_tx_t. */ if (srs.srs_tx_ring_count == 0) { mdb_printf("%?p %8d %8d %8d\n", 0, mc.mc_tx_fanout_cpus[0], mc.mc_tx_intr_cpu[0], mc.mc_tx_retargeted_cpu[0]); break; } for (s_ringp = srs.srs_soft_ring_head, i = 0; s_ringp != NULL; s_ringp = s_ring.s_ring_next, i++) { (void) mdb_vread(&s_ring, sizeof (s_ring), (uintptr_t)s_ringp); if (first) { mdb_printf("%?p %8d %8d %8d\n", s_ringp, mc.mc_tx_fanout_cpus[i], mc.mc_tx_intr_cpu[i], mc.mc_tx_retargeted_cpu[i]); first = B_FALSE; continue; } mdb_printf("%?s %-12s %?p %8d %8d %8d\n", "", "", s_ringp, mc.mc_tx_fanout_cpus[i], mc.mc_tx_intr_cpu[i], mc.mc_tx_retargeted_cpu[i]); } break; } case MAC_SRS_TXINTR: { mac_cpus_t mc = srs.srs_cpu; mac_soft_ring_t *s_ringp, s_ring; mac_ring_t *m_ringp, m_ring; boolean_t first = B_TRUE; int i; if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-12s %?s %8s %?s %6s %6s\n", "", "", "SOFT", "WORKER", "MAC", "", "INTR"); mdb_printf("%%?s %-12s %?s %8s %?s %6s %6s%\n", "ADDR", "LINK_NAME", "RING", "CPU", "RING", "SHARED", "CPU"); } if (!(srs.srs_type & SRST_TX)) return (DCMD_OK); mdb_printf("%?p %-12s ", addr, mci.mci_name); /* * Case of no soft rings, print the info from * mac_srs_tx_t. */ if (srs.srs_tx_ring_count == 0) { m_ringp = srs.srs_tx.st_arg2; if (m_ringp != NULL) { (void) mdb_vread(&m_ring, sizeof (m_ring), (uintptr_t)m_ringp); mdb_printf("%?p %8d %?p %6d %6d\n", 0, mc.mc_tx_fanout_cpus[0], m_ringp, m_ring.mr_info.mri_intr.mi_ddi_shared, mc.mc_tx_retargeted_cpu[0]); } else { mdb_printf("%?p %8d %?p %6d %6d\n", 0, mc.mc_tx_fanout_cpus[0], 0, 0, mc.mc_tx_retargeted_cpu[0]); } break; } for (s_ringp = srs.srs_soft_ring_head, i = 0; s_ringp != NULL; s_ringp = s_ring.s_ring_next, i++) { (void) mdb_vread(&s_ring, sizeof (s_ring), (uintptr_t)s_ringp); m_ringp = s_ring.s_ring_tx_arg2; (void) mdb_vread(&m_ring, sizeof (m_ring), (uintptr_t)m_ringp); if (first) { mdb_printf("%?p %8d %?p %6d %6d\n", s_ringp, mc.mc_tx_fanout_cpus[i], m_ringp, m_ring.mr_info.mri_intr.mi_ddi_shared, mc.mc_tx_retargeted_cpu[i]); first = B_FALSE; continue; } mdb_printf("%?s %-12s %?p %8d %?p %6d %6d\n", "", "", s_ringp, mc.mc_tx_fanout_cpus[i], m_ringp, m_ring.mr_info.mri_intr.mi_ddi_shared, mc.mc_tx_retargeted_cpu[i]); } break; } case MAC_SRS_RXINTR: { mac_cpus_t mc = srs.srs_cpu; mac_ring_t *m_ringp, m_ring; if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-12s %?s %8s %6s %6s\n", "", "", "MAC", "", "POLL", "INTR"); mdb_printf("%%?s %-12s %?s %8s %6s %6s%\n", "ADDR", "LINK_NAME", "RING", "SHARED", "CPU", "CPU"); } if ((args & MAC_SRS_RX) && (srs.srs_type & SRST_TX)) return (DCMD_OK); mdb_printf("%?p %-12s ", addr, mci.mci_name); m_ringp = srs.srs_ring; if (m_ringp != NULL) { (void) mdb_vread(&m_ring, sizeof (m_ring), (uintptr_t)m_ringp); mdb_printf("%?p %8d %6d %6d\n", m_ringp, m_ring.mr_info.mri_intr.mi_ddi_shared, mc.mc_rx_pollid, mc.mc_rx_intr_cpu); } else { mdb_printf("%?p %8d %6d %6d\n", 0, 0, mc.mc_rx_pollid, mc.mc_rx_intr_cpu); } break; } case MAC_SRS_RXCPUVERBOSE: case MAC_SRS_TXCPUVERBOSE: { mac_cpus_t mc = srs.srs_cpu; int cpu_index = 0, fanout_index = 0, len = 0; boolean_t cpu_done = B_FALSE, fanout_done = B_FALSE; if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-20s %-20s %-20s\n", "", "", "CPU_COUNT", "FANOUT_CPU_COUNT"); mdb_printf("%%?s %-20s " "%-20s %-20s%\n", "ADDR", "LINK_NAME", "(CPU_LIST)", "(CPU_LIST)"); } if (((args & MAC_SRS_TX) && !(srs.srs_type & SRST_TX)) || ((args & MAC_SRS_RX) && (srs.srs_type & SRST_TX))) return (DCMD_OK); mdb_printf("%?p %-20s %-20d %-20d\n", addr, mci.mci_name, mc.mc_ncpus, mc.mc_rx_fanout_cnt); if (mc.mc_ncpus == 0 && mc.mc_rx_fanout_cnt == 0) break; /* print all cpus and cpus for soft rings */ while (!cpu_done || !fanout_done) { boolean_t old_value = cpu_done; if (!cpu_done) { mdb_printf("%?s %20s ", "", ""); cpu_done = mac_srs_print_cpu(&cpu_index, mc.mc_ncpus, mc.mc_cpus, &len); } if (!fanout_done) { if (old_value) mdb_printf("%?s %-40s", "", ""); else mdb_printf("%*s", len, ""); fanout_done = mac_srs_print_cpu(&fanout_index, mc.mc_rx_fanout_cnt, mc.mc_rx_fanout_cpus, NULL); } mdb_printf("\n"); } break; } case MAC_SRS_RXSTAT: { mac_rx_stats_t *mac_rx_stat = &srs.srs_rx.sr_stat; if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-16s %8s %8s " "%8s %8s %8s\n", "", "", "INTR", "POLL", "CHAIN", "CHAIN", "CHAIN"); mdb_printf("%%?s %-16s %8s %8s " "%8s %8s %8s%\n", "ADDR", "LINK_NAME", "COUNT", "COUNT", "<10", "10-50", ">50"); } if (srs.srs_type & SRST_TX) return (DCMD_OK); mdb_printf("%?p %-16s %8d " "%8d %8d " "%8d %8d\n", addr, mci.mci_name, mac_rx_stat->mrs_intrcnt, mac_rx_stat->mrs_pollcnt, mac_rx_stat->mrs_chaincntundr10, mac_rx_stat->mrs_chaincnt10to50, mac_rx_stat->mrs_chaincntover50); break; } case MAC_SRS_TXSTAT: { mac_tx_stats_t *mac_tx_stat = &srs.srs_tx.st_stat; mac_soft_ring_t *s_ringp, s_ring; boolean_t first = B_TRUE; if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-20s %?s %8s %8s %8s\n", "", "", "SOFT", "DROP", "BLOCK", "UNBLOCK"); mdb_printf("%%?s %-20s %?s %8s %8s %8s%\n", "ADDR", "LINK_NAME", "RING", "COUNT", "COUNT", "COUNT"); } if (!(srs.srs_type & SRST_TX)) return (DCMD_OK); mdb_printf("%?p %-20s ", addr, mci.mci_name); /* * Case of no soft rings, print the info from * mac_srs_tx_t. */ if (srs.srs_tx_ring_count == 0) { mdb_printf("%?p %8d %8d %8d\n", 0, mac_tx_stat->mts_sdrops, mac_tx_stat->mts_blockcnt, mac_tx_stat->mts_unblockcnt); break; } for (s_ringp = srs.srs_soft_ring_head; s_ringp != NULL; s_ringp = s_ring.s_ring_next) { (void) mdb_vread(&s_ring, sizeof (s_ring), (uintptr_t)s_ringp); mac_tx_stat = &s_ring.s_st_stat; if (first) { mdb_printf("%?p %8d %8d %8d\n", s_ringp, mac_tx_stat->mts_sdrops, mac_tx_stat->mts_blockcnt, mac_tx_stat->mts_unblockcnt); first = B_FALSE; continue; } mdb_printf("%?s %-20s %?p %8d %8d %8d\n", "", "", s_ringp, mac_tx_stat->mts_sdrops, mac_tx_stat->mts_blockcnt, mac_tx_stat->mts_unblockcnt); } break; } case MAC_SRS_NONE: { if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %-20s %?s %?s %-3s%\n", "ADDR", "LINK_NAME", "FLENT", "HW RING", "DIR"); } mdb_printf("%?p %-20s %?p %?p " "%-3s ", addr, mci.mci_name, srs.srs_flent, srs.srs_ring, (srs.srs_type & SRST_TX ? "TX" : "RX")); break; } default: return (DCMD_USAGE); } return (DCMD_OK); } static int mac_srs_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk(LAYERED_WALKER_FOR_SRS, wsp) == -1) { mdb_warn("failed to walk 'mac_srs'"); return (WALK_ERR); } return (WALK_NEXT); } static char * mac_ring_state2str(mac_ring_state_t state) { switch (state) { case MR_FREE: return ("free"); case MR_NEWLY_ADDED: return ("new"); case MR_INUSE: return ("inuse"); } return ("--"); } static char * mac_ring_classify2str(mac_classify_type_t classify) { switch (classify) { case MAC_NO_CLASSIFIER: return ("no"); case MAC_SW_CLASSIFIER: return ("sw"); case MAC_HW_CLASSIFIER: return ("hw"); case MAC_PASSTHRU_CLASSIFIER: return ("pass"); } return ("--"); } static int mac_ring_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mac_ring_t ring; mac_group_t group; flow_entry_t flent; mac_soft_ring_set_t srs; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("mac_ring", "mac_ring", argc, argv) == -1) { mdb_warn("failed to walk 'mac_ring'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&ring, sizeof (ring), addr) == -1) { mdb_warn("failed to read struct mac_ring_s at %p", addr); return (DCMD_ERR); } bzero(&flent, sizeof (flent)); if (mdb_vread(&srs, sizeof (srs), (uintptr_t)ring.mr_srs) != -1) { (void) mdb_vread(&flent, sizeof (flent), (uintptr_t)srs.srs_flent); } (void) mdb_vread(&group, sizeof (group), (uintptr_t)ring.mr_gh); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %4s %5s %4s %?s " "%5s %?s %?s %s %\n", "ADDR", "TYPE", "STATE", "FLAG", "GROUP", "CLASS", "MIP", "SRS", "FLOW NAME"); } mdb_printf("%?p %-4s " "%5s %04x " "%?p %-5s " "%?p %?p %s\n", addr, ((ring.mr_type == 1)? "RX" : "TX"), mac_ring_state2str(ring.mr_state), ring.mr_flag, ring.mr_gh, mac_ring_classify2str(ring.mr_classify_type), group.mrg_mh, ring.mr_srs, flent.fe_flow_name); return (DCMD_OK); } static int mac_ring_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk(LAYERED_WALKER_FOR_RING, wsp) == -1) { mdb_warn("failed to walk `mac_ring`"); return (WALK_ERR); } return (WALK_NEXT); } static void mac_ring_help(void) { mdb_printf("If an address is specified, then mac_ring_t " "structure at that address is printed. Otherwise all the " "hardware rings in the system are printed.\n"); } /* * To walk groups we have to have our own somewhat-complicated state machine. We * basically start by walking the mac_impl_t walker as all groups are stored off * of the various mac_impl_t in the system. The tx and rx rings are kept * separately. So we'll need to walk through all the rx rings and then all of * the tx rings. */ static int mac_group_walk_init(mdb_walk_state_t *wsp) { int ret; if (wsp->walk_addr != 0) { mdb_warn("non-global walks are not supported\n"); return (WALK_ERR); } if ((ret = mdb_layered_walk(LAYERED_WALKER_FOR_GROUP, wsp)) == -1) { mdb_warn("couldn't walk '%s'", LAYERED_WALKER_FOR_GROUP); return (ret); } return (WALK_NEXT); } static int mac_group_walk_step(mdb_walk_state_t *wsp) { int ret; mac_impl_t mi; mac_group_t mg; uintptr_t mgp; /* * Nothing to do if we can't find the layer above us. But the kmem * walkers are a bit unsporting, they don't actually read in the data * for us. */ if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&mi, sizeof (mac_impl_t), wsp->walk_addr) == -1) { mdb_warn("failed to read mac_impl_t at %p", wsp->walk_addr); return (DCMD_ERR); } /* * First go for rx groups, then tx groups. */ mgp = (uintptr_t)mi.mi_rx_groups; while (mgp != 0) { if (mdb_vread(&mg, sizeof (mac_group_t), mgp) == -1) { mdb_warn("failed to read mac_group_t at %p", mgp); return (WALK_ERR); } ret = wsp->walk_callback(mgp, &mg, wsp->walk_cbdata); if (ret != WALK_NEXT) return (ret); mgp = (uintptr_t)mg.mrg_next; } mgp = (uintptr_t)mi.mi_tx_groups; while (mgp != 0) { if (mdb_vread(&mg, sizeof (mac_group_t), mgp) == -1) { mdb_warn("failed to read mac_group_t at %p", mgp); return (WALK_ERR); } ret = wsp->walk_callback(mgp, &mg, wsp->walk_cbdata); if (ret != WALK_NEXT) return (ret); mgp = (uintptr_t)mg.mrg_next; } return (WALK_NEXT); } static int mac_group_count_clients(mac_group_t *mgp) { int clients = 0; uintptr_t mcp = (uintptr_t)mgp->mrg_clients; while (mcp != 0) { mac_grp_client_t c; if (mdb_vread(&c, sizeof (c), mcp) == -1) { mdb_warn("failed to read mac_grp_client_t at %p", mcp); return (-1); } clients++; mcp = (uintptr_t)c.mgc_next; } return (clients); } static const char * mac_group_type(mac_group_t *mgp) { const char *ret; switch (mgp->mrg_type) { case MAC_RING_TYPE_RX: ret = "RECEIVE"; break; case MAC_RING_TYPE_TX: ret = "TRANSMIT"; break; default: ret = "UNKNOWN"; break; } return (ret); } static const char * mac_group_state(mac_group_t *mgp) { const char *ret; switch (mgp->mrg_state) { case MAC_GROUP_STATE_UNINIT: ret = "UNINT"; break; case MAC_GROUP_STATE_REGISTERED: ret = "REGISTERED"; break; case MAC_GROUP_STATE_RESERVED: ret = "RESERVED"; break; case MAC_GROUP_STATE_SHARED: ret = "SHARED"; break; default: ret = "UNKNOWN"; break; } return (ret); } static int mac_group_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t args = MAC_SRS_NONE; mac_group_t mg; int clients; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("mac_group", "mac_group", argc, argv) == -1) { mdb_warn("failed to walk 'mac_group'"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'r', MDB_OPT_SETBITS, MAC_GROUP_RX, &args, 't', MDB_OPT_SETBITS, MAC_GROUP_TX, &args, 'u', MDB_OPT_SETBITS, MAC_GROUP_UNINIT, &args, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&mg, sizeof (mac_group_t), addr) == -1) { mdb_warn("failed to read mac_group_t at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags) && !(flags & DCMD_PIPE_OUT)) { mdb_printf("%%-?s %-8s %-10s %6s %8s %-?s%\n", "ADDR", "TYPE", "STATE", "NRINGS", "NCLIENTS", "RINGS"); } if ((args & MAC_GROUP_RX) != 0 && mg.mrg_type != MAC_RING_TYPE_RX) return (DCMD_OK); if ((args & MAC_GROUP_TX) != 0 && mg.mrg_type != MAC_RING_TYPE_TX) return (DCMD_OK); /* * By default, don't show uninitialized groups. They're not very * interesting. They have no rings and no clients. */ if (mg.mrg_state == MAC_GROUP_STATE_UNINIT && (args & MAC_GROUP_UNINIT) == 0) return (DCMD_OK); if (flags & DCMD_PIPE_OUT) { mdb_printf("%lr\n", addr); return (DCMD_OK); } clients = mac_group_count_clients(&mg); mdb_printf("%?p %-8s %-10s %6d %8d %?p\n", addr, mac_group_type(&mg), mac_group_state(&mg), mg.mrg_cur_count, clients, mg.mrg_rings); return (DCMD_OK); } /* Supported dee-commands */ static const mdb_dcmd_t dcmds[] = { {"mac_flow", "?[-u] [-aprtsm]", "display Flow Entry structures", mac_flow_dcmd, mac_flow_help}, {"mac_group", "?[-rtu]", "display MAC Ring Groups", mac_group_dcmd, NULL }, {"mac_srs", "?[ -r[i|s|c[v]] | -t[i|s|c[v]] ]", "display MAC Soft Ring Set" " structures", mac_srs_dcmd, mac_srs_help}, {"mac_ring", "?", "display MAC ring (hardware) structures", mac_ring_dcmd, mac_ring_help}, { NULL } }; /* Supported walkers */ static const mdb_walker_t walkers[] = { {"mac_flow", "walk list of flow entry structures", mac_flow_walk_init, mac_common_walk_step, NULL, NULL}, {"mac_group", "walk list of ring group structures", mac_group_walk_init, mac_group_walk_step, NULL, NULL}, {"mac_srs", "walk list of mac soft ring set structures", mac_srs_walk_init, mac_common_walk_step, NULL, NULL}, {"mac_ring", "walk list of mac ring structures", mac_ring_walk_init, mac_common_walk_step, NULL, NULL}, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * MDB developer support module. This module is loaded automatically when the * proc target is initialized and the target is mdb itself. In the future, we * should document these facilities in the answerbook to aid module developers. */ #define _MDB #include #include #include #include #include #include static const mdb_t * get_mdb(void) { static mdb_t m; if (mdb_readvar(&m, "mdb") == -1) mdb_warn("failed to read mdb_t state"); return (&m); } static int cmd_stack(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char sep[] = "-----------------------------------------------------------------"; if (flags & DCMD_ADDRSPEC) { char buf[MDB_NV_NAMELEN + 1]; uintptr_t sp, pc; mdb_idcmd_t idc; mdb_frame_t f; mdb_cmd_t c; mdb_arg_t *ap; size_t i; if (mdb_vread(&f, sizeof (f), addr) == -1) { mdb_warn("failed to read frame at %p", addr); return (DCMD_ERR); } bzero(&c, sizeof (mdb_cmd_t)); if (mdb_vread(&c, sizeof (c), (uintptr_t)f.f_cp) < 0 || mdb_vread(&idc, sizeof (idc), (uintptr_t)c.c_dcmd) < 0 || mdb_readstr(buf, sizeof (buf), (uintptr_t)idc.idc_name) < 1) (void) strcpy(buf, "?"); mdb_printf("+>\tframe <%u> %p (%s", f.f_id, addr, buf); ap = mdb_alloc(c.c_argv.a_nelems * sizeof (mdb_arg_t), UM_GC); if (ap != NULL && mdb_vread(ap, c.c_argv.a_nelems * sizeof (mdb_arg_t), (uintptr_t)c.c_argv.a_data) > 0) { for (i = 0; i < c.c_argv.a_nelems; i++) { switch (ap[i].a_type) { case MDB_TYPE_STRING: if (mdb_readstr(buf, sizeof (buf), (uintptr_t)ap[i].a_un.a_str) > 0) mdb_printf(" %s", buf); else mdb_printf(" ", ap[i].a_un.a_str); break; case MDB_TYPE_IMMEDIATE: mdb_printf(" $[ 0x%llx ]", ap[i].a_un.a_val); break; case MDB_TYPE_CHAR: mdb_printf(" '%c'", ap[i].a_un.a_char); break; default: mdb_printf(" ", ap[i].a_type); } } } mdb_printf(")\n\tf_list = %-?p\tf_cmds = %p\n", addr + OFFSETOF(mdb_frame_t, f_list), addr + OFFSETOF(mdb_frame_t, f_cmds)); mdb_printf("\tf_istk = %-?p\tf_ostk = %p\n", addr + OFFSETOF(mdb_frame_t, f_istk), addr + OFFSETOF(mdb_frame_t, f_ostk)); mdb_printf("\tf_wcbs = %-?p\tf_mblks = %p\n", f.f_wcbs, f.f_mblks); mdb_printf("\tf_pcmd = %-?p\tf_pcb = %p\n", f.f_pcmd, addr + OFFSETOF(mdb_frame_t, f_pcb)); mdb_printf("\tf_cp = %-?p\t\tf_flags = 0x%x\n\n", f.f_cp, f.f_flags); #if defined(__sparc) sp = ((uintptr_t *)f.f_pcb)[1]; pc = ((uintptr_t *)f.f_pcb)[2]; #elif defined(__amd64) sp = ((uintptr_t *)f.f_pcb)[5]; pc = ((uintptr_t *)f.f_pcb)[7]; #elif defined(__i386) sp = ((uintptr_t *)f.f_pcb)[3]; pc = ((uintptr_t *)f.f_pcb)[5]; #else #error Unknown ISA #endif if (pc != 0) mdb_printf(" [ %0?lr %a() ]\n", sp, pc); mdb_set_dot(sp); mdb_inc_indent(8); mdb_eval("<.$C0"); mdb_dec_indent(8); mdb_printf("%s\n", sep); } else { mdb_printf("%s\n", sep); (void) mdb_walk_dcmd("mdb_frame", "mdb_stack", argc, argv); } return (DCMD_OK); } static int cmd_frame(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags & DCMD_ADDRSPEC) && argc == 0) return (cmd_stack(addr, flags, argc, argv)); return (DCMD_USAGE); } /*ARGSUSED*/ static int cmd_iob(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_iob_t iob; mdb_io_t io; if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %6s %6s %?s %s\n", "IOB", "NBYTES", "FLAGS", "IOP", "OPS"); } if (mdb_vread(&iob, sizeof (iob), addr) == -1 || mdb_vread(&io, sizeof (io), (uintptr_t)iob.iob_iop) == -1) { mdb_warn("failed to read iob at %p", addr); return (DCMD_ERR); } mdb_printf("%?p %6lu %6x %?p %a\n", addr, (ulong_t)iob.iob_nbytes, iob.iob_flags, iob.iob_iop, io.io_ops); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_in(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_printf("%p\n", get_mdb()->m_in); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_out(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_printf("%p\n", get_mdb()->m_out); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_err(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_printf("%p\n", get_mdb()->m_err); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_target(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t t; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) addr = (uintptr_t)get_mdb()->m_target; if (mdb_vread(&t, sizeof (t), addr) != sizeof (t)) { mdb_warn("failed to read target at %p", addr); return (DCMD_ERR); } mdb_printf("+>\ttarget %p (%a)\n", addr, t.t_ops); mdb_printf("\tt_active = %-?p\tt_idle = %p\n", addr + OFFSETOF(mdb_tgt_t, t_active), addr + OFFSETOF(mdb_tgt_t, t_idle)); mdb_printf("\tt_xdlist = %-?p\tt_module = %a\n", addr + OFFSETOF(mdb_tgt_t, t_xdlist), t.t_module); mdb_printf("\tt_pshandle = %-?p\tt_data = %p\n", t.t_pshandle, t.t_data); mdb_printf("\tt_status = %-?p\tt_matched = %p\n", addr + OFFSETOF(mdb_tgt_t, t_status), t.t_matched); mdb_printf("\tt_flags = %-?x\tt_vecnt = 0t%u\n", t.t_flags, t.t_vecnt); mdb_printf("\tt_vepos = %-?d\tt_veneg = %d\n\n", t.t_vepos, t.t_veneg); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_sespec(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_sespec_t se; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&se, sizeof (se), addr) != sizeof (se)) { mdb_warn("failed to read sespec at %p", addr); return (DCMD_ERR); } mdb_printf("+>\tsespec %p (%a)\n", addr, se.se_ops); mdb_printf("\tse_selist = %-?p\tse_velist = %p\n", addr + OFFSETOF(mdb_sespec_t, se_selist), addr + OFFSETOF(mdb_sespec_t, se_velist)); mdb_printf("\tse_data = %-?p\tse_refs = %u\n", se.se_data, se.se_refs); mdb_printf("\tse_state = %-?d\tse_errno = %d\n\n", se.se_state, se.se_errno); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_vespec(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_vespec_t ve; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&ve, sizeof (ve), addr) != sizeof (ve)) { mdb_warn("failed to read vespec at %p", addr); return (DCMD_ERR); } mdb_printf("+>\tvespec %p (id %d)\n", addr, ve.ve_id); mdb_printf("\tve_list = %-?p\tve_flags = 0x%x\n", addr + OFFSETOF(mdb_vespec_t, ve_list), ve.ve_flags); mdb_printf("\tve_se = %-?p\tve_refs = %u\n", ve.ve_se, ve.ve_refs); mdb_printf("\tve_hits = %-?u\tve_lim = %u\n", ve.ve_hits, ve.ve_limit); mdb_printf("\tve_data = %-?p\tve_callback = %a\n", ve.ve_data, ve.ve_callback); mdb_printf("\tve_args = %-?p\tve_dtor = %a\n\n", ve.ve_args, ve.ve_dtor); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_wr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char path[MAXPATHLEN]; kmdb_wr_t wn; char dir; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&wn, sizeof (wn), addr) != sizeof (wn)) { mdb_warn("failed to read wr node at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%-9s %3s %?s %s\n", "COMMAND", "ERR", "MODCTL", "NAME"); } dir = "><"[WR_ISACK(&wn) != 0]; switch (WR_TASK(&wn)) { case WNTASK_DMOD_LOAD: { kmdb_wr_load_t dlr; if (mdb_vread(&dlr, sizeof (dlr), addr) != sizeof (dlr)) { mdb_warn("failed to read kmdb_wr_load_t at %p", addr); return (DCMD_ERR); } if (mdb_readstr(path, sizeof (path), (uintptr_t)dlr.dlr_fname) < 0) { mdb_warn("failed to read path name at %p", dlr.dlr_fname); *path = '\0'; } mdb_printf("%cload %3d %?p %s\n", dir, dlr.dlr_errno, dlr.dlr_modctl, path); break; } case WNTASK_DMOD_LOAD_ALL: mdb_printf("%cload all %3d\n", dir, wn.wn_errno); break; case WNTASK_DMOD_UNLOAD: { kmdb_wr_unload_t dur; if (mdb_vread(&dur, sizeof (dur), addr) != sizeof (dur)) { mdb_warn("failed to read kmdb_wr_unload_t at %p", addr); return (DCMD_ERR); } if (mdb_readstr(path, sizeof (path), (uintptr_t)dur.dur_modname) < 0) { mdb_warn("failed to read module name at %p", dur.dur_modname); *path = '\0'; } mdb_printf("%cunload %3d %?p %s\n", dir, dur.dur_errno, dur.dur_modctl, path); break; } case WNTASK_DMOD_PATH_CHANGE: { kmdb_wr_path_t dpth; uintptr_t pathp; int first = 1; if (mdb_vread(&dpth, sizeof (dpth), addr) != sizeof (dpth)) { mdb_warn("failed to read kmdb_wr_path_t at %p", addr); return (DCMD_ERR); } mdb_printf("%cpath chg %3d ", dir, dpth.dpth_errno); for (;;) { if (mdb_vread(&pathp, sizeof (pathp), (uintptr_t)dpth.dpth_path) != sizeof (pathp)) { mdb_warn("failed to read path pointer at %p", dpth.dpth_path); break; } dpth.dpth_path++; if (pathp == 0) break; if (mdb_readstr(path, sizeof (path), pathp) < 0) { mdb_warn("failed to read path at %p", pathp); *path = '\0'; } mdb_printf("%s%s", (first ? "" : "\n "), path); first = 0; } mdb_printf("\n"); break; } default: mdb_warn("unknown task type %d\n", wn.wn_task); return (DCMD_ERR); } return (DCMD_OK); } static int iob_stack_walk_init(mdb_walk_state_t *wsp) { mdb_iob_stack_t stk; if (mdb_vread(&stk, sizeof (stk), wsp->walk_addr) == -1) { mdb_warn("failed to read iob_stack at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)stk.stk_top; return (WALK_NEXT); } static int iob_stack_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; mdb_iob_t iob; if (addr == 0) return (WALK_DONE); if (mdb_vread(&iob, sizeof (iob), addr) == -1) { mdb_warn("failed to read iob at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)iob.iob_next; return (wsp->walk_callback(addr, &iob, wsp->walk_cbdata)); } static int frame_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) wsp->walk_addr = (uintptr_t)get_mdb()->m_flist.ml_prev; return (WALK_NEXT); } static int frame_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; mdb_frame_t f; if (addr == 0) return (WALK_DONE); if (mdb_vread(&f, sizeof (f), addr) == -1) { mdb_warn("failed to read frame at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)f.f_list.ml_prev; return (wsp->walk_callback(addr, &f, wsp->walk_cbdata)); } static int target_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) wsp->walk_addr = (uintptr_t)get_mdb()->m_target; return (WALK_NEXT); } static int target_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; mdb_tgt_t t; if (addr == 0) return (WALK_DONE); if (mdb_vread(&t, sizeof (t), addr) == -1) { mdb_warn("failed to read target at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)t.t_tgtlist.ml_next; return (wsp->walk_callback(addr, &t, wsp->walk_cbdata)); } static int sespec_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; mdb_sespec_t s; if (addr == 0) return (WALK_DONE); if (mdb_vread(&s, sizeof (s), addr) == -1) { mdb_warn("failed to read sespec at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)s.se_selist.ml_next; return (wsp->walk_callback(addr, &s, wsp->walk_cbdata)); } static int vespec_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; mdb_vespec_t v; if (addr == 0) return (WALK_DONE); if (mdb_vread(&v, sizeof (v), addr) == -1) { mdb_warn("failed to read vespec at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)v.ve_list.ml_next; return (wsp->walk_callback(addr, &v, wsp->walk_cbdata)); } static int se_matched_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; mdb_sespec_t s; if (addr == 0) return (WALK_DONE); if (mdb_vread(&s, sizeof (s), addr) == -1) { mdb_warn("failed to read sespec at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)s.se_matched; return (wsp->walk_callback(addr, &s, wsp->walk_cbdata)); } static const mdb_dcmd_t dcmds[] = { { "mdb_stack", "?", "print debugger stack", cmd_stack }, { "mdb_frame", ":", "print debugger frame", cmd_frame }, { "mdb_iob", ":", "print i/o buffer information", cmd_iob }, { "mdb_in", NULL, "print stdin iob", cmd_in }, { "mdb_out", NULL, "print stdout iob", cmd_out }, { "mdb_err", NULL, "print stderr iob", cmd_err }, { "mdb_tgt", "?", "print current target", cmd_target }, { "mdb_sespec", ":", "print software event specifier", cmd_sespec }, { "mdb_vespec", ":", "print virtual event specifier", cmd_vespec }, { "kmdb_wr", NULL, "print a work queue entry", cmd_wr }, { NULL } }; static const mdb_walker_t walkers[] = { { "mdb_frame", "iterate over mdb_frame stack", frame_walk_init, frame_walk_step, NULL }, { "mdb_iob_stack", "iterate over mdb_iob_stack elements", iob_stack_walk_init, iob_stack_walk_step, NULL }, { "mdb_tgt", "iterate over active targets", target_walk_init, target_walk_step, NULL }, { "mdb_sespec", "iterate over software event specifiers", NULL, sespec_walk_step, NULL }, { "mdb_vespec", "iterate over virtual event specifiers", NULL, vespec_walk_step, NULL }, { "se_matched", "iterate over matched software event specifiers", NULL, se_matched_walk_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { char buf[256]; uintptr_t addr; int rcount; GElf_Sym sym; if (mdb_lookup_by_name("mdb", &sym) == -1) { mdb_warn("failed to read mdb state structure"); return (NULL); } if (sym.st_size != sizeof (mdb_t)) { mdb_printf("mdb: WARNING: mdb_ds may not match mdb " "implementation (mdb_t mismatch)\n"); } if (mdb_readvar(&addr, "_mdb_abort_str") != -1 && addr != 0 && mdb_readstr(buf, sizeof (buf), addr) > 0) mdb_printf("mdb: debugger failed with error: %s\n", buf); if (mdb_readvar(&rcount, "_mdb_abort_rcount") != -1 && rcount != 0) mdb_printf("mdb: WARNING: resume executed %d times\n", rcount); return (&modinfo); } /* * 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 (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright 2023 RackTop Systems, Inc. * Copyright 2025 Oxide Computer Company */ /* * Mdb kernel support module. This module is loaded automatically when the * kvm target is initialized. Any global functions declared here are exported * for the resolution of symbols in subsequently loaded modules. * * WARNING: Do not assume that static variables in mdb_ks will be initialized * to zero. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MDB_PATH_NELEM 256 /* Maximum path components */ typedef struct mdb_path { size_t mdp_nelem; /* Number of components */ uint_t mdp_complete; /* Path completely resolved? */ uintptr_t mdp_vnode[MDB_PATH_NELEM]; /* Array of vnode_t addresses */ char *mdp_name[MDB_PATH_NELEM]; /* Array of name components */ } mdb_path_t; static int mdb_autonode2path(uintptr_t, mdb_path_t *); static int mdb_sprintpath(char *, size_t, mdb_path_t *); /* * Kernel parameters from which we keep in-core: */ unsigned long _mdb_ks_pagesize; unsigned int _mdb_ks_pageshift; unsigned long _mdb_ks_pageoffset; unsigned long long _mdb_ks_pagemask; unsigned long _mdb_ks_mmu_pagesize; unsigned int _mdb_ks_mmu_pageshift; unsigned long _mdb_ks_mmu_pageoffset; unsigned long _mdb_ks_mmu_pagemask; uintptr_t _mdb_ks_kernelbase; uintptr_t _mdb_ks_userlimit; uintptr_t _mdb_ks_userlimit32; unsigned long _mdb_ks_msg_bsize; unsigned long _mdb_ks_defaultstksz; int _mdb_ks_ncpu; int _mdb_ks_ncpu_log2; int _mdb_ks_ncpu_p2; /* * In-core copy of DNLC information: */ #define MDB_DNLC_HSIZE 1024 #define MDB_DNLC_HASH(vp) (((uintptr_t)(vp) >> 3) & (MDB_DNLC_HSIZE - 1)) #define MDB_DNLC_NCACHE_SZ(ncp) (sizeof (ncache_t) + (ncp)->namlen) #define MDB_DNLC_MAX_RETRY 4 static ncache_t **dnlc_hash; /* mdbs hash array of dnlc entries */ /* * copy of page_hash-related data */ static int page_hash_loaded; static long mdb_page_hashsz; static uint_t mdb_page_hashsz_shift; /* Needed for PAGE_HASH_FUNC */ static uintptr_t mdb_page_hash; /* base address of page hash */ #define page_hashsz mdb_page_hashsz #define page_hashsz_shift mdb_page_hashsz_shift /* * This will be the location of the vnodeops pointer for "autofs_vnodeops" * The pointer still needs to be read with mdb_vread() to get the location * of the vnodeops structure for autofs. */ static struct vnodeops *autofs_vnops_ptr; /* * STREAMS queue registrations: */ typedef struct mdb_qinfo { const mdb_qops_t *qi_ops; /* Address of ops vector */ uintptr_t qi_addr; /* Address of qinit structure (key) */ struct mdb_qinfo *qi_next; /* Next qinfo in list */ } mdb_qinfo_t; static mdb_qinfo_t *qi_head; /* Head of qinfo chain */ /* * Device naming callback structure: */ typedef struct nm_query { const char *nm_name; /* Device driver name [in/out] */ major_t nm_major; /* Device major number [in/out] */ ushort_t nm_found; /* Did we find a match? [out] */ } nm_query_t; /* * Address-to-modctl callback structure: */ typedef struct a2m_query { uintptr_t a2m_addr; /* Virtual address [in] */ uintptr_t a2m_where; /* Modctl address [out] */ } a2m_query_t; /* * Segment-to-mdb_map callback structure: */ typedef struct { struct seg_ops *asm_segvn_ops; /* Address of segvn ops [in] */ void (*asm_callback)(const struct mdb_map *, void *); /* Callb [in] */ void *asm_cbdata; /* Callback data [in] */ } asmap_arg_t; static void dnlc_free(void) { ncache_t *ncp, *next; int i; if (dnlc_hash == NULL) { return; } /* * Free up current dnlc entries */ for (i = 0; i < MDB_DNLC_HSIZE; i++) { for (ncp = dnlc_hash[i]; ncp; ncp = next) { next = ncp->hash_next; mdb_free(ncp, MDB_DNLC_NCACHE_SZ(ncp)); } } mdb_free(dnlc_hash, MDB_DNLC_HSIZE * sizeof (ncache_t *)); dnlc_hash = NULL; } char bad_dnlc[] = "inconsistent dnlc chain: %d, ncache va: %p" " - continuing with the rest\n"; static int dnlc_load(void) { int i; /* hash index */ int retry_cnt = 0; int skip_bad_chains = 0; int nc_hashsz; /* kernel hash array size */ uintptr_t nc_hash_addr; /* kernel va of ncache hash array */ uintptr_t head; /* kernel va of head of hash chain */ /* * If we've already cached the DNLC and we're looking at a dump, * our cache is good forever, so don't bother re-loading. */ if (dnlc_hash && mdb_prop_postmortem) { return (0); } /* * For a core dump, retries wont help. * Just print and skip any bad chains. */ if (mdb_prop_postmortem) { skip_bad_chains = 1; } retry: if (retry_cnt++ >= MDB_DNLC_MAX_RETRY) { /* * Give up retrying the rapidly changing dnlc. * Just print and skip any bad chains */ skip_bad_chains = 1; } dnlc_free(); /* Free up the mdb hashed dnlc - if any */ /* * Although nc_hashsz and the location of nc_hash doesn't currently * change, it may do in the future with a more dynamic dnlc. * So always read these values afresh. */ if (mdb_readvar(&nc_hashsz, "nc_hashsz") == -1) { mdb_warn("failed to read nc_hashsz"); return (-1); } if (mdb_readvar(&nc_hash_addr, "nc_hash") == -1) { mdb_warn("failed to read nc_hash"); return (-1); } /* * Allocate the mdb dnlc hash array */ dnlc_hash = mdb_zalloc(MDB_DNLC_HSIZE * sizeof (ncache_t *), UM_SLEEP); /* for each kernel hash chain */ for (i = 0, head = nc_hash_addr; i < nc_hashsz; i++, head += sizeof (nc_hash_t)) { nc_hash_t nch; /* kernel hash chain header */ ncache_t *ncp; /* name cache pointer */ int hash; /* mdb hash value */ uintptr_t nc_va; /* kernel va of next ncache */ uintptr_t ncprev_va; /* kernel va of previous ncache */ int khash; /* kernel dnlc hash value */ uchar_t namelen; /* name length */ ncache_t nc; /* name cache entry */ int nc_size; /* size of a name cache entry */ /* * We read each element of the nc_hash array individually * just before we process the entries in its chain. This is * because the chain can change so rapidly on a running system. */ if (mdb_vread(&nch, sizeof (nc_hash_t), head) == -1) { mdb_warn("failed to read nc_hash chain header %d", i); dnlc_free(); return (-1); } ncprev_va = head; nc_va = (uintptr_t)(nch.hash_next); /* for each entry in the chain */ while (nc_va != head) { /* * The size of the ncache entries varies * because the name is appended to the structure. * So we read in the structure then re-read * for the structure plus name. */ if (mdb_vread(&nc, sizeof (ncache_t), nc_va) == -1) { if (skip_bad_chains) { mdb_warn(bad_dnlc, i, nc_va); break; } goto retry; } nc_size = MDB_DNLC_NCACHE_SZ(&nc); ncp = mdb_alloc(nc_size, UM_SLEEP); if (mdb_vread(ncp, nc_size - 1, nc_va) == -1) { mdb_free(ncp, nc_size); if (skip_bad_chains) { mdb_warn(bad_dnlc, i, nc_va); break; } goto retry; } /* * Check for chain consistency */ if ((uintptr_t)ncp->hash_prev != ncprev_va) { mdb_free(ncp, nc_size); if (skip_bad_chains) { mdb_warn(bad_dnlc, i, nc_va); break; } goto retry; } /* * Terminate the new name with a null. * Note, we allowed space for this null when * allocating space for the entry. */ ncp->name[ncp->namlen] = '\0'; /* * Validate new entry by re-hashing using the * kernel dnlc hash function and comparing the hash */ DNLCHASH(ncp->name, ncp->dp, khash, namelen); if ((namelen != ncp->namlen) || (khash != ncp->hash)) { mdb_free(ncp, nc_size); if (skip_bad_chains) { mdb_warn(bad_dnlc, i, nc_va); break; } goto retry; } /* * Finally put the validated entry into the mdb * hash chains. Reuse the kernel next hash field * for the mdb hash chain pointer. */ hash = MDB_DNLC_HASH(ncp->vp); ncprev_va = nc_va; nc_va = (uintptr_t)(ncp->hash_next); ncp->hash_next = dnlc_hash[hash]; dnlc_hash[hash] = ncp; } } return (0); } /*ARGSUSED*/ int dnlcdump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ncache_t *ent; int i; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (dnlc_load() == -1) return (DCMD_ERR); mdb_printf("%%-?s %-?s %-32s%\n", "VP", "DVP", "NAME"); for (i = 0; i < MDB_DNLC_HSIZE; i++) { for (ent = dnlc_hash[i]; ent != NULL; ent = ent->hash_next) { mdb_printf("%0?p %0?p %s\n", ent->vp, ent->dp, ent->name); } } return (DCMD_OK); } static int mdb_sprintpath(char *buf, size_t len, mdb_path_t *path) { char *s = buf; int i; if (len < sizeof ("/...")) return (-1); if (!path->mdp_complete) { (void) strcpy(s, "??"); s += 2; if (path->mdp_nelem == 0) return (-1); } if (path->mdp_nelem == 0) { (void) strcpy(s, "/"); return (0); } for (i = path->mdp_nelem - 1; i >= 0; i--) { /* * Number of bytes left is the distance from where we * are to the end, minus 2 for '/' and '\0' */ ssize_t left = (ssize_t)(&buf[len] - s) - 2; if (left <= 0) break; *s++ = '/'; (void) strncpy(s, path->mdp_name[i], left); s[left - 1] = '\0'; s += strlen(s); if (left < strlen(path->mdp_name[i])) break; } if (i >= 0) (void) strcpy(&buf[len - 4], "..."); return (0); } static int mdb_autonode2path(uintptr_t addr, mdb_path_t *path) { fninfo_t fni; fnnode_t fn; vnode_t vn; vfs_t vfs; struct vnodeops *autofs_vnops = NULL; /* * "autofs_vnops_ptr" is the address of the pointer to the vnodeops * structure for autofs. We want to read it each time we access * it since autofs could (in theory) be unloaded and reloaded. */ if (mdb_vread(&autofs_vnops, sizeof (autofs_vnops), (uintptr_t)autofs_vnops_ptr) == -1) return (-1); if (mdb_vread(&vn, sizeof (vn), addr) == -1) return (-1); if (autofs_vnops == NULL || vn.v_op != autofs_vnops) return (-1); addr = (uintptr_t)vn.v_data; if (mdb_vread(&vfs, sizeof (vfs), (uintptr_t)vn.v_vfsp) == -1 || mdb_vread(&fni, sizeof (fni), (uintptr_t)vfs.vfs_data) == -1 || mdb_vread(&vn, sizeof (vn), (uintptr_t)fni.fi_rootvp) == -1) return (-1); for (;;) { size_t elem = path->mdp_nelem++; char elemstr[MAXNAMELEN]; char *c, *p; if (elem == MDB_PATH_NELEM) { path->mdp_nelem--; return (-1); } if (mdb_vread(&fn, sizeof (fn), addr) != sizeof (fn)) { path->mdp_nelem--; return (-1); } if (mdb_readstr(elemstr, sizeof (elemstr), (uintptr_t)fn.fn_name) <= 0) { (void) strcpy(elemstr, "?"); } c = mdb_alloc(strlen(elemstr) + 1, UM_SLEEP | UM_GC); (void) strcpy(c, elemstr); path->mdp_vnode[elem] = (uintptr_t)fn.fn_vnode; if (addr == (uintptr_t)fn.fn_parent) { path->mdp_name[elem] = &c[1]; path->mdp_complete = TRUE; break; } if ((p = strrchr(c, '/')) != NULL) path->mdp_name[elem] = p + 1; else path->mdp_name[elem] = c; addr = (uintptr_t)fn.fn_parent; } return (0); } int mdb_vnode2path(uintptr_t addr, char *buf, size_t buflen) { uintptr_t rootdir; ncache_t *ent; vnode_t vp; mdb_path_t path; /* * Check to see if we have a cached value for this vnode */ if (mdb_vread(&vp, sizeof (vp), addr) != -1 && vp.v_path != NULL && mdb_readstr(buf, buflen, (uintptr_t)vp.v_path) != -1) return (0); if (dnlc_load() == -1) return (-1); if (mdb_readvar(&rootdir, "rootdir") == -1) { mdb_warn("failed to read 'rootdir'"); return (-1); } bzero(&path, sizeof (mdb_path_t)); again: if ((addr == 0) && (path.mdp_nelem == 0)) { /* * 0 elems && complete tells sprintpath to just print "/" */ path.mdp_complete = TRUE; goto out; } if (addr == rootdir) { path.mdp_complete = TRUE; goto out; } for (ent = dnlc_hash[MDB_DNLC_HASH(addr)]; ent; ent = ent->hash_next) { if ((uintptr_t)ent->vp == addr) { if (strcmp(ent->name, "..") == 0 || strcmp(ent->name, ".") == 0) continue; path.mdp_vnode[path.mdp_nelem] = (uintptr_t)ent->vp; path.mdp_name[path.mdp_nelem] = ent->name; path.mdp_nelem++; if (path.mdp_nelem == MDB_PATH_NELEM) { path.mdp_nelem--; mdb_warn("path exceeded maximum expected " "elements\n"); return (-1); } addr = (uintptr_t)ent->dp; goto again; } } (void) mdb_autonode2path(addr, &path); out: return (mdb_sprintpath(buf, buflen, &path)); } uintptr_t mdb_pid2proc(pid_t pid, proc_t *proc) { int pid_hashsz, hash; uintptr_t paddr, pidhash, procdir; struct pid pidp; if (mdb_readvar(&pidhash, "pidhash") == -1) return (0); if (mdb_readvar(&pid_hashsz, "pid_hashsz") == -1) return (0); if (mdb_readvar(&procdir, "procdir") == -1) return (0); hash = pid & (pid_hashsz - 1); if (mdb_vread(&paddr, sizeof (paddr), pidhash + (hash * sizeof (paddr))) == -1) return (0); while (paddr != 0) { if (mdb_vread(&pidp, sizeof (pidp), paddr) == -1) return (0); if (pidp.pid_id == pid) { uintptr_t procp; if (mdb_vread(&procp, sizeof (procp), procdir + (pidp.pid_prslot * sizeof (procp))) == -1) return (0); if (proc != NULL) (void) mdb_vread(proc, sizeof (proc_t), procp); return (procp); } paddr = (uintptr_t)pidp.pid_link; } return (0); } int mdb_cpu2cpuid(uintptr_t cpup) { cpu_t cpu; if (mdb_vread(&cpu, sizeof (cpu_t), cpup) != sizeof (cpu_t)) return (-1); return (cpu.cpu_id); } int mdb_cpuset_find(uintptr_t cpusetp) { ulong_t *cpuset; size_t nr_words = BT_BITOUL(NCPU); size_t sz = nr_words * sizeof (ulong_t); size_t i; int cpu = -1; cpuset = mdb_alloc(sz, UM_SLEEP); if (mdb_vread((void *)cpuset, sz, cpusetp) != sz) goto out; for (i = 0; i < nr_words; i++) { size_t j; ulong_t m; for (j = 0, m = 1; j < BT_NBIPUL; j++, m <<= 1) { if (cpuset[i] & m) { cpu = i * BT_NBIPUL + j; goto out; } } } out: mdb_free(cpuset, sz); return (cpu); } static int page_hash_load(void) { if (page_hash_loaded) { return (1); } if (mdb_readvar(&mdb_page_hashsz, "page_hashsz") == -1) { mdb_warn("unable to read page_hashsz"); return (0); } if (mdb_readvar(&mdb_page_hashsz_shift, "page_hashsz_shift") == -1) { mdb_warn("unable to read page_hashsz_shift"); return (0); } if (mdb_readvar(&mdb_page_hash, "page_hash") == -1) { mdb_warn("unable to read page_hash"); return (0); } page_hash_loaded = 1; /* zeroed on state change */ return (1); } uintptr_t mdb_page_lookup(uintptr_t vp, u_offset_t offset) { size_t ndx; uintptr_t page_hash_entry, pp; if (!page_hash_loaded && !page_hash_load()) { return (0); } ndx = PAGE_HASH_FUNC(vp, offset); page_hash_entry = mdb_page_hash + ndx * sizeof (uintptr_t); if (mdb_vread(&pp, sizeof (pp), page_hash_entry) < 0) { mdb_warn("unable to read page_hash[%ld] (%p)", ndx, page_hash_entry); return (0); } while (pp != 0) { page_t page; long nndx; if (mdb_vread(&page, sizeof (page), pp) < 0) { mdb_warn("unable to read page_t at %p", pp); return (0); } if ((uintptr_t)page.p_vnode == vp && (uint64_t)page.p_offset == offset) return (pp); /* * Double check that the pages actually hash to the * bucket we're searching. If not, our version of * PAGE_HASH_FUNC() doesn't match the kernel's, and we're * not going to be able to find the page. The most * likely reason for this that mdb_ks doesn't match the * kernel we're running against. */ nndx = PAGE_HASH_FUNC(page.p_vnode, page.p_offset); if (page.p_vnode != NULL && nndx != ndx) { mdb_warn("mdb_page_lookup: mdb_ks PAGE_HASH_FUNC() " "mismatch: in bucket %ld, but page %p hashes to " "bucket %ld\n", ndx, pp, nndx); return (0); } pp = (uintptr_t)page.p_hash; } return (0); } char mdb_vtype2chr(vtype_t type, mode_t mode) { static const char vttab[] = { ' ', /* VNON */ ' ', /* VREG */ '/', /* VDIR */ ' ', /* VBLK */ ' ', /* VCHR */ '@', /* VLNK */ '|', /* VFIFO */ '>', /* VDOOR */ ' ', /* VPROC */ '=', /* VSOCK */ ' ', /* VBAD */ }; if (type < 0 || type >= sizeof (vttab) / sizeof (vttab[0])) return ('?'); if (type == VREG && (mode & 0111) != 0) return ('*'); return (vttab[type]); } struct pfn2page { pfn_t pfn; page_t *pp; }; /*ARGSUSED*/ static int pfn2page_cb(uintptr_t addr, const struct memseg *msp, void *data) { struct pfn2page *p = data; if (p->pfn >= msp->pages_base && p->pfn < msp->pages_end) { p->pp = msp->pages + (p->pfn - msp->pages_base); return (WALK_DONE); } return (WALK_NEXT); } uintptr_t mdb_pfn2page(pfn_t pfn) { struct pfn2page arg; struct page page; arg.pfn = pfn; arg.pp = NULL; if (mdb_walk("memseg", (mdb_walk_cb_t)pfn2page_cb, &arg) == -1) { mdb_warn("pfn2page: can't walk memsegs"); return (0); } if (arg.pp == NULL) { mdb_warn("pfn2page: unable to find page_t for pfn %lx\n", pfn); return (0); } if (mdb_vread(&page, sizeof (page_t), (uintptr_t)arg.pp) == -1) { mdb_warn("pfn2page: can't read page 0x%lx at %p", pfn, arg.pp); return (0); } if (page.p_pagenum != pfn) { mdb_warn("pfn2page: page_t 0x%p should have PFN 0x%lx, " "but actually has 0x%lx\n", arg.pp, pfn, page.p_pagenum); return (0); } return ((uintptr_t)arg.pp); } pfn_t mdb_page2pfn(uintptr_t addr) { struct page page; if (mdb_vread(&page, sizeof (page_t), addr) == -1) { mdb_warn("pp2pfn: can't read page at %p", addr); return ((pfn_t)(-1)); } return (page.p_pagenum); } struct a2m_ctf_module { char *text; char *data; size_t text_size; size_t data_size; }; static int a2m_walk_modctl(uintptr_t addr, const struct modctl *m, a2m_query_t *a2m) { struct a2m_ctf_module mod; if (m->mod_mp == NULL) return (0); if (mdb_ctf_vread(&mod, "struct module", "struct a2m_ctf_module", (uintptr_t)m->mod_mp, 0) == -1) { mdb_warn("couldn't read modctl %p's module", addr); return (0); } if (a2m->a2m_addr >= (uintptr_t)mod.text && a2m->a2m_addr < (uintptr_t)mod.text + mod.text_size) goto found; if (a2m->a2m_addr >= (uintptr_t)mod.data && a2m->a2m_addr < (uintptr_t)mod.data + mod.data_size) goto found; return (0); found: a2m->a2m_where = addr; return (-1); } uintptr_t mdb_addr2modctl(uintptr_t addr) { a2m_query_t a2m; a2m.a2m_addr = addr; a2m.a2m_where = 0; (void) mdb_walk("modctl", (mdb_walk_cb_t)a2m_walk_modctl, &a2m); return (a2m.a2m_where); } static mdb_qinfo_t * qi_lookup(uintptr_t qinit_addr) { mdb_qinfo_t *qip; for (qip = qi_head; qip != NULL; qip = qip->qi_next) { if (qip->qi_addr == qinit_addr) return (qip); } return (NULL); } void mdb_qops_install(const mdb_qops_t *qops, uintptr_t qinit_addr) { mdb_qinfo_t *qip = qi_lookup(qinit_addr); if (qip != NULL) { qip->qi_ops = qops; return; } qip = mdb_alloc(sizeof (mdb_qinfo_t), UM_SLEEP); qip->qi_ops = qops; qip->qi_addr = qinit_addr; qip->qi_next = qi_head; qi_head = qip; } void mdb_qops_remove(const mdb_qops_t *qops, uintptr_t qinit_addr) { mdb_qinfo_t *qip, *p = NULL; for (qip = qi_head; qip != NULL; p = qip, qip = qip->qi_next) { if (qip->qi_addr == qinit_addr && qip->qi_ops == qops) { if (qi_head == qip) qi_head = qip->qi_next; else p->qi_next = qip->qi_next; mdb_free(qip, sizeof (mdb_qinfo_t)); return; } } } char * mdb_qname(const queue_t *q, char *buf, size_t nbytes) { struct module_info mi; struct qinit qi; if (mdb_vread(&qi, sizeof (qi), (uintptr_t)q->q_qinfo) == -1) { mdb_warn("failed to read qinit at %p", q->q_qinfo); goto err; } if (mdb_vread(&mi, sizeof (mi), (uintptr_t)qi.qi_minfo) == -1) { mdb_warn("failed to read module_info at %p", qi.qi_minfo); goto err; } if (mdb_readstr(buf, nbytes, (uintptr_t)mi.mi_idname) <= 0) { mdb_warn("failed to read mi_idname at %p", mi.mi_idname); goto err; } return (buf); err: (void) mdb_snprintf(buf, nbytes, "???"); return (buf); } void mdb_qinfo(const queue_t *q, char *buf, size_t nbytes) { mdb_qinfo_t *qip = qi_lookup((uintptr_t)q->q_qinfo); buf[0] = '\0'; if (qip != NULL) qip->qi_ops->q_info(q, buf, nbytes); } uintptr_t mdb_qrnext(const queue_t *q) { mdb_qinfo_t *qip = qi_lookup((uintptr_t)q->q_qinfo); if (qip != NULL) return (qip->qi_ops->q_rnext(q)); return (0); } uintptr_t mdb_qwnext(const queue_t *q) { mdb_qinfo_t *qip = qi_lookup((uintptr_t)q->q_qinfo); if (qip != NULL) return (qip->qi_ops->q_wnext(q)); return (0); } uintptr_t mdb_qrnext_default(const queue_t *q) { return ((uintptr_t)q->q_next); } uintptr_t mdb_qwnext_default(const queue_t *q) { return ((uintptr_t)q->q_next); } /* * The following three routines borrowed from modsubr.c */ static int nm_hash(const char *name) { char c; int hash = 0; for (c = *name++; c; c = *name++) hash ^= c; return (hash & MOD_BIND_HASHMASK); } static uintptr_t find_mbind(const char *name, uintptr_t *hashtab) { int hashndx; uintptr_t mb; struct bind mb_local; char node_name[MAXPATHLEN + 1]; hashndx = nm_hash(name); mb = hashtab[hashndx]; while (mb) { if (mdb_vread(&mb_local, sizeof (mb_local), mb) == -1) { mdb_warn("failed to read struct bind at %p", mb); return (0); } if (mdb_readstr(node_name, sizeof (node_name), (uintptr_t)mb_local.b_name) == -1) { mdb_warn("failed to read node name string at %p", mb_local.b_name); return (0); } if (strcmp(name, node_name) == 0) break; mb = (uintptr_t)mb_local.b_next; } return (mb); } int mdb_name_to_major(const char *name, major_t *major) { uintptr_t mbind; uintptr_t mb_hashtab[MOD_BIND_HASHSIZE]; struct bind mbind_local; if (mdb_readsym(mb_hashtab, sizeof (mb_hashtab), "mb_hashtab") == -1) { mdb_warn("failed to read symbol 'mb_hashtab'"); return (-1); } if ((mbind = find_mbind(name, mb_hashtab)) != 0) { if (mdb_vread(&mbind_local, sizeof (mbind_local), mbind) == -1) { mdb_warn("failed to read mbind struct at %p", mbind); return (-1); } *major = (major_t)mbind_local.b_num; return (0); } return (-1); } const char * mdb_major_to_name(major_t major) { static char name[MODMAXNAMELEN]; uintptr_t devnamesp; struct devnames dn; uint_t devcnt; if (mdb_readvar(&devcnt, "devcnt") == -1 || major >= devcnt || mdb_readvar(&devnamesp, "devnamesp") == -1) return (NULL); if (mdb_vread(&dn, sizeof (struct devnames), devnamesp + major * sizeof (struct devnames)) != sizeof (struct devnames)) return (NULL); if (mdb_readstr(name, sizeof (name), (uintptr_t)dn.dn_name) == -1) return (NULL); return ((const char *)name); } /* * Return the name of the driver attached to the dip in drivername. */ int mdb_devinfo2driver(uintptr_t dip_addr, char *drivername, size_t namebufsize) { struct dev_info devinfo; char bind_name[MAXPATHLEN + 1]; major_t major; const char *namestr; if (mdb_vread(&devinfo, sizeof (devinfo), dip_addr) == -1) { mdb_warn("failed to read devinfo at %p", dip_addr); return (-1); } if (mdb_readstr(bind_name, sizeof (bind_name), (uintptr_t)devinfo.devi_binding_name) == -1) { mdb_warn("failed to read binding name at %p", devinfo.devi_binding_name); return (-1); } /* * Many->one relation: various names to one major number */ if (mdb_name_to_major(bind_name, &major) == -1) { mdb_warn("failed to translate bind name to major number\n"); return (-1); } /* * One->one relation: one major number corresponds to one driver */ if ((namestr = mdb_major_to_name(major)) == NULL) { (void) strncpy(drivername, "???", namebufsize); return (-1); } (void) strncpy(drivername, namestr, namebufsize); return (0); } /* * Find the name of the driver attached to this dip (if any), given: * - the address of a dip (in core) * - the NAME of the global pointer to the driver's i_ddi_soft_state struct * - pointer to a pointer to receive the address */ int mdb_devinfo2statep(uintptr_t dip_addr, char *soft_statep_name, uintptr_t *statep) { struct dev_info dev_info; if (mdb_vread(&dev_info, sizeof (dev_info), dip_addr) == -1) { mdb_warn("failed to read devinfo at %p", dip_addr); return (-1); } return (mdb_get_soft_state_byname(soft_statep_name, dev_info.devi_instance, statep, NULL, 0)); } /* * Returns a pointer to the top of the soft state struct for the instance * specified (in state_addr), given the address of the global soft state * pointer and size of the struct. Also fills in the buffer pointed to by * state_buf_p (if non-NULL) with the contents of the state struct. */ int mdb_get_soft_state_byaddr(uintptr_t ssaddr, uint_t instance, uintptr_t *state_addr, void *state_buf_p, size_t sizeof_state) { struct i_ddi_soft_state ss; void *statep; if (mdb_vread(&ss, sizeof (ss), ssaddr) == -1) return (-1); if (instance >= ss.n_items) return (-1); if (mdb_vread(&statep, sizeof (statep), (uintptr_t)ss.array + (sizeof (statep) * instance)) == -1) return (-1); if (state_addr != NULL) *state_addr = (uintptr_t)statep; if (statep == NULL) { errno = ENOENT; return (-1); } if (state_buf_p != NULL) { /* Read the state struct into the buffer in local space. */ if (mdb_vread(state_buf_p, sizeof_state, (uintptr_t)statep) == -1) return (-1); } return (0); } /* * Returns a pointer to the top of the soft state struct for the instance * specified (in state_addr), given the name of the global soft state pointer * and size of the struct. Also fills in the buffer pointed to by * state_buf_p (if non-NULL) with the contents of the state struct. */ int mdb_get_soft_state_byname(char *softstatep_name, uint_t instance, uintptr_t *state_addr, void *state_buf_p, size_t sizeof_state) { uintptr_t ssaddr; if (mdb_readvar((void *)&ssaddr, softstatep_name) == -1) return (-1); return (mdb_get_soft_state_byaddr(ssaddr, instance, state_addr, state_buf_p, sizeof_state)); } static void tdelta_help(void) { mdb_printf( "Prints the difference between two nanosecond (hrtime_t) timestamps.\n\n" "The time used for comparison depends on the context in which mdb is invoked:\n" " Live system The current time (gethrtime()).\n" " Crash dump The time the dump started writing to the dump device.\n" " KMDB The most recent time the system entered KMDB.\n" "\n" "Note that with crash dumps, the dump time is often a few milliseconds\n" "after the time of the panic (panic_hrtime).\n" "\n" "The following options are supported:\n" " -h Print the delta in human readable form.\n" " -t %ts% Use %ts% as the comparison timestamp.\n"); } static int tdelta(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int64_t val = 0; int64_t delta = 0; int do_nice = FALSE; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("dcmd requires an input hrtime_t"); return (DCMD_ERR); } val = (int64_t)mdb_gethrtime(); if (mdb_getopts(argc, argv, 'h', MDB_OPT_SETBITS, TRUE, &do_nice, 't', MDB_OPT_UINT64, &val, NULL) != argc) { return (DCMD_USAGE); } delta = (int64_t)addr - val; if (do_nice) { char buf[32] = { 0 }; mdb_nicetime(delta, buf, sizeof (buf)); mdb_printf("%s\n", buf); } else { mdb_printf("%ll#R\n", delta); } return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "dnlc", NULL, "print DNLC contents", dnlcdump }, { "tdelta", "?[-h][-t ts]", "compute ns time delta", tdelta, tdelta_help }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds }; /*ARGSUSED*/ static void update_vars(void *arg) { GElf_Sym sym; if (mdb_lookup_by_name("auto_vnodeops", &sym) == 0) autofs_vnops_ptr = (struct vnodeops *)(uintptr_t)sym.st_value; else autofs_vnops_ptr = NULL; (void) mdb_readvar(&_mdb_ks_pagesize, "_pagesize"); (void) mdb_readvar(&_mdb_ks_pageshift, "_pageshift"); (void) mdb_readvar(&_mdb_ks_pageoffset, "_pageoffset"); (void) mdb_readvar(&_mdb_ks_pagemask, "_pagemask"); (void) mdb_readvar(&_mdb_ks_mmu_pagesize, "_mmu_pagesize"); (void) mdb_readvar(&_mdb_ks_mmu_pageshift, "_mmu_pageshift"); (void) mdb_readvar(&_mdb_ks_mmu_pageoffset, "_mmu_pageoffset"); (void) mdb_readvar(&_mdb_ks_mmu_pagemask, "_mmu_pagemask"); (void) mdb_readvar(&_mdb_ks_kernelbase, "_kernelbase"); (void) mdb_readvar(&_mdb_ks_userlimit, "_userlimit"); (void) mdb_readvar(&_mdb_ks_userlimit32, "_userlimit32"); (void) mdb_readvar(&_mdb_ks_msg_bsize, "_msg_bsize"); (void) mdb_readvar(&_mdb_ks_defaultstksz, "_defaultstksz"); (void) mdb_readvar(&_mdb_ks_ncpu, "_ncpu"); (void) mdb_readvar(&_mdb_ks_ncpu_log2, "_ncpu_log2"); (void) mdb_readvar(&_mdb_ks_ncpu_p2, "_ncpu_p2"); page_hash_loaded = 0; /* invalidate cached page_hash state */ } const mdb_modinfo_t * _mdb_init(void) { /* * When used with mdb, mdb_ks is a separate dmod. With kmdb, however, * mdb_ks is compiled into the debugger module. kmdb cannot * automatically modunload itself when it exits. If it restarts after * debugger fault, static variables may not be initialized to zero. * They must be manually reinitialized here. */ dnlc_hash = NULL; qi_head = NULL; mdb_callback_add(MDB_CALLBACK_STCHG, update_vars, NULL); update_vars(NULL); return (&modinfo); } void _mdb_fini(void) { dnlc_free(); while (qi_head != NULL) { mdb_qinfo_t *qip = qi_head; qi_head = qip->qi_next; mdb_free(qip, sizeof (mdb_qinfo_t)); } } /* * Interface between MDB kproc target and mdb_ks. The kproc target relies * on looking up and invoking these functions in mdb_ks so that dependencies * on the current kernel implementation are isolated in mdb_ks. */ /* * Given the address of a proc_t, return the p.p_as pointer; return NULL * if we were unable to read a proc structure from the given address. */ uintptr_t mdb_kproc_as(uintptr_t proc_addr) { proc_t p; if (mdb_vread(&p, sizeof (p), proc_addr) == sizeof (p)) return ((uintptr_t)p.p_as); return (0); } /* * Given the address of a proc_t, return the p.p_model value; return * PR_MODEL_UNKNOWN if we were unable to read a proc structure or if * the model value does not match one of the two known values. */ uint_t mdb_kproc_model(uintptr_t proc_addr) { proc_t p; if (mdb_vread(&p, sizeof (p), proc_addr) == sizeof (p)) { switch (p.p_model) { case DATAMODEL_ILP32: return (PR_MODEL_ILP32); case DATAMODEL_LP64: return (PR_MODEL_LP64); } } return (PR_MODEL_UNKNOWN); } /* * Callback function for walking process's segment list. For each segment, * we fill in an mdb_map_t describing its properties, and then invoke * the callback function provided by the kproc target. */ static int asmap_step(uintptr_t addr, const struct seg *seg, asmap_arg_t *asmp) { struct segvn_data svd; mdb_map_t map; if (seg->s_ops == asmp->asm_segvn_ops && mdb_vread(&svd, sizeof (svd), (uintptr_t)seg->s_data) == sizeof (svd)) { if (svd.vp != NULL) { if (mdb_vnode2path((uintptr_t)svd.vp, map.map_name, MDB_TGT_MAPSZ) != 0) { (void) mdb_snprintf(map.map_name, MDB_TGT_MAPSZ, "[ vnode %p ]", svd.vp); } } else (void) strcpy(map.map_name, "[ anon ]"); } else { (void) mdb_snprintf(map.map_name, MDB_TGT_MAPSZ, "[ seg %p ]", addr); } map.map_base = (uintptr_t)seg->s_base; map.map_size = seg->s_size; map.map_flags = 0; asmp->asm_callback((const struct mdb_map *)&map, asmp->asm_cbdata); return (WALK_NEXT); } /* * Given a process address space, walk its segment list using the seg walker, * convert the segment data to an mdb_map_t, and pass this information * back to the kproc target via the given callback function. */ int mdb_kproc_asiter(uintptr_t as, void (*func)(const struct mdb_map *, void *), void *p) { asmap_arg_t arg; GElf_Sym sym; arg.asm_segvn_ops = NULL; arg.asm_callback = func; arg.asm_cbdata = p; if (mdb_lookup_by_name("segvn_ops", &sym) == 0) arg.asm_segvn_ops = (struct seg_ops *)(uintptr_t)sym.st_value; return (mdb_pwalk("seg", (mdb_walk_cb_t)asmap_step, &arg, as)); } /* * Copy the auxv array from the given process's u-area into the provided * buffer. If the buffer is NULL, only return the size of the auxv array * so the caller knows how much space will be required. */ int mdb_kproc_auxv(uintptr_t proc, auxv_t *auxv) { if (auxv != NULL) { proc_t p; if (mdb_vread(&p, sizeof (p), proc) != sizeof (p)) return (-1); bcopy(p.p_user.u_auxv, auxv, sizeof (auxv_t) * __KERN_NAUXV_IMPL); } return (__KERN_NAUXV_IMPL); } /* * Given a process address, return the PID. */ pid_t mdb_kproc_pid(uintptr_t proc_addr) { struct pid pid; proc_t p; if (mdb_vread(&p, sizeof (p), proc_addr) == sizeof (p) && mdb_vread(&pid, sizeof (pid), (uintptr_t)p.p_pidp) == sizeof (pid)) return (pid.pid_id); return (-1); } /* * Interface between the MDB kvm target and mdb_ks. The kvm target relies * on looking up and invoking these functions in mdb_ks so that dependencies * on the current kernel implementation are isolated in mdb_ks. */ /* * Determine whether or not the thread that panicked the given kernel was a * kernel thread (panic_thread->t_procp == &p0). */ void mdb_dump_print_content(dumphdr_t *dh, pid_t content) { GElf_Sym sym; uintptr_t pt; uintptr_t procp; int expcont = 0; int actcont; (void) mdb_readvar(&expcont, "dump_conflags"); actcont = dh->dump_flags & DF_CONTENT; if (actcont == DF_ALL) { mdb_printf("dump content: all kernel and user pages\n"); return; } else if (actcont == DF_CURPROC) { mdb_printf("dump content: kernel pages and pages from " "PID %d", content); return; } mdb_printf("dump content: kernel pages only\n"); if (!(expcont & DF_CURPROC)) return; if (mdb_readvar(&pt, "panic_thread") != sizeof (pt) || pt == 0) goto kthreadpanic_err; if (mdb_vread(&procp, sizeof (procp), pt + OFFSETOF(kthread_t, t_procp)) == -1 || procp == 0) goto kthreadpanic_err; if (mdb_lookup_by_name("p0", &sym) != 0) goto kthreadpanic_err; if (procp == (uintptr_t)sym.st_value) { mdb_printf(" (curproc requested, but a kernel thread " "panicked)\n"); } else { mdb_printf(" (curproc requested, but the process that " "panicked could not be dumped)\n"); } return; kthreadpanic_err: mdb_printf(" (curproc requested, but the process that panicked could " "not be found)\n"); } /* * Determine the process that was saved in a `curproc' dump. This process will * be recorded as the first element in dump_pids[]. */ int mdb_dump_find_curproc(void) { uintptr_t pidp; pid_t pid = -1; if (mdb_readvar(&pidp, "dump_pids") == sizeof (pidp) && mdb_vread(&pid, sizeof (pid), pidp) == sizeof (pid) && pid > 0) return (pid); else return (-1); } /* * Following three funcs extracted from sunddi.c */ /* * Return core address of root node of devinfo tree */ static uintptr_t mdb_ddi_root_node(void) { uintptr_t top_devinfo_addr; /* return (top_devinfo); */ if (mdb_readvar(&top_devinfo_addr, "top_devinfo") == -1) { mdb_warn("failed to read top_devinfo"); return (0); } return (top_devinfo_addr); } /* * Return the name of the devinfo node pointed at by 'dip_addr' in the buffer * pointed at by 'name.' * * - dip_addr is a pointer to a dev_info struct in core. */ static char * mdb_ddi_deviname(uintptr_t dip_addr, char *name, size_t name_size) { uintptr_t addrname; ssize_t length; char *local_namep = name; size_t local_name_size = name_size; struct dev_info local_dip; if (dip_addr == mdb_ddi_root_node()) { if (name_size < 1) { mdb_warn("failed to get node name: buf too small\n"); return (NULL); } *name = '\0'; return (name); } if (name_size < 2) { mdb_warn("failed to get node name: buf too small\n"); return (NULL); } local_namep = name; *local_namep++ = '/'; *local_namep = '\0'; local_name_size--; if (mdb_vread(&local_dip, sizeof (struct dev_info), dip_addr) == -1) { mdb_warn("failed to read devinfo struct"); } length = mdb_readstr(local_namep, local_name_size, (uintptr_t)local_dip.devi_node_name); if (length == -1) { mdb_warn("failed to read node name"); return (NULL); } local_namep += length; local_name_size -= length; addrname = (uintptr_t)local_dip.devi_addr; if (addrname != 0) { if (local_name_size < 2) { mdb_warn("not enough room for node address string"); return (name); } *local_namep++ = '@'; *local_namep = '\0'; local_name_size--; length = mdb_readstr(local_namep, local_name_size, addrname); if (length == -1) { mdb_warn("failed to read name"); return (NULL); } } return (name); } /* * Generate the full path under the /devices dir to the device entry. * * dip is a pointer to a devinfo struct in core (not in local memory). */ char * mdb_ddi_pathname(uintptr_t dip_addr, char *path, size_t pathlen) { struct dev_info local_dip; uintptr_t parent_dip; char *bp; size_t buf_left; if (dip_addr == mdb_ddi_root_node()) { *path = '\0'; return (path); } if (mdb_vread(&local_dip, sizeof (struct dev_info), dip_addr) == -1) { mdb_warn("failed to read devinfo struct"); } parent_dip = (uintptr_t)local_dip.devi_parent; (void) mdb_ddi_pathname(parent_dip, path, pathlen); bp = path + strlen(path); buf_left = pathlen - strlen(path); (void) mdb_ddi_deviname(dip_addr, bp, buf_left); return (path); } /* * Read in the string value of a refstr, which is appended to the end of * the structure. */ ssize_t mdb_read_refstr(uintptr_t refstr_addr, char *str, size_t nbytes) { struct refstr *r = (struct refstr *)refstr_addr; return (mdb_readstr(str, nbytes, (uintptr_t)r->rs_string)); } /* * Chase an mblk list by b_next and return the length. */ int mdb_mblk_count(const mblk_t *mb) { int count; mblk_t mblk; if (mb == NULL) return (0); count = 1; while (mb->b_next != NULL) { count++; if (mdb_vread(&mblk, sizeof (mblk), (uintptr_t)mb->b_next) == -1) break; mb = &mblk; } return (count); } /* * Write the given MAC address as a printable string in the usual colon- * separated format. Assumes that buflen is at least 2. */ void mdb_mac_addr(const uint8_t *addr, size_t alen, char *buf, size_t buflen) { int slen; if (alen == 0 || buflen < 4) { (void) strcpy(buf, "?"); return; } for (;;) { /* * If there are more MAC address bytes available, but we won't * have any room to print them, then add "..." to the string * instead. See below for the 'magic number' explanation. */ if ((alen == 2 && buflen < 6) || (alen > 2 && buflen < 7)) { (void) strcpy(buf, "..."); break; } slen = mdb_snprintf(buf, buflen, "%02x", *addr++); buf += slen; if (--alen == 0) break; *buf++ = ':'; buflen -= slen + 1; /* * At this point, based on the first 'if' statement above, * either alen == 1 and buflen >= 3, or alen > 1 and * buflen >= 4. The first case leaves room for the final "xx" * number and trailing NUL byte. The second leaves room for at * least "...". Thus the apparently 'magic' numbers chosen for * that statement. */ } } /* * Produce a string that represents a DLPI primitive, or NULL if no such string * is possible. */ const char * mdb_dlpi_prim(int prim) { switch (prim) { case DL_INFO_REQ: return ("DL_INFO_REQ"); case DL_INFO_ACK: return ("DL_INFO_ACK"); case DL_ATTACH_REQ: return ("DL_ATTACH_REQ"); case DL_DETACH_REQ: return ("DL_DETACH_REQ"); case DL_BIND_REQ: return ("DL_BIND_REQ"); case DL_BIND_ACK: return ("DL_BIND_ACK"); case DL_UNBIND_REQ: return ("DL_UNBIND_REQ"); case DL_OK_ACK: return ("DL_OK_ACK"); case DL_ERROR_ACK: return ("DL_ERROR_ACK"); case DL_ENABMULTI_REQ: return ("DL_ENABMULTI_REQ"); case DL_DISABMULTI_REQ: return ("DL_DISABMULTI_REQ"); case DL_PROMISCON_REQ: return ("DL_PROMISCON_REQ"); case DL_PROMISCOFF_REQ: return ("DL_PROMISCOFF_REQ"); case DL_UNITDATA_REQ: return ("DL_UNITDATA_REQ"); case DL_UNITDATA_IND: return ("DL_UNITDATA_IND"); case DL_UDERROR_IND: return ("DL_UDERROR_IND"); case DL_PHYS_ADDR_REQ: return ("DL_PHYS_ADDR_REQ"); case DL_PHYS_ADDR_ACK: return ("DL_PHYS_ADDR_ACK"); case DL_SET_PHYS_ADDR_REQ: return ("DL_SET_PHYS_ADDR_REQ"); case DL_NOTIFY_REQ: return ("DL_NOTIFY_REQ"); case DL_NOTIFY_ACK: return ("DL_NOTIFY_ACK"); case DL_NOTIFY_IND: return ("DL_NOTIFY_IND"); case DL_NOTIFY_CONF: return ("DL_NOTIFY_CONF"); case DL_CAPABILITY_REQ: return ("DL_CAPABILITY_REQ"); case DL_CAPABILITY_ACK: return ("DL_CAPABILITY_ACK"); case DL_CONTROL_REQ: return ("DL_CONTROL_REQ"); case DL_CONTROL_ACK: return ("DL_CONTROL_ACK"); case DL_PASSIVE_REQ: return ("DL_PASSIVE_REQ"); default: return (NULL); } } /* * mdb_gethrtime() returns the hires system time. This will be the timestamp at * which we dropped into, if called from, kmdb(1); the core dump's hires time * if inspecting one; or the running system's hires time if we're inspecting * a live kernel. */ hrtime_t mdb_gethrtime(void) { uintptr_t ptr; GElf_Sym sym; lbolt_info_t lbi; hrtime_t ts; /* * We first check whether the lbolt info structure has been allocated * and initialized. If not, lbolt_hybrid will be pointing at * lbolt_bootstrap. */ if (mdb_lookup_by_name("lbolt_bootstrap", &sym) == -1) return (0); if (mdb_readvar(&ptr, "lbolt_hybrid") == -1) return (0); if (ptr == (uintptr_t)sym.st_value) return (0); #ifdef _KMDB if (mdb_readvar(&ptr, "lb_info") == -1) return (0); if (mdb_vread(&lbi, sizeof (lbolt_info_t), ptr) != sizeof (lbolt_info_t)) return (0); ts = lbi.lbi_debug_ts; #else if (mdb_prop_postmortem) { if (mdb_readvar(&ptr, "lb_info") == -1) return (0); if (mdb_vread(&lbi, sizeof (lbolt_info_t), ptr) != sizeof (lbolt_info_t)) return (0); ts = lbi.lbi_debug_ts; } else { ts = gethrtime(); } #endif return (ts); } /* * mdb_get_lbolt() returns the number of clock ticks since system boot. * Depending on the context in which it's called, the value will be derived * from different sources per mdb_gethrtime(). If inspecting a panicked * system, the routine returns the 'panic_lbolt64' variable from the core file. */ int64_t mdb_get_lbolt(void) { lbolt_info_t lbi; uintptr_t ptr; int64_t pl; hrtime_t ts; int nsec; if (mdb_readvar(&pl, "panic_lbolt64") != -1 && pl > 0) return (pl); /* * mdb_gethrtime() will return zero if the lbolt info structure hasn't * been allocated and initialized yet, or if it fails to read it. */ if ((ts = mdb_gethrtime()) <= 0) return (0); /* * Load the time spent in kmdb, if any. */ if (mdb_readvar(&ptr, "lb_info") == -1) return (0); if (mdb_vread(&lbi, sizeof (lbolt_info_t), ptr) != sizeof (lbolt_info_t)) return (0); if (mdb_readvar(&nsec, "nsec_per_tick") == -1 || nsec == 0) { mdb_warn("failed to read 'nsec_per_tick'"); return (-1); } return ((ts/nsec) - lbi.lbi_debug_time); } void mdb_print_buildversion(void) { GElf_Sym sym; if (mdb_lookup_by_name("buildversion", &sym) != 0) return; char *str = mdb_zalloc(4096, UM_SLEEP | UM_GC); if (mdb_readstr(str, 4096, sym.st_value) < 1) return; mdb_printf("build version: %s\n", str); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2025 Oxide Computer Company */ /* * MDB Regression Test Module * * This module contains dcmds and walkers that exercise various aspects of * MDB and the MDB Module API. It can be manually loaded and executed to * verify that MDB is still working properly. */ #include #define _MDB #include #include #undef _MDB static int cd_init(mdb_walk_state_t *wsp) { wsp->walk_addr = 0xf; return (WALK_NEXT); } static int cd_step(mdb_walk_state_t *wsp) { int status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata); if (wsp->walk_addr-- == 0) return (WALK_DONE); return (status); } /*ARGSUSED*/ static int cmd_praddr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags != (DCMD_ADDRSPEC|DCMD_LOOP|DCMD_PIPE)) && (flags != (DCMD_ADDRSPEC|DCMD_LOOP|DCMD_PIPE|DCMD_LOOPFIRST))) { mdb_warn("ERROR: praddr invoked with flags = 0x%x\n", flags); return (DCMD_ERR); } if (argc != 0) { mdb_warn("ERROR: praddr invoked with argc = %lu\n", argc); return (DCMD_ERR); } mdb_printf("%lr\n", addr); return (DCMD_OK); } static int compare(const void *lp, const void *rp) { uintptr_t lhs = *((const uintptr_t *)lp); uintptr_t rhs = *((const uintptr_t *)rp); return (lhs - rhs); } /*ARGSUSED*/ static int cmd_qsort(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_pipe_t p; size_t i; if (flags != (DCMD_ADDRSPEC | DCMD_LOOP | DCMD_LOOPFIRST | DCMD_PIPE | DCMD_PIPE_OUT)) { mdb_warn("ERROR: qsort invoked with flags = 0x%x\n", flags); return (DCMD_ERR); } if (argc != 0) { mdb_warn("ERROR: qsort invoked with argc = %lu\n", argc); return (DCMD_ERR); } mdb_get_pipe(&p); if (p.pipe_data == NULL || p.pipe_len != 16) { mdb_warn("ERROR: qsort got bad results from mdb_get_pipe\n"); return (DCMD_ERR); } if (p.pipe_data[0] != addr) { mdb_warn("ERROR: qsort pipe_data[0] != addr\n"); return (DCMD_ERR); } qsort(p.pipe_data, p.pipe_len, sizeof (uintptr_t), compare); mdb_set_pipe(&p); for (i = 0; i < 16; i++) { if (p.pipe_data[i] != i) { mdb_warn("ERROR: qsort got bad data in slot %lu\n", i); return (DCMD_ERR); } } return (DCMD_OK); } /*ARGSUSED*/ static int cmd_runtest(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_walker_t w = { "count", "count", cd_init, cd_step, NULL }; int state, i; mdb_printf("- adding countdown walker\n"); if (mdb_add_walker(&w) != 0) { mdb_warn("ERROR: failed to add walker"); return (DCMD_ERR); } mdb_printf("- executing countdown pipeline\n"); if (mdb_eval("::walk mdb_test`count |::mdb_test`qsort |::praddr")) { mdb_warn("ERROR: failed to eval command"); return (DCMD_ERR); } mdb_printf("- removing countdown walker\n"); if (mdb_remove_walker("count") != 0) { mdb_warn("ERROR: failed to remove walker"); return (DCMD_ERR); } state = mdb_get_state(); mdb_printf("- kernel=%d state=%d\n", mdb_prop_kernel, state); if (mdb_prop_kernel && (state == MDB_STATE_DEAD || state == MDB_STATE_RUNNING)) { mdb_printf("- exercising pipelines\n"); for (i = 0; i < 100; i++) { if (mdb_eval("::walk proc p | ::map *. | ::grep .==0 " "| ::map

= 0) { mdb_printf("mdb_vread of %lu bytes returned %ld\n", nbytes, rbytes); } else mdb_warn("mdb_vread returned %ld", rbytes); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_pread(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { size_t nbytes; ssize_t rbytes; void *buf; if (!(flags & DCMD_ADDRSPEC) || argc != 1) return (DCMD_USAGE); nbytes = (size_t)mdb_argtoull(argv); buf = mdb_alloc(nbytes, UM_SLEEP | UM_GC); rbytes = mdb_pread(buf, nbytes, mdb_get_dot()); if (rbytes >= 0) { mdb_printf("mdb_pread of %lu bytes returned %ld\n", nbytes, rbytes); } else mdb_warn("mdb_pread returned %ld", rbytes); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_readsym(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { size_t nbytes; ssize_t rbytes; void *buf; if ((flags & DCMD_ADDRSPEC) || argc != 2 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); nbytes = (size_t)mdb_argtoull(&argv[1]); buf = mdb_alloc(nbytes, UM_SLEEP | UM_GC); rbytes = mdb_readsym(buf, nbytes, argv->a_un.a_str); if (rbytes >= 0) { mdb_printf("mdb_readsym of %lu bytes returned %ld\n", nbytes, rbytes); } else mdb_warn("mdb_readsym returned %ld", rbytes); return (DCMD_OK); } static int cmd_call_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *dcmd; if (argc < 1 || argv->a_type != MDB_TYPE_STRING) return (DCMD_USAGE); dcmd = argv->a_un.a_str; argv++; argc--; if (mdb_call_dcmd(dcmd, addr, flags, argc, argv) == -1) { mdb_warn("failed to execute %s", dcmd); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ static int cmd_getsetdot(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 0) return (DCMD_USAGE); mdb_set_dot(0x12345678feedbeefULL); if (mdb_get_dot() != 0x12345678feedbeefULL) { mdb_warn("mdb_get_dot() returned wrong value!\n"); return (DCMD_ERR); } return (DCMD_OK); } /* * kmdb doesn't export some of the symbols used by these tests - namely mdb and * mdb_iob_.*. We therefore can't use these tests with kmdb. */ #ifndef _KMDB static void do_nputs_tests(const char *banner, uint_t flags, size_t rows, size_t cols, size_t ocols) { uint_t oflags; int i; oflags = mdb_iob_getflags(mdb.m_out) & (MDB_IOB_AUTOWRAP | MDB_IOB_INDENT); mdb_printf("%s:\n", banner); for (i = 0; i < 8; i++) mdb_printf("0123456789"); mdb_printf("\n"); mdb_iob_clrflags(mdb.m_out, MDB_IOB_AUTOWRAP | MDB_IOB_INDENT); mdb_iob_setflags(mdb.m_out, flags); mdb_iob_resize(mdb.m_out, rows, cols); for (i = 0; i < 50; i++) mdb_printf(" xx"); mdb_printf("\n"); mdb_iob_clrflags(mdb.m_out, flags); mdb_iob_setflags(mdb.m_out, oflags); mdb_iob_resize(mdb.m_out, rows, ocols); } /*ARGSUSED*/ static int cmd_nputs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { size_t rows = mdb.m_out->iob_rows; size_t cols = mdb.m_out->iob_cols; if (argc != 0) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) addr = cols; do_nputs_tests("tests with (~WRAP, ~INDENT)", 0, rows, addr, cols); do_nputs_tests("tests with (WRAP, ~INDENT)", MDB_IOB_AUTOWRAP, rows, addr, cols); do_nputs_tests("tests with (~WRAP, INDENT)", MDB_IOB_INDENT, rows, addr, cols); do_nputs_tests("tests with (WRAP, INDENT)", MDB_IOB_AUTOWRAP | MDB_IOB_INDENT, rows, addr, cols); return (DCMD_OK); } #endif /*ARGSUSED*/ static int cmd_printf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc != 2 || argv[0].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); if (argv[1].a_type == MDB_TYPE_STRING) mdb_printf(argv[0].a_un.a_str, argv[1].a_un.a_str); else mdb_printf(argv[0].a_un.a_str, argv[1].a_un.a_val); return (DCMD_OK); } /*ARGSUSED*/ static int cmd_abort(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_printf("hello"); /* stuff something in stdout's buffer */ return (*((volatile int *)NULL)); } static const mdb_dcmd_t dcmds[] = { { "runtest", NULL, "run MDB regression tests", cmd_runtest }, { "qsort", NULL, "qsort addresses", cmd_qsort }, { "praddr", NULL, "print addresses", cmd_praddr }, { "vread", ":nbytes", "call mdb_vread", cmd_vread }, { "pread", ":nbytes", "call mdb_pread", cmd_pread }, { "readsym", "symbol nbytes", "call mdb_readsym", cmd_readsym }, { "call_dcmd", "dcmd [ args ... ]", "call dcmd", cmd_call_dcmd }, { "getsetdot", NULL, "test get and set dot", cmd_getsetdot }, #ifndef _KMDB { "nputs", "?", "test iob nputs engine", cmd_nputs }, #endif { "printf", "fmt arg", "test printf engine", cmd_printf }, { "abort", NULL, "test unexpected dcmd abort", cmd_abort }, { NULL } }; static const mdb_walker_t walkers[] = { { "countdown", "count down from 16 to 0", cd_init, cd_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2015, Joyent, Inc. All rights reserved. */ #include #include #include typedef struct kmemlog_walk { uintptr_t kmlw_addr; mm_logentry_t *kmlw_entries; int kmlw_nentries; int kmlw_entry; int kmlw_oldest; } kmemlog_walk_t; static int kmemlog_walk_init(mdb_walk_state_t *wsp) { kmemlog_walk_t *kw; GElf_Sym sym; if (mdb_lookup_by_name("mm_kmemlog", &sym) != 0) { mdb_warn("couldn't find symbol 'mm_kmemlog'"); return (WALK_ERR); } kw = mdb_zalloc(sizeof (kmemlog_walk_t), UM_SLEEP); kw->kmlw_entries = mdb_zalloc(sym.st_size, UM_SLEEP); kw->kmlw_addr = sym.st_value; if (mdb_vread(kw->kmlw_entries, sym.st_size, sym.st_value) == -1) { mdb_warn("couldn't read log at %p", sym.st_value); mdb_free(kw->kmlw_entries, sym.st_size); mdb_free(kw, sizeof (kmemlog_walk_t)); return (WALK_ERR); } kw->kmlw_nentries = sym.st_size / sizeof (mm_logentry_t); mdb_readvar(&kw->kmlw_entry, "mm_kmemlogent"); kw->kmlw_oldest = kw->kmlw_entry; wsp->walk_data = kw; return (WALK_NEXT); } static int kmemlog_walk_step(mdb_walk_state_t *wsp) { kmemlog_walk_t *kw = wsp->walk_data; mm_logentry_t *ent; int rval = WALK_NEXT; ent = &kw->kmlw_entries[kw->kmlw_entry]; if (++kw->kmlw_entry == kw->kmlw_nentries) kw->kmlw_entry = 0; if (ent->mle_hrtime != 0) { rval = wsp->walk_callback(kw->kmlw_addr + ((uintptr_t)ent - (uintptr_t)kw->kmlw_entries), ent, wsp->walk_cbdata); } if (rval == WALK_NEXT && kw->kmlw_entry == kw->kmlw_oldest) return (WALK_DONE); return (rval); } static void kmemlog_walk_fini(mdb_walk_state_t *wsp) { kmemlog_walk_t *kw = wsp->walk_data; mdb_free(kw->kmlw_entries, kw->kmlw_nentries * sizeof (mm_logentry_t)); mdb_free(kw, sizeof (kmemlog_walk_t)); } static int kmemlog(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mm_logentry_t ent; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("kmemlog", "kmemlog", argc, argv) == -1) { mdb_warn("can't walk 'kmemlog'"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%?s %-20s %?s %5s %s\n", "ADDR", "TIME", "VADDR", "PID", "PSARGS"); } if (mdb_vread(&ent, sizeof (ent), addr) == -1) { mdb_warn("can't read mm_logentry_t at %p", addr); return (DCMD_ERR); } mdb_printf("%?p %-20Y %?p %5d %s\n", addr, ent.mle_hrestime.tv_sec, ent.mle_vaddr, ent.mle_pid, ent.mle_psargs); return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "kmemlog", NULL, "print log of writes via /dev/kmem", kmemlog }, { NULL } }; static const mdb_walker_t walkers[] = { { "kmemlog", "walk entries in /dev/kmem write log", kmemlog_walk_init, kmemlog_walk_step, kmemlog_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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) 2017 Joyent, Inc. * Copyright (c) 2014, Tegile Systems Inc. All rights reserved. * Copyright 2023 Racktop Systems, Inc. */ #include #include #include #include #include #include #include #pragma pack(1) #include #include #include #include #include #include #include #include #pragma pack() #include struct { int value; char *text; } devinfo_array[] = { { MPI2_SAS_DEVICE_INFO_SEP, "SEP" }, { MPI2_SAS_DEVICE_INFO_ATAPI_DEVICE, "ATAPI device" }, { MPI2_SAS_DEVICE_INFO_LSI_DEVICE, "LSI device" }, { MPI2_SAS_DEVICE_INFO_DIRECT_ATTACH, "direct attach" }, { MPI2_SAS_DEVICE_INFO_SSP_TARGET, "SSP tgt" }, { MPI2_SAS_DEVICE_INFO_STP_TARGET, "STP tgt" }, { MPI2_SAS_DEVICE_INFO_SMP_TARGET, "SMP tgt" }, { MPI2_SAS_DEVICE_INFO_SATA_DEVICE, "SATA dev" }, { MPI2_SAS_DEVICE_INFO_SSP_INITIATOR, "SSP init" }, { MPI2_SAS_DEVICE_INFO_STP_INITIATOR, "STP init" }, { MPI2_SAS_DEVICE_INFO_SMP_INITIATOR, "SMP init" }, { MPI2_SAS_DEVICE_INFO_SATA_HOST, "SATA host" } }; int construct_path(uintptr_t addr, char *result) { struct dev_info d; char devi_node[PATH_MAX]; char devi_addr[PATH_MAX]; if (mdb_vread(&d, sizeof (d), addr) == -1) { mdb_warn("couldn't read dev_info"); return (DCMD_ERR); } if (d.devi_parent) { construct_path((uintptr_t)d.devi_parent, result); mdb_readstr(devi_node, sizeof (devi_node), (uintptr_t)d.devi_node_name); mdb_readstr(devi_addr, sizeof (devi_addr), (uintptr_t)d.devi_addr); mdb_snprintf(result+strlen(result), PATH_MAX-strlen(result), "/%s%s%s", devi_node, (*devi_addr ? "@" : ""), devi_addr); } return (DCMD_OK); } /* ARGSUSED */ int mdi_info_cb(uintptr_t addr, const void *data, void *cbdata) { struct mdi_pathinfo pi; struct mdi_client c; char dev_path[PATH_MAX]; char string[PATH_MAX]; int mdi_target = 0, mdi_lun = 0; int target = *(int *)cbdata; if (mdb_vread(&pi, sizeof (pi), addr) == -1) { mdb_warn("couldn't read mdi_pathinfo"); return (DCMD_ERR); } mdb_readstr(string, sizeof (string), (uintptr_t)pi.pi_addr); mdi_target = (int)mdb_strtoull(string); mdi_lun = (int)mdb_strtoull(strchr(string, ',') + 1); if (target != mdi_target) return (0); if (mdb_vread(&c, sizeof (c), (uintptr_t)pi.pi_client) == -1) { mdb_warn("couldn't read mdi_client"); return (-1); } *dev_path = '\0'; if (construct_path((uintptr_t)c.ct_dip, dev_path) != DCMD_OK) strcpy(dev_path, "unknown"); mdb_printf("LUN %d: %s\n", mdi_lun, dev_path); mdb_printf(" dip: %p %s path", c.ct_dip, (pi.pi_preferred ? "preferred" : "")); switch (pi.pi_state & MDI_PATHINFO_STATE_MASK) { case MDI_PATHINFO_STATE_INIT: mdb_printf(" initializing"); break; case MDI_PATHINFO_STATE_ONLINE: mdb_printf(" online"); break; case MDI_PATHINFO_STATE_STANDBY: mdb_printf(" standby"); break; case MDI_PATHINFO_STATE_FAULT: mdb_printf(" fault"); break; case MDI_PATHINFO_STATE_OFFLINE: mdb_printf(" offline"); break; default: mdb_printf(" invalid state"); break; } mdb_printf("\n"); return (0); } void mdi_info(struct mptsas *mp, int target) { struct dev_info d; struct mdi_phci p; if (mdb_vread(&d, sizeof (d), (uintptr_t)mp->m_dip) == -1) { mdb_warn("couldn't read m_dip"); return; } if (MDI_PHCI(&d)) { if (mdb_vread(&p, sizeof (p), (uintptr_t)d.devi_mdi_xhci) == -1) { mdb_warn("couldn't read m_dip.devi_mdi_xhci"); return; } if (p.ph_path_head) mdb_pwalk("mdipi_phci_list", (mdb_walk_cb_t)mdi_info_cb, &target, (uintptr_t)p.ph_path_head); return; } } void print_cdb(mptsas_cmd_t *m) { struct scsi_pkt pkt; uchar_t cdb[512]; /* an arbitrarily large number */ int j; if (mdb_vread(&pkt, sizeof (pkt), (uintptr_t)m->cmd_pkt) == -1) { mdb_warn("couldn't read cmd_pkt"); return; } /* * We use cmd_cdblen here because 5.10 doesn't * have the cdb length in the pkt */ if (mdb_vread(&cdb, m->cmd_cdblen, (uintptr_t)pkt.pkt_cdbp) == -1) { mdb_warn("couldn't read pkt_cdbp"); return; } mdb_printf("%3d,%-3d [ ", pkt.pkt_address.a_target, pkt.pkt_address.a_lun); for (j = 0; j < m->cmd_cdblen; j++) mdb_printf("%02x ", cdb[j]); mdb_printf("]\n"); } void display_ports(struct mptsas *mp) { int i; mdb_printf("\n"); mdb_printf("phy number and port mapping table\n"); for (i = 0; i < MPTSAS_MAX_PHYS; i++) { if (mp->m_phy_info[i].attached_devhdl) { mdb_printf("phy %x --> port %x, phymask %x," "attached_devhdl %x\n", i, mp->m_phy_info[i].port_num, mp->m_phy_info[i].phy_mask, mp->m_phy_info[i].attached_devhdl); } } mdb_printf("\n"); } static uintptr_t klist_head(list_t *lp, uintptr_t klp) { if ((uintptr_t)lp->list_head.list_next == klp + offsetof(struct list, list_head)) return (0); return ((uintptr_t)(((char *)lp->list_head.list_next) - lp->list_offset)); } static uintptr_t klist_next(list_t *lp, uintptr_t klp, void *op) { /* LINTED E_BAD_PTR_CAST_ALIG */ struct list_node *np = (struct list_node *)(((char *)op) + lp->list_offset); if ((uintptr_t)np->list_next == klp + offsetof(struct list, list_head)) return (0); return (((uintptr_t)(np->list_next)) - lp->list_offset); } static void * krefhash_first(uintptr_t khp, uintptr_t *addr) { refhash_t mh; uintptr_t klp; uintptr_t kop; void *rp; mdb_vread(&mh, sizeof (mh), khp); klp = klist_head(&mh.rh_objs, khp + offsetof(refhash_t, rh_objs)); if (klp == 0) return (NULL); kop = klp - mh.rh_link_off; if (addr) *addr = kop; rp = mdb_alloc(mh.rh_obj_size, UM_SLEEP); mdb_vread(rp, mh.rh_obj_size, kop); return (rp); } static void * krefhash_next(uintptr_t khp, void *op, uintptr_t *addr) { refhash_t mh; void *prev = op; refhash_link_t *lp; uintptr_t klp; uintptr_t kop; refhash_link_t ml; void *rp; mdb_vread(&mh, sizeof (mh), khp); /* LINTED E_BAD_PTR_CAST_ALIG */ lp = (refhash_link_t *)(((char *)(op)) + mh.rh_link_off); ml = *lp; while ((klp = klist_next(&mh.rh_objs, khp + offsetof(refhash_t, rh_objs), &ml)) != 0) { mdb_vread(&ml, sizeof (ml), klp); if (!(ml.rhl_flags & RHL_F_DEAD)) break; } if (klp == 0) { mdb_free(prev, mh.rh_obj_size); return (NULL); } kop = klp - mh.rh_link_off; if (addr) *addr = kop; rp = mdb_alloc(mh.rh_obj_size, UM_SLEEP); mdb_vread(rp, mh.rh_obj_size, kop); mdb_free(prev, mh.rh_obj_size); return (rp); } void display_targets(struct mptsas *mp, uint_t verbose) { mptsas_target_t *ptgt; mptsas_smp_t *psmp; int loop, comma; uintptr_t p_addr; mdb_printf("\n"); mdb_printf(" mptsas_target_t slot devhdl wwn ncmds throttle " "dr_flag dups\n"); mdb_printf("---------------------------------------" "-------------------------------\n"); for (ptgt = krefhash_first((uintptr_t)mp->m_targets, &p_addr); ptgt != NULL; ptgt = krefhash_next((uintptr_t)mp->m_targets, ptgt, &p_addr)) { if (ptgt->m_addr.mta_wwn || ptgt->m_deviceinfo) { mdb_printf("%16p ", p_addr); mdb_printf("%4d ", ptgt->m_slot_num); mdb_printf("%4d ", ptgt->m_devhdl); if (ptgt->m_addr.mta_wwn) mdb_printf("%"PRIx64" ", ptgt->m_addr.mta_wwn); mdb_printf("%3d", ptgt->m_t_ncmds); switch (ptgt->m_t_throttle) { case QFULL_THROTTLE: mdb_printf(" QFULL "); break; case DRAIN_THROTTLE: mdb_printf(" DRAIN "); break; case HOLD_THROTTLE: mdb_printf(" HOLD "); break; case MAX_THROTTLE: mdb_printf(" MAX "); break; default: mdb_printf("%8d ", ptgt->m_t_throttle); } switch (ptgt->m_dr_flag) { case MPTSAS_DR_INACTIVE: mdb_printf(" INACTIVE "); break; case MPTSAS_DR_INTRANSITION: mdb_printf("TRANSITION "); break; default: mdb_printf(" UNKNOWN "); break; } mdb_printf("%d\n", ptgt->m_dups); if (verbose) { mdb_inc_indent(5); if ((ptgt->m_deviceinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_FANOUT_EXPANDER) mdb_printf("Fanout expander: "); if ((ptgt->m_deviceinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_EDGE_EXPANDER) mdb_printf("Edge expander: "); if ((ptgt->m_deviceinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_END_DEVICE) mdb_printf("End device: "); if ((ptgt->m_deviceinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_NO_DEVICE) mdb_printf("No device "); for (loop = 0, comma = 0; loop < (sizeof (devinfo_array) / sizeof (devinfo_array[0])); loop++) { if (ptgt->m_deviceinfo & devinfo_array[loop].value) { mdb_printf("%s%s", (comma ? ", " : ""), devinfo_array[loop].text); comma++; } } mdb_printf("\n"); mdi_info(mp, ptgt->m_slot_num); mdb_dec_indent(5); } } } mdb_printf("\n"); mdb_printf(" mptsas_smp_t devhdl wwn phymask\n"); mdb_printf("---------------------------------------" "------------------\n"); for (psmp = (mptsas_smp_t *)krefhash_first( (uintptr_t)mp->m_smp_targets, &p_addr); psmp != NULL; psmp = krefhash_next((uintptr_t)mp->m_smp_targets, psmp, &p_addr)) { mdb_printf("%16p ", p_addr); mdb_printf("%4d %"PRIx64" %04x\n", psmp->m_devhdl, psmp->m_addr.mta_wwn, psmp->m_addr.mta_phymask); if (!verbose) continue; mdb_inc_indent(5); if ((psmp->m_deviceinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_FANOUT_EXPANDER) mdb_printf("Fanout expander: "); if ((psmp->m_deviceinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_EDGE_EXPANDER) mdb_printf("Edge expander: "); if ((psmp->m_deviceinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_END_DEVICE) mdb_printf("End device: "); if ((psmp->m_deviceinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) == MPI2_SAS_DEVICE_INFO_NO_DEVICE) mdb_printf("No device "); for (loop = 0, comma = 0; loop < (sizeof (devinfo_array) / sizeof (devinfo_array[0])); loop++) { if (psmp->m_deviceinfo & devinfo_array[loop].value) { mdb_printf("%s%s", (comma ? ", " : ""), devinfo_array[loop].text); comma++; } } mdb_printf("\n"); mdb_dec_indent(5); } } int display_slotinfo(struct mptsas *mp, struct mptsas_slots *s) { int i, nslots; struct mptsas_cmd c, *q, *slots; mptsas_target_t *ptgt; int header_output = 0; int rv = DCMD_OK; int slots_in_use = 0; int tcmds = 0; int mismatch = 0; int wq, dq; int ncmds = 0; ulong_t saved_indent; uintptr_t panicstr; int state; if ((state = mdb_get_state()) == MDB_STATE_RUNNING) { mdb_warn("mptsas: slot info can only be displayed on a system " "dump or under kmdb\n"); return (DCMD_ERR); } if (mdb_readvar(&panicstr, "panicstr") == -1) { mdb_warn("can't read variable 'panicstr'"); return (DCMD_ERR); } if (state != MDB_STATE_STOPPED && panicstr == 0) { mdb_warn("mptsas: slot info not available for live dump\n"); return (DCMD_ERR); } nslots = s->m_n_normal; slots = mdb_alloc(sizeof (mptsas_cmd_t) * nslots, UM_SLEEP); for (i = 0; i < nslots; i++) if (s->m_slot[i]) { slots_in_use++; if (mdb_vread(&slots[i], sizeof (mptsas_cmd_t), (uintptr_t)s->m_slot[i]) == -1) { mdb_warn("couldn't read slot"); s->m_slot[i] = NULL; } if ((slots[i].cmd_flags & CFLAG_CMDIOC) == 0) tcmds++; if (i != slots[i].cmd_slot) mismatch++; } for (q = mp->m_waitq, wq = 0; q; q = c.cmd_linkp, wq++) if (mdb_vread(&c, sizeof (mptsas_cmd_t), (uintptr_t)q) == -1) { mdb_warn("couldn't follow m_waitq"); rv = DCMD_ERR; goto exit; } for (q = mp->m_doneq, dq = 0; q; q = c.cmd_linkp, dq++) if (mdb_vread(&c, sizeof (mptsas_cmd_t), (uintptr_t)q) == -1) { mdb_warn("couldn't follow m_doneq"); rv = DCMD_ERR; goto exit; } for (ptgt = krefhash_first((uintptr_t)mp->m_targets, NULL); ptgt != NULL; ptgt = krefhash_next((uintptr_t)mp->m_targets, ptgt, NULL)) { if (ptgt->m_addr.mta_wwn || ptgt->m_deviceinfo) { ncmds += ptgt->m_t_ncmds; } } mdb_printf("\n"); mdb_printf(" mpt. slot mptsas_slots slot"); mdb_printf("\n"); mdb_printf("m_ncmds total" " targ throttle m_t_ncmds targ_tot wq dq"); mdb_printf("\n"); mdb_printf("----------------------------------------------------"); mdb_printf("\n"); mdb_printf("%7d ", mp->m_ncmds); mdb_printf("%s", (mp->m_ncmds == slots_in_use ? " " : "!=")); mdb_printf("%3d total %3d ", slots_in_use, ncmds); mdb_printf("%s", (tcmds == ncmds ? " " : " !=")); mdb_printf("%3d %2d %2d\n", tcmds, wq, dq); saved_indent = mdb_dec_indent(0); mdb_dec_indent(saved_indent); for (i = 0; i < s->m_n_normal; i++) if (s->m_slot[i]) { if (!header_output) { mdb_printf("\n"); mdb_printf("mptsas_cmd slot cmd_slot " "cmd_flags cmd_pkt_flags scsi_pkt " " targ,lun [ pkt_cdbp ...\n"); mdb_printf("-------------------------------" "--------------------------------------" "--------------------------------------" "------\n"); header_output = 1; } mdb_printf("%16p %4d %s %4d %8x %8x %16p ", s->m_slot[i], i, (i == slots[i].cmd_slot?" ":"BAD"), slots[i].cmd_slot, slots[i].cmd_flags, slots[i].cmd_pkt_flags, slots[i].cmd_pkt); (void) print_cdb(&slots[i]); } /* print the wait queue */ for (q = mp->m_waitq; q; q = c.cmd_linkp) { if (q == mp->m_waitq) mdb_printf("\n"); if (mdb_vread(&c, sizeof (mptsas_cmd_t), (uintptr_t)q) == -1) { mdb_warn("couldn't follow m_waitq"); rv = DCMD_ERR; goto exit; } mdb_printf("%16p wait n/a %4d %8x %8x %16p ", q, c.cmd_slot, c.cmd_flags, c.cmd_pkt_flags, c.cmd_pkt); print_cdb(&c); } /* print the done queue */ for (q = mp->m_doneq; q; q = c.cmd_linkp) { if (q == mp->m_doneq) mdb_printf("\n"); if (mdb_vread(&c, sizeof (mptsas_cmd_t), (uintptr_t)q) == -1) { mdb_warn("couldn't follow m_doneq"); rv = DCMD_ERR; goto exit; } mdb_printf("%16p done n/a %4d %8x %8x %16p ", q, c.cmd_slot, c.cmd_flags, c.cmd_pkt_flags, c.cmd_pkt); print_cdb(&c); } mdb_inc_indent(saved_indent); if (mp->m_ncmds != slots_in_use) mdb_printf("WARNING: mpt.m_ncmds does not match the number of " "slots in use\n"); if (tcmds != ncmds) mdb_printf("WARNING: the total of m_target[].m_t_ncmds does " "not match the slots in use\n"); if (mismatch) mdb_printf("WARNING: corruption in slot table, " "m_slot[].cmd_slot incorrect\n"); /* now check for corruptions */ for (q = mp->m_waitq; q; q = c.cmd_linkp) { for (i = 0; i < nslots; i++) if (s->m_slot[i] == q) mdb_printf("WARNING: m_waitq entry" "(mptsas_cmd_t) %p is in m_slot[%i]\n", q, i); if (mdb_vread(&c, sizeof (mptsas_cmd_t), (uintptr_t)q) == -1) { mdb_warn("couldn't follow m_waitq"); rv = DCMD_ERR; goto exit; } } for (q = mp->m_doneq; q; q = c.cmd_linkp) { for (i = 0; i < nslots; i++) if (s->m_slot[i] == q) mdb_printf("WARNING: m_doneq entry " "(mptsas_cmd_t) %p is in m_slot[%i]\n", q, i); if (mdb_vread(&c, sizeof (mptsas_cmd_t), (uintptr_t)q) == -1) { mdb_warn("couldn't follow m_doneq"); rv = DCMD_ERR; goto exit; } if ((c.cmd_flags & CFLAG_FINISHED) == 0) mdb_printf("WARNING: m_doneq entry (mptsas_cmd_t) %p " "should have CFLAG_FINISHED set\n", q); if (c.cmd_flags & CFLAG_IN_TRANSPORT) mdb_printf("WARNING: m_doneq entry (mptsas_cmd_t) %p " "should not have CFLAG_IN_TRANSPORT set\n", q); if (c.cmd_flags & CFLAG_CMDARQ) mdb_printf("WARNING: m_doneq entry (mptsas_cmd_t) %p " "should not have CFLAG_CMDARQ set\n", q); if (c.cmd_flags & CFLAG_COMPLETED) mdb_printf("WARNING: m_doneq entry (mptsas_cmd_t) %p " "should not have CFLAG_COMPLETED set\n", q); } exit: mdb_free(slots, sizeof (mptsas_cmd_t) * nslots); return (rv); } void display_deviceinfo(struct mptsas *mp) { char device_path[PATH_MAX]; *device_path = 0; if (construct_path((uintptr_t)mp->m_dip, device_path) != DCMD_OK) { strcpy(device_path, "couldn't determine device path"); } mdb_printf("\n"); mdb_printf("base_wwid phys " " prodid devid revid ssid\n"); mdb_printf("-----------------------------" "----------------------------------\n"); mdb_printf("%"PRIx64" %2d " "0x%04x 0x%04x ", mp->un.m_base_wwid, mp->m_num_phys, mp->m_productid, mp->m_devid); switch (mp->m_devid) { case MPI2_MFGPAGE_DEVID_SAS2004: mdb_printf("(SAS2004) "); break; case MPI2_MFGPAGE_DEVID_SAS2008: mdb_printf("(SAS2008) "); break; case MPI2_MFGPAGE_DEVID_SAS2108_1: case MPI2_MFGPAGE_DEVID_SAS2108_2: case MPI2_MFGPAGE_DEVID_SAS2108_3: mdb_printf("(SAS2108) "); break; case MPI2_MFGPAGE_DEVID_SAS2116_1: case MPI2_MFGPAGE_DEVID_SAS2116_2: mdb_printf("(SAS2116) "); break; case MPI2_MFGPAGE_DEVID_SSS6200: mdb_printf("(SSS6200) "); break; case MPI2_MFGPAGE_DEVID_SAS2208_1: case MPI2_MFGPAGE_DEVID_SAS2208_2: case MPI2_MFGPAGE_DEVID_SAS2208_3: case MPI2_MFGPAGE_DEVID_SAS2208_4: case MPI2_MFGPAGE_DEVID_SAS2208_5: case MPI2_MFGPAGE_DEVID_SAS2208_6: mdb_printf("(SAS2208) "); break; case MPI2_MFGPAGE_DEVID_SAS2308_1: case MPI2_MFGPAGE_DEVID_SAS2308_2: case MPI2_MFGPAGE_DEVID_SAS2308_3: mdb_printf("(SAS2308) "); break; case MPI25_MFGPAGE_DEVID_SAS3004: mdb_printf("(SAS3004) "); break; case MPI25_MFGPAGE_DEVID_SAS3008: mdb_printf("(SAS3008) "); break; case MPI25_MFGPAGE_DEVID_SAS3108_1: case MPI25_MFGPAGE_DEVID_SAS3108_2: case MPI25_MFGPAGE_DEVID_SAS3108_5: case MPI25_MFGPAGE_DEVID_SAS3108_6: mdb_printf("(SAS3108) "); break; case MPI26_MFGPAGE_DEVID_SAS3216: case MPI26_MFGPAGE_DEVID_SAS3316_1: case MPI26_MFGPAGE_DEVID_SAS3316_2: case MPI26_MFGPAGE_DEVID_SAS3316_3: case MPI26_MFGPAGE_DEVID_SAS3316_4: mdb_printf("(SAS3216) "); break; case MPI26_MFGPAGE_DEVID_SAS3224: case MPI26_MFGPAGE_DEVID_SAS3324_1: case MPI26_MFGPAGE_DEVID_SAS3324_2: case MPI26_MFGPAGE_DEVID_SAS3324_3: case MPI26_MFGPAGE_DEVID_SAS3324_4: mdb_printf("(SAS3224) "); break; case MPI26_MFGPAGE_DEVID_SAS3408: mdb_printf("(SAS3408) "); break; case MPI26_MFGPAGE_DEVID_SAS3416: mdb_printf("(SAS3416) "); break; case MPI26_MFGPAGE_DEVID_SAS3508: case MPI26_MFGPAGE_DEVID_SAS3508_1: mdb_printf("(SAS3508) "); break; case MPI26_MFGPAGE_DEVID_SAS3516: case MPI26_MFGPAGE_DEVID_SAS3516_1: mdb_printf("(SAS3516) "); break; case MPI26_MFGPAGE_DEVID_SAS3616: mdb_printf("(SAS3616) "); break; case MPI26_MFGPAGE_DEVID_SAS3708: mdb_printf("(SAS3708) "); break; case MPI26_MFGPAGE_DEVID_SAS3716: mdb_printf("(SAS3716) "); break; case MPI26_MFGPAGE_DEVID_SAS4008: mdb_printf("(SAS4008) "); break; default: mdb_printf("(SAS????) "); break; } mdb_printf("0x%02x 0x%04x\n", mp->m_revid, mp->m_ssid); mdb_printf("%s\n", device_path); } void dump_debug_log(void) { uint32_t idx; size_t linecnt, linelen; char *logbuf; int i; if (mdb_readsym(&idx, sizeof (uint32_t), "mptsas_dbglog_idx") == -1) { mdb_warn("No debug log buffer present"); return; } if (mdb_readsym(&linecnt, sizeof (size_t), "mptsas_dbglog_linecnt") == -1) { mdb_warn("No debug linecnt present"); return; } if (mdb_readsym(&linelen, sizeof (size_t), "mptsas_dbglog_linelen") == -1) { mdb_warn("No debug linelen present"); return; } logbuf = mdb_alloc(linelen * linecnt, UM_SLEEP); if (mdb_readsym(logbuf, linelen * linecnt, "mptsas_dbglog_bufs") == -1) { mdb_warn("No debug log buffer present"); return; } mdb_printf("\n"); idx &= linecnt - 1; for (i = 0; i < linecnt; i++) { mdb_printf("%s\n", &logbuf[idx * linelen]); idx++; idx &= linecnt - 1; } mdb_free(logbuf, linelen * linecnt); } static int mptsas_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct mptsas m; struct mptsas_slots *s; int nslots; int slot_size = 0; uint_t verbose = FALSE; uint_t target_info = FALSE; uint_t slot_info = FALSE; uint_t device_info = FALSE; uint_t port_info = FALSE; uint_t debug_log = FALSE; int rv = DCMD_OK; if (!(flags & DCMD_ADDRSPEC)) { void *mptsas_state = NULL; if (mdb_readvar(&mptsas_state, "mptsas_state") == -1) { mdb_warn("can't read mptsas_state"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("genunix`softstate", "mpt_sas`mptsas", argc, argv, (uintptr_t)mptsas_state) == -1) { mdb_warn("mdb_pwalk_dcmd failed"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 's', MDB_OPT_SETBITS, TRUE, &slot_info, 'd', MDB_OPT_SETBITS, TRUE, &device_info, 't', MDB_OPT_SETBITS, TRUE, &target_info, 'p', MDB_OPT_SETBITS, TRUE, &port_info, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'D', MDB_OPT_SETBITS, TRUE, &debug_log, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&m, sizeof (m), addr) == -1) { mdb_warn("couldn't read mpt struct at 0x%p", addr); return (DCMD_ERR); } s = mdb_alloc(sizeof (mptsas_slots_t), UM_SLEEP); if (mdb_vread(s, sizeof (mptsas_slots_t), (uintptr_t)m.m_active) == -1) { mdb_warn("couldn't read small mptsas_slots_t at 0x%p", m.m_active); mdb_free(s, sizeof (mptsas_slots_t)); return (DCMD_ERR); } nslots = s->m_n_normal; mdb_free(s, sizeof (mptsas_slots_t)); slot_size = sizeof (mptsas_slots_t) + (sizeof (mptsas_cmd_t *) * (nslots-1)); s = mdb_alloc(slot_size, UM_SLEEP); if (mdb_vread(s, slot_size, (uintptr_t)m.m_active) == -1) { mdb_warn("couldn't read large mptsas_slots_t at 0x%p", m.m_active); mdb_free(s, slot_size); return (DCMD_ERR); } /* processing completed */ if (((flags & DCMD_ADDRSPEC) && !(flags & DCMD_LOOP)) || (flags & DCMD_LOOPFIRST) || slot_info || device_info || target_info) { if ((flags & DCMD_LOOP) && !(flags & DCMD_LOOPFIRST)) mdb_printf("\n"); mdb_printf(" mptsas_t inst ncmds suspend power"); mdb_printf("\n"); mdb_printf("=========================================" "======================================="); mdb_printf("\n"); } mdb_printf("%16p %4d %5d ", addr, m.m_instance, m.m_ncmds); mdb_printf("%7d", m.m_suspended); switch (m.m_power_level) { case PM_LEVEL_D0: mdb_printf(" ON=D0 "); break; case PM_LEVEL_D1: mdb_printf(" D1 "); break; case PM_LEVEL_D2: mdb_printf(" D2 "); break; case PM_LEVEL_D3: mdb_printf("OFF=D3 "); break; default: mdb_printf("INVALD "); } mdb_printf("\n"); mdb_inc_indent(17); if (target_info) display_targets(&m, verbose); if (port_info) display_ports(&m); if (device_info) display_deviceinfo(&m); if (slot_info) display_slotinfo(&m, s); if (debug_log) dump_debug_log(); mdb_dec_indent(17); mdb_free(s, slot_size); return (rv); } void mptsas_help() { mdb_printf("Prints summary information about each mpt_sas instance, " "including warning\nmessages when slot usage doesn't match " "summary information.\n" "Without the address of a \"struct mptsas\", prints every " "instance.\n\n" "Switches:\n" " -t[v] includes information about targets, v = be more verbose\n" " -p includes information about port\n" " -s includes information about mpt slots\n" " -d includes information about the hardware\n" " -D print the mptsas specific debug log\n"); } static const mdb_dcmd_t dcmds[] = { { "mptsas", "?[-tpsdD]", "print mpt_sas information", mptsas_dcmd, mptsas_help}, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2013 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include "mr_sas.h" int construct_path(uintptr_t addr, char *result) { struct dev_info d; char devi_node[PATH_MAX]; char devi_addr[PATH_MAX]; if (mdb_vread(&d, sizeof (d), addr) == -1) { mdb_warn("couldn't read dev_info"); return (DCMD_ERR); } if (d.devi_parent) { construct_path((uintptr_t)d.devi_parent, result); mdb_readstr(devi_node, sizeof (devi_node), (uintptr_t)d.devi_node_name); mdb_readstr(devi_addr, sizeof (devi_addr), (uintptr_t)d.devi_addr); mdb_snprintf(result+strlen(result), PATH_MAX-strlen(result), "/%s%s%s", devi_node, (*devi_addr ? "@" : ""), devi_addr); } return (DCMD_OK); } void display_targets(struct mrsas_instance *m, int verbose) { int tgt; struct mrsas_ld mr_ldp[MRDRV_MAX_LD]; struct mrsas_tbolt_pd mr_pdp[MRSAS_TBOLT_PD_TGT_MAX]; char device_path[PATH_MAX]; if (verbose) { *device_path = 0; if (construct_path((uintptr_t)m->dip, device_path) != DCMD_OK) { strcpy(device_path, "couldn't determine device path"); } } mdb_printf("\n"); if (verbose) mdb_printf("%s\n", device_path); mdb_printf("Physical/Logical Target\n"); mdb_printf("-----------------------\n"); if (mdb_vread(&mr_ldp, sizeof (mr_ldp), (uintptr_t)m->mr_ld_list) == -1 || mdb_vread(&mr_pdp, sizeof (mr_pdp), (uintptr_t)m->mr_tbolt_pd_list) == -1) { mdb_warn("can't read list of disks"); return; } for (tgt = 0; tgt < MRDRV_MAX_LD; tgt++) { if (mr_ldp[tgt].dip != NULL && mr_ldp[tgt].lun_type == MRSAS_LD_LUN) { mdb_printf("Logical sd %d\n", tgt); } } for (tgt = 0; tgt < MRSAS_TBOLT_PD_TGT_MAX; tgt++) { if (mr_pdp[tgt].dip != NULL && mr_pdp[tgt].lun_type == MRSAS_TBOLT_PD_LUN) { mdb_printf("Physical sd %d\n", tgt); } } mdb_printf("\n"); } void display_deviceinfo(struct mrsas_instance *m) { uint16_t vid, did, svid, sid; vid = m->vendor_id; did = m->device_id; svid = m->subsysvid; sid = m->subsysid; mdb_printf("\n"); mdb_printf("vendor_id device_id subsysvid subsysid"); mdb_printf("\n"); mdb_printf("--------------------------------------"); mdb_printf("\n"); mdb_printf(" 0x%x 0x%x 0x%x 0x%x", vid, did, svid, sid); mdb_printf("\n"); } static int mr_sas_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct mrsas_instance m; int instance; uint16_t ncmds; uint_t verbose = FALSE; uint_t device_info = FALSE; uint_t target_info = FALSE; int rv = DCMD_OK; void *mrsas_state; if (!(flags & DCMD_ADDRSPEC)) { mrsas_state = NULL; if (mdb_readvar(&mrsas_state, "mrsas_state") == -1) { mdb_warn("can't read mrsas_state"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("genunix`softstate", "mr_sas`mr_sas", argc, argv, (uintptr_t)mrsas_state) == -1) { mdb_warn("mdb_pwalk_dcmd failed"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &device_info, 't', MDB_OPT_SETBITS, TRUE, &target_info, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&m, sizeof (m), addr) == -1) { mdb_warn("couldn't read mrsas_instance struct at 0x%p", addr); return (DCMD_ERR); } instance = m.instance; /* cmd slot info */ ncmds = m.max_fw_cmds; /* processing completed */ if (((flags & DCMD_ADDRSPEC) && !(flags & DCMD_LOOP)) || (flags & DCMD_LOOPFIRST)) { if ((flags & DCMD_LOOP) && !(flags & DCMD_LOOPFIRST)) mdb_printf("\n"); mdb_printf(" mrsas_t inst max_fw_cmds intr_type"); mdb_printf("\n"); mdb_printf("==========================================="); mdb_printf("\n"); } mdb_printf("%16p %4d %4d ", addr, instance, ncmds); switch (m.intr_type) { case DDI_INTR_TYPE_MSIX: mdb_printf("MSI-X"); break; case DDI_INTR_TYPE_MSI: mdb_printf("MSI"); break; case DDI_INTR_TYPE_FIXED: mdb_printf("FIXED"); break; default: mdb_printf("INVALD"); } mdb_printf("\n"); if (target_info) display_targets(&m, verbose); if (device_info) display_deviceinfo(&m); return (rv); } void mr_sas_help(void) { mdb_printf("Prints summary information about each mr_sas instance, " "Without the address of a \"struct mrsas_instance\", prints every " "instance.\n\n" "Switches:\n" " -t includes information about targets\n" " -d includes information about the hardware\n" " -v displays extra information for some options\n"); } static const mdb_dcmd_t dcmds[] = { { "mr_sas", "?[-tdv]", "print mr_sas information", mr_sas_dcmd, mr_sas_help }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include /* * PROT_LENGTH is the max length. If the true length is bigger * it is truncated. */ #define PROT_LENGTH 32 /* * List pfhooks netinfo information. */ /*ARGSUSED*/ int netinfolist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct neti_stack *nts; struct netd_listhead nlh; struct net_data nd, *p; char str[PROT_LENGTH]; if (argc) return (DCMD_USAGE); if (mdb_vread((void *)&nts, sizeof (nts), (uintptr_t)(addr + OFFSETOF(netstack_t, netstack_neti))) == -1) { mdb_warn("couldn't read netstack_neti"); return (DCMD_ERR); } if (mdb_vread((void *)&nlh, sizeof (nlh), (uintptr_t)((uintptr_t)nts + OFFSETOF(neti_stack_t, nts_netd_head))) == -1) { mdb_warn("couldn't read netd list head"); return (DCMD_ERR); } mdb_printf("%%?s %?s %10s%\n", "ADDR(netinfo)", "ADDR(hookevent)", "netinfo"); p = LIST_FIRST(&nlh); while (p) { if (mdb_vread((void *)&nd, sizeof (nd), (uintptr_t)p) == -1) { mdb_warn("couldn't read netinfo at %p", p); return (DCMD_ERR); } if (!nd.netd_info.netp_name) { mdb_warn("netinfo at %p has null protocol", nd.netd_info.netp_name); return (DCMD_ERR); } if (mdb_readstr((char *)str, sizeof (str), (uintptr_t)nd.netd_info.netp_name) == -1) { mdb_warn("couldn't read protocol at %p", nd.netd_info.netp_name); return (DCMD_ERR); } mdb_printf("%0?p %0?p %10s\n", (char *)p + (uintptr_t)&((struct net_data *)0)->netd_info, nd.netd_hooks, str); p = LIST_NEXT(&nd, netd_list); } return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "netinfolist", "", "display netinfo information", netinfolist, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include "nfssrv.h" #include "common.h" struct common_zsd_cb_data { zone_key_t key; /* Key of ZSD for which we're looking */ uint_t found; /* Was the specific ZSD entry found? */ uintptr_t zsd_data; /* Result */ }; int zoned_find_zsd_cb(uintptr_t addr, const void *data, void *cb_data) { const struct zsd_entry *entry = data; struct common_zsd_cb_data *cbd = cb_data; if (cbd->key != entry->zsd_key) return (WALK_NEXT); /* Match */ cbd->zsd_data = (uintptr_t)entry->zsd_data; cbd->found = TRUE; return (WALK_DONE); } int zoned_get_nfs_globals(uintptr_t zonep, uintptr_t *result) { return (zoned_get_zsd(zonep, "nfssrv_zone_key", result)); } int zoned_get_zsd(uintptr_t zonep, char *key_str, uintptr_t *result) { zone_key_t key; struct common_zsd_cb_data cbd; if (mdb_readsym(&key, sizeof (zone_key_t), key_str) == -1) { mdb_warn("failed to get %s", key_str); return (DCMD_ERR); } cbd.key = key; cbd.found = FALSE; if (mdb_pwalk("zsd", zoned_find_zsd_cb, &cbd, zonep) != 0) { mdb_warn("failed to walk zsd"); return (DCMD_ERR); } if (cbd.found == FALSE) { mdb_warn("no ZSD entry found"); return (DCMD_ERR); } *result = cbd.zsd_data; return (DCMD_OK); } const char * common_mutex(kmutex_t *mp) { char *s; size_t sz; mutex_impl_t *lp = (mutex_impl_t *)mp; if (MUTEX_TYPE_SPIN(lp)) { const char *fmt = "spin - lock(%x)/oldspl(%x)/minspl(%x)"; sz = 1 + mdb_snprintf(NULL, 0, fmt, lp->m_spin.m_spinlock, lp->m_spin.m_oldspl, lp->m_spin.m_minspl); s = mdb_alloc(sz, UM_SLEEP | UM_GC); (void) mdb_snprintf(s, sz, fmt, lp->m_spin.m_spinlock, lp->m_spin.m_oldspl, lp->m_spin.m_minspl); return (s); } if (MUTEX_TYPE_ADAPTIVE(lp)) { const char *fmt = "adaptive - owner %p%s"; if ((MUTEX_OWNER(lp) == NULL) && !MUTEX_HAS_WAITERS(lp)) return ("mutex not held"); sz = 1 + mdb_snprintf(NULL, 0, fmt, MUTEX_OWNER(lp), MUTEX_HAS_WAITERS(lp) ? " has waiters" : ""); s = mdb_alloc(sz, UM_SLEEP | UM_GC); (void) mdb_snprintf(s, sz, fmt, MUTEX_OWNER(lp), MUTEX_HAS_WAITERS(lp) ? " has waiters" : ""); return (s); } return ("mutex dead"); } const char * common_rwlock(krwlock_t *lp) { char *s; size_t sz; uintptr_t w = ((rwlock_impl_t *)lp)->rw_wwwh; uintptr_t o = w & RW_OWNER; const char *hw = (w & RW_HAS_WAITERS) ? " has_waiters" : ""; const char *ww = (w & RW_WRITE_WANTED) ? " write_wanted" : ""; const char *wl = (w & RW_WRITE_LOCKED) ? " write_locked" : ""; sz = 1 + mdb_snprintf(NULL, 0, "owner %p%s%s%s", o, hw, ww, wl); s = mdb_alloc(sz, UM_SLEEP | UM_GC); (void) mdb_snprintf(s, sz, "owner %p%s%s%s", o, hw, ww, wl); return (s); } const char * common_netbuf_str(struct netbuf *nb) { struct sockaddr_in *in; in = mdb_alloc(nb->len + 1, UM_SLEEP | UM_GC); if (mdb_vread(in, nb->len, (uintptr_t)nb->buf) == -1) return (""); if (nb->len < sizeof (struct sockaddr_in)) { ((char *)in)[nb->len] = '\0'; return ((char *)in); } if (in->sin_family == AF_INET) { char *s; ssize_t sz; mdb_nhconvert(&in->sin_port, &in->sin_port, sizeof (in->sin_port)); sz = 1 + mdb_snprintf(NULL, 0, "%I:%d", in->sin_addr.s_addr, in->sin_port); s = mdb_alloc(sz, UM_SLEEP | UM_GC); (void) mdb_snprintf(s, sz, "%I:%d", in->sin_addr.s_addr, in->sin_port); return (s); } else if ((in->sin_family == AF_INET6) && (nb->len >= sizeof (struct sockaddr_in6))) { struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)in; char *s; size_t sz; mdb_nhconvert(&in6->sin6_port, &in6->sin6_port, sizeof (in6->sin6_port)); sz = 1 + mdb_snprintf(NULL, 0, "[%N]:%d", in6->sin6_addr.s6_addr, in6->sin6_port); s = mdb_alloc(sz, UM_SLEEP | UM_GC); (void) mdb_snprintf(s, sz, "[%N]:%d", in6->sin6_addr.s6_addr, in6->sin6_port); return (s); } else { ((char *)in)[nb->len] = '\0'; return ((char *)in); } } /* * Generic hash table walker * */ typedef struct hash_table_walk_data { uintptr_t table; /* current table pointer */ int count; /* number of entries to process */ void *member; /* copy of the current member structure */ } hash_table_walk_data_t; int hash_table_walk_init(mdb_walk_state_t *wsp) { hash_table_walk_arg_t *arg = wsp->walk_arg; hash_table_walk_data_t *wd = mdb_alloc(sizeof (*wd), UM_SLEEP); wd->table = arg->array_addr; wd->count = arg->array_len; wd->member = mdb_alloc(arg->member_size, UM_SLEEP); wsp->walk_addr = 0; wsp->walk_data = wd; return (WALK_NEXT); } int hash_table_walk_step(mdb_walk_state_t *wsp) { hash_table_walk_arg_t *arg = wsp->walk_arg; hash_table_walk_data_t *wd = wsp->walk_data; uintptr_t addr = wsp->walk_addr; if (wd->count == 0) return (WALK_DONE); if (addr == 0) { if (mdb_vread(&wsp->walk_addr, sizeof (wsp->walk_addr), wd->table + arg->first_offset) == -1) { mdb_warn("can't read %s", arg->first_name); return (WALK_ERR); } if (wsp->walk_addr == 0) wsp->walk_addr = wd->table; return (WALK_NEXT); } if (addr == wd->table) { wd->count--; wd->table += arg->head_size; wsp->walk_addr = 0; return (WALK_NEXT); } if (mdb_vread(wd->member, arg->member_size, addr) == -1) { mdb_warn("unable to read %s", arg->member_type_name); return (WALK_ERR); } wsp->walk_addr = *(uintptr_t *)((uintptr_t)wd->member + arg->next_offset); if (wsp->walk_addr == 0) wsp->walk_addr = wd->table; return (wsp->walk_callback(addr, wd->member, wsp->walk_cbdata)); } void hash_table_walk_fini(mdb_walk_state_t *wsp) { hash_table_walk_arg_t *arg = wsp->walk_arg; hash_table_walk_data_t *wd = wsp->walk_data; mdb_free(wd->member, arg->member_size); mdb_free(wd, sizeof (*wd)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #ifndef _COMMON_H #define _COMMON_H #include #include #include #include #include extern int zoned_zsd_find_by_key(uintptr_t, zone_key_t, uintptr_t *); extern int zoned_get_nfs_globals(uintptr_t, uintptr_t *); extern int zoned_get_zsd(uintptr_t, char *, uintptr_t *); extern const char *common_mutex(kmutex_t *); extern const char *common_rwlock(krwlock_t *); extern const char *common_netbuf_str(struct netbuf *); /* * Generic hash table walker * * Generic hash table is an array of head structures starting at address * array_addr. The number of the head structures in the array is array_len. * Size of the head structure is head_size. There is a pointer in the head * structure called first_name with offset first_offset that points to the * linked list of member structures. The member structure type name is stored * in member_type_name. Size of the member structure is member_size. The * member structure have a pointer to the next member structure at offset * next_offset. * * A pointer to the hash_table_walk_arg_t should be passed as walk_arg to the * hash_table_walk_init(). */ typedef struct hash_table_walk_arg { uintptr_t array_addr; int array_len; size_t head_size; const char *first_name; size_t first_offset; const char *member_type_name; size_t member_size; size_t next_offset; } hash_table_walk_arg_t; extern int hash_table_walk_init(mdb_walk_state_t *); extern int hash_table_walk_step(mdb_walk_state_t *); extern void hash_table_walk_fini(mdb_walk_state_t *); #endif /* _COMMON_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include "idmap.h" #include "common.h" /* * nfs4_idmap dcmd implementation */ int nfs4_idmap_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { nfsidmap_t idmap; char *s; if (argc > 0) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of nfsidmap_t\n"); return (DCMD_USAGE); } if (mdb_vread(&idmap, sizeof (idmap), addr) == -1) { mdb_warn("unable to read nfsidmap_t"); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%%-20s %10s %-20s%%\n", "TimeStamp", "Number", "String"); s = mdb_alloc(idmap.id_str.utf8string_len + 1, UM_NOSLEEP | UM_GC); if (s == NULL || mdb_readstr(s, idmap.id_str.utf8string_len + 1, (uintptr_t)idmap.id_str.utf8string_val) == -1) s = "??"; mdb_printf("%-20Y %10i %s\n", idmap.id_time, idmap.id_no, s); return (DCMD_OK); } /* * nfs4_idmap_info dcmd implementation */ int nfs4_idmap_info_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t u2s, g2s, s2u, s2g; int i; if ((flags & DCMD_ADDRSPEC) == 0) addr = 0; u2s = g2s = s2u = s2g = argc == 0; for (i = 0; i < argc; i++) { const char *s; if (argv[i].a_type != MDB_TYPE_STRING) return (DCMD_USAGE); s = argv[i].a_un.a_str; if (strcmp(s, "u2s") == 0) u2s = TRUE; else if (strcmp(s, "g2s") == 0) g2s = TRUE; else if (strcmp(s, "s2u") == 0) s2u = TRUE; else if (strcmp(s, "s2g") == 0) s2g = TRUE; else return (DCMD_USAGE); } if (u2s) { mdb_printf("%NFSv4 uid-to-string idmap cache:%\n"); mdb_pwalk_dcmd("nfs4_u2s", "nfs4_idmap", 0, NULL, addr); } if (g2s) { mdb_printf("%NFSv4 gid-to-string idmap cache:%\n"); mdb_pwalk_dcmd("nfs4_g2s", "nfs4_idmap", 0, NULL, addr); } if (s2u) { mdb_printf("%NFSv4 string-to-uid idmap cache:%\n"); mdb_pwalk_dcmd("nfs4_s2u", "nfs4_idmap", 0, NULL, addr); } if (s2g) { mdb_printf("%NFSv4 string-to-gid idmap cache:%\n"); mdb_pwalk_dcmd("nfs4_s2g", "nfs4_idmap", 0, NULL, addr); } return (DCMD_OK); } void nfs4_idmap_info_help(void) { mdb_printf( "u2s display entries from NFSv4 uid-to-string idmap cache\n" "g2s display entries from NFSv4 gid-to-string idmap cache\n" "s2u display entries from NFSv4 string-to-uid idmap cache\n" "s2g display entries from NFSv4 string-to-gid idmap cache\n" "\nWithout arguments display entries from all caches.\n"); } /* * nfs4_u2s/nfs4_s2u/nfs4_g2s/nfs4_s2g walker implementation */ int nfs4_idmap_walk_init(mdb_walk_state_t *wsp) { uintptr_t table; hash_table_walk_arg_t *arg; int status; /* Use global zone by default */ if (wsp->walk_addr == 0) { /* wsp->walk_addr = global_zone */ if (mdb_readvar(&wsp->walk_addr, "global_zone") == -1) { mdb_warn("failed to locate global_zone"); return (WALK_ERR); } } if (zoned_get_zsd(wsp->walk_addr, "nfsidmap_zone_key", &wsp->walk_addr) != DCMD_OK) { mdb_warn("failed to get zoned idmap"); return (WALK_ERR); } if (mdb_vread(&table, sizeof (table), wsp->walk_addr + (uintptr_t)wsp->walk_arg + OFFSETOF(idmap_cache_info_t, table)) == -1) { mdb_warn("unable to read table pointer"); return (WALK_ERR); } arg = mdb_alloc(sizeof (hash_table_walk_arg_t), UM_SLEEP); arg->array_addr = table; arg->array_len = NFSID_CACHE_ANCHORS; arg->head_size = sizeof (nfsidhq_t); arg->first_name = "hq_lru_forw"; arg->first_offset = OFFSETOF(nfsidhq_t, hq_lru_forw); arg->member_type_name = "nfsidmap_t"; arg->member_size = sizeof (nfsidmap_t); arg->next_offset = OFFSETOF(nfsidmap_t, id_forw); wsp->walk_arg = arg; status = hash_table_walk_init(wsp); if (status != WALK_NEXT) mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t)); return (status); } void nfs4_idmap_walk_fini(mdb_walk_state_t *wsp) { hash_table_walk_fini(wsp); mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #ifndef _IDMAP_H #define _IDMAP_H #include extern int nfs4_idmap_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs4_idmap_info_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs4_idmap_info_help(void); extern int nfs4_idmap_walk_init(mdb_walk_state_t *); extern void nfs4_idmap_walk_fini(mdb_walk_state_t *); #endif /* _IDMAP_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "svc.h" #include "rfs4.h" #include "nfssrv.h" #include "idmap.h" #include "nfs_clnt.h" typedef struct nfs_rnode_cbdata { int printed_hdr; uintptr_t vfs_addr; /* for nfs_rnode4find */ } nfs_rnode_cbdata_t; static const mdb_bitmask_t vfs_flags[] = { { "VFS_RDONLY", VFS_RDONLY, VFS_RDONLY }, { "VFS_NOMNTTAB", VFS_NOMNTTAB, VFS_NOMNTTAB }, { "VFS_NOSETUID", VFS_NOSETUID, VFS_NOSETUID }, { "VFS_REMOUNT", VFS_REMOUNT, VFS_REMOUNT }, { "VFS_NOTRUNC", VFS_NOTRUNC, VFS_NOTRUNC }, { "VFS_PXFS", VFS_PXFS, VFS_PXFS }, { "VFS_NBMAND", VFS_NBMAND, VFS_NBMAND }, { "VFS_XATTR", VFS_XATTR, VFS_XATTR }, { "VFS_NOEXEC", VFS_NOEXEC, VFS_NOEXEC }, { "VFS_STATS", VFS_STATS, VFS_STATS }, { "VFS_XID", VFS_XID, VFS_XID }, { "VFS_UNLINKABLE", VFS_UNLINKABLE, VFS_UNLINKABLE }, { "VFS_UNMOUNTED", VFS_UNMOUNTED, VFS_UNMOUNTED }, { NULL, 0, 0 } }; static const mdb_bitmask_t nfs_mi4_flags[] = { { "MI4_HARD", MI4_HARD, MI4_HARD }, { "MI4_PRINTED", MI4_PRINTED, MI4_PRINTED }, { "MI4_INT", MI4_INT, MI4_INT }, { "MI4_DOWN", MI4_DOWN, MI4_DOWN }, { "MI4_NOAC", MI4_NOAC, MI4_NOAC }, { "MI4_NOCTO", MI4_NOCTO, MI4_NOCTO }, { "MI4_LLOCK", MI4_LLOCK, MI4_LLOCK }, { "MI4_GRPID", MI4_GRPID, MI4_GRPID }, { "MI4_SHUTDOWN", MI4_SHUTDOWN, MI4_SHUTDOWN }, { "MI4_LINK", MI4_LINK, MI4_LINK }, { "MI4_SYMLINK", MI4_SYMLINK, MI4_SYMLINK }, { "MI4_ACL", MI4_ACL, MI4_ACL }, { "MI4_REFERRAL", MI4_REFERRAL, MI4_REFERRAL }, { "MI4_NOPRINT", MI4_NOPRINT, MI4_NOPRINT }, { "MI4_DIRECTIO", MI4_DIRECTIO, MI4_DIRECTIO }, { "MI4_PUBLIC", MI4_PUBLIC, MI4_PUBLIC }, { "MI4_MOUNTING", MI4_MOUNTING, MI4_MOUNTING }, { "MI4_DEAD", MI4_DEAD, MI4_DEAD }, { "MI4_TIMEDOUT", MI4_TIMEDOUT, MI4_TIMEDOUT }, { "MI4_MIRRORMOUNT", MI4_MIRRORMOUNT, MI4_MIRRORMOUNT }, { "MI4_RECOV_ACTIV", MI4_RECOV_ACTIV, MI4_RECOV_ACTIV }, { "MI4_RECOV_FAIL", MI4_RECOV_FAIL, MI4_RECOV_FAIL }, { "MI4_POSIX_LOCK", MI4_POSIX_LOCK, MI4_POSIX_LOCK }, { "MI4_LOCK_DEBUG", MI4_LOCK_DEBUG, MI4_LOCK_DEBUG }, { "MI4_INACTIVE_IDLE", MI4_INACTIVE_IDLE, MI4_INACTIVE_IDLE }, { "MI4_BADOWNER_DEBUG", MI4_BADOWNER_DEBUG, MI4_BADOWNER_DEBUG }, { "MI4_ASYNC_MGR_STOP", MI4_ASYNC_MGR_STOP, MI4_ASYNC_MGR_STOP }, { "MI4_EPHEMERAL", MI4_EPHEMERAL, MI4_EPHEMERAL }, { "MI4_REMOVE_ON_LAST_CLOSE", MI4_REMOVE_ON_LAST_CLOSE, MI4_REMOVE_ON_LAST_CLOSE }, { NULL, 0, 0 } }; static const mdb_bitmask_t nfs_mi4_recover[] = { { "MI4R_NEED_CLIENTID", MI4R_NEED_CLIENTID, MI4R_NEED_CLIENTID }, { "MI4R_REOPEN_FILES", MI4R_REOPEN_FILES, MI4R_REOPEN_FILES }, { "MI4R_NEED_SECINFO", MI4R_NEED_SECINFO, MI4R_NEED_SECINFO }, { "MI4R_REOPEN_FILES", MI4R_REOPEN_FILES, MI4R_REOPEN_FILES }, { "MI4R_SRV_REBOOT", MI4R_SRV_REBOOT, MI4R_SRV_REBOOT }, { "MI4R_LOST_STATE", MI4R_LOST_STATE, MI4R_LOST_STATE }, { "MI4R_BAD_SEQID", MI4R_BAD_SEQID, MI4R_BAD_SEQID }, { "MI4R_MOVED", MI4R_MOVED, MI4R_MOVED }, { "MI4R_NEED_NEW_SERVER", MI4R_NEED_NEW_SERVER, MI4R_NEED_NEW_SERVER }, { NULL, 0, 0 } }; static const char * nfs4_tag_str(int tag) { switch (tag) { case TAG_NONE: return ("TAG_NONE"); case TAG_ACCESS: return ("TAG_ACCESS"); case TAG_CLOSE: return ("TAG_CLOSE"); case TAG_CLOSE_LOST: return ("TAG_CLOSE_LOST"); case TAG_CLOSE_UNDO: return ("TAG_CLOSE_UNDO"); case TAG_COMMIT: return ("TAG_COMMIT"); case TAG_DELEGRETURN: return ("TAG_DELEGRETURN"); case TAG_FSINFO: return ("TAG_FSINFO"); case TAG_GET_SYMLINK: return ("TAG_GET_SYMLINK"); case TAG_GETATTR: return ("TAG_GETATTR"); case TAG_GETATTR_FSLOCATION: return ("TAG_GETATTR_FSLOCATION"); case TAG_INACTIVE: return ("TAG_INACTIVE"); case TAG_LINK: return ("TAG_LINK"); case TAG_LOCK: return ("TAG_LOCK"); case TAG_LOCK_RECLAIM: return ("TAG_LOCK_RECLAIM"); case TAG_LOCK_RESEND: return ("TAG_LOCK_RESEND"); case TAG_LOCK_REINSTATE: return ("TAG_LOCK_REINSTATE"); case TAG_LOCK_UNKNOWN: return ("TAG_LOCK_UNKNOWN"); case TAG_LOCKT: return ("TAG_LOCKT"); case TAG_LOCKU: return ("TAG_LOCKU"); case TAG_LOCKU_RESEND: return ("TAG_LOCKU_RESEND"); case TAG_LOCKU_REINSTATE: return ("TAG_LOCKU_REINSTATE"); case TAG_LOOKUP: return ("TAG_LOOKUP"); case TAG_LOOKUP_PARENT: return ("TAG_LOOKUP_PARENT"); case TAG_LOOKUP_VALID: return ("TAG_LOOKUP_VALID"); case TAG_LOOKUP_VPARENT: return ("TAG_LOOKUP_VPARENT"); case TAG_MKDIR: return ("TAG_MKDIR"); case TAG_MKNOD: return ("TAG_MKNOD"); case TAG_MOUNT: return ("TAG_MOUNT"); case TAG_OPEN: return ("TAG_OPEN"); case TAG_OPEN_CONFIRM: return ("TAG_OPEN_CONFIRM"); case TAG_OPEN_CONFIRM_LOST: return ("TAG_OPEN_CONFIRM_LOST"); case TAG_OPEN_DG: return ("TAG_OPEN_DG"); case TAG_OPEN_DG_LOST: return ("TAG_OPEN_DG_LOST"); case TAG_OPEN_LOST: return ("TAG_OPEN_LOST"); case TAG_OPENATTR: return ("TAG_OPENATTR"); case TAG_PATHCONF: return ("TAG_PATHCONF"); case TAG_PUTROOTFH: return ("TAG_PUTROOTFH"); case TAG_READ: return ("TAG_READ"); case TAG_READAHEAD: return ("TAG_READAHEAD"); case TAG_READDIR: return ("TAG_READDIR"); case TAG_READLINK: return ("TAG_READLINK"); case TAG_RELOCK: return ("TAG_RELOCK"); case TAG_REMAP_LOOKUP: return ("TAG_REMAP_LOOKUP"); case TAG_REMAP_LOOKUP_AD: return ("TAG_REMAP_LOOKUP_AD"); case TAG_REMAP_LOOKUP_NA: return ("TAG_REMAP_LOOKUP_NA"); case TAG_REMAP_MOUNT: return ("TAG_REMAP_MOUNT"); case TAG_RMDIR: return ("TAG_RMDIR"); case TAG_REMOVE: return ("TAG_REMOVE"); case TAG_RENAME: return ("TAG_RENAME"); case TAG_RENAME_VFH: return ("TAG_RENAME_VFH"); case TAG_RENEW: return ("TAG_RENEW"); case TAG_REOPEN: return ("TAG_REOPEN"); case TAG_REOPEN_LOST: return ("TAG_REOPEN_LOST"); case TAG_SECINFO: return ("TAG_SECINFO"); case TAG_SETATTR: return ("TAG_SETATTR"); case TAG_SETCLIENTID: return ("TAG_SETCLIENTID"); case TAG_SETCLIENTID_CF: return ("TAG_SETCLIENTID_CF"); case TAG_SYMLINK: return ("TAG_SYMLINK"); case TAG_WRITE: return ("TAG_WRITE"); default: return ("Undefined"); } } /* * Return stringified NFS4 error. * Note, it may return pointer to static buffer (in case of unknown error) */ static const char * nfs4_stat_str(nfsstat4 err) { static char str[64]; switch (err) { case NFS4_OK: return ("NFS4_OK"); case NFS4ERR_PERM: return ("NFS4ERR_PERM"); case NFS4ERR_NOENT: return ("NFS4ERR_NOENT"); case NFS4ERR_IO: return ("NFS4ERR_IO"); case NFS4ERR_NXIO: return ("NFS4ERR_NXIO"); case NFS4ERR_ACCESS: return ("NFS4ERR_ACCESS"); case NFS4ERR_EXIST: return ("NFS4ERR_EXIST"); case NFS4ERR_XDEV: return ("NFS4ERR_XDEV"); case NFS4ERR_NOTDIR: return ("NFS4ERR_NOTDIR"); case NFS4ERR_ISDIR: return ("NFS4ERR_ISDIR"); case NFS4ERR_INVAL: return ("NFS4ERR_INVAL"); case NFS4ERR_FBIG: return ("NFS4ERR_FBIG"); case NFS4ERR_NOSPC: return ("NFS4ERR_NOSPC"); case NFS4ERR_ROFS: return ("NFS4ERR_ROFS"); case NFS4ERR_MLINK: return ("NFS4ERR_MLINK"); case NFS4ERR_NAMETOOLONG: return ("NFS4ERR_NAMETOOLONG"); case NFS4ERR_NOTEMPTY: return ("NFS4ERR_NOTEMPTY"); case NFS4ERR_DQUOT: return ("NFS4ERR_DQUOT"); case NFS4ERR_STALE: return ("NFS4ERR_STALE"); case NFS4ERR_BADHANDLE: return ("NFS4ERR_BADHANDLE"); case NFS4ERR_BAD_COOKIE: return ("NFS4ERR_BAD_COOKIE"); case NFS4ERR_NOTSUPP: return ("NFS4ERR_NOTSUPP"); case NFS4ERR_TOOSMALL: return ("NFS4ERR_TOOSMALL"); case NFS4ERR_SERVERFAULT: return ("NFS4ERR_SERVERFAULT"); case NFS4ERR_BADTYPE: return ("NFS4ERR_BADTYPE"); case NFS4ERR_DELAY: return ("NFS4ERR_DELAY"); case NFS4ERR_SAME: return ("NFS4ERR_SAME"); case NFS4ERR_DENIED: return ("NFS4ERR_DENIED"); case NFS4ERR_EXPIRED: return ("NFS4ERR_EXPIRED"); case NFS4ERR_LOCKED: return ("NFS4ERR_LOCKED"); case NFS4ERR_GRACE: return ("NFS4ERR_GRACE"); case NFS4ERR_FHEXPIRED: return ("NFS4ERR_FHEXPIRED"); case NFS4ERR_SHARE_DENIED: return ("NFS4ERR_SHARE_DENIED"); case NFS4ERR_WRONGSEC: return ("NFS4ERR_WRONGSEC"); case NFS4ERR_CLID_INUSE: return ("NFS4ERR_CLID_INUSE"); case NFS4ERR_RESOURCE: return ("NFS4ERR_RESOURCE"); case NFS4ERR_MOVED: return ("NFS4ERR_MOVED"); case NFS4ERR_NOFILEHANDLE: return ("NFS4ERR_NOFILEHANDLE"); case NFS4ERR_MINOR_VERS_MISMATCH: return ("NFS4ERR_MINOR_VERS_MISMATCH"); case NFS4ERR_STALE_CLIENTID: return ("NFS4ERR_STALE_CLIENTID"); case NFS4ERR_STALE_STATEID: return ("NFS4ERR_STALE_STATEID"); case NFS4ERR_OLD_STATEID: return ("NFS4ERR_OLD_STATEID"); case NFS4ERR_BAD_STATEID: return ("NFS4ERR_BAD_STATEID"); case NFS4ERR_BAD_SEQID: return ("NFS4ERR_BAD_SEQID"); case NFS4ERR_NOT_SAME: return ("NFS4ERR_NOT_SAME"); case NFS4ERR_LOCK_RANGE: return ("NFS4ERR_LOCK_RANGE"); case NFS4ERR_SYMLINK: return ("NFS4ERR_SYMLINK"); case NFS4ERR_RESTOREFH: return ("NFS4ERR_RESTOREFH"); case NFS4ERR_LEASE_MOVED: return ("NFS4ERR_LEASE_MOVED"); case NFS4ERR_ATTRNOTSUPP: return ("NFS4ERR_ATTRNOTSUPP"); case NFS4ERR_NO_GRACE: return ("NFS4ERR_NO_GRACE"); case NFS4ERR_RECLAIM_BAD: return ("NFS4ERR_RECLAIM_BAD"); case NFS4ERR_RECLAIM_CONFLICT: return ("NFS4ERR_RECLAIM_CONFLICT"); case NFS4ERR_BADXDR: return ("NFS4ERR_BADXDR"); case NFS4ERR_LOCKS_HELD: return ("NFS4ERR_LOCKS_HELD"); case NFS4ERR_OPENMODE: return ("NFS4ERR_OPENMODE"); case NFS4ERR_BADOWNER: return ("NFS4ERR_BADOWNER"); case NFS4ERR_BADCHAR: return ("NFS4ERR_BADCHAR"); case NFS4ERR_BADNAME: return ("NFS4ERR_BADNAME"); case NFS4ERR_BAD_RANGE: return ("NFS4ERR_BAD_RANGE"); case NFS4ERR_LOCK_NOTSUPP: return ("NFS4ERR_LOCK_NOTSUPP"); case NFS4ERR_OP_ILLEGAL: return ("NFS4ERR_OP_ILLEGAL"); case NFS4ERR_DEADLOCK: return ("NFS4ERR_DEADLOCK"); case NFS4ERR_FILE_OPEN: return ("NFS4ERR_FILE_OPEN"); case NFS4ERR_ADMIN_REVOKED: return ("NFS4ERR_ADMIN_REVOKED"); case NFS4ERR_CB_PATH_DOWN: return ("NFS4ERR_CB_PATH_DOWN"); default: mdb_snprintf(str, sizeof (str), "Unknown %d", err); return (str); } } static const char * nfs4_op_str(uint_t op) { switch (op) { case OP_ACCESS: return ("OP_ACCESS"); case OP_CLOSE: return ("OP_CLOSE"); case OP_COMMIT: return ("OP_COMMIT"); case OP_CREATE: return ("OP_CREATE"); case OP_DELEGPURGE: return ("OP_DELEGPURGE"); case OP_DELEGRETURN: return ("OP_DELEGRETURN"); case OP_GETATTR: return ("OP_GETATTR"); case OP_GETFH: return ("OP_GETFH"); case OP_LINK: return ("OP_LINK"); case OP_LOCK: return ("OP_LOCK"); case OP_LOCKT: return ("OP_LOCKT"); case OP_LOCKU: return ("OP_LOCKU"); case OP_LOOKUP: return ("OP_LOOKUP"); case OP_LOOKUPP: return ("OP_LOOKUPP"); case OP_NVERIFY: return ("OP_NVERIFY"); case OP_OPEN: return ("OP_OPEN"); case OP_OPENATTR: return ("OP_OPENATTR"); case OP_OPEN_CONFIRM: return ("OP_OPEN_CONFIRM"); case OP_OPEN_DOWNGRADE: return ("OP_OPEN_DOWNGRADE"); case OP_PUTFH: return ("OP_PUTFH"); case OP_PUTPUBFH: return ("OP_PUTPUBFH"); case OP_PUTROOTFH: return ("OP_PUTROOTFH"); case OP_READ: return ("OP_READ"); case OP_READDIR: return ("OP_READDIR"); case OP_READLINK: return ("OP_READLINK"); case OP_REMOVE: return ("OP_REMOVE"); case OP_RENAME: return ("OP_RENAME"); case OP_RENEW: return ("OP_RENEW"); case OP_RESTOREFH: return ("OP_RESTOREFH"); case OP_SAVEFH: return ("OP_SAVEFH"); case OP_SECINFO: return ("OP_SECINFO"); case OP_SETATTR: return ("OP_SETATTR"); case OP_SETCLIENTID: return ("OP_SETCLIENTID"); case OP_SETCLIENTID_CONFIRM: return ("OP_SETCLIENTID_CONFIRM"); case OP_VERIFY: return ("OP_VERIFY"); case OP_WRITE: return ("OP_WRITE"); case OP_RELEASE_LOCKOWNER: return ("OP_RELEASE_LOCKOWNER"); case OP_ILLEGAL: return ("OP_ILLEGAL"); default: return ("Unknown"); } } static const char * nfs4_recov_str(uint_t act) { switch (act) { case NR_UNUSED: return ("NR_UNUSED"); case NR_STALE: return ("NR_STALE"); case NR_FAILOVER: return ("NR_FAILOVER"); case NR_CLIENTID: return ("NR_CLIENTID"); case NR_OPENFILES: return ("NR_OPENFILES"); case NR_WRONGSEC: return ("NR_WRONGSEC"); case NR_EXPIRED: return ("NR_EXPIRED"); case NR_BAD_STATEID: return ("NR_BAD_STATEID"); case NR_FHEXPIRED: return ("NR_FHEXPIRED"); case NR_BADHANDLE: return ("NR_BADHANDLE"); case NR_BAD_SEQID: return ("NR_BAD_SEQID"); case NR_OLDSTATEID: return ("NR_OLDSTATEID"); case NR_GRACE: return ("NR_GRACE"); case NR_DELAY: return ("NR_DELAY"); case NR_LOST_LOCK: return ("NR_LOST_LOCK"); case NR_LOST_STATE_RQST: return ("NR_LOST_STATE_RQST"); case NR_MOVED: return ("NR_MOVED"); default: return ("Unknown"); } } static void nfs_addr_by_conf(uintptr_t knconf, struct netbuf *addr, char *s, size_t nbytes) { struct knetconfig conf; char buf[16]; if (mdb_vread(&conf, sizeof (conf), knconf) == -1) { mdb_warn("can't read sv_knconf"); return; } if (mdb_readstr(buf, sizeof (buf), (uintptr_t)conf.knc_protofmly) == -1) { mdb_warn("can't read knc_protofmly"); return; } /* Support only IPv4 addresses */ if (strcmp(NC_INET, buf) == 0) { struct sockaddr_in *in; in = mdb_alloc(addr->len + 1, UM_SLEEP | UM_GC); if (mdb_vread(in, addr->len, (uintptr_t)addr->buf) == -1) return; mdb_nhconvert(&in->sin_port, &in->sin_port, sizeof (in->sin_port)); (void) mdb_snprintf(s, nbytes, "%I:%d", in->sin_addr.s_addr, in->sin_port); } } /* * Get IPv4 string address by servinfo4_t * * in case of error does not modify 's' */ static void nfs_addr_by_servinfo4(uintptr_t addr, char *s, size_t nbytes) { struct servinfo4 *si; si = mdb_alloc(sizeof (*si), UM_SLEEP | UM_GC); if (mdb_vread(si, sizeof (*si), addr) == -1) { mdb_warn("can't read servinfo4"); return; } nfs_addr_by_conf((uintptr_t)si->sv_knconf, &si->sv_addr, s, nbytes); } /* * Get IPv4 string address by servinfo_t * * in case of error does not modify 's' */ static void nfs_addr_by_servinfo(uintptr_t addr, char *s, size_t nbytes) { struct servinfo *si; si = mdb_alloc(sizeof (*si), UM_SLEEP | UM_GC); if (mdb_vread(si, sizeof (*si), addr) == -1) { mdb_warn("can't read servinfo"); return; } nfs_addr_by_conf((uintptr_t)si->sv_knconf, &si->sv_addr, s, nbytes); } static void nfs_queue_show_event(const nfs4_debug_msg_t *msg) { const nfs4_revent_t *re; time_t time; char *re_char1 = "", *re_char2 = ""; re = &msg->rmsg_u.msg_event; time = msg->msg_time.tv_sec; if (re->re_char1 != NULL) { char *s; s = mdb_alloc(MAXPATHLEN, UM_SLEEP | UM_GC); if (mdb_readstr(s, MAXPATHLEN, (uintptr_t)re->re_char1) != -1) re_char1 = s; else mdb_warn("can't read re_char1"); } if (re->re_char2 != NULL) { char *s; s = mdb_alloc(MAXPATHLEN, UM_SLEEP | UM_GC); if (mdb_readstr(s, MAXPATHLEN, (uintptr_t)re->re_char2) != -1) re_char2 = s; else mdb_warn("can't read re_char2"); } switch (re->re_type) { case RE_BAD_SEQID: mdb_printf("[NFS4]%Y: Op %s for file %s rnode_pt %p\n" "pid %d using seqid %d got %s. Last good seqid was %d " "for operation %s\n", time, nfs4_tag_str(re->re_tag1), re->re_char1, re->re_rp1, re->re_pid, re->re_seqid1, nfs4_stat_str(re->re_stat4), re->re_seqid2, nfs4_tag_str(re->re_tag2)); break; case RE_BADHANDLE: mdb_printf("[NFS4]%Y: server said filehandle was " "invalid for file: %s rnode_pt 0x%p\n", time, re_char1, re->re_rp1); break; case RE_CLIENTID: mdb_printf("[NFS4]%Y: Can't recover clientid on mountpoint %s\n" "mi %p due to error %d (%s). Marking file system " "as unusable\n", time, msg->msg_mntpt, re->re_mi, re->re_uint, nfs4_stat_str(re->re_stat4)); break; case RE_DEAD_FILE: mdb_printf("[NFS4]%Y: File: %s rnode_pt: %p was closed on NFS\n" "recovery error [%s %s]\n", time, re_char1, re->re_rp1, re_char2, nfs4_stat_str(re->re_stat4)); break; case RE_END: mdb_printf("[NFS4]%Y: NFS Recovery done for mi %p " "rnode_pt1 %s (%p), rnode_pt2 %s (%p)\n", time, re->re_mi, re_char1, re->re_rp1, re_char2, re->re_rp2); break; case RE_FAIL_RELOCK: mdb_printf("[NFS4]%Y: Couldn't reclaim lock for pid %d for\n" "file %s (rnode_pt %p) error %d\n", time, re->re_pid, re_char1, re->re_rp1, re->re_uint ? re->re_uint : re->re_stat4); break; case RE_FAIL_REMAP_LEN: mdb_printf("[NFS4]%Y: remap_lookup: returned bad\n" "fhandle length %d\n", time, re->re_uint); break; case RE_FAIL_REMAP_OP: mdb_printf("[NFS4]%Y: remap_lookup: didn't get expected " " OP_GETFH\n", time); break; case RE_FAILOVER: mdb_printf("[NFS4]%Y: failing over to %s\n", time, re_char1); break; case RE_FILE_DIFF: mdb_printf("[NFS4]%Y: File %s rnode_pt: %p was closed\n" "and failed attempted failover since its is different\n" "than the original file\n", time, re_char1, re->re_rp1); break; case RE_LOST_STATE: mdb_printf("[NFS4]%Y: Lost %s request file %s\n" "rnode_pt: %p, dir %s (%p)\n", time, nfs4_op_str(re->re_uint), re_char1, re->re_rp1, re_char2, re->re_rp2); break; case RE_OPENS_CHANGED: mdb_printf("[NFS4]%Y: The number of open files to reopen\n" "changed for mount %s mi %p old %d, new %d\n", time, msg->msg_mntpt, re->re_mi, re->re_uint, re->re_pid); break; case RE_SIGLOST: case RE_SIGLOST_NO_DUMP: mdb_printf("[NFS4]%Y: Process %d lost its locks on file %s\n" "rnode_pt: %p due to NFS recovery error (%d:%s)\n", time, re->re_pid, re_char1, re->re_rp1, re->re_uint, nfs4_stat_str(re->re_stat4)); break; case RE_START: mdb_printf("[NFS4]%Y: NFS Starting recovery for\n" "mi %p mi_recovflags [0x%x] rnode_pt1 %s %p " "rnode_pt2 %s %p\n", time, re->re_mi, re->re_uint, re_char1, re->re_rp1, re_char2, re->re_rp2); break; case RE_UNEXPECTED_ACTION: mdb_printf("[NFS4]%Y: NFS recovery: unexpected action %s\n", time, nfs4_recov_str(re->re_uint)); break; case RE_UNEXPECTED_ERRNO: mdb_printf("[NFS4]%Y: NFS recovery: unexpected errno %d\n", time, re->re_uint); break; case RE_UNEXPECTED_STATUS: mdb_printf("[NFS4]%Y: NFS recovery: unexpected status" "code (%s)\n", time, nfs4_stat_str(re->re_stat4)); break; case RE_WRONGSEC: mdb_printf("[NFS4]%Y: NFS can't recover from NFS4ERR_WRONGSEC\n" "error %d rnode_pt1 %s (%p) rnode_pt2 %s (0x%p)\n", time, re->re_uint, re_char1, re->re_rp1, re_char2, re->re_rp2); break; case RE_LOST_STATE_BAD_OP: mdb_printf("[NFS4]%Y: NFS lost state with unrecognized op %d\n" "fs %s, pid %d, file %s (rnode_pt: %p) dir %s (%p)\n", time, re->re_uint, msg->msg_mntpt, re->re_pid, re_char1, re->re_rp1, re_char2, re->re_rp2); break; case RE_REFERRAL: mdb_printf("[NFS4]%Y: being referred to %s\n", time, re_char1); break; default: mdb_printf("illegal event %d\n", re->re_type); break; } } static void nfs_queue_show_fact(const nfs4_debug_msg_t *msg) { time_t time; const nfs4_rfact_t *rf; char *rf_char1 = ""; rf = &msg->rmsg_u.msg_fact; time = msg->msg_time.tv_sec; if (rf->rf_char1 != NULL) { char *s; s = mdb_alloc(MAXPATHLEN, UM_SLEEP | UM_GC); if (mdb_readstr(s, MAXPATHLEN, (uintptr_t)rf->rf_char1) != -1) rf_char1 = s; else mdb_warn("can't read rf_char1"); } switch (rf->rf_type) { case RF_ERR: mdb_printf("[NFS4]%Y: NFS op %s got " "error %s:%d causing recovery action %s.%s\n", time, nfs4_op_str(rf->rf_op), rf->rf_error ? "" : nfs4_stat_str(rf->rf_stat4), rf->rf_error, nfs4_recov_str(rf->rf_action), rf->rf_reboot ? " Client also suspects that the server rebooted," " or experienced a network partition." : ""); break; case RF_RENEW_EXPIRED: mdb_printf("[NFS4]%Y: NFS4 renew thread detected client's " "lease has expired. Current open files/locks/IO may fail\n", time); break; case RF_SRV_NOT_RESPOND: mdb_printf("[NFS4]%Y: NFS server not responding;" "still trying\n", time); break; case RF_SRV_OK: mdb_printf("[NFS4]%Y: NFS server ok\n", time); break; case RF_SRVS_NOT_RESPOND: mdb_printf("[NFS4]%Y: NFS servers not responding; " "still trying\n", time); break; case RF_SRVS_OK: mdb_printf("[NFS4]%Y: NFS servers ok\n", time); break; case RF_DELMAP_CB_ERR: mdb_printf("[NFS4]%Y: NFS op %s got error %s when executing " "delmap on file %s rnode_pt %p\n", time, nfs4_op_str(rf->rf_op), nfs4_stat_str(rf->rf_stat4), rf_char1, rf->rf_rp1); break; case RF_SENDQ_FULL: mdb_printf("[NFS4]%Y: sending queue to NFS server is full; " "still trying\n", time); break; default: mdb_printf("queue_print_fact: illegal fact %d\n", rf->rf_type); } } static int nfs4_show_message(uintptr_t addr, const void *arg, void *data) { nfs4_debug_msg_t msg; if (mdb_vread(&msg, sizeof (msg), addr) == -1) { mdb_warn("failed to read nfs4_debug_msg_t at %p", addr); return (WALK_ERR); } if (msg.msg_type == RM_EVENT) nfs_queue_show_event(&msg); else if (msg.msg_type == RM_FACT) nfs_queue_show_fact(&msg); else mdb_printf("Wrong msg_type %d\n", msg.msg_type); return (WALK_NEXT); } static void nfs4_print_messages(uintptr_t head) { mdb_printf("-----------------------------\n"); mdb_printf("Messages queued:\n"); mdb_inc_indent(2); mdb_pwalk("list", nfs4_show_message, NULL, (uintptr_t)head); mdb_dec_indent(2); mdb_printf("-----------------------------\n"); } static void nfs_print_mi4(uintptr_t miaddr, int verbose) { mntinfo4_t *mi; char str[INET6_ADDRSTRLEN] = ""; mi = mdb_alloc(sizeof (*mi), UM_SLEEP | UM_GC); if (mdb_vread(mi, sizeof (*mi), miaddr) == -1) { mdb_warn("can't read mntinfo"); return; } mdb_printf("mntinfo4_t: %p\n", miaddr); mdb_printf("NFS Version: 4\n"); mdb_printf("mi_flags: %b\n", mi->mi_flags, nfs_mi4_flags); mdb_printf("mi_error: %x\n", mi->mi_error); mdb_printf("mi_open_files: %d\n", mi->mi_open_files); mdb_printf("mi_msg_count: %d\n", mi->mi_msg_count); mdb_printf("mi_recovflags: %b\n", mi->mi_recovflags, nfs_mi4_recover); mdb_printf("mi_recovthread: %p\n", mi->mi_recovthread); mdb_printf("mi_in_recovery: %d\n", mi->mi_in_recovery); if (verbose == 0) return; mdb_printf("mi_zone: %p\n", mi->mi_zone); mdb_printf("mi_curread: %d\n", mi->mi_curread); mdb_printf("mi_curwrite: %d\n", mi->mi_curwrite); mdb_printf("mi_retrans: %d\n", mi->mi_retrans); mdb_printf("mi_timeo: %d\n", mi->mi_timeo); mdb_printf("mi_acregmin: %llu\n", mi->mi_acregmin); mdb_printf("mi_acregmax: %llu\n", mi->mi_acregmax); mdb_printf("mi_acdirmin: %llu\n", mi->mi_acdirmin); mdb_printf("mi_acdirmax: %llu\n", mi->mi_acdirmax); mdb_printf("mi_count: %u\n", mi->mi_count); mdb_printf("\nServer list: %p\n", mi->mi_servers); nfs_addr_by_servinfo4((uintptr_t)mi->mi_curr_serv, str, sizeof (str)); mdb_printf("Curr Server: %p %s\n", mi->mi_curr_serv, str); mdb_printf("Total:\n"); mdb_inc_indent(2); mdb_printf("Server Non-responses: %u\n", mi->mi_noresponse); mdb_printf("Server Failovers: %u\n\n", mi->mi_failover); mdb_dec_indent(2); mdb_printf("\nAsync Request queue:\n"); mdb_inc_indent(2); mdb_printf("max threads: %u\n", mi->mi_max_threads); mdb_printf("active threads: %u\n", mi->mi_threads[NFS_ASYNC_QUEUE]); mdb_dec_indent(2); nfs4_print_messages(miaddr + OFFSETOF(mntinfo4_t, mi_msg_list)); } static void nfs_print_mi(uintptr_t miaddr, uint_t vers) { mntinfo_t *mi; char str[INET6_ADDRSTRLEN] = ""; mi = mdb_alloc(sizeof (*mi), UM_SLEEP | UM_GC); if (mdb_vread(mi, sizeof (*mi), miaddr) == -1) { mdb_warn("can't read mntinfo"); return; } mdb_printf("\nServer list: %p\n", mi->mi_servers); nfs_addr_by_servinfo((uintptr_t)mi->mi_curr_serv, str, sizeof (str)); mdb_printf("Curr Server: %p %s\n", mi->mi_curr_serv, str); mdb_printf("Total:\n"); mdb_inc_indent(2); mdb_printf("Server Non-responses: %u\n", mi->mi_noresponse); mdb_printf("Server Failovers: %u\n\n", mi->mi_failover); mdb_dec_indent(2); mdb_printf("\nAsync Request queue:\n"); mdb_inc_indent(2); mdb_printf("max threads: %u\n", mi->mi_max_threads); mdb_printf("active threads: %u\n", mi->mi_threads[NFS_ASYNC_QUEUE]); mdb_dec_indent(2); } static int nfs_vfs_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { vfs_t *vfs; char buf[MAXNAMELEN]; int verbose = 0; if ((flags & DCMD_ADDRSPEC) == 0) { if (mdb_walk_dcmd("nfs_vfs", "nfs_vfs", argc, argv) == -1) { mdb_warn("failed to walk nfs_vfs"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); vfs = mdb_alloc(sizeof (*vfs), UM_SLEEP | UM_GC); if (mdb_vread(vfs, sizeof (*vfs), addr) == -1) { mdb_warn("failed to read vfs"); return (DCMD_ERR); } mdb_printf("vfs_t->%p, data = %p, ops = %p\n", addr, vfs->vfs_data, vfs->vfs_op); /* do not need do vread for vfs_mntpt because take address */ if (mdb_readstr(buf, MAXNAMELEN, (uintptr_t)&vfs->vfs_mntpt->rs_string) == -1) return (DCMD_ERR); mdb_inc_indent(2); mdb_printf("mount point: %s\n", buf); if (mdb_readstr(buf, MAXNAMELEN, (uintptr_t)&vfs->vfs_resource->rs_string) == -1) { mdb_warn("can't read rs_string"); goto err; } mdb_printf("mount from: %s\n", buf); if (verbose) { uintptr_t nfs4_ops; mntopt_t m; uint_t i; mdb_printf("vfs_flags: %b\n", vfs->vfs_flag, vfs_flags); mdb_printf("mount opts: "); for (i = 0; i < vfs->vfs_mntopts.mo_count; i++) { uintptr_t a = (uintptr_t)(vfs->vfs_mntopts.mo_list + i); if (mdb_vread(&m, sizeof (m), a) == -1) { mdb_warn("can't read mntopt"); continue; } if (m.mo_flags & MO_EMPTY) continue; if (mdb_readstr(buf, sizeof (buf), (uintptr_t)m.mo_name) == -1) { mdb_warn("can't read mo_name"); continue; } if (m.mo_flags & MO_HASVALUE) { char val[64]; if (mdb_readstr(val, sizeof (val), (uintptr_t)m.mo_arg) == -1) { mdb_warn("can't read mo_arg"); continue; } mdb_printf("%s(%s), ", buf, val); } else mdb_printf("%s, ", buf); } mdb_printf("\n+--------------------------------------+\n"); if (mdb_readvar(&nfs4_ops, "nfs4_vfsops") == -1) { mdb_warn("failed read %s", "nfs4_vfsops"); goto err; } if (nfs4_ops == (uintptr_t)vfs->vfs_op) { nfs_print_mi4((uintptr_t)VFTOMI4(vfs), 1); } else { int vers = 3; uintptr_t nfs3_ops; if (mdb_readvar(&nfs3_ops, "nfs3_vfsops") == -1) { mdb_warn("failed read %s", "nfs3_vfsops"); goto err; } if (nfs3_ops != (uintptr_t)vfs->vfs_op) vers = 2; nfs_print_mi((uintptr_t)VFTOMI(vfs), vers); } } mdb_dec_indent(2); mdb_printf("\n"); return (DCMD_OK); err: mdb_dec_indent(2); mdb_printf("\n"); return (DCMD_ERR); } static int nfs4_diag_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mntinfo4_t *mi; vfs_t *vfs; char buf[MAXNAMELEN]; if ((flags & DCMD_ADDRSPEC) == 0) { if (mdb_walk_dcmd("nfs4_mnt", "nfs4_diag", argc, argv) == -1) { mdb_warn("failed to walk nfs4_mnt"); return (DCMD_ERR); } return (DCMD_OK); } mi = mdb_alloc(sizeof (*mi), UM_SLEEP | UM_GC); if (mdb_vread(mi, sizeof (*mi), addr) == -1) { mdb_warn("can't read mntinfo4"); return (WALK_ERR); } vfs = mdb_alloc(sizeof (*vfs), UM_SLEEP | UM_GC); if (mdb_vread(vfs, sizeof (*vfs), (uintptr_t)mi->mi_vfsp) == -1) { mdb_warn("failed to read vfs"); return (DCMD_ERR); } mdb_printf("****************************************\n"); mdb_printf("vfs: %-16p mi: %-16p\n", mi->mi_vfsp, addr); if (mdb_readstr(buf, MAXNAMELEN, (uintptr_t)&vfs->vfs_mntpt->rs_string) == -1) return (DCMD_ERR); mdb_inc_indent(2); mdb_printf("mount point: %s\n", buf); if (mdb_readstr(buf, MAXNAMELEN, (uintptr_t)&vfs->vfs_resource->rs_string) == -1) { mdb_warn("can't read rs_string"); mdb_dec_indent(2); return (DCMD_ERR); } mdb_printf("mount from: %s\n", buf); nfs4_print_messages(addr + OFFSETOF(mntinfo4_t, mi_msg_list)); mdb_dec_indent(2); mdb_printf("\n"); return (DCMD_OK); } static void nfs4_diag_help(void) { mdb_printf(" ::nfs4_diag <-s>\n" " -> assumes client is an illumos NFSv4 client\n"); } static int nfs_rnode4_cb(uintptr_t addr, const void *data, void *arg) { const rnode4_t *rp = data; nfs_rnode_cbdata_t *cbd = arg; vnode_t *vp; if (addr == 0) return (WALK_DONE); vp = mdb_alloc(sizeof (*vp), UM_SLEEP | UM_GC); if (mdb_vread(vp, sizeof (*vp), (uintptr_t)rp->r_vnode) == -1) { mdb_warn("can't read vnode_t %p", (uintptr_t)rp->r_vnode); return (WALK_ERR); } if (cbd->vfs_addr != 0 && cbd->vfs_addr != (uintptr_t)vp->v_vfsp) return (WALK_NEXT); if (cbd->printed_hdr == 0) { mdb_printf("%-16s %-16s %-16s %-8s\n" "%-16s %-8s %-8s %s\n", "Address", "r_vnode", "vfsp", "r_fh", "r_server", "r_error", "r_flags", "r_count"); cbd->printed_hdr = 1; } mdb_printf("%-?p %-8p %-8p %-8p\n" "%-16p %-8u %-8x %u\n", addr, rp->r_vnode, vp->v_vfsp, rp->r_fh, rp->r_server, (int)rp->r_error, rp->r_flags, rp->r_count); return (WALK_NEXT); } static int nfs_rnode4_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { nfs_rnode_cbdata_t *cbd; rnode4_t *rp; cbd = mdb_zalloc(sizeof (*cbd), UM_SLEEP | UM_GC); if ((flags & DCMD_ADDRSPEC) == 0) { if (mdb_walk("nfs_rtable4", nfs_rnode4_cb, cbd) == -1) { mdb_warn("failed to walk nfs_rnode4"); return (DCMD_ERR); } return (DCMD_OK); } /* address was specified */ rp = mdb_alloc(sizeof (*rp), UM_SLEEP | UM_GC); if (mdb_vread(rp, sizeof (*rp), addr) == -1) { mdb_warn("can't read rnode4_t"); return (DCMD_ERR); } nfs_rnode4_cb(addr, rp, cbd); return (DCMD_OK); } static void nfs_rnode4_help(void) { mdb_printf("::nfs_rnode4\n\n" "This prints NFSv4 rnode at address specified. If address\n" "is not specified, walks entire NFSv4 rnode table.\n"); } static int nfs_rnode4find_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { nfs_rnode_cbdata_t *cbd; cbd = mdb_zalloc(sizeof (*cbd), UM_SLEEP | UM_GC); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of vfs_t\n"); return (DCMD_USAGE); } cbd->vfs_addr = addr; if (mdb_walk("nfs_rtable4", nfs_rnode4_cb, cbd) == -1) { mdb_warn("failed to walk nfs_rnode4"); return (DCMD_ERR); } return (DCMD_OK); } static void nfs_rnode4find_help(void) { mdb_printf("::nfs_rnode4find\n\n" "This prints all NFSv4 rnodes that belong to\n" "the VFS address specified\n"); } static int nfs_help_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs_stat_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); static const mdb_dcmd_t dcmds[] = { /* svc */ { "svc_pool", "?[-v] [poolid ...]", "display SVCPOOL information\n" "\t\t\t(Optional address of SVCPOOL)", svc_pool_dcmd, svc_pool_help }, { "svc_mxprt", ":[-w]", "display master xprt info given SVCMASTERXPRT", svc_mxprt_dcmd, svc_mxprt_help }, /* rfs4 */ { "rfs4_db", "?", "dump NFSv4 server database\n" "\t\t\t(Optional address of zone_t)", rfs4_db_dcmd }, { "rfs4_tbl", ":[-vw]", "dump NFSv4 server table given rfs4_table_t", rfs4_tbl_dcmd, rfs4_tbl_help }, { "rfs4_idx", ":[-w]", "dump NFSv4 server index given rfs4_index_t", rfs4_idx_dcmd, rfs4_idx_help }, { "rfs4_bkt", ":", "dump NFSv4 server index buckets given rfs4_index_t", rfs4_bkt_dcmd }, { "rfs4_oo", "?", "dump NFSv4 rfs4_openowner_t structures from bucket data\n" "\t\t\t(Optional address of rfs4_openowner_t)", rfs4_oo_dcmd }, { "rfs4_osid", "?[-v]", "dump NFSv4 rfs4_state_t structures from bucket data\n" "\t\t\t(Optional address of rfs4_state_t)", rfs4_osid_dcmd }, { "rfs4_file", "?[-v]", "dump NFSv4 rfs4_file_t structures from bucket data\n" "\t\t\t(Optional address of rfs4_file_t)", rfs4_file_dcmd }, { "rfs4_deleg", "?[-v]", "dump NFSv4 rfs4_deleg_state_t structures from bucket data\n" "\t\t\t(Optional address of rfs4_deleg_state_t)", rfs4_deleg_dcmd }, { "rfs4_lo", "?", "dump NFSv4 rfs4_lockowner_t structures from bucket data\n" "\t\t\t(Optional address of rfs4_lockowner_t)", rfs4_lo_dcmd }, { "rfs4_lsid", "?[-v]", "dump NFSv4 rfs4_lo_state_t structures from bucket data\n" "\t\t\t(Optional address of rfs4_lo_state_t)", rfs4_lsid_dcmd }, { "rfs4_client", "?[-c ]", "dump NFSv4 rfs4_client_t structures from bucket data\n" "\t\t\t(Optional address of rfs4_client_t)", rfs4_client_dcmd, rfs4_client_help }, /* NFS server */ { "nfs_expvis", ":", "dump exp_visible_t structure", nfs_expvis_dcmd }, { "nfs_expinfo", ":", "dump struct exportinfo", nfs_expinfo_dcmd }, { "nfs_exptable", "?", "dump exportinfo structures for a zone\n" "\t\t\t(Optional address of zone_t)", nfs_exptable_dcmd }, { "nfs_exptable_path", "?", "dump exportinfo structures for a zone\n" "\t\t\t(Optional address of zone_t)", nfs_exptable_path_dcmd }, { "nfs_nstree", "?[-v]", "dump NFS server pseudo namespace tree for a zone\n" "\t\t\t(Optional address of zone_t)", nfs_nstree_dcmd, nfs_nstree_help }, { "nfs_fid_hashdist", ":[-v]", "show fid hash distribution of the exportinfo table", nfs_fid_hashdist_dcmd, nfs_hashdist_help }, { "nfs_path_hashdist", "[-v]", "show path hash distribution of the exportinfo table", nfs_path_hashdist_dcmd, nfs_hashdist_help }, /* NFSv4 idmap */ { "nfs4_idmap", ":", "dump nfsidmap_t", nfs4_idmap_dcmd }, { "nfs4_idmap_info", "?[u2s | g2s | s2u | s2g ...]", "dump NFSv4 idmap information\n" "\t\t\t(Optional address of zone_t)", nfs4_idmap_info_dcmd, nfs4_idmap_info_help }, /* NFS client */ { "nfs_mntinfo", "?[-v]", "print mntinfo_t information\n" "\t\t\t(Optional address of mntinfo_t)", nfs_mntinfo_dcmd, nfs_mntinfo_help }, { "nfs_servinfo", ":[-v]", "print servinfo_t information", nfs_servinfo_dcmd, nfs_servinfo_help }, /* WIP */ { "nfs4_mntinfo", "?[-mv]", "print mntinfo4_t information\n" "\t\t\t(Optional address of mntinfo4_t)", nfs4_mntinfo_dcmd, nfs4_mntinfo_help }, { "nfs4_servinfo", ":[-v]", "print servinfo4_t information", nfs4_servinfo_dcmd, nfs4_servinfo_help }, { "nfs4_server_info", "?[-cs]", "print nfs4_server_t information", nfs4_server_info_dcmd, nfs4_server_info_help }, /* WIP */ { "nfs4_mimsg", ":[-s]", "print queued messages for given address of mi_msg_list", nfs4_mimsg_dcmd, nfs4_mimsg_help }, { "nfs4_fname", ":", "print path name of nfs4_fname_t specified", nfs4_fname_dcmd }, { "nfs4_svnode", ":", "print svnode_t info at specified address", nfs4_svnode_dcmd }, /* NFSv2/3/4 clnt */ { "nfs_vfs", "?[-v]", "print all nfs vfs struct (-v for mntinfo)\n" "\t\t\t(Optional address of vfs_t)", nfs_vfs_dcmd }, /* NFSv4 clnt */ { "nfs_rnode4", "?", "dump NFSv4 rnodes\n" "\t\t\t(Optional address of rnode4_t)", nfs_rnode4_dcmd, nfs_rnode4_help }, { "nfs4_diag", "?[-s]", "print queued recovery messages for NFSv4 client\n" "\t\t\t(Optional address of mntinfo4_t)", nfs4_diag_dcmd, nfs4_diag_help }, { "nfs_rnode4find", ":", "dump NFSv4 rnodes for given vfs_t", nfs_rnode4find_dcmd, nfs_rnode4find_help }, { "nfs4_foo", ":[-v]", "dump free open owners for NFSv4 client", nfs4_foo_dcmd }, { "nfs4_oob", ":[-v]", "dump open owners for NFSv4 client", nfs4_oob_dcmd }, { "nfs4_os", "?[-v]", "dump open streams for NFSv4 Client\n" "\t\t\t(Optional address of rnode4_t)", nfs4_os_dcmd }, /* generic commands */ { "nfs_stat", "?[-csb][-234][-anr] | $[count]", "Print NFS statistics for zone\n" "\t\t\t(Optional address of zone_t)", nfs_stat_dcmd }, { "nfs_help", "[-dw]", "Show nfs commands", nfs_help_dcmd }, {NULL, NULL, NULL, NULL} }; static const mdb_walker_t walkers[] = { /* svc */ { "svc_pool", "walk SVCPOOL structs for given zone", svc_pool_walk_init, svc_pool_walk_step }, { "svc_mxprt", "walk master xprts", svc_mxprt_walk_init, svc_mxprt_walk_step }, /* rfs4 */ { "rfs4_db_tbl", "walk NFSv4 server rfs4_table_t structs", rfs4_db_tbl_walk_init, rfs4_db_tbl_walk_step }, { "rfs4_db_idx", "walk NFSv4 server rfs4_index_t structs", rfs4_db_idx_walk_init, rfs4_db_idx_walk_step }, { "rfs4_db_bkt", "walk NFSv4 server buckets for given index", rfs4_db_bkt_walk_init, rfs4_db_bkt_walk_step, rfs4_db_bkt_walk_fini }, /* NFS server */ { "nfs_expinfo", "walk exportinfo structures from the exptable", nfs_expinfo_walk_init, hash_table_walk_step, nfs_expinfo_walk_fini, &nfs_expinfo_arg }, { "nfs_expinfo_path", "walk exportinfo structures from the exptable_path_hash", nfs_expinfo_walk_init, hash_table_walk_step, nfs_expinfo_walk_fini, &nfs_expinfo_path_arg }, { "nfs_expvis", "walk list of exp_visible structs", nfs_expvis_walk_init, nfs_expvis_walk_step }, { "nfssrv_globals", "walk list of zones NFS globals", nfssrv_globals_walk_init, nfssrv_globals_walk_step }, /* NFSv4 idmap */ { "nfs4_u2s", "walk uid-to-string idmap cache for given zone", nfs4_idmap_walk_init, hash_table_walk_step, nfs4_idmap_walk_fini, (void *)OFFSETOF(struct nfsidmap_globals, u2s_ci) }, { "nfs4_s2u", "walk string-to-uid idmap cache for given zone", nfs4_idmap_walk_init, hash_table_walk_step, nfs4_idmap_walk_fini, (void *)OFFSETOF(struct nfsidmap_globals, s2u_ci) }, { "nfs4_g2s", "walk gid-to-string idmap cache for given zone", nfs4_idmap_walk_init, hash_table_walk_step, nfs4_idmap_walk_fini, (void *)OFFSETOF(struct nfsidmap_globals, g2s_ci) }, { "nfs4_s2g", "walk string-to-gid idmap cache for given zone", nfs4_idmap_walk_init, hash_table_walk_step, nfs4_idmap_walk_fini, (void *)OFFSETOF(struct nfsidmap_globals, s2g_ci) }, /* NFS client */ { "nfs_rtable", "walk rnodes in rtable cache", nfs_rtable_walk_init, hash_table_walk_step, hash_table_walk_fini, &nfs_rtable_arg }, { "nfs_rtable4", "walk rnode4s in rtable4 cache", nfs_rtable4_walk_init, hash_table_walk_step, hash_table_walk_fini, &nfs_rtable4_arg }, { "nfs_vfs", "walk NFS-mounted vfs structs", nfs_vfs_walk_init, nfs_vfs_walk_step, nfs_vfs_walk_fini }, { "nfs_mnt", "walk NFSv2/3-mounted vfs structs, pass mntinfo", nfs_mnt_walk_init, nfs_mnt_walk_step, nfs_mnt_walk_fini }, { "nfs4_mnt", "walk NFSv4-mounted vfs structs, pass mntinfo4", nfs4_mnt_walk_init, nfs4_mnt_walk_step, nfs4_mnt_walk_fini }, { "nfs_serv", "walk linkedlist of servinfo structs", nfs_serv_walk_init, nfs_serv_walk_step }, { "nfs4_serv", "walk linkedlist of servinfo4 structs", nfs4_serv_walk_init, nfs4_serv_walk_step }, { "nfs4_svnode", "walk svnode list at given svnode address", nfs4_svnode_walk_init, nfs4_svnode_walk_step }, { "nfs4_server", "walk nfs4_server_t structs", nfs4_server_walk_init, nfs4_server_walk_step }, { "nfs_async", "walk list of async requests", nfs_async_walk_init, nfs_async_walk_step }, { "nfs4_async", "walk list of NFSv4 async requests", nfs4_async_walk_init, nfs4_async_walk_step }, { "nfs_acache_rnode", "walk acache entries for a given rnode", nfs_acache_rnode_walk_init, nfs_acache_rnode_walk_step }, { "nfs_acache", "walk entire nfs_access_cache", nfs_acache_walk_init, hash_table_walk_step, nfs_acache_walk_fini }, { "nfs_acache4_rnode", "walk acache4 entries for a given NFSv4 rnode", nfs_acache4_rnode_walk_init, nfs_acache4_rnode_walk_step }, { "nfs_acache4", "walk entire nfs4_access_cache", nfs_acache4_walk_init, hash_table_walk_step, nfs_acache4_walk_fini }, {NULL, NULL, NULL, NULL} }; static int nfs_help_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i = 0; uint_t opt_d = FALSE; uint_t opt_w = FALSE; if ((flags & DCMD_ADDRSPEC) != 0) return (DCMD_USAGE); if (argc == 0) { mdb_printf("::nfs_help -w -d\n"); mdb_printf(" -w Will show nfs specific walkers\n"); mdb_printf(" -d Will show nfs specific dcmds\n"); return (DCMD_ERR); } if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &opt_d, 'w', MDB_OPT_SETBITS, TRUE, &opt_w, NULL) != argc) return (DCMD_USAGE); if (opt_d) { for (i = 0; dcmds[i].dc_name != NULL; i++) mdb_printf("%-20s %s\n", dcmds[i].dc_name, dcmds[i].dc_descr); } if (opt_w) { for (i = 0; walkers[i].walk_name != NULL; i++) mdb_printf("%-20s %s\n", walkers[i].walk_name, walkers[i].walk_descr); } return (DCMD_OK); } static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include "nfs_clnt.h" #include "common.h" /* * Common functions */ static void nfs_print_io_stat(uintptr_t kstat_addr) { kstat_t kstat; kstat_io_t kstat_io; mdb_printf("IO statistics for this mount:\n"); mdb_inc_indent(2); if (mdb_vread(&kstat, sizeof (kstat), kstat_addr) == -1 || mdb_vread(&kstat_io, sizeof (kstat_io), (uintptr_t)KSTAT_IO_PTR(&kstat)) == -1) { mdb_printf("No. of bytes read: %9s\n", "??"); mdb_printf("No. of read operations: %9s\n", "??"); mdb_printf("No. of bytes written: %9s\n", "??"); mdb_printf("No. of write operations: %9s\n", "??"); } else { mdb_printf("No. of bytes read: %9llu\n", kstat_io.nread); mdb_printf("No. of read operations: %9lu\n", kstat_io.reads); mdb_printf("No. of bytes written: %9llu\n", kstat_io.nwritten); mdb_printf("No. of write operations: %9lu\n", kstat_io.writes); } mdb_dec_indent(2); } static int walk_count_cb(uintptr_t addr, const void *data, void *cb_data) { (*(size_t *)cb_data)++; return (WALK_NEXT); } #define TBL_ENTRY(e) {#e, e} static const struct { const char *str; nfs_opnum4 op; } nfs4_op_tbl[] = { TBL_ENTRY(OP_ACCESS), TBL_ENTRY(OP_CLOSE), TBL_ENTRY(OP_COMMIT), TBL_ENTRY(OP_CREATE), TBL_ENTRY(OP_DELEGPURGE), TBL_ENTRY(OP_DELEGRETURN), TBL_ENTRY(OP_GETATTR), TBL_ENTRY(OP_GETFH), TBL_ENTRY(OP_LINK), TBL_ENTRY(OP_LOCK), TBL_ENTRY(OP_LOCKT), TBL_ENTRY(OP_LOCKU), TBL_ENTRY(OP_LOOKUP), TBL_ENTRY(OP_LOOKUPP), TBL_ENTRY(OP_NVERIFY), TBL_ENTRY(OP_OPEN), TBL_ENTRY(OP_OPENATTR), TBL_ENTRY(OP_OPEN_CONFIRM), TBL_ENTRY(OP_OPEN_DOWNGRADE), TBL_ENTRY(OP_PUTFH), TBL_ENTRY(OP_PUTPUBFH), TBL_ENTRY(OP_PUTROOTFH), TBL_ENTRY(OP_READ), TBL_ENTRY(OP_READDIR), TBL_ENTRY(OP_READLINK), TBL_ENTRY(OP_REMOVE), TBL_ENTRY(OP_RENAME), TBL_ENTRY(OP_RENEW), TBL_ENTRY(OP_RESTOREFH), TBL_ENTRY(OP_SAVEFH), TBL_ENTRY(OP_SECINFO), TBL_ENTRY(OP_SETATTR), TBL_ENTRY(OP_SETCLIENTID), TBL_ENTRY(OP_SETCLIENTID_CONFIRM), TBL_ENTRY(OP_VERIFY), TBL_ENTRY(OP_WRITE), TBL_ENTRY(OP_RELEASE_LOCKOWNER), TBL_ENTRY(OP_ILLEGAL), TBL_ENTRY(OP_CCREATE), TBL_ENTRY(OP_CLINK), TBL_ENTRY(OP_CLOOKUP), TBL_ENTRY(OP_COPEN), TBL_ENTRY(OP_CPUTFH), TBL_ENTRY(OP_CREMOVE), TBL_ENTRY(OP_CRENAME), TBL_ENTRY(OP_CSECINFO), {NULL} }; static const char * nfs4_op_str(nfs_opnum4 op) { int i; for (i = 0; nfs4_op_tbl[i].str != NULL; i++) if (nfs4_op_tbl[i].op == op) return (nfs4_op_tbl[i].str); return ("??"); } static const struct { const char *str; nfs4_recov_t action; } nfs4_recov_tbl[] = { TBL_ENTRY(NR_UNUSED), TBL_ENTRY(NR_CLIENTID), TBL_ENTRY(NR_OPENFILES), TBL_ENTRY(NR_FHEXPIRED), TBL_ENTRY(NR_FAILOVER), TBL_ENTRY(NR_WRONGSEC), TBL_ENTRY(NR_EXPIRED), TBL_ENTRY(NR_BAD_STATEID), TBL_ENTRY(NR_BADHANDLE), TBL_ENTRY(NR_BAD_SEQID), TBL_ENTRY(NR_OLDSTATEID), TBL_ENTRY(NR_GRACE), TBL_ENTRY(NR_DELAY), TBL_ENTRY(NR_LOST_LOCK), TBL_ENTRY(NR_LOST_STATE_RQST), TBL_ENTRY(NR_STALE), TBL_ENTRY(NR_MOVED), {NULL} }; static const char * nfs4_recov_str(nfs4_recov_t action) { int i; for (i = 0; nfs4_recov_tbl[i].str != NULL; i++) if (nfs4_recov_tbl[i].action == action) return (nfs4_recov_tbl[i].str); return ("??"); } static const struct { const char *str; nfsstat4 stat; } nfs4_stat_tbl[] = { TBL_ENTRY(NFS4_OK), TBL_ENTRY(NFS4ERR_PERM), TBL_ENTRY(NFS4ERR_NOENT), TBL_ENTRY(NFS4ERR_IO), TBL_ENTRY(NFS4ERR_NXIO), TBL_ENTRY(NFS4ERR_ACCESS), TBL_ENTRY(NFS4ERR_EXIST), TBL_ENTRY(NFS4ERR_XDEV), TBL_ENTRY(NFS4ERR_NOTDIR), TBL_ENTRY(NFS4ERR_ISDIR), TBL_ENTRY(NFS4ERR_INVAL), TBL_ENTRY(NFS4ERR_FBIG), TBL_ENTRY(NFS4ERR_NOSPC), TBL_ENTRY(NFS4ERR_ROFS), TBL_ENTRY(NFS4ERR_MLINK), TBL_ENTRY(NFS4ERR_NAMETOOLONG), TBL_ENTRY(NFS4ERR_NOTEMPTY), TBL_ENTRY(NFS4ERR_DQUOT), TBL_ENTRY(NFS4ERR_STALE), TBL_ENTRY(NFS4ERR_BADHANDLE), TBL_ENTRY(NFS4ERR_BAD_COOKIE), TBL_ENTRY(NFS4ERR_NOTSUPP), TBL_ENTRY(NFS4ERR_TOOSMALL), TBL_ENTRY(NFS4ERR_SERVERFAULT), TBL_ENTRY(NFS4ERR_BADTYPE), TBL_ENTRY(NFS4ERR_DELAY), TBL_ENTRY(NFS4ERR_SAME), TBL_ENTRY(NFS4ERR_DENIED), TBL_ENTRY(NFS4ERR_EXPIRED), TBL_ENTRY(NFS4ERR_LOCKED), TBL_ENTRY(NFS4ERR_GRACE), TBL_ENTRY(NFS4ERR_FHEXPIRED), TBL_ENTRY(NFS4ERR_SHARE_DENIED), TBL_ENTRY(NFS4ERR_WRONGSEC), TBL_ENTRY(NFS4ERR_CLID_INUSE), TBL_ENTRY(NFS4ERR_RESOURCE), TBL_ENTRY(NFS4ERR_MOVED), TBL_ENTRY(NFS4ERR_NOFILEHANDLE), TBL_ENTRY(NFS4ERR_MINOR_VERS_MISMATCH), TBL_ENTRY(NFS4ERR_STALE_CLIENTID), TBL_ENTRY(NFS4ERR_STALE_STATEID), TBL_ENTRY(NFS4ERR_OLD_STATEID), TBL_ENTRY(NFS4ERR_BAD_STATEID), TBL_ENTRY(NFS4ERR_BAD_SEQID), TBL_ENTRY(NFS4ERR_NOT_SAME), TBL_ENTRY(NFS4ERR_LOCK_RANGE), TBL_ENTRY(NFS4ERR_SYMLINK), TBL_ENTRY(NFS4ERR_RESTOREFH), TBL_ENTRY(NFS4ERR_LEASE_MOVED), TBL_ENTRY(NFS4ERR_ATTRNOTSUPP), TBL_ENTRY(NFS4ERR_NO_GRACE), TBL_ENTRY(NFS4ERR_RECLAIM_BAD), TBL_ENTRY(NFS4ERR_RECLAIM_CONFLICT), TBL_ENTRY(NFS4ERR_BADXDR), TBL_ENTRY(NFS4ERR_LOCKS_HELD), TBL_ENTRY(NFS4ERR_OPENMODE), TBL_ENTRY(NFS4ERR_BADOWNER), TBL_ENTRY(NFS4ERR_BADCHAR), TBL_ENTRY(NFS4ERR_BADNAME), TBL_ENTRY(NFS4ERR_BAD_RANGE), TBL_ENTRY(NFS4ERR_LOCK_NOTSUPP), TBL_ENTRY(NFS4ERR_OP_ILLEGAL), TBL_ENTRY(NFS4ERR_DEADLOCK), TBL_ENTRY(NFS4ERR_FILE_OPEN), TBL_ENTRY(NFS4ERR_ADMIN_REVOKED), TBL_ENTRY(NFS4ERR_CB_PATH_DOWN), {NULL} }; static const char * nfs4_stat_str(nfsstat4 stat) { int i; for (i = 0; nfs4_stat_tbl[i].str != NULL; i++) if (nfs4_stat_tbl[i].stat == stat) return (nfs4_stat_tbl[i].str); return ("??"); } static const struct { const char *str; nfs4_tag_type_t tt; } nfs4_tag_tbl[] = { {"", TAG_NONE}, {"access", TAG_ACCESS}, {"close", TAG_CLOSE}, {"lost close", TAG_CLOSE_LOST}, {"undo close", TAG_CLOSE_UNDO}, {"commit", TAG_COMMIT}, {"delegreturn", TAG_DELEGRETURN}, {"fsinfo", TAG_FSINFO}, {"get symlink text", TAG_GET_SYMLINK}, {"getattr", TAG_GETATTR}, {"getattr fslocation", TAG_GETATTR_FSLOCATION}, {"inactive", TAG_INACTIVE}, {"link", TAG_LINK}, {"lock", TAG_LOCK}, {"reclaim lock", TAG_LOCK_RECLAIM}, {"resend lock", TAG_LOCK_RESEND}, {"reinstate lock", TAG_LOCK_REINSTATE}, {"unknown lock", TAG_LOCK_UNKNOWN}, {"lock test", TAG_LOCKT}, {"unlock", TAG_LOCKU}, {"resend locku", TAG_LOCKU_RESEND}, {"reinstate unlock", TAG_LOCKU_REINSTATE}, {"lookup", TAG_LOOKUP}, {"lookup parent", TAG_LOOKUP_PARENT}, {"lookup valid", TAG_LOOKUP_VALID}, {"lookup valid parent", TAG_LOOKUP_VPARENT}, {"mkdir", TAG_MKDIR}, {"mknod", TAG_MKNOD}, {"mount", TAG_MOUNT}, {"open", TAG_OPEN}, {"open confirm", TAG_OPEN_CONFIRM}, {"lost open confirm", TAG_OPEN_CONFIRM_LOST}, {"open downgrade", TAG_OPEN_DG}, {"lost open downgrade", TAG_OPEN_DG_LOST}, {"lost open", TAG_OPEN_LOST}, {"openattr", TAG_OPENATTR}, {"pathconf", TAG_PATHCONF}, {"putrootfh", TAG_PUTROOTFH}, {"read", TAG_READ}, {"readahead", TAG_READAHEAD}, {"readdir", TAG_READDIR}, {"readlink", TAG_READLINK}, {"relock", TAG_RELOCK}, {"remap lookup", TAG_REMAP_LOOKUP}, {"remap lookup attr dir", TAG_REMAP_LOOKUP_AD}, {"remap lookup named attrs", TAG_REMAP_LOOKUP_NA}, {"remap mount", TAG_REMAP_MOUNT}, {"rmdir", TAG_RMDIR}, {"remove", TAG_REMOVE}, {"rename", TAG_RENAME}, {"rename volatile fh", TAG_RENAME_VFH}, {"renew", TAG_RENEW}, {"reopen", TAG_REOPEN}, {"lost reopen", TAG_REOPEN_LOST}, {"secinfo", TAG_SECINFO}, {"setattr", TAG_SETATTR}, {"setclientid", TAG_SETCLIENTID}, {"setclientid_confirm", TAG_SETCLIENTID_CF}, {"symlink", TAG_SYMLINK}, {"write", TAG_WRITE}, {NULL, 0} }; static const char * nfs4_tag_str(nfs4_tag_type_t tt) { int i; for (i = 0; nfs4_tag_tbl[i].str != NULL; i++) if (nfs4_tag_tbl[i].tt == tt) return (nfs4_tag_tbl[i].str); return ("??"); } /* * nfs_mntinfo dcmd implementation */ static const mdb_bitmask_t nfs_mi_flags[] = { {"MI_HARD", MI_HARD, MI_HARD}, {"MI_PRINTED", MI_PRINTED, MI_PRINTED}, {"MI_INT", MI_INT, MI_INT}, {"MI_DOWN", MI_DOWN, MI_DOWN}, {"MI_NOAC", MI_NOAC, MI_NOAC}, {"MI_NOCTO", MI_NOCTO, MI_NOCTO}, {"MI_DYNAMIC", MI_DYNAMIC, MI_DYNAMIC}, {"MI_LLOCK", MI_LLOCK, MI_LLOCK}, {"MI_GRPID", MI_GRPID, MI_GRPID}, {"MI_RPCTIMESYNC", MI_RPCTIMESYNC, MI_RPCTIMESYNC}, {"MI_LINK", MI_LINK, MI_LINK}, {"MI_SYMLINK", MI_SYMLINK, MI_SYMLINK}, {"MI_READDIRONLY", MI_READDIRONLY, MI_READDIRONLY}, {"MI_ACL", MI_ACL, MI_ACL}, {"MI_BINDINPROG", MI_BINDINPROG, MI_BINDINPROG}, {"MI_LOOPBACK", MI_LOOPBACK, MI_LOOPBACK}, {"MI_SEMISOFT", MI_SEMISOFT, MI_SEMISOFT}, {"MI_NOPRINT", MI_NOPRINT, MI_NOPRINT}, {"MI_DIRECTIO", MI_DIRECTIO, MI_DIRECTIO}, {"MI_EXTATTR", MI_EXTATTR, MI_EXTATTR}, {"MI_ASYNC_MGR_STOP", MI_ASYNC_MGR_STOP, MI_ASYNC_MGR_STOP}, {"MI_DEAD", MI_DEAD, MI_DEAD}, {NULL, 0, 0} }; static int nfs_print_mntinfo_cb(uintptr_t addr, const void *data, void *cb_data) { const mntinfo_t *mi = data; uintptr_t nfs3_ops; vfs_t vfs; char buf[MAXPATHLEN]; uint_t opt_v = *(uint_t *)cb_data; int i; if (mdb_readvar(&nfs3_ops, "nfs3_vfsops") == -1) { mdb_warn("failed to read %s", "nfs3_vfsops"); return (WALK_ERR); } if (mdb_vread(&vfs, sizeof (vfs), (uintptr_t)mi->mi_vfsp) == -1) { mdb_warn("failed to read vfs_t at %p", mi->mi_vfsp); return (WALK_ERR); } mdb_printf("NFS Version: %d\n", nfs3_ops == (uintptr_t)vfs.vfs_op ? 3 : 2); mdb_inc_indent(2); mdb_printf("mi_flags: %b\n", mi->mi_flags, nfs_mi_flags); if (mdb_read_refstr((uintptr_t)vfs.vfs_mntpt, buf, sizeof (buf)) == -1) strcpy(buf, "??"); mdb_printf("mount point: %s\n", buf); if (mdb_read_refstr((uintptr_t)vfs.vfs_resource, buf, sizeof (buf)) == -1) strcpy(buf, "??"); mdb_printf("mount from: %s\n", buf); mdb_dec_indent(2); mdb_printf("\n"); if (!opt_v) return (WALK_NEXT); mdb_inc_indent(2); mdb_printf("mi_zone = %p\n", mi->mi_zone); mdb_printf("mi_curread = %i, mi_curwrite = %i, mi_retrans = %i, " "mi_timeo = %i\n", mi->mi_curread, mi->mi_curwrite, mi->mi_retrans, mi->mi_timeo); mdb_printf("mi_acregmin = %lu, mi_acregmax = %lu, mi_acdirmin = %lu, " "mi_acdirmax = %lu\n", mi->mi_acregmin, mi->mi_acregmax, mi->mi_acdirmin, mi->mi_acdirmax); mdb_printf("\nServer list: %p\n", mi->mi_servers); mdb_inc_indent(2); if (mdb_pwalk_dcmd("nfs_serv", "nfs_servinfo", 0, NULL, (uintptr_t)mi->mi_servers) == -1) mdb_printf("??\n"); mdb_dec_indent(2); mdb_printf("Current Server: %p ", mi->mi_curr_serv); if (mdb_call_dcmd("nfs_servinfo", (uintptr_t)mi->mi_curr_serv, DCMD_ADDRSPEC, 0, NULL) == -1) mdb_printf("??\n"); mdb_printf( "\nTotal: Server Non-responses = %u, Server Failovers = %u\n", mi->mi_noresponse, mi->mi_failover); if (mi->mi_io_kstats != NULL) nfs_print_io_stat((uintptr_t)mi->mi_io_kstats); mdb_printf("\nAsync Request queue:\n"); mdb_inc_indent(2); mdb_printf("max threads = %u, active threads = %u\n", mi->mi_max_threads, mi->mi_threads[NFS_ASYNC_QUEUE]); mdb_printf("Async reserved page operation only active threads = %u\n", mi->mi_threads[NFS_ASYNC_PGOPS_QUEUE]); mdb_printf("number requests queued:\n"); for (i = 0; i < NFS_ASYNC_TYPES; i++) { const char *opname; size_t count = 0; switch (i) { case NFS_PUTAPAGE: opname = "PUTAPAGE"; break; case NFS_PAGEIO: opname = "PAGEIO"; break; case NFS_COMMIT: opname = "COMMIT"; break; case NFS_READ_AHEAD: opname = "READ_AHEAD"; break; case NFS_READDIR: opname = "READDIR"; break; case NFS_INACTIVE: opname = "INACTIVE"; break; default: opname = "??"; break; } if (mi->mi_async_reqs[i] == NULL || mdb_pwalk("nfs_async", walk_count_cb, &count, (uintptr_t)mi->mi_async_reqs[i]) == -1) mdb_printf("\t%s = ??", opname); else mdb_printf("\t%s = %llu", opname, count); } mdb_printf("\n"); mdb_dec_indent(2); if (mi->mi_printftime) mdb_printf("\nLast error report time = %Y\n", mi->mi_printftime); mdb_dec_indent(2); mdb_printf("\n"); return (WALK_NEXT); } int nfs_mntinfo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mntinfo_t mi; uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { if (mdb_walk("nfs_mnt", nfs_print_mntinfo_cb, &opt_v) == -1) { mdb_warn("failed to walk nfs_mnt"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&mi, sizeof (mi), addr) == -1) { mdb_warn("failed to read mntinfo_t"); return (DCMD_ERR); } return (nfs_print_mntinfo_cb(addr, &mi, &opt_v) == WALK_ERR ? DCMD_ERR : DCMD_OK); } void nfs_mntinfo_help(void) { mdb_printf("-v verbose information\n"); } /* * nfs_servinfo dcmd implementation */ int nfs_servinfo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { servinfo_t si; uint_t opt_v = FALSE; const char *addr_str; struct knetconfig knconf; char *hostname; int i; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of servinfo_t\n"); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&si, sizeof (si), addr) == -1) { mdb_warn("can't read servinfo_t"); return (DCMD_ERR); } addr_str = common_netbuf_str(&si.sv_addr); if (!opt_v) { mdb_printf("%s\n", addr_str); return (DCMD_OK); } mdb_printf("secdata ptr = %p\n", si.sv_secdata); mdb_printf("address = "); if (mdb_vread(&knconf, sizeof (knconf), (uintptr_t)si.sv_knconf) == -1) { mdb_printf("?\?/?\?/??"); } else { char knc_str[KNC_STRSIZE]; mdb_printf("%u", knconf.knc_semantics); if (mdb_readstr(knc_str, sizeof (knc_str), (uintptr_t)knconf.knc_protofmly) == -1) mdb_printf("/??"); else mdb_printf("/%s", knc_str); if (mdb_readstr(knc_str, sizeof (knc_str), (uintptr_t)knconf.knc_proto) == -1) mdb_printf("/??"); else mdb_printf("/%s", knc_str); } mdb_printf("/%s\n", addr_str); if (si.sv_hostnamelen <= 0 || (hostname = mdb_alloc(si.sv_hostnamelen, UM_NOSLEEP | UM_GC)) == NULL || mdb_readstr(hostname, si.sv_hostnamelen, (uintptr_t)si.sv_hostname) == -1) mdb_printf("hostname = ??\n"); else mdb_printf("hostname = %s\n", hostname); mdb_printf("filehandle = "); if (si.sv_fhandle.fh_len >= 0 && si.sv_fhandle.fh_len <= NFS_FHANDLE_LEN) for (i = 0; i < si.sv_fhandle.fh_len; i++) mdb_printf("%02x", (unsigned char)si.sv_fhandle.fh_buf[i]); else mdb_printf("??"); mdb_printf("\n\n"); return (DCMD_OK); } void nfs_servinfo_help(void) { mdb_printf("-v verbose information\n"); } /* * nfs4_mntinfo dcmd implementation */ static const mdb_bitmask_t nfs_mi4_flags[] = { {"MI4_HARD", MI4_HARD, MI4_HARD}, {"MI4_PRINTED", MI4_PRINTED, MI4_PRINTED}, {"MI4_INT", MI4_INT, MI4_INT}, {"MI4_DOWN", MI4_DOWN, MI4_DOWN}, {"MI4_NOAC", MI4_NOAC, MI4_NOAC}, {"MI4_NOCTO", MI4_NOCTO, MI4_NOCTO}, {"MI4_LLOCK", MI4_LLOCK, MI4_LLOCK}, {"MI4_GRPID", MI4_GRPID, MI4_GRPID}, {"MI4_SHUTDOWN", MI4_SHUTDOWN, MI4_SHUTDOWN}, {"MI4_LINK", MI4_LINK, MI4_LINK}, {"MI4_SYMLINK", MI4_SYMLINK, MI4_SYMLINK}, {"MI4_EPHEMERAL_RECURSED", MI4_EPHEMERAL_RECURSED, MI4_EPHEMERAL_RECURSED}, {"MI4_ACL", MI4_ACL, MI4_ACL}, {"MI4_MIRRORMOUNT", MI4_MIRRORMOUNT, MI4_MIRRORMOUNT}, {"MI4_REFERRAL", MI4_REFERRAL, MI4_REFERRAL}, {"MI4_EPHEMERAL", MI4_EPHEMERAL, MI4_EPHEMERAL}, {"MI4_NOPRINT", MI4_NOPRINT, MI4_NOPRINT}, {"MI4_DIRECTIO", MI4_DIRECTIO, MI4_DIRECTIO}, {"MI4_RECOV_ACTIV", MI4_RECOV_ACTIV, MI4_RECOV_ACTIV}, {"MI4_REMOVE_ON_LAST_CLOSE", MI4_REMOVE_ON_LAST_CLOSE, MI4_REMOVE_ON_LAST_CLOSE}, {"MI4_RECOV_FAIL", MI4_RECOV_FAIL, MI4_RECOV_FAIL}, {"MI4_PUBLIC", MI4_PUBLIC, MI4_PUBLIC}, {"MI4_MOUNTING", MI4_MOUNTING, MI4_MOUNTING}, {"MI4_POSIX_LOCK", MI4_POSIX_LOCK, MI4_POSIX_LOCK}, {"MI4_LOCK_DEBUG", MI4_LOCK_DEBUG, MI4_LOCK_DEBUG}, {"MI4_DEAD", MI4_DEAD, MI4_DEAD}, {"MI4_INACTIVE_IDLE", MI4_INACTIVE_IDLE, MI4_INACTIVE_IDLE}, {"MI4_BADOWNER_DEBUG", MI4_BADOWNER_DEBUG, MI4_BADOWNER_DEBUG}, {"MI4_ASYNC_MGR_STOP", MI4_ASYNC_MGR_STOP, MI4_ASYNC_MGR_STOP}, {"MI4_TIMEDOUT", MI4_TIMEDOUT, MI4_TIMEDOUT}, {NULL, 0, 0} }; static const mdb_bitmask_t nfs_mi4_recovflags[] = { {"MI4R_NEED_CLIENTID", MI4R_NEED_CLIENTID, MI4R_NEED_CLIENTID}, {"MI4R_REOPEN_FILES", MI4R_REOPEN_FILES, MI4R_REOPEN_FILES}, {"MI4R_NEED_SECINFO", MI4R_NEED_SECINFO, MI4R_NEED_SECINFO}, {"MI4R_NEED_NEW_SERVER", MI4R_NEED_NEW_SERVER, MI4R_NEED_NEW_SERVER}, {"MI4R_REMAP_FILES", MI4R_REMAP_FILES, MI4R_REMAP_FILES}, {"MI4R_SRV_REBOOT", MI4R_SRV_REBOOT, MI4R_SRV_REBOOT}, {"MI4R_LOST_STATE", MI4R_LOST_STATE, MI4R_LOST_STATE}, {"MI4R_BAD_SEQID", MI4R_BAD_SEQID, MI4R_BAD_SEQID}, {"MI4R_MOVED", MI4R_MOVED, MI4R_MOVED}, {NULL, 0, 0} }; int nfs4_mntinfo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mntinfo4_t mi; vfs_t vfs; char buf[MAXPATHLEN]; uint_t opt_m = FALSE; uint_t opt_v = FALSE; if ((flags & DCMD_ADDRSPEC) == 0) { if (mdb_walk_dcmd("nfs4_mnt", "nfs4_mntinfo", argc, argv) == -1) { mdb_warn("failed to walk nfs4_mnt"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'm', MDB_OPT_SETBITS, TRUE, &opt_m, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&mi, sizeof (mi), addr) == -1) { mdb_warn("failed to read mntinfo4_t at %p", addr); return (DCMD_ERR); } if (mdb_vread(&vfs, sizeof (vfs), (uintptr_t)mi.mi_vfsp) == -1) { mdb_warn("failed to read vfs_t at %p", mi.mi_vfsp); return (DCMD_ERR); } mdb_printf("+--------------------------------------+\n"); mdb_printf(" mntinfo4_t: 0x%p\n", addr); mdb_printf(" NFS Version: 4\n"); mdb_printf(" mi_flags: %b\n", mi.mi_flags, nfs_mi4_flags); mdb_printf(" mi_error: %u\n", mi.mi_error); mdb_printf(" mi_open_files: %i\n", mi.mi_open_files); mdb_printf(" mi_msg_count: %i\n", mi.mi_msg_count); mdb_printf(" mi_recovflags: %b\n", mi.mi_recovflags, nfs_mi4_recovflags); mdb_printf("mi_recovthread: 0x%p\n", mi.mi_recovthread); mdb_printf("mi_in_recovery: %i\n", mi.mi_in_recovery); if (mdb_read_refstr((uintptr_t)vfs.vfs_mntpt, buf, sizeof (buf)) == -1) strcpy(buf, "??"); mdb_printf(" mount point: %s\n", buf); if (mdb_read_refstr((uintptr_t)vfs.vfs_resource, buf, sizeof (buf)) == -1) strcpy(buf, "??"); mdb_printf(" mount from: %s\n", buf); if (opt_v) { int i; mdb_printf("\n"); mdb_inc_indent(2); mdb_printf("mi_zone = %p\n", mi.mi_zone); mdb_printf("mi_curread = %i, mi_curwrite = %i, " "mi_retrans = %i, mi_timeo = %i\n", mi.mi_curread, mi.mi_curwrite, mi.mi_retrans, mi.mi_timeo); mdb_printf("mi_acregmin = %lu, mi_acregmax = %lu, " "mi_acdirmin = %lu, mi_acdirmax = %lu\n", mi.mi_acregmin, mi.mi_acregmax, mi.mi_acdirmin, mi.mi_acdirmax); mdb_printf("\nServer list: %p\n", mi.mi_servers); mdb_inc_indent(2); if (mdb_pwalk_dcmd("nfs4_serv", "nfs4_servinfo", 0, NULL, (uintptr_t)mi.mi_servers) == -1) mdb_printf("??\n"); mdb_dec_indent(2); mdb_printf("Current Server: %p ", mi.mi_curr_serv); if (mdb_call_dcmd("nfs4_servinfo", (uintptr_t)mi.mi_curr_serv, DCMD_ADDRSPEC, 0, NULL) == -1) mdb_printf("??\n"); mdb_printf("\nTotal: Server Non-responses = %u, " "Server Failovers = %u\n", mi.mi_noresponse, mi.mi_failover); if (mi.mi_io_kstats != NULL) nfs_print_io_stat((uintptr_t)mi.mi_io_kstats); mdb_printf("\nAsync Request queue:\n"); mdb_inc_indent(2); mdb_printf("max threads = %u, active threads = %u\n", mi.mi_max_threads, mi.mi_threads[NFS4_ASYNC_QUEUE]); mdb_printf("Async reserved page operation only active " "threads = %u\n", mi.mi_threads[NFS4_ASYNC_PGOPS_QUEUE]); mdb_printf("number requests queued:\n"); for (i = 0; i < NFS4_ASYNC_TYPES; i++) { const char *opname; size_t count = 0; switch (i) { case NFS4_PUTAPAGE: opname = "PUTAPAGE"; break; case NFS4_PAGEIO: opname = "PAGEIO"; break; case NFS4_COMMIT: opname = "COMMIT"; break; case NFS4_READ_AHEAD: opname = "READ_AHEAD"; break; case NFS4_READDIR: opname = "READDIR"; break; case NFS4_INACTIVE: opname = "INACTIVE"; break; default: opname = "??"; break; } if (mi.mi_async_reqs[i] != NULL && mdb_pwalk("nfs4_async", walk_count_cb, &count, (uintptr_t)mi.mi_async_reqs[i]) == -1) mdb_printf("\t%s = ??", opname); else mdb_printf("\t%s = %llu", opname, count); } mdb_printf("\n"); mdb_dec_indent(2); mdb_dec_indent(2); } return (DCMD_OK); } void nfs4_mntinfo_help(void) { mdb_printf("::nfs4_mntinfo -> gives mntinfo4_t information\n" " ::nfs4_mntinfo -> walks thru all NFSv4 mntinfo4_t\n" "Each of these formats also takes the following argument\n" " -v -> Verbose output\n"); } /* * nfs4_servinfo dcmd implementation */ int nfs4_servinfo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { servinfo4_t si; uint_t opt_v = FALSE; const char *addr_str; struct knetconfig knconf; char *hostname; int i; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of servinfo4_t\n"); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&si, sizeof (si), addr) == -1) { mdb_warn("can't read servinfo_t"); return (DCMD_ERR); } addr_str = common_netbuf_str(&si.sv_addr); if (!opt_v) { mdb_printf("%s\n", addr_str); return (DCMD_OK); } mdb_printf("secdata ptr = %p\n", si.sv_secdata); mdb_printf("address = "); if (mdb_vread(&knconf, sizeof (knconf), (uintptr_t)si.sv_knconf) == -1) { mdb_printf("?\?/?\?/??"); } else { char knc_str[KNC_STRSIZE]; mdb_printf("%u", knconf.knc_semantics); if (mdb_readstr(knc_str, sizeof (knc_str), (uintptr_t)knconf.knc_protofmly) == -1) mdb_printf("/??"); else mdb_printf("/%s", knc_str); if (mdb_readstr(knc_str, sizeof (knc_str), (uintptr_t)knconf.knc_proto) == -1) mdb_printf("/??"); else mdb_printf("/%s", knc_str); } mdb_printf("/%s\n", addr_str); if (si.sv_hostnamelen <= 0 || (hostname = mdb_alloc(si.sv_hostnamelen, UM_NOSLEEP | UM_GC)) == NULL || mdb_readstr(hostname, si.sv_hostnamelen, (uintptr_t)si.sv_hostname) == -1) mdb_printf("hostname = ??\n"); else mdb_printf("hostname = %s\n", hostname); mdb_printf("server filehandle = "); if (si.sv_fhandle.fh_len >= 0 && si.sv_fhandle.fh_len <= NFS4_FHSIZE) for (i = 0; i < si.sv_fhandle.fh_len; i++) mdb_printf("%02x", (unsigned char)si.sv_fhandle.fh_buf[i]); else mdb_printf("??"); mdb_printf("\nparent dir filehandle = "); if (si.sv_pfhandle.fh_len >= 0 && si.sv_pfhandle.fh_len <= NFS4_FHSIZE) for (i = 0; i < si.sv_pfhandle.fh_len; i++) mdb_printf("%02x", (unsigned char)si.sv_pfhandle.fh_buf[i]); else mdb_printf("??"); mdb_printf("\n\n"); return (DCMD_OK); } void nfs4_servinfo_help(void) { mdb_printf("-v verbose information\n"); } /* * nfs4_server_info dcmd implementation */ static const mdb_bitmask_t nfs4_si_flags[] = { {"N4S_CLIENTID_SET", N4S_CLIENTID_SET, N4S_CLIENTID_SET}, {"N4S_CLIENTID_PEND", N4S_CLIENTID_PEND, N4S_CLIENTID_PEND}, {"N4S_CB_PINGED", N4S_CB_PINGED, N4S_CB_PINGED}, {"N4S_CB_WAITER", N4S_CB_WAITER, N4S_CB_WAITER}, {"N4S_INSERTED", N4S_INSERTED, N4S_INSERTED}, {"N4S_BADOWNER_DEBUG", N4S_BADOWNER_DEBUG, N4S_BADOWNER_DEBUG}, {NULL, 0, 0} }; int nfs4_server_info_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { nfs4_server_t srv; char *id_val; uint_t opt_c = FALSE; uint_t opt_s = FALSE; if ((flags & DCMD_ADDRSPEC) == 0) { if (mdb_walk_dcmd("nfs4_server", "nfs4_server_info", argc, argv) == -1) { mdb_warn("nfs4_server walker failed"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'c', MDB_OPT_SETBITS, TRUE, &opt_c, 's', MDB_OPT_SETBITS, TRUE, &opt_s, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&srv, sizeof (srv), addr) == -1) { mdb_warn("failed to read nfs4_server_t at %p", addr); return (DCMD_ERR); } if (srv.saddr.len == 0) return (DCMD_OK); mdb_printf("Address: %p Zone: %i Server: %s\n", addr, srv.zoneid, common_netbuf_str(&srv.saddr)); mdb_printf("Program: %x Flags: %b\n", srv.s_program, srv.s_flags, nfs4_si_flags); mdb_printf("Client ID: %#llx", srv.clientid); if (opt_s) { struct { uint32_t start_time; uint32_t c_id; } *impl_id = (void *)&srv.clientid; mdb_printf(" (srvrboot: %Y, c_id: %u)", impl_id->start_time, impl_id->c_id); } mdb_printf("\nCLIDtoSend: [verifier: %llx", srv.clidtosend.verifier); if (opt_c) { struct { uint32_t sec; uint32_t subsec; } *curtime = (void *)&srv.clidtosend.verifier; mdb_printf(" (%Y + %u nsec)", curtime->sec, curtime->subsec); } mdb_printf(", client identifier: "); id_val = mdb_alloc(srv.clidtosend.id_len, UM_NOSLEEP | UM_GC); if (id_val != NULL && mdb_vread(id_val, srv.clidtosend.id_len, (uintptr_t)srv.clidtosend.id_val) == srv.clidtosend.id_len) { uint_t i; if (opt_c) { size_t l; struct netbuf nb; l = strlen(id_val) + 1; nb.len = nb.maxlen = srv.clidtosend.id_len - l; nb.buf = srv.clidtosend.id_val + l; mdb_printf("(%s/%s) ", id_val, common_netbuf_str(&nb)); } for (i = 0; i < srv.clidtosend.id_len; i++) mdb_printf("%02x", (unsigned char)id_val[i]); } else { mdb_printf("??"); } mdb_printf(" ]\n"); mdb_printf("mntinfo4 list: %p\n", srv.mntinfo4_list); mdb_printf("Deleg list: %p ::walk list\n", addr + OFFSETOF(nfs4_server_t, s_deleg_list)); mdb_printf("Lease Valid: "); switch (srv.lease_valid) { case NFS4_LEASE_INVALID: mdb_printf("INVALID\n"); break; case NFS4_LEASE_VALID: mdb_printf("VALID\n"); break; case NFS4_LEASE_UNINITIALIZED: mdb_printf("UNINIT\n"); break; case NFS4_LEASE_NOT_STARTED: mdb_printf("NOT_STARTED\n"); break; default: mdb_printf("??\n"); break; } mdb_printf("Lease Time: %i sec\n", srv.s_lease_time); mdb_printf("Last renewal: %Y\n", srv.last_renewal_time); mdb_printf("Propgn Delay: %li sec : %li nsec\n", srv.propagation_delay.tv_sec, srv.propagation_delay.tv_nsec); mdb_printf("Credential: %p\n\n", srv.s_cred); return (DCMD_OK); } void nfs4_server_info_help(void) { mdb_printf( "-c assumes client is an illumos NFSv4 Client\n" "-s assumes server is an illumos NFSv4 Server\n" "\n" "The -c option enables the dcmd to decode the client generated\n" "structure CLIDtoSend that is normally opaque to the server.\n" "The -s option enables the dcmd to decode the server generated\n" "structure Client ID that is normally opaque to the client.\n"); } /* * nfs4_mimsg dcmd implementation */ static const struct { const char *str; nfs4_event_type_t et; } nfs4_event_type_tbl[] = { TBL_ENTRY(RE_BAD_SEQID), TBL_ENTRY(RE_BADHANDLE), TBL_ENTRY(RE_CLIENTID), TBL_ENTRY(RE_DEAD_FILE), TBL_ENTRY(RE_END), TBL_ENTRY(RE_FAIL_RELOCK), TBL_ENTRY(RE_FAIL_REMAP_LEN), TBL_ENTRY(RE_FAIL_REMAP_OP), TBL_ENTRY(RE_FAILOVER), TBL_ENTRY(RE_FILE_DIFF), TBL_ENTRY(RE_LOST_STATE), TBL_ENTRY(RE_OPENS_CHANGED), TBL_ENTRY(RE_SIGLOST), TBL_ENTRY(RE_SIGLOST_NO_DUMP), TBL_ENTRY(RE_START), TBL_ENTRY(RE_UNEXPECTED_ACTION), TBL_ENTRY(RE_UNEXPECTED_ERRNO), TBL_ENTRY(RE_UNEXPECTED_STATUS), TBL_ENTRY(RE_WRONGSEC), TBL_ENTRY(RE_LOST_STATE_BAD_OP), TBL_ENTRY(RE_REFERRAL), {NULL} }; static const struct { const char *str; nfs4_fact_type_t ft; } nfs4_fact_type_tbl[] = { TBL_ENTRY(RF_BADOWNER), TBL_ENTRY(RF_ERR), TBL_ENTRY(RF_RENEW_EXPIRED), TBL_ENTRY(RF_SRV_NOT_RESPOND), TBL_ENTRY(RF_SRV_OK), TBL_ENTRY(RF_SRVS_NOT_RESPOND), TBL_ENTRY(RF_SRVS_OK), TBL_ENTRY(RF_DELMAP_CB_ERR), TBL_ENTRY(RF_SENDQ_FULL), {NULL} }; static void mimsg_print_event(const nfs4_debug_msg_t *msg) { const nfs4_revent_t *ep = &msg->rmsg_u.msg_event; char msg_srv[MAXPATHLEN]; char msg_mntpt[MAXPATHLEN]; char char1[MAXPATHLEN]; char *char1p = char1; char char2[MAXPATHLEN]; char *char2p = char2; if (mdb_readstr(msg_srv, sizeof (msg_srv), (uintptr_t)msg->msg_srv) == -1) strcpy(msg_srv, "??"); if (mdb_readstr(msg_mntpt, sizeof (msg_mntpt), (uintptr_t)msg->msg_mntpt) == -1) strcpy(msg_mntpt, "??"); if (ep->re_char1 != NULL) { if (mdb_readstr(char1, sizeof (char1), (uintptr_t)ep->re_char1) == -1) strcpy(char1, "??"); } else { char1[0] = '\0'; char1p = NULL; } if (ep->re_char2 != NULL) { if (mdb_readstr(char2, sizeof (char2), (uintptr_t)ep->re_char2) == -1) strcpy(char2, "??"); } else { char2[0] = '\0'; char2p = NULL; } switch (ep->re_type) { case RE_BAD_SEQID: mdb_printf("Operation %s for file %s (rnode_pt 0x%p), pid %d " "using seqid %u got %s on server %s. Last good seqid was " "%u for operation %s.", nfs4_tag_str(ep->re_tag1), char1p, ep->re_rp1, ep->re_pid, ep->re_seqid1, nfs4_stat_str(ep->re_stat4), msg_srv, ep->re_seqid2, nfs4_tag_str(ep->re_tag2)); break; case RE_BADHANDLE: if (ep->re_char1 != NULL) { mdb_printf("server %s said filehandle was invalid for " "file: %s (rnode_pt %p) on mount %s", msg_srv, char1, ep->re_rp1, msg_mntpt); } else { mdb_printf("server %s said filehandle was invalid for " "file: (rnode_pt %p) on mount %s", msg_srv, ep->re_rp1, msg_mntpt); } break; case RE_CLIENTID: mdb_printf("Can't recover clientid on mi 0x%p due to error %u " "(%s), for server %s. Marking file system as unusable.", ep->re_mi, ep->re_uint, nfs4_stat_str(ep->re_stat4), msg_srv); break; case RE_DEAD_FILE: mdb_printf("File %s (rnode_pt %p) on server %s could not be " "recovered and was closed. %s %s.", char1p, ep->re_rp1, msg_srv, char2, ep->re_stat4 ? nfs4_stat_str(ep->re_stat4) : ""); break; case RE_END: mdb_printf("Recovery done for mount %s (0x%p) on server %s, " "rnode_pt1 %s (0x%p), rnode_pt2 %s (0x%p)", msg_mntpt, ep->re_mi, msg_srv, char1p, ep->re_rp1, char2p, ep->re_rp2); break; case RE_FAIL_RELOCK: mdb_printf("Couldn't reclaim lock for pid %d for file %s " "(rnode_pt %p) on (server %s): error %u", ep->re_pid, char1p, ep->re_rp1, msg_srv, ep->re_uint ? ep->re_uint : ep->re_stat4); break; case RE_FAIL_REMAP_LEN: mdb_printf("remap_lookup: server %s returned bad fhandle " "length (%u)", msg_srv, ep->re_uint); break; case RE_FAIL_REMAP_OP: mdb_printf("remap_lookup: didn't get expected OP_GETFH for " "server %s", msg_srv); break; case RE_FAILOVER: if (ep->re_char1) mdb_printf("Failing over from %s to %s", msg_srv, char1p); else mdb_printf("Failing over: selecting original server %s", msg_srv); break; case RE_FILE_DIFF: mdb_printf("Replicas %s and %s: file %s(%p) not same", msg_srv, msg_mntpt, ep->re_char1, (void *)ep->re_rp1); break; case RE_LOST_STATE: /* * if char1 is null you should use ::nfs4_fname for re_rp1 */ mdb_printf("client has a lost %s request for rnode_pt1 %s " "(0x%p), rnode_pt2 %s (0x%p) on fs %s, server %s.", nfs4_op_str(ep->re_uint), char1p, ep->re_rp1, char2p, ep->re_rp2, msg_mntpt, msg_srv); break; case RE_OPENS_CHANGED: mdb_printf("Recovery: number of open files changed " "for mount %s (0x%p) (old %d, new %d) on server %s\n", msg_mntpt, (void *)ep->re_mi, ep->re_uint, ep->re_pid, msg_srv); break; case RE_SIGLOST: case RE_SIGLOST_NO_DUMP: mdb_printf("Process %d lost its locks on file %s (rnode_pt %p) " "due to a NFS error (%u) on server %s", ep->re_pid, char1p, ep->re_rp1, ep->re_uint ? ep->re_uint : ep->re_stat4, msg_srv); break; case RE_START: mdb_printf("Starting recovery for mount %s (0x%p, flags %#x) " "on server %s, rnode_pt1 %s (0x%p), rnode_pt2 %s (0x%p)", msg_mntpt, ep->re_mi, ep->re_uint, msg_srv, char1p, ep->re_rp1, char2p, ep->re_rp2); break; case RE_UNEXPECTED_ACTION: mdb_printf("Recovery, unexpected action (%d) on server %s\n", ep->re_uint, msg_srv); break; case RE_UNEXPECTED_ERRNO: mdb_printf("Recovery, unexpected errno (%d) on server %s\n", ep->re_uint, msg_srv); break; case RE_UNEXPECTED_STATUS: mdb_printf("Recovery, unexpected NFS status code (%s) " "on server %s\n", nfs4_stat_str(ep->re_stat4), msg_srv); break; case RE_WRONGSEC: mdb_printf("Recovery, can't recover from NFS4ERR_WRONGSEC." " error %d for mount %s server %s: rnode_pt1 %s (0x%p)" " rnode_pt2 %s (0x%p)", ep->re_uint, msg_mntpt, msg_srv, ep->re_char1, (void *)ep->re_rp1, ep->re_char2, (void *)ep->re_rp2); break; case RE_LOST_STATE_BAD_OP: mdb_printf("NFS lost state with unrecognized op (%d)." " fs %s, server %s, pid %d, file %s (rnode_pt: 0x%p), " "dir %s (0x%p)", ep->re_uint, msg_mntpt, msg_srv, ep->re_pid, ep->re_char1, (void *)ep->re_rp1, ep->re_char2, (void *)ep->re_rp2); break; case RE_REFERRAL: if (ep->re_char1) mdb_printf("Referal, Server: %s on Mntpt: %s" "being referred from %s to %s", msg_srv, msg_mntpt, msg_srv, ep->re_char1); else mdb_printf("Referal, Server: %s on Mntpt: %s" "NFS4: being referred from %s to unknown server", msg_srv, msg_mntpt, msg->msg_srv); break; default: mdb_printf("illegal event %d", ep->re_type); break; } } static void mimsg_print_fact(const nfs4_debug_msg_t *msg) { const nfs4_rfact_t *fp = &msg->rmsg_u.msg_fact; char msg_srv[MAXPATHLEN]; char file[MAXPATHLEN]; if (mdb_readstr(msg_srv, sizeof (msg_srv), (uintptr_t)msg->msg_srv) == -1) strcpy(msg_srv, "??"); switch (fp->rf_type) { case RF_BADOWNER: mdb_printf("NFSMAPID_DOMAIN does not match server: %s's " "domain.", msg_srv); break; case RF_ERR: mdb_printf("Op %s got error ", nfs4_op_str(fp->rf_op)); if (fp->rf_error) mdb_printf("%d", fp->rf_error); else mdb_printf("%s", nfs4_stat_str(fp->rf_stat4)); mdb_printf(" causing recovery action %s.", nfs4_recov_str(fp->rf_action)); if (fp->rf_reboot) mdb_printf(" Client also suspects server rebooted"); break; case RF_RENEW_EXPIRED: mdb_printf("Client's lease expired on server %s.", msg_srv); break; case RF_SRV_NOT_RESPOND: mdb_printf("Server %s not responding, still trying", msg_srv); break; case RF_SRV_OK: mdb_printf("Server %s ok", msg_srv); break; case RF_SRVS_NOT_RESPOND: mdb_printf("Servers %s not responding, still trying", msg_srv); break; case RF_SRVS_OK: mdb_printf("Servers %s ok", msg_srv); break; case RF_DELMAP_CB_ERR: if (mdb_readstr(file, sizeof (file), (uintptr_t)fp->rf_char1) == -1) strcpy(file, "??"); mdb_printf("Op %s got error %s when executing delmap on file " "%s (rnode_pt 0x%p).", nfs4_op_str(fp->rf_op), nfs4_stat_str(fp->rf_stat4), file, fp->rf_rp1); break; case RF_SENDQ_FULL: mdb_printf("Send queue to NFS server %s is full; still trying", msg_srv); break; default: mdb_printf("??"); break; } } static int print_mimsg_cb(uintptr_t addr, const void *data, void *cb_data) { nfs4_debug_msg_t msg; uint_t opt_s = *(uint_t *)cb_data; if (mdb_vread(&msg, sizeof (msg), addr) == -1) { mdb_warn("failed to read nfs4_debug_msg_t at %p", addr); return (WALK_ERR); } if (opt_s) { const char *msg_type = "??"; const char *ef_type = "??"; int i; switch (msg.msg_type) { case RM_EVENT: msg_type = "event"; for (i = 0; nfs4_event_type_tbl[i].str != NULL; i++) if (nfs4_event_type_tbl[i].et == msg.rmsg_u.msg_event.re_type) { ef_type = nfs4_event_type_tbl[i].str; break; } break; case RM_FACT: msg_type = "fact"; for (i = 0; nfs4_fact_type_tbl[i].str != NULL; i++) if (nfs4_fact_type_tbl[i].ft == msg.rmsg_u.msg_fact.rf_type) { ef_type = nfs4_fact_type_tbl[i].str; break; } break; } mdb_printf("%Y: %s %s\n", msg.msg_time.tv_sec, msg_type, ef_type); return (WALK_NEXT); } mdb_printf("[NFS4]%Y: ", msg.msg_time.tv_sec); switch (msg.msg_type) { case RM_EVENT: mimsg_print_event(&msg); break; case RM_FACT: mimsg_print_fact(&msg); break; default: mdb_printf("??"); break; } mdb_printf("\n"); return (WALK_NEXT); } int nfs4_mimsg_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_s = FALSE; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of mi_msg_list\n"); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 's', MDB_OPT_SETBITS, TRUE, &opt_s, NULL) != argc) return (DCMD_USAGE); if (mdb_pwalk("list", print_mimsg_cb, &opt_s, addr) == -1) { mdb_warn("failed to walk mi_msg_list list"); return (DCMD_ERR); } return (DCMD_OK); } void nfs4_mimsg_help(void) { mdb_printf( "-c assumes client is an illumos NFSv4 Client\n" "-s assumes server is an illumos NFSv4 Server\n" "\n" "The -c option enables the dcmd to decode the client generated\n" "structure CLIDtoSend that is normally opaque to the server.\n" "The -s option enables the dcmd to decode the server generated\n" "structure Client ID that is normally opaque to the client.\n"); } /* * nfs4_fname dcmd implementation */ static void print_nfs4_fname(uintptr_t addr) { char path[MAXPATHLEN]; char *p = path + sizeof (path) - 1; *p = '\0'; while (addr != 0) { nfs4_fname_t fn; char name[MAXNAMELEN]; if (mdb_vread(&fn, sizeof (fn), addr) == -1 || fn.fn_len >= sizeof (name) || fn.fn_len < 0 || p - fn.fn_len - 1 < path || mdb_readstr(name, sizeof (name), (uintptr_t)fn.fn_name) != fn.fn_len) { mdb_printf("??"); break; } bcopy(name, p -= fn.fn_len, fn.fn_len); if ((addr = (uintptr_t)fn.fn_parent) != 0) *--p = '/'; } mdb_printf("%s", p); } int nfs4_fname_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of nfs4_fname_t \n"); return (DCMD_USAGE); } if (argc != 0) return (DCMD_USAGE); print_nfs4_fname(addr); mdb_printf("\n"); return (DCMD_OK); } /* * Open Owner commands and walkers */ int nfs4_oo_cb(uintptr_t addr, const void *data, void *varg) { nfs4_open_owner_t oop; if (mdb_vread(&oop, sizeof (nfs4_open_owner_t), addr) == -1) { mdb_warn("failed to read nfs4_open_onwer at %p", addr); return (WALK_ERR); } mdb_printf("%p %p %d %d %s %s\n", addr, oop.oo_cred, oop.oo_ref_count, oop.oo_seqid, oop.oo_just_created ? "True" : "False", oop.oo_seqid_inuse ? "True" : "False"); return (WALK_NEXT); } /* * nfs4_foo dcmd implementation */ int nfs4_foo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int foo_off; uintptr_t list_addr; mntinfo4_t mi; foo_off = offsetof(mntinfo4_t, mi_foo_list); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of mntinfo4_t\n"); return (DCMD_USAGE); } if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&mi, sizeof (mntinfo4_t), addr) == -1) { mdb_warn("Failed to read mntinfo at %p", addr); return (DCMD_ERR); } list_addr = addr + foo_off; mdb_printf("mntinfo4: %p, mi_foo_num=%d, mi_foo_max=%d \n", addr, mi.mi_foo_num, mi.mi_foo_max); mdb_printf("Address Cred RefCnt SeqID "); mdb_printf("JustCre SeqInUse BadSeqid\n"); if (mdb_pwalk("list", nfs4_oo_cb, NULL, list_addr) == -1) { mdb_warn("failed to walk 'nfs4_foo'"); return (DCMD_ERR); } return (DCMD_OK); } /* * nfs4_oob_dcmd dcmd */ int nfs4_oob_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int oo_off; uintptr_t list_addr; uintptr_t list_inst; int i; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of mntinfo4_t\n"); return (DCMD_USAGE); } if (argc != 0) return (DCMD_USAGE); oo_off = offsetof(mntinfo4_t, mi_oo_list); list_addr = addr + oo_off; mdb_printf("Address Cred RefCnt SeqID "); mdb_printf("JustCre SeqInUse BadSeqid\n"); for (i = 0; i < NFS4_NUM_OO_BUCKETS; i++) { list_inst = list_addr + (sizeof (nfs4_oo_hash_bucket_t) * i); if (mdb_pwalk("list", nfs4_oo_cb, NULL, list_inst) == -1) { mdb_warn("failed to walk 'nfs4_oob'"); return (DCMD_ERR); } } return (DCMD_OK); } /* * print out open stream entry */ int nfs4_openstream_print(uintptr_t addr, void *buf, int *opts) { nfs4_open_stream_t os; if (mdb_vread(&os, sizeof (nfs4_open_stream_t), addr) == -1) { mdb_warn("Failed to read open stream at %p", addr); return (DCMD_ERR); } mdb_printf("%p\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t" "%d\t%d\t%d\n", addr, os.os_ref_count, os.os_share_acc_read, os.os_share_acc_write, os.os_mmap_read, os.os_mmap_write, os.os_share_deny_none, os.os_share_deny_read, os.os_share_deny_write, os.os_open_ref_count, os.os_dc_openacc, os.os_mapcnt); if (opts && *opts & TRUE) { mdb_printf(" "); if (os.os_valid) mdb_printf("os_valid "); if (os.os_delegation) mdb_printf("os_delegation "); if (os.os_final_close) mdb_printf("os_final_close "); if (os.os_pending_close) mdb_printf("os_pending_close "); if (os.os_failed_reopen) mdb_printf("os_failed_reopen "); if (os.os_force_close) mdb_printf("os_force_close "); mdb_printf("os_orig_oo_name: %s\n", (uchar_t *)&os.os_orig_oo_name); } return (DCMD_OK); } /* * nfs4_svnode dcmd implementation */ int nfs4_openstreams_cb(uintptr_t addr, void *private, int *opts) { mdb_ctf_id_t ctfid; ulong_t offset; uintptr_t os_list_ptr; /* * Walk the rnode4 ptr's r_open_streams list. */ if ((mdb_ctf_lookup_by_name("rnode4_t", &ctfid) == 0) && (mdb_ctf_offsetof(ctfid, "r_open_streams", &offset) == 0) && (offset % (sizeof (uintptr_t) * NBBY) == 0)) { offset /= NBBY; } else { offset = offsetof(rnode4_t, r_open_streams); } os_list_ptr = addr + offset; if (mdb_pwalk("list", (mdb_walk_cb_t)nfs4_openstream_print, opts, os_list_ptr) == -1) { mdb_warn("Failed to walk r_open_streams"); return (DCMD_ERR); } return (DCMD_OK); } /* * nfs4_os_dcmd list open streams */ int nfs4_os_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int opts = 0; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opts, NULL) != argc) { return (DCMD_USAGE); } mdb_printf(" ref\t| os_share | os_mmap | " "os_share_deny | open | deleg |\t|\n"); mdb_printf("%%-?s %-s|%s %s|%s %s|%s %s %s|" "%s |%s |%s |%\n", "Address", "count", "acc_read", "acc_write", "read", "write", "none", "read", "write", "count", "access", "mapcnt"); /* * Walk the rnode4 cache if no address is specified */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk("nfs_rtable4", (mdb_walk_cb_t)nfs4_openstreams_cb, &opts) == -1) { mdb_warn("unable to walk nfs_rtable4"); return (DCMD_ERR); } return (DCMD_OK); } return (nfs4_openstreams_cb(addr, NULL, &opts)); } int nfs4_svnode_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { svnode_t sn; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of svnode_t\n"); return (DCMD_USAGE); } if (argc != 0) return (DCMD_USAGE); if (mdb_vread(&sn, sizeof (sn), addr) == -1) { mdb_warn("can't read svnode_t at %p", addr); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%%-?s %-?s %-20s%%\n", "SVNODE", "VNODE", "PATH"); mdb_printf("%-?p %-?p ", addr, sn.sv_r_vnode); print_nfs4_fname((uintptr_t)sn.sv_name); mdb_printf("\n"); return (DCMD_OK); } /* * nfs_rtable walker implementation */ hash_table_walk_arg_t nfs_rtable_arg = { 0, /* will be set in the init */ 0, /* will be set in the init */ sizeof (rhashq_t), "r_hashf", OFFSETOF(rhashq_t, r_hashf), "rnode_t", sizeof (rnode_t), OFFSETOF(struct rnode, r_hashf) }; static int nfs_rtable_common_init(mdb_walk_state_t *wsp, const char *tabname, const char *sizename) { hash_table_walk_arg_t *arg = wsp->walk_arg; int rtsize; uintptr_t rtaddr; if (mdb_readsym(&rtsize, sizeof (rtsize), sizename) == -1) { mdb_warn("failed to get %s", sizename); return (WALK_ERR); } if (rtsize < 0) { mdb_warn("%s is negative: %d", sizename, rtsize); return (WALK_ERR); } if (mdb_readsym(&rtaddr, sizeof (rtaddr), tabname) == -1) { mdb_warn("failed to get %s", tabname); return (WALK_ERR); } arg->array_addr = rtaddr; arg->array_len = rtsize; return (hash_table_walk_init(wsp)); } int nfs_rtable_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { mdb_warn("nfs_rtable supports only global walks"); return (WALK_ERR); } return (nfs_rtable_common_init(wsp, "rtable", "rtablesize")); } /* * nfs_rtable4 walker implementation */ hash_table_walk_arg_t nfs_rtable4_arg = { 0, /* will be set in the init */ 0, /* will be set in the init */ sizeof (r4hashq_t), "r_hashf", OFFSETOF(r4hashq_t, r_hashf), "rnode4_t", sizeof (rnode4_t), OFFSETOF(struct rnode4, r_hashf) }; int nfs_rtable4_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { mdb_warn("nfs_rtable4 supports only global walks"); return (WALK_ERR); } return (nfs_rtable_common_init(wsp, "rtable4", "rtable4size")); } /* * nfs_vfs walker implementation */ typedef struct nfs_vfs_walk { uintptr_t nfs2_ops; uintptr_t nfs3_ops; uintptr_t nfs4_ops; void *data; /* walker specific data */ } nfs_vfs_walk_t; int nfs_vfs_walk_init(mdb_walk_state_t *wsp) { nfs_vfs_walk_t data; nfs_vfs_walk_t *datap; if (mdb_readvar(&data.nfs2_ops, "nfs_vfsops") == -1) { mdb_warn("failed to read %s", "nfs_vfsops"); return (WALK_ERR); } if (mdb_readvar(&data.nfs3_ops, "nfs3_vfsops") == -1) { mdb_warn("failed to read %s", "nfs3_vfsops"); return (WALK_ERR); } if (mdb_readvar(&data.nfs4_ops, "nfs4_vfsops") == -1) { mdb_warn("failed to read %s", "nfs4_vfsops"); return (WALK_ERR); } if (mdb_layered_walk("genunix`vfs", wsp) == -1) { mdb_warn("failed to walk vfs"); return (WALK_ERR); } datap = mdb_alloc(sizeof (data), UM_SLEEP); *datap = data; wsp->walk_data = datap; return (WALK_NEXT); } int nfs_vfs_walk_step(mdb_walk_state_t *wsp) { nfs_vfs_walk_t *data = (nfs_vfs_walk_t *)wsp->walk_data; vfs_t *vfs = (vfs_t *)wsp->walk_layer; if (data->nfs2_ops != (uintptr_t)vfs->vfs_op && data->nfs3_ops != (uintptr_t)vfs->vfs_op && data->nfs4_ops != (uintptr_t)vfs->vfs_op) return (WALK_NEXT); return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } void nfs_vfs_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (nfs_vfs_walk_t)); } /* * nfs_mnt walker implementation */ int nfs_mnt_walk_init(mdb_walk_state_t *wsp) { int status; nfs_vfs_walk_t *data; status = nfs_vfs_walk_init(wsp); if (status != WALK_NEXT) return (status); data = wsp->walk_data; data->data = mdb_alloc(sizeof (mntinfo_t), UM_SLEEP); return (WALK_NEXT); } int nfs_mnt_walk_step(mdb_walk_state_t *wsp) { nfs_vfs_walk_t *data = (nfs_vfs_walk_t *)wsp->walk_data; vfs_t *vfs = (vfs_t *)wsp->walk_layer; if (data->nfs2_ops != (uintptr_t)vfs->vfs_op && data->nfs3_ops != (uintptr_t)vfs->vfs_op) return (WALK_NEXT); if (mdb_vread(data->data, sizeof (mntinfo_t), (uintptr_t)VFTOMI(vfs)) == -1) { mdb_warn("can't read mntinfo"); return (WALK_ERR); } return (wsp->walk_callback((uintptr_t)VFTOMI(vfs), data->data, wsp->walk_cbdata)); } void nfs_mnt_walk_fini(mdb_walk_state_t *wsp) { nfs_vfs_walk_t *data = (nfs_vfs_walk_t *)wsp->walk_data; mdb_free(data->data, sizeof (mntinfo_t)); nfs_vfs_walk_fini(wsp); } /* * nfs4_mnt walker implementation */ int nfs4_mnt_walk_init(mdb_walk_state_t *wsp) { int status; nfs_vfs_walk_t *data; status = nfs_vfs_walk_init(wsp); if (status != WALK_NEXT) return (status); data = wsp->walk_data; data->data = mdb_alloc(sizeof (mntinfo4_t), UM_SLEEP); return (WALK_NEXT); } int nfs4_mnt_walk_step(mdb_walk_state_t *wsp) { nfs_vfs_walk_t *data = (nfs_vfs_walk_t *)wsp->walk_data; vfs_t *vfs = (vfs_t *)wsp->walk_layer; if (data->nfs4_ops != (uintptr_t)vfs->vfs_op) return (WALK_NEXT); if (mdb_vread(data->data, sizeof (mntinfo4_t), (uintptr_t)VFTOMI4(vfs)) == -1) { mdb_warn("can't read mntinfo4"); return (WALK_ERR); } return (wsp->walk_callback((uintptr_t)VFTOMI4(vfs), data->data, wsp->walk_cbdata)); } void nfs4_mnt_walk_fini(mdb_walk_state_t *wsp) { nfs_vfs_walk_t *data = (nfs_vfs_walk_t *)wsp->walk_data; mdb_free(data->data, sizeof (mntinfo4_t)); nfs_vfs_walk_fini(wsp); } /* * nfs_serv walker implementation */ int nfs_serv_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } return (WALK_NEXT); } int nfs_serv_walk_step(mdb_walk_state_t *wsp) { servinfo_t si; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&si, sizeof (si), addr) == -1) { mdb_warn("can't read servinfo_t"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)si.sv_next; return (wsp->walk_callback(addr, &si, wsp->walk_cbdata)); } /* * nfs4_serv walker implementation */ int nfs4_serv_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } return (WALK_NEXT); } int nfs4_serv_walk_step(mdb_walk_state_t *wsp) { servinfo4_t si; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&si, sizeof (si), addr) == -1) { mdb_warn("can't read servinfo4_t"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)si.sv_next; return (wsp->walk_callback(addr, &si, wsp->walk_cbdata)); } /* * nfs4_svnode walker implementation */ int nfs4_svnode_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int nfs4_svnode_walk_step(mdb_walk_state_t *wsp) { svnode_t sn; uintptr_t addr = wsp->walk_addr; int status; if (mdb_vread(&sn, sizeof (sn), addr) == -1) { mdb_warn("can't read svnode_t"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sn.sv_forw; status = wsp->walk_callback(addr, &sn, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); return (((void *)wsp->walk_addr == wsp->walk_data) ? WALK_DONE : WALK_NEXT); } /* * nfs4_server walker implementation */ int nfs4_server_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { GElf_Sym sym; if (mdb_lookup_by_name("nfs4_server_lst", &sym) == -1) { mdb_warn("failed to find 'nfs4_server_lst'"); return (WALK_ERR); } wsp->walk_addr = sym.st_value; } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int nfs4_server_walk_step(mdb_walk_state_t *wsp) { nfs4_server_t srv; uintptr_t addr = wsp->walk_addr; int status; if (mdb_vread(&srv, sizeof (srv), addr) == -1) { mdb_warn("can't read nfs4_server_t"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)srv.forw; status = wsp->walk_callback(addr, &srv, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); return (((void *)wsp->walk_addr == wsp->walk_data) ? WALK_DONE : WALK_NEXT); } /* * nfs_async walker implementation */ int nfs_async_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } return (WALK_NEXT); } int nfs_async_walk_step(mdb_walk_state_t *wsp) { struct nfs_async_reqs areq; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&areq, sizeof (areq), addr) == -1) { mdb_warn("can't read struct nfs_async_reqs"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)areq.a_next; return (wsp->walk_callback(addr, &areq, wsp->walk_cbdata)); } /* * nfs4_async walker implementation */ int nfs4_async_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } return (WALK_NEXT); } int nfs4_async_walk_step(mdb_walk_state_t *wsp) { struct nfs4_async_reqs areq; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&areq, sizeof (areq), addr) == -1) { mdb_warn("can't read struct nfs4_async_reqs"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)areq.a_next; return (wsp->walk_callback(addr, &areq, wsp->walk_cbdata)); } /* * nfs_acache_rnode walker implementation */ int nfs_acache_rnode_walk_init(mdb_walk_state_t *wsp) { rnode_t rn; if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } if (mdb_vread(&rn, sizeof (rn), wsp->walk_addr) == -1) { mdb_warn("can't read rnode_t at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)rn.r_acache; return (WALK_NEXT); } int nfs_acache_rnode_walk_step(mdb_walk_state_t *wsp) { acache_t ac; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&ac, sizeof (ac), addr) == -1) { mdb_warn("can't read acache_t at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)ac.list; return (wsp->walk_callback(addr, &ac, wsp->walk_cbdata)); } /* * nfs_acache walker implementation */ static const hash_table_walk_arg_t nfs_acache_arg = { 0, /* placeholder */ 0, /* placeholder */ sizeof (acache_hash_t), "next", OFFSETOF(acache_hash_t, next), "acache_t", sizeof (acache_t), OFFSETOF(acache_t, next) }; int nfs_acache_walk_init(mdb_walk_state_t *wsp) { hash_table_walk_arg_t *arg; int size; uintptr_t addr; int status; if (wsp->walk_addr != 0) { mdb_warn("local walk not supported"); return (WALK_ERR); } if (mdb_readsym(&size, sizeof (size), "acachesize") == -1) { mdb_warn("failed to get %s", "acachesize"); return (WALK_ERR); } if (size < 0) { mdb_warn("%s is negative: %d", "acachesize", size); return (WALK_ERR); } if (mdb_readsym(&addr, sizeof (addr), "acache") == -1) { mdb_warn("failed to get %s", "acache"); return (WALK_ERR); } arg = mdb_alloc(sizeof (*arg), UM_SLEEP); bcopy(&nfs_acache_arg, arg, sizeof (*arg)); arg->array_addr = addr; arg->array_len = size; wsp->walk_arg = arg; status = hash_table_walk_init(wsp); if (status != WALK_NEXT) mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t)); return (status); } void nfs_acache_walk_fini(mdb_walk_state_t *wsp) { hash_table_walk_fini(wsp); mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t)); } /* * nfs_acache4_rnode walker implementation */ int nfs_acache4_rnode_walk_init(mdb_walk_state_t *wsp) { rnode4_t rn; if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } if (mdb_vread(&rn, sizeof (rn), wsp->walk_addr) == -1) { mdb_warn("can't read rnode4_t at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)rn.r_acache; return (WALK_NEXT); } int nfs_acache4_rnode_walk_step(mdb_walk_state_t *wsp) { acache4_t ac; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&ac, sizeof (ac), addr) == -1) { mdb_warn("can't read acache4_t at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)ac.list; return (wsp->walk_callback(addr, &ac, wsp->walk_cbdata)); } /* * nfs_acache4 walker implementation */ static const hash_table_walk_arg_t nfs_acache4_arg = { 0, /* placeholder */ 0, /* placeholder */ sizeof (acache4_hash_t), "next", OFFSETOF(acache4_hash_t, next), "acache4_t", sizeof (acache4_t), OFFSETOF(acache4_t, next) }; int nfs_acache4_walk_init(mdb_walk_state_t *wsp) { hash_table_walk_arg_t *arg; int size; uintptr_t addr; int status; if (wsp->walk_addr != 0) { mdb_warn("local walk not supported"); return (WALK_ERR); } if (mdb_readsym(&size, sizeof (size), "acache4size") == -1) { mdb_warn("failed to get %s", "acache4size"); return (WALK_ERR); } if (size < 0) { mdb_warn("%s is negative: %d\n", "acache4size", size); return (WALK_ERR); } if (mdb_readsym(&addr, sizeof (addr), "acache4") == -1) { mdb_warn("failed to get %s", "acache4"); return (WALK_ERR); } arg = mdb_alloc(sizeof (*arg), UM_SLEEP); bcopy(&nfs_acache4_arg, arg, sizeof (*arg)); arg->array_addr = addr; arg->array_len = size; wsp->walk_arg = arg; status = hash_table_walk_init(wsp); if (status != WALK_NEXT) mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t)); return (status); } void nfs_acache4_walk_fini(mdb_walk_state_t *wsp) { hash_table_walk_fini(wsp); mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #ifndef _NFS_CLNT_H #define _NFS_CLNT_H #include #include "common.h" extern int nfs_mntinfo_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs_mntinfo_help(void); extern int nfs_servinfo_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs_servinfo_help(void); extern int nfs4_mntinfo_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs4_mntinfo_help(void); extern int nfs4_servinfo_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs4_servinfo_help(void); extern int nfs4_server_info_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs4_server_info_help(void); extern int nfs4_mimsg_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs4_mimsg_help(void); extern int nfs4_fname_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs4_foo_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs4_oob_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs4_os_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs4_svnode_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern hash_table_walk_arg_t nfs_rtable_arg; extern int nfs_rtable_walk_init(mdb_walk_state_t *); extern hash_table_walk_arg_t nfs_rtable4_arg; extern int nfs_rtable4_walk_init(mdb_walk_state_t *); extern int nfs_vfs_walk_init(mdb_walk_state_t *); extern int nfs_vfs_walk_step(mdb_walk_state_t *); extern void nfs_vfs_walk_fini(mdb_walk_state_t *); extern int nfs_mnt_walk_init(mdb_walk_state_t *); extern int nfs_mnt_walk_step(mdb_walk_state_t *); extern void nfs_mnt_walk_fini(mdb_walk_state_t *); extern int nfs4_mnt_walk_init(mdb_walk_state_t *); extern int nfs4_mnt_walk_step(mdb_walk_state_t *); extern void nfs4_mnt_walk_fini(mdb_walk_state_t *); extern int nfs_serv_walk_init(mdb_walk_state_t *); extern int nfs_serv_walk_step(mdb_walk_state_t *); extern int nfs4_serv_walk_init(mdb_walk_state_t *); extern int nfs4_serv_walk_step(mdb_walk_state_t *); extern int nfs4_svnode_walk_init(mdb_walk_state_t *); extern int nfs4_svnode_walk_step(mdb_walk_state_t *); extern int nfs4_server_walk_init(mdb_walk_state_t *); extern int nfs4_server_walk_step(mdb_walk_state_t *); extern int nfs_async_walk_init(mdb_walk_state_t *); extern int nfs_async_walk_step(mdb_walk_state_t *); extern int nfs4_async_walk_init(mdb_walk_state_t *); extern int nfs4_async_walk_step(mdb_walk_state_t *); extern int nfs_acache_walk_init(mdb_walk_state_t *); extern void nfs_acache_walk_fini(mdb_walk_state_t *); extern int nfs_acache_rnode_walk_init(mdb_walk_state_t *); extern int nfs_acache_rnode_walk_step(mdb_walk_state_t *); extern int nfs_acache4_walk_init(mdb_walk_state_t *); extern void nfs_acache4_walk_fini(mdb_walk_state_t *); extern int nfs_acache4_rnode_walk_init(mdb_walk_state_t *); extern int nfs_acache4_rnode_walk_step(mdb_walk_state_t *); #endif /* _NFS_CLNT_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include "svc.h" #include "rfs4.h" #include "nfssrv.h" #include "common.h" #define NFS4_MINOR_VERS_COUNT 0 #define NFS_STAT_NUM_STATS 79 /* * Structure used to group kstats we want to print. */ typedef struct nfs_mdb_stats { struct nfs_stats nfsstats; struct rpcstat rpcstats; struct nfs_globals nfsglbls; uintptr_t clntstat; uintptr_t clntstat4; /* extend this for NFS4.X */ uintptr_t callback_stats; } nfs_mdb_stats_t; static int nfs_stat_clnt(nfs_mdb_stats_t *, int, int); static int nfs_stat_srv(nfs_mdb_stats_t *, int, int); static int nfs_srvstat(nfs_mdb_stats_t *, int); static int nfs_srvstat_rpc(nfs_mdb_stats_t *); static int nfs_srvstat_acl(nfs_mdb_stats_t *, int); static int nfs_clntstat(nfs_mdb_stats_t *, int); static int nfs_clntstat_rpc(nfs_mdb_stats_t *); static int nfs_clntstat_acl(nfs_mdb_stats_t *, int); static int nfs_srvstat_cb(nfs_mdb_stats_t *); #define NFS_SRV_STAT 0x1 #define NFS_CLNT_STAT 0x2 #define NFS_CB_STAT 0x4 #define NFS_NFS_STAT 0x1 #define NFS_ACL_STAT 0x2 #define NFS_RPC_STAT 0x4 #define NFS_V2_STAT 0x1 #define NFS_V3_STAT 0x2 #define NFS_V4_STAT 0x4 static int prt_nfs_stats(uintptr_t, char *); static void kstat_prtout(char *, uint64_t *, int); void nfs_stat_help(void) { mdb_printf("Switches similar to those of nfsstat command.\n", " ::nfs_stat -a -> ACL Statistics.\n" " ::nfs_stat -b -> Callback Stats. (V4 only)\n" " ::nfs_stat -c -> Client Statistics.\n" " ::nfs_stat -n -> NFS Statistics.\n" " ::nfs_stat -r -> RPC Statistics.\n" " ::nfs_stat -s -> Server Statistics.\n" " ::nfs_stat -2 -> Version 2.\n" " ::nfs_stat -3 -> Version 3.\n" " ::nfs_stat -4 -> Version 4.\n"); } int nfs_stat_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int host_flag = 0; /* host or client flag */ int type_flag = 0; /* type acl, rpc of nfs */ int vers_flag = 0; /* NFS version flag */ nfs_mdb_stats_t mdb_stats; uintptr_t glbls; uintptr_t cb_glbls; uintptr_t zonep; if (argc == 1 && argv->a_type == MDB_TYPE_IMMEDIATE) { kstat_named_t ksts; int i; for (i = argv->a_un.a_val; i; i--) { if (mdb_vread(&ksts, sizeof (ksts), addr) < 0) { mdb_warn("failed to read kstat_name_t"); return (DCMD_ERR); } mdb_printf(" %8s %30d\n", ksts.name, ksts.value.ui64); addr += sizeof (ksts); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, NFS_ACL_STAT, &type_flag, 'b', MDB_OPT_SETBITS, NFS_CB_STAT, &host_flag, 'c', MDB_OPT_SETBITS, NFS_CLNT_STAT, &host_flag, 'n', MDB_OPT_SETBITS, NFS_NFS_STAT, &type_flag, 'r', MDB_OPT_SETBITS, NFS_RPC_STAT, &type_flag, 's', MDB_OPT_SETBITS, NFS_SRV_STAT, &host_flag, '2', MDB_OPT_SETBITS, NFS_V2_STAT, &vers_flag, '3', MDB_OPT_SETBITS, NFS_V3_STAT, &vers_flag, '4', MDB_OPT_SETBITS, NFS_V4_STAT, &vers_flag, NULL) != argc) { return (DCMD_USAGE); } if (flags & DCMD_ADDRSPEC) { zonep = addr; } else { if (mdb_readsym(&zonep, sizeof (uintptr_t), "global_zone") == -1) { mdb_warn("Failed to find global_zone"); return (DCMD_ERR); } } if (zoned_get_zsd(zonep, "nfssrv_zone_key", &glbls)) { mdb_warn("Failed to find nfssrv_zone_key"); return (DCMD_ERR); } if (mdb_vread(&mdb_stats.nfsglbls, sizeof (struct nfs_globals), glbls) == -1) { mdb_warn("Failed to read nfs_stats at %p", glbls); return (DCMD_ERR); } if (zoned_get_zsd(zonep, "nfsstat_zone_key", &glbls)) { mdb_warn("Failed to find %s", "nfsstat_zone_key"); return (DCMD_ERR); } if (mdb_vread(&mdb_stats.nfsstats, sizeof (struct nfs_stats), glbls) == -1) { mdb_warn("Failed to read nfs_stats at %p", glbls); return (DCMD_ERR); } if (zoned_get_zsd(zonep, "rpcstat_zone_key", &glbls)) { mdb_warn("Failed to find %s", "rpcstat_zone_key"); return (DCMD_ERR); } if (mdb_vread(&mdb_stats.rpcstats, sizeof (struct rpcstat), glbls) == -1) { mdb_warn("Failed to read nfs_stats at %p", glbls); return (DCMD_ERR); } if (zoned_get_zsd(zonep, "nfsclnt_zone_key", &glbls)) { mdb_warn("Failed to find %s", "nfsclnt_zone_key"); return (DCMD_ERR); } mdb_stats.clntstat = glbls + offsetof(struct nfs_clnt, nfscl_stat); if (zoned_get_zsd(zonep, "nfs4clnt_zone_key", &glbls)) { mdb_warn("Failed to find %s", "nfs4clnt_zone_key"); return (DCMD_ERR); } /* * currently only have NFSv4.0 is availble. When NFSv4.1 and above are * available stats support will need to be added. */ mdb_stats.clntstat4 = glbls + offsetof(struct nfs4_clnt, nfscl_stat); if (zoned_get_zsd(zonep, "nfs4_callback_zone_key", (uintptr_t *)&cb_glbls)) { mdb_warn("Failed to find %s", "nfs4_callback_zone_key"); return (DCMD_ERR); } mdb_stats.callback_stats = (cb_glbls + offsetof(struct nfs4_callback_globals, nfs4_callback_stats)); if (host_flag == 0) host_flag = NFS_SRV_STAT | NFS_CLNT_STAT | NFS_CB_STAT; if (vers_flag == 0) vers_flag = NFS_V2_STAT | NFS_V3_STAT | NFS_V4_STAT; if (type_flag == 0) type_flag = NFS_NFS_STAT | NFS_ACL_STAT | NFS_RPC_STAT; if (host_flag & NFS_CB_STAT) if (nfs_srvstat_cb(&mdb_stats)) return (DCMD_ERR); if (host_flag & NFS_SRV_STAT) if (nfs_stat_srv(&mdb_stats, type_flag, vers_flag)) return (DCMD_ERR); if (host_flag & NFS_CLNT_STAT) if (nfs_stat_clnt(&mdb_stats, type_flag, vers_flag)) return (DCMD_ERR); return (DCMD_OK); } static int nfs_srvstat_cb(nfs_mdb_stats_t *stptr) { int ret = 0; mdb_printf("CALLBACK STATISTICS:\n"); ret = prt_nfs_stats(stptr->callback_stats, "nfs4_callback_stats_tmpl"); return (ret); } static int nfs_stat_srv(nfs_mdb_stats_t *stptr, int type_flag, int vers_flag) { mdb_printf("NFS SERVER STATS:\n"); if (type_flag & NFS_SRV_STAT) { if (nfs_srvstat(stptr, vers_flag) != 0) return (1); } if (type_flag & NFS_RPC_STAT) { if (nfs_srvstat_rpc(stptr) != 0) return (1); } if (type_flag & NFS_ACL_STAT) { if (nfs_srvstat_acl(stptr, vers_flag) != 0) return (1); } return (0); } static int nfs_stat_clnt(nfs_mdb_stats_t *stptr, int type_flag, int vers_flag) { mdb_printf("CLIENT STATISTICS:\n"); if (type_flag & NFS_CLNT_STAT) { if (nfs_clntstat(stptr, vers_flag)) return (1); } if (type_flag & NFS_ACL_STAT) { if (nfs_clntstat_acl(stptr, vers_flag)) return (1); } if (type_flag & NFS_RPC_STAT) { if (nfs_clntstat_rpc(stptr)) return (1); } return (0); } static int nfs_srvstat(nfs_mdb_stats_t *stptr, int flag) { mdb_printf("NFS Statistics\n"); if (flag & NFS_V2_STAT) { mdb_printf("NFSv2\n"); if (prt_nfs_stats((uintptr_t)stptr->nfsglbls.svstat[2], "svstat_tmpl") || prt_nfs_stats((uintptr_t)stptr->nfsglbls.rfsproccnt[2], "rfsproccnt_v2_tmpl")) return (-1); } if (flag & NFS_V3_STAT) { mdb_printf("NFSv3\n"); if (prt_nfs_stats((uintptr_t)stptr->nfsglbls.svstat[3], "svstat_tmpl") || prt_nfs_stats((uintptr_t)stptr->nfsglbls.rfsproccnt[3], "rfsproccnt_v3_tmpl")) return (-1); } if (flag & NFS_V4_STAT) { mdb_printf("NFSv4\n"); if (prt_nfs_stats((uintptr_t)stptr->nfsglbls.svstat[4], "svstat_tmpl") || prt_nfs_stats((uintptr_t)stptr->nfsglbls.rfsproccnt[4], "rfsproccnt_v4_tmpl")) return (-1); } return (0); } static int nfs_srvstat_rpc(nfs_mdb_stats_t *stptr) { mdb_printf("NFS RPC Statistics\n"); mdb_printf("ConnectionLess\n"); if (prt_nfs_stats((uintptr_t)stptr->rpcstats.rpc_clts_server, "clts_rsstat_tmpl")) return (-1); mdb_printf("ConnectionOriented\n"); if (prt_nfs_stats((uintptr_t)stptr->rpcstats.rpc_cots_server, "cots_rsstat_tmpl")) return (-1); return (0); } static int nfs_srvstat_acl(nfs_mdb_stats_t *stptr, int flags) { mdb_printf("NFS ACL Statistics\n"); if (flags & NFS_V2_STAT) { mdb_printf("NFSv2\n"); if (prt_nfs_stats((uintptr_t)stptr->nfsglbls.aclproccnt[2], "aclproccnt_v2_tmpl")) return (-1); } if (flags & NFS_V3_STAT) { mdb_printf("NFSv3\n"); if (prt_nfs_stats((uintptr_t)stptr->nfsglbls.aclproccnt[3], "aclproccnt_v3_tmpl")) return (-1); } if (flags & NFS_V4_STAT) { mdb_printf("NFSv4\n"); if (prt_nfs_stats((uintptr_t)stptr->nfsglbls.aclproccnt[4], "aclreqcnt_v4_tmpl")) return (-1); } return (0); } static int nfs_clntstat(nfs_mdb_stats_t *stptr, int flags) { mdb_printf("NFS Statistics\n"); if (prt_nfs_stats((uintptr_t)stptr->clntstat, "clstat_tmpl")) return (-1); if (flags & NFS_V2_STAT) { mdb_printf("Version 2\n"); if (prt_nfs_stats( (uintptr_t)stptr->nfsstats.nfs_stats_v2.rfsreqcnt_ptr, "rfsreqcnt_v2_tmpl")) return (-1); } if (flags & NFS_V3_STAT) { mdb_printf("Version 3\n"); if (prt_nfs_stats( (uintptr_t)stptr->nfsstats.nfs_stats_v3.rfsreqcnt_ptr, "rfsreqcnt_v3_tmpl")) return (-1); } if (flags & NFS_V4_STAT) { mdb_printf("NFSv4 client\n"); if (prt_nfs_stats((uintptr_t)stptr->clntstat, "clstat4_tmpl")) return (-1); mdb_printf("Version 4\n"); if (prt_nfs_stats( (uintptr_t)stptr->nfsstats.nfs_stats_v4.rfsreqcnt_ptr, "rfsreqcnt_v4_tmpl")) return (-1); } return (0); } static int nfs_clntstat_rpc(nfs_mdb_stats_t *stptr) { mdb_printf("NFS RPC Statistics\n"); mdb_printf("ConnectionLess\n"); if (prt_nfs_stats((uintptr_t)stptr->rpcstats.rpc_clts_client, "clts_rcstat_tmpl")) return (-1); mdb_printf("ConnectionOriented\n"); if (prt_nfs_stats((uintptr_t)stptr->rpcstats.rpc_cots_client, "cots_rcstat_tmpl")) return (-1); return (0); } static int nfs_clntstat_acl(nfs_mdb_stats_t *stptr, int flags) { mdb_printf("ACL Statistics\n"); if (flags & NFS_V2_STAT) { mdb_printf("Version 2\n"); if (prt_nfs_stats( (uintptr_t)stptr->nfsstats.nfs_stats_v2.aclreqcnt_ptr, "aclreqcnt_v2_tmpl")) return (-1); } if (flags & NFS_V3_STAT) { mdb_printf("Version 3\n"); if (prt_nfs_stats( (uintptr_t)stptr->nfsstats.nfs_stats_v3.aclreqcnt_ptr, "aclreqcnt_v3_tmpl")) return (-1); } if (flags & NFS_V4_STAT) { mdb_printf("Version 4\n"); if (prt_nfs_stats( (uintptr_t)stptr->nfsstats.nfs_stats_v4.aclreqcnt_ptr, "aclreqcnt_v4_tmpl")) return (-1); } return (0); } /* * helper functions for printing out the kstat data */ #define NFS_STAT_NUM_CLMNS 16 static int prt_nfs_stats(uintptr_t addr, char *name) { GElf_Sym sym; kstat_named_t kstats; char *kstat_line; uint64_t *value; uint_t count; int i = 0, status = 0; if (mdb_lookup_by_name(name, &sym) != 0) { mdb_warn("failed to find %s", name); return (1); } count = sym.st_size / sizeof (kstat_named_t); kstat_line = mdb_alloc(count * NFS_STAT_NUM_CLMNS, UM_SLEEP); value = mdb_alloc(count * sizeof (uint64_t), UM_SLEEP); for (i = 0; i < count; i++) { if (mdb_vread(&kstats, sizeof (kstat_named_t), addr + (i * sizeof (kstat_named_t))) < 0) { status = 1; goto done; } mdb_snprintf(&kstat_line[NFS_STAT_NUM_CLMNS * i], NFS_STAT_NUM_CLMNS, "%s", kstats.name); value[i] = kstats.value.ui64; } kstat_prtout(kstat_line, value, count); done: mdb_free(kstat_line, count * NFS_STAT_NUM_CLMNS); mdb_free(value, count * sizeof (uint64_t)); return (status); } static void kstat_prtout(char *ks_line, uint64_t *values, int count) { char val_str[32]; int i = 0, num = 0; while (i < count) { mdb_printf("%-*s", NFS_STAT_NUM_CLMNS, &ks_line[NFS_STAT_NUM_CLMNS * i]); num++; if (num == NFS_STAT_NUM_STATS / NFS_STAT_NUM_CLMNS) { mdb_printf("\n"); while (num > 0) { mdb_snprintf(val_str, 24, "%ld ", values[i+1-num]); mdb_printf("%-*s", NFS_STAT_NUM_CLMNS, val_str); --num; } mdb_printf("\n"); } i++; } mdb_printf("\n"); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include "nfssrv.h" #include "common.h" /* * nfs_expvis dcmd implementation */ static const mdb_bitmask_t sec_flag_bits[] = { {"M_RO", M_RO, M_RO}, {"M_ROL", M_ROL, M_ROL}, {"M_RW", M_RW, M_RW}, {"M_RWL", M_RWL, M_RWL}, {"M_ROOT", M_ROOT, M_ROOT}, {"M_EXP", M_4SEC_EXPORTED, M_4SEC_EXPORTED}, {NULL, 0, 0} }; static int print_sec(int cnt, uintptr_t addr) { int i; if (cnt == 0) return (DCMD_OK); mdb_printf("Security Flavors :\n"); mdb_inc_indent(4); for (i = 0; i < cnt; i++) { struct secinfo si; const char *s; if (mdb_vread(&si, sizeof (si), addr) == -1) { mdb_warn("can't read struct secinfo"); return (DCMD_ERR); } switch (si.s_secinfo.sc_nfsnum) { case AUTH_NONE: s = "none"; break; case AUTH_SYS: s = "sys"; break; case AUTH_DH: s = "dh"; break; case 390003: s = "krb5"; break; case 390004: s = "krb5i"; break; case 390005: s = "krb5p"; break; default: s = "???"; break; } mdb_printf("%-8s ref: %-8i flag: %#x (%b)\n", s, si.s_refcnt, si.s_flags, si.s_flags, sec_flag_bits); addr = (uintptr_t)((struct secinfo *)addr + 1); } mdb_dec_indent(4); return (DCMD_OK); } int nfs_expvis_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { exp_visible_t expvis; uintptr_t vp; char *s; int status; if (argc > 0) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of exp_visible_t\n"); return (DCMD_USAGE); } if (mdb_vread(&expvis, sizeof (expvis), addr) == -1) { mdb_warn("can't read exp_visible_t"); return (DCMD_ERR); } /* vp = expvis.vis_vp->v_path */ if (mdb_vread(&vp, sizeof (vp), (uintptr_t)expvis.vis_vp + OFFSETOF(vnode_t, v_path)) == -1) { mdb_warn("can't read vnode_t"); return (DCMD_ERR); } s = mdb_alloc(PATH_MAX, UM_SLEEP | UM_GC); if (mdb_readstr(s, PATH_MAX, vp) == -1) { mdb_warn("can't read v_path"); return (DCMD_ERR); } mdb_printf("%s\n", s); mdb_inc_indent(4); mdb_printf("addr: %?p exp : %i ref: %i\n", addr, expvis.vis_exported, expvis.vis_count); mdb_printf("vp : %?p ino : %llu (%#llx)\n", expvis.vis_vp, expvis.vis_ino, expvis.vis_ino); mdb_printf("seci: %?p nsec: %i\n", expvis.vis_secinfo, expvis.vis_seccnt); status = print_sec(expvis.vis_seccnt, (uintptr_t)expvis.vis_secinfo); mdb_dec_indent(4); return (status); } /* * nfs_expinfo dcmd implementation */ static const mdb_bitmask_t exp_flag_bits[] = { {"EX_NOSUID", EX_NOSUID, EX_NOSUID}, {"EX_ACLOK", EX_ACLOK, EX_ACLOK}, {"EX_PUBLIC", EX_PUBLIC, EX_PUBLIC}, {"EX_NOSUB", EX_NOSUB, EX_NOSUB}, {"EX_INDEX", EX_INDEX, EX_INDEX}, {"EX_LOG", EX_LOG, EX_LOG}, {"EX_LOG_ALLOPS", EX_LOG_ALLOPS, EX_LOG_ALLOPS}, {"EX_PSEUDO", EX_PSEUDO, EX_PSEUDO}, {NULL, 0, 0} }; int nfs_expinfo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct exportinfo exi; char *path; int status; uint_t v_flag; if (argc > 0) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of struct exporinfo\n"); return (DCMD_USAGE); } if (mdb_vread(&exi, sizeof (exi), addr) == -1) { mdb_warn("can't read struct exportinfo"); return (DCMD_ERR); } if (mdb_vread(&v_flag, sizeof (v_flag), (uintptr_t)exi.exi_vp + OFFSETOF(vnode_t, v_flag)) == -1) { mdb_warn("can't read v_flag"); return (DCMD_ERR); } path = mdb_alloc(exi.exi_export.ex_pathlen + 1, UM_SLEEP | UM_GC); if (mdb_readstr(path, exi.exi_export.ex_pathlen + 1, (uintptr_t)exi.exi_export.ex_path) == -1) { mdb_warn("can't read ex_path"); return (DCMD_ERR); } mdb_printf("\n%s %p\n", path, addr); mdb_inc_indent(4); mdb_printf("rtvp: %?p ref : %-8u flag: %#x (%b)%s\n", exi.exi_vp, exi.exi_count, exi.exi_export.ex_flags, exi.exi_export.ex_flags, exp_flag_bits, v_flag & VROOT ? " VROOT" : ""); mdb_printf("dvp : %?p anon: %-8u logb: %p\n", exi.exi_dvp, exi.exi_export.ex_anon, exi.exi_logbuffer); mdb_printf("seci: %?p nsec: %-8i fsid: (%#x %#x)\n", exi.exi_export.ex_secinfo, exi.exi_export.ex_seccnt, exi.exi_fsid.val[0], exi.exi_fsid.val[1]); status = print_sec(exi.exi_export.ex_seccnt, (uintptr_t)exi.exi_export.ex_secinfo); if (status != DCMD_OK) return (status); if (exi.exi_visible) { mdb_printf("PseudoFS Nodes:\n"); mdb_inc_indent(4); if (mdb_pwalk_dcmd("nfs_expvis", "nfs_expvis", 0, NULL, (uintptr_t)exi.exi_visible) == -1) { mdb_warn("walk through exi_visible failed"); return (DCMD_ERR); } mdb_dec_indent(4); } mdb_dec_indent(4); return (DCMD_OK); } /* * nfs_exptable dcmd implementation */ int nfs_exptable_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t glbls; uintptr_t zonep; nfs_globals_t nfsglbls; if (argc > 0) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) != 0) { zonep = addr; } else { if (mdb_readsym( &zonep, sizeof (uintptr_t), "global_zone") == -1) { mdb_warn("Failed to find global_zone"); return (DCMD_ERR); } } mdb_printf("The zone is %p\n", zonep); if (zoned_get_nfs_globals(zonep, &glbls) != DCMD_OK) { mdb_warn("failed to get zoned specific NFS globals"); return (DCMD_ERR); } mdb_printf("The nfs zone globals are %p\n", glbls); if (mdb_vread(&nfsglbls, sizeof (nfs_globals_t), glbls) == -1) { mdb_warn("can't read zone globals"); return (DCMD_ERR); } mdb_printf("The nfs globals are %p\n", nfsglbls); mdb_printf("The address of nfsglbls.nfs_export is %p\n", nfsglbls.nfs_export); mdb_printf("The exptable address is %p\n", nfsglbls.nfs_export->exptable); if (mdb_pwalk_dcmd("nfs_expinfo", "nfs_expinfo", 0, NULL, (uintptr_t)nfsglbls.nfs_export->exptable) == -1) { mdb_warn("exptable walk failed"); return (DCMD_ERR); } return (DCMD_OK); } /* * nfs_exptable_path dcmd implementation */ int nfs_exptable_path_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t glbls; uintptr_t zonep; nfs_globals_t nfsglbls; if (argc > 0) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) != 0) { zonep = addr; } else { if (mdb_readsym(&zonep, sizeof (uintptr_t), "global_zone") == -1) { mdb_warn("Failed to find global_zone"); return (DCMD_ERR); } } if (zoned_get_nfs_globals(zonep, &glbls) != DCMD_OK) { mdb_warn("failed to get zoned specific NFS globals"); return (DCMD_ERR); } if (mdb_vread(&nfsglbls, sizeof (nfs_globals_t), glbls) == -1) { mdb_warn("can't read zone globals"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("nfs_expinfo_path", "nfs_expinfo", 0, NULL, (uintptr_t)nfsglbls.nfs_export->exptable) == -1) { mdb_warn("exptable walk failed"); return (DCMD_ERR); } return (DCMD_OK); } /* * nfs_nstree dcmd implementation */ static int print_tree(uintptr_t addr, uint_t opt_v, treenode_t *tn, char *s) { while (addr != 0) { uintptr_t a; if (mdb_vread(tn, sizeof (*tn), addr) == -1) { mdb_warn("can't read treenode"); return (DCMD_ERR); } /* a = tn->tree_exi->exi_vp */ if (mdb_vread(&a, sizeof (a), (uintptr_t)tn->tree_exi + OFFSETOF(struct exportinfo, exi_vp)) == -1) { mdb_warn("can't read exi_vp"); return (DCMD_ERR); } /* a = ((vnode_t *)a)->v_path */ if (mdb_vread(&a, sizeof (a), a + OFFSETOF(vnode_t, v_path)) == -1) { mdb_warn("can't read v_path"); return (DCMD_ERR); } if (mdb_readstr(s, PATH_MAX, a) == -1) { mdb_warn("can't read v_path string"); return (DCMD_ERR); } mdb_printf("\n\nTREENODE:\n%s\n", s); mdb_inc_indent(2); if (opt_v) mdb_printf("\nDump treenode:\n\n"); mdb_printf("addr: %p\n", addr); mdb_printf("tree_parent: %p\n", tn->tree_parent); mdb_printf("tree_child_first: %p\n", tn->tree_child_first); mdb_printf("tree_sibling: %p\n", tn->tree_sibling); mdb_printf("tree_exi: %p\n", tn->tree_exi); mdb_printf("tree_vis: %p\n", tn->tree_vis); if (opt_v) { mdb_printf("\nDump exportinfo:\n"); if (mdb_call_dcmd("nfs_expinfo", (uintptr_t)tn->tree_exi, DCMD_ADDRSPEC, 0, NULL) == -1) return (DCMD_ERR); if (tn->tree_vis) { mdb_printf("\nDump exp_visible:\n\n"); if (mdb_call_dcmd("nfs_expvis", (uintptr_t)tn->tree_vis, DCMD_ADDRSPEC, 0, NULL) == -1) return (DCMD_ERR); } } addr = (uintptr_t)tn->tree_sibling; if (tn->tree_child_first != NULL) { int status; mdb_inc_indent(2); status = print_tree((uintptr_t)tn->tree_child_first, opt_v, tn, s); if (status != DCMD_OK) return (status); mdb_dec_indent(2); } mdb_dec_indent(2); } return (DCMD_OK); } int nfs_nstree_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t glbls; uintptr_t zonep; nfs_globals_t nfsglbls; nfs_export_t exp; uint_t opt_v = FALSE; treenode_t tn; char *s; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) != 0) { zonep = addr; } else { if (mdb_readsym(&zonep, sizeof (uintptr_t), "global_zone") == -1) { mdb_warn("Failed to find global_zone"); return (DCMD_ERR); } } if (zoned_get_nfs_globals(zonep, &glbls) != DCMD_OK) { mdb_warn("failed to get zoned specific NFS globals"); return (DCMD_ERR); } if (mdb_vread(&nfsglbls, sizeof (nfs_globals_t), glbls) == -1) { mdb_warn("can't read zone globals"); return (DCMD_ERR); } if (mdb_vread(&exp, sizeof (nfs_export_t), (uintptr_t)nfsglbls.nfs_export) == -1) { mdb_warn("can't read nfs_export"); return (DCMD_ERR); } s = mdb_alloc(PATH_MAX, UM_SLEEP | UM_GC); return (print_tree((uintptr_t)exp.ns_root, opt_v, &tn, s)); } void nfs_nstree_help(void) { mdb_printf( "-v dump also exportinfo and exp_visible structures\n"); } /* * nfs_fid_hashdist dcmd implementation */ static int calc_hashdist(struct exp_walk_arg *arg, uint_t opt_v, uintptr_t tbladdr) { struct exportinfo **table; int i; u_longlong_t min = 0; u_longlong_t max = 0; u_longlong_t sum = 0; u_longlong_t sum_sqr = 0; table = mdb_alloc(arg->size * sizeof (struct exportinfo *), UM_SLEEP | UM_GC); if (mdb_vread(table, arg->size * sizeof (struct exportinfo *), tbladdr) == -1) { mdb_warn("can't vreadsym exptable"); return (DCMD_ERR); } for (i = 0; i < arg->size; i++) { u_longlong_t len; uintptr_t addr; for (addr = (uintptr_t)table[i], len = 0; addr; len++) if (mdb_vread(&addr, sizeof (addr), addr + arg->offset) == -1) { mdb_warn("unable to read pointer to next " "exportinfo struct"); return (DCMD_ERR); } if (i == 0 || len < min) min = len; if (len > max) max = len; sum += len; sum_sqr += len * len; if (opt_v) mdb_printf("%u\n", len); } mdb_printf("TABLE: %s\n", arg->name); mdb_printf("items/size = %u/%u\n", sum, arg->size); mdb_printf("min/avg/max/variance = %u/%u/%u/%u\n", min, sum / arg->size, max, (sum_sqr - (sum * sum) / arg->size) / arg->size); return (DCMD_OK); } int nfs_fid_hashdist_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of export table\n"); return (DCMD_USAGE); } return (calc_hashdist(&nfs_expinfo_arg, opt_v, addr)); } void nfs_hashdist_help(void) { mdb_printf( "-v displays individual bucket lengths\n"); } /* * nfs_path_hashdist dcmd implementation */ int nfs_path_hashdist_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of export table\n"); return (DCMD_USAGE); } return (calc_hashdist(&nfs_expinfo_path_arg, opt_v, addr)); } /* * nfs_expinfo/nfs_expinfo_path walkers implementation */ struct exp_walk_arg nfs_expinfo_arg = { "exptable", EXPTABLESIZE, OFFSETOF(struct exportinfo, fid_hash) + OFFSETOF(struct exp_hash, next) }; struct exp_walk_arg nfs_expinfo_path_arg = { "exptable_path_hash", PKP_HASH_SIZE, OFFSETOF(struct exportinfo, path_hash) + OFFSETOF(struct exp_hash, next) }; int nfs_expinfo_walk_init(mdb_walk_state_t *wsp) { struct exp_walk_arg *exp_arg = wsp->walk_arg; hash_table_walk_arg_t *arg; int status; if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } arg = mdb_alloc(sizeof (hash_table_walk_arg_t), UM_SLEEP); arg->array_addr = wsp->walk_addr; arg->array_len = exp_arg->size; arg->head_size = sizeof (struct exportinfo *); arg->first_name = "exportinfo pointer"; arg->first_offset = 0; arg->member_type_name = "struct exportinfo"; arg->member_size = sizeof (struct exportinfo); arg->next_offset = exp_arg->offset; wsp->walk_arg = arg; status = hash_table_walk_init(wsp); if (status != WALK_NEXT) mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t)); return (status); } void nfs_expinfo_walk_fini(mdb_walk_state_t *wsp) { hash_table_walk_fini(wsp); mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t)); } /* * nfs_expvis walker implementation */ int nfs_expvis_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } return (WALK_NEXT); } int nfs_expvis_walk_step(mdb_walk_state_t *wsp) { exp_visible_t vis; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&vis, sizeof (vis), addr) == -1) { mdb_warn("failed to read exp_visible_t at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)vis.vis_next; return (wsp->walk_callback(addr, &vis, wsp->walk_cbdata)); } /* * nfssrv_globals walker, gets the nfs globals for each zone * * Note: Most of the NFS dcmds take a zone pointer, at some point we may * want to change that to take the nfs globals address and aviod the zone * key lookup. This walker could be helpful in that change. */ int nfssrv_globals_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; if (wsp->walk_addr == 0) { if (mdb_lookup_by_name("nfssrv_globals_list", &sym) == -1) { mdb_warn("failed to find 'nfssrv_globals_list'"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sym.st_value; } else { mdb_printf("nfssrv_globals walk only supports global walks\n"); return (WALK_ERR); } if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("couldn't walk 'list'"); return (WALK_ERR); } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int nfssrv_globals_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #ifndef _NFSSRV_H #define _NFSSRV_H #include extern int nfs_expvis_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs_expinfo_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs_exptable_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs_exptable_path_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs_nstree_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs_nstree_help(void); extern int nfs_fid_hashdist_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int nfs_path_hashdist_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void nfs_hashdist_help(void); struct exp_walk_arg { const char *name; /* variable name with the exportinfo array */ int size; /* size of the exportinfo array */ size_t offset; /* offset for the walker */ }; extern struct exp_walk_arg nfs_expinfo_arg; extern struct exp_walk_arg nfs_expinfo_path_arg; extern int nfs_expinfo_walk_init(mdb_walk_state_t *); extern void nfs_expinfo_walk_fini(mdb_walk_state_t *); extern int nfs_expvis_walk_init(mdb_walk_state_t *); extern int nfs_expvis_walk_step(mdb_walk_state_t *); extern int nfssrv_globals_walk_init(mdb_walk_state_t *); extern int nfssrv_globals_walk_step(mdb_walk_state_t *); #endif /* _NFSSRV_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include "rfs4.h" #include "common.h" /* * rfs4_db dcmd implementation */ int rfs4_db_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t glbls; uintptr_t zonep; rfs4_database_t rfs4_db; nfs_globals_t nfsglbls; struct nfs4_srv nfs4srv; if (argc > 0) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) != 0) { zonep = addr; } else { if (mdb_readsym(&zonep, sizeof (uintptr_t), "global_zone") == -1) { mdb_warn("Failed to find global_zone"); return (DCMD_ERR); } } if (zoned_get_nfs_globals(zonep, &glbls) != DCMD_OK) { mdb_warn("failed to get zoned specific NFS globals"); return (DCMD_ERR); } if (mdb_vread(&nfsglbls, sizeof (nfs_globals_t), glbls) == -1) { mdb_warn("can't read zone globals"); return (DCMD_ERR); } if (mdb_vread(&nfs4srv, sizeof (struct nfs4_srv), (uintptr_t)nfsglbls.nfs4_srv) == -1) { mdb_warn("can't read NFS4 server structure"); return (DCMD_ERR); } if (mdb_vread(&rfs4_db, sizeof (rfs4_database_t), (uintptr_t)nfs4srv.nfs4_server_state) == -1) { mdb_warn("can't read NFS4 server state"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("rfs4_db_tbl", "rfs4_tbl", 0, NULL, (uintptr_t)rfs4_db.db_tables) == -1) { mdb_warn("failed to walk tables"); return (DCMD_ERR); } return (DCMD_OK); } /* * rfs4_tbl dcmd implementation */ int rfs4_tbl_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rfs4_table_t tbl; char name[14]; uint_t opt_v = FALSE; uint_t opt_w = FALSE; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of rfs4_table_t\n"); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, 'w', MDB_OPT_SETBITS, TRUE, &opt_w, NULL) != argc) return (DCMD_USAGE); if (opt_w) { mdb_arg_t arg; mdb_arg_t *argp = NULL; int n = 0; if (opt_v) { arg.a_type = MDB_TYPE_STRING; arg.a_un.a_str = "-v"; argp = &arg; n = 1; } /* Walk through all tables */ if (mdb_pwalk_dcmd("rfs4_db_tbl", "rfs4_tbl", n, argp, addr) == -1) { mdb_warn("failed to walk tables\n"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&tbl, sizeof (tbl), addr) == -1) { mdb_warn("can't read rfs4_table_t"); return (DCMD_ERR); } if (!opt_v && DCMD_HDRSPEC(flags)) { const size_t ptrsize = mdb_snprintf(NULL, 0, "%?p"); size_t sz = ptrsize + 1 + sizeof (name) + 8 + 5 - 7; size_t i; mdb_printf("%"); for (i = 0; i < (sz + 1) / 2; i++) mdb_printf("-"); mdb_printf(" Table "); for (i = 0; i < sz - (sz + 1) / 2; i++) mdb_printf("-"); mdb_printf(" Bkt "); sz = ptrsize + 5 + 5 - 9; for (i = 0; i < (sz + 1) / 2; i++) mdb_printf("-"); mdb_printf(" Indices "); for (i = 0; i < sz - (sz + 1) / 2; i++) mdb_printf("-"); mdb_printf("%\n"); mdb_printf("%%%-?s %-*s%-8s Cnt % %Cnt % " "%%-?s Cnt Max %%\n", "Address", sizeof (name), "Name", "Flags", "Pointer"); } if (mdb_readstr(name, sizeof (name), (uintptr_t)tbl.dbt_name) == -1) { mdb_warn("can't read dbt_name"); return (DCMD_ERR); } mdb_printf("%?p %-*s%08x %04u %04u %?p %04u %04u\n", addr, sizeof (name), name, tbl.dbt_debug, tbl.dbt_count, tbl.dbt_len, tbl.dbt_indices, tbl.dbt_idxcnt, tbl.dbt_maxcnt); if (opt_v) { mdb_inc_indent(8); mdb_printf("db = %p\n", tbl.dbt_db); mdb_printf("t_lock = %s\n", common_rwlock(tbl.dbt_t_lock)); mdb_printf("lock = %s\n", common_mutex(tbl.dbt_lock)); mdb_printf("id_space = %p\n", tbl.dbt_id_space); mdb_printf("min_cache_time = %lu\n", tbl.dbt_min_cache_time); mdb_printf("max_cache_time = %lu\n", tbl.dbt_max_cache_time); mdb_printf("usize = %u\n", tbl.dbt_usize); mdb_printf("maxentries = %u\n", tbl.dbt_maxentries); mdb_printf("len = %u\n", tbl.dbt_len); mdb_printf("count = %u\n", tbl.dbt_count); mdb_printf("idxcnt = %u\n", tbl.dbt_idxcnt); mdb_printf("maxcnt = %u\n", tbl.dbt_maxcnt); mdb_printf("ccnt = %u\n", tbl.dbt_ccnt); mdb_printf("indices = %p\n", tbl.dbt_indices); mdb_printf("create = %a\n", tbl.dbt_create); mdb_printf("destroy = %a\n", tbl.dbt_destroy); mdb_printf("expiry = %a\n", tbl.dbt_expiry); mdb_printf("mem_cache = %p\n", tbl.dbt_mem_cache); mdb_printf("debug = %08x\n", tbl.dbt_debug); mdb_printf("reaper_shutdown = %s\n\n", tbl.dbt_reaper_shutdown ? "TRUE" : "FALSE"); mdb_dec_indent(8); } return (DCMD_OK); } void rfs4_tbl_help(void) { mdb_printf( "-v display more details about the table\n" "-w walks along all tables in the list\n" "\n" "The following two commands are equivalent:\n" " ::rfs4_tbl -w\n" " ::walk rfs4_db_tbl|::rfs4_tbl\n"); } /* * rfs4_tbl dcmd implementation */ int rfs4_idx_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rfs4_index_t idx; char name[19]; uint_t opt_w = FALSE; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of rfs4_index_t\n"); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'w', MDB_OPT_SETBITS, TRUE, &opt_w, NULL) != argc) return (DCMD_USAGE); if (opt_w) { /* Walk through all tables */ if (mdb_pwalk_dcmd("rfs4_db_idx", "rfs4_idx", 0, NULL, addr) == -1) { mdb_warn("failed to walk indices"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&idx, sizeof (idx), addr) == -1) { mdb_warn("can't read rfs4_index_t"); return (DCMD_ERR); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%%-?s %-*sCreat Tndx %-?s%%\n", "Address", sizeof (name), "Name", "Buckets"); if (mdb_readstr(name, sizeof (name), (uintptr_t)idx.dbi_keyname) == -1) { mdb_warn("can't read dbi_keyname"); return (DCMD_ERR); } mdb_printf("%?p %-*s%-5s %04u %?p\n", addr, sizeof (name), name, idx.dbi_createable ? "TRUE" : "FALSE", idx.dbi_tblidx, idx.dbi_buckets); return (DCMD_OK); } void rfs4_idx_help(void) { mdb_printf( "-w walks along all indices in the list\n" "\n" "The following two commands are equivalent:\n" " ::rfs4_idx -w\n" " ::walk rfs4_db_idx|::rfs4_idx\n"); } /* * rfs4_bkt dcmd implementation */ static int rfs4_print_bkt_cb(uintptr_t addr, const void *data, void *cb_data) { const rfs4_bucket_t *bkt = data; rfs4_link_t *lp; rfs4_link_t rl; for (lp = bkt->dbk_head; lp; lp = rl.next) { rfs4_dbe_t dbe; if (mdb_vread(&rl, sizeof (rl), (uintptr_t)lp) == -1) { mdb_warn("can't read rfs4_link_t"); return (WALK_ERR); } if (mdb_vread(&dbe, sizeof (dbe), (uintptr_t)rl.entry) == -1) { mdb_warn("can't read rfs4_dbe_t"); return (WALK_ERR); } mdb_inc_indent(4); mdb_printf( "DBE { Address=%p data->%p refcnt=%u skipsearch=%u\n" " invalid=%u time_rele=%Y\n}\n", rl.entry, dbe.dbe_data, dbe.dbe_refcnt, dbe.dbe_skipsearch, dbe.dbe_invalid, dbe.dbe_time_rele); mdb_dec_indent(4); } return (WALK_NEXT); } int rfs4_bkt_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc > 0) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of rfs4_index_t\n"); return (DCMD_USAGE); } if (mdb_pwalk("rfs4_db_bkt", rfs4_print_bkt_cb, NULL, addr) == -1) { mdb_warn("bucket walking failed"); return (DCMD_ERR); } return (DCMD_OK); } /* * rfs4_oo dcmd implementation */ static int rfs4_print_oo(uintptr_t addr, uintptr_t client) { rfs4_openowner_t oo; uint8_t *owner_val; uint_t i; if (mdb_vread(&oo, sizeof (oo), addr) == -1) { mdb_warn("can't read rfs4_openowner_t"); return (DCMD_ERR); } if (client && (client != (uintptr_t)oo.ro_client)) return (DCMD_OK); owner_val = mdb_alloc(oo.ro_owner.owner_len, UM_SLEEP | UM_GC); if (mdb_vread(owner_val, oo.ro_owner.owner_len, (uintptr_t)oo.ro_owner.owner_val) == -1) { mdb_warn("can't read owner_val"); return (DCMD_ERR); } mdb_printf("%?p %?p %?p %10u %16llx ", addr, oo.ro_dbe, oo.ro_client, oo.ro_open_seqid, oo.ro_owner.clientid); for (i = 0; i < oo.ro_owner.owner_len; i++) mdb_printf("%02x", owner_val[i]); mdb_printf("\n"); return (DCMD_OK); } static int rfs4_print_oo_cb(uintptr_t addr, const void *data, void *cb_data) { if (mdb_vread(&addr, sizeof (addr), addr + OFFSETOF(rfs4_dbe_t, dbe_data)) == -1) { mdb_warn("failed to read dbe_data"); return (WALK_ERR); } return (rfs4_print_oo(addr, (uintptr_t)cb_data) == DCMD_OK ? WALK_NEXT : WALK_ERR); } static void print_oo_hdr(void) { mdb_printf("%%%-?s %-?s %-?s %10s %16s %-16s%%\n", "Address", "Dbe", "Client", "OpenSeqID", "clientid", "owner"); } int rfs4_oo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc > 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) print_oo_hdr(); if (flags & DCMD_ADDRSPEC) return (rfs4_print_oo(addr, 0)); if (mdb_walk("OpenOwner_entry_cache", rfs4_print_oo_cb, NULL) == -1) { mdb_warn("walking of %s failed", "OpenOwner_entry_cache"); return (DCMD_ERR); } return (DCMD_OK); } /* * rfs4_osid dcmd implementation */ static void print_stateid(const stateid_t *st) { const char *s; mdb_printf("chgseq=%u boottime=%x pid=%i\n", st->bits.chgseq, st->bits.boottime, st->bits.pid); switch ((stateid_type_t)st->bits.type) { case OPENID: s = "OpenID"; break; case LOCKID: s = "LockID"; break; case DELEGID: s = "DelegID"; break; default: s = ""; break; } mdb_printf("type=%s ident=%x\n", s, st->bits.ident); } static int rfs4_print_osid(uintptr_t addr, uint_t opt_v) { rfs4_state_t osid; size_t i; const char *s; if (mdb_vread(&osid, sizeof (osid), addr) == -1) { mdb_warn("can't read rfs4_state_t"); return (DCMD_ERR); } mdb_printf("%?p %?p %?p %?p ", addr, osid.rs_dbe, osid.rs_owner, osid.rs_finfo); for (i = 0; i < sizeof (stateid4); i++) mdb_printf("%02x", ((uint8_t *)&osid.rs_stateid)[i]); mdb_printf("\n"); if (!opt_v) return (DCMD_OK); mdb_inc_indent(8); print_stateid(&osid.rs_stateid); switch (osid.rs_share_access) { case 0: s = "none"; break; case OPEN4_SHARE_ACCESS_READ: s = "read"; break; case OPEN4_SHARE_ACCESS_WRITE: s = "write"; break; case OPEN4_SHARE_ACCESS_BOTH: s = "read-write"; break; default: s = ""; break; } mdb_printf("share_access: %s", s); switch (osid.rs_share_deny) { case OPEN4_SHARE_DENY_NONE: s = "none"; break; case OPEN4_SHARE_DENY_READ: s = "read"; break; case OPEN4_SHARE_DENY_WRITE: s = "write"; break; case OPEN4_SHARE_DENY_BOTH: s = "read-write"; break; default: s = ""; break; } mdb_printf(" share_deny: %s file is: %s\n", s, osid.rs_closed ? "CLOSED" : "OPEN"); mdb_dec_indent(8); return (DCMD_OK); } static int rfs4_print_osid_cb(uintptr_t addr, const void *data, void *cb_data) { /* addr = ((rfs4_dbe_t *)addr)->dbe_data */ if (mdb_vread(&addr, sizeof (addr), addr + OFFSETOF(rfs4_dbe_t, dbe_data)) == -1) { mdb_warn("failed to read dbe_data"); return (WALK_ERR); } return (rfs4_print_osid(addr, *(uint_t *)cb_data) == DCMD_OK ? WALK_NEXT : WALK_ERR); } int rfs4_osid_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%%%-?s %-?s %-?s %-?s %-*s%%\n", "Address", "Dbe", "Owner", "finfo", 2 * sizeof (stateid4), "StateID"); if (flags & DCMD_ADDRSPEC) return (rfs4_print_osid(addr, opt_v)); if (mdb_walk("OpenStateID_entry_cache", rfs4_print_osid_cb, &opt_v) == -1) { mdb_warn("walking of %s failed", "OpenStateID_entry_cache"); return (DCMD_ERR); } return (DCMD_OK); } /* * rfs4_file dcmd implementation */ static void print_time(time_t t) { if (t == 0) mdb_printf("0"); else mdb_printf("%Y", t); } static int rfs4_print_file(uintptr_t addr, uint_t opt_v) { rfs4_file_t f; uint8_t *nfs_fh4_val; uint_t i; uintptr_t vp; char *s; const rfs4_dinfo_t *di; if (mdb_vread(&f, sizeof (f), addr) == -1) { mdb_warn("can't read rfs4_file_t"); return (DCMD_ERR); } nfs_fh4_val = mdb_alloc(f.rf_filehandle.nfs_fh4_len, UM_SLEEP | UM_GC); if (mdb_vread(nfs_fh4_val, f.rf_filehandle.nfs_fh4_len, (uintptr_t)f.rf_filehandle.nfs_fh4_val) == -1) { mdb_warn("can't read nfs_fh4_val"); return (DCMD_ERR); } mdb_printf("%?p %?p %?p ", addr, f.rf_dbe, f.rf_vp); for (i = 0; i < f.rf_filehandle.nfs_fh4_len; i++) mdb_printf("%02x", nfs_fh4_val[i]); mdb_printf("\n"); if (!opt_v) return (DCMD_OK); /* vp = f.rf_vp->v_path */ if (mdb_vread(&vp, sizeof (vp), (uintptr_t)f.rf_vp + OFFSETOF(vnode_t, v_path)) == -1) { mdb_warn("can't read vnode_t"); return (DCMD_ERR); } s = mdb_alloc(PATH_MAX, UM_SLEEP | UM_GC); if (mdb_readstr(s, PATH_MAX, vp) == -1) { mdb_warn("can't read v_path"); return (DCMD_ERR); } mdb_inc_indent(8); mdb_printf("path=%s\n", s); di = &f.rf_dinfo; switch (di->rd_dtype) { case OPEN_DELEGATE_NONE: s = "None"; break; case OPEN_DELEGATE_READ: s = "Read"; break; case OPEN_DELEGATE_WRITE: s = "Write"; break; default: s = "?????"; break; } mdb_printf("dtype=%-5s rdgrants=%u wrgrants=%u recall_cnt=%i " "ever_recalled=%s\n", s, di->rd_rdgrants, di->rd_wrgrants, di->rd_recall_count, (di->rd_ever_recalled == TRUE) ? "True" : "False"); mdb_printf("Time: "); mdb_inc_indent(6); mdb_printf("returned="); print_time(di->rd_time_returned); mdb_printf(" recalled="); print_time(di->rd_time_recalled); mdb_printf("\nlastgrant="); print_time(di->rd_time_lastgrant); mdb_printf(" lastwrite="); print_time(di->rd_time_lastwrite); mdb_printf("\nrm_delayed="); print_time(di->rd_time_rm_delayed); mdb_printf("\n"); mdb_dec_indent(14); return (DCMD_OK); } static int rfs4_print_file_cb(uintptr_t addr, const void *data, void *cb_data) { /* addr = ((rfs4_dbe_t *)addr)->dbe_data */ if (mdb_vread(&addr, sizeof (addr), addr + OFFSETOF(rfs4_dbe_t, dbe_data)) == -1) { mdb_warn("failed to read dbe_data"); return (WALK_ERR); } return (rfs4_print_file(addr, *(uint_t *)cb_data) == DCMD_OK ? WALK_NEXT : WALK_ERR); } int rfs4_file_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%%%-?s %-?s %-?s %-32s%%\n", "Address", "Dbe", "Vnode", "Filehandle"); if (flags & DCMD_ADDRSPEC) return (rfs4_print_file(addr, opt_v)); if (mdb_walk("File_entry_cache", rfs4_print_file_cb, &opt_v) == -1) { mdb_warn("walking of %s failed", "File_entry_cache"); return (DCMD_ERR); } return (DCMD_OK); } /* * rfs4_deleg dcmd implementation */ static int rfs4_print_deleg(uintptr_t addr, uint_t opt_v, uintptr_t client) { rfs4_deleg_state_t ds; size_t i; uintptr_t pa; char *s; if (mdb_vread(&ds, sizeof (ds), addr) == -1) { mdb_warn("can't read rfs4_deleg_state_t"); return (DCMD_ERR); } if (client && (client != (uintptr_t)ds.rds_client)) return (DCMD_OK); mdb_printf("%?p %?p ", addr, ds.rds_dbe); for (i = 0; i < sizeof (stateid4); i++) mdb_printf("%02x", ((uint8_t *)&ds.rds_delegid)[i]); mdb_printf(" %?p %?p\n", ds.rds_finfo, ds.rds_client); if (!opt_v) return (DCMD_OK); /* pa = ds.rds_finfo->rf_vp */ if (mdb_vread(&pa, sizeof (pa), (uintptr_t)ds.rds_finfo + OFFSETOF(rfs4_file_t, rf_vp)) == -1) { mdb_warn("can't read rf_vp"); return (DCMD_ERR); } /* pa = ((vnode_t *)pa)->v_path */ if (mdb_vread(&pa, sizeof (pa), pa + OFFSETOF(vnode_t, v_path)) == -1) { mdb_warn("can't read rf_vp"); return (DCMD_ERR); } s = mdb_alloc(PATH_MAX, UM_SLEEP | UM_GC); if (mdb_readstr(s, PATH_MAX, pa) == -1) { mdb_warn("can't read v_path"); return (DCMD_ERR); } mdb_inc_indent(8); mdb_printf("Time: "); mdb_inc_indent(6); mdb_printf("granted="); print_time(ds.rds_time_granted); mdb_printf(" recalled="); print_time(ds.rds_time_recalled); mdb_printf(" revoked="); print_time(ds.rds_time_revoked); mdb_dec_indent(6); mdb_printf("\npath=%s\n", s); mdb_dec_indent(8); return (DCMD_OK); } static int rfs4_print_deleg_cb(uintptr_t addr, const void *data, void *cb_data) { /* addr = ((rfs4_dbe_t *)addr)->dbe_data */ if (mdb_vread(&addr, sizeof (addr), addr + OFFSETOF(rfs4_dbe_t, dbe_data)) == -1) { mdb_warn("failed to read dbe_data"); return (WALK_ERR); } return (rfs4_print_deleg(addr, *(uint_t *)cb_data, 0) == DCMD_OK ? WALK_NEXT : WALK_ERR); } static void print_deleg_hdr(void) { mdb_printf("%%%-?s %-?s %-*s %-?s %-?s%%\n", "Address", "Dbe", 2 * sizeof (stateid4), "StateID", "File Info", "Client"); } int rfs4_deleg_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) print_deleg_hdr(); if (flags & DCMD_ADDRSPEC) return (rfs4_print_deleg(addr, opt_v, 0)); if (mdb_walk("DelegStateID_entry_cache", rfs4_print_deleg_cb, &opt_v) == -1) { mdb_warn("walking of %s failed", "DelegStateID_entry_cache"); return (DCMD_ERR); } return (DCMD_OK); } /* * rfs4_lo dcmd implementation */ static int rfs4_print_lo(uintptr_t addr, uintptr_t client) { rfs4_lockowner_t lo; uint8_t *owner_val; uint_t i; if (mdb_vread(&lo, sizeof (lo), addr) == -1) { mdb_warn("can't read rfs4_lockowner_t"); return (DCMD_ERR); } if (client && (client != (uintptr_t)lo.rl_client)) return (DCMD_OK); owner_val = mdb_alloc(lo.rl_owner.owner_len, UM_SLEEP | UM_GC); if (mdb_vread(owner_val, lo.rl_owner.owner_len, (uintptr_t)lo.rl_owner.owner_val) == -1) { mdb_warn("can't read owner_val"); return (DCMD_ERR); } mdb_printf("%?p %?p %?p %10i %16llx ", addr, lo.rl_dbe, lo.rl_client, lo.rl_pid, lo.rl_owner.clientid); for (i = 0; i < lo.rl_owner.owner_len; i++) mdb_printf("%02x", owner_val[i]); mdb_printf("\n"); return (DCMD_OK); } static int rfs4_print_lo_cb(uintptr_t addr, const void *data, void *cb_data) { if (mdb_vread(&addr, sizeof (addr), addr + OFFSETOF(rfs4_dbe_t, dbe_data)) == -1) { mdb_warn("failed to read dbe_data"); return (WALK_ERR); } return (rfs4_print_lo(addr, (uintptr_t)cb_data) == DCMD_OK ? WALK_NEXT : WALK_ERR); } static void print_lo_hdr(void) { mdb_printf("%%%-?s %-?s %-?s %10s %16s %-16s%%\n", "Address", "Dbe", "Client", "Pid", "clientid", "owner"); } int rfs4_lo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc > 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) print_lo_hdr(); if (flags & DCMD_ADDRSPEC) return (rfs4_print_lo(addr, 0)); if (mdb_walk("Lockowner_entry_cache", rfs4_print_lo_cb, NULL) == -1) { mdb_warn("walking of %s failed", "Lockowner_entry_cache"); return (DCMD_ERR); } return (DCMD_OK); } /* * rfs4_lsid dcmd implementation */ static int rfs4_print_lsid(uintptr_t addr, uint_t opt_v) { rfs4_lo_state_t lsid; size_t i; if (mdb_vread(&lsid, sizeof (lsid), addr) == -1) { mdb_warn("can't read rfs4_lo_state_t"); return (DCMD_ERR); } mdb_printf("%?p %?p %?p %10u ", addr, lsid.rls_dbe, lsid.rls_locker, lsid.rls_seqid); for (i = 0; i < sizeof (stateid4); i++) mdb_printf("%02x", ((uint8_t *)&lsid.rls_lockid)[i]); mdb_printf("\n"); if (!opt_v) return (DCMD_OK); mdb_inc_indent(8); print_stateid(&lsid.rls_lockid); mdb_dec_indent(8); return (DCMD_OK); } static int rfs4_print_lsid_cb(uintptr_t addr, const void *data, void *cb_data) { if (mdb_vread(&addr, sizeof (addr), addr + OFFSETOF(rfs4_dbe_t, dbe_data)) == -1) { mdb_warn("failed to read dbe_data"); return (WALK_ERR); } return (rfs4_print_lsid(addr, *(uint_t *)cb_data) == DCMD_OK ? WALK_NEXT : WALK_ERR); } int rfs4_lsid_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opt_v = FALSE; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%%%-?s %-?s %-?s %10s %-*s%%\n", "Address", "Dbe", "Locker", "SeqID", 2 * sizeof (stateid4), "Lockid"); if (flags & DCMD_ADDRSPEC) return (rfs4_print_lsid(addr, opt_v)); if (mdb_walk("LockStateID_entry_cache", rfs4_print_lsid_cb, &opt_v) == -1) { mdb_warn("walking of %s failed", "LockStateID_entry_cache"); return (DCMD_ERR); } return (DCMD_OK); } /* * rfs4_client dcmd implementation */ static int rfs4_print_client(uintptr_t addr) { rfs4_client_t cl; if (mdb_vread(&cl, sizeof (cl), addr) == -1) { mdb_warn("can't read rfs4_client_t"); return (DCMD_ERR); } mdb_printf("%?p %?p %-16llx %-16llx %-5s %-5s %?p %-20Y\n", addr, cl.rc_dbe, cl.rc_clientid, cl.rc_confirm_verf, cl.rc_need_confirm ? "True" : "False", cl.rc_unlksys_completed ? "True" : "False", cl.rc_cp_confirmed, cl.rc_last_access); return (DCMD_OK); } static int rfs4_client_deleg_cb(uintptr_t addr, const void *data, void *cb_data) { /* addr = ((rfs4_dbe_t *)addr)->dbe_data */ if (mdb_vread(&addr, sizeof (addr), addr + OFFSETOF(rfs4_dbe_t, dbe_data)) == -1) { mdb_warn("failed to read dbe_data"); return (WALK_ERR); } return (rfs4_print_deleg(addr, FALSE, (uintptr_t)cb_data) == DCMD_OK ? WALK_NEXT : WALK_ERR); } static int rfs4_print_client_cb(uintptr_t addr, const void *data, void *cb_data) { clientid4 clid; /* addr = ((rfs4_dbe_t *)addr)->dbe_data */ if (mdb_vread(&addr, sizeof (addr), addr + OFFSETOF(rfs4_dbe_t, dbe_data)) == -1) { mdb_warn("failed to read dbe_data"); return (WALK_ERR); } /* if no clid specified then print all clients */ if (!cb_data) return (rfs4_print_client(addr) == DCMD_OK ? WALK_NEXT : WALK_ERR); /* clid = ((rfs4_client_t *)addr)->rc_clientid */ if (mdb_vread(&clid, sizeof (clid), addr + OFFSETOF(rfs4_client_t, rc_clientid)) == -1) { mdb_warn("can't read rc_clientid"); return (WALK_ERR); } /* clid does not match, do not print the client */ if (clid != *(clientid4 *)cb_data) return (WALK_NEXT); if (rfs4_print_client(addr) != DCMD_OK) return (WALK_ERR); mdb_printf("\n"); print_oo_hdr(); if (mdb_walk("OpenOwner_entry_cache", rfs4_print_oo_cb, (void *)addr) == -1) { mdb_warn("walking of %s failed", "OpenOwner_entry_cache"); return (WALK_ERR); } mdb_printf("\n"); print_lo_hdr(); if (mdb_walk("Lockowner_entry_cache", rfs4_print_lo_cb, (void *)addr) == -1) { mdb_warn("walking of %s failed", "Lockowner_entry_cache"); return (WALK_ERR); } mdb_printf("\n"); print_deleg_hdr(); if (mdb_walk("DelegStateID_entry_cache", rfs4_client_deleg_cb, (void *)addr) == -1) { mdb_warn("walking of %s failed", "DelegStateID_entry_cache"); return (WALK_ERR); } return (WALK_DONE); } int rfs4_client_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { clientid4 clid; if (mdb_getopts(argc, argv, 'c', MDB_OPT_UINT64, &clid, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) mdb_printf("%%%-?s %-?s %-16s %-16s NCnfm unlnk %-?s " "%-20s%%\n", "Address", "dbe", "clientid", "confirm_verf", "cp_confirmed", "Last Access"); if ((argc == 0) && (flags & DCMD_ADDRSPEC)) return (rfs4_print_client(addr)); if (mdb_walk("Client_entry_cache", rfs4_print_client_cb, (argc > 0) ? &clid : NULL) == -1) { mdb_warn("walking of %s failed", "Client_entry_cache"); return (DCMD_ERR); } return (DCMD_OK); } void rfs4_client_help(void) { mdb_printf( "-c print all NFSv4 server state entries referencing\n" " the client. In this case the supplied\n" " address is ignored\n"); } /* * rfs4_db_tbl walker implementation */ int rfs4_db_tbl_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("db tbl global walk not supported"); return (WALK_ERR); } return (WALK_NEXT); } int rfs4_db_tbl_walk_step(mdb_walk_state_t *wsp) { rfs4_table_t tbl; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&tbl, sizeof (tbl), addr) == -1) { mdb_warn("can't read rfs4_table_t"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)tbl.dbt_tnext; return (wsp->walk_callback(addr, &tbl, wsp->walk_cbdata)); } /* * rfs4_db_idx walker implementation */ int rfs4_db_idx_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("db idx global walk not supported"); return (WALK_ERR); } return (WALK_NEXT); } int rfs4_db_idx_walk_step(mdb_walk_state_t *wsp) { rfs4_index_t idx; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&idx, sizeof (idx), addr) == -1) { mdb_warn("can't read rfs4_index_t"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)idx.dbi_inext; return (wsp->walk_callback(addr, &idx, wsp->walk_cbdata)); } /* * rfs4_db_bkt walker implementation */ int rfs4_db_bkt_walk_init(mdb_walk_state_t *wsp) { rfs4_index_t idx; uint32_t dbt_len; if (wsp->walk_addr == 0) { mdb_warn("db bkt global walk not supported"); return (WALK_ERR); } if (mdb_vread(&idx, sizeof (idx), wsp->walk_addr) == -1) { mdb_warn("can't read rfs4_index_t"); return (WALK_ERR); } /* dbt_len = idx.dbi_table->dbt_len */ if (mdb_vread(&dbt_len, sizeof (dbt_len), (uintptr_t)idx.dbi_table + OFFSETOF(rfs4_table_t, dbt_len)) == -1) { mdb_warn("can't read dbt_len"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (dbt_len), UM_SLEEP); *(uint32_t *)wsp->walk_data = dbt_len; wsp->walk_addr = (uintptr_t)idx.dbi_buckets; return (WALK_NEXT); } int rfs4_db_bkt_walk_step(mdb_walk_state_t *wsp) { rfs4_bucket_t bkt; uintptr_t addr = wsp->walk_addr; if (*(uint32_t *)wsp->walk_data == 0) return (WALK_DONE); if (mdb_vread(&bkt, sizeof (bkt), addr) == -1) { mdb_warn("can't read rfs4_bucket_t"); return (WALK_ERR); } (*(uint32_t *)wsp->walk_data)--; wsp->walk_addr = (uintptr_t)((rfs4_bucket_t *)addr + 1); return (wsp->walk_callback(addr, &bkt, wsp->walk_cbdata)); } void rfs4_db_bkt_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (uint32_t)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #ifndef _RFS4_H #define _RFS4_H #include extern int rfs4_db_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int rfs4_tbl_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void rfs4_tbl_help(void); extern int rfs4_idx_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void rfs4_idx_help(void); extern int rfs4_bkt_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int rfs4_oo_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int rfs4_osid_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int rfs4_file_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int rfs4_deleg_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int rfs4_lo_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int rfs4_lsid_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int rfs4_client_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void rfs4_client_help(void); extern int rfs4_db_tbl_walk_init(mdb_walk_state_t *); extern int rfs4_db_tbl_walk_step(mdb_walk_state_t *); extern int rfs4_db_idx_walk_init(mdb_walk_state_t *); extern int rfs4_db_idx_walk_step(mdb_walk_state_t *); extern int rfs4_db_bkt_walk_init(mdb_walk_state_t *); extern int rfs4_db_bkt_walk_step(mdb_walk_state_t *); extern void rfs4_db_bkt_walk_fini(mdb_walk_state_t *); #endif /* _RFS4_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include "common.h" #include "svc.h" /* * svc_pool dcmd implementation */ static const char * svc_idname(uint_t id) { switch (id) { case NFS_SVCPOOL_ID: return ("NFS"); case NLM_SVCPOOL_ID: return ("NLM"); case NFS_CB_SVCPOOL_ID: return ("NFS_CB"); default: return (""); } } static void svc_print_pool(SVCPOOL *pool, uintptr_t addr) { mdb_printf("SVCPOOL = %p -> POOL ID = %s(%d)\n", addr, svc_idname(pool->p_id), pool->p_id); mdb_printf("Non detached threads = %d\n", pool->p_threads); mdb_printf("Detached threads = %d\n", pool->p_detached_threads); mdb_printf("Max threads = %d\n", pool->p_maxthreads); mdb_printf("`redline' = %d\n", pool->p_redline); mdb_printf("Reserved threads = %d\n", pool->p_reserved_threads); mdb_printf("Thread lock = %s\n", common_mutex(&pool->p_thread_lock)); mdb_printf("Asleep threads = %d\n", pool->p_asleep); mdb_printf("Request lock = %s\n", common_mutex(&pool->p_req_lock)); mdb_printf("Pending requests = %d\n", pool->p_reqs); mdb_printf("Walking threads = %d\n", pool->p_walkers); mdb_printf("Max requests from xprt = %d\n", pool->p_max_same_xprt); mdb_printf("Stack size for svc_run = %d\n", pool->p_stksize); mdb_printf("Creator lock = %s\n", common_mutex(&pool->p_creator_lock)); mdb_printf("No of Master xprt's = %d\n", pool->p_lcount); mdb_printf("rwlock for mxprtlist = %s\n", common_rwlock(&pool->p_lrwlock)); mdb_printf("master xprt list ptr = %p\n\n", pool->p_lhead); } int svc_pool_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { SVCPOOL svcpool; uint_t opt_v = FALSE; int *pools; int count, i; if ((flags & DCMD_ADDRSPEC) == 0) { /* Walk through all svcpools in the global zone */ if (mdb_walk_dcmd("svc_pool", "svc_pool", argc, argv) == -1) { mdb_warn("failed to walk svcpools"); return (DCMD_ERR); } return (DCMD_OK); } count = mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL); argc -= count; argv += count; pools = mdb_alloc(argc * sizeof (*pools), UM_SLEEP | UM_GC); for (i = 0; i < argc; i++) { const char *s; switch (argv[i].a_type) { case MDB_TYPE_STRING: s = argv[i].a_un.a_str; if (strcmp(s, "nfs") == 0) pools[i] = NFS_SVCPOOL_ID; else if (strcmp(s, "nlm") == 0) pools[i] = NLM_SVCPOOL_ID; else if (strcmp(s, "nfs_cb") == 0) pools[i] = NFS_CB_SVCPOOL_ID; else return (DCMD_USAGE); break; case MDB_TYPE_IMMEDIATE: pools[i] = (int)argv[i].a_un.a_val; break; default: return (DCMD_USAGE); } } if (mdb_vread(&svcpool, sizeof (svcpool), addr) == -1) { mdb_warn("failed to read svcpool"); return (DCMD_ERR); } /* * Make sure the svcpool is on the list (or the list is empty). * If not, just return with DCMD_OK. */ for (i = 0; i < argc; i++) { if (svcpool.p_id == pools[i]) { argc = 0; break; } } if (argc != 0) return (DCMD_OK); /* Print the svcpool */ svc_print_pool(&svcpool, addr); if (opt_v && svcpool.p_lhead && (mdb_pwalk_dcmd("svc_mxprt", "svc_mxprt", 0, NULL, (uintptr_t)svcpool.p_lhead) == -1)) return (DCMD_ERR); return (DCMD_OK); } void svc_pool_help(void) { mdb_printf( "-v display also the master xprts for the svcpools\n" "poolid either $[numeric] or verbose: nfs or nlm or nfs_cb\n" "\n" "If the poolid list is specified, only those svcpools are dumped\n" "whose poolid is in the list.\n"); } /* * svc_mxprt dcmd implementation */ static void svc_print_masterxprt(SVCMASTERXPRT *xprt) { mdb_printf("svcxprt_common structure:\n"); mdb_printf("queue ptr = %p\n", xprt->xp_wq); mdb_printf("cached cred for server = %d\n", xprt->xp_cred); mdb_printf("transport type = %d\n", xprt->xp_type); mdb_printf("TSDU or TIDU size = %d\n", xprt->xp_msg_size); mdb_printf("address = %s\n", common_netbuf_str(&xprt->xp_rtaddr)); mdb_printf("Request queue head = %p\n", xprt->xp_req_head); mdb_printf("Request queue tail = %p\n", xprt->xp_req_tail); mdb_printf("Request lock address = %s\n", common_mutex(&xprt->xp_req_lock)); mdb_printf("Current no of attached threads = %d\n", xprt->xp_threads); mdb_printf("Current no of detached threads = %d\n", xprt->xp_detached_threads); mdb_printf("Thread count lock address = %s\n\n", common_mutex(&xprt->xp_thread_lock)); } int svc_mxprt_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { SVCMASTERXPRT xprt; uint_t opt_w = FALSE; if (mdb_getopts(argc, argv, 'w', MDB_OPT_SETBITS, TRUE, &opt_w, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) { mdb_printf("requires address of SVCMASTERXPRT\n"); return (DCMD_USAGE); } if (opt_w) { /* Walk through all xprts */ if (mdb_pwalk_dcmd("svc_mxprt", "svc_mxprt", 0, NULL, addr) == -1) { mdb_warn("failed to walk svc_mxprt"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&xprt, sizeof (xprt), addr) == -1) { mdb_warn("failed to read xprt"); return (DCMD_ERR); } svc_print_masterxprt(&xprt); return (DCMD_OK); } void svc_mxprt_help(void) { mdb_printf( "-w walks along all master xprts in the list\n" "\n" "The following two commands are equivalent:\n" " ::svc_mxprt -w\n" " ::walk svc_mxprt|::svc_mxprt\n"); } /* * svc_pool walker implementation */ static int svc_get_pool(uintptr_t zone_addr, uintptr_t *svc_addr) { mdb_ctf_id_t id; ulong_t offset; uintptr_t glob_addr; if (zoned_get_zsd(zone_addr, "svc_zone_key", &glob_addr) != DCMD_OK) { mdb_warn("failed to get zoned svc"); return (WALK_ERR); } if (mdb_ctf_lookup_by_name("struct svc_globals", &id)) { mdb_warn("failed to look up type %s", "struct svc_globals"); return (WALK_ERR); } if (mdb_ctf_offsetof(id, "svc_pools", &offset)) { mdb_warn("failed to get %s offset", "svc_pools"); return (WALK_ERR); } offset /= NBBY; if (mdb_vread(svc_addr, sizeof (*svc_addr), glob_addr + offset) == -1) { mdb_warn("failed to read svc_pools address"); return (WALK_ERR); } return (WALK_NEXT); } int svc_pool_walk_init(mdb_walk_state_t *wsp) { /* Use global zone by default */ if (wsp->walk_addr == 0) { /* wsp->walk_addr = global_zone */ if (mdb_readvar(&wsp->walk_addr, "global_zone") == -1) { mdb_warn("failed to locate global_zone"); return (WALK_ERR); } } /* put svcpool address of the zone into wsp->walk_addr */ return (svc_get_pool(wsp->walk_addr, &wsp->walk_addr)); } int svc_pool_walk_step(mdb_walk_state_t *wsp) { SVCPOOL pool; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&pool, sizeof (pool), addr) == -1) { mdb_warn("failed to read SVCPOOL"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)pool.p_next; return (wsp->walk_callback(addr, &pool, wsp->walk_cbdata)); } /* * svc_mxprt walker implementation */ int svc_mxprt_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("global walk not supported"); return (WALK_ERR); } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int svc_mxprt_walk_step(mdb_walk_state_t *wsp) { SVCMASTERXPRT xprt; uintptr_t addr = wsp->walk_addr; int status; if (mdb_vread(&xprt, sizeof (xprt), addr) == -1) { mdb_warn("can't read SVCMASTERXPRT"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)xprt.xp_next; status = wsp->walk_callback(addr, &xprt, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); return (((void *)wsp->walk_addr == wsp->walk_data) ? WALK_DONE : WALK_NEXT); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #ifndef _SVC_H #define _SVC_H #include extern int svc_pool_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void svc_pool_help(void); extern int svc_mxprt_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern void svc_mxprt_help(void); extern int svc_pool_walk_init(mdb_walk_state_t *); extern int svc_pool_walk_step(mdb_walk_state_t *); extern int svc_mxprt_walk_init(mdb_walk_state_t *); extern int svc_mxprt_walk_step(mdb_walk_state_t *); #endif /* _SVC_H */ /* * 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 2018 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #ifdef _KERNEL #define NSMB_OBJNAME "nsmb" #else #define NSMB_OBJNAME "libfknsmb.so.1" #endif #define OPT_VERBOSE 0x0001 /* Be [-v]erbose in dcmd's */ #define OPT_RECURSE 0x0002 /* recursive display */ /* * We need to read in a private copy * of every string we want to print out. */ void print_str(uintptr_t addr) { char buf[32]; int len, mx = sizeof (buf) - 4; if ((len = mdb_readstr(buf, sizeof (buf), addr)) <= 0) { mdb_printf(" (%p)", addr); } else { if (len > mx) strcpy(&buf[mx], "..."); mdb_printf(" %s", buf); } } /* * Walker for smb_connobj_t structures, including * smb_vc_t and smb_share_t which "inherit" from it. * Tricky: Exploit the "inheritance" of smb_connobj_t * with common functions for walk_init, walk_next. */ typedef struct smb_co_walk_data { uintptr_t pp; int level; /* SMBL_SM, SMBL_VC, SMBL_SHARE, ... */ int size; /* sizeof (union member) */ union co_u { smb_connobj_t co; /* copy of the list element */ smb_vc_t vc; smb_share_t ss; smb_fh_t fh; } u; } smb_co_walk_data_t; /* * Common walk_init for walking structs inherited * from smb_connobj_t (smb_vc_t, smb_share_t) */ int smb_co_walk_init(mdb_walk_state_t *wsp, int level) { smb_co_walk_data_t *smbw; size_t psz; if (wsp->walk_addr == 0) return (WALK_ERR); smbw = mdb_alloc(sizeof (*smbw), UM_SLEEP | UM_GC); wsp->walk_data = smbw; /* * Save the parent pointer for later checks, and * the level so we know which union member it is. * Also the size of this union member. */ smbw->pp = wsp->walk_addr; smbw->level = level; switch (level) { case SMBL_SM: smbw->size = sizeof (smbw->u.co); break; case SMBL_VC: smbw->size = sizeof (smbw->u.vc); break; case SMBL_SHARE: smbw->size = sizeof (smbw->u.ss); break; case SMBL_FH: smbw->size = sizeof (smbw->u.fh); break; default: smbw->size = sizeof (smbw->u); break; } /* * Read in the parent object. Just need the * invariant part (smb_connobj_t) so we can * get the list of children below it. */ psz = sizeof (smbw->u.co); if (mdb_vread(&smbw->u.co, psz, smbw->pp) != psz) { mdb_warn("cannot read connobj from %p", smbw->pp); return (WALK_ERR); } /* * Finally, setup to walk the list of children. */ wsp->walk_addr = (uintptr_t)smbw->u.co.co_children.slh_first; return (WALK_NEXT); } /* * Walk the (global) VC list. */ int smb_vc_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; if (wsp->walk_addr != 0) { mdb_warn("::walk smb_vc only supports global walks\n"); return (WALK_ERR); } /* Locate the VC list head. */ if (mdb_lookup_by_obj(NSMB_OBJNAME, "smb_vclist", &sym)) { mdb_warn("failed to lookup `smb_vclist'\n"); return (WALK_ERR); } wsp->walk_addr = sym.st_value; return (smb_co_walk_init(wsp, SMBL_VC)); } /* * Walk the share list below some VC. */ int smb_ss_walk_init(mdb_walk_state_t *wsp) { /* * Initial walk_addr is address of parent (VC) */ if (wsp->walk_addr == 0) { mdb_warn("::walk smb_ss does not support global walks\n"); return (WALK_ERR); } return (smb_co_walk_init(wsp, SMBL_SHARE)); } /* * Walk the file hande list below some share. */ int smb_fh_walk_init(mdb_walk_state_t *wsp) { /* * Initial walk_addr is address of parent (share) */ if (wsp->walk_addr == 0) { mdb_warn("::walk smb_fh does not support global walks\n"); return (WALK_ERR); } return (smb_co_walk_init(wsp, SMBL_FH)); } /* * Common walk_step for walking structs inherited * from smb_connobj_t (smb_vc_t, smb_share_t) */ int smb_co_walk_step(mdb_walk_state_t *wsp) { smb_co_walk_data_t *smbw = wsp->walk_data; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&smbw->u, smbw->size, wsp->walk_addr) != smbw->size) { mdb_warn("cannot read connobj from %p", wsp->walk_addr); return (WALK_ERR); } /* XXX: Sanity check level? parent pointer? */ status = wsp->walk_callback(wsp->walk_addr, &smbw->u, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)smbw->u.co.co_next.sle_next; return (status); } /* * Dcmd (and callback function) to print a summary of * all VCs, and optionally all shares under each VC. */ typedef struct smb_co_cbdata { int flags; /* OPT_... */ int printed_header; mdb_ctf_id_t ctf_id; } smb_co_cbdata_t; /* * Call-back function for walking a file handle list. */ /* ARGSUSED */ int smb_fh_cb(uintptr_t addr, const void *data, void *arg) { const smb_fh_t *fhp = data; // smb_co_cbdata_t *cbd = arg; mdb_inc_indent(2); mdb_printf(" %-p", addr); if (fhp->fh_fid2.fid_volatile != 0) { mdb_printf("\t0x%llx\n", (long long) fhp->fh_fid2.fid_volatile); } else { mdb_printf("\t0x%x\n", fhp->fh_fid1); } mdb_dec_indent(2); return (WALK_NEXT); } /* * Call-back function for walking a share list. */ int smb_ss_cb(uintptr_t addr, const void *data, void *arg) { const smb_share_t *ssp = data; smb_co_cbdata_t *cbd = arg; uint32_t tid; tid = ssp->ss2_tree_id; if (tid == 0) tid = ssp->ss_tid; mdb_printf(" %-p\t0x%x\t%s\n", addr, tid, ssp->ss_name); if (cbd->flags & OPT_RECURSE) { mdb_inc_indent(2); if (mdb_pwalk("nsmb_fh", smb_fh_cb, cbd, addr) < 0) { mdb_warn("failed to walk 'nsmb_fh'"); /* Don't: return (WALK_ERR); */ } mdb_dec_indent(2); } return (WALK_NEXT); } static const char * vcstate_str(smb_co_cbdata_t *cbd, int stval) { static const char prefix[] = "SMBIOD_ST_"; int prefix_len = sizeof (prefix) - 1; mdb_ctf_id_t vcst_enum; const char *cp; /* Got this in smb_vc_dcmd. */ vcst_enum = cbd->ctf_id; /* Get the name for the enum value. */ if ((cp = mdb_ctf_enum_name(vcst_enum, stval)) == NULL) return ("?"); /* Skip the prefix part. */ if (strncmp(cp, prefix, prefix_len) == 0) cp += prefix_len; return (cp); } /* * Call-back function for walking the VC list. */ int smb_vc_cb(uintptr_t addr, const void *data, void *arg) { const smb_vc_t *vcp = data; smb_co_cbdata_t *cbd = arg; if (cbd->printed_header == 0) { cbd->printed_header = 1; mdb_printf("// smb_vc_t uid server \tuser\t\tstate\n"); } mdb_printf("%-p", addr); mdb_printf(" %7d", vcp->vc_owner); switch (vcp->vc_srvaddr.sa.sa_family) { case AF_INET: mdb_printf(" %I", vcp->vc_srvaddr.sin.sin_addr); break; case AF_INET6: mdb_printf(" %N", &vcp->vc_srvaddr.sin6.sin6_addr); break; default: mdb_printf(" %15s", "(bad af)"); break; } if (vcp->vc_username[0] != '\0') mdb_printf("\t%s", vcp->vc_username); else mdb_printf("\t%s", "(?)"); if (vcp->vc_domain[0] != '\0') mdb_printf("@%s", vcp->vc_domain); mdb_printf("\t%s\n", vcstate_str(cbd, vcp->vc_state)); if (cbd->flags & OPT_RECURSE) { mdb_inc_indent(2); if (mdb_pwalk("nsmb_ss", smb_ss_cb, cbd, addr) < 0) { mdb_warn("failed to walk 'nsmb_ss'"); /* Don't: return (WALK_ERR); */ } mdb_dec_indent(2); } return (WALK_NEXT); } int smb_vc_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { smb_co_cbdata_t cbd; smb_vc_t *vcp; size_t vcsz; memset(&cbd, 0, sizeof (cbd)); if (mdb_getopts(argc, argv, 'r', MDB_OPT_SETBITS, OPT_RECURSE, &cbd.flags, 'v', MDB_OPT_SETBITS, OPT_VERBOSE, &cbd.flags, NULL) != argc) { return (DCMD_USAGE); } if (mdb_ctf_lookup_by_name("enum smbiod_state", &cbd.ctf_id) == -1) { mdb_warn("Could not find enum smbiod_state"); } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk("nsmb_vc", smb_vc_cb, &cbd) == -1) { mdb_warn("failed to walk 'nsmb_vc'"); return (DCMD_ERR); } return (DCMD_OK); } vcsz = sizeof (*vcp); vcp = mdb_alloc(vcsz, UM_SLEEP | UM_GC); if (mdb_vread(vcp, vcsz, addr) != vcsz) { mdb_warn("cannot read VC from %p", addr); return (DCMD_ERR); } smb_vc_cb(addr, vcp, &cbd); return (DCMD_OK); } void smb_vc_help(void) { mdb_printf("Options:\n" " -r recursive display of share lists\n" " -v be verbose when displaying smb_vc\n"); } /* * Walker for the request list on a VC, * and dcmd to show a summary. */ int rqlist_walk_init(mdb_walk_state_t *wsp) { struct smb_rqhead rqh; uintptr_t addr; /* * Initial walk_addr is the address of the VC. * Add offsetof(iod_rqlist) to get the rqhead. */ if (wsp->walk_addr == 0) { mdb_warn("::walk smb_ss does not support global walks\n"); return (WALK_ERR); } addr = wsp->walk_addr; addr += OFFSETOF(smb_vc_t, iod_rqlist); if (mdb_vread(&rqh, sizeof (rqh), addr) == -1) { mdb_warn("failed to read smb_rqhead at %p", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)rqh.tqh_first; return (WALK_NEXT); } int rqlist_walk_step(mdb_walk_state_t *wsp) { smb_rq_t rq; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&rq, sizeof (rq), wsp->walk_addr) == -1) { mdb_warn("cannot read smb_rq from %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &rq, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)rq.sr_link.tqe_next; return (status); } typedef struct rqlist_cbdata { int printed_header; int vcflags; uintptr_t uid; /* optional filtering by UID */ } rqlist_cbdata_t; int rqlist_cb(uintptr_t addr, const void *data, void *arg) { const smb_rq_t *rq = data; rqlist_cbdata_t *cbd = arg; if (cbd->printed_header == 0) { cbd->printed_header = 1; mdb_printf("// smb_rq_t MID cmd sr_state sr_flags\n"); } mdb_printf(" %-p", addr); /* smb_rq_t */ if ((cbd->vcflags & SMBV_SMB2) != 0) { mdb_printf(" x%04llx", (long long)rq->sr2_messageid); mdb_printf(" x%02x", rq->sr2_command); } else { mdb_printf(" x%04x", rq->sr_mid); mdb_printf(" x%02x", rq->sr_cmd); } mdb_printf(" %d", rq->sr_state); mdb_printf(" x%x", rq->sr_flags); mdb_printf("\n"); return (WALK_NEXT); } /*ARGSUSED*/ int rqlist_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rqlist_cbdata_t cbd; smb_vc_t *vcp; size_t vcsz; memset(&cbd, 0, sizeof (cbd)); /* Need the VC again to get */ vcsz = sizeof (*vcp); vcp = mdb_alloc(vcsz, UM_SLEEP | UM_GC); if (mdb_vread(vcp, vcsz, addr) != vcsz) { mdb_warn("cannot read VC from %p", addr); return (DCMD_ERR); } cbd.vcflags = vcp->vc_flags; /* * Initial walk_addr is address of parent (VC) */ if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("address required\n"); return (DCMD_ERR); } if (mdb_pwalk("nsmb_rqlist", rqlist_cb, &cbd, addr) == -1) { mdb_warn("failed to walk 'nsmb_rqlist'"); return (DCMD_ERR); } return (DCMD_OK); } /* * AVL walker for the passwords AVL tree, * and dcmd to show a summary. */ static int pwtree_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; if (wsp->walk_addr != 0) { mdb_warn("pwtree walk only supports global walks\n"); return (WALK_ERR); } if (mdb_lookup_by_obj(NSMB_OBJNAME, "smb_ptd", &sym) == -1) { mdb_warn("failed to find symbol 'smb_ptd'"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sym.st_value; if (mdb_layered_walk("avl", wsp) == -1) { mdb_warn("failed to walk 'avl'\n"); return (WALK_ERR); } return (WALK_NEXT); } static int pwtree_walk_step(mdb_walk_state_t *wsp) { smb_passid_t ptnode; if (mdb_vread(&ptnode, sizeof (ptnode), wsp->walk_addr) == -1) { mdb_warn("failed to read smb_passid_t at %p", wsp->walk_addr); return (WALK_ERR); } return (wsp->walk_callback(wsp->walk_addr, &ptnode, wsp->walk_cbdata)); } typedef struct pwtree_cbdata { int printed_header; uid_t uid; /* optional filtering by UID */ } pwtree_cbdata_t; int pwtree_cb(uintptr_t addr, const void *data, void *arg) { const smb_passid_t *ptn = data; pwtree_cbdata_t *cbd = arg; /* Optional filtering by UID. */ if (cbd->uid != (uid_t)-1 && cbd->uid != ptn->uid) { return (WALK_NEXT); } if (cbd->printed_header == 0) { cbd->printed_header = 1; mdb_printf("// smb_passid_t UID domain user\n"); } mdb_printf(" %-p", addr); /* smb_passid_t */ mdb_printf(" %d", (uintptr_t)ptn->uid); print_str((uintptr_t)ptn->srvdom); print_str((uintptr_t)ptn->username); mdb_printf("\n"); return (WALK_NEXT); } /*ARGSUSED*/ int pwtree_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pwtree_cbdata_t cbd; char *uid_str = NULL; char buf[32]; memset(&cbd, 0, sizeof (cbd)); if (mdb_getopts(argc, argv, 'u', MDB_OPT_STR, &uid_str, NULL) != argc) { return (DCMD_USAGE); } if (uid_str) { /* * Want the the default radix to be 10 here. * If the string has some kind of radix prefix, * just use that as-is, otherwise prepend "0t". * Cheating on the "not a digit" test, but * mdb_strtoull will do a real syntax check. */ if (uid_str[0] == '0' && uid_str[1] > '9') { cbd.uid = (uid_t)mdb_strtoull(uid_str); } else { strcpy(buf, "0t"); strlcat(buf, uid_str, sizeof (buf)); cbd.uid = (uid_t)mdb_strtoull(buf); } } else cbd.uid = (uid_t)-1; if (flags & DCMD_ADDRSPEC) { mdb_warn("address not allowed\n"); return (DCMD_ERR); } if (mdb_pwalk("nsmb_pwtree", pwtree_cb, &cbd, 0) == -1) { mdb_warn("failed to walk 'nsmb_pwtree'"); return (DCMD_ERR); } return (DCMD_OK); } void pwtree_help(void) { mdb_printf("Options:\n" " -u uid show only entries belonging to uid (decimal)\n"); } static const mdb_dcmd_t dcmds[] = { { "nsmb_vc", "?[-rv]", "show smb_vc (or list)", smb_vc_dcmd, smb_vc_help }, { "nsmb_rqlist", ":", "show smb_rq list on a VC", rqlist_dcmd, NULL }, { "nsmb_pwtree", "?[-u uid]", "list smb_passid_t (password tree)", pwtree_dcmd, pwtree_help }, {NULL} }; static const mdb_walker_t walkers[] = { { "nsmb_vc", "walk nsmb VC list", smb_vc_walk_init, smb_co_walk_step, NULL }, { "nsmb_ss", "walk nsmb share list for some VC", smb_ss_walk_init, smb_co_walk_step, NULL }, { "nsmb_fh", "walk nsmb share list for some VC", smb_fh_walk_init, smb_co_walk_step, NULL }, { "nsmb_rqlist", "walk request list for some VC", rqlist_walk_init, rqlist_walk_step, NULL }, { "nsmb_pwtree", "walk passord AVL tree", pwtree_walk_init, pwtree_walk_step, NULL }, {NULL} }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Milan Jurik. All rights reserved. * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #ifndef _KMDB #include #include #include #include #endif /* _KMDB */ /* * We need use this to pass the settings when display_iport */ typedef struct per_iport_setting { uint_t pis_damap_info; /* -m: DAM/damap */ uint_t pis_dtc_info; /* -d: device tree children: dev_info/path_info */ } per_iport_setting_t; /* * This structure is used for sorting work structures by the wserno */ typedef struct wserno_list { int serno; int idx; struct wserno_list *next; struct wserno_list *prev; } wserno_list_t; #define MDB_RD(a, b, c) mdb_vread(a, b, (uintptr_t)c) #define NOREAD(a, b) mdb_warn("could not read " #a " at 0x%p", b) static pmcs_hw_t ss; static pmcs_xscsi_t **targets = NULL; static int target_idx; static uint32_t sas_phys, sata_phys, exp_phys, num_expanders, empty_phys; static pmcs_phy_t *pmcs_next_sibling(pmcs_phy_t *phyp); static void display_one_work(pmcwork_t *wp, int verbose, int idx); static void print_sas_address(pmcs_phy_t *phy) { int idx; for (idx = 0; idx < 8; idx++) { mdb_printf("%02x", phy->sas_address[idx]); } } static void pmcs_fwtime_to_systime(struct pmcs_hw ss, uint32_t fw_hi, uint32_t fw_lo, struct timespec *stime) { uint64_t fwtime; time_t secs; long nsecs; boolean_t backward_time = B_FALSE; fwtime = ((uint64_t)fw_hi << 32) | fw_lo; /* * If fwtime < ss.fw_timestamp, then we need to adjust the clock * time backwards from ss.sys_timestamp. Otherwise, the adjustment * goes forward in time */ if (fwtime >= ss.fw_timestamp) { fwtime -= ss.fw_timestamp; } else { fwtime = ss.fw_timestamp - fwtime; backward_time = B_TRUE; } secs = ((time_t)fwtime / NSECS_PER_SEC); nsecs = ((long)fwtime % NSECS_PER_SEC); stime->tv_sec = ss.sys_timestamp.tv_sec; stime->tv_nsec = ss.sys_timestamp.tv_nsec; if (backward_time) { if (stime->tv_nsec < nsecs) { stime->tv_sec--; stime->tv_nsec = stime->tv_nsec + NSECS_PER_SEC - nsecs; } else { stime->tv_nsec -= nsecs; } stime->tv_sec -= secs; } else { if (stime->tv_nsec + nsecs > NSECS_PER_SEC) { stime->tv_sec++; } stime->tv_nsec = (stime->tv_nsec + nsecs) % NSECS_PER_SEC; stime->tv_sec += secs; } } /*ARGSUSED*/ static void display_ic(struct pmcs_hw m, int verbose) { int msec_per_tick; if (mdb_readvar(&msec_per_tick, "msec_per_tick") == -1) { mdb_warn("can't read msec_per_tick"); msec_per_tick = 0; } mdb_printf("\n"); mdb_printf("Interrupt coalescing timer info\n"); mdb_printf("-------------------------------\n"); if (msec_per_tick == 0) { mdb_printf("Quantum : ?? ms\n"); } else { mdb_printf("Quantum : %d ms\n", m.io_intr_coal.quantum * msec_per_tick); } mdb_printf("Timer enabled : "); if (m.io_intr_coal.timer_on) { mdb_printf("Yes\n"); mdb_printf("Coalescing timer value : %d us\n", m.io_intr_coal.intr_coal_timer); } else { mdb_printf("No\n"); } mdb_printf("Total nsecs between interrupts: %ld\n", m.io_intr_coal.nsecs_between_intrs); mdb_printf("Time of last I/O interrupt : %ld\n", m.io_intr_coal.last_io_comp); mdb_printf("Number of I/O interrupts : %d\n", m.io_intr_coal.num_intrs); mdb_printf("Number of I/O completions : %d\n", m.io_intr_coal.num_io_completions); mdb_printf("Max I/O completion interrupts : %d\n", m.io_intr_coal.max_io_completions); mdb_printf("Measured ECHO int latency : %d ns\n", m.io_intr_coal.intr_latency); mdb_printf("Interrupt threshold : %d\n", m.io_intr_coal.intr_threshold); } /*ARGSUSED*/ static int pmcs_iport_phy_walk_cb(uintptr_t addr, const void *wdata, void *priv) { struct pmcs_phy phy; if (mdb_vread(&phy, sizeof (struct pmcs_phy), addr) != sizeof (struct pmcs_phy)) { return (DCMD_ERR); } mdb_printf("%16p %2d\n", addr, phy.phynum); return (0); } static int display_iport_damap(dev_info_t *pdip) { int rval = DCMD_ERR; struct dev_info dip; scsi_hba_tran_t sht; mdb_ctf_id_t istm_ctfid; /* impl_scsi_tgtmap_t ctf_id */ ulong_t tmd_offset = 0; /* tgtmap_dam offset to impl_scsi_tgtmap_t */ uintptr_t dam0; uintptr_t dam1; if (mdb_vread(&dip, sizeof (struct dev_info), (uintptr_t)pdip) != sizeof (struct dev_info)) { return (rval); } if (dip.devi_driver_data == NULL) { return (rval); } if (mdb_vread(&sht, sizeof (scsi_hba_tran_t), (uintptr_t)dip.devi_driver_data) != sizeof (scsi_hba_tran_t)) { return (rval); } if (sht.tran_tgtmap == NULL) { return (rval); } if (mdb_ctf_lookup_by_name("impl_scsi_tgtmap_t", &istm_ctfid) != 0) { return (rval); } if (mdb_ctf_offsetof(istm_ctfid, "tgtmap_dam", &tmd_offset) != 0) { return (rval); } tmd_offset /= NBBY; mdb_vread(&dam0, sizeof (dam0), (uintptr_t)(tmd_offset + (char *)sht.tran_tgtmap)); mdb_vread(&dam1, sizeof (dam1), (uintptr_t)(sizeof (dam0) + tmd_offset + (char *)sht.tran_tgtmap)); if (dam0 != 0) { rval = mdb_call_dcmd("damap", dam0, DCMD_ADDRSPEC, 0, NULL); mdb_printf("\n"); if (rval != DCMD_OK) { return (rval); } } if (dam1 != 0) { rval = mdb_call_dcmd("damap", dam1, DCMD_ADDRSPEC, 0, NULL); mdb_printf("\n"); } return (rval); } /* ARGSUSED */ static int display_iport_di_cb(uintptr_t addr, const void *wdata, void *priv) { uint_t *idx = (uint_t *)priv; struct dev_info dip; char devi_name[MAXNAMELEN]; char devi_addr[MAXNAMELEN]; if (mdb_vread(&dip, sizeof (struct dev_info), (uintptr_t)addr) != sizeof (struct dev_info)) { return (DCMD_ERR); } if (mdb_readstr(devi_name, sizeof (devi_name), (uintptr_t)dip.devi_node_name) == -1) { devi_name[0] = '?'; devi_name[1] = '\0'; } if (mdb_readstr(devi_addr, sizeof (devi_addr), (uintptr_t)dip.devi_addr) == -1) { devi_addr[0] = '?'; devi_addr[1] = '\0'; } mdb_printf(" %3d: @%-21s%10s@\t%p::devinfo -s\n", (*idx)++, devi_addr, devi_name, addr); return (DCMD_OK); } /* ARGSUSED */ static int display_iport_pi_cb(uintptr_t addr, const void *wdata, void *priv) { uint_t *idx = (uint_t *)priv; struct mdi_pathinfo mpi; char pi_addr[MAXNAMELEN]; if (mdb_vread(&mpi, sizeof (struct mdi_pathinfo), (uintptr_t)addr) != sizeof (struct mdi_pathinfo)) { return (DCMD_ERR); } if (mdb_readstr(pi_addr, sizeof (pi_addr), (uintptr_t)mpi.pi_addr) == -1) { pi_addr[0] = '?'; pi_addr[1] = '\0'; } mdb_printf(" %3d: @%-21s %p::print struct mdi_pathinfo\n", (*idx)++, pi_addr, addr); return (DCMD_OK); } static int display_iport_dtc(dev_info_t *pdip) { int rval = DCMD_ERR; struct dev_info dip; struct mdi_phci phci; uint_t didx = 1; uint_t pidx = 1; if (mdb_vread(&dip, sizeof (struct dev_info), (uintptr_t)pdip) != sizeof (struct dev_info)) { return (rval); } mdb_printf("Device tree children - dev_info:\n"); if (dip.devi_child == NULL) { mdb_printf("\tdevi_child is NULL, no dev_info\n\n"); goto skip_di; } /* * First, we dump the iport's children dev_info node information. * use existing walker: devinfo_siblings */ mdb_printf("\t#: @unit-address name@\tdrill-down\n"); rval = mdb_pwalk("devinfo_siblings", display_iport_di_cb, (void *)&didx, (uintptr_t)dip.devi_child); mdb_printf("\n"); skip_di: /* * Then we try to dump the iport's path_info node information. * use existing walker: mdipi_phci_list */ mdb_printf("Device tree children - path_info:\n"); if (mdb_vread(&phci, sizeof (struct mdi_phci), (uintptr_t)dip.devi_mdi_xhci) != sizeof (struct mdi_phci)) { mdb_printf("\tdevi_mdi_xhci is NULL, no path_info\n\n"); return (rval); } if (phci.ph_path_head == NULL) { mdb_printf("\tph_path_head is NULL, no path_info\n\n"); return (rval); } mdb_printf("\t#: @unit-address drill-down\n"); rval = mdb_pwalk("mdipi_phci_list", display_iport_pi_cb, (void *)&pidx, (uintptr_t)phci.ph_path_head); mdb_printf("\n"); return (rval); } static void display_iport_more(dev_info_t *dip, per_iport_setting_t *pis) { if (pis->pis_damap_info) { (void) display_iport_damap(dip); } if (pis->pis_dtc_info) { (void) display_iport_dtc(dip); } } /*ARGSUSED*/ static int pmcs_iport_walk_cb(uintptr_t addr, const void *wdata, void *priv) { struct pmcs_iport iport; uintptr_t list_addr; char *ua_state; char portid[4]; char unit_address[34]; per_iport_setting_t *pis = (per_iport_setting_t *)priv; if (mdb_vread(&iport, sizeof (struct pmcs_iport), addr) != sizeof (struct pmcs_iport)) { return (DCMD_ERR); } if (mdb_readstr(unit_address, sizeof (unit_address), (uintptr_t)(iport.ua)) == -1) { strncpy(unit_address, "Unset", sizeof (unit_address)); } if (iport.portid == 0xffff) { mdb_snprintf(portid, sizeof (portid), "%s", "-"); } else if (iport.portid == PMCS_IPORT_INVALID_PORT_ID) { mdb_snprintf(portid, sizeof (portid), "%s", "N/A"); } else { mdb_snprintf(portid, sizeof (portid), "%d", iport.portid); } switch (iport.ua_state) { case UA_INACTIVE: ua_state = "Inactive"; break; case UA_PEND_ACTIVATE: ua_state = "PendActivate"; break; case UA_ACTIVE: ua_state = "Active"; break; case UA_PEND_DEACTIVATE: ua_state = "PendDeactivate"; break; default: ua_state = "Unknown"; } if (strlen(unit_address) < 3) { /* Standard iport unit address */ mdb_printf("UA %-16s %16s %8s %8s %16s", "Iport", "UA State", "PortID", "NumPhys", "DIP\n"); mdb_printf("%2s %16p %16s %8s %8d %16p\n", unit_address, addr, ua_state, portid, iport.nphy, iport.dip); } else { /* Temporary iport unit address */ mdb_printf("%-32s %16s %20s %8s %8s %16s", "UA", "Iport", "UA State", "PortID", "NumPhys", "DIP\n"); mdb_printf("%32s %16p %20s %8s %8d %16p\n", unit_address, addr, ua_state, portid, iport.nphy, iport.dip); } if (iport.nphy > 0) { mdb_inc_indent(4); mdb_printf("%-18s %8s", "Phy", "PhyNum\n"); mdb_inc_indent(2); list_addr = (uintptr_t)(addr + offsetof(struct pmcs_iport, phys)); if (mdb_pwalk("list", pmcs_iport_phy_walk_cb, NULL, list_addr) == -1) { mdb_warn("pmcs iport walk failed"); } mdb_dec_indent(6); mdb_printf("\n"); } /* * See if we need to show more information based on 'd' or 'm' options */ display_iport_more(iport.dip, pis); return (0); } /*ARGSUSED*/ static void display_iport(struct pmcs_hw m, uintptr_t addr, int verbose, per_iport_setting_t *pis) { uintptr_t list_addr; if (m.iports_attached) { mdb_printf("Iport information:\n"); mdb_printf("-----------------\n"); } else { mdb_printf("No Iports found.\n\n"); return; } list_addr = (uintptr_t)(addr + offsetof(struct pmcs_hw, iports)); if (mdb_pwalk("list", pmcs_iport_walk_cb, pis, list_addr) == -1) { mdb_warn("pmcs iport walk failed"); } mdb_printf("\n"); } /* ARGSUSED */ static int pmcs_utarget_walk_cb(uintptr_t addr, const void *wdata, void *priv) { pmcs_phy_t phy; if (mdb_vread(&phy, sizeof (pmcs_phy_t), (uintptr_t)addr) == -1) { mdb_warn("pmcs_utarget_walk_cb: Failed to read PHY at %p", (void *)addr); return (DCMD_ERR); } if (phy.configured && (phy.target == NULL)) { mdb_printf("SAS address: "); print_sas_address(&phy); mdb_printf(" DType: "); switch (phy.dtype) { case SAS: mdb_printf("%4s", "SAS"); break; case SATA: mdb_printf("%4s", "SATA"); break; case EXPANDER: mdb_printf("%4s", "SMP"); break; default: mdb_printf("%4s", "N/A"); break; } mdb_printf(" Path: %s\n", phy.path); } return (0); } static void display_unconfigured_targets(uintptr_t addr) { mdb_printf("Unconfigured target SAS address:\n\n"); if (mdb_pwalk("pmcs_phys", pmcs_utarget_walk_cb, NULL, addr) == -1) { mdb_warn("pmcs phys walk failed"); } } static void display_completion_queue(struct pmcs_hw ss) { pmcs_iocomp_cb_t ccb, *ccbp; pmcwork_t work; if (ss.iocomp_cb_head == NULL) { mdb_printf("Completion queue is empty.\n"); return; } ccbp = ss.iocomp_cb_head; mdb_printf("%8s %10s %20s %8s %8s O D\n", "HTag", "State", "Phy Path", "Target", "Timer"); while (ccbp) { if (mdb_vread(&ccb, sizeof (pmcs_iocomp_cb_t), (uintptr_t)ccbp) != sizeof (pmcs_iocomp_cb_t)) { mdb_warn("Unable to read completion queue entry\n"); return; } if (mdb_vread(&work, sizeof (pmcwork_t), (uintptr_t)ccb.pwrk) != sizeof (pmcwork_t)) { mdb_warn("Unable to read work structure\n"); return; } /* * Only print the work structure if it's still active. If * it's not, it's been completed since we started looking at * it. */ if (work.state != PMCS_WORK_STATE_NIL) { display_one_work(&work, 0, 0); } ccbp = ccb.next; } } static void display_event_log(struct pmcs_hw ss) { pmcs_fw_event_hdr_t fwhdr; char *header_id, *entry, *fwlogp; uint32_t total_size = PMCS_FWLOG_SIZE, log_size, index, *swapp, sidx; pmcs_fw_event_entry_t *fw_entryp; struct timespec systime; if (ss.fwlogp == NULL) { mdb_printf("There is no firmware event log.\n"); return; } fwlogp = (char *)ss.fwlogp; while (total_size != 0) { if (mdb_vread(&fwhdr, sizeof (pmcs_fw_event_hdr_t), (uintptr_t)fwlogp) != sizeof (pmcs_fw_event_hdr_t)) { mdb_warn("Unable to read firmware event log header\n"); return; } /* * Firmware event log is little-endian */ swapp = (uint32_t *)&fwhdr; for (sidx = 0; sidx < (sizeof (pmcs_fw_event_hdr_t) / sizeof (uint32_t)); sidx++) { *swapp = LE_32(*swapp); swapp++; } if (fwhdr.fw_el_signature == PMCS_FWLOG_AAP1_SIG) { header_id = "AAP1"; } else if (fwhdr.fw_el_signature == PMCS_FWLOG_IOP_SIG) { header_id = "IOP"; } else { mdb_warn("Invalid firmware event log signature\n"); return; } mdb_printf("Event Log: %s\n", header_id); mdb_printf("Oldest entry: %d\n", fwhdr.fw_el_oldest_idx); mdb_printf("Latest entry: %d\n", fwhdr.fw_el_latest_idx); entry = mdb_alloc(fwhdr.fw_el_entry_size, UM_SLEEP); fw_entryp = (pmcs_fw_event_entry_t *)((void *)entry); total_size -= sizeof (pmcs_fw_event_hdr_t); log_size = fwhdr.fw_el_buf_size; fwlogp += fwhdr.fw_el_entry_start_offset; swapp = (uint32_t *)((void *)entry); index = 0; mdb_printf("%8s %16s %32s %8s %3s %8s %8s %8s %8s", "Index", "Timestamp", "Time", "Seq Num", "Sev", "Word 0", "Word 1", "Word 2", "Word 3"); mdb_printf("\n"); while (log_size != 0) { if (mdb_vread(entry, fwhdr.fw_el_entry_size, (uintptr_t)fwlogp) != fwhdr.fw_el_entry_size) { mdb_warn("Unable to read event log entry\n"); goto bail_out; } for (sidx = 0; sidx < (fwhdr.fw_el_entry_size / sizeof (uint32_t)); sidx++) { *(swapp + sidx) = LE_32(*(swapp + sidx)); } if (fw_entryp->ts_upper || fw_entryp->ts_lower) { pmcs_fwtime_to_systime(ss, fw_entryp->ts_upper, fw_entryp->ts_lower, &systime); mdb_printf("%8d %08x%08x [%Y.%09ld] %8d %3d " "%08x %08x %08x %08x\n", index, fw_entryp->ts_upper, fw_entryp->ts_lower, systime, fw_entryp->seq_num, fw_entryp->severity, fw_entryp->logw0, fw_entryp->logw1, fw_entryp->logw2, fw_entryp->logw3); } fwlogp += fwhdr.fw_el_entry_size; total_size -= fwhdr.fw_el_entry_size; log_size -= fwhdr.fw_el_entry_size; index++; } mdb_printf("\n"); } bail_out: mdb_free(entry, fwhdr.fw_el_entry_size); } /*ARGSUSED*/ static void display_hwinfo(struct pmcs_hw m, int verbose) { struct pmcs_hw *mp = &m; char *fwsupport; switch (PMCS_FW_TYPE(mp)) { case PMCS_FW_TYPE_RELEASED: fwsupport = "Released"; break; case PMCS_FW_TYPE_DEVELOPMENT: fwsupport = "Development"; break; case PMCS_FW_TYPE_ALPHA: fwsupport = "Alpha"; break; case PMCS_FW_TYPE_BETA: fwsupport = "Beta"; break; default: fwsupport = "Special"; break; } mdb_printf("\nHardware information:\n"); mdb_printf("---------------------\n"); mdb_printf("Chip revision: %c\n", 'A' + m.chiprev); mdb_printf("SAS WWID: %"PRIx64"\n", m.sas_wwns[0]); mdb_printf("Firmware version: %x.%x.%x (%s)\n", PMCS_FW_MAJOR(mp), PMCS_FW_MINOR(mp), PMCS_FW_MICRO(mp), fwsupport); mdb_printf("ILA version: %08x\n", m.ila_ver); mdb_printf("Active f/w img: %c\n", (m.fw_active_img) ? 'A' : 'B'); mdb_printf("Number of PHYs: %d\n", m.nphy); mdb_printf("Maximum commands: %d\n", m.max_cmd); mdb_printf("Maximum devices: %d\n", m.max_dev); mdb_printf("I/O queue depth: %d\n", m.ioq_depth); mdb_printf("Open retry intvl: %d usecs\n", m.open_retry_interval); if (m.fwlog == 0) { mdb_printf("Firmware logging: Disabled\n"); } else { mdb_printf("Firmware logging: Enabled (%d)\n", m.fwlog); } if (m.fwlog_file == 0) { mdb_printf("Firmware logfile: Not configured\n"); } else { mdb_printf("Firmware logfile: Configured\n"); mdb_inc_indent(2); mdb_printf("AAP1 log file: %s\n", m.fwlogfile_aap1); mdb_printf("IOP logfile: %s\n", m.fwlogfile_iop); mdb_dec_indent(2); } } static void display_targets(struct pmcs_hw m, int verbose, int totals_only) { char *dtype; pmcs_xscsi_t xs; pmcs_phy_t phy; uint16_t max_dev, idx; uint32_t sas_targets = 0, smp_targets = 0, sata_targets = 0; max_dev = m.max_dev; if (targets == NULL) { targets = mdb_alloc(sizeof (targets) * max_dev, UM_SLEEP); } if (MDB_RD(targets, sizeof (targets) * max_dev, m.targets) == -1) { NOREAD(targets, m.targets); return; } if (!totals_only) { mdb_printf("\nTarget information:\n"); mdb_printf("---------------------------------------\n"); mdb_printf("VTGT %-16s %-16s %-5s %4s %6s %s", "SAS Address", "PHY Address", "DType", "Actv", "OnChip", "DS"); mdb_printf("\n"); } for (idx = 0; idx < max_dev; idx++) { if (targets[idx] == NULL) { continue; } if (MDB_RD(&xs, sizeof (xs), targets[idx]) == -1) { NOREAD(pmcs_xscsi_t, targets[idx]); continue; } /* * It has to be new or assigned to be of interest. */ if (xs.new == 0 && xs.assigned == 0) { continue; } switch (xs.dtype) { case NOTHING: dtype = "None"; break; case SATA: dtype = "SATA"; sata_targets++; break; case SAS: dtype = "SAS"; sas_targets++; break; case EXPANDER: dtype = "SMP"; smp_targets++; break; default: dtype = "Unknown"; break; } if (totals_only) { continue; } if (xs.phy) { if (MDB_RD(&phy, sizeof (phy), xs.phy) == -1) { NOREAD(pmcs_phy_t, xs.phy); continue; } mdb_printf("%4d ", idx); print_sas_address(&phy); mdb_printf(" %16p", xs.phy); } else { mdb_printf("%4d %16s", idx, ""); } mdb_printf(" %5s", dtype); mdb_printf(" %4d", xs.actv_pkts); mdb_printf(" %6d", xs.actv_cnt); mdb_printf(" %2d", xs.dev_state); if (verbose) { if (xs.new) { mdb_printf(" new"); } if (xs.assigned) { mdb_printf(" assigned"); } if (xs.draining) { mdb_printf(" draining"); } if (xs.reset_wait) { mdb_printf(" reset_wait"); } if (xs.resetting) { mdb_printf(" resetting"); } if (xs.recover_wait) { mdb_printf(" recover_wait"); } if (xs.recovering) { mdb_printf(" recovering"); } if (xs.event_recovery) { mdb_printf(" event recovery"); } if (xs.special_running) { mdb_printf(" special_active"); } if (xs.ncq) { mdb_printf(" ncq_tagmap=0x%x qdepth=%d", xs.tagmap, xs.qdepth); } else if (xs.pio) { mdb_printf(" pio"); } } mdb_printf("\n"); } if (!totals_only) { mdb_printf("\n"); } mdb_printf("%19s %d (%d SAS + %d SATA + %d SMP)\n", "Configured targets:", (sas_targets + sata_targets + smp_targets), sas_targets, sata_targets, smp_targets); } static char * work_state_to_string(uint32_t state) { char *state_string; switch (state) { case PMCS_WORK_STATE_NIL: state_string = "Free"; break; case PMCS_WORK_STATE_READY: state_string = "Ready"; break; case PMCS_WORK_STATE_ONCHIP: state_string = "On Chip"; break; case PMCS_WORK_STATE_INTR: state_string = "In Intr"; break; case PMCS_WORK_STATE_IOCOMPQ: state_string = "I/O Comp"; break; case PMCS_WORK_STATE_ABORTED: state_string = "I/O Aborted"; break; case PMCS_WORK_STATE_TIMED_OUT: state_string = "I/O Timed Out"; break; default: state_string = "INVALID"; break; } return (state_string); } static void display_one_work(pmcwork_t *wp, int verbose, int idx) { char *state, *last_state; char *path; pmcs_xscsi_t xs; pmcs_phy_t phy; int tgt; state = work_state_to_string(wp->state); last_state = work_state_to_string(wp->last_state); if (wp->ssp_event && wp->ssp_event != 0xffffffff) { mdb_printf("SSP event 0x%x", wp->ssp_event); } tgt = -1; if (wp->xp) { if (MDB_RD(&xs, sizeof (xs), wp->xp) == -1) { NOREAD(pmcs_xscsi_t, wp->xp); } else { tgt = xs.target_num; } } if (wp->phy) { if (MDB_RD(&phy, sizeof (phy), wp->phy) == -1) { NOREAD(pmcs_phy_t, wp->phy); } path = phy.path; } else { path = "N/A"; } if (verbose) { mdb_printf("%4d ", idx); } if (tgt == -1) { mdb_printf("%08x %10s %20s N/A %8u %1d %1d ", wp->htag, state, path, wp->timer, wp->onwire, wp->dead); } else { mdb_printf("%08x %10s %20s %8d %8u %1d %1d ", wp->htag, state, path, tgt, wp->timer, wp->onwire, wp->dead); } if (verbose) { mdb_printf("%08x %10s 0x%016p 0x%016p 0x%016p\n", wp->last_htag, last_state, wp->last_phy, wp->last_xp, wp->last_arg); } else { mdb_printf("\n"); } } static void display_work(struct pmcs_hw m, int verbose, int wserno) { int idx; boolean_t header_printed = B_FALSE; pmcwork_t *wp; wserno_list_t *sernop, *sp, *newsp, *sphead = NULL; uintptr_t _wp; int serno; wp = mdb_alloc(sizeof (pmcwork_t) * m.max_cmd, UM_SLEEP); _wp = (uintptr_t)m.work; sernop = mdb_alloc(sizeof (wserno_list_t) * m.max_cmd, UM_SLEEP); bzero(sernop, sizeof (wserno_list_t) * m.max_cmd); mdb_printf("\nActive Work structure information:\n"); mdb_printf("----------------------------------\n"); /* * Read in all the work structures */ for (idx = 0; idx < m.max_cmd; idx++, _wp += sizeof (pmcwork_t)) { if (MDB_RD(wp + idx, sizeof (pmcwork_t), _wp) == -1) { NOREAD(pmcwork_t, _wp); continue; } } /* * Sort by serial number? */ if (wserno) { for (idx = 0; idx < m.max_cmd; idx++) { if ((wp + idx)->htag == 0) { serno = PMCS_TAG_SERNO((wp + idx)->last_htag); } else { serno = PMCS_TAG_SERNO((wp + idx)->htag); } /* Start at the beginning of the list */ sp = sphead; newsp = sernop + idx; /* If this is the first entry, just add it */ if (sphead == NULL) { sphead = sernop; sphead->serno = serno; sphead->idx = idx; sphead->next = NULL; sphead->prev = NULL; continue; } newsp->serno = serno; newsp->idx = idx; /* Find out where in the list this goes */ while (sp) { /* This item goes before sp */ if (serno < sp->serno) { newsp->next = sp; newsp->prev = sp->prev; if (newsp->prev == NULL) { sphead = newsp; } else { newsp->prev->next = newsp; } sp->prev = newsp; break; } /* * If sp->next is NULL, this entry goes at the * end of the list */ if (sp->next == NULL) { sp->next = newsp; newsp->next = NULL; newsp->prev = sp; break; } sp = sp->next; } } /* * Now print the sorted list */ mdb_printf(" Idx %8s %10s %20s %8s %8s O D ", "HTag", "State", "Phy Path", "Target", "Timer"); mdb_printf("%8s %10s %18s %18s %18s\n", "LastHTAG", "LastState", "LastPHY", "LastTgt", "LastArg"); sp = sphead; while (sp) { display_one_work(wp + sp->idx, 1, sp->idx); sp = sp->next; } goto out; } /* * Now print the list, sorted by index */ for (idx = 0; idx < m.max_cmd; idx++) { if (!verbose && ((wp + idx)->htag == PMCS_TAG_TYPE_FREE)) { continue; } if (header_printed == B_FALSE) { if (verbose) { mdb_printf("%4s ", "Idx"); } mdb_printf("%8s %10s %20s %8s %8s O D ", "HTag", "State", "Phy Path", "Target", "Timer"); if (verbose) { mdb_printf("%8s %10s %18s %18s %18s\n", "LastHTAG", "LastState", "LastPHY", "LastTgt", "LastArg"); } else { mdb_printf("\n"); } header_printed = B_TRUE; } display_one_work(wp + idx, verbose, idx); } out: mdb_free(wp, sizeof (pmcwork_t) * m.max_cmd); mdb_free(sernop, sizeof (wserno_list_t) * m.max_cmd); } static void print_spcmd(pmcs_cmd_t *sp, void *kaddr, int printhdr, int verbose) { int cdb_size, idx; struct scsi_pkt pkt; uchar_t cdb[256]; if (printhdr) { if (verbose) { mdb_printf("%16s %16s %16s %8s %s CDB\n", "Command", "SCSA pkt", "DMA Chunks", "HTAG", "SATL Tag"); } else { mdb_printf("%16s %16s %16s %8s %s\n", "Command", "SCSA pkt", "DMA Chunks", "HTAG", "SATL Tag"); } } mdb_printf("%16p %16p %16p %08x %08x ", kaddr, sp->cmd_pkt, sp->cmd_clist, sp->cmd_tag, sp->cmd_satltag); /* * If we're printing verbose, dump the CDB as well. */ if (verbose) { if (sp->cmd_pkt) { if (mdb_vread(&pkt, sizeof (struct scsi_pkt), (uintptr_t)sp->cmd_pkt) != sizeof (struct scsi_pkt)) { mdb_warn("Unable to read SCSI pkt\n"); return; } cdb_size = pkt.pkt_cdblen; if (mdb_vread(&cdb[0], cdb_size, (uintptr_t)pkt.pkt_cdbp) != cdb_size) { mdb_warn("Unable to read CDB\n"); return; } for (idx = 0; idx < cdb_size; idx++) { mdb_printf("%02x ", cdb[idx]); } } else { mdb_printf("N/A"); } mdb_printf("\n"); } else { mdb_printf("\n"); } } /*ARGSUSED1*/ static void display_waitqs(struct pmcs_hw m, int verbose) { pmcs_cmd_t *sp, s; pmcs_xscsi_t xs; int first, i; int max_dev = m.max_dev; sp = m.dq.stqh_first; first = 1; while (sp) { if (first) { mdb_printf("\nDead Command Queue:\n"); mdb_printf("---------------------------\n"); } if (MDB_RD(&s, sizeof (s), sp) == -1) { NOREAD(pmcs_cmd_t, sp); break; } print_spcmd(&s, sp, first, verbose); sp = s.cmd_next.stqe_next; first = 0; } sp = m.cq.stqh_first; first = 1; while (sp) { if (first) { mdb_printf("\nCompletion Command Queue:\n"); mdb_printf("---------------------------\n"); } if (MDB_RD(&s, sizeof (s), sp) == -1) { NOREAD(pmcs_cmd_t, sp); break; } print_spcmd(&s, sp, first, verbose); sp = s.cmd_next.stqe_next; first = 0; } if (targets == NULL) { targets = mdb_alloc(sizeof (targets) * max_dev, UM_SLEEP); } if (MDB_RD(targets, sizeof (targets) * max_dev, m.targets) == -1) { NOREAD(targets, m.targets); return; } for (i = 0; i < max_dev; i++) { if (targets[i] == NULL) { continue; } if (MDB_RD(&xs, sizeof (xs), targets[i]) == -1) { NOREAD(pmcs_xscsi_t, targets[i]); continue; } sp = xs.wq.stqh_first; first = 1; while (sp) { if (first) { mdb_printf("\nTarget %u Wait Queue:\n", xs.target_num); mdb_printf("---------------------------\n"); } if (MDB_RD(&s, sizeof (s), sp) == -1) { NOREAD(pmcs_cmd_t, sp); break; } print_spcmd(&s, sp, first, verbose); sp = s.cmd_next.stqe_next; first = 0; } sp = xs.aq.stqh_first; first = 1; while (sp) { if (first) { mdb_printf("\nTarget %u Active Queue:\n", xs.target_num); mdb_printf("---------------------------\n"); } if (MDB_RD(&s, sizeof (s), sp) == -1) { NOREAD(pmcs_cmd_t, sp); break; } print_spcmd(&s, sp, first, verbose); sp = s.cmd_next.stqe_next; first = 0; } sp = xs.sq.stqh_first; first = 1; while (sp) { if (first) { mdb_printf("\nTarget %u Special Queue:\n", xs.target_num); mdb_printf("---------------------------\n"); } if (MDB_RD(&s, sizeof (s), sp) == -1) { NOREAD(pmcs_cmd_t, sp); break; } print_spcmd(&s, sp, first, verbose); sp = s.cmd_next.stqe_next; first = 0; } } } static char * ibq_type(int qnum) { if (qnum < 0 || qnum >= PMCS_NIQ) { return ("UNKNOWN"); } if (qnum < PMCS_IQ_OTHER) { return ("I/O"); } return ("Other"); } static char * obq_type(int qnum) { switch (qnum) { case PMCS_OQ_IODONE: return ("I/O"); case PMCS_OQ_GENERAL: return ("General"); case PMCS_OQ_EVENTS: return ("Events"); default: return ("UNKNOWN"); } } static char * iomb_cat(uint32_t cat) { switch (cat) { case PMCS_IOMB_CAT_NET: return ("NET"); case PMCS_IOMB_CAT_FC: return ("FC"); case PMCS_IOMB_CAT_SAS: return ("SAS"); case PMCS_IOMB_CAT_SCSI: return ("SCSI"); default: return ("???"); } } static char * iomb_event(uint8_t event) { switch (event) { case IOP_EVENT_PHY_STOP_STATUS: return ("PHY STOP"); case IOP_EVENT_SAS_PHY_UP: return ("PHY UP"); case IOP_EVENT_SATA_PHY_UP: return ("SATA PHY UP"); case IOP_EVENT_SATA_SPINUP_HOLD: return ("SATA SPINUP HOLD"); case IOP_EVENT_PHY_DOWN: return ("PHY DOWN"); case IOP_EVENT_BROADCAST_CHANGE: return ("BROADCAST CHANGE"); case IOP_EVENT_BROADCAST_SES: return ("BROADCAST SES"); case IOP_EVENT_PHY_ERR_INBOUND_CRC: return ("INBOUND CRC ERROR"); case IOP_EVENT_HARD_RESET_RECEIVED: return ("HARD RESET"); case IOP_EVENT_EVENT_ID_FRAME_TIMO: return ("IDENTIFY FRAME TIMEOUT"); case IOP_EVENT_BROADCAST_EXP: return ("BROADCAST EXPANDER"); case IOP_EVENT_PHY_START_STATUS: return ("PHY START"); case IOP_EVENT_PHY_ERR_INVALID_DWORD: return ("INVALID DWORD"); case IOP_EVENT_PHY_ERR_DISPARITY_ERROR: return ("DISPARITY ERROR"); case IOP_EVENT_PHY_ERR_CODE_VIOLATION: return ("CODE VIOLATION"); case IOP_EVENT_PHY_ERR_LOSS_OF_DWORD_SYN: return ("LOSS OF DWORD SYNC"); case IOP_EVENT_PHY_ERR_PHY_RESET_FAILD: return ("PHY RESET FAILED"); case IOP_EVENT_PORT_RECOVERY_TIMER_TMO: return ("PORT RECOVERY TIMEOUT"); case IOP_EVENT_PORT_RECOVER: return ("PORT RECOVERY"); case IOP_EVENT_PORT_RESET_TIMER_TMO: return ("PORT RESET TIMEOUT"); case IOP_EVENT_PORT_RESET_COMPLETE: return ("PORT RESET COMPLETE"); case IOP_EVENT_BROADCAST_ASYNC_EVENT: return ("BROADCAST ASYNC"); case IOP_EVENT_IT_NEXUS_LOSS: return ("I/T NEXUS LOSS"); default: return ("Unknown Event"); } } static char * inbound_iomb_opcode(uint32_t opcode) { switch (opcode) { case PMCIN_ECHO: return ("ECHO"); case PMCIN_GET_INFO: return ("GET_INFO"); case PMCIN_GET_VPD: return ("GET_VPD"); case PMCIN_PHY_START: return ("PHY_START"); case PMCIN_PHY_STOP: return ("PHY_STOP"); case PMCIN_SSP_INI_IO_START: return ("INI_IO_START"); case PMCIN_SSP_INI_TM_START: return ("INI_TM_START"); case PMCIN_SSP_INI_EXT_IO_START: return ("INI_EXT_IO_START"); case PMCIN_DEVICE_HANDLE_ACCEPT: return ("DEVICE_HANDLE_ACCEPT"); case PMCIN_SSP_TGT_IO_START: return ("TGT_IO_START"); case PMCIN_SSP_TGT_RESPONSE_START: return ("TGT_RESPONSE_START"); case PMCIN_SSP_INI_EDC_EXT_IO_START: return ("INI_EDC_EXT_IO_START"); case PMCIN_SSP_INI_EDC_EXT_IO_START1: return ("INI_EDC_EXT_IO_START1"); case PMCIN_SSP_TGT_EDC_IO_START: return ("TGT_EDC_IO_START"); case PMCIN_SSP_ABORT: return ("SSP_ABORT"); case PMCIN_DEREGISTER_DEVICE_HANDLE: return ("DEREGISTER_DEVICE_HANDLE"); case PMCIN_GET_DEVICE_HANDLE: return ("GET_DEVICE_HANDLE"); case PMCIN_SMP_REQUEST: return ("SMP_REQUEST"); case PMCIN_SMP_RESPONSE: return ("SMP_RESPONSE"); case PMCIN_SMP_ABORT: return ("SMP_ABORT"); case PMCIN_ASSISTED_DISCOVERY: return ("ASSISTED_DISCOVERY"); case PMCIN_REGISTER_DEVICE: return ("REGISTER_DEVICE"); case PMCIN_SATA_HOST_IO_START: return ("SATA_HOST_IO_START"); case PMCIN_SATA_ABORT: return ("SATA_ABORT"); case PMCIN_LOCAL_PHY_CONTROL: return ("LOCAL_PHY_CONTROL"); case PMCIN_GET_DEVICE_INFO: return ("GET_DEVICE_INFO"); case PMCIN_TWI: return ("TWI"); case PMCIN_FW_FLASH_UPDATE: return ("FW_FLASH_UPDATE"); case PMCIN_SET_VPD: return ("SET_VPD"); case PMCIN_GPIO: return ("GPIO"); case PMCIN_SAS_DIAG_MODE_START_END: return ("SAS_DIAG_MODE_START_END"); case PMCIN_SAS_DIAG_EXECUTE: return ("SAS_DIAG_EXECUTE"); case PMCIN_SAS_HW_EVENT_ACK: return ("SAS_HW_EVENT_ACK"); case PMCIN_GET_TIME_STAMP: return ("GET_TIME_STAMP"); case PMCIN_PORT_CONTROL: return ("PORT_CONTROL"); case PMCIN_GET_NVMD_DATA: return ("GET_NVMD_DATA"); case PMCIN_SET_NVMD_DATA: return ("SET_NVMD_DATA"); case PMCIN_SET_DEVICE_STATE: return ("SET_DEVICE_STATE"); case PMCIN_GET_DEVICE_STATE: return ("GET_DEVICE_STATE"); default: return ("UNKNOWN"); } } static char * outbound_iomb_opcode(uint32_t opcode) { switch (opcode) { case PMCOUT_ECHO: return ("ECHO"); case PMCOUT_GET_INFO: return ("GET_INFO"); case PMCOUT_GET_VPD: return ("GET_VPD"); case PMCOUT_SAS_HW_EVENT: return ("SAS_HW_EVENT"); case PMCOUT_SSP_COMPLETION: return ("SSP_COMPLETION"); case PMCOUT_SMP_COMPLETION: return ("SMP_COMPLETION"); case PMCOUT_LOCAL_PHY_CONTROL: return ("LOCAL_PHY_CONTROL"); case PMCOUT_SAS_ASSISTED_DISCOVERY_EVENT: return ("SAS_ASSISTED_DISCOVERY_SENT"); case PMCOUT_SATA_ASSISTED_DISCOVERY_EVENT: return ("SATA_ASSISTED_DISCOVERY_SENT"); case PMCOUT_DEVICE_REGISTRATION: return ("DEVICE_REGISTRATION"); case PMCOUT_DEREGISTER_DEVICE_HANDLE: return ("DEREGISTER_DEVICE_HANDLE"); case PMCOUT_GET_DEVICE_HANDLE: return ("GET_DEVICE_HANDLE"); case PMCOUT_SATA_COMPLETION: return ("SATA_COMPLETION"); case PMCOUT_SATA_EVENT: return ("SATA_EVENT"); case PMCOUT_SSP_EVENT: return ("SSP_EVENT"); case PMCOUT_DEVICE_HANDLE_ARRIVED: return ("DEVICE_HANDLE_ARRIVED"); case PMCOUT_SSP_REQUEST_RECEIVED: return ("SSP_REQUEST_RECEIVED"); case PMCOUT_DEVICE_INFO: return ("DEVICE_INFO"); case PMCOUT_FW_FLASH_UPDATE: return ("FW_FLASH_UPDATE"); case PMCOUT_SET_VPD: return ("SET_VPD"); case PMCOUT_GPIO: return ("GPIO"); case PMCOUT_GPIO_EVENT: return ("GPIO_EVENT"); case PMCOUT_GENERAL_EVENT: return ("GENERAL_EVENT"); case PMCOUT_TWI: return ("TWI"); case PMCOUT_SSP_ABORT: return ("SSP_ABORT"); case PMCOUT_SATA_ABORT: return ("SATA_ABORT"); case PMCOUT_SAS_DIAG_MODE_START_END: return ("SAS_DIAG_MODE_START_END"); case PMCOUT_SAS_DIAG_EXECUTE: return ("SAS_DIAG_EXECUTE"); case PMCOUT_GET_TIME_STAMP: return ("GET_TIME_STAMP"); case PMCOUT_SAS_HW_EVENT_ACK_ACK: return ("SAS_HW_EVENT_ACK_ACK"); case PMCOUT_PORT_CONTROL: return ("PORT_CONTROL"); case PMCOUT_SKIP_ENTRIES: return ("SKIP_ENTRIES"); case PMCOUT_SMP_ABORT: return ("SMP_ABORT"); case PMCOUT_GET_NVMD_DATA: return ("GET_NVMD_DATA"); case PMCOUT_SET_NVMD_DATA: return ("SET_NVMD_DATA"); case PMCOUT_DEVICE_HANDLE_REMOVED: return ("DEVICE_HANDLE_REMOVED"); case PMCOUT_SET_DEVICE_STATE: return ("SET_DEVICE_STATE"); case PMCOUT_GET_DEVICE_STATE: return ("GET_DEVICE_STATE"); case PMCOUT_SET_DEVICE_INFO: return ("SET_DEVICE_INFO"); default: return ("UNKNOWN"); } } static uint32_t get_devid_from_ob_iomb(struct pmcs_hw ss, uint32_t *qentryp, uint16_t opcode) { uint32_t devid = PMCS_INVALID_DEVICE_ID; switch (opcode) { /* * These are obtained via the HTAG which is in word 1 */ case PMCOUT_SSP_COMPLETION: case PMCOUT_SMP_COMPLETION: case PMCOUT_DEREGISTER_DEVICE_HANDLE: case PMCOUT_GET_DEVICE_HANDLE: case PMCOUT_SATA_COMPLETION: case PMCOUT_SSP_ABORT: case PMCOUT_SATA_ABORT: case PMCOUT_SMP_ABORT: case PMCOUT_SAS_HW_EVENT_ACK_ACK: { uint32_t htag; pmcwork_t *wp; pmcs_phy_t *phy; uintptr_t _wp, _phy; uint16_t index; htag = LE_32(*(qentryp + 1)); index = htag & PMCS_TAG_INDEX_MASK; wp = mdb_alloc(sizeof (pmcwork_t), UM_SLEEP); _wp = (uintptr_t)ss.work + (sizeof (pmcwork_t) * index); if (MDB_RD(wp, sizeof (pmcwork_t), _wp) == -1) { NOREAD(pmcwork_t, _wp); mdb_free(wp, sizeof (pmcwork_t)); break; } phy = mdb_alloc(sizeof (pmcs_phy_t), UM_SLEEP); if (wp->phy == NULL) { _phy = (uintptr_t)wp->last_phy; } else { _phy = (uintptr_t)wp->phy; } /* * If we have a PHY, read it in and get it's handle */ if (_phy != 0) { if (MDB_RD(phy, sizeof (*phy), _phy) == -1) { NOREAD(pmcs_phy_t, phy); } else { devid = phy->device_id; } } mdb_free(phy, sizeof (pmcs_phy_t)); mdb_free(wp, sizeof (pmcwork_t)); break; } /* * The device ID is in the outbound IOMB at word 1 */ case PMCOUT_SSP_REQUEST_RECEIVED: devid = LE_32(*(qentryp + 1)) & PMCS_DEVICE_ID_MASK; break; /* * The device ID is in the outbound IOMB at word 2 */ case PMCOUT_DEVICE_HANDLE_ARRIVED: case PMCOUT_DEVICE_HANDLE_REMOVED: devid = LE_32(*(qentryp + 2)) & PMCS_DEVICE_ID_MASK; break; /* * In this (very rare - never seen it) state, the device ID * comes from the HTAG in the inbound IOMB, which would be word * 3 in the outbound IOMB */ case PMCOUT_GENERAL_EVENT: /* * The device ID is in the outbound IOMB at word 3 */ case PMCOUT_DEVICE_REGISTRATION: case PMCOUT_DEVICE_INFO: case PMCOUT_SET_DEVICE_STATE: case PMCOUT_GET_DEVICE_STATE: case PMCOUT_SET_DEVICE_INFO: devid = LE_32(*(qentryp + 3)) & PMCS_DEVICE_ID_MASK; break; /* * Device ID is in the outbound IOMB at word 4 */ case PMCOUT_SATA_EVENT: case PMCOUT_SSP_EVENT: devid = LE_32(*(qentryp + 4)) & PMCS_DEVICE_ID_MASK; break; } return (devid); } static boolean_t iomb_is_dev_hdl_specific(uint32_t word0, boolean_t inbound) { uint16_t opcode = word0 & PMCS_IOMB_OPCODE_MASK; if (inbound) { switch (opcode) { case PMCIN_SSP_INI_IO_START: case PMCIN_SSP_INI_TM_START: case PMCIN_SSP_INI_EXT_IO_START: case PMCIN_SSP_TGT_IO_START: case PMCIN_SSP_TGT_RESPONSE_START: case PMCIN_SSP_ABORT: case PMCIN_DEREGISTER_DEVICE_HANDLE: case PMCIN_SMP_REQUEST: case PMCIN_SMP_RESPONSE: case PMCIN_SMP_ABORT: case PMCIN_ASSISTED_DISCOVERY: case PMCIN_SATA_HOST_IO_START: case PMCIN_SATA_ABORT: case PMCIN_GET_DEVICE_INFO: case PMCIN_SET_DEVICE_STATE: case PMCIN_GET_DEVICE_STATE: return (B_TRUE); } return (B_FALSE); } switch (opcode) { case PMCOUT_SSP_COMPLETION: case PMCOUT_SMP_COMPLETION: case PMCOUT_DEVICE_REGISTRATION: case PMCOUT_DEREGISTER_DEVICE_HANDLE: case PMCOUT_GET_DEVICE_HANDLE: case PMCOUT_SATA_COMPLETION: case PMCOUT_SATA_EVENT: case PMCOUT_SSP_EVENT: case PMCOUT_DEVICE_HANDLE_ARRIVED: case PMCOUT_SSP_REQUEST_RECEIVED: case PMCOUT_DEVICE_INFO: case PMCOUT_FW_FLASH_UPDATE: case PMCOUT_GENERAL_EVENT: case PMCOUT_SSP_ABORT: case PMCOUT_SATA_ABORT: case PMCOUT_SAS_HW_EVENT_ACK_ACK: case PMCOUT_SMP_ABORT: case PMCOUT_DEVICE_HANDLE_REMOVED: case PMCOUT_SET_DEVICE_STATE: case PMCOUT_GET_DEVICE_STATE: case PMCOUT_SET_DEVICE_INFO: return (B_TRUE); } return (B_FALSE); } static void dump_one_qentry_outbound(struct pmcs_hw ss, uint32_t *qentryp, int idx, uint64_t devid_filter) { int qeidx; uint32_t word0 = LE_32(*qentryp); uint32_t word1 = LE_32(*(qentryp + 1)); uint8_t iop_event; uint32_t devid; /* * Check to see if we're filtering on a device ID */ if (devid_filter != PMCS_INVALID_DEVICE_ID) { if (!iomb_is_dev_hdl_specific(word0, B_FALSE)) { return; } /* * Go find the device id. It might be in the outbound * IOMB or we may have to go find the work structure and * get it from there. */ devid = get_devid_from_ob_iomb(ss, qentryp, word0 & PMCS_IOMB_OPCODE_MASK); if ((devid == PMCS_INVALID_DEVICE_ID) || (devid_filter != devid)) { return; } } mdb_printf("Entry #%02d\n", idx); mdb_inc_indent(2); mdb_printf("Header: 0x%08x (", word0); if (word0 & PMCS_IOMB_VALID) { mdb_printf("VALID, "); } if (word0 & PMCS_IOMB_HIPRI) { mdb_printf("HIPRI, "); } mdb_printf("OBID=%d, ", (word0 & PMCS_IOMB_OBID_MASK) >> PMCS_IOMB_OBID_SHIFT); mdb_printf("CAT=%s, ", iomb_cat((word0 & PMCS_IOMB_CAT_MASK) >> PMCS_IOMB_CAT_SHIFT)); mdb_printf("OPCODE=%s", outbound_iomb_opcode(word0 & PMCS_IOMB_OPCODE_MASK)); if ((word0 & PMCS_IOMB_OPCODE_MASK) == PMCOUT_SAS_HW_EVENT) { iop_event = IOP_EVENT_EVENT(word1); mdb_printf(" <%s>", iomb_event(iop_event)); } mdb_printf(")\n"); mdb_printf("Remaining Payload:\n"); mdb_inc_indent(2); for (qeidx = 1; qeidx < (PMCS_QENTRY_SIZE / 4); qeidx++) { mdb_printf("%08x ", LE_32(*(qentryp + qeidx))); } mdb_printf("\n"); mdb_dec_indent(4); } static void display_outbound_queues(struct pmcs_hw ss, uint64_t devid_filter, uint_t verbose) { int idx, qidx; uintptr_t obqp; uint32_t *cip; uint32_t *qentryp = mdb_alloc(PMCS_QENTRY_SIZE, UM_SLEEP); uint32_t last_consumed, oqpi; mdb_printf("\n"); mdb_printf("Outbound Queues\n"); mdb_printf("---------------\n"); mdb_inc_indent(2); for (qidx = 0; qidx < PMCS_NOQ; qidx++) { obqp = (uintptr_t)ss.oqp[qidx]; if (obqp == 0) { mdb_printf("No outbound queue ptr for queue #%d\n", qidx); continue; } mdb_printf("Outbound Queue #%d (Queue Type = %s)\n", qidx, obq_type(qidx)); /* * Chip is the producer, so read the actual producer index * and not the driver's version */ cip = (uint32_t *)((void *)ss.cip); if (MDB_RD(&oqpi, 4, cip + OQPI_BASE_OFFSET + (qidx * 4)) == -1) { mdb_warn("Couldn't read oqpi\n"); break; } mdb_printf("Producer index: %d Consumer index: %d\n\n", LE_32(oqpi), ss.oqci[qidx]); mdb_inc_indent(2); if (ss.oqci[qidx] == 0) { last_consumed = ss.ioq_depth - 1; } else { last_consumed = ss.oqci[qidx] - 1; } if (!verbose) { mdb_printf("Last processed entry:\n"); if (MDB_RD(qentryp, PMCS_QENTRY_SIZE, (obqp + (PMCS_QENTRY_SIZE * last_consumed))) == -1) { mdb_warn("Couldn't read queue entry at 0x%p\n", (obqp + (PMCS_QENTRY_SIZE * last_consumed))); break; } dump_one_qentry_outbound(ss, qentryp, last_consumed, devid_filter); mdb_printf("\n"); mdb_dec_indent(2); continue; } for (idx = 0; idx < ss.ioq_depth; idx++) { if (MDB_RD(qentryp, PMCS_QENTRY_SIZE, (obqp + (PMCS_QENTRY_SIZE * idx))) == -1) { mdb_warn("Couldn't read queue entry at 0x%p\n", (obqp + (PMCS_QENTRY_SIZE * idx))); break; } dump_one_qentry_outbound(ss, qentryp, idx, devid_filter); } mdb_printf("\n"); mdb_dec_indent(2); } mdb_dec_indent(2); mdb_free(qentryp, PMCS_QENTRY_SIZE); } static void dump_one_qentry_inbound(uint32_t *qentryp, int idx, uint64_t devid_filter) { int qeidx; uint32_t word0 = LE_32(*qentryp); uint32_t devid = LE_32(*(qentryp + 2)); /* * Check to see if we're filtering on a device ID */ if (devid_filter != PMCS_INVALID_DEVICE_ID) { if (iomb_is_dev_hdl_specific(word0, B_TRUE)) { if (devid_filter != devid) { return; } } else { return; } } mdb_printf("Entry #%02d\n", idx); mdb_inc_indent(2); mdb_printf("Header: 0x%08x (", word0); if (word0 & PMCS_IOMB_VALID) { mdb_printf("VALID, "); } if (word0 & PMCS_IOMB_HIPRI) { mdb_printf("HIPRI, "); } mdb_printf("OBID=%d, ", (word0 & PMCS_IOMB_OBID_MASK) >> PMCS_IOMB_OBID_SHIFT); mdb_printf("CAT=%s, ", iomb_cat((word0 & PMCS_IOMB_CAT_MASK) >> PMCS_IOMB_CAT_SHIFT)); mdb_printf("OPCODE=%s", inbound_iomb_opcode(word0 & PMCS_IOMB_OPCODE_MASK)); mdb_printf(")\n"); mdb_printf("HTAG: 0x%08x\n", LE_32(*(qentryp + 1))); mdb_printf("Remaining Payload:\n"); mdb_inc_indent(2); for (qeidx = 2; qeidx < (PMCS_QENTRY_SIZE / 4); qeidx++) { mdb_printf("%08x ", LE_32(*(qentryp + qeidx))); } mdb_printf("\n"); mdb_dec_indent(4); } static void display_inbound_queues(struct pmcs_hw ss, uint64_t devid_filter, uint_t verbose) { int idx, qidx, iqci, last_consumed; uintptr_t ibqp; uint32_t *qentryp = mdb_alloc(PMCS_QENTRY_SIZE, UM_SLEEP); uint32_t *cip; mdb_printf("\n"); mdb_printf("Inbound Queues\n"); mdb_printf("--------------\n"); mdb_inc_indent(2); for (qidx = 0; qidx < PMCS_NIQ; qidx++) { ibqp = (uintptr_t)ss.iqp[qidx]; if (ibqp == 0) { mdb_printf("No inbound queue ptr for queue #%d\n", qidx); continue; } mdb_printf("Inbound Queue #%d (Queue Type = %s)\n", qidx, ibq_type(qidx)); cip = (uint32_t *)((void *)ss.cip); if (MDB_RD(&iqci, 4, cip + (qidx * 4)) == -1) { mdb_warn("Couldn't read iqci\n"); break; } iqci = LE_32(iqci); mdb_printf("Producer index: %d Consumer index: %d\n\n", ss.shadow_iqpi[qidx], iqci); mdb_inc_indent(2); if (iqci == 0) { last_consumed = ss.ioq_depth - 1; } else { last_consumed = iqci - 1; } if (!verbose) { mdb_printf("Last processed entry:\n"); if (MDB_RD(qentryp, PMCS_QENTRY_SIZE, (ibqp + (PMCS_QENTRY_SIZE * last_consumed))) == -1) { mdb_warn("Couldn't read queue entry at 0x%p\n", (ibqp + (PMCS_QENTRY_SIZE * last_consumed))); break; } dump_one_qentry_inbound(qentryp, last_consumed, devid_filter); mdb_printf("\n"); mdb_dec_indent(2); continue; } for (idx = 0; idx < ss.ioq_depth; idx++) { if (MDB_RD(qentryp, PMCS_QENTRY_SIZE, (ibqp + (PMCS_QENTRY_SIZE * idx))) == -1) { mdb_warn("Couldn't read queue entry at 0x%p\n", (ibqp + (PMCS_QENTRY_SIZE * idx))); break; } dump_one_qentry_inbound(qentryp, idx, devid_filter); } mdb_printf("\n"); mdb_dec_indent(2); } mdb_dec_indent(2); mdb_free(qentryp, PMCS_QENTRY_SIZE); } /* * phy is our copy of the PHY structure. phyp is the pointer to the actual * kernel PHY data structure */ static void display_phy(struct pmcs_phy phy, struct pmcs_phy *phyp, int verbose, int totals_only) { char *dtype, *speed; char *yes = "Yes"; char *no = "No"; char *cfgd = no; char *apend = no; char *asent = no; char *dead = no; char *changed = no; char route_attr, route_method = '\0'; switch (phy.dtype) { case NOTHING: dtype = "None"; break; case SATA: dtype = "SATA"; if (phy.configured) { ++sata_phys; } break; case SAS: dtype = "SAS"; if (phy.configured) { ++sas_phys; } break; case EXPANDER: dtype = "EXP"; if (phy.configured) { ++exp_phys; } break; default: dtype = "Unknown"; break; } if (phy.dtype == NOTHING) { empty_phys++; } else if ((phy.dtype == EXPANDER) && phy.configured) { num_expanders++; } if (totals_only) { return; } switch (phy.link_rate) { case SAS_LINK_RATE_1_5GBIT: speed = "1.5Gb/s"; break; case SAS_LINK_RATE_3GBIT: speed = "3 Gb/s"; break; case SAS_LINK_RATE_6GBIT: speed = "6 Gb/s"; break; default: speed = "N/A"; break; } if ((phy.dtype != NOTHING) || verbose) { print_sas_address(&phy); if (phy.device_id != PMCS_INVALID_DEVICE_ID) { mdb_printf(" %3d %4d %6s %4s ", phy.device_id, phy.phynum, speed, dtype); } else { mdb_printf(" N/A %4d %6s %4s ", phy.phynum, speed, dtype); } if (verbose) { if (phy.abort_sent) { asent = yes; } if (phy.abort_pending) { apend = yes; } if (phy.configured) { cfgd = yes; } if (phy.dead) { dead = yes; } if (phy.changed) { changed = yes; } switch (phy.routing_attr) { case SMP_ROUTING_DIRECT: route_attr = 'D'; break; case SMP_ROUTING_SUBTRACTIVE: route_attr = 'S'; break; case SMP_ROUTING_TABLE: route_attr = 'T'; break; default: route_attr = '?'; break; } switch (phy.routing_method) { case SMP_ROUTING_DIRECT: route_method = 'D'; break; case SMP_ROUTING_SUBTRACTIVE: route_method = 'S'; break; case SMP_ROUTING_TABLE: route_method = 'T'; break; default: route_attr = '?'; break; } mdb_printf("%-4s %-4s %-4s %-4s %-4s %3d %3c/%1c %3d " "%1d 0x%p ", cfgd, apend, asent, changed, dead, phy.ref_count, route_attr, route_method, phy.enum_attempts, phy.reenumerate, phy.phy_lock); } mdb_printf("Path: %s\n", phy.path); /* * In verbose mode, on the next line print the drill down * info to see either the DISCOVER response or the REPORT * GENERAL response depending on the PHY's dtype */ if (verbose) { uintptr_t tphyp = (uintptr_t)phyp; mdb_inc_indent(4); switch (phy.dtype) { case EXPANDER: if (!phy.configured) { break; } mdb_printf("REPORT GENERAL response: %p::" "print smp_report_general_resp_t\n", (tphyp + offsetof(struct pmcs_phy, rg_resp))); break; case SAS: case SATA: mdb_printf("DISCOVER response: %p::" "print smp_discover_resp_t\n", (tphyp + offsetof(struct pmcs_phy, disc_resp))); break; default: break; } mdb_dec_indent(4); } } } static void display_phys(struct pmcs_hw ss, int verbose, struct pmcs_phy *parent, int level, int totals_only) { pmcs_phy_t phy; pmcs_phy_t *pphy = parent; mdb_inc_indent(3); if (parent == NULL) { pphy = (pmcs_phy_t *)ss.root_phys; } else { pphy = (pmcs_phy_t *)parent; } if (level == 0) { sas_phys = 0; sata_phys = 0; exp_phys = 0; num_expanders = 0; empty_phys = 0; } if (!totals_only) { if (level == 0) { mdb_printf("PHY information\n"); } mdb_printf("--------\n"); mdb_printf("Level %2d\n", level); mdb_printf("--------\n"); mdb_printf("SAS Address Hdl Phy# Speed Type "); if (verbose) { mdb_printf("Cfgd AbtP AbtS Chgd Dead Ref RtA/M Enm R " "Lock\n"); } else { mdb_printf("\n"); } } while (pphy) { if (MDB_RD(&phy, sizeof (phy), (uintptr_t)pphy) == -1) { NOREAD(pmcs_phy_t, phy); break; } display_phy(phy, pphy, verbose, totals_only); if (phy.children) { display_phys(ss, verbose, phy.children, level + 1, totals_only); if (!totals_only) { mdb_printf("\n"); } } pphy = phy.sibling; } mdb_dec_indent(3); if (level == 0) { if (verbose) { mdb_printf("%19s %d (%d SAS + %d SATA + %d SMP) " "(+%d subsidiary + %d empty)\n", "Occupied PHYs:", (sas_phys + sata_phys + num_expanders), sas_phys, sata_phys, num_expanders, (exp_phys - num_expanders), empty_phys); } else { mdb_printf("%19s %d (%d SAS + %d SATA + %d SMP)\n", "Occupied PHYs:", (sas_phys + sata_phys + num_expanders), sas_phys, sata_phys, num_expanders); } } } /* * filter is used to indicate whether we are filtering log messages based * on "instance". The other filtering (based on options) depends on the * values that are passed in for "sas_addr" and "phy_path". * * MAX_INST_STRLEN is the largest string size from which we will attempt * to convert to an instance number. The string will be formed up as * "0t\0" so that mdb_strtoull can parse it properly. */ #define MAX_INST_STRLEN 8 static int pmcs_dump_tracelog(boolean_t filter, int instance, uint64_t tail_lines, const char *phy_path, uint64_t sas_address, uint64_t verbose) { pmcs_tbuf_t *tbuf_addr; uint_t tbuf_idx; pmcs_tbuf_t tbuf; boolean_t wrap, elem_filtered; uint_t start_idx, elems_to_print, idx, tbuf_num_elems; char *bufp; char elem_inst[MAX_INST_STRLEN], ei_idx; uint64_t sas_addr; uint8_t *sas_addressp; /* Get the address of the first element */ if (mdb_readvar(&tbuf_addr, "pmcs_tbuf") == -1) { mdb_warn("can't read pmcs_tbuf"); return (DCMD_ERR); } /* Get the total number */ if (mdb_readvar(&tbuf_num_elems, "pmcs_tbuf_num_elems") == -1) { mdb_warn("can't read pmcs_tbuf_num_elems"); return (DCMD_ERR); } /* Get the current index */ if (mdb_readvar(&tbuf_idx, "pmcs_tbuf_idx") == -1) { mdb_warn("can't read pmcs_tbuf_idx"); return (DCMD_ERR); } /* Indicator as to whether the buffer has wrapped */ if (mdb_readvar(&wrap, "pmcs_tbuf_wrap") == -1) { mdb_warn("can't read pmcs_tbuf_wrap"); return (DCMD_ERR); } /* * On little-endian systems, the SAS address passed in will be * byte swapped. Take care of that here. */ #if defined(_LITTLE_ENDIAN) sas_addr = ((sas_address << 56) | ((sas_address << 40) & 0xff000000000000ULL) | ((sas_address << 24) & 0xff0000000000ULL) | ((sas_address << 8) & 0xff00000000ULL) | ((sas_address >> 8) & 0xff000000ULL) | ((sas_address >> 24) & 0xff0000ULL) | ((sas_address >> 40) & 0xff00ULL) | (sas_address >> 56)); #else sas_addr = sas_address; #endif sas_addressp = (uint8_t *)&sas_addr; /* Ensure the tail number isn't greater than the size of the log */ if (tail_lines > tbuf_num_elems) { tail_lines = tbuf_num_elems; } /* Figure out where we start and stop */ if (wrap) { if (tail_lines) { /* Do we need to wrap backwards? */ if (tail_lines > tbuf_idx) { start_idx = tbuf_num_elems - (tail_lines - tbuf_idx); } else { start_idx = tbuf_idx - tail_lines; } elems_to_print = tail_lines; } else { start_idx = tbuf_idx; elems_to_print = tbuf_num_elems; } } else { if (tail_lines > tbuf_idx) { tail_lines = tbuf_idx; } if (tail_lines) { start_idx = tbuf_idx - tail_lines; elems_to_print = tail_lines; } else { start_idx = 0; elems_to_print = tbuf_idx; } } idx = start_idx; /* Dump the buffer contents */ while (elems_to_print != 0) { if (MDB_RD(&tbuf, sizeof (pmcs_tbuf_t), (tbuf_addr + idx)) == -1) { NOREAD(tbuf, (tbuf_addr + idx)); return (DCMD_ERR); } /* * Check for filtering on HBA instance */ elem_filtered = B_FALSE; if (filter) { bufp = tbuf.buf; /* Skip the driver name */ while (*bufp < '0' || *bufp > '9') { bufp++; } ei_idx = 0; elem_inst[ei_idx++] = '0'; elem_inst[ei_idx++] = 't'; while (*bufp != ':' && ei_idx < (MAX_INST_STRLEN - 1)) { elem_inst[ei_idx++] = *bufp; bufp++; } elem_inst[ei_idx] = 0; /* Get the instance */ if ((int)mdb_strtoull(elem_inst) != instance) { elem_filtered = B_TRUE; } } if (!elem_filtered && (phy_path || sas_address)) { /* * This message is not being filtered by HBA instance. * Now check to see if we're filtering based on * PHY path or SAS address. * Filtering is an "OR" operation. So, if any of the * criteria matches, this message will be printed. */ elem_filtered = B_TRUE; if (phy_path != NULL) { if (strncmp(phy_path, tbuf.phy_path, PMCS_TBUF_UA_MAX_SIZE) == 0) { elem_filtered = B_FALSE; } } if (sas_address != 0) { if (memcmp(sas_addressp, tbuf.phy_sas_address, 8) == 0) { elem_filtered = B_FALSE; } } } if (!elem_filtered) { /* * If the -v flag was given, print the firmware * timestamp along with the clock time */ mdb_printf("%Y.%09ld ", tbuf.timestamp); if (verbose) { mdb_printf("(0x%" PRIx64 ") ", tbuf.fw_timestamp); } mdb_printf("%s\n", tbuf.buf); } --elems_to_print; if (++idx == tbuf_num_elems) { idx = 0; } } return (DCMD_OK); } /* * Walkers */ static int targets_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("Can not perform global walk\n"); return (WALK_ERR); } /* * Address provided belongs to HBA softstate. Get the targets pointer * to begin the walk. */ if (mdb_vread(&ss, sizeof (pmcs_hw_t), wsp->walk_addr) != sizeof (pmcs_hw_t)) { mdb_warn("Unable to read HBA softstate\n"); return (WALK_ERR); } if (targets == NULL) { targets = mdb_alloc(sizeof (targets) * ss.max_dev, UM_SLEEP); } if (MDB_RD(targets, sizeof (targets) * ss.max_dev, ss.targets) == -1) { NOREAD(targets, ss.targets); return (WALK_ERR); } target_idx = 0; wsp->walk_addr = (uintptr_t)(targets[0]); wsp->walk_data = mdb_alloc(sizeof (pmcs_xscsi_t), UM_SLEEP); return (WALK_NEXT); } static int targets_walk_s(mdb_walk_state_t *wsp) { int status; if (target_idx == ss.max_dev) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (pmcs_xscsi_t), wsp->walk_addr) == -1) { mdb_warn("Failed to read target at %p", (void *)wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); do { wsp->walk_addr = (uintptr_t)(targets[++target_idx]); } while ((wsp->walk_addr == 0) && (target_idx < ss.max_dev)); if (target_idx == ss.max_dev) { return (WALK_DONE); } return (status); } static void targets_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (pmcs_xscsi_t)); } static pmcs_phy_t * pmcs_next_sibling(pmcs_phy_t *phyp) { pmcs_phy_t parent; /* * First, if this is a root PHY, there are no more siblings */ if (phyp->level == 0) { return (NULL); } /* * Otherwise, next sibling is the parent's sibling */ while (phyp->level > 0) { if (mdb_vread(&parent, sizeof (pmcs_phy_t), (uintptr_t)phyp->parent) == -1) { mdb_warn("pmcs_next_sibling: Failed to read PHY at %p", (void *)phyp->parent); return (NULL); } if (parent.sibling != NULL) { break; } /* * If this PHY's sibling is NULL and it's a root phy, * we're done. */ if (parent.level == 0) { return (NULL); } phyp = phyp->parent; } return (parent.sibling); } static int phy_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("Can not perform global walk\n"); return (WALK_ERR); } /* * Address provided belongs to HBA softstate. Get the targets pointer * to begin the walk. */ if (mdb_vread(&ss, sizeof (pmcs_hw_t), wsp->walk_addr) != sizeof (pmcs_hw_t)) { mdb_warn("Unable to read HBA softstate\n"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(ss.root_phys); wsp->walk_data = mdb_alloc(sizeof (pmcs_phy_t), UM_SLEEP); return (WALK_NEXT); } static int phy_walk_s(mdb_walk_state_t *wsp) { pmcs_phy_t *phyp, *nphyp; int status; if (mdb_vread(wsp->walk_data, sizeof (pmcs_phy_t), wsp->walk_addr) == -1) { mdb_warn("phy_walk_s: Failed to read PHY at %p", (void *)wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); phyp = (pmcs_phy_t *)wsp->walk_data; if (phyp->children) { wsp->walk_addr = (uintptr_t)(phyp->children); } else { wsp->walk_addr = (uintptr_t)(phyp->sibling); } if (wsp->walk_addr == 0) { /* * We reached the end of this sibling list. Trudge back up * to the parent and find the next sibling after the expander * we just finished traversing, if there is one. */ nphyp = pmcs_next_sibling(phyp); if (nphyp == NULL) { return (WALK_DONE); } wsp->walk_addr = (uintptr_t)nphyp; } return (status); } static void phy_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (pmcs_phy_t)); } static void display_matching_work(struct pmcs_hw ss, uintmax_t index, uintmax_t snum, uintmax_t tag_type) { int idx; pmcwork_t work, *wp = &work; uintptr_t _wp; boolean_t printed_header = B_FALSE; uint32_t mask, mask_val, match_val; char *match_type = NULL; if (index != UINT_MAX) { match_type = "index"; mask = PMCS_TAG_INDEX_MASK; mask_val = index << PMCS_TAG_INDEX_SHIFT; match_val = index; } else if (snum != UINT_MAX) { match_type = "serial number"; mask = PMCS_TAG_SERNO_MASK; mask_val = snum << PMCS_TAG_SERNO_SHIFT; match_val = snum; } else { switch (tag_type) { case PMCS_TAG_TYPE_NONE: match_type = "tag type NONE"; break; case PMCS_TAG_TYPE_CBACK: match_type = "tag type CBACK"; break; case PMCS_TAG_TYPE_WAIT: match_type = "tag type WAIT"; break; } mask = PMCS_TAG_TYPE_MASK; mask_val = tag_type << PMCS_TAG_TYPE_SHIFT; match_val = tag_type; } _wp = (uintptr_t)ss.work; for (idx = 0; idx < ss.max_cmd; idx++, _wp += sizeof (pmcwork_t)) { if (MDB_RD(&work, sizeof (pmcwork_t), _wp) == -1) { NOREAD(pmcwork_t, _wp); continue; } if ((work.htag & mask) != mask_val) { continue; } if (printed_header == B_FALSE) { if (tag_type) { mdb_printf("\nWork structures matching %s\n\n", match_type, match_val); } else { mdb_printf("\nWork structures matching %s of " "0x%x\n\n", match_type, match_val); } mdb_printf("%8s %10s %20s %8s %8s O D\n", "HTag", "State", "Phy Path", "Target", "Timer"); printed_header = B_TRUE; } display_one_work(wp, 0, 0); } if (!printed_header) { mdb_printf("No work structure matches found\n"); } } static int pmcs_tag(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct pmcs_hw ss; uintmax_t tag_type = UINT_MAX; uintmax_t snum = UINT_MAX; uintmax_t index = UINT_MAX; int args = 0; void *pmcs_state; char *state_str = NULL; struct dev_info dip; if (!(flags & DCMD_ADDRSPEC)) { pmcs_state = NULL; if (mdb_readvar(&pmcs_state, "pmcs_softc_state") == -1) { mdb_warn("can't read pmcs_softc_state"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("genunix`softstate", "pmcs`pmcs_tag", argc, argv, (uintptr_t)pmcs_state) == -1) { mdb_warn("mdb_pwalk_dcmd failed"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'i', MDB_OPT_UINT64, &index, 's', MDB_OPT_UINT64, &snum, 't', MDB_OPT_UINT64, &tag_type, NULL) != argc) return (DCMD_USAGE); /* * Count the number of supplied options and make sure they are * within appropriate ranges. If they're set to UINT_MAX, that means * they were not supplied, in which case reset them to 0. */ if (index != UINT_MAX) { args++; if (index > PMCS_TAG_INDEX_MASK) { mdb_warn("Index is out of range\n"); return (DCMD_USAGE); } } if (tag_type != UINT_MAX) { args++; switch (tag_type) { case PMCS_TAG_TYPE_NONE: case PMCS_TAG_TYPE_CBACK: case PMCS_TAG_TYPE_WAIT: break; default: mdb_warn("Invalid tag type\n"); return (DCMD_USAGE); } } if (snum != UINT_MAX) { args++; if (snum > (PMCS_TAG_SERNO_MASK >> PMCS_TAG_SERNO_SHIFT)) { mdb_warn("Serial number is out of range\n"); return (DCMD_USAGE); } } /* * Make sure 1 and only 1 option is specified */ if ((args == 0) || (args > 1)) { mdb_warn("Exactly one of -i, -s and -t must be specified\n"); return (DCMD_USAGE); } if (MDB_RD(&ss, sizeof (ss), addr) == -1) { NOREAD(pmcs_hw_t, addr); return (DCMD_ERR); } if (MDB_RD(&dip, sizeof (struct dev_info), ss.dip) == -1) { NOREAD(pmcs_hw_t, addr); return (DCMD_ERR); } /* processing completed */ if (((flags & DCMD_ADDRSPEC) && !(flags & DCMD_LOOP)) || (flags & DCMD_LOOPFIRST)) { if ((flags & DCMD_LOOP) && !(flags & DCMD_LOOPFIRST)) mdb_printf("\n"); mdb_printf("%16s %9s %4s B C WorkFlags wserno DbgMsk %16s\n", "Address", "State", "Inst", "DIP"); mdb_printf("=================================" "============================================\n"); } switch (ss.state) { case STATE_NIL: state_str = "Invalid"; break; case STATE_PROBING: state_str = "Probing"; break; case STATE_RUNNING: state_str = "Running"; break; case STATE_UNPROBING: state_str = "Unprobing"; break; case STATE_DEAD: state_str = "Dead"; break; case STATE_IN_RESET: state_str = "In Reset"; break; } mdb_printf("%16p %9s %4d %1d %1d 0x%08x 0x%04x 0x%04x %16p\n", addr, state_str, dip.devi_instance, ss.blocked, ss.configuring, ss.work_flags, ss.wserno, ss.debug_mask, ss.dip); mdb_printf("\n"); mdb_inc_indent(4); display_matching_work(ss, index, snum, tag_type); mdb_dec_indent(4); mdb_printf("\n"); return (DCMD_OK); } #ifndef _KMDB static int pmcs_dump_fwlog(struct pmcs_hw *ss, int instance, const char *ofile) { uint8_t *fwlogp; int ofilefd = -1; char ofilename[MAXPATHLEN]; int rval = DCMD_OK; if (ss->fwlogp == NULL) { mdb_warn("Firmware event log disabled for instance %d", instance); return (DCMD_OK); } if (snprintf(ofilename, MAXPATHLEN, "%s%d", ofile, instance) > MAXPATHLEN) { mdb_warn("Output filename is too long for instance %d", instance); return (DCMD_ERR); } fwlogp = mdb_alloc(PMCS_FWLOG_SIZE, UM_SLEEP); if (MDB_RD(fwlogp, PMCS_FWLOG_SIZE, ss->fwlogp) == -1) { NOREAD(fwlogp, ss->fwlogp); rval = DCMD_ERR; goto cleanup; } ofilefd = open(ofilename, O_WRONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH); if (ofilefd < 0) { mdb_warn("Unable to open '%s' to dump instance %d event log", ofilename, instance); rval = DCMD_ERR; goto cleanup; } if (write(ofilefd, fwlogp, PMCS_FWLOG_SIZE) != PMCS_FWLOG_SIZE) { mdb_warn("Failed to write %d bytes to output file: instance %d", PMCS_FWLOG_SIZE, instance); rval = DCMD_ERR; goto cleanup; } mdb_printf("Event log for instance %d written to %s\n", instance, ofilename); cleanup: if (ofilefd >= 0) { close(ofilefd); } mdb_free(fwlogp, PMCS_FWLOG_SIZE); return (rval); } static int pmcs_fwlog(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { void *pmcs_state; const char *ofile = NULL; struct pmcs_hw ss; struct dev_info dip; if (mdb_getopts(argc, argv, 'o', MDB_OPT_STR, &ofile, NULL) != argc) { return (DCMD_USAGE); } if (ofile == NULL) { mdb_printf("No output file specified\n"); return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { pmcs_state = NULL; if (mdb_readvar(&pmcs_state, "pmcs_softc_state") == -1) { mdb_warn("can't read pmcs_softc_state"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("genunix`softstate", "pmcs`pmcs_fwlog", argc, argv, (uintptr_t)pmcs_state) == -1) { mdb_warn("mdb_pwalk_dcmd failed for pmcs_log"); return (DCMD_ERR); } return (DCMD_OK); } if (MDB_RD(&ss, sizeof (ss), addr) == -1) { NOREAD(pmcs_hw_t, addr); return (DCMD_ERR); } if (MDB_RD(&dip, sizeof (struct dev_info), ss.dip) == -1) { NOREAD(pmcs_hw_t, addr); return (DCMD_ERR); } return (pmcs_dump_fwlog(&ss, dip.devi_instance, ofile)); } #endif /* _KMDB */ static int pmcs_log(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { void *pmcs_state; struct pmcs_hw ss; struct dev_info dip; const char *match_phy_path = NULL; uint64_t match_sas_address = 0, tail_lines = 0; uint_t verbose = 0; if (!(flags & DCMD_ADDRSPEC)) { pmcs_state = NULL; if (mdb_readvar(&pmcs_state, "pmcs_softc_state") == -1) { mdb_warn("can't read pmcs_softc_state"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("genunix`softstate", "pmcs`pmcs_log", argc, argv, (uintptr_t)pmcs_state) == -1) { mdb_warn("mdb_pwalk_dcmd failed for pmcs_log"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'l', MDB_OPT_UINT64, &tail_lines, 'p', MDB_OPT_STR, &match_phy_path, 's', MDB_OPT_UINT64, &match_sas_address, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) { return (DCMD_USAGE); } if (MDB_RD(&ss, sizeof (ss), addr) == -1) { NOREAD(pmcs_hw_t, addr); return (DCMD_ERR); } if (MDB_RD(&dip, sizeof (struct dev_info), ss.dip) == -1) { NOREAD(pmcs_hw_t, addr); return (DCMD_ERR); } if (!(flags & DCMD_LOOP)) { return (pmcs_dump_tracelog(B_TRUE, dip.devi_instance, tail_lines, match_phy_path, match_sas_address, verbose)); } else if (flags & DCMD_LOOPFIRST) { return (pmcs_dump_tracelog(B_FALSE, 0, tail_lines, match_phy_path, match_sas_address, verbose)); } else { return (DCMD_OK); } } static int pmcs_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct pmcs_hw ss; uint_t verbose = FALSE; uint_t phy_info = FALSE; uint_t hw_info = FALSE; uint_t target_info = FALSE; uint_t work_info = FALSE; uint_t ic_info = FALSE; uint_t iport_info = FALSE; uint_t waitqs_info = FALSE; uint_t ibq = FALSE; uint_t obq = FALSE; uint_t tgt_phy_count = FALSE; uint_t compq = FALSE; uint_t unconfigured = FALSE; uint_t damap_info = FALSE; uint_t dtc_info = FALSE; uint_t wserno = FALSE; uint_t fwlog = FALSE; boolean_t devid_filter = FALSE; uintptr_t pdevid; uint32_t devid = 0; int rv = DCMD_OK; void *pmcs_state; char *state_str = NULL; struct dev_info dip; per_iport_setting_t pis; if (!(flags & DCMD_ADDRSPEC)) { pmcs_state = NULL; if (mdb_readvar(&pmcs_state, "pmcs_softc_state") == -1) { mdb_warn("can't read pmcs_softc_state"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("genunix`softstate", "pmcs`pmcs", argc, argv, (uintptr_t)pmcs_state) == -1) { mdb_warn("mdb_pwalk_dcmd failed"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'c', MDB_OPT_SETBITS, TRUE, &compq, 'd', MDB_OPT_SETBITS, TRUE, &dtc_info, 'D', MDB_OPT_UINTPTR_SET, &devid_filter, &pdevid, 'e', MDB_OPT_SETBITS, TRUE, &fwlog, 'h', MDB_OPT_SETBITS, TRUE, &hw_info, 'i', MDB_OPT_SETBITS, TRUE, &ic_info, 'I', MDB_OPT_SETBITS, TRUE, &iport_info, 'm', MDB_OPT_SETBITS, TRUE, &damap_info, 'p', MDB_OPT_SETBITS, TRUE, &phy_info, 'q', MDB_OPT_SETBITS, TRUE, &ibq, 'Q', MDB_OPT_SETBITS, TRUE, &obq, 's', MDB_OPT_SETBITS, TRUE, &wserno, 't', MDB_OPT_SETBITS, TRUE, &target_info, 'T', MDB_OPT_SETBITS, TRUE, &tgt_phy_count, 'u', MDB_OPT_SETBITS, TRUE, &unconfigured, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'w', MDB_OPT_SETBITS, TRUE, &work_info, 'W', MDB_OPT_SETBITS, TRUE, &waitqs_info, NULL) != argc) return (DCMD_USAGE); /* * The 'd' and 'm' options implicitly enable the 'I' option */ pis.pis_damap_info = damap_info; pis.pis_dtc_info = dtc_info; if (damap_info || dtc_info) { iport_info = TRUE; } /* * The -D option is meaningless without -q and/or -Q, and implies * verbosity. */ if (devid_filter) { devid = (uint64_t)pdevid & 0xffffffff; if (!ibq && !obq) { mdb_printf("-D requires either -q or -Q\n"); return (DCMD_USAGE); } if (devid > PMCS_DEVICE_ID_MASK) { mdb_printf("Device ID invalid\n"); return (DCMD_USAGE); } verbose = TRUE; } if (MDB_RD(&ss, sizeof (ss), addr) == -1) { NOREAD(pmcs_hw_t, addr); return (DCMD_ERR); } if (MDB_RD(&dip, sizeof (struct dev_info), ss.dip) == -1) { NOREAD(pmcs_hw_t, addr); return (DCMD_ERR); } /* processing completed */ if (((flags & DCMD_ADDRSPEC) && !(flags & DCMD_LOOP)) || (flags & DCMD_LOOPFIRST) || phy_info || target_info || hw_info || work_info || waitqs_info || ibq || obq || tgt_phy_count || compq || unconfigured || fwlog) { if ((flags & DCMD_LOOP) && !(flags & DCMD_LOOPFIRST)) mdb_printf("\n"); mdb_printf("%16s %9s %4s B C WorkFlags wserno DbgMsk %16s\n", "Address", "State", "Inst", "DIP"); mdb_printf("=================================" "============================================\n"); } switch (ss.state) { case STATE_NIL: state_str = "Invalid"; break; case STATE_PROBING: state_str = "Probing"; break; case STATE_RUNNING: state_str = "Running"; break; case STATE_UNPROBING: state_str = "Unprobing"; break; case STATE_DEAD: state_str = "Dead"; break; case STATE_IN_RESET: state_str = "In Reset"; break; } mdb_printf("%16p %9s %4d %1d %1d 0x%08x 0x%04x 0x%04x %16p\n", addr, state_str, dip.devi_instance, ss.blocked, ss.configuring, ss.work_flags, ss.wserno, ss.debug_mask, ss.dip); mdb_printf("\n"); mdb_inc_indent(4); if (waitqs_info) display_waitqs(ss, verbose); if (hw_info) display_hwinfo(ss, verbose); if (phy_info || tgt_phy_count) display_phys(ss, verbose, NULL, 0, tgt_phy_count); if (target_info || tgt_phy_count) display_targets(ss, verbose, tgt_phy_count); if (work_info || wserno) display_work(ss, verbose, wserno); if (ic_info) display_ic(ss, verbose); if (ibq) display_inbound_queues(ss, devid, verbose); if (obq) display_outbound_queues(ss, devid, verbose); if (iport_info) display_iport(ss, addr, verbose, &pis); if (compq) display_completion_queue(ss); if (unconfigured) display_unconfigured_targets(addr); if (fwlog) display_event_log(ss); mdb_dec_indent(4); return (rv); } void pmcs_help() { mdb_printf("Prints summary information about each pmcs instance.\n" " -c: Dump the completion queue\n" " -d: Print per-iport information about device tree children\n" " -D : With -q/-Q, filter by device handle\n" " -e: Display the in-memory firmware event log\n" " -h: Print more detailed hardware information\n" " -i: Print interrupt coalescing information\n" " -I: Print information about each iport\n" " -m: Print per-iport information about DAM/damap state\n" " -p: Print information about each attached PHY\n" " -q: Dump inbound queues\n" " -Q: Dump outbound queues\n" " -s: Dump all work structures sorted by serial number\n" " -t: Print information about each configured target\n" " -T: Print target and PHY count summary\n" " -u: Show SAS address of all unconfigured targets\n" " -w: Dump work structures\n" " -W: List pmcs cmds waiting on various queues\n" " -v: Add verbosity to the above options\n"); } void pmcs_log_help() { mdb_printf("Dump the pmcs log buffer, possibly with filtering.\n" " -l TAIL_LINES: Dump the last TAIL_LINES messages\n" " -p PHY_PATH: Dump messages matching PHY_PATH\n" " -s SAS_ADDRESS: Dump messages matching SAS_ADDRESS\n\n" "Where: PHY_PATH can be found with ::pmcs -p (e.g. pp04.18.18.01)\n" " SAS_ADDRESS can be found with ::pmcs -t " "(e.g. 5000c5000358c221)\n"); } void pmcs_tag_help() { mdb_printf("Print all work structures by matching the tag.\n" " -i index: Match tag index (0x000 - 0xfff)\n" " -s serialnumber: Match serial number (0x0000 - 0xffff)\n" " -t tagtype: Match tag type [NONE(1), CBACK(2), " "WAIT(3)]\n"); } static const mdb_dcmd_t dcmds[] = { { "pmcs", "?[-cdehiImpQqtTuwWv] [-D ]", "print pmcs information", pmcs_dcmd, pmcs_help }, { "pmcs_log", "?[-v] [-p PHY_PATH | -s SAS_ADDRESS | -l TAIL_LINES]", "dump pmcs log file", pmcs_log, pmcs_log_help }, { "pmcs_tag", "?[-t tagtype|-s serialnum|-i index]", "Find work structures by tag type, serial number or index", pmcs_tag, pmcs_tag_help }, #ifndef _KMDB { "pmcs_fwlog", "?-o output_file", "dump pmcs firmware event log to output_file", pmcs_fwlog, NULL }, #endif /* _KMDB */ { NULL } }; static const mdb_walker_t walkers[] = { { "pmcs_targets", "walk target structures", targets_walk_i, targets_walk_s, targets_walk_f }, { "pmcs_phys", "walk PHY structures", phy_walk_i, phy_walk_s, phy_walk_f }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2017 Joyent, Inc. */ #include #include #include #include #include typedef struct pt_flags { const char *pt_name; const char *pt_descr; } ptflags_t; static const struct pt_flags pf[] = { { "PTLOCK", "Master/slave pair is locked" }, { "PTMOPEN", "Master side is open" }, { "PTSOPEN", "Slave side is open" }, { "PTSTTY", "Slave side is tty" }, { NULL }, }; static int pt_parse_flag(const ptflags_t ftable[], const char *arg, uint32_t *flag) { int i; for (i = 0; ftable[i].pt_name != NULL; i++) { if (strcasecmp(arg, ftable[i].pt_name) == 0) { *flag |= (1 << i); return (0); } } return (-1); } static void pt_flag_usage(const ptflags_t ftable[]) { int i; for (i = 0; ftable[i].pt_name != NULL; i++) mdb_printf("%12s %s\n", ftable[i].pt_name, ftable[i].pt_descr); } static void ptms_pr_qinfo(char *buf, size_t nbytes, struct pt_ttys *pt, char *peername, queue_t *peerq, char *procname) { (void) mdb_snprintf(buf, nbytes, "pts/%d:%s: %p\nprocess: %d(%s)", pt->pt_minor, peername, peerq, pt->pt_pid, procname); } static int ptms(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const int PT_FLGDELT = (int)(sizeof (uintptr_t) * 2 + 5); struct pt_ttys pt; char c[MAXCOMLEN + 1]; const char *flag = NULL, *not_flag = NULL; proc_t p; uint_t verbose = FALSE; uint32_t mask = 0, not_mask = 0; if (!(flags & DCMD_ADDRSPEC)) return (mdb_walk_dcmd("ptms", "ptms", argc, argv)); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'f', MDB_OPT_STR, &flag, 'F', MDB_OPT_STR, ¬_flag, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags) && flag == NULL && not_flag == NULL) { (void) mdb_printf("%?-s %s %s %?-s %?-s %3s %-6s %s\n", "ADDR", "PTY", "FL", "MASTERQ", "SLAVEQ", "ZID", "PID", "PROC"); } if (flag != NULL && pt_parse_flag(pf, flag, &mask) == -1) { mdb_warn("unrecognized pty flag '%s'\n", flag); pt_flag_usage(pf); return (DCMD_USAGE); } if (not_flag != NULL && pt_parse_flag(pf, not_flag, ¬_mask) == -1) { mdb_warn("unrecognized queue flag '%s'\n", flag); pt_flag_usage(pf); return (DCMD_USAGE); } if (mdb_vread(&pt, sizeof (pt), addr) == -1) { mdb_warn("failed to read pty structure"); return (DCMD_ERR); } if (mask != 0 && !(pt.pt_state & mask)) return (DCMD_OK); if (not_mask != 0 && (pt.pt_state & not_mask)) return (DCMD_OK); /* * Options are specified for filtering, so If any option is specified on * the command line, just print address and exit. */ if (flag != NULL || not_flag != NULL) { mdb_printf("%0?p\n", addr); return (DCMD_OK); } if (pt.pt_pid != 0) { if (mdb_pid2proc(pt.pt_pid, &p) == 0) (void) strcpy(c, ""); else (void) strcpy(c, p.p_user.u_comm); } else (void) strcpy(c, ""); (void) mdb_printf("%0?p %3d %2x %0?p %0?p %3d %6d %s\n", addr, pt.pt_minor, pt.pt_state, pt.ptm_rdq, pt.pts_rdq, pt.pt_zoneid, pt.pt_pid, c); if (verbose) { int i, arm = 0; for (i = 0; pf[i].pt_name != NULL; i++) { if (!(pt.pt_state & (1 << i))) continue; if (!arm) { mdb_printf("%*s|\n%*s+--> ", PT_FLGDELT, "", PT_FLGDELT, ""); arm = 1; } else mdb_printf("%*s ", PT_FLGDELT, ""); mdb_printf("%-12s %s\n", pf[i].pt_name, pf[i].pt_descr); } } return (DCMD_OK); } static void ptms_qinfo(const queue_t *q, char *buf, size_t nbytes, int ismaster) { char c[MAXCOMLEN + 1]; struct pt_ttys pt; proc_t p; (void) mdb_vread(&pt, sizeof (pt), (uintptr_t)q->q_ptr); if (pt.pt_pid != 0) { if (mdb_pid2proc(pt.pt_pid, &p) == 0) (void) strcpy(c, ""); else (void) strcpy(c, p.p_user.u_comm); } else (void) strcpy(c, ""); if (ismaster) ptms_pr_qinfo(buf, nbytes, &pt, "slave", pt.pts_rdq, c); else ptms_pr_qinfo(buf, nbytes, &pt, "master", pt.ptm_rdq, c); } void ptm_qinfo(const queue_t *q, char *buf, size_t nbytes) { ptms_qinfo(q, buf, nbytes, 1); } void pts_qinfo(const queue_t *q, char *buf, size_t nbytes) { ptms_qinfo(q, buf, nbytes, 0); } static int ptms_walk_init(mdb_walk_state_t *wsp) { size_t nslots; if (wsp->walk_addr != 0) { mdb_warn("ptms supports only global walks"); return (WALK_ERR); } if (mdb_readvar(&wsp->walk_addr, "ptms_slots") == -1) { mdb_warn("failed to read 'ptms_slots'"); return (WALK_ERR); } if (mdb_readvar(&nslots, "ptms_nslots") == -1) { mdb_warn("failed to read 'ptms_nslots'"); return (WALK_ERR); } /* * We remember the pointer value at the end of the array. When * the walk gets there, we're done. */ wsp->walk_arg = (((struct pt_ttys **)wsp->walk_addr) + (nslots - 1)); wsp->walk_data = mdb_alloc(sizeof (struct pt_ttys), UM_SLEEP); return (WALK_NEXT); } static int ptms_walk_step(mdb_walk_state_t *wsp) { int status; uintptr_t ptr; if (wsp->walk_addr > (uintptr_t)wsp->walk_arg) return (WALK_DONE); if (mdb_vread(&ptr, sizeof (struct pt_ttys *), wsp->walk_addr) != (sizeof (struct pt_ttys *))) { mdb_warn("failed to read pt_ttys* at %p", wsp->walk_addr); return (WALK_DONE); } if (ptr == 0) { wsp->walk_addr += sizeof (uintptr_t); return (WALK_NEXT); } if (mdb_vread(wsp->walk_data, sizeof (struct pt_ttys), ptr) != sizeof (struct pt_ttys)) { mdb_warn("failed to read pt_ttys at %p", ptr); return (WALK_DONE); } status = wsp->walk_callback(ptr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr += sizeof (uintptr_t); return (status); } static void ptms_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct pt_ttys)); } static const mdb_dcmd_t dcmds[] = { { "ptms", "?[-v] [-f flag] [-F flag]", "print pseudo-terminal information", ptms }, { "pty", "?[-v] [-f flag] [-F flag]", "print pseudo-terminal information (alias of ::ptms", ptms }, { NULL } }; static const mdb_walker_t walkers[] = { { "ptms", "walk list of pseudo-tty's", ptms_walk_init, ptms_walk_step, ptms_walk_fini }, { "pty", "walk list of pseudo-tty's (alias of ::walk ptms)", ptms_walk_init, ptms_walk_step, ptms_walk_fini }, { NULL } }; static const mdb_qops_t ptm_qops = { .q_info = ptm_qinfo, .q_rnext = mdb_qrnext_default, .q_wnext = mdb_qwnext_default, }; static const mdb_qops_t pts_qops = { .q_info = pts_qinfo, .q_rnext = mdb_qrnext_default, .q_wnext = mdb_qwnext_default, }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_obj("ptm", "ptmwint", &sym) == 0) mdb_qops_install(&ptm_qops, (uintptr_t)sym.st_value); if (mdb_lookup_by_obj("pts", "ptswint", &sym) == 0) mdb_qops_install(&pts_qops, (uintptr_t)sym.st_value); return (&modinfo); } void _mdb_fini(void) { GElf_Sym sym; if (mdb_lookup_by_obj("ptm", "ptmwint", &sym) == 0) mdb_qops_remove(&ptm_qops, (uintptr_t)sym.st_value); if (mdb_lookup_by_obj("pts", "ptswint", &sym) == 0) mdb_qops_remove(&pts_qops, (uintptr_t)sym.st_value); } /* * 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 2015 QLogic Corporation */ /* * ISP2xxx Solaris Fibre Channel Adapter (FCA) qlc mdb source file. * * *********************************************************************** * * ** * * NOTICE ** * * COPYRIGHT (C) 1996-2015 QLOGIC CORPORATION ** * * ALL RIGHTS RESERVED ** * * ** * *********************************************************************** * */ /* * Copyright 2019 Joyent, Inc. */ #include #include #include #include /* * local prototypes */ static int32_t ql_doprint(uintptr_t, int8_t *); static void ql_dump_flags(uint64_t, int8_t **); static int qlclinks_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); static int qlcstate_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); static int qlc_osc_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); static int qlc_wdog_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); static int qlc_getdump_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); static int qlc_gettrace_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); #if 0 static int qlc_triggerdump_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); #endif static int qlcver_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); static int qlstates_walk_init(mdb_walk_state_t *); static int qlstates_walk_step(mdb_walk_state_t *); static void qlstates_walk_fini(mdb_walk_state_t *); static int qlsrb_walk_init(mdb_walk_state_t *); static int qlsrb_walk_step(mdb_walk_state_t *); static void qlsrb_walk_fini(mdb_walk_state_t *); static int get_next_link(ql_link_t *); static int get_first_link(ql_head_t *, ql_link_t *); static int ql_24xx_dump_dcmd(ql_adapter_state_t *, uint_t, int, const mdb_arg_t *); static int ql_23xx_dump_dcmd(ql_adapter_state_t *, uint_t, int, const mdb_arg_t *); static int ql_25xx_dump_dcmd(ql_adapter_state_t *, uint_t, int, const mdb_arg_t *); static int ql_81xx_dump_dcmd(ql_adapter_state_t *, uint_t, int, const mdb_arg_t *); static int ql_8021_dump_dcmd(ql_adapter_state_t *, uint_t, int, const mdb_arg_t *); static int ql_8300_dump_dcmd(ql_adapter_state_t *, uint_t, int, const mdb_arg_t *); static void ql_elog_common(ql_adapter_state_t *, boolean_t); /* * local adapter state flags strings */ int8_t *adapter_state_flags[] = { "FCA_BOUND", "QL_OPENED", "ONLINE", "INTERRUPTS_ENABLED", "ABORT_CMDS_LOOP_DOWN_TMO", "POINT_TO_POINT", "IP_ENABLED", "IP_INITIALIZED", "MENLO_LOGIN_OPERATIONAL", "ADAPTER_SUSPENDED", "FW_DUMP_NEEDED", "PARITY_ERROR", "FLASH_ERRLOG_MARKER", "VP_ENABLED", "FDISC_ENABLED", "MULTI_QUEUE", "MPI_RESET_NEEDED", "VP_ID_NOT_ACQUIRED", "IDC_STALL_NEEDED", "POLL_INTR", "IDC_RESTART_NEEDED", "IDC_ACK_NEEDED", "LOOPBACK_ACTIVE", "QUEUE_SHADOW_PTRS", "NO_INTR_HANDSHAKE", "COMP_THD_TERMINATE", "DISABLE_NIC_FW_DMP", "MULTI_CHIP_ADAPTER", NULL }; int8_t *adapter_config_flags[] = { "CTRL_27XX", "ENABLE_64BIT_ADDRESSING", "ENABLE_LIP_RESET", "ENABLE_FULL_LIP_LOGIN", "ENABLE_TARGET_RESET", "ENABLE_LINK_DOWN_REPORTING", "DISABLE_EXTENDED_LOGGING_TRACE", "ENABLE_FCP_2_SUPPORT", "CTRL_83XX", "SBUS_CARD", "CTRL_23XX", "CTRL_63XX", "CTRL_22XX", "CTRL_24XX", "CTRL_25XX", "ENABLE_EXTENDED_LOGGING", "DISABLE_RISC_CODE_LOAD", "SET_CACHE_LINE_SIZE_1", "CTRL_MENLO", "EXT_FW_INTERFACE", "LOAD_FLASH_FW", "DUMP_MAILBOX_TIMEOUT", "DUMP_ISP_SYSTEM_ERROR", "DUMP_DRIVER_COMMAND_TIMEOUT", "DUMP_LOOP_OFFLINE_TIMEOUT", "ENABLE_FWEXTTRACE", "ENABLE_FWFCETRACE", "CTRL_80XX", "CTRL_81XX", "CTRL_82XX", "FAST_TIMEOUT", "LR_SUPPORT", NULL }; /* * local task daemon flags strings */ int8_t *task_daemon_flags[] = { "TASK_DAEMON_STOP_FLG", "TASK_DAEMON_SLEEPING_FLG", "TASK_DAEMON_ALIVE_FLG", "TASK_DAEMON_IDLE_CHK_FLG", "SUSPENDED_WAKEUP_FLG", "FC_STATE_CHANGE", "NEED_UNSOLICITED_BUFFERS", "MARKER_NEEDED", "MARKER_ACTIVE", "ISP_ABORT_NEEDED", "ABORT_ISP_ACTIVE", "LOOP_RESYNC_NEEDED", "LOOP_RESYNC_ACTIVE", "LOOP_DOWN", "DRIVER_STALL", "COMMAND_WAIT_NEEDED", "COMMAND_WAIT_ACTIVE", "STATE_ONLINE", "ABORT_QUEUES_NEEDED", "TASK_DAEMON_STALLED_FLG", "SEND_PLOGI", "FIRMWARE_UP", "IDC_POLL_NEEDED", "FIRMWARE_LOADED", "RSCN_UPDATE_NEEDED", "HANDLE_PORT_BYPASS_CHANGE", "PORT_RETRY_NEEDED", "TASK_DAEMON_POWERING_DOWN", "TD_IIDMA_NEEDED", "WATCHDOG_NEEDED", "LED_BLINK", NULL }; /* * local interrupt aif flags */ int8_t *aif_flags[] = { "IFLG_INTR_LEGACY", "IFLG_INTR_FIXED", "IFLG_INTR_MSI", "IFLG_INTR_MSIX", NULL }; int8_t *qlsrb_flags[] = { "SRB_ISP_STARTED", "SRB_ISP_COMPLETED", "SRB_RETRY", "SRB_POLL", "SRB_WATCHDOG_ENABLED", "SRB_ABORT", "SRB_UB_IN_FCA", "SRB_UB_IN_ISP", "SRB_UB_CALLBACK", "SRB_UB_RSCN", "SRB_UB_FCP", "SRB_FCP_CMD_PKT", "SRB_FCP_DATA_PKT", "SRB_FCP_RSP_PKT", "SRB_IP_PKT", "SRB_GENERIC_SERVICES_PKT", "SRB_COMMAND_TIMEOUT", "SRB_ABORTING", "SRB_IN_DEVICE_QUEUE", "SRB_IN_TOKEN_ARRAY", "SRB_UB_FREE_REQUESTED", "SRB_UB_ACQUIRED", "SRB_MS_PKT", "SRB_ELS_PKT", NULL }; int8_t *qllun_flags[] = { "LQF_UNTAGGED_PENDING", NULL }; int8_t *qltgt_flags[] = { "TQF_TAPE_DEVICE", "TQF_QUEUE_SUSPENDED", "TQF_FABRIC_DEVICE", "TQF_INITIATOR_DEVICE", "TQF_RSCN_RCVD", "TQF_NEED_AUTHENTICATION", "TQF_PLOGI_PROGRS", "TQF_IIDMA_NEEDED", "TQF_LOGIN_NEEDED", NULL }; int8_t *qldump_flags[] = { "QL_DUMPING", "QL_DUMP_VALID", "QL_DUMP_UPLOADED", NULL }; /* * qlclinks_dcmd * mdb dcmd which prints out the ql_hba pointers * * Input: * addr = User supplied address -- error if supplied. * flags = mdb flags. * argc = Number of user supplied args -- error if non-zero. * argv = Arg array. * * Returns: * DCMD_ERR, DCMD_USAGE, or DCMD_OK * * Context: * User context. * */ /*ARGSUSED*/ static int qlclinks_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ql_head_t ql_hba; ql_adapter_state_t *qlstate; uintptr_t hbaptr = 0; if ((flags & DCMD_ADDRSPEC) || argc != 0) { return (DCMD_USAGE); } if (mdb_readvar(&ql_hba, "ql_hba") == -1) { mdb_warn("failed to read ql_hba structure"); return (DCMD_ERR); } if (ql_hba.first == NULL) { mdb_warn("failed to read ql_hba structure -- is qlc loaded?"); return (DCMD_ERR); } mdb_printf("\nqlc adapter state linkages (f=0x%llx, l=0x%llx)\n\n", ql_hba.first, ql_hba.last); if ((qlstate = (ql_adapter_state_t *)mdb_alloc( sizeof (ql_adapter_state_t), UM_SLEEP)) == NULL) { mdb_warn("Unable to allocate memory for ql_adapter_state\n"); return (DCMD_OK); } (void) mdb_inc_indent((ulong_t)4); mdb_printf("%%-?s\t%-45s%\n\n", "baseaddr", "instance"); hbaptr = (uintptr_t)ql_hba.first; while (hbaptr != 0) { if (mdb_vread(qlstate, sizeof (ql_adapter_state_t), hbaptr) == -1) { mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_warn("failed to read ql_adapter_state at %p", hbaptr); return (DCMD_OK); } mdb_printf("%0x%016p%t%d%\n", qlstate->hba.base_address, qlstate->instance); /* * If vp exists, loop through those */ if ((qlstate->flags & VP_ENABLED) && (qlstate->vp_next != NULL)) { ql_adapter_state_t *vqlstate; uintptr_t vhbaptr = 0; vhbaptr = (uintptr_t)qlstate->vp_next; if ((vqlstate = (ql_adapter_state_t *)mdb_alloc( sizeof (ql_adapter_state_t), UM_SLEEP)) == NULL) { mdb_warn("Unable to allocate memory for " "ql_adapter_state vp\n"); mdb_free(qlstate, sizeof (ql_adapter_state_t)); return (DCMD_OK); } (void) mdb_inc_indent((ulong_t)30); mdb_printf("%vp baseaddr\t\tvp index%\n"); while (vhbaptr != 0) { if (mdb_vread(vqlstate, sizeof (ql_adapter_state_t), vhbaptr) == -1) { mdb_free(vqlstate, sizeof (ql_adapter_state_t)); mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_warn("failed to read vp " "ql_adapter_state at %p", vhbaptr); return (DCMD_OK); } mdb_printf("%0x%016p%t%d%\n", vqlstate->hba.base_address, vqlstate->vp_index); vhbaptr = (uintptr_t)vqlstate->vp_next; } mdb_free(vqlstate, sizeof (ql_adapter_state_t)); (void) mdb_dec_indent((ulong_t)30); mdb_printf("\n"); } hbaptr = (uintptr_t)qlstate->hba.next; } (void) mdb_dec_indent((ulong_t)4); mdb_free(qlstate, sizeof (ql_adapter_state_t)); return (DCMD_OK); } /* * qlcver_dcmd * mdb dcmd which prints out the qlc driver version the mdb * module was compiled with, and the verison of qlc which is * currently loaded on the machine. * * Input: * addr = User supplied address -- error if supplied. * flags = mdb flags. * argc = Number of user supplied args -- error if non-zero. * argv = Arg array. * * Returns: * DCMD_USAGE, or DCMD_OK * * Context: * User context. * */ /*ARGSUSED*/ static int qlcver_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int8_t qlcversion[100]; struct fw_table fw_table[10], *fwt = NULL; uint8_t *fwverptr = NULL; ql_head_t ql_hba; uint32_t found = 0; if ((flags & DCMD_ADDRSPEC) || argc != 0) { return (DCMD_USAGE); } if (mdb_readvar(&qlcversion, "qlc_driver_version") == -1) { mdb_warn("unable to read qlc driver version\n"); } else { mdb_printf("\n%s version currently loaded is: %s\n", QL_NAME, qlcversion); } mdb_printf("qlc mdb library compiled with %s version: %s\n", QL_NAME, QL_VERSION); if ((fwverptr = (uint8_t *)(mdb_alloc(50, UM_SLEEP))) == NULL) { mdb_warn("unable to alloc fwverptr\n"); return (DCMD_OK); } if (mdb_readvar(&fw_table, "fw_table") == -1) { mdb_warn("unable to read firmware table\n"); } else { ql_adapter_state_t *qlstate; uintptr_t hbaptr = 0; if (mdb_readvar(&ql_hba, "ql_hba") == -1) { mdb_warn("failed to read ql_hba structure"); return (DCMD_ERR); } if ((qlstate = (ql_adapter_state_t *)mdb_alloc( sizeof (ql_adapter_state_t), UM_SLEEP)) == NULL) { mdb_warn("Unable to allocate memory for " "ql_adapter_state\n"); return (DCMD_OK); } mdb_printf("\n%-8s%-11s%s\n", "f/w", "compiled", "loaded"); mdb_printf("%%-8s%-11s%-13s%s%\n\n", "class", "version", "version", "instance list"); for (fwt = &fw_table[0]; fwt->fw_class; fwt++) { if (mdb_vread(fwverptr, sizeof (void *), (uintptr_t)fwt->fw_version) == -1) { mdb_warn("unable to read fwverptr\n"); mdb_free(fwverptr, sizeof (void *)); mdb_free(qlstate, sizeof (ql_adapter_state_t)); return (DCMD_OK); } mdb_printf("%x\t%-11s", fwt->fw_class, fwverptr); if (ql_hba.first == NULL) { mdb_warn("failed to read ql_hba structure"); hbaptr = 0; } else { hbaptr = (uintptr_t)ql_hba.first; } found = 0; while (hbaptr != 0) { if (mdb_vread(qlstate, sizeof (ql_adapter_state_t), hbaptr) == -1) { mdb_warn("failed to read " "ql_adapter_state at %p", hbaptr); break; } if (qlstate->fw_class == fwt->fw_class) { if (found == 0) { mdb_printf("%x.%02x.%02x\t", qlstate->fw_major_version, qlstate->fw_minor_version, qlstate-> fw_subminor_version); mdb_printf("%d", qlstate->instance); } else { mdb_printf(", %d", qlstate->instance); } found = 1; } hbaptr = (uintptr_t)qlstate->hba.next; } if (found == 1) { mdb_printf("\n"); } else { mdb_printf("not loaded\n"); } } mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_free(fwverptr, sizeof (void *)); } return (DCMD_OK); } /* * qlc_el_dcmd * mdb dcmd which turns the extended logging bit on or off * for the specificed qlc instance(s). * * Input: * addr = User supplied address -- error if supplied. * flags = mdb flags. * argc = Number of user supplied args -- error if non-zero. * argv = Arg array. * * Returns: * DCMD_USAGE, or DCMD_OK * * Context: * User context. * */ /*ARGSUSED*/ static int qlc_el_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int8_t qlcversion[100]; boolean_t elswitch; uint32_t argcnt; int mdbs; uint32_t instance; uint32_t qlsize = sizeof (ql_adapter_state_t); ql_adapter_state_t *qlstate; uintptr_t hbaptr = 0; ql_head_t ql_hba; if ((mdbs = mdb_get_state()) == MDB_STATE_DEAD) { mdb_warn("Cannot change core file data (state=%xh)\n", mdbs); return (DCMD_OK); } if ((flags & DCMD_ADDRSPEC) || argc < 2) { return (DCMD_USAGE); } /* * Check and make sure the driver version and the mdb versions * match so all the structures and flags line up */ if (mdb_readvar(&qlcversion, "qlc_driver_version") == -1) { mdb_warn("unable to read qlc driver version\n"); return (DCMD_OK); } if ((strcmp(QL_VERSION, (const char *)&qlcversion)) != 0) { mdb_warn("Error: qlc driver/qlc mdb version mismatch\n"); mdb_printf("\tqlc mdb library compiled version is: %s\n", QL_VERSION); mdb_printf("\tqlc driver version is: %s\n", qlcversion); return (DCMD_OK); } if ((strcasecmp(argv[0].a_un.a_str, "on")) == 0) { elswitch = TRUE; } else if ((strcasecmp(argv[0].a_un.a_str, "off")) == 0) { elswitch = FALSE; } else { return (DCMD_USAGE); } if (mdb_readvar(&ql_hba, "ql_hba") == -1) { mdb_warn("failed to read ql_hba structure"); return (DCMD_ERR); } if (ql_hba.first == NULL) { mdb_warn("failed to read ql_hba structure - is qlc loaded?"); return (DCMD_ERR); } if ((qlstate = (ql_adapter_state_t *)mdb_alloc(qlsize, UM_SLEEP)) == NULL) { mdb_warn("Unable to allocate memory for " "ql_adapter_state\n"); return (DCMD_OK); } if ((strcasecmp(argv[1].a_un.a_str, "all")) == 0) { if (argc != 2) { mdb_free(qlstate, qlsize); return (DCMD_USAGE); } hbaptr = (uintptr_t)ql_hba.first; while (hbaptr != 0) { if (mdb_vread(qlstate, qlsize, hbaptr) == -1) { mdb_free(qlstate, qlsize); mdb_warn("failed to read ql_adapter_state " "at %p", hbaptr); return (DCMD_OK); } ql_elog_common(qlstate, elswitch); hbaptr = (uintptr_t)qlstate->hba.next; } } else { for (argcnt = 1; argcnt < argc; argcnt++) { instance = (uint32_t)mdb_strtoull( argv[argcnt].a_un.a_str); /* find the correct instance to change */ hbaptr = (uintptr_t)ql_hba.first; while (hbaptr != 0) { if (mdb_vread(qlstate, qlsize, hbaptr) == -1) { mdb_free(qlstate, qlsize); mdb_warn("failed to read " "ql_adapter_state at %p", hbaptr); return (DCMD_OK); } if (qlstate->instance == instance) { break; } hbaptr = (uintptr_t)qlstate->hba.next; } if (hbaptr == 0) { mdb_printf("instance %d is not loaded", instance); continue; } ql_elog_common(qlstate, elswitch); } } mdb_free(qlstate, qlsize); return (DCMD_OK); } /* * qlc_elog_common * mdb helper function which set/resets the extended logging bit * * Input: * qlstate = adapter state structure * elswitch = boolean which specifies to reset (0) or set (1) the * extended logging bit. * * Returns: * * Context: * User context. * */ static void ql_elog_common(ql_adapter_state_t *qlstate, boolean_t elswitch) { uintptr_t hbaptr = (uintptr_t)qlstate->hba.base_address; size_t qlsize = sizeof (ql_adapter_state_t); if (elswitch) { if ((qlstate->cfg_flags & CFG_ENABLE_EXTENDED_LOGGING) == 0) { qlstate->cfg_flags |= CFG_ENABLE_EXTENDED_LOGGING; if ((mdb_vwrite((const void *)qlstate, qlsize, hbaptr)) != (ssize_t)qlsize) { mdb_warn("instance %d - unable to update", qlstate->instance); } else { mdb_printf("instance %d extended logging is " "now on\n", qlstate->instance); } } else { mdb_printf("instance %d extended logging is " "already on\n", qlstate->instance); } } else { if ((qlstate->cfg_flags & CFG_ENABLE_EXTENDED_LOGGING) != 0) { qlstate->cfg_flags &= ~CFG_ENABLE_EXTENDED_LOGGING; if ((mdb_vwrite((const void *)qlstate, qlsize, hbaptr)) != (ssize_t)qlsize) { mdb_warn("instance %d - unable to update", qlstate->instance); } else { mdb_printf("instance %d extended logging is " "now off\n", qlstate->instance); } } else { mdb_printf("instance %d extended logging is " "already off\n", qlstate->instance); } } } /* * qlc_ocs_dcmd * mdb dcmd which prints out the outstanding command array using * caller supplied address (which sb the ha structure). * * Input: * addr = User supplied ha address. * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_USAGE, or DCMD_OK * * Context: * User context. * * */ static int /*ARGSUSED*/ qlc_osc_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ql_adapter_state_t *qlstate; uintptr_t qlosc, ptr1; uint32_t indx, found = 0; ql_srb_t *qlsrb; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if ((qlstate = (ql_adapter_state_t *) mdb_alloc(sizeof (ql_adapter_state_t), UM_SLEEP)) == NULL) { mdb_warn("Unable to allocate memory for ql_adapter_state\n"); return (DCMD_OK); } if (mdb_vread(qlstate, sizeof (ql_adapter_state_t), addr) == -1) { mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_warn("failed to read ql_adapter_state at %p", addr); return (DCMD_OK); } qlosc = (uintptr_t)qlstate->outstanding_cmds; mdb_printf("qlc instance: %d, base addr = %llx, osc base = %p\n", qlstate->instance, qlstate->hba.base_address, qlosc); if ((qlsrb = (ql_srb_t *)mdb_alloc(sizeof (ql_srb_t), UM_SLEEP)) == NULL) { mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_warn("failed to allocate space for srb_t\n"); return (DCMD_OK); } for (indx = 0; indx < qlstate->osc_max_cnt; indx++, qlosc += 8) { if (mdb_vread(&ptr1, 8, qlosc) == -1) { mdb_warn("failed to read ptr1, indx=%d", indx); break; } if (ptr1 == 0) { continue; } mdb_printf("osc ptr = %p, indx = %xh\n", ptr1, indx); if (mdb_vread(qlsrb, sizeof (ql_srb_t), ptr1) == -1) { mdb_warn("failed to read ql_srb_t at %p", ptr1); break; } (void) ql_doprint(ptr1, "struct ql_srb"); found++; } mdb_free(qlsrb, sizeof (ql_srb_t)); mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_printf("number of outstanding command srb's is: %d\n", found); return (DCMD_OK); } /* * qlc_wdog_dcmd * mdb dcmd which prints out the commands which are linked * on the watchdog linked list. Caller supplied address (which * sb the ha structure). * * Input: * addr = User supplied ha address. * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_USAGE, or DCMD_OK * * Context: * User context. * * */ static int /*ARGSUSED*/ qlc_wdog_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ql_adapter_state_t *qlstate; uint16_t index, count; ql_head_t *dev; ql_srb_t *srb; ql_tgt_t *tq; ql_lun_t *lq; ql_link_t *tqlink, *srblink, *lqlink; int nextlink; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("Address required\n", addr); return (DCMD_USAGE); } if ((qlstate = (ql_adapter_state_t *) mdb_alloc(sizeof (ql_adapter_state_t), UM_SLEEP)) == NULL) { mdb_warn("Unable to allocate memory for ql_adapter_state\n"); return (DCMD_OK); } if (mdb_vread(qlstate, sizeof (ql_adapter_state_t), addr) == -1) { mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_warn("failed to read ql_adapter_state at %p", addr); return (DCMD_OK); } /* * Read in the device array */ dev = (ql_head_t *) mdb_alloc(sizeof (ql_head_t) * DEVICE_HEAD_LIST_SIZE, UM_SLEEP); if (mdb_vread(dev, sizeof (ql_head_t) * DEVICE_HEAD_LIST_SIZE, (uintptr_t)qlstate->dev) == -1) { mdb_warn("failed to read ql_head_t (dev) at %p", qlstate->dev); mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_free(dev, sizeof (ql_head_t) * DEVICE_HEAD_LIST_SIZE); return (DCMD_OK); } tqlink = (ql_link_t *)mdb_alloc(sizeof (ql_link_t), UM_SLEEP); tq = (ql_tgt_t *)mdb_alloc(sizeof (ql_tgt_t), UM_SLEEP); lqlink = (ql_link_t *)mdb_alloc(sizeof (ql_link_t), UM_SLEEP); lq = (ql_lun_t *)mdb_alloc(sizeof (ql_lun_t), UM_SLEEP); srblink = (ql_link_t *)mdb_alloc(sizeof (ql_link_t), UM_SLEEP); srb = (ql_srb_t *)mdb_alloc(sizeof (ql_srb_t), UM_SLEEP); /* * Validate the devices watchdog queue */ for (index = 0; index < DEVICE_HEAD_LIST_SIZE; index++) { /* Skip empty ones */ if (dev[index].first == NULL) { continue; } mdb_printf("dev array index = %x\n", index); /* Loop through targets on device linked list */ /* get the first link */ nextlink = get_first_link(&dev[index], tqlink); /* * traverse the targets linked list at this device array index. */ while (nextlink == DCMD_OK) { /* Get the target */ if (mdb_vread(tq, sizeof (ql_tgt_t), (uintptr_t)(tqlink->base_address)) == -1) { mdb_warn("failed to read ql_tgt at %p", tqlink->base_address); break; } mdb_printf("tgt q base = %llx, ", tqlink->base_address); mdb_printf("flags: (%xh)", tq->flags); if (tq->flags) { ql_dump_flags((uint64_t)tq->flags, qltgt_flags); } mdb_printf("tgt: %02x%02x%02x%02x%02x%02x%02x%02x ", tq->node_name[0], tq->node_name[1], tq->node_name[2], tq->node_name[3], tq->node_name[4], tq->node_name[5], tq->node_name[6], tq->node_name[7]); /* * Loop through commands on this targets watchdog queue. */ /* Get the first link on the targets cmd wdg q. */ if (tq->wdg.first == NULL) { mdb_printf(" watchdog list empty "); break; } else { if (mdb_vread(srblink, sizeof (ql_link_t), (uintptr_t)tq->wdg.first) == -1) { mdb_warn("failed to read ql_link_t" " at %p", tq->wdg.first); break; } /* There is aleast one. */ count = 1; /* * Count the remaining items in the * cmd watchdog list. */ while (srblink->next != NULL) { /* Read in the next ql_link_t header */ if (mdb_vread(srblink, sizeof (ql_link_t), (uintptr_t)srblink->next) == -1) { mdb_warn("failed to read" " ql_link_t next at %p", srblink->next); break; } count = (uint16_t)(count + 1); } mdb_printf(" watchdog list: %d entries\n", count); /* get the first one again */ if (mdb_vread(srblink, sizeof (ql_link_t), (uintptr_t)tq->wdg.first) == -1) { mdb_warn("failed to read ql_link_t" " at %p", tq->wdg.first); break; } } /* * Traverse the targets cmd watchdog linked list * verifying srb's from the list are on a lun cmd list. */ while (nextlink == DCMD_OK) { int found = 0; /* get the srb */ if (mdb_vread(srb, sizeof (ql_srb_t), (uintptr_t)srblink->base_address) == -1) { mdb_warn("failed to read ql_srb_t" " at %p", srblink->base_address); break; } mdb_printf("ql_srb %llx ", srblink->base_address); /* * Get the lun q the srb is on */ if (mdb_vread(lq, sizeof (ql_lun_t), (uintptr_t)srb->lun_queue) == -1) { mdb_warn("failed to read ql_srb_t" " at %p", srb->lun_queue); break; } nextlink = get_first_link(&lq->cmd, lqlink); /* * traverse the lun cmd linked list looking * for the srb from the targets watchdog list */ while (nextlink == DCMD_OK) { if (srblink->base_address == lqlink->base_address) { mdb_printf("on lun %d cmd q\n", lq->lun_no); found = 1; break; } /* get next item on lun cmd list */ nextlink = get_next_link(lqlink); } if (!found) { mdb_printf("not found on lun cmd q\n"); } /* get next item in the watchdog list */ nextlink = get_next_link(srblink); } /* End targets command watchdog list */ /* get next item in this target list */ nextlink = get_next_link(tqlink); } /* End traverse the device targets linked list */ mdb_printf("\n"); } /* End device array */ mdb_free(tq, sizeof (ql_tgt_t)); mdb_free(lq, sizeof (ql_lun_t)); mdb_free(srb, sizeof (ql_srb_t)); mdb_free(tqlink, sizeof (ql_link_t)); mdb_free(srblink, sizeof (ql_link_t)); mdb_free(lqlink, sizeof (ql_link_t)); mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_free(dev, sizeof (ql_head_t)*DEVICE_HEAD_LIST_SIZE); return (DCMD_OK); } /* * get_first_link * Gets the first ql_link_t header on ql_head. * * Input: * ql_head = pointer to a ql_head_t structure. * ql_link = pointer to a ql_link_t structure. * * Returns: * DCMD_ABORT, or DCMD_OK * * Context: * User context. * */ static int get_first_link(ql_head_t *qlhead, ql_link_t *qllink) { int rval = DCMD_ABORT; if (qlhead != NULL) { if (qlhead->first != NULL) { /* Read in the first ql_link_t header */ if (mdb_vread(qllink, sizeof (ql_link_t), (uintptr_t)(qlhead->first)) == -1) { mdb_warn("failed to read ql_link_t " "next at %p", qlhead->first); } else { rval = DCMD_OK; } } } return (rval); } /* * get_next_link * Gets the next ql_link_t structure. * * Input: * ql_link = pointer to a ql_link_t structure. * * Returns: * DCMD_ABORT, or DCMD_OK * * Context: * User context. * */ static int get_next_link(ql_link_t *qllink) { int rval = DCMD_ABORT; if (qllink != NULL) { if (qllink->next != NULL) { /* Read in the next ql_link_t header */ if (mdb_vread(qllink, sizeof (ql_link_t), (uintptr_t)(qllink->next)) == -1) { mdb_warn("failed to read ql_link_t " "next at %p", qllink->next); } else { rval = DCMD_OK; } } } return (rval); } /* * qlcstate_dcmd * mdb dcmd which prints out the ql_state info using * caller supplied address. * * Input: * addr = User supplied address. * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_USAGE, or DCMD_OK * * Context: * User context. * */ static int qlcstate_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ql_adapter_state_t *qlstate; int verbose = 0; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) { return (DCMD_USAGE); } if ((qlstate = (ql_adapter_state_t *) mdb_alloc(sizeof (ql_adapter_state_t), UM_SLEEP)) == NULL) { mdb_warn("failed to allocate memory for ql_adapter_state\n"); return (DCMD_OK); } if (mdb_vread(qlstate, sizeof (ql_adapter_state_t), addr) == -1) { mdb_free(qlstate, sizeof (ql_adapter_state_t)); mdb_warn("failed to read ql_adapter_state at %p", addr); return (DCMD_OK); } mdb_printf("qlc instance: %d, base addr = %llx\n", qlstate->instance, addr); mdb_printf("\nadapter state flags:\n"); ql_dump_flags((uint64_t)qlstate->flags, adapter_state_flags); mdb_printf("\nadapter cfg flags:\n"); ql_dump_flags((uint64_t)qlstate->cfg_flags, adapter_config_flags); mdb_printf("\ntask daemon state flags:\n"); ql_dump_flags((uint64_t)qlstate->task_daemon_flags, task_daemon_flags); if (verbose) { (void) ql_doprint(addr, "struct ql_adapter_state"); } mdb_free(qlstate, sizeof (ql_adapter_state_t)); return (DCMD_OK); } /* * qlcstates_walk_init * mdb walker init which prints out all qlc states info. * * Input: * wsp - Pointer to walker state struct * * Returns: * WALK_ERR, or WALK_NEXT * * Context: * User context. * */ static int qlstates_walk_init(mdb_walk_state_t *wsp) { ql_head_t ql_hba; if (wsp->walk_addr == 0) { if ((mdb_readvar(&ql_hba, "ql_hba") == -1) || (&ql_hba == NULL)) { mdb_warn("failed to read ql_hba structure"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)ql_hba.first; wsp->walk_data = mdb_alloc(sizeof (ql_adapter_state_t), UM_SLEEP); return (WALK_NEXT); } else { return (ql_doprint(wsp->walk_addr, "struct ql_adapter_state")); } } /* * qlstates_walk_step * mdb walker step which prints out all qlc states info. * * Input: * wsp - Pointer to walker state struct * * Returns: * WALK_DONE, or WALK_NEXT * * Context: * User context. * */ static int qlstates_walk_step(mdb_walk_state_t *wsp) { ql_adapter_state_t *qlstate; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (ql_adapter_state_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ql_adapter_state at %p", wsp->walk_addr); return (WALK_DONE); } qlstate = (ql_adapter_state_t *)(wsp->walk_data); mdb_printf("qlc instance: %d, base addr = %llx\n", qlstate->instance, wsp->walk_addr); mdb_printf("\nadapter state flags:\n"); ql_dump_flags((uint64_t)qlstate->flags, adapter_state_flags); mdb_printf("\nadapter cfg flags:\n"); ql_dump_flags((uint64_t)qlstate->cfg_flags, adapter_config_flags); mdb_printf("\ntask daemon state flags:\n"); ql_dump_flags((uint64_t)qlstate->task_daemon_flags, task_daemon_flags); mdb_printf("\nadapter state:\n"); (void) ql_doprint(wsp->walk_addr, "struct ql_adapter_state"); mdb_printf("\n"); wsp->walk_addr = (uintptr_t) (((ql_adapter_state_t *)wsp->walk_data)->hba.next); return (WALK_NEXT); } /* * qlstates_walk_fini * mdb walker fini which wraps up the walker * * Input: * wsp - Pointer to walker state struct * * Returns: * * Context: * User context. * */ static void qlstates_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (ql_adapter_state_t)); } /* * qlsrb_walk_init * mdb walker init which prints out linked srb's * * Input: * wsp - Pointer to walker ql_srb struct * * Returns: * WALK_ERR, or WALK_NEXT * * Context: * User context. * */ static int qlsrb_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("failed to read ql_srb addr at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (ql_srb_t), UM_SLEEP); return (WALK_NEXT); } /* * qlcsrb_walk_step * mdb walker step which prints out linked ql_srb structures * * Input: * wsp - Pointer to walker srb struct * * Returns: * WALK_DONE, or WALK_NEXT * * Context: * User context. * */ static int qlsrb_walk_step(mdb_walk_state_t *wsp) { ql_srb_t *qlsrb; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (ql_srb_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ql_srb at %p", wsp->walk_addr); return (WALK_DONE); } qlsrb = (ql_srb_t *)(wsp->walk_data); mdb_printf("ql_srb base addr = %llx\n", wsp->walk_addr); mdb_printf("\nql_srb flags:\n"); ql_dump_flags((uint64_t)qlsrb->flags, qlsrb_flags); mdb_printf("\nql_srb:\n"); (void) ql_doprint(wsp->walk_addr, "struct ql_srb"); mdb_printf("\n"); wsp->walk_addr = (uintptr_t) (((ql_srb_t *)wsp->walk_data)->cmd.next); return (WALK_NEXT); } /* * qlsrb_walk_fini * mdb walker fini which wraps up the walker * * Input: * wsp - Pointer to walker state struct * * Returns: * * Context: * User context. * */ static void qlsrb_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (ql_srb_t)); } /* * qllunq_dcmd * mdb walker which prints out lun q's * * Input: * wsp - Pointer to walker ql_lun struct * * Returns: * WALK_ERR, or WALK_NEXT * * Context: * User context. * */ static int qllunq_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("failed to read ql_lun addr at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (ql_lun_t), UM_SLEEP); return (WALK_NEXT); } /* * qlclunq_walk_step * mdb walker step which prints out linked ql_lun structures * * Input: * wsp - Pointer to walker srb struct * * Returns: * WALK_DONE, or WALK_NEXT * * Context: * User context. * */ static int qllunq_walk_step(mdb_walk_state_t *wsp) { ql_lun_t *qllun; ql_link_t ql_link; ql_link_t *qllink; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (ql_lun_t), wsp->walk_addr) == -1) { mdb_warn("failed to read ql_lun at %p", wsp->walk_addr); return (WALK_DONE); } qllun = (ql_lun_t *)(wsp->walk_data); mdb_printf("ql_lun base addr = %llx\n", wsp->walk_addr); mdb_printf("\nql_lun flags:\n"); ql_dump_flags((uint64_t)qllun->flags, qllun_flags); mdb_printf("\nql_lun:\n"); (void) ql_doprint(wsp->walk_addr, "struct ql_lun"); mdb_printf("\n"); qllink = (ql_link_t *) (((ql_lun_t *)wsp->walk_data)->link.next); if (qllink == NULL) { return (WALK_DONE); } else { /* * Read in the next link_t header */ if (mdb_vread(&ql_link, sizeof (ql_link_t), (uintptr_t)qllink) == -1) { mdb_warn("failed to read ql_link_t " "next at %p", qllink->next); return (WALK_DONE); } qllink = &ql_link; } wsp->walk_addr = (uintptr_t)qllink->base_address; return (WALK_NEXT); } /* * qllunq_walk_fini * mdb walker fini which wraps up the walker * * Input: * wsp - Pointer to walker state struct * * Returns: * * Context: * User context. * */ static void qllunq_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (ql_lun_t)); } /* * qltgtq_dcmd * mdb dcmd which prints out an hs's tq struct info. * * Input: * addr = User supplied address. (NB: nust be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_USAGE, or DCMD_OK * * Context: * User context. * */ /*ARGSUSED*/ static int qltgtq_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ql_adapter_state_t *ha; ql_link_t *link; ql_tgt_t *tq; uint32_t index; ql_head_t *dev; if ((!(flags & DCMD_ADDRSPEC)) || addr == 0) { mdb_warn("ql_hba structure addr is required"); return (DCMD_USAGE); } /* * Get the adapter state struct which was passed */ ha = (ql_adapter_state_t *)mdb_alloc(sizeof (ql_adapter_state_t), UM_SLEEP); if (mdb_vread(ha, sizeof (ql_adapter_state_t), addr) == -1) { mdb_warn("failed to read ql_adapter_state at %p", addr); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } if (ha->dev == NULL) { mdb_warn("dev ptr is NULL for ha: %p", addr); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } /* * Read in the device array */ dev = (ql_head_t *) mdb_alloc(sizeof (ql_head_t) * DEVICE_HEAD_LIST_SIZE, UM_SLEEP); if (mdb_vread(dev, sizeof (ql_head_t) * DEVICE_HEAD_LIST_SIZE, (uintptr_t)ha->dev) == -1) { mdb_warn("failed to read ql_head_t (dev) at %p", ha->dev); mdb_free(ha, sizeof (ql_adapter_state_t)); mdb_free(dev, sizeof (ql_head_t) * DEVICE_HEAD_LIST_SIZE); } tq = (ql_tgt_t *)mdb_alloc(sizeof (ql_tgt_t), UM_SLEEP); link = (ql_link_t *)mdb_alloc(sizeof (ql_link_t), UM_SLEEP); for (index = 0; index < DEVICE_HEAD_LIST_SIZE; index++) { if (dev[index].first == NULL) { continue; } if (mdb_vread(link, sizeof (ql_link_t), (uintptr_t)dev[index].first) == -1) { mdb_warn("failed to read ql_link_t at %p", dev[index].first); break; } while (link != NULL) { if (mdb_vread(tq, sizeof (ql_tgt_t), (uintptr_t)(link->base_address)) == -1) { mdb_warn("failed to read ql_tgt at %p", link->base_address); break; } mdb_printf("tgt queue base addr = %llx\n", link->base_address); mdb_printf("\ntgt queue flags: (%xh)\n", tq->flags); ql_dump_flags((uint64_t)tq->flags, qltgt_flags); mdb_printf("\ntgt queue:\n"); (void) ql_doprint((uintptr_t)link->base_address, "struct ql_target"); mdb_printf("\n"); if (get_next_link(link) != DCMD_OK) { break; } } } mdb_free(ha, sizeof (ql_adapter_state_t)); mdb_free(tq, sizeof (ql_tgt_t)); mdb_free(link, sizeof (ql_link_t)); mdb_free(dev, sizeof (ql_head_t)*DEVICE_HEAD_LIST_SIZE); return (DCMD_OK); } /* * ql_triggerdump_dcmd * Triggers the driver to take a firmware dump * * Input: * addr = User supplied address (optional) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array (instance #, optional). * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ #if 0 /*ARGSUSED*/ static int qlc_triggerdump_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ql_adapter_state_t *qlstate; uintptr_t hbaptr = NULL; ql_head_t ql_hba; uint32_t qlsize = sizeof (ql_adapter_state_t); int mdbs; if ((mdbs = mdb_get_state()) == MDB_STATE_DEAD) { mdb_warn("Cannot change core file data (state=%xh)\n", mdbs); return (DCMD_OK); } if ((qlstate = (ql_adapter_state_t *)mdb_alloc(qlsize, UM_SLEEP)) == NULL) { mdb_warn("Unable to allocate memory for ql_adapter_state\n"); return (DCMD_OK); } if (addr == NULL) { uint32_t instance; if (argc == 0) { mdb_warn("must specify either the ha addr or " "the instance number\n"); mdb_free(qlstate, qlsize); return (DCMD_OK); } /* * find the specified instance in the ha list */ instance = (uint32_t)mdb_strtoull(argv[1].a_un.a_str); if (mdb_readvar(&ql_hba, "ql_hba") == -1) { mdb_warn("failed to read ql_hba structure"); mdb_free(qlstate, qlsize); return (DCMD_ERR); } if (&ql_hba == NULL) { mdb_warn("failed to read ql_hba structure - " "is qlc loaded?"); mdb_free(qlstate, qlsize); return (DCMD_ERR); } hbaptr = (uintptr_t)ql_hba.first; while (hbaptr != NULL) { if (mdb_vread(qlstate, qlsize, hbaptr) == -1) { mdb_free(qlstate, qlsize); mdb_warn("failed to read " "ql_adapter_state at %p", hbaptr); return (DCMD_OK); } if (qlstate->instance == instance) { break; } hbaptr = (uintptr_t)qlstate->hba.next; } } else { /* * verify the addr specified */ if (mdb_readvar(&ql_hba, "ql_hba") == -1) { mdb_warn("failed to read ql_hba structure"); mdb_free(qlstate, qlsize); return (DCMD_ERR); } if (&ql_hba == NULL) { mdb_warn("failed to read ql_hba structure - " "is qlc loaded?"); mdb_free(qlstate, qlsize); return (DCMD_ERR); } hbaptr = (uintptr_t)ql_hba.first; while (hbaptr != NULL) { if (mdb_vread(qlstate, qlsize, hbaptr) == -1) { mdb_free(qlstate, qlsize); mdb_warn("failed to read " "ql_adapter_state at %p", hbaptr); return (DCMD_OK); } if (hbaptr == addr) { break; } hbaptr = (uintptr_t)qlstate->hba.next; } } if (hbaptr == NULL) { mdb_free(qlstate, qlsize); if (argc == 0) { mdb_warn("addr specified is not in the hba list\n"); } else { mdb_warn("instance specified does not exist\n"); } return (DCMD_OK); } if (((qlstate->ql_dump_state & QL_DUMP_VALID) != 0) || (qlstate->ql_dump_ptr != NULL)) { mdb_warn("instance %d already has a valid dump\n", qlstate->instance); mdb_free(qlstate, qlsize); return (DCMD_OK); } } #endif /* * ql_getdump_dcmd * prints out the firmware dump buffer * * Input: * addr = User supplied address. (NB: must be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ static int qlc_getdump_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ql_adapter_state_t *ha; ql_head_t ql_hba; uintptr_t hbaptr = 0; int verbose = 0; if ((!(flags & DCMD_ADDRSPEC)) || addr == 0) { mdb_warn("ql_adapter_state structure addr is required"); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) { return (DCMD_USAGE); } /* * Get the adapter state struct which was passed */ if ((ha = (ql_adapter_state_t *)mdb_alloc(sizeof (ql_adapter_state_t), UM_SLEEP)) == NULL) { mdb_warn("failed to allocate memory for ql_adapter_state\n"); return (DCMD_OK); } /* * show user which instances have valid f/w dumps available if * user has specified verbose option */ if (mdb_readvar(&ql_hba, "ql_hba") == -1) { mdb_warn("failed to read ql_hba structure"); } else if (ql_hba.first == NULL) { mdb_warn("failed to read ql_hba structure -- is qlc loaded?"); } else if (verbose) { hbaptr = (uintptr_t)ql_hba.first; while (hbaptr != 0) { if (mdb_vread(ha, sizeof (ql_adapter_state_t), hbaptr) == -1) { mdb_free(ha, sizeof (ql_adapter_state_t)); mdb_warn("failed read ql_adapter_state at %p", hbaptr); return (DCMD_OK); } mdb_printf("instance %d:\n", ha->instance); (void) mdb_inc_indent((ulong_t)4); if (ha->ql_dump_state == 0) { mdb_printf("no dump flags\n"); } else { ql_dump_flags((uint64_t)ha->ql_dump_state, qldump_flags); } if (ha->ql_dump_ptr == NULL) { mdb_printf("no dump address\n"); } else { mdb_printf("dump address is: %p\n", ha->ql_dump_ptr); } (void) mdb_dec_indent((ulong_t)4); hbaptr = (uintptr_t)ha->hba.next; } mdb_printf("\n"); } if (mdb_vread(ha, sizeof (ql_adapter_state_t), addr) == -1) { mdb_warn("failed to read ql_adapter_state at %p", addr); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } /* * If its not a valid dump or there's not a f/w dump binary (???) * then bail out */ if (((ha->ql_dump_state & QL_DUMP_VALID) == 0) || (ha->ql_dump_ptr == NULL)) { mdb_warn("dump does not exist for instance %d (%x, %p)\n", ha->instance, ha->ql_dump_state, ha->ql_dump_ptr); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } if (CFG_IST(ha, CFG_CTRL_24XX)) { (void) ql_24xx_dump_dcmd(ha, flags, argc, argv); } else if (CFG_IST(ha, CFG_CTRL_25XX)) { (void) ql_25xx_dump_dcmd(ha, flags, argc, argv); } else if (CFG_IST(ha, CFG_CTRL_81XX)) { (void) ql_81xx_dump_dcmd(ha, flags, argc, argv); } else if (CFG_IST(ha, CFG_CTRL_82XX | CFG_CTRL_27XX)) { (void) ql_8021_dump_dcmd(ha, flags, argc, argv); } else if (CFG_IST(ha, CFG_CTRL_83XX)) { (void) ql_8300_dump_dcmd(ha, flags, argc, argv); } else { (void) ql_23xx_dump_dcmd(ha, flags, argc, argv); } mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } /* * ql_8021_dump_dcmd * prints out a firmware dump buffer * * Input: * addr = User supplied address. (NB: nust be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ /*ARGSUSED*/ static int ql_8021_dump_dcmd(ql_adapter_state_t *ha, uint_t flags, int argc, const mdb_arg_t *argv) { uint8_t *fw, *bp; uint32_t cnt = 0; bp = fw = (uint8_t *)mdb_alloc(ha->ql_dump_size, UM_SLEEP); if (mdb_vread(fw, ha->ql_dump_size, (uintptr_t)ha->ql_dump_ptr) == -1) { mdb_warn("failed to read ql_dump_ptr (no f/w dump active?)"); mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } while (cnt < ha->ql_dump_size) { mdb_printf("%02x ", *bp++); if (++cnt % 16 == 0) { mdb_printf("\n"); } } if (cnt % 16 != 0) { mdb_printf("\n"); } mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } /* * ql_83xx_dump_dcmd * prints out a firmware dump buffer * * Input: * addr = User supplied address. (NB: must be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ /*ARGSUSED*/ static int ql_8300_dump_dcmd(ql_adapter_state_t *ha, uint_t flags, int argc, const mdb_arg_t *argv) { ql_83xx_fw_dump_t *fw; ql_response_q_t **rsp_queues, *rsp_q; uint32_t cnt, cnt1, *dp, *dp2; fw = mdb_alloc(ha->ql_dump_size, UM_SLEEP); rsp_queues = mdb_alloc(ha->rsp_queues_cnt * sizeof (ql_response_q_t *), UM_SLEEP); rsp_q = mdb_alloc(sizeof (ql_response_q_t), UM_SLEEP); if (mdb_vread(fw, ha->ql_dump_size, (uintptr_t)ha->ql_dump_ptr) == -1 || mdb_vread(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *), (uintptr_t)ha->rsp_queues) == -1) { mdb_warn("failed to read fw_dump_buffer (no f/w dump active?)"); mdb_free(rsp_q, sizeof (ql_response_q_t)); mdb_free(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *)); mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } mdb_printf("\nISP FW Version %d.%02d.%02d Attributes %X\n", ha->fw_major_version, ha->fw_minor_version, ha->fw_subminor_version, ha->fw_attributes); mdb_printf("\nHCCR Register\n%08x\n", fw->hccr); mdb_printf("\nR2H Status Register\n%08x\n", fw->r2h_status); mdb_printf("\nAER Uncorrectable Error Status Register\n%08x\n", fw->aer_ues); mdb_printf("\nHostRisc Registers"); for (cnt = 0; cnt < sizeof (fw->hostrisc_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->hostrisc_reg[cnt]); } mdb_printf("\n\nPCIe Registers"); for (cnt = 0; cnt < sizeof (fw->pcie_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->pcie_reg[cnt]); } dp = fw->req_rsp_ext_mem; for (cnt = 0; cnt < ha->rsp_queues_cnt; cnt++) { mdb_printf("\n\nQueue Pointers #%d:\n", cnt); for (cnt1 = 0; cnt1 < 4; cnt1++) { mdb_printf("%08x ", *dp++); } } mdb_printf("\n\nHost Interface Registers"); for (cnt = 0; cnt < sizeof (fw->host_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->host_reg[cnt]); } mdb_printf("\n\nShadow Registers"); for (cnt = 0; cnt < sizeof (fw->shadow_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->shadow_reg[cnt]); } mdb_printf("\n\nRISC IO Register\n%08x", fw->risc_io); mdb_printf("\n\nMailbox Registers"); for (cnt = 0; cnt < sizeof (fw->mailbox_reg) / 2; cnt++) { if (cnt % 16 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->mailbox_reg[cnt]); } mdb_printf("\n\nXSEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_gp_reg[cnt]); } mdb_printf("\n\nXSEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_0_reg[cnt]); } mdb_printf("\n\nXSEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_1_reg[cnt]); } mdb_printf("\n\nXSEQ-2 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_2_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_2_reg[cnt]); } mdb_printf("\n\nRSEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_gp_reg[cnt]); } mdb_printf("\n\nRSEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_0_reg[cnt]); } mdb_printf("\n\nRSEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_1_reg[cnt]); } mdb_printf("\n\nRSEQ-2 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_2_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_2_reg[cnt]); } mdb_printf("\n\nRSEQ-3 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_3_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_3_reg[cnt]); } mdb_printf("\n\nASEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_gp_reg[cnt]); } mdb_printf("\n\nASEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_0_reg[cnt]); } mdb_printf("\n\nASEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_1_reg[cnt]); } mdb_printf("\n\nASEQ-2 Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_2_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_2_reg[cnt]); } mdb_printf("\n\nASEQ-3 Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_3_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_3_reg[cnt]); } mdb_printf("\n\nCommand DMA Registers"); for (cnt = 0; cnt < sizeof (fw->cmd_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->cmd_dma_reg[cnt]); } mdb_printf("\n\nRequest0 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->req0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->req0_dma_reg[cnt]); } mdb_printf("\n\nResponse0 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->resp0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->resp0_dma_reg[cnt]); } mdb_printf("\n\nRequest1 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->req1_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->req1_dma_reg[cnt]); } mdb_printf("\n\nXMT0 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt0_dma_reg[cnt]); } mdb_printf("\n\nXMT1 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt1_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt1_dma_reg[cnt]); } mdb_printf("\n\nXMT2 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt2_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt2_dma_reg[cnt]); } mdb_printf("\n\nXMT3 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt3_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt3_dma_reg[cnt]); } mdb_printf("\n\nXMT4 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt4_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt4_dma_reg[cnt]); } mdb_printf("\n\nXMT Data DMA Common Registers"); for (cnt = 0; cnt < sizeof (fw->xmt_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt_data_dma_reg[cnt]); } mdb_printf("\n\nRCV Thread 0 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->rcvt0_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rcvt0_data_dma_reg[cnt]); } mdb_printf("\n\nRCV Thread 1 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->rcvt1_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rcvt1_data_dma_reg[cnt]); } mdb_printf("\n\nRISC GP Registers"); for (cnt = 0; cnt < sizeof (fw->risc_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->risc_gp_reg[cnt]); } mdb_printf("\n\nLMC Registers"); for (cnt = 0; cnt < sizeof (fw->lmc_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->lmc_reg[cnt]); } mdb_printf("\n\nFPM Hardware Registers"); for (cnt = 0; cnt < sizeof (fw->fpm_hdw_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->fpm_hdw_reg[cnt]); } mdb_printf("\n\nRQ0 Array Registers"); for (cnt = 0; cnt < sizeof (fw->rq0_array_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rq0_array_reg[cnt]); } mdb_printf("\n\nRQ1 Array Registers"); for (cnt = 0; cnt < sizeof (fw->rq1_array_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rq1_array_reg[cnt]); } mdb_printf("\n\nRP0 Array Registers"); for (cnt = 0; cnt < sizeof (fw->rp0_array_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rp0_array_reg[cnt]); } mdb_printf("\n\nRP1 Array Registers"); for (cnt = 0; cnt < sizeof (fw->rp1_array_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rp1_array_reg[cnt]); } mdb_printf("\n\nAT0 Array Registers"); for (cnt = 0; cnt < sizeof (fw->ato_array_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->ato_array_reg[cnt]); } mdb_printf("\n\nQueue Control Registers"); for (cnt = 0; cnt < sizeof (fw->queue_control_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->queue_control_reg[cnt]); } mdb_printf("\n\nFB Hardware Registers"); for (cnt = 0; cnt < sizeof (fw->fb_hdw_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->fb_hdw_reg[cnt]); } mdb_printf("\n\nCode RAM"); for (cnt = 0; cnt < sizeof (fw->code_ram) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt + 0x20000); } mdb_printf("%08x ", fw->code_ram[cnt]); } mdb_printf("\n\nExternal Memory"); dp = (uint32_t *)(void *)((caddr_t)fw->req_rsp_ext_mem + fw->req_q_size[0] + fw->req_q_size[1] + fw->rsp_q_size + (ha->rsp_queues_cnt * 16)); for (cnt = 0; cnt < ha->fw_ext_memory_size / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt + 0x100000); } mdb_printf("%08x ", *dp++); } mdb_printf("\n\n[<==END] ISP Debug Dump"); dp = fw->req_rsp_ext_mem + (ha->rsp_queues_cnt * 4); for (cnt = 0; cnt < 2 && fw->req_q_size[cnt]; cnt++) { dp2 = dp; for (cnt1 = 0; cnt1 < fw->req_q_size[cnt] / 4; cnt1++) { if (*dp2++) { break; } } if (cnt1 == fw->req_q_size[cnt] / 4) { dp = dp2; continue; } mdb_printf("\n\nRequest Queue\nQueue 0%d:", cnt); for (cnt1 = 0; cnt1 < fw->req_q_size[cnt] / 4; cnt1++) { if (cnt1 % 8 == 0) { mdb_printf("\n%08x: ", cnt1); } mdb_printf("%08x ", *dp++); } } for (cnt = 0; cnt < ha->rsp_queues_cnt; cnt++) { if (mdb_vread(rsp_q, sizeof (ql_response_q_t), (uintptr_t)rsp_queues[cnt]) == -1) { mdb_warn("failed to read ha->rsp_queues[%d]", cnt); break; } dp2 = dp; for (cnt1 = 0; cnt1 < rsp_q->rsp_ring.size / 4; cnt1++) { if (*dp2++) { break; } } if (cnt1 == rsp_q->rsp_ring.size / 4) { dp = dp2; continue; } mdb_printf("\n\nResponse Queue\nQueue 0%d:", cnt); for (cnt1 = 0; cnt1 < rsp_q->rsp_ring.size / 4; cnt1++) { if (cnt1 % 8 == 0) { mdb_printf("\n%08x: ", cnt1); } mdb_printf("%08x ", *dp++); } } if (ha->fwexttracebuf.dma_handle != NULL) { uint32_t cnt_b; uint32_t *w32 = ha->fwexttracebuf.bp; mdb_printf("\n\nExtended Trace Buffer Memory"); /* show data address as a byte address, data as long words */ for (cnt = 0; cnt < FWEXTSIZE / 4; cnt++) { cnt_b = cnt * 4; if (cnt_b % 32 == 0) { mdb_printf("\n%08x: ", w32 + cnt_b); } mdb_printf("%08x ", fw->ext_trace_buf[cnt]); } } if (ha->fwfcetracebuf.dma_handle != NULL) { uint32_t cnt_b; uint32_t *w32 = ha->fwfcetracebuf.bp; mdb_printf("\n\nFC Event Trace Buffer Memory"); /* show data address as a byte address, data as long words */ for (cnt = 0; cnt < FWFCESIZE / 4; cnt++) { cnt_b = cnt * 4; if (cnt_b % 32 == 0) { mdb_printf("\n%08x: ", w32 + cnt_b); } mdb_printf("%08x ", fw->fce_trace_buf[cnt]); } } mdb_free(rsp_q, sizeof (ql_response_q_t)); mdb_free(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *)); mdb_free(fw, ha->ql_dump_size); mdb_printf("\n\nreturn exit\n"); return (DCMD_OK); } /* * ql_23xx_dump_dcmd * prints out a firmware dump buffer * * Input: * addr = User supplied address. (NB: nust be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ /*ARGSUSED*/ static int ql_23xx_dump_dcmd(ql_adapter_state_t *ha, uint_t flags, int argc, const mdb_arg_t *argv) { ql_fw_dump_t *fw; uint32_t cnt = 0; int mbox_cnt; fw = (ql_fw_dump_t *)mdb_alloc(ha->ql_dump_size, UM_SLEEP); if (mdb_vread(fw, ha->ql_dump_size, (uintptr_t)ha->ql_dump_ptr) == -1) { mdb_warn("failed to read ql_dump_ptr (no f/w dump active?)"); mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } if (ha->cfg_flags & CFG_CTRL_23XX) { mdb_printf("\nISP 2300IP "); } else if (ha->cfg_flags & CFG_CTRL_63XX) { mdb_printf("\nISP 2322/6322FLX "); } else { mdb_printf("\nISP 2200IP "); } mdb_printf("Firmware Version %d.%d.%d\n", ha->fw_major_version, ha->fw_minor_version, ha->fw_subminor_version); mdb_printf("\nPBIU Registers:"); for (cnt = 0; cnt < sizeof (fw->pbiu_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->pbiu_reg[cnt]); } if (ha->cfg_flags & CFG_CTRL_2363) { mdb_printf("\n\nReqQ-RspQ-Risc2Host Status registers:"); for (cnt = 0; cnt < sizeof (fw->risc_host_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_host_reg[cnt]); } } mdb_printf("\n\nMailbox Registers:"); mbox_cnt = ha->cfg_flags & CFG_CTRL_2363 ? 16 : 8; for (cnt = 0; cnt < mbox_cnt; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->mailbox_reg[cnt]); } if (ha->cfg_flags & CFG_CTRL_2363) { mdb_printf("\n\nAuto Request Response DMA Registers:"); for (cnt = 0; cnt < sizeof (fw->resp_dma_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->resp_dma_reg[cnt]); } } mdb_printf("\n\nDMA Registers:"); for (cnt = 0; cnt < sizeof (fw->dma_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->dma_reg[cnt]); } mdb_printf("\n\nRISC Hardware Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_hdw_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_hdw_reg[cnt]); } mdb_printf("\n\nRISC GP0 Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_gp0_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_gp0_reg[cnt]); } mdb_printf("\n\nRISC GP1 Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_gp1_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_gp1_reg[cnt]); } mdb_printf("\n\nRISC GP2 Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_gp2_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_gp2_reg[cnt]); } mdb_printf("\n\nRISC GP3 Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_gp3_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_gp3_reg[cnt]); } mdb_printf("\n\nRISC GP4 Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_gp4_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_gp4_reg[cnt]); } mdb_printf("\n\nRISC GP5 Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_gp5_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_gp5_reg[cnt]); } mdb_printf("\n\nRISC GP6 Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_gp6_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_gp6_reg[cnt]); } mdb_printf("\n\nRISC GP7 Registers:"); for (cnt = 0; cnt < sizeof (fw->risc_gp7_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->risc_gp7_reg[cnt]); } mdb_printf("\n\nFrame Buffer Hardware Registers:"); for (cnt = 0; cnt < sizeof (fw->frame_buf_hdw_reg) / 2; cnt++) { if ((cnt == 16) && ((ha->cfg_flags & CFG_CTRL_2363) == 0)) { break; } if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->frame_buf_hdw_reg[cnt]); } mdb_printf("\n\nFPM B0 Registers:"); for (cnt = 0; cnt < sizeof (fw->fpm_b0_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->fpm_b0_reg[cnt]); } mdb_printf("\n\nFPM B1 Registers:"); for (cnt = 0; cnt < sizeof (fw->fpm_b1_reg) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->fpm_b1_reg[cnt]); } if (ha->cfg_flags & CFG_CTRL_2363) { mdb_printf("\n\nCode RAM Dump:"); for (cnt = 0; cnt < sizeof (fw->risc_ram) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%05x: ", cnt + 0x0800); } mdb_printf("%04x ", fw->risc_ram[cnt]); } mdb_printf("\n\nStack RAM Dump:"); for (cnt = 0; cnt < sizeof (fw->stack_ram) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%05x: ", cnt + 0x010000); } mdb_printf("%04x ", fw->stack_ram[cnt]); } mdb_printf("\n\nData RAM Dump:"); for (cnt = 0; cnt < sizeof (fw->data_ram) / 2; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%05x: ", cnt + 0x010800); } mdb_printf("%04x ", fw->data_ram[cnt]); } mdb_printf("\n\n[<==END] ISP Debug Dump.\n"); mdb_printf("\n\nRequest Queue"); for (cnt = 0; cnt < REQUEST_QUEUE_SIZE / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt); } mdb_printf("%08x ", fw->req_q[cnt]); } mdb_printf("\n\nResponse Queue"); for (cnt = 0; cnt < RESPONSE_QUEUE_SIZE / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt); } mdb_printf("%08x ", fw->rsp_q[cnt]); } mdb_printf("\n"); } else { mdb_printf("\n\nRISC SRAM:"); for (cnt = 0; cnt < 0xf000; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%04x: ", cnt + 0x1000); } mdb_printf("%04x ", fw->risc_ram[cnt]); } } mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } /* * ql_24xx_dump_dcmd * prints out a firmware dump buffer * * Input: * addr = User supplied address. (NB: nust be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ /*ARGSUSED*/ static int ql_24xx_dump_dcmd(ql_adapter_state_t *ha, uint_t flags, int argc, const mdb_arg_t *argv) { ql_24xx_fw_dump_t *fw; uint32_t cnt = 0; fw = (ql_24xx_fw_dump_t *)mdb_alloc(ha->ql_dump_size, UM_SLEEP); if (mdb_vread(fw, ha->ql_dump_size, (uintptr_t)ha->ql_dump_ptr) == -1) { mdb_warn("failed to read ql_dump_ptr (no f/w dump active?)"); mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } mdb_printf("ISP FW Version %d.%02d.%02d Attributes %X\n", ha->fw_major_version, ha->fw_minor_version, ha->fw_subminor_version, ha->fw_attributes); mdb_printf("\nHCCR Register\n%08x\n", fw->hccr); mdb_printf("\nHost Interface Registers"); for (cnt = 0; cnt < sizeof (fw->host_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->host_reg[cnt]); } mdb_printf("\n\nMailbox Registers"); for (cnt = 0; cnt < sizeof (fw->mailbox_reg) / 2; cnt++) { if (cnt % 16 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->mailbox_reg[cnt]); } mdb_printf("\n\nXSEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_gp_reg[cnt]); } mdb_printf("\n\nXSEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_0_reg[cnt]); } mdb_printf("\n\nXSEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_1_reg[cnt]); } mdb_printf("\n\nRSEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_gp_reg[cnt]); } mdb_printf("\n\nRSEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_0_reg[cnt]); } mdb_printf("\n\nRSEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_1_reg[cnt]); } mdb_printf("\n\nRSEQ-2 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_2_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_2_reg[cnt]); } mdb_printf("\n\nCommand DMA Registers"); for (cnt = 0; cnt < sizeof (fw->cmd_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->cmd_dma_reg[cnt]); } mdb_printf("\n\nRequest0 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->req0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->req0_dma_reg[cnt]); } mdb_printf("\n\nResponse0 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->resp0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->resp0_dma_reg[cnt]); } mdb_printf("\n\nRequest1 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->req1_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->req1_dma_reg[cnt]); } mdb_printf("\n\nXMT0 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt0_dma_reg[cnt]); } mdb_printf("\n\nXMT1 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt1_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt1_dma_reg[cnt]); } mdb_printf("\n\nXMT2 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt2_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt2_dma_reg[cnt]); } mdb_printf("\n\nXMT3 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt3_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt3_dma_reg[cnt]); } mdb_printf("\n\nXMT4 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt4_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt4_dma_reg[cnt]); } mdb_printf("\n\nXMT Data DMA Common Registers"); for (cnt = 0; cnt < sizeof (fw->xmt_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt_data_dma_reg[cnt]); } mdb_printf("\n\nRCV Thread 0 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->rcvt0_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rcvt0_data_dma_reg[cnt]); } mdb_printf("\n\nRCV Thread 1 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->rcvt1_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rcvt1_data_dma_reg[cnt]); } mdb_printf("\n\nRISC GP Registers"); for (cnt = 0; cnt < sizeof (fw->risc_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->risc_gp_reg[cnt]); } mdb_printf("\n\nShadow Registers"); for (cnt = 0; cnt < sizeof (fw->shadow_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->shadow_reg[cnt]); } mdb_printf("\n\nLMC Registers"); for (cnt = 0; cnt < sizeof (fw->lmc_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->lmc_reg[cnt]); } mdb_printf("\n\nFPM Hardware Registers"); for (cnt = 0; cnt < sizeof (fw->fpm_hdw_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->fpm_hdw_reg[cnt]); } mdb_printf("\n\nFB Hardware Registers"); for (cnt = 0; cnt < sizeof (fw->fb_hdw_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->fb_hdw_reg[cnt]); } mdb_printf("\n\nCode RAM"); for (cnt = 0; cnt < sizeof (fw->code_ram) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt + 0x20000); } mdb_printf("%08x ", fw->code_ram[cnt]); } mdb_printf("\n\nExternal Memory"); for (cnt = 0; cnt < ha->fw_ext_memory_size / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt + 0x100000); } mdb_printf("%08x ", fw->ext_mem[cnt]); } mdb_printf("\n[<==END] ISP Debug Dump"); mdb_printf("\n\nRequest Queue"); for (cnt = 0; cnt < REQUEST_QUEUE_SIZE / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt); } mdb_printf("%08x ", fw->req_q[cnt]); } mdb_printf("\n\nResponse Queue"); for (cnt = 0; cnt < RESPONSE_QUEUE_SIZE / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt); } mdb_printf("%08x ", fw->rsp_q[cnt]); } if ((ha->cfg_flags & CFG_ENABLE_FWEXTTRACE) && (ha->fwexttracebuf.bp != NULL)) { uint32_t cnt_b = 0; uint32_t *w32 = ha->fwexttracebuf.bp; mdb_printf("\n\nExtended Trace Buffer Memory"); /* show data address as a byte address, data as long words */ for (cnt = 0; cnt < FWEXTSIZE / 4; cnt++) { cnt_b = cnt * 4; if (cnt_b % 32 == 0) { mdb_printf("\n%08x: ", w32 + cnt_b); } mdb_printf("%08x ", fw->ext_trace_buf[cnt]); } } if ((ha->cfg_flags & CFG_ENABLE_FWFCETRACE) && (ha->fwfcetracebuf.bp != NULL)) { uint32_t cnt_b = 0; uint32_t *w32 = ha->fwfcetracebuf.bp; mdb_printf("\n\nFC Event Trace Buffer Memory"); /* show data address as a byte address, data as long words */ for (cnt = 0; cnt < FWFCESIZE / 4; cnt++) { cnt_b = cnt * 4; if (cnt_b % 32 == 0) { mdb_printf("\n%08x: ", w32 + cnt_b); } mdb_printf("%08x ", fw->fce_trace_buf[cnt]); } } mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } /* * ql_25xx_dump_dcmd * prints out a firmware dump buffer * * Input: * addr = User supplied address. (NB: nust be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ /*ARGSUSED*/ static int ql_25xx_dump_dcmd(ql_adapter_state_t *ha, uint_t flags, int argc, const mdb_arg_t *argv) { ql_25xx_fw_dump_t *fw; ql_response_q_t **rsp_queues, *rsp_q; uint32_t cnt, cnt1, *dp, *dp2; fw = (ql_25xx_fw_dump_t *)mdb_alloc(ha->ql_dump_size, UM_SLEEP); rsp_queues = mdb_alloc(ha->rsp_queues_cnt * sizeof (ql_response_q_t *), UM_SLEEP); rsp_q = mdb_alloc(sizeof (ql_response_q_t), UM_SLEEP); if (mdb_vread(fw, ha->ql_dump_size, (uintptr_t)ha->ql_dump_ptr) == -1 || mdb_vread(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *), (uintptr_t)ha->rsp_queues) == -1) { mdb_warn("failed to read ql_dump_ptr (no f/w dump active?)"); mdb_free(rsp_q, sizeof (ql_response_q_t)); mdb_free(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *)); mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } mdb_printf("\nISP FW Version %d.%02d.%02d Attributes %X\n", ha->fw_major_version, ha->fw_minor_version, ha->fw_subminor_version, ha->fw_attributes); mdb_printf("\nHCCR Register\n%08x\n", fw->hccr); mdb_printf("\nR2H Register\n%08x\n", fw->r2h_status); mdb_printf("\nAER Uncorrectable Error Status Register\n%08x\n", fw->aer_ues); mdb_printf("\n\nHostRisc Registers"); for (cnt = 0; cnt < sizeof (fw->hostrisc_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->hostrisc_reg[cnt]); } mdb_printf("\n\nPCIe Registers"); for (cnt = 0; cnt < sizeof (fw->pcie_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->pcie_reg[cnt]); } mdb_printf("\n\nHost Interface Registers"); for (cnt = 0; cnt < sizeof (fw->host_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->host_reg[cnt]); } mdb_printf("\n\nShadow Registers"); for (cnt = 0; cnt < sizeof (fw->shadow_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->shadow_reg[cnt]); } mdb_printf("\n\nMailbox Registers"); for (cnt = 0; cnt < sizeof (fw->mailbox_reg) / 2; cnt++) { if (cnt % 16 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->mailbox_reg[cnt]); } mdb_printf("\n\nXSEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_gp_reg[cnt]); } mdb_printf("\n\nXSEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_0_reg[cnt]); } mdb_printf("\n\nXSEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_1_reg[cnt]); } mdb_printf("\n\nRSEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_gp_reg[cnt]); } mdb_printf("\n\nRSEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_0_reg[cnt]); } mdb_printf("\n\nRSEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_1_reg[cnt]); } mdb_printf("\n\nRSEQ-2 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_2_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_2_reg[cnt]); } mdb_printf("\n\nASEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_gp_reg[cnt]); } mdb_printf("\n\nASEQ-0 GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_0_reg[cnt]); } mdb_printf("\n\nASEQ-1 GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_1_reg[cnt]); } mdb_printf("\n\nASEQ-2 GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_2_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_2_reg[cnt]); } mdb_printf("\n\nCommand DMA Registers"); for (cnt = 0; cnt < sizeof (fw->cmd_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->cmd_dma_reg[cnt]); } mdb_printf("\n\nRequest0 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->req0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->req0_dma_reg[cnt]); } mdb_printf("\n\nResponse0 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->resp0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->resp0_dma_reg[cnt]); } mdb_printf("\n\nRequest1 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->req1_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->req1_dma_reg[cnt]); } mdb_printf("\n\nXMT0 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt0_dma_reg[cnt]); } mdb_printf("\n\nXMT1 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt1_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt1_dma_reg[cnt]); } mdb_printf("\n\nXMT2 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt2_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt2_dma_reg[cnt]); } mdb_printf("\n\nXMT3 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt3_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt3_dma_reg[cnt]); } mdb_printf("\n\nXMT4 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt4_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt4_dma_reg[cnt]); } mdb_printf("\n\nXMT Data DMA Common Registers"); for (cnt = 0; cnt < sizeof (fw->xmt_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt_data_dma_reg[cnt]); } mdb_printf("\n\nRCV Thread 0 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->rcvt0_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rcvt0_data_dma_reg[cnt]); } mdb_printf("\n\nRCV Thread 1 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->rcvt1_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rcvt1_data_dma_reg[cnt]); } mdb_printf("\n\nRISC GP Registers"); for (cnt = 0; cnt < sizeof (fw->risc_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->risc_gp_reg[cnt]); } mdb_printf("\n\nRISC IO Register\n%08x", fw->risc_io); mdb_printf("\n\nLMC Registers"); for (cnt = 0; cnt < sizeof (fw->lmc_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->lmc_reg[cnt]); } mdb_printf("\n\nFPM Hardware Registers"); for (cnt = 0; cnt < sizeof (fw->fpm_hdw_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->fpm_hdw_reg[cnt]); } mdb_printf("\n\nFB Hardware Registers"); for (cnt = 0; cnt < sizeof (fw->fb_hdw_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->fb_hdw_reg[cnt]); } mdb_printf("\n\nCode RAM"); for (cnt = 0; cnt < sizeof (fw->code_ram) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt + 0x20000); } mdb_printf("%08x ", fw->code_ram[cnt]); } mdb_printf("\n\nExternal Memory"); dp = (uint32_t *)(void *)((caddr_t)fw->req_rsp_ext_mem + fw->req_q_size[0] + fw->req_q_size[1] + fw->rsp_q_size + (ha->rsp_queues_cnt * 16)); for (cnt = 0; cnt < ha->fw_ext_memory_size / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt + 0x100000); } mdb_printf("%08x ", *dp++); } mdb_printf("\n[<==END] ISP Debug Dump"); mdb_printf("\n\nRequest Queue"); dp = fw->req_rsp_ext_mem + (ha->rsp_queues_cnt * 4); for (cnt = 0; cnt < 2 && fw->req_q_size[cnt]; cnt++) { dp2 = dp; for (cnt1 = 0; cnt1 < fw->req_q_size[cnt] / 4; cnt1++) { if (*dp2++) { break; } } if (cnt1 == fw->req_q_size[cnt] / 4) { dp = dp2; continue; } mdb_printf("\n\nRequest Queue\nQueue 0%d:", cnt); for (cnt1 = 0; cnt1 < fw->req_q_size[cnt] / 4; cnt1++) { if (cnt1 % 8 == 0) { mdb_printf("\n%08x: ", cnt1); } mdb_printf("%08x ", *dp++); } } for (cnt = 0; cnt < ha->rsp_queues_cnt; cnt++) { if (mdb_vread(rsp_q, sizeof (ql_response_q_t), (uintptr_t)rsp_queues[cnt]) == -1) { mdb_warn("failed to read ha->rsp_queues[%d]", cnt); break; } dp2 = dp; for (cnt1 = 0; cnt1 < rsp_q->rsp_ring.size / 4; cnt1++) { if (*dp2++) { break; } } if (cnt1 == rsp_q->rsp_ring.size / 4) { dp = dp2; continue; } mdb_printf("\n\nResponse Queue\nQueue 0%d:", cnt); for (cnt1 = 0; cnt1 < rsp_q->rsp_ring.size / 4; cnt1++) { if (cnt1 % 8 == 0) { mdb_printf("\n%08x: ", cnt1); } mdb_printf("%08x ", *dp++); } } if ((ha->cfg_flags & CFG_ENABLE_FWEXTTRACE) && (ha->fwexttracebuf.bp != NULL)) { uint32_t cnt_b = 0; uint32_t *w32 = ha->fwexttracebuf.bp; mdb_printf("\n\nExtended Trace Buffer Memory"); /* show data address as a byte address, data as long words */ for (cnt = 0; cnt < FWEXTSIZE / 4; cnt++) { cnt_b = cnt * 4; if (cnt_b % 32 == 0) { mdb_printf("\n%08x: ", w32 + cnt_b); } mdb_printf("%08x ", fw->ext_trace_buf[cnt]); } } if ((ha->cfg_flags & CFG_ENABLE_FWFCETRACE) && (ha->fwfcetracebuf.bp != NULL)) { uint32_t cnt_b = 0; uint32_t *w32 = ha->fwfcetracebuf.bp; mdb_printf("\n\nFC Event Trace Buffer Memory"); /* show data address as a byte address, data as long words */ for (cnt = 0; cnt < FWFCESIZE / 4; cnt++) { cnt_b = cnt * 4; if (cnt_b % 32 == 0) { mdb_printf("\n%08x: ", w32 + cnt_b); } mdb_printf("%08x ", fw->fce_trace_buf[cnt]); } } mdb_free(rsp_q, sizeof (ql_response_q_t)); mdb_free(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *)); mdb_free(fw, ha->ql_dump_size); mdb_printf("\n\nreturn exit\n"); return (DCMD_OK); } /* * ql_81xx_dump_dcmd * prints out a firmware dump buffer * * Input: * addr = User supplied address. (NB: nust be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ /*ARGSUSED*/ static int ql_81xx_dump_dcmd(ql_adapter_state_t *ha, uint_t flags, int argc, const mdb_arg_t *argv) { ql_81xx_fw_dump_t *fw; ql_response_q_t **rsp_queues, *rsp_q; uint32_t cnt, cnt1, *dp, *dp2; fw = (ql_81xx_fw_dump_t *)mdb_alloc(ha->ql_dump_size, UM_SLEEP); rsp_queues = mdb_alloc(ha->rsp_queues_cnt * sizeof (ql_response_q_t *), UM_SLEEP); rsp_q = mdb_alloc(sizeof (ql_response_q_t), UM_SLEEP); if (mdb_vread(fw, ha->ql_dump_size, (uintptr_t)ha->ql_dump_ptr) == -1 || mdb_vread(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *), (uintptr_t)ha->rsp_queues) == -1) { mdb_warn("failed to read ql_dump_ptr (no f/w dump active?)"); mdb_free(rsp_q, sizeof (ql_response_q_t)); mdb_free(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *)); mdb_free(fw, ha->ql_dump_size); return (DCMD_OK); } mdb_printf("\nISP FW Version %d.%02d.%02d Attributes %X\n", ha->fw_major_version, ha->fw_minor_version, ha->fw_subminor_version, ha->fw_attributes); mdb_printf("\nHCCR Register\n%08x\n", fw->hccr); mdb_printf("\nR2H Register\n%08x\n", fw->r2h_status); mdb_printf("\nAER Uncorrectable Error Status Register\n%08x\n", fw->aer_ues); mdb_printf("\n\nHostRisc Registers"); for (cnt = 0; cnt < sizeof (fw->hostrisc_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->hostrisc_reg[cnt]); } mdb_printf("\n\nPCIe Registers"); for (cnt = 0; cnt < sizeof (fw->pcie_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->pcie_reg[cnt]); } mdb_printf("\n\nHost Interface Registers"); for (cnt = 0; cnt < sizeof (fw->host_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->host_reg[cnt]); } mdb_printf("\n\nShadow Registers"); for (cnt = 0; cnt < sizeof (fw->shadow_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->shadow_reg[cnt]); } mdb_printf("\n\nMailbox Registers"); for (cnt = 0; cnt < sizeof (fw->mailbox_reg) / 2; cnt++) { if (cnt % 16 == 0) { mdb_printf("\n"); } mdb_printf("%04x ", fw->mailbox_reg[cnt]); } mdb_printf("\n\nXSEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_gp_reg[cnt]); } mdb_printf("\n\nXSEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_0_reg[cnt]); } mdb_printf("\n\nXSEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->xseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xseq_1_reg[cnt]); } mdb_printf("\n\nRSEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_gp_reg[cnt]); } mdb_printf("\n\nRSEQ-0 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_0_reg[cnt]); } mdb_printf("\n\nRSEQ-1 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_1_reg[cnt]); } mdb_printf("\n\nRSEQ-2 Registers"); for (cnt = 0; cnt < sizeof (fw->rseq_2_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rseq_2_reg[cnt]); } mdb_printf("\n\nASEQ GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_gp_reg[cnt]); } mdb_printf("\n\nASEQ-0 GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_0_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_0_reg[cnt]); } mdb_printf("\n\nASEQ-1 GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_1_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_1_reg[cnt]); } mdb_printf("\n\nASEQ-2 GP Registers"); for (cnt = 0; cnt < sizeof (fw->aseq_2_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->aseq_2_reg[cnt]); } mdb_printf("\n\nCommand DMA Registers"); for (cnt = 0; cnt < sizeof (fw->cmd_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->cmd_dma_reg[cnt]); } mdb_printf("\n\nRequest0 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->req0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->req0_dma_reg[cnt]); } mdb_printf("\n\nResponse0 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->resp0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->resp0_dma_reg[cnt]); } mdb_printf("\n\nRequest1 Queue DMA Channel Registers"); for (cnt = 0; cnt < sizeof (fw->req1_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->req1_dma_reg[cnt]); } mdb_printf("\n\nXMT0 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt0_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt0_dma_reg[cnt]); } mdb_printf("\n\nXMT1 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt1_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt1_dma_reg[cnt]); } mdb_printf("\n\nXMT2 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt2_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt2_dma_reg[cnt]); } mdb_printf("\n\nXMT3 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt3_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt3_dma_reg[cnt]); } mdb_printf("\n\nXMT4 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->xmt4_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt4_dma_reg[cnt]); } mdb_printf("\n\nXMT Data DMA Common Registers"); for (cnt = 0; cnt < sizeof (fw->xmt_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->xmt_data_dma_reg[cnt]); } mdb_printf("\n\nRCV Thread 0 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->rcvt0_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rcvt0_data_dma_reg[cnt]); } mdb_printf("\n\nRCV Thread 1 Data DMA Registers"); for (cnt = 0; cnt < sizeof (fw->rcvt1_data_dma_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->rcvt1_data_dma_reg[cnt]); } mdb_printf("\n\nRISC GP Registers"); for (cnt = 0; cnt < sizeof (fw->risc_gp_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->risc_gp_reg[cnt]); } mdb_printf("\n\nRISC IO Register\n%08x", fw->risc_io); mdb_printf("\n\nLMC Registers"); for (cnt = 0; cnt < sizeof (fw->lmc_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->lmc_reg[cnt]); } mdb_printf("\n\nFPM Hardware Registers"); for (cnt = 0; cnt < sizeof (fw->fpm_hdw_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->fpm_hdw_reg[cnt]); } mdb_printf("\n\nFB Hardware Registers"); for (cnt = 0; cnt < sizeof (fw->fb_hdw_reg) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n"); } mdb_printf("%08x ", fw->fb_hdw_reg[cnt]); } mdb_printf("\n\nCode RAM"); for (cnt = 0; cnt < sizeof (fw->code_ram) / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt + 0x20000); } mdb_printf("%08x ", fw->code_ram[cnt]); } mdb_printf("\n\nExternal Memory"); dp = (uint32_t *)(void *)((caddr_t)fw->req_rsp_ext_mem + fw->req_q_size[0] + fw->req_q_size[1] + fw->rsp_q_size + (ha->rsp_queues_cnt * 16)); for (cnt = 0; cnt < ha->fw_ext_memory_size / 4; cnt++) { if (cnt % 8 == 0) { mdb_printf("\n%08x: ", cnt + 0x100000); } mdb_printf("%08x ", *dp++); } mdb_printf("\n[<==END] ISP Debug Dump"); mdb_printf("\n\nRequest Queue"); dp = fw->req_rsp_ext_mem + (ha->rsp_queues_cnt * 4); for (cnt = 0; cnt < 2 && fw->req_q_size[cnt]; cnt++) { dp2 = dp; for (cnt1 = 0; cnt1 < fw->req_q_size[cnt] / 4; cnt1++) { if (*dp2++) { break; } } if (cnt1 == fw->req_q_size[cnt] / 4) { dp = dp2; continue; } mdb_printf("\n\nRequest Queue\nQueue 0%d:", cnt); for (cnt1 = 0; cnt1 < fw->req_q_size[cnt] / 4; cnt1++) { if (cnt1 % 8 == 0) { mdb_printf("\n%08x: ", cnt1); } mdb_printf("%08x ", *dp++); } } for (cnt = 0; cnt < ha->rsp_queues_cnt; cnt++) { if (mdb_vread(rsp_q, sizeof (ql_response_q_t), (uintptr_t)rsp_queues[cnt]) == -1) { mdb_warn("failed to read ha->rsp_queues[%d]", cnt); break; } dp2 = dp; for (cnt1 = 0; cnt1 < rsp_q->rsp_ring.size / 4; cnt1++) { if (*dp2++) { break; } } if (cnt1 == rsp_q->rsp_ring.size / 4) { dp = dp2; continue; } mdb_printf("\n\nResponse Queue\nQueue 0%d:", cnt); for (cnt1 = 0; cnt1 < rsp_q->rsp_ring.size / 4; cnt1++) { if (cnt1 % 8 == 0) { mdb_printf("\n%08x: ", cnt1); } mdb_printf("%08x ", *dp++); } } if ((ha->cfg_flags & CFG_ENABLE_FWEXTTRACE) && (ha->fwexttracebuf.bp != NULL)) { uint32_t cnt_b = 0; uint32_t *w32 = ha->fwexttracebuf.bp; mdb_printf("\n\nExtended Trace Buffer Memory"); /* show data address as a byte address, data as long words */ for (cnt = 0; cnt < FWEXTSIZE / 4; cnt++) { cnt_b = cnt * 4; if (cnt_b % 32 == 0) { mdb_printf("\n%08x: ", w32 + cnt_b); } mdb_printf("%08x ", fw->ext_trace_buf[cnt]); } } if ((ha->cfg_flags & CFG_ENABLE_FWFCETRACE) && (ha->fwfcetracebuf.bp != NULL)) { uint32_t cnt_b = 0; uint32_t *w32 = ha->fwfcetracebuf.bp; mdb_printf("\n\nFC Event Trace Buffer Memory"); /* show data address as a byte address, data as long words */ for (cnt = 0; cnt < FWFCESIZE / 4; cnt++) { cnt_b = cnt * 4; if (cnt_b % 32 == 0) { mdb_printf("\n%08x: ", w32 + cnt_b); } mdb_printf("%08x ", fw->fce_trace_buf[cnt]); } } mdb_free(rsp_q, sizeof (ql_response_q_t)); mdb_free(rsp_queues, ha->rsp_queues_cnt * sizeof (ql_response_q_t *)); mdb_free(fw, ha->ql_dump_size); mdb_printf("\n\nreturn exit\n"); return (DCMD_OK); } /* * ql_gettrace_dcmd * prints out the Extended Logging trace buffer * * Input: * addr = User supplied address. (NB: must be an ha) * flags = mdb flags. * argc = Number of user supplied args. * argv = Arg array. * * Returns: * DCMD_OK or DCMD_ERR * * Context: * User context. * */ static int qlc_gettrace_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ql_adapter_state_t *ha; ql_trace_desc_t *trace_desc; ql_trace_entry_t entry; uint32_t i = 0; char merge[1024]; if ((!(flags & DCMD_ADDRSPEC)) || addr == 0) { mdb_warn("ql_adapter_state structure addr is required"); return (DCMD_USAGE); } if (argc != 0) { return (DCMD_USAGE); } if (mdb_getopts(argc, argv, NULL) != argc) { return (DCMD_USAGE); } /* * Get the adapter state struct which was passed */ if ((ha = (ql_adapter_state_t *)mdb_alloc(sizeof (ql_adapter_state_t), UM_SLEEP)) == NULL) { mdb_warn("failed to allocate memory for ql_adapter_state\n"); return (DCMD_OK); } if (mdb_vread(ha, sizeof (ql_adapter_state_t), addr) == -1) { mdb_warn("failed to read ql_adapter_state at %p", addr); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } if (ha->ql_trace_desc == NULL) { mdb_warn("trace descriptor does not exist for instance %d\n", ha->instance); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } else { trace_desc = (ql_trace_desc_t *) mdb_alloc(sizeof (ql_trace_desc_t), UM_SLEEP); if (mdb_vread(trace_desc, sizeof (ql_trace_desc_t), (uintptr_t)ha->ql_trace_desc) == -1) { mdb_warn("failed to read ql_adapter_state at %p", addr); mdb_free(trace_desc, sizeof (ql_trace_desc_t)); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } if (trace_desc->trace_buffer == NULL) { mdb_warn("trace buffer does not exist for " "instance %d\n", ha->instance); mdb_free(trace_desc, sizeof (ql_trace_desc_t)); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } } /* Check if anything logged or not */ if (trace_desc->csize == 0) { mdb_warn("Extended log buffer is empty.\n"); mdb_free(trace_desc, sizeof (ql_trace_desc_t)); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } /* * Locate the start and end point. If ever wrapped, then * always print from start to end in the circular buffer */ /* always print from start to end */ for (i = trace_desc->start; i < trace_desc->csize; i++) { if (mdb_vread(&entry, sizeof (ql_trace_entry_t), (uintptr_t)&trace_desc->trace_buffer[i]) != sizeof (ql_trace_entry_t)) { mdb_warn("Cannot read trace entry. %xh", i); mdb_free(trace_desc, sizeof (ql_trace_desc_t)); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_ERR); } if (entry.buf[0] != '\0') { (void) mdb_snprintf(merge, sizeof (merge), "[%Y:%03d:%03d:%03d] " "%s", entry.hs_time.tv_sec, (int)entry.hs_time.tv_nsec / 1000000, (int)(entry.hs_time.tv_nsec / 1000) % 1000, (int)entry.hs_time.tv_nsec % 1000, entry.buf + 1); } mdb_printf("%s", merge); } if (trace_desc->start != 0) { for (i = 0; i < trace_desc->start; i++) { if (mdb_vread(&entry, sizeof (ql_trace_entry_t), (uintptr_t)&trace_desc->trace_buffer[i]) != sizeof (ql_trace_entry_t)) { mdb_warn("Cannot read trace entry. %xh", i); mdb_free(trace_desc, sizeof (ql_trace_desc_t)); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_ERR); } if (entry.buf[0] != '\0') { (void) mdb_snprintf(merge, sizeof (merge), "[%Y:%03d:%03d:%03d] " "%s", entry.hs_time.tv_sec, (int)entry.hs_time.tv_nsec / 1000000, (int)(entry.hs_time.tv_nsec / 1000) % 1000, (int)entry.hs_time.tv_nsec % 1000, entry.buf + 1); } mdb_printf("%s", merge); } } mdb_printf("\n"); mdb_free(trace_desc, sizeof (ql_trace_desc_t)); mdb_free(ha, sizeof (ql_adapter_state_t)); return (DCMD_OK); } /* * ql_doprint * ql generic function to call the print dcmd * * Input: * addr - address to struct * prtsting - address to string * * Returns: * WALK_DONE * * Context: * User context. * */ static int32_t ql_doprint(uintptr_t addr, int8_t *prtstring) { struct mdb_arg printarg; printarg.a_un.a_str = (int8_t *)(mdb_zalloc(strlen(prtstring), UM_SLEEP)); printarg.a_type = MDB_TYPE_STRING; (void) strcpy((int8_t *)(printarg.a_un.a_str), prtstring); if ((mdb_call_dcmd("print", addr, DCMD_ADDRSPEC, 1, &printarg)) == -1) { mdb_warn("ql_doprint: failed print dcmd: %s" "at addr: %llxh", prtstring, addr); } mdb_free((void *)(printarg.a_un.a_str), strlen(prtstring)); return (WALK_DONE); } /* * ql_dump_flags * mdb utility to print the flag string * * Input: * flags - flags to print * strings - text to print when flag is set * * Returns: * * * Context: * User context. * */ static void ql_dump_flags(uint64_t flags, int8_t **strings) { int i, linel, first = 1; uint64_t mask = 1; linel = 8; mdb_printf("\t"); for (i = 0; i < 64; i++) { if (strings[i] == NULL) break; if (flags & mask) { if (!first) { mdb_printf(" | "); } else { first = 0; } linel += (int32_t)strlen(strings[i]) + 3; if (linel > 80) { mdb_printf("\n\t"); linel = (int32_t)strlen(strings[i]) + 1 + 8; } mdb_printf("%s", strings[i]); } mask <<= 1; } mdb_printf("\n"); } /* * MDB module linkage information * * * dcmd structures for the _mdb_init function */ static const mdb_dcmd_t dcmds[] = { { "qlclinks", NULL, "Prints qlc link information", qlclinks_dcmd }, { "qlcosc", NULL, "Prints outstanding cmd info", qlc_osc_dcmd }, { "qlcver", NULL, "Prints driver/mdb version", qlcver_dcmd }, { "qlc_elog", "[on|off] [|all]", "Turns qlc extended logging " "on / off", qlc_el_dcmd }, { "qlcstate", ":[-v]", "Prints qlc adapter state information", qlcstate_dcmd }, { "qlctgtq", NULL, "Prints qlc target queues", qltgtq_dcmd }, { "qlcwdog", NULL, "Prints out watchdog linked list", qlc_wdog_dcmd}, { "qlcgetdump", ":[-v]", "Retrieves the ASCII f/w dump", qlc_getdump_dcmd }, { "qlcgettrace", ":", "Retrieves the ASCII Extended Logging trace", qlc_gettrace_dcmd }, { NULL } }; /* * walker structures for the _mdb_init function */ static const mdb_walker_t walkers[] = { { "qlcstates", "walk list of qlc ql_state_t structures", qlstates_walk_init, qlstates_walk_step, qlstates_walk_fini }, { "qlcsrbs", "walk list of qlc ql_srb_t strctures", qlsrb_walk_init, qlsrb_walk_step, qlsrb_walk_fini }, { "qlclunq", "walk list of qlc ql_lun_t strctures", qllunq_walk_init, qllunq_walk_step, qllunq_walk_fini }, { NULL } }; static const mdb_modinfo_t ql_mdb_modinfo = { MDB_API_VERSION, dcmds, walkers }; /* * Registration function which lists the dcmds and walker structures */ const mdb_modinfo_t * _mdb_init(void) { return (&ql_mdb_modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include /* * rnd_stats dcmd - Print out the global rnd_stats structure, nicely formatted. */ /*ARGSUSED*/ static int rnd_get_stats(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { rnd_stats_t rnd_stats, rnd_stats_cpu; uint32_t random_max_ncpus; size_t rndmag_t_size; ulong_t rndmag_t_offset; uintptr_t rndmag; mdb_ctf_id_t rndmag_id; int i; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_readvar(&rnd_stats, "rnd_stats") == -1) { mdb_warn("failed to read rnd_stats structure"); return (DCMD_ERR); } if ((mdb_ctf_lookup_by_name("rndmag_t", &rndmag_id) != 0) || (mdb_ctf_offsetof(rndmag_id, "rm_stats", &rndmag_t_offset) != 0) || (mdb_readvar(&random_max_ncpus, "random_max_ncpus") == -1) || (mdb_readvar(&rndmag, "rndmag") == -1) || ((rndmag_t_size = mdb_ctf_type_size(rndmag_id)) == 0)) { /* Can't find per-cpu stats. Don't add them in. */ random_max_ncpus = 0; } rndmag_t_offset /= 8; /* * Read and aggregate per-cpu stats if we have them. */ for (i = 0; i < random_max_ncpus; i++) { mdb_vread(&rnd_stats_cpu, sizeof (rnd_stats_cpu), rndmag + rndmag_t_offset + i * rndmag_t_size); rnd_stats.rs_rndOut += rnd_stats_cpu.rs_rndOut; rnd_stats.rs_rndcOut += rnd_stats_cpu.rs_rndcOut; rnd_stats.rs_urndOut += rnd_stats_cpu.rs_urndOut; } mdb_printf("Random number device statistics:\n"); mdb_printf("%8llu bytes generated for /dev/random\n", rnd_stats.rs_rndOut); mdb_printf("%8llu bytes read from /dev/random cache\n", rnd_stats.rs_rndcOut); mdb_printf("%8llu bytes generated for /dev/urandom\n", rnd_stats.rs_urndOut); return (DCMD_OK); } /* * swrand_stats dcmd - Print out the global swrand_stats structure, * nicely formatted. */ /*ARGSUSED*/ static int swrand_get_stats(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { swrand_stats_t swrand_stats; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_readvar(&swrand_stats, "swrand_stats") == -1) { mdb_warn("failed to read swrand_stats structure"); return (DCMD_ERR); } mdb_printf("Software-based Random number generator statistics:\n"); mdb_printf("%8u bits of entropy estimate\n", swrand_stats.ss_entEst); mdb_printf("%8llu bits of entropy added to the pool\n", swrand_stats.ss_entIn); mdb_printf("%8llu bits of entropy extracted from the pool\n", swrand_stats.ss_entOut); mdb_printf("%8llu bytes added to the random pool\n", swrand_stats.ss_bytesIn); mdb_printf("%8llu bytes extracted from the random pool\n", swrand_stats.ss_bytesOut); return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { "rnd_stats", NULL, "print random number device statistics", rnd_get_stats }, { "swrand_stats", NULL, "print kernel random number provider statistics", swrand_get_stats }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999-2000 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include static int print_node_info(s1394_hal_t *hal); /* * speedmap() * is used to print node information (speed map, node number, GUID, etc.) * about the 1394 devices currently attached to the 1394 bus. */ static int speedmap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { s1394_hal_t hal; int ret; if (flags & DCMD_ADDRSPEC) { if (mdb_vread(&hal, sizeof (s1394_hal_t), addr) == -1) { mdb_warn("failed to read the HAL structure"); return (DCMD_ERR); } ret = print_node_info(&hal); if (ret == DCMD_ERR) return (DCMD_ERR); } else { (void) mdb_walk_dcmd("speedmap", "speedmap", argc, argv); } return (DCMD_OK); } static int speedmap_walk_init(mdb_walk_state_t *wsp) { s1394_state_t *statep; s1394_state_t state; if (wsp->walk_addr == 0) { if (mdb_readvar(&statep, "s1394_statep") == -1) { mdb_warn("failed to find the s1394_statep pointer"); return (WALK_ERR); } if (mdb_vread(&state, sizeof (s1394_state_t), (uintptr_t)statep) == -1) { mdb_warn("failed to read the s1394_statep structure"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)state.hal_head; } return (WALK_NEXT); } static int speedmap_walk_step(mdb_walk_state_t *wsp) { s1394_hal_t hal; uintptr_t addr = wsp->walk_addr; if (addr == 0) return (WALK_DONE); if (mdb_vread(&hal, sizeof (s1394_hal_t), addr) == -1) { mdb_warn("failed to read the HAL structure"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)hal.hal_next; return (wsp->walk_callback(addr, &hal, wsp->walk_cbdata)); } /*ARGSUSED*/ static void speedmap_walk_fini(mdb_walk_state_t *wsp) { /* Nothing to do here */ } static const mdb_dcmd_t dcmds[] = { { "speedmap", NULL, "print 1394 bus information", speedmap }, { NULL } }; static const mdb_walker_t walkers[] = { { "speedmap", "iterate over HAL structures", speedmap_walk_init, speedmap_walk_step, speedmap_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * print_node_info() * is used to do the actual printing, given a HAL pointer. */ static int print_node_info(s1394_hal_t *hal) { s1394_node_t node[IEEE1394_MAX_NODES]; uint32_t cfgrom[IEEE1394_CONFIG_ROM_QUAD_SZ]; char str[512], tmp[512]; uint_t hal_node_num, num_nodes; int i, j; num_nodes = hal->number_of_nodes; if (mdb_vread(node, (num_nodes * sizeof (s1394_node_t)), (uintptr_t)hal->topology_tree) == -1) { mdb_warn("failed to read the node structures"); return (DCMD_ERR); } hal_node_num = IEEE1394_NODE_NUM(hal->node_id); mdb_printf("Speed Map:\n"); (void) strcpy(str, " |"); for (i = 0; i < num_nodes; i++) { (void) mdb_snprintf(tmp, sizeof (tmp), " %2d ", i); (void) strcat(str, tmp); } (void) strcat(str, " | GUID\n"); mdb_printf("%s", str); (void) strcpy(str, "----|"); for (i = 0; i < hal->number_of_nodes; i++) { (void) mdb_snprintf(tmp, sizeof (tmp), "----"); (void) strcat(str, tmp); } (void) strcat(str, "--|------------------\n"); mdb_printf("%s", str); for (i = 0; i < num_nodes; i++) { if (node[i].cfgrom != NULL) { if (mdb_vread(&cfgrom, IEEE1394_CONFIG_ROM_SZ, (uintptr_t)node[i].cfgrom) == -1) { mdb_warn("failed to read Config ROM"); return (DCMD_ERR); } } (void) mdb_snprintf(str, sizeof (str), " %2d |", i); for (j = 0; j < num_nodes; j++) { (void) mdb_snprintf(tmp, sizeof (tmp), " %3d", hal->speed_map[i][j]); (void) strcat(str, tmp); } if (i == hal_node_num) { (void) strcat(str, " | Local OHCI Card\n"); } else if (node[i].link_active == 0) { (void) strcat(str, " | Link off\n"); } else if (CFGROM_BIB_READ(&node[i])) { (void) mdb_snprintf(tmp, sizeof (tmp), " | %08x%08x\n", cfgrom[3], cfgrom[4]); (void) strcat(str, tmp); } else { (void) strcat(str, " | ????????????????\n"); } mdb_printf("%s", str); } mdb_printf("\n"); return (DCMD_OK); } /* * 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. * * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FT(var, typ) (*((typ *)(&(var)))) static int dump_states(uintptr_t array_vaddr, int verbose, struct i_ddi_soft_state *sp); static int i_vhci_states(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, struct i_ddi_soft_state *sp); static int vhci_states(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); static int mdiclient(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); static int vhciguid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); static int vhcilun(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); static int i_vhcilun(uintptr_t addr, uint_t display_single_guid, char *guid); /* Utils */ static int get_mdbstr(uintptr_t addr, char *name); static void dump_mutex(kmutex_t m, char *name); static void dump_condvar(kcondvar_t c, char *name); static void dump_string(uintptr_t addr, char *name); static void dump_flags(unsigned long long flags, char **strings); static void dump_state_str(char *name, uintptr_t addr, char **strings); static int mpxio_walk_cb(uintptr_t addr, const void *data, void *cbdata); static const mdb_dcmd_t dcmds[] = { { "vhci_states", "[ -v ]", "dump all the vhci state pointers", vhci_states }, { "vhciguid", NULL, "list all clients or given a guid, list one client", vhciguid }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; static char *client_lb_str[] = { "NONE", "RR", "LBA", NULL }; static char *mdi_client_states[] = { NULL, "OPTIMAL", "DEGRADED", "FAILED", NULL }; static char *client_flags[] = { "MDI_CLIENT_FLAGS_OFFLINE", "MDI_CLIENT_FLAGS_SUSPEND", "MDI_CLIENT_FLAGS_POWER_DOWN", "MDI_CLIENT_FLAGS_DETACH", "MDI_CLIENT_FLAGS_FAILOVER", "MDI_CLIENT_FLAGS_REPORT_DEV", "MDI_CLIENT_FLAGS_PATH_FREE_IN_PROGRESS", "MDI_CLIENT_FLAGS_ASYNC_FREE", "MDI_CLIENT_FLAGS_DEV_NOT_SUPPORTED", NULL }; static char *vhci_conf_flags[] = { "VHCI_CONF_FLAGS_AUTO_FAILBACK", NULL }; static char *svlun_flags[] = { "VLUN_TASK_D_ALIVE_FLG", "VLUN_RESERVE_ACTIVE_FLG", "VLUN_QUIESCED_FLG", NULL }; static char mdipathinfo_cb_str[] = "::print struct mdi_pathinfo"; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * mdiclient() * * Dump mdi_client_t info and list all paths. */ /* ARGSUSED */ static int mdiclient(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct mdi_client value; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("mdiclient: requires an address"); return (DCMD_ERR); } if (mdb_vread(&value, sizeof (struct mdi_client), addr) != sizeof (struct mdi_client)) { mdb_warn("mdiclient: Failed read on %l#r\n", addr); return (DCMD_ERR); } mdb_printf("----------------- mdi_client @ %#lr ----------\n", addr); dump_string((uintptr_t)value.ct_guid, "GUID (ct_guid)"); dump_string((uintptr_t)value.ct_drvname, "Driver Name (ct_drvname)"); dump_state_str("Load Balance (ct_lb)", value.ct_lb, client_lb_str); mdb_printf("\n"); mdb_printf("ct_hnext: %26l#r::print struct mdi_client\n", value.ct_hnext); mdb_printf("ct_hprev: %26l#r::print struct mdi_client\n", value.ct_hprev); mdb_printf("ct_dip: %28l#r::print struct dev_info\n", value.ct_dip); mdb_printf("ct_vhci: %27l#r::print struct mdi_vhci\n", value.ct_vhci); mdb_printf("ct_cprivate: %23l#r\n", value.ct_cprivate); mdb_printf("\nct_path_head: %22l#r::print struct mdi_pathinfo\n", value.ct_path_head); mdb_printf("ct_path_tail: %22l#r::print struct mdi_pathinfo\n", value.ct_path_tail); mdb_printf("ct_path_last: %22l#r::print struct mdi_pathfinfo\n", value.ct_path_last); mdb_printf("ct_path_count: %21d\n", value.ct_path_count); mdb_printf("List of paths:\n"); mdb_pwalk("mdipi_client_list", (mdb_walk_cb_t)mpxio_walk_cb, mdipathinfo_cb_str, (uintptr_t)value.ct_path_head); mdb_printf("\n"); dump_state_str("Client State (ct_state)", value.ct_state, mdi_client_states); dump_mutex(value.ct_mutex, "per-client mutex (ct_mutex):"); mdb_printf("ct_flags: %26d\n", value.ct_flags); if (value.ct_flags) { dump_flags((unsigned long long)value.ct_flags, client_flags); } mdb_printf("ct_unstable: %23d\n", value.ct_unstable); dump_condvar(value.ct_unstable_cv, "ct_unstable_cv"); dump_condvar(value.ct_failover_cv, "ct_failover_cv"); mdb_printf("\n"); mdb_printf("ct_failover_flags TEMP_VAR: %8d\n", value.ct_failover_flags); mdb_printf("ct_failover_status UNUSED: %9d\n", value.ct_failover_status); return (DCMD_OK); } /* * vhcilun() * * Get client info given a guid. */ /* ARGSUSED */ static int vhcilun(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("sv_lun: requires an address"); return (DCMD_ERR); } return (i_vhcilun(addr, 0 /* display_single_guid */, 0)); } /* * vhciguid() * * List all the clients. */ /* ARGSUSED */ static int vhciguid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct i_ddi_soft_state ss; int i; mdi_vhci_t *mdi_vhci_value; mdi_client_t *mdi_client_value; struct client_hash *ct_hash_val; struct client_hash *ct_hash_table_val; int len = strlen(MDI_HCI_CLASS_SCSI); int mdi_vhci_len = sizeof (*mdi_vhci_value); int mdi_client_len = sizeof (*mdi_client_value); int ct_hash_len = sizeof (*ct_hash_val); int ct_hash_count = 0; char *class; int found = 0; uintptr_t buf; uintptr_t temp; if (flags & DCMD_ADDRSPEC) mdb_warn("This command doesn't use an address\n"); if (i_vhci_states(0, 0, 0, 0, &ss) != DCMD_OK) return (DCMD_ERR); if (mdb_readvar(&buf, "mdi_vhci_head") == -1) { mdb_warn("mdi driver variable mdi_vhci_head not found.\n"); mdb_warn("Is the driver loaded ?\n"); return (DCMD_ERR); } mdb_printf("----------------- mdi_vhci_head @ %#lr ----------\n", buf); mdi_vhci_value = (mdi_vhci_t *)mdb_alloc(mdi_vhci_len, UM_SLEEP|UM_GC); if (mdb_vread(mdi_vhci_value, mdi_vhci_len, buf) != mdi_vhci_len) { mdb_warn("vhciguid: Failed read on %l#r\n", mdi_vhci_value); mdb_free(mdi_vhci_value, mdi_vhci_len); return (DCMD_ERR); } temp = (uintptr_t)mdi_vhci_value->vh_class; class = (char *)mdb_alloc(len, UM_SLEEP|UM_GC); if (mdb_vread(class, strlen(MDI_HCI_CLASS_SCSI), temp) != strlen(MDI_HCI_CLASS_SCSI)) { mdb_warn("vhciguid: Failed read of class %l#r\n", mdi_vhci_value); mdb_free(mdi_vhci_value, mdi_vhci_len); mdb_free(class, len); return (DCMD_ERR); } class[len] = 0; mdb_printf("----------------- class @ %s----------\n", class); while (class) { if (strcmp(class, MDI_HCI_CLASS_SCSI) == 0) { found = 1; break; } if (mdi_vhci_value->vh_next == NULL) { break; } temp = (uintptr_t)mdi_vhci_value->vh_next; if (mdb_vread(mdi_vhci_value, mdi_vhci_len, temp) != mdi_vhci_len) { mdb_warn("vhciguid: Failed read on vh->next %l#r\n", mdi_vhci_value); break; } temp = (uintptr_t)mdi_vhci_value->vh_class; if (mdb_vread(class, strlen(MDI_HCI_CLASS_SCSI), temp) != strlen(MDI_HCI_CLASS_SCSI)) { mdb_warn("vhciguid: Failed read on vh->next %l#r\n", mdi_vhci_value); break; } class[len] = 0; } if (found == 0) { mdb_warn("vhciguid: No scsi_vhci class found"); mdb_free(mdi_vhci_value, mdi_vhci_len); mdb_free(class, len); return (DCMD_ERR); } mdb_printf("----- Number of devices found %d ----------\n", mdi_vhci_value->vh_client_count); for (i = 0; i < CLIENT_HASH_TABLE_SIZE; i++) { ct_hash_table_val = &mdi_vhci_value->vh_client_table[i]; if (ct_hash_table_val == NULL) continue; /* Read client_hash structure */ ct_hash_val = (struct client_hash *)mdb_alloc(ct_hash_len, UM_SLEEP|UM_GC); temp = (uintptr_t)ct_hash_table_val; if (mdb_vread(ct_hash_val, ct_hash_len, temp) != ct_hash_len) { mdb_warn("Failed read on hash %l#r\n", ct_hash_val); break; } mdb_printf("----hash[%d] %l#r: devices mapped = %d --\n", i, ct_hash_table_val, ct_hash_val->ct_hash_count); if (ct_hash_val->ct_hash_count == 0) { continue; } ct_hash_count = ct_hash_val->ct_hash_count; /* Read mdi_client structures */ mdi_client_value = (mdi_client_t *)mdb_alloc(mdi_client_len, UM_SLEEP|UM_GC); temp = (uintptr_t)ct_hash_val->ct_hash_head; if (mdb_vread(mdi_client_value, mdi_client_len, temp) != mdi_client_len) { mdb_warn("Failed read on client %l#r\n", mdi_client_value); break; } mdb_printf("mdi_client %l#r %l#r ------\n", mdi_client_value, mdi_client_value->ct_vprivate); vhcilun((uintptr_t)mdi_client_value->ct_vprivate, DCMD_ADDRSPEC, 0, 0); while (--ct_hash_count) { temp = (uintptr_t)mdi_client_value->ct_hnext; if (mdb_vread(mdi_client_value, mdi_client_len, temp) != mdi_client_len) { mdb_warn("Failed read on client %l#r\n", mdi_client_value); break; } vhcilun((uintptr_t)mdi_client_value->ct_vprivate, DCMD_ADDRSPEC, 0, 0); } } mdb_printf("----------done----------\n"); return (DCMD_OK); } /* * Print the flag name by comparing flags to the mask variable. */ static void dump_flags(unsigned long long flags, char **strings) { int i, linel = 8, first = 1; unsigned long long mask = 1; for (i = 0; i < 64; i++) { if (strings[i] == NULL) break; if (flags & mask) { if (!first) { mdb_printf(" | "); } else { first = 0; } /* make output pretty */ linel += strlen(strings[i]) + 3; if (linel > 80) { mdb_printf("\n\t"); linel = strlen(strings[i]) + 1 + 8; } mdb_printf("%s", strings[i]); } mask <<= 1; } mdb_printf("\n"); } /* ARGSUSED */ static int vhci_states(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { return (i_vhci_states(addr, flags, argc, argv, NULL)); } /* * dump_states() * * Print the state information for vhci_states(). */ static int dump_states(uintptr_t array_vaddr, int verbose, struct i_ddi_soft_state *sp) { int i; int array_size; struct i_ddi_soft_state *ss; struct scsi_vhci vhci; if (sp == NULL) { ss = (struct i_ddi_soft_state *)mdb_alloc(sizeof (*ss), UM_SLEEP|UM_GC); } else { ss = sp; } if (mdb_vread(ss, sizeof (*ss), array_vaddr) != sizeof (*ss)) { mdb_warn("Cannot read softstate struct (Invalid pointer?).\n"); return (DCMD_ERR); } array_size = ss->n_items * (sizeof (void *)); array_vaddr = (uintptr_t)ss->array; ss->array = mdb_alloc(array_size, UM_SLEEP|UM_GC); if (mdb_vread(ss->array, array_size, array_vaddr) != array_size) { mdb_warn("Corrupted softstate struct.\n"); return (DCMD_ERR); } if (sp != NULL) return (DCMD_OK); if (verbose) { /* * ss->size is of type size_t which is 4 bytes and 8 bytes * on 32-bit and 64-bit systems respectively. */ #ifdef _LP64 mdb_printf("Softstate size is %lld(0x%llx) bytes.\n\n", ss->size, ss->size); #else mdb_printf("Softstate size is %ld(0x%lx) bytes.\n\n", ss->size, ss->size); #endif mdb_printf("state pointer\t\t\t\t\tinstance\n"); mdb_printf("=============\t\t\t\t\t========\n"); } for (i = 0; i < ss->n_items; i++) { if (ss->array[i] == 0) continue; if (mdb_vread(&vhci, sizeof (vhci), (uintptr_t)ss->array[i]) != sizeof (vhci)) { mdb_warn("Corrupted softstate struct.\n"); return (DCMD_ERR); } if (verbose) { mdb_printf("%l#r::print struct scsi_vhci\t\t %d\n", ss->array[i], i); mdb_printf("\nvhci_conf_flags: %d\n", vhci.vhci_conf_flags); if (vhci.vhci_conf_flags) { mdb_printf("\t"); dump_flags((unsigned long long) vhci.vhci_conf_flags, vhci_conf_flags); } } else { mdb_printf("%l#r\n", ss->array[i]); } } return (DCMD_OK); } static int get_mdbstr(uintptr_t addr, char *string_val) { if (mdb_readstr(string_val, MAXNAMELEN, addr) == -1) { mdb_warn("Error Reading String from %l#r\n", addr); return (1); } return (0); } static void dump_state_str(char *name, uintptr_t addr, char **strings) { mdb_printf("%s: %s (%l#r)\n", name, strings[(unsigned long)addr], addr); } /* VHCI UTILS */ /* * i_vhci_states() * * Internal routine for vhci_states() to check for -v arg and then * print state info. */ /* ARGSUSED */ static int i_vhci_states(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, struct i_ddi_soft_state *sp) { uintptr_t adr; int verbose = 0; if (mdb_readvar(&adr, "vhci_softstate") == -1) { mdb_warn("vhci driver variable vhci_softstate not found.\n"); mdb_warn("Is the driver loaded ?\n"); return (DCMD_ERR); } if (sp == NULL) { if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) { return (DCMD_USAGE); } } return (dump_states(adr, verbose, sp)); } /* * i_vhcilun() * * Internal routine for vhciguid() to print client info. */ static int i_vhcilun(uintptr_t addr, uint_t display_single_guid, char *guid) { scsi_vhci_lun_t value; struct dev_info dev_info_value; char string_val[MAXNAMELEN]; int found = 0; struct mdi_client ct_value; uintptr_t temp_addr; do { if (mdb_vread(&value, sizeof (scsi_vhci_lun_t), addr) != sizeof (scsi_vhci_lun_t)) { mdb_warn("sv_lun: Failed read on %l#r", addr); return (DCMD_ERR); } temp_addr = addr; addr = (uintptr_t)value.svl_hash_next; if (!get_mdbstr((uintptr_t)value.svl_lun_wwn, string_val)) { if (display_single_guid) { if (strcmp(string_val, guid) == 0) { found = 1; } else continue; } } mdb_printf("%t%l#r::print struct scsi_vhci_lun", temp_addr); if (mdb_vread(&dev_info_value, sizeof (struct dev_info), (uintptr_t)value.svl_dip) != sizeof (struct dev_info)) { mdb_warn("svl_dip: Failed read on %l#r", value.svl_dip); return (DCMD_ERR); } mdb_printf("\n%tGUID: %s\n", string_val); if (value.svl_active_pclass != NULL) { if (!get_mdbstr((uintptr_t)value.svl_active_pclass, string_val)) { mdb_printf("%tActv_cl: %s", string_val); } } else { mdb_printf(" No active pclass"); } if (display_single_guid) { mdb_printf(" (%l#r)", value.svl_active_pclass); } mdb_printf("\n%t%l#r::print struct mdi_client", dev_info_value.devi_mdi_client); if (value.svl_flags) { mdb_printf("\t"); dump_flags((unsigned long long)value.svl_flags, svlun_flags); } else { mdb_printf("\n"); } if (found) { mdiclient((uintptr_t)dev_info_value.devi_mdi_client, DCMD_ADDRSPEC, 0, 0); } else { if (mdb_vread(&ct_value, sizeof (struct mdi_client), (uintptr_t)dev_info_value.devi_mdi_client) != sizeof (struct mdi_client)) { mdb_warn("mdiclient: Failed read on %l#r", dev_info_value.devi_mdi_client); return (DCMD_ERR); } if (ct_value.ct_flags) { mdb_printf("\t"); dump_flags((unsigned long long) ct_value.ct_flags, client_flags); } mdb_printf("%t"); dump_state_str("LB", ct_value.ct_lb, client_lb_str); mdb_printf("\n"); } } while (addr && !found); return (DCMD_OK); } static void dump_mutex(kmutex_t m, char *name) { mdb_printf("%s is%s held\n", name, FT(m, uint64_t) == 0 ? " not" : ""); } static void dump_condvar(kcondvar_t c, char *name) { mdb_printf("Threads sleeping on %s = %d\n", name, (int)FT(c, ushort_t)); } static void dump_string(uintptr_t addr, char *name) { char string_val[MAXNAMELEN]; if (get_mdbstr(addr, string_val)) { return; } mdb_printf("%s: %s (%l#r)\n", name, string_val, addr); } /* ARGSUSED */ static int mpxio_walk_cb(uintptr_t addr, const void *data, void *cbdata) { mdb_printf("%t%l#r%s\n", addr, (char *)cbdata); return (0); } /* * 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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MDB_SCTP_SHOW_FLAGS 0x1 #define MDB_SCTP_DUMP_ADDRS 0x2 #define MDB_SCTP_SHOW_HASH 0x4 #define MDB_SCTP_SHOW_OUT 0x8 #define MDB_SCTP_SHOW_IN 0x10 #define MDB_SCTP_SHOW_MISC 0x20 #define MDB_SCTP_SHOW_RTT 0x40 #define MDB_SCTP_SHOW_STATS 0x80 #define MDB_SCTP_SHOW_FLOW 0x100 #define MDB_SCTP_SHOW_HDR 0x200 #define MDB_SCTP_SHOW_PMTUD 0x400 #define MDB_SCTP_SHOW_RXT 0x800 #define MDB_SCTP_SHOW_CONN 0x1000 #define MDB_SCTP_SHOW_CLOSE 0x2000 #define MDB_SCTP_SHOW_EXT 0x4000 #define MDB_SCTP_SHOW_ALL 0xffffffff /* * Copy from usr/src/uts/common/os/list.c. Should we have a generic * mdb list walker? */ #define list_object(a, node) ((void *)(((char *)node) - (a)->list_offset)) static int ns_to_stackid(uintptr_t kaddr) { netstack_t nss; if (mdb_vread(&nss, sizeof (nss), kaddr) == -1) { mdb_warn("failed to read netdstack info %p", kaddr); return (0); } return (nss.netstack_stackid); } int sctp_stacks_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("netstack", wsp) == -1) { mdb_warn("can't walk 'netstack'"); return (WALK_ERR); } return (WALK_NEXT); } int sctp_stacks_walk_step(mdb_walk_state_t *wsp) { uintptr_t kaddr; netstack_t nss; if (mdb_vread(&nss, sizeof (nss), wsp->walk_addr) == -1) { mdb_warn("can't read netstack at %p", wsp->walk_addr); return (WALK_ERR); } kaddr = (uintptr_t)nss.netstack_modules[NS_SCTP]; return (wsp->walk_callback(kaddr, wsp->walk_layer, wsp->walk_cbdata)); } static char * sctp_faddr_state(int state) { char *statestr; switch (state) { case SCTP_FADDRS_UNREACH: statestr = "Unreachable"; break; case SCTP_FADDRS_DOWN: statestr = "Down"; break; case SCTP_FADDRS_ALIVE: statestr = "Alive"; break; case SCTP_FADDRS_UNCONFIRMED: statestr = "Unconfirmed"; break; default: statestr = "Unknown"; break; } return (statestr); } /* ARGSUSED */ static int sctp_faddr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { sctp_faddr_t fa[1]; char *statestr; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(fa, sizeof (*fa), addr) == -1) { mdb_warn("cannot read fadder at %p", addr); return (DCMD_ERR); } statestr = sctp_faddr_state(fa->sf_state); mdb_printf("%%p\t%%N%\t%s%\n", addr, &fa->sf_faddr, statestr); mdb_printf("next\t\t%?p\tsaddr\t%N\n", fa->sf_next, &fa->sf_saddr); mdb_printf("rto\t\t%?d\tsrtt\t\t%?d\n", fa->sf_rto, fa->sf_srtt); mdb_printf("rttvar\t\t%?d\trtt_updates\t%?u\n", fa->sf_rttvar, fa->sf_rtt_updates); mdb_printf("strikes\t\t%?d\tmax_retr\t%?d\n", fa->sf_strikes, fa->sf_max_retr); mdb_printf("hb_expiry\t%?ld\thb_interval\t%?u\n", fa->sf_hb_expiry, fa->sf_hb_interval); mdb_printf("pmss\t\t%?u\tcwnd\t\t%?u\n", fa->sf_pmss, fa->sf_cwnd); mdb_printf("ssthresh\t%?u\tsuna\t\t%?u\n", fa->sf_ssthresh, fa->sf_suna); mdb_printf("pba\t\t%?u\tacked\t\t%?u\n", fa->sf_pba, fa->sf_acked); mdb_printf("lastactive\t%?ld\thb_secret\t%?#lx\n", fa->sf_lastactive, fa->sf_hb_secret); mdb_printf("rxt_unacked\t%?u\n", fa->sf_rxt_unacked); mdb_printf("timer_mp\t%?p\tixa\t\t%?p\n", fa->sf_timer_mp, fa->sf_ixa); mdb_printf("hb_enabled\t%?d\thb_pending\t%?d\n" "timer_running\t%?d\tdf\t\t%?d\n" "pmtu_discovered\t%?d\tisv4\t\t%?d\n" "retransmissions\t%?u\n", fa->sf_hb_enabled, fa->sf_hb_pending, fa->sf_timer_running, fa->sf_df, fa->sf_pmtu_discovered, fa->sf_isv4, fa->sf_T3expire); return (DCMD_OK); } static void print_set(sctp_set_t *sp) { mdb_printf("\tbegin\t%%?x%\t\tend\t%%?x%\n", sp->begin, sp->end); mdb_printf("\tnext\t%?p\tprev\t%?p\n", sp->next, sp->prev); } /* ARGSUSED */ static int sctp_set(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { sctp_set_t sp[1]; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(sp, sizeof (*sp), addr) == -1) return (DCMD_ERR); print_set(sp); return (DCMD_OK); } static void dump_sack_info(uintptr_t addr) { sctp_set_t sp[1]; while (addr != 0) { if (mdb_vread(sp, sizeof (*sp), addr) == -1) { mdb_warn("failed to read sctp_set at %p", addr); return; } addr = (uintptr_t)sp->next; print_set(sp); } } static int dump_msghdr(mblk_t *meta) { sctp_msg_hdr_t smh; if (mdb_vread(&smh, sizeof (smh), (uintptr_t)meta->b_rptr) == -1) return (-1); mdb_printf("%msg_hdr_t at \t%?p\tsentto\t%?p%\n", meta->b_rptr, SCTP_CHUNK_DEST(meta)); mdb_printf("\tttl\t%?ld\ttob\t%?ld\n", smh.smh_ttl, smh.smh_tob); mdb_printf("\tsid\t%?u\tssn\t%?u\n", smh.smh_sid, smh.smh_ssn); mdb_printf("\tppid\t%?u\tflags\t%?s\n", smh.smh_ppid, smh.smh_flags & MSG_UNORDERED ? "unordered" : " "); mdb_printf("\tcontext\t%?u\tmsglen\t%?d\n", smh.smh_context, smh.smh_msglen); return (0); } static int dump_datahdr(mblk_t *mp) { sctp_data_hdr_t sdc; uint16_t sdh_int16; uint32_t sdh_int32; if (mdb_vread(&sdc, sizeof (sdc), (uintptr_t)mp->b_rptr) == -1) return (-1); mdb_printf("%data_chunk_t \t%?p\tsentto\t%?p%\n", mp->b_rptr, SCTP_CHUNK_DEST(mp)); mdb_printf("\tsent\t%?d\t", SCTP_CHUNK_ISSENT(mp)?1:0); mdb_printf("retrans\t%?d\n", SCTP_CHUNK_WANT_REXMIT(mp)?1:0); mdb_printf("\tacked\t%?d\t", SCTP_CHUNK_ISACKED(mp)?1:0); mdb_printf("sackcnt\t%?u\n", SCTP_CHUNK_SACKCNT(mp)); mdb_nhconvert(&sdh_int16, &sdc.sdh_len, sizeof (sdc.sdh_len)); mdb_printf("\tlen\t%?d\t", sdh_int16); mdb_printf("BBIT=%d", SCTP_DATA_GET_BBIT(&sdc) == 0 ? 0 : 1); mdb_printf("EBIT=%d", SCTP_DATA_GET_EBIT(&sdc) == 0 ? 0 : 1); mdb_nhconvert(&sdh_int32, &sdc.sdh_tsn, sizeof (sdc.sdh_tsn)); mdb_nhconvert(&sdh_int16, &sdc.sdh_sid, sizeof (sdc.sdh_sid)); mdb_printf("\ttsn\t%?x\tsid\t%?hu\n", sdh_int32, sdh_int16); mdb_nhconvert(&sdh_int16, &sdc.sdh_ssn, sizeof (sdc.sdh_ssn)); mdb_nhconvert(&sdh_int32, &sdc.sdh_payload_id, sizeof (sdc.sdh_payload_id)); mdb_printf("\tssn\t%?hu\tppid\t%?d\n", sdh_int16, sdh_int32); return (0); } static int sctp_sent_list(mblk_t *addr) { mblk_t meta, mp; if (!addr) return (0); if (mdb_vread(&meta, sizeof (meta), (uintptr_t)addr) == -1) return (-1); for (;;) { dump_msghdr(&meta); if (meta.b_cont == NULL) { mdb_printf("No data chunks with message header!\n"); return (-1); } if (mdb_vread(&mp, sizeof (mp), (uintptr_t)meta.b_cont) == -1) { return (-1); } for (;;) { dump_datahdr(&mp); if (!mp.b_next) break; if (mdb_vread(&mp, sizeof (mp), (uintptr_t)(mp.b_next)) == -1) return (-1); } if (meta.b_next == NULL) break; if (mdb_vread(&meta, sizeof (meta), (uintptr_t)meta.b_next) == -1) return (-1); } return (0); } static int sctp_unsent_list(mblk_t *addr) { mblk_t meta; if (!addr) return (0); if (mdb_vread(&meta, sizeof (meta), (uintptr_t)addr) == -1) return (-1); for (;;) { dump_msghdr(&meta); if (meta.b_next == NULL) break; if (mdb_vread(&meta, sizeof (meta), (uintptr_t)meta.b_next) == -1) return (-1); } return (0); } /* ARGSUSED */ static int sctp_xmit_list(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { sctp_t sctp; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&sctp, sizeof (sctp), addr) == -1) return (DCMD_ERR); mdb_printf("%Chunkified TX list%\n"); if (sctp_sent_list(sctp.sctp_xmit_head) < 0) return (DCMD_ERR); mdb_printf("%Unchunkified TX list%\n"); if (sctp_unsent_list(sctp.sctp_xmit_unsent) < 0) return (DCMD_ERR); return (DCMD_OK); } /* ARGSUSED */ static int sctp_mdata_chunk(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { sctp_data_hdr_t dc; mblk_t mp; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&mp, sizeof (mp), addr) == -1) return (DCMD_ERR); if (mdb_vread(&dc, sizeof (dc), (uintptr_t)mp.b_rptr) == -1) return (DCMD_ERR); mdb_printf("%%-?p%tsn\t%?x\tsid\t%?hu\n", addr, dc.sdh_tsn, dc.sdh_sid); mdb_printf("%-?sssn\t%?hu\tppid\t%?x\n", "", dc.sdh_ssn, dc.sdh_payload_id); return (DCMD_OK); } /* ARGSUSED */ static int sctp_istr_msgs(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { mblk_t istrmp; mblk_t dmp; sctp_data_hdr_t dp; uintptr_t daddr; uintptr_t chaddr; boolean_t bbit; boolean_t ebit; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); do { if (mdb_vread(&istrmp, sizeof (istrmp), addr) == -1) return (DCMD_ERR); mdb_printf("\tistr mblk at %p: next: %?p\n" "\t\tprev: %?p\tcont: %?p\n", addr, istrmp.b_next, istrmp.b_prev, istrmp.b_cont); daddr = (uintptr_t)&istrmp; do { if (mdb_vread(&dmp, sizeof (dmp), daddr) == -1) break; chaddr = (uintptr_t)dmp.b_rptr; if (mdb_vread(&dp, sizeof (dp), chaddr) == -1) break; bbit = (SCTP_DATA_GET_BBIT(&dp) != 0); ebit = (SCTP_DATA_GET_EBIT(&dp) != 0); mdb_printf("\t\t\ttsn: %x bbit: %d ebit: %d\n", dp.sdh_tsn, bbit, ebit); daddr = (uintptr_t)dmp.b_cont; } while (daddr != 0); addr = (uintptr_t)istrmp.b_next; } while (addr != 0); return (DCMD_OK); } /* ARGSUSED */ static int sctp_reass_list(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { sctp_reass_t srp; mblk_t srpmp; sctp_data_hdr_t dp; mblk_t dmp; uintptr_t daddr; uintptr_t chaddr; boolean_t bbit, ebit; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); do { if (mdb_vread(&srpmp, sizeof (srpmp), addr) == -1) return (DCMD_ERR); if (mdb_vread(&srp, sizeof (srp), (uintptr_t)srpmp.b_datap->db_base) == -1) return (DCMD_ERR); mdb_printf("\treassembly mblk at %p: next: %?p\n" "\t\tprev: %?p\tcont: %?p\n", addr, srpmp.b_next, srpmp.b_prev, srpmp.b_cont); mdb_printf("\t\tssn: %hu\tneeded: %hu\tgot: %hu\ttail: %?p\n" "\t\tpartial_delivered: %s\n", srp.sr_ssn, srp.sr_needed, srp.sr_got, srp.sr_tail, srp.sr_partial_delivered ? "TRUE" : "FALSE"); /* display the contents of this ssn's reassemby list */ daddr = DB_TYPE(&srpmp) == M_CTL ? (uintptr_t)srpmp.b_cont : (uintptr_t)&srpmp; do { if (mdb_vread(&dmp, sizeof (dmp), daddr) == -1) break; chaddr = (uintptr_t)dmp.b_rptr; if (mdb_vread(&dp, sizeof (dp), chaddr) == -1) break; bbit = (SCTP_DATA_GET_BBIT(&dp) != 0); ebit = (SCTP_DATA_GET_EBIT(&dp) != 0); mdb_printf("\t\t\ttsn: %x bbit: %d ebit: %d\n", dp.sdh_tsn, bbit, ebit); daddr = (uintptr_t)dmp.b_cont; } while (daddr != 0); addr = (uintptr_t)srpmp.b_next; } while (addr != 0); return (DCMD_OK); } /* ARGSUSED */ static int sctp_uo_reass_list(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { sctp_data_hdr_t dp; mblk_t dmp; uintptr_t chaddr; boolean_t bbit; boolean_t ebit; boolean_t ubit; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); do { if (mdb_vread(&dmp, sizeof (dmp), addr) == -1) return (DCMD_ERR); mdb_printf("\treassembly mblk at %p: next: %?p\n" "\t\tprev: %?p\n", addr, dmp.b_next, dmp.b_prev); chaddr = (uintptr_t)dmp.b_rptr; if (mdb_vread(&dp, sizeof (dp), chaddr) == -1) break; bbit = (SCTP_DATA_GET_BBIT(&dp) != 0); ebit = (SCTP_DATA_GET_EBIT(&dp) != 0); ubit = (SCTP_DATA_GET_UBIT(&dp) != 0); mdb_printf("\t\t\tsid: %hu ssn: %hu tsn: %x " "flags: %x (U=%d B=%d E=%d)\n", dp.sdh_sid, dp.sdh_ssn, dp.sdh_tsn, dp.sdh_flags, ubit, bbit, ebit); addr = (uintptr_t)dmp.b_next; } while (addr != 0); return (DCMD_OK); } static int sctp_instr(uintptr_t addr, uint_t flags, int ac, const mdb_arg_t *av) { sctp_instr_t sip; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&sip, sizeof (sip), addr) == -1) return (DCMD_ERR); mdb_printf("%%-?p%\n\tmsglist\t%?p\tnmsgs\t%?d\n" "\tnextseq\t%?d\treass\t%?p\n", addr, sip.istr_msgs, sip.istr_nmsgs, sip.nextseq, sip.istr_reass); mdb_set_dot(addr + sizeof (sip)); return (sctp_reass_list((uintptr_t)sip.istr_reass, flags, ac, av)); } static const char * state2str(sctp_t *sctp) { switch (sctp->sctp_state) { case SCTPS_IDLE: return ("SCTPS_IDLE"); case SCTPS_BOUND: return ("SCTPS_BOUND"); case SCTPS_LISTEN: return ("SCTPS_LISTEN"); case SCTPS_COOKIE_WAIT: return ("SCTPS_COOKIE_WAIT"); case SCTPS_COOKIE_ECHOED: return ("SCTPS_COOKIE_ECHOED"); case SCTPS_ESTABLISHED: return ("SCTPS_ESTABLISHED"); case SCTPS_SHUTDOWN_PENDING: return ("SCTPS_SHUTDOWN_PENDING"); case SCTPS_SHUTDOWN_SENT: return ("SCTPS_SHUTDOWN_SENT"); case SCTPS_SHUTDOWN_RECEIVED: return ("SCTPS_SHUTDOWN_RECEIVED"); case SCTPS_SHUTDOWN_ACK_SENT: return ("SCTPS_SHUTDOWN_ACK_SENT"); default: return ("UNKNOWN STATE"); } } static void show_sctp_flags(sctp_t *sctp) { mdb_printf("\tunderstands_asconf\t%d\n", sctp->sctp_understands_asconf); mdb_printf("\tdebug\t\t\t%d\n", sctp->sctp_connp->conn_debug); mdb_printf("\tcchunk_pend\t\t%d\n", sctp->sctp_cchunk_pend); mdb_printf("\tdgram_errind\t\t%d\n", sctp->sctp_connp->conn_dgram_errind); mdb_printf("\tlinger\t\t\t%d\n", sctp->sctp_connp->conn_linger); if (sctp->sctp_lingering) return; mdb_printf("\tlingering\t\t%d\n", sctp->sctp_lingering); mdb_printf("\tloopback\t\t%d\n", sctp->sctp_loopback); mdb_printf("\tforce_sack\t\t%d\n", sctp->sctp_force_sack); mdb_printf("\tack_timer_runing\t%d\n", sctp->sctp_ack_timer_running); mdb_printf("\trecvdstaddr\t\t%d\n", sctp->sctp_connp->conn_recv_ancillary.crb_recvdstaddr); mdb_printf("\thwcksum\t\t\t%d\n", sctp->sctp_hwcksum); mdb_printf("\tunderstands_addip\t%d\n", sctp->sctp_understands_addip); mdb_printf("\tbound_to_all\t\t%d\n", sctp->sctp_bound_to_all); mdb_printf("\tcansleep\t\t%d\n", sctp->sctp_cansleep); mdb_printf("\tdetached\t\t%d\n", sctp->sctp_detached); mdb_printf("\tsend_adaptation\t\t%d\n", sctp->sctp_send_adaptation); mdb_printf("\trecv_adaptation\t\t%d\n", sctp->sctp_recv_adaptation); mdb_printf("\tndelay\t\t\t%d\n", sctp->sctp_ndelay); mdb_printf("\tcondemned\t\t%d\n", sctp->sctp_condemned); mdb_printf("\tchk_fast_rexmit\t\t%d\n", sctp->sctp_chk_fast_rexmit); mdb_printf("\tprsctp_aware\t\t%d\n", sctp->sctp_prsctp_aware); mdb_printf("\tlinklocal\t\t%d\n", sctp->sctp_linklocal); mdb_printf("\trexmitting\t\t%d\n", sctp->sctp_rexmitting); mdb_printf("\tzero_win_probe\t\t%d\n", sctp->sctp_zero_win_probe); mdb_printf("\trecvsndrcvinfo\t\t%d\n", sctp->sctp_recvsndrcvinfo); mdb_printf("\trecvassocevnt\t\t%d\n", sctp->sctp_recvassocevnt); mdb_printf("\trecvpathevnt\t\t%d\n", sctp->sctp_recvpathevnt); mdb_printf("\trecvsendfailevnt\t%d\n", sctp->sctp_recvsendfailevnt); mdb_printf("\trecvpeerevnt\t\t%d\n", sctp->sctp_recvpeererr); mdb_printf("\trecvchutdownevnt\t%d\n", sctp->sctp_recvshutdownevnt); mdb_printf("\trecvcpdnevnt\t\t%d\n", sctp->sctp_recvpdevnt); mdb_printf("\trecvcalevnt\t\t%d\n\n", sctp->sctp_recvalevnt); } /* * Given a sctp_saddr_ipif_t, print out its address. This assumes * that addr contains the sctp_addr_ipif_t structure already and this * function does not need to read it in. */ /* ARGSUSED */ static int print_saddr(uintptr_t ptr, const void *addr, void *cbdata) { sctp_saddr_ipif_t *saddr = (sctp_saddr_ipif_t *)addr; sctp_ipif_t ipif; char *statestr; /* Read in the sctp_ipif object */ if (mdb_vread(&ipif, sizeof (ipif), (uintptr_t)saddr->saddr_ipifp) == -1) { mdb_warn("cannot read ipif at %p", saddr->saddr_ipifp); return (WALK_ERR); } switch (ipif.sctp_ipif_state) { case SCTP_IPIFS_CONDEMNED: statestr = "Condemned"; break; case SCTP_IPIFS_INVALID: statestr = "Invalid"; break; case SCTP_IPIFS_DOWN: statestr = "Down"; break; case SCTP_IPIFS_UP: statestr = "Up"; break; default: statestr = "Unknown"; break; } mdb_printf("\t%p\t%N% (%s", saddr->saddr_ipifp, &ipif.sctp_ipif_saddr, statestr); if (saddr->saddr_ipif_dontsrc == 1) mdb_printf("/Dontsrc"); if (saddr->saddr_ipif_unconfirmed == 1) mdb_printf("/Unconfirmed"); if (saddr->saddr_ipif_delete_pending == 1) mdb_printf("/DeletePending"); mdb_printf(")\n"); mdb_printf("\t\t\tid %d zoneid %d IPIF flags %x\n", ipif.sctp_ipif_id, ipif.sctp_ipif_zoneid, ipif.sctp_ipif_flags); return (WALK_NEXT); } /* * Given a sctp_faddr_t, print out its address. This assumes that * addr contains the sctp_faddr_t structure already and this function * does not need to read it in. */ static int print_faddr(uintptr_t ptr, const void *addr, void *cbdata) { char *statestr; sctp_faddr_t *faddr = (sctp_faddr_t *)addr; int *i = cbdata; statestr = sctp_faddr_state(faddr->sf_state); mdb_printf("\t%d:\t%N\t%?p (%s)\n", (*i)++, &faddr->sf_faddr, ptr, statestr); return (WALK_NEXT); } int sctp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { sctp_t sctps, *sctp; conn_t conns, *connp; int i; uint_t opts = 0; uint_t paddr = 0; in_port_t lport, fport; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&sctps, sizeof (sctps), addr) == -1) { mdb_warn("failed to read sctp_t at: %p\n", addr); return (DCMD_ERR); } sctp = &sctps; if (mdb_vread(&conns, sizeof (conns), (uintptr_t)sctp->sctp_connp) == -1) { mdb_warn("failed to read conn_t at: %p\n", sctp->sctp_connp); return (DCMD_ERR); } connp = &conns; connp->conn_sctp = sctp; sctp->sctp_connp = connp; if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, MDB_SCTP_SHOW_ALL, &opts, 'f', MDB_OPT_SETBITS, MDB_SCTP_SHOW_FLAGS, &opts, 'h', MDB_OPT_SETBITS, MDB_SCTP_SHOW_HASH, &opts, 'o', MDB_OPT_SETBITS, MDB_SCTP_SHOW_OUT, &opts, 'i', MDB_OPT_SETBITS, MDB_SCTP_SHOW_IN, &opts, 'm', MDB_OPT_SETBITS, MDB_SCTP_SHOW_MISC, &opts, 'r', MDB_OPT_SETBITS, MDB_SCTP_SHOW_RTT, &opts, 'S', MDB_OPT_SETBITS, MDB_SCTP_SHOW_STATS, &opts, 'F', MDB_OPT_SETBITS, MDB_SCTP_SHOW_FLOW, &opts, 'H', MDB_OPT_SETBITS, MDB_SCTP_SHOW_HDR, &opts, 'p', MDB_OPT_SETBITS, MDB_SCTP_SHOW_PMTUD, &opts, 'R', MDB_OPT_SETBITS, MDB_SCTP_SHOW_RXT, &opts, 'C', MDB_OPT_SETBITS, MDB_SCTP_SHOW_CONN, &opts, 'c', MDB_OPT_SETBITS, MDB_SCTP_SHOW_CLOSE, &opts, 'e', MDB_OPT_SETBITS, MDB_SCTP_SHOW_EXT, &opts, 'P', MDB_OPT_SETBITS, 1, &paddr, 'd', MDB_OPT_SETBITS, MDB_SCTP_DUMP_ADDRS, &opts, NULL) != argc) { return (DCMD_USAGE); } /* non-verbose faddrs, suitable for pipelines to sctp_faddr */ if (paddr != 0) { sctp_faddr_t faddr, *fp; for (fp = sctp->sctp_faddrs; fp != NULL; fp = faddr.sf_next) { if (mdb_vread(&faddr, sizeof (faddr), (uintptr_t)fp) == -1) { mdb_warn("failed to read faddr at %p", fp); return (DCMD_ERR); } mdb_printf("%p\n", fp); } return (DCMD_OK); } mdb_nhconvert(&lport, &connp->conn_lport, sizeof (lport)); mdb_nhconvert(&fport, &connp->conn_fport, sizeof (fport)); mdb_printf("%%p% %22s S=%-6hu D=%-6hu% STACK=%d ZONE=%d%", addr, state2str(sctp), lport, fport, ns_to_stackid((uintptr_t)connp->conn_netstack), connp->conn_zoneid); if (sctp->sctp_faddrs) { sctp_faddr_t faddr; if (mdb_vread(&faddr, sizeof (faddr), (uintptr_t)sctp->sctp_faddrs) != -1) mdb_printf("% %N%", &faddr.sf_faddr); } mdb_printf("\n"); if (opts & MDB_SCTP_DUMP_ADDRS) { mdb_printf("%Local and Peer Addresses%\n"); /* Display source addresses */ mdb_printf("nsaddrs\t\t%?d\n", sctp->sctp_nsaddrs); (void) mdb_pwalk("sctp_walk_saddr", print_saddr, NULL, addr); /* Display peer addresses */ mdb_printf("nfaddrs\t\t%?d\n", sctp->sctp_nfaddrs); i = 1; (void) mdb_pwalk("sctp_walk_faddr", print_faddr, &i, addr); mdb_printf("lastfaddr\t%?p\tprimary\t\t%?p\n", sctp->sctp_lastfaddr, sctp->sctp_primary); mdb_printf("current\t\t%?p\tlastdata\t%?p\n", sctp->sctp_current, sctp->sctp_lastdata); } if (opts & MDB_SCTP_SHOW_OUT) { mdb_printf("%Outbound Data%\n"); mdb_printf("xmit_head\t%?p\txmit_tail\t%?p\n", sctp->sctp_xmit_head, sctp->sctp_xmit_tail); mdb_printf("xmit_unsent\t%?p\txmit_unsent_tail%?p\n", sctp->sctp_xmit_unsent, sctp->sctp_xmit_unsent_tail); mdb_printf("xmit_unacked\t%?p\n", sctp->sctp_xmit_unacked); mdb_printf("unacked\t\t%?u\tunsent\t\t%?ld\n", sctp->sctp_unacked, sctp->sctp_unsent); mdb_printf("ltsn\t\t%?x\tlastack_rxd\t%?x\n", sctp->sctp_ltsn, sctp->sctp_lastack_rxd); mdb_printf("recovery_tsn\t%?x\tadv_pap\t\t%?x\n", sctp->sctp_recovery_tsn, sctp->sctp_adv_pap); mdb_printf("num_ostr\t%?hu\tostrcntrs\t%?p\n", sctp->sctp_num_ostr, sctp->sctp_ostrcntrs); mdb_printf("pad_mp\t\t%?p\terr_chunks\t%?p\n", sctp->sctp_pad_mp, sctp->sctp_err_chunks); mdb_printf("err_len\t\t%?u\n", sctp->sctp_err_len); mdb_printf("%Default Send Parameters%\n"); mdb_printf("def_stream\t%?u\tdef_flags\t%?x\n", sctp->sctp_def_stream, sctp->sctp_def_flags); mdb_printf("def_ppid\t%?x\tdef_context\t%?x\n", sctp->sctp_def_ppid, sctp->sctp_def_context); mdb_printf("def_timetolive\t%?u\n", sctp->sctp_def_timetolive); } if (opts & MDB_SCTP_SHOW_IN) { mdb_printf("%Inbound Data%\n"); mdb_printf("sack_info\t%?p\tsack_gaps\t%?d\n", sctp->sctp_sack_info, sctp->sctp_sack_gaps); dump_sack_info((uintptr_t)sctp->sctp_sack_info); mdb_printf("ftsn\t\t%?x\tlastacked\t%?x\n", sctp->sctp_ftsn, sctp->sctp_lastacked); mdb_printf("istr_nmsgs\t%?d\tsack_toggle\t%?d\n", sctp->sctp_istr_nmsgs, sctp->sctp_sack_toggle); mdb_printf("ack_mp\t\t%?p\n", sctp->sctp_ack_mp); mdb_printf("num_istr\t%?hu\tinstr\t\t%?p\n", sctp->sctp_num_istr, sctp->sctp_instr); mdb_printf("unord_reass\t%?p\n", sctp->sctp_uo_frags); } if (opts & MDB_SCTP_SHOW_RTT) { mdb_printf("%RTT Tracking%\n"); mdb_printf("rtt_tsn\t\t%?x\tout_time\t%?ld\n", sctp->sctp_rtt_tsn, sctp->sctp_out_time); } if (opts & MDB_SCTP_SHOW_FLOW) { mdb_printf("%Flow Control%\n"); mdb_printf("tconn_sndbuf\t%?d\n" "conn_sndlowat\t%?d\tfrwnd\t\t%?u\n" "rwnd\t\t%?u\tlast advertised rwnd\t%?u\n" "rxqueued\t%?u\tcwnd_max\t%?u\n", connp->conn_sndbuf, connp->conn_sndlowat, sctp->sctp_frwnd, sctp->sctp_rwnd, sctp->sctp_arwnd, sctp->sctp_rxqueued, sctp->sctp_cwnd_max); } if (opts & MDB_SCTP_SHOW_HDR) { mdb_printf("%Composite Headers%\n"); mdb_printf("iphc\t\t%?p\tiphc6\t\t%?p\n" "iphc_len\t%?d\tiphc6_len\t%?d\n" "hdr_len\t\t%?d\thdr6_len\t%?d\n" "ipha\t\t%?p\tip6h\t\t%?p\n" "ip_hdr_len\t%?d\tip_hdr6_len\t%?d\n" "sctph\t\t%?p\tsctph6\t\t%?p\n" "lvtag\t\t%?x\tfvtag\t\t%?x\n", sctp->sctp_iphc, sctp->sctp_iphc6, sctp->sctp_iphc_len, sctp->sctp_iphc6_len, sctp->sctp_hdr_len, sctp->sctp_hdr6_len, sctp->sctp_ipha, sctp->sctp_ip6h, sctp->sctp_ip_hdr_len, sctp->sctp_ip_hdr6_len, sctp->sctp_sctph, sctp->sctp_sctph6, sctp->sctp_lvtag, sctp->sctp_fvtag); } if (opts & MDB_SCTP_SHOW_PMTUD) { mdb_printf("%PMTUd%\n"); mdb_printf("last_mtu_probe\t%?ld\tmtu_probe_intvl\t%?ld\n" "mss\t\t%?u\n", sctp->sctp_last_mtu_probe, sctp->sctp_mtu_probe_intvl, sctp->sctp_mss); } if (opts & MDB_SCTP_SHOW_RXT) { mdb_printf("%Retransmit Info%\n"); mdb_printf("cookie_mp\t%?p\tstrikes\t\t%?d\n" "max_init_rxt\t%?d\tpa_max_rxt\t%?d\n" "pp_max_rxt\t%?d\trto_max\t\t%?u\n" "rto_min\t\t%?u\trto_initial\t%?u\n" "init_rto_max\t%?u\n" "rxt_nxttsn\t%?u\trxt_maxtsn\t%?u\n", sctp->sctp_cookie_mp, sctp->sctp_strikes, sctp->sctp_max_init_rxt, sctp->sctp_pa_max_rxt, sctp->sctp_pp_max_rxt, sctp->sctp_rto_max, sctp->sctp_rto_min, sctp->sctp_rto_initial, sctp->sctp_rto_max_init, sctp->sctp_rxt_nxttsn, sctp->sctp_rxt_maxtsn); } if (opts & MDB_SCTP_SHOW_CONN) { mdb_printf("%Connection State%\n"); mdb_printf("last_secret_update%?ld\n", sctp->sctp_last_secret_update); mdb_printf("secret\t\t"); for (i = 0; i < SCTP_SECRET_LEN; i++) { if (i % 2 == 0) mdb_printf("0x%02x", sctp->sctp_secret[i]); else mdb_printf("%02x ", sctp->sctp_secret[i]); } mdb_printf("\n"); mdb_printf("old_secret\t"); for (i = 0; i < SCTP_SECRET_LEN; i++) { if (i % 2 == 0) mdb_printf("0x%02x", sctp->sctp_old_secret[i]); else mdb_printf("%02x ", sctp->sctp_old_secret[i]); } mdb_printf("\n"); } if (opts & MDB_SCTP_SHOW_STATS) { mdb_printf("%Stats Counters%\n"); mdb_printf("opkts\t\t%?llu\tobchunks\t%?llu\n" "odchunks\t%?llu\toudchunks\t%?llu\n" "rxtchunks\t%?llu\tT1expire\t%?lu\n" "T2expire\t%?lu\tT3expire\t%?lu\n" "msgcount\t%?llu\tprsctpdrop\t%?llu\n" "AssocStartTime\t%?lu\n", sctp->sctp_opkts, sctp->sctp_obchunks, sctp->sctp_odchunks, sctp->sctp_oudchunks, sctp->sctp_rxtchunks, sctp->sctp_T1expire, sctp->sctp_T2expire, sctp->sctp_T3expire, sctp->sctp_msgcount, sctp->sctp_prsctpdrop, sctp->sctp_assoc_start_time); mdb_printf("ipkts\t\t%?llu\tibchunks\t%?llu\n" "idchunks\t%?llu\tiudchunks\t%?llu\n" "fragdmsgs\t%?llu\treassmsgs\t%?llu\n", sctp->sctp_ipkts, sctp->sctp_ibchunks, sctp->sctp_idchunks, sctp->sctp_iudchunks, sctp->sctp_fragdmsgs, sctp->sctp_reassmsgs); } if (opts & MDB_SCTP_SHOW_HASH) { mdb_printf("%Hash Tables%\n"); mdb_printf("conn_hash_next\t%?p\t", sctp->sctp_conn_hash_next); mdb_printf("conn_hash_prev\t%?p\n", sctp->sctp_conn_hash_prev); mdb_printf("listen_hash_next%?p\t", sctp->sctp_listen_hash_next); mdb_printf("listen_hash_prev%?p\n", sctp->sctp_listen_hash_prev); mdb_nhconvert(&lport, &connp->conn_lport, sizeof (lport)); mdb_printf("[ listen_hash bucket\t%?d ]\n", SCTP_LISTEN_HASH(lport)); mdb_printf("conn_tfp\t%?p\t", sctp->sctp_conn_tfp); mdb_printf("listen_tfp\t%?p\n", sctp->sctp_listen_tfp); mdb_printf("bind_hash\t%?p\tptpbhn\t\t%?p\n", sctp->sctp_bind_hash, sctp->sctp_ptpbhn); mdb_printf("bind_lockp\t%?p\n", sctp->sctp_bind_lockp); mdb_printf("[ bind_hash bucket\t%?d ]\n", SCTP_BIND_HASH(lport)); } if (opts & MDB_SCTP_SHOW_CLOSE) { mdb_printf("%Cleanup / Close%\n"); mdb_printf("shutdown_faddr\t%?p\tclient_errno\t%?d\n" "lingertime\t%?d\trefcnt\t\t%?hu\n", sctp->sctp_shutdown_faddr, sctp->sctp_client_errno, connp->conn_lingertime, sctp->sctp_refcnt); } if (opts & MDB_SCTP_SHOW_MISC) { mdb_printf("%Miscellaneous%\n"); mdb_printf("bound_if\t%?u\theartbeat_mp\t%?p\n" "family\t\t%?u\tipversion\t%?hu\n" "hb_interval\t%?u\tautoclose\t%?d\n" "active\t\t%?ld\ttx_adaptation_code%?x\n" "rx_adaptation_code%?x\ttimer_mp\t%?p\n" "partial_delivery_point\t%?d\n", connp->conn_bound_if, sctp->sctp_heartbeat_mp, connp->conn_family, connp->conn_ipversion, sctp->sctp_hb_interval, sctp->sctp_autoclose, sctp->sctp_active, sctp->sctp_tx_adaptation_code, sctp->sctp_rx_adaptation_code, sctp->sctp_timer_mp, sctp->sctp_pd_point); } if (opts & MDB_SCTP_SHOW_EXT) { mdb_printf("%Extensions and Reliable Ctl Chunks%\n"); mdb_printf("cxmit_list\t%?p\tlcsn\t\t%?x\n" "fcsn\t\t%?x\n", sctp->sctp_cxmit_list, sctp->sctp_lcsn, sctp->sctp_fcsn); } if (opts & MDB_SCTP_SHOW_FLAGS) { mdb_printf("%Flags%\n"); show_sctp_flags(sctp); } return (DCMD_OK); } typedef struct fanout_walk_data { int index; int size; uintptr_t sctp; sctp_tf_t *fanout; uintptr_t (*getnext)(sctp_t *); } fanout_walk_data_t; typedef struct fanout_init { const char *nested_walker_name; size_t offset; /* for what used to be a symbol */ int (*getsize)(sctp_stack_t *); uintptr_t (*getnext)(sctp_t *); } fanout_init_t; static uintptr_t listen_next(sctp_t *sctp) { return ((uintptr_t)sctp->sctp_listen_hash_next); } /* ARGSUSED */ static int listen_size(sctp_stack_t *sctps) { return (SCTP_LISTEN_FANOUT_SIZE); } static uintptr_t conn_next(sctp_t *sctp) { return ((uintptr_t)sctp->sctp_conn_hash_next); } static int conn_size(sctp_stack_t *sctps) { int size; uintptr_t kaddr; kaddr = (uintptr_t)&sctps->sctps_conn_hash_size; if (mdb_vread(&size, sizeof (size), kaddr) == -1) { mdb_warn("can't read 'sctps_conn_hash_size' at %p", kaddr); return (1); } return (size); } static uintptr_t bind_next(sctp_t *sctp) { return ((uintptr_t)sctp->sctp_bind_hash); } /* ARGSUSED */ static int bind_size(sctp_stack_t *sctps) { return (SCTP_BIND_FANOUT_SIZE); } static uintptr_t find_next_hash_item(fanout_walk_data_t *fw) { sctp_tf_t tf; sctp_t sctp; /* first try to continue down the hash chain */ if (fw->sctp != 0) { /* try to get next in hash chain */ if (mdb_vread(&sctp, sizeof (sctp), fw->sctp) == -1) { mdb_warn("failed to read sctp at %p", fw->sctp); return (0); } fw->sctp = fw->getnext(&sctp); if (fw->sctp != 0) return (fw->sctp); else /* end of chain; go to next bucket */ fw->index++; } /* find a new hash chain, traversing the buckets */ for (; fw->index < fw->size; fw->index++) { /* read the current hash line for an sctp */ if (mdb_vread(&tf, sizeof (tf), (uintptr_t)(fw->fanout + fw->index)) == -1) { mdb_warn("failed to read tf at %p", fw->fanout + fw->index); return (0); } if (tf.tf_sctp != NULL) { /* start of a new chain */ fw->sctp = (uintptr_t)tf.tf_sctp; return (fw->sctp); } } return (0); } static int fanout_stack_walk_init(mdb_walk_state_t *wsp) { fanout_walk_data_t *lw; fanout_init_t *fi = wsp->walk_arg; sctp_stack_t *sctps = (sctp_stack_t *)wsp->walk_addr; uintptr_t kaddr; if (mdb_vread(&kaddr, sizeof (kaddr), wsp->walk_addr + fi->offset) == -1) { mdb_warn("can't read sctp fanout at %p", wsp->walk_addr + fi->offset); return (WALK_ERR); } lw = mdb_alloc(sizeof (*lw), UM_SLEEP); lw->index = 0; lw->size = fi->getsize(sctps); lw->sctp = 0; lw->fanout = (sctp_tf_t *)kaddr; lw->getnext = fi->getnext; if ((wsp->walk_addr = find_next_hash_item(lw)) == 0) { return (WALK_DONE); } wsp->walk_data = lw; return (WALK_NEXT); } static int fanout_stack_walk_step(mdb_walk_state_t *wsp) { fanout_walk_data_t *fw = wsp->walk_data; uintptr_t addr = wsp->walk_addr; sctp_t sctp; int status; if (mdb_vread(&sctp, sizeof (sctp), addr) == -1) { mdb_warn("failed to read sctp at %p", addr); return (WALK_DONE); } status = wsp->walk_callback(addr, &sctp, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); if ((wsp->walk_addr = find_next_hash_item(fw)) == 0) return (WALK_DONE); return (WALK_NEXT); } static void fanout_stack_walk_fini(mdb_walk_state_t *wsp) { fanout_walk_data_t *fw = wsp->walk_data; mdb_free(fw, sizeof (*fw)); } int fanout_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("sctp_stacks", wsp) == -1) { mdb_warn("can't walk 'sctp_stacks'"); return (WALK_ERR); } return (WALK_NEXT); } int fanout_walk_step(mdb_walk_state_t *wsp) { fanout_init_t *fi = wsp->walk_arg; if (mdb_pwalk(fi->nested_walker_name, wsp->walk_callback, wsp->walk_cbdata, wsp->walk_addr) == -1) { mdb_warn("couldn't walk '%s'for address %p", fi->nested_walker_name, wsp->walk_addr); return (WALK_ERR); } return (WALK_NEXT); } int sctps_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("sctp_stacks", wsp) == -1) { mdb_warn("can't walk 'sctp_stacks'"); return (WALK_ERR); } return (WALK_NEXT); } int sctps_walk_step(mdb_walk_state_t *wsp) { uintptr_t kaddr; kaddr = wsp->walk_addr + OFFSETOF(sctp_stack_t, sctps_g_list); if (mdb_pwalk("list", wsp->walk_callback, wsp->walk_cbdata, kaddr) == -1) { mdb_warn("couldn't walk 'list' for address %p", kaddr); return (WALK_ERR); } return (WALK_NEXT); } static int sctp_walk_faddr_init(mdb_walk_state_t *wsp) { sctp_t sctp; if (wsp->walk_addr == 0) return (WALK_ERR); if (mdb_vread(&sctp, sizeof (sctp), wsp->walk_addr) == -1) { mdb_warn("failed to read sctp at %p", wsp->walk_addr); return (WALK_ERR); } if ((wsp->walk_addr = (uintptr_t)sctp.sctp_faddrs) != 0) return (WALK_NEXT); else return (WALK_DONE); } static int sctp_walk_faddr_step(mdb_walk_state_t *wsp) { uintptr_t faddr_ptr = wsp->walk_addr; sctp_faddr_t sctp_faddr; int status; if (mdb_vread(&sctp_faddr, sizeof (sctp_faddr_t), faddr_ptr) == -1) { mdb_warn("failed to read sctp_faddr_t at %p", faddr_ptr); return (WALK_ERR); } status = wsp->walk_callback(faddr_ptr, &sctp_faddr, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); if ((faddr_ptr = (uintptr_t)sctp_faddr.sf_next) == 0) { return (WALK_DONE); } else { wsp->walk_addr = faddr_ptr; return (WALK_NEXT); } } /* * Helper structure for sctp_walk_saddr. It stores the sctp_t being walked, * the current index to the sctp_saddrs[], and the current count of the * sctp_saddr_ipif_t list. */ typedef struct { sctp_t sctp; int hash_index; int cur_cnt; } saddr_walk_t; static int sctp_walk_saddr_init(mdb_walk_state_t *wsp) { sctp_t *sctp; int i; saddr_walk_t *swalker; if (wsp->walk_addr == 0) return (WALK_ERR); swalker = mdb_alloc(sizeof (saddr_walk_t), UM_SLEEP); sctp = &swalker->sctp; if (mdb_vread(sctp, sizeof (sctp_t), wsp->walk_addr) == -1) { mdb_warn("failed to read sctp at %p", wsp->walk_addr); mdb_free(swalker, sizeof (saddr_walk_t)); return (WALK_ERR); } /* Find the first source address. */ for (i = 0; i < SCTP_IPIF_HASH; i++) { if (sctp->sctp_saddrs[i].ipif_count > 0) { list_t *addr_list; addr_list = &sctp->sctp_saddrs[i].sctp_ipif_list; wsp->walk_addr = (uintptr_t)list_object(addr_list, addr_list->list_head.list_next); /* Recode the current info */ swalker->hash_index = i; swalker->cur_cnt = 1; wsp->walk_data = swalker; return (WALK_NEXT); } } return (WALK_DONE); } static int sctp_walk_saddr_step(mdb_walk_state_t *wsp) { uintptr_t saddr_ptr = wsp->walk_addr; sctp_saddr_ipif_t saddr; saddr_walk_t *swalker; sctp_t *sctp; int status; int i, j; if (mdb_vread(&saddr, sizeof (sctp_saddr_ipif_t), saddr_ptr) == -1) { mdb_warn("failed to read sctp_saddr_ipif_t at %p", saddr_ptr); return (WALK_ERR); } status = wsp->walk_callback(saddr_ptr, &saddr, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); swalker = (saddr_walk_t *)wsp->walk_data; sctp = &swalker->sctp; i = swalker->hash_index; j = swalker->cur_cnt; /* * If there is still a source address in the current list, return it. * Otherwise, go to the next list in the sctp_saddrs[]. */ if (j++ < sctp->sctp_saddrs[i].ipif_count) { wsp->walk_addr = (uintptr_t)saddr.saddr_ipif.list_next; swalker->cur_cnt = j; return (WALK_NEXT); } else { list_t *lst; for (i = i + 1; i < SCTP_IPIF_HASH; i++) { if (sctp->sctp_saddrs[i].ipif_count > 0) { lst = &sctp->sctp_saddrs[i].sctp_ipif_list; wsp->walk_addr = (uintptr_t)list_object( lst, lst->list_head.list_next); swalker->hash_index = i; swalker->cur_cnt = 1; return (WALK_NEXT); } } } return (WALK_DONE); } static void sctp_walk_saddr_fini(mdb_walk_state_t *wsp) { saddr_walk_t *swalker = (saddr_walk_t *)wsp->walk_data; mdb_free(swalker, sizeof (saddr_walk_t)); } typedef struct ill_walk_data { sctp_ill_hash_t ills[SCTP_ILL_HASH]; uint32_t count; } ill_walk_data_t; typedef struct ipuf_walk_data { sctp_ipif_hash_t ipifs[SCTP_IPIF_HASH]; uint32_t count; } ipif_walk_data_t; int sctp_ill_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("sctp_stacks", wsp) == -1) { mdb_warn("can't walk 'sctp_stacks'"); return (WALK_ERR); } return (WALK_NEXT); } int sctp_ill_walk_step(mdb_walk_state_t *wsp) { if (mdb_pwalk("sctp_stack_walk_ill", wsp->walk_callback, wsp->walk_cbdata, wsp->walk_addr) == -1) { mdb_warn("couldn't walk 'sctp_stack_walk_ill' for addr %p", wsp->walk_addr); return (WALK_ERR); } return (WALK_NEXT); } /* * wsp->walk_addr is the address of sctps_ill_list */ static int sctp_stack_ill_walk_init(mdb_walk_state_t *wsp) { ill_walk_data_t iw; intptr_t i; uintptr_t kaddr, uaddr; size_t offset; kaddr = wsp->walk_addr + OFFSETOF(sctp_stack_t, sctps_ills_count); if (mdb_vread(&iw.count, sizeof (iw.count), kaddr) == -1) { mdb_warn("can't read sctps_ills_count at %p", kaddr); return (WALK_ERR); } kaddr = wsp->walk_addr + OFFSETOF(sctp_stack_t, sctps_g_ills); if (mdb_vread(&kaddr, sizeof (kaddr), kaddr) == -1) { mdb_warn("can't read scpts_g_ills %p", kaddr); return (WALK_ERR); } if (mdb_vread(&iw.ills, sizeof (iw.ills), kaddr) == -1) { mdb_warn("failed to read 'sctps_g_ills'"); return (0); } /* Find the first ill. */ for (i = 0; i < SCTP_ILL_HASH; i++) { if (iw.ills[i].ill_count > 0) { uaddr = (uintptr_t)&iw.ills[i].sctp_ill_list; offset = uaddr - (uintptr_t)&iw.ills; if (mdb_pwalk("list", wsp->walk_callback, wsp->walk_cbdata, kaddr+offset) == -1) { mdb_warn("couldn't walk 'list' for address %p", kaddr); return (WALK_ERR); } } } return (WALK_DONE); } static int sctp_stack_ill_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } int sctp_ipif_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("sctp_stacks", wsp) == -1) { mdb_warn("can't walk 'sctp_stacks'"); return (WALK_ERR); } return (WALK_NEXT); } int sctp_ipif_walk_step(mdb_walk_state_t *wsp) { if (mdb_pwalk("sctp_stack_walk_ipif", wsp->walk_callback, wsp->walk_cbdata, wsp->walk_addr) == -1) { mdb_warn("couldn't walk 'sctp_stack_walk_ipif' for addr %p", wsp->walk_addr); return (WALK_ERR); } return (WALK_NEXT); } /* * wsp->walk_addr is the address of sctps_ipif_list */ static int sctp_stack_ipif_walk_init(mdb_walk_state_t *wsp) { ipif_walk_data_t iw; intptr_t i; uintptr_t kaddr, uaddr; size_t offset; kaddr = wsp->walk_addr + OFFSETOF(sctp_stack_t, sctps_g_ipifs_count); if (mdb_vread(&iw.count, sizeof (iw.count), kaddr) == -1) { mdb_warn("can't read sctps_g_ipifs_count at %p", kaddr); return (WALK_ERR); } kaddr = wsp->walk_addr + OFFSETOF(sctp_stack_t, sctps_g_ipifs); if (mdb_vread(&kaddr, sizeof (kaddr), kaddr) == -1) { mdb_warn("can't read scpts_g_ipifs %p", kaddr); return (WALK_ERR); } if (mdb_vread(&iw.ipifs, sizeof (iw.ipifs), kaddr) == -1) { mdb_warn("failed to read 'sctps_g_ipifs'"); return (0); } /* Find the first ipif. */ for (i = 0; i < SCTP_IPIF_HASH; i++) { if (iw.ipifs[i].ipif_count > 0) { uaddr = (uintptr_t)&iw.ipifs[i].sctp_ipif_list; offset = uaddr - (uintptr_t)&iw.ipifs; if (mdb_pwalk("list", wsp->walk_callback, wsp->walk_cbdata, kaddr+offset) == -1) { mdb_warn("couldn't walk 'list' for address %p", kaddr); return (WALK_ERR); } } } return (WALK_DONE); } static int sctp_stack_ipif_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * Initialization function for the per CPU SCTP stats counter walker of a given * SCTP stack. */ int sctps_sc_walk_init(mdb_walk_state_t *wsp) { sctp_stack_t sctps; if (wsp->walk_addr == 0) return (WALK_ERR); if (mdb_vread(&sctps, sizeof (sctps), wsp->walk_addr) == -1) { mdb_warn("failed to read sctp_stack_t at %p", wsp->walk_addr); return (WALK_ERR); } if (sctps.sctps_sc_cnt == 0) return (WALK_DONE); /* * Store the sctp_stack_t pointer in walk_data. The stepping function * used it to calculate if the end of the counter has reached. */ wsp->walk_data = (void *)wsp->walk_addr; wsp->walk_addr = (uintptr_t)sctps.sctps_sc; return (WALK_NEXT); } /* * Stepping function for the per CPU SCTP stats counterwalker. */ int sctps_sc_walk_step(mdb_walk_state_t *wsp) { int status; sctp_stack_t sctps; sctp_stats_cpu_t *stats; char *next, *end; if (mdb_vread(&sctps, sizeof (sctps), (uintptr_t)wsp->walk_data) == -1) { mdb_warn("failed to read sctp_stack_t at %p", wsp->walk_addr); return (WALK_ERR); } if (mdb_vread(&stats, sizeof (stats), wsp->walk_addr) == -1) { mdb_warn("failed ot read sctp_stats_cpu_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback((uintptr_t)stats, &stats, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); next = (char *)wsp->walk_addr + sizeof (sctp_stats_cpu_t *); end = (char *)sctps.sctps_sc + sctps.sctps_sc_cnt * sizeof (sctp_stats_cpu_t *); if (next >= end) return (WALK_DONE); wsp->walk_addr = (uintptr_t)next; return (WALK_NEXT); } static void sctp_help(void) { mdb_printf("Print information for a given SCTP sctp_t\n\n"); mdb_printf("Options:\n"); mdb_printf("\t-a\t All the information\n"); mdb_printf("\t-f\t Flags\n"); mdb_printf("\t-h\t Hash Tables\n"); mdb_printf("\t-o\t Outbound Data\n"); mdb_printf("\t-i\t Inbound Data\n"); mdb_printf("\t-m\t Miscellaneous Information\n"); mdb_printf("\t-r\t RTT Tracking\n"); mdb_printf("\t-S\t Stats Counters\n"); mdb_printf("\t-F\t Flow Control\n"); mdb_printf("\t-H\t Composite Headers\n"); mdb_printf("\t-p\t PMTUD\n"); mdb_printf("\t-R\t Retransmit Information\n"); mdb_printf("\t-C\t Connection State\n"); mdb_printf("\t-c\t Cleanup / Close\n"); mdb_printf("\t-e\t Extensions and Reliable Control Chunks\n"); mdb_printf("\t-d\t Local and Peer addresses\n"); mdb_printf("\t-P\t Peer addresses\n"); } static const mdb_dcmd_t dcmds[] = { { "sctp", ":[-afhoimrSFHpRCcedP]", "display sctp control structure", sctp, sctp_help }, { "sctp_set", ":", "display a SCTP set", sctp_set }, { "sctp_faddr", ":", "display a faddr", sctp_faddr }, { "sctp_istr_msgs", ":", "display msg list on an instream", sctp_istr_msgs }, { "sctp_mdata_chunk", ":", "display a data chunk in an mblk", sctp_mdata_chunk }, { "sctp_xmit_list", ":", "display sctp xmit lists", sctp_xmit_list }, { "sctp_instr", ":", "display instr", sctp_instr }, { "sctp_reass_list", ":", "display reass list", sctp_reass_list }, { "sctp_uo_reass_list", ":", "display un-ordered reass list", sctp_uo_reass_list }, { NULL } }; static const fanout_init_t listen_fanout_init = { "sctp_stack_listen_fanout", OFFSETOF(sctp_stack_t, sctps_listen_fanout), listen_size, listen_next }; static const fanout_init_t conn_fanout_init = { "sctp_stack_conn_fanout", OFFSETOF(sctp_stack_t, sctps_conn_fanout), conn_size, conn_next }; static const fanout_init_t bind_fanout_init = { "sctp_stack_bind_fanout", OFFSETOF(sctp_stack_t, sctps_bind_fanout), bind_size, bind_next }; static const mdb_walker_t walkers[] = { { "sctps", "walk the full chain of sctps for all stacks", sctps_walk_init, sctps_walk_step, NULL }, { "sctp_listen_fanout", "walk the sctp listen fanout for all stacks", fanout_walk_init, fanout_walk_step, NULL, (void *)&listen_fanout_init }, { "sctp_conn_fanout", "walk the sctp conn fanout for all stacks", fanout_walk_init, fanout_walk_step, NULL, (void *)&conn_fanout_init }, { "sctp_bind_fanout", "walk the sctp bind fanout for all stacks", fanout_walk_init, fanout_walk_step, NULL, (void *)&bind_fanout_init }, { "sctp_stack_listen_fanout", "walk the sctp listen fanout for one stack", fanout_stack_walk_init, fanout_stack_walk_step, fanout_stack_walk_fini, (void *)&listen_fanout_init }, { "sctp_stack_conn_fanout", "walk the sctp conn fanout for one stack", fanout_stack_walk_init, fanout_stack_walk_step, fanout_stack_walk_fini, (void *)&conn_fanout_init }, { "sctp_stack_bind_fanout", "walk the sctp bind fanoutfor one stack", fanout_stack_walk_init, fanout_stack_walk_step, fanout_stack_walk_fini, (void *)&bind_fanout_init }, { "sctp_walk_faddr", "walk the peer address list of a given sctp_t", sctp_walk_faddr_init, sctp_walk_faddr_step, NULL }, { "sctp_walk_saddr", "walk the local address list of a given sctp_t", sctp_walk_saddr_init, sctp_walk_saddr_step, sctp_walk_saddr_fini }, { "sctp_walk_ill", "walk the sctp_g_ills list for all stacks", sctp_ill_walk_init, sctp_ill_walk_step, NULL }, { "sctp_walk_ipif", "walk the sctp_g_ipif list for all stacks", sctp_ipif_walk_init, sctp_ipif_walk_step, NULL }, { "sctp_stack_walk_ill", "walk the sctp_g_ills list for one stack", sctp_stack_ill_walk_init, sctp_stack_ill_walk_step, NULL }, { "sctp_stack_walk_ipif", "walk the sctp_g_ipif list for one stack", sctp_stack_ipif_walk_init, sctp_stack_ipif_walk_step, NULL }, { "sctps_sc", "walk all the per CPU stats counters of a sctp_stack_t", sctps_sc_walk_init, sctps_sc_walk_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2022 RackTop Systems, Inc. */ #include #include #include #include #include /* Represents global soft state data in walk_step, walk_init */ #define SD_DATA(param) ((sd_str_p)wsp->walk_data)->param /* Represents global soft state data in callback and related routines */ #define SD_DATA_IN_CBACK(param) ((sd_str_p)walk_data)->param #define SUCCESS WALK_NEXT #define FAIL WALK_ERR /* * Primary attribute struct for buf extensions. */ struct __ddi_xbuf_attr { kmutex_t xa_mutex; size_t xa_allocsize; uint32_t xa_pending; /* call to xbuf_iostart() is iminent */ uint32_t xa_active_limit; uint32_t xa_active_count; uint32_t xa_active_lowater; struct buf *xa_headp; /* FIFO buf queue head ptr */ struct buf *xa_tailp; /* FIFO buf queue tail ptr */ kmutex_t xa_reserve_mutex; uint32_t xa_reserve_limit; uint32_t xa_reserve_count; void *xa_reserve_headp; void (*xa_strategy)(struct buf *, void *, void *); void *xa_attr_arg; timeout_id_t xa_timeid; taskq_t *xa_tq; }; /* * Provides soft state information like the number of elements, pointer * to soft state elements etc */ typedef struct i_ddi_soft_state sd_state_str_t, *sd_state_str_ptr; /* structure to store soft state statistics */ typedef struct sd_str { void *sd_state; uintptr_t current_root; int current_list_count; int valid_root_count; int silent; sd_state_str_t sd_state_data; } sd_str_t, *sd_str_p; /* * Function: buf_avforw_walk_init * * Description: MDB calls the init function to initiate the walk, * in response to mdb_walk() function called by the * dcmd 'buf_avforw' or when the user executes the * walk dcmd 'address::walk buf_avforw'. * * Arguments: new mdb_walk_state_t structure. A new structure is * created for each walk, so that multiple instances of * the walker can be active simultaneously. */ static int buf_avforw_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("buffer address required with the command\n"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (buf_t), UM_SLEEP); return (WALK_NEXT); } /* * Function: buf_avforw_walk_step * * Description: The step function is invoked by the walker during each * iteration. Its primary job is to determine the address * of the next 'buf_avforw' object, read in the local copy * of this object, call the callback 'buf_callback' function, * and return its status. The iteration is terminated when * the walker encounters a null queue pointer which signifies * end of queue. * * Arguments: mdb_walk_state_t structure */ static int buf_avforw_walk_step(mdb_walk_state_t *wsp) { int status; /* * if walk_addr is null then it effectively means an end of all * buf structures, hence end the iterations. */ if (wsp->walk_addr == 0) { return (WALK_DONE); } /* * Read the contents of the current object, invoke the callback * and assign the next objects address to mdb_walk_state_t structure. */ if (mdb_vread(wsp->walk_data, sizeof (buf_t), wsp->walk_addr) == -1) { mdb_warn("failed to read buf at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((buf_t *)wsp->walk_data)->av_forw); return (status); } /* * Function: buf_callback * * Description: This is the callback function called by the 'buf_avforw' * walker when 'buf_avforw' dcmd is invoked. * It is called during each walk step. It displays the contents * of the current object (addr) passed to it by the step * function. It also prints the header and footer during the * first and the last iteration of the walker. * * Arguments: addr -> current buf_avforw objects address. * walk_data -> private storage for the walker. * buf_entries -> private data for the callback. It represents * the count of objects processed so far. */ static int buf_callback(uintptr_t addr, const void *walk_data, void *buf_entries) { int *count = (int *)buf_entries; /* * If this is the first invocation of the command, print a * header line for the output that will follow. */ if (*count == 0) { mdb_printf("============================\n"); mdb_printf("Walking buf list via av_forw\n"); mdb_printf("============================\n"); } /* * read the object and print the contents. */ mdb_set_dot(addr); mdb_eval("$av_forw == NULL) { mdb_printf("---------------------------\n"); mdb_printf("Processed %d Buf entries\n", *count); mdb_printf("---------------------------\n"); return (WALK_DONE); } return (WALK_NEXT); } /* * Function: buf_avforw_walk_fini * * Description: The buf_avforw_walk_fini is called when the walk is terminated * in response to WALK_DONE in buf_avforw_walk_step. It frees * the walk_data structure. * * Arguments: mdb_walk_state_t structure */ static void buf_avforw_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (buf_t)); } /* * Function: dump_xbuf_attr * * Description: Prints the contents of Xbuf queue. * * Arguments: object contents pointer and address. */ static void dump_xbuf_attr(struct __ddi_xbuf_attr *xba_ptr, uintptr_t mem_addr) { mdb_printf("0x%8lx:\tmutex\t\tallocsize\tpending\n", mem_addr + offsetof(struct __ddi_xbuf_attr, xa_mutex)); mdb_printf(" \t%lx\t\t%d\t\t%d\n", xba_ptr->xa_mutex._opaque[0], xba_ptr->xa_allocsize, xba_ptr->xa_pending); mdb_printf("0x%8lx:\tactive_limit\tactive_count\tactive_lowater\n", mem_addr + offsetof(struct __ddi_xbuf_attr, xa_active_limit)); mdb_printf(" \t%lx\t\t%lx\t\t%lx\n", xba_ptr->xa_active_limit, xba_ptr->xa_active_count, xba_ptr->xa_active_lowater); mdb_printf("0x%8lx:\theadp\t\ttailp\n", mem_addr + offsetof(struct __ddi_xbuf_attr, xa_headp)); mdb_printf(" \t%lx%c\t%lx\n", xba_ptr->xa_headp, (xba_ptr->xa_headp == 0?'\t':' '), xba_ptr->xa_tailp); mdb_printf( "0x%8lx:\treserve_mutex\treserve_limit\treserve_count\treserve_headp\n", mem_addr + offsetof(struct __ddi_xbuf_attr, xa_reserve_mutex)); mdb_printf(" \t%lx\t\t%lx\t\t%lx\t\t%lx\n", xba_ptr->xa_reserve_mutex._opaque[0], xba_ptr->xa_reserve_limit, xba_ptr->xa_reserve_count, xba_ptr->xa_reserve_headp); mdb_printf("0x%8lx:\ttimeid\t\ttq\n", mem_addr + offsetof(struct __ddi_xbuf_attr, xa_timeid)); mdb_printf(" \t%lx%c\t%lx\n", xba_ptr->xa_timeid, (xba_ptr->xa_timeid == 0?'\t':' '), xba_ptr->xa_tq); } /* * Function: init_softstate_members * * Description: Initialize mdb_walk_state_t structure with either 'sd' or * 'ssd' related information. * * Arguments: new mdb_walk_state_t structure */ static int init_softstate_members(mdb_walk_state_t *wsp) { wsp->walk_data = mdb_alloc(sizeof (sd_str_t), UM_SLEEP); /* * store the soft state statistics variables like non-zero * soft state entries, base address, actual count of soft state * processed etc. */ SD_DATA(sd_state) = (sd_state_str_ptr)wsp->walk_addr; SD_DATA(current_list_count) = 0; SD_DATA(valid_root_count) = 0; if (mdb_vread((void *)&SD_DATA(sd_state_data), sizeof (sd_state_str_t), wsp->walk_addr) == -1) { mdb_warn("failed to sd_state at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(SD_DATA(sd_state_data.array)); SD_DATA(current_root) = wsp->walk_addr; return (WALK_NEXT); } #if (!defined(__fibre)) /* * Function: sd_state_walk_init * * Description: MDB calls the init function to initiate the walk, * in response to mdb_walk() function called by the * dcmd 'sd_state' or when the user executes the * walk dcmd '::walk sd_state'. * The init function initializes the walker to either * the user specified address or the default kernel * 'sd_state' pointer. * * Arguments: new mdb_walk_state_t structure */ static int sd_state_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "sd_state") == -1) { mdb_warn("failed to read 'sd_state'"); return (WALK_ERR); } return (init_softstate_members(wsp)); } #else /* * Function: ssd_state_walk_init * * Description: MDB calls the init function to initiate the walk, * in response to mdb_walk() function called by the * dcmd 'ssd_state' or when the user executes the * walk dcmd '::walk ssd_state'. * The init function initializes the walker to either * the user specified address or the default kernel * 'ssd_state' pointer. * * Arguments: new mdb_walk_state_t structure */ static int ssd_state_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == (uintptr_t)NULL && mdb_readvar(&wsp->walk_addr, "ssd_state") == -1) { mdb_warn("failed to read 'ssd_state'"); return (WALK_ERR); } return (init_softstate_members(wsp)); } #endif /* * Function: sd_state_walk_step * * Description: The step function is invoked by the walker during each * iteration. Its primary job is to determine the address * of the next 'soft state' object, read in the local copy * of this object, call the callback 'sd_callback' function, * and return its status. The iteration is terminated when * the soft state counter equals the total soft state count * obtained initially. * * Arguments: mdb_walk_state_t structure */ static int sd_state_walk_step(mdb_walk_state_t *wsp) { int status; void *tp; /* * If all the soft state entries have been processed then stop * future iterations. */ if (SD_DATA(current_list_count) >= SD_DATA(sd_state_data.n_items)) { return (WALK_DONE); } /* * read the object contents, invoke the callback and set the * mdb_walk_state_t structure to the next object. */ if (mdb_vread(&tp, sizeof (void *), wsp->walk_addr) == -1) { mdb_warn("failed to read at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback((uintptr_t)tp, wsp->walk_data, wsp->walk_cbdata); if (tp != 0) { /* Count the number of non-zero un entries. */ SD_DATA(valid_root_count++); } wsp->walk_addr += sizeof (void *); SD_DATA(current_list_count++); return (status); } /* * Function: sd_state_walk_fini * * Description: The sd_state_walk_fini is called when the walk is terminated * in response to WALK_DONE in sd_state_walk_step. It frees * the walk_data structure. * * Arguments: mdb_walk_state_t structure */ static void sd_state_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (sd_str_t)); } /* * Function: process_sdlun_waitq * * Description: Iterate over the wait Q members of the soft state. * Print the contents of each member. In case of silent mode * the contents are avoided and only the address is printed. * * Arguments: starting queue address, print mode. */ static int process_sdlun_waitq(uintptr_t walk_addr, int silent) { uintptr_t rootBuf; buf_t currentBuf; int sdLunQ_count = 0; rootBuf = walk_addr; if (!silent) { mdb_printf("\nUN WAIT Q:\n"); mdb_printf("----------\n"); } mdb_printf("UN wait Q head: %lx\n", rootBuf); while (rootBuf) { /* Process the device's cmd. wait Q */ if (!silent) { mdb_printf("UN WAIT Q list entry:\n"); mdb_printf("------------------\n"); } if (mdb_vread(¤tBuf, sizeof (buf_t), (uintptr_t)rootBuf) == -1) { mdb_warn("failed to read buf at %p", (uintptr_t)rootBuf); return (FAIL); } if (!silent) { mdb_set_dot(rootBuf); mdb_eval("$= (SD_DATA_IN_CBACK(sd_state_data.n_items) - 1)) { mdb_printf("---------------------------\n"); mdb_printf("Processed %d UN softstate entries\n", SD_DATA_IN_CBACK(valid_root_count)); mdb_printf("---------------------------\n"); } } /* * Function: sd_callback * * Description: This is the callback function called by the * 'sd_state/ssd_state' walker when 'sd_state/ssd_state' dcmd * invokes the walker. * It is called during each walk step. It displays the contents * of the current soft state object (addr) passed to it by the * step function. It also prints the header and footer during the * first and the last step of the walker. * The contents of the soft state also includes various queues * it includes like Xbuf, semo_close, sdlun_waitq. * * Arguments: addr -> current soft state objects address. * walk_data -> private storage for the walker. * flg_silent -> private data for the callback. It represents * the silent mode of operation. */ static int sd_callback(uintptr_t addr, const void *walk_data, void *flg_silent) { struct sd_lun sdLun; int silent = *(int *)flg_silent; /* * If this is the first invocation of the command, print a * header line for the output that will follow. */ if (SD_DATA_IN_CBACK(current_list_count) == 0) { mdb_printf("walk_addr = %lx\n", SD_DATA_IN_CBACK(sd_state)); mdb_printf("walking sd_state units via ptr: %lx\n", SD_DATA_IN_CBACK(current_root)); mdb_printf("%d entries in sd_state table\n", SD_DATA_IN_CBACK(sd_state_data.n_items)); } mdb_printf("\nun %d: %lx\n", SD_DATA_IN_CBACK(current_list_count), addr); mdb_printf("--------------\n"); /* if null soft state iterate over to next one */ if (addr == 0) { print_footer(walk_data); return (SUCCESS); } /* * For each buf, we need to read the sd_lun struct, * and then print out its contents, and get the next. */ else if (mdb_vread(&sdLun, sizeof (struct sd_lun), (uintptr_t)addr) == sizeof (sdLun)) { if (!silent) { mdb_set_dot(addr); mdb_eval("$ user specified address or NULL if no address is * specified. * flags -> integer reflecting whether an address was specified, * or if it was invoked by the walker in a loop etc. * argc -> the number of arguments supplied to the dcmd. * argv -> the actual arguments supplied by the user. */ /*ARGSUSED*/ static int dcmd_sd_state(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct sd_lun sdLun; uint_t silent = 0; /* Enable the silent mode if '-s' option specified the user */ if (mdb_getopts(argc, argv, 's', MDB_OPT_SETBITS, TRUE, &silent, NULL) != argc) { return (DCMD_USAGE); } /* * If no address is specified on the command line, perform * a global walk invoking 'sd_state' walker. If a particular address * is specified then print the soft state and its queues. */ if (!(flags & DCMD_ADDRSPEC)) { mdb_walk("sd_state", sd_callback, (void *)&silent); return (DCMD_OK); } else { mdb_printf("\nun: %lx\n", addr); mdb_printf("--------------\n"); /* read the sd_lun struct and print the contents */ if (mdb_vread(&sdLun, sizeof (struct sd_lun), (uintptr_t)addr) == sizeof (sdLun)) { if (!silent) { mdb_set_dot(addr); mdb_eval("$ user specified address or NULL if no address is * specified. * flags -> integer reflecting whether an address was specified, * or if it was invoked by the walker in a loop etc. * argc -> the number of arguments supplied to the dcmd. * argv -> the actual arguments supplied by the user. */ /*ARGSUSED*/ static int dcmd_ssd_state(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct sd_lun sdLun; uint_t silent = 0; /* Enable the silent mode if '-s' option specified the user */ if (mdb_getopts(argc, argv, 's', MDB_OPT_SETBITS, TRUE, &silent, NULL) != argc) { return (DCMD_USAGE); } /* * If no address is specified on the command line, perform * a global walk invoking 'sd_state' walker. If a particular address * is specified then print the soft state and its queues. */ if (!(flags & DCMD_ADDRSPEC)) { mdb_walk("ssd_state", sd_callback, (void *)&silent); return (DCMD_OK); } else { mdb_printf("\nun: %lx\n", addr); mdb_printf("--------------\n"); /* read the sd_lun struct and print the contents */ if (mdb_vread(&sdLun, sizeof (struct sd_lun), (uintptr_t)addr) == sizeof (sdLun)) { if (!silent) { mdb_set_dot(addr); mdb_eval("$ user specified address. * flags -> integer reflecting whether an address was specified, * or if it was invoked by the walker in a loop etc. * argc -> the number of arguments supplied to the dcmd. * argv -> the actual arguments supplied by the user. */ /*ARGSUSED*/ static int dcmd_buf_avforw(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int buf_entries = 0; /* it does not take any arguments */ if (argc != 0) return (DCMD_USAGE); /* * If no address was specified on the command line, print the * error msg, else scan and * print out all the buffers available by invoking buf_avforw walker. */ if ((flags & DCMD_ADDRSPEC)) { mdb_pwalk("buf_avforw", buf_callback, (void *)&buf_entries, addr); return (DCMD_OK); } else { mdb_printf("buffer address required with the command\n"); } return (DCMD_USAGE); } /* * MDB module linkage information: * * List of structures describing our dcmds, a list of structures * describing our walkers, and a function named _mdb_init to return a pointer * to our module information. */ static const mdb_dcmd_t dcmds[] = { { "buf_avforw", ":", "buf_t list via av_forw", dcmd_buf_avforw}, #if (!defined(__fibre)) { "sd_state", "[-s]", "sd soft state list", dcmd_sd_state}, #else { "ssd_state", "[-s]", "ssd soft state list", dcmd_ssd_state}, #endif { NULL } }; static const mdb_walker_t walkers[] = { { "buf_avforw", "walk list of buf_t structures via av_forw", buf_avforw_walk_init, buf_avforw_walk_step, buf_avforw_walk_fini }, #if (!defined(__fibre)) { "sd_state", "walk all sd soft state queues", sd_state_walk_init, sd_state_walk_step, sd_state_walk_fini }, #else { "ssd_state", "walk all ssd soft state queues", ssd_state_walk_init, sd_state_walk_step, sd_state_walk_fini }, #endif { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; /* * Function: _mdb_init * * Description: Returns mdb_modinfo_t structure which provides linkage and * module identification information to the debugger. * * Arguments: void */ const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2023 Nexenta by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define INVALID_OPT_VAL ((uintptr_t)(-1)) /* ---- Forward references ---- */ static int smartpqi(uintptr_t, uint_t, int, const mdb_arg_t *); static void smartpqi_help(void); static const mdb_dcmd_t dcmds[] = { { "smartpqi", "-c [-v]", "display smartpqi state", smartpqi, smartpqi_help }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; static void smartpqi_help(void) { mdb_printf("%s", "-c display the state for and the no." " of devices attached.\n" "-v provide detailed information about each device attached.\n"); } char * bool_to_str(int v) { return (v ? "TRUE" : "FALSE"); } const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } static void display_sense_data(struct scsi_extended_sense data) { mdb_printf(" SCSI sense data es_key 0x%x ", data.es_key); mdb_printf(" es_add_code 0x%x ", data.es_add_code); mdb_printf(" es_qual_code 0x%x\n", data.es_qual_code); } static void display_scsi_status(struct scsi_arq_status scsi_status) { mdb_printf(" req pkt status\t\t\t0x%x\n", *(int8_t *)&scsi_status.sts_rqpkt_status); mdb_printf(" req pkt resid\t\t\t0x%x\n", scsi_status.sts_rqpkt_resid); mdb_printf(" req pkt state\t\t\t%d\n", scsi_status.sts_rqpkt_state); mdb_printf(" req pkt state\t\t\t%d\n", scsi_status.sts_rqpkt_statistics); if (scsi_status.sts_status.sts_chk) display_sense_data(scsi_status.sts_sensedata); } static char * cmd_action_str(pqi_cmd_action_t action, char *tmpstr, int tmplen) { switch (action) { case PQI_CMD_UNINIT: return ("UNINIT"); case PQI_CMD_QUEUE: return ("QUEUE"); case PQI_CMD_START: return ("START"); case PQI_CMD_CMPLT: return ("COMPLETE"); case PQI_CMD_TIMEOUT: return ("TIMEOUT"); case PQI_CMD_FAIL: return ("FAIL"); default: (void) mdb_snprintf(tmpstr, tmplen, "BAD ACTION <0x%x>", action); return (tmpstr); } } struct scsi_key_strings pqi_cmds[] = { SCSI_CMDS_KEY_STRINGS, BMIC_READ, "BMIC Read", BMIC_WRITE, "BMIC Write", CISS_REPORT_LOG, "CISS Report Logical", CISS_REPORT_PHYS, "CISS Report Physical", -1, NULL }; static char * mdb_cdb_to_str(uint8_t scsi_cmd, char *tmpstr, int tmplen) { int i = 0; while (pqi_cmds[i].key != -1) { if (scsi_cmd == pqi_cmds[i].key) return ((char *)pqi_cmds[i].message); i++; } (void) mdb_snprintf(tmpstr, tmplen, "", scsi_cmd); return (tmpstr); } static void display_cdb(uint8_t *cdb) { int i, tmplen; char tmpstr[64]; tmplen = sizeof (tmpstr); mdb_printf("CDB %s", mdb_cdb_to_str(cdb[0], tmpstr, tmplen)); for (i = 1; i < SCSI_CDB_SIZE; i++) mdb_printf(":%02x", cdb[i]); mdb_printf("\n"); } static char * pqi_iu_type_to_str(int val) { switch (val) { case PQI_RESPONSE_IU_RAID_PATH_IO_SUCCESS: return ("Success"); case PQI_RESPONSE_IU_AIO_PATH_IO_SUCCESS: return ("AIO Success"); case PQI_RESPONSE_IU_GENERAL_MANAGEMENT: return ("General"); case PQI_RESPONSE_IU_TASK_MANAGEMENT: return ("Task"); case PQI_RESPONSE_IU_RAID_PATH_IO_ERROR: return ("IO Error"); case PQI_RESPONSE_IU_AIO_PATH_IO_ERROR: return ("AIO IO Error"); case PQI_RESPONSE_IU_AIO_PATH_DISABLED: return ("AIO Path Disabled"); default: return ("UNHANDLED"); } } static void display_raid_error_info(uintptr_t error_info) { struct pqi_raid_error_info info; int cnt; if (error_info == 0) return; if ((cnt = mdb_vread((void *)&info, sizeof (struct pqi_raid_error_info), (uintptr_t)error_info)) != sizeof (struct pqi_raid_error_info)) { mdb_warn(" Unable to read Raid error info(%d,%p)\n", cnt, error_info); return; } mdb_printf(" ---- Raid error info ----\n"); mdb_printf(" data_in_result %d\n", info.data_in_result); mdb_printf(" data_out_result %d\n", info.data_out_result); mdb_printf(" status %d\n", info.status); mdb_printf(" status_qualifier %d\n", info.status_qualifier); mdb_printf(" sense_data_length %d\n", info.sense_data_length); mdb_printf(" response_data_length %d\n", info.response_data_length); mdb_printf(" data_in_transferred %d\n", info.data_in_transferred); mdb_printf(" data_out_transferred %d\n", info.data_out_transferred); } static void display_io_request(pqi_io_request_t *io) { if (io == (pqi_io_request_t *)0) return; mdb_printf(" ---- Command IO request ----\n"); mdb_printf(" io_refcount\t\t\t\t%d\n", io->io_refcount); mdb_printf(" io_index\t\t\t\t%d\n", io->io_index); mdb_printf(" io_gen\t\t\t\t%d\n", io->io_gen); mdb_printf(" io_serviced\t\t\t\t%s\n", bool_to_str(io->io_serviced)); mdb_printf(" io_raid_bypass\t\t\t%d\n", io->io_raid_bypass); mdb_printf(" io_status\t\t\t\t%d\n", io->io_status); mdb_printf(" io_iu\t\t\t\t0x%p\n", io->io_iu); mdb_printf(" io_pi\t\t\t\t%d\n", io->io_pi); mdb_printf(" io_iu_type\t\t\t\t%s\n", pqi_iu_type_to_str(io->io_iu_type)); display_raid_error_info((uintptr_t)io->io_error_info); } static int display_cmd(pqi_cmd_t *cmdp) { int read_cnt, tmplen; char tmpstr[64]; pqi_io_request_t pqi_io; tmplen = sizeof (tmpstr); display_cdb(cmdp->pc_cdb); mdb_printf(" cur action\t\t\t%s\n", cmd_action_str(cmdp->pc_cur_action, tmpstr, tmplen)); mdb_printf(" last action\t\t\t%s\n", cmd_action_str(cmdp->pc_last_action, tmpstr, tmplen)); display_scsi_status(cmdp->pc_cmd_scb); mdb_printf(" pc_dma_count\t\t\t%d\n", cmdp->pc_dma_count); mdb_printf(" pc_flags\t\t\t\t0x%x\n", cmdp->pc_flags); mdb_printf(" pc_statuslen\t\t\t%d\n", cmdp->pc_statuslen); mdb_printf(" pc_cmdlen\t\t\t\t%d\n", cmdp->pc_cmdlen); if (cmdp->pc_io_rqst == (pqi_io_request_t *)0) return (DCMD_OK); read_cnt = mdb_vread(&pqi_io, sizeof (pqi_io_request_t), (uintptr_t)cmdp->pc_io_rqst); if (read_cnt == -1) { mdb_warn(" Error reading IO structure address 0x%p - " "skipping diplay of IO commands\n", cmdp->pc_io_rqst); return (DCMD_ERR); } else if (read_cnt != sizeof (pqi_io_request_t)) { mdb_warn(" cannot read IO structure count %d at0x%p - " "skipping diplay of IO commands\n", read_cnt, cmdp->pc_io_rqst); return (DCMD_ERR); } else { display_io_request(&pqi_io); } return (DCMD_OK); } /* * listp - the pointer to the head of the linked list * sz - size of the lest element to be read * current - pointer to current list_node structure in local storage */ static list_node_t * pqi_list_next(list_node_t *listp, size_t sz, void *structp, list_node_t *current) { int rval; if (current->list_next == (list_node_t *)listp) return ((list_node_t *)NULL); if (current->list_next == (list_node_t *)NULL) return ((list_node_t *)NULL); if (current->list_next == current->list_prev) return ((list_node_t *)NULL); rval = mdb_vread(structp, sz, (uintptr_t)current->list_next); if (rval == -1 || (size_t)rval != sz) { mdb_warn("Error reading a next list element so " "skipping display of remaining elements\n"); return ((list_node_t *)NULL); } return (current); } static void pqi_list_head(list_t list, uint8_t *drvp, size_t offset, list_node_t **list_anchor) { *list_anchor = (list_node_t *)(drvp + offset + offsetof(list_t, list_head)); if (*list_anchor == list.list_head.list_next) { *list_anchor = NULL; } } static int pqi_device_list_head(list_t s_devnodes, uint8_t *addr, list_node_t **dev_head, struct pqi_device *dev) { int rval; pqi_list_head(s_devnodes, addr, offsetof(struct pqi_state, s_devnodes), dev_head); if (*dev_head == NULL) return (DCMD_ERR); rval = mdb_vread((void *)dev, sizeof (struct pqi_device), (uintptr_t)s_devnodes.list_head.list_next); if (rval == -1) { mdb_warn(" cannot read device list head (0x%p)\n", *dev_head); return (DCMD_ERR); } return (DCMD_OK); } static int pqi_cmd_list_head(list_t cmds, uint8_t *addr, list_node_t **cmd_head, struct pqi_cmd *cmdp) { int rval; pqi_list_head(cmds, (uint8_t *)addr, offsetof(struct pqi_device, pd_cmd_list), cmd_head); /* Read in the first entry of the command list */ rval = mdb_vread(cmdp, sizeof (struct pqi_cmd), (uintptr_t)cmds.list_head.list_next); if (rval == -1) { mdb_warn(" cannot read initial entry in " "command list (0x%p)\n", cmds.list_head.list_next); } return (rval); } static char * pqi_get_guid(char *pd_guid) { static char myguid[41]; if (mdb_vread(myguid, sizeof (myguid) - 1, (uintptr_t)pd_guid) == -1) myguid[0] = '\0'; return (myguid); } static void display_device_info(pqi_device_t *dev) { char str[40]; mdb_printf("-- Device pd_target %d --\n", dev->pd_target); mdb_printf("pd_devtype\t\t\t\t%d\n", dev->pd_devtype); mdb_printf("device pd_flags\t\t\t\t0x%x\n", dev->pd_flags); mdb_printf("pd_active_cmds\t\t\t\t%d\n", dev->pd_active_cmds); mdb_printf("pd_dip\t\t\t\t\t0x%p\n", dev->pd_dip); mdb_printf("pd_pip\t\t\t\t\t0x%p\n", dev->pd_pip); mdb_printf("pd_pip_offlined\t\t\t\t0x%p\n", dev->pd_pip_offlined); mdb_printf("pd_online\t\t\t\t%s\n", bool_to_str(dev->pd_online)); mdb_printf("pd_scanned\t\t\t\t%s\n", bool_to_str(dev->pd_scanned)); mdb_printf("pd_phys_dev\t\t\t\t%s\n", bool_to_str(dev->pd_phys_dev)); mdb_printf("pd_external_raid\t\t\t%s\n", bool_to_str(dev->pd_external_raid)); mdb_printf("pd_pd_aio_enabled\t\t\t%s\n", bool_to_str(dev->pd_aio_enabled)); mdb_printf("GUID\t\t\t\t\t%s\n", pqi_get_guid(dev->pd_guid)); (void) strncpy(str, (char *)(dev->pd_vendor), sizeof (dev->pd_vendor)); str[sizeof (dev->pd_vendor)] = '\0'; mdb_printf("pd_vendor\t\t\t\t%s\n", str); (void) strncpy(str, (char *)(dev->pd_model), sizeof (dev->pd_model)); str[sizeof (dev->pd_model)] = '\0'; mdb_printf("pd_model\t\t\t\t%s\n", str); } /* * display device info: number of drives attached, number of commands running on * each device, drive data and command data. */ static void pqi_display_devices(list_t s_devnodes, pqi_state_t *drvp, uint_t dev_verbose) { int rval; int dev_count = 0; struct pqi_device d; pqi_device_t *next_dp; pqi_cmd_t *cmdp; struct list_node *list_head; struct list_node *d_list_head; struct list_node *dev_current; struct list_node *cmd_current; struct pqi_device *d_drvrp; /* driver addr of device list entry */ mdb_printf("---- Devices for controller (0x%p) ----\n", ((uint8_t *)drvp) + offsetof(struct pqi_state, s_devnodes)); rval = pqi_device_list_head(s_devnodes, (uint8_t *)drvp, &d_list_head, &d); if (d_list_head == NULL) { mdb_printf("Number of devices %d\n", dev_count); return; } cmdp = (pqi_cmd_t *)mdb_alloc(sizeof (struct pqi_cmd), UM_SLEEP|UM_GC); next_dp = &d; dev_current = (list_node_t *)((uint8_t *)(&d) + offsetof(struct pqi_device, pd_list)); d_drvrp = (pqi_device_t *)(s_devnodes.list_head.list_next); while (dev_current != NULL) { if (dev_verbose) { display_device_info((pqi_device_t *)&d); /* now display command information */ rval = pqi_cmd_list_head(d.pd_cmd_list, (uint8_t *)d_drvrp, &list_head, cmdp); if (rval == -1) { mdb_warn("unable to read the command list head" " for device %d\n", d.pd_target); list_head = NULL; } if (list_head != NULL) { mdb_printf(" ---- Commands for device %d" " (0x%p) ----\n", next_dp->pd_target, list_head); cmd_current = (list_node_t *)((uint8_t *)(cmdp) + offsetof(struct pqi_cmd, pc_list)); while (cmd_current != NULL) { rval = display_cmd(cmdp); if (rval != DCMD_OK) { mdb_warn("Display of commands" " aborted (%d)\n", rval); break; } cmd_current = pqi_list_next(list_head, sizeof (struct pqi_cmd), (void *)cmdp, cmd_current); } } } d_drvrp = (pqi_device_t *)(next_dp->pd_list.list_next); dev_current = pqi_list_next( d_list_head, sizeof (struct pqi_device), (void*)next_dp, dev_current); dev_count++; } if (!dev_verbose) mdb_printf("Number of devices\t\t\t%d\n", dev_count); } static void pqi_display_instance(pqi_state_t *pqi_statep) { mdb_printf("s_dip\t\t\t\t\t0x%p\n", pqi_statep->s_dip); mdb_printf("s_flags\t\t\t\t\t0x%x\n", pqi_statep->s_flags); mdb_printf("s_firmware_version\t\t\t%s\n", pqi_statep->s_firmware_version); mdb_printf("s_offline\t\t\t\t%s\ns_disable_mpxio\t\t\t\t%s\n", bool_to_str(pqi_statep->s_offline), bool_to_str(pqi_statep->s_disable_mpxio)); mdb_printf("s_debug level\t\t\t\t%d\n", pqi_statep->s_debug_level); mdb_printf("---- State for watchdog----\n"); mdb_printf("s_intr_count\t\t\t\t%d\n", pqi_statep->s_intr_count); mdb_printf("s_last_intr_count\t\t\t%d\n", pqi_statep->s_last_intr_count); mdb_printf("s_last_heartbeat_count\t\t\t%d\n", pqi_statep->s_last_heartbeat_count); mdb_printf("---- PQI cpabilities from controller ----\n"); mdb_printf("s_max_inbound_queues\t\t\t%d\n", pqi_statep->s_max_inbound_queues); mdb_printf("s_max_elements_per_iq\t\t\t%d\n", pqi_statep->s_max_elements_per_iq); mdb_printf("s_max_iq_element_length\t\t\t%d\n", pqi_statep->s_max_iq_element_length); mdb_printf("s_max_outbound_queues\t\t\t%d\n", pqi_statep->s_max_outbound_queues); mdb_printf("s_max_elements_per_oq\t\t\t%d\n", pqi_statep->s_max_elements_per_oq); mdb_printf("s_max_elements_per_oq\t\t\t%d\n", pqi_statep->s_max_elements_per_oq); mdb_printf("s_max_oq_element_length\t\t\t%d\n", pqi_statep->s_max_oq_element_length); mdb_printf("s_max_inbound_iu_length_per_firmware\t%d\n", pqi_statep->s_max_inbound_iu_length_per_firmware); mdb_printf("s_max_inbound_queues\t\t\t%d\n", pqi_statep->s_max_inbound_queues); mdb_printf("s_inbound_spanning_supported:\t\t%d\n", pqi_statep->s_inbound_spanning_supported); mdb_printf("s_outbound_spanning_supported:\t\t%dk\n", pqi_statep->s_outbound_spanning_supported); mdb_printf("s_outbound_spanning_supported:\t\t%d\n", pqi_statep->s_outbound_spanning_supported); mdb_printf("s_pqi_mode_enabled:\t\t\t%d\n", pqi_statep->s_pqi_mode_enabled); mdb_printf("s_cmd_queue_len\t\t\t\t%d\n", pqi_statep->s_cmd_queue_len); mdb_printf("---- SIS capabilities from controller ----\n"); mdb_printf("s_max_sg_entries\t\t\t%d\n", pqi_statep->s_max_sg_entries); mdb_printf("s_max_xfer_size\t\t\t\t%d\n", pqi_statep->s_max_xfer_size); mdb_printf("s_max_outstainding_requests\t\t%d\n", pqi_statep->s_max_sg_entries); mdb_printf("---- Computed values from config ----\n"); mdb_printf("s_max_sg_per_iu\t\t\t\t%d\n", pqi_statep->s_max_sg_per_iu); mdb_printf("s_num_elements_per_iq\t\t\t%d\n", pqi_statep->s_num_elements_per_iq); mdb_printf("s_num_elements_per_oq\t\t\t%d\n", pqi_statep->s_num_elements_per_oq); mdb_printf("s_max_inbound_iu_length\t\t\t%d\n", pqi_statep->s_max_inbound_iu_length); mdb_printf("s_num_queue_groups\t\t\t%d\n", pqi_statep->s_num_queue_groups); mdb_printf("s_max_io_slots\t\t\t\t%d\n", pqi_statep->s_max_io_slots); mdb_printf("s_sg_chain_buf_length\t\t\t%d\n", pqi_statep->s_sg_chain_buf_length); mdb_printf("s_max_sectors\t\t\t\t%d\n", pqi_statep->s_max_sectors); mdb_printf("---- IO slot information ----\n"); mdb_printf("s_io_rqst_pool\t\t\t\t0x%p\n", pqi_statep->s_io_rqst_pool); mdb_printf("s_io_wait_cnt\t\t\t\t%d\n", pqi_statep->s_io_wait_cnt); mdb_printf("s_next_io_slot\t\t\t\t%d\n", pqi_statep->s_next_io_slot); mdb_printf("s_io_need\t\t\t\t%d\n", pqi_statep->s_io_need); mdb_printf("s_io_had2wait\t\t\t\t%d\n", pqi_statep->s_io_had2wait); mdb_printf("s_io_sig\t\t\t\t%d\n", pqi_statep->s_io_sig); } static int pqi_getopts(uintptr_t addr, int argc, const mdb_arg_t *argv, uintptr_t *cntlr, uint_t *print_devices) { uintptr_t device = INVALID_OPT_VAL; *cntlr = INVALID_OPT_VAL; *print_devices = FALSE; mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, print_devices, 'c', MDB_OPT_UINTPTR, (uintptr_t)cntlr, 'd', MDB_OPT_UINTPTR, (uintptr_t)&device, NULL); if (*cntlr == INVALID_OPT_VAL) { mdb_warn("-c required\n"); return (DCMD_USAGE); } return (DCMD_OK); } static int smartpqi(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int array_size; int rval; int pqi_statesz = sizeof (struct pqi_state); uintptr_t instance = INVALID_OPT_VAL; uintptr_t adr; void **array_vaddr; void **pqi_array; pqi_state_t *pqi_drvp; struct i_ddi_soft_state ss; pqi_state_t *pqi_statep; uint_t print_devices = 0; if ((flags & DCMD_ADDRSPEC) == 0) { /* * MDB has this peculiarity that addr can be non-null * from a previous invocation: * e.g. 0xfffffef49cd64000::smartpqi, * but flags shows that * the command line is ::spamrtpqi or ::smartpai * To make sure the desired command line options are * honored, we set addr to 0 and proceed with evaluating * these command as entered. */ addr = (uintptr_t)0; } rval = pqi_getopts(addr, argc, argv, &instance, &print_devices); if (rval != DCMD_OK) { return (rval); } /* read the address of the pqi_state variable in the smartpqi driver */ if (mdb_readvar((void *)&adr, "pqi_state") == -1) { mdb_warn("Cannot read pqi driver variable pqi_softstate.\n"); return (DCMD_ERR); } /* now read the i_ddi_soft_state structure pointer */ if (mdb_vread((void *)&ss, sizeof (ss), adr) != sizeof (ss)) { mdb_warn("Cannot read smartpqi softstate struct" " pqi_state (Invalid pointer?(0x%p)).\n", (uintptr_t)adr); return (DCMD_ERR); } /* * now allocate space for the array containing the pqi_state * pointers and read in this array */ array_size = ss.n_items * (sizeof (void*)); array_vaddr = ss.array; pqi_array = (void **)mdb_alloc(array_size, UM_SLEEP|UM_GC); if (mdb_vread(pqi_array, array_size, (uintptr_t)array_vaddr) != array_size) { mdb_warn("Corrupted softstate struct\n"); return (DCMD_ERR); } if (instance >= ss.n_items || pqi_array[instance] == NULL) { mdb_warn("smartpqi - no information available for %d\n", instance); return (DCMD_USAGE); } pqi_statep = mdb_alloc(sizeof (struct pqi_state), UM_SLEEP|UM_GC); adr = (uintptr_t)pqi_array[instance]; pqi_drvp = (pqi_state_t *)adr; if (mdb_vread(pqi_statep, pqi_statesz, adr) != pqi_statesz) { mdb_warn("Cannot read pqi_state. adr 0x%p, size %d\n", adr, pqi_statesz); return (DCMD_ERR); } mdb_printf("-------- Controller %d pqi_state (0x%p) --------\n", instance, adr); pqi_display_instance(pqi_statep); pqi_display_devices(pqi_statep->s_devnodes, pqi_drvp, print_devices); return (DCMD_OK); } /* * 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 2017 Nexenta Systems, Inc. All rights reserved. * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #ifdef _USER #include "../genunix/avl.h" #define _FAKE_KERNEL #endif #include #include #include #include #include #define OPT_VERBOSE 0x0001 /* Be [-v]erbose in dcmd's */ /* * This macro lets us easily use both sizeof (typename) * and the string-ified typename for the error message. */ #define SMBFS_OBJ_FETCH(obj_addr, obj_type, dest, err) \ if (mdb_vread(dest, sizeof (obj_type), ((uintptr_t)obj_addr)) \ != sizeof (obj_type)) { \ mdb_warn("error reading "#obj_type" at %p", obj_addr); \ return (err); \ } /* * We need to read in a private copy * of every string we want to print out. */ void print_str(uintptr_t addr) { char buf[64]; int len, mx = sizeof (buf) - 4; if ((len = mdb_readstr(buf, sizeof (buf), addr)) <= 0) { mdb_printf(" (%p)", addr); } else { if (len > mx) strcpy(&buf[mx], "..."); mdb_printf(" %s", buf); } } /* * Dcmd (and callback function) to print a summary of * all "smbfs" entries in the VFS list. */ typedef struct smbfs_vfs_cbdata { int flags; int printed_header; uintptr_t vfsops; /* filter by vfs ops pointer */ smbmntinfo_t smi; /* scratch space for smbfs_vfs_cb */ } smbfs_vfs_cbdata_t; int smbfs_vfs_cb(uintptr_t addr, const void *data, void *arg) { const vfs_t *vfs = data; smbfs_vfs_cbdata_t *cbd = arg; uintptr_t ta; /* Filter by matching smbfs ops vector. */ if (cbd->vfsops && cbd->vfsops != (uintptr_t)vfs->vfs_op) { return (WALK_NEXT); } if (cbd->printed_header == 0) { cbd->printed_header = 1; mdb_printf("// vfs_t smbmntinfo_t mnt_path\n"); } mdb_printf(" %-p", addr); /* vfs_t */ mdb_printf(" %-p", (uintptr_t)vfs->vfs_data); /* * Note: vfs_mntpt is a refstr_t. * Advance to string member. */ ta = (uintptr_t)vfs->vfs_mntpt; ta += OFFSETOF(struct refstr, rs_string); print_str(ta); mdb_printf("\n"); if (cbd->flags & OPT_VERBOSE) { mdb_inc_indent(2); /* Don't fail the walk if this fails. */ if (mdb_vread(&cbd->smi, sizeof (cbd->smi), (uintptr_t)vfs->vfs_data) == -1) { mdb_warn("error reading smbmntinfo_t at %p", (uintptr_t)vfs->vfs_data); } else { /* Interesting parts of smbmntinfo_t */ mdb_printf("smi_share: %p, smi_root: %p\n", cbd->smi.smi_share, cbd->smi.smi_root); } mdb_dec_indent(2); } return (WALK_NEXT); } int smbfs_vfs_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { smbfs_vfs_cbdata_t *cbd; vfs_t *vfs; cbd = mdb_zalloc(sizeof (*cbd), UM_SLEEP | UM_GC); /* * Get the ops address here, so things work * even if the smbfs module is loaded later * than this mdb module. */ if (mdb_readvar(&cbd->vfsops, "smbfs_vfsops") == -1) { mdb_warn("failed to find 'smbfs_vfsops'\n"); return (DCMD_ERR); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, OPT_VERBOSE, &cbd->flags, NULL) != argc) { return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk("vfs", smbfs_vfs_cb, cbd) == -1) { mdb_warn("can't walk smbfs vfs"); return (DCMD_ERR); } return (DCMD_OK); } vfs = mdb_alloc(sizeof (*vfs), UM_SLEEP | UM_GC); SMBFS_OBJ_FETCH(addr, vfs_t, vfs, DCMD_ERR); smbfs_vfs_cb(addr, vfs, cbd); return (DCMD_OK); } void smbfs_vfs_help(void) { mdb_printf( "Display addresses of the mounted smbfs structures\n" "and the pathname of the mountpoint\n" "\nOptions:\n" " -v display details of the smbmntinfo\n"); } /* * Dcmd (and callback function) to print a summary of * all smbnodes in the node "hash" (cache) AVL tree. */ typedef struct smbfs_node_cbdata { int flags; int printed_header; vnode_t vn; } smbfs_node_cbdata_t; int smbfs_node_cb(uintptr_t addr, const void *data, void *arg) { const smbnode_t *np = data; smbfs_node_cbdata_t *cbd = arg; if (cbd->printed_header == 0) { cbd->printed_header = 1; mdb_printf("// vnode smbnode rpath\n"); } mdb_printf(" %-p", (uintptr_t)np->r_vnode); mdb_printf(" %-p", addr); /* smbnode */ print_str((uintptr_t)np->n_rpath); mdb_printf("\n"); if (cbd->flags & OPT_VERBOSE) { mdb_inc_indent(2); /* Don't fail the walk if this fails. */ if (mdb_vread(&cbd->vn, sizeof (cbd->vn), (uintptr_t)np->r_vnode) == -1) { mdb_warn("error reading vnode_t at %p", (uintptr_t)np->r_vnode); } else { /* Interesting parts of vnode_t */ mdb_printf("v_type=%d v_count=%d", cbd->vn.v_type, cbd->vn.v_count); mdb_printf("\n"); } mdb_dec_indent(2); } return (WALK_NEXT); } int smbfs_node_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { smbfs_node_cbdata_t *cbd; cbd = mdb_zalloc(sizeof (*cbd), UM_SLEEP | UM_GC); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, OPT_VERBOSE, &cbd->flags, NULL) != argc) { return (DCMD_USAGE); } if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("expect an smbmntinfo_t addr"); return (DCMD_USAGE); } addr += OFFSETOF(smbmntinfo_t, smi_hash_avl); if (mdb_pwalk("avl", smbfs_node_cb, cbd, addr) == -1) { mdb_warn("cannot walk smbfs nodes"); return (DCMD_ERR); } return (DCMD_OK); } void smbfs_node_help(void) { mdb_printf("Options:\n" " -v be verbose when displaying smbnodes\n"); } static const mdb_dcmd_t dcmds[] = { { "smbfs_vfs", "?[-v]", "show smbfs-mounted vfs structs", smbfs_vfs_dcmd, smbfs_vfs_help }, { "smbfs_node", "?[-v]", "given an smbmntinfo_t, list smbnodes", smbfs_node_dcmd, smbfs_node_help }, {NULL} }; #ifdef _USER /* * Sadly, can't just compile ../genunix/vfs.c with this since * it has become a catch-all for FS-specific headers etc. */ int vfs_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0 && mdb_readvar(&wsp->walk_addr, "rootvfs") == -1) { mdb_warn("failed to read 'rootvfs'"); return (WALK_ERR); } wsp->walk_data = (void *)wsp->walk_addr; return (WALK_NEXT); } int vfs_walk_step(mdb_walk_state_t *wsp) { vfs_t vfs; int status; if (mdb_vread(&vfs, sizeof (vfs), wsp->walk_addr) == -1) { mdb_warn("failed to read vfs_t at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, &vfs, wsp->walk_cbdata); if (vfs.vfs_next == wsp->walk_data) return (WALK_DONE); wsp->walk_addr = (uintptr_t)vfs.vfs_next; return (status); } #endif // _USER static const mdb_walker_t walkers[] = { #ifdef _USER /* from avl.c */ { AVL_WALK_NAME, AVL_WALK_DESC, avl_walk_init, avl_walk_step, avl_walk_fini }, /* from vfs.c */ { "vfs", "walk file system list", vfs_walk_init, vfs_walk_step }, #endif // _USER {NULL} }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Nexenta by DDN, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #ifndef _KMDB #include "smbsrv_pcap.h" #endif #ifdef _KERNEL #define SMBSRV_OBJNAME "smbsrv" #else #define SMBSRV_OBJNAME "libfksmbsrv.so.1" #endif #define SMBSRV_SCOPE SMBSRV_OBJNAME "`" #define SMB_DCMD_INDENT 2 #define ACE_TYPE_TABLEN (ACE_ALL_TYPES + 1) #define ACE_TYPE_ENTRY(_v_) {_v_, #_v_} #define SMB_COM_ENTRY(_v_, _x_) {#_v_, _x_} #define SMB_MDB_MAX_OPTS 10 #define SMB_OPT_SERVER 0x00000001 #define SMB_OPT_SESSION 0x00000002 #define SMB_OPT_REQUEST 0x00000004 #define SMB_OPT_USER 0x00000008 #define SMB_OPT_TREE 0x00000010 #define SMB_OPT_OFILE 0x00000020 #define SMB_OPT_ODIR 0x00000040 #define SMB_OPT_WALK 0x00000100 #define SMB_OPT_VERBOSE 0x00000200 #define SMB_OPT_ALL_OBJ 0x000000FF /* * Use CTF to set var = OFFSETOF(typ, mem) if possible, otherwise * fall back to just OFFSETOF. The fall back is more convenient * than trying to return an error where this is used, and also * let's us find out at compile time if we're referring to any * typedefs or member names that don't exist. Without that * OFFSETOF fall back, we'd only find out at run time. */ #define GET_OFFSET(var, typ, mem) do { \ var = mdb_ctf_offsetof_by_name(#typ, #mem); \ if (var < 0) { \ mdb_warn("cannot lookup: " #typ " ." #mem); \ var = (int)OFFSETOF(typ, mem); \ } \ _NOTE(CONSTCOND) } while (0) /* * Structure associating an ACE type to a string. */ typedef struct { uint8_t ace_type_value; const char *ace_type_sting; } ace_type_entry_t; /* * Structure containing strings describing an SMB command. */ typedef struct { const char *smb_com; const char *smb_andx; } smb_com_entry_t; /* * Structure describing an object to be expanded (displayed). */ typedef struct { uint_t ex_mask; const char *ex_walker; int (*ex_offset)(void); const char *ex_dcmd; const char *ex_name; } smb_exp_t; /* * List of supported options. Ther order has the match the bits SMB_OPT_xxx. */ typedef struct smb_mdb_opts { char *o_name; uint32_t o_value; } smb_mdb_opts_t; static smb_mdb_opts_t smb_opts[SMB_MDB_MAX_OPTS] = { { "-s", SMB_OPT_SERVER }, { "-e", SMB_OPT_SESSION }, { "-r", SMB_OPT_REQUEST }, { "-u", SMB_OPT_USER }, { "-t", SMB_OPT_TREE }, { "-f", SMB_OPT_OFILE }, { "-d", SMB_OPT_ODIR }, { "-w", SMB_OPT_WALK }, { "-v", SMB_OPT_VERBOSE } }; /* * These access mask bits are generic enough they could move into the * genunix mdb module or somewhere so they could be shared. */ static const mdb_bitmask_t nt_access_bits[] = { { "READ_DATA", FILE_READ_DATA, FILE_READ_DATA }, { "WRITE_DATA", FILE_WRITE_DATA, FILE_WRITE_DATA }, { "APPEND_DATA", FILE_APPEND_DATA, FILE_APPEND_DATA }, { "READ_EA", FILE_READ_EA, FILE_READ_EA }, { "WRITE_EA", FILE_WRITE_EA, FILE_WRITE_EA }, { "EXECUTE", FILE_EXECUTE, FILE_EXECUTE }, { "DELETE_CHILD", FILE_DELETE_CHILD, FILE_DELETE_CHILD }, { "READ_ATTR", FILE_READ_ATTRIBUTES, FILE_READ_ATTRIBUTES }, { "WRITE_ATTR", FILE_WRITE_ATTRIBUTES, FILE_WRITE_ATTRIBUTES }, { "DELETE", DELETE, DELETE }, { "READ_CTRL", READ_CONTROL, READ_CONTROL }, { "WRITE_DAC", WRITE_DAC, WRITE_DAC }, { "WRITE_OWNER", WRITE_OWNER, WRITE_OWNER }, { "SYNCH", SYNCHRONIZE, SYNCHRONIZE }, { "ACC_SEC", ACCESS_SYSTEM_SECURITY, ACCESS_SYSTEM_SECURITY }, { "MAX_ALLOWED", MAXIMUM_ALLOWED, MAXIMUM_ALLOWED }, { "GEN_X", GENERIC_EXECUTE, GENERIC_EXECUTE }, { "GEN_W", GENERIC_WRITE, GENERIC_WRITE }, { "GEN_R", GENERIC_READ, GENERIC_READ }, { NULL, 0, 0 } }; static smb_com_entry_t smb_com[256] = { SMB_COM_ENTRY(SMB_COM_CREATE_DIRECTORY, "No"), SMB_COM_ENTRY(SMB_COM_DELETE_DIRECTORY, "No"), SMB_COM_ENTRY(SMB_COM_OPEN, "No"), SMB_COM_ENTRY(SMB_COM_CREATE, "No"), SMB_COM_ENTRY(SMB_COM_CLOSE, "No"), SMB_COM_ENTRY(SMB_COM_FLUSH, "No"), SMB_COM_ENTRY(SMB_COM_DELETE, "No"), SMB_COM_ENTRY(SMB_COM_RENAME, "No"), SMB_COM_ENTRY(SMB_COM_QUERY_INFORMATION, "No"), SMB_COM_ENTRY(SMB_COM_SET_INFORMATION, "No"), SMB_COM_ENTRY(SMB_COM_READ, "No"), SMB_COM_ENTRY(SMB_COM_WRITE, "No"), SMB_COM_ENTRY(SMB_COM_LOCK_BYTE_RANGE, "No"), SMB_COM_ENTRY(SMB_COM_UNLOCK_BYTE_RANGE, "No"), SMB_COM_ENTRY(SMB_COM_CREATE_TEMPORARY, "No"), SMB_COM_ENTRY(SMB_COM_CREATE_NEW, "No"), SMB_COM_ENTRY(SMB_COM_CHECK_DIRECTORY, "No"), SMB_COM_ENTRY(SMB_COM_PROCESS_EXIT, "No"), SMB_COM_ENTRY(SMB_COM_SEEK, "No"), SMB_COM_ENTRY(SMB_COM_LOCK_AND_READ, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_AND_UNLOCK, "No"), SMB_COM_ENTRY(0x15, "?"), SMB_COM_ENTRY(0x16, "?"), SMB_COM_ENTRY(0x17, "?"), SMB_COM_ENTRY(0x18, "?"), SMB_COM_ENTRY(0x19, "?"), SMB_COM_ENTRY(SMB_COM_READ_RAW, "No"), SMB_COM_ENTRY(SMB_COM_READ_MPX, "No"), SMB_COM_ENTRY(SMB_COM_READ_MPX_SECONDARY, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_RAW, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_MPX, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_MPX_SECONDARY, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_COMPLETE, "No"), SMB_COM_ENTRY(SMB_COM_QUERY_SERVER, "No"), SMB_COM_ENTRY(SMB_COM_SET_INFORMATION2, "No"), SMB_COM_ENTRY(SMB_COM_QUERY_INFORMATION2, "No"), SMB_COM_ENTRY(SMB_COM_LOCKING_ANDX, "No"), SMB_COM_ENTRY(SMB_COM_TRANSACTION, "No"), SMB_COM_ENTRY(SMB_COM_TRANSACTION_SECONDARY, "No"), SMB_COM_ENTRY(SMB_COM_IOCTL, "No"), SMB_COM_ENTRY(SMB_COM_IOCTL_SECONDARY, "No"), SMB_COM_ENTRY(SMB_COM_COPY, "No"), SMB_COM_ENTRY(SMB_COM_MOVE, "No"), SMB_COM_ENTRY(SMB_COM_ECHO, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_AND_CLOSE, "No"), SMB_COM_ENTRY(SMB_COM_OPEN_ANDX, "No"), SMB_COM_ENTRY(SMB_COM_READ_ANDX, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_ANDX, "No"), SMB_COM_ENTRY(SMB_COM_NEW_FILE_SIZE, "No"), SMB_COM_ENTRY(SMB_COM_CLOSE_AND_TREE_DISC, "No"), SMB_COM_ENTRY(SMB_COM_TRANSACTION2, "No"), SMB_COM_ENTRY(SMB_COM_TRANSACTION2_SECONDARY, "No"), SMB_COM_ENTRY(SMB_COM_FIND_CLOSE2, "No"), SMB_COM_ENTRY(SMB_COM_FIND_NOTIFY_CLOSE, "No"), SMB_COM_ENTRY(0x36, "?"), SMB_COM_ENTRY(0x37, "?"), SMB_COM_ENTRY(0x38, "?"), SMB_COM_ENTRY(0x39, "?"), SMB_COM_ENTRY(0x3A, "?"), SMB_COM_ENTRY(0x3B, "?"), SMB_COM_ENTRY(0x3C, "?"), SMB_COM_ENTRY(0x3D, "?"), SMB_COM_ENTRY(0x3E, "?"), SMB_COM_ENTRY(0x3F, "?"), SMB_COM_ENTRY(0x40, "?"), SMB_COM_ENTRY(0x41, "?"), SMB_COM_ENTRY(0x42, "?"), SMB_COM_ENTRY(0x43, "?"), SMB_COM_ENTRY(0x44, "?"), SMB_COM_ENTRY(0x45, "?"), SMB_COM_ENTRY(0x46, "?"), SMB_COM_ENTRY(0x47, "?"), SMB_COM_ENTRY(0x48, "?"), SMB_COM_ENTRY(0x49, "?"), SMB_COM_ENTRY(0x4A, "?"), SMB_COM_ENTRY(0x4B, "?"), SMB_COM_ENTRY(0x4C, "?"), SMB_COM_ENTRY(0x4D, "?"), SMB_COM_ENTRY(0x4E, "?"), SMB_COM_ENTRY(0x4F, "?"), SMB_COM_ENTRY(0x50, "?"), SMB_COM_ENTRY(0x51, "?"), SMB_COM_ENTRY(0x52, "?"), SMB_COM_ENTRY(0x53, "?"), SMB_COM_ENTRY(0x54, "?"), SMB_COM_ENTRY(0x55, "?"), SMB_COM_ENTRY(0x56, "?"), SMB_COM_ENTRY(0x57, "?"), SMB_COM_ENTRY(0x58, "?"), SMB_COM_ENTRY(0x59, "?"), SMB_COM_ENTRY(0x5A, "?"), SMB_COM_ENTRY(0x5B, "?"), SMB_COM_ENTRY(0x5C, "?"), SMB_COM_ENTRY(0x5D, "?"), SMB_COM_ENTRY(0x5E, "?"), SMB_COM_ENTRY(0x5F, "?"), SMB_COM_ENTRY(0x60, "?"), SMB_COM_ENTRY(0x61, "?"), SMB_COM_ENTRY(0x62, "?"), SMB_COM_ENTRY(0x63, "?"), SMB_COM_ENTRY(0x64, "?"), SMB_COM_ENTRY(0x65, "?"), SMB_COM_ENTRY(0x66, "?"), SMB_COM_ENTRY(0x67, "?"), SMB_COM_ENTRY(0x68, "?"), SMB_COM_ENTRY(0x69, "?"), SMB_COM_ENTRY(0x6A, "?"), SMB_COM_ENTRY(0x6B, "?"), SMB_COM_ENTRY(0x6C, "?"), SMB_COM_ENTRY(0x6D, "?"), SMB_COM_ENTRY(0x6E, "?"), SMB_COM_ENTRY(0x6F, "?"), SMB_COM_ENTRY(SMB_COM_TREE_CONNECT, "No"), SMB_COM_ENTRY(SMB_COM_TREE_DISCONNECT, "No"), SMB_COM_ENTRY(SMB_COM_NEGOTIATE, "No"), SMB_COM_ENTRY(SMB_COM_SESSION_SETUP_ANDX, "No"), SMB_COM_ENTRY(SMB_COM_LOGOFF_ANDX, "No"), SMB_COM_ENTRY(SMB_COM_TREE_CONNECT_ANDX, "No"), SMB_COM_ENTRY(0x76, "?"), SMB_COM_ENTRY(0x77, "?"), SMB_COM_ENTRY(0x78, "?"), SMB_COM_ENTRY(0x79, "?"), SMB_COM_ENTRY(0x7A, "?"), SMB_COM_ENTRY(0x7B, "?"), SMB_COM_ENTRY(0x7C, "?"), SMB_COM_ENTRY(0x7D, "?"), SMB_COM_ENTRY(0x7E, "?"), SMB_COM_ENTRY(0x7F, "?"), SMB_COM_ENTRY(SMB_COM_QUERY_INFORMATION_DISK, "No"), SMB_COM_ENTRY(SMB_COM_SEARCH, "No"), SMB_COM_ENTRY(SMB_COM_FIND, "No"), SMB_COM_ENTRY(SMB_COM_FIND_UNIQUE, "No"), SMB_COM_ENTRY(SMB_COM_FIND_CLOSE, "No"), SMB_COM_ENTRY(0x85, "?"), SMB_COM_ENTRY(0x86, "?"), SMB_COM_ENTRY(0x87, "?"), SMB_COM_ENTRY(0x88, "?"), SMB_COM_ENTRY(0x89, "?"), SMB_COM_ENTRY(0x8A, "?"), SMB_COM_ENTRY(0x8B, "?"), SMB_COM_ENTRY(0x8C, "?"), SMB_COM_ENTRY(0x8D, "?"), SMB_COM_ENTRY(0x8E, "?"), SMB_COM_ENTRY(0x8F, "?"), SMB_COM_ENTRY(0x90, "?"), SMB_COM_ENTRY(0x91, "?"), SMB_COM_ENTRY(0x92, "?"), SMB_COM_ENTRY(0x93, "?"), SMB_COM_ENTRY(0x94, "?"), SMB_COM_ENTRY(0x95, "?"), SMB_COM_ENTRY(0x96, "?"), SMB_COM_ENTRY(0x97, "?"), SMB_COM_ENTRY(0x98, "?"), SMB_COM_ENTRY(0x99, "?"), SMB_COM_ENTRY(0x9A, "?"), SMB_COM_ENTRY(0x9B, "?"), SMB_COM_ENTRY(0x9C, "?"), SMB_COM_ENTRY(0x9D, "?"), SMB_COM_ENTRY(0x9E, "?"), SMB_COM_ENTRY(0x9F, "?"), SMB_COM_ENTRY(SMB_COM_NT_TRANSACT, "No"), SMB_COM_ENTRY(SMB_COM_NT_TRANSACT_SECONDARY, "No"), SMB_COM_ENTRY(SMB_COM_NT_CREATE_ANDX, "No"), SMB_COM_ENTRY(0xA3, "?"), SMB_COM_ENTRY(SMB_COM_NT_CANCEL, "No"), SMB_COM_ENTRY(SMB_COM_NT_RENAME, "No"), SMB_COM_ENTRY(0xA6, "?"), SMB_COM_ENTRY(0xA7, "?"), SMB_COM_ENTRY(0xA8, "?"), SMB_COM_ENTRY(0xA9, "?"), SMB_COM_ENTRY(0xAA, "?"), SMB_COM_ENTRY(0xAB, "?"), SMB_COM_ENTRY(0xAC, "?"), SMB_COM_ENTRY(0xAD, "?"), SMB_COM_ENTRY(0xAE, "?"), SMB_COM_ENTRY(0xAF, "?"), SMB_COM_ENTRY(0xB0, "?"), SMB_COM_ENTRY(0xB1, "?"), SMB_COM_ENTRY(0xB2, "?"), SMB_COM_ENTRY(0xB3, "?"), SMB_COM_ENTRY(0xB4, "?"), SMB_COM_ENTRY(0xB5, "?"), SMB_COM_ENTRY(0xB6, "?"), SMB_COM_ENTRY(0xB7, "?"), SMB_COM_ENTRY(0xB8, "?"), SMB_COM_ENTRY(0xB9, "?"), SMB_COM_ENTRY(0xBA, "?"), SMB_COM_ENTRY(0xBB, "?"), SMB_COM_ENTRY(0xBC, "?"), SMB_COM_ENTRY(0xBD, "?"), SMB_COM_ENTRY(0xBE, "?"), SMB_COM_ENTRY(0xBF, "?"), SMB_COM_ENTRY(SMB_COM_OPEN_PRINT_FILE, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_PRINT_FILE, "No"), SMB_COM_ENTRY(SMB_COM_CLOSE_PRINT_FILE, "No"), SMB_COM_ENTRY(SMB_COM_GET_PRINT_QUEUE, "No"), SMB_COM_ENTRY(0xC4, "?"), SMB_COM_ENTRY(0xC5, "?"), SMB_COM_ENTRY(0xC6, "?"), SMB_COM_ENTRY(0xC7, "?"), SMB_COM_ENTRY(0xC8, "?"), SMB_COM_ENTRY(0xC9, "?"), SMB_COM_ENTRY(0xCA, "?"), SMB_COM_ENTRY(0xCB, "?"), SMB_COM_ENTRY(0xCC, "?"), SMB_COM_ENTRY(0xCD, "?"), SMB_COM_ENTRY(0xCE, "?"), SMB_COM_ENTRY(0xCF, "?"), SMB_COM_ENTRY(0xD0, "?"), SMB_COM_ENTRY(0xD1, "?"), SMB_COM_ENTRY(0xD2, "?"), SMB_COM_ENTRY(0xD3, "?"), SMB_COM_ENTRY(0xD4, "?"), SMB_COM_ENTRY(0xD5, "?"), SMB_COM_ENTRY(0xD6, "?"), SMB_COM_ENTRY(0xD7, "?"), SMB_COM_ENTRY(SMB_COM_READ_BULK, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_BULK, "No"), SMB_COM_ENTRY(SMB_COM_WRITE_BULK_DATA, "No"), SMB_COM_ENTRY(0xDB, "?"), SMB_COM_ENTRY(0xDC, "?"), SMB_COM_ENTRY(0xDD, "?"), SMB_COM_ENTRY(0xDE, "?"), SMB_COM_ENTRY(0xDF, "?"), SMB_COM_ENTRY(0xE0, "?"), SMB_COM_ENTRY(0xE1, "?"), SMB_COM_ENTRY(0xE2, "?"), SMB_COM_ENTRY(0xE3, "?"), SMB_COM_ENTRY(0xE4, "?"), SMB_COM_ENTRY(0xE5, "?"), SMB_COM_ENTRY(0xE6, "?"), SMB_COM_ENTRY(0xE7, "?"), SMB_COM_ENTRY(0xE8, "?"), SMB_COM_ENTRY(0xE9, "?"), SMB_COM_ENTRY(0xEA, "?"), SMB_COM_ENTRY(0xEB, "?"), SMB_COM_ENTRY(0xEC, "?"), SMB_COM_ENTRY(0xED, "?"), SMB_COM_ENTRY(0xEE, "?"), SMB_COM_ENTRY(0xEF, "?"), SMB_COM_ENTRY(0xF0, "?"), SMB_COM_ENTRY(0xF1, "?"), SMB_COM_ENTRY(0xF2, "?"), SMB_COM_ENTRY(0xF3, "?"), SMB_COM_ENTRY(0xF4, "?"), SMB_COM_ENTRY(0xF5, "?"), SMB_COM_ENTRY(0xF6, "?"), SMB_COM_ENTRY(0xF7, "?"), SMB_COM_ENTRY(0xF8, "?"), SMB_COM_ENTRY(0xF9, "?"), SMB_COM_ENTRY(0xFA, "?"), SMB_COM_ENTRY(0xFB, "?"), SMB_COM_ENTRY(0xFC, "?"), SMB_COM_ENTRY(0xFD, "?"), SMB_COM_ENTRY(0xFE, "?"), SMB_COM_ENTRY(0xFF, "?") }; static const char *smb2_cmd_names[SMB2__NCMDS] = { "smb2_negotiate", "smb2_session_setup", "smb2_logoff", "smb2_tree_connect", "smb2_tree_disconn", "smb2_create", "smb2_close", "smb2_flush", "smb2_read", "smb2_write", "smb2_lock", "smb2_ioctl", "smb2_cancel", "smb2_echo", "smb2_query_dir", "smb2_change_notify", "smb2_query_info", "smb2_set_info", "smb2_oplock_break", "smb2_invalid_cmd" }; struct mdb_smb_oplock; static int smb_sid_print(uintptr_t); static int smb_dcmd_getopt(uint_t *, int, const mdb_arg_t *); static int smb_dcmd_setopt(uint_t, int, mdb_arg_t *); static int smb_obj_expand(uintptr_t, uint_t, const smb_exp_t *, ulong_t); static int smb_obj_list(const char *, uint_t, uint_t); static int smb_worker_findstack(uintptr_t); static int smb_node_get_oplock(uintptr_t, struct mdb_smb_oplock **); static int smb_node_oplock_cnt(struct mdb_smb_oplock *); static void smb_inaddr_ntop(smb_inaddr_t *, char *, size_t); static void get_enum(char *, size_t, const char *, int, const char *); typedef int (*dump_func_t)(struct mbuf_chain *, int32_t, smb_inaddr_t *, uint16_t, smb_inaddr_t *, uint16_t, hrtime_t, boolean_t); static int smb_req_dump(struct mbuf_chain *, int32_t, smb_inaddr_t *, uint16_t, smb_inaddr_t *, uint16_t, hrtime_t, boolean_t); static int smb_req_dump_m(uintptr_t, const void *, void *); /* * ***************************************************************************** * ****************************** Top level DCMD ******************************* * ***************************************************************************** */ static void smblist_help(void) { mdb_printf( "Displays the list of objects using an indented tree format.\n" "If no option is specified the entire tree is displayed\n\n"); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf( "-v\tDisplay verbose information\n" "-s\tDisplay the list of servers\n" "-e\tDisplay the list of sessions\n" "-r\tDisplay the list of smb requests\n" "-u\tDisplay the list of users\n" "-t\tDisplay the list of trees\n" "-f\tDisplay the list of open files\n" "-d\tDisplay the list of open searches\n"); } /* * ::smblist * * This function lists the objects specified on the command line. If no object * is specified the entire tree (server through ofile and odir) is displayed. * */ /*ARGSUSED*/ static int smblist_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { GElf_Sym sym; uint_t opts = 0; int new_argc; mdb_arg_t new_argv[SMB_MDB_MAX_OPTS]; int ll_off; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(opts & ~(SMB_OPT_WALK | SMB_OPT_VERBOSE))) opts |= SMB_OPT_ALL_OBJ; opts |= SMB_OPT_WALK; new_argc = smb_dcmd_setopt(opts, SMB_MDB_MAX_OPTS, new_argv); if (mdb_lookup_by_obj(SMBSRV_OBJNAME, "smb_servers", &sym) == -1) { mdb_warn("failed to find symbol smb_servers"); return (DCMD_ERR); } GET_OFFSET(ll_off, smb_llist_t, ll_list); addr = (uintptr_t)sym.st_value + ll_off; if (mdb_pwalk_dcmd("list", "smbsrv", new_argc, new_argv, addr)) { mdb_warn("cannot walk smb_server list"); return (DCMD_ERR); } return (DCMD_OK); } /* * ***************************************************************************** * ***************************** smb_server_t ********************************** * ***************************************************************************** */ typedef struct mdb_smb_server { smb_server_state_t sv_state; zoneid_t sv_zid; smb_hash_t *sv_persistid_ht; } mdb_smb_server_t; static int smb_server_exp_off_sv_list(void) { int svl_off, ll_off; /* OFFSETOF(smb_server_t, sv_session_list.ll_list); */ GET_OFFSET(svl_off, smb_server_t, sv_session_list); GET_OFFSET(ll_off, smb_llist_t, ll_list); return (svl_off + ll_off); } static int smb_server_exp_off_nbt_list(void) { int svd_off, lds_off, ll_off; /* OFFSETOF(smb_server_t, sv_nbt_daemon.ld_session_list.ll_list); */ GET_OFFSET(svd_off, smb_server_t, sv_nbt_daemon); /* * We can't do OFFSETOF() because the member doesn't exist, * but we want backwards compatibility to old cores */ lds_off = mdb_ctf_offsetof_by_name("smb_listener_daemon_t", "ld_session_list"); if (lds_off < 0) { mdb_warn("cannot lookup: " "smb_listener_daemon_t .ld_session_list"); return (-1); } GET_OFFSET(ll_off, smb_llist_t, ll_list); return (svd_off + lds_off + ll_off); } static int smb_server_exp_off_tcp_list(void) { int svd_off, lds_off, ll_off; /* OFFSETOF(smb_server_t, sv_tcp_daemon.ld_session_list.ll_list); */ GET_OFFSET(svd_off, smb_server_t, sv_tcp_daemon); /* * We can't do OFFSETOF() because the member doesn't exist, * but we want backwards compatibility to old cores */ lds_off = mdb_ctf_offsetof_by_name("smb_listener_daemon_t", "ld_session_list"); if (lds_off < 0) { mdb_warn("cannot lookup: " "smb_listener_daemon_t .ld_session_list"); return (-1); } GET_OFFSET(ll_off, smb_llist_t, ll_list); return (svd_off + lds_off + ll_off); } /* * List of objects that can be expanded under a server structure. */ static const smb_exp_t smb_server_exp[] = { { SMB_OPT_ALL_OBJ, "list", smb_server_exp_off_sv_list, "smbsess", "smb_session"}, { 0 } }; /* for backwards compatibility only */ static const smb_exp_t smb_server_exp_old[] = { { SMB_OPT_ALL_OBJ, "list", smb_server_exp_off_nbt_list, "smbsess", "smb_session"}, { SMB_OPT_ALL_OBJ, "list", smb_server_exp_off_tcp_list, "smbsess", "smb_session"}, { 0 } }; /* * ::smbsrv * * smbsrv dcmd - Print out smb_server structures. */ /*ARGSUSED*/ static int smbsrv_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; ulong_t indent = 0; const smb_exp_t *sv_exp; mdb_ctf_id_t id; ulong_t off; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (smb_obj_list("smb_server", opts | SMB_OPT_SERVER, flags)); if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_SERVER)) || !(opts & SMB_OPT_WALK)) { mdb_smb_server_t *sv; char state[40]; sv = mdb_zalloc(sizeof (*sv), UM_SLEEP | UM_GC); if (mdb_ctf_vread(sv, SMBSRV_SCOPE "smb_server_t", "mdb_smb_server_t", addr, 0) < 0) { mdb_warn("failed to read smb_server at %p", addr); return (DCMD_ERR); } indent = SMB_DCMD_INDENT; if (opts & SMB_OPT_VERBOSE) { mdb_arg_t argv; argv.a_type = MDB_TYPE_STRING; argv.a_un.a_str = "smb_server_t"; if (mdb_call_dcmd("print", addr, flags, 1, &argv)) return (DCMD_ERR); } else { if (DCMD_HDRSPEC(flags)) mdb_printf( "%%%-?s% " "%-4s% " "%-32s% " "%%\n", "SERVER", "ZONE", "STATE"); get_enum(state, sizeof (state), "smb_server_state_t", sv->sv_state, "SMB_SERVER_STATE_"); mdb_printf("%-?p %-4d %-32s \n", addr, sv->sv_zid, state); } } /* if we can't look up the type name, just error out */ if (mdb_ctf_lookup_by_name("smb_server_t", &id) == -1) return (DCMD_ERR); if (mdb_ctf_offsetof(id, "sv_session_list", &off) == -1) /* sv_session_list doesn't exist; old core */ sv_exp = smb_server_exp_old; else sv_exp = smb_server_exp; if (smb_obj_expand(addr, opts, sv_exp, indent)) return (DCMD_ERR); return (DCMD_OK); } /* * ***************************************************************************** * ***************************** smb_session_t ********************************* * ***************************************************************************** */ /* * After some changes merged from upstream, "::smblist" was failing with * "inexact match for union au_addr (au_addr)" because the CTF data for * the target vs mdb were apparently not exactly the same (unknown why). * * As described above mdb_ctf_vread(), the recommended way to read a * union is to use an mdb struct with only the union "arm" appropriate * to the given type instance. That's difficult in this case, so we * use a local union with only the in6_addr_t union arm (otherwise * identical to smb_inaddr_t) and just cast it to an smb_inaddr_t */ typedef struct mdb_smb_inaddr { union { #if 0 /* The real smb_inaddr_t has these too. */ in_addr_t au_ipv4; in6_addr_t au_ipv6; #endif in6_addr_t au_ip; } au_addr; int a_family; } mdb_smb_inaddr_t; typedef struct mdb_smb_session { uint64_t s_kid; smb_session_state_t s_state; uint32_t s_flags; uint16_t s_local_port; uint16_t s_remote_port; mdb_smb_inaddr_t ipaddr; mdb_smb_inaddr_t local_ipaddr; int dialect; smb_slist_t s_req_list; smb_llist_t s_xa_list; smb_llist_t s_user_list; smb_llist_t s_tree_list; volatile uint32_t s_tree_cnt; volatile uint32_t s_file_cnt; volatile uint32_t s_dir_cnt; char workstation[SMB_PI_MAX_HOST]; } mdb_smb_session_t; static int smb_session_exp_off_req_list(void) { int rl_off, sl_off; /* OFFSETOF(smb_session_t, s_req_list.sl_list); */ GET_OFFSET(rl_off, smb_session_t, s_req_list); GET_OFFSET(sl_off, smb_slist_t, sl_list); return (rl_off + sl_off); } static int smb_session_exp_off_user_list(void) { int ul_off, ll_off; /* OFFSETOF(smb_session_t, s_user_list.ll_list); */ GET_OFFSET(ul_off, smb_session_t, s_user_list); GET_OFFSET(ll_off, smb_llist_t, ll_list); return (ul_off + ll_off); } static int smb_session_exp_off_tree_list(void) { int tl_off, ll_off; /* OFFSETOF(smb_session_t, s_tree_list.ll_list); */ GET_OFFSET(tl_off, smb_session_t, s_tree_list); GET_OFFSET(ll_off, smb_llist_t, ll_list); return (tl_off + ll_off); } /* * List of objects that can be expanded under a session structure. */ static const smb_exp_t smb_session_exp[] = { { SMB_OPT_USER, "list", smb_session_exp_off_user_list, "smbuser", "smb_user"}, { SMB_OPT_TREE | SMB_OPT_OFILE | SMB_OPT_ODIR, "list", smb_session_exp_off_tree_list, "smbtree", "smb_tree"}, { SMB_OPT_REQUEST, "list", smb_session_exp_off_req_list, "smbreq", "smb_request"}, { 0 } }; static void smbsess_help(void) { mdb_printf( "Display the contents of smb_session_t, with optional" " filtering.\n\n"); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf( "-v\tDisplay verbose smb_session information\n" "-r\tDisplay the list of smb requests attached\n" "-u\tDisplay the list of users attached\n"); } /* * ::smbsess * * smbsess dcmd - Print out the smb_session structure. */ static int smbsess_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; ulong_t indent = 0; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { opts |= SMB_OPT_SESSION; opts &= ~SMB_OPT_SERVER; return (smb_obj_list("smb_session", opts, flags)); } if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_SESSION)) || !(opts & SMB_OPT_WALK)) { char cipaddr[INET6_ADDRSTRLEN]; char lipaddr[INET6_ADDRSTRLEN]; int ipaddrstrlen = INET6_ADDRSTRLEN; mdb_smb_session_t *se; char state[40]; indent = SMB_DCMD_INDENT; se = mdb_zalloc(sizeof (*se), UM_SLEEP | UM_GC); if (mdb_ctf_vread(se, SMBSRV_SCOPE "smb_session_t", "mdb_smb_session_t", addr, 0) < 0) { mdb_warn("failed to read smb_session at %p", addr); return (DCMD_ERR); } get_enum(state, sizeof (state), "smb_session_state_t", se->s_state, "SMB_SESSION_STATE_"); smb_inaddr_ntop((smb_inaddr_t *)&se->ipaddr, cipaddr, ipaddrstrlen); smb_inaddr_ntop((smb_inaddr_t *)&se->local_ipaddr, lipaddr, ipaddrstrlen); if (opts & SMB_OPT_VERBOSE) { mdb_printf("%%SMB session information " "(%p): %%\n", addr); mdb_printf("Client IP address: %s %d\n", cipaddr, se->s_remote_port); mdb_printf("Local IP Address: %s %d\n", lipaddr, se->s_local_port); mdb_printf("Session KID: %u\n", se->s_kid); mdb_printf("Workstation Name: %s\n", se->workstation); mdb_printf("Session state: %u (%s)\n", se->s_state, state); mdb_printf("Session dialect: %#x\n", se->dialect); mdb_printf("Number of Users: %u\n", se->s_user_list.ll_count); mdb_printf("Number of Trees: %u\n", se->s_tree_cnt); mdb_printf("Number of Files: %u\n", se->s_file_cnt); mdb_printf("Number of Shares: %u\n", se->s_dir_cnt); mdb_printf("Number of active Transact.: %u\n\n", se->s_xa_list.ll_count); } else { /* * Use a reasonable mininum field width for the * IP addr so the summary (usually) won't wrap. */ int ipwidth = 22; if (DCMD_HDRSPEC(flags)) { mdb_printf( "%%%-?s %-*s %-8s %-8s %-12s%%\n", "SESSION", ipwidth, "IP_ADDR", "PORT", "DIALECT", "STATE"); } mdb_printf("%-?p %-*s %-8d %-8#x %s\n", addr, ipwidth, cipaddr, se->s_remote_port, se->dialect, state); } } if (smb_obj_expand(addr, opts, smb_session_exp, indent)) return (DCMD_ERR); return (DCMD_OK); } /* * ***************************************************************************** * **************************** smb_request_t ********************************** * ***************************************************************************** */ typedef struct mdb_smb_request { smb_req_state_t sr_state; smb_session_t *session; struct mbuf_chain command; struct mbuf_chain reply; unsigned char first_smb_com; unsigned char smb_com; uint16_t smb_tid; uint32_t smb_pid; uint16_t smb_uid; uint16_t smb_mid; uint16_t smb_fid; uint16_t smb2_cmd_code; uint64_t smb2_messageid; uint64_t smb2_ssnid; struct smb_tree *tid_tree; struct smb_ofile *fid_ofile; smb_user_t *uid_user; kthread_t *sr_worker; hrtime_t sr_time_submitted; hrtime_t sr_time_active; hrtime_t sr_time_start; } mdb_smb_request_t; #define SMB_REQUEST_BANNER \ "%%%-?s %-14s %-?s %-16s %-16s%%\n" #define SMB_REQUEST_FORMAT \ "%-?p 0x%-12llx %-?p %-16s %s\n" /* * ::smbreq * * smbreq dcmd - Print out smb_request_t */ static int smbreq_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { opts |= SMB_OPT_REQUEST; opts &= ~(SMB_OPT_SERVER | SMB_OPT_SESSION | SMB_OPT_USER); return (smb_obj_list("smb_request", opts, flags)); } if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_REQUEST)) || !(opts & SMB_OPT_WALK)) { mdb_smb_request_t *sr; char state[40]; const char *cur_cmd_name; uint_t cur_cmd_code; uint64_t msg_id; sr = mdb_zalloc(sizeof (*sr), UM_SLEEP | UM_GC); if (mdb_ctf_vread(sr, SMBSRV_SCOPE "smb_request_t", "mdb_smb_request_t", addr, 0) < 0) { mdb_warn("failed to read smb_request at %p", addr); return (DCMD_ERR); } get_enum(state, sizeof (state), "smb_req_state_t", sr->sr_state, "SMB_REQ_STATE_"); if (sr->smb2_cmd_code != 0) { /* SMB2 request */ cur_cmd_code = sr->smb2_cmd_code; if (cur_cmd_code > SMB2_INVALID_CMD) cur_cmd_code = SMB2_INVALID_CMD; cur_cmd_name = smb2_cmd_names[cur_cmd_code]; msg_id = sr->smb2_messageid; } else { /* SMB1 request */ cur_cmd_code = sr->smb_com & 0xFF; cur_cmd_name = smb_com[cur_cmd_code].smb_com; msg_id = sr->smb_mid; } if (opts & SMB_OPT_VERBOSE) { mdb_printf( "%%SMB request information (%p):" "%%\n\n", addr); if (sr->smb2_cmd_code == 0) { /* SMB1 request */ mdb_printf( "first SMB COM: %u (%s)\n", sr->first_smb_com, smb_com[sr->first_smb_com].smb_com); } mdb_printf( "current SMB COM: %u (%s)\n", cur_cmd_code, cur_cmd_name); mdb_printf( "state: %u (%s)\n", sr->sr_state, state); if (sr->smb2_ssnid != 0) { mdb_printf( "SSNID(user): 0x%llx (%p)\n", sr->smb2_ssnid, sr->uid_user); } else { mdb_printf( "UID(user): %u (%p)\n", sr->smb_uid, sr->uid_user); } mdb_printf( "TID(tree): %u (%p)\n", sr->smb_tid, sr->tid_tree); mdb_printf( "FID(file): %u (%p)\n", sr->smb_fid, sr->fid_ofile); mdb_printf( "PID: %u\n", sr->smb_pid); mdb_printf( "MID: 0x%llx\n", msg_id); /* * Note: mdb_gethrtime() is only available in kmdb */ #ifdef _KERNEL if (sr->sr_time_submitted != 0) { uint64_t waiting = 0; uint64_t running = 0; if (sr->sr_time_active != 0) { waiting = sr->sr_time_active - sr->sr_time_submitted; running = mdb_gethrtime() - sr->sr_time_active; } else { waiting = mdb_gethrtime() - sr->sr_time_submitted; } waiting /= NANOSEC; running /= NANOSEC; mdb_printf( "waiting time: %lld\n", waiting); mdb_printf( "running time: %lld\n", running); } #endif /* _KERNEL */ mdb_printf( "worker thread: %p\n", sr->sr_worker); if (sr->sr_worker != NULL) { smb_worker_findstack((uintptr_t)sr->sr_worker); } } else { if (DCMD_HDRSPEC(flags)) mdb_printf( SMB_REQUEST_BANNER, "REQUEST", "MSG_ID", "WORKER", "STATE", "COMMAND"); mdb_printf( SMB_REQUEST_FORMAT, addr, msg_id, sr->sr_worker, state, cur_cmd_name); } } return (DCMD_OK); } static void smbreq_dump_help(void) { mdb_printf( "Dump the network data for an smb_request_t, either" " command, reply, or (by default) both. Optionally" " append data to a pcap file (mdb only, not kmdb).\n\n"); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf( "-c\tDump only the SMB command message\n" "-r\tDump only the SMB reply message (if present)\n" "-o FILE\tOutput to FILE (append) in pcap format\n"); } #define SMB_RDOPT_COMMAND 1 #define SMB_RDOPT_REPLY 2 #define SMB_RDOPT_OUTFILE 4 /* * Like "smbreq" but just dump the command/reply messages. * With the output file option, append to a pcap file. */ static int smbreq_dump_dcmd(uintptr_t rqaddr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_smb_session_t *ssn; mdb_smb_request_t *sr; char *outfile = NULL; dump_func_t dump_func; uint64_t msgid; uintptr_t ssnaddr; uint_t opts = 0; int rc = DCMD_OK; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'c', MDB_OPT_SETBITS, SMB_RDOPT_COMMAND, &opts, 'r', MDB_OPT_SETBITS, SMB_RDOPT_REPLY, &opts, 'o', MDB_OPT_STR, &outfile, NULL) != argc) return (DCMD_USAGE); #ifdef _KMDB if (outfile != NULL) { mdb_warn("smbreq_dump -o option not supported in kmdb\n"); return (DCMD_ERR); } #endif /* _KMDB */ /* * Default without -c or -r is to dump both. */ if ((opts & (SMB_RDOPT_COMMAND | SMB_RDOPT_REPLY)) == 0) opts |= SMB_RDOPT_COMMAND | SMB_RDOPT_REPLY; /* * Get the smb_request_t, for the cmd/reply messages. */ sr = mdb_zalloc(sizeof (*sr), UM_SLEEP | UM_GC); if (mdb_ctf_vread(sr, SMBSRV_SCOPE "smb_request_t", "mdb_smb_request_t", rqaddr, 0) < 0) { mdb_warn("failed to read smb_request at %p", rqaddr); return (DCMD_ERR); } /* * Get the session too, for the IP addresses & ports. */ ssnaddr = (uintptr_t)sr->session; ssn = mdb_zalloc(sizeof (*ssn), UM_SLEEP | UM_GC); if (mdb_ctf_vread(ssn, SMBSRV_SCOPE "smb_session_t", "mdb_smb_session_t", ssnaddr, 0) < 0) { mdb_warn("failed to read smb_request at %p", ssnaddr); return (DCMD_ERR); } #ifndef _KMDB if (outfile != NULL) { rc = smbsrv_pcap_open(outfile); if (rc != DCMD_OK) return (rc); dump_func = smbsrv_pcap_dump; } else #endif /* _KMDB */ { dump_func = smb_req_dump; } if (sr->smb2_messageid != 0) msgid = sr->smb2_messageid; else msgid = sr->smb_mid; mdb_printf("Dumping request %-?p, Msg_ID 0x%llx\n", rqaddr, msgid); if (opts & SMB_RDOPT_COMMAND) { /* * Dump the command, length=max_bytes * src=remote, dst=local */ rc = dump_func(&sr->command, sr->command.max_bytes, (smb_inaddr_t *)&ssn->ipaddr, ssn->s_remote_port, (smb_inaddr_t *)&ssn->local_ipaddr, ssn->s_local_port, sr->sr_time_submitted, B_FALSE); } if ((opts & SMB_RDOPT_REPLY) != 0 && rc == DCMD_OK) { /* * Dump the reply, length=chain_offset * src=local, dst=remote */ rc = dump_func(&sr->reply, sr->reply.chain_offset, (smb_inaddr_t *)&ssn->local_ipaddr, ssn->s_local_port, (smb_inaddr_t *)&ssn->ipaddr, ssn->s_remote_port, sr->sr_time_start, B_TRUE); } #ifndef _KMDB if (outfile != NULL) { smbsrv_pcap_close(); } #endif return (DCMD_OK); } struct req_dump_state { int32_t rem_len; }; static int smb_req_dump(struct mbuf_chain *mbc, int32_t smb_len, smb_inaddr_t *src_ip, uint16_t src_port, smb_inaddr_t *dst_ip, uint16_t dst_port, hrtime_t rqtime, boolean_t is_reply) { char src_buf[INET6_ADDRSTRLEN]; char dst_buf[INET6_ADDRSTRLEN]; struct req_dump_state dump_state; _NOTE(ARGUNUSED(rqtime)); if (smb_len < 4) return (DCMD_OK); if (mbc->chain == NULL) return (DCMD_ERR); smb_inaddr_ntop(src_ip, src_buf, sizeof (src_buf)); smb_inaddr_ntop(dst_ip, dst_buf, sizeof (dst_buf)); mdb_printf("%-8s SRC: %s/%u DST: %s/%u LEN: %u\n", (is_reply) ? "Reply:" : "Call:", src_buf, src_port, dst_buf, dst_port, smb_len); /* * Calling "smb_mbuf_dump" with a wrapper function * so we can set its length arg, and decrement * req_dump_state.rem_len as it goes. */ dump_state.rem_len = smb_len; if (mdb_pwalk("smb_mbuf_walker", smb_req_dump_m, &dump_state, (uintptr_t)mbc->chain) == -1) { mdb_warn("cannot walk smb_req mbuf_chain"); return (DCMD_ERR); } return (DCMD_OK); } static int smb_req_dump_m(uintptr_t m_addr, const void *data, void *arg) { struct req_dump_state *st = arg; const struct mbuf *m = data; mdb_arg_t argv; int cnt; cnt = st->rem_len; if (cnt > m->m_len) cnt = m->m_len; if (cnt <= 0) return (WALK_DONE); argv.a_type = MDB_TYPE_IMMEDIATE; argv.a_un.a_val = cnt; if (mdb_call_dcmd("smb_mbuf_dump", m_addr, 0, 1, &argv) < 0) { mdb_warn("%p::smb_mbuf_dump failed\n", m_addr); return (WALK_ERR); } st->rem_len -= cnt; return (WALK_NEXT); } /* * ***************************************************************************** * ****************************** smb_user_t *********************************** * ***************************************************************************** */ typedef struct mdb_smb_user { smb_user_state_t u_state; struct smb_server *u_server; smb_session_t *u_session; uint16_t u_name_len; char *u_name; uint16_t u_domain_len; char *u_domain; time_t u_logon_time; cred_t *u_cred; cred_t *u_privcred; uint64_t u_ssnid; uint32_t u_refcnt; uint32_t u_flags; uint32_t u_privileges; uint16_t u_uid; } mdb_smb_user_t; static const mdb_bitmask_t user_flag_bits[] = { { "ANON", SMB_USER_FLAG_ANON, SMB_USER_FLAG_ANON }, { "GUEST", SMB_USER_FLAG_GUEST, SMB_USER_FLAG_GUEST }, { "POWER_USER", SMB_USER_FLAG_POWER_USER, SMB_USER_FLAG_POWER_USER }, { "BACKUP_OP", SMB_USER_FLAG_BACKUP_OPERATOR, SMB_USER_FLAG_BACKUP_OPERATOR }, { "ADMIN", SMB_USER_FLAG_ADMIN, SMB_USER_FLAG_ADMIN }, { NULL, 0, 0 } }; static const mdb_bitmask_t user_priv_bits[] = { /* * Old definitions of these bits, for when we're * looking at an older core file. These happen to * have no overlap with the current definitions. */ { "TAKE_OWNER", 1, 1 }, { "BACKUP", 2, 2 }, { "RESTORE", 4, 4 }, { "SECURITY", 8, 8 }, /* * Current definitions */ { "SECURITY", SMB_USER_PRIV_SECURITY, SMB_USER_PRIV_SECURITY }, { "TAKE_OWNER", SMB_USER_PRIV_TAKE_OWNERSHIP, SMB_USER_PRIV_TAKE_OWNERSHIP }, { "BACKUP", SMB_USER_PRIV_BACKUP, SMB_USER_PRIV_BACKUP }, { "RESTORE", SMB_USER_PRIV_RESTORE, SMB_USER_PRIV_RESTORE }, { "CHANGE_NOTIFY", SMB_USER_PRIV_CHANGE_NOTIFY, SMB_USER_PRIV_CHANGE_NOTIFY }, { "READ_FILE", SMB_USER_PRIV_READ_FILE, SMB_USER_PRIV_READ_FILE }, { "WRITE_FILE", SMB_USER_PRIV_WRITE_FILE, SMB_USER_PRIV_WRITE_FILE }, { NULL, 0, 0 } }; static void smbuser_help(void) { mdb_printf( "Display the contents of smb_user_t, with optional filtering.\n\n"); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf( "-v\tDisplay verbose smb_user information\n"); } static int smbuser_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { opts |= SMB_OPT_USER; opts &= ~(SMB_OPT_SERVER | SMB_OPT_SESSION | SMB_OPT_REQUEST); return (smb_obj_list("smb_user", opts, flags)); } if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_USER)) || !(opts & SMB_OPT_WALK)) { mdb_smb_user_t *user; char *account; user = mdb_zalloc(sizeof (*user), UM_SLEEP | UM_GC); if (mdb_ctf_vread(user, SMBSRV_SCOPE "smb_user_t", "mdb_smb_user_t", addr, 0) < 0) { mdb_warn("failed to read smb_user at %p", addr); return (DCMD_ERR); } account = mdb_zalloc(user->u_domain_len + user->u_name_len + 2, UM_SLEEP | UM_GC); if (user->u_domain_len) (void) mdb_vread(account, user->u_domain_len, (uintptr_t)user->u_domain); strcat(account, "\\"); if (user->u_name_len) (void) mdb_vread(account + strlen(account), user->u_name_len, (uintptr_t)user->u_name); if (opts & SMB_OPT_VERBOSE) { char state[40]; get_enum(state, sizeof (state), "smb_user_state_t", user->u_state, "SMB_USER_STATE_"); mdb_printf("%%SMB user information (%p):" "%%\n", addr); mdb_printf("UID: %u\n", user->u_uid); mdb_printf("SSNID: %llx\n", user->u_ssnid); mdb_printf("State: %d (%s)\n", user->u_state, state); mdb_printf("Flags: 0x%08x <%b>\n", user->u_flags, user->u_flags, user_flag_bits); mdb_printf("Privileges: 0x%08x <%b>\n", user->u_privileges, user->u_privileges, user_priv_bits); mdb_printf("Credential: %p\n", user->u_cred); mdb_printf("Reference Count: %d\n", user->u_refcnt); mdb_printf("User Account: %s\n\n", account); } else { if (DCMD_HDRSPEC(flags)) mdb_printf( "%%%?-s " "%-5s " "%-16s " "%-32s%%\n", "USER", "UID", "SSNID", "ACCOUNT"); mdb_printf("%-?p %-5u %-16llx %-32s\n", addr, user->u_uid, user->u_ssnid, account); } } return (DCMD_OK); } /* * ***************************************************************************** * ****************************** smb_tree_t *********************************** * ***************************************************************************** */ typedef struct mdb_smb_tree { smb_tree_state_t t_state; smb_node_t *t_snode; smb_lavl_t t_ofile_list; smb_llist_t t_odir_list; uint32_t t_refcnt; uint32_t t_flags; int32_t t_res_type; uint16_t t_tid; uint16_t t_umask; char t_sharename[MAXNAMELEN]; char t_resource[MAXPATHLEN]; char t_typename[SMB_TYPENAMELEN]; char t_volume[SMB_VOLNAMELEN]; } mdb_smb_tree_t; static int smb_tree_exp_off_ofile_avl(void) { int tf_off, la_off; /* OFFSETOF(smb_tree_t, t_ofile_list.ll_list); */ GET_OFFSET(tf_off, smb_tree_t, t_ofile_list); GET_OFFSET(la_off, smb_lavl_t, la_tree); return (tf_off + la_off); } static int smb_tree_exp_off_odir_list(void) { int td_off, ll_off; /* OFFSETOF(smb_tree_t, t_odir_list.ll_list); */ GET_OFFSET(td_off, smb_tree_t, t_odir_list); GET_OFFSET(ll_off, smb_llist_t, ll_list); return (td_off + ll_off); } /* * List of objects that can be expanded under a tree structure. */ static const smb_exp_t smb_tree_exp[] = { { SMB_OPT_OFILE, "avl", smb_tree_exp_off_ofile_avl, "smbofile", "smb_ofile"}, { SMB_OPT_ODIR, "list", smb_tree_exp_off_odir_list, "smbodir", "smb_odir"}, { 0 } }; static const mdb_bitmask_t tree_flag_bits[] = { { "RO", SMB_TREE_READONLY, SMB_TREE_READONLY }, { "ACLS", SMB_TREE_SUPPORTS_ACLS, SMB_TREE_SUPPORTS_ACLS }, { "STREAMS", SMB_TREE_STREAMS, SMB_TREE_STREAMS }, { "CI", SMB_TREE_CASEINSENSITIVE, SMB_TREE_CASEINSENSITIVE }, { "NO_CS", SMB_TREE_NO_CASESENSITIVE, SMB_TREE_NO_CASESENSITIVE }, { "NO_EXPORT", SMB_TREE_NO_EXPORT, SMB_TREE_NO_EXPORT }, { "OPLOCKS", SMB_TREE_OPLOCKS, SMB_TREE_OPLOCKS }, { "SHORTNAMES", SMB_TREE_SHORTNAMES, SMB_TREE_SHORTNAMES }, { "XVATTR", SMB_TREE_XVATTR, SMB_TREE_XVATTR }, { "DIRENTFLAGS", SMB_TREE_DIRENTFLAGS, SMB_TREE_DIRENTFLAGS }, { "ACL_CR", SMB_TREE_ACLONCREATE, SMB_TREE_ACLONCREATE }, { "ACEMASK", SMB_TREE_ACEMASKONACCESS, SMB_TREE_ACEMASKONACCESS }, { "NFS_MNT", SMB_TREE_NFS_MOUNTED, SMB_TREE_NFS_MOUNTED }, { "UNICODE", SMB_TREE_UNICODE_ON_DISK, SMB_TREE_UNICODE_ON_DISK }, { "CATIA", SMB_TREE_CATIA, SMB_TREE_CATIA }, { "ABE", SMB_TREE_ABE, SMB_TREE_ABE }, { "QUOTA", SMB_TREE_QUOTA, SMB_TREE_QUOTA }, { "DFSROOT", SMB_TREE_DFSROOT, SMB_TREE_DFSROOT }, { "SPARSE", SMB_TREE_SPARSE, SMB_TREE_SPARSE }, { "XMOUNTS", SMB_TREE_TRAVERSE_MOUNTS, SMB_TREE_TRAVERSE_MOUNTS }, { "FORCE_L2_OPLOCK", SMB_TREE_FORCE_L2_OPLOCK, SMB_TREE_FORCE_L2_OPLOCK }, { "CA", SMB_TREE_CA, SMB_TREE_CA }, { NULL, 0, 0 } }; static void smbtree_help(void) { mdb_printf( "Display the contents of smb_tree_t, with optional filtering.\n\n"); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf( "-v\tDisplay verbose smb_tree information\n" "-d\tDisplay the list of smb_odirs attached\n" "-f\tDisplay the list of smb_ofiles attached\n"); } static int smbtree_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; ulong_t indent = 0; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { opts |= SMB_OPT_TREE; opts &= ~(SMB_OPT_SERVER | SMB_OPT_SESSION | SMB_OPT_REQUEST | SMB_OPT_USER); return (smb_obj_list("smb_tree", opts, flags)); } if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_TREE)) || !(opts & SMB_OPT_WALK)) { mdb_smb_tree_t *tree; indent = SMB_DCMD_INDENT; tree = mdb_zalloc(sizeof (*tree), UM_SLEEP | UM_GC); if (mdb_ctf_vread(tree, SMBSRV_SCOPE "smb_tree_t", "mdb_smb_tree_t", addr, 0) < 0) { mdb_warn("failed to read smb_tree at %p", addr); return (DCMD_ERR); } if (opts & SMB_OPT_VERBOSE) { char state[40]; get_enum(state, sizeof (state), "smb_tree_state_t", tree->t_state, "SMB_TREE_STATE_"); mdb_printf("%%SMB tree information (%p):" "%%\n\n", addr); mdb_printf("TID: %04x\n", tree->t_tid); mdb_printf("State: %d (%s)\n", tree->t_state, state); mdb_printf("Share: %s\n", tree->t_sharename); mdb_printf("Resource: %s\n", tree->t_resource); mdb_printf("Type: %s\n", tree->t_typename); mdb_printf("Volume: %s\n", tree->t_volume); mdb_printf("Umask: %04x\n", tree->t_umask); mdb_printf("Flags: %08x <%b>\n", tree->t_flags, tree->t_flags, tree_flag_bits); mdb_printf("SMB Node: %llx\n", tree->t_snode); mdb_printf("Reference Count: %d\n\n", tree->t_refcnt); } else { if (DCMD_HDRSPEC(flags)) mdb_printf( "%%%-?s %-5s %-16s %-32s%%\n", "TREE", "TID", "SHARE NAME", "RESOURCE"); mdb_printf("%-?p %-5u %-16s %-32s\n", addr, tree->t_tid, tree->t_sharename, tree->t_resource); } } if (smb_obj_expand(addr, opts, smb_tree_exp, indent)) return (DCMD_ERR); return (DCMD_OK); } /* * ***************************************************************************** * ****************************** smb_odir_t *********************************** * ***************************************************************************** */ typedef struct mdb_smb_odir { smb_odir_state_t d_state; smb_session_t *d_session; smb_user_t *d_user; smb_tree_t *d_tree; smb_node_t *d_dnode; uint16_t d_odid; uint32_t d_refcnt; char d_pattern[MAXNAMELEN]; } mdb_smb_odir_t; static int smbodir_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { opts |= SMB_OPT_ODIR; opts &= ~(SMB_OPT_SERVER | SMB_OPT_SESSION | SMB_OPT_REQUEST | SMB_OPT_USER | SMB_OPT_TREE | SMB_OPT_OFILE); return (smb_obj_list("smb_odir", opts, flags)); } if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_ODIR)) || !(opts & SMB_OPT_WALK)) { mdb_smb_odir_t *od; od = mdb_zalloc(sizeof (*od), UM_SLEEP | UM_GC); if (mdb_ctf_vread(od, SMBSRV_SCOPE "smb_odir_t", "mdb_smb_odir_t", addr, 0) < 0) { mdb_warn("failed to read smb_odir at %p", addr); return (DCMD_ERR); } if (opts & SMB_OPT_VERBOSE) { char state[40]; get_enum(state, sizeof (state), "smb_odir_state_t", od->d_state, "SMB_ODIR_STATE_"); mdb_printf( "%%SMB odir information (%p):%%\n\n", addr); mdb_printf("State: %d (%s)\n", od->d_state, state); mdb_printf("SID: %u\n", od->d_odid); mdb_printf("User: %p\n", od->d_user); mdb_printf("Tree: %p\n", od->d_tree); mdb_printf("Reference Count: %d\n", od->d_refcnt); mdb_printf("Pattern: %s\n", od->d_pattern); mdb_printf("SMB Node: %p\n\n", od->d_dnode); } else { if (DCMD_HDRSPEC(flags)) mdb_printf( "%%%-?s " "%-5s " "%-?s " "%-16s%%\n", "ODIR", "SID", "VNODE", "PATTERN"); mdb_printf("%?p %-5u %-16p %s\n", addr, od->d_odid, od->d_dnode, od->d_pattern); } } return (DCMD_OK); } /* * ***************************************************************************** * ****************************** smb_ofile_t ********************************** * ***************************************************************************** */ typedef struct mdb_smb_ofile { smb_ofile_state_t f_state; struct smb_server *f_server; smb_session_t *f_session; smb_user_t *f_user; smb_tree_t *f_tree; smb_node_t *f_node; smb_odir_t *f_odir; smb_opipe_t *f_pipe; uint32_t f_uniqid; uint32_t f_refcnt; uint32_t f_flags; uint32_t f_granted_access; uint32_t f_share_access; uint16_t f_fid; uint16_t f_ftype; uint64_t f_llf_pos; int f_mode; cred_t *f_cr; pid_t f_pid; uintptr_t f_lease; smb_dh_vers_t dh_vers; } mdb_smb_ofile_t; static const mdb_bitmask_t ofile_flag_bits[] = { { "RO", 1, 1 }, /* old SMB_OFLAGS_READONLY */ { "EXEC", SMB_OFLAGS_EXECONLY, SMB_OFLAGS_EXECONLY }, { "DELETE", SMB_OFLAGS_SET_DELETE_ON_CLOSE, SMB_OFLAGS_SET_DELETE_ON_CLOSE }, { "POS_VALID", SMB_OFLAGS_LLF_POS_VALID, SMB_OFLAGS_LLF_POS_VALID }, { NULL, 0, 0} }; static const mdb_bitmask_t smb_sharemode_bits[] = { { "READ", FILE_SHARE_READ, FILE_SHARE_READ }, { "WRITE", FILE_SHARE_WRITE, FILE_SHARE_WRITE }, { "DELETE", FILE_SHARE_DELETE, FILE_SHARE_DELETE }, { NULL, 0, 0} }; static int smbofile_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { opts |= SMB_OPT_OFILE; opts &= ~(SMB_OPT_SERVER | SMB_OPT_SESSION | SMB_OPT_REQUEST | SMB_OPT_USER | SMB_OPT_TREE | SMB_OPT_ODIR); return (smb_obj_list("smb_ofile", opts, flags)); } if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_OFILE)) || !(opts & SMB_OPT_WALK)) { mdb_smb_ofile_t *of; of = mdb_zalloc(sizeof (*of), UM_SLEEP | UM_GC); if (mdb_ctf_vread(of, SMBSRV_SCOPE "smb_ofile_t", "mdb_smb_ofile_t", addr, 0) < 0) { mdb_warn("failed to read smb_ofile at %p", addr); return (DCMD_ERR); } if (opts & SMB_OPT_VERBOSE) { char state[40]; char durable[40]; get_enum(state, sizeof (state), "smb_ofile_state_t", of->f_state, "SMB_OFILE_STATE_"); get_enum(durable, sizeof (durable), "smb_dh_vers_t", of->dh_vers, "SMB2_"); mdb_printf( "%%SMB ofile information (%p):%%\n\n", addr); mdb_printf("FID: %u\n", of->f_fid); mdb_printf("State: %d (%s)\n", of->f_state, state); mdb_printf("DH Type: %d (%s)\n", of->dh_vers, durable); mdb_printf("Lease: %p\n", of->f_lease); mdb_printf("SMB Node: %p\n", of->f_node); mdb_printf("LLF Offset: 0x%llx (%s)\n", of->f_llf_pos, ((of->f_flags & SMB_OFLAGS_LLF_POS_VALID) ? "Valid" : "Invalid")); mdb_printf("Flags: 0x%08x <%b>\n", of->f_flags, of->f_flags, ofile_flag_bits); mdb_printf("Granted Acc.: 0x%08x <%b>\n", of->f_granted_access, of->f_granted_access, nt_access_bits); mdb_printf("Share Mode: 0x%08x <%b>\n", of->f_share_access, of->f_share_access, smb_sharemode_bits); mdb_printf("User: %p\n", of->f_user); mdb_printf("Tree: %p\n", of->f_tree); mdb_printf("Credential: %p\n\n", of->f_cr); } else { if (DCMD_HDRSPEC(flags)) mdb_printf( "%%%-?s " "%-5s " "%-?s " "%-?s " "%-?s " "%%\n", "OFILE", "FID", "NODE", "CRED", "LEASE"); mdb_printf("%?p %-5u %-p %-p %-p\n", addr, of->f_fid, of->f_node, of->f_cr, of->f_lease); } } return (DCMD_OK); } static int smbdurable_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_smb_server_t *sv; if (!(flags & DCMD_ADDRSPEC)) { mdb_printf("require address of an smb_server_t\n"); return (WALK_ERR); } sv = mdb_zalloc(sizeof (*sv), UM_SLEEP | UM_GC); if (mdb_ctf_vread(sv, SMBSRV_SCOPE "smb_server_t", "mdb_smb_server_t", addr, 0) < 0) { mdb_warn("failed to read smb_server at %p", addr); return (DCMD_ERR); } if (mdb_pwalk_dcmd("smb_hash_walker", "smbofile", argc, argv, (uintptr_t)sv->sv_persistid_ht) == -1) { mdb_warn("failed to walk 'smb_ofile'"); return (DCMD_ERR); } return (DCMD_OK); } static int smb_hash_walk_init(mdb_walk_state_t *wsp) { smb_hash_t hash; int ll_off, sll_off, i; uintptr_t addr = wsp->walk_addr; if (addr == 0) { mdb_printf("require address of an smb_hash_t\n"); return (WALK_ERR); } GET_OFFSET(sll_off, smb_bucket_t, b_list); GET_OFFSET(ll_off, smb_llist_t, ll_list); if (mdb_vread(&hash, sizeof (hash), addr) == -1) { mdb_warn("failed to read smb_hash_t at %p", addr); return (WALK_ERR); } for (i = 0; i < hash.num_buckets; i++) { wsp->walk_addr = (uintptr_t)hash.buckets + (i * sizeof (smb_bucket_t)) + sll_off + ll_off; if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("failed to walk 'list'"); return (WALK_ERR); } } return (WALK_NEXT); } static int smb_hash_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } static int smbhashstat_cb(uintptr_t addr, const void *data, void *varg) { _NOTE(ARGUNUSED(varg)) const smb_bucket_t *bucket = data; mdb_printf("%-?p ", addr); /* smb_bucket_t */ mdb_printf("%-6u ", bucket->b_list.ll_count); mdb_printf("%-16u", bucket->b_max_seen); mdb_printf("%-u\n", (bucket->b_list.ll_wrop + bucket->b_list.ll_count) / 2); return (WALK_NEXT); } static int smbhashstat_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { _NOTE(ARGUNUSED(argc, argv)) if (!(flags & DCMD_ADDRSPEC)) { mdb_printf("require address of an smb_hash_t\n"); return (DCMD_USAGE); } if (DCMD_HDRSPEC(flags)) { mdb_printf( "%%" "%-?s " "%-6s " "%-16s" "%-s" "%%\n", "smb_bucket_t", "count", "largest seen", "inserts"); } if (mdb_pwalk("smb_hashstat_walker", smbhashstat_cb, NULL, addr) == -1) { mdb_warn("failed to walk 'smb_ofile'"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct smb_hash_wd { smb_bucket_t *bucket; smb_bucket_t *end; } smb_hash_wd_t; static int smb_hashstat_walk_init(mdb_walk_state_t *wsp) { int sll_off, ll_off; smb_hash_t hash; smb_bucket_t *buckets; uintptr_t addr = wsp->walk_addr; uint32_t arr_sz; smb_hash_wd_t *wd; if (addr == 0) { mdb_printf("require address of an smb_hash_t\n"); return (WALK_ERR); } GET_OFFSET(sll_off, smb_bucket_t, b_list); GET_OFFSET(ll_off, smb_llist_t, ll_list); if (mdb_vread(&hash, sizeof (hash), addr) == -1) { mdb_warn("failed to read smb_hash_t at %p", addr); return (WALK_ERR); } arr_sz = hash.num_buckets * sizeof (smb_bucket_t); buckets = mdb_alloc(arr_sz, UM_SLEEP | UM_GC); if (mdb_vread(buckets, arr_sz, (uintptr_t)hash.buckets) == -1) { mdb_warn("failed to read smb_bucket_t array at %p", hash.buckets); return (WALK_ERR); } wd = mdb_alloc(sizeof (*wd), UM_SLEEP | UM_GC); wd->bucket = buckets; wd->end = buckets + hash.num_buckets; wsp->walk_addr = (uintptr_t)hash.buckets; wsp->walk_data = wd; return (WALK_NEXT); } static int smb_hashstat_walk_step(mdb_walk_state_t *wsp) { int rc; smb_hash_wd_t *wd = wsp->walk_data; if (wd->bucket >= wd->end) return (WALK_DONE); rc = wsp->walk_callback(wsp->walk_addr, wd->bucket++, wsp->walk_cbdata); wsp->walk_addr += sizeof (smb_bucket_t); return (rc); } /* * smbsrv_leases */ static int smbsrv_leases_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; int ht_off; uintptr_t ht_addr; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { mdb_printf("require address of an smb_server_t\n"); return (DCMD_USAGE); } ht_off = mdb_ctf_offsetof_by_name("smb_server_t", "sv_lease_ht"); if (ht_off < 0) { mdb_warn("No .sv_lease_ht in server (old kernel?)"); return (DCMD_ERR); } addr += ht_off; if (mdb_vread(&ht_addr, sizeof (ht_addr), addr) <= 0) { mdb_warn("failed to read server .sv_lease_ht"); return (DCMD_ERR); } if (mdb_pwalk_dcmd("smb_hash_walker", "smblease", argc, argv, ht_addr) == -1) { mdb_warn("failed to walk 'smb_lease'"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct mdb_smb_lease { struct smb_node *ls_node; uint32_t ls_refcnt; uint32_t ls_state; uint16_t ls_epoch; uint8_t ls_key[SMB_LEASE_KEY_SZ]; } mdb_smb_lease_t; static const mdb_bitmask_t oplock_bits[]; static int smblease_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_smb_lease_t *ls; uint_t opts; int i; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { mdb_printf("require address of an smb_lease_t\n"); return (DCMD_USAGE); } if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_OFILE)) || !(opts & SMB_OPT_WALK)) { ls = mdb_zalloc(sizeof (*ls), UM_SLEEP | UM_GC); if (mdb_ctf_vread(ls, SMBSRV_SCOPE "smb_lease_t", "mdb_smb_lease_t", addr, 0) < 0) { mdb_warn("failed to read smb_lease_t at %p", addr); return (DCMD_ERR); } if (opts & SMB_OPT_VERBOSE) { mdb_printf( "%%SMB lease (%p):%%\n\n", addr); mdb_printf("SMB Node: %p\n", ls->ls_node); mdb_printf("Refcount: %u\n", ls->ls_refcnt); mdb_printf("Epoch: %u\n", ls->ls_epoch); mdb_printf("State: 0x%x <%b>\n", ls->ls_state, ls->ls_state, oplock_bits); mdb_printf("Key: ["); for (i = 0; i < SMB_LEASE_KEY_SZ; i++) { mdb_printf(" %02x", ls->ls_key[i] & 0xFF); if ((i & 3) == 3) mdb_printf(" "); } mdb_printf(" ]\n"); } else { if (DCMD_HDRSPEC(flags)) mdb_printf( "%%" "%-?s %-?s %-?s %-?s" "%%\n", "LEASE", "SMB NODE", "STATE", "KEY"); mdb_printf("%?p ", addr); mdb_printf("%-?p ", ls->ls_node); mdb_printf("%#-?x ", ls->ls_state); mdb_printf("["); for (i = 0; i < 8; i++) { mdb_printf(" %02x", ls->ls_key[i] & 0xFF); } mdb_printf(" ...]\n"); } } return (DCMD_OK); } /* * ***************************************************************************** * ******************************** smb_kshare_t ******************************* * ***************************************************************************** */ struct smb_kshare_cb_args { uint_t opts; char name[MAXNAMELEN]; char path[MAXPATHLEN]; }; static int smb_kshare_cb(uintptr_t addr, const void *data, void *varg) { struct smb_kshare_cb_args *args = varg; const smb_kshare_t *shr = data; if (args->opts & SMB_OPT_VERBOSE) { mdb_arg_t argv; argv.a_type = MDB_TYPE_STRING; argv.a_un.a_str = "smb_kshare_t"; /* Don't fail the walk if this fails. */ mdb_printf("%-?p ", addr); mdb_call_dcmd("print", addr, 0, 1, &argv); return (WALK_NEXT); } /* * Summary line for an smb_kshare_t * Don't fail the walk if any of these fail. * * Get the shr_name and shr_path strings. */ if (mdb_readstr(args->name, sizeof (args->name), (uintptr_t)shr->shr_name) <= 0) strcpy(args->name, "?"); if (mdb_readstr(args->path, sizeof (args->path), (uintptr_t)shr->shr_path) <= 0) strcpy(args->path, "?"); mdb_printf("%-?p ", addr); /* smb_kshare_t */ mdb_printf("%-16s ", args->name); mdb_printf("%-s\n", args->path); return (WALK_NEXT); } /* * ::smbshare * * smbshare dcmd - Print out smb_kshare structures. * requires addr of an smb_server_t */ /*ARGSUSED*/ static int smbshare_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct smb_kshare_cb_args *args; args = mdb_zalloc(sizeof (*args), UM_SLEEP | UM_GC); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, SMB_OPT_VERBOSE, &args->opts, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { if ((args->opts & SMB_OPT_VERBOSE) != 0) { mdb_printf("%%SMB kshares list:%%\n"); } else { mdb_printf( "%%" "%-?s " "%-16s " "%-s" "%%\n", "smb_kshare_t", "name", "path"); } } if (mdb_pwalk("smbshare_walker", smb_kshare_cb, args, addr) == -1) { mdb_warn("cannot walk smb_kshare avl"); return (DCMD_ERR); } return (DCMD_OK); } /* * Initialize the smb_kshare_t walker to point to the smb_export * in the specified smb_server_t instance. (no global walks) */ static int smb_kshare_walk_init(mdb_walk_state_t *wsp) { int sv_exp_off, ex_sha_off, avl_tr_off; if (wsp->walk_addr == 0) { mdb_printf("require address of an smb_server_t\n"); return (WALK_ERR); } /* * Using CTF to get the equivalent of: * OFFSETOF(smb_server_t, sv_export.e_share_avl.avl_tree); */ GET_OFFSET(sv_exp_off, smb_server_t, sv_export); GET_OFFSET(ex_sha_off, smb_export_t, e_share_avl); GET_OFFSET(avl_tr_off, smb_avl_t, avl_tree); wsp->walk_addr += (sv_exp_off + ex_sha_off + avl_tr_off); if (mdb_layered_walk("avl", wsp) == -1) { mdb_warn("failed to walk list of smb_kshare_t"); return (WALK_ERR); } return (WALK_NEXT); } static int smb_kshare_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * ***************************************************************************** * ******************************** smb_vfs_t ********************************** * ***************************************************************************** */ typedef struct mdb_smb_vfs { list_node_t sv_lnd; uint32_t sv_magic; uint32_t sv_refcnt; vfs_t *sv_vfsp; vnode_t *sv_rootvp; } mdb_smb_vfs_t; struct smb_vfs_cb_args { uint_t opts; vnode_t vn; char path[MAXPATHLEN]; }; /*ARGSUSED*/ static int smb_vfs_cb(uintptr_t addr, const void *data, void *varg) { struct smb_vfs_cb_args *args = varg; mdb_smb_vfs_t sf; if (args->opts & SMB_OPT_VERBOSE) { mdb_arg_t argv; argv.a_type = MDB_TYPE_STRING; argv.a_un.a_str = "smb_vfs_t"; /* Don't fail the walk if this fails. */ mdb_printf("%-?p ", addr); mdb_call_dcmd("print", addr, 0, 1, &argv); return (WALK_NEXT); } /* * Summary line for an smb_vfs_t * Don't fail the walk if any of these fail. * * Get the vnode v_path string if we can. */ if (mdb_ctf_vread(&sf, SMBSRV_SCOPE "smb_vfs_t", "mdb_smb_vfs_t", addr, 0) < 0) { mdb_warn("failed to read struct smb_vfs at %p", addr); return (DCMD_ERR); } strcpy(args->path, "?"); if (mdb_vread(&args->vn, sizeof (args->vn), (uintptr_t)sf.sv_rootvp) == sizeof (args->vn)) (void) mdb_readstr(args->path, sizeof (args->path), (uintptr_t)args->vn.v_path); mdb_printf("%-?p ", addr); mdb_printf("%-10d ", sf.sv_refcnt); mdb_printf("%-?p ", sf.sv_vfsp); mdb_printf("%-?p ", sf.sv_rootvp); mdb_printf("%-s\n", args->path); return (WALK_NEXT); } /* * ::smbvfs * * smbvfs dcmd - Prints out smb_vfs structures. * requires addr of an smb_server_t */ /*ARGSUSED*/ static int smbvfs_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct smb_vfs_cb_args *args; args = mdb_zalloc(sizeof (*args), UM_SLEEP | UM_GC); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, SMB_OPT_VERBOSE, &args->opts, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { if ((args->opts & SMB_OPT_VERBOSE) != 0) { mdb_printf("%%SMB VFS list:%%\n"); } else { mdb_printf( "%%" "%-?s " "%-10s " "%-16s " "%-16s" "%-s" "%%\n", "SMB_VFS", "REFCNT", "VFS", "VNODE", "ROOT"); } } if (mdb_pwalk("smbvfs_walker", smb_vfs_cb, args, addr) == -1) { mdb_warn("cannot walk smb_vfs list"); return (DCMD_ERR); } return (DCMD_OK); } /* * Initialize the smb_vfs_t walker to point to the smb_export * in the specified smb_server_t instance. (no global walks) */ static int smb_vfs_walk_init(mdb_walk_state_t *wsp) { int sv_exp_off, ex_vfs_off, ll_off; if (wsp->walk_addr == 0) { mdb_printf("require address of an smb_server_t\n"); return (WALK_ERR); } /* * Using CTF to get the equivalent of: * OFFSETOF(smb_server_t, sv_export.e_vfs_list.ll_list); */ GET_OFFSET(sv_exp_off, smb_server_t, sv_export); /* GET_OFFSET(ex_vfs_off, smb_export_t, e_vfs_list); */ ex_vfs_off = mdb_ctf_offsetof_by_name("smb_export_t", "e_vfs_list"); if (ex_vfs_off < 0) { mdb_warn("cannot lookup: smb_export_t .e_vfs_list"); return (WALK_ERR); } GET_OFFSET(ll_off, smb_llist_t, ll_list); wsp->walk_addr += (sv_exp_off + ex_vfs_off + ll_off); if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("failed to walk list of smb_vfs_t"); return (WALK_ERR); } return (WALK_NEXT); } static int smb_vfs_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * ***************************************************************************** * ******************************* smb_node_t ********************************** * ***************************************************************************** */ typedef struct mdb_smb_node { smb_node_state_t n_state; uint32_t n_refcnt; uint32_t n_open_count; uint32_t n_opening_count; smb_llist_t n_ofile_list; smb_llist_t n_lock_list; volatile int flags; struct smb_node *n_dnode; struct smb_node *n_unode; char od_name[MAXNAMELEN]; vnode_t *vp; smb_audit_buf_node_t *n_audit_buf; /* Newer members (not in old kernels) - keep last! */ smb_llist_t n_wlock_list; } mdb_smb_node_t; typedef struct mdb_smb_node_old { /* Note: MUST be layout as above! */ smb_node_state_t n_state; uint32_t n_refcnt; uint32_t n_open_count; uint32_t n_opening_count; smb_llist_t n_ofile_list; smb_llist_t n_lock_list; volatile int flags; struct smb_node *n_dnode; struct smb_node *n_unode; char od_name[MAXNAMELEN]; vnode_t *vp; smb_audit_buf_node_t *n_audit_buf; /* Newer members omitted from _old */ } mdb_smb_node_old_t; static void smbnode_help(void) { mdb_printf( "Display the contents of smb_node_t, with optional filtering.\n\n"); (void) mdb_dec_indent(2); mdb_printf("%OPTIONS%\n"); (void) mdb_inc_indent(2); mdb_printf( "-v\tDisplay verbose smb_node information\n" "-p\tDisplay the full path of the vnode associated\n" "-s\tDisplay the stack of the last 16 calls that modified the " "reference\n\tcount\n"); } /* * ::smbnode * * smb_node dcmd - Print out smb_node structure. */ static int smbnode_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { static smb_llist_t zero_llist = {0}; mdb_smb_node_t node; int rc; int verbose = FALSE; int print_full_path = FALSE; int stack_trace = FALSE; int ol_cnt = 0; vnode_t vnode; char od_name[MAXNAMELEN]; char path_name[1024]; uintptr_t list_addr; struct mdb_smb_oplock *node_oplock; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, 'p', MDB_OPT_SETBITS, TRUE, &print_full_path, 's', MDB_OPT_SETBITS, TRUE, &stack_trace, NULL) != argc) return (DCMD_USAGE); /* * If no smb_node address was specified on the command line, we can * print out all smb nodes by invoking the smb_node walker, using * this dcmd itself as the callback. */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("smbnode_walker", "smbnode", argc, argv) == -1) { mdb_warn("failed to walk 'smb_node'"); return (DCMD_ERR); } return (DCMD_OK); } /* * For each smb_node, we just need to read the smb_node_t struct, read * and then print out the following fields. */ if (mdb_ctf_vread(&node, SMBSRV_SCOPE "smb_node_t", "mdb_smb_node_t", addr, 0) < 0) { /* * Fall-back handling for mdb_smb_node_old_t * Should remove after a while. */ if (mdb_ctf_vread(&node, SMBSRV_SCOPE "smb_node_t", "mdb_smb_node_old_t", addr, 0) < 0) { mdb_warn("failed to read struct smb_node at %p", addr); return (DCMD_ERR); } node.n_wlock_list = zero_llist; } (void) mdb_snprintf(od_name, sizeof (od_name), "%s", node.od_name); if (print_full_path) { if (mdb_vread(&vnode, sizeof (vnode_t), (uintptr_t)node.vp) == sizeof (vnode_t)) { if (mdb_readstr(path_name, sizeof (path_name), (uintptr_t)vnode.v_path) <= 0) { (void) mdb_snprintf(path_name, sizeof (path_name), "N/A"); } } } rc = smb_node_get_oplock(addr, &node_oplock); if (rc != DCMD_OK) return (rc); ol_cnt = smb_node_oplock_cnt(node_oplock); if (verbose) { int nol_off, nll_off, wll_off, ll_off; GET_OFFSET(nol_off, smb_node_t, n_ofile_list); GET_OFFSET(nll_off, smb_node_t, n_lock_list); GET_OFFSET(ll_off, smb_llist_t, ll_list); /* This one is optional (for now). */ /* GET_OFFSET(wll_off, smb_node_t, n_wlock_list); */ wll_off = mdb_ctf_offsetof_by_name( "smb_node_t", "n_wlock_list"); mdb_printf("%%SMB node information " "(%p):%%\n", addr); mdb_printf("VP: %p\n", node.vp); mdb_printf("Name: %s\n", od_name); if (print_full_path) mdb_printf("V-node Path: %s\n", path_name); mdb_printf("Reference Count: %u\n", node.n_refcnt); mdb_printf("Ofiles: %u\n", node.n_ofile_list.ll_count); if (node.n_ofile_list.ll_count != 0 && nol_off != -1) { (void) mdb_inc_indent(SMB_DCMD_INDENT); list_addr = addr + nol_off + ll_off; if (mdb_pwalk_dcmd("list", "smbofile", 0, NULL, list_addr)) { mdb_warn("failed to walk node's ofiles"); } (void) mdb_dec_indent(SMB_DCMD_INDENT); } mdb_printf("Granted Locks: %u\n", node.n_lock_list.ll_count); if (node.n_lock_list.ll_count != 0) { (void) mdb_inc_indent(SMB_DCMD_INDENT); list_addr = addr + nll_off + ll_off; if (mdb_pwalk_dcmd("list", "smblock", 0, NULL, list_addr)) { mdb_warn("failed to walk node's granted" " locks"); } (void) mdb_dec_indent(SMB_DCMD_INDENT); } mdb_printf("Waiting Locks: %u\n", node.n_wlock_list.ll_count); if (node.n_wlock_list.ll_count != 0 && wll_off != -1) { (void) mdb_inc_indent(SMB_DCMD_INDENT); list_addr = addr + wll_off + ll_off; if (mdb_pwalk_dcmd("list", "smblock", 0, NULL, list_addr)) { mdb_warn("failed to walk node's waiting" " locks"); } (void) mdb_dec_indent(SMB_DCMD_INDENT); } if (ol_cnt == 0) { mdb_printf("Opportunistic Locks: (none)\n"); } else { mdb_printf("Opportunistic Locks:\n"); (void) mdb_inc_indent(SMB_DCMD_INDENT); /* Takes node address */ rc = mdb_call_dcmd("smbnode_oplock", addr, flags, argc, argv); (void) mdb_dec_indent(SMB_DCMD_INDENT); if (rc != DCMD_OK) return (rc); } } else { if (DCMD_HDRSPEC(flags)) { mdb_printf( "%%%-?s " "%-?s " "%-18s " "%-6s " "%-6s " "%-8s " "%-8s " "%-6s%%\n", "ADDR", "VP", "NODE-NAME", "OFILES", "LOCKS", "WLOCKS", "OPLOCK", "REF"); } mdb_printf("%-?p %-?p %-18s %-6d %-6d %-8d %-8d %-6d ", addr, node.vp, od_name, node.n_ofile_list.ll_count, node.n_lock_list.ll_count, node.n_wlock_list.ll_count, ol_cnt, node.n_refcnt); if (print_full_path) mdb_printf("\t%s\n", path_name); } if (stack_trace && node.n_audit_buf) { int ctr; smb_audit_buf_node_t *anb; anb = mdb_alloc(sizeof (smb_audit_buf_node_t), UM_SLEEP | UM_GC); if (mdb_vread(anb, sizeof (*anb), (uintptr_t)node.n_audit_buf) != sizeof (*anb)) { mdb_warn("failed to read audit buffer"); return (DCMD_ERR); } ctr = anb->anb_max_index + 1; anb->anb_index--; anb->anb_index &= anb->anb_max_index; while (ctr) { smb_audit_record_node_t *anr; anr = anb->anb_records + anb->anb_index; if (anr->anr_depth) { char c[MDB_SYM_NAMLEN]; GElf_Sym sym; int i; mdb_printf("\nRefCnt: %u\t", anr->anr_refcnt); for (i = 0; i < anr->anr_depth; i++) { if (mdb_lookup_by_addr( anr->anr_stack[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) { continue; } mdb_printf("%s+0x%1x", c, anr->anr_stack[i] - (uintptr_t)sym.st_value); ++i; break; } while (i < anr->anr_depth) { if (mdb_lookup_by_addr( anr->anr_stack[i], MDB_SYM_FUZZY, c, sizeof (c), &sym) == -1) { ++i; continue; } mdb_printf("\n\t\t%s+0x%1x", c, anr->anr_stack[i] - (uintptr_t)sym.st_value); ++i; } mdb_printf("\n"); } anb->anb_index--; anb->anb_index &= anb->anb_max_index; ctr--; } } return (DCMD_OK); } /* * Initialize the smb_node_t walker by reading the value of smb_node_hash_table * in the kernel's symbol table. Only global walk supported. */ static int smb_node_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; uintptr_t node_hash_table_addr; int ll_off; int i; if (wsp->walk_addr == 0) { if (mdb_lookup_by_obj(SMBSRV_OBJNAME, "smb_node_hash_table", &sym) == -1) { mdb_warn("failed to find 'smb_node_hash_table'"); return (WALK_ERR); } node_hash_table_addr = (uintptr_t)sym.st_value; } else { mdb_printf("smb_node walk only supports global walks\n"); return (WALK_ERR); } GET_OFFSET(ll_off, smb_llist_t, ll_list); for (i = 0; i < SMBND_HASH_MASK + 1; i++) { wsp->walk_addr = node_hash_table_addr + (i * sizeof (smb_llist_t)) + ll_off; if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("failed to walk 'list'"); return (WALK_ERR); } } return (WALK_NEXT); } static int smb_node_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * ***************************************************************************** * ****************************** smb_lock_t *********************************** * ***************************************************************************** */ typedef struct mdb_smb_lock { smb_ofile_t *l_file; struct smb_lock *l_blocked_by; uint64_t l_start; uint64_t l_length; uint32_t l_pid; uint32_t l_type; uint32_t l_flags; /* Newer members (not in old kernels) - keep last! */ uint32_t l_conflicts; } mdb_smb_lock_t; typedef struct mdb_smb_lock_old { /* Note: MUST be same layout as above! */ smb_ofile_t *l_file; struct smb_lock *l_blocked_by; uint64_t l_start; uint64_t l_length; uint32_t l_pid; uint32_t l_type; uint32_t l_flags; /* Newer members omitted from _old */ } mdb_smb_lock_old_t; static int smblock_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_smb_lock_t lock; int verbose = FALSE; char *lock_type; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); /* * An smb_lock_t address must be specified. */ if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_ctf_vread(&lock, SMBSRV_SCOPE "smb_lock_t", "mdb_smb_lock_t", addr, 0) < 0) { /* * Fall-back handling for mdb_smb_lock_old_t * Should remove after a while. */ if (mdb_ctf_vread(&lock, SMBSRV_SCOPE "smb_lock_t", "mdb_smb_lock_old_t", addr, 0) < 0) { mdb_warn("failed to read struct smb_lock at %p", addr); return (DCMD_ERR); } lock.l_conflicts = 0; } switch (lock.l_type) { case SMB_LOCK_TYPE_READWRITE: lock_type = "RW"; break; case SMB_LOCK_TYPE_READONLY: lock_type = "RO"; break; default: lock_type = "?"; break; } if (verbose) { mdb_printf("%%SMB lock information " "(%p):%%\n", addr); mdb_printf("Type :\t%s (%u)\n", lock_type, lock.l_type); mdb_printf("Start :\t%llu\n", lock.l_start); mdb_printf("Length :\t%llu\n", lock.l_length); mdb_printf("OFile :\t%p\n", lock.l_file); mdb_printf("Process ID :\t%u\n", lock.l_pid); mdb_printf("Conflicts :\t%u\n", lock.l_conflicts); mdb_printf("Blocked by :\t%p\n", lock.l_blocked_by); mdb_printf("Flags :\t0x%x\n", lock.l_flags); mdb_printf("\n"); } else { if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-?s %4s %16s %8s %9s %-?s%\n", "Locks: ", "TYPE", "START", "LENGTH", "CONFLICTS", "BLOCKED-BY"); } mdb_printf("%?p %4s %16llx %08llx %9u %?p", addr, lock_type, lock.l_start, lock.l_length, lock.l_conflicts, lock.l_blocked_by); } return (DCMD_OK); } /* * ***************************************************************************** * ************************** smb_oplock_grant_t ******************************* * ***************************************************************************** */ typedef struct mdb_smb_oplock_grant { uint32_t og_state; /* latest sent to client */ uint8_t onlist_II; uint8_t onlist_R; uint8_t onlist_RH; uint8_t onlist_RHBQ; uint8_t BreakingToRead; } mdb_smb_oplock_grant_t; static const mdb_bitmask_t oplock_bits[] = { { "READ_CACHING", READ_CACHING, READ_CACHING }, { "HANDLE_CACHING", HANDLE_CACHING, HANDLE_CACHING }, { "WRITE_CACHING", WRITE_CACHING, WRITE_CACHING }, { "EXCLUSIVE", EXCLUSIVE, EXCLUSIVE }, { "MIXED_R_AND_RH", MIXED_R_AND_RH, MIXED_R_AND_RH }, { "LEVEL_TWO_OPLOCK", LEVEL_TWO_OPLOCK, LEVEL_TWO_OPLOCK }, { "LEVEL_ONE_OPLOCK", LEVEL_ONE_OPLOCK, LEVEL_ONE_OPLOCK }, { "BATCH_OPLOCK", BATCH_OPLOCK, BATCH_OPLOCK }, { "BREAK_TO_TWO", BREAK_TO_TWO, BREAK_TO_TWO }, { "BREAK_TO_NONE", BREAK_TO_NONE, BREAK_TO_NONE }, { "BREAK_TO_TWO_TO_NONE", BREAK_TO_TWO_TO_NONE, BREAK_TO_TWO_TO_NONE }, { "BREAK_TO_READ_CACHING", BREAK_TO_READ_CACHING, BREAK_TO_READ_CACHING }, { "BREAK_TO_HANDLE_CACHING", BREAK_TO_HANDLE_CACHING, BREAK_TO_HANDLE_CACHING }, { "BREAK_TO_WRITE_CACHING", BREAK_TO_WRITE_CACHING, BREAK_TO_WRITE_CACHING }, { "BREAK_TO_NO_CACHING", BREAK_TO_NO_CACHING, BREAK_TO_NO_CACHING }, { "NO_OPLOCK", NO_OPLOCK, NO_OPLOCK }, { NULL, 0, 0 } }; /* * Show smb_ofile_t oplock info * address is the ofile */ /*ARGSUSED*/ static int smbofile_oplock_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_smb_oplock_grant_t og; int verbose = FALSE; static int og_off; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (og_off <= 0) { og_off = mdb_ctf_offsetof_by_name( "smb_ofile_t", "f_oplock"); if (og_off < 0) { mdb_warn("cannot lookup: smb_ofile_t .f_oplock"); return (DCMD_ERR); } } if (mdb_ctf_vread(&og, SMBSRV_SCOPE "smb_oplock_grant_t", "mdb_smb_oplock_grant_t", addr + og_off, 0) < 0) { mdb_warn("failed to read oplock grant in ofile at %p", addr); return (DCMD_ERR); } if (verbose) { mdb_printf("%%SMB ofile (oplock_grant) " "(%p):%%\n", addr); mdb_printf("State: 0x%x <%b>\n", og.og_state, og.og_state, oplock_bits); mdb_printf("OnList_II: %d\n", og.onlist_II); mdb_printf("OnList_R: %d\n", og.onlist_R); mdb_printf("OnList_RH: %d\n", og.onlist_RH); mdb_printf("OnList_RHBQ: %d\n", og.onlist_RHBQ); mdb_printf("BrkToRead: %d\n", og.BreakingToRead); } else { if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-16s %-10s %-16s%\n", "OFILE", "STATE", "OnList..."); } mdb_printf("%-16p", addr); mdb_printf(" 0x%x", og.og_state); if (og.onlist_II) mdb_printf(" II"); if (og.onlist_R) mdb_printf(" R"); if (og.onlist_RH) mdb_printf(" RH"); if (og.onlist_RHBQ) mdb_printf(" RHBQ"); if (og.BreakingToRead) mdb_printf(" BrkToRd"); mdb_printf("\n"); } return (DCMD_OK); } /* * ***************************************************************************** * ***************************** smb_oplock_t ********************************** * ***************************************************************************** */ typedef struct mdb_smb_oplock { struct smb_ofile *excl_open; uint32_t ol_state; int32_t cnt_II; int32_t cnt_R; int32_t cnt_RH; int32_t cnt_RHBQ; int32_t waiters; } mdb_smb_oplock_t; /* * Helpers for smbnode_dcmd and smbnode_oplock_dcmd */ /* * Read the smb_oplock_t part of the node * addr is the smb_node */ static int smb_node_get_oplock(uintptr_t addr, struct mdb_smb_oplock **ol_ret) { mdb_smb_oplock_t *ol; static int ol_off; if (ol_off <= 0) { ol_off = mdb_ctf_offsetof_by_name( "smb_node_t", "n_oplock"); if (ol_off < 0) { mdb_warn("cannot lookup: smb_node_t .n_oplock"); return (DCMD_ERR); } } ol = mdb_alloc(sizeof (*ol), UM_SLEEP | UM_GC); if (mdb_ctf_vread(ol, SMBSRV_SCOPE "smb_oplock_t", "mdb_smb_oplock_t", addr + ol_off, 0) < 0) { mdb_warn("failed to read smb_oplock in node at %p", addr); return (DCMD_ERR); } *ol_ret = ol; return (DCMD_OK); } /* * Return the oplock count */ static int smb_node_oplock_cnt(struct mdb_smb_oplock *ol) { int ol_cnt = 0; /* Compute total oplock count. */ if (ol->excl_open != NULL) ol_cnt++; ol_cnt += ol->cnt_II; ol_cnt += ol->cnt_R; ol_cnt += ol->cnt_RH; return (ol_cnt); } /* * Show smb_node_t oplock info, and optionally the * list of ofiles with oplocks on this node. * Address is the smb_node_t. */ /*ARGSUSED*/ static int smbnode_oplock_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_smb_oplock_t *ol; int verbose = FALSE; int ol_cnt, rc; int fl_off, ll_off; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); rc = smb_node_get_oplock(addr, &ol); if (rc != DCMD_OK) return (rc); ol_cnt = smb_node_oplock_cnt(ol); if (verbose) { mdb_printf("%%SMB node (oplock) " "(%p):%%\n", addr); mdb_printf("State: 0x%x <%b>\n", ol->ol_state, ol->ol_state, oplock_bits); mdb_printf("Exclusive Open: %p\n", ol->excl_open); mdb_printf("cnt_II: %d\n", ol->cnt_II); mdb_printf("cnt_R: %d\n", ol->cnt_R); mdb_printf("cnt_RH: %d\n", ol->cnt_RH); mdb_printf("cnt_RHBQ: %d\n", ol->cnt_RHBQ); mdb_printf("waiters: %d\n", ol->waiters); } else { if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-16s %-10s %-16s%\n", "NODE", "STATE", "OPLOCKS"); } mdb_printf("%-16p 0x%x %d\n", addr, ol->ol_state, ol_cnt); } if (ol_cnt == 0) return (DCMD_OK); GET_OFFSET(fl_off, smb_node_t, n_ofile_list); GET_OFFSET(ll_off, smb_llist_t, ll_list); (void) mdb_inc_indent(SMB_DCMD_INDENT); if (mdb_pwalk_dcmd("list", "smbofile_oplock", argc, argv, addr + fl_off + ll_off)) { mdb_warn("failed to walk ofile oplocks"); } (void) mdb_dec_indent(SMB_DCMD_INDENT); return (DCMD_OK); } /* * ******************************************************************* * (smb) mbuf_t * * ::smb_mbuf_dump [max_len] * dcmd to dump the data portion of an mbuf_t * stop at max_len */ static int smb_mbuf_dump_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct m_hdr mh; uintptr_t mdata; int len, max_len; int dumpptr_flags; if (mdb_vread(&mh, sizeof (mh), addr) < 0) { mdb_warn("failed to read mbuf at %p", addr); return (DCMD_ERR); } len = mh.mh_len; mdata = (uintptr_t)mh.mh_data; if (argc > 0) { max_len = (int)mdb_argtoull(&argv[0]); if (len > max_len) len = max_len; } if (len <= 0) return (DCMD_OK); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-16s %-16s %-12s%\n", "mbuf_t", "m_data", "m_len"); } mdb_printf("%-16p %-16p %-12u\n", addr, mdata, mh.mh_len); dumpptr_flags = MDB_DUMP_RELATIVE | MDB_DUMP_ASCII | MDB_DUMP_HEADER; if (mdb_dumpptr(mdata, len, dumpptr_flags, NULL, NULL) < 0) return (DCMD_ERR); return (DCMD_OK); } static int smb_mbuf_walk_init(mdb_walk_state_t *wsp) { mbuf_t *m; if (wsp->walk_addr == 0) { mdb_printf("require address of an mbuf_t\n"); return (WALK_ERR); } m = mdb_alloc(sizeof (*m), UM_SLEEP | UM_GC); wsp->walk_data = m; return (WALK_NEXT); } static int smb_mbuf_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; mbuf_t *m = wsp->walk_data; int rc; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(m, sizeof (*m), addr) == -1) { mdb_warn("failed to read mbuf_t at %p", addr); return (WALK_ERR); } rc = wsp->walk_callback(addr, m, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)m->m_next; return (rc); } /* * ***************************************************************************** * ******************************** smb_ace_t ********************************** * ***************************************************************************** */ static const ace_type_entry_t ace_types[ACE_TYPE_TABLEN] = { ACE_TYPE_ENTRY(ACE_ACCESS_ALLOWED_ACE_TYPE), ACE_TYPE_ENTRY(ACE_ACCESS_DENIED_ACE_TYPE), ACE_TYPE_ENTRY(ACE_SYSTEM_AUDIT_ACE_TYPE), ACE_TYPE_ENTRY(ACE_SYSTEM_ALARM_ACE_TYPE), ACE_TYPE_ENTRY(ACE_ACCESS_ALLOWED_COMPOUND_ACE_TYPE), ACE_TYPE_ENTRY(ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE), ACE_TYPE_ENTRY(ACE_ACCESS_DENIED_OBJECT_ACE_TYPE), ACE_TYPE_ENTRY(ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE), ACE_TYPE_ENTRY(ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE), ACE_TYPE_ENTRY(ACE_ACCESS_ALLOWED_CALLBACK_ACE_TYPE), ACE_TYPE_ENTRY(ACE_ACCESS_DENIED_CALLBACK_ACE_TYPE), ACE_TYPE_ENTRY(ACE_ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE), ACE_TYPE_ENTRY(ACE_ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE), ACE_TYPE_ENTRY(ACE_SYSTEM_AUDIT_CALLBACK_ACE_TYPE), ACE_TYPE_ENTRY(ACE_SYSTEM_ALARM_CALLBACK_ACE_TYPE), ACE_TYPE_ENTRY(ACE_SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE), ACE_TYPE_ENTRY(ACE_SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE), ACE_TYPE_ENTRY(0x11), ACE_TYPE_ENTRY(0x12), ACE_TYPE_ENTRY(0x13), ACE_TYPE_ENTRY(0x14), ACE_TYPE_ENTRY(0x15), ACE_TYPE_ENTRY(0x16), ACE_TYPE_ENTRY(0x17), ACE_TYPE_ENTRY(0x18), ACE_TYPE_ENTRY(0x19), ACE_TYPE_ENTRY(0x1A), ACE_TYPE_ENTRY(0x1B), ACE_TYPE_ENTRY(0x1C), ACE_TYPE_ENTRY(0x1D), ACE_TYPE_ENTRY(0x1E), ACE_TYPE_ENTRY(0x1F) }; static const mdb_bitmask_t ace_flag_bits[] = { { "OBJECT_INHERIT_ACE", OBJECT_INHERIT_ACE, OBJECT_INHERIT_ACE }, { "CONTAINER_INHERIT_ACE", CONTAINER_INHERIT_ACE, CONTAINER_INHERIT_ACE }, { "NO_PROPOGATE_INHERIT_ACE", NO_PROPOGATE_INHERIT_ACE, NO_PROPOGATE_INHERIT_ACE }, { "INHERIT_ONLY_ACE", INHERIT_ONLY_ACE, INHERIT_ONLY_ACE }, { "INHERITED_ACE", INHERITED_ACE, INHERITED_ACE }, { "SUCCESSFUL_ACCESS_ACE_FLAG", SUCCESSFUL_ACCESS_ACE_FLAG, SUCCESSFUL_ACCESS_ACE_FLAG }, { "FAILED_ACCESS_ACE_FLAG", FAILED_ACCESS_ACE_FLAG, FAILED_ACCESS_ACE_FLAG }, { NULL, 0, 0 } }; /* * ::smbace */ static int smbace_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { smb_ace_t ace; int verbose = FALSE; const char *ptr; int rc; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); /* * An smb_ace address is required. */ if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&ace, sizeof (ace), addr) != sizeof (ace)) { mdb_warn("failed to read struct smb_ace at %p", addr); return (DCMD_ERR); } if (verbose) { if (ace.se_hdr.se_type < ACE_TYPE_TABLEN) ptr = ace_types[ace.se_hdr.se_type].ace_type_sting; else ptr = "Unknown"; mdb_printf("ACE Type: 0x%02x (%s)\n", ace.se_hdr.se_type, ptr); mdb_printf("ACE Flags: %b\n", (int)ace.se_hdr.se_flags, ace_flag_bits); mdb_printf("ACE Wire Size: 0x%04x\n", ace.se_hdr.se_bsize); mdb_printf("ACE Mask: 0x%08x\n", ace.se_mask); mdb_printf("ACE SID: "); } else { if (DCMD_HDRSPEC(flags)) mdb_printf( "%%%?-s %-4s %-4s %-8s %s%%\n", "ACE", "TYPE", "FLAGS", "MASK", "SID"); mdb_printf("%?p 0x%02x 0x%02x 0x%08x ", addr, ace.se_hdr.se_type, ace.se_hdr.se_flags, ace.se_mask); } rc = smb_sid_print((uintptr_t)ace.se_sid); mdb_printf("\n"); return (rc); } static int smb_ace_walk_init(mdb_walk_state_t *wsp) { int sal_off; if (wsp->walk_addr == 0) { mdb_printf("smb_ace walk only supports local walks\n"); return (WALK_ERR); } GET_OFFSET(sal_off, smb_acl_t, sl_sorted); wsp->walk_addr += sal_off; if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("failed to walk list of ACEs"); return (WALK_ERR); } return (WALK_NEXT); } static int smb_ace_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * ***************************************************************************** * ******************************** smb_acl_t ********************************** * ***************************************************************************** */ /* * ::smbacl */ static int smbacl_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { smb_acl_t acl; /* An smb_acl address is required. */ if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&acl, sizeof (acl), addr) != sizeof (acl)) { mdb_warn("failed to read struct smb_acl at %p", addr); return (DCMD_ERR); } mdb_printf("ACL Revision: %d\n", acl.sl_revision); mdb_printf("ACL Size on Wire: %d\n", acl.sl_bsize); mdb_printf("ACL Number of ACEs: %d\n", acl.sl_acecnt); (void) mdb_inc_indent(SMB_DCMD_INDENT); if (mdb_pwalk_dcmd("smbace_walker", "smbace", argc, argv, addr)) { (void) mdb_dec_indent(SMB_DCMD_INDENT); mdb_warn("failed to walk list of ACEs for ACL %p", addr); return (DCMD_ERR); } (void) mdb_dec_indent(SMB_DCMD_INDENT); return (DCMD_OK); } /* * ***************************************************************************** * ********************************* smb_sd_t ********************************** * ***************************************************************************** */ /* * ::smbsd */ static int smbsd_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { smb_sd_t sd; int rc; /* * An smb_sid address is required. */ if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&sd, sizeof (sd), addr) != sizeof (sd)) { mdb_warn("failed to read struct smb_sd at %p", addr); return (DCMD_ERR); } mdb_printf("SD Revision: %d\n", sd.sd_revision); mdb_printf("SD Control: %04x\n", sd.sd_control); if (sd.sd_control & SE_OWNER_DEFAULTED) mdb_printf("\t SE_OWNER_DEFAULTED\n"); if (sd.sd_control & SE_GROUP_DEFAULTED) mdb_printf("\t SE_GROUP_DEFAULTED\n"); if (sd.sd_control & SE_DACL_PRESENT) mdb_printf("\t SE_DACL_PRESENT\n"); if (sd.sd_control & SE_DACL_DEFAULTED) mdb_printf("\t SE_DACL_DEFAULTED\n"); if (sd.sd_control & SE_SACL_PRESENT) mdb_printf("\t SE_SACL_PRESENT\n"); if (sd.sd_control & SE_SACL_DEFAULTED) mdb_printf("\t SE_SACL_DEFAULTED\n"); if (sd.sd_control & SE_DACL_AUTO_INHERIT_REQ) mdb_printf("\t SE_DACL_AUTO_INHERIT_REQ\n"); if (sd.sd_control & SE_SACL_AUTO_INHERIT_REQ) mdb_printf("\t SE_SACL_AUTO_INHERIT_REQ\n"); if (sd.sd_control & SE_DACL_AUTO_INHERITED) mdb_printf("\t SE_DACL_AUTO_INHERITED\n"); if (sd.sd_control & SE_SACL_AUTO_INHERITED) mdb_printf("\t SE_SACL_AUTO_INHERITED\n"); if (sd.sd_control & SE_DACL_PROTECTED) mdb_printf("\t SE_DACL_PROTECTED\n"); if (sd.sd_control & SE_SACL_PROTECTED) mdb_printf("\t SE_SACL_PROTECTED\n"); if (sd.sd_control & SE_SELF_RELATIVE) mdb_printf("\t SE_SELF_RELATIVE\n"); mdb_printf("SID of Owner: "); rc = smb_sid_print((uintptr_t)sd.sd_owner); if (rc != DCMD_OK) return (rc); mdb_printf("\nSID of Group: "); rc = smb_sid_print((uintptr_t)sd.sd_group); if (rc != DCMD_OK) return (rc); mdb_printf("\n"); if (sd.sd_control & SE_SACL_PRESENT && sd.sd_sacl) { mdb_printf("%%System ACL%%\n"); (void) mdb_inc_indent(SMB_DCMD_INDENT); rc = mdb_call_dcmd("smbacl", (uintptr_t)sd.sd_sacl, flags, argc, argv); (void) mdb_dec_indent(SMB_DCMD_INDENT); if (rc != DCMD_OK) return (rc); } if (sd.sd_control & SE_DACL_PRESENT && sd.sd_dacl) { mdb_printf("%%Discretionary ACL%%\n"); (void) mdb_inc_indent(SMB_DCMD_INDENT); rc = mdb_call_dcmd("smbacl", (uintptr_t)sd.sd_dacl, flags, argc, argv); (void) mdb_dec_indent(SMB_DCMD_INDENT); if (rc != DCMD_OK) return (rc); } return (DCMD_OK); } /* * ***************************************************************************** * ********************************* smb_sid_t ********************************* * ***************************************************************************** */ /* * ::smbsid */ /*ARGSUSED*/ static int smbsid_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { /* * An smb_sid address is required. */ if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); return (smb_sid_print(addr)); } /* * smb_sid_print */ static int smb_sid_print(uintptr_t addr) { smb_sid_t sid; smb_sid_t *psid; size_t sid_size; uint64_t authority; int ssa_off; int i; GET_OFFSET(ssa_off, smb_sid_t, sid_subauth); sid_size = ssa_off; if (mdb_vread(&sid, sid_size, addr) != sid_size) { mdb_warn("failed to read struct smb_sid at %p", addr); return (DCMD_ERR); } sid_size += sid.sid_subauthcnt * sizeof (sid.sid_subauth[0]); psid = mdb_zalloc(sid_size, UM_SLEEP | UM_GC); if (mdb_vread(psid, sid_size, addr) != sid_size) { mdb_warn("failed to read struct smb_sid at %p", addr); return (DCMD_ERR); } mdb_printf("S-%d", psid->sid_revision); authority = 0; for (i = 0; i < NT_SID_AUTH_MAX; i++) { authority += ((uint64_t)psid->sid_authority[i]) << (8 * (NT_SID_AUTH_MAX - 1) - i); } mdb_printf("-%ll", authority); for (i = 0; i < psid->sid_subauthcnt; i++) mdb_printf("-%d", psid->sid_subauth[i]); return (DCMD_OK); } /* * ***************************************************************************** * ********************************* smb_fssd_t ******************************** * ***************************************************************************** */ /* * ::smbfssd */ static int smbfssd_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { smb_fssd_t fssd; int rc; /* * An smb_fssd address is required. */ if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&fssd, sizeof (fssd), addr) != sizeof (fssd)) { mdb_warn("failed to read struct smb_fssd at %p", addr); return (DCMD_ERR); } mdb_printf("FSSD secinfo: 0x%x\n", fssd.sd_secinfo); if (fssd.sd_secinfo & SMB_OWNER_SECINFO) mdb_printf("FSSD uid: %d\n", fssd.sd_uid); if (fssd.sd_secinfo & SMB_GROUP_SECINFO) mdb_printf("FSSD gid: %d\n", fssd.sd_gid); if (fssd.sd_secinfo & SMB_SACL_SECINFO && fssd.sd_zsacl) { mdb_printf("%%System ACL%%\n"); (void) mdb_inc_indent(SMB_DCMD_INDENT); rc = mdb_call_dcmd("smbacl", (uintptr_t)fssd.sd_zsacl, flags, argc, argv); (void) mdb_dec_indent(SMB_DCMD_INDENT); if (rc != DCMD_OK) return (rc); } if (fssd.sd_secinfo & SMB_DACL_SECINFO && fssd.sd_zdacl) { mdb_printf("%%Discretionary ACL%%\n"); (void) mdb_inc_indent(SMB_DCMD_INDENT); rc = mdb_call_dcmd("smbacl", (uintptr_t)fssd.sd_zdacl, flags, argc, argv); (void) mdb_dec_indent(SMB_DCMD_INDENT); if (rc != DCMD_OK) return (rc); } return (DCMD_OK); } /* * ***************************************************************************** * **************************** Utility Funcions ******************************* * ***************************************************************************** */ /* * smb_dcmd_getopt * * This function analyzes the arguments passed in and sets the bit corresponding * to the options found in the opts variable. * * Return Value * * -1 An error occured during the decoding * 0 The decoding was successful */ static int smb_dcmd_getopt(uint_t *opts, int argc, const mdb_arg_t *argv) { *opts = 0; if (mdb_getopts(argc, argv, 's', MDB_OPT_SETBITS, SMB_OPT_SERVER, opts, 'e', MDB_OPT_SETBITS, SMB_OPT_SESSION, opts, 'r', MDB_OPT_SETBITS, SMB_OPT_REQUEST, opts, 'u', MDB_OPT_SETBITS, SMB_OPT_USER, opts, 't', MDB_OPT_SETBITS, SMB_OPT_TREE, opts, 'f', MDB_OPT_SETBITS, SMB_OPT_OFILE, opts, 'd', MDB_OPT_SETBITS, SMB_OPT_ODIR, opts, 'w', MDB_OPT_SETBITS, SMB_OPT_WALK, opts, 'v', MDB_OPT_SETBITS, SMB_OPT_VERBOSE, opts, NULL) != argc) return (-1); return (0); } /* * smb_dcmd_setopt * * This function set the arguments corresponding to the bits set in opts. * * Return Value * * Number of arguments set. */ static int smb_dcmd_setopt(uint_t opts, int max_argc, mdb_arg_t *argv) { int i; int argc = 0; for (i = 0; i < SMB_MDB_MAX_OPTS; i++) { if ((opts & smb_opts[i].o_value) && (argc < max_argc)) { argv->a_type = MDB_TYPE_STRING; argv->a_un.a_str = smb_opts[i].o_name; argc++; argv++; } } return (argc); } /* * smb_obj_expand */ static int smb_obj_expand(uintptr_t addr, uint_t opts, const smb_exp_t *x, ulong_t indent) { int rc = 0; int ex_off; int argc; mdb_arg_t argv[SMB_MDB_MAX_OPTS]; argc = smb_dcmd_setopt(opts | SMB_OPT_WALK, SMB_MDB_MAX_OPTS, argv); (void) mdb_inc_indent(indent); while (x->ex_dcmd) { if (x->ex_mask & opts) { ex_off = (x->ex_offset)(); if (ex_off < 0) { mdb_warn("failed to get the list offset for %s", x->ex_name); rc = ex_off; break; } rc = mdb_pwalk_dcmd(x->ex_walker, x->ex_dcmd, argc, argv, addr + ex_off); if (rc) { mdb_warn("failed to walk the list of %s in %p", x->ex_name, addr + ex_off); break; } } x++; } (void) mdb_dec_indent(indent); return (rc); } /* * smb_obj_list * * Function called by the DCMDs when no address is provided. It expands the * tree under the object type associated with the calling DCMD (based on the * flags passed in). * * Return Value * * DCMD_OK * DCMD_ERR */ static int smb_obj_list(const char *name, uint_t opts, uint_t flags) { int argc; mdb_arg_t argv[SMB_MDB_MAX_OPTS]; argc = smb_dcmd_setopt(opts, SMB_MDB_MAX_OPTS, argv); if (mdb_call_dcmd("smblist", 0, flags, argc, argv)) { mdb_warn("failed to list %s", name); return (DCMD_ERR); } return (DCMD_OK); } static int smb_worker_findstack(uintptr_t addr) { char cmd[80]; mdb_arg_t cmdarg; mdb_inc_indent(2); mdb_snprintf(cmd, sizeof (cmd), "<.$c%d", 16); cmdarg.a_type = MDB_TYPE_STRING; cmdarg.a_un.a_str = cmd; (void) mdb_call_dcmd("findstack", addr, DCMD_ADDRSPEC, 1, &cmdarg); mdb_dec_indent(2); mdb_printf("\n"); return (DCMD_OK); } static void smb_inaddr_ntop(smb_inaddr_t *ina, char *buf, size_t sz) { switch (ina->a_family) { case AF_INET: (void) mdb_snprintf(buf, sz, "%I", ina->a_ipv4); break; case AF_INET6: (void) mdb_snprintf(buf, sz, "%N", &ina->a_ipv6); break; default: (void) mdb_snprintf(buf, sz, "(?)"); break; } } /* * Get the name for an enum value */ static void get_enum(char *out, size_t size, const char *type_str, int val, const char *prefix) { mdb_ctf_id_t type_id; const char *cp; if (mdb_ctf_lookup_by_name(type_str, &type_id) != 0) goto errout; if (mdb_ctf_type_resolve(type_id, &type_id) != 0) goto errout; if ((cp = mdb_ctf_enum_name(type_id, val)) == NULL) goto errout; if (prefix != NULL) { size_t len = strlen(prefix); if (strncmp(cp, prefix, len) == 0) cp += len; } (void) strncpy(out, cp, size); return; errout: mdb_snprintf(out, size, "? (%d)", val); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, a list of structures * describing our walkers and a function named _mdb_init to return a pointer * to our module information. */ static const mdb_dcmd_t dcmds[] = { { "smblist", "[-seutfdwv]", "print tree of SMB objects", smblist_dcmd, smblist_help }, { "smbsrv", "[-seutfdwv]", "print smb_server information", smbsrv_dcmd }, { "smbshare", ":[-v]", "print smb_kshare_t information", smbshare_dcmd }, { "smbvfs", ":[-v]", "print smb_vfs information", smbvfs_dcmd }, { "smbnode", "?[-vps]", "print smb_node_t information", smbnode_dcmd, smbnode_help }, { "smbsess", "[-utfdwv]", "print smb_session_t information", smbsess_dcmd, smbsess_help}, { "smbreq", ":[-v]", "print smb_request_t information", smbreq_dcmd }, { "smbreq_dump", ":[-cr] [-o outfile]", "dump smb_request_t packets (cmd/reply)", smbreq_dump_dcmd, smbreq_dump_help, }, { "smblock", ":[-v]", "print smb_lock_t information", smblock_dcmd }, { "smbuser", ":[-vdftq]", "print smb_user_t information", smbuser_dcmd, smbuser_help }, { "smbtree", ":[-vdf]", "print smb_tree_t information", smbtree_dcmd, smbtree_help }, { "smbodir", ":[-v]", "print smb_odir_t information", smbodir_dcmd }, { "smbofile", "[-v]", "print smb_file_t information", smbofile_dcmd }, { "smbsrv_leases", "[-v]", "print lease table for a server", smbsrv_leases_dcmd }, { "smblease", "[-v]", "print smb_lease_t information", smblease_dcmd }, { "smbnode_oplock", NULL, "print smb_node_t oplock information", smbnode_oplock_dcmd }, { "smbofile_oplock", NULL, "print smb_ofile_t oplock information", smbofile_oplock_dcmd }, { "smbace", "[-v]", "print smb_ace_t information", smbace_dcmd }, { "smbacl", "[-v]", "print smb_acl_t information", smbacl_dcmd }, { "smbsid", "[-v]", "print smb_sid_t information", smbsid_dcmd }, { "smbsd", "[-v]", "print smb_sd_t information", smbsd_dcmd }, { "smbfssd", "[-v]", "print smb_fssd_t information", smbfssd_dcmd }, { "smb_mbuf_dump", ":[max_len]", "print mbuf_t data", smb_mbuf_dump_dcmd }, { "smbdurable", "[-v]", "list ofiles on sv->sv_persistid_ht", smbdurable_dcmd }, { "smbhashstat", "[-v]", "list stats from an smb_hash_t structure", smbhashstat_dcmd }, { NULL } }; static const mdb_walker_t walkers[] = { { "smbnode_walker", "walk list of smb_node_t structures", smb_node_walk_init, smb_node_walk_step, NULL, NULL }, { "smbshare_walker", "walk list of smb_kshare_t structures", smb_kshare_walk_init, smb_kshare_walk_step, NULL, NULL }, { "smbvfs_walker", "walk list of smb_vfs_t structures", smb_vfs_walk_init, smb_vfs_walk_step, NULL, NULL }, { "smbace_walker", "walk list of smb_ace_t structures", smb_ace_walk_init, smb_ace_walk_step, NULL, NULL }, { "smb_mbuf_walker", "walk list of mbuf_t structures", smb_mbuf_walk_init, smb_mbuf_walk_step, NULL, NULL }, { "smb_hash_walker", "walk an smb_hash_t structure", smb_hash_walk_init, smb_hash_walk_step, NULL, NULL }, { "smb_hashstat_walker", "walk the buckets from an smb_hash_t structure", smb_hashstat_walk_init, smb_hashstat_walk_step, NULL, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Support functions for dumping SMB request and response data from a * crash dump as a pcap file. This allows using tools like wireshark * to examine the request we were working on when we crashed. * * This feature is only available in mdb (not in kmdb). */ #ifdef _KMDB #error "Makefile should have excluded this file." #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smbsrv_pcap.h" /* Not sure why this isn't declared... */ extern int fstat(int, struct stat *); /* * In the capture file, packets are truncated at 64k. * The SMB len is shorter so that after we add the * (faked up) headers we're still below PCAP_SNAPLEN. */ #define PCAP_SNAPLEN (1<<16) #define MAX_SMB_LEN (PCAP_SNAPLEN - 0x40) /* * pcap file format stuff, mostly from: * wiki.wireshark.org/Development/LibpcapFileFormat */ #define PCAP_MAGIC 0xa1b2c3d4 #define PCAP_VMAJOR 2 #define PCAP_VMINOR 4 #define PCAP_DLT_RAW 0xc struct pcap_file_hdr { uint32_t magic_number; uint16_t version_major; uint16_t version_minor; uint32_t thiszone; /* TZ correction */ uint32_t sigflags; /* accuracy of timestamps */ uint32_t snaplen; /* max legnth of captured packets */ uint32_t network; /* data link type */ }; struct pcap_frame_hdr { uint32_t ts_sec; /* timestamp seconds */ uint32_t ts_usec; /* timestamp microseconds */ uint32_t incl_len; /* number of octets of packet saved in file */ uint32_t orig_len; /* actual length of packet */ }; struct my_ip6_hdr { uint8_t ip6_vers; /* 6 */ uint8_t ip6_class; uint16_t ip6_xflow; uint16_t ip6_paylen; uint8_t ip6_nexthdr; uint8_t ip6_hoplim; in6_addr_t ip6_src; in6_addr_t ip6_dst; }; static int pcap_fd = -1; /* For faking TCP sequence numbers. */ static uint32_t call_seqno; static uint32_t reply_seqno; static int pcap_file_header(char *, int); static int smb_req_pcap_m(uintptr_t, const void *, void *); void smbsrv_pcap_close(void) { if (pcap_fd != -1) { close(pcap_fd); pcap_fd = -1; } } int smbsrv_pcap_open(char *outfile) { int fd; fd = open(outfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0644); if (fd < 0) { mdb_warn("Can't open pcap output file: %s\n", outfile); return (DCMD_ERR); } if (pcap_file_header(outfile, fd) < 0) { close(fd); return (DCMD_ERR); } pcap_fd = fd; call_seqno = 1; reply_seqno = 1; return (DCMD_OK); } /* * Check or create a pcap file header */ static int pcap_file_header(char *outfile, int fd) { struct stat st; struct pcap_file_hdr hdr; int n; if (fstat(fd, &st) < 0) { mdb_warn("Can't stat pcap output file: %s\n", outfile); return (-1); } if (st.st_size < sizeof (hdr)) goto create; n = read(fd, &hdr, sizeof (hdr)); if (n != sizeof (hdr)) goto create; /* * This only supports appending to files we created, * so the file headers should all be native endian * and have the values we write when creating. */ if (hdr.magic_number != PCAP_MAGIC || hdr.version_major != PCAP_VMAJOR || hdr.version_minor != PCAP_VMINOR || hdr.snaplen != PCAP_SNAPLEN || hdr.network != PCAP_DLT_RAW) { mdb_warn("Existing file not pcap: %s\n", outfile); return (-1); } /* We will append to this file. */ (void) lseek(fd, st.st_size, SEEK_SET); return (0); create: hdr.magic_number = PCAP_MAGIC; hdr.version_major = PCAP_VMAJOR; hdr.version_minor = PCAP_VMINOR; hdr.thiszone = 0; hdr.sigflags = 0; hdr.snaplen = PCAP_SNAPLEN; hdr.network = PCAP_DLT_RAW; (void) lseek(fd, (off_t)0, SEEK_SET); n = write(fd, &hdr, sizeof (hdr)); if (n != sizeof (hdr)) { mdb_warn("Can't write output file: %s\n", outfile); return (-1); } (void) ftruncate(fd, (off_t)sizeof (hdr)); return (0); } struct req_dump_state { int32_t rem_len; int tbuf_size; char *tbuf; }; /* * Simlar to smb_req_dump, but write a pcap frame. * The headers are faked up, intended only to be * good enough so wireshark will display this. * These NEVER go over any network. */ int smbsrv_pcap_dump(struct mbuf_chain *mbc, int32_t smb_len, smb_inaddr_t *src_ip, uint16_t src_port, smb_inaddr_t *dst_ip, uint16_t dst_port, hrtime_t rqtime, boolean_t is_reply) { struct req_dump_state dump_state; struct pcap_frame_hdr phdr; struct my_ip6_hdr ip6_hdr; struct ipha_s ip_hdr; tcpha_t tcp_hdr; uint32_t nb_hdr; uint32_t *seqno; uint32_t *ackno; void *ip_hdr_p; int ip_hdr_len; int len_w_hdrs; int truncated; int n, rc; off_t pkt_off; if (smb_len < sizeof (nb_hdr)) return (DCMD_OK); if (mbc->chain == NULL) return (DCMD_ERR); /* * This code is not making fragments (for now), so just * limit SMB frames to 64k - header(s) size. */ if (smb_len > MAX_SMB_LEN) { truncated = smb_len - MAX_SMB_LEN; smb_len = MAX_SMB_LEN; } else { truncated = 0; } switch (src_ip->a_family) { case AF_INET: ip_hdr_len = sizeof (ip_hdr); break; case AF_INET6: ip_hdr_len = sizeof (ip6_hdr); break; default: mdb_warn("unknown network addr family\n"); return (DCMD_ERR); } /* Which is seq/ack? */ if (is_reply) { /* it's a reply */ seqno = &reply_seqno; ackno = &call_seqno; } else { /* it's a call */ seqno = &call_seqno; ackno = &reply_seqno; } /* * Build & dump the (faked up) frame headers: * pcap packet header * IP header (v4 or v6) * TCP header * NetBIOS header * * Build back to front, computing lengths, * then write them all out. */ /* NetBIOS (just a 32-bit payload len) */ nb_hdr = htonl(smb_len); len_w_hdrs = smb_len + sizeof (nb_hdr); /* TCP (w/ faked seq. numbers) */ tcp_hdr.tha_lport = htons(src_port); tcp_hdr.tha_fport = htons(dst_port); tcp_hdr.tha_seq = htonl(*seqno); tcp_hdr.tha_ack = htonl(*ackno); tcp_hdr.tha_offset_and_reserved = 0x50; tcp_hdr.tha_flags = 0x10; /* ACK */ tcp_hdr.tha_win = htons(0xFF00); tcp_hdr.tha_sum = 0; tcp_hdr.tha_urp = 0; (*seqno) += len_w_hdrs; len_w_hdrs += sizeof (tcp_hdr); /* IP header */ switch (src_ip->a_family) { case AF_INET: ip_hdr_p = &ip_hdr; ip_hdr_len = sizeof (ip_hdr); /* IPv4 len includes the IP4 header */ len_w_hdrs += ip_hdr_len; ip_hdr.ipha_version_and_hdr_length = 0x45; ip_hdr.ipha_type_of_service = 0; if (len_w_hdrs > 0xFFFF) ip_hdr.ipha_length = 0xFFFF; else ip_hdr.ipha_length = htons(len_w_hdrs); ip_hdr.ipha_ident = 0; ip_hdr.ipha_fragment_offset_and_flags = 0; ip_hdr.ipha_ttl = 60; ip_hdr.ipha_protocol = 6; /* TCP */ ip_hdr.ipha_hdr_checksum = 0; ip_hdr.ipha_src = src_ip->a_ipv4; ip_hdr.ipha_dst = dst_ip->a_ipv4; break; case AF_INET6: ip_hdr_p = &ip_hdr; ip_hdr_len = sizeof (ip6_hdr); ip6_hdr.ip6_vers = 6; ip6_hdr.ip6_class = 0; ip6_hdr.ip6_xflow = 0; if (len_w_hdrs > 0xFFFF) ip6_hdr.ip6_paylen = 0xFFFF; else ip6_hdr.ip6_paylen = htons(len_w_hdrs); ip6_hdr.ip6_nexthdr = 6; /* TCP */ ip6_hdr.ip6_hoplim = 64; bcopy(&src_ip->a_ipv6, &ip6_hdr.ip6_src, sizeof (ip6_hdr.ip6_src)); bcopy(&dst_ip->a_ipv6, &ip6_hdr.ip6_dst, sizeof (ip6_hdr.ip6_dst)); len_w_hdrs += ip_hdr_len; break; default: ip_hdr_p = NULL; ip_hdr_len = 0; break; } /* pcap header */ phdr.ts_sec = rqtime / NANOSEC; phdr.ts_usec = (rqtime / 1000) % MICROSEC; phdr.incl_len = len_w_hdrs; /* not incl. pcap header */ phdr.orig_len = len_w_hdrs + truncated; len_w_hdrs += sizeof (phdr); /* * Write out all the headers: * pcap, IP, TCP, NetBIOS * * To avoid any possibility of scrambling the * pcap file, save the offset here and seek to * where we should be when done writing. */ pkt_off = lseek(pcap_fd, (off_t)0, SEEK_CUR); n = write(pcap_fd, &phdr, sizeof (phdr)); if (n != sizeof (phdr)) { mdb_warn("failed to write pcap hdr\n"); goto errout; } n = write(pcap_fd, ip_hdr_p, ip_hdr_len); if (n != ip_hdr_len) { mdb_warn("failed to write IP hdr\n"); goto errout; } n = write(pcap_fd, &tcp_hdr, sizeof (tcp_hdr)); if (n != sizeof (tcp_hdr)) { mdb_warn("failed to write TCP hdr\n"); goto errout; } n = write(pcap_fd, &nb_hdr, sizeof (nb_hdr)); if (n != sizeof (nb_hdr)) { mdb_warn("failed to write NBT hdr\n"); goto errout; } /* * Finally, walk the mbuf chain writing SMB data * to the pcap file, for exactly smb_len bytes. */ dump_state.rem_len = smb_len; dump_state.tbuf_size = MCLBYTES; dump_state.tbuf = mdb_alloc(dump_state.tbuf_size, UM_SLEEP); rc = mdb_pwalk("smb_mbuf_walker", smb_req_pcap_m, &dump_state, (uintptr_t)mbc->chain); mdb_free(dump_state.tbuf, dump_state.tbuf_size); if (rc < 0) { mdb_warn("cannot walk smb_req mbuf_chain"); goto errout; } pkt_off += len_w_hdrs; (void) lseek(pcap_fd, pkt_off, SEEK_SET); return (DCMD_OK); errout: (void) lseek(pcap_fd, pkt_off, SEEK_SET); (void) ftruncate(pcap_fd, pkt_off); return (DCMD_ERR); } /* * Call-back function, called for each mbuf_t in a chain. * Copy data from this mbuf to the pcap file. */ static int smb_req_pcap_m(uintptr_t mbuf_addr, const void *data, void *arg) { struct req_dump_state *st = arg; const struct mbuf *m = data; uintptr_t addr; int cnt, mlen, n, x; addr = (uintptr_t)m->m_data; mlen = m->m_len; if (mlen > st->rem_len) mlen = st->rem_len; if (mlen <= 0) return (WALK_DONE); cnt = mlen; while (cnt > 0) { x = MIN(cnt, st->tbuf_size); n = mdb_vread(st->tbuf, x, addr); if (n != x) { mdb_warn("failed copying mbuf %p\n", mbuf_addr); return (WALK_ERR); } n = write(pcap_fd, st->tbuf, x); if (n != x) { mdb_warn("failed writing pcap data\n"); return (WALK_ERR); } addr += x; cnt -= x; } st->rem_len -= mlen; return (WALK_NEXT); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ #ifndef _SMBSRV_PCAP_H #define _SMBSRV_PCAP_H extern void smbsrv_pcap_close(); extern int smbsrv_pcap_open(char *); extern int smbsrv_pcap_dump(struct mbuf_chain *, int32_t, smb_inaddr_t *, uint16_t, smb_inaddr_t *, uint16_t, hrtime_t, boolean_t); #endif /* _SMBSRV_PCAP_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include /* * Look up the symbol name for the given sockparams list and walk * all the entries. */ static boolean_t sockparams_walk_list(const char *symname, int argc, const mdb_arg_t *argv) { GElf_Sym sym; if (mdb_lookup_by_name(symname, &sym)) { mdb_warn("can't find symbol %s", symname); return (B_FALSE); } if (mdb_pwalk_dcmd("list", "sockfs`sockparams", argc, argv, sym.st_value) != 0) { mdb_warn("can't walk %s", symname); return (B_FALSE); } return (B_TRUE); } /* * dcmd to print sockparams info. * * If no address is given then the default is to print all sockparams on the * global list (i.e., installed with soconfig(8)). To also print the ephemeral * entries the '-e' flag should be used. Only ephemeral entries can be printed * by specifying the '-E' flag. */ static int sockparams_prt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct sockparams sp; char strdev[MAXPATHLEN]; char sockmod[MODMAXNAMELEN]; if ((flags & DCMD_ADDRSPEC) == 0) { uint_t opt_e = 0; uint_t opt_E = 0; /* * Determine what lists should be printed */ if (mdb_getopts(argc, argv, 'e', MDB_OPT_SETBITS, 1, &opt_e, 'E', MDB_OPT_SETBITS, 1, &opt_E, NULL) != argc) return (DCMD_USAGE); if (!opt_E) { if (!sockparams_walk_list("sphead", argc, argv)) return (DCMD_ERR); } if (opt_e || opt_E) { if (!sockparams_walk_list("sp_ephem_list", argc, argv)) return (DCMD_ERR); } return (DCMD_OK); } /* * If we are piping the output, then just print out the address, * otherwise summarize the sockparams info. */ if ((flags & DCMD_PIPE_OUT) != 0) { mdb_printf("%#lr\n", addr); return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%-?s %3s %3s %3s %15s %15s %6s %6s\n", "ADDR", "FAM", "TYP", "PRO", "STRDEV", "SOCKMOD", "REFS", "FLGS"); } if (mdb_vread(&sp, sizeof (sp), addr) == -1) { mdb_warn("failed to read sockparams at %0?p", addr); return (DCMD_ERR); } if ((sp.sp_sdev_info.sd_devpath == NULL) || (mdb_readstr(strdev, sizeof (strdev), (uintptr_t)sp.sp_sdev_info.sd_devpath) <= 0)) strcpy(strdev, "-"); if (mdb_readstr(sockmod, sizeof (sockmod), (uintptr_t)sp.sp_smod_name) <= 0) strcpy(sockmod, ""); mdb_printf("%0?p %3u %3u %3u %15s %15s %6u %#6x\n", addr, sp.sp_family, sp.sp_type, sp.sp_protocol, strdev, sockmod, sp.sp_refcnt, sp.sp_flags); return (DCMD_OK); } /* * Help function */ void sockparams_help(void) { mdb_printf("Print sockparams information for a give sockparams ptr.\n" "Without the address, list available sockparams. Default " "behavior is to list only entries that were installed by the " "admin (via soconfig(8)).\n\n" "Options:\n" " -e:\t\tlist ephemeral sockparams\n" " -E:\t\tonly list ephemeral sockparams\n"); } static const mdb_dcmd_t dcmds[] = { { "sockparams", "[-eE]", "print sockparams", sockparams_prt, sockparams_help }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include typedef struct snode_walk_data { int sw_stablesz; uintptr_t sw_stable; } snode_walk_data_t; int snode_walk_init(mdb_walk_state_t *wsp) { int stablesz; GElf_Sym sym; uintptr_t stable; uintptr_t sp; snode_walk_data_t *sw; if (mdb_readvar(&stablesz, "stablesz") == -1) { mdb_warn("failed to read 'stablesz'"); return (WALK_ERR); } if (stablesz == 0) return (WALK_DONE); if (mdb_lookup_by_name("stable", &sym) == -1) { mdb_warn("failed to read 'stable'"); return (WALK_ERR); } stable = (uintptr_t)sym.st_value; if (mdb_vread(&sp, sizeof (sp), stable) == -1) { mdb_warn("failed to read stable entry at %p", stable); return (WALK_DONE); } sw = mdb_alloc(sizeof (snode_walk_data_t), UM_SLEEP); sw->sw_stablesz = stablesz; sw->sw_stable = stable; wsp->walk_addr = sp; wsp->walk_data = sw; return (WALK_NEXT); } int snode_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; snode_walk_data_t *sw = wsp->walk_data; struct snode *sp; struct snode snode; while (addr == 0) { if (--sw->sw_stablesz == 0) return (WALK_DONE); sw->sw_stable += sizeof (struct snode *); if (mdb_vread(&sp, sizeof (sp), sw->sw_stable) == -1) { mdb_warn("failed to read stable entry at %p", sw->sw_stable); return (WALK_DONE); } addr = (uintptr_t)sp; } if (mdb_vread(&snode, sizeof (snode), addr) == -1) { mdb_warn("failed to read snode at %p", addr); return (WALK_DONE); } wsp->walk_addr = (uintptr_t)snode.s_next; return (wsp->walk_callback(addr, &snode, wsp->walk_cbdata)); } void snode_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (snode_walk_data_t)); } typedef struct snode_cbdata { int sd_major; int sd_minor; int sd_verbose; } snode_cbdata_t; static int snode_cb(uintptr_t addr, const struct snode *snode, snode_cbdata_t *sd) { static const mdb_bitmask_t s_flag_masks[] = { { "UPD", SUPD, SUPD }, { "ACC", SACC, SACC }, { "CHG", SCHG, SCHG }, { "PRIV", SPRIV, SPRIV }, { "LOFFSET", SLOFFSET, SLOFFSET }, { "LOCKED", SLOCKED, SLOCKED }, { "WANT", SWANT, SWANT }, { "CLONE", SCLONE, SCLONE }, { "NEEDCLOSE", SNEEDCLOSE, SNEEDCLOSE }, { "DIPSET", SDIPSET, SDIPSET }, { "SIZEVALID", SSIZEVALID, SSIZEVALID }, { "MUXED", SMUXED, SMUXED }, { "SELFCLONE", SSELFCLONE, SSELFCLONE }, { "NOFLUSH", SNOFLUSH, SNOFLUSH }, { "CLOSING", SCLOSING, SCLOSING }, { NULL, 0, 0 } }; int major = getmajor(snode->s_dev); int minor = getminor(snode->s_dev); if (sd->sd_major != -1 && sd->sd_major != major) return (WALK_NEXT); if (sd->sd_minor != -1 && sd->sd_minor != minor) return (WALK_NEXT); if (sd->sd_verbose) { mdb_printf("%0?p %?p %6d %16lx <%b>\n", addr, snode->s_vnode, snode->s_count, snode->s_dev, snode->s_flag, s_flag_masks); } else { mdb_printf("%p\n", addr); } return (WALK_NEXT); } /*ARGSUSED*/ int snode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { snode_cbdata_t sd; struct snode snode; uintptr_t major = 0, dev = 0; sd.sd_major = -1; sd.sd_minor = -1; sd.sd_verbose = !(flags & DCMD_PIPE_OUT); if (mdb_getopts(argc, argv, 'm', MDB_OPT_UINTPTR, &major, 'd', MDB_OPT_UINTPTR, &dev, NULL) != argc) return (DCMD_USAGE); if (dev != 0) { sd.sd_major = getmajor(dev); sd.sd_minor = getminor(dev); } if (major != 0) sd.sd_major = major; if (DCMD_HDRSPEC(flags) && !(flags & DCMD_PIPE_OUT)) { mdb_printf("%%?s %?s %6s %16s %-15s%\n", "ADDR", "VNODE", "COUNT", "DEV", "FLAG"); } if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk("snode", (mdb_walk_cb_t)snode_cb, &sd) == -1) { mdb_warn("can't walk snodes"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&snode, sizeof (snode), addr) == -1) { mdb_warn("failed to read snode structure at %p", addr); return (DCMD_ERR); } snode_cb(addr, &snode, &sd); return (DCMD_OK); } /*ARGSUSED3*/ int major2snode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { snode_cbdata_t sd; if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); sd.sd_major = addr; sd.sd_minor = -1; sd.sd_verbose = 0; if (mdb_pwalk("snode", (mdb_walk_cb_t)snode_cb, &sd, 0) != 0) return (DCMD_ERR); return (DCMD_OK); } /*ARGSUSED3*/ int dev2snode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { snode_cbdata_t sd; if (!(flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); sd.sd_major = getmajor(addr); sd.sd_minor = getminor(addr); sd.sd_verbose = 0; if (mdb_pwalk("snode", (mdb_walk_cb_t)snode_cb, &sd, 0) != 0) return (DCMD_ERR); return (DCMD_OK); } void snode_help(void) { mdb_printf("Options:\n" " -d device filter snodes of the specified dev_t\n" " -m major filter snodes of the specified major number\n"); } /* * MDB module linkage */ static const mdb_dcmd_t dcmds[] = { { "dev2snode", ":", "given a dev_t, return the snode", dev2snode }, { "major2snode", ":", "given a major number, return the snode(s)", major2snode }, { "snode", "?[-d device] [-m major]", "filter and display snode structures", snode, snode_help }, { NULL } }; static const mdb_walker_t walkers[] = { { "snode", "walk global snode lists", snode_walk_init, snode_walk_step, snode_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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. */ #include #include #include #include #include #define SOL2 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ****************** sppp ****************** */ static int sppp_walk_init(mdb_walk_state_t *wsp) { if (mdb_readvar(&wsp->walk_addr, "sps_list") == -1) { mdb_warn("failed to read sps_list"); return (WALK_ERR); } return (WALK_NEXT); } static int sppp_walk_step(mdb_walk_state_t *wsp) { spppstr_t sps; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&sps, sizeof (sps), wsp->walk_addr) == -1) { mdb_warn("can't read spppstr_t at %p", wsp->walk_addr); return (WALK_ERR); } status = (wsp->walk_callback(wsp->walk_addr, &sps, wsp->walk_cbdata)); wsp->walk_addr = (uintptr_t)sps.sps_nextmn; return (status); } static int sps_format(uintptr_t addr, const spppstr_t *sps, uint_t *qfmt) { sppa_t ppa; queue_t upq; uintptr_t upaddr, illaddr; ill_t ill; ipif_t ipif; mdb_printf("%?p ", addr); if (*qfmt) mdb_printf("%?p ", sps->sps_rq); if (sps->sps_ppa == NULL) { mdb_printf("? unset "); } else if (mdb_vread(&ppa, sizeof (ppa), (uintptr_t)sps->sps_ppa) == -1) { mdb_printf("? ?%p ", sps->sps_ppa); } else { mdb_printf("%-6d sppp%-5d ", ppa.ppa_zoneid, ppa.ppa_ppa_id); } if (IS_SPS_CONTROL(sps)) { mdb_printf("Control\n"); } else if (IS_SPS_PIOATTACH(sps)) { mdb_printf("Stats\n"); } else if (sps->sps_dlstate == DL_UNATTACHED) { mdb_printf("Unknown\n"); } else if (sps->sps_dlstate != DL_IDLE) { mdb_printf("DLPI Unbound\n"); } else { upaddr = (uintptr_t)sps->sps_rq; upq.q_ptr = NULL; illaddr = 0; while (upaddr != 0) { if (mdb_vread(&upq, sizeof (upq), upaddr) == -1) { upq.q_ptr = NULL; break; } if ((upaddr = (uintptr_t)upq.q_next) != 0) illaddr = (uintptr_t)upq.q_ptr; } if (illaddr != 0) { if (mdb_vread(&ill, sizeof (ill), illaddr) == -1 || mdb_vread(&ipif, sizeof (ipif), (uintptr_t)ill.ill_ipif) == -1) { illaddr = 0; } } switch (sps->sps_req_sap) { case ETHERTYPE_IP: mdb_printf("DLPI IPv4 "); if (*qfmt) { mdb_printf("\n"); } else if (illaddr == 0) { mdb_printf("(no addresses)\n"); } else { /* * SCCS oddity here -- % % * suffers from keyword replacement. * Avoid that by using ANSI string * pasting. */ mdb_printf("%I:%I" "%s\n", ipif.ipif_lcl_addr, ipif.ipif_pp_dst_addr, (ipif.ipif_next ? " ..." : "")); } break; case ETHERTYPE_IPV6: mdb_printf("DLPI IPv6 "); if (*qfmt) { mdb_printf("\n"); break; } if (illaddr == 0) { mdb_printf("(no addresses)\n"); break; } mdb_printf("%N\n%?s%21s", &ipif.ipif_v6lcl_addr, "", ""); mdb_printf("%N\n", &ipif.ipif_v6pp_dst_addr); break; case ETHERTYPE_ALLSAP: mdb_printf("DLPI Snoop\n"); break; default: mdb_printf("DLPI SAP 0x%04X\n", sps->sps_req_sap); break; } } return (WALK_NEXT); } static int sppp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t qfmt = FALSE; spppstr_t sps; if (mdb_getopts(argc, argv, 'q', MDB_OPT_SETBITS, TRUE, &qfmt, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { if (qfmt) { mdb_printf("%%?s %?s %-6s %-9s %s%\n", "Address", "RecvQ", "ZoneID", "Interface", "Type"); } else { mdb_printf("%%?s %-6s %-9s %s%\n", "Address", "ZoneID", "Interface", "Type"); } } if (flags & DCMD_ADDRSPEC) { (void) mdb_vread(&sps, sizeof (sps), addr); (void) sps_format(addr, &sps, &qfmt); } else if (mdb_walk("sppp", (mdb_walk_cb_t)sps_format, &qfmt) == -1) { mdb_warn("failed to walk sps_list"); return (DCMD_ERR); } return (DCMD_OK); } static int sppa_walk_init(mdb_walk_state_t *wsp) { if (mdb_readvar(&wsp->walk_addr, "ppa_list") == -1) { mdb_warn("failed to read ppa_list"); return (WALK_ERR); } return (WALK_NEXT); } static int sppa_walk_step(mdb_walk_state_t *wsp) { sppa_t ppa; int status; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&ppa, sizeof (ppa), wsp->walk_addr) == -1) { mdb_warn("can't read spppstr_t at %p", wsp->walk_addr); return (WALK_ERR); } status = (wsp->walk_callback(wsp->walk_addr, &ppa, wsp->walk_cbdata)); wsp->walk_addr = (uintptr_t)ppa.ppa_nextppa; return (status); } /* ARGSUSED */ static int ppa_format(uintptr_t addr, const sppa_t *ppa, uint_t *qfmt) { mdb_printf("%?p %-6d sppp%-5d %?p %?p\n", addr, ppa->ppa_zoneid, ppa->ppa_ppa_id, ppa->ppa_ctl, ppa->ppa_lower_wq); return (WALK_NEXT); } /* ARGSUSED */ static int sppa(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t qfmt = FALSE; sppa_t ppa; if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s %-6s %-9s %?s %?s%\n", "Address", "ZoneID", "Interface", "Control", "LowerQ"); } if (flags & DCMD_ADDRSPEC) { (void) mdb_vread(&ppa, sizeof (ppa), addr); (void) ppa_format(addr, &ppa, &qfmt); } else if (mdb_walk("sppa", (mdb_walk_cb_t)ppa_format, &qfmt) == -1) { mdb_warn("failed to walk ppa_list"); return (DCMD_ERR); } return (DCMD_OK); } static void sppp_qinfo(const queue_t *q, char *buf, size_t nbytes) { spppstr_t sps; sppa_t ppa; if (mdb_vread(&sps, sizeof (sps), (uintptr_t)q->q_ptr) == sizeof (sps)) { if (sps.sps_ppa == NULL || mdb_vread(&ppa, sizeof (ppa), (uintptr_t)sps.sps_ppa) == -1) { (void) mdb_snprintf(buf, nbytes, "minor %d", sps.sps_mn_id); } else { (void) mdb_snprintf(buf, nbytes, "sppp%d", ppa.ppa_ppa_id); } } } static uintptr_t sppp_rnext(const queue_t *q) { spppstr_t sps; if (mdb_vread(&sps, sizeof (sps), (uintptr_t)q->q_ptr) == sizeof (sps)) return ((uintptr_t)sps.sps_rq); return (0); } static uintptr_t sppp_wnext(const queue_t *q) { spppstr_t sps; sppa_t ppa; if (mdb_vread(&sps, sizeof (sps), (uintptr_t)q->q_ptr) != sizeof (sps)) return (0); if (sps.sps_ppa != NULL && mdb_vread(&ppa, sizeof (ppa), (uintptr_t)sps.sps_ppa) == sizeof (ppa)) return ((uintptr_t)ppa.ppa_lower_wq); return (0); } /* ****************** sppptun ****************** */ struct tcl_walk_data { size_t tcl_nslots; size_t walkpos; tuncl_t *tcl_slots[1]; }; static void tuncl_walk_fini(mdb_walk_state_t *wsp) { struct tcl_walk_data *twd; if (wsp != NULL && wsp->walk_addr != 0) { twd = (struct tcl_walk_data *)wsp->walk_addr; mdb_free(twd, sizeof (*twd) + ((twd->tcl_nslots - 1) * sizeof (twd->tcl_slots[0]))); wsp->walk_addr = 0; } } static int tuncl_walk_init(mdb_walk_state_t *wsp) { size_t tcl_nslots; tuncl_t **tcl_slots; struct tcl_walk_data *twd; if (wsp == NULL) return (WALK_ERR); if (wsp->walk_addr != 0) tuncl_walk_fini(wsp); if (mdb_readvar(&tcl_nslots, "tcl_nslots") == -1) { mdb_warn("failed to read tcl_nslots"); return (WALK_ERR); } if (tcl_nslots == 0) return (WALK_DONE); if (mdb_readvar(&tcl_slots, "tcl_slots") == -1) { mdb_warn("failed to read tcl_slots"); return (WALK_ERR); } twd = (struct tcl_walk_data *)mdb_alloc(sizeof (*twd) + (tcl_nslots - 1) * sizeof (*tcl_slots), UM_NOSLEEP); if (twd == NULL) return (WALK_ERR); twd->tcl_nslots = tcl_nslots; twd->walkpos = 0; wsp->walk_addr = (uintptr_t)twd; if (mdb_vread(twd->tcl_slots, tcl_nslots * sizeof (twd->tcl_slots[0]), (uintptr_t)tcl_slots) == -1) { mdb_warn("can't read tcl_slots at %p", tcl_slots); tuncl_walk_fini(wsp); return (WALK_ERR); } return (WALK_NEXT); } static int tuncl_walk_step(mdb_walk_state_t *wsp) { tuncl_t tcl; int status; struct tcl_walk_data *twd; uintptr_t addr; if (wsp == NULL || wsp->walk_addr == 0) return (WALK_DONE); twd = (struct tcl_walk_data *)wsp->walk_addr; while (twd->walkpos < twd->tcl_nslots && twd->tcl_slots[twd->walkpos] == NULL) twd->walkpos++; if (twd->walkpos >= twd->tcl_nslots) return (WALK_DONE); addr = (uintptr_t)twd->tcl_slots[twd->walkpos]; if (mdb_vread(&tcl, sizeof (tcl), addr) == -1) { mdb_warn("can't read tuncl_t at %p", addr); return (WALK_ERR); } status = wsp->walk_callback(addr, &tcl, wsp->walk_cbdata); twd->walkpos++; return (status); } /* ARGSUSED */ static int tuncl_format(uintptr_t addr, const tuncl_t *tcl, uint_t *qfmt) { mdb_printf("%?p %-6d %?p %?p", addr, tcl->tcl_zoneid, tcl->tcl_data_tll, tcl->tcl_ctrl_tll); mdb_printf(" %-2d %04X %04X ", tcl->tcl_style, tcl->tcl_lsessid, tcl->tcl_rsessid); if (tcl->tcl_flags & TCLF_DAEMON) { mdb_printf("\n"); } else { mdb_printf("sppp%d\n", tcl->tcl_unit); } return (WALK_NEXT); } /* ARGSUSED */ static int tuncl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t qfmt = FALSE; tuncl_t tcl; if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s %-6s %?s %?s Ty LSes RSes %s%\n", "Address", "ZoneID", "Data", "Control", "Interface"); } if (flags & DCMD_ADDRSPEC) { if (mdb_vread(&tcl, sizeof (tcl), addr) == -1) mdb_warn("failed to read tuncl_t at %p", addr); else tuncl_format(addr, &tcl, &qfmt); } else if (mdb_walk("tuncl", (mdb_walk_cb_t)tuncl_format, &qfmt) == -1) { mdb_warn("failed to walk tcl_slots"); return (DCMD_ERR); } return (DCMD_OK); } struct tll_walk_data { void *listhead; void *next; }; static void tunll_walk_fini(mdb_walk_state_t *wsp) { struct tll_walk_data *twd; if (wsp != NULL && wsp->walk_addr != 0) { twd = (struct tll_walk_data *)wsp->walk_addr; mdb_free(twd, sizeof (*twd)); wsp->walk_addr = 0; } } static int tunll_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; struct tll_walk_data *twd; struct qelem tunll_list; if (wsp->walk_addr != 0) tunll_walk_fini(wsp); if (mdb_lookup_by_obj("sppptun", "tunll_list", &sym) != 0) { mdb_warn("failed to find tunll_list"); return (WALK_ERR); } if (mdb_vread(&tunll_list, sizeof (tunll_list), (uintptr_t)sym.st_value) == -1) { mdb_warn("can't read tunll_list at %p", (uintptr_t)sym.st_value); return (WALK_ERR); } twd = (struct tll_walk_data *)mdb_alloc(sizeof (*twd), UM_NOSLEEP); if (twd == NULL) return (WALK_ERR); twd->listhead = (void *)(uintptr_t)sym.st_value; twd->next = (void *)tunll_list.q_forw; wsp->walk_addr = (uintptr_t)twd; return (WALK_NEXT); } static int tunll_walk_step(mdb_walk_state_t *wsp) { struct tll_walk_data *twd; tunll_t tll; int status; uintptr_t addr; if (wsp == NULL || wsp->walk_addr == 0) return (WALK_DONE); twd = (struct tll_walk_data *)wsp->walk_addr; if (twd->next == NULL || twd->next == twd->listhead) return (WALK_DONE); /* LINTED */ addr = (uintptr_t)TO_TLL(twd->next); if (mdb_vread(&tll, sizeof (tll), addr) == -1) { mdb_warn("can't read tunll_t at %p", addr); return (WALK_ERR); } status = wsp->walk_callback(addr, &tll, wsp->walk_cbdata); twd->next = (void *)tll.tll_next; return (status); } /* ARGSUSED */ static int tunll_format(uintptr_t addr, const tunll_t *tll, uint_t *qfmt) { mdb_printf("%?p %-6d %-14s %?p", addr, tll->tll_zoneid, tll->tll_name, tll->tll_defcl); if (tll->tll_style == PTS_PPPOE) { mdb_printf(" %x:%x:%x:%x:%x:%x", tll->tll_lcladdr.pta_pppoe.ptma_mac[0], tll->tll_lcladdr.pta_pppoe.ptma_mac[1], tll->tll_lcladdr.pta_pppoe.ptma_mac[2], tll->tll_lcladdr.pta_pppoe.ptma_mac[3], tll->tll_lcladdr.pta_pppoe.ptma_mac[4], tll->tll_lcladdr.pta_pppoe.ptma_mac[5]); } mdb_printf("\n"); return (WALK_NEXT); } /* ARGSUSED */ static int tunll(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t qfmt = FALSE; tunll_t tll; if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s %-6s %-14s %?s %s%\n", "Address", "ZoneID", "Interface Name", "Daemon", "Local Address"); } if (flags & DCMD_ADDRSPEC) { if (mdb_vread(&tll, sizeof (tll), addr) == -1) mdb_warn("failed to read tunll_t at %p", addr); else tunll_format(addr, &tll, &qfmt); } else if (mdb_walk("tunll", (mdb_walk_cb_t)tunll_format, &qfmt) == -1) { mdb_warn("failed to walk tunll_list"); return (DCMD_ERR); } return (DCMD_OK); } union tun_state { uint32_t tunflags; tuncl_t tcl; tunll_t tll; }; static int tun_state_read(void *ptr, union tun_state *ts) { /* * First, get the flags on this structure. This is either a * tuncl_t or a tunll_t. */ if (mdb_vread(&ts->tunflags, sizeof (ts->tunflags), (uintptr_t)ptr) == sizeof (ts->tunflags)) { if (ts->tunflags & TCLF_ISCLIENT) { if (mdb_vread(&ts->tcl, sizeof (ts->tcl), (uintptr_t)ptr) == sizeof (ts->tcl)) { return (0); } } else { if (mdb_vread(&ts->tll, sizeof (ts->tll), (uintptr_t)ptr) == sizeof (ts->tll)) { return (0); } } } return (-1); } static void sppptun_qinfo(const queue_t *q, char *buf, size_t nbytes) { union tun_state ts; if (tun_state_read(q->q_ptr, &ts) == -1) return; if (ts.tcl.tcl_flags & TCLF_ISCLIENT) mdb_snprintf(buf, nbytes, "sppp%d client %04X", ts.tcl.tcl_unit, ts.tcl.tcl_lsessid); else mdb_snprintf(buf, nbytes, "%s", ts.tll.tll_name); } static uintptr_t sppptun_rnext(const queue_t *q) { union tun_state ts; if (tun_state_read(q->q_ptr, &ts) == -1) return (0); if (ts.tcl.tcl_flags & TCLF_ISCLIENT) { return ((uintptr_t)ts.tcl.tcl_rq); } else { /* Not quite right, but ... */ return ((uintptr_t)ts.tll.tll_defcl); } } static uintptr_t sppptun_wnext(const queue_t *q) { union tun_state ts; if (tun_state_read(q->q_ptr, &ts) == -1) return (0); if (ts.tcl.tcl_flags & TCLF_ISCLIENT) { if (ts.tcl.tcl_data_tll == NULL) return (0); if (mdb_vread(&ts.tll, sizeof (ts.tll), (uintptr_t)ts.tcl.tcl_data_tll) != sizeof (ts.tll)) { return (0); } } return ((uintptr_t)ts.tll.tll_wq); } static const mdb_dcmd_t dcmds[] = { { "sppp", "[-q]", "display PPP stream state structures", sppp }, { "sppa", "", "display PPP attachment state structures", sppa }, { "tuncl", "", "display sppptun client stream state structures", tuncl }, { "tunll", "", "display sppptun lower stream state structures", tunll }, { NULL } }; static const mdb_walker_t walkers[] = { { "sppp", "walk active spppstr_t structures", sppp_walk_init, sppp_walk_step, NULL }, { "sppa", "walk active sppa_t structures", sppa_walk_init, sppa_walk_step, NULL }, { "tuncl", "walk active tuncl_t structures", tuncl_walk_init, tuncl_walk_step, tuncl_walk_fini }, { "tunll", "walk active tunll_t structures", tunll_walk_init, tunll_walk_step, tunll_walk_fini }, { NULL } }; static const mdb_qops_t sppp_qops = { .q_info = sppp_qinfo, .q_rnext = sppp_rnext, .q_wnext = sppp_wnext, }; static const mdb_qops_t sppptun_qops = { .q_info = sppptun_qinfo, .q_rnext = sppptun_rnext, .q_wnext = sppptun_wnext, }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_obj("sppp", "sppp_uwinit", &sym) == 0) mdb_qops_install(&sppp_qops, (uintptr_t)sym.st_value); if (mdb_lookup_by_obj("sppptun", "sppptun_uwinit", &sym) == 0) mdb_qops_install(&sppptun_qops, (uintptr_t)sym.st_value); return (&modinfo); } void _mdb_fini(void) { GElf_Sym sym; if (mdb_lookup_by_obj("sppptun", "sppptun_uwinit", &sym) == 0) mdb_qops_remove(&sppptun_qops, (uintptr_t)sym.st_value); if (mdb_lookup_by_obj("sppp", "sppp_uwinit", &sym) == 0) mdb_qops_remove(&sppp_qops, (uintptr_t)sym.st_value); } /* * 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. */ #include #include #include #include #include #include #include #include /* * byteswap macros since ntohl not available in kernel mdb */ #if defined(_LITTLE_ENDIAN) #define SRPT_BSWAP_32(x) (((uint32_t)(x) << 24) | \ (((uint32_t)(x) << 8) & 0xff0000) | \ (((uint32_t)(x) >> 8) & 0xff00) | \ ((uint32_t)(x) >> 24)) #define SRPT_BSWAP_16(x) ((((x) & 0xff) << 8) | ((x) >> 8)) #else #define SRPT_BSWAP_32(x) (x) #define SRPT_BSWAP_16(x) (x) #endif /* _LITTLE_ENDIAN */ /* * Walker to list the addresses of all the active I/O Controllers */ static int srpt_ioc_walk_init(mdb_walk_state_t *wsp) { srpt_ctxt_t *srpt; uintptr_t srpt_global_addr, list_addr; if (mdb_readvar(&srpt, "srpt_ctxt") == -1) { mdb_warn("failed to read srpt soft state"); return (WALK_ERR); } srpt_global_addr = (uintptr_t)srpt; list_addr = srpt_global_addr + offsetof(srpt_ctxt_t, sc_ioc_list); wsp->walk_addr = list_addr; if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("list walk failed"); return (WALK_ERR); } return (WALK_NEXT); } static int srpt_list_walk_step(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { return (WALK_DONE); } return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } /* * Walker to list the target services per I/O Controller. The I/O Controller is * provided as input. */ static int srpt_tgt_walk_init(mdb_walk_state_t *wsp) { srpt_ioc_t srpt_ioc; /* * Input should be a srpt_ioc_t, read it to get the * srpt_target_port_t */ if (wsp->walk_addr == 0) { mdb_warn("::walk srpt_target\n"); return (WALK_ERR); } if (mdb_vread(&srpt_ioc, sizeof (srpt_ioc_t), wsp->walk_addr) == -1) { mdb_warn("failed to read in the srpt_ioc\n "); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)srpt_ioc.ioc_tgt_port; wsp->walk_data = mdb_alloc(sizeof (srpt_target_port_t), UM_SLEEP); return (WALK_NEXT); } static int srpt_tgt_walk_step(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { return (WALK_DONE); } (void) wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); /* Currently there is only one target per IOC */ return (WALK_DONE); } static void srpt_tgt_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (srpt_target_port_t)); } /* * Walker to list the channels per SRP target service. The target port is * provided as input. */ static int srpt_channel_walk_init(mdb_walk_state_t *wsp) { /* * Input should be a srpt_target_port_t, read it to get the * list of channels */ if (wsp->walk_addr == 0) { mdb_warn("::walk srpt_channel\n"); return (WALK_ERR); } wsp->walk_addr += offsetof(srpt_target_port_t, tp_ch_list); if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("Could not walk tp_ch_list"); return (WALK_ERR); } return (WALK_NEXT); } /* * Walker to list the SCSI sessions per target. The target is * provided as input. */ static int srpt_scsi_session_walk_init(mdb_walk_state_t *wsp) { /* * Input should be a srpt_target_port_t, read it to get the * srpt_session_t */ if (wsp->walk_addr == 0) { mdb_warn("::walk srpt_scsi_session\n"); return (WALK_ERR); } wsp->walk_addr += offsetof(srpt_target_port_t, tp_sess_list); if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("target session list walk failed"); return (WALK_ERR); } return (WALK_NEXT); } /* * Walker to list the tasks in a session. The session is * provided as input. */ static int srpt_task_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("::walk srpt_tasks\n"); return (WALK_ERR); } wsp->walk_addr += offsetof(srpt_session_t, ss_task_list); if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("session task list walk failed"); return (WALK_ERR); } return (WALK_NEXT); } /* ARGSUSED */ static int srpt_print_ioc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { srpt_ioc_t ioc; char mask[9]; int i; if (addr == 0) { mdb_warn("address of srpt_ioc should be specified\n"); return (DCMD_ERR); } if (mdb_vread(&ioc, sizeof (srpt_ioc_t), addr) == -1) { mdb_warn("failed to read srpt_ioc at %p", addr); return (DCMD_ERR); } mdb_printf("IOC %p\n", addr); mdb_printf(" guid: %x\n", ioc.ioc_guid); mdb_printf(" target port: %p\n", ioc.ioc_tgt_port); mdb_printf(" srq handle: %p\n", ioc.ioc_srq_hdl); mdb_printf(" current srq size: %u\n", ioc.ioc_num_iu_entries); mdb_printf(" max srq size: %d\n", ioc.ioc_srq_attr.srq_wr_sz); mdb_printf(" iu pool: %p\n", ioc.ioc_iu_pool); mdb_printf(" profile send qdepth: %d\n", SRPT_BSWAP_16(ioc.ioc_profile.ioc_send_msg_qdepth)); mdb_printf(" profile rmda read qdepth: %d\n", ioc.ioc_profile.ioc_rdma_read_qdepth); mdb_printf(" profile send msg size: %d\n", SRPT_BSWAP_32(ioc.ioc_profile.ioc_send_msg_sz)); mdb_printf(" profile rmda xfer size: %d\n", SRPT_BSWAP_32(ioc.ioc_profile.ioc_rdma_xfer_sz)); for (i = 0; i < 8; i++) { if (ioc.ioc_profile.ioc_ctrl_opcap_mask & 1< #include #include #include #include #include #include #include #include #include #include #include #include "cmd_options.h" static int stmf_ilport_walk_i(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { struct stmf_state state; if (mdb_readsym(&state, sizeof (struct stmf_state), "stmf_state") == -1) { mdb_warn("failed to read stmf_state"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)state.stmf_ilportlist; } wsp->walk_data = mdb_alloc(sizeof (stmf_i_local_port_t), UM_SLEEP); return (WALK_NEXT); } static int stmf_ilport_walk_s(mdb_walk_state_t *wsp) { int status = WALK_NEXT; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (struct stmf_i_local_port), wsp->walk_addr) == -1) { mdb_warn("failed to read stmf_i_local_port_t at %p", wsp->walk_addr); return (WALK_ERR); } if (wsp->walk_callback) status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t) (((struct stmf_i_local_port *)wsp->walk_data)->ilport_next); return (status); } static void stmf_ilport_walk_f(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct stmf_i_local_port)); } static int dump_ilport(struct stmf_i_local_port *ilportp, int verbose) { if (ilportp == NULL) return (DCMD_OK); mdb_printf("%p\n", ilportp); if (verbose) { /* here assume the alias is maximumly 1024 bytes */ char alias[255]; struct stmf_local_port lport; struct stmf_i_local_port ilport; if (mdb_vread(&ilport, sizeof (ilport), (uintptr_t)ilportp) == -1) { mdb_warn("failed to read stmf_i_local_port at %p", ilportp); return (DCMD_ERR); } memset(alias, 0, sizeof (alias)); if (mdb_vread(&lport, sizeof (lport), (uintptr_t)ilport.ilport_lport) == -1) { mdb_warn("failed to read stmf_local_port at %p", ilport.ilport_lport); return (DCMD_ERR); } if (lport.lport_alias && mdb_vread(alias, sizeof (alias), (uintptr_t)lport.lport_alias) == -1) { mdb_warn("failed to read memory at %p", lport.lport_alias); return (DCMD_ERR); } mdb_printf(" lport: %p\n", ilport.ilport_lport); if (lport.lport_alias) mdb_printf(" port alias: %s\n", alias); mdb_printf(" port provider: %p\n", lport.lport_pp); } return (DCMD_OK); } /*ARGSUSED*/ static int stmf_ilports(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; int verbose = 0; mdb_walk_state_t ws = {NULL, }; for (i = 0; i < argc; i++) { char *ptr = (char *)argv[i].a_un.a_str; if (ptr[0] == '-') ptr++; while (*ptr) { if (*ptr == 'v') verbose = 1; ptr++; } } if (stmf_ilport_walk_i(&ws) == WALK_ERR) return (DCMD_ERR); dump_ilport((stmf_i_local_port_t *)ws.walk_addr, verbose); while (stmf_ilport_walk_s(&ws) == WALK_NEXT) dump_ilport((stmf_i_local_port_t *)ws.walk_addr, verbose); stmf_ilport_walk_f(&ws); return (DCMD_OK); } struct stmf_i_local_port * next_stmf_port(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { if (stmf_ilport_walk_i(wsp) == WALK_ERR) { stmf_ilport_walk_f(wsp); return (NULL); } if (wsp->walk_addr == 0) stmf_ilport_walk_f(wsp); return ((struct stmf_i_local_port *)wsp->walk_addr); } if (stmf_ilport_walk_s(wsp) == WALK_ERR) { stmf_ilport_walk_f(wsp); return (NULL); } if (wsp->walk_addr == 0) stmf_ilport_walk_f(wsp); return ((struct stmf_i_local_port *)wsp->walk_addr); } /*ARGSUSED*/ static int stmf_iss(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct stmf_i_local_port iport; struct stmf_i_scsi_session *issp; struct stmf_i_scsi_session iss; int i; int verbose = 0; for (i = 0; i < argc; i++) { char *ptr = (char *)argv[i].a_un.a_str; if (ptr[0] == '-') ptr++; while (*ptr) { if (*ptr == 'v') verbose = 1; ptr++; } } if (addr == 0) { mdb_warn("address of stmf_i_local_port should be specified\n"); return (DCMD_ERR); } /* * Input should be stmf_i_local_port_t. */ if (mdb_vread(&iport, sizeof (struct stmf_i_local_port), addr) != sizeof (struct stmf_i_local_port)) { mdb_warn("Unable to read in stmf_i_local_port at %p\n", addr); return (DCMD_ERR); } issp = iport.ilport_ss_list; while (issp) { if (mdb_vread(&iss, sizeof (iss), (uintptr_t)issp) == -1) { mdb_warn("failed to read stmf_i_scsi_session_t at %p", issp); return (DCMD_ERR); } mdb_printf("%p\n", issp); if (verbose) { mdb_printf(" scsi session: %p\n", iss.iss_ss); } issp = iss.iss_next; } return (DCMD_OK); } /*ARGSUSED*/ static int stmf_ilus(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct stmf_state state; struct stmf_i_lu ilu; struct stmf_i_lu *ilup; int i; int verbose = 0; for (i = 0; i < argc; i++) { char *ptr = (char *)argv[i].a_un.a_str; if (ptr[0] == '-') ptr++; while (*ptr) { if (*ptr == 'v') verbose = 1; ptr++; } } if (mdb_readsym(&state, sizeof (struct stmf_state), "stmf_state") == -1) { mdb_warn("failed to read stmf_state"); return (DCMD_ERR); } ilup = state.stmf_ilulist; while (ilup) { if (mdb_vread(&ilu, sizeof (struct stmf_i_lu), (uintptr_t)ilup) == -1) { mdb_warn("failed to read stmf_i_lu_t at %p", ilup); return (DCMD_ERR); } mdb_printf("%p\n", ilup); if (verbose) { mdb_printf(" lu: %p\n", ilu.ilu_lu); /* XXX lu_alias? what is its size? */ } ilup = ilu.ilu_next; } return (DCMD_OK); } /*ARGSUSED*/ static int stmf_i_lu_providers(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct stmf_state state; struct stmf_i_lu_provider ilp; struct stmf_i_lu_provider *ilpp; int i; int verbose = 0; for (i = 0; i < argc; i++) { char *ptr = (char *)argv[i].a_un.a_str; if (ptr[0] == '-') ptr++; while (*ptr) { if (*ptr == 'v') verbose = 1; ptr++; } } if (mdb_readsym(&state, sizeof (struct stmf_state), "stmf_state") == -1) { mdb_warn("failed to read stmf_state"); return (DCMD_ERR); } ilpp = state.stmf_ilplist; while (ilpp) { if (mdb_vread(&ilp, sizeof (stmf_i_lu_provider_t), (uintptr_t)ilpp) == -1) { mdb_warn("failed to read stmf_i_lu_provider_t at %p", ilpp); return (DCMD_ERR); } mdb_printf("%p\n", ilpp); if (verbose) { mdb_printf(" lu provider: %p\n", ilp.ilp_lp); } ilpp = ilp.ilp_next; } return (DCMD_OK); } /*ARGSUSED*/ static int stmf_i_port_providers(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct stmf_state state; struct stmf_i_port_provider ipp; struct stmf_i_port_provider *ippp; int i; int verbose = 0; for (i = 0; i < argc; i++) { char *ptr = (char *)argv[i].a_un.a_str; if (ptr[0] == '-') ptr++; while (*ptr) { if (*ptr == 'v') verbose = 1; ptr++; } } if (mdb_readsym(&state, sizeof (struct stmf_state), "stmf_state") == -1) { mdb_warn("failed to read stmf_state"); return (DCMD_ERR); } ippp = state.stmf_ipplist; while (ippp) { if (mdb_vread(&ipp, sizeof (stmf_i_port_provider_t), (uintptr_t)ippp) == -1) { mdb_warn("failed to read stmf_i_port_provider_t at %p", ippp); return (DCMD_ERR); } mdb_printf("%p\n", ippp); if (verbose) { mdb_printf(" port provider: %p\n", ipp.ipp_pp); } ippp = ipp.ipp_next; } return (DCMD_OK); } int string2wwn(const char *s, uint8_t wwn[8]); static uint16_t port_max_logins; static int rp_index; /* * Cervert stmf_i_local_port to fct_i_local_port */ /*ARGSUSED*/ static struct fct_i_local_port * __ilport2iport(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct stmf_i_local_port iport; struct stmf_local_port lport; struct fct_local_port fport; if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("stmf_i_local_port address should be specified"); return (NULL); } /* * Input should be stmf_i_local_port_t. */ if (mdb_vread(&iport, sizeof (struct stmf_i_local_port), addr) != sizeof (struct stmf_i_local_port)) { mdb_warn("Unable to read in stmf_i_local_port\n"); return (NULL); } if (mdb_vread(&lport, sizeof (stmf_local_port_t), (uintptr_t)iport.ilport_lport) != sizeof (stmf_local_port_t)) { mdb_warn("Unable to read in stmf_local_port\n"); return (NULL); } if (mdb_vread(&fport, sizeof (fct_local_port_t), (uintptr_t)lport.lport_port_private) != sizeof (fct_local_port_t)) { mdb_warn("Unable to read in fct_local_port\n"); return (NULL); } return (fport.port_fct_private); } static int ilport2iport(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct fct_i_local_port *iportp; int i; int verbose = 0; for (i = 0; i < argc; i++) { char *ptr = (char *)argv[i].a_un.a_str; if (ptr[0] == '-') ptr++; while (*ptr) { if (*ptr == 'v') verbose = 1; ptr++; } } iportp = __ilport2iport(addr, flags, argc, argv); if (iportp) { mdb_printf("%p\n", iportp); if (verbose) { struct fct_i_local_port iport; /* is the alias always 16 bytes in size ? */ char alias[16]; memset(alias, 0, sizeof (alias)); if (mdb_vread(&iport, sizeof (fct_i_local_port_t), (uintptr_t)iportp) != sizeof (fct_i_local_port_t)) { mdb_warn("Unable to read in fct_i_local_port" "at %p\n", iportp); return (DCMD_ERR); } if (iport.iport_alias && mdb_vread(alias, sizeof (alias), (uintptr_t)iport.iport_alias) != sizeof (alias)) { mdb_warn("Unable to read in memory at %p", iport.iport_alias); return (DCMD_ERR); } mdb_printf(" port: %p\n", iport.iport_port); if (iport.iport_alias) mdb_printf(" alias: %s\n", alias); } } return (DCMD_OK); } /* * by wwn, we can only find one local port */ static struct stmf_i_local_port * find_lport_by_wwn(uint8_t wwn[8]) { struct stmf_i_local_port *siport; struct fct_i_local_port *fiport; struct fct_i_local_port iport; struct fct_local_port fport; mdb_walk_state_t ws = {NULL, }; while ((siport = next_stmf_port(&ws)) != NULL) { fiport = __ilport2iport((uintptr_t)siport, DCMD_ADDRSPEC, 0, NULL); if (fiport == NULL) return (NULL); if (mdb_vread(&iport, sizeof (fct_i_local_port_t), (uintptr_t)fiport) != sizeof (fct_i_local_port_t)) { mdb_warn("Unable to read in fct_i_local_port\n"); return (NULL); } if (mdb_vread(&fport, sizeof (fct_local_port_t), (uintptr_t)iport.iport_port) != sizeof (fct_local_port_t)) { mdb_warn("Unable to read in fct_local_port\n"); return (NULL); } #if 0 mdb_printf("pwwn=%02x%02x%02x%02x%02x%02x%02x%02x\n", fport.port_pwwn[0], fport.port_pwwn[1], fport.port_pwwn[2], fport.port_pwwn[3], fport.port_pwwn[4], fport.port_pwwn[5], fport.port_pwwn[6], fport.port_pwwn[7]); #endif if (memcmp(fport.port_pwwn, wwn, 8) == 0) { return (siport); } } return (NULL); } /*ARGSUSED*/ static int stmf_find_ilport(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct find_options *options; struct stmf_i_local_port *siport; options = parse_options(argc, argv); /* need to free options manually ? */ if (options == NULL || ! options->lpname_defined) { mdb_printf("lpname= " "should be specified\n"); return (DCMD_OK); } if ((siport = find_lport_by_wwn(options->lpname)) != NULL) mdb_printf("%p\n", siport); return (DCMD_OK); } static int fct_irp_walk_i(mdb_walk_state_t *wsp) { struct fct_local_port port; struct fct_i_local_port iport; if (wsp->walk_addr == 0) { mdb_warn("Can not perform global walk"); return (WALK_ERR); } /* * Input should be fct_i_local_port_t. */ if (mdb_vread(&iport, sizeof (struct fct_i_local_port), wsp->walk_addr) != sizeof (struct fct_i_local_port)) { mdb_warn("Unable to read in fct_i_local_port\n"); return (WALK_ERR); } if (mdb_vread(&port, sizeof (struct fct_local_port), (uintptr_t)iport.iport_port) != sizeof (struct fct_local_port)) { mdb_warn("Unable to read in fct_local_port\n"); return (WALK_ERR); } port_max_logins = port.port_max_logins; rp_index = 0; wsp->walk_addr = (uintptr_t)iport.iport_rp_slots; return (WALK_NEXT); } static int fct_irp_walk_s(mdb_walk_state_t *wsp) { int status = WALK_NEXT; fct_i_remote_port_t *rp; if (wsp->walk_addr == 0) return (WALK_DONE); if (rp_index++ >= port_max_logins) return (WALK_DONE); if (mdb_vread(&rp, sizeof (fct_i_remote_port_t *), wsp->walk_addr) == -1) { mdb_warn("failed to read address of fct_i_remote_port_t at %p", wsp->walk_addr); return (WALK_DONE); } if (rp != NULL && wsp->walk_callback != NULL) status = wsp->walk_callback((uintptr_t)rp, rp, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t) &(((fct_i_remote_port_t **)wsp->walk_addr)[1]); return (status); } static void fct_irp_walk_f(mdb_walk_state_t *wsp) { wsp->walk_addr = 0; } /* * to set remote_port */ /*ARGSUSED*/ static int walk_fct_irp_cb(uintptr_t p, const void * arg, void *cbdata) { *((uintptr_t *)cbdata) = p; return (WALK_NEXT); } static int fct_irps(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { static uint64_t cbdata = 0; mdb_walk_state_t ws = {walk_fct_irp_cb, &cbdata, addr}; fct_i_remote_port_t *irpp; int i; int verbose = 0; for (i = 0; i < argc; i++) { char *ptr = (char *)argv[i].a_un.a_str; if (ptr[0] == '-') ptr++; while (*ptr) { if (*ptr == 'v') verbose = 1; ptr++; } } if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("fct_i_local_port_t address should be specified"); return (DCMD_ERR); } fct_irp_walk_i(&ws); while (fct_irp_walk_s(&ws) == WALK_NEXT) { irpp = *((fct_i_remote_port_t **)ws.walk_cbdata); if (irpp) { *((fct_i_remote_port_t **)ws.walk_cbdata) = NULL; mdb_printf("%p\n", irpp); if (verbose) { fct_i_remote_port_t irp; if (mdb_vread(&irp, sizeof (irp), (uintptr_t)irpp) != sizeof (irp)) { mdb_warn("Unable to read in " "fct_i_remote_port at %p\n", irpp); return (DCMD_ERR); } mdb_printf(" remote port: %p\n", irp.irp_rp); mdb_printf(" port id: %x\n", irp.irp_portid); } } } fct_irp_walk_f(&ws); return (DCMD_OK); } static uintptr_t cur_iport_for_irp_loop = 0; static fct_i_remote_port_t * next_rport(struct fct_i_local_port *iport) { static uint64_t cbdata = 0; static mdb_walk_state_t ws = {walk_fct_irp_cb, &cbdata}; int ret; fct_i_remote_port_t *irp; if (ws.walk_addr == 0 || cur_iport_for_irp_loop != (uintptr_t)iport) { *((fct_i_remote_port_t **)ws.walk_cbdata) = NULL; cur_iport_for_irp_loop = (uintptr_t)iport; ws.walk_addr = (uintptr_t)iport; if (fct_irp_walk_i(&ws) == WALK_ERR) { fct_irp_walk_f(&ws); return (NULL); } if (ws.walk_addr == 0) { fct_irp_walk_f(&ws); return (NULL); } } while ((ret = fct_irp_walk_s(&ws)) == WALK_NEXT) { if (*((fct_i_remote_port_t **)ws.walk_cbdata) != 0) { irp = *((fct_i_remote_port_t **)ws.walk_cbdata); *((fct_i_remote_port_t **)ws.walk_cbdata) = NULL; return (irp); } } fct_irp_walk_f(&ws); /* * If it is WALK_DONE, there may be one remote port there */ if (ret == WALK_DONE) { irp = *((fct_i_remote_port_t **)ws.walk_cbdata); *((fct_i_remote_port_t **)ws.walk_cbdata) = NULL; return (irp); } return (NULL); } static struct stmf_i_local_port * irp_to_ilport(struct fct_i_remote_port *irpp) { struct fct_i_remote_port irp; struct fct_remote_port rp; struct fct_local_port port; struct stmf_local_port lport; if (mdb_vread(&irp, sizeof (struct fct_i_remote_port), (uintptr_t)irpp) != sizeof (struct fct_i_remote_port)) { mdb_warn("Unable to read in fct_i_remote_port\n"); return (NULL); } if (mdb_vread(&rp, sizeof (struct fct_remote_port), (uintptr_t)irp.irp_rp) != sizeof (struct fct_remote_port)) { mdb_warn("Unable to read in fct_remote_port\n"); return (NULL); } if (mdb_vread(&port, sizeof (struct fct_local_port), (uintptr_t)rp.rp_port) != sizeof (struct fct_local_port)) { mdb_warn("Unable to read in fct_local_port\n"); return (NULL); } if (mdb_vread(&lport, sizeof (struct stmf_local_port), (uintptr_t)port.port_lport) != sizeof (struct stmf_local_port)) { mdb_warn("Unable to read in stmf_local_port\n"); return (NULL); } return (lport.lport_stmf_private); } /* * by wwn, we may find more than one remote port, so we need to know its * corresponding local port */ static struct fct_i_remote_port * find_irp_by_wwn(struct stmf_i_local_port *siport, uint8_t wwn[8]) { struct fct_i_local_port *fiport; fct_i_remote_port_t *irpp; struct fct_i_remote_port irp; struct fct_remote_port rp; fct_i_remote_port_t *ret = NULL; fiport = __ilport2iport((uintptr_t)siport, DCMD_ADDRSPEC, 0, NULL); if (fiport == NULL) return (NULL); while ((irpp = next_rport(fiport)) != NULL) { if (mdb_vread(&irp, sizeof (struct fct_i_remote_port), (uintptr_t)irpp) != sizeof (struct fct_i_remote_port)) { mdb_warn("Unable to read in fct_i_remote_port\n"); break; } if (mdb_vread(&rp, sizeof (struct fct_remote_port), (uintptr_t)irp.irp_rp) != sizeof (struct fct_remote_port)) { mdb_warn("Unable to read in fct_remote_port\n"); break; } if (memcmp(rp.rp_pwwn, wwn, 8) == 0) { ret = irpp; break; } } cur_iport_for_irp_loop = 0; return (ret); } /*ARGSUSED*/ static int stmf_find_fct_irp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct stmf_i_local_port *siport; struct find_options *options; fct_i_remote_port_t *irpp; mdb_walk_state_t ws = {NULL, }; options = parse_options(argc, argv); /* need to free options manually ? */ if (options == NULL || (options->rpname_defined == 0 && options->rp_defined == 0)) { mdb_printf("rpname= or rp=<3000586778734>" " should be specified\n"); return (DCMD_OK); } if (options->rpname_defined && options->rp_defined) { mdb_printf("rpname= or rp=<3000586778734>" " should be specified, but not both\n"); return (DCMD_OK); } if (options->rp_defined) { siport = irp_to_ilport(options->rp); if (siport != NULL) mdb_printf("stmf_i_local_port=%p," " fct_i_remote_port=%p\n", siport, options->rp); return (DCMD_OK); } /* if options->rpname_defined */ while ((siport = next_stmf_port(&ws)) != NULL) { if ((irpp = find_irp_by_wwn(siport, options->rpname)) != NULL) mdb_printf("stmf_i_local_port=%p, " "fct_i_remote_port=%p\n", siport, irpp); } return (DCMD_OK); } typedef void (*cmd_filter_t) (struct fct_i_cmd *, struct find_options *, void *); /*ARGSUSED*/ static void print_tasks(struct fct_i_cmd *icmdp, struct find_options *options, void *arg) { struct fct_i_cmd icmd; struct fct_cmd cmd; if (mdb_vread(&icmd, sizeof (struct fct_i_cmd), (uintptr_t)icmdp) != sizeof (struct fct_i_cmd)) { mdb_warn("Unable to read in fct_i_cmd\n"); return; } if (mdb_vread(&cmd, sizeof (struct fct_cmd), (uintptr_t)icmd.icmd_cmd) != sizeof (struct fct_cmd)) { mdb_warn("Unable to read in fct_cmd\n"); return; } if (cmd.cmd_type == FCT_CMD_FCP_XCHG) { struct scsi_task task; int colon_printed = 0; if (mdb_vread(&task, sizeof (struct scsi_task), (uintptr_t)cmd.cmd_specific) != sizeof (struct scsi_task)) { mdb_warn("Unable to read in scsi_task\n"); return; } mdb_printf("%p", cmd.cmd_specific); if (options->show_task_flags) { mdb_printf(":"); colon_printed = 1; mdb_printf(" task_flags=%x", task.task_flags); } if (options->show_lport) { if (colon_printed == 0) { mdb_printf(":"); colon_printed = 1; } mdb_printf(" lport=%p", task.task_lport); } mdb_printf("\n"); } } static void print_tasks_on_rp(struct fct_i_cmd *icmdp, struct find_options *options, void *arg) { struct fct_i_cmd icmd; struct fct_cmd cmd; fct_i_remote_port_t irp; if (mdb_vread(&icmd, sizeof (struct fct_i_cmd), (uintptr_t)icmdp) != sizeof (struct fct_i_cmd)) { mdb_warn("Unable to read in fct_i_cmd\n"); return; } if (mdb_vread(&cmd, sizeof (struct fct_cmd), (uintptr_t)icmd.icmd_cmd) != sizeof (struct fct_cmd)) { mdb_warn("Unable to read in fct_cmd\n"); return; } /* arg is a pointer to fct_i_remote_port */ if (mdb_vread(&irp, sizeof (struct fct_i_remote_port), (uintptr_t)arg) != sizeof (struct fct_i_remote_port)) { mdb_warn("Unable to read in fct_i_remote_port\n"); return; } if (cmd.cmd_type == FCT_CMD_FCP_XCHG && cmd.cmd_rp == irp.irp_rp) { struct scsi_task task; int colon_printed = 0; if (mdb_vread(&task, sizeof (struct scsi_task), (uintptr_t)cmd.cmd_specific) != sizeof (struct scsi_task)) { mdb_warn("Unable to read in scsi_task\n"); return; } mdb_printf("%p", cmd.cmd_specific); if (options->show_task_flags) { mdb_printf(":"); colon_printed = 1; mdb_printf(" task_flags=%x", task.task_flags); } if (options->show_lport) { if (colon_printed == 0) { mdb_printf(":"); colon_printed = 1; } mdb_printf(" lport=%p", task.task_lport); } mdb_printf("\n"); } } /*ARGSUSED*/ static void print_all_cmds(struct fct_i_cmd *icmd, struct find_options *options, void *arg) { mdb_printf("%p\n", icmd); } /* * find outstanding cmds (fct_i_cmd) on local port */ static int outstanding_cmds_on_lport(struct stmf_i_local_port *siport, cmd_filter_t filter, struct find_options *options, void *arg) { struct fct_i_local_port *iportp; struct fct_i_local_port iport; struct fct_local_port port; struct fct_cmd_slot *slotp; struct fct_cmd_slot slot; int i; iportp = __ilport2iport((uintptr_t)siport, DCMD_ADDRSPEC, 0, NULL); if (iportp == NULL) return (DCMD_ERR); if (mdb_vread(&iport, sizeof (struct fct_i_local_port), (uintptr_t)iportp) != sizeof (struct fct_i_local_port)) { mdb_warn("Unable to read in fct_i_local_port\n"); return (DCMD_ERR); } if (mdb_vread(&port, sizeof (struct fct_local_port), (uintptr_t)iport.iport_port) != sizeof (struct fct_local_port)) { mdb_warn("Unable to read in fct_local_port\n"); return (DCMD_ERR); } slotp = iport.iport_cmd_slots; for (i = 0; i < port.port_max_xchges; i++) { if (mdb_vread(&slot, sizeof (struct fct_cmd_slot), (uintptr_t)slotp) != sizeof (struct fct_cmd_slot)) { mdb_warn("Unable to read in fct_cmd_slot\n"); return (DCMD_ERR); } if (slot.slot_cmd != NULL) { if (filter == NULL) mdb_printf("%p\n", slot.slot_cmd); else filter(slot.slot_cmd, options, arg); } slotp ++; } return (DCMD_OK); } /*ARGSUSED*/ static int stmf_find_tasks(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct find_options *options; struct stmf_i_local_port *siport; options = parse_options(argc, argv); if (options == NULL || (options->lpname_defined == 0 && options->rpname_defined == 0)) { mdb_printf("lpname= or rpname=" " should be specified\n"); return (DCMD_OK); } if (options->lpname_defined) { siport = find_lport_by_wwn(options->lpname); if (siport == NULL) return (DCMD_ERR); outstanding_cmds_on_lport(siport, print_tasks, options, NULL); return (DCMD_OK); } if (options->rpname_defined) { mdb_walk_state_t ws = {NULL, }; fct_i_remote_port_t *irpp; while ((siport = next_stmf_port(&ws)) != NULL) { if ((irpp = find_irp_by_wwn(siport, options->rpname)) != NULL) { outstanding_cmds_on_lport(siport, print_tasks_on_rp, options, irpp); } } } return (DCMD_OK); } /*ARGSUSED*/ static int fct_find_cmds(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct find_options *options; struct stmf_i_local_port *siport; options = parse_options(argc, argv); if (options == NULL || options->lpname_defined == 0) { mdb_printf("lpname= should be specified\n"); return (DCMD_OK); } siport = find_lport_by_wwn(options->lpname); if (siport == NULL) return (DCMD_ERR); outstanding_cmds_on_lport(siport, print_all_cmds, options, NULL); return (DCMD_OK); } static int fct_icmds(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct fct_i_local_port iport; struct fct_i_cmd icmd; struct fct_i_cmd *icmdp; int i; int verbose = 0; for (i = 0; i < argc; i++) { char *ptr = (char *)argv[i].a_un.a_str; if (ptr[0] == '-') ptr++; while (*ptr) { if (*ptr == 'v') verbose = 1; ptr++; } } if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("fct_i_local_port_t address should be specified"); return (DCMD_ERR); } if (mdb_vread(&iport, sizeof (struct fct_i_local_port), addr) != sizeof (struct fct_i_local_port)) { mdb_warn("Unable to read in fct_i_local_port at %p\n", addr); return (DCMD_ERR); } icmdp = iport.iport_cached_cmdlist; while (icmdp) { if (mdb_vread(&icmd, sizeof (struct fct_i_cmd), (uintptr_t)icmdp) == -1) { mdb_warn("failed to read fct_i_cmd at %p", icmdp); return (DCMD_ERR); } mdb_printf("%p\n", icmdp); if (verbose) { mdb_printf(" fct cmd: %p\n", icmd.icmd_cmd); } icmdp = icmd.icmd_next; } return (DCMD_OK); } /* * Walker to list the addresses of all the active STMF scsi tasks (scsi_task_t), * given a stmf_worker address * * To list all the active STMF scsi tasks, use * "::walk stmf_worker |::walk stmf_scsi_task" * To list the active tasks of a particular worker, use * ::walk stmf_scsi_task */ static int stmf_scsi_task_walk_init(mdb_walk_state_t *wsp) { stmf_worker_t worker; /* * Input should be a stmf_worker, so read it to get the * worker_task_head to get the start of the task list */ if (wsp->walk_addr == 0) { mdb_warn("::walk stmf_scsi_task\n"); return (WALK_ERR); } if (mdb_vread(&worker, sizeof (stmf_worker_t), wsp->walk_addr) != sizeof (stmf_worker_t)) { mdb_warn("failed to read in the task address\n"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)(worker.worker_task_head); wsp->walk_data = mdb_alloc(sizeof (scsi_task_t), UM_SLEEP); return (WALK_NEXT); } static int stmf_scsi_task_walk_step(mdb_walk_state_t *wsp) { stmf_i_scsi_task_t itask; int status; if (wsp->walk_addr == 0) { return (WALK_DONE); } /* Save the stmf_i_scsi_task for use later to get the next entry */ if (mdb_vread(&itask, sizeof (stmf_i_scsi_task_t), wsp->walk_addr) != sizeof (stmf_i_scsi_task_t)) { mdb_warn("failed to read stmf_i_scsi_task at %p", wsp->walk_addr); return (WALK_DONE); } wsp->walk_addr = (uintptr_t)itask.itask_task; if (mdb_vread(wsp->walk_data, sizeof (scsi_task_t), wsp->walk_addr) != sizeof (scsi_task_t)) { mdb_warn("failed to read scsi_task_t at %p", wsp->walk_addr); return (DCMD_ERR); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(itask.itask_worker_next); return (status); } static void stmf_scsi_task_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (scsi_task_t)); } /*ARGSUSED*/ static int stmf_scsi_task(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { stmf_worker_t worker; stmf_i_scsi_task_t itask; scsi_task_t *task_addr, task; /* * A stmf_worker address is given to the left of ::stmf_scsi_task * i.e. display the scsi_task for the given worker */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("stmf_worker", "stmf_scsi_task", argc, argv) == -1) { mdb_warn("Failed to walk the stmf_scsi_task entries"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags) && (!(flags & DCMD_PIPE_OUT))) { mdb_printf("%%-19s %-10s %-19s%\n", "scsi_task_t", "Flags", "LPort"); } if (mdb_vread(&worker, sizeof (stmf_worker_t), addr) != sizeof (stmf_worker_t)) { mdb_warn("failed to read in the worker address"); return (DCMD_ERR); } /* Read the scsi_task */ if (worker.worker_task_head == NULL) { return (DCMD_OK); } if (mdb_vread(&itask, sizeof (stmf_i_scsi_task_t), (uintptr_t)worker.worker_task_head) == -1) { mdb_warn("failed to read stmf_i_scsi_task_t at %p", worker.worker_task_head); return (DCMD_ERR); } task_addr = itask.itask_task; if (mdb_vread(&task, sizeof (scsi_task_t), (uintptr_t)task_addr) != sizeof (scsi_task_t)) { mdb_warn("failed to read scsi_task_t at %p", task_addr); return (DCMD_ERR); } if ((flags & DCMD_PIPE_OUT)) { mdb_printf("%p\n", task_addr); } else { /* pretty print */ mdb_printf("%-19p %-10x %-19p\n", task_addr, task.task_flags, task.task_lport); } return (DCMD_OK); } /* * Walker to list the addresses of all the stmf_worker in the queue */ typedef struct stmf_worker_walk_data { int worker_current; int worker_count; } stmf_worker_walk_data_t; /* stmf_workers_state definition from stmf.c (static) */ enum { STMF_WORKERS_DISABLED = 0, STMF_WORKERS_ENABLING, STMF_WORKERS_ENABLED } stmf_workers_state; /* * Initialize the stmf_worker_t walker by either using the given starting * address, or reading the value of the kernel's global stmf_workers pointer. */ /*ARGSUSED*/ static int stmf_worker_walk_init(mdb_walk_state_t *wsp) { int worker_state; int nworkers; stmf_worker_t *worker; stmf_worker_walk_data_t *walk_data; if (mdb_readvar(&worker_state, "stmf_workers_state") == -1) { mdb_warn("failed to read stmf_workers_state"); return (WALK_ERR); } if (worker_state != STMF_WORKERS_ENABLED) { mdb_warn("stmf_workers_state not initialized"); return (WALK_ERR); } /* * Look up the stmf_nworkers_accepting_cmds to * determine number of entries in the worker queue */ if (mdb_readvar(&nworkers, "stmf_nworkers_accepting_cmds") == -1) { mdb_warn("failed to read stmf_nworkers_accepting_cmds"); return (WALK_ERR); } if (mdb_readvar(&worker, "stmf_workers") == -1) { mdb_warn("failed to read stmf_workers"); return (WALK_ERR); } walk_data = mdb_alloc(sizeof (stmf_worker_walk_data_t), UM_SLEEP); walk_data->worker_current = 0; walk_data->worker_count = nworkers; wsp->walk_addr = (uintptr_t)worker; wsp->walk_data = walk_data; return (WALK_NEXT); } static int stmf_worker_walk_step(mdb_walk_state_t *wsp) { stmf_worker_walk_data_t *walk_data = wsp->walk_data; int status; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (walk_data->worker_current >= walk_data->worker_count) { return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); walk_data->worker_current++; wsp->walk_addr += sizeof (stmf_worker_t); return (status); } static void stmf_worker_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (stmf_worker_walk_data_t)); } int stmf_worker(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { stmf_worker_t worker; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("stmf_worker", "stmf_worker", argc, argv) == -1) { mdb_warn("Failed to walk the stmf_worker entries"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_vread(&worker, sizeof (stmf_worker_t), addr) != sizeof (stmf_worker_t)) { mdb_warn("failed to read stmf_worker at %p", addr); return (DCMD_ERR); } if (flags & DCMD_PIPE_OUT) { mdb_printf("%-19p\n", addr); } else { /* pretty print */ if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-19s %-10s %-10s %-10s%\n", "stmf_worker_t", "State", "Ref_Count", "Tasks"); } mdb_printf("%-19p %-10s %-10d %-5d%\n", addr, (worker.worker_flags == STMF_WORKER_STARTED) ? "STARTED" : (worker.worker_flags & STMF_WORKER_ACTIVE) ? "ACTIVE" : "TERMINATED", worker.worker_ref_count, worker.worker_queue_depth); } return (DCMD_OK); } struct find_options * parse_options(int argc, const mdb_arg_t *argv) { int i; struct find_options *options; int len; char *ptr; int ret; if (argc == 0) return (NULL); options = mdb_zalloc(sizeof (struct find_options), UM_SLEEP); for (i = 0; i < argc; i++) { switch (argv[i].a_type) { case MDB_TYPE_STRING: break; case MDB_TYPE_IMMEDIATE: case MDB_TYPE_CHAR: mdb_printf("unknown type\n"); } if ((ptr = strchr(argv[i].a_un.a_str, '=')) == NULL) { mdb_printf("invalid argument: %s\n", argv[i].a_un.a_str); goto out; } len = ptr - argv[i].a_un.a_str; ptr++; /* point to value now */ if (len == strlen("lpname") && strncmp(argv[i].a_un.a_str, "lpname", len) == 0) { if (strstr(ptr, "wwn.") == ptr) ptr += 4; ret = string2wwn(ptr, options->lpname); if (ret == -1) goto out; #if 0 mdb_printf("wwn=%02x%02x%02x%02x%02x%02x%02x%02x\n", wwn[0], wwn[1], wwn[2], wwn[3], wwn[4], wwn[5], wwn[6], wwn[7]); #endif options->lpname_defined = 1; } else if (len == strlen("rp") && strncmp(argv[i].a_un.a_str, "rp", len) == 0) { options->rp_defined = 1; options->rp = (void *)(unsigned long)mdb_strtoull(ptr); } else if (len == strlen("rpname") && strncmp(argv[i].a_un.a_str, "rpname", len) == 0) { if (strstr(ptr, "wwn.") == ptr) ptr += 4; ret = string2wwn(ptr, options->rpname); if (ret == -1) goto out; options->rpname_defined = 1; } else if (len == strlen("show") && strncmp(argv[i].a_un.a_str, "show", len) == 0) { char *s; int l; for (;;) { s = strchr(ptr, ','); if (s) l = s - ptr; else l = strlen(ptr); if (l == strlen("task_flags") && strncmp(ptr, "task_flags", l) == 0) options->show_task_flags = 1; else if (l == strlen("lport") && strncmp(ptr, "lport", l) == 0) options->show_lport = 1; else { mdb_printf("unknown shower: %s\n", ptr); goto out; } if (s == NULL) break; ptr = s + 1; } } else { mdb_printf("unknown argument: %s\n", argv[i].a_un.a_str); goto out; } } return (options); out: mdb_free(options, sizeof (struct find_options)); return (NULL); } int string2wwn(const char *s, uint8_t wwn[8]) { int i; char tmp[17]; char *p; if (strlen(s) > 16) { mdb_printf("invalid wwn %s\n", s); return (-1); } strcpy(tmp, s); p = tmp + strlen(tmp) - 2; memset(wwn, 0, 8); /* figure out wwn from the tail to beginning */ for (i = 7; i >= 0 && p >= tmp; i--, p -= 2) { wwn[i] = mdb_strtoull(p); *p = 0; } return (0); } void fct_find_cmds_help(void) { mdb_printf( "Find all cached fct_i_cmd_t for a local port. If a local port \n" "name is specified, find all pending cmds for it and print the \n" "address. Example:\n" " fct_find_cmds lpname=\n"); } void stmf_find_ilport_help(void) { mdb_printf( "Find the fct_i_local_port if local port name is " "specified. Example:\n" " stmf_find_ilport lpname=\n"); } void stmf_find_fct_irp_help(void) { mdb_printf( "If a remote port name or stmf_i_remote_port_t address is\n" "specified, loop through all local ports, to which this remote \n" "port has logged in, print address for stmf_i_local_port_t and \n" "stmf_i_remote_port. Example:\n" " stmf_find_fct_irp rpname=\n" " stmf_find_fct_irp rp=<3000586778734>\n"); } void stmf_find_tasks_help(void) { mdb_printf( "Find all pending scsi_task_t for a given local port and/or\n" "remote port. Various different fields for each task are printed\n" "depending on what is requested. Example:\n" " stmf_find_tasks rpname=\n" " stmf_find_tasks lpname= " "show=task_flags,lport\n"); } void stmf_scsi_task_help(void) { mdb_printf( "List all active scsi_task_t on a given stmf_worker_t. Example\n" " addr::stmf_scsi_task\n"); } static const mdb_dcmd_t dcmds[] = { { "stmf_ilports", "[-v]", "Print a list of stmf_i_local_port", stmf_ilports }, { "ilport2iport", "?[-v]", "Convert stmf_i_local_port to corresponding fct_i_local_port", ilport2iport }, { "stmf_iss", "?[-v]", "List all active sessions for a given local port", stmf_iss }, { "stmf_ilus", "[-v]", "Print a list of stmf_i_lu", stmf_ilus }, { "stmf_i_lu_providers", "[-v]", "Print a list of stmf_i_lu_provider", stmf_i_lu_providers }, { "stmf_i_port_providers", "[-v]", "Print a list of stmf_i_port_provider", stmf_i_port_providers }, { "fct_irps", "?[-v]", "Print all fct_i_remote_port for a given fct_i_local_port", fct_irps }, { "fct_icmds", "?[-v]", "Print all cached fct_i_cmd_t on fct_i_local_port", fct_icmds }, { "fct_find_cmds", "lpname", "Find all fct_i_cmd_t for a given local port", fct_find_cmds, fct_find_cmds_help}, { "stmf_find_ilport", "lpname", "Find local port information based on its wwn", stmf_find_ilport, stmf_find_ilport_help}, { "stmf_find_fct_irp", "rpname|rp", "Print fct remote port information based on its wwn", stmf_find_fct_irp, stmf_find_fct_irp_help}, { "stmf_find_tasks", "lpname|rpname [show]", "Find all pending task for a local port or remote port", stmf_find_tasks, stmf_find_tasks_help}, { "stmf_worker", "?", "List all the stmf_worker entries", stmf_worker}, { "stmf_scsi_task", ":", "List all the active STMF SCSI tasks per worker", stmf_scsi_task, stmf_scsi_task_help}, { NULL } }; static const mdb_walker_t walkers[] = { { "stmf_worker", "Walk STMF worker queue", stmf_worker_walk_init, stmf_worker_walk_step, stmf_worker_walk_fini}, { "stmf_scsi_task", "Walk active STMF SCSI tasks per worker", stmf_scsi_task_walk_init, stmf_scsi_task_walk_step, stmf_scsi_task_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #define STMF_SBD_STR_MAX 2048 #define STMF_SBD_VERBOSE 0x00000001 #define ARRAY_SIZE(a) (sizeof (a) / sizeof (*a)) /* structure to pass arguments to mdb_walker callback function */ typedef struct stmf_sbd_cb_s { uint32_t flag; } stmf_sbd_cb_t; static const char *stmf_protocol_str[] = { "FIBRE_CHANNEL", /* PROTOCOL_FIBRE_CHANNEL 0 */ "PARALLEL_SCSI", /* PROTOCOL_PARALLEL_SCSI 1 */ "SSA", /* PROTOCOL_SSA 2 */ "IEEE_1394", /* PROTOCOL_IEEE_1394 3 */ "SRP", /* PROTOCOL_SRP 4 */ "iSCSI", /* PROTOCOL_iSCSI 5 */ "SAS", /* PROTOCOL_SAS 6 */ "ADT", /* PROTOCOL_ADT 7 */ "ATAPI" /* PROTOCOL_ATAPI 8 */ }; /* * Support functions. */ static uint64_t nhconvert_8bytes(const void *src) { uint64_t dest; mdb_nhconvert(&dest, src, 8); return (dest); } /* * Variable 'bits' is a collection of flags for which a corresponding * description string is available at flag_ary. * So flag_ary should be an ary of strings with total_bits strings. */ static void stmf_sbd_print_bit_flags(const char *flag_ary[], int total_bits, uint32_t bits) { uint32_t curbit = 0x01; int i, delim = 0; for (i = 0; i < total_bits; i++) { if (bits & curbit) { mdb_printf("%s%s", (delim) ? " | " : "", flag_ary[i]); delim = 1; } curbit <<= 1; } mdb_printf("\n"); } static void stmf_sbd_print_pgr_info(sbd_pgr_t *pgr) { static const char *pgr_flag_str[] = { "SBD_PGR_APTPL", /* 0x01 */ "SBD_PGR_RSVD_ONE", /* 0x02 */ "SBD_PGR_RSVD_ALL_REGISTRANTS", /* 0x04 */ "SBD_PGR_ALL_KEYS_HAS_IT" /* 0x08 */ }; static const char *pgr_type_desc[] = { "ILLEGAL", /* 0x0 */ "Write Exclusive", /* 0x1 */ "ILLEGAL", /* 0x2 */ "Exclusive Access", /* 0x3 */ "ILLEGAL", /* 0x4 */ "Write Exclusive, Registrants Only", /* 0x5 */ "Exclusive Access, Registrants Only", /* 0x6 */ "Write Exclusive, All Registrants", /* 0x7 */ "Exclusive Access, All Registrants" /* 0x8 */ }; mdb_printf("PGR flags: "); stmf_sbd_print_bit_flags(pgr_flag_str, ARRAY_SIZE(pgr_flag_str), pgr->pgr_flags); if (pgr->pgr_rsvholder || pgr->pgr_flags & SBD_PGR_RSVD_ALL_REGISTRANTS) { mdb_printf("Reservation Details \n"); mdb_printf("\tReservation holder: "); if (pgr->pgr_rsvholder) mdb_printf("%p\n", pgr->pgr_rsvholder); else mdb_printf("All Registrants\n"); mdb_printf("\t type : %d => %s\n", pgr->pgr_rsv_type, (pgr->pgr_rsv_type < ARRAY_SIZE(pgr_type_desc)) ? pgr_type_desc[pgr->pgr_rsv_type] : "ILLEGAL"); mdb_printf("\t scope : %d\n", pgr->pgr_rsv_scope); } else { mdb_printf("No reservations.\n"); } } void print_scsi_devid_desc(uintptr_t addr, uint16_t len, char *spacer) { scsi_devid_desc_t *id; if (len < sizeof (*id)) { mdb_warn("%sError: Devid Size = %d < sizeof(scsi_devid_desc_t)" "\n", spacer, len); return; } id = mdb_zalloc(len, UM_SLEEP); if (mdb_vread(id, len, addr) == -1) { mdb_warn("failed to read scsi_devid_desc at %p\n", addr); mdb_free(id, len); return; } mdb_printf("%sTotal length:\t%d\n", spacer, len); mdb_printf("%sProtocol:\t%d => %-16s\n", spacer, id->protocol_id, (id->protocol_id < ARRAY_SIZE(stmf_protocol_str)) ? stmf_protocol_str[id->protocol_id] : ""); mdb_printf("%sCode Set:\t%d\n", spacer, id->code_set); mdb_printf("%sIdent Length:\t%d\n", spacer, id->ident_length); if (len < sizeof (*id) + id->ident_length - 1) { mdb_printf("%s(Can not recognize ident data)\n", spacer); } else { id->ident[id->ident_length] = '\0'; mdb_printf("%sIdent:\t\t%s\n", spacer, id->ident); } mdb_free(id, len); mdb_printf("\n"); } /* * Decipher and print transport id which is pointed by addr variable. */ static int print_transport_id(uintptr_t addr, uint16_t tpd_len, char *spacer) { scsi_transport_id_t *tpd; if (tpd_len < sizeof (*tpd)) { mdb_warn("%sError: Transport ID Size = %d < " "sizeof (scsi_transport_id_t)\n", spacer, tpd_len); return (DCMD_ERR); } tpd = mdb_zalloc(tpd_len, UM_SLEEP); if (mdb_vread(tpd, tpd_len, addr) == -1) { mdb_warn("failed to read scsi_transport_id at %p\n", addr); mdb_free(tpd, tpd_len); return (DCMD_ERR); } mdb_printf("%sTotal length:\t%d\n", spacer, tpd_len); mdb_printf("%sProtocol:\t%d => %16s\n", spacer, tpd->protocol_id, (tpd->protocol_id < ARRAY_SIZE(stmf_protocol_str)) ? stmf_protocol_str[tpd->protocol_id] : ""); mdb_printf("%sFormat Code:\t0x%x\n", spacer, tpd->format_code); switch (tpd->protocol_id) { case PROTOCOL_FIBRE_CHANNEL: { uint8_t *p = ((scsi_fc_transport_id_t *)tpd)->port_name; mdb_printf("%sFC Port Name:\t%016llX\n", spacer, nhconvert_8bytes(p)); } break; case PROTOCOL_PARALLEL_SCSI: case PROTOCOL_SSA: case PROTOCOL_IEEE_1394: break; case PROTOCOL_SRP: { uint8_t *p = ((scsi_srp_transport_id_t *)tpd)->srp_name; /* Print 8 byte initiator extention and guid in order */ mdb_printf("%sSRP Name:\t%016llX:%016llX\n", spacer, nhconvert_8bytes(&p[8]), nhconvert_8bytes(&p[0])); } break; case PROTOCOL_iSCSI: mdb_printf("%sISCSI Name:\t%s\n", spacer, ((iscsi_transport_id_t *)tpd)->iscsi_name); break; case PROTOCOL_SAS: case PROTOCOL_ADT: case PROTOCOL_ATAPI: default: break; } mdb_free(tpd, tpd_len); return (DCMD_OK); } void stmf_sbd_pgr_key_dcmd_help(void) { mdb_printf( "Prints info about pgr keys and reservations on the given lun.\n\n" "Usage: ::stmf_sbd_pgr_key [-akv]\n" " where represent the address of\n" " sbd_lu_t by default\n" " or\n" " sbd_pgr_key_t if '-a' option is specified.\n" "Options:\n" " -a if specified, represents address of sbd_pgr_key_t\n" " -k if specified, only prints key information\n" " -v verbose output\n"); } /* * MDB WALKERS implementations */ static int stmf_sbd_lu_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { if (mdb_readvar(&wsp->walk_addr, "sbd_lu_list") == -1) { mdb_warn("failed to read sbd_lu_list\n"); return (WALK_ERR); } } return (WALK_NEXT); } static int stmf_sbd_lu_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; sbd_lu_t slu; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&slu, sizeof (sbd_lu_t), addr) == -1) { mdb_warn("failed to read sbd_lu_t at %p\n", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)slu.sl_next; return (wsp->walk_callback(addr, &slu, wsp->walk_cbdata)); } char * stmf_sbd_getstr(uintptr_t addr, char *str) { if ((addr == 0) || (mdb_readstr(str, STMF_SBD_STR_MAX, addr) == -1)) str = NULL; return (str); } static int stmf_sbd_lu_cb(uintptr_t addr, const sbd_lu_t *slu, stmf_sbd_cb_t *cb_st) { if (cb_st->flag & STMF_SBD_VERBOSE) { char str[STMF_SBD_STR_MAX]; mdb_printf("\nsbd_lu - %p\n", addr); /* sl_device_id contains 4 bytes hdr + 16 bytes(GUID) */ mdb_printf("\tsl_deviceid: %-?p GUID => %016llX%016llX\n", slu->sl_device_id, nhconvert_8bytes(&slu->sl_device_id[4]), nhconvert_8bytes(&slu->sl_device_id[12])); mdb_printf("\tsl_name: %-?p %s\n", slu->sl_name, stmf_sbd_getstr((uintptr_t)slu->sl_name, str)); mdb_printf("\tsl_alias: %-?p %s\n", slu->sl_alias, stmf_sbd_getstr((uintptr_t)slu->sl_alias, str)); mdb_printf("\tsl_meta_filename: %-?p %s\n", slu->sl_meta_filename, stmf_sbd_getstr((uintptr_t)slu->sl_meta_filename, str)); mdb_printf("\tsl_data_filename: %-?p %s\n", slu->sl_data_filename, stmf_sbd_getstr((uintptr_t)slu->sl_data_filename, str)); mdb_printf("\tsl_mgmt_url: %-?p %s\n", slu->sl_mgmt_url, stmf_sbd_getstr((uintptr_t)slu->sl_mgmt_url, str)); mdb_printf("\tsl_zfs_meta: %-?p\n", slu->sl_zfs_meta); mdb_printf("\tsl_it_list: %-?p\n", slu->sl_it_list); mdb_printf("\tsl_pgr: %-?p\n", slu->sl_pgr); } else { mdb_printf("%p\n", addr); } return (WALK_NEXT); } static int stmf_sbd_pgr_key_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("::walk stmf_sbd_pgr_key\n"); return (WALK_ERR); } return (WALK_NEXT); } static int stmf_sbd_pgr_key_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; sbd_pgr_key_t key; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&key, sizeof (sbd_pgr_key_t), addr) == -1) { mdb_warn("failed to read sbd_pgr_key_t at %p\n", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)key.pgr_key_next; return (wsp->walk_callback(addr, &key, wsp->walk_cbdata)); } static int stmf_sbd_pgr_key_cb(uintptr_t addr, const sbd_pgr_key_t *key, stmf_sbd_cb_t *cb_st) { static const char *key_flag_str [] = { "SBD_PGR_KEY_ALL_TG_PT", /* 0x01 */ "SBD_PGR_KEY_TPT_ID_FLAG" /* 0x02 */ }; if (cb_st->flag & STMF_SBD_VERBOSE) { mdb_printf("sbd_pgr_key - %p\n", addr); mdb_printf("\tRegistered key: 0x%016llx\n", key->pgr_key); mdb_printf("\tKey Flags: "); stmf_sbd_print_bit_flags(key_flag_str, ARRAY_SIZE(key_flag_str), key->pgr_key_flags); mdb_printf("\tpgr_key_it: %?-p\n", key->pgr_key_it); mdb_printf("\tLocal Device ID: %?-p\n", key->pgr_key_lpt_id); print_scsi_devid_desc((uintptr_t)key->pgr_key_lpt_id, key->pgr_key_lpt_len, " "); mdb_printf("\tRemote Transport ID: %?-p\n", key->pgr_key_rpt_id); print_transport_id((uintptr_t)key->pgr_key_rpt_id, key->pgr_key_rpt_len, " "); } else { mdb_printf("%p\n", addr); } return (WALK_NEXT); } static int stmf_sbd_it_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("::walk stmf_sbd_pgr_key\n"); return (WALK_ERR); } return (WALK_NEXT); } static int stmf_sbd_it_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; sbd_it_data_t it; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&it, sizeof (sbd_it_data_t), addr) == -1) { mdb_warn("failed to read sbd_it_data_t at %p\n", addr); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)it.sbd_it_next; return (wsp->walk_callback(addr, &it, wsp->walk_cbdata)); } static int stmf_sbd_it_cb(uintptr_t addr, const sbd_it_data_t *it, stmf_sbd_cb_t *cb_st) { static const char *it_flag_str [] = { "SBD_IT_HAS_SCSI2_RESERVATION", /* 0x0001 */ "SBD_IT_PGR_REGISTERED", /* 0x0002 */ "SBD_IT_PGR_EXCLUSIVE_RSV_HOLDER", /* 0x0004 */ "SBD_IT_PGR_CHECK_FLAG" /* 0x0008 */ }; if (cb_st->flag & STMF_SBD_VERBOSE) { mdb_printf("SBD IT DATA - %p\n", addr); mdb_printf("\tSession ID: 0x%0-lx\n", it->sbd_it_session_id); mdb_printf("\tIT Flags: "); stmf_sbd_print_bit_flags(it_flag_str, ARRAY_SIZE(it_flag_str), it->sbd_it_flags); mdb_printf("\tPGR Key: %-p\n", it->pgr_key_ptr); } else { mdb_printf("%p\n", addr); } return (WALK_NEXT); } /* * MDB DCMDS implementations. */ int stmf_sbd_lu(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE; sbd_lu_t slu; stmf_sbd_cb_t cb_st = {0}; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (verbose) cb_st.flag |= STMF_SBD_VERBOSE; if (flags & DCMD_ADDRSPEC) { cb_st.flag |= STMF_SBD_VERBOSE; if (mdb_vread(&slu, sizeof (sbd_lu_t), addr) == -1) { mdb_warn("failed to read sbd_lu_t at %p\n", addr); return (DCMD_ERR); } if (stmf_sbd_lu_cb(addr, &slu, &cb_st) == WALK_ERR) return (DCMD_ERR); } else { if (mdb_walk("stmf_sbd_lu", (mdb_walk_cb_t)stmf_sbd_lu_cb, &cb_st) == -1) { mdb_warn("failed to walk sbd_lu_list\n"); return (DCMD_ERR); } } return (DCMD_OK); } static int stmf_sbd_pgr_key(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE, keyonly = FALSE, pgrkeyaddr = FALSE; sbd_lu_t slu; sbd_pgr_t pgr; sbd_pgr_key_t key; stmf_sbd_cb_t cb_st = {0}; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &pgrkeyaddr, 'k', MDB_OPT_SETBITS, TRUE, &keyonly, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (pgrkeyaddr || verbose) cb_st.flag |= STMF_SBD_VERBOSE; /* If address of pgr_key is given, just print that key and return */ if (pgrkeyaddr) { if (mdb_vread(&key, sizeof (sbd_pgr_key_t), addr) == -1) { mdb_warn("failed to read sbd_pgr_key at %p\n", addr); return (DCMD_ERR); } if (stmf_sbd_pgr_key_cb(addr, &key, &cb_st) == WALK_ERR) { return (DCMD_ERR); } return (DCMD_OK); } else { if (mdb_vread(&slu, sizeof (sbd_lu_t), addr) == -1) { mdb_warn("failed to read sbd_lu at %p\n", addr); return (DCMD_ERR); } } if (verbose) { mdb_printf("\nLU:- %p\n", addr); } /* Just a sanity check, not necessarily needed */ if (slu.sl_pgr == NULL) { if (verbose) mdb_warn("pgr structure not found for lun %p\n", addr); return (DCMD_OK); } if (mdb_vread(&pgr, sizeof (sbd_pgr_t), (uintptr_t)slu.sl_pgr) == -1) { mdb_warn("failed to read sbd_lu at %p\n", slu.sl_pgr); return (DCMD_ERR); } if (!keyonly) stmf_sbd_print_pgr_info(&pgr); if (pgr.pgr_keylist == NULL) { if (verbose) mdb_printf("No registered pgr keys found\n"); return (DCMD_OK); } else { if (!keyonly) mdb_printf("\nKeys\n"); } if (mdb_pwalk("stmf_sbd_pgr_key", (mdb_walk_cb_t)stmf_sbd_pgr_key_cb, &cb_st, (uintptr_t)pgr.pgr_keylist) == -1) { mdb_warn("failed to walk pgr_keylist\n"); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ static int stmf_remote_port(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { stmf_remote_port_t rpt; int ret = DCMD_OK; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&rpt, sizeof (stmf_remote_port_t), addr) == -1) { mdb_warn("failed to read stmf_remote_port_t at %p\n", addr); return (DCMD_ERR); } ret = print_transport_id((uintptr_t)rpt.rport_tptid, rpt.rport_tptid_sz, " "); return (ret); } static int stmf_sbd_it(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE; sbd_lu_t slu; stmf_sbd_cb_t cb_st = {0}; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (verbose) { cb_st.flag |= STMF_SBD_VERBOSE; mdb_printf("\nLU:- %p\n", addr); } /* If address of pgr_key is given, just print that key and return */ if (mdb_vread(&slu, sizeof (sbd_lu_t), addr) == -1) { mdb_warn("failed to read sbd_lu at %p\n", addr); return (DCMD_ERR); } /* Just a sanity check, not necessarily needed */ if (slu.sl_it_list == NULL) { if (verbose) mdb_printf("sbd_it_list is empty\n", addr); return (DCMD_OK); } if (mdb_pwalk("stmf_sbd_it", (mdb_walk_cb_t)stmf_sbd_it_cb, &cb_st, (uintptr_t)slu.sl_it_list) == -1) { mdb_warn("failed to walk sbd_lu_it_list\n"); return (DCMD_ERR); } return (DCMD_OK); } /* * MDB dmcds and walkers definitions */ static const mdb_dcmd_t dcmds[] = { { "stmf_sbd_lu", "?[-v]", "Print the list of sbd_lu_t", stmf_sbd_lu, NULL }, { "stmf_sbd_it", ":[-v]", "Print the list of sbd_it_data for given lu", stmf_sbd_it, NULL }, { "stmf_sbd_pgr_key", ":[-kov]", "Print the list of pgr keys", stmf_sbd_pgr_key, stmf_sbd_pgr_key_dcmd_help }, { "stmf_remote_port", ":", "decipher info in a stmf_remote_port", stmf_remote_port, NULL }, { NULL } }; static const mdb_walker_t walkers[] = { { "stmf_sbd_lu", "walk list of stmf_sbd_lu structures", stmf_sbd_lu_walk_init, stmf_sbd_lu_walk_step, NULL }, { "stmf_sbd_pgr_key", "walk the pgr keys of the given pgr key list", stmf_sbd_pgr_key_walk_init, stmf_sbd_pgr_key_walk_step, NULL }, { "stmf_sbd_it", "walk the sbd_it_data for the given it list", stmf_sbd_it_walk_init, stmf_sbd_it_walk_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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. */ #include #include #include #include #include #include #include #include "ufs_cmds.h" typedef struct inode_walk_data { int iw_inohsz; int iw_inohcnt; uintptr_t iw_ihead; inode_t iw_inode; } inode_walk_data_t; static int inode_walk_init(mdb_walk_state_t *wsp) { int inohsz; uintptr_t ihead; union ihead ih; inode_walk_data_t *iw; if (wsp->walk_addr != 0) { mdb_warn("inode_cache only supports global walks\n"); return (WALK_ERR); } if (mdb_readvar(&inohsz, "inohsz") == -1) { mdb_warn("failed to read 'inohsz'"); return (WALK_ERR); } if (inohsz == 0) return (WALK_DONE); if (mdb_readvar(&ihead, "ihead") == -1) { mdb_warn("failed to read 'ihead'"); return (WALK_ERR); } if (mdb_vread(&ih, sizeof (union ihead), ihead) == -1) { mdb_warn("failed to read ihead at %p", ihead); return (WALK_DONE); } iw = mdb_alloc(sizeof (inode_walk_data_t), UM_SLEEP); iw->iw_inohsz = inohsz; iw->iw_inohcnt = 0; iw->iw_ihead = ihead; wsp->walk_addr = (uintptr_t)ih.ih_chain[0]; wsp->walk_data = iw; return (WALK_NEXT); } static int inode_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; inode_walk_data_t *iw = wsp->walk_data; union ihead ih; while (addr == iw->iw_ihead) { if (++iw->iw_inohcnt >= iw->iw_inohsz) return (WALK_DONE); iw->iw_ihead += sizeof (union ihead); if (mdb_vread(&ih, sizeof (union ihead), iw->iw_ihead) == -1) { mdb_warn("failed to read ihead at %p", iw->iw_ihead); return (WALK_DONE); } addr = (uintptr_t)ih.ih_chain[0]; } if (mdb_vread(&iw->iw_inode, sizeof (inode_t), addr) == -1) { mdb_warn("failed to read inode at %p", addr); return (WALK_DONE); } wsp->walk_addr = (uintptr_t)iw->iw_inode.i_forw; return (wsp->walk_callback(addr, (void *)(uintptr_t)iw->iw_inohcnt, wsp->walk_cbdata)); } static void inode_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (inode_walk_data_t)); } typedef struct inode_cbdata { ino_t id_inumber; dev_t id_device; uintptr_t id_addr; uint_t id_flags; } inode_cbdata_t; static int inode_cache_cb(uintptr_t addr, const int inohcnt, inode_cbdata_t *id) { inode_t inode; int inohsz; if (mdb_vread(&inode, sizeof (inode), addr) == -1) { mdb_warn("failed to read inode_t at %p", addr); return (WALK_ERR); } if (id->id_device != 0 && inode.i_dev != id->id_device) return (WALK_NEXT); if (id->id_inumber != 0 && inode.i_number != id->id_inumber) return (WALK_NEXT); if (id->id_flags & DCMD_ADDRSPEC && addr != id->id_addr) return (WALK_NEXT); if (id->id_flags & DCMD_PIPE_OUT) { mdb_printf("%p\n", addr); return (WALK_NEXT); } mdb_printf("%0?p %10lld %15lx", addr, (u_longlong_t)inode.i_number, inode.i_dev); /* * INOHASH needs inohsz. */ if (mdb_readvar(&inohsz, "inohsz") == -1) { mdb_warn("failed to read 'inohsz'"); return (WALK_ERR); } /* * Is the inode in the hash chain it should be? */ if (inohcnt == INOHASH(inode.i_number)) { mdb_printf(" %5d\n", inohcnt); } else { mdb_printf(" %%5d/%5d ??\n", inohcnt, INOHASH(inode.i_number)); } return (WALK_NEXT); } /*ARGSUSED*/ static int inode_cache(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { inode_cbdata_t id; id.id_inumber = 0; id.id_device = 0; id.id_addr = addr; id.id_flags = flags; if (mdb_getopts(argc, argv, 'i', MDB_OPT_UINT64, &id.id_inumber, 'd', MDB_OPT_UINTPTR, &id.id_device, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags) && (flags & DCMD_PIPE_OUT) == 0) { mdb_printf("%%-?s %10s %15s %5s%\n", "ADDR", "INUMBER", "DEVICE", "CHAIN"); } if (mdb_walk("inode_cache", (mdb_walk_cb_t)(uintptr_t)inode_cache_cb, &id) == -1) { mdb_warn("can't walk inode cache"); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ static int inode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE; inode_t inode; char buf[64]; char path[MAXPATHLEN]; static const mdb_bitmask_t i_flag_masks[] = { { "UPD", IUPD, IUPD }, { "ACC", IACC, IACC }, { "MOD", IMOD, IMOD }, { "CHG", ICHG, ICHG }, { "NOACC", INOACC, INOACC }, { "MODTIME", IMODTIME, IMODTIME }, { "REF", IREF, IREF }, { "SYNC", ISYNC, ISYNC }, { "FASTSYMLNK", IFASTSYMLNK, IFASTSYMLNK }, { "MODACC", IMODACC, IMODACC }, { "ATTCHG", IATTCHG, IATTCHG }, { "BDWRITE", IBDWRITE, IBDWRITE }, { "STALE", ISTALE, ISTALE }, { "DEL", IDEL, IDEL }, { "DIRECTIO", IDIRECTIO, IDIRECTIO }, { "JUNKIQ", IJUNKIQ, IJUNKIQ }, { NULL, 0, 0 } }; static const mdb_bitmask_t i_modetype_masks[] = { { "p", IFMT, IFIFO }, { "c", IFMT, IFCHR }, { "d", IFMT, IFDIR }, { "b", IFMT, IFBLK }, { "-", IFMT, IFREG }, { "l", IFMT, IFLNK }, { "S", IFMT, IFSHAD }, { "s", IFMT, IFSOCK }, { "A", IFMT, IFATTRDIR }, { NULL, 0, 0 } }; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags) && (flags & DCMD_PIPE_OUT) == 0) { mdb_printf("%%-?s %10s %1s %5s %8s", "ADDR", "INUMBER", "T", "MODE", "SIZE"); if (verbose) mdb_printf(" %11s %-22s%\n", "DEVICE", "FLAG"); else mdb_printf(" %-12s %-21s%\n", "MTIME", "NAME"); } if (mdb_vread(&inode, sizeof (inode), addr) == -1) { mdb_warn("failed to read inode_t at %p", addr); return (DCMD_ERR); } mdb_printf("%0?p %10lld %b %5#o %8llx", addr, (u_longlong_t)inode.i_number, inode.i_mode, i_modetype_masks, inode.i_mode & ~IFMT, inode.i_size); if (verbose) { mdb_printf(" %11lx <%b>\n", inode.i_dev, inode.i_flag, i_flag_masks); mdb_inc_indent(2); mdb_printf("%Y\n", inode.i_mtime.tv_sec); if (mdb_vnode2path((uintptr_t)inode.i_vnode, path, sizeof (path)) == 0 && *path != '\0') mdb_printf("%s\n", path); else mdb_printf("??\n"); mdb_dec_indent(2); return (DCMD_OK); } /* * Not verbose, everything must fit into one line. */ mdb_snprintf(buf, sizeof (buf), "%Y", inode.i_mtime.tv_sec); buf[17] = '\0'; /* drop seconds */ if (buf[0] == '1' || buf[0] == '2') mdb_printf(" %12s", buf + 5); /* drop year */ else mdb_printf(" %-12s", "?"); if (mdb_vnode2path((uintptr_t)inode.i_vnode, path, sizeof (path)) == 0 && *path != '\0') { if (strlen(path) <= 21) mdb_printf(" %-21s\n", path); else mdb_printf(" ...%-18s\n", path + strlen(path) - 18); } else { mdb_printf(" ??\n"); } return (DCMD_OK); } static struct { int am_offset; char *am_tag; } acl_map[] = { { offsetof(si_t, aowner), "USER_OBJ" }, { offsetof(si_t, agroup), "GROUP_OBJ" }, { offsetof(si_t, aother), "OTHER_OBJ" }, { offsetof(si_t, ausers), "USER" }, { offsetof(si_t, agroups), "GROUP" }, { offsetof(si_t, downer), "DEF_USER_OBJ" }, { offsetof(si_t, dgroup), "DEF_GROUP_OBJ" }, { offsetof(si_t, dother), "DEF_OTHER_OBJ" }, { offsetof(si_t, dusers), "DEF_USER" }, { offsetof(si_t, dgroups), "DEF_GROUP" }, { -1, NULL } }; static int acl_walk_init(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; inode_t inode; si_t *si; ufs_ic_acl_t **aclpp; if (addr == 0) { mdb_warn("acl walk needs an inode address\n"); return (WALK_ERR); } if (mdb_vread(&inode, sizeof (inode), addr) == -1) { mdb_warn("failed to read inode_t at %p", addr); return (WALK_ERR); } if (inode.i_ufs_acl == NULL) return (WALK_DONE); si = mdb_alloc(sizeof (si_t), UM_SLEEP); if (mdb_vread(si, sizeof (si_t), (uintptr_t)inode.i_ufs_acl) == -1) { mdb_warn("failed to read si_t at %p", inode.i_ufs_acl); mdb_free(si, sizeof (si_t)); return (WALK_ERR); } /* LINTED - alignment */ aclpp = (ufs_ic_acl_t **)((caddr_t)si + acl_map[0].am_offset); wsp->walk_addr = (uintptr_t)*aclpp; wsp->walk_data = si; wsp->walk_arg = 0; return (WALK_NEXT); } static int acl_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; si_t *si = wsp->walk_data; uint_t i = (uintptr_t)wsp->walk_arg; ufs_ic_acl_t **aclpp; ufs_ic_acl_t acl; while (addr == 0) { wsp->walk_arg = (void *)(uintptr_t)++i; if (acl_map[i].am_offset == -1) return (WALK_DONE); /* LINTED - alignment */ aclpp = (ufs_ic_acl_t **)((caddr_t)si + acl_map[i].am_offset); addr = (uintptr_t)*aclpp; } if (mdb_vread(&acl, sizeof (acl), addr) == -1) { mdb_warn("failed to read acl at %p", addr); return (WALK_DONE); } wsp->walk_addr = (uintptr_t)acl.acl_ic_next; return (wsp->walk_callback(addr, &acl, acl_map[i].am_tag)); } static void acl_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (si_t)); } static int acl_cb(uintptr_t addr, const void *arg, void *data) { ufs_ic_acl_t *aclp = (ufs_ic_acl_t *)arg; mdb_printf("%?p %-16s %7#o %10d\n", addr, (char *)data, aclp->acl_ic_perm, aclp->acl_ic_who); return (WALK_NEXT); } /*ARGSUSED*/ static int acl_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (argc != 0) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %-16s %7s %10s%\n", "ADDR", "TAG", "PERM", "WHO"); } if (mdb_pwalk("acl", (mdb_walk_cb_t)acl_cb, NULL, addr) == -1) { mdb_warn("can't walk acls of inode %p", addr); return (DCMD_ERR); } return (DCMD_OK); } static int cg_walk_init(mdb_walk_state_t *wsp) { if (mdb_layered_walk("buf", wsp) == -1) { mdb_warn("couldn't walk bio buf hash"); return (WALK_ERR); } return (WALK_NEXT); } static int cg_walk_step(mdb_walk_state_t *wsp) { uintptr_t addr = (uintptr_t)((const buf_t *)wsp->walk_layer)->b_un.b_cg; struct cg cg; if (mdb_vread(&cg, sizeof (cg), addr) == -1) { mdb_warn("failed to read cg struct at %p", addr); return (WALK_ERR); } if (cg.cg_magic != CG_MAGIC) return (WALK_NEXT); return (wsp->walk_callback(addr, &cg, wsp->walk_cbdata)); } static void pbits(const uchar_t *cp, const int max, const int linelen) { int i, j, len; char entry[40]; int linecnt = -1; for (i = 0; i < max; i++) { if (isset(cp, i)) { len = mdb_snprintf(entry, sizeof (entry), "%d", i); j = i; while ((i + 1) < max && isset(cp, i+1)) i++; if (i != j) len += mdb_snprintf(entry + len, sizeof (entry) - len, "-%d", i); if (linecnt == -1) { /* first entry */ mdb_printf("%s", entry); linecnt = linelen - len; } else if (linecnt - (len + 3) > 0) { /* subsequent entry on same line */ mdb_printf(", %s", entry); linecnt -= len + 2; } else { /* subsequent enty on new line */ mdb_printf(",\n%s", entry); linecnt = linelen - len; } } } mdb_printf("\n"); } /*ARGSUSED*/ static int cg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t verbose = FALSE; struct cg cg; struct cg *cgp = &cg; size_t size; int i, j, cnt, off; int32_t *blktot; short *blks; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("cg", "cg", argc, argv) == -1) { mdb_warn("can't walk cylinder group structs"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(cgp, sizeof (cg), addr) == -1) { mdb_warn("failed to read cg struct at %p", addr); return (DCMD_ERR); } if (!verbose) { if (DCMD_HDRSPEC(flags)) mdb_printf("%%4s %?s %10s %10s %10s %10s%\n", "CGX", "CG", "NDIR", "NBFREE", "NIFREE", "NFFREE"); mdb_printf("%4d %?p %10d %10d %10d %10d\n", cgp->cg_cgx, addr, cgp->cg_cs.cs_ndir, cgp->cg_cs.cs_nbfree, cgp->cg_cs.cs_nifree, cgp->cg_cs.cs_nffree); return (DCMD_OK); } /* * Verbose: produce output similiar to "fstyp -v". */ if (cgp->cg_btotoff >= cgp->cg_nextfreeoff || cgp->cg_boff >= cgp->cg_nextfreeoff || cgp->cg_iusedoff >= cgp->cg_nextfreeoff || cgp->cg_freeoff >= cgp->cg_nextfreeoff) { mdb_warn("struct cg at %p seems broken\n", addr); return (DCMD_ERR); } size = cgp->cg_nextfreeoff; cgp = mdb_alloc(size, UM_SLEEP); if (mdb_vread(cgp, size, addr) == -1) { mdb_warn("failed to read struct cg and maps at %p", addr); mdb_free(cgp, size); return (DCMD_ERR); } mdb_printf("%cg %d (%0?p)%\n", cgp->cg_cgx, addr); mdb_inc_indent(4); mdb_printf("time:\t%Y\n", cgp->cg_time); mdb_printf("ndir:\t%d\n", cgp->cg_cs.cs_ndir); mdb_printf("nbfree:\t%d\n", cgp->cg_cs.cs_nbfree); mdb_printf("nifree:\t%d\n", cgp->cg_cs.cs_nifree); mdb_printf("nffree:\t%d\n", cgp->cg_cs.cs_nffree); mdb_printf("frsum:"); for (i = 1; i < MAXFRAG; i++) mdb_printf("\t%d", cgp->cg_frsum[i]); mdb_printf("\n"); off = cgp->cg_iusedoff; mdb_printf("used inode map (%0?p):\n", (char *)addr + off); mdb_inc_indent(4); pbits((uchar_t *)cgp + off, cgp->cg_niblk / sizeof (char), 72); mdb_dec_indent(4); off = cgp->cg_freeoff; mdb_printf("free block map (%0?p):\n", (char *)addr + off); mdb_inc_indent(4); pbits((uchar_t *)cgp + off, cgp->cg_ndblk / sizeof (char), 72); mdb_dec_indent(4); /* LINTED - alignment */ blktot = (int32_t *)((char *)cgp + cgp->cg_btotoff); /* LINTED - alignment */ blks = (short *)((char *)cgp + cgp->cg_boff); cnt = (cgp->cg_iusedoff - cgp->cg_boff) / cgp->cg_ncyl / sizeof (short); mdb_printf("free block positions:\n"); mdb_inc_indent(4); for (i = 0; i < cgp->cg_ncyl; i++) { mdb_printf("c%d:\t(%d)\t", i, blktot[i]); for (j = 0; j < cnt; j++) mdb_printf(" %d", blks[i*cnt + j]); mdb_printf("\n"); } mdb_dec_indent(4); mdb_printf("\n"); mdb_dec_indent(4); mdb_free(cgp, size); return (DCMD_OK); } void inode_cache_help(void) { mdb_printf( "Displays cached inode_t. If an address, an inode number and/or a\n" "device is specified, searches inode cache for inodes which match\n" "the specified criteria. Prints nothing but the address, if\n" "output is a pipe.\n" "\n" "Options:\n" " -d device Filter out inodes, which reside on the specified" " device.\n" " -i inumber Filter out inodes with the specified inode" " number.\n"); } /* * MDB module linkage */ static const mdb_dcmd_t dcmds[] = { { "inode_cache", "?[-d device] [-i inumber]", "search/display inodes from inode cache", inode_cache, inode_cache_help }, { "inode", ":[-v]", "display summarized inode_t", inode }, { "acl", ":", "given an inode, display its in core acl's", acl_dcmd }, { "cg", "?[-v]", "display a summarized cylinder group structure", cg }, { "mapentry", ":", "dumps ufslog mapentry", mapentry_dcmd }, { "mapstats", ":", "dumps ufslog stats", mapstats_dcmd }, { NULL } }; static const mdb_walker_t walkers[] = { { "inode_cache", "walk inode cache", inode_walk_init, inode_walk_step, inode_walk_fini }, { "acl", "given an inode, walk chains of in core acl's", acl_walk_init, acl_walk_step, acl_walk_fini }, { "cg", "walk cg's in bio buffer cache", cg_walk_init, cg_walk_step, NULL }, { "ufslogmap", "walk map entries in a ufs_log mt_map", ufslogmap_walk_init, ufslogmap_walk_step, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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. */ #ifndef _UFS_CMDS_H #define _UFS_CMDS_H #ifdef __cplusplus extern "C" { #endif extern int mapentry_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int mapstats_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); extern int ufslogmap_walk_init(mdb_walk_state_t *); extern int ufslogmap_walk_step(mdb_walk_state_t *); #ifdef __cplusplus } #endif #endif /* _UFS_CMDS_H */ /* * 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. */ #include #include #include #include #include #include "ufs_cmds.h" typedef struct ufslogmap_walk_data { mapentry_t me; mapentry_t *start_addr; mapentry_t *prev_addr; } ufslogmap_walk_data_t; /* * Ensure we are started with a user specified address. * We also allocate a ufslogmap_walk_data_t for storage, * and save this using the walk_data pointer. */ int ufslogmap_walk_init(mdb_walk_state_t *wsp) { ufslogmap_walk_data_t *uw; if (wsp->walk_addr == 0) { mdb_warn("must specify an address\n"); return (WALK_ERR); } uw = mdb_zalloc(sizeof (ufslogmap_walk_data_t), UM_SLEEP | UM_GC); uw->start_addr = (mapentry_t *)wsp->walk_addr; wsp->walk_data = uw; return (WALK_NEXT); } /* * Routine to step through one element of the list. */ int ufslogmap_walk_step(mdb_walk_state_t *wsp) { ufslogmap_walk_data_t *uw = wsp->walk_data; uintptr_t walk_addr = wsp->walk_addr; /* * Read the mapentry at the current walk address */ if (mdb_vread(&uw->me, sizeof (mapentry_t), walk_addr) == -1) { mdb_warn("failed to read mapentry_t at %p", walk_addr); return (WALK_DONE); } /* * Check for empty list. */ if (uw->me.me_next == uw->me.me_prev) { return (WALK_DONE); } /* * Check for end of list. */ if (uw->me.me_next == uw->start_addr) { return (WALK_DONE); } /* * Check for proper linkage */ if (uw->prev_addr && (uw->me.me_prev != uw->prev_addr)) { mdb_warn("invalid linkage mapentry_t at %p", walk_addr); return (WALK_DONE); } uw->prev_addr = (mapentry_t *)walk_addr; /* * Save next address and call callback with current address */ wsp->walk_addr = (uintptr_t)uw->me.me_next; return (wsp->walk_callback(walk_addr, wsp->walk_data, wsp->walk_cbdata)); } static const char * delta2str(delta_t delta_type) { switch (delta_type) { case DT_NONE: return ("none"); case DT_SB: return ("sb"); case DT_CG: return ("cg"); case DT_SI: return ("si"); case DT_AB: return ("ab"); case DT_ABZERO: return ("abzero"); case DT_DIR: return ("dir"); case DT_INODE: return ("inode"); case DT_FBI: return ("fbi"); case DT_QR: return ("quota"); case DT_COMMIT: return ("commit"); case DT_CANCEL: return ("cancel"); case DT_BOT: return ("trans"); case DT_EOT: return ("etrans"); case DT_UD: return ("udata"); case DT_SUD: return ("sudata"); case DT_SHAD: return ("shadow"); default: return ("???"); } } /* ARGSUSED */ int mapentry_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mapentry_t me; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %6s %8s %8s %s%\n", "ADDR", "TYPE", "SIZE", "TRANS", "HANDLER"); } if (mdb_vread(&me, sizeof (me), addr) == -1) { mdb_warn("couldn't read ufslog mapentry at %p", addr); return (DCMD_ABORT); } /* * Validate mapentry */ if (me.me_delta.d_typ >= DT_MAX) { mdb_warn("Invalid delta type for mapentry at %p", addr); return (DCMD_ABORT); } mdb_printf("%0?p %6s %8x %8x %a\n", addr, delta2str(me.me_delta.d_typ), me.me_delta.d_nb, me.me_tid, me.me_func); return (DCMD_OK); } typedef struct { uint64_t nentries; /* number of mapentries */ uint64_t totalsize; /* total number of bytes */ uint32_t transid; /* first transaction id */ int transdiff; /* transaction different */ uint32_t delta_cnt[DT_MAX]; /* count of each delta */ uint64_t delta_sum[DT_MAX]; /* total number of bytes for delta */ } mapstats_t; /* ARGSUSED */ int mapadd(uintptr_t *addr, ufslogmap_walk_data_t *uw, mapstats_t *msp) { if (msp->nentries == 0) { msp->transid = uw->me.me_tid; } else { if (msp->transid != uw->me.me_tid) { msp->transdiff = TRUE; } } msp->nentries++; msp->totalsize += uw->me.me_nb; if (uw->me.me_dt >= DT_MAX) { mdb_warn("Invalid delta type for mapentry at %p", addr); } else { msp->delta_cnt[uw->me.me_dt]++; msp->delta_sum[uw->me.me_dt] += uw->me.me_nb; } return (WALK_NEXT); } /*ARGSUSED*/ int mapstats_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mapstats_t *msp; int i; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); msp = mdb_zalloc(sizeof (mapstats_t), UM_SLEEP | UM_GC); msp->transdiff = FALSE; if (mdb_pwalk("ufslogmap", (mdb_walk_cb_t)(uintptr_t)mapadd, msp, addr) == -1) { mdb_warn("can't walk ufslogmap for stats"); return (DCMD_ERR); } mdb_printf("Number of entries 0x%llx\n", msp->nentries); mdb_printf("Total map size 0x%llx\n", msp->totalsize); if (msp->transdiff) { mdb_printf("Multiple transactions\n"); } else { mdb_printf("All the same transaction id = %d\n", msp->transid); } if (msp->nentries) { mdb_printf("%delta count(hex) avsize(hex)%\n"); for (i = 0; i < DT_MAX; i++) { if (msp->delta_cnt[i]) { mdb_printf("%6s %10X %10X\n", delta2str(i), msp->delta_cnt[i], msp->delta_sum[i] / msp->delta_cnt[i]); } } } return (DCMD_OK); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #define UHCI_TD 0 #define UHCI_QH 1 /* Prototypes */ int uhci_td(uintptr_t, uint_t, int, const mdb_arg_t *); int uhci_qh(uintptr_t, uint_t, int, const mdb_arg_t *); int uhci_td_walk_init(mdb_walk_state_t *); int uhci_td_walk_step(mdb_walk_state_t *); int uhci_qh_walk_init(mdb_walk_state_t *); int uhci_qh_walk_step(mdb_walk_state_t *); /* * Callback for find_uhci_statep (called back from walk "softstate" in * find_uhci_statep). * * - uhci_instancep is the value of the current pointer in the array of soft * state instance pointers (see i_ddi_soft_state in ddi_impldefs.h) * - local_ss is a pointer to the copy of the i_ddi_soft_state in local space * - cb_arg is a pointer to the cb arg (an instance of state_find_data). * * For the current uchi_state_t*, see if the td address is in its pool. * * Returns WALK_NEXT on success (match not found yet), WALK_ERR on errors. * * WALK_DONE is returned, cb_data.found is set to TRUE, and * *cb_data.fic_uhci_statep is filled in with the contents of the state * struct in core. This forces the walk to terminate. */ typedef struct find_instance_struct { void *fic_td_qh; /* td/qh we want uhci instance for */ boolean_t fic_td_or_qh; /* which one td_qh points to */ boolean_t fic_found; uhci_state_t *fic_uhci_statep; /* buffer uhci_state's written into */ } find_instance_cb_t; /*ARGSUSED*/ static int find_uhci_instance(uintptr_t uhci_instancep, const void *local_ss, void *cb_arg) { int td_pool_size, qh_pool_size; find_instance_cb_t *cb_data = (find_instance_cb_t *)cb_arg; uhci_state_t *uhcip = cb_data->fic_uhci_statep; if (mdb_vread(cb_data->fic_uhci_statep, sizeof (uhci_state_t), uhci_instancep) == -1) { mdb_warn("failed to read uhci_state at %p", uhci_instancep); return (-1); } if (mdb_readsym(&td_pool_size, sizeof (int), "uhci_td_pool_size") == -1) { mdb_warn("failed to read uhci_td_pool_size"); return (-1); } if (mdb_readsym(&qh_pool_size, sizeof (int), "uhci_qh_pool_size") == -1) { mdb_warn("failed to read uhci_td_pool_size"); return (-1); } /* * See if the addr is within the appropriate pool for this instance. */ if ((cb_data->fic_td_or_qh == UHCI_TD && ((uhci_td_t *)cb_data->fic_td_qh >= uhcip->uhci_td_pool_addr && (uhci_td_t *)cb_data->fic_td_qh <= (uhcip->uhci_td_pool_addr + td_pool_size - sizeof (uhci_td_t)))) || (cb_data->fic_td_or_qh == UHCI_QH && ((queue_head_t *)cb_data->fic_td_qh >= uhcip->uhci_qh_pool_addr && (queue_head_t *)cb_data->fic_td_qh <= (uhcip->uhci_qh_pool_addr + qh_pool_size - sizeof (queue_head_t))))) { /* td/qh address is within pool for this instance of uhci. */ cb_data->fic_found = TRUE; return (WALK_DONE); } return (WALK_NEXT); } /* * Figure out which instance of uhci owns a td/qh. * * - td_qh: a pointer to a uhci td or qh * - td_or_qh: a flag indicating which it is (td/qh), * - uhci_statep, pointer to a uhci_state_t, to be filled in with data from * the found instance of uhci_state_t. * * Only works for Cntl/Interrupt tds/qhs; others are dynamically allocated * and so cannot be found with this method. * * Returns 0 on success (no match found), 1 on success (match found), * -1 on errors. */ static int find_uhci_statep(void *td_qh, boolean_t td_or_qh, uhci_state_t *uhci_statep) { find_instance_cb_t cb_data; uintptr_t uhci_ss; if (uhci_statep == NULL) { mdb_warn("failed to find uhci statep: " "NULL uhci_statep param\n"); return (-1); } cb_data.fic_td_qh = td_qh; cb_data.fic_td_or_qh = td_or_qh; cb_data.fic_found = FALSE; cb_data.fic_uhci_statep = uhci_statep; if (mdb_readsym(&uhci_ss, sizeof (uhci_statep), "uhci_statep") == -1) { mdb_warn("failed to read uhci_statep"); return (-1); } /* * Walk all instances of uhci. * The callback func checks if td_qh belongs to a given instance * of uhci. */ if (mdb_pwalk("softstate", find_uhci_instance, &cb_data, uhci_ss) != 0) { mdb_warn("failed to walk softstate"); return (-1); } if (cb_data.fic_found == TRUE) { return (1); } return (0); } /* * Dump a UHCI TD (transaction descriptor); * or (-d) the chain of TDs starting with the one specified. */ int uhci_td(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t depth_flag = FALSE; uhci_state_t uhci_state, *uhcip = &uhci_state; uhci_td_t td; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (addr & ~QH_LINK_PTR_MASK) { mdb_warn("address must be on a 16-byte boundary.\n"); return (DCMD_ERR); } if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, TRUE, &depth_flag, NULL) != argc) { return (DCMD_USAGE); } if (depth_flag) { if (mdb_pwalk_dcmd("uhci_td", "uhci_td", 0, NULL, addr) == -1) { mdb_warn("failed to walk 'uhci_td'"); return (DCMD_ERR); } return (DCMD_OK); } if (find_uhci_statep((void *)addr, UHCI_TD, uhcip) != 1) { mdb_warn("failed to find uhci_statep"); return (DCMD_ERR); } if (mdb_vread(&td, sizeof (td), addr) != sizeof (td)) { mdb_warn("failed to read td at vaddr %p", addr); return (DCMD_ERR); } mdb_printf("\n UHCI td struct at (vaddr) %08x:\n", addr); if (!(td.link_ptr & HC_END_OF_LIST) && td.link_ptr != 0) { mdb_printf(" link_ptr (paddr) : %-8x " "(vaddr) : %p\n", td.link_ptr, /* Note: uhcip needed by TD_VADDR macro */ TD_VADDR(td.link_ptr & QH_LINK_PTR_MASK)); } else { mdb_printf(" link_ptr (paddr) : %-8x\n", td.link_ptr); } mdb_printf(" td_dword2 : %08x\n", td.dw2); mdb_printf(" td_dword3 : %08x\n", td.dw3); mdb_printf(" buffer_address : %08x\n", td.buffer_address); mdb_printf(" qh_td_prev : %?p " "tw_td_next : %?p\n", td.qh_td_prev, td.tw_td_next); mdb_printf(" outst_td_prev : %?p " "outst_td_next : %?p\n", td.outst_td_prev, td.outst_td_next); mdb_printf(" tw : %?p " "flag : %02x\n", td.tw, td.flag); mdb_printf(" isoc_next : %?p " "isoc_prev : %0x\n", td.isoc_next, td.isoc_prev); mdb_printf(" isoc_pkt_index : %0x " "startingframe: %0x\n", td.isoc_pkt_index, td.starting_frame); if (td.link_ptr == 0) { mdb_printf(" --> Link pointer = NULL\n"); return (DCMD_ERR); } else { /* Inform user if link is to a TD or QH. */ if (td.link_ptr & HC_END_OF_LIST) { mdb_printf(" " "--> Link pointer invalid (terminate bit set).\n"); } else { if ((td.link_ptr & HC_QUEUE_HEAD) == HC_QUEUE_HEAD) { mdb_printf(" " "--> Link pointer points to a QH.\n"); } else { mdb_printf(" " "--> Link pointer points to a TD.\n"); } } } return (DCMD_OK); } /* * Dump a UHCI QH (queue head). * -b walk/dump the chian of QHs starting with the one specified. * -d also dump the chain of TDs starting with the one specified. */ int uhci_qh(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t breadth_flag = FALSE, depth_flag = FALSE; uhci_state_t uhci_state, *uhcip = &uhci_state; queue_head_t qh; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (addr & ~QH_LINK_PTR_MASK) { mdb_warn("address must be on a 16-byte boundary.\n"); return (DCMD_ERR); } if (mdb_getopts(argc, argv, 'b', MDB_OPT_SETBITS, TRUE, &breadth_flag, 'd', MDB_OPT_SETBITS, TRUE, &depth_flag, NULL) != argc) { return (DCMD_USAGE); } if (breadth_flag) { uint_t new_argc = 0; mdb_arg_t new_argv[1]; if (depth_flag) { new_argc = 1; new_argv[0].a_type = MDB_TYPE_STRING; new_argv[0].a_un.a_str = "-d"; } if ((mdb_pwalk_dcmd("uhci_qh", "uhci_qh", new_argc, new_argv, addr)) != 0) { mdb_warn("failed to walk 'uhci_qh'"); return (DCMD_ERR); } return (DCMD_OK); } if (find_uhci_statep((void *)addr, UHCI_QH, uhcip) != 1) { mdb_warn("failed to find uhci_statep"); return (DCMD_ERR); } if (mdb_vread(&qh, sizeof (qh), addr) != sizeof (qh)) { mdb_warn("failed to read qh at vaddr %p", addr); return (DCMD_ERR); } mdb_printf("\n UHCI qh struct at (vaddr) %08x:\n", addr); if (!(qh.link_ptr & HC_END_OF_LIST) && qh.link_ptr != 0) { mdb_printf(" link_ptr (paddr) : %08x " "(vaddr) : %p\n", qh.link_ptr, /* Note: uhcip needed by QH_VADDR macro */ QH_VADDR(qh.link_ptr & QH_LINK_PTR_MASK)); } else { mdb_printf( " link_ptr (paddr) : %08x\n", qh.link_ptr); } if (!(qh.element_ptr & HC_END_OF_LIST) && qh.element_ptr != 0) { mdb_printf(" element_ptr (paddr) : %08x " "(vaddr) : %p\n", qh.element_ptr, /* Note: uhcip needed by TD_VADDR macro */ TD_VADDR(qh.element_ptr & QH_LINK_PTR_MASK)); } else { mdb_printf( " element_ptr (paddr) : %08x\n", qh.element_ptr); } mdb_printf(" node : %04x " "flag : %04x\n", qh.node, qh.qh_flag); mdb_printf(" prev_qh : %?p " "td_tailp : %?p\n", qh.prev_qh, qh.td_tailp); mdb_printf(" bulk_xfer_isoc_info : %?p\n", qh.bulk_xfer_info); if (qh.link_ptr == 0) { mdb_printf(" --> Link pointer = NULL\n"); return (DCMD_ERR); } else { /* Inform user if next link is a TD or QH. */ if (qh.link_ptr & HC_END_OF_LIST) { mdb_printf(" " "--> Link pointer invalid (terminate bit set).\n"); } else { if ((qh.link_ptr & HC_QUEUE_HEAD) == HC_QUEUE_HEAD) { mdb_printf(" " "--> Link pointer points to a QH.\n"); } else { /* Should never happen. */ mdb_warn(" " "--> Link pointer points to a TD.\n"); return (DCMD_ERR); } } } if (qh.element_ptr == 0) { mdb_printf(" element_ptr = NULL\n"); return (DCMD_ERR); } else { /* Inform user if next element is a TD or QH. */ if (qh.element_ptr & HC_END_OF_LIST) { mdb_printf(" " "-->Element pointer invalid (terminate bit set)." "\n"); return (DCMD_OK); } else { if ((qh.element_ptr & HC_QUEUE_HEAD) == HC_QUEUE_HEAD) { mdb_printf(" " "--> Element pointer points to a QH.\n"); /* Should never happen in UHCI implementation */ return (DCMD_ERR); } else { mdb_printf(" " "--> Element pointer points to a TD.\n"); } } } /* * If the user specified the -d (depth) option, * dump all TDs linked to this TD via the element_ptr. */ if (depth_flag) { /* Traverse and display all the TDs in the chain */ if (mdb_pwalk_dcmd("uhci_td", "uhci_td", argc, argv, (uintptr_t)(TD_VADDR(qh.element_ptr & QH_LINK_PTR_MASK))) == -1) { mdb_warn("failed to walk 'uhci_td'"); return (DCMD_ERR); } } return (DCMD_OK); } /* * Walk a list of UHCI Transaction Descriptors (td's). * Stop at the end of the list, or if the next element in the list is a * queue head (qh). * User must specify the address of the first td to look at. */ int uhci_td_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { return (DCMD_USAGE); } wsp->walk_data = mdb_alloc(sizeof (uhci_td_t), UM_SLEEP | UM_GC); wsp->walk_arg = mdb_alloc(sizeof (uhci_state_t), UM_SLEEP | UM_GC); /* * Read the uhci_state_t for the instance of uhci * using this td address into buf pointed to by walk_arg. */ if (find_uhci_statep((void *)wsp->walk_addr, UHCI_TD, wsp->walk_arg) != 1) { mdb_warn("failed to find uhci_statep"); return (WALK_ERR); } return (WALK_NEXT); } /* * At each step, read a TD into our private storage, and then invoke * the callback function. We terminate when we reach a QH, or * link_ptr is NULL. */ int uhci_td_walk_step(mdb_walk_state_t *wsp) { int status; uhci_state_t *uhcip = (uhci_state_t *)wsp->walk_arg; if (mdb_vread(wsp->walk_data, sizeof (uhci_td_t), wsp->walk_addr) == -1) { mdb_warn("failed to read td at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); /* Next td. */ wsp->walk_addr = ((uhci_td_t *)wsp->walk_data)->link_ptr; /* Check if we're at the last element */ if (wsp->walk_addr == 0 || wsp->walk_addr & HC_END_OF_LIST) return (WALK_DONE); /* Make sure next element is a TD. If a QH, stop. */ if (((((uhci_td_t *)wsp->walk_data)->link_ptr) & HC_QUEUE_HEAD) == HC_QUEUE_HEAD) { return (WALK_DONE); } /* Strip terminate etc. bits. */ wsp->walk_addr &= QH_LINK_PTR_MASK; /* there is no TD_LINK_PTR_MASK */ if (wsp->walk_addr == 0) return (WALK_DONE); /* * Convert link_ptr paddr to vaddr * Note: uhcip needed by TD_VADDR macro */ wsp->walk_addr = (uintptr_t)TD_VADDR(wsp->walk_addr); return (status); } /* * Walk a list of UHCI Queue Heads (qh's). * Stop at the end of the list, or if the next element in the list is a * Transaction Descriptor (td). * User must specify the address of the first qh to look at. */ int uhci_qh_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) return (DCMD_USAGE); wsp->walk_data = mdb_alloc(sizeof (queue_head_t), UM_SLEEP | UM_GC); wsp->walk_arg = mdb_alloc(sizeof (uhci_state_t), UM_SLEEP | UM_GC); /* * Read the uhci_state_t for the instance of uhci * using this td address into buf pointed to by walk_arg. */ if (find_uhci_statep((void *)wsp->walk_addr, UHCI_QH, (uhci_state_t *)wsp->walk_arg) != 1) { mdb_warn("failed to find uhci_statep"); return (WALK_ERR); } return (WALK_NEXT); } /* * At each step, read a QH into our private storage, and then invoke * the callback function. We terminate when we reach a QH, or * link_ptr is NULL. */ int uhci_qh_walk_step(mdb_walk_state_t *wsp) { int status; uhci_state_t *uhcip = (uhci_state_t *)wsp->walk_arg; if (wsp->walk_addr == 0) /* Should never occur */ return (WALK_DONE); if (mdb_vread(wsp->walk_data, sizeof (queue_head_t), wsp->walk_addr) == -1) { mdb_warn("failure reading qh at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); /* Next QH. */ wsp->walk_addr = ((queue_head_t *)wsp->walk_data)->link_ptr; /* Check if we're at the last element */ if (wsp->walk_addr == 0 || wsp->walk_addr & HC_END_OF_LIST) { return (WALK_DONE); } /* Make sure next element is a QH. If a TD, stop. */ if (((((queue_head_t *)wsp->walk_data)->link_ptr) & HC_QUEUE_HEAD) != HC_QUEUE_HEAD) { return (WALK_DONE); } /* Strip terminate etc. bits. */ wsp->walk_addr &= QH_LINK_PTR_MASK; if (wsp->walk_addr == 0) return (WALK_DONE); /* * Convert link_ptr paddr to vaddr * Note: uhcip needed by QH_VADDR macro */ wsp->walk_addr = (uintptr_t)QH_VADDR(wsp->walk_addr); return (status); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, and a function * named _mdb_init to return a pointer to our module information. */ static const mdb_dcmd_t dcmds[] = { { "uhci_td", ": [-d]", "print UHCI TD", uhci_td, NULL }, { "uhci_qh", ": [-bd]", "print UHCI QH", uhci_qh, NULL}, { NULL } }; static const mdb_walker_t walkers[] = { { "uhci_td", "walk list of UHCI TD structures", uhci_td_walk_init, uhci_td_walk_step, NULL, NULL }, { "uhci_qh", "walk list of UHCI QH structures", uhci_qh_walk_init, uhci_qh_walk_step, NULL, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include /* ****************************************************************** */ /* extenal definition */ typedef struct mdb_ctf_id { void *_opaque[2]; } mdb_ctf_id_t; extern int mdb_ctf_lookup_by_name(const char *, mdb_ctf_id_t *); extern int mdb_devinfo2driver(uintptr_t, char *, size_t); extern int mdb_devinfo2statep(uintptr_t, char *, uintptr_t *); extern char *mdb_ddi_pathname(uintptr_t, char *, size_t); /* ****************************************************************** */ /* internal definition */ #define OPT_TREE 0x01 #define OPT_VERB 0x02 #define STRLEN 256 #define BYTE_OFFSET 8 typedef struct usb_descr_item { uint_t nlen; /* if it's an byte array, nlen += BYTE_OFFSET */ char *name; /* descriptor item name */ } usb_descr_item_t; /* define the known descriptor items */ static usb_descr_item_t usb_cfg_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {2, "wTotalLength"}, {1, "bNumInterfaces"}, {1, "bConfigurationValue"}, {1, "iConfiguration"}, {1, "bmAttributes"}, {1, "bMaxPower"}, }; static uint_t usb_cfg_item = 8; static usb_descr_item_t usb_ia_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bFirstInterface"}, {1, "bInterfaceCount"}, {1, "bFunctionClass"}, {1, "bFunctionSubClass"}, {1, "bFunctionProtocol"}, {1, "iFunction"}, }; static uint_t usb_ia_item = 8; static usb_descr_item_t usb_if_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bInterfaceNumber"}, {1, "bAlternateSetting"}, {1, "bNumEndpoints"}, {1, "bInterfaceClass"}, {1, "bInterfaceSubClass"}, {1, "bInterfaceProtocol"}, {1, "iInterface"}, }; static uint_t usb_if_item = 9; static usb_descr_item_t usb_ep_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bEndpointAddress"}, {1, "bmAttributes"}, {2, "wMaxPacketSize"}, {1, "bInterval"}, }; static uint_t usb_ep_item = 6; static usb_descr_item_t usb_ep_ss_comp_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bMaxBurst"}, {1, "bmAttributes"}, {2, "wBytesPerInterval"} }; static uint_t usb_ep_ss_comp_item = 5; static usb_descr_item_t usb_qlf_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {2, "bcdUSB"}, {1, "bDeviceClass"}, {1, "bDeviceSubClass"}, {1, "bDeviceProtocol"}, {1, "bMaxPacketSize0"}, {1, "bNumConfigurations"}, {1, "bReserved"}, }; static uint_t usb_qlf_item = 9; static usb_descr_item_t usb_str_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bString"}, }; static uint_t usb_str_item = 3; static usb_descr_item_t usb_wa_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {2, "bcdWAVersion"}, {1, "bNumPorts"}, {1, "bmAttributes"}, {2, "wNumRPipes"}, {2, "wRPipeMaxBlock"}, {1, "bRPipeBlockSize"}, {1, "bPwrOn2PwrGood"}, {1, "bNumMMCIEs"}, {1, "DeviceRemovable"}, }; static uint_t usb_wa_item = 11; static usb_descr_item_t usb_hid_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {2, "bcdHID"}, {1, "bCountryCode"}, {1, "bNumDescriptors"}, {1, "bReportDescriptorType"}, {2, "wReportDescriptorLength"}, }; static uint_t usb_hid_item = 7; static usb_descr_item_t usb_ac_header_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {2, "bcdADC"}, {2, "wTotalLength"}, {1, "blnCollection"}, {1, "baInterfaceNr"}, }; static uint_t usb_ac_header_item = 7; static usb_descr_item_t usb_ac_input_term_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bTerminalID"}, {2, "wTerminalType"}, {1, "bAssocTerminal"}, {1, "bNrChannels"}, {2, "wChannelConfig"}, {1, "iChannelNames"}, {1, "iTerminal"}, }; static uint_t usb_ac_input_term_item = 10; static usb_descr_item_t usb_ac_output_term_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bTerminalID"}, {2, "wTerminalType"}, {1, "bAssocTerminal"}, {1, "bSourceID"}, {1, "iTerminal"}, }; static uint_t usb_ac_output_term_item = 8; static usb_descr_item_t usb_ac_mixer_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bUnitID"}, {1, "bNrInPins"}, {1, "baSourceID"}, }; static uint_t usb_ac_mixer_item = 6; static usb_descr_item_t usb_ac_selector_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bUnitID"}, {1, "bNrInPins"}, {1, "baSourceID"}, }; static uint_t usb_ac_selector_item = 6; static usb_descr_item_t usb_ac_feature_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bUnitID"}, {1, "bSourceID"}, {1, "bControlSize"}, {1, "bmaControls"}, }; static uint_t usb_ac_feature_item = 7; static usb_descr_item_t usb_ac_processing_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bUnitID"}, {1, "wProcessType"}, {1, "bNrInPins"}, {1, "baSourceID"}, }; static uint_t usb_ac_processing_item = 7; static usb_descr_item_t usb_ac_extension_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "wExtensionCode"}, {1, "bUnitID"}, {1, "bNrInPins"}, {1, "baSourceID"}, }; static uint_t usb_ac_extension_item = 7; static usb_descr_item_t usb_as_ep_descr[] = { {1, "blength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bmAttributes"}, {1, "bLockDelayUnits"}, {2, "wLockDelay"}, }; static uint_t usb_as_ep_item = 6; static usb_descr_item_t usb_as_if_descr[] = { {1, "blength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bTerminalLink"}, {1, "bDelay"}, {2, "wFormatTag"}, }; static uint_t usb_as_if_item = 6; static usb_descr_item_t usb_as_format_descr[] = { {1, "blength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bFormatType"}, {1, "bNrChannels"}, {1, "bSubFrameSize"}, {1, "bBitResolution"}, {1, "bSamFreqType"}, {1, "bSamFreqs"}, }; static uint_t usb_as_format_item = 9; static usb_descr_item_t usb_vc_header_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubtype"}, {2, "bcdUVC"}, {2, "wTotalLength"}, {4, "dwClockFrequency"}, {1, "bInCollection"}, }; static uint_t usb_vc_header_item = 7; static usb_descr_item_t usb_vc_input_term_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bTerminalID"}, {2, "wTerminalType"}, {1, "AssocTerminal"}, {1, "iTerminal"}, }; static uint_t usb_vc_input_term_item = 7; static usb_descr_item_t usb_vc_output_term_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bTerminalID"}, {2, "wTerminalType"}, {1, "AssocTerminal"}, {1, "bSourceID"}, {1, "iTerminal"}, }; static uint_t usb_vc_output_term_item = 8; static usb_descr_item_t usb_vc_processing_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bUnitID"}, {1, "bSourceID"}, {2, "wMaxMultiplier"}, {1, "bControlSize"}, {1, "bmControls"}, }; static uint_t usb_vc_processing_item = 8; static usb_descr_item_t usb_vc_selector_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bUnitID"}, {1, "bNrInPins"}, }; static uint_t usb_vc_selector_item = 5; static usb_descr_item_t usb_vc_extension_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bUnitID"}, {16 + BYTE_OFFSET, "guidExtensionCode[16]"}, {1, "bNumControls"}, {1, "bNrInPins"}, }; static uint_t usb_vc_extension_item = 7; static usb_descr_item_t usb_vs_input_header_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bNumFormats"}, {2, "wTotalLength"}, {1, "bEndpointAddress"}, {1, "bmInfo"}, {1, "bTerminalLink"}, {1, "bStillCaptureMethod"}, {1, "bTriggerSupport"}, {1, "bTriggerUsage"}, {1, "bControlSize"}, {1, "bmaControls"}, }; static uint_t usb_vs_input_header_item = 13; static usb_descr_item_t usb_vs_output_header_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bNumFormats"}, {2, "wTotalLength"}, {1, "bEndpointAddress"}, {1, "bTerminalLink"}, {1, "bControlSize"}, {1, "bmaControls"}, }; static uint_t usb_vs_output_header_item = 9; static usb_descr_item_t usb_vs_still_image_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bEndpointAddress"}, {1, "bNumImageSizePatterns"}, {2, "wWidth"}, {2, "wHeight"}, }; static uint_t usb_vs_still_image_item = 7; static usb_descr_item_t usb_vs_color_matching_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubtype"}, {1, "bColorPrimaries"}, {1, "bTransferCharacteristics"}, {1, "bMatrixCoefficients"}, }; static uint_t usb_vs_color_matching_item = 6; static usb_descr_item_t usb_vs_2frame_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bFrameIndex"}, {1, "bmCapabilities"}, {2, "wWidth"}, {2, "wHeight"}, {4, "dwMinBitRate"}, {4, "dwMaxBitRate"}, {4, "dwMaxVideoFrameBufferSize"}, {4, "dwDefaultFrameInterval"}, {1, "bFrameIntervalType"}, }; static uint_t usb_vs_2frame_item = 12; static usb_descr_item_t usb_vs_format_mjpeg_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bFormatIndex"}, {1, "bNumFrameDescriptors"}, {1, "bmFlags"}, {1, "bDefaultFrameIndex"}, {1, "bAspectRatioX"}, {1, "bAspectRatioY"}, {1, "bmInterlaceFlags"}, {1, "bCopyProtect"}, }; static uint_t usb_vs_format_mjpeg_item = 11; static usb_descr_item_t usb_vs_format_uncps_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bFormatIndex"}, {1, "bNumFrameDescriptors"}, {16 + BYTE_OFFSET, "guidFormat[16]"}, {1, "bBitsPerPixel"}, {1, "bDefaultFrameIndex"}, {1, "bAspectRatioX"}, {1, "bAspectRatioY"}, {1, "bmInterlaceFlags"}, {1, "bCopyProtect"}, }; static uint_t usb_vs_format_uncps_item = 12; static usb_descr_item_t usb_vs_format_mp2ts_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bFormatIndex"}, {1, "bDataOffset"}, {1, "bPacketLength"}, {1, "bStrideLength"}, {16 + BYTE_OFFSET, "guidStrideFormat[16]"}, }; static uint_t usb_vs_format_mp2ts_item = 8; static usb_descr_item_t usb_vs_format_dv_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {1, "bDescriptorSubType"}, {1, "bFormatIndex"}, {4, "dwMaxVideoFrameBufferSize"}, {1, "bFormatType"}, }; static uint_t usb_vs_format_dv_item = 6; static usb_descr_item_t usb_ccid_descr[] = { {1, "bLength"}, {1, "bDescriptorType"}, {2, "bcdCCID"}, {1, "bMaxSlotIndex"}, {1, "bVoltageSupport"}, {4, "dwProtocols"}, {4, "dwDefaultClock"}, {4, "dwMaximumClock"}, {1, "bNumClockSupported"}, {4, "dwDataRate"}, {4, "dwMaxDataRate"}, {1, "bNumDataRatesSupported"}, {4, "dwMaxIFSD"}, {4, "dwSyncProtocols"}, {4, "dwMechanical"}, {4, "dwFeatures"}, {4, "dwMaxCCIDMessageLength"}, {1, "bClassGetResponse"}, {1, "bClassEnvelope"}, {2, "wLcdLayout"}, {1, "bPinSupport"}, {1, "bMaxCCIDBusySlots"} }; static uint_t usb_ccid_item = ARRAY_SIZE(usb_ccid_descr); /* ****************************************************************** */ typedef struct hci_state { void *hci_dip; uint_t hci_instance; void *hci_hcdi_ops; uint_t hci_flags; uint16_t vendor_id; uint16_t device_id; } hci_state_t; static int prt_usb_tree(uintptr_t paddr, uint_t flag); static int prt_usb_tree_node(uintptr_t paddr); static void prt_usb_hid_item(uintptr_t paddr); static void prt_usb_hid_item_params(entity_item_t *item); static void prt_usb_hid_item_attrs(uintptr_t paddr); static void prt_usb_hid_item_tags(uint_t tag); static void prt_usb_hid_item_data(uintptr_t paddr, uint_t len); static int prt_usb_desc(uintptr_t usb_cfg, uint_t cfg_len); static int prt_usb_ac_desc(uintptr_t paddr, uint_t nlen); static int prt_usb_as_desc(uintptr_t paddr, uint_t nlen); static int prt_usb_vc_desc(uintptr_t paddr, uint_t nlen); static int prt_usb_vs_desc(uintptr_t paddr, uint_t nlen); static int print_descr(uintptr_t, uint_t, usb_descr_item_t *, uint_t); static int print_struct(uintptr_t, uint_t, mdb_arg_t *); static int prt_usb_buf(uintptr_t, uint_t); /* ****************************************************************** */ /* exported functions */ void prt_usb_usage(void); int prtusb(uintptr_t, uint_t, int, const mdb_arg_t *); /* ****************************************************************** */ /* help of prtusb */ void prt_usb_usage(void) { mdb_printf("%-8s : %s\n", "-v", "print all descriptors"); mdb_printf("%-8s : %s\n", "-t", "print device trees"); mdb_printf("%-8s : %s\n", "-i index", "print the device by index"); } /* the entry of ::prtusb */ int prtusb(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { static int count = 1; uint64_t sel_num = 0; uint_t usb_flag = 0; usba_device_t usb_dev; usb_dev_descr_t dev_desc; struct dev_info usb_dip; char strbuf[STRLEN]; /* print all usba devices if no address assigned */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("usba_device", "prtusb", argc, argv) == -1) { mdb_warn("failed to walk usba_device"); return (DCMD_ERR); } return (DCMD_OK); } /* for the first device, print head */ if (DCMD_HDRSPEC(flags)) { count = 1; mdb_printf("%%-8s%-12s%-6s%-14s%-5s%-12s%-20s%\n", "INDEX", "DRIVER", "INST", "NODE", "GEN", "VID.PID", "PRODUCT"); } if (mdb_getopts(argc, argv, 'i', MDB_OPT_UINT64, &sel_num, 't', MDB_OPT_SETBITS, OPT_TREE, &usb_flag, 'v', MDB_OPT_SETBITS, OPT_VERB, &usb_flag, NULL) != argc) { return (DCMD_USAGE); } if (mdb_vread(&usb_dev, sizeof (usba_device_t), addr) == -1) { mdb_warn("Failed to read usba_device!\n"); return (DCMD_ERR); } if (mdb_vread(&usb_dip, sizeof (struct dev_info), (uintptr_t)usb_dev.usb_dip) == -1) { mdb_warn("Failed to read dev_info!\n"); return (DCMD_ERR); } /* process the "-i" */ if (sel_num && sel_num != count) { count++; return (DCMD_OK); } /* index number of device node */ mdb_printf("%-8x", count++); /* driver and instance */ mdb_devinfo2driver((uintptr_t)usb_dev.usb_dip, strbuf, STRLEN); mdb_printf("%-12s%-6d", strbuf, usb_dip.devi_instance); /* node name */ if (mdb_readstr(strbuf, STRLEN, (uintptr_t)usb_dip.devi_node_name) != -1) { mdb_printf("%-14s", strbuf); } else { mdb_printf("%-14s", "No Node Name"); } if (mdb_vread(&dev_desc, sizeof (usb_dev_descr_t), (uintptr_t)usb_dev.usb_dev_descr) != -1) { /* gen (note we read this from the bcd) */ mdb_printf("%01x.%01x ", dev_desc.bcdUSB >> 8, (dev_desc.bcdUSB & 0xf0) >> 4); /* vid.pid */ mdb_printf("%04x.%04x ", dev_desc.idVendor, dev_desc.idProduct); } /* product string */ if (mdb_readstr(strbuf, STRLEN, (uintptr_t)usb_dev.usb_product_str) != -1) { mdb_printf("%s\n", strbuf); } else { mdb_printf("%s\n", "No Product String"); } /* tree, print usb device tree info */ if (usb_flag & OPT_TREE) { mdb_printf("\nusba_device: 0x%lx\n", addr); mdb_printf("mfg_prod_sn: "); if (mdb_readstr(strbuf, STRLEN, (uintptr_t)usb_dev.usb_mfg_str) != -1) { mdb_printf("%s - ", strbuf); } else { mdb_printf("NULL - "); } if (mdb_readstr(strbuf, STRLEN, (uintptr_t)usb_dev.usb_product_str) != -1) { mdb_printf("%s - ", strbuf); } else { mdb_printf("NULL -"); } if (mdb_readstr(strbuf, STRLEN, (uintptr_t)usb_dev.usb_serialno_str) != -1) { mdb_printf("%s", strbuf); } else { mdb_printf("NULL"); } mdb_printf("\n\n"); prt_usb_tree((uintptr_t)usb_dev.usb_dip, 0); } /* verbose, print all descriptors */ if (usb_flag & OPT_VERB) { int i; uintptr_t cfg_buf; uint16_t cfg_len; mdb_printf("\n"); /* device descriptor */ prt_usb_desc((uintptr_t)usb_dev.usb_dev_descr, 18); /* config cloud descriptors */ if (usb_dev.usb_n_cfgs == 1) { mdb_inc_indent(4); mdb_printf("-- Active Config Index 0\n"); mdb_dec_indent(4); prt_usb_desc((uintptr_t)usb_dev.usb_cfg, usb_dev.usb_cfg_length); } else { /* multiple configs */ for (i = 0; i < usb_dev.usb_n_cfgs; i++) { if ((mdb_vread(&cfg_len, sizeof (uint16_t), (uintptr_t)(usb_dev.usb_cfg_array_len + i)) != -1) && (mdb_vread(&cfg_buf, sizeof (uintptr_t), (uintptr_t)(usb_dev.usb_cfg_array + i)) != -1)) { mdb_inc_indent(4); if (cfg_buf == (uintptr_t)usb_dev.usb_cfg) { mdb_printf("-- Active Config" " Index %x\n", i); } else { mdb_printf("-- Inactive Config" " Index %x\n", i); } mdb_dec_indent(4); prt_usb_desc(cfg_buf, cfg_len); } } } } if (usb_flag) { mdb_printf("%%-72s%\n", " "); } return (DCMD_OK); } /* print the info required by "-t" */ static int prt_usb_tree(uintptr_t paddr, uint_t flag) { struct dev_info usb_dip; if (mdb_vread(&usb_dip, sizeof (struct dev_info), paddr) == -1) { mdb_warn("prt_usb_tree: Failed to read dev_info!\n"); return (DCMD_ERR); } prt_usb_tree_node(paddr); if (usb_dip.devi_child) { mdb_printf("{\n"); mdb_inc_indent(4); prt_usb_tree((uintptr_t)usb_dip.devi_child, 1); mdb_dec_indent(4); mdb_printf("}\n\n"); } if (usb_dip.devi_sibling && flag == 1) { /* print the sibling if flag == 1 */ prt_usb_tree((uintptr_t)usb_dip.devi_sibling, 1); } return (DCMD_OK); } static int prt_usb_tree_node(uintptr_t paddr) { struct dev_info usb_dip; uintptr_t statep; uint_t errlevel; char driver_name[STRLEN] = ""; char strbuf[STRLEN] = ""; if (mdb_vread(&usb_dip, sizeof (struct dev_info), paddr) == -1) { mdb_warn("prt_usb_tree_node: Failed to read dev_info!\n"); return (DCMD_ERR); } /* node name */ if (mdb_readstr(strbuf, STRLEN, (uintptr_t)usb_dip.devi_node_name) != -1) { mdb_printf("%s, ", strbuf); } else { mdb_printf("%s, ", "node_name"); } /* instance */ mdb_printf("instance #%d ", usb_dip.devi_instance); /* driver name */ if (DDI_CF2(&usb_dip)) { mdb_devinfo2driver(paddr, driver_name, STRLEN); mdb_printf("(driver name: %s)\n", driver_name); } else { mdb_printf("(driver not attached)\n"); } /* device path */ mdb_ddi_pathname(paddr, strbuf, STRLEN); mdb_printf(" %s\n", strbuf); /* dip addr */ mdb_printf(" dip: 0x%lx\n", paddr); /* softe_sate */ mdb_snprintf(strbuf, STRLEN, "%s_statep", driver_name); if (mdb_devinfo2statep(paddr, strbuf, &statep) != -1) { mdb_printf(" %s: 0x%lx\n", strbuf, statep); } /* error level */ mdb_snprintf(strbuf, STRLEN, "%s_errlevel", driver_name); if (mdb_readvar(&errlevel, strbuf) != -1) { mdb_printf(" %s: 0x%x\n", strbuf, errlevel); } if (strcmp(driver_name, "ehci") == 0) { mdb_arg_t argv[] = { {MDB_TYPE_STRING, {"ehci_state_t"}}, {MDB_TYPE_STRING, {"ehci_root_hub.rh_descr"}} }; mdb_call_dcmd("print", statep, DCMD_ADDRSPEC, 2, argv); } if (strcmp(driver_name, "ohci") == 0) { mdb_arg_t argv[] = { {MDB_TYPE_STRING, {"ohci_state_t"}}, {MDB_TYPE_STRING, {"ohci_root_hub.rh_descr"}} }; mdb_call_dcmd("print", statep, DCMD_ADDRSPEC, 2, argv); } if (strcmp(driver_name, "uhci") == 0) { mdb_arg_t argv[] = { {MDB_TYPE_STRING, {"uhci_state_t"}}, {MDB_TYPE_STRING, {"uhci_root_hub.rh_descr"}} }; mdb_call_dcmd("print", statep, DCMD_ADDRSPEC, 2, argv); } if (strcmp(driver_name, "hubd") == 0) { mdb_arg_t argv[] = { {MDB_TYPE_STRING, {"hubd_t"}}, {MDB_TYPE_STRING, {"h_ep1_xdescr.uex_ep"}} }; mdb_call_dcmd("print", statep, DCMD_ADDRSPEC, 2, argv); } if (strcmp(driver_name, "hid") == 0) { hid_state_t hidp; if (mdb_vread(&hidp, sizeof (hid_state_t), statep) != -1) { hidparser_handle hid_report; if (mdb_vread(&hid_report, sizeof (hidparser_handle), (uintptr_t)hidp.hid_report_descr) != -1) { mdb_inc_indent(2); mdb_printf("\n"); prt_usb_hid_item((uintptr_t) hid_report.hidparser_handle_parse_tree); mdb_dec_indent(2); } } } mdb_printf("\n"); return (DCMD_OK); } /* print hid report descriptor */ static void prt_usb_hid_item(uintptr_t paddr) { entity_item_t item; if (mdb_vread(&item, sizeof (entity_item_t), paddr) != -1) { prt_usb_hid_item_attrs((uintptr_t)item.entity_item_attributes); prt_usb_hid_item_params(&item); if (item.info.child) { mdb_inc_indent(4); prt_usb_hid_item((uintptr_t)item.info.child); mdb_dec_indent(4); } if (item.entity_item_right_sibling) { prt_usb_hid_item((uintptr_t) item.entity_item_right_sibling); } } } static void prt_usb_hid_item_params(entity_item_t *item) { switch (item->entity_item_type) { case 0x80: mdb_printf("INPUT "); break; case 0x90: mdb_printf("OUTPUT "); break; case 0xA0: mdb_printf("COLLECTION "); break; case 0xB0: mdb_printf("FEATURE "); break; case 0xC0: mdb_printf("END_COLLECTION "); break; default: mdb_printf("MAIN_ITEM "); break; } prt_usb_hid_item_data((uintptr_t)item->entity_item_params, item->entity_item_params_leng); mdb_printf("\n"); } static void prt_usb_hid_item_attrs(uintptr_t paddr) { entity_attribute_t attr; if (mdb_vread(&attr, sizeof (entity_attribute_t), paddr) != -1) { prt_usb_hid_item_tags(attr.entity_attribute_tag); prt_usb_hid_item_data((uintptr_t)attr.entity_attribute_value, attr.entity_attribute_length); mdb_printf("\n"); if (attr.entity_attribute_next) { prt_usb_hid_item_attrs((uintptr_t) attr.entity_attribute_next); } } } static void prt_usb_hid_item_data(uintptr_t paddr, uint_t len) { char data[4]; int i; if (len > 4) { mdb_warn("Incorrect entity_item_length: 0x%x\n", len); return; } if (mdb_vread(data, len, paddr) != -1) { mdb_printf("( "); for (i = 0; i < len; i++) { mdb_printf("0x%02x ", data[i] & 0xff); } mdb_printf(")"); } } static void prt_usb_hid_item_tags(uint_t tag) { switch (tag) { case 0x04: mdb_printf("usage page "); break; case 0x14: mdb_printf("logical minimum "); break; case 0x24: mdb_printf("logical maximum "); break; case 0x34: mdb_printf("physical minimum "); break; case 0x44: mdb_printf("physical maximum "); break; case 0x54: mdb_printf("exponent "); break; case 0x64: mdb_printf("unit "); break; case 0x74: mdb_printf("report size "); break; case 0x84: mdb_printf("report id "); break; case 0x94: mdb_printf("report count "); break; case 0x08: mdb_printf("usage "); break; case 0x18: mdb_printf("usage min "); break; case 0x28: mdb_printf("usage max "); break; default: mdb_printf("tag "); } } /* print the info required by "-v" */ static int prt_usb_desc(uintptr_t usb_cfg, uint_t cfg_len) { uintptr_t paddr = usb_cfg; uintptr_t pend = usb_cfg + cfg_len; uchar_t desc_type, nlen; usb_if_descr_t usb_if; ulong_t indent = 0; mdb_arg_t argv = {MDB_TYPE_STRING, {"usb_dev_descr_t"}}; if (mdb_vread(&nlen, 1, paddr) == -1) { return (DCMD_ERR); } while ((paddr + nlen <= pend) && (nlen > 0)) { if (mdb_vread(&desc_type, 1, paddr + 1) == -1) { return (DCMD_ERR); } switch (desc_type) { case USB_DESCR_TYPE_DEV: mdb_printf("Device Descriptor\n"); print_struct(paddr, nlen, &argv); break; case USB_DESCR_TYPE_CFG: indent = 4; mdb_inc_indent(indent); mdb_printf("Configuration Descriptor\n"); print_descr(paddr, nlen, usb_cfg_descr, usb_cfg_item); mdb_dec_indent(indent); break; case USB_DESCR_TYPE_STRING: mdb_printf("String Descriptor\n"); print_descr(paddr, nlen, usb_str_descr, usb_str_item); break; case USB_DESCR_TYPE_IF: indent = 8; mdb_inc_indent(indent); mdb_printf("Interface Descriptor\n"); print_descr(paddr, nlen, usb_if_descr, usb_if_item); mdb_dec_indent(indent); mdb_vread(&usb_if, sizeof (usb_if_descr_t), paddr); break; case USB_DESCR_TYPE_EP: indent = 8; mdb_inc_indent(indent); mdb_printf("Endpoint Descriptor\n"); print_descr(paddr, nlen, usb_ep_descr, usb_ep_item); mdb_dec_indent(indent); break; case USB_DESCR_TYPE_SS_EP_COMP: indent = 12; mdb_inc_indent(indent); mdb_printf("SuperSpeed Endpoint Companion " "Descriptor\n"); print_descr(paddr, nlen, usb_ep_ss_comp_descr, usb_ep_ss_comp_item); mdb_dec_indent(indent); break; case USB_DESCR_TYPE_DEV_QLF: mdb_printf("Device_Qualifier Descriptor\n"); print_descr(paddr, nlen, usb_qlf_descr, usb_qlf_item); break; case USB_DESCR_TYPE_OTHER_SPEED_CFG: indent = 4; mdb_inc_indent(indent); mdb_printf("Other_Speed_Configuration Descriptor\n"); print_descr(paddr, nlen, usb_cfg_descr, usb_cfg_item); mdb_dec_indent(indent); break; case USB_DESCR_TYPE_IA: indent = 6; mdb_inc_indent(indent); mdb_printf("Interface_Association Descriptor\n"); print_descr(paddr, nlen, usb_ia_descr, usb_ia_item); mdb_dec_indent(indent); break; case 0x21: /* hid descriptor */ indent = 12; mdb_inc_indent(indent); if (usb_if.bInterfaceClass == 0xe0 && usb_if.bInterfaceSubClass == 0x02) { mdb_printf("WA Descriptor\n"); print_descr(paddr, nlen, usb_wa_descr, usb_wa_item); } else if (usb_if.bInterfaceClass == USB_CLASS_CCID && usb_if.bInterfaceSubClass == 0x0) { mdb_printf("CCID Descriptor\n"); print_descr(paddr, nlen, usb_ccid_descr, usb_ccid_item); } else { mdb_printf("HID Descriptor\n"); print_descr(paddr, nlen, usb_hid_descr, usb_hid_item); } mdb_dec_indent(indent); break; case 0x24: /* class specific interfce descriptor */ indent = 12; mdb_inc_indent(indent); if (usb_if.bInterfaceClass == 1 && usb_if.bInterfaceSubClass == 1) { mdb_printf("AudioControl_Interface: "); prt_usb_ac_desc(paddr, nlen); } else if (usb_if.bInterfaceClass == 1 && usb_if.bInterfaceSubClass == 2) { mdb_printf("AudioStream_Interface: "); prt_usb_as_desc(paddr, nlen); } else if (usb_if.bInterfaceClass == 0x0E && usb_if.bInterfaceSubClass == 1) { mdb_printf("VideoControl_Interface: "); prt_usb_vc_desc(paddr, nlen); } else if (usb_if.bInterfaceClass == 0x0E && usb_if.bInterfaceSubClass == 2) { mdb_printf("VideoStream_Interface: "); prt_usb_vs_desc(paddr, nlen); } else { mdb_printf("Unknown_Interface:" "0x%x\n", desc_type); prt_usb_buf(paddr, nlen); } mdb_dec_indent(indent); break; case 0x25: /* class specific endpoint descriptor */ indent = 12; mdb_inc_indent(indent); if (usb_if.bInterfaceClass == 0x01) { mdb_printf("AudioEndpoint:\n"); print_descr(paddr, nlen, usb_as_ep_descr, usb_as_ep_item); } else if (usb_if.bInterfaceClass == 0x0E) { mdb_printf("VideoEndpoint:\n"); print_descr(paddr, nlen, usb_ep_descr, usb_ep_item); } else { mdb_printf("Unknown_Endpoint:" "0x%x\n", desc_type); prt_usb_buf(paddr, nlen); } mdb_dec_indent(indent); break; default: mdb_inc_indent(indent); mdb_printf("Unknown Descriptor: 0x%x\n", desc_type); prt_usb_buf(paddr, nlen); mdb_dec_indent(indent); break; } paddr += nlen; if (mdb_vread(&nlen, 1, paddr) == -1) { return (DCMD_ERR); } }; return (DCMD_OK); } /* print audio class specific control descriptor */ static int prt_usb_ac_desc(uintptr_t addr, uint_t nlen) { uchar_t sub_type; if (mdb_vread(&sub_type, 1, addr + 2) == -1) { return (DCMD_ERR); } switch (sub_type) { case 0x01: mdb_printf("header Descriptor\n"); print_descr(addr, nlen, usb_ac_header_descr, usb_ac_header_item); break; case 0x02: mdb_printf("input_terminal Descriptor\n"); print_descr(addr, nlen, usb_ac_input_term_descr, usb_ac_input_term_item); break; case 0x03: mdb_printf("output_terminal Descriptor\n"); print_descr(addr, nlen, usb_ac_output_term_descr, usb_ac_output_term_item); break; case 0x04: mdb_printf("mixer_unit Descriptor\n"); print_descr(addr, nlen, usb_ac_mixer_descr, usb_ac_mixer_item); break; case 0x05: mdb_printf("selector_unit Descriptor\n"); print_descr(addr, nlen, usb_ac_selector_descr, usb_ac_selector_item); break; case 0x06: mdb_printf("feature_unit Descriptor\n"); print_descr(addr, nlen, usb_ac_feature_descr, usb_ac_feature_item); break; case 0x07: mdb_printf("processing_unit Descriptor\n"); print_descr(addr, nlen, usb_ac_processing_descr, usb_ac_processing_item); break; case 0x08: mdb_printf("extension_unit Descriptor\n"); print_descr(addr, nlen, usb_ac_extension_descr, usb_ac_extension_item); break; default: mdb_printf("Unknown AC sub-descriptor 0x%x\n", sub_type); prt_usb_buf(addr, nlen); break; } return (DCMD_OK); } /* print audio class specific stream descriptor */ static int prt_usb_as_desc(uintptr_t addr, uint_t nlen) { uchar_t sub_type; if (mdb_vread(&sub_type, 1, addr + 2) == -1) { return (DCMD_ERR); } switch (sub_type) { case 0x01: mdb_printf("general_interface Descriptor\n"); print_descr(addr, nlen, usb_as_if_descr, usb_as_if_item); break; case 0x02: mdb_printf("format_type Descriptor\n"); print_descr(addr, nlen, usb_as_format_descr, usb_as_format_item); break; default: mdb_printf("Unknown AS sub-descriptor 0x%x\n", sub_type); prt_usb_buf(addr, nlen); break; } return (DCMD_OK); } /* print video class specific control descriptor */ static int prt_usb_vc_desc(uintptr_t addr, uint_t nlen) { uchar_t sub_type; if (mdb_vread(&sub_type, 1, addr + 2) == -1) { return (DCMD_ERR); } switch (sub_type) { case 0x01: mdb_printf("header Descriptor\n"); print_descr(addr, nlen, usb_vc_header_descr, usb_vc_header_item); break; case 0x02: mdb_printf("input_terminal Descriptor\n"); print_descr(addr, nlen, usb_vc_input_term_descr, usb_vc_input_term_item); break; case 0x03: mdb_printf("output_terminal Descriptor\n"); print_descr(addr, nlen, usb_vc_output_term_descr, usb_vc_output_term_item); break; case 0x04: mdb_printf("selector_unit Descriptor\n"); print_descr(addr, nlen, usb_vc_selector_descr, usb_vc_selector_item); break; case 0x05: mdb_printf("processing_unit Descriptor\n"); print_descr(addr, nlen, usb_vc_processing_descr, usb_vc_processing_item); break; case 0x06: mdb_printf("extension_unit Descriptor\n"); print_descr(addr, nlen, usb_vc_extension_descr, usb_vc_extension_item); break; default: mdb_printf("Unknown VC sub-descriptor 0x%x\n", sub_type); prt_usb_buf(addr, nlen); break; } return (DCMD_OK); } /* print video class specific stream descriptor */ static int prt_usb_vs_desc(uintptr_t addr, uint_t nlen) { uchar_t sub_type; if (mdb_vread(&sub_type, 1, addr + 2) == -1) { return (DCMD_ERR); } switch (sub_type) { case 0x01: mdb_printf("input_header Descriptor\n"); print_descr(addr, nlen, usb_vs_input_header_descr, usb_vs_input_header_item); break; case 0x02: mdb_printf("output_header Descriptor\n"); print_descr(addr, nlen, usb_vs_output_header_descr, usb_vs_output_header_item); break; case 0x03: mdb_printf("still_image_frame Descriptor\n"); print_descr(addr, nlen, usb_vs_still_image_descr, usb_vs_still_image_item); break; case 0x04: mdb_printf("format_uncompressed Descriptor\n"); print_descr(addr, nlen, usb_vs_format_uncps_descr, usb_vs_format_uncps_item); break; case 0x05: mdb_printf("frame_uncompressed Descriptor\n"); print_descr(addr, nlen, usb_vs_2frame_descr, usb_vs_2frame_item); break; case 0x06: mdb_printf("format_mjpeg Descriptor\n"); print_descr(addr, nlen, usb_vs_format_mjpeg_descr, usb_vs_format_mjpeg_item); break; case 0x07: mdb_printf("frame_mjpeg Descriptor\n"); print_descr(addr, nlen, usb_vs_2frame_descr, usb_vs_2frame_item); break; case 0x0A: mdb_printf("format_mpeg2ts Descriptor\n"); print_descr(addr, nlen, usb_vs_format_mp2ts_descr, usb_vs_format_mp2ts_item); break; case 0x0C: mdb_printf("format_dv Descriptor\n"); print_descr(addr, nlen, usb_vs_format_dv_descr, usb_vs_format_dv_item); break; case 0x0D: mdb_printf("color_matching Descriptor\n"); print_descr(addr, nlen, usb_vs_color_matching_descr, usb_vs_color_matching_item); break; default: mdb_printf("Unknown VS sub-descriptor 0x%x\n", sub_type); prt_usb_buf(addr, nlen); break; } return (DCMD_OK); } /* parse and print the descriptor items */ static int print_descr(uintptr_t addr, uint_t nlen, usb_descr_item_t *item, uint_t nitem) { int i, j; uint8_t buf[8]; uint64_t value; uintptr_t paddr = addr; usb_descr_item_t *p = item; mdb_printf("{"); for (i = 0; (i < nitem) && (paddr < addr + nlen); i++) { mdb_printf("\n %s =", p->name); switch (p->nlen) { case 1: /* uint8_t */ if (mdb_vread(buf, 1, paddr) == -1) { return (DCMD_ERR); } value = buf[0]; break; case 2: /* uint16_t */ if (mdb_vread(buf, 2, paddr) == -1) { return (DCMD_ERR); } value = buf[0] | (buf[1] << 8); break; case 4: /* uint32_t */ if (mdb_vread(buf, 4, paddr) == -1) { return (DCMD_ERR); } value = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); break; case 8: /* uint64_t */ if (mdb_vread(buf, 8, paddr) == -1) { return (DCMD_ERR); } value = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); value = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24) | (value << 32); break; default: /* byte array */ value = 0; /* print an array instead of a value */ for (j = 0; j < p->nlen - BYTE_OFFSET; j++) { if (mdb_vread(buf, 1, paddr + j) == -1) { break; } mdb_printf(" 0x%x", buf[0]); } break; } if (p->nlen > BYTE_OFFSET) { paddr += p->nlen - BYTE_OFFSET; } else { mdb_printf(" 0x%x", value); paddr += p->nlen; } p++; } /* print the unresolved bytes */ if (paddr < addr + nlen) { mdb_printf("\n ... ="); } while (paddr < addr + nlen) { if (mdb_vread(buf, 1, paddr++) == -1) { break; } mdb_printf(" 0x%x", buf[0]); } mdb_printf("\n}\n"); return (DCMD_OK); } /* print the buffer as a struct */ static int print_struct(uintptr_t addr, uint_t nlen, mdb_arg_t *arg) { mdb_ctf_id_t id; if (mdb_ctf_lookup_by_name(arg->a_un.a_str, &id) == 0) { mdb_call_dcmd("print", addr, DCMD_ADDRSPEC, 1, arg); } else { prt_usb_buf(addr, nlen); } return (DCMD_OK); } /* print the buffer as a byte array */ static int prt_usb_buf(uintptr_t addr, uint_t nlen) { int i; uchar_t val; mdb_printf("{\n"); for (i = 0; i < nlen; i++) { if (mdb_vread(&val, 1, addr + i) == -1) { break; } mdb_printf("%02x ", val); } if (nlen) { mdb_printf("\n"); } mdb_printf("}\n"); return (DCMD_OK); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2016 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Prototypes */ /* usba.c */ extern uintptr_t mdb_usba_get_usba_device(uintptr_t); extern uintptr_t mdb_usba_hcdi_get_hcdi(struct dev_info *); /* * Defines */ /* dcmd options */ #define USB_DUMP_VERBOSE 0x01 #define USB_DUMP_ACTIVE_PIPES 0x02 /* Hardcoded slop factor designed into debug buf logic */ #define USB_DEBUG_SIZE_EXTRA_ALLOC 8 /* * Callback arg struct for find_dip (callback func used in usba_device2devinfo). */ typedef struct usba_device2devinfo_data { uintptr_t u2d_target_usb_dev_p; /* one we're looking for */ uintptr_t *u2d_dip_addr; /* Where to store result */ boolean_t u2d_found; /* Match found */ } usba_device2devinfo_cbdata_t; /* * Callback for usba_device2dip. * Callback called from the devinfo_children walk invoked in usba_device2dip. * * For the current dip, get the (potential) pointer to its usba_device_t * struct. * See if this pointer matches the address of the usba_device_t we're looking * for (passed in as usb_dev_p). If so, stuff its value in u2d_dip_addr, * and terminate the walk. * * - dip_addr is the address in core of the dip currently being processed by the * walk * - local_dip is a pointer to a copy of the struct dev_info in local memory * - cb_data is the addr of the callback arg the walker was invoked with * (passed through transparently from walk invoker). * * Returns: * - WALK_NEXT on success (match not found yet) * - WALK_ERR on errors. * - WALK_DONE is returned, cb_data.found is set to TRUE, and * *cb_data.u2d_dip_addr is set to the matched dip addr if a dip corresponding * to the desired usba_device_t* is found. */ /*ARGSUSED*/ static int find_dip(uintptr_t dip_addr, const void *local_dip, void *cb_arg) { uintptr_t cur_usb_dev; usba_device2devinfo_cbdata_t *cb_data = (usba_device2devinfo_cbdata_t *)cb_arg; if ((cur_usb_dev = mdb_usba_get_usba_device(dip_addr)) == 0) { /* * If there's no corresponding usba_device_t, this dip isn't * a usb node. Might be an sd node. Ignore it. */ return (WALK_NEXT); } if (cur_usb_dev == cb_data->u2d_target_usb_dev_p) { *cb_data->u2d_dip_addr = dip_addr; cb_data->u2d_found = TRUE; return (WALK_DONE); } return (WALK_NEXT); } /* * Given a usba_device pointer, figure out which dip is associated with it. * Relies on usba_device.usb_root_hub_dip being accurate. * * - usb_dev_addr is a pointer to a usba_device_t in core. * - dip_addr is the address of a uintptr_t to receive the address in core * of the found dip (if any). * * Returns: * 0 on success (no match found) * 1 on success (match found) * -1 on errors. */ static int usba_device2dip(uintptr_t usb_dev_addr, uintptr_t *dip_addr) { usba_device_t usb_dev; usba_device2devinfo_cbdata_t cb_data; /* * Walk all USB children of the root hub devinfo. * The callback func looks for a match on the usba_device address. */ cb_data.u2d_target_usb_dev_p = usb_dev_addr; cb_data.u2d_dip_addr = dip_addr; cb_data.u2d_found = FALSE; if (mdb_vread(&usb_dev, sizeof (usba_device_t), usb_dev_addr) == -1) { mdb_warn("failed to read usba_device struct"); return (-1); } /* * Walk devinfo children starting with the root hub node, * looking for a match on the usba_device pointer (which is what * find_dip does). * Result is placed in cb_data.dip_addr. */ if (mdb_pwalk("devinfo_children", find_dip, &cb_data, (uintptr_t)usb_dev.usb_root_hub_dip) != 0) { mdb_warn("failed to walk devinfo_children"); return (-1); } if (cb_data.u2d_found == TRUE) { return (1); } return (0); } /* * Generic walker usba_list_entry_t walker. * Works for any usba_list_entry_t list. */ int usba_list_walk_init(mdb_walk_state_t *wsp) { /* Must have a start addr. */ if (wsp->walk_addr == 0) { mdb_warn("not a global walk. Starting address required\n"); return (WALK_ERR); } return (WALK_NEXT); } /* * Generic list walker step routine. * NOTE: multiple walkers share this routine. */ int usba_list_walk_step(mdb_walk_state_t *wsp) { int status; usba_list_entry_t list_entry; if (mdb_vread(&list_entry, sizeof (usba_list_entry_t), (uintptr_t)wsp->walk_addr) == -1) { mdb_warn("failed to read usba_list_entry_t at %p", wsp->walk_addr); return (WALK_ERR); } status = wsp->walk_callback(wsp->walk_addr, &list_entry, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)list_entry.next; /* Check if we're at the last element */ if (wsp->walk_addr == 0) { return (WALK_DONE); } return (status); } /* * usb_pipe_handle walker * Given a pointer to a usba_device_t, walk the array of endpoint * pipe_handle lists. * For each list, traverse the list, invoking the callback on each element. * * Note this function takes the address of a usba_device struct (which is * easily obtainable), but actually traverses a sub-portion of the struct * (which address is not so easily obtainable). */ int usb_pipe_handle_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("not a global walk; usba_device_t required\n"); return (WALK_ERR); } wsp->walk_data = mdb_alloc((sizeof (usba_ph_impl_t)) * USBA_N_ENDPOINTS, UM_SLEEP | UM_GC); /* * Read the usb_ph_list array into local memory. * Set start address to first element/endpoint in usb_pipehandle_list */ if (mdb_vread(wsp->walk_data, (sizeof (usba_ph_impl_t)) * USBA_N_ENDPOINTS, (uintptr_t)((size_t)(wsp->walk_addr) + offsetof(usba_device_t, usb_ph_list))) == -1) { mdb_warn("failed to read usb_pipehandle_list at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_arg = 0; return (WALK_NEXT); } int usb_pipe_handle_walk_step(mdb_walk_state_t *wsp) { int status; usba_ph_impl_t *impl_list = (usba_ph_impl_t *)(wsp->walk_data); intptr_t index = (intptr_t)wsp->walk_arg; /* Find the first valid endpoint, starting from where we left off. */ while ((index < USBA_N_ENDPOINTS) && (impl_list[index].usba_ph_data == NULL)) { index++; } /* No more valid endpoints. */ if (index >= USBA_N_ENDPOINTS) { return (WALK_DONE); } status = wsp->walk_callback((uintptr_t)impl_list[index].usba_ph_data, wsp->walk_data, wsp->walk_cbdata); /* Set up to start at next pipe handle next time. */ wsp->walk_arg = (void *)(index + 1); return (status); } /* * Given the address of a usba_pipe_handle_data_t, dump summary info. */ /*ARGSUSED*/ int usb_pipe_handle(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char *dir, *type, *state; usb_ep_descr_t ept_descr; usba_pipe_handle_data_t pipe_handle; usba_ph_impl_t ph_impl; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_vread(&pipe_handle, sizeof (usba_pipe_handle_data_t), addr) == -1) { mdb_warn("failed to read pipe handle at %p", addr); return (DCMD_ERR); } if (mdb_vread(&ph_impl, sizeof (usba_ph_impl_t), (uintptr_t)pipe_handle.p_ph_impl) == -1) { state = "*******"; } else { switch (ph_impl.usba_ph_state) { case USB_PIPE_STATE_CLOSED: state = "CLOSED "; break; case USB_PIPE_STATE_IDLE: state = "IDLE "; break; case USB_PIPE_STATE_ACTIVE: state = "ACTIVE "; break; case USB_PIPE_STATE_ERROR: state = "ERROR "; break; case USB_PIPE_STATE_CLOSING: state = "CLOSING"; break; default: state = "ILLEGAL"; break; } } bcopy(&pipe_handle.p_ep, &ept_descr, sizeof (usb_ep_descr_t)); if (DCMD_HDRSPEC(flags)) { mdb_printf("\n %%-3s %5s %3s %7s %-?s %-?s %-?s%\n", "EP", "TYPE ", "DIR", "STATE ", "P_HANDLE", "P_POLICY", "EP DESCR"); } dir = ((ept_descr.bEndpointAddress & USB_EP_DIR_MASK) & USB_EP_DIR_IN) ? "In " : "Out"; switch (ept_descr.bmAttributes & USB_EP_ATTR_MASK) { case USB_EP_ATTR_CONTROL: type = "Cntrl"; break; case USB_EP_ATTR_ISOCH: type = "Isoch"; break; case USB_EP_ATTR_BULK: type = "Bulk "; break; case USB_EP_ATTR_INTR: type = "Intr "; break; default: type = "*****"; break; } mdb_printf(" %3d %5s %3s %7s %-?p %-?p %-?p\n", ept_descr.bEndpointAddress & USB_EP_NUM_MASK, type, dir, state, addr, addr + offsetof(usba_pipe_handle_data_t, p_policy), addr + offsetof(usba_pipe_handle_data_t, p_ep)); return (DCMD_OK); } /* * usba_device walker: * * walks the chain of usba_device structs headed by usba_device_list in usba.c * NOTE: It uses the generic list walk step routine usba_list_walk_step. * No walk_fini routine is needed. */ int usba_device_walk_init(mdb_walk_state_t *wsp) { usba_list_entry_t list_entry; if (wsp->walk_addr != 0) { mdb_warn( "global walk only. Must be invoked without an address\n"); return (WALK_ERR); } if (mdb_readvar(&list_entry, "usba_device_list") == -1) { mdb_warn("failed to read usba_device_list"); return (WALK_ERR); } /* List head is not part of usba_device_t, get first usba_device_t */ wsp->walk_addr = (uintptr_t)list_entry.next; return (WALK_NEXT); } int usba_hubd_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { mdb_warn("hubd only supports global walks.\n"); return (WALK_ERR); } if (mdb_layered_walk("usba_device", wsp) == -1) { mdb_warn("couldn't walk 'usba_device'"); return (WALK_ERR); } return (WALK_NEXT); } /* * Getting the hub state is annoying. The root hubs are stored on dev_info_t * while the normal hubs are stored as soft state. */ int usba_hubd_walk_step(mdb_walk_state_t *wsp) { usba_device_t ud; hubd_t hubd; struct dev_info dev_info; uintptr_t state_addr; if (mdb_vread(&ud, sizeof (ud), wsp->walk_addr) != sizeof (ud)) { mdb_warn("failed to read usba_device_t at %p", wsp->walk_addr); return (WALK_ERR); } if (ud.usb_root_hubd != NULL) { if (mdb_vread(&hubd, sizeof (hubd), (uintptr_t)ud.usb_root_hubd) != sizeof (hubd)) { mdb_warn("failed to read hubd at %p", ud.usb_root_hubd); return (WALK_ERR); } return (wsp->walk_callback((uintptr_t)ud.usb_root_hubd, &hubd, wsp->walk_cbdata)); } if (ud.usb_hubdi == NULL) return (WALK_NEXT); /* * For non-root hubs, the hubd_t is stored in the soft state. Figure out * the instance from the dev_info_t and then get its soft state. */ if (mdb_vread(&dev_info, sizeof (struct dev_info), (uintptr_t)ud.usb_dip) != sizeof (struct dev_info)) { mdb_warn("failed to read dev_info_t for device %p at %p", wsp->walk_addr, ud.usb_dip); return (WALK_ERR); } if (mdb_get_soft_state_byname("hubd_statep", dev_info.devi_instance, &state_addr, &hubd, sizeof (hubd)) == -1) { mdb_warn("failed to read hubd soft state for instance %d from " "usb device %p", dev_info.devi_instance, wsp->walk_addr); return (WALK_ERR); } return (wsp->walk_callback(state_addr, &hubd, wsp->walk_cbdata)); } /* * usba_device dcmd * Given the address of a usba_device struct, dump summary info * -v: Print more (verbose) info * -p: Walk/dump all open pipes for this usba_device */ /*ARGSUSED*/ int usba_device(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int status; char pathname[MAXNAMELEN]; char dname[MODMAXNAMELEN] = ""; /* Driver name */ char drv_statep[MODMAXNAMELEN+ 10]; uint_t usb_flag = 0; boolean_t no_driver_attached = FALSE; uintptr_t dip_addr; struct dev_info devinfo; if (!(flags & DCMD_ADDRSPEC)) { /* Global walk */ if (mdb_walk_dcmd("usba_device", "usba_device", argc, argv) == -1) { mdb_warn("failed to walk usba_device"); return (DCMD_ERR); } return (DCMD_OK); } if (mdb_getopts(argc, argv, 'p', MDB_OPT_SETBITS, USB_DUMP_ACTIVE_PIPES, &usb_flag, 'v', MDB_OPT_SETBITS, USB_DUMP_VERBOSE, &usb_flag, NULL) != argc) { return (DCMD_USAGE); } if (usb_flag && !(DCMD_HDRSPEC(flags))) { mdb_printf("\n"); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-15s %4s %-?s %-42s%\n", "NAME", "INST", "DIP", "PATH "); } status = usba_device2dip(addr, &dip_addr); /* * -1 = error * 0 = no error, no match * 1 = no error, match */ if (status != 1) { if (status == -1) { mdb_warn("error looking for dip for usba_device %p", addr); } else { mdb_warn("failed to find dip for usba_device %p\n", addr); } mdb_warn("dip and statep unobtainable\n"); return (DCMD_ERR); } /* Figure out what driver (name) is attached to this node. */ (void) mdb_devinfo2driver(dip_addr, (char *)dname, sizeof (dname)); if (mdb_vread(&devinfo, sizeof (struct dev_info), dip_addr) == -1) { mdb_warn("failed to read devinfo"); return (DCMD_ERR); } if (!(DDI_CF2(&devinfo))) { no_driver_attached = TRUE; } (void) mdb_ddi_pathname(dip_addr, pathname, sizeof (pathname)); mdb_printf("%-15s %2d %-?p %s\n", dname, devinfo.devi_instance, dip_addr, pathname); if (usb_flag & USB_DUMP_VERBOSE) { int i; uintptr_t statep = 0; char *string_descr; char **config_cloud, **conf_str_descr; usb_dev_descr_t usb_dev_descr; usba_device_t usba_device_struct; if (mdb_vread(&usba_device_struct, sizeof (usba_device_t), addr) == -1) { mdb_warn("failed to read usba_device struct"); return (DCMD_ERR); } mdb_printf(" usba_device: %-16p\n\n", (usba_device_t *)addr); if (mdb_vread(&usb_dev_descr, sizeof (usb_dev_descr), (uintptr_t)usba_device_struct.usb_dev_descr) == -1) { mdb_warn("failed to read usb_dev_descr_t struct"); return (DCMD_ERR); } mdb_printf("\n idVendor: 0x%04x idProduct: 0x%04x " "usb_addr: 0x%02x\n", usb_dev_descr.idVendor, usb_dev_descr.idProduct, usba_device_struct.usb_addr); /* Get the string descriptor string into local space. */ string_descr = (char *)mdb_alloc(USB_MAXSTRINGLEN, UM_GC); if (usba_device_struct.usb_mfg_str == NULL) { (void) strcpy(string_descr, ""); } else { if (mdb_readstr(string_descr, USB_MAXSTRINGLEN, (uintptr_t)usba_device_struct.usb_mfg_str) == -1) { mdb_warn("failed to read manufacturer " "string descriptor"); (void) strcpy(string_descr, "???"); } } mdb_printf("\n Manufacturer String:\t%s\n", string_descr); if (usba_device_struct.usb_product_str == NULL) { (void) strcpy(string_descr, ""); } else { if (mdb_readstr(string_descr, USB_MAXSTRINGLEN, (uintptr_t)usba_device_struct.usb_product_str) == -1) { mdb_warn("failed to read product string " "descriptor"); (void) strcpy(string_descr, "???"); } } mdb_printf(" Product String:\t\t%s\n", string_descr); if (usba_device_struct.usb_serialno_str == NULL) { (void) strcpy(string_descr, ""); } else { if (mdb_readstr(string_descr, USB_MAXSTRINGLEN, (uintptr_t)usba_device_struct.usb_serialno_str) == -1) { mdb_warn("failed to read serial number string " "descriptor"); (void) strcpy(string_descr, "???"); } } mdb_printf(" SerialNumber String:\t%s\n", string_descr); if (no_driver_attached) { mdb_printf("\n"); } else { mdb_printf(" state_p: "); /* * Given the dip, find the associated statep. The * convention to generate this soft state anchor is: * _statep */ (void) mdb_snprintf(drv_statep, sizeof (drv_statep), "%s_statep", dname); if (mdb_devinfo2statep(dip_addr, drv_statep, &statep) == -1) { mdb_warn("failed to find %s state struct for " "dip %p", drv_statep, dip_addr); return (DCMD_ERR); } mdb_printf("%-?p\n", statep); } config_cloud = (char **)mdb_alloc(sizeof (void *) * usba_device_struct.usb_n_cfgs, UM_GC); conf_str_descr = (char **)mdb_alloc(sizeof (void *) * usba_device_struct.usb_n_cfgs, UM_GC); if ((usba_device_struct.usb_cfg_array) && (usba_device_struct.usb_cfg_str_descr)) { if ((mdb_vread(config_cloud, sizeof (void *) * usba_device_struct.usb_n_cfgs, (uintptr_t)usba_device_struct.usb_cfg_array) == -1) || (mdb_vread(conf_str_descr, sizeof (void *) * usba_device_struct.usb_n_cfgs, (uintptr_t) usba_device_struct.usb_cfg_str_descr)) == -1) { mdb_warn("failed to read config cloud " "pointers"); } else { mdb_printf("\n Device Config Clouds:\n" " Index\tConfig\t\tConfiguration " "String\n" " -----\t------\t\t" "--------------------\n"); for (i = 0; i < usba_device_struct.usb_n_cfgs; i++) { if (mdb_readstr(string_descr, USB_MAXSTRINGLEN, (uintptr_t)conf_str_descr[i]) == -1) { (void) strcpy(string_descr, ""); } mdb_printf(" %4d\t0x%p\t%s\n", i, config_cloud[i], string_descr); } } } mdb_printf("\n Active configuration index: %d\n", usba_device_struct.usb_active_cfg_ndx); } if (usb_flag & USB_DUMP_ACTIVE_PIPES) { if (mdb_pwalk_dcmd("usb_pipe_handle", "usb_pipe_handle", 0, NULL, addr) == -1) { mdb_warn("failed to walk usb_pipe_handle"); return (DCMD_ERR); } } return (DCMD_OK); } /* * Dump the contents of the usba_debug_buf, from the oldest to newest, * wrapping around if necessary. */ /*ARGSUSED*/ int usba_debug_buf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char *debug_buf_addr; /* addr in core */ char *local_debug_buf; /* local copy of buf */ int debug_buf_size; char *term_p; int being_cleared; if (flags & DCMD_ADDRSPEC) { return (DCMD_USAGE); } if (mdb_readvar(&being_cleared, "usba_clear_debug_buf_flag") == -1) { mdb_warn("failed to read usba_clear_debug_buf_flag"); return (DCMD_ERR); } if (being_cleared) { return (DCMD_OK); } if (mdb_readvar(&debug_buf_addr, "usba_debug_buf") == -1) { mdb_warn("failed to read usba_debug_buf"); return (DCMD_ERR); } if (debug_buf_addr == NULL) { mdb_warn("usba_debug_buf not allocated\n"); return (DCMD_OK); } if (mdb_readvar(&debug_buf_size, "usba_debug_buf_size") == -1) { mdb_warn("failed to read usba_debug_buf_size"); return (DCMD_ERR); } debug_buf_size += USB_DEBUG_SIZE_EXTRA_ALLOC; local_debug_buf = (char *)mdb_alloc(debug_buf_size, UM_SLEEP | UM_GC); if ((mdb_vread(local_debug_buf, debug_buf_size, (uintptr_t)debug_buf_addr)) == -1) { mdb_warn("failed to read usba_debug_buf at %p", local_debug_buf); return (DCMD_ERR); } local_debug_buf[debug_buf_size - 1] = '\0'; if (strlen(local_debug_buf) == 0) { return (DCMD_OK); } if ((term_p = strstr(local_debug_buf, ">>>>")) == NULL) { mdb_warn("failed to find terminator \">>>>\"\n"); return (DCMD_ERR); } /* * Print the chunk of buffer from the terminator to the end. * This will print a null string if no wrap has occurred yet. */ mdb_printf("%s", term_p+5); /* after >>>>\0 to end of buf */ mdb_printf("%s\n", local_debug_buf); /* beg of buf to >>>>\0 */ return (DCMD_OK); } /*ARGSUSED*/ int usba_clear_debug_buf( uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int clear = 1; /* stop the tracing */ if (mdb_writevar((void*)&clear, "usba_clear_debug_buf_flag") == -1) { mdb_warn("failed to set usba_clear_debug_buf_flag"); return (DCMD_ERR); } return (DCMD_OK); } /* prtusb entries */ extern int prtusb(uintptr_t, uint_t, int, const mdb_arg_t *); extern void prt_usb_usage(void); /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, and a function * named _mdb_init to return a pointer to our module information. */ static const mdb_dcmd_t dcmds[] = { { "usb_pipe_handle", ":", "print a usb_pipe_handle struct", usb_pipe_handle, NULL}, { "usba_device", ": [-pv]", "print summary info for a usba_device_t struct", usba_device, NULL}, { "usba_debug_buf", NULL, "print usba_debug_buf", usba_debug_buf, NULL}, { "usba_clear_debug_buf", NULL, "clear usba_debug_buf", usba_clear_debug_buf, NULL}, { "prtusb", "?[-t] [-v] [-i index]", "print trees and descriptors for usba_device_t", prtusb, prt_usb_usage}, { NULL } }; static const mdb_walker_t walkers[] = { /* Generic list walker. */ { "usba_list_entry", "walk list of usba_list_entry_t structures", usba_list_walk_init, usba_list_walk_step, NULL, NULL }, { "usb_pipe_handle", "walk USB pipe handles, given a usba_device_t ptr", usb_pipe_handle_walk_init, usb_pipe_handle_walk_step, NULL, NULL }, { "usba_device", "walk global list of usba_device_t structures", usba_device_walk_init, usba_list_walk_step, NULL, NULL }, { "hubd", "walk hubd instances", usba_hubd_walk_init, usba_hubd_walk_step, NULL, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2000-2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Routines extracted from usr/src/uts/common/io/usb/usba/usba.c. */ #include #include #include #include #include #include #include /* * check whether this dip is the root hub. * dip_addr is address of the devinfo struct in the core image (not local space) */ static int mdb_usba_is_root_hub(struct dev_info *dip) { uintptr_t p = (uintptr_t)dip->devi_hw_prop_ptr; while (p != 0) { ddi_prop_t prop; char prop_name[128]; if (mdb_vread(&prop, sizeof (prop), p) == -1) { mdb_warn("failed to read property"); break; } if (mdb_readstr(prop_name, sizeof (prop_name), (uintptr_t)prop.prop_name) == -1) { mdb_warn("failed to read property name"); } if (strcmp(prop_name, "root-hub") == 0) { return (1); } p = (uintptr_t)prop.prop_next; } return (0); } /* * retrieve hcdi structure from the dip * * dip_addr is address of the devinfo struct in the core image (not local space) */ uintptr_t mdb_usba_hcdi_get_hcdi(struct dev_info *dip) { return ((uintptr_t)dip->devi_driver_data); } /* * get usba_device pointer in the devi * * dip_addr is address of the devinfo struct in the core image (not local space) */ uintptr_t mdb_usba_get_usba_device(uintptr_t dip_addr) { struct dev_info devinfo; if (mdb_vread(&devinfo, sizeof (struct dev_info), dip_addr) == -1) { mdb_warn("failed to read dev_info at %p", dip_addr); return (0); } /* * we cannot use parent_data in the usb node because its * bus parent (eg. PCI nexus driver) uses this data * * we cannot use driver data in the other usb nodes since * usb drivers may need to use this */ if (mdb_usba_is_root_hub(&devinfo)) { usba_hcdi_t hcdi_struct; uintptr_t hcdi_addr = mdb_usba_hcdi_get_hcdi(&devinfo); if (!hcdi_addr) { return (0); } /* Read hcdi struct into local address space. */ if (mdb_vread(&hcdi_struct, sizeof (usba_hcdi_t), hcdi_addr) == -1) { mdb_warn("failed to read hcdi struct"); return (0); } return ((uintptr_t)hcdi_struct.hcdi_usba_device); } else { struct dev_info devinfo; if (mdb_vread(&devinfo, sizeof (struct dev_info), dip_addr) == -1) { mdb_warn("failed to read dev_info at %p", dip_addr); return (0); } /* casts needed to keep lint happy */ return ((uintptr_t)devinfo.devi_parent_data); } } #!/bin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" find_files "s.*" usr/src/uts/common/fs/zfs/sys echo_file usr/src/uts/common/sys/fs/zfs.h /* * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2011 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2011, 2018 by Delphix. All rights reserved. * Copyright 2020 Joyent, Inc. * Copyright 2025 Oxide Computer Company */ /* Portions Copyright 2010 Robert Milkowski */ /* * ZFS_MDB lets dmu.h know that we don't have dmu_ot, and we will define our * own macros to access the target's dmu_ot. Therefore it must be defined * before including any ZFS headers. Note that we don't define * DMU_OT_IS_ENCRYPTED_IMPL() or DMU_OT_BYTESWAP_IMPL(), therefore using them * will result in a compilation error. If they are needed in the future, we * can implement them similarly to mdb_dmu_ot_is_encrypted_impl(). */ #define ZFS_MDB #define DMU_OT_IS_ENCRYPTED_IMPL(ot) mdb_dmu_ot_is_encrypted_impl(ot) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _KERNEL #define ZFS_OBJ_NAME "zfs" #else #define ZFS_OBJ_NAME "libzpool.so.1" #endif extern int64_t mdb_gethrtime(void); #define ZFS_STRUCT "struct " ZFS_OBJ_NAME "`" #ifndef _KERNEL int aok; #endif enum spa_flags { SPA_FLAG_CONFIG = 1 << 0, SPA_FLAG_VDEVS = 1 << 1, SPA_FLAG_ERRORS = 1 << 2, SPA_FLAG_METASLAB_GROUPS = 1 << 3, SPA_FLAG_METASLABS = 1 << 4, SPA_FLAG_HISTOGRAMS = 1 << 5 }; /* * If any of these flags are set, call spa_vdevs in spa_print */ #define SPA_FLAG_ALL_VDEV \ (SPA_FLAG_VDEVS | SPA_FLAG_ERRORS | SPA_FLAG_METASLAB_GROUPS | \ SPA_FLAG_METASLABS) static int getmember(uintptr_t addr, const char *type, mdb_ctf_id_t *idp, const char *member, int len, void *buf) { mdb_ctf_id_t id; ulong_t off; char name[64]; if (idp == NULL) { if (mdb_ctf_lookup_by_name(type, &id) == -1) { mdb_warn("couldn't find type %s", type); return (DCMD_ERR); } idp = &id; } else { type = name; mdb_ctf_type_name(*idp, name, sizeof (name)); } if (mdb_ctf_offsetof(*idp, member, &off) == -1) { mdb_warn("couldn't find member %s of type %s\n", member, type); return (DCMD_ERR); } if (off % 8 != 0) { mdb_warn("member %s of type %s is unsupported bitfield", member, type); return (DCMD_ERR); } off /= 8; if (mdb_vread(buf, len, addr + off) == -1) { mdb_warn("failed to read %s from %s at %p", member, type, addr + off); return (DCMD_ERR); } /* mdb_warn("read %s from %s at %p+%llx\n", member, type, addr, off); */ return (0); } #define GETMEMB(addr, structname, member, dest) \ getmember(addr, ZFS_STRUCT structname, NULL, #member, \ sizeof (dest), &(dest)) #define GETMEMBID(addr, ctfid, member, dest) \ getmember(addr, NULL, ctfid, #member, sizeof (dest), &(dest)) static boolean_t strisprint(const char *cp) { for (; *cp; cp++) { if (!isprint(*cp)) return (B_FALSE); } return (B_TRUE); } /* * ::sm_entries * * Treat the buffer specified by the given address as a buffer that contains * space map entries. Iterate over the specified number of entries and print * them in both encoded and decoded form. */ /* ARGSUSED */ static int sm_entries(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint64_t bufsz = 0; boolean_t preview = B_FALSE; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (argc < 1) { preview = B_TRUE; bufsz = 2; } else if (argc != 1) { return (DCMD_USAGE); } else { switch (argv[0].a_type) { case MDB_TYPE_STRING: bufsz = mdb_strtoull(argv[0].a_un.a_str); break; case MDB_TYPE_IMMEDIATE: bufsz = argv[0].a_un.a_val; break; default: return (DCMD_USAGE); } } char *actions[] = { "ALLOC", "FREE", "INVALID" }; for (uintptr_t bufend = addr + bufsz; addr < bufend; addr += sizeof (uint64_t)) { uint64_t nwords; uint64_t start_addr = addr; uint64_t word = 0; if (mdb_vread(&word, sizeof (word), addr) == -1) { mdb_warn("failed to read space map entry %p", addr); return (DCMD_ERR); } if (SM_PREFIX_DECODE(word) == SM_DEBUG_PREFIX) { (void) mdb_printf("\t [%6llu] %s: txg %llu, " "pass %llu\n", (u_longlong_t)(addr), actions[SM_DEBUG_ACTION_DECODE(word)], (u_longlong_t)SM_DEBUG_TXG_DECODE(word), (u_longlong_t)SM_DEBUG_SYNCPASS_DECODE(word)); continue; } char entry_type; uint64_t raw_offset, raw_run, vdev_id = SM_NO_VDEVID; if (SM_PREFIX_DECODE(word) != SM2_PREFIX) { entry_type = (SM_TYPE_DECODE(word) == SM_ALLOC) ? 'A' : 'F'; raw_offset = SM_OFFSET_DECODE(word); raw_run = SM_RUN_DECODE(word); nwords = 1; } else { ASSERT3U(SM_PREFIX_DECODE(word), ==, SM2_PREFIX); raw_run = SM2_RUN_DECODE(word); vdev_id = SM2_VDEV_DECODE(word); /* it is a two-word entry so we read another word */ addr += sizeof (uint64_t); if (addr >= bufend) { mdb_warn("buffer ends in the middle of a two " "word entry\n", addr); return (DCMD_ERR); } if (mdb_vread(&word, sizeof (word), addr) == -1) { mdb_warn("failed to read space map entry %p", addr); return (DCMD_ERR); } entry_type = (SM2_TYPE_DECODE(word) == SM_ALLOC) ? 'A' : 'F'; raw_offset = SM2_OFFSET_DECODE(word); nwords = 2; } (void) mdb_printf("\t [%6llx] %c range:" " %010llx-%010llx size: %06llx vdev: %06llu words: %llu\n", (u_longlong_t)start_addr, entry_type, (u_longlong_t)raw_offset, (u_longlong_t)(raw_offset + raw_run), (u_longlong_t)raw_run, (u_longlong_t)vdev_id, (u_longlong_t)nwords); if (preview) break; } return (DCMD_OK); } static int mdb_dsl_dir_name(uintptr_t addr, char *buf) { static int gotid; static mdb_ctf_id_t dd_id; uintptr_t dd_parent; char dd_myname[ZFS_MAX_DATASET_NAME_LEN]; if (!gotid) { if (mdb_ctf_lookup_by_name(ZFS_STRUCT "dsl_dir", &dd_id) == -1) { mdb_warn("couldn't find struct dsl_dir"); return (DCMD_ERR); } gotid = TRUE; } if (GETMEMBID(addr, &dd_id, dd_parent, dd_parent) || GETMEMBID(addr, &dd_id, dd_myname, dd_myname)) { return (DCMD_ERR); } if (dd_parent) { if (mdb_dsl_dir_name(dd_parent, buf)) return (DCMD_ERR); strcat(buf, "/"); } if (dd_myname[0]) strcat(buf, dd_myname); else strcat(buf, "???"); return (0); } static int objset_name(uintptr_t addr, char *buf) { static int gotid; static mdb_ctf_id_t os_id, ds_id; uintptr_t os_dsl_dataset; char ds_snapname[ZFS_MAX_DATASET_NAME_LEN]; uintptr_t ds_dir; buf[0] = '\0'; if (!gotid) { if (mdb_ctf_lookup_by_name(ZFS_STRUCT "objset", &os_id) == -1) { mdb_warn("couldn't find struct objset"); return (DCMD_ERR); } if (mdb_ctf_lookup_by_name(ZFS_STRUCT "dsl_dataset", &ds_id) == -1) { mdb_warn("couldn't find struct dsl_dataset"); return (DCMD_ERR); } gotid = TRUE; } if (GETMEMBID(addr, &os_id, os_dsl_dataset, os_dsl_dataset)) return (DCMD_ERR); if (os_dsl_dataset == 0) { strcat(buf, "mos"); return (0); } if (GETMEMBID(os_dsl_dataset, &ds_id, ds_snapname, ds_snapname) || GETMEMBID(os_dsl_dataset, &ds_id, ds_dir, ds_dir)) { return (DCMD_ERR); } if (ds_dir && mdb_dsl_dir_name(ds_dir, buf)) return (DCMD_ERR); if (ds_snapname[0]) { strcat(buf, "@"); strcat(buf, ds_snapname); } return (0); } static int enum_lookup(char *type, int val, const char *prefix, size_t size, char *out) { const char *cp; size_t len = strlen(prefix); mdb_ctf_id_t enum_type; if (mdb_ctf_lookup_by_name(type, &enum_type) != 0) { mdb_warn("Could not find enum for %s", type); return (-1); } if ((cp = mdb_ctf_enum_name(enum_type, val)) != NULL) { if (strncmp(cp, prefix, len) == 0) cp += len; (void) strncpy(out, cp, size); } else { mdb_snprintf(out, size, "? (%d)", val); } return (0); } /* ARGSUSED */ static int zfs_params(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { /* * This table can be approximately generated by running: * egrep "^[a-z0-9_]+ [a-z0-9_]+( =.*)?;" *.c | cut -d ' ' -f 2 */ static const char *params[] = { "arc_lotsfree_percent", "arc_pages_pp_reserve", "arc_reduce_dnlc_percent", "arc_swapfs_reserve", "arc_zio_arena_free_shift", "dbuf_cache_hiwater_pct", "dbuf_cache_lowater_pct", "dbuf_cache_max_bytes", "dbuf_cache_max_shift", "ddt_zap_indirect_blockshift", "ddt_zap_leaf_blockshift", "ditto_same_vdev_distance_shift", "dmu_find_threads", "dmu_rescan_dnode_threshold", "dsl_scan_delay_completion", "fzap_default_block_shift", "l2arc_feed_again", "l2arc_feed_min_ms", "l2arc_feed_secs", "l2arc_headroom", "l2arc_headroom_boost", "l2arc_noprefetch", "l2arc_norw", "l2arc_write_boost", "l2arc_write_max", "metaslab_aliquot", "metaslab_bias_enabled", "metaslab_debug_load", "metaslab_debug_unload", "metaslab_df_alloc_threshold", "metaslab_df_free_pct", "metaslab_fragmentation_factor_enabled", "metaslab_force_ganging", "metaslab_lba_weighting_enabled", "metaslab_load_pct", "metaslab_min_alloc_size", "metaslab_ndf_clump_shift", "metaslab_preload_enabled", "metaslab_preload_limit", "metaslab_trace_enabled", "metaslab_trace_max_entries", "metaslab_unload_delay", "metaslabs_per_vdev", "reference_history", "reference_tracking_enable", "send_holes_without_birth_time", "spa_asize_inflation", "spa_load_verify_data", "spa_load_verify_maxinflight", "spa_load_verify_metadata", "spa_max_replication_override", "spa_min_slop", "spa_mode_global", "spa_slop_shift", "space_map_blksz", "vdev_mirror_shift", "zfetch_max_distance", "zfs_abd_chunk_size", "zfs_abd_scatter_enabled", "zfs_arc_average_blocksize", "zfs_arc_evict_batch_limit", "zfs_arc_grow_retry", "zfs_arc_max", "zfs_arc_meta_limit", "zfs_arc_meta_min", "zfs_arc_min", "zfs_arc_p_min_shift", "zfs_arc_shrink_shift", "zfs_async_block_max_blocks", "zfs_ccw_retry_interval", "zfs_commit_timeout_pct", "zfs_compressed_arc_enabled", "zfs_condense_indirect_commit_entry_delay_ticks", "zfs_condense_indirect_vdevs_enable", "zfs_condense_max_obsolete_bytes", "zfs_condense_min_mapping_bytes", "zfs_condense_pct", "zfs_dbgmsg_maxsize", "zfs_deadman_checktime_ms", "zfs_deadman_enabled", "zfs_deadman_synctime_ms", "zfs_dedup_prefetch", "zfs_default_bs", "zfs_default_ibs", "zfs_delay_max_ns", "zfs_delay_min_dirty_percent", "zfs_delay_resolution_ns", "zfs_delay_scale", "zfs_dirty_data_max", "zfs_dirty_data_max_max", "zfs_dirty_data_max_percent", "zfs_dirty_data_sync", "zfs_flags", "zfs_free_bpobj_enabled", "zfs_free_leak_on_eio", "zfs_free_min_time_ms", "zfs_immediate_write_sz", "zfs_indirect_condense_obsolete_pct", "zfs_lua_check_instrlimit_interval", "zfs_lua_max_instrlimit", "zfs_lua_max_memlimit", "zfs_max_recordsize", "zfs_mdcomp_disable", "zfs_metaslab_condense_block_threshold", "zfs_metaslab_fragmentation_threshold", "zfs_metaslab_segment_weight_enabled", "zfs_metaslab_switch_threshold", "zfs_mg_fragmentation_threshold", "zfs_mg_noalloc_threshold", "zfs_multilist_num_sublists", "zfs_no_scrub_io", "zfs_no_scrub_prefetch", "zfs_nocacheflush", "zfs_nopwrite_enabled", "zfs_object_remap_one_indirect_delay_ticks", "zfs_obsolete_min_time_ms", "zfs_pd_bytes_max", "zfs_per_txg_dirty_frees_percent", "zfs_prefetch_disable", "zfs_read_chunk_size", "zfs_recover", "zfs_recv_queue_length", "zfs_redundant_metadata_most_ditto_level", "zfs_remap_blkptr_enable", "zfs_remove_max_copy_bytes", "zfs_remove_max_segment", "zfs_resilver_min_time_ms", "zfs_scan_min_time_ms", "zfs_scrub_limit", "zfs_send_corrupt_data", "zfs_send_queue_length", "zfs_send_set_freerecords_bit", "zfs_sync_pass_deferred_free", "zfs_sync_pass_dont_compress", "zfs_sync_pass_rewrite", "zfs_sync_taskq_batch_pct", "zfs_top_maxinflight", "zfs_txg_timeout", "zfs_vdev_aggregation_limit", "zfs_vdev_async_read_max_active", "zfs_vdev_async_read_min_active", "zfs_vdev_async_write_active_max_dirty_percent", "zfs_vdev_async_write_active_min_dirty_percent", "zfs_vdev_async_write_max_active", "zfs_vdev_async_write_min_active", "zfs_vdev_cache_bshift", "zfs_vdev_cache_max", "zfs_vdev_cache_size", "zfs_vdev_max_active", "zfs_vdev_queue_depth_pct", "zfs_vdev_read_gap_limit", "zfs_vdev_removal_max_active", "zfs_vdev_removal_min_active", "zfs_vdev_scrub_max_active", "zfs_vdev_scrub_min_active", "zfs_vdev_sync_read_max_active", "zfs_vdev_sync_read_min_active", "zfs_vdev_sync_write_max_active", "zfs_vdev_sync_write_min_active", "zfs_vdev_write_gap_limit", "zfs_write_implies_delete_child", "zfs_zil_clean_taskq_maxalloc", "zfs_zil_clean_taskq_minalloc", "zfs_zil_clean_taskq_nthr_pct", "zil_replay_disable", "zil_slog_bulk", "zio_buf_debug_limit", "zio_dva_throttle_enabled", "zio_injection_enabled", "zvol_immediate_write_sz", "zvol_maxphys", "zvol_unmap_enabled", "zvol_unmap_sync_enabled", "zfs_max_dataset_nesting", }; for (int i = 0; i < sizeof (params) / sizeof (params[0]); i++) { int sz; uint64_t val64; uint32_t *val32p = (uint32_t *)&val64; sz = mdb_readvar(&val64, params[i]); if (sz == 4) { mdb_printf("%s = 0x%x\n", params[i], *val32p); } else if (sz == 8) { mdb_printf("%s = 0x%llx\n", params[i], val64); } else { mdb_warn("variable %s not found", params[i]); } } return (DCMD_OK); } /* ARGSUSED */ static int dva(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dva_t dva; if (mdb_vread(&dva, sizeof (dva_t), addr) == -1) { mdb_warn("failed to read dva_t"); return (DCMD_ERR); } mdb_printf("<%llu:%llx:%llx>\n", (u_longlong_t)DVA_GET_VDEV(&dva), (u_longlong_t)DVA_GET_OFFSET(&dva), (u_longlong_t)DVA_GET_ASIZE(&dva)); return (DCMD_OK); } typedef struct mdb_dmu_object_type_info { boolean_t ot_encrypt; } mdb_dmu_object_type_info_t; static boolean_t mdb_dmu_ot_is_encrypted_impl(dmu_object_type_t ot) { mdb_dmu_object_type_info_t mdoti; GElf_Sym sym; size_t sz = mdb_ctf_sizeof_by_name("dmu_object_type_info_t"); if (mdb_lookup_by_obj(ZFS_OBJ_NAME, "dmu_ot", &sym)) { mdb_warn("failed to find " ZFS_OBJ_NAME "`dmu_ot"); return (B_FALSE); } if (mdb_ctf_vread(&mdoti, "dmu_object_type_info_t", "mdb_dmu_object_type_info_t", sym.st_value + sz * ot, 0) != 0) { return (B_FALSE); } return (mdoti.ot_encrypt); } /* ARGSUSED */ static int blkptr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char type[80], checksum[80], compress[80]; blkptr_t blk, *bp = &blk; char buf[BP_SPRINTF_LEN]; if (mdb_vread(&blk, sizeof (blkptr_t), addr) == -1) { mdb_warn("failed to read blkptr_t"); return (DCMD_ERR); } if (enum_lookup("enum dmu_object_type", BP_GET_TYPE(bp), "DMU_OT_", sizeof (type), type) == -1 || enum_lookup("enum zio_checksum", BP_GET_CHECKSUM(bp), "ZIO_CHECKSUM_", sizeof (checksum), checksum) == -1 || enum_lookup("enum zio_compress", BP_GET_COMPRESS(bp), "ZIO_COMPRESS_", sizeof (compress), compress) == -1) { mdb_warn("Could not find blkptr enumerated types"); return (DCMD_ERR); } SNPRINTF_BLKPTR(mdb_snprintf, '\n', buf, sizeof (buf), bp, type, checksum, compress); mdb_printf("%s\n", buf); return (DCMD_OK); } typedef struct mdb_dmu_buf_impl { struct { uint64_t db_object; uintptr_t db_data; } db; uintptr_t db_objset; uint64_t db_level; uint64_t db_blkid; struct { uint64_t rc_count; } db_holds; } mdb_dmu_buf_impl_t; /* ARGSUSED */ static int dbuf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_dmu_buf_impl_t db; char objectname[32]; char blkidname[32]; char path[ZFS_MAX_DATASET_NAME_LEN]; int ptr_width = (int)(sizeof (void *)) * 2; if (DCMD_HDRSPEC(flags)) mdb_printf("%*s %8s %3s %9s %5s %s\n", ptr_width, "addr", "object", "lvl", "blkid", "holds", "os"); if (mdb_ctf_vread(&db, ZFS_STRUCT "dmu_buf_impl", "mdb_dmu_buf_impl_t", addr, 0) == -1) return (DCMD_ERR); if (db.db.db_object == DMU_META_DNODE_OBJECT) (void) strcpy(objectname, "mdn"); else (void) mdb_snprintf(objectname, sizeof (objectname), "%llx", (u_longlong_t)db.db.db_object); if (db.db_blkid == DMU_BONUS_BLKID) (void) strcpy(blkidname, "bonus"); else (void) mdb_snprintf(blkidname, sizeof (blkidname), "%llx", (u_longlong_t)db.db_blkid); if (objset_name(db.db_objset, path)) { return (DCMD_ERR); } mdb_printf("%*p %8s %3u %9s %5llu %s\n", ptr_width, addr, objectname, (int)db.db_level, blkidname, db.db_holds.rc_count, path); return (DCMD_OK); } /* ARGSUSED */ static int dbuf_stats(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { #define HISTOSZ 32 uintptr_t dbp; dmu_buf_impl_t db; dbuf_hash_table_t ht; uint64_t bucket, ndbufs; uint64_t histo[HISTOSZ]; uint64_t histo2[HISTOSZ]; int i, maxidx; if (mdb_readvar(&ht, "dbuf_hash_table") == -1) { mdb_warn("failed to read 'dbuf_hash_table'"); return (DCMD_ERR); } for (i = 0; i < HISTOSZ; i++) { histo[i] = 0; histo2[i] = 0; } ndbufs = 0; for (bucket = 0; bucket < ht.hash_table_mask+1; bucket++) { int len; if (mdb_vread(&dbp, sizeof (void *), (uintptr_t)(ht.hash_table+bucket)) == -1) { mdb_warn("failed to read hash bucket %u at %p", bucket, ht.hash_table+bucket); return (DCMD_ERR); } len = 0; while (dbp != 0) { if (mdb_vread(&db, sizeof (dmu_buf_impl_t), dbp) == -1) { mdb_warn("failed to read dbuf at %p", dbp); return (DCMD_ERR); } dbp = (uintptr_t)db.db_hash_next; for (i = MIN(len, HISTOSZ - 1); i >= 0; i--) histo2[i]++; len++; ndbufs++; } if (len >= HISTOSZ) len = HISTOSZ-1; histo[len]++; } mdb_printf("hash table has %llu buckets, %llu dbufs " "(avg %llu buckets/dbuf)\n", ht.hash_table_mask+1, ndbufs, (ht.hash_table_mask+1)/ndbufs); mdb_printf("\n"); maxidx = 0; for (i = 0; i < HISTOSZ; i++) if (histo[i] > 0) maxidx = i; mdb_printf("hash chain length number of buckets\n"); for (i = 0; i <= maxidx; i++) mdb_printf("%u %llu\n", i, histo[i]); mdb_printf("\n"); maxidx = 0; for (i = 0; i < HISTOSZ; i++) if (histo2[i] > 0) maxidx = i; mdb_printf("hash chain depth number of dbufs\n"); for (i = 0; i <= maxidx; i++) mdb_printf("%u or more %llu %llu%%\n", i, histo2[i], histo2[i]*100/ndbufs); return (DCMD_OK); } #define CHAIN_END 0xffff /* * ::zap_leaf [-v] * * Print a zap_leaf_phys_t, assumed to be 16k */ /* ARGSUSED */ static int zap_leaf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char buf[16*1024]; int verbose = B_FALSE; int four = B_FALSE; dmu_buf_t l_dbuf; zap_leaf_t l; zap_leaf_phys_t *zlp = (void *)buf; int i; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, '4', MDB_OPT_SETBITS, TRUE, &four, NULL) != argc) return (DCMD_USAGE); l_dbuf.db_data = zlp; l.l_dbuf = &l_dbuf; l.l_bs = 14; /* assume 16k blocks */ if (four) l.l_bs = 12; if (!(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } if (mdb_vread(buf, sizeof (buf), addr) == -1) { mdb_warn("failed to read zap_leaf_phys_t at %p", addr); return (DCMD_ERR); } if (zlp->l_hdr.lh_block_type != ZBT_LEAF || zlp->l_hdr.lh_magic != ZAP_LEAF_MAGIC) { mdb_warn("This does not appear to be a zap_leaf_phys_t"); return (DCMD_ERR); } mdb_printf("zap_leaf_phys_t at %p:\n", addr); mdb_printf(" lh_prefix_len = %u\n", zlp->l_hdr.lh_prefix_len); mdb_printf(" lh_prefix = %llx\n", zlp->l_hdr.lh_prefix); mdb_printf(" lh_nentries = %u\n", zlp->l_hdr.lh_nentries); mdb_printf(" lh_nfree = %u\n", zlp->l_hdr.lh_nfree, zlp->l_hdr.lh_nfree * 100 / (ZAP_LEAF_NUMCHUNKS(&l))); mdb_printf(" lh_freelist = %u\n", zlp->l_hdr.lh_freelist); mdb_printf(" lh_flags = %x (%s)\n", zlp->l_hdr.lh_flags, zlp->l_hdr.lh_flags & ZLF_ENTRIES_CDSORTED ? "ENTRIES_CDSORTED" : ""); if (verbose) { mdb_printf(" hash table:\n"); for (i = 0; i < ZAP_LEAF_HASH_NUMENTRIES(&l); i++) { if (zlp->l_hash[i] != CHAIN_END) mdb_printf(" %u: %u\n", i, zlp->l_hash[i]); } } mdb_printf(" chunks:\n"); for (i = 0; i < ZAP_LEAF_NUMCHUNKS(&l); i++) { /* LINTED: alignment */ zap_leaf_chunk_t *zlc = &ZAP_LEAF_CHUNK(&l, i); switch (zlc->l_entry.le_type) { case ZAP_CHUNK_FREE: if (verbose) { mdb_printf(" %u: free; lf_next = %u\n", i, zlc->l_free.lf_next); } break; case ZAP_CHUNK_ENTRY: mdb_printf(" %u: entry\n", i); if (verbose) { mdb_printf(" le_next = %u\n", zlc->l_entry.le_next); } mdb_printf(" le_name_chunk = %u\n", zlc->l_entry.le_name_chunk); mdb_printf(" le_name_numints = %u\n", zlc->l_entry.le_name_numints); mdb_printf(" le_value_chunk = %u\n", zlc->l_entry.le_value_chunk); mdb_printf(" le_value_intlen = %u\n", zlc->l_entry.le_value_intlen); mdb_printf(" le_value_numints = %u\n", zlc->l_entry.le_value_numints); mdb_printf(" le_cd = %u\n", zlc->l_entry.le_cd); mdb_printf(" le_hash = %llx\n", zlc->l_entry.le_hash); break; case ZAP_CHUNK_ARRAY: mdb_printf(" %u: array", i); if (strisprint((char *)zlc->l_array.la_array)) mdb_printf(" \"%s\"", zlc->l_array.la_array); mdb_printf("\n"); if (verbose) { int j; mdb_printf(" "); for (j = 0; j < ZAP_LEAF_ARRAY_BYTES; j++) { mdb_printf("%02x ", zlc->l_array.la_array[j]); } mdb_printf("\n"); } if (zlc->l_array.la_next != CHAIN_END) { mdb_printf(" lf_next = %u\n", zlc->l_array.la_next); } break; default: mdb_printf(" %u: undefined type %u\n", zlc->l_entry.le_type); } } return (DCMD_OK); } typedef struct dbufs_data { mdb_ctf_id_t id; uint64_t objset; uint64_t object; uint64_t level; uint64_t blkid; char *osname; } dbufs_data_t; #define DBUFS_UNSET (0xbaddcafedeadbeefULL) /* ARGSUSED */ static int dbufs_cb(uintptr_t addr, const void *unknown, void *arg) { dbufs_data_t *data = arg; uintptr_t objset; dmu_buf_t db; uint8_t level; uint64_t blkid; char osname[ZFS_MAX_DATASET_NAME_LEN]; if (GETMEMBID(addr, &data->id, db_objset, objset) || GETMEMBID(addr, &data->id, db, db) || GETMEMBID(addr, &data->id, db_level, level) || GETMEMBID(addr, &data->id, db_blkid, blkid)) { return (WALK_ERR); } if ((data->objset == DBUFS_UNSET || data->objset == objset) && (data->osname == NULL || (objset_name(objset, osname) == 0 && strcmp(data->osname, osname) == 0)) && (data->object == DBUFS_UNSET || data->object == db.db_object) && (data->level == DBUFS_UNSET || data->level == level) && (data->blkid == DBUFS_UNSET || data->blkid == blkid)) { mdb_printf("%#lr\n", addr); } return (WALK_NEXT); } /* ARGSUSED */ static int dbufs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { dbufs_data_t data; char *object = NULL; char *blkid = NULL; data.objset = data.object = data.level = data.blkid = DBUFS_UNSET; data.osname = NULL; if (mdb_getopts(argc, argv, 'O', MDB_OPT_UINT64, &data.objset, 'n', MDB_OPT_STR, &data.osname, 'o', MDB_OPT_STR, &object, 'l', MDB_OPT_UINT64, &data.level, 'b', MDB_OPT_STR, &blkid, NULL) != argc) { return (DCMD_USAGE); } if (object) { if (strcmp(object, "mdn") == 0) { data.object = DMU_META_DNODE_OBJECT; } else { data.object = mdb_strtoull(object); } } if (blkid) { if (strcmp(blkid, "bonus") == 0) { data.blkid = DMU_BONUS_BLKID; } else { data.blkid = mdb_strtoull(blkid); } } if (mdb_ctf_lookup_by_name(ZFS_STRUCT "dmu_buf_impl", &data.id) == -1) { mdb_warn("couldn't find struct dmu_buf_impl_t"); return (DCMD_ERR); } if (mdb_walk("dmu_buf_impl_t", dbufs_cb, &data) != 0) { mdb_warn("can't walk dbufs"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct abuf_find_data { dva_t dva; mdb_ctf_id_t id; } abuf_find_data_t; /* ARGSUSED */ static int abuf_find_cb(uintptr_t addr, const void *unknown, void *arg) { abuf_find_data_t *data = arg; dva_t dva; if (GETMEMBID(addr, &data->id, b_dva, dva)) { return (WALK_ERR); } if (dva.dva_word[0] == data->dva.dva_word[0] && dva.dva_word[1] == data->dva.dva_word[1]) { mdb_printf("%#lr\n", addr); } return (WALK_NEXT); } typedef struct mdb_arc_state { uintptr_t arcs_list[ARC_BUFC_NUMTYPES]; } mdb_arc_state_t; /* ARGSUSED */ static int abuf_find(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { abuf_find_data_t data; GElf_Sym sym; int i, j; const char *syms[] = { "ARC_mru", "ARC_mru_ghost", "ARC_mfu", "ARC_mfu_ghost", }; if (argc != 2) return (DCMD_USAGE); for (i = 0; i < 2; i ++) { switch (argv[i].a_type) { case MDB_TYPE_STRING: data.dva.dva_word[i] = mdb_strtoull(argv[i].a_un.a_str); break; case MDB_TYPE_IMMEDIATE: data.dva.dva_word[i] = argv[i].a_un.a_val; break; default: return (DCMD_USAGE); } } if (mdb_ctf_lookup_by_name(ZFS_STRUCT "arc_buf_hdr", &data.id) == -1) { mdb_warn("couldn't find struct arc_buf_hdr"); return (DCMD_ERR); } for (i = 0; i < sizeof (syms) / sizeof (syms[0]); i++) { mdb_arc_state_t mas; if (mdb_lookup_by_obj(ZFS_OBJ_NAME, syms[i], &sym)) { mdb_warn("can't find symbol %s", syms[i]); return (DCMD_ERR); } if (mdb_ctf_vread(&mas, "arc_state_t", "mdb_arc_state_t", sym.st_value, 0) != 0) { mdb_warn("can't read arcs_list of %s", syms[i]); return (DCMD_ERR); } for (j = 0; j < ARC_BUFC_NUMTYPES; j++) { uintptr_t addr = mas.arcs_list[j]; if (addr == 0) continue; if (mdb_pwalk("multilist", abuf_find_cb, &data, addr) != 0) { mdb_warn("can't walk %s", syms[i]); return (DCMD_ERR); } } } return (DCMD_OK); } typedef struct dbgmsg_arg { boolean_t da_address; boolean_t da_hrtime; boolean_t da_timedelta; boolean_t da_time; boolean_t da_whatis; hrtime_t da_curtime; } dbgmsg_arg_t; static int dbgmsg_cb(uintptr_t addr, const void *unknown __unused, void *arg) { static mdb_ctf_id_t id; static boolean_t gotid; static ulong_t off; dbgmsg_arg_t *da = arg; time_t timestamp; hrtime_t hrtime; char buf[1024]; if (!gotid) { if (mdb_ctf_lookup_by_name(ZFS_STRUCT "zfs_dbgmsg", &id) == -1) { mdb_warn("couldn't find struct zfs_dbgmsg"); return (WALK_ERR); } gotid = TRUE; if (mdb_ctf_offsetof(id, "zdm_msg", &off) == -1) { mdb_warn("couldn't find zdm_msg"); return (WALK_ERR); } off /= 8; } if (GETMEMBID(addr, &id, zdm_timestamp, timestamp)) { return (WALK_ERR); } if (da->da_hrtime || da->da_timedelta) { if (GETMEMBID(addr, &id, zdm_hrtime, hrtime)) { return (WALK_ERR); } } if (mdb_readstr(buf, sizeof (buf), addr + off) == -1) { mdb_warn("failed to read zdm_msg at %p\n", addr + off); return (DCMD_ERR); } if (da->da_address) mdb_printf("%p ", addr); if (da->da_timedelta) { int64_t diff; char dbuf[32] = { 0 }; if (da->da_curtime == 0) da->da_curtime = mdb_gethrtime(); diff = (int64_t)hrtime - da->da_curtime; mdb_nicetime(diff, dbuf, sizeof (dbuf)); mdb_printf("%-20s ", dbuf); } else if (da->da_hrtime) { mdb_printf("%016x ", hrtime); } else if (da->da_time) { mdb_printf("%Y ", timestamp); } mdb_printf("%s\n", buf); if (da->da_whatis) (void) mdb_call_dcmd("whatis", addr, DCMD_ADDRSPEC, 0, NULL); return (WALK_NEXT); } static int dbgmsg(uintptr_t addr, uint_t flags __unused, int argc, const mdb_arg_t *argv) { GElf_Sym sym; dbgmsg_arg_t da = { 0 }; boolean_t verbose = B_FALSE; if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, B_TRUE, &da.da_address, 'r', MDB_OPT_SETBITS, B_TRUE, &da.da_hrtime, 't', MDB_OPT_SETBITS, B_TRUE, &da.da_timedelta, 'T', MDB_OPT_SETBITS, B_TRUE, &da.da_time, 'v', MDB_OPT_SETBITS, B_TRUE, &verbose, 'w', MDB_OPT_SETBITS, B_TRUE, &da.da_whatis, NULL) != argc) { return (DCMD_USAGE); } if (verbose) da.da_address = da.da_time = B_TRUE; if (mdb_lookup_by_obj(ZFS_OBJ_NAME, "zfs_dbgmsgs", &sym)) { mdb_warn("can't find zfs_dbgmsgs"); return (DCMD_ERR); } if (mdb_pwalk("list", dbgmsg_cb, &da, sym.st_value) != 0) { mdb_warn("can't walk zfs_dbgmsgs"); return (DCMD_ERR); } return (DCMD_OK); } static void dbgmsg_help(void) { mdb_printf("Print entries from the ZFS debug log.\n\n" "%OPTIONS%\n" "\t-a\tInclude the address of each zfs_dbgmsg_t.\n" "\t-r\tDisplay high-resolution timestamps.\n" "\t-t\tInclude the age of the message.\n" "\t-T\tInclude the date/time of the message.\n" "\t-v\tEquivalent to -aT.\n" "\t-w\tRun ::whatis on each zfs_dbgmsg_t. Useful in DEBUG kernels\n" "\t\tto show the origin of the message.\n"); } /*ARGSUSED*/ static int arc_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { kstat_named_t *stats; GElf_Sym sym; int nstats, i; uint_t opt_a = FALSE; uint_t opt_b = FALSE; uint_t shift = 0; const char *suffix; static const char *bytestats[] = { "p", "c", "c_min", "c_max", "size", "duplicate_buffers_size", "arc_meta_used", "arc_meta_limit", "arc_meta_max", "arc_meta_min", "hdr_size", "data_size", "metadata_size", "other_size", "anon_size", "anon_evictable_data", "anon_evictable_metadata", "mru_size", "mru_evictable_data", "mru_evictable_metadata", "mru_ghost_size", "mru_ghost_evictable_data", "mru_ghost_evictable_metadata", "mfu_size", "mfu_evictable_data", "mfu_evictable_metadata", "mfu_ghost_size", "mfu_ghost_evictable_data", "mfu_ghost_evictable_metadata", "evict_l2_cached", "evict_l2_eligible", "evict_l2_ineligible", "l2_read_bytes", "l2_write_bytes", "l2_size", "l2_asize", "l2_hdr_size", "compressed_size", "uncompressed_size", "overhead_size", NULL }; static const char *extras[] = { "arc_no_grow", "arc_tempreserve", NULL }; if (mdb_lookup_by_obj(ZFS_OBJ_NAME, "arc_stats", &sym) == -1) { mdb_warn("failed to find 'arc_stats'"); return (DCMD_ERR); } stats = mdb_zalloc(sym.st_size, UM_SLEEP | UM_GC); if (mdb_vread(stats, sym.st_size, sym.st_value) == -1) { mdb_warn("couldn't read 'arc_stats' at %p", sym.st_value); return (DCMD_ERR); } nstats = sym.st_size / sizeof (kstat_named_t); /* NB: -a / opt_a are ignored for backwards compatability */ if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &opt_a, 'b', MDB_OPT_SETBITS, TRUE, &opt_b, 'k', MDB_OPT_SETBITS, 10, &shift, 'm', MDB_OPT_SETBITS, 20, &shift, 'g', MDB_OPT_SETBITS, 30, &shift, NULL) != argc) return (DCMD_USAGE); if (!opt_b && !shift) shift = 20; switch (shift) { case 0: suffix = "B"; break; case 10: suffix = "KB"; break; case 20: suffix = "MB"; break; case 30: suffix = "GB"; break; default: suffix = "XX"; } for (i = 0; i < nstats; i++) { int j; boolean_t bytes = B_FALSE; for (j = 0; bytestats[j]; j++) { if (strcmp(stats[i].name, bytestats[j]) == 0) { bytes = B_TRUE; break; } } if (bytes) { mdb_printf("%-25s = %9llu %s\n", stats[i].name, stats[i].value.ui64 >> shift, suffix); } else { mdb_printf("%-25s = %9llu\n", stats[i].name, stats[i].value.ui64); } } for (i = 0; extras[i]; i++) { uint64_t buf; if (mdb_lookup_by_obj(ZFS_OBJ_NAME, extras[i], &sym) == -1) { mdb_warn("failed to find '%s'", extras[i]); return (DCMD_ERR); } if (sym.st_size != sizeof (uint64_t) && sym.st_size != sizeof (uint32_t)) { mdb_warn("expected scalar for variable '%s'\n", extras[i]); return (DCMD_ERR); } if (mdb_vread(&buf, sym.st_size, sym.st_value) == -1) { mdb_warn("couldn't read '%s'", extras[i]); return (DCMD_ERR); } mdb_printf("%-25s = ", extras[i]); /* NB: all the 64-bit extras happen to be byte counts */ if (sym.st_size == sizeof (uint64_t)) mdb_printf("%9llu %s\n", buf >> shift, suffix); if (sym.st_size == sizeof (uint32_t)) mdb_printf("%9d\n", *((uint32_t *)&buf)); } return (DCMD_OK); } typedef struct mdb_spa_print { pool_state_t spa_state; char spa_name[ZFS_MAX_DATASET_NAME_LEN]; uintptr_t spa_normal_class; } mdb_spa_print_t; const char histo_stars[] = "****************************************"; const int histo_width = sizeof (histo_stars) - 1; static void dump_histogram(const uint64_t *histo, int size, int offset) { int i; int minidx = size - 1; int maxidx = 0; uint64_t max = 0; for (i = 0; i < size; i++) { if (histo[i] > max) max = histo[i]; if (histo[i] > 0 && i > maxidx) maxidx = i; if (histo[i] > 0 && i < minidx) minidx = i; } if (max < histo_width) max = histo_width; for (i = minidx; i <= maxidx; i++) { mdb_printf("%3u: %6llu %s\n", i + offset, (u_longlong_t)histo[i], &histo_stars[(max - histo[i]) * histo_width / max]); } } typedef struct mdb_metaslab_class { uint64_t mc_histogram[RANGE_TREE_HISTOGRAM_SIZE]; } mdb_metaslab_class_t; /* * spa_class_histogram(uintptr_t class_addr) * * Prints free space histogram for a device class * * Returns DCMD_OK, or DCMD_ERR. */ static int spa_class_histogram(uintptr_t class_addr) { mdb_metaslab_class_t mc; if (mdb_ctf_vread(&mc, "metaslab_class_t", "mdb_metaslab_class_t", class_addr, 0) == -1) return (DCMD_ERR); mdb_inc_indent(4); dump_histogram(mc.mc_histogram, RANGE_TREE_HISTOGRAM_SIZE, 0); mdb_dec_indent(4); return (DCMD_OK); } /* * ::spa * * -c Print configuration information as well * -v Print vdev state * -e Print vdev error stats * -m Print vdev metaslab info * -M print vdev metaslab group info * -h Print histogram info (must be combined with -m or -M) * * Print a summarized spa_t. When given no arguments, prints out a table of all * active pools on the system. */ /* ARGSUSED */ static int spa_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { const char *statetab[] = { "ACTIVE", "EXPORTED", "DESTROYED", "SPARE", "L2CACHE", "UNINIT", "UNAVAIL", "POTENTIAL" }; const char *state; int spa_flags = 0; if (mdb_getopts(argc, argv, 'c', MDB_OPT_SETBITS, SPA_FLAG_CONFIG, &spa_flags, 'v', MDB_OPT_SETBITS, SPA_FLAG_VDEVS, &spa_flags, 'e', MDB_OPT_SETBITS, SPA_FLAG_ERRORS, &spa_flags, 'M', MDB_OPT_SETBITS, SPA_FLAG_METASLAB_GROUPS, &spa_flags, 'm', MDB_OPT_SETBITS, SPA_FLAG_METASLABS, &spa_flags, 'h', MDB_OPT_SETBITS, SPA_FLAG_HISTOGRAMS, &spa_flags, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("spa", "spa", argc, argv) == -1) { mdb_warn("can't walk spa"); return (DCMD_ERR); } return (DCMD_OK); } if (flags & DCMD_PIPE_OUT) { mdb_printf("%#lr\n", addr); return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%-?s %9s %-*s%\n", "ADDR", "STATE", sizeof (uintptr_t) == 4 ? 60 : 52, "NAME"); mdb_spa_print_t spa; if (mdb_ctf_vread(&spa, "spa_t", "mdb_spa_print_t", addr, 0) == -1) return (DCMD_ERR); if (spa.spa_state < 0 || spa.spa_state > POOL_STATE_UNAVAIL) state = "UNKNOWN"; else state = statetab[spa.spa_state]; mdb_printf("%0?p %9s %s\n", addr, state, spa.spa_name); if (spa_flags & SPA_FLAG_HISTOGRAMS) spa_class_histogram(spa.spa_normal_class); if (spa_flags & SPA_FLAG_CONFIG) { mdb_printf("\n"); mdb_inc_indent(4); if (mdb_call_dcmd("spa_config", addr, flags, 0, NULL) != DCMD_OK) return (DCMD_ERR); mdb_dec_indent(4); } if (spa_flags & SPA_FLAG_ALL_VDEV) { mdb_arg_t v; char opts[100] = "-"; int args = (spa_flags | SPA_FLAG_VDEVS) == SPA_FLAG_VDEVS ? 0 : 1; if (spa_flags & SPA_FLAG_ERRORS) strcat(opts, "e"); if (spa_flags & SPA_FLAG_METASLABS) strcat(opts, "m"); if (spa_flags & SPA_FLAG_METASLAB_GROUPS) strcat(opts, "M"); if (spa_flags & SPA_FLAG_HISTOGRAMS) strcat(opts, "h"); v.a_type = MDB_TYPE_STRING; v.a_un.a_str = opts; mdb_printf("\n"); mdb_inc_indent(4); if (mdb_call_dcmd("spa_vdevs", addr, flags, args, &v) != DCMD_OK) return (DCMD_ERR); mdb_dec_indent(4); } return (DCMD_OK); } typedef struct mdb_spa_config_spa { uintptr_t spa_config; } mdb_spa_config_spa_t; /* * ::spa_config * * Given a spa_t, print the configuration information stored in spa_config. * Since it's just an nvlist, format it as an indented list of name=value pairs. * We simply read the value of spa_config and pass off to ::nvlist. */ /* ARGSUSED */ static int spa_print_config(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_spa_config_spa_t spa; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_ctf_vread(&spa, ZFS_STRUCT "spa", "mdb_spa_config_spa_t", addr, 0) == -1) return (DCMD_ERR); if (spa.spa_config == 0) { mdb_printf("(none)\n"); return (DCMD_OK); } return (mdb_call_dcmd("nvlist", spa.spa_config, flags, 0, NULL)); } typedef struct mdb_range_tree { struct { uint64_t bt_num_elems; uint64_t bt_num_nodes; } rt_root; uint64_t rt_space; range_seg_type_t rt_type; uint8_t rt_shift; uint64_t rt_start; } mdb_range_tree_t; typedef struct mdb_metaslab_group { uint64_t mg_fragmentation; uint64_t mg_histogram[RANGE_TREE_HISTOGRAM_SIZE]; uintptr_t mg_vd; } mdb_metaslab_group_t; typedef struct mdb_metaslab { uint64_t ms_id; uint64_t ms_start; uint64_t ms_size; int64_t ms_deferspace; uint64_t ms_fragmentation; uint64_t ms_weight; uintptr_t ms_allocating[TXG_SIZE]; uintptr_t ms_checkpointing; uintptr_t ms_freeing; uintptr_t ms_freed; uintptr_t ms_allocatable; uintptr_t ms_unflushed_frees; uintptr_t ms_unflushed_allocs; uintptr_t ms_sm; } mdb_metaslab_t; typedef struct mdb_space_map_phys_t { int64_t smp_alloc; uint64_t smp_histogram[SPACE_MAP_HISTOGRAM_SIZE]; } mdb_space_map_phys_t; typedef struct mdb_space_map { uint64_t sm_size; uint8_t sm_shift; uintptr_t sm_phys; } mdb_space_map_t; typedef struct mdb_vdev { uint64_t vdev_id; uint64_t vdev_state; uintptr_t vdev_ops; struct { uint64_t vs_aux; uint64_t vs_ops[VS_ZIO_TYPES]; uint64_t vs_bytes[VS_ZIO_TYPES]; uint64_t vs_read_errors; uint64_t vs_write_errors; uint64_t vs_checksum_errors; } vdev_stat; uintptr_t vdev_child; uint64_t vdev_children; uint64_t vdev_ms_count; uintptr_t vdev_mg; uintptr_t vdev_ms; uintptr_t vdev_path; } mdb_vdev_t; typedef struct mdb_vdev_ops { char vdev_op_type[16]; } mdb_vdev_ops_t; static int metaslab_stats(mdb_vdev_t *vd, int spa_flags) { mdb_inc_indent(4); mdb_printf("%%-?s %6s %20s %10s %10s %10s%\n", "ADDR", "ID", "OFFSET", "FREE", "FRAG", "UCMU"); uintptr_t *vdev_ms = mdb_alloc(vd->vdev_ms_count * sizeof (vdev_ms), UM_SLEEP | UM_GC); if (mdb_vread(vdev_ms, vd->vdev_ms_count * sizeof (uintptr_t), vd->vdev_ms) == -1) { mdb_warn("failed to read vdev_ms at %p\n", vd->vdev_ms); return (DCMD_ERR); } for (int m = 0; m < vd->vdev_ms_count; m++) { mdb_metaslab_t ms; mdb_space_map_t sm = { 0 }; mdb_space_map_phys_t smp = { 0 }; mdb_range_tree_t rt; uint64_t uallocs, ufrees, raw_free, raw_uchanges_mem; char free[MDB_NICENUM_BUFLEN]; char uchanges_mem[MDB_NICENUM_BUFLEN]; if (mdb_ctf_vread(&ms, "metaslab_t", "mdb_metaslab_t", vdev_ms[m], 0) == -1) return (DCMD_ERR); if (ms.ms_sm != 0 && mdb_ctf_vread(&sm, "space_map_t", "mdb_space_map_t", ms.ms_sm, 0) == -1) return (DCMD_ERR); if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_unflushed_frees, 0) == -1) return (DCMD_ERR); ufrees = rt.rt_space; raw_uchanges_mem = rt.rt_root.bt_num_nodes * BTREE_LEAF_SIZE; if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_unflushed_allocs, 0) == -1) return (DCMD_ERR); uallocs = rt.rt_space; raw_uchanges_mem += rt.rt_root.bt_num_nodes * BTREE_LEAF_SIZE; mdb_nicenum(raw_uchanges_mem, uchanges_mem); raw_free = ms.ms_size; if (ms.ms_sm != 0 && sm.sm_phys != 0) { (void) mdb_ctf_vread(&smp, "space_map_phys_t", "mdb_space_map_phys_t", sm.sm_phys, 0); raw_free -= smp.smp_alloc; } raw_free += ufrees - uallocs; mdb_nicenum(raw_free, free); mdb_printf("%0?p %6llu %20llx %10s ", vdev_ms[m], ms.ms_id, ms.ms_start, free); if (ms.ms_fragmentation == ZFS_FRAG_INVALID) mdb_printf("%9s ", "-"); else mdb_printf("%9llu%% ", ms.ms_fragmentation); mdb_printf("%10s\n", uchanges_mem); if ((spa_flags & SPA_FLAG_HISTOGRAMS) && ms.ms_sm != 0 && sm.sm_phys != 0) { dump_histogram(smp.smp_histogram, SPACE_MAP_HISTOGRAM_SIZE, sm.sm_shift); } } mdb_dec_indent(4); return (DCMD_OK); } static int metaslab_group_stats(mdb_vdev_t *vd, int spa_flags) { mdb_metaslab_group_t mg; if (mdb_ctf_vread(&mg, "metaslab_group_t", "mdb_metaslab_group_t", vd->vdev_mg, 0) == -1) { mdb_warn("failed to read vdev_mg at %p\n", vd->vdev_mg); return (DCMD_ERR); } mdb_inc_indent(4); mdb_printf("%%-?s %7s %9s%\n", "ADDR", "FRAG", "UCMU"); if (mg.mg_fragmentation == ZFS_FRAG_INVALID) mdb_printf("%0?p %6s\n", vd->vdev_mg, "-"); else mdb_printf("%0?p %6llu%%", vd->vdev_mg, mg.mg_fragmentation); uintptr_t *vdev_ms = mdb_alloc(vd->vdev_ms_count * sizeof (vdev_ms), UM_SLEEP | UM_GC); if (mdb_vread(vdev_ms, vd->vdev_ms_count * sizeof (uintptr_t), vd->vdev_ms) == -1) { mdb_warn("failed to read vdev_ms at %p\n", vd->vdev_ms); return (DCMD_ERR); } uint64_t raw_uchanges_mem = 0; char uchanges_mem[MDB_NICENUM_BUFLEN]; for (int m = 0; m < vd->vdev_ms_count; m++) { mdb_metaslab_t ms; mdb_range_tree_t rt; if (mdb_ctf_vread(&ms, "metaslab_t", "mdb_metaslab_t", vdev_ms[m], 0) == -1) return (DCMD_ERR); if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_unflushed_frees, 0) == -1) return (DCMD_ERR); raw_uchanges_mem += rt.rt_root.bt_num_nodes * BTREE_LEAF_SIZE; if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_unflushed_allocs, 0) == -1) return (DCMD_ERR); raw_uchanges_mem += rt.rt_root.bt_num_nodes * BTREE_LEAF_SIZE; } mdb_nicenum(raw_uchanges_mem, uchanges_mem); mdb_printf("%10s\n", uchanges_mem); if (spa_flags & SPA_FLAG_HISTOGRAMS) dump_histogram(mg.mg_histogram, RANGE_TREE_HISTOGRAM_SIZE, 0); mdb_dec_indent(4); return (DCMD_OK); } /* * ::vdev * * Print out a summarized vdev_t, in the following form: * * ADDR STATE AUX DESC * fffffffbcde23df0 HEALTHY - /dev/dsk/c0t0d0 * * If '-r' is specified, recursively visit all children. * * With '-e', the statistics associated with the vdev are printed as well. */ static int do_print_vdev(uintptr_t addr, int flags, int depth, boolean_t recursive, int spa_flags) { mdb_vdev_t vd; if (mdb_ctf_vread(&vd, "vdev_t", "mdb_vdev_t", (uintptr_t)addr, 0) == -1) return (DCMD_ERR); if (flags & DCMD_PIPE_OUT) { mdb_printf("%#lr\n", addr); } else { char desc[MAXNAMELEN]; if (vd.vdev_path != 0) { if (mdb_readstr(desc, sizeof (desc), (uintptr_t)vd.vdev_path) == -1) { mdb_warn("failed to read vdev_path at %p\n", vd.vdev_path); return (DCMD_ERR); } } else if (vd.vdev_ops != 0) { vdev_ops_t ops; if (mdb_vread(&ops, sizeof (ops), (uintptr_t)vd.vdev_ops) == -1) { mdb_warn("failed to read vdev_ops at %p\n", vd.vdev_ops); return (DCMD_ERR); } (void) strcpy(desc, ops.vdev_op_type); } else { (void) strcpy(desc, ""); } if (depth == 0 && DCMD_HDRSPEC(flags)) mdb_printf("%%-?s %-9s %-12s %-*s%\n", "ADDR", "STATE", "AUX", sizeof (uintptr_t) == 4 ? 43 : 35, "DESCRIPTION"); mdb_printf("%0?p ", addr); const char *state, *aux; switch (vd.vdev_state) { case VDEV_STATE_CLOSED: state = "CLOSED"; break; case VDEV_STATE_OFFLINE: state = "OFFLINE"; break; case VDEV_STATE_CANT_OPEN: state = "CANT_OPEN"; break; case VDEV_STATE_DEGRADED: state = "DEGRADED"; break; case VDEV_STATE_HEALTHY: state = "HEALTHY"; break; case VDEV_STATE_REMOVED: state = "REMOVED"; break; case VDEV_STATE_FAULTED: state = "FAULTED"; break; default: state = "UNKNOWN"; break; } switch (vd.vdev_stat.vs_aux) { case VDEV_AUX_NONE: aux = "-"; break; case VDEV_AUX_OPEN_FAILED: aux = "OPEN_FAILED"; break; case VDEV_AUX_CORRUPT_DATA: aux = "CORRUPT_DATA"; break; case VDEV_AUX_NO_REPLICAS: aux = "NO_REPLICAS"; break; case VDEV_AUX_BAD_GUID_SUM: aux = "BAD_GUID_SUM"; break; case VDEV_AUX_TOO_SMALL: aux = "TOO_SMALL"; break; case VDEV_AUX_BAD_LABEL: aux = "BAD_LABEL"; break; case VDEV_AUX_VERSION_NEWER: aux = "VERS_NEWER"; break; case VDEV_AUX_VERSION_OLDER: aux = "VERS_OLDER"; break; case VDEV_AUX_UNSUP_FEAT: aux = "UNSUP_FEAT"; break; case VDEV_AUX_SPARED: aux = "SPARED"; break; case VDEV_AUX_ERR_EXCEEDED: aux = "ERR_EXCEEDED"; break; case VDEV_AUX_IO_FAILURE: aux = "IO_FAILURE"; break; case VDEV_AUX_BAD_LOG: aux = "BAD_LOG"; break; case VDEV_AUX_EXTERNAL: aux = "EXTERNAL"; break; case VDEV_AUX_SPLIT_POOL: aux = "SPLIT_POOL"; break; case VDEV_AUX_CHILDREN_OFFLINE: aux = "CHILDREN_OFFLINE"; break; default: aux = "UNKNOWN"; break; } mdb_printf("%-9s %-12s %*s%s\n", state, aux, depth, "", desc); if (spa_flags & SPA_FLAG_ERRORS) { int i; mdb_inc_indent(4); mdb_printf("\n"); mdb_printf("% %12s %12s %12s %12s " "%12s%\n", "READ", "WRITE", "FREE", "CLAIM", "IOCTL"); mdb_printf("OPS "); for (i = 1; i < VS_ZIO_TYPES; i++) mdb_printf("%11#llx%s", vd.vdev_stat.vs_ops[i], i == VS_ZIO_TYPES - 1 ? "" : " "); mdb_printf("\n"); mdb_printf("BYTES "); for (i = 1; i < VS_ZIO_TYPES; i++) mdb_printf("%11#llx%s", vd.vdev_stat.vs_bytes[i], i == VS_ZIO_TYPES - 1 ? "" : " "); mdb_printf("\n"); mdb_printf("EREAD %10#llx\n", vd.vdev_stat.vs_read_errors); mdb_printf("EWRITE %10#llx\n", vd.vdev_stat.vs_write_errors); mdb_printf("ECKSUM %10#llx\n", vd.vdev_stat.vs_checksum_errors); mdb_dec_indent(4); mdb_printf("\n"); } if ((spa_flags & SPA_FLAG_METASLAB_GROUPS) && vd.vdev_mg != 0) { metaslab_group_stats(&vd, spa_flags); } if ((spa_flags & SPA_FLAG_METASLABS) && vd.vdev_ms != 0) { metaslab_stats(&vd, spa_flags); } } uint64_t children = vd.vdev_children; if (children == 0 || !recursive) return (DCMD_OK); uintptr_t *child = mdb_alloc(children * sizeof (child), UM_SLEEP | UM_GC); if (mdb_vread(child, children * sizeof (void *), vd.vdev_child) == -1) { mdb_warn("failed to read vdev children at %p", vd.vdev_child); return (DCMD_ERR); } for (uint64_t c = 0; c < children; c++) { if (do_print_vdev(child[c], flags, depth + 2, recursive, spa_flags)) { return (DCMD_ERR); } } return (DCMD_OK); } static int vdev_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint64_t depth = 0; boolean_t recursive = B_FALSE; int spa_flags = 0; if (mdb_getopts(argc, argv, 'e', MDB_OPT_SETBITS, SPA_FLAG_ERRORS, &spa_flags, 'm', MDB_OPT_SETBITS, SPA_FLAG_METASLABS, &spa_flags, 'M', MDB_OPT_SETBITS, SPA_FLAG_METASLAB_GROUPS, &spa_flags, 'h', MDB_OPT_SETBITS, SPA_FLAG_HISTOGRAMS, &spa_flags, 'r', MDB_OPT_SETBITS, TRUE, &recursive, 'd', MDB_OPT_UINT64, &depth, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { mdb_warn("no vdev_t address given\n"); return (DCMD_ERR); } return (do_print_vdev(addr, flags, (int)depth, recursive, spa_flags)); } typedef struct mdb_metaslab_alloc_trace { uintptr_t mat_mg; uintptr_t mat_msp; uint64_t mat_size; uint64_t mat_weight; uint64_t mat_offset; uint32_t mat_dva_id; int mat_allocator; } mdb_metaslab_alloc_trace_t; static void metaslab_print_weight(uint64_t weight) { char buf[100]; if (WEIGHT_IS_SPACEBASED(weight)) { mdb_nicenum( weight & ~(METASLAB_ACTIVE_MASK | METASLAB_WEIGHT_TYPE), buf); } else { char size[MDB_NICENUM_BUFLEN]; mdb_nicenum(1ULL << WEIGHT_GET_INDEX(weight), size); (void) mdb_snprintf(buf, sizeof (buf), "%llu x %s", WEIGHT_GET_COUNT(weight), size); } mdb_printf("%11s ", buf); } /* ARGSUSED */ static int metaslab_weight(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint64_t weight = 0; char active; if (argc == 0 && (flags & DCMD_ADDRSPEC)) { if (mdb_vread(&weight, sizeof (uint64_t), addr) == -1) { mdb_warn("failed to read weight at %p\n", addr); return (DCMD_ERR); } } else if (argc == 1 && !(flags & DCMD_ADDRSPEC)) { weight = (uint64_t)mdb_argtoull(&argv[0]); } else { return (DCMD_USAGE); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%-6s %9s %9s%\n", "ACTIVE", "ALGORITHM", "WEIGHT"); } if (weight & METASLAB_WEIGHT_PRIMARY) active = 'P'; else if (weight & METASLAB_WEIGHT_SECONDARY) active = 'S'; else active = '-'; mdb_printf("%6c %8s ", active, WEIGHT_IS_SPACEBASED(weight) ? "SPACE" : "SEGMENT"); metaslab_print_weight(weight); mdb_printf("\n"); return (DCMD_OK); } /* ARGSUSED */ static int metaslab_trace(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_metaslab_alloc_trace_t mat; mdb_metaslab_group_t mg = { 0 }; char result_type[100]; if (mdb_ctf_vread(&mat, "metaslab_alloc_trace_t", "mdb_metaslab_alloc_trace_t", addr, 0) == -1) { return (DCMD_ERR); } if (!(flags & DCMD_PIPE_OUT) && DCMD_HDRSPEC(flags)) { mdb_printf("%%6s %6s %8s %11s %11s %18s %18s%\n", "MSID", "DVA", "ASIZE", "ALLOCATOR", "WEIGHT", "RESULT", "VDEV"); } if (mat.mat_msp != 0) { mdb_metaslab_t ms; if (mdb_ctf_vread(&ms, "metaslab_t", "mdb_metaslab_t", mat.mat_msp, 0) == -1) { return (DCMD_ERR); } mdb_printf("%6llu ", ms.ms_id); } else { mdb_printf("%6s ", "-"); } mdb_printf("%6d %8llx %11llx ", mat.mat_dva_id, mat.mat_size, mat.mat_allocator); metaslab_print_weight(mat.mat_weight); if ((int64_t)mat.mat_offset < 0) { if (enum_lookup("enum trace_alloc_type", mat.mat_offset, "TRACE_", sizeof (result_type), result_type) == -1) { mdb_warn("Could not find enum for trace_alloc_type"); return (DCMD_ERR); } mdb_printf("%18s ", result_type); } else { mdb_printf("%%18llx% ", mat.mat_offset); } if (mat.mat_mg != 0 && mdb_ctf_vread(&mg, "metaslab_group_t", "mdb_metaslab_group_t", mat.mat_mg, 0) == -1) { return (DCMD_ERR); } if (mg.mg_vd != 0) { mdb_vdev_t vdev; char desc[MAXNAMELEN]; if (mdb_ctf_vread(&vdev, "vdev_t", "mdb_vdev_t", mg.mg_vd, 0) == -1) { return (DCMD_ERR); } if (vdev.vdev_path != 0) { char path[MAXNAMELEN]; if (mdb_readstr(path, sizeof (path), vdev.vdev_path) == -1) { mdb_warn("failed to read vdev_path at %p\n", vdev.vdev_path); return (DCMD_ERR); } char *slash; if ((slash = strrchr(path, '/')) != NULL) { strcpy(desc, slash + 1); } else { strcpy(desc, path); } } else if (vdev.vdev_ops != 0) { mdb_vdev_ops_t ops; if (mdb_ctf_vread(&ops, "vdev_ops_t", "mdb_vdev_ops_t", vdev.vdev_ops, 0) == -1) { mdb_warn("failed to read vdev_ops at %p\n", vdev.vdev_ops); return (DCMD_ERR); } (void) mdb_snprintf(desc, sizeof (desc), "%s-%llu", ops.vdev_op_type, vdev.vdev_id); } else { (void) strcpy(desc, ""); } mdb_printf("%18s\n", desc); } return (DCMD_OK); } typedef struct metaslab_walk_data { uint64_t mw_numvdevs; uintptr_t *mw_vdevs; int mw_curvdev; uint64_t mw_nummss; uintptr_t *mw_mss; int mw_curms; } metaslab_walk_data_t; static int metaslab_walk_step(mdb_walk_state_t *wsp) { metaslab_walk_data_t *mw = wsp->walk_data; metaslab_t ms; uintptr_t msp; if (mw->mw_curvdev >= mw->mw_numvdevs) return (WALK_DONE); if (mw->mw_mss == NULL) { uintptr_t mssp; uintptr_t vdevp; ASSERT(mw->mw_curms == 0); ASSERT(mw->mw_nummss == 0); vdevp = mw->mw_vdevs[mw->mw_curvdev]; if (GETMEMB(vdevp, "vdev", vdev_ms, mssp) || GETMEMB(vdevp, "vdev", vdev_ms_count, mw->mw_nummss)) { return (WALK_ERR); } mw->mw_mss = mdb_alloc(mw->mw_nummss * sizeof (void*), UM_SLEEP | UM_GC); if (mdb_vread(mw->mw_mss, mw->mw_nummss * sizeof (void*), mssp) == -1) { mdb_warn("failed to read vdev_ms at %p", mssp); return (WALK_ERR); } } if (mw->mw_curms >= mw->mw_nummss) { mw->mw_mss = NULL; mw->mw_curms = 0; mw->mw_nummss = 0; mw->mw_curvdev++; return (WALK_NEXT); } msp = mw->mw_mss[mw->mw_curms]; if (mdb_vread(&ms, sizeof (metaslab_t), msp) == -1) { mdb_warn("failed to read metaslab_t at %p", msp); return (WALK_ERR); } mw->mw_curms++; return (wsp->walk_callback(msp, &ms, wsp->walk_cbdata)); } static int metaslab_walk_init(mdb_walk_state_t *wsp) { metaslab_walk_data_t *mw; uintptr_t root_vdevp; uintptr_t childp; if (wsp->walk_addr == 0) { mdb_warn("must supply address of spa_t\n"); return (WALK_ERR); } mw = mdb_zalloc(sizeof (metaslab_walk_data_t), UM_SLEEP | UM_GC); if (GETMEMB(wsp->walk_addr, "spa", spa_root_vdev, root_vdevp) || GETMEMB(root_vdevp, "vdev", vdev_children, mw->mw_numvdevs) || GETMEMB(root_vdevp, "vdev", vdev_child, childp)) { return (DCMD_ERR); } mw->mw_vdevs = mdb_alloc(mw->mw_numvdevs * sizeof (void *), UM_SLEEP | UM_GC); if (mdb_vread(mw->mw_vdevs, mw->mw_numvdevs * sizeof (void *), childp) == -1) { mdb_warn("failed to read root vdev children at %p", childp); return (DCMD_ERR); } wsp->walk_data = mw; return (WALK_NEXT); } typedef struct mdb_spa { uintptr_t spa_dsl_pool; uintptr_t spa_root_vdev; } mdb_spa_t; typedef struct mdb_dsl_pool { uintptr_t dp_root_dir; } mdb_dsl_pool_t; typedef struct mdb_dsl_dir { uintptr_t dd_dbuf; int64_t dd_space_towrite[TXG_SIZE]; } mdb_dsl_dir_t; typedef struct mdb_dsl_dir_phys { uint64_t dd_used_bytes; uint64_t dd_compressed_bytes; uint64_t dd_uncompressed_bytes; } mdb_dsl_dir_phys_t; typedef struct space_data { uint64_t ms_allocating[TXG_SIZE]; uint64_t ms_checkpointing; uint64_t ms_freeing; uint64_t ms_freed; uint64_t ms_unflushed_frees; uint64_t ms_unflushed_allocs; uint64_t ms_allocatable; int64_t ms_deferspace; uint64_t avail; } space_data_t; /* ARGSUSED */ static int space_cb(uintptr_t addr, const void *unknown, void *arg) { space_data_t *sd = arg; mdb_metaslab_t ms; mdb_range_tree_t rt; mdb_space_map_t sm = { 0 }; mdb_space_map_phys_t smp = { 0 }; uint64_t uallocs, ufrees; int i; if (mdb_ctf_vread(&ms, "metaslab_t", "mdb_metaslab_t", addr, 0) == -1) return (WALK_ERR); for (i = 0; i < TXG_SIZE; i++) { if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_allocating[i], 0) == -1) return (WALK_ERR); sd->ms_allocating[i] += rt.rt_space; } if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_checkpointing, 0) == -1) return (WALK_ERR); sd->ms_checkpointing += rt.rt_space; if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_freeing, 0) == -1) return (WALK_ERR); sd->ms_freeing += rt.rt_space; if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_freed, 0) == -1) return (WALK_ERR); sd->ms_freed += rt.rt_space; if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_allocatable, 0) == -1) return (WALK_ERR); sd->ms_allocatable += rt.rt_space; if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_unflushed_frees, 0) == -1) return (WALK_ERR); sd->ms_unflushed_frees += rt.rt_space; ufrees = rt.rt_space; if (mdb_ctf_vread(&rt, "range_tree_t", "mdb_range_tree_t", ms.ms_unflushed_allocs, 0) == -1) return (WALK_ERR); sd->ms_unflushed_allocs += rt.rt_space; uallocs = rt.rt_space; if (ms.ms_sm != 0 && mdb_ctf_vread(&sm, "space_map_t", "mdb_space_map_t", ms.ms_sm, 0) == -1) return (WALK_ERR); if (sm.sm_phys != 0) { (void) mdb_ctf_vread(&smp, "space_map_phys_t", "mdb_space_map_phys_t", sm.sm_phys, 0); } sd->ms_deferspace += ms.ms_deferspace; sd->avail += sm.sm_size - smp.smp_alloc + ufrees - uallocs; return (WALK_NEXT); } /* * ::spa_space [-b] * * Given a spa_t, print out it's on-disk space usage and in-core * estimates of future usage. If -b is given, print space in bytes. * Otherwise print in megabytes. */ /* ARGSUSED */ static int spa_space(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_spa_t spa; mdb_dsl_pool_t dp; mdb_dsl_dir_t dd; mdb_dmu_buf_impl_t db; mdb_dsl_dir_phys_t dsp; space_data_t sd; int shift = 20; char *suffix = "M"; int bytes = B_FALSE; if (mdb_getopts(argc, argv, 'b', MDB_OPT_SETBITS, TRUE, &bytes, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (bytes) { shift = 0; suffix = ""; } if (mdb_ctf_vread(&spa, ZFS_STRUCT "spa", "mdb_spa_t", addr, 0) == -1 || mdb_ctf_vread(&dp, ZFS_STRUCT "dsl_pool", "mdb_dsl_pool_t", spa.spa_dsl_pool, 0) == -1 || mdb_ctf_vread(&dd, ZFS_STRUCT "dsl_dir", "mdb_dsl_dir_t", dp.dp_root_dir, 0) == -1 || mdb_ctf_vread(&db, ZFS_STRUCT "dmu_buf_impl", "mdb_dmu_buf_impl_t", dd.dd_dbuf, 0) == -1 || mdb_ctf_vread(&dsp, ZFS_STRUCT "dsl_dir_phys", "mdb_dsl_dir_phys_t", db.db.db_data, 0) == -1) { return (DCMD_ERR); } mdb_printf("dd_space_towrite = %llu%s %llu%s %llu%s %llu%s\n", dd.dd_space_towrite[0] >> shift, suffix, dd.dd_space_towrite[1] >> shift, suffix, dd.dd_space_towrite[2] >> shift, suffix, dd.dd_space_towrite[3] >> shift, suffix); mdb_printf("dd_phys.dd_used_bytes = %llu%s\n", dsp.dd_used_bytes >> shift, suffix); mdb_printf("dd_phys.dd_compressed_bytes = %llu%s\n", dsp.dd_compressed_bytes >> shift, suffix); mdb_printf("dd_phys.dd_uncompressed_bytes = %llu%s\n", dsp.dd_uncompressed_bytes >> shift, suffix); bzero(&sd, sizeof (sd)); if (mdb_pwalk("metaslab", space_cb, &sd, addr) != 0) { mdb_warn("can't walk metaslabs"); return (DCMD_ERR); } mdb_printf("ms_allocmap = %llu%s %llu%s %llu%s %llu%s\n", sd.ms_allocating[0] >> shift, suffix, sd.ms_allocating[1] >> shift, suffix, sd.ms_allocating[2] >> shift, suffix, sd.ms_allocating[3] >> shift, suffix); mdb_printf("ms_checkpointing = %llu%s\n", sd.ms_checkpointing >> shift, suffix); mdb_printf("ms_freeing = %llu%s\n", sd.ms_freeing >> shift, suffix); mdb_printf("ms_freed = %llu%s\n", sd.ms_freed >> shift, suffix); mdb_printf("ms_unflushed_frees = %llu%s\n", sd.ms_unflushed_frees >> shift, suffix); mdb_printf("ms_unflushed_allocs = %llu%s\n", sd.ms_unflushed_allocs >> shift, suffix); mdb_printf("ms_allocatable = %llu%s\n", sd.ms_allocatable >> shift, suffix); mdb_printf("ms_deferspace = %llu%s\n", sd.ms_deferspace >> shift, suffix); mdb_printf("current avail = %llu%s\n", sd.avail >> shift, suffix); return (DCMD_OK); } typedef struct mdb_spa_aux_vdev { int sav_count; uintptr_t sav_vdevs; } mdb_spa_aux_vdev_t; typedef struct mdb_spa_vdevs { uintptr_t spa_root_vdev; mdb_spa_aux_vdev_t spa_l2cache; mdb_spa_aux_vdev_t spa_spares; } mdb_spa_vdevs_t; static int spa_print_aux(mdb_spa_aux_vdev_t *sav, uint_t flags, mdb_arg_t *v, const char *name) { uintptr_t *aux; size_t len; int ret, i; /* * Iterate over aux vdevs and print those out as well. This is a * little annoying because we don't have a root vdev to pass to ::vdev. * Instead, we print a single line and then call it for each child * vdev. */ if (sav->sav_count != 0) { v[1].a_type = MDB_TYPE_STRING; v[1].a_un.a_str = "-d"; v[2].a_type = MDB_TYPE_IMMEDIATE; v[2].a_un.a_val = 2; len = sav->sav_count * sizeof (uintptr_t); aux = mdb_alloc(len, UM_SLEEP); if (mdb_vread(aux, len, sav->sav_vdevs) == -1) { mdb_free(aux, len); mdb_warn("failed to read l2cache vdevs at %p", sav->sav_vdevs); return (DCMD_ERR); } mdb_printf("%-?s %-9s %-12s %s\n", "-", "-", "-", name); for (i = 0; i < sav->sav_count; i++) { ret = mdb_call_dcmd("vdev", aux[i], flags, 3, v); if (ret != DCMD_OK) { mdb_free(aux, len); return (ret); } } mdb_free(aux, len); } return (0); } /* * ::spa_vdevs * * -e Include error stats * -m Include metaslab information * -M Include metaslab group information * -h Include histogram information (requires -m or -M) * * Print out a summarized list of vdevs for the given spa_t. * This is accomplished by invoking "::vdev -re" on the root vdev, as well as * iterating over the cache devices. */ /* ARGSUSED */ static int spa_vdevs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_arg_t v[3]; int ret; char opts[100] = "-r"; int spa_flags = 0; if (mdb_getopts(argc, argv, 'e', MDB_OPT_SETBITS, SPA_FLAG_ERRORS, &spa_flags, 'm', MDB_OPT_SETBITS, SPA_FLAG_METASLABS, &spa_flags, 'M', MDB_OPT_SETBITS, SPA_FLAG_METASLAB_GROUPS, &spa_flags, 'h', MDB_OPT_SETBITS, SPA_FLAG_HISTOGRAMS, &spa_flags, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); mdb_spa_vdevs_t spa; if (mdb_ctf_vread(&spa, "spa_t", "mdb_spa_vdevs_t", addr, 0) == -1) return (DCMD_ERR); /* * Unitialized spa_t structures can have a NULL root vdev. */ if (spa.spa_root_vdev == 0) { mdb_printf("no associated vdevs\n"); return (DCMD_OK); } if (spa_flags & SPA_FLAG_ERRORS) strcat(opts, "e"); if (spa_flags & SPA_FLAG_METASLABS) strcat(opts, "m"); if (spa_flags & SPA_FLAG_METASLAB_GROUPS) strcat(opts, "M"); if (spa_flags & SPA_FLAG_HISTOGRAMS) strcat(opts, "h"); v[0].a_type = MDB_TYPE_STRING; v[0].a_un.a_str = opts; ret = mdb_call_dcmd("vdev", (uintptr_t)spa.spa_root_vdev, flags, 1, v); if (ret != DCMD_OK) return (ret); if (spa_print_aux(&spa.spa_l2cache, flags, v, "cache") != 0 || spa_print_aux(&spa.spa_spares, flags, v, "spares") != 0) return (DCMD_ERR); return (DCMD_OK); } /* * ::zio * * Print a summary of zio_t and all its children. This is intended to display a * zio tree, and hence we only pick the most important pieces of information for * the main summary. More detailed information can always be found by doing a * '::print zio' on the underlying zio_t. The columns we display are: * * ADDRESS TYPE STAGE WAITER TIME_ELAPSED * * The 'address' column is indented by one space for each depth level as we * descend down the tree. */ #define ZIO_MAXINDENT 7 #define ZIO_MAXWIDTH (sizeof (uintptr_t) * 2 + ZIO_MAXINDENT) #define ZIO_WALK_SELF 0 #define ZIO_WALK_CHILD 1 #define ZIO_WALK_PARENT 2 typedef struct zio_print_args { int zpa_current_depth; int zpa_min_depth; int zpa_max_depth; int zpa_type; uint_t zpa_flags; } zio_print_args_t; typedef struct mdb_zio { enum zio_type io_type; enum zio_stage io_stage; uintptr_t io_waiter; uintptr_t io_spa; struct { struct { uintptr_t list_next; } list_head; } io_parent_list; int io_error; } mdb_zio_t; typedef struct mdb_zio_timestamp { hrtime_t io_timestamp; } mdb_zio_timestamp_t; static int zio_child_cb(uintptr_t addr, const void *unknown, void *arg); static int zio_print_cb(uintptr_t addr, zio_print_args_t *zpa) { mdb_ctf_id_t type_enum, stage_enum; int indent = zpa->zpa_current_depth; const char *type, *stage; uintptr_t laddr; mdb_zio_t zio; mdb_zio_timestamp_t zio_timestamp = { 0 }; if (mdb_ctf_vread(&zio, ZFS_STRUCT "zio", "mdb_zio_t", addr, 0) == -1) return (WALK_ERR); (void) mdb_ctf_vread(&zio_timestamp, ZFS_STRUCT "zio", "mdb_zio_timestamp_t", addr, MDB_CTF_VREAD_QUIET); if (indent > ZIO_MAXINDENT) indent = ZIO_MAXINDENT; if (mdb_ctf_lookup_by_name("enum zio_type", &type_enum) == -1 || mdb_ctf_lookup_by_name("enum zio_stage", &stage_enum) == -1) { mdb_warn("failed to lookup zio enums"); return (WALK_ERR); } if ((type = mdb_ctf_enum_name(type_enum, zio.io_type)) != NULL) type += sizeof ("ZIO_TYPE_") - 1; else type = "?"; if (zio.io_error == 0) { stage = mdb_ctf_enum_name(stage_enum, zio.io_stage); if (stage != NULL) stage += sizeof ("ZIO_STAGE_") - 1; else stage = "?"; } else { stage = "FAILED"; } if (zpa->zpa_current_depth >= zpa->zpa_min_depth) { if (zpa->zpa_flags & DCMD_PIPE_OUT) { mdb_printf("%?p\n", addr); } else { mdb_printf("%*s%-*p %-5s %-16s ", indent, "", ZIO_MAXWIDTH - indent, addr, type, stage); if (zio.io_waiter != 0) mdb_printf("%-16lx ", zio.io_waiter); else mdb_printf("%-16s ", "-"); #ifdef _KERNEL if (zio_timestamp.io_timestamp != 0) { mdb_printf("%llums", (mdb_gethrtime() - zio_timestamp.io_timestamp) / 1000000); } else { mdb_printf("%-12s ", "-"); } #else mdb_printf("%-12s ", "-"); #endif mdb_printf("\n"); } } if (zpa->zpa_current_depth >= zpa->zpa_max_depth) return (WALK_NEXT); if (zpa->zpa_type == ZIO_WALK_PARENT) laddr = addr + mdb_ctf_offsetof_by_name(ZFS_STRUCT "zio", "io_parent_list"); else laddr = addr + mdb_ctf_offsetof_by_name(ZFS_STRUCT "zio", "io_child_list"); zpa->zpa_current_depth++; if (mdb_pwalk("list", zio_child_cb, zpa, laddr) != 0) { mdb_warn("failed to walk zio_t children at %p\n", laddr); return (WALK_ERR); } zpa->zpa_current_depth--; return (WALK_NEXT); } /* ARGSUSED */ static int zio_child_cb(uintptr_t addr, const void *unknown, void *arg) { zio_link_t zl; uintptr_t ziop; zio_print_args_t *zpa = arg; if (mdb_vread(&zl, sizeof (zl), addr) == -1) { mdb_warn("failed to read zio_link_t at %p", addr); return (WALK_ERR); } if (zpa->zpa_type == ZIO_WALK_PARENT) ziop = (uintptr_t)zl.zl_parent; else ziop = (uintptr_t)zl.zl_child; return (zio_print_cb(ziop, zpa)); } /* ARGSUSED */ static int zio_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { zio_print_args_t zpa = { 0 }; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'r', MDB_OPT_SETBITS, INT_MAX, &zpa.zpa_max_depth, 'c', MDB_OPT_SETBITS, ZIO_WALK_CHILD, &zpa.zpa_type, 'p', MDB_OPT_SETBITS, ZIO_WALK_PARENT, &zpa.zpa_type, NULL) != argc) return (DCMD_USAGE); zpa.zpa_flags = flags; if (zpa.zpa_max_depth != 0) { if (zpa.zpa_type == ZIO_WALK_SELF) zpa.zpa_type = ZIO_WALK_CHILD; } else if (zpa.zpa_type != ZIO_WALK_SELF) { zpa.zpa_min_depth = 1; zpa.zpa_max_depth = 1; } if (!(flags & DCMD_PIPE_OUT) && DCMD_HDRSPEC(flags)) { mdb_printf("%%-*s %-5s %-16s %-16s %-12s%\n", ZIO_MAXWIDTH, "ADDRESS", "TYPE", "STAGE", "WAITER", "TIME_ELAPSED"); } if (zio_print_cb(addr, &zpa) != WALK_NEXT) return (DCMD_ERR); return (DCMD_OK); } /* * [addr]::zio_state * * Print a summary of all zio_t structures on the system, or for a particular * pool. This is equivalent to '::walk zio_root | ::zio'. */ /*ARGSUSED*/ static int zio_state(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { /* * MDB will remember the last address of the pipeline, so if we don't * zero this we'll end up trying to walk zio structures for a * non-existent spa_t. */ if (!(flags & DCMD_ADDRSPEC)) addr = 0; return (mdb_pwalk_dcmd("zio_root", "zio", argc, argv, addr)); } typedef struct mdb_zfs_btree_hdr { uintptr_t bth_parent; boolean_t bth_core; /* * For both leaf and core nodes, represents the number of elements in * the node. For core nodes, they will have bth_count + 1 children. */ uint32_t bth_count; } mdb_zfs_btree_hdr_t; typedef struct mdb_zfs_btree_core { mdb_zfs_btree_hdr_t btc_hdr; uintptr_t btc_children[BTREE_CORE_ELEMS + 1]; uint8_t btc_elems[]; } mdb_zfs_btree_core_t; typedef struct mdb_zfs_btree_leaf { mdb_zfs_btree_hdr_t btl_hdr; uint8_t btl_elems[]; } mdb_zfs_btree_leaf_t; typedef struct mdb_zfs_btree { uintptr_t bt_root; size_t bt_elem_size; } mdb_zfs_btree_t; typedef struct btree_walk_data { mdb_zfs_btree_t bwd_btree; mdb_zfs_btree_hdr_t *bwd_node; uint64_t bwd_offset; // In units of bt_node_size } btree_walk_data_t; static uintptr_t btree_leftmost_child(uintptr_t addr, mdb_zfs_btree_hdr_t *buf) { size_t size = offsetof(zfs_btree_core_t, btc_children) + sizeof (uintptr_t); for (;;) { if (mdb_vread(buf, size, addr) == -1) { mdb_warn("failed to read at %p\n", addr); return ((uintptr_t)0ULL); } if (!buf->bth_core) return (addr); mdb_zfs_btree_core_t *node = (mdb_zfs_btree_core_t *)buf; addr = node->btc_children[0]; } } static int btree_walk_step(mdb_walk_state_t *wsp) { btree_walk_data_t *bwd = wsp->walk_data; size_t elem_size = bwd->bwd_btree.bt_elem_size; if (wsp->walk_addr == 0ULL) return (WALK_DONE); if (!bwd->bwd_node->bth_core) { /* * For the first element in a leaf node, read in the full * leaf, since we only had part of it read in before. */ if (bwd->bwd_offset == 0) { if (mdb_vread(bwd->bwd_node, BTREE_LEAF_SIZE, wsp->walk_addr) == -1) { mdb_warn("failed to read at %p\n", wsp->walk_addr); return (WALK_ERR); } } int status = wsp->walk_callback((uintptr_t)(wsp->walk_addr + offsetof(mdb_zfs_btree_leaf_t, btl_elems) + bwd->bwd_offset * elem_size), bwd->bwd_node, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); bwd->bwd_offset++; /* Find the next element, if we're at the end of the leaf. */ while (bwd->bwd_offset == bwd->bwd_node->bth_count) { uintptr_t par = bwd->bwd_node->bth_parent; uintptr_t cur = wsp->walk_addr; wsp->walk_addr = par; if (par == 0ULL) return (WALK_NEXT); size_t size = sizeof (zfs_btree_core_t) + BTREE_CORE_ELEMS * elem_size; if (mdb_vread(bwd->bwd_node, size, wsp->walk_addr) == -1) { mdb_warn("failed to read at %p\n", wsp->walk_addr); return (WALK_ERR); } mdb_zfs_btree_core_t *node = (mdb_zfs_btree_core_t *)bwd->bwd_node; int i; for (i = 0; i <= bwd->bwd_node->bth_count; i++) { if (node->btc_children[i] == cur) break; } if (i > bwd->bwd_node->bth_count) { mdb_warn("btree parent/child mismatch at " "%#lx\n", cur); return (WALK_ERR); } bwd->bwd_offset = i; } return (WALK_NEXT); } if (!bwd->bwd_node->bth_core) { mdb_warn("Invalid btree node at %#lx\n", wsp->walk_addr); return (WALK_ERR); } mdb_zfs_btree_core_t *node = (mdb_zfs_btree_core_t *)bwd->bwd_node; int status = wsp->walk_callback((uintptr_t)(wsp->walk_addr + offsetof(mdb_zfs_btree_core_t, btc_elems) + bwd->bwd_offset * elem_size), bwd->bwd_node, wsp->walk_cbdata); if (status != WALK_NEXT) return (status); uintptr_t new_child = node->btc_children[bwd->bwd_offset + 1]; wsp->walk_addr = btree_leftmost_child(new_child, bwd->bwd_node); if (wsp->walk_addr == 0ULL) return (WALK_ERR); bwd->bwd_offset = 0; return (WALK_NEXT); } static int btree_walk_init(mdb_walk_state_t *wsp) { btree_walk_data_t *bwd; if (wsp->walk_addr == 0ULL) { mdb_warn("must supply address of zfs_btree_t\n"); return (WALK_ERR); } bwd = mdb_zalloc(sizeof (btree_walk_data_t), UM_SLEEP); if (mdb_ctf_vread(&bwd->bwd_btree, "zfs_btree_t", "mdb_zfs_btree_t", wsp->walk_addr, 0) == -1) { mdb_free(bwd, sizeof (*bwd)); return (WALK_ERR); } if (bwd->bwd_btree.bt_elem_size == 0) { mdb_warn("invalid or uninitialized btree at %#lx\n", wsp->walk_addr); mdb_free(bwd, sizeof (*bwd)); return (WALK_ERR); } size_t size = MAX(BTREE_LEAF_SIZE, sizeof (zfs_btree_core_t) + BTREE_CORE_ELEMS * bwd->bwd_btree.bt_elem_size); bwd->bwd_node = mdb_zalloc(size, UM_SLEEP); uintptr_t node = (uintptr_t)bwd->bwd_btree.bt_root; if (node == 0ULL) { wsp->walk_addr = 0ULL; wsp->walk_data = bwd; return (WALK_NEXT); } node = btree_leftmost_child(node, bwd->bwd_node); if (node == 0ULL) { mdb_free(bwd->bwd_node, size); mdb_free(bwd, sizeof (*bwd)); return (WALK_ERR); } bwd->bwd_offset = 0; wsp->walk_addr = node; wsp->walk_data = bwd; return (WALK_NEXT); } static void btree_walk_fini(mdb_walk_state_t *wsp) { btree_walk_data_t *bwd = (btree_walk_data_t *)wsp->walk_data; if (bwd == NULL) return; size_t size = MAX(BTREE_LEAF_SIZE, sizeof (zfs_btree_core_t) + BTREE_CORE_ELEMS * bwd->bwd_btree.bt_elem_size); if (bwd->bwd_node != NULL) mdb_free(bwd->bwd_node, size); mdb_free(bwd, sizeof (*bwd)); } typedef struct mdb_multilist { uint64_t ml_num_sublists; uintptr_t ml_sublists; } mdb_multilist_t; static int multilist_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, wsp->walk_cbdata)); } static int multilist_walk_init(mdb_walk_state_t *wsp) { mdb_multilist_t ml; ssize_t sublist_sz; int list_offset; size_t i; if (wsp->walk_addr == 0) { mdb_warn("must supply address of multilist_t\n"); return (WALK_ERR); } if (mdb_ctf_vread(&ml, "multilist_t", "mdb_multilist_t", wsp->walk_addr, 0) == -1) { return (WALK_ERR); } if (ml.ml_num_sublists == 0 || ml.ml_sublists == 0) { mdb_warn("invalid or uninitialized multilist at %#lx\n", wsp->walk_addr); return (WALK_ERR); } /* mdb_ctf_sizeof_by_name() will print an error for us */ sublist_sz = mdb_ctf_sizeof_by_name("multilist_sublist_t"); if (sublist_sz == -1) return (WALK_ERR); /* mdb_ctf_offsetof_by_name will print an error for us */ list_offset = mdb_ctf_offsetof_by_name("multilist_sublist_t", "mls_list"); if (list_offset == -1) return (WALK_ERR); for (i = 0; i < ml.ml_num_sublists; i++) { wsp->walk_addr = ml.ml_sublists + i * sublist_sz + list_offset; if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("can't walk multilist sublist"); return (WALK_ERR); } } return (WALK_NEXT); } typedef struct mdb_txg_list { size_t tl_offset; uintptr_t tl_head[TXG_SIZE]; } mdb_txg_list_t; typedef struct txg_list_walk_data { uintptr_t lw_head[TXG_SIZE]; int lw_txgoff; int lw_maxoff; size_t lw_offset; void *lw_obj; } txg_list_walk_data_t; static int txg_list_walk_init_common(mdb_walk_state_t *wsp, int txg, int maxoff) { txg_list_walk_data_t *lwd; mdb_txg_list_t list; int i; lwd = mdb_alloc(sizeof (txg_list_walk_data_t), UM_SLEEP | UM_GC); if (mdb_ctf_vread(&list, "txg_list_t", "mdb_txg_list_t", wsp->walk_addr, 0) == -1) { mdb_warn("failed to read txg_list_t at %#lx", wsp->walk_addr); return (WALK_ERR); } for (i = 0; i < TXG_SIZE; i++) lwd->lw_head[i] = list.tl_head[i]; lwd->lw_offset = list.tl_offset; lwd->lw_obj = mdb_alloc(lwd->lw_offset + sizeof (txg_node_t), UM_SLEEP | UM_GC); lwd->lw_txgoff = txg; lwd->lw_maxoff = maxoff; wsp->walk_addr = lwd->lw_head[lwd->lw_txgoff]; wsp->walk_data = lwd; return (WALK_NEXT); } static int txg_list_walk_init(mdb_walk_state_t *wsp) { return (txg_list_walk_init_common(wsp, 0, TXG_SIZE-1)); } static int txg_list0_walk_init(mdb_walk_state_t *wsp) { return (txg_list_walk_init_common(wsp, 0, 0)); } static int txg_list1_walk_init(mdb_walk_state_t *wsp) { return (txg_list_walk_init_common(wsp, 1, 1)); } static int txg_list2_walk_init(mdb_walk_state_t *wsp) { return (txg_list_walk_init_common(wsp, 2, 2)); } static int txg_list3_walk_init(mdb_walk_state_t *wsp) { return (txg_list_walk_init_common(wsp, 3, 3)); } static int txg_list_walk_step(mdb_walk_state_t *wsp) { txg_list_walk_data_t *lwd = wsp->walk_data; uintptr_t addr; txg_node_t *node; int status; while (wsp->walk_addr == 0 && lwd->lw_txgoff < lwd->lw_maxoff) { lwd->lw_txgoff++; wsp->walk_addr = lwd->lw_head[lwd->lw_txgoff]; } if (wsp->walk_addr == 0) return (WALK_DONE); addr = wsp->walk_addr - lwd->lw_offset; if (mdb_vread(lwd->lw_obj, lwd->lw_offset + sizeof (txg_node_t), addr) == -1) { mdb_warn("failed to read list element at %#lx", addr); return (WALK_ERR); } status = wsp->walk_callback(addr, lwd->lw_obj, wsp->walk_cbdata); node = (txg_node_t *)((uintptr_t)lwd->lw_obj + lwd->lw_offset); wsp->walk_addr = (uintptr_t)node->tn_next[lwd->lw_txgoff]; return (status); } /* * ::walk spa * * Walk all named spa_t structures in the namespace. This is nothing more than * a layered avl walk. */ static int spa_walk_init(mdb_walk_state_t *wsp) { GElf_Sym sym; if (wsp->walk_addr != 0) { mdb_warn("spa walk only supports global walks\n"); return (WALK_ERR); } if (mdb_lookup_by_obj(ZFS_OBJ_NAME, "spa_namespace_avl", &sym) == -1) { mdb_warn("failed to find symbol 'spa_namespace_avl'"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)sym.st_value; if (mdb_layered_walk("avl", wsp) == -1) { mdb_warn("failed to walk 'avl'\n"); return (WALK_ERR); } return (WALK_NEXT); } static int spa_walk_step(mdb_walk_state_t *wsp) { return (wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata)); } /* * [addr]::walk zio * * Walk all active zio_t structures on the system. This is simply a layered * walk on top of ::walk zio_cache, with the optional ability to limit the * structures to a particular pool. */ static int zio_walk_init(mdb_walk_state_t *wsp) { wsp->walk_data = (void *)wsp->walk_addr; if (mdb_layered_walk("zio_cache", wsp) == -1) { mdb_warn("failed to walk 'zio_cache'\n"); return (WALK_ERR); } return (WALK_NEXT); } static int zio_walk_step(mdb_walk_state_t *wsp) { mdb_zio_t zio; uintptr_t spa = (uintptr_t)wsp->walk_data; if (mdb_ctf_vread(&zio, ZFS_STRUCT "zio", "mdb_zio_t", wsp->walk_addr, 0) == -1) return (WALK_ERR); if (spa != 0 && spa != zio.io_spa) return (WALK_NEXT); return (wsp->walk_callback(wsp->walk_addr, &zio, wsp->walk_cbdata)); } /* * [addr]::walk zio_root * * Walk only root zio_t structures, optionally for a particular spa_t. */ static int zio_walk_root_step(mdb_walk_state_t *wsp) { mdb_zio_t zio; uintptr_t spa = (uintptr_t)wsp->walk_data; if (mdb_ctf_vread(&zio, ZFS_STRUCT "zio", "mdb_zio_t", wsp->walk_addr, 0) == -1) return (WALK_ERR); if (spa != 0 && spa != zio.io_spa) return (WALK_NEXT); /* If the parent list is not empty, ignore */ if (zio.io_parent_list.list_head.list_next != wsp->walk_addr + mdb_ctf_offsetof_by_name(ZFS_STRUCT "zio", "io_parent_list") + mdb_ctf_offsetof_by_name("struct list", "list_head")) return (WALK_NEXT); return (wsp->walk_callback(wsp->walk_addr, &zio, wsp->walk_cbdata)); } /* * ::zfs_blkstats * * -v print verbose per-level information * */ static int zfs_blkstats(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { boolean_t verbose = B_FALSE; zfs_all_blkstats_t stats; dmu_object_type_t t; zfs_blkstat_t *tzb; uint64_t ditto; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (GETMEMB(addr, "spa", spa_dsl_pool, addr) || GETMEMB(addr, "dsl_pool", dp_blkstats, addr) || mdb_vread(&stats, sizeof (zfs_all_blkstats_t), addr) == -1) { mdb_warn("failed to read data at %p;", addr); mdb_printf("maybe no stats? run \"zpool scrub\" first."); return (DCMD_ERR); } tzb = &stats.zab_type[DN_MAX_LEVELS][DMU_OT_TOTAL]; if (tzb->zb_gangs != 0) { mdb_printf("Ganged blocks: %llu\n", (longlong_t)tzb->zb_gangs); } ditto = tzb->zb_ditto_2_of_2_samevdev + tzb->zb_ditto_2_of_3_samevdev + tzb->zb_ditto_3_of_3_samevdev; if (ditto != 0) { mdb_printf("Dittoed blocks on same vdev: %llu\n", (longlong_t)ditto); } mdb_printf("\nBlocks\tLSIZE\tPSIZE\tASIZE" "\t avg\t comp\t%%Total\tType\n"); for (t = 0; t <= DMU_OT_TOTAL; t++) { char csize[MDB_NICENUM_BUFLEN], lsize[MDB_NICENUM_BUFLEN]; char psize[MDB_NICENUM_BUFLEN], asize[MDB_NICENUM_BUFLEN]; char avg[MDB_NICENUM_BUFLEN]; char comp[MDB_NICENUM_BUFLEN], pct[MDB_NICENUM_BUFLEN]; char typename[64]; int l; if (t == DMU_OT_DEFERRED) strcpy(typename, "deferred free"); else if (t == DMU_OT_OTHER) strcpy(typename, "other"); else if (t == DMU_OT_TOTAL) strcpy(typename, "Total"); else if (enum_lookup("enum dmu_object_type", t, "DMU_OT_", sizeof (typename), typename) == -1) { mdb_warn("failed to read type name"); return (DCMD_ERR); } if (stats.zab_type[DN_MAX_LEVELS][t].zb_asize == 0) continue; for (l = -1; l < DN_MAX_LEVELS; l++) { int level = (l == -1 ? DN_MAX_LEVELS : l); zfs_blkstat_t *zb = &stats.zab_type[level][t]; if (zb->zb_asize == 0) continue; /* * Don't print each level unless requested. */ if (!verbose && level != DN_MAX_LEVELS) continue; /* * If all the space is level 0, don't print the * level 0 separately. */ if (level == 0 && zb->zb_asize == stats.zab_type[DN_MAX_LEVELS][t].zb_asize) continue; mdb_nicenum(zb->zb_count, csize); mdb_nicenum(zb->zb_lsize, lsize); mdb_nicenum(zb->zb_psize, psize); mdb_nicenum(zb->zb_asize, asize); mdb_nicenum(zb->zb_asize / zb->zb_count, avg); (void) mdb_snprintfrac(comp, MDB_NICENUM_BUFLEN, zb->zb_lsize, zb->zb_psize, 2); (void) mdb_snprintfrac(pct, MDB_NICENUM_BUFLEN, 100 * zb->zb_asize, tzb->zb_asize, 2); mdb_printf("%6s\t%5s\t%5s\t%5s\t%5s" "\t%5s\t%6s\t", csize, lsize, psize, asize, avg, comp, pct); if (level == DN_MAX_LEVELS) mdb_printf("%s\n", typename); else mdb_printf(" L%d %s\n", level, typename); } } return (DCMD_OK); } typedef struct mdb_reference { uintptr_t ref_holder; uintptr_t ref_removed; uint64_t ref_number; } mdb_reference_t; /* ARGSUSED */ static int reference_cb(uintptr_t addr, const void *ignored, void *arg) { mdb_reference_t ref; boolean_t holder_is_str = B_FALSE; char holder_str[128]; boolean_t removed = (boolean_t)arg; if (mdb_ctf_vread(&ref, "reference_t", "mdb_reference_t", addr, 0) == -1) return (DCMD_ERR); if (mdb_readstr(holder_str, sizeof (holder_str), ref.ref_holder) != -1) holder_is_str = strisprint(holder_str); if (removed) mdb_printf("removed "); mdb_printf("reference "); if (ref.ref_number != 1) mdb_printf("with count=%llu ", ref.ref_number); mdb_printf("with tag %lx", ref.ref_holder); if (holder_is_str) mdb_printf(" \"%s\"", holder_str); mdb_printf(", held at:\n"); (void) mdb_call_dcmd("whatis", addr, DCMD_ADDRSPEC, 0, NULL); if (removed) { mdb_printf("removed at:\n"); (void) mdb_call_dcmd("whatis", ref.ref_removed, DCMD_ADDRSPEC, 0, NULL); } mdb_printf("\n"); return (WALK_NEXT); } typedef struct mdb_zfs_refcount { uint64_t rc_count; } mdb_zfs_refcount_t; typedef struct mdb_zfs_refcount_removed { uint_t rc_removed_count; } mdb_zfs_refcount_removed_t; typedef struct mdb_zfs_refcount_tracked { boolean_t rc_tracked; } mdb_zfs_refcount_tracked_t; /* ARGSUSED */ static int zfs_refcount(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_zfs_refcount_t rc; mdb_zfs_refcount_removed_t rcr; mdb_zfs_refcount_tracked_t rct; int off; boolean_t released = B_FALSE; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'r', MDB_OPT_SETBITS, B_TRUE, &released, NULL) != argc) return (DCMD_USAGE); if (mdb_ctf_vread(&rc, "zfs_refcount_t", "mdb_zfs_refcount_t", addr, 0) == -1) return (DCMD_ERR); if (mdb_ctf_vread(&rcr, "zfs_refcount_t", "mdb_zfs_refcount_removed_t", addr, MDB_CTF_VREAD_QUIET) == -1) { mdb_printf("zfs_refcount_t at %p has %llu holds (untracked)\n", addr, (longlong_t)rc.rc_count); return (DCMD_OK); } if (mdb_ctf_vread(&rct, "zfs_refcount_t", "mdb_zfs_refcount_tracked_t", addr, MDB_CTF_VREAD_QUIET) == -1) { /* If this is an old target, it might be tracked. */ rct.rc_tracked = B_TRUE; } mdb_printf("zfs_refcount_t at %p has %llu current holds, " "%llu recently released holds\n", addr, (longlong_t)rc.rc_count, (longlong_t)rcr.rc_removed_count); if (rct.rc_tracked && rc.rc_count > 0) mdb_printf("current holds:\n"); off = mdb_ctf_offsetof_by_name("zfs_refcount_t", "rc_tree"); if (off == -1) return (DCMD_ERR); mdb_pwalk("avl", reference_cb, (void *)B_FALSE, addr + off); if (released && rcr.rc_removed_count > 0) { mdb_printf("released holds:\n"); off = mdb_ctf_offsetof_by_name("zfs_refcount_t", "rc_removed"); if (off == -1) return (DCMD_ERR); mdb_pwalk("list", reference_cb, (void *)B_TRUE, addr + off); } return (DCMD_OK); } /* ARGSUSED */ static int sa_attr_table(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { sa_attr_table_t *table; sa_os_t sa_os; char *name; int i; if (mdb_vread(&sa_os, sizeof (sa_os_t), addr) == -1) { mdb_warn("failed to read sa_os at %p", addr); return (DCMD_ERR); } table = mdb_alloc(sizeof (sa_attr_table_t) * sa_os.sa_num_attrs, UM_SLEEP | UM_GC); name = mdb_alloc(MAXPATHLEN, UM_SLEEP | UM_GC); if (mdb_vread(table, sizeof (sa_attr_table_t) * sa_os.sa_num_attrs, (uintptr_t)sa_os.sa_attr_table) == -1) { mdb_warn("failed to read sa_os at %p", addr); return (DCMD_ERR); } mdb_printf("%%-10s %-10s %-10s %-10s %s%\n", "ATTR ID", "REGISTERED", "LENGTH", "BSWAP", "NAME"); for (i = 0; i != sa_os.sa_num_attrs; i++) { mdb_readstr(name, MAXPATHLEN, (uintptr_t)table[i].sa_name); mdb_printf("%5x %8x %8x %8x %-s\n", (int)table[i].sa_attr, (int)table[i].sa_registered, (int)table[i].sa_length, table[i].sa_byteswap, name); } return (DCMD_OK); } static int sa_get_off_table(uintptr_t addr, uint32_t **off_tab, int attr_count) { uintptr_t idx_table; if (GETMEMB(addr, "sa_idx_tab", sa_idx_tab, idx_table)) { mdb_printf("can't find offset table in sa_idx_tab\n"); return (-1); } *off_tab = mdb_alloc(attr_count * sizeof (uint32_t), UM_SLEEP | UM_GC); if (mdb_vread(*off_tab, attr_count * sizeof (uint32_t), idx_table) == -1) { mdb_warn("failed to attribute offset table %p", idx_table); return (-1); } return (DCMD_OK); } /*ARGSUSED*/ static int sa_attr_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint32_t *offset_tab; int attr_count; uint64_t attr_id; uintptr_t attr_addr; uintptr_t bonus_tab, spill_tab; uintptr_t db_bonus, db_spill; uintptr_t os, os_sa; uintptr_t db_data; if (argc != 1) return (DCMD_USAGE); if (argv[0].a_type == MDB_TYPE_STRING) attr_id = mdb_strtoull(argv[0].a_un.a_str); else return (DCMD_USAGE); if (GETMEMB(addr, "sa_handle", sa_bonus_tab, bonus_tab) || GETMEMB(addr, "sa_handle", sa_spill_tab, spill_tab) || GETMEMB(addr, "sa_handle", sa_os, os) || GETMEMB(addr, "sa_handle", sa_bonus, db_bonus) || GETMEMB(addr, "sa_handle", sa_spill, db_spill)) { mdb_printf("Can't find necessary information in sa_handle " "in sa_handle\n"); return (DCMD_ERR); } if (GETMEMB(os, "objset", os_sa, os_sa)) { mdb_printf("Can't find os_sa in objset\n"); return (DCMD_ERR); } if (GETMEMB(os_sa, "sa_os", sa_num_attrs, attr_count)) { mdb_printf("Can't find sa_num_attrs\n"); return (DCMD_ERR); } if (attr_id > attr_count) { mdb_printf("attribute id number is out of range\n"); return (DCMD_ERR); } if (bonus_tab) { if (sa_get_off_table(bonus_tab, &offset_tab, attr_count) == -1) { return (DCMD_ERR); } if (GETMEMB(db_bonus, "dmu_buf", db_data, db_data)) { mdb_printf("can't find db_data in bonus dbuf\n"); return (DCMD_ERR); } } if (bonus_tab && !TOC_ATTR_PRESENT(offset_tab[attr_id]) && spill_tab == 0) { mdb_printf("Attribute does not exist\n"); return (DCMD_ERR); } else if (!TOC_ATTR_PRESENT(offset_tab[attr_id]) && spill_tab) { if (sa_get_off_table(spill_tab, &offset_tab, attr_count) == -1) { return (DCMD_ERR); } if (GETMEMB(db_spill, "dmu_buf", db_data, db_data)) { mdb_printf("can't find db_data in spill dbuf\n"); return (DCMD_ERR); } if (!TOC_ATTR_PRESENT(offset_tab[attr_id])) { mdb_printf("Attribute does not exist\n"); return (DCMD_ERR); } } attr_addr = db_data + TOC_OFF(offset_tab[attr_id]); mdb_printf("%p\n", attr_addr); return (DCMD_OK); } /* ARGSUSED */ static int zfs_ace_print_common(uintptr_t addr, uint_t flags, uint64_t id, uint32_t access_mask, uint16_t ace_flags, uint16_t ace_type, int verbose) { if (DCMD_HDRSPEC(flags) && !verbose) mdb_printf("%%-?s %-8s %-8s %-8s %s%\n", "ADDR", "FLAGS", "MASK", "TYPE", "ID"); if (!verbose) { mdb_printf("%0?p %-8x %-8x %-8x %-llx\n", addr, ace_flags, access_mask, ace_type, id); return (DCMD_OK); } switch (ace_flags & ACE_TYPE_FLAGS) { case ACE_OWNER: mdb_printf("owner@:"); break; case (ACE_IDENTIFIER_GROUP | ACE_GROUP): mdb_printf("group@:"); break; case ACE_EVERYONE: mdb_printf("everyone@:"); break; case ACE_IDENTIFIER_GROUP: mdb_printf("group:%llx:", (u_longlong_t)id); break; case 0: /* User entry */ mdb_printf("user:%llx:", (u_longlong_t)id); break; } /* print out permission mask */ if (access_mask & ACE_READ_DATA) mdb_printf("r"); else mdb_printf("-"); if (access_mask & ACE_WRITE_DATA) mdb_printf("w"); else mdb_printf("-"); if (access_mask & ACE_EXECUTE) mdb_printf("x"); else mdb_printf("-"); if (access_mask & ACE_APPEND_DATA) mdb_printf("p"); else mdb_printf("-"); if (access_mask & ACE_DELETE) mdb_printf("d"); else mdb_printf("-"); if (access_mask & ACE_DELETE_CHILD) mdb_printf("D"); else mdb_printf("-"); if (access_mask & ACE_READ_ATTRIBUTES) mdb_printf("a"); else mdb_printf("-"); if (access_mask & ACE_WRITE_ATTRIBUTES) mdb_printf("A"); else mdb_printf("-"); if (access_mask & ACE_READ_NAMED_ATTRS) mdb_printf("R"); else mdb_printf("-"); if (access_mask & ACE_WRITE_NAMED_ATTRS) mdb_printf("W"); else mdb_printf("-"); if (access_mask & ACE_READ_ACL) mdb_printf("c"); else mdb_printf("-"); if (access_mask & ACE_WRITE_ACL) mdb_printf("C"); else mdb_printf("-"); if (access_mask & ACE_WRITE_OWNER) mdb_printf("o"); else mdb_printf("-"); if (access_mask & ACE_SYNCHRONIZE) mdb_printf("s"); else mdb_printf("-"); mdb_printf(":"); /* Print out inheritance flags */ if (ace_flags & ACE_FILE_INHERIT_ACE) mdb_printf("f"); else mdb_printf("-"); if (ace_flags & ACE_DIRECTORY_INHERIT_ACE) mdb_printf("d"); else mdb_printf("-"); if (ace_flags & ACE_INHERIT_ONLY_ACE) mdb_printf("i"); else mdb_printf("-"); if (ace_flags & ACE_NO_PROPAGATE_INHERIT_ACE) mdb_printf("n"); else mdb_printf("-"); if (ace_flags & ACE_SUCCESSFUL_ACCESS_ACE_FLAG) mdb_printf("S"); else mdb_printf("-"); if (ace_flags & ACE_FAILED_ACCESS_ACE_FLAG) mdb_printf("F"); else mdb_printf("-"); if (ace_flags & ACE_INHERITED_ACE) mdb_printf("I"); else mdb_printf("-"); switch (ace_type) { case ACE_ACCESS_ALLOWED_ACE_TYPE: mdb_printf(":allow\n"); break; case ACE_ACCESS_DENIED_ACE_TYPE: mdb_printf(":deny\n"); break; case ACE_SYSTEM_AUDIT_ACE_TYPE: mdb_printf(":audit\n"); break; case ACE_SYSTEM_ALARM_ACE_TYPE: mdb_printf(":alarm\n"); break; default: mdb_printf(":?\n"); } return (DCMD_OK); } /* ARGSUSED */ static int zfs_ace_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { zfs_ace_t zace; int verbose = FALSE; uint64_t id; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, TRUE, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&zace, sizeof (zfs_ace_t), addr) == -1) { mdb_warn("failed to read zfs_ace_t"); return (DCMD_ERR); } if ((zace.z_hdr.z_flags & ACE_TYPE_FLAGS) == 0 || (zace.z_hdr.z_flags & ACE_TYPE_FLAGS) == ACE_IDENTIFIER_GROUP) id = zace.z_fuid; else id = -1; return (zfs_ace_print_common(addr, flags, id, zace.z_hdr.z_access_mask, zace.z_hdr.z_flags, zace.z_hdr.z_type, verbose)); } /* ARGSUSED */ static int zfs_ace0_print(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ace_t ace; uint64_t id; int verbose = FALSE; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, TRUE, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&ace, sizeof (ace_t), addr) == -1) { mdb_warn("failed to read ace_t"); return (DCMD_ERR); } if ((ace.a_flags & ACE_TYPE_FLAGS) == 0 || (ace.a_flags & ACE_TYPE_FLAGS) == ACE_IDENTIFIER_GROUP) id = ace.a_who; else id = -1; return (zfs_ace_print_common(addr, flags, id, ace.a_access_mask, ace.a_flags, ace.a_type, verbose)); } typedef struct acl_dump_args { int a_argc; const mdb_arg_t *a_argv; uint16_t a_version; int a_flags; } acl_dump_args_t; /* ARGSUSED */ static int acl_aces_cb(uintptr_t addr, const void *unknown, void *arg) { acl_dump_args_t *acl_args = (acl_dump_args_t *)arg; if (acl_args->a_version == 1) { if (mdb_call_dcmd("zfs_ace", addr, DCMD_ADDRSPEC|acl_args->a_flags, acl_args->a_argc, acl_args->a_argv) != DCMD_OK) { return (WALK_ERR); } } else { if (mdb_call_dcmd("zfs_ace0", addr, DCMD_ADDRSPEC|acl_args->a_flags, acl_args->a_argc, acl_args->a_argv) != DCMD_OK) { return (WALK_ERR); } } acl_args->a_flags = DCMD_LOOP; return (WALK_NEXT); } /* ARGSUSED */ static int acl_cb(uintptr_t addr, const void *unknown, void *arg) { acl_dump_args_t *acl_args = (acl_dump_args_t *)arg; if (acl_args->a_version == 1) { if (mdb_pwalk("zfs_acl_node_aces", acl_aces_cb, arg, addr) != 0) { mdb_warn("can't walk ACEs"); return (DCMD_ERR); } } else { if (mdb_pwalk("zfs_acl_node_aces0", acl_aces_cb, arg, addr) != 0) { mdb_warn("can't walk ACEs"); return (DCMD_ERR); } } return (WALK_NEXT); } /* ARGSUSED */ static int zfs_acl_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { zfs_acl_t zacl; int verbose = FALSE; acl_dump_args_t acl_args; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &verbose, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&zacl, sizeof (zfs_acl_t), addr) == -1) { mdb_warn("failed to read zfs_acl_t"); return (DCMD_ERR); } acl_args.a_argc = argc; acl_args.a_argv = argv; acl_args.a_version = zacl.z_version; acl_args.a_flags = DCMD_LOOPFIRST; if (mdb_pwalk("zfs_acl_node", acl_cb, &acl_args, addr) != 0) { mdb_warn("can't walk ACL"); return (DCMD_ERR); } return (DCMD_OK); } /* ARGSUSED */ static int zfs_acl_node_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("must supply address of zfs_acl_node_t\n"); return (WALK_ERR); } wsp->walk_addr += mdb_ctf_offsetof_by_name(ZFS_STRUCT "zfs_acl", "z_acl"); if (mdb_layered_walk("list", wsp) == -1) { mdb_warn("failed to walk 'list'\n"); return (WALK_ERR); } return (WALK_NEXT); } static int zfs_acl_node_walk_step(mdb_walk_state_t *wsp) { zfs_acl_node_t aclnode; if (mdb_vread(&aclnode, sizeof (zfs_acl_node_t), wsp->walk_addr) == -1) { mdb_warn("failed to read zfs_acl_node at %p", wsp->walk_addr); return (WALK_ERR); } return (wsp->walk_callback(wsp->walk_addr, &aclnode, wsp->walk_cbdata)); } typedef struct ace_walk_data { int ace_count; int ace_version; } ace_walk_data_t; static int zfs_aces_walk_init_common(mdb_walk_state_t *wsp, int version, int ace_count, uintptr_t ace_data) { ace_walk_data_t *ace_walk_data; if (wsp->walk_addr == 0) { mdb_warn("must supply address of zfs_acl_node_t\n"); return (WALK_ERR); } ace_walk_data = mdb_alloc(sizeof (ace_walk_data_t), UM_SLEEP | UM_GC); ace_walk_data->ace_count = ace_count; ace_walk_data->ace_version = version; wsp->walk_addr = ace_data; wsp->walk_data = ace_walk_data; return (WALK_NEXT); } static int zfs_acl_node_aces_walk_init_common(mdb_walk_state_t *wsp, int version) { static int gotid; static mdb_ctf_id_t acl_id; int z_ace_count; uintptr_t z_acldata; if (!gotid) { if (mdb_ctf_lookup_by_name("struct zfs_acl_node", &acl_id) == -1) { mdb_warn("couldn't find struct zfs_acl_node"); return (DCMD_ERR); } gotid = TRUE; } if (GETMEMBID(wsp->walk_addr, &acl_id, z_ace_count, z_ace_count)) { return (DCMD_ERR); } if (GETMEMBID(wsp->walk_addr, &acl_id, z_acldata, z_acldata)) { return (DCMD_ERR); } return (zfs_aces_walk_init_common(wsp, version, z_ace_count, z_acldata)); } /* ARGSUSED */ static int zfs_acl_node_aces_walk_init(mdb_walk_state_t *wsp) { return (zfs_acl_node_aces_walk_init_common(wsp, 1)); } /* ARGSUSED */ static int zfs_acl_node_aces0_walk_init(mdb_walk_state_t *wsp) { return (zfs_acl_node_aces_walk_init_common(wsp, 0)); } static int zfs_aces_walk_step(mdb_walk_state_t *wsp) { ace_walk_data_t *ace_data = wsp->walk_data; zfs_ace_t zace; ace_t *acep; int status; int entry_type; int allow_type; uintptr_t ptr; if (ace_data->ace_count == 0) return (WALK_DONE); if (mdb_vread(&zace, sizeof (zfs_ace_t), wsp->walk_addr) == -1) { mdb_warn("failed to read zfs_ace_t at %#lx", wsp->walk_addr); return (WALK_ERR); } switch (ace_data->ace_version) { case 0: acep = (ace_t *)&zace; entry_type = acep->a_flags & ACE_TYPE_FLAGS; allow_type = acep->a_type; break; case 1: entry_type = zace.z_hdr.z_flags & ACE_TYPE_FLAGS; allow_type = zace.z_hdr.z_type; break; default: return (WALK_ERR); } ptr = (uintptr_t)wsp->walk_addr; switch (entry_type) { case ACE_OWNER: case ACE_EVERYONE: case (ACE_IDENTIFIER_GROUP | ACE_GROUP): ptr += ace_data->ace_version == 0 ? sizeof (ace_t) : sizeof (zfs_ace_hdr_t); break; case ACE_IDENTIFIER_GROUP: default: switch (allow_type) { case ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE: case ACE_ACCESS_DENIED_OBJECT_ACE_TYPE: case ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE: case ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE: ptr += ace_data->ace_version == 0 ? sizeof (ace_t) : sizeof (zfs_object_ace_t); break; default: ptr += ace_data->ace_version == 0 ? sizeof (ace_t) : sizeof (zfs_ace_t); break; } } ace_data->ace_count--; status = wsp->walk_callback(wsp->walk_addr, (void *)(uintptr_t)&zace, wsp->walk_cbdata); wsp->walk_addr = ptr; return (status); } typedef struct mdb_zfs_rrwlock { uintptr_t rr_writer; boolean_t rr_writer_wanted; } mdb_zfs_rrwlock_t; static uint_t rrw_key; /* ARGSUSED */ static int rrwlock(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_zfs_rrwlock_t rrw; if (rrw_key == 0) { if (mdb_ctf_readsym(&rrw_key, "uint_t", "rrw_tsd_key", 0) == -1) return (DCMD_ERR); } if (mdb_ctf_vread(&rrw, "rrwlock_t", "mdb_zfs_rrwlock_t", addr, 0) == -1) return (DCMD_ERR); if (rrw.rr_writer != 0) { mdb_printf("write lock held by thread %lx\n", rrw.rr_writer); return (DCMD_OK); } if (rrw.rr_writer_wanted) { mdb_printf("writer wanted\n"); } mdb_printf("anonymous references:\n"); (void) mdb_call_dcmd("zfs_refcount", addr + mdb_ctf_offsetof_by_name(ZFS_STRUCT "rrwlock", "rr_anon_rcount"), DCMD_ADDRSPEC, 0, NULL); mdb_printf("linked references:\n"); (void) mdb_call_dcmd("zfs_refcount", addr + mdb_ctf_offsetof_by_name(ZFS_STRUCT "rrwlock", "rr_linked_rcount"), DCMD_ADDRSPEC, 0, NULL); /* * XXX This should find references from * "::walk thread | ::tsd -v ", but there is no support * for programmatic consumption of dcmds, so this would be * difficult, potentially requiring reimplementing ::tsd (both * user and kernel versions) in this MDB module. */ return (DCMD_OK); } typedef struct mdb_arc_buf_hdr_t { uint16_t b_psize; uint16_t b_lsize; struct { uint32_t b_bufcnt; uintptr_t b_state; } b_l1hdr; } mdb_arc_buf_hdr_t; enum arc_cflags { ARC_CFLAG_VERBOSE = 1 << 0, ARC_CFLAG_ANON = 1 << 1, ARC_CFLAG_MRU = 1 << 2, ARC_CFLAG_MFU = 1 << 3, ARC_CFLAG_BUFS = 1 << 4, }; typedef struct arc_compression_stats_data { GElf_Sym anon_sym; /* ARC_anon symbol */ GElf_Sym mru_sym; /* ARC_mru symbol */ GElf_Sym mrug_sym; /* ARC_mru_ghost symbol */ GElf_Sym mfu_sym; /* ARC_mfu symbol */ GElf_Sym mfug_sym; /* ARC_mfu_ghost symbol */ GElf_Sym l2c_sym; /* ARC_l2c_only symbol */ uint64_t *anon_c_hist; /* histogram of compressed sizes in anon */ uint64_t *anon_u_hist; /* histogram of uncompressed sizes in anon */ uint64_t *anon_bufs; /* histogram of buffer counts in anon state */ uint64_t *mru_c_hist; /* histogram of compressed sizes in mru */ uint64_t *mru_u_hist; /* histogram of uncompressed sizes in mru */ uint64_t *mru_bufs; /* histogram of buffer counts in mru */ uint64_t *mfu_c_hist; /* histogram of compressed sizes in mfu */ uint64_t *mfu_u_hist; /* histogram of uncompressed sizes in mfu */ uint64_t *mfu_bufs; /* histogram of buffer counts in mfu */ uint64_t *all_c_hist; /* histogram of compressed anon + mru + mfu */ uint64_t *all_u_hist; /* histogram of uncompressed anon + mru + mfu */ uint64_t *all_bufs; /* histogram of buffer counts in all states */ int arc_cflags; /* arc compression flags, specified by user */ int hist_nbuckets; /* number of buckets in each histogram */ ulong_t l1hdr_off; /* offset of b_l1hdr in arc_buf_hdr_t */ } arc_compression_stats_data_t; int highbit64(uint64_t i) { int h = 1; if (i == 0) return (0); if (i & 0xffffffff00000000ULL) { h += 32; i >>= 32; } if (i & 0xffff0000) { h += 16; i >>= 16; } if (i & 0xff00) { h += 8; i >>= 8; } if (i & 0xf0) { h += 4; i >>= 4; } if (i & 0xc) { h += 2; i >>= 2; } if (i & 0x2) { h += 1; } return (h); } /* ARGSUSED */ static int arc_compression_stats_cb(uintptr_t addr, const void *unknown, void *arg) { arc_compression_stats_data_t *data = arg; arc_flags_t flags; mdb_arc_buf_hdr_t hdr; int cbucket, ubucket, bufcnt; /* * mdb_ctf_vread() uses the sizeof the target type (e.g. * sizeof (arc_buf_hdr_t) in the target) to read in the entire contents * of the target type into a buffer and then copy the values of the * desired members from the mdb typename (e.g. mdb_arc_buf_hdr_t) from * this buffer. Unfortunately, the way arc_buf_hdr_t is used by zfs, * the actual size allocated by the kernel for arc_buf_hdr_t is often * smaller than `sizeof (arc_buf_hdr_t)` (see the definitions of * l1arc_buf_hdr_t and arc_buf_hdr_t in * usr/src/uts/common/fs/zfs/arc.c). Attempting to read the entire * contents of arc_buf_hdr_t from the target (as mdb_ctf_vread() does) * can cause an error if the allocated size is indeed smaller--it's * possible that the 'missing' trailing members of arc_buf_hdr_t * (l1arc_buf_hdr_t and/or arc_buf_hdr_crypt_t) may fall into unmapped * memory. * * We use the GETMEMB macro instead which performs an mdb_vread() * but only reads enough of the target to retrieve the desired struct * member instead of the entire struct. */ if (GETMEMB(addr, "arc_buf_hdr", b_flags, flags) == -1) return (WALK_ERR); /* * We only count headers that have data loaded in the kernel. * This means an L1 header must be present as well as the data * that corresponds to the L1 header. If there's no L1 header, * we can skip the arc_buf_hdr_t completely. If it's present, we * must look at the ARC state (b_l1hdr.b_state) to determine if * the data is present. */ if ((flags & ARC_FLAG_HAS_L1HDR) == 0) return (WALK_NEXT); if (GETMEMB(addr, "arc_buf_hdr", b_psize, hdr.b_psize) == -1 || GETMEMB(addr, "arc_buf_hdr", b_lsize, hdr.b_lsize) == -1 || GETMEMB(addr + data->l1hdr_off, "l1arc_buf_hdr", b_bufcnt, hdr.b_l1hdr.b_bufcnt) == -1 || GETMEMB(addr + data->l1hdr_off, "l1arc_buf_hdr", b_state, hdr.b_l1hdr.b_state) == -1) return (WALK_ERR); /* * Headers in the ghost states, or the l2c_only state don't have * arc buffers linked off of them. Thus, their compressed size * is meaningless, so we skip these from the stats. */ if (hdr.b_l1hdr.b_state == data->mrug_sym.st_value || hdr.b_l1hdr.b_state == data->mfug_sym.st_value || hdr.b_l1hdr.b_state == data->l2c_sym.st_value) { return (WALK_NEXT); } /* * The physical size (compressed) and logical size * (uncompressed) are in units of SPA_MINBLOCKSIZE. By default, * we use the log2 of this value (rounded down to the nearest * integer) to determine the bucket to assign this header to. * Thus, the histogram is logarithmic with respect to the size * of the header. For example, the following is a mapping of the * bucket numbers and the range of header sizes they correspond to: * * 0: 0 byte headers * 1: 512 byte headers * 2: [1024 - 2048) byte headers * 3: [2048 - 4096) byte headers * 4: [4096 - 8192) byte headers * 5: [8192 - 16394) byte headers * 6: [16384 - 32768) byte headers * 7: [32768 - 65536) byte headers * 8: [65536 - 131072) byte headers * 9: 131072 byte headers * * If the ARC_CFLAG_VERBOSE flag was specified, we use the * physical and logical sizes directly. Thus, the histogram will * no longer be logarithmic; instead it will be linear with * respect to the size of the header. The following is a mapping * of the first many bucket numbers and the header size they * correspond to: * * 0: 0 byte headers * 1: 512 byte headers * 2: 1024 byte headers * 3: 1536 byte headers * 4: 2048 byte headers * 5: 2560 byte headers * 6: 3072 byte headers * * And so on. Keep in mind that a range of sizes isn't used in * the case of linear scale because the headers can only * increment or decrement in sizes of 512 bytes. So, it's not * possible for a header to be sized in between whats listed * above. * * Also, the above mapping values were calculated assuming a * SPA_MINBLOCKSHIFT of 512 bytes and a SPA_MAXBLOCKSIZE of 128K. */ if (data->arc_cflags & ARC_CFLAG_VERBOSE) { cbucket = hdr.b_psize; ubucket = hdr.b_lsize; } else { cbucket = highbit64(hdr.b_psize); ubucket = highbit64(hdr.b_lsize); } bufcnt = hdr.b_l1hdr.b_bufcnt; if (bufcnt >= data->hist_nbuckets) bufcnt = data->hist_nbuckets - 1; /* Ensure we stay within the bounds of the histogram array */ ASSERT3U(cbucket, <, data->hist_nbuckets); ASSERT3U(ubucket, <, data->hist_nbuckets); if (hdr.b_l1hdr.b_state == data->anon_sym.st_value) { data->anon_c_hist[cbucket]++; data->anon_u_hist[ubucket]++; data->anon_bufs[bufcnt]++; } else if (hdr.b_l1hdr.b_state == data->mru_sym.st_value) { data->mru_c_hist[cbucket]++; data->mru_u_hist[ubucket]++; data->mru_bufs[bufcnt]++; } else if (hdr.b_l1hdr.b_state == data->mfu_sym.st_value) { data->mfu_c_hist[cbucket]++; data->mfu_u_hist[ubucket]++; data->mfu_bufs[bufcnt]++; } data->all_c_hist[cbucket]++; data->all_u_hist[ubucket]++; data->all_bufs[bufcnt]++; return (WALK_NEXT); } /* ARGSUSED */ static int arc_compression_stats(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { arc_compression_stats_data_t data = { 0 }; unsigned int max_shifted = SPA_MAXBLOCKSIZE >> SPA_MINBLOCKSHIFT; unsigned int hist_size; char range[32]; int rc = DCMD_OK; int off; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, ARC_CFLAG_VERBOSE, &data.arc_cflags, 'a', MDB_OPT_SETBITS, ARC_CFLAG_ANON, &data.arc_cflags, 'b', MDB_OPT_SETBITS, ARC_CFLAG_BUFS, &data.arc_cflags, 'r', MDB_OPT_SETBITS, ARC_CFLAG_MRU, &data.arc_cflags, 'f', MDB_OPT_SETBITS, ARC_CFLAG_MFU, &data.arc_cflags, NULL) != argc) return (DCMD_USAGE); if (mdb_lookup_by_obj(ZFS_OBJ_NAME, "ARC_anon", &data.anon_sym) || mdb_lookup_by_obj(ZFS_OBJ_NAME, "ARC_mru", &data.mru_sym) || mdb_lookup_by_obj(ZFS_OBJ_NAME, "ARC_mru_ghost", &data.mrug_sym) || mdb_lookup_by_obj(ZFS_OBJ_NAME, "ARC_mfu", &data.mfu_sym) || mdb_lookup_by_obj(ZFS_OBJ_NAME, "ARC_mfu_ghost", &data.mfug_sym) || mdb_lookup_by_obj(ZFS_OBJ_NAME, "ARC_l2c_only", &data.l2c_sym)) { mdb_warn("can't find arc state symbol"); return (DCMD_ERR); } /* * Determine the maximum expected size for any header, and use * this to determine the number of buckets needed for each * histogram. If ARC_CFLAG_VERBOSE is specified, this value is * used directly; otherwise the log2 of the maximum size is * used. Thus, if using a log2 scale there's a maximum of 10 * possible buckets, while the linear scale (when using * ARC_CFLAG_VERBOSE) has a maximum of 257 buckets. */ if (data.arc_cflags & ARC_CFLAG_VERBOSE) data.hist_nbuckets = max_shifted + 1; else data.hist_nbuckets = highbit64(max_shifted) + 1; hist_size = sizeof (uint64_t) * data.hist_nbuckets; data.anon_c_hist = mdb_zalloc(hist_size, UM_SLEEP); data.anon_u_hist = mdb_zalloc(hist_size, UM_SLEEP); data.anon_bufs = mdb_zalloc(hist_size, UM_SLEEP); data.mru_c_hist = mdb_zalloc(hist_size, UM_SLEEP); data.mru_u_hist = mdb_zalloc(hist_size, UM_SLEEP); data.mru_bufs = mdb_zalloc(hist_size, UM_SLEEP); data.mfu_c_hist = mdb_zalloc(hist_size, UM_SLEEP); data.mfu_u_hist = mdb_zalloc(hist_size, UM_SLEEP); data.mfu_bufs = mdb_zalloc(hist_size, UM_SLEEP); data.all_c_hist = mdb_zalloc(hist_size, UM_SLEEP); data.all_u_hist = mdb_zalloc(hist_size, UM_SLEEP); data.all_bufs = mdb_zalloc(hist_size, UM_SLEEP); if ((off = mdb_ctf_offsetof_by_name(ZFS_STRUCT "arc_buf_hdr", "b_l1hdr")) == -1) { mdb_warn("could not get offset of b_l1hdr from arc_buf_hdr_t"); rc = DCMD_ERR; goto out; } data.l1hdr_off = off; if (mdb_walk("arc_buf_hdr_t_full", arc_compression_stats_cb, &data) != 0) { mdb_warn("can't walk arc_buf_hdr's"); rc = DCMD_ERR; goto out; } if (data.arc_cflags & ARC_CFLAG_VERBOSE) { rc = mdb_snprintf(range, sizeof (range), "[n*%llu, (n+1)*%llu)", SPA_MINBLOCKSIZE, SPA_MINBLOCKSIZE); } else { rc = mdb_snprintf(range, sizeof (range), "[2^(n-1)*%llu, 2^n*%llu)", SPA_MINBLOCKSIZE, SPA_MINBLOCKSIZE); } if (rc < 0) { /* snprintf failed, abort the dcmd */ rc = DCMD_ERR; goto out; } else { /* snprintf succeeded above, reset return code */ rc = DCMD_OK; } if (data.arc_cflags & ARC_CFLAG_ANON) { if (data.arc_cflags & ARC_CFLAG_BUFS) { mdb_printf("Histogram of the number of anon buffers " "that are associated with an arc hdr.\n"); dump_histogram(data.anon_bufs, data.hist_nbuckets, 0); mdb_printf("\n"); } mdb_printf("Histogram of compressed anon buffers.\n" "Each bucket represents buffers of size: %s.\n", range); dump_histogram(data.anon_c_hist, data.hist_nbuckets, 0); mdb_printf("\n"); mdb_printf("Histogram of uncompressed anon buffers.\n" "Each bucket represents buffers of size: %s.\n", range); dump_histogram(data.anon_u_hist, data.hist_nbuckets, 0); mdb_printf("\n"); } if (data.arc_cflags & ARC_CFLAG_MRU) { if (data.arc_cflags & ARC_CFLAG_BUFS) { mdb_printf("Histogram of the number of mru buffers " "that are associated with an arc hdr.\n"); dump_histogram(data.mru_bufs, data.hist_nbuckets, 0); mdb_printf("\n"); } mdb_printf("Histogram of compressed mru buffers.\n" "Each bucket represents buffers of size: %s.\n", range); dump_histogram(data.mru_c_hist, data.hist_nbuckets, 0); mdb_printf("\n"); mdb_printf("Histogram of uncompressed mru buffers.\n" "Each bucket represents buffers of size: %s.\n", range); dump_histogram(data.mru_u_hist, data.hist_nbuckets, 0); mdb_printf("\n"); } if (data.arc_cflags & ARC_CFLAG_MFU) { if (data.arc_cflags & ARC_CFLAG_BUFS) { mdb_printf("Histogram of the number of mfu buffers " "that are associated with an arc hdr.\n"); dump_histogram(data.mfu_bufs, data.hist_nbuckets, 0); mdb_printf("\n"); } mdb_printf("Histogram of compressed mfu buffers.\n" "Each bucket represents buffers of size: %s.\n", range); dump_histogram(data.mfu_c_hist, data.hist_nbuckets, 0); mdb_printf("\n"); mdb_printf("Histogram of uncompressed mfu buffers.\n" "Each bucket represents buffers of size: %s.\n", range); dump_histogram(data.mfu_u_hist, data.hist_nbuckets, 0); mdb_printf("\n"); } if (data.arc_cflags & ARC_CFLAG_BUFS) { mdb_printf("Histogram of all buffers that " "are associated with an arc hdr.\n"); dump_histogram(data.all_bufs, data.hist_nbuckets, 0); mdb_printf("\n"); } mdb_printf("Histogram of all compressed buffers.\n" "Each bucket represents buffers of size: %s.\n", range); dump_histogram(data.all_c_hist, data.hist_nbuckets, 0); mdb_printf("\n"); mdb_printf("Histogram of all uncompressed buffers.\n" "Each bucket represents buffers of size: %s.\n", range); dump_histogram(data.all_u_hist, data.hist_nbuckets, 0); out: mdb_free(data.anon_c_hist, hist_size); mdb_free(data.anon_u_hist, hist_size); mdb_free(data.anon_bufs, hist_size); mdb_free(data.mru_c_hist, hist_size); mdb_free(data.mru_u_hist, hist_size); mdb_free(data.mru_bufs, hist_size); mdb_free(data.mfu_c_hist, hist_size); mdb_free(data.mfu_u_hist, hist_size); mdb_free(data.mfu_bufs, hist_size); mdb_free(data.all_c_hist, hist_size); mdb_free(data.all_u_hist, hist_size); mdb_free(data.all_bufs, hist_size); return (rc); } typedef struct mdb_range_seg64 { uint64_t rs_start; uint64_t rs_end; } mdb_range_seg64_t; typedef struct mdb_range_seg32 { uint32_t rs_start; uint32_t rs_end; } mdb_range_seg32_t; /* ARGSUSED */ static int range_tree_cb(uintptr_t addr, const void *unknown, void *arg) { mdb_range_tree_t *rt = (mdb_range_tree_t *)arg; uint64_t start, end; if (rt->rt_type == RANGE_SEG64) { mdb_range_seg64_t rs; if (mdb_ctf_vread(&rs, ZFS_STRUCT "range_seg64", "mdb_range_seg64_t", addr, 0) == -1) return (DCMD_ERR); start = rs.rs_start; end = rs.rs_end; } else { ASSERT3U(rt->rt_type, ==, RANGE_SEG32); mdb_range_seg32_t rs; if (mdb_ctf_vread(&rs, ZFS_STRUCT "range_seg32", "mdb_range_seg32_t", addr, 0) == -1) return (DCMD_ERR); start = ((uint64_t)rs.rs_start << rt->rt_shift) + rt->rt_start; end = ((uint64_t)rs.rs_end << rt->rt_shift) + rt->rt_start; } mdb_printf("\t[%llx %llx) (length %llx)\n", start, end, end - start); return (0); } /* ARGSUSED */ static int range_tree(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_range_tree_t rt; uintptr_t btree_addr; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_ctf_vread(&rt, ZFS_STRUCT "range_tree", "mdb_range_tree_t", addr, 0) == -1) return (DCMD_ERR); mdb_printf("%p: range tree of %llu entries, %llu bytes\n", addr, rt.rt_root.bt_num_elems, rt.rt_space); btree_addr = addr + mdb_ctf_offsetof_by_name(ZFS_STRUCT "range_tree", "rt_root"); if (mdb_pwalk("zfs_btree", range_tree_cb, &rt, btree_addr) != 0) { mdb_warn("can't walk range_tree segments"); return (DCMD_ERR); } return (DCMD_OK); } typedef struct mdb_spa_log_sm { uint64_t sls_sm_obj; uint64_t sls_txg; uint64_t sls_nblocks; uint64_t sls_mscount; } mdb_spa_log_sm_t; /* ARGSUSED */ static int logsm_stats_cb(uintptr_t addr, const void *unknown, void *arg) { mdb_spa_log_sm_t sls; if (mdb_ctf_vread(&sls, ZFS_STRUCT "spa_log_sm", "mdb_spa_log_sm_t", addr, 0) == -1) return (WALK_ERR); mdb_printf("%7lld %7lld %7lld %7lld\n", sls.sls_txg, sls.sls_nblocks, sls.sls_mscount, sls.sls_sm_obj); return (WALK_NEXT); } typedef struct mdb_log_summary_entry { uint64_t lse_start; uint64_t lse_blkcount; uint64_t lse_mscount; } mdb_log_summary_entry_t; /* ARGSUSED */ static int logsm_summary_cb(uintptr_t addr, const void *unknown, void *arg) { mdb_log_summary_entry_t lse; if (mdb_ctf_vread(&lse, ZFS_STRUCT "log_summary_entry", "mdb_log_summary_entry_t", addr, 0) == -1) return (WALK_ERR); mdb_printf("%7lld %7lld %7lld\n", lse.lse_start, lse.lse_blkcount, lse.lse_mscount); return (WALK_NEXT); } /* ARGSUSED */ static int logsm_stats(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); uintptr_t sls_avl_addr = addr + mdb_ctf_offsetof_by_name(ZFS_STRUCT "spa", "spa_sm_logs_by_txg"); uintptr_t summary_addr = addr + mdb_ctf_offsetof_by_name(ZFS_STRUCT "spa", "spa_log_summary"); mdb_printf("Log Entries:\n"); mdb_printf("%7s %7s %7s %7s\n", "txg", "blk", "ms", "obj"); if (mdb_pwalk("avl", logsm_stats_cb, NULL, sls_avl_addr) != 0) return (DCMD_ERR); mdb_printf("\nSummary Entries:\n"); mdb_printf("%7s %7s %7s\n", "txg", "blk", "ms"); if (mdb_pwalk("list", logsm_summary_cb, NULL, summary_addr) != 0) return (DCMD_ERR); return (DCMD_OK); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, and a function * named _mdb_init to return a pointer to our module information. */ static const mdb_dcmd_t dcmds[] = { { "arc", "[-bkmg]", "print ARC variables", arc_print }, { "blkptr", ":", "print blkptr_t", blkptr }, { "dva", ":", "print dva_t", dva }, { "dbuf", ":", "print dmu_buf_impl_t", dbuf }, { "dbuf_stats", ":", "dbuf stats", dbuf_stats }, { "dbufs", "\t[-O objset_t*] [-n objset_name | \"mos\"] " "[-o object | \"mdn\"] \n" "\t[-l level] [-b blkid | \"bonus\"]", "find dmu_buf_impl_t's that match specified criteria", dbufs }, { "abuf_find", "dva_word[0] dva_word[1]", "find arc_buf_hdr_t of a specified DVA", abuf_find }, { "logsm_stats", ":", "print log space map statistics of a spa_t", logsm_stats}, { "spa", "?[-cevmMh]\n" "\t-c display spa config\n" "\t-e display vdev statistics\n" "\t-v display vdev information\n" "\t-m display metaslab statistics\n" "\t-M display metaslab group statistics\n" "\t-h display histogram (requires -m or -M)\n", "spa_t summary", spa_print }, { "spa_config", ":", "print spa_t configuration", spa_print_config }, { "spa_space", ":[-b]", "print spa_t on-disk space usage", spa_space }, { "spa_vdevs", ":[-emMh]\n" "\t-e display vdev statistics\n" "\t-m dispaly metaslab statistics\n" "\t-M display metaslab group statistic\n" "\t-h display histogram (requires -m or -M)\n", "given a spa_t, print vdev summary", spa_vdevs }, { "sm_entries", "", "print out space map entries from a buffer decoded", sm_entries}, { "vdev", ":[-remMh]\n" "\t-r display recursively\n" "\t-e display statistics\n" "\t-m display metaslab statistics (top level vdev only)\n" "\t-M display metaslab group statistics (top level vdev only)\n" "\t-h display histogram (requires -m or -M)\n", "vdev_t summary", vdev_print }, { "zio", ":[-cpr]\n" "\t-c display children\n" "\t-p display parents\n" "\t-r display recursively", "zio_t summary", zio_print }, { "zio_state", "?", "print out all zio_t structures on system or " "for a particular pool", zio_state }, { "zfs_blkstats", ":[-v]", "given a spa_t, print block type stats from last scrub", zfs_blkstats }, { "zfs_params", "", "print zfs tunable parameters", zfs_params }, { "zfs_refcount", ":[-r]\n" "\t-r display recently removed references", "print zfs_refcount_t holders", zfs_refcount }, { "zap_leaf", "", "print zap_leaf_phys_t", zap_leaf }, { "zfs_aces", ":[-v]", "print all ACEs from a zfs_acl_t", zfs_acl_dump }, { "zfs_ace", ":[-v]", "print zfs_ace", zfs_ace_print }, { "zfs_ace0", ":[-v]", "print zfs_ace0", zfs_ace0_print }, { "sa_attr_table", ":", "print SA attribute table from sa_os_t", sa_attr_table}, { "sa_attr", ": attr_id", "print SA attribute address when given sa_handle_t", sa_attr_print}, { "zfs_dbgmsg", ":[-artTvw]", "print zfs debug log", dbgmsg, dbgmsg_help}, { "rrwlock", ":", "print rrwlock_t, including readers", rrwlock}, { "metaslab_weight", "weight", "print metaslab weight", metaslab_weight}, { "metaslab_trace", ":", "print metaslab allocation trace records", metaslab_trace}, { "arc_compression_stats", ":[-vabrf]\n" "\t-v verbose, display a linearly scaled histogram\n" "\t-a display ARC_anon state statistics individually\n" "\t-r display ARC_mru state statistics individually\n" "\t-f display ARC_mfu state statistics individually\n" "\t-b display histogram of buffer counts\n", "print a histogram of compressed arc buffer sizes", arc_compression_stats}, { "range_tree", ":", "print entries in range_tree_t", range_tree}, { NULL } }; static const mdb_walker_t walkers[] = { { "txg_list", "given any txg_list_t *, walk all entries in all txgs", txg_list_walk_init, txg_list_walk_step, NULL }, { "txg_list0", "given any txg_list_t *, walk all entries in txg 0", txg_list0_walk_init, txg_list_walk_step, NULL }, { "txg_list1", "given any txg_list_t *, walk all entries in txg 1", txg_list1_walk_init, txg_list_walk_step, NULL }, { "txg_list2", "given any txg_list_t *, walk all entries in txg 2", txg_list2_walk_init, txg_list_walk_step, NULL }, { "txg_list3", "given any txg_list_t *, walk all entries in txg 3", txg_list3_walk_init, txg_list_walk_step, NULL }, { "zio", "walk all zio structures, optionally for a particular spa_t", zio_walk_init, zio_walk_step, NULL }, { "zio_root", "walk all root zio_t structures, optionally for a particular spa_t", zio_walk_init, zio_walk_root_step, NULL }, { "spa", "walk all spa_t entries in the namespace", spa_walk_init, spa_walk_step, NULL }, { "metaslab", "given a spa_t *, walk all metaslab_t structures", metaslab_walk_init, metaslab_walk_step, NULL }, { "multilist", "given a multilist_t *, walk all list_t structures", multilist_walk_init, multilist_walk_step, NULL }, { "zfs_acl_node", "given a zfs_acl_t, walk all zfs_acl_nodes", zfs_acl_node_walk_init, zfs_acl_node_walk_step, NULL }, { "zfs_acl_node_aces", "given a zfs_acl_node_t, walk all ACEs", zfs_acl_node_aces_walk_init, zfs_aces_walk_step, NULL }, { "zfs_acl_node_aces0", "given a zfs_acl_node_t, walk all ACEs as ace_t", zfs_acl_node_aces0_walk_init, zfs_aces_walk_step, NULL }, { "zfs_btree", "given a zfs_btree_t *, walk all entries", btree_walk_init, btree_walk_step, btree_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); }