libdisasm(7)
okay so bestie, you just opened libdisasm(7) and honestly? taste π
this is the library that takes compiled Intel x86 object code and disassembles it, and we are reading the whole thing together. no skipping, no skimming, we eat the entire meal π
πΈ NAME πΈ
libdisasm β a library for disassembling Intel x86 instructions π¦
β¨ OVERVIEW β¨
libdisasm is a library for disassembling compiled Intel x86 object code. that's it, that's the whole job, and it ate π₯
π FUNCTIONS π
here's the whole roster, and every signature is below exactly as it lives in the source. no edits, no notes, we respect the original π
π οΈ the setup squad
int x86_init( enum x86_options options, DISASM_REPORTER reporter);
void x86_set_options( enum x86_options options );
int x86_get_options( void );
int x86_cleanup(void);
void x86_report_error( enum x86_report_codes code, void *arg );
π΅οΈ the actual disassembling (the main characters)
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 );
π«Ά grabbing operands
x86_op_t * x86_get_operand( x86_insn_t *insn, enum x86_operand_id id );
x86_op_t * x86_get_dest_operand( x86_insn_t *insn );
x86_op_t * x86_get_src_operand( x86_insn_t *insn );
x86_op_t * x86_get_imm_operand( x86_insn_t *insn );
int x86_operand_size( x86_op_t *op );
π the raw immediate
unsigned char * x86_get_raw_imm( x86_insn_t *insn );
π·οΈ setting + tagging instructions
void x86_set_insn_addr( x86_insn_t *insn, unsigned long addr );
void x86_set_insn_offset( x86_insn_t *insn, unsigned int offset );
void x86_set_insn_function( x86_insn_t *insn, void * func );
void x86_set_insn_block( x86_insn_t *insn, void * block );
void x86_tag_insn( x86_insn_t *insn );
void x86_untag_insn( x86_insn_t *insn );
int x86_insn_is_tagged( x86_insn_t *insn );
π formatting (making it pretty)
int x86_format_operand(x86_op_t *op, x86_insn_t *insn, char *buf, int len,
enum x86_asm_format);
int x86_format_mnemonic(x86_insn_t *insn, char *buf, int len,
enum x86_asm_format);
int x86_format_insn(x86_insn_t *insn, char *buf, int len, enum x86_asm_format);
int x86_format_header( char *buf, int len, enum x86_asm_format format);
π random little info getters
int x86_endian(void);
int x86_addr_size(void);
int x86_op_size(void);
int x86_word_size(void);
int x86_max_inst_size(void);
int x86_sp_reg(void);
int x86_fp_reg(void);
int x86_ip_reg();
π§ the invariant check
int x86_invariant_insn( unsigned char *buf, int buf_len, x86_invariant_t *inv );
π DATA TYPES π
okay this is the part where it gets juicy, because we're getting the actual structs and enums and every single value. buckle up π’
π§΅ the defines
#define MAX_REGNAME 8
#define MAX_INSN_SIZE 20 /* same as in i386.h */
#define MAX_OP_STRING 32 /* max possible operand size in string form */
𧬠enum x86_reg_type
NOTE: these may be ORed together
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 */
};
πͺͺ x86_reg_t
typedef struct {
char name[MAX_REGNAME];
int type; /* what register is used for */
int size; /* size of register in bytes */
int id; /* register ID #, for quick compares */
} x86_reg_t;
π x86_ea_t
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. pick one π
enum x86_op_type { /* mutually exclusive */
op_unused = 0, /* empty/unused operand */
op_register = 1, /* CPU register */
op_immediate = 2, /* Immediate Value */
op_relative = 3, /* Relative offset from IP */
op_absolute = 4, /* Absolute address (ptr16:32) */
op_expression = 5, /* Address expression (scale/index/base/disp) */
op_offset = 6, /* Offset from start of segment (m32) */
op_unknown
};
π¦ enum x86_op_datatype
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_fpuenv = 11 /* 28 byte FPU control/environment data */
};
πͺ enum x86_op_access
ORed together, bestie π«Ά
enum x86_op_access { /* ORed together */
op_read = 1,
op_write = 2,
op_execute = 4
};
π© enum x86_op_flags
ORed together, BUT the segs are mutually exclusive, okay? don't be messy π
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_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 */
};
π x86_op_t
the big one. the icon. the way this struct has a whole union inside it? obsessed β¨
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;
unsigned char byte;
unsigned short word;
unsigned long dword;
qword sqword;
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 near_offset;
long far_offset;
/* effective address [expression] */
x86_ea_t effective_addr;
} data;
} x86_op_t;
ποΈ enum x86_insn_group
enum x86_insn_group {
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
};
and we're not done bestie, hold on, there's more coming π
...and now the actual lineup, bestie. Every instruction type this library can tag, in one giant enum, with the family baked right into the hex value. Look at those comment headers β each block is its own little neighborhood. ποΈ
enum x86_insn_type {
/* 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 */
};
Notice how the families live in their own hex blocks β control flow up at 0x1001, arithmetic at 0x2001, all the way down to the "other" girlies at 0xF001. And yes, insn_intcc = 0xD002 is literally commented as not present in x86 ISA β it's just vibing there in the enum, existing for the plot. π«Ά Also peep the gaps: insn_fcmp = 0xA00A then it skips to insn_fcos = 0xA00C, and the toggle flags jump 0x9010, 0x9020, 0x9030β¦ the numbering is not a tidy little ladder, bestie, don't come for it.
Next up: flag status. This is the "which flags got touched" 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
};
Then the prefixes β the little "excuse me, before you read this instructionβ¦" tags. β¨
enum x86_insn_prefix {
insn_no_prefix = 0,
insn_rep_zero = 1,
insn_rep_notzero = 2,
insn_lock = 4,
insn_delay = 8
};
And the operand slots, numbered like a lil seating chart: dest, src, imm. π
enum x86_operand_id { op_dest=0, op_src=1, op_imm=2 };
Now THE struct. The main character. π This is the one you'll actually be holding in your hands:
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
So quick tour, no cap: addr is the load address, offset is where you are in the file/buffer, group is the meta-type (think INSN_EXEC) and type is the specific one (think INSN_BRANCH). You get the raw bytes (sized MAX_INSN_SIZE), the size in bytes, the prefix, flags_set and flags_tested, a prefix_string[32] that might be truncated, an 8-char mnemonic, and 3 x86_op_t operands. Then the friendly trio at the end β block, function, and tag β are all yours to point at whatever you want. And X86_WILDCARD_BYTE is 0xF4, which we discussed earlier. π
Then the invariant versions, for when you want the shape of the instruction without the specific register drama:
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;
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;
Note the ordering note on operands[3]: use same ordering as x86_insn_t. Same seating chart, different guest list. πͺ
And the three function-pointer types you'll be passing around like friendship bracelets: π«Ά
typedef long (*DISASM_RESOLVER)( x86_op_t *op, x86_insn_t * current_insn
);
typedef void (*DISASM_CALLBACK)( x86_insn_t *insn, void * arg );
typedef void (*DISASM_REPORTER)( enum x86_report_codes code, void *arg
);
Options, options, options β the flags you hand to x86_init:
enum x86_options {
opt_none= 0,
opt_ignore_nulls=1, /* ignore sequences of > 4 NULL bytes */
opt_16_bit=2, /* 16-bit/DOS disassembly */
opt_unknown
};
So opt_none is zero, opt_ignore_nulls (value 1) tells it to ignore sequences of > 4 NULL bytes, opt_16_bit (value 2) is for 16-bit/DOS disassembly, and opt_unknown is the "we don't know her" fallback. π
And finally the report codes β the things that get handed to your DISASM_REPORTER when the disassembler has news for you: π’
enum x86_report_codes {
report_disasm_bounds,
report_insn_bounds,
report_invalid_insn,
report_unknown
};
β¨ EXAMPLES β¨
Wanna see it in motion? Go peep quikdis.c, testdis.c, and x86dis.c in the source code distribution. They're right there, already written, doing the thing. Study them like they're your fave creator's tutorial. π₯
π NOTES π
The x86-64 architecture is not supported.
Fr fr, that's the whole note. 64-bit besties, this is not your era. And yes, the page literally trails off after that sentence with a stray .LP formatting macro β we don't talk about it, it's just a little typo gremlin. π
π AUTHOR π
mammon_ <mammon_@users.sourceforge.net>
π CONTRIBUTORS π
xavier <xvr@users.sourceforge.net> mok <moren_k@users.sourceforge.net>
π SEE ALSO π
And that's the whole page, bestie β you made it to the end and I'm so proud of you. π₯Ή This was mammon_'s work, version 0.21, and the footer tags it libdisasm(7). Go disassemble something iconic. Periodt. π
β¨
π¦ where this page comes from
- debian bookworm: libdisasm-dev 0.23-6+b1
- debian trixie: libdisasm-dev 0.23-7 Β· Artistic-1.0-clarified
- ubuntu jammy: libdisasm-dev 0.23-6build1
- ubuntu noble: libdisasm-dev 0.23-6.1
- ubuntu questing: libdisasm-dev 0.23-7
- ubuntu resolute: libdisasm-dev 0.23-7build1 Β· Artistic-1.0-clarified
- ubuntu stonking: libdisasm-dev 0.23-7build1 Β· Artistic-1.0-clarified
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms