x86_disasm(3)

omg bestie! welcome to x86_disasm(3) from libdisasm πŸ’– this page is literally about turning a bytestream into x86 assembly language instructions. it's giving decoder core energy, fr fr. πŸ¦‹


✨ NAME ✨

x86_disasm, x86_disasm_forward, and x86_disasm_range β€” that's the trio. they disassemble a bytestream to x86 assembly language instructions. periodt. πŸ’…


🌸 SYNOPSIS 🌸

okay so here's the whole synopsis, bestie. include, typedefs, and the three function signatures. no cap:

#include <libdis.h>

typedef void (*DISASM_CALLBACK)( x86_insn_t *, void * );
typedef long (*DISASM_RESOLVER)( x86_op_t *, x86_insn_t *, void * );

int x86_disasm( unsigned char *buf, unsigned int buf_len,
          unsigned long buf_rva, unsigned int offset,
          x86_insn_t * insn );
int x86_disasm_range( unsigned char *buf, unsigned long buf_rva,
                unsigned int offset, unsigned int len,
                DISASM_CALLBACK func, void *arg );
int x86_disasm_forward( unsigned char *buf, unsigned int buf_len,
               unsigned long buf_rva, unsigned int offset,
               DISASM_CALLBACK func, void *arg,
               DISASM_RESOLVER resolver, void *r_arg );

πŸ’– DESCRIPTION πŸ’–

okay so the DESCRIPTION is long, bestie, but we're gonna get through it together. first up: the constants. these are the limits, the boundaries, the vibes. no cap. πŸ’…

#define MAX_REGNAME 8
#define MAX_PREFIX_STR 32
#define MAX_MNEM_STR 16
#define MAX_INSN_SIZE 20 /* same as in i386.h */
#define MAX_OP_STRING 32 /* max possible operand size in string form */
#define MAX_OP_RAW_STRING 64    /* max possible operand size in raw form
*/
#define MAX_OP_XML_STRING 256   /* max possible operand size in xml form
*/
#define MAX_NUM_OPERANDS 8    /* max # implicit and explicit operands */
#define MAX_INSN_STRING 512        /* 2 * 8 * MAX_OP_STRING */
#define MAX_INSN_RAW_STRING 1024   /* 2 * 8 * MAX_OP_RAW_STRING */
#define MAX_INSN_XML_STRING 4096   /* 2 * 8 * MAX_OP_XML_STRING */

next up: enum x86_reg_type. it's giving register taxonomy, and the note says these may be ORed together, so you can stack 'em like accessories. πŸ’…

enum x86_reg_type {      /* NOTE: these may be ORed together */
     reg_gen     = 0x00001,   /* general purpose */
     reg_in      = 0x00002,   /* incoming args, ala RISC */
     reg_out     = 0x00004,   /* args to calls, ala RISC */
     reg_local   = 0x00008,   /* local vars, ala RISC */
     reg_fpu     = 0x00010,   /* FPU data register */
     reg_seg     = 0x00020,   /* segment register */
     reg_simd    = 0x00040,   /* SIMD/MMX reg */
     reg_sys     = 0x00080,   /* restricted/system register */
     reg_sp      = 0x00100,   /* stack pointer */
     reg_fp      = 0x00200,   /* frame pointer */
     reg_pc      = 0x00400,   /* program counter */
     reg_retaddr = 0x00800,   /* return addr for func */
     reg_cond    = 0x01000,   /* condition code / flags */
     reg_zero    = 0x02000,   /* zero register, ala RISC */
     reg_ret     = 0x04000,   /* return value */
     reg_src     = 0x10000,   /* array/rep source */
     reg_dest    = 0x20000,   /* array/rep destination */
     reg_count   = 0x40000    /* array/rep/loop counter */
};

and here's x86_reg_t, the struct that holds a register's whole identity:

typedef struct {
     char name[MAX_REGNAME];
     enum x86_reg_type type;            /* what register is used for */
     unsigned int size;            /* size of register in bytes */
     unsigned int id;                   /* register ID #, for quick
compares */
     unsigned int alias;           /* ID of reg this is an alias of */
     unsigned int shift;           /* amount to shift aliased reg by */
} x86_reg_t;

then x86_ea_t, the effective address struct. it's giving scale/index/base/disp energy:

typedef struct {
     unsigned int     scale;       /* scale factor */
     x86_reg_t        index, base; /* index, base registers */
     long             disp;        /* displacement */
     char             disp_sign;   /* is negative? 1/0 */
     char             disp_size;   /* 0, 1, 2, 4 */
} x86_ea_t;

enum x86_op_type β€” mutually exclusive, bestie, so pick one. it's giving operand categories:

enum x86_op_type {  /* mutually exclusive */
     op_unused = 0,      /* empty/unused operand */
     op_register = 1,    /* CPU register */
     op_immediate = 2,   /* Immediate Value */
     op_relative_near = 3,    /* Relative offset from IP */
     op_relative_far = 4,
     op_absolute = 5,    /* Absolute address (ptr16:32) */
     op_expression = 6,  /* Address expression (scale/index/base/disp)
*/
     op_offset = 7,      /* Offset from start of segment (m32) */
     op_unknown
};

enum x86_op_datatype β€” and the original says "these use Intel's lame terminology" 😭 so you know it's real. here's the size/type menu:

enum x86_op_datatype {        /* these use Intel's lame terminology */
     op_byte = 1,        /* 1 byte integer */
     op_word = 2,        /* 2 byte integer */
     op_dword = 3,       /* 4 byte integer */
     op_qword = 4,       /* 8 byte integer */
     op_dqword = 5,      /* 16 byte integer */
     op_sreal = 6,       /* 4 byte real (single real) */
     op_dreal = 7,       /* 8 byte real (double real) */
     op_extreal = 8,          /* 10 byte real (extended real) */
     op_bcd = 9,         /* 10 byte binary-coded decimal */
     op_simd = 10,       /* 16 byte packed (SIMD, MMX) */
        op_ssimd = 10,          /* 16 byte : 4 packed single FP (SIMD,
MMX) */
        op_dsimd = 11,          /* 16 byte : 2 packed double FP (SIMD,
MMX) */
        op_sssimd = 12,         /* 4 byte : scalar single FP (SIMD, MMX)
*/
        op_sdsimd = 13,         /* 8 byte : scalar double FP (SIMD, MMX)
*/
     op_descr32 = 14,    /* 6 byte Intel descriptor 2:4 */
     op_descr16 = 15,    /* 4 byte Intel descriptor 2:2 */
     op_pdescr32 = 16,   /* 6 byte Intel pseudo-descriptor 32:16 */
     op_pdescr16 = 17,   /* 6 byte Intel pseudo-descriptor 8:24:16 */
     op_fpuenv = 11      /* 28 byte FPU control/environment data */
};

enum x86_op_access β€” ORed together, so read/write/execute can stack:

enum x86_op_access {     /* ORed together */
     op_read = 1,
     op_write = 2,
     op_execute = 4
};

enum x86_op_flags β€” also ORed together, but heads up: the segs are mutually exclusive. so don't try to have two segment overrides at once, bestie. it's not the vibe. 🚫

enum x86_op_flags { /* ORed together, but segs are mutually exclusive */
     op_signed = 1,      /* signed integer */
     op_string = 2,      /* possible string or array */
     op_constant = 4,    /* symbolic constant */
     op_pointer = 8,          /* operand points to a memory address */
     op_sysref = 0x010,  /* operand is a syscall number */
     op_implied = 0x020, /* operand is implicit in insn */
     op_hardcode = 0x040, /* operans is hardcoded in insn */
     op_es_seg = 0x100,  /* ES segment override */
     op_cs_seg = 0x200,  /* CS segment override */
     op_ss_seg = 0x300,  /* SS segment override */
     op_ds_seg = 0x400,  /* DS segment override */
     op_fs_seg = 0x500,  /* FS segment override */
     op_gs_seg = 0x600   /* GS segment override */
};

and now x86_op_t, the big one. it's got type, datatype, access, flags, and a whole union of data. it's giving versatile, no cap:

typedef struct {
     enum x86_op_type    type;          /* operand type */
     enum x86_op_datatype     datatype; /* operand size */
     enum x86_op_access  access;        /* operand access [RWX] */
     enum x86_op_flags   flags;         /* misc flags */
     union {
          /* immediate values */
          char           sbyte;
          short          sword;
          long           sdword;
          qword          sqword;
          unsigned char  byte;
          unsigned short      word;
          unsigned long  dword;
          qword               qword;
          float             sreal;
          double            dreal;
          /* misc large/non-native types */
          unsigned char  extreal[10];
          unsigned char  bcd[10];
          qword          dqword[2];
          unsigned char  simd[16];
          unsigned char  fpuenv[28];
          /* absolute address */
          void           * address;
          /* offset from segment */
          unsigned long  offset;
          /* ID of CPU register */
          x86_reg_t reg;
          /* offsets from current insn */
          char           relative_near;
          long           relative_far;
          /* effective address [expression] */
          x86_ea_t  expression;
     } data;
     void * insn;
} x86_op_t;

then x86_oplist_t, a linked list of operands. it's giving next pointer energy:

typedef struct x86_operand_list {
     x86_op_t op;
     struct x86_operand_list *next;
} x86_oplist_t;

and finally for this part, enum x86_insn_group β€” instruction categories, bestie. note the numbering jumps from 10 to 13, and that's just how it is. no cap.

enum x86_insn_group {
     insn_none = 0,
     insn_controlflow = 1,
     insn_arithmetic = 2,
     insn_logic = 3,
     insn_stack = 4,
     insn_comparison = 5,
     insn_move = 6,
     insn_string = 7,
     insn_bit_manip = 8,
     insn_flag_manip = 9,
     insn_fpu = 10,
     insn_interrupt = 13,
     insn_system = 14,
     insn_other = 15
};

okay continuing, bestie β€” the next bit is where libdisasm opens its whole wardrobe and shows you every type it owns. πŸŽ€ this is the ✨type buffet✨, so read it like a menu and not like a novel. every one of these shows up in the structs you'll be poking at, i promise. πŸ¦‹

first up, enum x86_insn_type β€” the big one, the instruction-type mood board. every instruction gets sorted into exactly one of these, and the little comments tell you which family each value lives in. 🌈

enum x86_insn_type {
     insn_invalid = 0,
     /* insn_controlflow */
     insn_jmp = 0x1001,
     insn_jcc = 0x1002,
     insn_call = 0x1003,
     insn_callcc = 0x1004,
     insn_return = 0x1005,
     insn_loop = 0x1006,
     /* insn_arithmetic */
     insn_add = 0x2001,
     insn_sub = 0x2002,
     insn_mul = 0x2003,
     insn_div = 0x2004,
     insn_inc = 0x2005,
     insn_dec = 0x2006,
     insn_shl = 0x2007,
     insn_shr = 0x2008,
     insn_rol = 0x2009,
     insn_ror = 0x200A,
     /* insn_logic */
     insn_and = 0x3001,
     insn_or = 0x3002,
     insn_xor = 0x3003,
     insn_not = 0x3004,
     insn_neg = 0x3005,
     /* insn_stack */
     insn_push = 0x4001,
     insn_pop = 0x4002,
     insn_pushregs = 0x4003,
     insn_popregs = 0x4004,
     insn_pushflags = 0x4005,
     insn_popflags = 0x4006,
     insn_enter = 0x4007,
     insn_leave = 0x4008,
     /* insn_comparison */
     insn_test = 0x5001,
     insn_cmp = 0x5002,
     /* insn_move */
     insn_mov = 0x6001,  /* move */
     insn_movcc = 0x6002,     /* conditional move */
     insn_xchg = 0x6003, /* exchange */
     insn_xchgcc = 0x6004,    /* conditional exchange */
     /* insn_string */
     insn_strcmp = 0x7001,
     insn_strload = 0x7002,
     insn_strmov = 0x7003,
     insn_strstore = 0x7004,
     insn_translate = 0x7005, /* xlat */
     /* insn_bit_manip */
     insn_bittest = 0x8001,
     insn_bitset = 0x8002,
     insn_bitclear = 0x8003,
     /* insn_flag_manip */
     insn_clear_carry = 0x9001,
     insn_clear_zero = 0x9002,
     insn_clear_oflow = 0x9003,
     insn_clear_dir = 0x9004,
     insn_clear_sign = 0x9005,
     insn_clear_parity = 0x9006,
     insn_set_carry = 0x9007,
     insn_set_zero = 0x9008,
     insn_set_oflow = 0x9009,
     insn_set_dir = 0x900A,
     insn_set_sign = 0x900B,
     insn_set_parity = 0x900C,
     insn_tog_carry = 0x9010,
     insn_tog_zero = 0x9020,
     insn_tog_oflow = 0x9030,
     insn_tog_dir = 0x9040,
     insn_tog_sign = 0x9050,
     insn_tog_parity = 0x9060,
     /* insn_fpu */
     insn_fmov = 0xA001,
     insn_fmovcc = 0xA002,
     insn_fneg = 0xA003,
     insn_fabs = 0xA004,
     insn_fadd = 0xA005,
     insn_fsub = 0xA006,
     insn_fmul = 0xA007,
     insn_fdiv = 0xA008,
     insn_fsqrt = 0xA009,
     insn_fcmp = 0xA00A,
     insn_fcos = 0xA00C,
     insn_fldpi = 0xA00D,
     insn_fldz = 0xA00E,
     insn_ftan = 0xA00F,
     insn_fsine = 0xA010,
     insn_fsys = 0xA020,
     /* insn_interrupt */
     insn_int = 0xD001,
     insn_intcc = 0xD002,     /* not present in x86 ISA */
     insn_iret = 0xD003,
     insn_bound = 0xD004,
     insn_debug = 0xD005,
     insn_trace = 0xD006,
     insn_invalid_op = 0xD007,
     insn_oflow = 0xD008,
     /* insn_system */
     insn_halt = 0xE001,
     insn_in = 0xE002,   /* input from port/bus */
     insn_out = 0xE003,  /* output to port/bus */
     insn_cpuid = 0xE004,
     /* insn_other */
     insn_nop = 0xF001,
     insn_bcdconv = 0xF002,   /* convert to or from BCD */
     insn_szconv = 0xF003     /* change size of operand */
};

two things to clock before you move on, bestie: insn_intcc is flagged right there as not present in x86 ISA (the source says so, not me πŸ™ˆ), and insn_translate is the one that's xlat. the insn_bcdconv one converts to or from BCD, and insn_szconv changes the size of the operand. insn_in is input from port/bus, insn_out is output to port/bus. periodt. πŸ’…


next, enum x86_insn_note β€” little sticky notes about where an instruction is even allowed to run. so niche, so important. πŸ‘‘

enum x86_insn_note {
     insn_note_ring0          = 1, /* Only available in ring 0 */
     insn_note_smm       = 2, /* "" in System Management Mode */
     insn_note_serial    = 4  /* Serializing instruction */
};

so insn_note_ring0 = only available in ring 0, insn_note_smm = same energy but in System Management Mode (the "" in the comment means "only available", fyi), and insn_note_serial = serializing instruction. no cap. ✨


then enum x86_flag_status β€” the flag gossip. which flags got set, which got cleared, which got tested. it's giving status report energy. 🚩

enum x86_flag_status {
     insn_carry_set = 0x1,
     insn_zero_set = 0x2,
     insn_oflow_set = 0x4,
     insn_dir_set = 0x8,
     insn_sign_set = 0x10,
     insn_parity_set = 0x20,
     insn_carry_or_zero_set = 0x40,
     insn_zero_set_or_sign_ne_oflow = 0x80,
     insn_carry_clear = 0x100,
     insn_zero_clear = 0x200,
     insn_oflow_clear = 0x400,
     insn_dir_clear = 0x800,
     insn_sign_clear = 0x1000,
     insn_parity_clear = 0x2000,
     insn_sign_eq_oflow = 0x4000,
     insn_sign_ne_oflow = 0x8000
};

enum x86_insn_cpu β€” the "which CPU even is this" list. Intel squad and AMD squad, all in one enum, all vibing. πŸ’œ

enum x86_insn_cpu {
     cpu_8086  = 1, /* Intel */
     cpu_80286 = 2,
     cpu_80386 = 3,
     cpu_80387 = 4,
     cpu_80486 = 5,
     cpu_pentium    = 6,
     cpu_pentiumpro = 7,
     cpu_pentium2   = 8,
     cpu_pentium3   = 9, >br>      cpu_pentium4   = 10,
     cpu_k6         = 16,     /* AMD */
     cpu_k7         = 32,
     cpu_athlon     = 48
};

Intel starts at 1, AMD picks up at 16, and yes there's a little stray >br> artifact sitting in the middle of that list β€” that's literally what the page says, i'm not touching it, don't @ me. 😭


enum x86_insn_isa β€” which instruction set architecture family the thing belongs to. the ✨aesthetic✨ of the instruction, if you will. 🌸

enum x86_insn_isa {
     isa_gp         = 1, /* general purpose */
     isa_fp         = 2, /* floating point */
     isa_fpumgt     = 3, /* FPU/SIMD management */
     isa_mmx        = 4, /* Intel MMX */
     isa_sse1  = 5, /* Intel SSE SIMD */
     isa_sse2  = 6, /* Intel SSE2 SIMD */
     isa_sse3  = 7, /* Intel SSE3 SIMD */
     isa_3dnow = 8, /* AMD 3DNow! SIMD */
     isa_sys        = 9  /* system instructions */
};

enum x86_insn_prefix β€” prefixes, the little accessories an instruction puts on before it leaves the house. πŸŽ€

enum x86_insn_prefix {
     insn_no_prefix = 0,
     insn_rep_zero = 1,
     insn_rep_notzero = 2,
     insn_lock = 4,
     insn_delay = 8
};

okay now the main character: x86_insn_t. this is the struct that holds one whole disassembled instruction, plus some convenience fields so you don't have to do the detective work yourself. πŸ‘‘

typedef struct {
     /* information about the instruction */
     unsigned long addr;      /* load address */
     unsigned long offset;         /* offset into file/buffer */
     enum x86_insn_group group;    /* meta-type, e.g. INSN_EXEC */
     enum x86_insn_type type; /* type, e.g. INSN_BRANCH */
     unsigned char bytes[MAX_INSN_SIZE];
     unsigned char size;      /* size of insn in bytes */
     enum x86_insn_prefix prefix;
     enum x86_flag_status flags_set; /* flags set or tested by insn */
     enum x86_flag_status flags_tested;
     /* the instruction proper */
     char prefix_string[32];       /* prefixes [might be truncated] */
     char mnemonic[8];
     x86_op_t operands[3];
     /* convenience fields for user */
     void *block;             /* code block containing this insn */
     void *function;               /* function containing this insn */
     void *tag;               /* tag the insn as seen/processed */
} x86_insn_t;
#define X86_WILDCARD_BYTE 0xF4

walk with me: addr is the load address, offset is the offset into the file/buffer, group is the meta-type (e.g. INSN_EXEC), type is the type (e.g. INSN_BRANCH), bytes is MAX_INSN_SIZE long, size is the size of the insn in bytes, then prefix, flags_set (flags set or tested by the insn), and flags_tested. 🫢

then the instruction proper: prefix_string[32] β€” and heads up, the comment literally says it might be truncated 😭 β€” plus mnemonic[8] and operands[3]. and for your own convenience: block (code block containing this insn), function (function containing this insn), and tag (tag the insn as seen/processed).

oh and don't forget, right underneath: #define X86_WILDCARD_BYTE 0xF4. iconic, no notes. πŸ’…


x86_invariant_op_t β€” the "same instruction, but described in a size-independent way" operand. very demure, very abstract. 🧚

typedef struct {
        enum x86_op_type        type;           /* operand type */
        enum x86_op_datatype    datatype;       /* operand size */
        enum x86_op_access      access;         /* operand access [RWX]
*/
        enum x86_op_flags       flags;          /* misc flags */
} x86_invariant_op_t;

and finally x86_invariant_t β€” the invariant representation of a whole instruction. the comment even tells you to use the same ordering as x86_insn_t. ⭐

typedef struct {
     unsigned char bytes[64]; /* invariant representation */
     unsigned int  size;      /* number of bytes in insn */
        enum x86_insn_group group;      /* meta-type, e.g. INSN_EXEC */
        enum x86_insn_type type;        /* type, e.g. INSN_BRANCH */
     x86_invariant_op_t operands[3];    /* use same ordering as
x86_insn_t */
} x86_invariant_t;

that's the whole type closet, bestie. you're so ready. 🌈


πŸ’– EXAMPLES πŸ’–

the following sample callback outputs instructions in raw syntax:
void raw_print( x86_insn_t *insn, void *arg ) {
        char line[1024];
        x86_format_insn(insn, line, 1024, raw_syntax);
        printf( "%s0, line);
}

the following sample resolver performs very limited checking on the operand of a jump or call to determine what program address the operand refers to:
long resolver( x86_op_t *op, x86_insn_t *insn ) {
        long retval = -1;
        /* this is a flat ripoff of internal_resolver in libdis.c --
           we don't do any register or stack resolving, or check
           to see if we have already encountered this RVA */
        if ( op->type == op_absolute || op->type == op_offset ) {
                retval = op->data.sdword;
        } else if (op->type == op_relative ){
                if ( op->datatype == op_byte ) {
                        retval = insn->addr + insn->size +
        op->data.sbyte;
                } else if ( op->datatype == op_word ) {
                        retval = insn->addr + insn->size +
        op->data.sword;
                } else if ( op->datatype == op_dword ) {
                        retval = insn->addr + insn->size +
        op->data.sdword;
                }
        }
        return( retval );
}

ngl that comment inside is literally in the source: it's a flat ripoff of internal_resolver in libdis.c, it does no register or stack resolving, and it does not check whether it's already encountered that RVA. so know that going in. πŸ™ˆ


the following code snippets demonstrate how to use the various disassembly routines:
        unsigned char *buf;  /* buffer of bytes to disassemble */
        unsigned int buf_len;/* length of buffer */
        unsigned long rva;   /* load address of start of buffer */
        unsigned int pos;    /* position in buffer */
        x86_insn_t insn;     /* disassembled instruction */

        /* disassemble entire buffer, printing automatically */
        x86_disasm_range( buf, buf_rva, pos, buf_len,
                          raw_print, NULL );

        /* disassemble a single instruction, then print it */
        if (x86_disasm( buf, buf_len, buf_rva, pos, &insn ) ) {
                raw_print( &insn, NULL );
        }

        /* disassemble forward in 'buf' starting at 'pos' */
        x86_disasm_forward( buf, buf_len, buf_rva, pos,
                            raw_print, NULL, resolver );

three moves, bestie: whole buffer at once with x86_disasm_range, one single instruction with x86_disasm (and only print it if it returns true, ok?), and then walking forward from pos with x86_disasm_forward while handing it your resolver. slay. πŸ”₯


πŸ¦‹ SEE ALSO πŸ¦‹

go read those too, they're the rest of the friend group. πŸ’•


and that's the whole page, bestie!! 🫢 this one came from mammon_, version 0.21, and it's the x86_disasm(3) page. thank you for coming to my ted talk, now go disassemble something iconic. πŸ‘‘βœ¨πŸ’