Disassembling ARM object files allows you to view the machine code instructions that were generated by the compiler. This can be useful for debugging, reverse engineering, and understanding how code executes on ARM processors. This guide will walk through the basic steps for disassembling ARM object files using common tools like objdump and GNU binutils.
Prerequisites
Before disassembling ARM object files, you’ll need:
- ARM cross compiler toolchain installed, like GNU Arm Embedded Toolchain
- objdump or other disassembler tools installed
- Sample ARM object file compiled from C/C++ code
It’s also helpful to have basic knowledge of:
- ARM assembly language
- ELF object file format
- How code compilation works
1. Compile your C/C++ code into an ARM object file
First, write or obtain some simple C/C++ code to disassemble. For example: int main() { int a = 1; int b = 2; int c = a + b; return c; }
Save this as file.c. Then compile it into an ARM object file using your cross compiler: $ arm-none-eabi-gcc -c file.c -o file.o
This will generate an ELF object file called file.o containing the machine code for your program.
2. Run objdump to disassemble the object file
The easiest way to view the disassembly is by running objdump with the -d flag: $ arm-none-eabi-objdump -d file.o
This will output the disassembly, which will look something like: file.o: file format elf32-littlearm Disassembly of section .text: 00000000 : 0: e3a03000 mov r3, #0 4: e3a01001 mov r1, #1 8: e3a02002 mov r2, #2 c: e0813002 add r3, r1, r2 10: e3a00003 mov r0, r3 14: e12fff1e bx lr
This shows the disassembly of the .text section, which contains the instructions corresponding to the main() function in our C code.
3. Read and understand the disassembly
Looking at the disassembly, we can see:
- The first column contains the memory address offset
- The second column is the machine code in hexadecimal
- The third column is the disassembled instruction
So at address 0x00000000, we have instruction “mov r3, #0” which moves the value 0 into register r3. Each line corresponds to a specific ARM instruction from the compiled C code.
We can clearly identify how the C code was translated into the following assembly instructions: int a = 1; -> mov r1, #1 int b = 2; -> mov r2, #2 int c = a+b; -> add r3, r1, r2 return c; -> mov r0, r3 bx lr
With some basic ARM assembly knowledge, we can start understanding how the higher level C code was compiled down to the specific machine instructions executed by the processor.
4. Disassemble specific sections
By default, objdump disassembles the .text section containing executable code. You can also selectively disassemble other sections containing data: $ arm-none-eabi-objdump -d -j .data file.o $ arm-none-eabi-objdump -d -j .bss file.o
This disassembles the .data and .bss sections respectively, which contain initialized and uninitialized global variables.
5. Disassemble intermixed with source
Objdump can also intermix the disassembly with your original C source code, making it easier to correlate instructions back to the lines of code that generated them: $ arm-none-eabi-objdump -S file.o
This will output something like: 00000000 : int main() { 0: e3a03000 mov r3, #0 int a = 1; 4: e3a01001 mov r1, #1 int b = 2; 8: e3a02002 mov r2, #2 int c = a + b; c: e0813002 add r3, r1, r2 return c; 10: e3a00003 mov r0, r3 14: e12fff1e bx lr
This makes it very easy to see how the C code corresponds to the generated instructions.
6. Use other disassemblers
Besides objdump, there are other disassembly tools that can provide additional options and output formats:
- IDA Pro – Advanced commercial disassembler with GUI and debugging
- Hopper – Commercial disassembler for macOS and Linux
- Radare2 – Open source disassembler and debugger
- Ghidra – Open source disassembler from NSA
These tools allow disassembling binary files interactively, and provide advanced analysis capabilities useful for reverse engineering.
7. Change disassembly format
By default objdump uses AT&T syntax for the disassembly output. You can switch this to Intel syntax with: $ arm-none-eabi-objdump -M intel -d file.o
The same code now disassembles to: 00000000 : 0: mov r3, #0 4: mov r1, #1 8: mov r2, #2 c: add r3, r1, r2 10: mov r0, r3 14: bx lr
The Intel syntax reverses the order of source and destination operands. This style is used by some other disassemblers.
8. Disassemble Thumb code
For ARM code compiled in Thumb mode, you need to add the -Mthumb option: $ arm-none-eabi-objdump -Mthumb -d file.o
This changes the disassembler to decode 16-bit Thumb instructions instead of 32-bit ARM. The Thumb disassembly will look different than the ARM output.
9. DisassemblePosition-independent code
For position-independent code (PIC) compiled with -fPIC, objdump needs help to correctly disassemble: $ arm-none-eabi-objdump -Mforce-thumb -Mno-underscore -d file_pic.o
The -Mno-underscore option avoids mangling symbol names, and -Mforce-thumb assumes Thumb PIC code.
10. Advanced objdump options
Objdump has many other useful options. For example, to print symbol names: $ arm-none-eabi-objdump -t file.o
This prints the symbol table, showing function and variable names. Pass -C to demangle C++ symbols.
Use -x to print headers, sections, and section contents in hexadecimal: $ arm-none-eabi-objdump -x file.o
See the objdump documentation for more disassembly features.
Conclusion
This covers the basic workflow for disassembling ARM object files with objdump. Disassembly gives visibility into compiled code and helps debug, optimize, and learn how programs execute on ARM processors.
With practice reading assembly code, you can start understanding the link between high level languages and the underlying processor instructions. Advanced tools provide even deeper analysis capabilities to study binary programs.
Being able to disassemble and interpret ARM code is an essential skill for embedded systems programmers, reverse engineers, and anyone working at the boundary between hardware and software.