root/usr/src/cmd/mdb/common/mdb/mdb_fmt.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 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 <mdb/mdb_types.h>
#include <mdb/mdb_target.h>
#include <mdb/mdb_io.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb.h>

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