root/usr/src/cmd/mdb/intel/mdb/mdb_bhyve.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2019 Joyent, Inc.
 * Copyright 2025 Oxide Computer Company
 */

/*
 * bhyve target
 *
 * The bhyve target is used to examine and manipulate a bhyve VM. Access to
 * a bhyve VM is provided by libvmm, which itself uses libvmmapi, which uses
 * the vmm driver's ioctl interface to carry out requests.
 *
 * The bhyve target does not know about threads or processes, but it handles
 * multiple vCPUs and can switch between them. Execution control is currently
 * limited to completely stopping or resuming all vCPUs of a VM, or single-
 * stepping a particular vCPU while all other vCPUs remain stopped. Breakpoints
 * are not implemented yet, and as such step-out and step-over don't work yet.
 * All known x86 instruction sets are support, legacy IA-16, IA-32 and AMD64.
 * The current CPU instruction set is automatically determined by parsing the
 * code segment (CS) attributes in the current vCPU.
 *
 * All of the VMs physical memory and device memory segments are mapped R/W
 * into mdb's address space by libvmm. All accesses to those memory are
 * facilitated through libvmm calls, which may include virtual address
 * translation according to the current vCPU mode. Both real-mode and protected-
 * mode segmentation are understood and used for translating virtual addresses
 * into linear addresses, which may further be translated using 2-level, 3-level
 * or 4-level paging.
 *
 * To handle disassembly and stack tracing properly when segmentation is used by
 * a vCPU (always in real mode, sometimes in protected mode) the bhyve target
 * has a notion of three virtual address spaces used for reading/writing memory:
 *   - MDB_TGT_AS_VIRT, the default virtual address space uses the DS segment
 *     by default, but this default can be changed with the ::defseg dcmd.
 *   - MDB_TGT_AS_VIRT_I, the virtual address space for instructions always
 *     uses the code segment (CS) for translation
 *   - MDB_TGT_AS_VIRT_S, the virtual address space for the stack always uses
 *     the stack segment (SS) for translation
 *
 * Register printing and stack tracing is using the common x86 ISA-specific code
 * in IA-32 and AMD64 modes. There is no stack tracing for IA-16 mode yet.
 *
 * Todo:
 *   - support for breakpoint, step-out, and step-over
 *   - support for x86 stack tracing
 */
#include <mdb/mdb_conf.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_signal.h>
#include <mdb/mdb_stack.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb_kreg_impl.h>
#include <mdb/mdb_target_impl.h>
#include <mdb/mdb_isautil.h>
#include <mdb/mdb_amd64util.h>
#include <mdb/mdb_ia32util.h>
#include <mdb/mdb_x86util.h>
#include <mdb/mdb.h>

#include <sys/controlregs.h>
#include <sys/debugreg.h>
#include <sys/sysmacros.h>
#include <sys/note.h>
#include <unistd.h>
#include <inttypes.h>

#include <libvmm.h>

#define MDB_DEF_PROMPT  "[%<_cpuid>]> "

typedef struct bhyve_data {
        vmm_t *bd_vmm;
        uint_t bd_curcpu;
        int bd_defseg;

        /* must be last */
        char bd_name[];
} bhyve_data_t;


const mdb_tgt_regdesc_t bhyve_kregs[] = {
        { "rdi",        KREG_RDI,       MDB_TGT_R_EXPORT },
        { "edi",        KREG_RDI,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "di",         KREG_RDI,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "dil",        KREG_RDI,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "rsi",        KREG_RSI,       MDB_TGT_R_EXPORT },
        { "esi",        KREG_RSI,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "si",         KREG_RSI,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "sil",        KREG_RSI,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "rdx",        KREG_RDX,       MDB_TGT_R_EXPORT },
        { "edx",        KREG_RDX,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "dx",         KREG_RDX,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "dh",         KREG_RDX,       MDB_TGT_R_EXPORT | MDB_TGT_R_8H },
        { "dl",         KREG_RDX,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "rcx",        KREG_RCX,       MDB_TGT_R_EXPORT },
        { "ecx",        KREG_RCX,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "cx",         KREG_RCX,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "ch",         KREG_RCX,       MDB_TGT_R_EXPORT | MDB_TGT_R_8H },
        { "cl",         KREG_RCX,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "r8",         KREG_R8,        MDB_TGT_R_EXPORT },
        { "r8d",        KREG_R8,        MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "r8w",        KREG_R8,        MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "r8l",        KREG_R8,        MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "r9",         KREG_R9,        MDB_TGT_R_EXPORT },
        { "r9d",        KREG_R8,        MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "r9w",        KREG_R8,        MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "r9l",        KREG_R8,        MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "rax",        KREG_RAX,       MDB_TGT_R_EXPORT },
        { "eax",        KREG_RAX,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "ax",         KREG_RAX,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "ah",         KREG_RAX,       MDB_TGT_R_EXPORT | MDB_TGT_R_8H },
        { "al",         KREG_RAX,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "rbx",        KREG_RBX,       MDB_TGT_R_EXPORT },
        { "ebx",        KREG_RBX,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "bx",         KREG_RBX,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "bh",         KREG_RBX,       MDB_TGT_R_EXPORT | MDB_TGT_R_8H },
        { "bl",         KREG_RBX,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "rbp",        KREG_RBP,       MDB_TGT_R_EXPORT },
        { "ebp",        KREG_RBP,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "bp",         KREG_RBP,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "bpl",        KREG_RBP,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "r10",        KREG_R10,       MDB_TGT_R_EXPORT },
        { "r10d",       KREG_R10,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "r10w",       KREG_R10,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "r10l",       KREG_R10,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "r11",        KREG_R11,       MDB_TGT_R_EXPORT },
        { "r11d",       KREG_R11,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "r11w",       KREG_R11,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "r11l",       KREG_R11,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "r12",        KREG_R12,       MDB_TGT_R_EXPORT },
        { "r12d",       KREG_R12,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "r12w",       KREG_R12,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "r12l",       KREG_R12,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "r13",        KREG_R13,       MDB_TGT_R_EXPORT },
        { "r13d",       KREG_R13,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "r13w",       KREG_R13,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "r13l",       KREG_R13,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "r14",        KREG_R14,       MDB_TGT_R_EXPORT },
        { "r14d",       KREG_R14,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "r14w",       KREG_R14,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "r14l",       KREG_R14,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "r15",        KREG_R15,       MDB_TGT_R_EXPORT },
        { "r15d",       KREG_R15,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "r15w",       KREG_R15,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "r15l",       KREG_R15,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "ds",         KREG_DS,        MDB_TGT_R_EXPORT },
        { "es",         KREG_ES,        MDB_TGT_R_EXPORT },
        { "fs",         KREG_FS,        MDB_TGT_R_EXPORT },
        { "gs",         KREG_GS,        MDB_TGT_R_EXPORT },
        { "rip",        KREG_RIP,       MDB_TGT_R_EXPORT },
        { "cs",         KREG_CS,        MDB_TGT_R_EXPORT },
        { "rflags",     KREG_RFLAGS,    MDB_TGT_R_EXPORT },
        { "eflags",     KREG_RFLAGS,    MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "rsp",        KREG_RSP,       MDB_TGT_R_EXPORT },
        { "esp",        KREG_RSP,       MDB_TGT_R_EXPORT | MDB_TGT_R_32 },
        { "sp",         KREG_RSP,       MDB_TGT_R_EXPORT | MDB_TGT_R_16 },
        { "spl",        KREG_RSP,       MDB_TGT_R_EXPORT | MDB_TGT_R_8L },
        { "ss",         KREG_SS,        MDB_TGT_R_EXPORT },
        { "cr2",        KREG_CR2,       MDB_TGT_R_EXPORT },
        { "cr3",        KREG_CR3,       MDB_TGT_R_EXPORT },
        { NULL, 0, 0 }
};

static const char *segments[] = { "CS", "DS", "ES", "FS", "GS", "SS" };


/*ARGSUSED*/
static uintmax_t
bhyve_cpuid_get(const mdb_var_t *v)
{
        bhyve_data_t *bd = mdb.m_target->t_data;

        return (bd->bd_curcpu);
}

static const mdb_nv_disc_t bhyve_cpuid_disc = {
        .disc_get = bhyve_cpuid_get
};


static uintmax_t
bhyve_reg_get(const mdb_var_t *v)
{
        mdb_tgt_reg_t r = 0;

        if (mdb_tgt_getareg(MDB_NV_COOKIE(v), 0, mdb_nv_get_name(v), &r) == -1)
                mdb_warn("failed to get %%%s register", mdb_nv_get_name(v));

        return (r);
}

static void
bhyve_reg_set(mdb_var_t *v, uintmax_t r)
{
        if (mdb_tgt_putareg(MDB_NV_COOKIE(v), 0, mdb_nv_get_name(v), r) == -1)
                mdb_warn("failed to modify %%%s register", mdb_nv_get_name(v));
}

static const mdb_nv_disc_t bhyve_reg_disc = {
        .disc_set = bhyve_reg_set,
        .disc_get = bhyve_reg_get
};

static int
bhyve_get_gregset(bhyve_data_t *bd, int cpu, mdb_tgt_gregset_t *gregs)
{
        vmm_desc_t fs, gs;

        /*
         * Register numbers to get, the order must match the definitions of
         * KREG_* in mdb_kreg.h so that we get a proper mdb_tgt_gregset_t
         * that the register printing functions will understand.
         *
         * There are a few fields in mdb_tgt_gregset_t that can't be accessed
         * with vmm_get_regset(), either because they don't exist in bhyve or
         * or because they need to be accessed with vmm_get_desc(). For these
         * cases we ask for RAX instead and fill it with 0 or the real value,
         * respectively.
         */
        static const int regnums[] = {
                KREG_RAX, /* dummy for SAVFP */
                KREG_RAX, /* dummy for SAVFP */
                KREG_RDI,
                KREG_RSI,
                KREG_RDX,
                KREG_RCX,
                KREG_R8,
                KREG_R9,
                KREG_RAX,
                KREG_RBX,
                KREG_RBP,
                KREG_R10,
                KREG_R11,
                KREG_R12,
                KREG_R13,
                KREG_R14,
                KREG_R15,
                KREG_RAX, /* dummy for FSBASE */
                KREG_RAX, /* dummy for GSBASE */
                KREG_RAX, /* dummy for KGSBASE */
                KREG_CR2,
                KREG_CR3,
                KREG_DS,
                KREG_ES,
                KREG_FS,
                KREG_GS,
                KREG_RAX, /* dummy for TRAPNO */
                KREG_RAX, /* dummy for ERR */
                KREG_RIP,
                KREG_CS,
                KREG_RFLAGS,
                KREG_RSP,
                KREG_SS
        };

        if (vmm_get_regset(bd->bd_vmm, cpu, KREG_NGREG, regnums,
            &gregs->kregs[0]) != 0) {
                mdb_warn("failed to get general-purpose registers for CPU %d",
                    cpu);
                return (-1);
        }

        if (vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_FS, &fs) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_GS, &gs) != 0) {
                mdb_warn("failed to get FS/GS descriptors for CPU %d", cpu);
                return (-1);
        }

        gregs->kregs[KREG_SAVFP] = 0;
        gregs->kregs[KREG_SAVPC] = 0;
        gregs->kregs[KREG_KGSBASE] = 0;
        gregs->kregs[KREG_TRAPNO] = 0;
        gregs->kregs[KREG_ERR] = 0;

        gregs->kregs[KREG_FSBASE] = fs.vd_base;
        gregs->kregs[KREG_GSBASE] = gs.vd_base;

        return (0);
}

static int
bhyve_cpuregs_dcmd(uintptr_t addr, uint_t flags, int argc,
    const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        uint64_t cpu = bd->bd_curcpu;
        mdb_tgt_gregset_t gregs;
        int i;


        if (flags & DCMD_ADDRSPEC) {
                if (argc != 0)
                        return (DCMD_USAGE);

                cpu = (uint64_t)addr;
        }

        i = mdb_getopts(argc, argv, 'c', MDB_OPT_UINT64, &cpu, NULL);

        argc -= i;
        argv += i;

        if (argc != 0)
                return (DCMD_USAGE);

        if (cpu >= vmm_ncpu(bd->bd_vmm)) {
                mdb_warn("no such CPU\n");
                return (DCMD_ERR);
        }

        if (bhyve_get_gregset(bd, cpu, &gregs) != 0)
                return (DCMD_ERR);


        switch (vmm_vcpu_isa(bd->bd_vmm, cpu)) {
        case VMM_ISA_64:
                mdb_amd64_printregs(&gregs);
                break;
        case VMM_ISA_32:
        case VMM_ISA_16:
                mdb_ia32_printregs(&gregs);
                break;
        default:
                mdb_warn("CPU %d mode unknown", cpu);
                return (DCMD_ERR);
        }

        return (0);
}

static int
bhyve_regs_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        if ((flags & DCMD_ADDRSPEC) || argc != 0)
                return (DCMD_USAGE);

        return (bhyve_cpuregs_dcmd(addr, flags, argc, argv));
}

static int
bhyve_stack_common(uintptr_t addr, uint_t flags, int argc,
    const mdb_arg_t *argv, int vcpu, mdb_stack_frame_flags_t sflags)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        mdb_stack_frame_hdl_t *hdl;
        mdb_tgt_t *t = mdb.m_target;
        uint_t arglim = mdb.m_nargs;
        int ret = DCMD_OK;
        int i;

        mdb_tgt_gregset_t gregs;

        if (vcpu == -1) {
                vcpu = bd->bd_curcpu;
        } else if (vcpu >= vmm_ncpu(bd->bd_vmm)) {
                mdb_warn("no such CPU\n");
                return (DCMD_ERR);
        }

        if (flags & DCMD_ADDRSPEC) {
                bzero(&gregs, sizeof (gregs));
                gregs.kregs[KREG_RBP] = addr;
        } else if (bhyve_get_gregset(bd, vcpu, &gregs) != 0)
                return (DCMD_ERR);

        i = mdb_getopts(argc, argv,
            'n', MDB_OPT_SETBITS, MSF_ADDR, &sflags,
            's', MDB_OPT_SETBITS, MSF_SIZES, &sflags,
            't', MDB_OPT_SETBITS, MSF_TYPES, &sflags,
            'v', MDB_OPT_SETBITS, MSF_VERBOSE, &sflags,
            NULL);

        argc -= i;
        argv += i;

        if ((hdl = mdb_stack_frame_init(t, arglim, sflags)) == NULL) {
                mdb_warn("failed to init stack frame\n");
                return (DCMD_ERR);
        }

        switch (vmm_vcpu_isa(bd->bd_vmm, vcpu)) {
        case VMM_ISA_32:
        case VMM_ISA_64:
                (void) mdb_isa_kvm_stack_iter(mdb.m_target, &gregs,
                    mdb_isa_kvm_frame, (void *)hdl);
                break;
        case VMM_ISA_16:
                mdb_warn("IA16 stack tracing not implemented\n");
                ret = DCMD_ERR;
                break;
        default:
                mdb_warn("CPU %d mode unknown", vcpu);
                ret = DCMD_ERR;
                break;
        }

        return (ret);
}

static int
bhyve_cpustack_dcmd(uintptr_t addr, uint_t flags, int argc,
    const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        uint64_t cpu = bd->bd_curcpu;
        boolean_t verbose;
        int i;

        if (flags & DCMD_ADDRSPEC) {
                if (argc != 0)
                        return (DCMD_USAGE);

                if (addr < vmm_ncpu(bd->bd_vmm)) {
                        cpu = (uint64_t)addr;
                        flags &= ~DCMD_ADDRSPEC;
                }
        }

        i = mdb_getopts(argc, argv,
            'c', MDB_OPT_UINT64, &cpu,
            'v', MDB_OPT_SETBITS, 1, &verbose,
            NULL);

        argc -= i;
        argv += i;

        if (argc != 0)
                return (DCMD_USAGE);

        return (bhyve_stack_common(addr, flags, argc, argv, cpu,
            verbose ? MSF_VERBOSE : 0));
}

static int
bhyve_stack_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        return (bhyve_stack_common(addr, flags, argc, argv, -1, 0));
}

static int
bhyve_stackv_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        return (bhyve_stack_common(addr, flags, argc, argv, -1, MSF_VERBOSE));
}

static int
bhyve_stackr_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        return (bhyve_stack_common(addr, flags, argc, argv, -1, MSF_VERBOSE));
}

static int
bhyve_status_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        vmm_mode_t mode;
        vmm_isa_t isa;

        static const char *modes[] = {
                "unknown mode",
                "real mode",
                "protected mode, no PAE",
                "protected mode, PAE",
                "long mode"
        };
        static const char *isas[] = {
                "unknown ISA",
                "IA16",
                "IA32",
                "AMD64"
        };

        if ((flags & DCMD_ADDRSPEC) || argc != 0)
                return (DCMD_USAGE);

        mode = vmm_vcpu_mode(bd->bd_vmm, bd->bd_curcpu);
        isa = vmm_vcpu_isa(bd->bd_vmm, bd->bd_curcpu);

        mdb_printf("debugging live VM '%s'\n", bd->bd_name);
        mdb_printf("VM memory size: %d MB\n",
            vmm_memsize(bd->bd_vmm) / 1024 / 1024);
        mdb_printf("vCPUs: %d\n", vmm_ncpu(bd->bd_vmm));
        mdb_printf("current CPU: %d (%s, %s)\n", bd->bd_curcpu, modes[mode],
            isas[isa]);
        mdb_printf("default segment: %s",
            segments[bd->bd_defseg - VMM_DESC_CS]);

        return (DCMD_OK);
}


static int
bhyve_sysregs_dcmd(uintptr_t addr, uint_t flags, int argc,
    const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        uint64_t cpu = bd->bd_curcpu;
        int ret = DCMD_ERR;
        struct sysregs sregs;
        int i;

        /*
         * This array must use the order of the elements of struct sysregs.
         */
        static const int regnums[] = {
                VMM_REG_CR0,
                VMM_REG_CR2,
                VMM_REG_CR3,
                VMM_REG_CR4,
                VMM_REG_DR0,
                VMM_REG_DR1,
                VMM_REG_DR2,
                VMM_REG_DR3,
                VMM_REG_DR6,
                VMM_REG_DR7,
                VMM_REG_EFER,
                VMM_REG_PDPTE0,
                VMM_REG_PDPTE1,
                VMM_REG_PDPTE2,
                VMM_REG_PDPTE3,
                VMM_REG_INTR_SHADOW
        };

        if (flags & DCMD_ADDRSPEC) {
                if (argc != 0)
                        return (DCMD_USAGE);

                cpu = (uint64_t)addr;
        }

        i = mdb_getopts(argc, argv, 'c', MDB_OPT_UINT64, &cpu, NULL);

        argc -= i;
        argv += i;

        if (argc != 0)
                return (DCMD_USAGE);

        if (cpu >= vmm_ncpu(bd->bd_vmm)) {
                mdb_warn("no such CPU\n");
                return (DCMD_ERR);
        }

        if (vmm_get_regset(bd->bd_vmm, cpu, ARRAY_SIZE(regnums), regnums,
            (uint64_t *)&sregs) != 0)
                goto fail;

        if (vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_GDTR,
            (vmm_desc_t *)&sregs.sr_gdtr) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_IDTR,
            (vmm_desc_t *)&sregs.sr_idtr) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_LDTR,
            (vmm_desc_t *)&sregs.sr_ldtr) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_TR,
            (vmm_desc_t *)&sregs.sr_tr) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_CS,
            (vmm_desc_t *)&sregs.sr_cs) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_DS,
            (vmm_desc_t *)&sregs.sr_ds) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_ES,
            (vmm_desc_t *)&sregs.sr_es) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_FS,
            (vmm_desc_t *)&sregs.sr_fs) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_GS,
            (vmm_desc_t *)&sregs.sr_gs) != 0 ||
            vmm_get_desc(bd->bd_vmm, cpu, VMM_DESC_SS,
            (vmm_desc_t *)&sregs.sr_ss) != 0)
                goto fail;

        mdb_x86_print_sysregs(&sregs, vmm_vcpu_mode(bd->bd_vmm, cpu) ==
            VMM_MODE_LONG);

        ret = DCMD_OK;

fail:
        if (ret != DCMD_OK)
                mdb_warn("failed to get system registers for CPU %d\n", cpu);
        return (ret);
}

static int
bhyve_dbgregs_dcmd(uintptr_t addr, uint_t flags, int argc,
    const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        uint64_t cpu = bd->bd_curcpu;
        int ret = DCMD_ERR;
        vmm_desc_t gdtr, ldtr, idtr, tr, cs, ds, es, fs, gs, ss;
        uint64_t *regvals;
        int i;

        /*
         * This array must use the order of definitions set in libvmm.h
         * to make GETREG() work.
         */
#define GETREG(r)       (regvals[r - VMM_REG_DR0])
        static const int regnums[] = {
                VMM_REG_DR0,
                VMM_REG_DR1,
                VMM_REG_DR2,
                VMM_REG_DR3,
                VMM_REG_DR6,
                VMM_REG_DR7,
        };

        static const mdb_bitmask_t dr6_flag_bits[] = {
                { "DR0",        DR_TRAP0,       DR_TRAP0 },
                { "DR1",        DR_TRAP1,       DR_TRAP1 },
                { "DR2",        DR_TRAP2,       DR_TRAP2 },
                { "DR3",        DR_TRAP3,       DR_TRAP3 },
                { "debug reg",  DR_ICEALSO,     DR_ICEALSO },
                { "single step", DR_SINGLESTEP, DR_SINGLESTEP },
                { "task switch", DR_TASKSWITCH, DR_TASKSWITCH },
                { NULL,         0,              0 }
        };

#define DR_RW(x, m)     \
        ((DR_RW_MASK & (m)) << (DR_CONTROL_SHIFT + (x) * DR_CONTROL_SIZE))
#define DR_LEN(x, m)    \
        ((DR_LEN_MASK & (m)) << (DR_CONTROL_SHIFT + (x) * DR_CONTROL_SIZE))

        static const mdb_bitmask_t dr7_flag_bits[] = {
                { "L0",   DR_ENABLE0,   DR_LOCAL_ENABLE_MASK & DR_ENABLE0 },
                { "G0",   DR_ENABLE0,   DR_GLOBAL_ENABLE_MASK & DR_ENABLE0 },
                { "L1",   DR_ENABLE1,   DR_LOCAL_ENABLE_MASK & DR_ENABLE1 },
                { "G1",   DR_ENABLE1,   DR_GLOBAL_ENABLE_MASK & DR_ENABLE1 },
                { "L2",   DR_ENABLE2,   DR_LOCAL_ENABLE_MASK & DR_ENABLE2 },
                { "G2",   DR_ENABLE2,   DR_GLOBAL_ENABLE_MASK & DR_ENABLE2 },
                { "L3",   DR_ENABLE3,   DR_LOCAL_ENABLE_MASK & DR_ENABLE3 },
                { "G3",   DR_ENABLE3,   DR_GLOBAL_ENABLE_MASK & DR_ENABLE3 },
                { "LE",   DR_LOCAL_SLOWDOWN,    DR_LOCAL_SLOWDOWN },
                { "GE",   DR_GLOBAL_SLOWDOWN,   DR_GLOBAL_SLOWDOWN },
                { "RTM",  DR_RTM,               DR_RTM },
                { "GD",   DR_GENERAL_DETECT,    DR_GENERAL_DETECT },
                { "0:X",  DR_RW(0, DR_RW_MASK),   DR_RW(0, DR_RW_EXECUTE) },
                { "0:W",  DR_RW(0, DR_RW_MASK),   DR_RW(0, DR_RW_WRITE) },
                { "0:IO", DR_RW(0, DR_RW_MASK),   DR_RW(0, DR_RW_IO_RW) },
                { "0:RW", DR_RW(0, DR_RW_MASK),   DR_RW(0, DR_RW_READ) },
                { "1:X",  DR_RW(1, DR_RW_MASK),   DR_RW(1, DR_RW_EXECUTE) },
                { "1:W",  DR_RW(1, DR_RW_MASK),   DR_RW(1, DR_RW_WRITE) },
                { "1:IO", DR_RW(1, DR_RW_MASK),   DR_RW(1, DR_RW_IO_RW) },
                { "1:RW", DR_RW(1, DR_RW_MASK),   DR_RW(1, DR_RW_READ) },
                { "2:X",  DR_RW(2, DR_RW_MASK),   DR_RW(2, DR_RW_EXECUTE) },
                { "2:W",  DR_RW(2, DR_RW_MASK),   DR_RW(2, DR_RW_WRITE) },
                { "2:IO", DR_RW(2, DR_RW_MASK),   DR_RW(2, DR_RW_IO_RW) },
                { "2:RW", DR_RW(2, DR_RW_MASK),   DR_RW(2, DR_RW_READ) },
                { "3:X",  DR_RW(3, DR_RW_MASK),   DR_RW(3, DR_RW_EXECUTE) },
                { "3:W",  DR_RW(3, DR_RW_MASK),   DR_RW(3, DR_RW_WRITE) },
                { "3:IO", DR_RW(3, DR_RW_MASK),   DR_RW(3, DR_RW_IO_RW) },
                { "3:RW", DR_RW(3, DR_RW_MASK),   DR_RW(3, DR_RW_READ) },
                { "0:1",  DR_LEN(0, DR_LEN_MASK), DR_LEN(0, DR_LEN_1) },
                { "0:2",  DR_LEN(0, DR_LEN_MASK), DR_LEN(0, DR_LEN_2) },
                { "0:4",  DR_LEN(0, DR_LEN_MASK), DR_LEN(0, DR_LEN_4) },
                { "0:8",  DR_LEN(0, DR_LEN_MASK), DR_LEN(0, DR_LEN_8) },
                { "1:1",  DR_LEN(1, DR_LEN_MASK), DR_LEN(1, DR_LEN_1) },
                { "1:2",  DR_LEN(1, DR_LEN_MASK), DR_LEN(1, DR_LEN_2) },
                { "1:4",  DR_LEN(1, DR_LEN_MASK), DR_LEN(1, DR_LEN_4) },
                { "1:8",  DR_LEN(1, DR_LEN_MASK), DR_LEN(1, DR_LEN_8) },
                { "2:1",  DR_LEN(2, DR_LEN_MASK), DR_LEN(2, DR_LEN_1) },
                { "2:2",  DR_LEN(2, DR_LEN_MASK), DR_LEN(2, DR_LEN_2) },
                { "2:4",  DR_LEN(2, DR_LEN_MASK), DR_LEN(2, DR_LEN_4) },
                { "2:8",  DR_LEN(2, DR_LEN_MASK), DR_LEN(2, DR_LEN_8) },
                { "3:1",  DR_LEN(3, DR_LEN_MASK), DR_LEN(3, DR_LEN_1) },
                { "3:2",  DR_LEN(3, DR_LEN_MASK), DR_LEN(3, DR_LEN_2) },
                { "3:4",  DR_LEN(3, DR_LEN_MASK), DR_LEN(3, DR_LEN_4) },
                { "3:8",  DR_LEN(3, DR_LEN_MASK), DR_LEN(3, DR_LEN_8) },
                { NULL, 0, 0 },
        };


        if (flags & DCMD_ADDRSPEC) {
                if (argc != 0)
                        return (DCMD_USAGE);

                cpu = (uint64_t)addr;
        }

        i = mdb_getopts(argc, argv, 'c', MDB_OPT_UINT64, &cpu, NULL);

        argc -= i;
        argv += i;

        if (argc != 0)
                return (DCMD_USAGE);

        if (cpu >= vmm_ncpu(bd->bd_vmm)) {
                mdb_warn("no such CPU\n");
                return (DCMD_ERR);
        }

        regvals = mdb_zalloc(ARRAY_SIZE(regnums) * sizeof (uint64_t), UM_SLEEP);

        if (vmm_get_regset(bd->bd_vmm, cpu, ARRAY_SIZE(regnums), regnums,
            regvals) != 0)
                goto fail;

        mdb_printf("%%dr0 = 0x%0?p %A\n",
            GETREG(VMM_REG_DR0), GETREG(VMM_REG_DR0));
        mdb_printf("%%dr1 = 0x%0?p %A\n",
            GETREG(VMM_REG_DR1), GETREG(VMM_REG_DR1));
        mdb_printf("%%dr2 = 0x%0?p %A\n",
            GETREG(VMM_REG_DR2), GETREG(VMM_REG_DR2));
        mdb_printf("%%dr3 = 0x%0?p %A\n",
            GETREG(VMM_REG_DR3), GETREG(VMM_REG_DR3));
        mdb_printf("%%dr6 = 0x%0lx <%b>\n",
            GETREG(VMM_REG_DR6), GETREG(VMM_REG_DR6), dr6_flag_bits);
        mdb_printf("%%dr7 = 0x%0lx <%b>\n",
            GETREG(VMM_REG_DR7), GETREG(VMM_REG_DR7), dr7_flag_bits);
#undef GETREG

        ret = DCMD_OK;

fail:
        if (ret != DCMD_OK)
                mdb_warn("failed to get debug registers for CPU %d\n", cpu);
        mdb_free(regvals, ARRAY_SIZE(regnums) * sizeof (uint64_t));
        return (ret);
}

static int
bhyve_switch_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        size_t cpu = (int)addr;

        if (!(flags & DCMD_ADDRSPEC) || argc != 0)
                return (DCMD_USAGE);

        if (cpu >= vmm_ncpu(bd->bd_vmm)) {
                mdb_warn("no such CPU\n");
                return (DCMD_ERR);
        }

        bd->bd_curcpu = cpu;
        return (DCMD_OK);

}

static int
bhyve_seg2reg(const char *seg)
{
        if (strcasecmp(seg, "cs") == 0)
                return (VMM_DESC_CS);
        else if (strcasecmp(seg, "ds") == 0)
                return (VMM_DESC_DS);
        else if (strcasecmp(seg, "es") == 0)
                return (VMM_DESC_ES);
        else if (strcasecmp(seg, "fs") == 0)
                return (VMM_DESC_FS);
        else if (strcasecmp(seg, "gs") == 0)
                return (VMM_DESC_GS);
        else if (strcasecmp(seg, "ss") == 0)
                return (VMM_DESC_SS);
        else
                return (-1);
}

static int
bhyve_vtol_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        int segreg = bd->bd_defseg;
        char *seg = "";
        uint64_t laddr;
        int i;

        if (!(flags & DCMD_ADDRSPEC))
                return (DCMD_USAGE);

        i = mdb_getopts(argc, argv, 's', MDB_OPT_STR, &seg, NULL);

        argc -= i;
        argv += i;

        if (i != 0) {
                if (argc != 0)
                        return (DCMD_USAGE);

                segreg = bhyve_seg2reg(seg);
                if (segreg == -1)
                        return (DCMD_USAGE);
        }

        if (vmm_vtol(bd->bd_vmm, bd->bd_curcpu, segreg, addr, &laddr) != 0) {
                if (errno == EFAULT)
                        (void) set_errno(EMDB_NOMAP);
                return (DCMD_ERR);
        }

        if (flags & DCMD_PIPE_OUT)
                mdb_printf("%llr\n", laddr);
        else
                mdb_printf("virtual %lr mapped to linear %llr\n", addr, laddr);

        return (DCMD_OK);
}

static int
bhyve_vtop_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        int segreg = bd->bd_defseg;
        char *seg = "";
        physaddr_t pa;
        int i;

        if (!(flags & DCMD_ADDRSPEC))
                return (DCMD_USAGE);

        i = mdb_getopts(argc, argv, 's', MDB_OPT_STR, &seg, NULL);

        argc -= i;
        argv += i;

        if (i != 0) {
                segreg = bhyve_seg2reg(seg);
                if (segreg == -1)
                        return (DCMD_USAGE);
        }

        if (vmm_vtop(bd->bd_vmm, bd->bd_curcpu, segreg, addr, &pa) == -1) {
                mdb_warn("failed to get physical mapping");
                return (DCMD_ERR);
        }

        if (flags & DCMD_PIPE_OUT)
                mdb_printf("%llr\n", pa);
        else
                mdb_printf("virtual %lr mapped to physical %llr\n", addr, pa);
        return (DCMD_OK);
}

static int
bhyve_defseg_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        bhyve_data_t *bd = mdb.m_target->t_data;
        int segreg = bd->bd_defseg;
        char *seg = "";
        int i;

        if (flags & DCMD_ADDRSPEC)
                return (DCMD_USAGE);

        i = mdb_getopts(argc, argv, 's', MDB_OPT_STR, &seg, NULL);

        argc -= i;
        argv += i;

        if (i != 0) {
                if (argc != 0)
                        return (DCMD_USAGE);

                segreg = bhyve_seg2reg(seg);
                if (segreg == -1)
                        return (DCMD_USAGE);

                bd->bd_defseg = segreg;
        }

        mdb_printf("using segment %s for virtual to linear address translation",
            segments[bd->bd_defseg - VMM_DESC_CS]);

        return (DCMD_OK);
}

static void
bhyve_stack_help(void)
{
        mdb_printf(
            "Options:\n"
            "  -n   do not resolve addresses to names\n"
            "  -s   show the size of each stack frame to the left\n"
            "  -t   where CTF is present, show types for functions and "
            "arguments\n"
            "  -v   include frame pointer information (this is the default "
            "with %<b>$C%</b>)\n"
            "\n"
            "If the optional %<u>cnt%</u> is given, no more than %<u>cnt%</u> "
            "arguments are shown\nfor each stack frame.\n");
}

static const mdb_dcmd_t bhyve_dcmds[] = {
        { "$c", "[-nstv]", "print stack backtrace", bhyve_stack_dcmd,
            bhyve_stack_help },
        { "$C", "[-nstv]", "print stack backtrace", bhyve_stackv_dcmd,
            bhyve_stack_help },
        { "$r", NULL, "print general-purpose registers", bhyve_regs_dcmd },
        { "$?", NULL, "print status and registers", bhyve_regs_dcmd },
        { ":x", ":", "change the active CPU", bhyve_switch_dcmd },
        { "cpustack", "?[-v] [-c cpuid] [cnt]", "print stack backtrace for a "
            "specific CPU", bhyve_cpustack_dcmd },
        { "cpuregs", "?[-c cpuid]", "print general-purpose registers for a "
            "specific CPU", bhyve_cpuregs_dcmd },
        { "dbgregs", "?[-c cpuid]", "print debug registers for a specific CPU",
            bhyve_dbgregs_dcmd },
        { "defseg", "?[-s segment]", "change the default segment used to "
            "translate addresses", bhyve_defseg_dcmd },
        { "regs", NULL, "print general-purpose registers", bhyve_regs_dcmd },
        { "stack", "[-nstv]", "print stack backtrace", bhyve_stack_dcmd,
            bhyve_stack_help },
        { "stackregs", "[-nstv]", "print stack backtrace and registers",
            bhyve_stackr_dcmd, bhyve_stack_help },
        { "status", NULL, "print summary of current target",
            bhyve_status_dcmd },
        { "sysregs", "?[-c cpuid]", "print system registers for a specific CPU",
            bhyve_sysregs_dcmd },
        { "switch", ":", "change the active CPU", bhyve_switch_dcmd },
        { "vtol", ":[-s segment]", "print linear mapping of virtual address",
            bhyve_vtol_dcmd },
        { "vtop", ":[-s segment]", "print physical mapping of virtual "
            "address", bhyve_vtop_dcmd },
        { NULL }
};

/*
 * t_setflags: change target flags
 */
static int
bhyve_setflags(mdb_tgt_t *tgt, int flags)
{
        bhyve_data_t *bd = tgt->t_data;

        if (((tgt->t_flags ^ flags) & MDB_TGT_F_RDWR) != 0) {
                boolean_t writable = (flags & MDB_TGT_F_RDWR) != 0;

                vmm_unmap(bd->bd_vmm);
                if (vmm_map(bd->bd_vmm, writable) != 0) {
                        mdb_warn("failed to map guest memory");
                        return (set_errno(EMDB_TGT));
                }
        }

        tgt->t_flags = flags;

        return (0);
}

/*
 * t_activate: activate target
 */
static void
bhyve_activate(mdb_tgt_t *tgt)
{
        mdb_tgt_status_t *tsp = &tgt->t_status;
        bhyve_data_t *bd = tgt->t_data;
        const char *format;
        char buf[BUFSIZ];

        (void) mdb_set_prompt(MDB_DEF_PROMPT);

        (void) mdb_tgt_register_dcmds(tgt, bhyve_dcmds, MDB_MOD_FORCE);
        mdb_tgt_register_regvars(tgt, bhyve_kregs, &bhyve_reg_disc, 0);

        (void) vmm_stop(bd->bd_vmm);

        if (mdb_tgt_status(tgt, tsp) != 0)
                return;

        if (tsp->st_pc != 0) {
                if (mdb_dis_ins2str(mdb.m_disasm, mdb.m_target,
                    MDB_TGT_AS_VIRT_I, buf, sizeof (buf), tsp->st_pc) !=
                    tsp->st_pc)
                        format = "target stopped at:\n%-#16a%8T%s\n";
                else
                        format = "target stopped at %a:\n";
                mdb_warn(format, tsp->st_pc, buf);
        }
}

/*
 * t_deactivate: deactivate target
 */
static void
bhyve_deactivate(mdb_tgt_t *tgt)
{
        bhyve_data_t *bd = tgt->t_data;
        const mdb_tgt_regdesc_t *rd;
        const mdb_dcmd_t *dc;

        for (rd = bhyve_kregs; rd->rd_name != NULL; rd++) {
                mdb_var_t *var;

                if (!(rd->rd_flags & MDB_TGT_R_EXPORT))
                        continue; /* didn't export register as variable */

                if ((var = mdb_nv_lookup(&mdb.m_nv, rd->rd_name)) != NULL) {
                        var->v_flags &= ~MDB_NV_PERSIST;
                        mdb_nv_remove(&mdb.m_nv, var);
                }
        }

        for (dc = bhyve_dcmds; dc->dc_name != NULL; dc++)
                if (mdb_module_remove_dcmd(tgt->t_module, dc->dc_name) == -1)
                        mdb_warn("failed to remove dcmd %s", dc->dc_name);

        (void) vmm_cont(bd->bd_vmm);
}

/*
 * t_name: return name of target
 */
static const char *
bhyve_name(mdb_tgt_t *tgt)
{
        _NOTE(ARGUNUSED(tgt));

        return ("bhyve");
}

/*
 * t_destroy: cleanup target private resources
 */
static void
bhyve_destroy(mdb_tgt_t *tgt)
{
        bhyve_data_t *bd = tgt->t_data;

        (void) vmm_cont(bd->bd_vmm);
        vmm_unmap(bd->bd_vmm);
        vmm_close_vm(bd->bd_vmm);
        mdb_free(bd, sizeof (bhyve_data_t));
        tgt->t_data = NULL;
}

/*
 * t_isa: return name of target ISA
 */
const char *
bhyve_isa(mdb_tgt_t *tgt)
{
        _NOTE(ARGUNUSED(tgt));

        return ("amd64");
}

/*
 * t_dmodel: return target data model
 */
static int
bhyve_dmodel(mdb_tgt_t *tgt)
{
        _NOTE(ARGUNUSED(tgt));

        return (MDB_TGT_MODEL_LP64);
}

/*ARGSUSED*/
static ssize_t
bhyve_aread(mdb_tgt_t *tgt, mdb_tgt_as_t as, void *buf, size_t nbytes,
    mdb_tgt_addr_t addr)
{
        bhyve_data_t *bd = tgt->t_data;
        ssize_t cnt = 0;

        switch ((uintptr_t)as) {
        case (uintptr_t)MDB_TGT_AS_VIRT:
                cnt = vmm_vread(bd->bd_vmm, bd->bd_curcpu, bd->bd_defseg, buf,
                    nbytes, addr);
                break;

        case (uintptr_t)MDB_TGT_AS_VIRT_I:
                cnt = vmm_vread(bd->bd_vmm, bd->bd_curcpu, VMM_DESC_CS, buf,
                    nbytes, addr);
                break;

        case (uintptr_t)MDB_TGT_AS_VIRT_S:
                cnt = vmm_vread(bd->bd_vmm, bd->bd_curcpu, VMM_DESC_SS, buf,
                    nbytes, addr);
                break;

        case (uintptr_t)MDB_TGT_AS_PHYS:
                cnt = vmm_pread(bd->bd_vmm, buf, nbytes, addr);
                break;

        case (uintptr_t)MDB_TGT_AS_FILE:
        case (uintptr_t)MDB_TGT_AS_IO:
                return (set_errno(EMDB_TGTNOTSUP));
        }

        if (errno == EFAULT)
                return (set_errno(EMDB_NOMAP));

        return (cnt);
}

/*ARGSUSED*/
static ssize_t
bhyve_awrite(mdb_tgt_t *tgt, mdb_tgt_as_t as, const void *buf, size_t nbytes,
    mdb_tgt_addr_t addr)
{
        bhyve_data_t *bd = tgt->t_data;
        ssize_t cnt = 0;

        switch ((uintptr_t)as) {
        case (uintptr_t)MDB_TGT_AS_VIRT:
                cnt = vmm_vwrite(bd->bd_vmm, bd->bd_curcpu, bd->bd_defseg, buf,
                    nbytes, addr);
                break;

        case (uintptr_t)MDB_TGT_AS_VIRT_I:
                cnt = vmm_vwrite(bd->bd_vmm, bd->bd_curcpu, VMM_DESC_CS, buf,
                    nbytes, addr);
                break;

        case (uintptr_t)MDB_TGT_AS_VIRT_S:
                cnt = vmm_vwrite(bd->bd_vmm, bd->bd_curcpu, VMM_DESC_SS, buf,
                    nbytes, addr);
                break;

        case (uintptr_t)MDB_TGT_AS_PHYS:
                cnt = vmm_pwrite(bd->bd_vmm, buf, nbytes, addr);
                break;

        case (uintptr_t)MDB_TGT_AS_FILE:
        case (uintptr_t)MDB_TGT_AS_IO:
                return (set_errno(EMDB_TGTNOTSUP));
        }

        if (errno == EFAULT)
                return (set_errno(EMDB_NOMAP));

        return (cnt);
}

/*
 * t_vread: read from virtual memory
 */
/*ARGSUSED*/
static ssize_t
bhyve_vread(mdb_tgt_t *tgt, void *buf, size_t nbytes, uintptr_t addr)
{
        return (bhyve_aread(tgt, MDB_TGT_AS_VIRT, buf, nbytes, addr));
}

/*
 * t_vwrite: write to virtual memory
 */
/*ARGSUSED*/
static ssize_t
bhyve_vwrite(mdb_tgt_t *tgt, const void *buf, size_t nbytes, uintptr_t addr)
{
        return (bhyve_awrite(tgt, MDB_TGT_AS_VIRT, buf, nbytes, addr));
}

/*
 * t_pread: read from physical memory
 */
/*ARGSUSED*/
static ssize_t
bhyve_pread(mdb_tgt_t *tgt, void *buf, size_t nbytes, physaddr_t addr)
{
        return (bhyve_aread(tgt, MDB_TGT_AS_PHYS, buf, nbytes, addr));
}

/*
 * t_pwrite: write to physical memory
 */
/*ARGSUSED*/
static ssize_t
bhyve_pwrite(mdb_tgt_t *tgt, const void *buf, size_t nbytes, physaddr_t addr)
{
        return (bhyve_awrite(tgt, MDB_TGT_AS_PHYS, buf, nbytes, addr));
}

/*
 * t_fread: read from core/object file
 */
/*ARGSUSED*/
static ssize_t
bhyve_fread(mdb_tgt_t *tgt, void *buf, size_t nbytes, uintptr_t addr)
{
        return (bhyve_aread(tgt, MDB_TGT_AS_FILE, buf, nbytes, addr));
}

/*
 * t_fwrite: write to core/object file
 */
/*ARGSUSED*/
static ssize_t
bhyve_fwrite(mdb_tgt_t *tgt, const void *buf, size_t nbytes, uintptr_t addr)
{
        return (bhyve_awrite(tgt, MDB_TGT_AS_FILE, buf, nbytes, addr));
}

/*
 * t_ioread: read from I/O space
 */
/*ARGSUSED*/
static ssize_t
bhyve_ioread(mdb_tgt_t *tgt, void *buf, size_t nbytes, uintptr_t addr)
{
        return (bhyve_aread(tgt, MDB_TGT_AS_IO, buf, nbytes, addr));
}

/*
 * t_iowrite: write to I/O space
 */
/*ARGSUSED*/
static ssize_t
bhyve_iowrite(mdb_tgt_t *tgt, const void *buf, size_t nbytes, uintptr_t addr)
{
        return (bhyve_awrite(tgt, MDB_TGT_AS_IO, buf, nbytes, addr));
}

/*
 * t_vtop: translate virtual to physical address
 */
static int
bhyve_vtop(mdb_tgt_t *tgt, mdb_tgt_as_t as, uintptr_t va, physaddr_t *pa)
{
        bhyve_data_t *bd = tgt->t_data;
        int seg;

        switch ((uintptr_t)as) {
        case (uintptr_t)MDB_TGT_AS_VIRT:
                seg = bd->bd_defseg;
                break;

        case (uintptr_t)MDB_TGT_AS_VIRT_I:
                seg = VMM_DESC_CS;
                break;

        case (uintptr_t)MDB_TGT_AS_VIRT_S:
                seg = VMM_DESC_SS;
                break;

        default:
                return (set_errno(EINVAL));
        }

        if (vmm_vtop(bd->bd_vmm, bd->bd_curcpu, seg, va, pa) != 0) {
                if (errno == EFAULT)
                        return (set_errno(EMDB_NOMAP));
                else
                        return (-1);
        }

        return (0);
}

/*
 * t_lookup_by_addr: find symbol information for a given name
 */
static int
bhyve_lookup_by_name(mdb_tgt_t *t, const char *object, const char *name,
    GElf_Sym *symp, mdb_syminfo_t *sip)
{
        int err;

        /*
         * Search only the private symbols, as nothing else will be populated.
         */
        err = mdb_gelf_symtab_lookup_by_name(mdb.m_prsym, name, symp,
            &sip->sym_id);
        sip->sym_table = MDB_TGT_PRVSYM;
        return (err);
}

/*
 * t_lookup_by_addr: find symbol information for a given address
 */
static int
bhyve_lookup_by_addr(mdb_tgt_t *tgt, uintptr_t addr, uint_t flags, char *buf,
    size_t nbytes, GElf_Sym *symp, mdb_syminfo_t *sip)
{
        int err;

        /*
         * Only the private symbols (created with ::nmadd) will be populated, so
         * search against those.
         */
        err = mdb_gelf_symtab_lookup_by_addr(mdb.m_prsym, addr, flags, buf,
            nbytes, symp, &sip->sym_id);
        sip->sym_table = MDB_TGT_PRVSYM;
        return (err);
}

/*
 * t_status: get target status
 */
static int
bhyve_status(mdb_tgt_t *tgt, mdb_tgt_status_t *tsp)
{
        bhyve_data_t *bd = tgt->t_data;
        mdb_tgt_reg_t rip;
        vmm_desc_t cs;
        int ret;

        bzero(tsp, sizeof (mdb_tgt_status_t));

        ret = vmm_getreg(bd->bd_vmm, bd->bd_curcpu, KREG_RIP, &rip);
        if (ret != 0) {
                tsp->st_state = MDB_TGT_UNDEAD;
        } else {
                tsp->st_state = MDB_TGT_STOPPED;
                tsp->st_pc = rip;
        }

        switch (vmm_vcpu_isa(bd->bd_vmm, bd->bd_curcpu)) {
        case VMM_ISA_16:
                (void) mdb_dis_select("ia16");
                break;
        case VMM_ISA_32:
                (void) mdb_dis_select("ia32");
                break;
        case VMM_ISA_64:
                (void) mdb_dis_select("amd64");
                break;
        default:
                break;
        }

        return (0);
}

static void
bhyve_sighdl(int sig, siginfo_t *sip, ucontext_t *ucp, mdb_tgt_t *tgt)
{
        mdb_tgt_status_t *tsp = &tgt->t_status;
        bhyve_data_t *bd = tgt->t_data;

        switch (sig) {
        case SIGINT:
                /*
                 * vmm_stop() may fail if the VM was destroyed while we were
                 * waiting. This will be handled by mdb_tgt_status().
                 */
                (void) vmm_stop(bd->bd_vmm);
                (void) mdb_tgt_status(tgt, tsp);
                break;
        }
}

/*
 * t_step: single-step target
 */
static int
bhyve_step(mdb_tgt_t *tgt, mdb_tgt_status_t *tsp)
{
        bhyve_data_t *bd = tgt->t_data;
        int ret;

        ret = vmm_step(bd->bd_vmm, bd->bd_curcpu);
        (void) mdb_tgt_status(tgt, tsp);

        return (ret);
}

/*
 * t_cont: continue target execution
 *
 * Catch SIGINT so that the target can be stopped with Ctrl-C.
 */
static int
bhyve_cont(mdb_tgt_t *tgt, mdb_tgt_status_t *tsp)
{
        bhyve_data_t *bd = tgt->t_data;
        mdb_signal_f *intf;
        void *intd;
        int ret;

        intf = mdb_signal_gethandler(SIGINT, &intd);
        (void) mdb_signal_sethandler(SIGINT, (mdb_signal_f *)bhyve_sighdl, tgt);

        if (ret = vmm_cont(bd->bd_vmm) != 0) {
                mdb_warn("failed to continue target execution: %d", ret);
                return (set_errno(EMDB_TGT));
        }

        tsp->st_state = MDB_TGT_RUNNING;
        (void) pause();

        (void) mdb_signal_sethandler(SIGINT, intf, intd);
        (void) mdb_tgt_status(tgt, tsp);

        return (ret);
}

static int
bhyve_lookup_reg(mdb_tgt_t *tgt, const char *rname)
{
        bhyve_data_t *bd = tgt->t_data;
        const mdb_tgt_regdesc_t *rd;

        for (rd = bhyve_kregs; rd->rd_name != NULL; rd++)
                if (strcmp(rd->rd_name, rname) == 0)
                        return (rd->rd_num);

        return (-1);
}

/*
 * t_getareg: get the value of a single register
 */
static int
bhyve_getareg(mdb_tgt_t *tgt, mdb_tgt_tid_t tid, const char *rname,
    mdb_tgt_reg_t *rp)
{
        bhyve_data_t *bd = tgt->t_data;
        int reg = bhyve_lookup_reg(tgt, rname);
        int ret;

        if (reg == -1)
                return (set_errno(EMDB_BADREG));

        ret = vmm_getreg(bd->bd_vmm, bd->bd_curcpu, reg, rp);
        if (ret == -1)
                return (set_errno(EMDB_BADREG));

        return (0);
}

/*
 * t_putareg: set the value of a single register
 */
static int
bhyve_putareg(mdb_tgt_t *tgt, mdb_tgt_tid_t tid, const char *rname,
    mdb_tgt_reg_t r)
{
        bhyve_data_t *bd = tgt->t_data;
        int reg = bhyve_lookup_reg(tgt, rname);
        int ret;

        if ((tgt->t_flags & MDB_TGT_F_RDWR) == 0)
                return (set_errno(EMDB_TGTRDONLY));

        if (reg == -1)
                return (set_errno(EMDB_BADREG));

        ret = vmm_setreg(bd->bd_vmm, bd->bd_curcpu, reg, r);
        if (ret == -1)
                return (set_errno(EMDB_BADREG));

        return (0);
}

static const mdb_tgt_ops_t bhyve_ops = {
        .t_setflags =           bhyve_setflags,
        .t_setcontext =         (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_activate =           bhyve_activate,
        .t_deactivate =         bhyve_deactivate,
        .t_periodic =           (void (*)())(uintptr_t)mdb_tgt_nop,
        .t_destroy =            bhyve_destroy,
        .t_name =               bhyve_name,
        .t_isa =                bhyve_isa,
        .t_platform =           (const char *(*)())mdb_conf_platform,
        .t_uname =              (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_dmodel =             bhyve_dmodel,
        .t_aread =              bhyve_aread,
        .t_awrite =             bhyve_awrite,
        .t_vread =              bhyve_vread,
        .t_vwrite =             bhyve_vwrite,
        .t_pread =              bhyve_pread,
        .t_pwrite =             bhyve_pwrite,
        .t_fread =              bhyve_fread,
        .t_fwrite =             bhyve_fwrite,
        .t_ioread =             bhyve_ioread,
        .t_iowrite =            bhyve_iowrite,
        .t_vtop =               bhyve_vtop,
        .t_lookup_by_name =     bhyve_lookup_by_name,
        .t_lookup_by_addr =     bhyve_lookup_by_addr,
        .t_symbol_iter =        (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_mapping_iter =       (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_object_iter =        (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_addr_to_map =        (const mdb_map_t *(*)())mdb_tgt_null,
        .t_name_to_map =        (const mdb_map_t *(*)())mdb_tgt_null,
        .t_addr_to_ctf =        (struct ctf_file *(*)())mdb_tgt_null,
        .t_name_to_ctf =        (struct ctf_file *(*)())mdb_tgt_null,
        .t_status =             bhyve_status,
        .t_run =                (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_step =               bhyve_step,
        .t_step_out =           (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_next =               (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_cont =               bhyve_cont,
        .t_signal =             (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_add_vbrkpt =         (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_sbrkpt =         (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_pwapt =          (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_vwapt =          (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_iowapt =         (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_sysenter =       (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_sysexit =        (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_signal =         (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_fault =          (int (*)())(uintptr_t)mdb_tgt_null,
        .t_getareg =            bhyve_getareg,
        .t_putareg =            bhyve_putareg,
        .t_stack_iter =         (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_auxv =               (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_thread_name =        (int (*)())(uintptr_t)mdb_tgt_notsup
};

int
mdb_bhyve_tgt_create(mdb_tgt_t *tgt, int argc, const char *argv[])
{
        bhyve_data_t *bd;
        vmm_t *vmm = NULL;
        boolean_t writable = (tgt->t_flags & MDB_TGT_F_RDWR) != 0;

        if (argc != 1)
                return (set_errno(EINVAL));

        vmm = vmm_open_vm(argv[0]);
        if (vmm == NULL) {
                mdb_warn("failed to open %s", argv[0]);
                return (set_errno(EMDB_TGT));
        }

        if (vmm_map(vmm, writable) != 0) {
                mdb_warn("failed to map %s", argv[0]);
                vmm_close_vm(vmm);
                return (set_errno(EMDB_TGT));
        }

        bd = mdb_zalloc(sizeof (bhyve_data_t) + strlen(argv[0]) + 1, UM_SLEEP);
        (void) strcpy(bd->bd_name, argv[0]);
        bd->bd_vmm = vmm;
        bd->bd_curcpu = 0;
        bd->bd_defseg = VMM_DESC_DS;

        tgt->t_ops = &bhyve_ops;
        tgt->t_data = bd;
        tgt->t_flags |= MDB_TGT_F_ASIO;

        (void) mdb_nv_insert(&mdb.m_nv, "cpuid", &bhyve_cpuid_disc, 0,
            MDB_NV_PERSIST | MDB_NV_RDONLY);

        return (0);
}