root/usr/src/cmd/mdb/common/mdb/mdb_modapi.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 (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 <mdb/mdb_modapi.h>
#include <mdb/mdb_module.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_callb.h>
#include <mdb/mdb_dump.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_io.h>
#include <mdb/mdb_lex.h>
#include <mdb/mdb_frame.h>
#include <mdb/mdb.h>
#include <inttypes.h>

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