/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright 1996-2025 The NASM Authors - All Rights Reserved */

/*
 * disasm.c   where all the _work_ gets done in the Netwide Disassembler
 *
 * See x86/bytecode.txt for the definition of the instruction encoding
 * byte codes.
 */

#include "compiler.h"

#include "disasm.h"
#include "sync.h"
#include "insns.h"
#include "tables.h"
#include "regdis.h"
#include "bytesex.h"
#include "disp8.h"

#define fetch_or_return(_start, _ptr, _size, _need)         \
    fetch_safe(_start, _ptr, _size, _need, return 0)

/*
 * Flags that go into the `segment' field of `operand' structures
 */
#define SEG_RELATIVE    1
#define SEG_RMREG       2
#define SEG_NODISP      4
#define SEG_DISP8       8
#define SEG_DISP16     16
#define SEG_DISP32     32
#define SEG_DISP64     64
#define SEG_DISPMASK  (SEG_NODISP|SEG_DISP8|SEG_DISP16|SEG_DISP32|SEG_DISP64)
#define SEG_SIGNED    128
#define SEG_RMMEM     256
#define SEG_DFV       512
#define SEG_16BIT     (16 << 8)
#define SEG_32BIT     (32 << 8)
#define SEG_64BIT     (64 << 8)
#define SEG_BITMASK   (SEG_16BIT|SEG_32BIT|SEG_64BIT)

/* These get the address size associated with *one particular operand* */
static inline uint32_t seg_set_asize(uint32_t seg, unsigned int asize)
{
    return (seg & ~SEG_BITMASK) | (asize << 8);
}
static inline unsigned int seg_get_asize(uint32_t seg)
{
    return (seg & SEG_BITMASK) >> 8;
}

/*
 * Important: regval must already have been adjusted for rex extensions;
 * the rex flags are only used to determine which 8-bit register decoding
 * to use.
 */
static enum reg_enum whichreg(opflags_t regflags, int regval, uint32_t rex)
{
    /* Unsigned value suitable for array lookup */
    const size_t r = regval;
    size_t i;

    static const struct {
        opflags_t flags;
        const enum reg_enum *regs;
    } regclasses[] = {
        {REG16, nasm_rd_reg16},
        {REG32, nasm_rd_reg32},
        {REG64, nasm_rd_reg64},
        {REG_SREG, nasm_rd_sreg},
        {REG_CREG, nasm_rd_creg},
        {REG_DREG, nasm_rd_dreg},
        {REG_TREG, nasm_rd_treg},
        {FPUREG, nasm_rd_fpureg},
        {MMXREG, nasm_rd_mmxreg},
        {XMMREG, nasm_rd_xmmreg},
        {YMMREG, nasm_rd_ymmreg},
        {ZMMREG, nasm_rd_zmmreg},
        {OPMASKREG, nasm_rd_opmaskreg},
        {BNDREG, nasm_rd_bndreg},
        {TMMREG, nasm_rd_tmmreg},
    };

    /* All the entries below look up regval in 32-entry arrays */
    if (r >= DISREGTBLSZ)
        return 0;

    if (!(regflags & (REGISTER|REGMEM)))
        return 0;        /* Registers not permissible?! */

    if (!(regflags & REG_CLASS_GPR))
        regflags &= ~SIZE_MASK;

    if (is_class(REG_CLASS_GPR|BITS8, regflags)) {
        if (rex & (REX_P|REX_NH))
            return nasm_rd_reg8_rex[r];
        else
            return nasm_rd_reg8[r];
    }

    for (i = 0; i < ARRAY_SIZE(regclasses); i++) {
        enum reg_enum reg = regclasses[i].regs[r];
        if (is_class(regflags, nasm_reg_flags[reg]))
            return reg;
    }

    return 0;                   /* Unknown register type */
}

/*
 * An implicit register operand.
 */

/* Deal with OpenWatcom 64-bit switch() braindamage */
#ifdef __WATCOMC__
# define imp(op,rn) if (regflags == op) return rn
# define impdef(rn) return rn
#else
# define imp(op,rn) case op: return rn
# define impdef(rn) default: return rn
#endif

static enum reg_enum implicit_reg(opflags_t regflags)
{
#ifndef __WATCOMC__
    switch (regflags)
#endif
    {
        imp(REG_AL,R_AL);
        imp(REG_AX,R_AX);
        imp(REG_EAX,R_EAX);
        imp(REG_RAX,R_RAX);
        imp(REG_DL,R_DL);
        imp(REG_DX,R_DX);
        imp(REG_EDX,R_EDX);
        imp(REG_RDX,R_RDX);
        imp(REG_CL,R_CL);
        imp(REG_CX,R_CX);
        imp(REG_ECX,R_ECX);
        imp(REG_RCX,R_RCX);
        imp(FPU0,R_ST0);
        imp(XMM0,R_XMM0);
        imp(YMM0,R_YMM0);
        imp(ZMM0,R_ZMM0);
        imp(REG_ES,R_ES);
        imp(REG_CS,R_CS);
        imp(REG_SS,R_SS);
        imp(REG_DS,R_DS);
        imp(REG_FS,R_FS);
        imp(REG_GS,R_GS);
        imp(OPMASK0,R_K0);
        impdef(0);
    }
}

/*
 * Pick the correct REX.B prefix to add to this base register and
 * get the corresponding register number.
 */
static enum reg_enum whichbreg(opflags_t regflags, int regval, uint32_t rex,
                               const struct prefix_info *pf)
{
    if (regflags & (REG_CLASS_RM_XMM|REG_CLASS_RM_YMM|
                    REG_CLASS_RM_ZMM|REG_CLASS_RM_TMM))
        regval += pf->rex.bregbv;
    else
        regval += pf->rex.breg;

    return whichreg(regflags, regval, rex);
}
static enum reg_enum whichrreg(opflags_t regflags, int regval, uint32_t rex,
                               const struct prefix_info *pf)
{
    return whichreg(regflags, regval + pf->rex.rreg, rex);
}

static uint32_t append_evex_reg_deco(char *buf, uint32_t num,
                                     decoflags_t deco,
                                     const struct prefix_info *pf)
{
    const char * const er_names[] = {"rn-sae", "rd-sae", "ru-sae", "rz-sae"};
    uint32_t num_chars = 0;

    if ((deco & MASK) && pf->rex.aaa) {
        enum reg_enum opmasknum = nasm_rd_opmaskreg[pf->rex.aaa];
        const char * regname = nasm_reg_names[opmasknum - EXPR_REG_START];

        num_chars += snprintf(buf + num_chars, num - num_chars,
                              "{%s}", regname);

        if ((deco & Z) && pf->rex.z) {
            num_chars += snprintf(buf + num_chars, num - num_chars,
                                  "{z}");
        }
    }

    if (pf->rex.b) {
        if (deco & ER) {
            num_chars += snprintf(buf + num_chars, num - num_chars,
                                  ",{%s}", er_names[pf->rex.l]);
        } else if (deco & SAE) {
            num_chars += snprintf(buf + num_chars, num - num_chars,
                                  ",{sae}");
        }
    }

    return num_chars;
}

static uint32_t append_evex_mem_deco(char *buf, uint32_t num, opflags_t type,
                                     decoflags_t deco,
                                     const struct prefix_info *pf)
{
    uint32_t num_chars = 0;

    if (pf->rex.b && (deco & BRDCAST_MASK)) {
        decoflags_t deco_brsize = deco & BRSIZE_MASK;
        opflags_t template_opsize = brsize_to_size(deco_brsize);
        unsigned int br_num = (type & SIZE_MASK) / BITS128 *
            BITS64 / template_opsize * 2;

        num_chars += snprintf(buf + num_chars, num - num_chars,
                              "{1to%d}", br_num);
    }

    if ((deco & MASK) && pf->rex.aaa) {
        enum reg_enum opmasknum = nasm_rd_opmaskreg[pf->rex.aaa];
        const char * regname = nasm_reg_names[opmasknum - EXPR_REG_START];

        num_chars += snprintf(buf + num_chars, num - num_chars,
                              "{%s}", regname);

        if ((deco & Z) && pf->rex.z) {
            num_chars += snprintf(buf + num_chars, num - num_chars,
                                  "{z}");
        }
    }

    return num_chars;
}

/*
 * Process an effective address (ModRM) specification.
 */
static const uint8_t *do_ea(const uint8_t *data, int modrm,
                            int bits, enum ea_type type,
                            operand *op, insn *ins,
                            const struct prefix_info *prefix)
{
    int mod, rm, scale, index, base;
    int asize = ins->addr_size;

    mod = (modrm >> 6) & 03;
    rm = modrm & 07;

    if (mod == 3) {             /* pure register version */
        op->basereg = whichbreg(op->type, rm, ins->rex, prefix);
        if (!op->basereg)
            return NULL;
        op->segment |= SEG_RMREG;
        return data;
    }

    op->disp_size = 0;
    op->eaflags = 0;
    op->segment = seg_set_asize(op->segment, asize) | SEG_RMMEM;

    if (asize == 16) {
        /*
         * <mod> specifies the displacement size (none, byte or
         * word), and <rm> specifies the register combination.
         * Exception: mod=0,rm=6 does not specify [BP] as one might
         * expect, but instead specifies [disp16].
         */

        if (type != EA_SCALAR)
            return NULL;

        op->indexreg = op->basereg = 0;
        op->scale = 1;          /* always, in 16 bits */
        switch (rm) {
        case 0:
            op->basereg = R_BX;
            op->indexreg = R_SI;
            break;
        case 1:
            op->basereg = R_BX;
            op->indexreg = R_DI;
            break;
        case 2:
            op->basereg = R_BP;
            op->indexreg = R_SI;
            break;
        case 3:
            op->basereg = R_BP;
            op->indexreg = R_DI;
            break;
        case 4:
            op->basereg = R_SI;
            break;
        case 5:
            op->basereg = R_DI;
            break;
        case 6:
            op->basereg = R_BP;
            break;
        case 7:
            op->basereg = R_BX;
            break;
        }
        switch (mod) {
        case 0:
            if (rm != 6) {
                op->segment |= SEG_NODISP;
                op->disp_size = 0;
                break;
            } else {
                /* disp16 only, no base register */
                op->basereg = 0;
            }
            /* fall through */
        case 2:
            op->segment |= SEG_DISP16;
            op->disp_size = 16;
            op->offset = gets16(data);
            data += 2;
            break;
        case 1:
            op->segment |= SEG_DISP8;
            op->offset = gets8(data) << get_disp8_shift(ins);
            op->disp_size = 8;
            data++;
            break;
        }
        return data;
    } else {
        /*
         * Once again, <mod> specifies displacement size (this time
         * none, byte or *dword*), while <rm> specifies the base
         * register. Again, [EBP] is missing, replaced by a pure
         * disp32 (this time that's mod=0,rm=*5*) in 32-bit mode,
         * and RIP-relative addressing in 64-bit mode.
         *
         * However, rm=4
         * indicates not a single base register, but instead the
         * presence of a SIB byte...
         */
        const enum reg_enum *regs;
        regs = (asize == 64) ? nasm_rd_reg64 : nasm_rd_reg32;

        op->basereg = op->indexreg = 0;
        base = rm;

        if (rm == 4) {
            /* There is a SIB byte */
            uint8_t sib = *data++;

            scale = (sib >> 6) & 03;
            index = (sib >> 3) & 07;
            base = sib & 07;

            op->scale = 1 << scale;

            if (type != EA_SCALAR) {
                index += prefix->rex.xregxv;

                /* A VSIB always has an index register */
                if (type == EA_XMMVSIB)
                    op->indexreg = nasm_rd_xmmreg[index];
                else if (type == EA_YMMVSIB)
                    op->indexreg = nasm_rd_ymmreg[index];
                else if (type == EA_ZMMVSIB)
                    op->indexreg = nasm_rd_zmmreg[index];
            } else {
                /* Not a VSIB */
                index += prefix->rex.xreg;

                /* ESP/RSP cannot be an index */
                if (index != 4)
                    op->indexreg = regs[index];
            }
        } else {
            /* Can't have VSIB without SIB */
            if (type != EA_SCALAR)
                return NULL;

            base = rm;
        }

        if (base == 5 && mod == 0) {
            /* disp32 without anything else */
            if (rm != 4 && bits == 64) {
                /* If no SIB byte, this is IP-relative in 64-bit mode */
                op->eaflags |= EAF_REL;
                op->segment |= SEG_RELATIVE;
            }
            op->basereg = 0;
            mod = 2;            /* force disp32 */
        } else {
            /* "Normal" base encoding */
            op->basereg = regs[base + prefix->rex.breg];
        }

        switch (mod) {
        case 0:
            op->segment |= SEG_NODISP;
            op->disp_size = 0;
            break;
        case 1:
            op->segment |= SEG_DISP8;
            op->disp_size = 8;
            op->offset = gets8(data) << get_disp8_shift(ins);
            data++;
            break;
        case 2:
            op->segment |= SEG_DISP32;
            op->disp_size = 32;
            op->offset = gets32(data);
            data += 4;
            break;
        }
        return data;
    }
}

/*
 * Return true if the following byte is a modr/m byte which is a memory
 * encoding (mod != 3).
 */
static bool is_mem_modrm(const uint8_t *bytecode, const uint8_t *data)
{
    uint8_t b = *bytecode & 0344;

    if (b == 0100 || b == 0200)
        return (*data & 0300) != 0300;
    else
        return false;           /* Not a modr/m per instruction template */
}

/*
 * Determine whether the instruction template in ins->itemp
 * corresponds to the data stream in data and parse it into "ins" if
 * so.  "ins" has already been partially initialized in disasm() to
 * point contain the address of the instruction (loc.offset), the mode
 * (bits), and the desired template (itemp); all other fields must be
 * initialized to zero.
 *
 * The "data" pointer should point to the first byte after parsed
 * prefixes (after call to parse_prefixes()), and must have enough
 * slack buffer space that the longest template cannot overrun the
 * data buffer, although the content beyond the end of any valid
 * instruction is allowed to be garbage.
 */
#define case4(x) case (x): case (x)+1: case (x)+2: case (x)+3

static int matches(const uint8_t *data, const struct prefix_info *prefix,
                   insn *ins)
{
    const struct itemplate * const t = ins->itemp;
    const uint8_t *r = t->code;
    const uint8_t * const origdata = data; /* First byte after prefixes */
    bool a_used = false, o_used = false;
    enum prefixes drep  = 0;
    enum prefixes dwait = 0;
    enum prefixes dlock = prefix->lock ? P_LOCK : 0;
    int osize = prefix->osize;
    int asize = prefix->asize;
    const int bits = ins->bits;
    const int defosize = (bits == 16) ? 16 : 32;
    int i, c;
    int op1, op2;
    struct operand *opx, *opy;
    uint8_t opex = 0;
    uint8_t regmask = (bits == 64) ? 31 : 7;
    enum ea_type eat = EA_SCALAR;
    decoflags_t decoflags = 0;

    if (bits == 64 && prefix->rex.w &&
        (prefix->rex.type < REX_VEX || itemp_has(t, IF_WW)))
        ins->op_size = osize = 64;

    ins->addr_size  = asize;
    ins->op_size    = osize;
    ins->itemp      = t;
    ins->opcode     = t->opcode;
    ins->operands   = t->operands;

    for (i = 0; i < ins->operands; i++) {
        struct operand *o = &ins->oprs[i];
        o->type  = t->opd[i];
        o->opidx = i;
        if (is_class(IMMEDIATE, t->opd[i])) {
            o->segment = seg_set_asize(bits, bits);
        } else if (is_class(MEM_OFFS, t->opd[i])) {
            a_used = true;
            o->segment = seg_set_asize(bits, asize) | SEG_RMMEM;
        } else {
            o->segment = seg_set_asize(bits, asize);
        }
        decoflags |= t->deco[i];
    }

    /* Decorators are only possible with EVEX */
    if (decoflags && prefix->rex.type != REX_EVEX)
        return 0;

    ins->rex = prefix->rex.flags;

    if (itemp_has(t, (bits == 64 ? IF_NOLONG : IF_LONG)))
        return 0;

    if (prefix->rep == 0xF2)
        drep = (itemp_has(t, IF_BND) ? P_BND : P_REPNE);
    else if (prefix->rep == 0xF3)
        drep = P_REP;

    dwait = prefix->wait ? P_WAIT : 0;

    while ((c = *r++) != 0) {
        op1 = (c & 3) + ((opex & 1) << 2);
        op2 = ((c >> 3) & 3) + ((opex & 2) << 1);
        opx = &ins->oprs[op1];
        opy = &ins->oprs[op2];
        opex = 0;

        switch (c) {
        case 01:
        case 02:
        case 03:
        case 04:
            while (c--)
                if (*r++ != *data++)
                    return 0;
            break;

        case 05:
        case 06:
        case 07:
            opex = c;
            break;

        case4(010):
        {
            int t = *r++, d = *data++;
            if (d < t || d > t + 7)
                return 0;
            else {
                opx->basereg = whichbreg(opx->type, d-t, ins->rex, prefix);
                opx->segment |= SEG_RMREG;
            }
            break;
        }

        case4(014):
            /* this is an separate index reg position of MIB operand (ICC) */
            /* Disassembler uses NASM's split EA form only                 */
            break;

        case4(0274):
            opx->offset = (int8_t)*data++;
            opx->disp_size = 8;
            opx->segment |= SEG_SIGNED;
            break;

        case4(0300):
            if (!is_hint_nop(*data))
                return 0;
            opx->offset = *data++;
            opx->disp_size = 8;
            break;

        case4(020):
        case4(024):
            opx->offset = *data++;
            opx->disp_size = 8;
            break;

        case4(030):
            opx->offset = getu16(data);
            opx->disp_size = 16;
            data += 2;
            break;

        case4(034):
            if (osize == 32) {
                opx->offset = getu32(data);
                opx->disp_size = 32;
                data += 4;
            } else {
                opx->offset = getu16(data);
                opx->disp_size = 16;
                data += 2;
            }
            break;

        case4(040):
            opx->offset = getu32(data);
            opx->disp_size = 32;
            data += 4;
            break;

        case4(0254):
            opx->offset = gets32(data);
            opx->disp_size = 32;
            data += 4;
            break;

        case4(044):
            opx->disp_size = asize;
            switch (asize) {
            case 16:
                opx->offset = getu16(data);
                data += 2;
                break;
            case 32:
                opx->offset = getu32(data);
                data += 4;
                break;
            case 64:
                opx->offset = getu64(data);
                data += 8;
                break;
            }
            break;

        case4(050):
            opx->offset = gets8(data++);
            opx->disp_size = 8;
            opx->segment |= SEG_RELATIVE;
            break;

        case4(054):
            opx->offset = getu64(data);
            opx->disp_size = 64;
            data += 8;
            break;

        case4(060):
            opx->offset = gets16(data);
            opx->disp_size = 16;
            data += 2;
            opx->segment = seg_set_asize(opx->segment, 16) | SEG_RELATIVE;
            break;

        case4(064):  /* rel */
            if (bits == 64) {
                /*
                 * In long mode rel is always 32 bits, sign extended.
                 * REX.W or 66 prefixes have no effect.
                 */
                opx->offset = gets32(data);
                opx->disp_size = 32;
                data += 4;
                opx->segment = seg_set_asize(opx->segment, 64) | SEG_RELATIVE;
            } else if (osize == 32) {
                opx->offset = gets32(data);
                opx->disp_size = 32;
                data += 4;
                opx->segment = seg_set_asize(opx->segment, 32) | SEG_RELATIVE;
            } else {
                opx->offset = gets16(data);
                opx->disp_size = 16;
                data += 2;
                opx->segment = seg_set_asize(opx->segment, 16) | SEG_RELATIVE;
            }
            break;

        case4(070):
            opx->offset = gets32(data);
            opx->disp_size = 32;
            data += 4;
            opx->disp_size = 32;
            opx->segment = seg_set_asize(opx->segment, bits == 64 ? 64 : 32)
                | SEG_RELATIVE;
            break;

        case4(0100):
        case4(0110):
        case4(0120):
        case4(0130):
        {
            int modrm = *data++;
            data = do_ea(data, modrm, bits, eat, opy, ins, prefix);
            if (!data)
                return 0;
            a_used |= !!(opy->segment & SEG_RMMEM);
            opx->basereg = whichrreg(opx->type, (modrm >> 3) & 7,
                                     ins->rex, prefix);
            if (!opx->basereg)
                return 0;
            opx->segment |= SEG_RMREG;
            break;
        }

        case 0171:
        {
            uint8_t t = *r++;
            uint8_t d = *data++;
            if ((d ^ t) & ~070) {
                return 0;
            } else {
                op2 = (op2 & ~3) | ((t >> 3) & 3);
                opy = &ins->oprs[op2];
                opy->basereg = whichrreg(opy->type, (d >> 3) & 7,
                                         ins->rex, prefix);
                if (!opy->basereg)
                    return 0;
                opy->segment |= SEG_RMREG;
            }
            break;
        }

        case 0172:
            c = *r++;

            opx = &ins->oprs[(c >> 3) & 7];
            ins->oprs[c & 7].offset = *data & 7;
            goto do_is4;

        case 0173:
            c = *r++;
            if ((c ^ *data) & 7)
                return 0;

            opx = &ins->oprs[(c >> 4) & 7];
            goto do_is4;

        case4(0174):
        do_is4:
        {
            uint8_t ximm = *data++;
            unsigned int nreg = ((ximm >> 4) + ((ximm & 8) << 1)) & regmask;
            opx->basereg = whichbreg(opx->type, nreg, ins->rex, prefix);
            if (!opx->basereg)
                return 0;
            opx->segment |= SEG_RMREG;
            break;
        }

        case4(0200):
        case4(0204):
        case4(0210):
        case4(0214):
        case4(0220):
        case4(0224):
        case4(0230):
        case4(0234):
        {
            int modrm = *data++;
            if (((modrm >> 3) & 07) != (c & 07))
                return 0;   /* spare field doesn't match up */
            data = do_ea(data, modrm, bits, eat, opy, ins, prefix);
            if (!data)
                return 0;
            a_used |= !!(opy->segment & SEG_RMMEM);
            break;
        }

        case4(0240):
        case 0250:
        {
            uint32_t evextemp, mismatch, matchmask;
            bool memop;
            unsigned int vmask;

            if (prefix->rex.type != REX_EVEX)
                return 0;

            ins->rex |= REX_EV;

            /* Get the evex prefix expected by the template */
            evextemp = (getu32(r-1) & ~0xff) + 0x62; r += 3;
            mismatch = prefix->rex.raw ^ evextemp;
            memop = is_mem_modrm(r, data+1);

            /* Now mask out "mismatched" bits that are proper parameters */
            matchmask = ~(EVEX_B4|EVEX_R4|EVEX_B3|EVEX_X3|EVEX_R3|EVEX_X4);

            if (c != 0250 || (memop && !itemp_has(t, IF_SCC))) {
                /*
                 * V4 unless X is a is a vector (which is only
                 * possible if this is a memory operation), in which
                 * case this is X4.  However, X4 is explicitly
                 * forbidden if this is not a memory operation,
                 * otherwise this bit is supposedly ignored if not
                 * used.
                 */
                matchmask &= ~EVEX_V4; /* Either V4 or X4 */
            }

            if (c == 0250) {
                vmask = 0;      /* No V register */
            } else if (is_class(IMMEDIATE, opx->type)) {
                vmask = 15;     /* V operand is used as immediate */
                opx->offset = (mismatch >> 19) & vmask;
                if (itemp_has(t, IF_DFV))
                    opx->segment |= SEG_DFV;
            } else {
                unsigned int regnum;
                vmask = regmask;

                opx->segment |= SEG_RMREG;
                if (eat >= EA_XMMVSIB)
                    regnum = prefix->rex.vregxv;
                else
                    regnum = prefix->rex.vreg;

                opx->basereg = whichreg(opx->type, regnum & vmask, ins->rex);
                if (!opx->basereg)
                    return 0;
            }

            matchmask &= ~((vmask & 15) << 19); /* VVVV field */

            if (prefix->rex.w)
                ins->rex |= REX_W;

            if (itemp_has(t, IF_WIG)) {
                ins->rex &= ~REX_W;
                ins->rex |=  REX_NW;
                matchmask &= ~EVEX_W;
            }

            if (itemp_has(t, IF_LIG) ||
                (prefix->rex.b && !memop)) /* LL used for rounding control */
                matchmask &= ~EVEX_LL;

            if (decoflags & MASK)
                mismatch &= ~EVEX_AAA;

            if (decoflags & Z)
                matchmask &= ~EVEX_Z;

            if (itemp_has(t, IF_NF_E)) {
                matchmask &= ~EVEX_NF;
                if (prefix->rex.nf)
                    ins->prefixes[PPS_NF] = P_NF;
            }

            if (itemp_has(t, IF_ZU_E)) {
                matchmask &= ~EVEX_ZU;
                if (prefix->rex.zu)
                    ins->prefixes[PPS_ZU] = P_ZU;
            }
#if 0
            if (mismatch & matchmask) {
                extern const struct itemplate instrux[];

                printf("EVEX mismatch: %4zd: raw %08x template %08x mask %08x -> %08x\n",
                       t - instrux, prefix->rex.raw, evextemp, matchmask,
                       mismatch & matchmask);
            }
#endif

            if (mismatch & matchmask)
                return 0;

            ins->evex_tuple = *r++ - 0300;

            if (itemp_has(t, IF_NF_E) && prefix->rex.nf)
                ins->prefixes[PPS_NF] = P_NF;

            if (itemp_has(t, IF_ZU_E) && prefix->rex.zu)
                ins->prefixes[PPS_ZU] = P_ZU;
            break;
        }

        case4(0260):
        case 0270:
        {
            uint8_t vexm   = *r++;
            uint8_t vexwlp = *r++;

            if (prefix->rex.type != REX_VEX)
                return 0;

            ins->rex |= REX_V;

            if ((vexm & 0x1f) != prefix->rex.map)
                return 0;

            if (!itemp_has(t, IF_WIG)) {
                if (prefix->rex.w != !!(vexwlp & 0x80))
                    return 0;
            }

            if ((vexwlp & 3) != prefix->rex.pp)
                return 0;

            if (!itemp_has(t, IF_LIG)) {
                if (((vexwlp >> 2) & 3) != prefix->rex.l)
                    return 0;
            }

            if (c == 0270) {
                if (prefix->rex.vreg != 0)
                    return 0;
            } else {
                opx->segment |= SEG_RMREG;
                opx->basereg =
                    whichreg(opx->type, prefix->rex.vreg, ins->rex);
                if (!opx->basereg)
                    return 0;
            }
            break;
        }

        case 0271:
            if (prefix->rep == 0xF3)
                drep = P_XRELEASE;
            break;

        case 0272:
            if (prefix->rep == 0xF2)
                drep = P_XACQUIRE;
            else if (prefix->rep == 0xF3)
                drep = P_XRELEASE;
            break;

        case 0273:
            if (prefix->lock == 0xF0) {
                if (prefix->rep == 0xF2)
                    drep = P_XACQUIRE;
                else if (prefix->rep == 0xF3)
                    drep = P_XRELEASE;
            }
            break;

        case 0310:
            if (asize != 16)
                return 0;
            else
                a_used = true;
            break;

        case 0311:
            if (asize != 32)
                return 0;
            else
                a_used = true;
            break;

        case 0312:
            if (asize != bits)
                return 0;
            else
                a_used = true;
            break;

        case 0313:
            if (asize != 64)
                return 0;
            else
                a_used = true;
            break;

        case 0314:
            if (prefix->rex.flags & REX_B)
                return 0;
            break;

        case 0315:
            if (prefix->rex.flags & REX_X)
                return 0;
            break;

        case 0316:
            if (prefix->rex.flags & REX_R)
                return 0;
            break;

        case 0317:
            if (prefix->rex.flags & REX_W)
                return 0;
            break;

        case 0320:
            if (ins->rex & (REX_V | REX_EV))
                osize = 16;
            else if (osize != 16)
                return 0;
            o_used = true;
            break;

        case 0321:
            if (ins->rex & (REX_V | REX_EV))
                osize = 32;
            else if (osize != 32)
                return 0;
            o_used = true;
            break;

        case 0322:
            if (ins->rex & (REX_V | REX_EV))
                osize = defosize;
            else if (osize != defosize)
                return 0;
            o_used = true;
            break;

        case 0327:
            if (bits != 64)
                break;
            /* else fall through */

        case 0323:
            /* No REX.W required for 64-bit osize */
            if (osize != 16)
                ins->op_size = osize = 64;
            ins->rex |= REX_NW;
            break;

        case 0324:
            if (osize != 64)
                return 0;
            o_used = true;
            break;

        case 0325:
            ins->rex |= REX_NH;
            break;

        case 0326:
            if (prefix->rep == 0xF3)
                return 0;
            break;

        case 0330:
            break;

        case 0331:
            if (prefix->rep)
                return 0;
            break;

        case 0332:
            if (prefix->rep != 0xF2)
                return 0;
            drep = 0;
            break;

        case 0333:
            if (prefix->rep != 0xF3)
                return 0;
            drep = 0;
            break;

        case 0334:
            if (bits != 64 && prefix->lock) {
                ins->rex |= REX_R;
                dlock = 0;
            }
            break;

        case 0335:
            if (drep == P_REP)
                drep = P_REPE;
            break;

        case 0336:
        case 0337:
            break;

        case 0340:
            return 0;

        case 0341:
            if (prefix->wait != 0x9B)
                return 0;
            dwait = 0;
            break;

        case 0342:
            if (osize != bits)
                return 0;
            o_used = true;
            break;

        case 0344:
            ins->rex |= REX_B;
            break;

        case 0345:
            ins->rex |= REX_X;
            break;

        case 0346:
            ins->rex |= REX_R;
            break;

        case 0347:
            ins->rex |= REX_W;
            break;

        case 0350:
            if (prefix->rex.type != REX_REX2)
                return 0;
            ins->rex |= REX_2;
            break;

        case 0351:
            if (prefix->rex.type != REX_REX2)
                return 0;
            ins->rex |= REX_2 | REX_W;
            break;

        case 0355:
        case 0356:
        case 0357:
            nasm_assert(prefix->rex.map == c - 0354);
            break;


        case 0360:
            if (prefix->osp || prefix->rep)
                return 0;
            break;

        case 0361:
            if (!prefix->osp || prefix->rep)
                return 0;
            o_used = true;
            break;

        case 0364:
            if (prefix->osp)
                return 0;
            break;

        case 0365:
            if (prefix->asp)
                return 0;
            break;

        case 0366:
            if (!prefix->osp)
                return 0;
            o_used = true;
            break;

        case 0367:
            if (!prefix->asp)
                return 0;
            a_used = true;
            break;

        case 0370:
        case 0371:
            break;

        case 0374:
            eat = EA_XMMVSIB;
            break;

        case 0375:
            eat = EA_YMMVSIB;
            break;

        case 0376:
            eat = EA_ZMMVSIB;
            break;

        default:
            fprintf(stderr, "ndisasm: unknown byte code: 0%03o\n", c);
            return 0;    /* Unknown code */
        }
    }

    /*
     * VEX or EVEX encoding in byte code, but no such prefix present,
     * or vice versa?
     */
    if ((prefix->rex.flags ^ ins->rex) & (REX_V | REX_EV))
        return 0;

    /* Required REX bits not present? */
    if ((ins->rex & ~prefix->rex.flags) &
        (REX_2 | REX_P | REX_B | REX_X | REX_R | REX_W))
        return 0;

    /*
     * Check for unused rep or a/o prefixes.
     */
    if (dlock) {
        if ((ins->rex & (REX_V | REX_EV)) || !itemp_has(t, IF_LOCK))
            return 0;           /* Instruction not lockable */
        ins->prefixes[PPS_LOCK] = dlock;
    }
    if (drep) {
        if (ins->prefixes[PPS_REP])
            return 0;
        ins->prefixes[PPS_REP] = drep;
    }
    ins->prefixes[PPS_WAIT] = dwait;

    if (prefix->rex.w && osize == 64 && (ins->rex & REX_NW))
        o_used = false;

    if (!o_used) {
        enum prefixes pfx = 0;

        if (prefix->osp) {
            if (osize == 16 && bits != 16)
                pfx = P_O16;
            else if (osize == 32 && bits == 16)
                pfx = P_O32;
            else
                pfx = P_OSP;
        } else if (prefix->rex.w && osize == 64) {
            pfx = P_O64;
        }

        if (ins->prefixes[PPS_OSIZE])
            return 0;
        ins->prefixes[PPS_OSIZE] = pfx;
    }

    if (itemp_has(t, IF_JCC_HINT)) {
        if ((prefix->seg & ~0x10) == 0x2e)
            ins->prefixes[PPS_SEG] = prefix->seg & 0x10 ? P_PT : P_PN;
    }

    if (!a_used) {
        /* Emit any possible segment override prefix explicitly */
        if (!ins->prefixes[PPS_SEG])
            ins->prefixes[PPS_SEG] = prefix->segover;

        if (prefix->asp) {
            if (ins->prefixes[PPS_ASIZE])
                return 0;
            ins->prefixes[PPS_ASIZE] = asize == 16 ? P_A16 : P_A32;
        }
    }

    if (itemp_has(t, IF_NF_R))
        ins->prefixes[PPS_NF] = P_NF;
    if (itemp_has(t, IF_ZU_R))
        ins->prefixes[PPS_ZU] = P_ZU;

    if (itemp_has(t, IF_LATEVEX) && prefix->rex.type == REX_VEX) {
            ins->prefixes[PPS_REX] = P_VEX;
    }

    if ((prefix->rex.raw & 0x807fff) == 0x0061c4) {
        /* A VEX3-prefixed instruction which could have been VEX2-encoded */
        ins->prefixes[PPS_REX] = P_VEX3;
    }

    /* Final fixup of operands */
    for (i = 0; i < ins->operands; i++) {
        struct operand *o = &ins->oprs[i];
        if (o->segment & SEG_RMREG) {
            o->type = nasm_reg_flags[o->basereg];
        } else if (o->segment & SEG_RMMEM) {
            o->type &= ~(REGISTER | IMMEDIATE);
            o->type |= MEMORY;
        } else if (is_class(REGISTER, o->type) && !o->basereg) {
            /* An implicit register operand? */
           o->basereg = implicit_reg(o->type);
            if (!o->basereg)
                return 0;
            o->segment |= SEG_RMREG;
        }
    }
    return data - origdata;
}

static const char *regname(enum reg_enum reg)
{
    if (!is_register(reg))
        return "";

    return nasm_reg_names[reg - EXPR_REG_START];
}

/*
 * If there is a SM indicator in the instruction template, then:
 * - If any of the operands is a sized register, remove sizes from
 *   any other size-matched non-register operands;
 * - Otherwise remove the sizes from all but the first such operand.
 */
static void remove_redundant_sizes(insn *ins)
{
    unsigned int smmask = itemp_smx(ins->itemp);
    int i;
    opflags_t sized = 0;

    for (i = 0; i < ins->operands; i++) {
        const struct operand *o = &ins->oprs[i];

        if (!(smmask & (1 << i)))
            continue;

        if (o->type & REGISTER)
            sized |= o->type & SIZE_MASK;
    }

    for (i = 0; i < ins->operands; i++) {
        struct operand *o = &ins->oprs[i];

        /* This is true if more than one bit is set in "sized" */
        if (sized & (sized-1))
            break;              /* How the heck did this even match? */

        if (!(smmask & (1 << i)))
            continue;

        /* True if exactly 0 or 1 bits set in "sized" */
        if ((o->type & SIZE_MASK) == sized)
            o->type -= sized;   /* Remove size */
        else
            sized |= o->type & SIZE_MASK;
    }
}

int32_t disasm(const uint8_t *dp, int32_t data_size,
               char *output, int outbufsize,
               int bits, int64_t offset, int autosync,
               iflag_t *prefer)
{
    const struct itemplate * const * const *ix;
    const struct itemplate * const *p;
    const struct itemplate *itemp, *best_itemp;
    int length, best_length = 0;
    int maxlen = 15;
    int i, slen;
    char separator;
    const uint8_t *origdata = dp;
    int works;
    insn ins;
    iflag_t goodness, best;
    int best_pref;
    struct prefix_info prefix;

    /*
     * Scan for prefixes.
     */
    dp = parse_prefixes(&prefix, dp, bits);
    if (!dp)                    /* Invalid or no space for even one opcode */
        return 0;

    /*
     * WAIT is not really a prefix, it is an instruction,
     * so it doesn't count toward the maximum instruction
     * length. See comment in rexdis.c.
     */
    if (prefix.wait)
        maxlen++;

    maxlen -= dp - origdata;    /* Prefixes count toward instruction length */

    iflag_set_all(&best); /* Worst possible */
    best_itemp = NULL;
    best_pref = INT_MAX;

    ix = ndisasm_itable[prefix.rex.xmap];
    if (!ix)
        return 0;        /* Instruction map does not exist */

    fetch_or_return(origdata, dp, data_size, 1);
    p = ix[*dp];
    /* dp now points to the primary opcode byte */
    if (!p)
        return 0;               /* No instructions for this opcode */

    nasm_zero(ins);
    while ((itemp = *p++)) {
        insn tmp_ins;

        nasm_zero(tmp_ins);
        tmp_ins.loc.offset = offset;
        tmp_ins.bits       = bits;
        tmp_ins.itemp      = itemp;

        if ((length = matches(dp, &prefix, &tmp_ins))) {
            if (length > maxlen)
                continue;       /* Instruction too long */

            works = true;

            /*
             * Final check to make sure the operand types are valid
             */
            for (i = 0; i < tmp_ins.operands; i++) {
                opflags_t tt = itemp->opd[i];
                opflags_t it = tmp_ins.oprs[i].type;

                /* Strip flags that are not applicable to disassembler matching */
                if (!is_class(REG_GPR, it))
                    tt &= ~SIZE_MASK;
                if (is_class(IMMEDIATE, tt))
                    tt &= ~SUBCLASS_MASK;

                if (!is_class(tt, it)) {
#if 0
                    bool is_reg = !!(tmp_ins.oprs[i].segment & SEG_RMREG);
                    printf("flags 0x%lx%s%s do not match template 0x%lx\n",
                            it,
                            is_reg ? " for register " : "",
                            is_reg ? regname(tmp_ins.oprs[i].basereg) : "",
                            tt);
#endif
                    works = false;
                    break;
                }
            }

            /*
             * Note: we always prefer instructions which incorporate
             * prefixes in the instructions themselves.  This is to allow
             * e.g. PAUSE to be preferred to REP NOP, and deal with
             * MMX/SSE instructions where prefixes are used to select
             * between MMX and SSE register sets or outright opcode
             * selection.
             */
            if (works) {
                int i, nprefix;
                goodness = iflag_pfmask(itemp);
                goodness = iflag_xor(&goodness, prefer);
		nprefix = 0;
		for (i = 0; i < MAXPREFIX; i++)
		    if (tmp_ins.prefixes[i])
			nprefix++;
                if (nprefix < best_pref ||
		    (nprefix == best_pref &&
                     iflag_cmp(&goodness, &best) < 0)) {
                    /* This is the best one found so far */
                    best = goodness;
                    best_itemp = itemp;
                    best_pref = nprefix;
                    best_length = length;
                    ins = tmp_ins;
                }

                if (itemp_has(itemp, IF_BESTDIS))
                    break;      /* Don't search any further */
            }
        }
    }

    if (!best_itemp)
        return 0;               /* no instruction was matched */

    /* Pick the best match */
    itemp = best_itemp;
    length = best_length;

    slen = 0;

    for (i = 0; i < MAXPREFIX; i++) {
        const char *pfx = prefix_name(ins.prefixes[i]);
        if (pfx)
            slen += snprintf(output+slen, outbufsize-slen, "%s ", pfx);
    }

    slen += snprintf(output + slen, outbufsize - slen, "%s",
                     nasm_insn_names[ins.opcode]);

    remove_redundant_sizes(&ins);

    separator = ' ';
    length += dp - origdata;  /* fix up for prefixes */
    for (i = 0; i < ins.operands; i++) {
        decoflags_t deco = itemp->deco[i];
        const operand *o = &ins.oprs[i];
        opflags_t t = o->type;
        int64_t offs = o->offset;
        int asize = seg_get_asize(o->segment);
        int nasize = 64 - asize; /* Address bits to mask off */

        output[slen++] = separator;

        if (o->segment & SEG_RELATIVE) {
            /*
             * sort out wraparound
             */
            offs += ins.loc.offset + length;
            offs = (uint64_t)offs << nasize >> nasize;
            if ((t & (IMMEDIATE|SIZE_MASK)) == IMMEDIATE) {
                if (asize != bits) {
                    switch (asize) {
                    case 16:
                        t |= BITS16;
                        break;
                    case 32:
                        t |= BITS32;
                        break;
                    case 64:
                        t |= BITS64;
                        break;
                    }
                }
            }

            /*
             * add sync marker, if autosync is on
             */
            if (autosync)
                add_sync(offs, 0L);
        }

        separator = (t & COLON) ? ':' : ',';

        if ((t & (REGISTER | FPUREG)) ||
                (o->segment & SEG_RMREG)) {
            enum reg_enum reg = o->basereg;
            if (t & TO)
                slen += snprintf(output + slen, outbufsize - slen, "to ");
            slen += snprintf(output + slen, outbufsize - slen, "%s",
                             regname(reg));
            if (t & REGSET_MASK)
                slen += snprintf(output + slen, outbufsize - slen, "+%d",
                                 (int)((t & REGSET_MASK) >> (REGSET_SHIFT-1))-1);
            if (deco)
                slen += append_evex_reg_deco(output + slen, outbufsize - slen,
                                             deco, &prefix);
        } else if (t & IMMEDIATE) {
            if (is_class(UNITY, t)) {
                output[slen++] = '1';
            } else if (o->segment & SEG_DFV) {
                int fl;
                static const char dfv_flags[] = "czso";
                const char *flsep = "";
                memcpy(&output[slen], "{dfv=", 5);
                slen += 5;
                for (fl = 0; fl < 4; fl++) {
                    if (offs & (1 << fl)) {
                        slen += snprintf(output + slen, outbufsize - slen,
                                         "%s%cf", flsep, dfv_flags[fl]);
                        flsep = ",";
                    }
                }
                output[slen++] = '}';
                separator = ' '; /* No comma after dfv */
            } else {
                /* Immediates that will actually be printed as numbers */

                if (o->segment & SEG_RELATIVE) {
                    /* Don't print any sizes for near jump targets */
                } else if (t & BITS8) {
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "byte ");
                    if (o->segment & SEG_SIGNED) {
                        if (offs < 0) {
                            offs = -offs;
                            output[slen++] = '-';
                        } else {
                            output[slen++] = '+';
                        }
                    }
                } else if (t & BITS16) {
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "word ");
                } else if (t & BITS32) {
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "dword ");
                } else if (t & BITS64) {
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "qword ");
                } else if (t & NEAR) {
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "near ");
                } else if (t & SHORT) {
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "short ");
                }
                slen +=
                    snprintf(output + slen, outbufsize - slen, "0x%"PRIx64"",
                             offs);
            }
        } else if (is_class(REGMEM, t)) {
            int started = false;

            if (t & BITS8)
                slen +=
                    snprintf(output + slen, outbufsize - slen, "byte ");
            if (t & BITS16)
                slen +=
                    snprintf(output + slen, outbufsize - slen, "word ");
            if (t & BITS32)
                slen +=
                    snprintf(output + slen, outbufsize - slen, "dword ");
            if (t & BITS64)
                slen +=
                    snprintf(output + slen, outbufsize - slen, "qword ");
            if (t & BITS80)
                slen +=
                    snprintf(output + slen, outbufsize - slen, "tword ");
            if (prefix.rex.b && (deco & BRDCAST_MASK)) {
                /* when broadcasting, each element size should be used */
                if (deco & BR_BITS16)
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "word ");
                else if (deco & BR_BITS32)
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "dword ");
                else if (deco & BR_BITS64)
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "qword ");
            } else {
                if (t & BITS128)
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "oword ");
                if (t & BITS256)
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "yword ");
                if (t & BITS512)
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "zword ");
            }
            if (t & FAR)
                slen += snprintf(output + slen, outbufsize - slen, "far ");
            if (t & NEAR)
                slen +=
                    snprintf(output + slen, outbufsize - slen, "near ");
            output[slen++] = '[';

            if (prefix.segover) {
                slen +=
                    snprintf(output + slen, outbufsize - slen, "%s:",
                             prefix_name(prefix.segover));
            }
            if (o->basereg) {
                slen += snprintf(output + slen, outbufsize - slen, "%s",
                                 regname(o->basereg));
                started = true;
            }
            if (o->indexreg && !itemp_has(best_itemp, IF_MIB)) {
                if (started)
                    output[slen++] = '+';
                slen += snprintf(output + slen, outbufsize - slen, "%s",
                                 regname(o->indexreg));
                if (o->scale > 1)
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "*%d",
                                o->scale);
                started = true;
            }


            /* Show a displacement */
            if (o->disp_size) {
                unsigned int defdisp = asize == 16 ? 16 : 32;
                bool do_sign = started || (o->segment & SEG_SIGNED);
                bool do_sext = do_sign || o->disp_size < asize;
                uint64_t offset;
                const char *rel = "";
                const char *sizename = "";
                const char *sign = "";

                if (o->eaflags & EAF_REL)
                    rel = "rel ";
                else if (!started && bits == 64)
                    rel = "abs ";

                if (o->disp_size != defdisp ||
                    (!started && asize != bits)) {
                    switch (o->disp_size) {
                    case 16:
                        sizename = "word ";
                        break;
                    case 32:
                        sizename = "dword ";
                        break;
                    case 64:
                        sizename = "qword ";
                        break;
                    default:
                        break;  /* This includes 8 bits */
                    }
                }

                if (do_sext)
                    offset = (int64_t)offs << nasize >> nasize;
                else
                    offset = (uint64_t)offs << nasize >> nasize;

                if (do_sign) {
                    if (started)
                        sign = "+";
                    if ((int64_t)offset < 0) {
                        sign = "-";
                        offset = -offset;
                    }
                }

                slen += snprintf(output + slen, outbufsize - slen,
                                 "%s%s%s0x%"PRIx64"",
                                 rel, sizename, sign, offset);
            }

            if (o->indexreg && itemp_has(best_itemp, IF_MIB)) {
                output[slen++] = ',';
                slen += snprintf(output + slen, outbufsize - slen, "%s",
                                 regname(o->indexreg));
                if (o->scale > 1)
                    slen +=
                        snprintf(output + slen, outbufsize - slen, "*%d",
                                o->scale);
                started = true;
            }

            output[slen++] = ']';

            if (deco)
                slen += append_evex_mem_deco(output + slen, outbufsize - slen,
                                             t, deco, &prefix);
        } else {
            slen +=
                snprintf(output + slen, outbufsize - slen, "<operand%d>",
                        i);
        }
    }
    output[slen] = '\0';
    return length;
}

/*
 * This is called when we don't have a complete instruction.  If it
 * is a standalone *single-byte* prefix show it as such, otherwise
 * print it as a literal.
 */
int32_t eatbyte(uint8_t byte, char *output, int outbufsize, int bits)
{
    const char *str = NULL;

    switch (byte) {
    case 0xF2:
        str = "repne";
        break;
    case 0xF3:
        str = "rep";
        break;
    case 0x9B:
        str = "wait";
        break;
    case 0xF0:
        str = "lock";
        break;
    case 0x2E:
        str = "cs";
        break;
    case 0x36:
        str = "ss";
        break;
    case 0x3E:
        str = "ds";
        break;
    case 0x26:
        str = "es";
        break;
    case 0x64:
        str = "fs";
        break;
    case 0x65:
        str = "gs";
        break;
    case 0x66:
        str = (bits == 16) ? "o32" : "o16";
        break;
    case 0x67:
        str = (bits == 32) ? "a16" : "a32";
        break;
    case REX_P + 0x0:
    case REX_P + 0x1:
    case REX_P + 0x2:
    case REX_P + 0x3:
    case REX_P + 0x4:
    case REX_P + 0x5:
    case REX_P + 0x6:
    case REX_P + 0x7:
    case REX_P + 0x8:
    case REX_P + 0x9:
    case REX_P + 0xA:
    case REX_P + 0xB:
    case REX_P + 0xC:
    case REX_P + 0xD:
    case REX_P + 0xE:
    case REX_P + 0xF:
        if (bits == 64) {
            snprintf(output, outbufsize, "rex%s%s%s%s%s",
                    (byte == REX_P) ? "" : ".",
                    (byte & REX_W) ? "w" : "",
                    (byte & REX_R) ? "r" : "",
                    (byte & REX_X) ? "x" : "",
                    (byte & REX_B) ? "b" : "");
            break;
        }
        /* else fall through */
    default:
        snprintf(output, outbufsize, "db 0x%02x", byte);
        break;
    }

    if (str)
        snprintf(output, outbufsize, "%s", str);

    return 1;
}
