Why use opdis instead of objdump?

The GNU objdump(1) utility is intended to examine the output of the gcc compiler and the utilities in the GNU binutils distribution. It cannot be used when examining file formats not supported by BFD (e.g. ROM images), and its disassembly options are limited.

The opdis utility is intended to complement objdump by providing a simple disassembler for object code. Features which distinguish opdis from objdump include:

The intended audience for opdis includes embedded system developers working with ROM images, security personnel examining shellcode strings, and software developers reverse engineering hostile or non-BFD-compliant binaries.

Why not use libopcodes directly?

The libopcodes library is a very serviceable disassembler, but it has three shortcomings:

This last point in particular makes libopcodes unsuitable for binary analysis.

Can opdis or libopdis be used in commercial projects?

Both opdis and libopdis are released under the terms of the GNU Public License (http://www.gnu.org/licenses/gpl.txt). Companies that find the terms of the GPL too restrictive can contact community@thoughtgang.org to discuss alternate licensing terms.


What target architectures does opdis support?

opdis will support whatever architectures are supported by the locally-installed GNU binutils. A list of supported architectures can be displayed by using the --list-architectures option.

Note that while an architecture is supported, it may not support advanced features such as generation of metadata or control flow disassembly (which relies on metadata). A warning will be printed to STDERR if control flow disassembly is not supported on the target architecture.

What are BFD targets?

A BFD target is a file that has been loaded using libbfd, part of the GNU binutils distribution. BFD targets have many advantages: the target architecture is automatically detected, memory maps are not required, and elements of the object file structure such as sections and symbols are available. Use of BFD targets is recommended when the target is supported by libbfd.

What output formats are available?

The built-in output formats are:

The default output format is 'dump'.

The asm format consists of the libopcodes strings followed by a comment containing the VMA of the instruction.

bash$ opdis -f asm -E /bin/ls  |more
xor    %ebp,%ebp        # [0x4026e0]
mov    %rdx,%r9 # [0x4026e2]
pop    %rsi     # [0x4026e5]
callq  *%rax    # [0x40271c]
add    $0x8,%rsp        # [0x40271e]
retq    # [0x402722]

The dump format consists of the instruction VMA and bytes followed by the assembly language representation of the instruction. Up to eight bytes of the instruction are printed per line.

bash$ opdis -f dump -E /bin/ls 
0x4026e0: 31 ED                      xor        %ebp, %ebp
0x4026e2: 49 89 D1                   mov        %rdx, %r9
0x4026e5: 5E                         pop        %rsi
0x40271c: FF D0                      callq      *%rax
0x40271e: 48 83 C4 08                add        $0x8, %rsp
0x402722: C3                         retq

The delim format consists of all fields of the instruction (offset, vma, bytes, ascii, prefixes, mnemonic, isa, category, flags, and each operand) separated by pipe characters. Note that the number of operand fields will vary on each line, and there are no empty fields for operands (the maximum number of operands is not defined). The contents of each operand field are colon-delimited and consist of the fields ascii, category, flags, value, and an option (i.e. not always present) name field. The value for each operand will be a number or an object contained in curly braces; the object will be semi-colon-delimited and can be a register, absolute address, or address expression.

bash$ opdis -f delim -E /bin/ls 
0x0|0x4026e0|31 ED|xor    %ebp,%ebp||xor|general purpose|bitwise|bitwise xor||%e
bp:register:read:{ebp;6;4;general,frame ptr}:SRC|%ebp:register:write:{ebp;6;4;ge
neral,frame ptr}:DEST
0x2|0x4026e2|49 89 D1|mov    %rdx,%r9||mov|general purpose|load/store|||%rdx:reg
0x5|0x4026e5|5E|pop    %rsi||pop|general purpose|stack|pop||%rsi:register:read:{
0x3c|0x40271c|FF D0|callq  *%rax||callq|general purpose|control flow|call||*%rax:register:read,exec:{rax;1;8;general}:TARGET
0x3e|0x40271e|48 83 C4 08|add    $0x8,%rsp||add|general purpose|arithmetic|||$0x8:immediate:read:0X8:SRC|%rsp:register:write:{rsp;5;8;general,stack ptr}:DEST
0x42|0x402722|C3|retq   ||retq|general purpose|control flow|return|

The xml format produces an XML representation of each instruction along with an embedded DTD.

bash$ opdis -f xml -E /bin/ls 
<?xml version="1.0"?>
<!DOCTYPE disassembly [
<!ELEMENT disassembly (instruction*)>
<!ELEMENT instruction (offset,vma,bytes,ascii?,mnemonic?,prefix?,isa?,category?,
<!ELEMENT offset (#PCDATA)>
<!ELEMENT bytes (byte+)>
<!ELEMENT ascii (#PCDATA)>
<!ELEMENT mnemonic (#PCDATA)>
<!ELEMENT prefix (#PCDATA)>
<!ELEMENT category (#PCDATA)>
<!ELEMENT flags (flag+)>
<!ELEMENT operands (operand*)>
<!ELEMENT operand (ascii,category,flags,value)>
<!ATTLIST operand type (target|src|dest) "">
<!ELEMENT value (register?,immediate?,absolute?,expression?)>
<!ELEMENT register (ascii,id,size,flags)>
<!ELEMENT immediate (#PCDATA)>
<!ELEMENT absolute (segment,immediate)>
<!ELEMENT segment (register)>
<!ELEMENT expression (base?,index?,scale,shift?,displacement?)>
<!ELEMENT base (register)>
<!ELEMENT index (register)>
<!ELEMENT scale (#PCDATA)>
<!ELEMENT shift (#PCDATA)>
<!ELEMENT displacement (absolute?,immediate?)>
  <ascii>xor    %ebp,%ebp</ascii>
  <isa>general purpose</isa>
    <flag>bitwise xor</flag>
    <operand name="src">
          <flag>frame ptr</flag>
    <operand name="dest">
          <flag>frame ptr</flag>
  <ascii>retq   </ascii>
  <isa>general purpose</isa>
  <category>control flow</category>
See also:

How can the output format be customized?

Custom output formats can be specified using a printf- or strftime-style format string as the argument to -f.

The available format operators are:


bash$ opdis -f '%a %i\n' -E /bin/ls  | head -3
0x4026e0 xor    %ebp,%ebp
0x4026e2 mov    %rdx,%r9
0x4026e5 pop    %rsi
bash$ opdis -f '%ao %l [%b]\n' -E /bin/ls  | head -3
0x0 2 [31 ED]
0x2 3 [49 89 D1]
0x5 1 [5E]
bash$ opdis -f '[%a]%p %m\t%o\t# %b\n' -E /bin/ls  | head -3
[0x4026e0] xor  %ebp, %ebp      # 31 ED
[0x4026e2] mov  %rdx, %r9       # 49 89 D1
[0x4026e5] pop  %rsi    # 5E
bash$ opdis -f '%m\t%os%?,%od\n' -E /bin/ls  | head -3
xor     %ebp,%ebp
mov     %rdx,%r9
pop     %rsi
opdis -f '%ao: %b "%bC"\n' -E /bin/ls  | head -3
0x0: 31 ED "1 ."
0x2: 49 89 D1 "I . ."
0x5: 5E "^"
bash$ opdis -f '%a %i\t#%iC%?:%iF\n' -E /bin/ls  | head -3
0x4026e0 xor    %ebp,%ebp       #bitwise:bitwise xor
0x4026e2 mov    %rdx,%r9        #load/store
0x4026e5 pop    %rsi    #stack:pop

How are target IDs assigned?

Each target specified on the command line is assigned an ID in the order it is encountered. This means that each -b option is assigned an ID, followed by each file argument.

The command

opdis -b '90 90 90 90' -b 'cc cc cc cc' a.out rom.bin

will assign target IDs as follows:

  1. : buffer '90 90 90 90'
  2. : buffer 'cc cc cc cc'
  3. : file 'a.out'
  4. : file 'rom.bin'

Use the --dry-run option to preview the target list.

How do jobs get created?

Disassembly jobs are created with the -l, -c, -E, -N, and -S options. Jobs are scheduled in the order they are encountered. Use the --dry-run to preview the job list.

See also:
Using opdis to disassemble an object file symbol
Using opdis to disassemble a hex string
Using opdis to disassemble an ELF binary

What are memory maps used for?

Memory maps have three main uses:

This last point is worth examining in more detail.

When opdis disassembles a target, it stores the disassembled instructions using their VMA as a primary key. The same storage is used for all targets; if an instruction is already stored for a VMA, all subsequent instructions with the same VMA will be discarded.

By default, the VMA for every target will be 0 (i.e., each instruction in the target will use its offset in the target as its VMA). Thus, when disassembling two targets, the instructions in the second will be discarded. Creating a memory map that places the second target after the first in memory (i.e. map it to a VMA equal to the first target's size) fixes this issue.

Note that BFD targets do not require memory maps, as the BFD structure contains sections that provide their own mapping. Also, all -b options are mapped sequentially in memory, so that they appear to be part of the same contiguous memory region.

See also:
Mapping memory regions

Why does a memory map size not limit disassembly?

The memory maps are only used when scheduling jobs. Once disassembly starts, libopcodes is invoked in a loop using the disassemble_info structure contained in the opdis_. This structure uses the target buffer to determine the bounds of disassembly.


What target architectures does libopdis support?

libopdis will support whatever architectures are supported by the locally-installed GNU binutils. The list of architectures and file formats that binutils supports can be displayed by running objdump -i.

Note that basic support for an architecture only means that the ascii, offset, vma, size, and bytes fields of the opdis_insn_t are guaranteed to be filled. All other fields, including the operand list, require that an architecture-specific decoder function be invoked by libopdis. Use the status field to determine if any of these additional fields have been filled.

Currently, libopdis ships only with an i386 (x86, x86-64) decoder.

See also:
Setting the target architecture
Setting disassembler options
What target architectures does libopdis support?

What is the difference between linear and control flow disassembly?

Most disassemblers, including objdump(1), implement what is known as linear or linear sweep disassembly: the algorithm starts disassembly at a given entry point, using the byte after a disassembled instruction as the start of the next instruction. This type of disassembly is fast and easy to implement, as there is no analysis of the instruction needed, and no danger of getting caught in an endless loop.

Control-flow disassembly attempts to follow the flow of control in a program as it disassembles instructions. The algorithm starts disassembly at an entry point, then examines the instruction to see if it is a branch (call or conditional jump), a jump, or a return. Branch instructions and jump instructions examine the target operand and begin disassembly at that location. Once the disassembly of the target operand is complete, branch instructions will continue disassembly at the byte after the current instruction, while jump instructions will stop disassemby. Return instructions always halt disassembly.

The implementation of control-flow disassembly is much more tricky than linear disassembly. A list of disassembled addresses must be maintained to avoid getting trapped in an endless loop (this may be reduced to a list of branch targets that have been visited, at the price of redundant disassembly). Each instruction must be analyzed to determine if it is a branch, jump, or return; in addition, the operand of these instructions must be analyzed and resolved to a vma if it is a register or reference.

While control-flow disassembly is more accurate than linear disassembly, it is still not perfect: instructions found by a linear disassembler might only be reachable by jumping to the contents of a register. It is recommended that control flow disassembly of the symbols and entry point in a program be supplemented by the linear disassembly of all executable and loadable sections in the program: instructions which are duplicates or which begin inside of existing instructions should be discarded.

What is the difference between a vma and an offset?

An offset is an index into a buffer of bytes, with offset 0 being the first item of the buffer and offset (buffer.size - 1) being the offset of the last item in the buffer.

A vma or virtual memory address is the load address of an offset. Some buffer contents (e.g. an executable code segment in a binary file) assume they have been loaded to a specific virtual address; this virtual address is used as the vma for the buffer. This vma is added to an offset into the buffer in order to determine the vma for an instruction.

A buffer can always have a load address of 0, meaning that the vma and the offset will be identical for its contents.

What are the various opdis_disasm_bfd functions used for?

The BFD support provided by libopdis consists of a configuration function and five disassembly functions:

Using libbfd to manage libopdis targets has some advantages and disadvantages. On the positive side, it simplifies the loading of the target by detecting the architecture, and provides access to symbolic information such as program sections and symbol tables. On the negative side, libbfd will fail to load a file which has malformed or missing headers, or which is not in a recognized file format.

It is recommended that users employ the BFD API where suitable, while keeping the standard API in reserve to handle files that BFD fails to load.

See also:
Writing a BFD-based disassembler

Why is metadata not being generated for a particular architecture?

The disassembly generated by libopcodes is a sequence of strings intended for writing to a stream. There is no metadata, so the strings must be examined to determine which are mnemonics and which are operands, and which of these are branch/jump/return instructions and what their targets are.

This means that the logic which generates an opdis_insn_t from libopcodes output must be able to parse every assembly language that is supported by GNU binutils. Doing do would, it goes without saying, be a long and time-consuming endeavor that only delay the release of libopdis. Instead, metadata support for the x86 architecture has been provided, and support for additional architectures will be add as time and merit warrant.

See also:
Writing a decoder callback

Why does control flow disassembly not work for a particular architecture?

The control-flow algorithm requires that metadata be generated for every instruction disassembled by libopcodes. As discussed in Why is metadata not being generated for a particular architecture?, this is infeasible, so most architectures supported by libopcodes will have no metadata generated by libopdis.

Those in need of control-flow disassembly on an architecture for which metadata support is missing may want to write their own decoder callback (see How much work is it to write a decoder?) to provide the metadata to libopdis.

See also:
Why is metadata not being generated for a particular architecture?
Writing a decoder callback

Why are duplicate instructions emitted during control flow disassembly?

When using the default display callback, or any display callback that does not perform its own instruction management, the same instruction may be disassembled multiple times.

The reason for this behavior is briefly mentioned in libopdis_algo: the control-flow algorithm has a choice of storing every address it disassembles, or only branch targets. In the interest of efficiency, the latter approach is taken by default: an opdis_vma_tree_t is allocated by the control-flow function and used to track visited branch targets until disassembly is complete. This has some unfortunate consequences:

There are two solutions to this problem. The first is to write a display callback which adds instructions to an opdis_insn_tree_t during disassembly, then print that tree when disassembly is complete. The second is to enable visited-address checking in the default handler by allocating the visited_addr field of the opdis_t as shown:

opdis->visited_addr = opdis_vma_tree_init();

Note: This will slow down disassembly considerably.

When will additional decoders be made available?

There are currently no plans to develop additional decoders for libopdis; the x86 decoder currently meets the needs of the libopdis developers. If there is sufficient demand, work might begin on ARM, SPARC, or IA-64 decoders. Submissions of decoder implementations for any architecture are of course welcome.

How much work is it to write a decoder?

As discussed in Why is metadata not being generated for a particular architecture?, libopcodes generates a sequence of strings for each instruction; libopdis stores these in an opdis_insn_buf_t. This means that a decoder must scan through a list of strings in order to detect instruction mnemonics and operands.

For the simple case of supporting control-flow disassembly, this is not too much work. The decoder can scan for the handful of relevant instructions, then generate an operand for the branch target and set all relevant metadata (instruction category, instruction flags, operand category, operand flags, instruction target operand pointer).

More complete support of an architecture, such as detection of register flags or stack trap instruction types, is a great deal of work, and should ideally be automated.

See also:
Writing a decoder callback.

Generated on Wed Mar 10 14:30:46 2010 for Opdis Disassembly Library by  doxygen 1.6.1