================================================================================ B A S T A R D disassembly environment LibDISASM: x86 disassembler library ================================================================================ Contents 1. Introduction 2. File Listing 3. Compilation 4. Usage 5. Implementation Notes 6. Bugs 7. TODO 8. Changelog ================================================================================ Introduction Libdisasm is a disassembler for Intel x86-compatible object code. It compiles as a shared and static library on Linux, FreeBSD, and Win32 platforms. The core disassembly engine is contained in files with the prefix "i386", and is shared with the x86 ARCH extension of the bastard disassembler. ================================================================================ File Listing bastard.h : Dummy header file to replace libbastard.so bin_from_dump.pl: A perl script that creates a flat binary from an objdump .lst extension.h : Dummy header file to replace libbastard.so i386.c : The core library code i386.h : Internal header file for the above i386.opcode.map : as it says; included in i386.h i386_opcode.h : Internal header for i386.c libdis.c : Wrappers for the bastard extension routines in i386.c libdis.h : The header file to use when linking to the .so op-conv.pl : Perl script for messing with opcode.map structure quikdis.c : a quick & dirty tester for the library quikdis_old.c : implementation using the legacy API testdis.c : a simple tester for files from bin_from_dump.pl vm.h : Dummy header file to replace libbastard.so ================================================================================ Compilation First, change to the source directory which, due to the bastard src structure, is unnecessarily deep: cd libdisasm_src-0.17/src/arch/i386/libdisasm To compile the .so and the test disassembler: make To compile the .so: make libdis To compile the test disassembler: make quikdis ...or... gcc -O3 -I. -L. -ldisasm quikdis.c -o quikdis To link to libdisasm: #include "libdis.h" gcc -ldisasm .... ================================================================================ Usage The basic usage of the library is as follows: 1. sys_initialize disassembler 2. Disassemble stuff 3. Un-init the disassembler This translates into C code like the following: char buf[BUF_SIZE]; /* buffer of bytes to disassemble */ int pos = 0; /* current position in buffer */ int size; /* size of instruction */ x86_insn_t insn; /* representation of the code instruction */ x86_init(opt_none, NULL); while ( pos < BUF_SIZE ) { size = x86_disasm( buf, buf_len, buf_rva, pos, &insn ); if (size) { /* ... do something with i */ pos += size; } else { /* invalid/unrecognized instruction */ pos++; } } x86_cleanup(); The first argument to x86_init() represents disassembler options; these are defined as enum x86_options { /* These may be ORed together */ opt_none, opt_ignore_nulls, /* ignore sequences of > 4 NULL bytes */ opt_16_bit, /* 16-bit/DOS disassembly */ opt_unknown }; though passing '0' will suffice. The second argument is the address of a function with the prototype void reporter_fn( enum x86_report_codes code, void *arg ); ...which serves as a callback that handles errors encountered during disassembly. This argument can be NULL. The x86_disasm() routine fills a structure with a disassembly of the instruction: int x86_disasm( unsigned char *buf, unsigned int buf_len, unsigned long buf_rva, unsigned int offset, x86_insn_t * insn ); The first argument to x86_disasm() is a pointer to the buffer of bytes being disassembled; this is usually a memory-mapped code section of the target. The second parameter is the length of this buffer [e.g. the length of the section], and the third parameter is the Virtual Address that the buffer will have at runtime [this can be 0 ... it is meant to be the load address of the section]. The fourth parameter specifies the offset into the buffer where disassembly is to begin; this can be 0 for the start of the buffer, or can be set to the offset of a program or section entry point located within the buffer. The final parameter is a pointer to a structure representing the instruction, which will be zeroed by x86_disasm() and filled with information about the disassembled instruction. The structure that is filled by x86_disasm() has the following definition: typedef struct { unsigned long addr; /* load address */ unsigned long offset; /* offset into file/buffer */ enum x86_insn_group group; /* meta-type */ enum x86_insn_type type; /* type */ unsigned char bytes[MAX_INSN_SIZE]; /* binary encoding of insn */ unsigned char size; /* size of insn in bytes */ enum x86_insn_prefix prefix; enum x86_flag_status flags_set; /* eflags toggled by insn */ enum x86_flag_status flags_tested; /* eflags tested by insn */ char prefix_string[32]; /* prefixes */ char mnemonic[8]; /* opcode */ x86_op_t operands[3]; void *block; /* code block containing insn */ void *function; /* function containing insn */ void *tag; /* tag the insn as processed */ } x86_insn_t; The 'addr' and 'offset' fields are based on the rva and offset provided to x86_disasm(). The 'group' and 'type' fields are enumerations defined in libdis.h; they serve to identify types of instructions: enum x86_insn_group { insn_controlflow, insn_arithmetic, insn_logic, insn_stack, insn_comparison, insn_move, insn_string, insn_bit_manip, insn_flag_manip, insn_fpu, insn_interrupt, insn_system, insn_other }; enum x86_insn_type { /* insn_controlflow group */ insn_jmp, /* jmp */ insn_jcc, /* jz, jnz */ insn_call, /* call */ insn_return, /* ret */ insn_loop, /* loop */ /* insn_arithmetic group */ insn_add, /* add, adc */ insn_sub, /* sub, sbb */ insn_mul, /* mul, imul */ insn_div, /* div, idiv */ insn_inc, /* inc */ insn_dec, /* dec */ insn_shl, /* shl */ insn_shr, /* shr */ insn_rol, /* rol */ insn_ror, /* ror */ /* insn_logic group */ insn_and, /* and */ insn_or, /* or */ insn_xor, /* xor */ insn_not, /* not */ insn_neg, /* neg */ /* insn_stack group */ insn_push, /* push */ insn_pop, /* pop */ insn_pushregs, /* pushad */ insn_popregs, /* popad */ insn_pushflags, /* pushf */ insn_popflags, /* popf */ insn_enter, /* enter */ insn_leave, /* leave */ /* insn_comparison group */ insn_test, /* test */ insn_cmp, /* cmp */ /* insn_move group */ insn_mov, /* mov */ insn_movcc, /* cmovz, cmovnz */ insn_xchg, /* xchg */ insn_xchgcc, /* cmpxchg */ /* insn_string group */ insn_strcmp, /* cmpsb, scasb */ insn_strload, /* lodsb */ insn_strmov, /* movsb */ insn_strstore, /* stosb */ insn_translate, /* xlat */ /* insn_bit_manip group */ insn_bittest, /* bt, btc */ insn_bitset, /* bts */ insn_bitclear, /* btr */ /* insn_flag_manip group */ insn_clear_carry, /* clc */ insn_clear_dir, /* cld */ insn_set_carry, /* stc */ insn_set_dir, /* std */ insn_tog_carry, /* cmc */ /* insn_fpu group */ insn_fmov, /* fmov */ insn_fmovcc, /* fcmovz */ insn_fabs, /* fabs */ insn_fadd, /* fadd */ insn_fsub, /* fsub */ insn_fmul, /* fmul */ insn_fdiv, /* fdiv */ insn_fsqrt, /* fsqrt */ insn_fcmp, /* ficom, ftst */ insn_fcos, /* fcos */ insn_fldpi, /* fldpi */ insn_fldz, /* fldz */ insn_ftan, /* ftan */ insn_fsine, /* fsin */ insn_fsys, /* fsave */ /* insn_interrupt group */ insn_int, /* int */ insn_iret, /* iret */ insn_bound, /* bound */ insn_debug, /* int3 */ insn_oflow, /* into */ /* insn_system group */ insn_halt, /* halt */ insn_in, /* in, insb */ insn_out, /* out, outsb */ insn_cpuid, /* cpuid */ /* insn_other group */ insn_nop, /* nop */ insn_bcdconv, /* aaa, aad */ insn_szconv /* cbw, cwde */ }; The 'bytes' field of the x86_insn_t type contains the binary representation of the instruction, suitable for a hexdump; 'size' contains the size of the instruction in bytes. Instruction prefixes are stored as 'x86_insn_prefix' enumeration types ORed together in the 'prefix' field, and as a string in the 'prefix_string' field. The prefix enumerations are enum x86_insn_prefix { insn_no_prefix = 0, insn_rep_zero = 1, insn_rep_notzero = 2, insn_lock = 4, insn_delay = 8 }; The 'flags_set' and 'flags_tested' fields specify which bits in the eflags register are modified or examined by the instruction; this allows the application to determine which specific code address is responsible for the value of a flag when a test or a conditional jump is encountered. The flag status definitions are an enumeration which specifies if the flag is being set to or tested for 0 or 1: enum x86_flag_status { insn_carry_set, insn_zero_set, insn_oflow_set, insn_dir_set, insn_sign_set, insn_parity_set, insn_carry_or_zero_set, insn_zero_set_or_sign_ne_oflow, insn_carry_clear, insn_zero_clear, insn_oflow_clear, insn_dir_clear, insn_sign_clear, insn_parity_clear, insn_sign_eq_oflow, insn_sign_ne_oflow }; The 'block', 'function', and 'tag' fields of x86_insn_t are provided for application use -- the first two can be used to associate an instruction with a program block or function, and the third can be used to mark whether an instruction has been processed, for example in a tree traversal. The instruction proper is contained in the 'mnemonic' and 'operands' fields; the first is the string representation of the opcode, and the second is an array of three x86_op_t structures. The order of the operands within this array is determined by the 'x86_operand_id' enum: enum x86_operand_id { op_dest=0, op_src=1, op_imm=2 }; The operands have the following structure: 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; /* signed byte */ short sword; /* ... word */ long sdword; /* ... dword */ unsigned char byte; /* unsigned byte */ unsigned short word; /* ... word */ unsigned long dword; /* ... dword */ qword sqword; /* ... qword */ /* misc large/non-native types */ unsigned char extreal[10]; unsigned char bcd[10]; qword dqword[2]; unsigned char simd[16]; unsigned char fpuenv[28]; /* addresses */ void * address; /* absolute address */ unsigned long offset; /* offset from segment start */ char near_offset; /* offset from current insn */ long far_offset; /* "" */ x86_ea_t effective_addr; /* displacement/expression */ /* registers */ x86_reg_t reg; /* register description */ } data; } x86_op_t; The 'type' field is used to determine which field of the 'data' union is to be used; it consists of one of the following enumerations: enum x86_op_type { op_unused = 0, /* empty/unused operand */ op_register = 1, /* CPU register */ op_immediate = 2, /* immediate value */ op_relative = 3, /* offset from CS:IP */ op_absolute = 4, /* absolute address (ptr16:32) */ op_expression = 5, /* effective address */ op_offset = 6, /* offset from segment (m32) */ op_unknown }; Note that the size and signedness of the operand must be determined using the 'datatype' and 'flags' fields. These field have the following enumerations: enum x86_op_datatype { 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_double = 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 environment data */ }; enum x86_op_flags { /* These may be ORed together */ 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 */ }; The 'access' field is provided to facilitate cross-reference tracking; each operand is marked with whether the instruction reads, writes, or executes the contents of the operand. These access methods are encoded with the following enumeration: enum x86_op_access { /* These may be ORed together */ op_read = 1, op_write = 2, op_execute = 4 }; The 'reg' field of the x86_op_t 'data' union contains a description of a CPU register with the following structure: typedef struct { char name[MAX_REGNAME]; int type; /* what register is used for */ int size; /* size of register in bytes */ int id; /* ID # of register */ } x86_reg_t; The 'name' field contains the human-readable name of the register, such as "eax"; the 'type' field provides information regarding the typical use of the register. The register types are, once again, provided in an enumeration: enum x86_reg_type { /* These may be ORed together */ reg_gen, /* general purpose */ reg_in, /* incoming args, ala RISC */ reg_out, /* args to calls, ala RISC */ reg_local, /* local vars, ala RISC */ reg_fpu, /* FPU data register */ reg_seg, /* segment register */ reg_simd, /* SIMD/MMX reg */ reg_sys, /* restricted/system register */ reg_sp, /* stack pointer */ reg_fp, /* frame pointer */ reg_pc, /* program counter */ reg_retaddr, /* return addr for func */ reg_cond, /* condition code / flags */ reg_zero, /* zero register, ala RISC */ reg_ret, /* return value */ reg_src, /* array/rep source */ reg_dest, /* array/rep destination */ reg_count /* array/rep/loop counter */ }; The 'effective address' field of the x86_op_t 'data' union represents an address expression, such as that encoded in the ModR/M and SIB bytes of an instruction. Each effective address is of the form displacement + (base + (scale * index)) and this is represented with the following structure: typedef struct { unsigned int scale; /* scale factor */ x86_reg_t index, base; /* index, base registers */ unsigned long disp; /* displacement */ char disp_sign; /* is negative? 1/0 */ char disp_size; /* 0, 1, 2, 4 */ } x86_ea_t; Note that any of 'scale', 'base', and 'disp' can be 0; 'index' is 1, 2, 4, or 8. The 'disp_sign" and 'disp_size' fields are used to display the 'disp' value correctly. The application can use the operand and instruction type information to implement higher-level disassembly features such as cross references (`if (i.operands[op_dest].access & op_execute)`), string or array references (`if (i.group == insn_string)`), subroutine recognition, and other automatic analyses. The use of enumerations for prefixes and register types will also facilitate automatic analysis. In addition to the x86_disasm() routine, libdisasm provides two more disassembly routines. The x86_disasm_range() routine is used to disassemble an entire buffer from start to finish; disassembly starts at a given offset into the buffer, and instructions are disassembled in sequence [i.e., the next instruction starts at the end of the current instruction] until the end of the buffer is reached. int x86_disasm_range( unsigned char *buf, unsigned long buf_rva, unsigned int offset, unsigned int len, DISASM_CALLBACK func, void *arg ); The first three arguments are familiar: the buffer containing the bytes to disassemble, the load address of the buffer, and the offset into the buffer at which to start disassembly. The 'len' argument refers to the number of bytes to disassemble; this allows a small section of the buffer to be disassembled, or disassembly can continue to the end of the buffer by setting 'len' to 'buf_len' - 'offset'. Note that the buffer length is therefore implied, and is actually set to 'offset + len' in the code. The 'func' argument points to a callback which is invoked when an instruction is disassembled, and 'arg' is arbitrary data to pass to that callback. The callback must have the prototype void callback( x86_insn_t *insn, void * arg ); ...where 'insn' is the instruction that was just disassembled. The application can use the callback high-level purposes such as printing the instruction or adding the instruction to a list or database. A sample callback that prints the instruction would look like this: void callback( x86_insn_t *insn, void *arg ) { char line[256]; x86_format_insn(insn, line, 256, att_syntax); printf( "%s\n", line); } The x86_disasm_forward() routine is more complex than x86_disasm_range(), and requires more work on the part of the application programmer. For the most part the arguments are the same as x86_disasm_range, except that 'buf_len' is used instead of 'len' since the entire buffer, not just a range of bytes within it, is being disassembled: 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 ); The disassembly in this case starts at 'offset', and proceeds forward following the flow of execution for the disassembled code. This means that when a jump, call, or conditional jump is encountered, x86_disasm_forward() recurses, using the offset of the target of the jump or call as the 'offset' argument. When a jump or return is encountered, x86_disasm_forward() returns, allowing its caller [either the application, or an outer invocation of x86_disasm_forward()] to continue. There is no provision for preventing infinite loops in this scheme, nor is there any means of resolving addresses stored on the stack or in registers. For this reason, the application programmer must supply a 'resolver' callback, whose duties are to return the RVA of the target of the jump or call, and to return -1 when that target has already been disassembled. The resolver has the following prototype: typedef long (*DISASM_RESOLVER)( x86_op_t *op, x86_insn_t * current_insn ); The 'op' field is, obviously enough, the operand containing the jump or call target, while 'current_insn' can be used to calculate offsets from the RVA of the current instruction. If the 'resolver' argument is not passed to x86_disasm_forward(), the default internal resolver in libdis.c will be used; however, this performs NO infinite loop checking. The internal resolver exists largely as a demonstration of how to resolve relative and absolute address operands to RVAs, and has the following code: static long internal_resolver( x86_op_t *op, x86_insn_t *insn ){ long next_addr = -1; if ( op->type == op_absolute || op->type == op_offset ) { next_addr = op->data.sdword; } else if ( op->type == op_relative ){ /* add offset to current rva+size based on op size */ if ( op->datatype == op_byte ) { next_addr = insn->addr + insn->size + op->data.sbyte; } else if ( op->datatype == op_word ) { next_addr = insn->addr + insn->size + op->data.sword; } else if ( op->datatype == op_dword ) { next_addr = insn->addr + insn->size + op->data.sdword; } } return( next_addr ); } When an instruction has been disassembled, most applications will at some point want to print it out. Libdisasm provides facilities for formatting an instruction or an operand to a character string. The syntax used in the formatting can be one of three types: enum x86_asm_format { native_syntax, /* addr\tbytes\tmnemonic\tdest\tsrc\timm */ intel_syntax, /* mnemonic\tdest, src, imm */ att_syntax /* mnemonic\tsrc, dest, imm */ }; The x86_format_* routines can be used to generate a string representation of either an instruction, or a single operand of an instruction. int x86_format_insn(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_operand(x86_op_t *op, x86_insn_t *insn, char *buf, int len, enum x86_asm_format); The rest of the API consists of convenience functions, which are largely self- explanatory. /* Operand accessor functions */ 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 ); /* get size of operand data in bytes */ int x86_operand_size( x86_op_t *op ); /* Manage instruction RVA, Offset, and function/block/tag fields */ 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 ); /* Endianness of CPU */ int x86_endian(void); /* Default address and operand size in bytes */ int x86_addr_size(void); int x86_op_size(void); /* Size of a machine word in bytes */ int x86_word_size(void); /* maximum size of a code instruction */ int x86_max_inst_size(void); ================================================================================ Implementation Notes Intel has a habit of implying operands in certain of its instructions, notably 0x6C INSB (e)di, dx 0x6D INSW (e)di, dx 0x6E OUTSB dx, (e)di 0x6F OUTSW dx, (e)di 0xA6 CMPSB (e)di, (e)si 0xA7 CMPSW (e)di, (e)si 0xA4 MOVSB (e)si, (e)di 0xA5 MOVSW (e)si, (e)di 0xAA STOSB (e)di, al 0xAB STOSW (e)di, (e)ax 0xAC LODSB al, (e)si 0xAD LODSW (e)ax, (e)si 0xAE SCASB al, (e)di 0xAF SCASW (e)ax, (e)di 0xF6 100 MUL al, Eb 0xF6 101 IMUL al, Eb 0xF6 110 DIV al, Eb 0xF6 111 IDIV al, Eb 0xF7 100 MUL (e)ax, Ev 0xF7 101 IMUL (e)ax, Ev 0xF7 110 DIV (e)ax, Ev 0xF7 111 IDIV (e)ax, Ev Libdisasm -- and programs that use it, such as the bastard -- include such "hidden operands" as the first operand (or second, i.e. as 'src' or 'dest', when appropriate) in an instruction. This means that the disassembly produced by libdisasm may not be compatible with standard Intel-syntax assemblers; the intent is to generate instructions that are suitable for automatic analysis, not for subsequent re-assembly. Blame Intel for blatantly encouraging the use of programming-through-side-effects...hell, blame them for 20-bit addressing, ModR/M opcode extensions, the SIB byte, and a lot of other bad design decisions. That should do it. As usual, flames, fixes, and contributions welcome. ================================================================================ Bugs In 16-bit mode, instructions with implied register operands [e.g. 0x5A pop edx] print 32-bit register names. There are no plans to fix this. ================================================================================ TODO (Maybe) Add a proper resolver that is recursion-proof (Maybe) Add register/stack tracking ================================================================================ Changelog ver 0.20 : API was rewritten to provide more low-level access to instruction information. The original API has been retained, but programmers are encouraged to use the new API as it is much more powerful. Operands are now stored internally as 64-bit data types. ver 0.17 : semantics of disassemble_address() and sprint_address() changed to allow user to specify bounds of the buffer to disassemble. Added a static library to the Makefile and forced the test tools to use it by default [thanks Rakan]. Provided macros in libdis.h for working with operand and instruction types. Finally wrote 16-bit mode.