root/usr/src/cmd/fm/modules/common/eversholt/eft_mdb.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2019 Joyent, Inc.
 */

#include <sys/mdb_modapi.h>

#include <lut.h>
#include <itree.h>
#include "ipath_impl.h"
#include "lut_impl.h"
#include "config_impl.h"
#include "stats_impl.h"

#define LUT_SIZE_INIT   300
#define LUT_SIZE_INCR   100

struct lut_cp {
        uintptr_t lutcp_addr;
        struct lut lutcp_lut;
};

#define LCPSZ   sizeof (struct lut_cp)

struct lut_dump_desc {
        struct lut_cp *ld_array;
        int ld_arraysz;
        int ld_nents;
};

static void
lut_dump_array_alloc(struct lut_dump_desc *lddp)
{
        struct lut_cp *new;

        if (lddp->ld_array == NULL) {
                lddp->ld_arraysz = LUT_SIZE_INIT;
                lddp->ld_array = mdb_zalloc(LUT_SIZE_INIT * LCPSZ, UM_SLEEP);
                return;
        }

        new = mdb_zalloc((lddp->ld_arraysz + LUT_SIZE_INCR) * LCPSZ, UM_SLEEP);
        bcopy(lddp->ld_array, new, lddp->ld_arraysz * LCPSZ);
        mdb_free(lddp->ld_array, lddp->ld_arraysz * LCPSZ);
        lddp->ld_array = new;
        lddp->ld_arraysz += LUT_SIZE_INCR;
}

static void
lut_dump_array_free(struct lut_dump_desc *lddp)
{
        if (lddp->ld_array != NULL) {
                mdb_free(lddp->ld_array, lddp->ld_arraysz * LCPSZ);
                lddp->ld_array = NULL;
        }
}

static void
lut_collect_addent(uintptr_t addr, struct lut *ent, struct lut_dump_desc *lddp)
{
        struct lut_cp *lcp;

        if (lddp->ld_nents == lddp->ld_arraysz)
                lut_dump_array_alloc(lddp);

        lcp = &lddp->ld_array[lddp->ld_nents++];

        lcp->lutcp_addr = addr;
        bcopy(ent, &lcp->lutcp_lut, sizeof (struct lut));
}

static int
eft_lut_walk(uintptr_t root, struct lut_dump_desc *lddp)
{
        struct lut lutent;

        if (root) {
                if (mdb_vread(&lutent, sizeof (struct lut), root) !=
                    sizeof (struct lut)) {
                        mdb_warn("failed to read struct lut at %p", root);
                        return (WALK_ERR);
                }

                if (eft_lut_walk((uintptr_t)lutent.lut_left, lddp) != WALK_NEXT)
                        return (WALK_ERR);

                lut_collect_addent(root, &lutent, lddp);

                if (eft_lut_walk((uintptr_t)lutent.lut_right, lddp) !=
                    WALK_NEXT)
                        return (WALK_ERR);
        }
        return (WALK_NEXT);
}

static int
lut_collect(uintptr_t addr, struct lut_dump_desc *lddp)
{
        lut_dump_array_alloc(lddp);

        if (eft_lut_walk(addr, lddp) != WALK_NEXT) {
                lut_dump_array_free(lddp);
                return (WALK_ERR);
        } else {
                return (WALK_NEXT);     /* caller must free dump array */
        }
}

static int
lut_walk_init(mdb_walk_state_t *wsp)
{
        if (wsp->walk_addr == 0) {
                mdb_warn("lut walker requires a lut table address\n");
                return (WALK_ERR);
        }

        wsp->walk_data = mdb_zalloc(sizeof (struct lut_dump_desc), UM_SLEEP);
        wsp->walk_arg = 0;

        if (lut_collect(wsp->walk_addr, wsp->walk_data) == WALK_NEXT) {
                return (WALK_NEXT);
        } else {
                mdb_warn("failed to suck in full lut\n");
                mdb_free(wsp->walk_data, sizeof (struct lut_dump_desc));
                return (WALK_ERR);
        }
}

static int
lut_walk_step(mdb_walk_state_t *wsp)
{
        struct lut_dump_desc *lddp = wsp->walk_data;
        int *ip = (int *)&wsp->walk_arg;
        struct lut_cp *lcp = &lddp->ld_array[*ip];

        if (*ip == lddp->ld_nents)
                return (WALK_DONE);

        ++*ip;

        return (wsp->walk_callback(lcp->lutcp_addr, &lcp->lutcp_lut,
            wsp->walk_cbdata));
}

static int
ipath_walk_init(mdb_walk_state_t *wsp)
{
        struct ipath *ipath;

        ipath = mdb_alloc(sizeof (struct ipath), UM_SLEEP);

        if (mdb_vread((void *)ipath, sizeof (struct ipath),
            wsp->walk_addr) != sizeof (struct ipath)) {
                mdb_warn("failed to read struct ipath at %p", wsp->walk_addr);
                return (WALK_ERR);
        }
        wsp->walk_data = (void *)ipath;

        if (ipath->s == NULL)
                return (WALK_DONE);
        else
                return (WALK_NEXT);
}

static void
ipath_walk_fini(mdb_walk_state_t *wsp)
{
        mdb_free(wsp->walk_data, sizeof (struct ipath));
}

static int
ipath_walk_step(mdb_walk_state_t *wsp)
{
        int status;
        struct ipath *ipath = (struct ipath *)wsp->walk_data;
        struct ipath *ip = (struct ipath *)wsp->walk_addr;

        if (ip == NULL || ipath->s == NULL)
                return (WALK_DONE);

        status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data,
            wsp->walk_cbdata);

        wsp->walk_addr = (uintptr_t)(ip + 1);

        if (mdb_vread(wsp->walk_data, sizeof (struct ipath),
            wsp->walk_addr) != sizeof (struct ipath)) {
                mdb_warn("failed to read struct ipath at %p", wsp->walk_addr);
                return (WALK_ERR);
        }

        return (status);
}

static void
lut_walk_fini(mdb_walk_state_t *wsp)
{
        struct lut_dump_desc *lddp = wsp->walk_data;

        lut_dump_array_free(lddp);
        mdb_free(lddp, sizeof (struct lut_dump_desc));
}

/*ARGSUSED*/
static int
ipath_node(uintptr_t addr, const void *data, void *arg)
{
        struct ipath *ipath = (struct ipath *)data;
        char buf[128];

        if (mdb_readstr(buf, (size_t)sizeof (buf), (uintptr_t)ipath->s) < 0)
                (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>",
                    ipath->s);

        mdb_printf("/%s=%d", buf, ipath->i);
        return (DCMD_OK);
}

/*ARGSUSED*/
static int
ipath(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        if (argc)
                return (DCMD_USAGE);
        if (!(flags & DCMD_ADDRSPEC))
                addr = mdb_get_dot();
        if (mdb_pwalk("eft_ipath", ipath_node, NULL, addr) != 0)
                return (DCMD_ERR);
        return (DCMD_OK);
}

/*ARGSUSED*/
static int
eft_count(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        struct lut lut;
        struct istat_entry istat_entry;
        struct stats count;
        GElf_Sym sym;
        char buf[128];

        if (argc)
                return (DCMD_USAGE);
        if (!(flags & DCMD_ADDRSPEC)) {
                if (mdb_lookup_by_obj(MDB_OBJ_EVERY, "Istats", &sym) == -1 ||
                    sym.st_size != sizeof (addr))
                        return (DCMD_ERR);
                if (mdb_vread(&addr, sizeof (addr),
                    (uintptr_t)sym.st_value) != sizeof (addr))
                        return (DCMD_ERR);
                if (addr == 0)
                        return (DCMD_OK);
                if (mdb_pwalk_dcmd("lut", "eft_count", argc, argv, addr) != 0)
                        return (DCMD_ERR);
                return (DCMD_OK);
        }

        if (mdb_vread(&lut, sizeof (struct lut), addr) != sizeof (struct lut)) {
                mdb_warn("failed to read struct lut at %p", addr);
                return (DCMD_ERR);
        }
        if (mdb_vread(&istat_entry, sizeof (struct istat_entry),
            (uintptr_t)lut.lut_lhs) != sizeof (struct istat_entry)) {
                mdb_warn("failed to read struct istat_entry at %p", addr);
                return (DCMD_ERR);
        }
        if (mdb_vread(&count, sizeof (struct stats),
            (uintptr_t)lut.lut_rhs) != sizeof (struct stats)) {
                mdb_warn("failed to read struct stats at %p", addr);
                return (DCMD_ERR);
        }

        if (mdb_readstr(buf, (size_t)sizeof (buf),
            (uintptr_t)istat_entry.ename) < 0)
                (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>",
                    istat_entry.ename);

        mdb_printf("%s@", buf);
        (void) ipath((uintptr_t)istat_entry.ipath, DCMD_ADDRSPEC, 0, NULL);
        mdb_printf(" %d\n", count.fmd_stats.fmds_value.i32);
        return (DCMD_OK);
}

/*ARGSUSED*/
static int
eft_time(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        unsigned long long val;
        unsigned long long ull;
        int opt_p = 0;

        if (!(flags & DCMD_ADDRSPEC))
                addr = mdb_get_dot();
        ull = addr;
        if (argc) {
                if (mdb_getopts(argc, argv,
                    'l', MDB_OPT_UINT64, &ull,
                    'p', MDB_OPT_SETBITS, TRUE, &opt_p, MDB_OPT_UINT64,
                    NULL) != argc) {
                        return (DCMD_USAGE);
                }
        }
        if (opt_p) {
                if (mdb_vread(&ull, sizeof (ull), addr) != sizeof (ull)) {
                        mdb_warn("failed to read timeval at %p", addr);
                        return (DCMD_ERR);
                }
        }
#define NOREMAINDER(den, num, val) (((val) = ((den) / (num))) * (num) == (den))
        if (ull == 0)
                mdb_printf("0us");
        else if (ull >= TIMEVAL_EVENTUALLY)
                mdb_printf("infinity");
        else if (NOREMAINDER(ull, 1000000000ULL*60*60*24*365, val))
                mdb_printf("%lluyear%s", val, (val == 1) ? "" : "s");
        else if (NOREMAINDER(ull, 1000000000ULL*60*60*24*30, val))
                mdb_printf("%llumonth%s", val, (val == 1) ? "" : "s");
        else if (NOREMAINDER(ull, 1000000000ULL*60*60*24*7, val))
                mdb_printf("%lluweek%s", val, (val == 1) ? "" : "s");
        else if (NOREMAINDER(ull, 1000000000ULL*60*60*24, val))
                mdb_printf("%lluday%s", val, (val == 1) ? "" : "s");
        else if (NOREMAINDER(ull, 1000000000ULL*60*60, val))
                mdb_printf("%lluhour%s", val, (val == 1) ? "" : "s");
        else if (NOREMAINDER(ull, 1000000000ULL*60, val))
                mdb_printf("%lluminute%s", val, (val == 1) ? "" : "s");
        else if (NOREMAINDER(ull, 1000000000ULL, val))
                mdb_printf("%llusecond%s", val, (val == 1) ? "" : "s");
        else if (NOREMAINDER(ull, 1000000ULL, val))
                mdb_printf("%llums", val);
        else if (NOREMAINDER(ull, 1000ULL, val))
                mdb_printf("%lluus", val);
        else
                mdb_printf("%lluns", ull);

        return (DCMD_OK);
}

/*ARGSUSED*/
static int
eft_node(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        struct node node;
        int opt_v = 0;
        char buf[128];

        if (!(flags & DCMD_ADDRSPEC))
                addr = mdb_get_dot();
        if (argc) {
                if (mdb_getopts(argc, argv,
                    'v', MDB_OPT_SETBITS, TRUE, &opt_v,
                    NULL) != argc) {
                        return (DCMD_USAGE);
                }
        }
        if (addr == 0)
                return (DCMD_OK);
        if (mdb_vread(&node, sizeof (node), addr) != sizeof (node)) {
                mdb_warn("failed to read struct node at %p", addr);
                return (DCMD_ERR);
        }
        if (opt_v) {
                if (mdb_readstr(buf, (size_t)sizeof (buf),
                    (uintptr_t)node.file) < 0)
                        (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>",
                            node.file);

                mdb_printf("%s len %d\n", buf, node.line);
        }
        switch (node.t) {
        case T_NOTHING:                 /* used to keep going on error cases */
                mdb_printf("nothing");
                break;
        case T_NAME:                    /* identifiers, sometimes chained */
                if (mdb_readstr(buf, (size_t)sizeof (buf),
                    (uintptr_t)node.u.name.s) < 0)
                        (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>",
                            node.u.name.s);

                mdb_printf("%s", buf);
                if (node.u.name.cp) {
                        struct config cp;
                        if (mdb_vread(&cp, sizeof (cp),
                            (uintptr_t)node.u.name.cp) != sizeof (cp)) {
                                mdb_warn("failed to read struct config at %p",
                                    node.u.name.cp);
                                return (DCMD_ERR);
                        }
                        mdb_printf("%d", cp.num);
                } else if (node.u.name.it == IT_HORIZONTAL) {
                        if (node.u.name.child && !node.u.name.childgen) {
                                mdb_printf("<");
                                (void) eft_node((uintptr_t)node.u.name.child,
                                    DCMD_ADDRSPEC, 0, NULL);
                                mdb_printf(">");
                        } else {
                                mdb_printf("<> ");
                        }
                } else if (node.u.name.child) {
                        mdb_printf("[");
                        (void) eft_node((uintptr_t)node.u.name.child,
                            DCMD_ADDRSPEC, 0, NULL);
                        mdb_printf("]");
                }
                if (node.u.name.next) {
                        if (node.u.name.it == IT_ENAME)
                                mdb_printf(".");
                        else
                                mdb_printf("/");
                        (void) eft_node((uintptr_t)node.u.name.next,
                            DCMD_ADDRSPEC, 0, NULL);
                }
                break;
        case T_GLOBID:                  /* globals (e.g. $a) */
                if (mdb_readstr(buf, (size_t)sizeof (buf),
                    (uintptr_t)node.u.globid.s) < 0)
                        (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>",
                            node.u.globid.s);

                mdb_printf("$%s", buf);
                break;
        case T_EVENT:                   /* class@path{expr} */
                (void) eft_node((uintptr_t)node.u.event.ename, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf("@");
                (void) eft_node((uintptr_t)node.u.event.epname, DCMD_ADDRSPEC,
                    0, NULL);
                if (node.u.event.eexprlist) {
                        mdb_printf(" { ");
                        (void) eft_node((uintptr_t)node.u.event.eexprlist,
                            DCMD_ADDRSPEC, 0, NULL);
                        mdb_printf(" }");
                }
                break;
        case T_ENGINE:                  /* upset threshold engine (e.g. SERD) */
                mdb_printf("engine ");
                (void) eft_node((uintptr_t)node.u.event.ename, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_ASRU:                    /* ASRU declaration */
                mdb_printf("asru ");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                }
                break;
        case T_FRU:                     /* FRU declaration */
                mdb_printf("fru ");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                }
                break;
        case T_TIMEVAL:                 /* num w/time suffix (ns internally) */
                {
                        mdb_arg_t mdb_arg[2];
                        mdb_arg[0].a_type = MDB_TYPE_STRING;
                        mdb_arg[0].a_un.a_str = "-l";
                        mdb_arg[1].a_type = MDB_TYPE_IMMEDIATE;
                        mdb_arg[1].a_un.a_val = node.u.ull;
                        (void) eft_time((uintptr_t)0, 0, 2, mdb_arg);
                        break;
                }
        case T_NUM:                     /* num (ull internally) */
                mdb_printf("%llu", node.u.ull);
                break;
        case T_QUOTE:                   /* quoted string */
                if (mdb_readstr(buf, (size_t)sizeof (buf),
                    (uintptr_t)node.u.quote.s) < 0)
                        (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>",
                            node.u.quote.s);

                mdb_printf("\"%s\"", buf);
                break;
        case T_FUNC:                    /* func(arglist) */
                if (mdb_readstr(buf, (size_t)sizeof (buf),
                    (uintptr_t)node.u.func.s) < 0)
                        (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>",
                            node.u.func.s);

                mdb_printf("%s(", buf);
                (void) eft_node((uintptr_t)node.u.func.arglist, DCMD_ADDRSPEC,
                    0, NULL);
                mdb_printf(")");
                break;
        case T_NVPAIR:                  /* name=value pair in decl */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" = ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_ASSIGN:                  /* assignment statement */
                mdb_printf("(");
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" = ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(")");
                break;
        case T_CONDIF:                  /* a and T_CONDELSE in (a ? b : c ) */
                mdb_printf("(");
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" ? ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(")");
                break;
        case T_CONDELSE:                /* lists b and c in (a ? b : c ) */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" : ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_NOT:                     /* boolean ! operator */
                mdb_printf("!");
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_AND:                     /* boolean && operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" && ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_OR:                      /* boolean || operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" || ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_EQ:                      /* boolean == operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" == ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_NE:                      /* boolean != operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" != ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_SUB:                     /* integer - operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" - ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_ADD:                     /* integer + operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" + ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_MUL:                     /* integer * operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" * ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_DIV:                     /* integer / operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" / ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_MOD:                     /* integer % operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" % ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_LT:                      /* boolean < operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" < ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_LE:                      /* boolean <= operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" <= ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_GT:                      /* boolean > operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" > ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_GE:                      /* boolean >= operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" >= ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_BITAND:                  /* bitwise & operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" & ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_BITOR:                   /* bitwise | operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" | ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_BITXOR:                  /* bitwise ^ operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" ^ ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_BITNOT:                  /* bitwise ~ operator */
                mdb_printf(" ~");
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_LSHIFT:                  /* bitwise << operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" << ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_RSHIFT:                  /* bitwise >> operator */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(" >> ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_ARROW:                   /* lhs (N)->(K) rhs */
                (void) eft_node((uintptr_t)node.u.arrow.lhs, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.arrow.nnp) {
                        mdb_printf("(");
                        (void) eft_node((uintptr_t)node.u.arrow.nnp,
                            DCMD_ADDRSPEC, 0, NULL);
                        mdb_printf(")");
                }
                mdb_printf("->");
                if (node.u.arrow.knp) {
                        mdb_printf("(");
                        (void) eft_node((uintptr_t)node.u.arrow.knp,
                            DCMD_ADDRSPEC, 0, NULL);
                        mdb_printf(")");
                }
                (void) eft_node((uintptr_t)node.u.arrow.rhs, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_LIST:                    /* comma-separated list */
                (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0,
                    NULL);
                mdb_printf(", ");
                (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_FAULT:                   /* fault declaration */
                mdb_printf("fault.");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                }
                break;
        case T_UPSET:                   /* upset declaration */
                mdb_printf("upset.");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                }
                break;
        case T_DEFECT:                  /* defect declaration */
                mdb_printf("defect.");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                }
                break;
        case T_ERROR:                   /* error declaration */
                mdb_printf("error.");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                }
                break;
        case T_EREPORT:                 /* ereport declaration */
                mdb_printf("ereport.");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                }
                break;
        case T_SERD:                    /* SERD engine declaration */
                mdb_printf("serd.");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                } else if (node.u.stmt.lutp) {
                        if (mdb_pwalk_dcmd("lut", "eft_node", 0, NULL,
                            (uintptr_t)node.u.stmt.lutp) != 0)
                                return (DCMD_ERR);
                }
                break;
        case T_STAT:                    /* STAT engine declaration */
                mdb_printf("stat.");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                } else if (node.u.stmt.lutp) {
                        if (mdb_pwalk_dcmd("lut", "eft_node", 0, NULL,
                            (uintptr_t)node.u.stmt.lutp) != 0)
                                return (DCMD_ERR);
                }
                break;
        case T_PROP:                    /* prop statement */
                mdb_printf("prop ");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_MASK:                    /* mask statement */
                mdb_printf("mask ");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                break;
        case T_CONFIG:                  /* config statement */
                mdb_printf("config ");
                (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0,
                    NULL);
                if (node.u.stmt.nvpairs) {
                        mdb_printf(" ");
                        (void) eft_node((uintptr_t)node.u.stmt.nvpairs,
                            DCMD_ADDRSPEC, 0, NULL);

                }
                break;
        default:
                mdb_printf("not a eversholt node\n");
                break;
        }
        return (DCMD_OK);
}

static const mdb_walker_t walkers[] = {
        { "lut", "walk a lookup table", lut_walk_init, lut_walk_step,
            lut_walk_fini, NULL },
        { "eft_ipath", "walk ipath", ipath_walk_init, ipath_walk_step,
            ipath_walk_fini, NULL },
        { NULL, NULL, NULL, NULL, NULL, NULL }
};

static const mdb_dcmd_t dcmds[] = {
        { "eft_ipath", "?", "print an ipath", ipath },
        { "eft_count", "?", "print eversholt stats", eft_count },
        { "eft_node", "?[-v]", "print eversholt node", eft_node },
        { "eft_time", "?[-p][-l time]", "print eversholt timeval", eft_time },
        { NULL }
};

static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers };

const mdb_modinfo_t *
_mdb_init(void)
{
        return (&modinfo);
}