Beispiel #1
0
/*
 * Refuse to single-step or break within any stub that loads a user %cr3 value.
 * As the KDI traps are not careful to restore such a %cr3, this can all go
 * wrong, both spectacularly and subtly.
 */
static boolean_t
kaif_toxic_text(uintptr_t addr)
{
	static GElf_Sym toxic_syms[2] = { 0, };
	size_t i;

	if (toxic_syms[0].st_name == 0) {
		if (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EXEC,
		    "tr_iret_user", &toxic_syms[0], NULL) != 0)
			warn("couldn't find tr_iret_user\n");
		if (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EXEC,
		    "tr_mmu_flush_user_range", &toxic_syms[1], NULL) != 0)
			warn("couldn't find tr_mmu_flush_user_range\n");
	}

	for (i = 0; i < ARRAY_SIZE(toxic_syms); i++) {
		if (addr >= toxic_syms[i].st_value &&
		    addr - toxic_syms[i].st_value < toxic_syms[i].st_size)
			return (B_TRUE);
	}

	return (B_FALSE);
}
Beispiel #2
0
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_EXEC,
	    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);
}
Beispiel #3
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);
}
Beispiel #4
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));
}
Beispiel #5
0
void
kt_amd64_init(mdb_tgt_t *t)
{
	kt_data_t *kt = t->t_data;
	panic_data_t pd;
	struct regs regs;
	uintptr_t addr;

	/*
	 * Initialize the machine-dependent parts of the kernel target
	 * structure.  Once this is complete and we fill in the ops
	 * vector, the target is now fully constructed and we can use
	 * the target API itself to perform the rest of our initialization.
	 */
	kt->k_rds = mdb_amd64_kregs;
	kt->k_regs = mdb_zalloc(sizeof (mdb_tgt_gregset_t), UM_SLEEP);
	kt->k_regsize = sizeof (mdb_tgt_gregset_t);
	kt->k_dcmd_regs = kt_regs;
	kt->k_dcmd_stack = kt_stack;
	kt->k_dcmd_stackv = kt_stackv;
	kt->k_dcmd_stackr = kt_stackv;
	kt->k_dcmd_cpustack = kt_cpustack;
	kt->k_dcmd_cpuregs = kt_cpuregs;

	t->t_ops = &kt_amd64_ops;

	(void) mdb_dis_select("amd64");

	/*
	 * Lookup the symbols corresponding to subroutines in locore.s where
	 * we expect a saved regs structure to be pushed on the stack.  When
	 * performing stack tracebacks we will attempt to detect interrupt
	 * frames by comparing the %eip value to these symbols.
	 */
	(void) mdb_tgt_lookup_by_name(t, MDB_TGT_OBJ_EXEC,
	    "cmnint", &kt->k_intr_sym, NULL);

	(void) mdb_tgt_lookup_by_name(t, MDB_TGT_OBJ_EXEC,
	    "cmntrap", &kt->k_trap_sym, NULL);

	/*
	 * Don't attempt to load any thread or register information if
	 * we're examining the live operating system.
	 */
	if (kt->k_symfile != NULL && strcmp(kt->k_symfile, "/dev/ksyms") == 0)
		return;

	/*
	 * If the panicbuf symbol is present and we can consume a panicbuf
	 * header of the appropriate version from this address, then we can
	 * initialize our current register set based on its contents.
	 * Prior to the re-structuring of panicbuf, our only register data
	 * was the panic_regs label_t, into which a setjmp() was performed,
	 * or the panic_reg register pointer, which was only non-zero if
	 * the system panicked as a result of a trap calling die().
	 */
	if (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &pd, sizeof (pd),
	    MDB_TGT_OBJ_EXEC, "panicbuf") == sizeof (pd) &&
	    pd.pd_version == PANICBUFVERS) {

		size_t pd_size = MIN(PANICBUFSIZE, pd.pd_msgoff);
		panic_data_t *pdp = mdb_zalloc(pd_size, UM_SLEEP);
		uint_t i, n;

		(void) mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, pdp, pd_size,
		    MDB_TGT_OBJ_EXEC, "panicbuf");

		n = (pd_size - (sizeof (panic_data_t) -
		    sizeof (panic_nv_t))) / sizeof (panic_nv_t);

		for (i = 0; i < n; i++) {
			(void) kt_putareg(t, kt->k_tid,
			    pdp->pd_nvdata[i].pnv_name,
			    pdp->pd_nvdata[i].pnv_value);
		}

		mdb_free(pdp, pd_size);

		return;
	};

	if (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &addr, sizeof (addr),
	    MDB_TGT_OBJ_EXEC, "panic_reg") == sizeof (addr) && addr != NULL &&
	    mdb_tgt_vread(t, &regs, sizeof (regs), addr) == sizeof (regs)) {
		kt_regs_to_kregs(&regs, kt->k_regs);
		return;
	}

	/*
	 * If we can't read any panic regs, then our final try is for any CPU
	 * context that may have been stored (for example, in Xen core dumps).
	 */
	if (kt_kvmregs(t, 0, kt->k_regs) == 0)
		return;

	warn("failed to read panicbuf and panic_reg -- "
	    "current register set will be unavailable\n");
}
Beispiel #6
0
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));
}
Beispiel #7
0
void
kt_amd64_init(mdb_tgt_t *t)
{
	kt_data_t *kt = t->t_data;

	panic_data_t pd;
	kreg_t *kregs;
	struct regs regs;
	uintptr_t addr;

	/*
	 * Initialize the machine-dependent parts of the kernel target
	 * structure.  Once this is complete and we fill in the ops
	 * vector, the target is now fully constructed and we can use
	 * the target API itself to perform the rest of our initialization.
	 */
	kt->k_rds = mdb_amd64_kregs;
	kt->k_regs = mdb_zalloc(sizeof (mdb_tgt_gregset_t), UM_SLEEP);
	kt->k_regsize = sizeof (mdb_tgt_gregset_t);
	kt->k_dcmd_regs = kt_regs;
	kt->k_dcmd_stack = kt_stack;
	kt->k_dcmd_stackv = kt_stackv;
	kt->k_dcmd_stackr = kt_stackv;

	t->t_ops = &kt_amd64_ops;
	kregs = kt->k_regs->kregs;

	(void) mdb_dis_select("amd64");

	/*
	 * Lookup the symbols corresponding to subroutines in locore.s where
	 * we expect a saved regs structure to be pushed on the stack.  When
	 * performing stack tracebacks we will attempt to detect interrupt
	 * frames by comparing the %eip value to these symbols.
	 */
	(void) mdb_tgt_lookup_by_name(t, MDB_TGT_OBJ_EXEC,
	    "cmnint", &kt->k_intr_sym, NULL);

	(void) mdb_tgt_lookup_by_name(t, MDB_TGT_OBJ_EXEC,
	    "cmntrap", &kt->k_trap_sym, NULL);

	/*
	 * Don't attempt to load any thread or register information if
	 * we're examining the live operating system.
	 */
	if (strcmp(kt->k_symfile, "/dev/ksyms") == 0)
		return;

	/*
	 * If the panicbuf symbol is present and we can consume a panicbuf
	 * header of the appropriate version from this address, then we can
	 * initialize our current register set based on its contents.
	 * Prior to the re-structuring of panicbuf, our only register data
	 * was the panic_regs label_t, into which a setjmp() was performed,
	 * or the panic_reg register pointer, which was only non-zero if
	 * the system panicked as a result of a trap calling die().
	 */
	if (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &pd, sizeof (pd),
	    MDB_TGT_OBJ_EXEC, "panicbuf") == sizeof (pd) &&
	    pd.pd_version == PANICBUFVERS) {

		size_t pd_size = MIN(PANICBUFSIZE, pd.pd_msgoff);
		panic_data_t *pdp = mdb_zalloc(pd_size, UM_SLEEP);
		uint_t i, n;

		(void) mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, pdp, pd_size,
		    MDB_TGT_OBJ_EXEC, "panicbuf");

		n = (pd_size - (sizeof (panic_data_t) -
		    sizeof (panic_nv_t))) / sizeof (panic_nv_t);

		for (i = 0; i < n; i++) {
			(void) kt_putareg(t, kt->k_tid,
			    pdp->pd_nvdata[i].pnv_name,
			    pdp->pd_nvdata[i].pnv_value);
		}

		mdb_free(pdp, pd_size);

	} else if (mdb_tgt_readsym(t, MDB_TGT_AS_VIRT, &addr, sizeof (addr),
	    MDB_TGT_OBJ_EXEC, "panic_reg") == sizeof (addr) && addr != NULL &&
	    mdb_tgt_vread(t, &regs, sizeof (regs), addr) == sizeof (regs)) {

		kregs[KREG_SAVFP] = regs.r_savfp;
		kregs[KREG_SAVPC] = regs.r_savpc;
		kregs[KREG_RDI] = regs.r_rdi;
		kregs[KREG_RSI] = regs.r_rsi;
		kregs[KREG_RDX] = regs.r_rdx;
		kregs[KREG_RCX] = regs.r_rcx;
		kregs[KREG_R8] = regs.r_r8;
		kregs[KREG_R9] = regs.r_r9;
		kregs[KREG_RAX] = regs.r_rax;
		kregs[KREG_RBX] = regs.r_rbx;
		kregs[KREG_RBP] = regs.r_rbp;
		kregs[KREG_R10] = regs.r_r10;
		kregs[KREG_R11] = regs.r_r11;
		kregs[KREG_R12] = regs.r_r12;
		kregs[KREG_R13] = regs.r_r13;
		kregs[KREG_R14] = regs.r_r14;
		kregs[KREG_R15] = regs.r_r15;
		kregs[KREG_FSBASE] = regs.r_fsbase;
		kregs[KREG_GSBASE] = regs.r_gsbase;
		kregs[KREG_DS] = regs.r_ds;
		kregs[KREG_ES] = regs.r_es;
		kregs[KREG_FS] = regs.r_fs;
		kregs[KREG_GS] = regs.r_gs;
		kregs[KREG_TRAPNO] = regs.r_trapno;
		kregs[KREG_ERR] = regs.r_err;
		kregs[KREG_RIP] = regs.r_rip;
		kregs[KREG_CS] = regs.r_cs;
		kregs[KREG_RFLAGS] = regs.r_rfl;
		kregs[KREG_RSP] = regs.r_rsp;
		kregs[KREG_SS] = regs.r_ss;

	} else {
		warn("failed to read panicbuf and panic_reg -- "
		    "current register set will be unavailable\n");
	}
}
Beispiel #8
0
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 == NULL)
			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 (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);
}
Beispiel #9
0
/*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, *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("%<u>");
		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("%</u>\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;

			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);
}