root/usr/src/cmd/mdb/common/mdb/mdb_nm.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) 2018, Joyent, Inc.
 */

#include <sys/elf.h>
#include <sys/elf_SPARC.h>

#include <libproc.h>
#include <libctf.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

#include <mdb/mdb_string.h>
#include <mdb/mdb_argvec.h>
#include <mdb/mdb_nv.h>
#include <mdb/mdb_fmt.h>
#include <mdb/mdb_target.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_conf.h>
#include <mdb/mdb_module.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_stdlib.h>
#include <mdb/mdb_lex.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb_help.h>
#include <mdb/mdb_disasm.h>
#include <mdb/mdb_frame.h>
#include <mdb/mdb_evset.h>
#include <mdb/mdb_print.h>
#include <mdb/mdb_nm.h>
#include <mdb/mdb_set.h>
#include <mdb/mdb_demangle.h>
#include <mdb/mdb.h>

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 = "<unknown type>";

        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("%<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;

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