I try to build a small disassembler for ARM, and I would like to know how do objdump manage to sort out the normal mode instructions (32-bits instruction wide) from the thumb mode instructions (16-bits instruction wide) without having to look at the t flag in the CPSR.

But first, let us build a small example and make some experiments on it.

I wrote this small piece of ARM assembly (gas syntax) as basis example:

.arm
    mov fp, #0
    moveq   r1, r0
.thumb
    mov r0, #0
    mov fp, r0

Then, I cross-compiled it like this:

$> arm-none-eabi-gcc -Wall -Wextra -mlittle-endian -c -o arm_sample arm_sample.s

And, here is the output of objdump on the ARM object file:

$> objdump -d ./arm32_mov

./arm32_mov:     file format elf32-littlearm

Disassembly of section .text:
00000000 <.text>:
   0:   e3a0b000    mov fp, #0
   4:   01a01000    moveq   r1, r0
   8:   2000        movs    r0, #0
   a:   4683        mov fp, r0

But, when I run my tool, I get:

 warning: decoder says at (0x8,0):'strmi r2, [r3], r0' : Unknown mnemonic
   0:   00 b0 a0 e3                 mov fp, #0
   4:   00 10 a0 01                 moveq   r1, r0
   8:   ...

My tool is based on libopcodes (exactly like objdump), so the third instruction is just interpreted as still in 32-bits mode and the two thumb mode instructions are just interpreted as one 32-bits instruction which gives strmi r2, [r3], r0.

My question is that I don't understand how objdump knows that there is a switch between normal mode to thumb mode. Before discovering this, I thought that this information was only available at execution time though the value of the t flag in the CPSR status register.

I tried to look at the code of objdump but, I didn't see any architecture dependent cases to treat the case of the ARM thumb mode. So, this is still a mystery to me...

Any suggestion is welcome !

EDIT

In fact, I worked on an object file (compiled with -c option), so there is not so much symbols. But, here is a more detailed output obtained through objdump:

$> objdump -x ./arm32_mov

./arm32_mov:     file format elf32-littlearm
./arm32_mov
architecture: armv4t, flags 0x00000010:
HAS_SYMS
start address 0x00000000
private flags = 5000000: [Version5 EABI]

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000000c  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000040  2**0
                  ALLOC
  3 .ARM.attributes 00000016  00000000  00000000  00000040  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .ARM.attributes    00000000 .ARM.attributes

And, here is the output of readelf:

$> readelf -a ./arm32_mov
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          148 (bytes into file)
  Flags:                             0x5000000, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         8
  Section header string table index: 5

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 00000c 00  AX  0   0  4
  [ 2] .data             PROGBITS        00000000 000040 000000 00  WA  0   0  1
  [ 3] .bss              NOBITS          00000000 000040 000000 00  WA  0   0  1
  [ 4] .ARM.attributes   ARM_ATTRIBUTES  00000000 000040 000016 00      0   0  1
  [ 5] .shstrtab         STRTAB          00000000 000056 00003c 00      0   0  1
  [ 6] .symtab           SYMTAB          00000000 0001d4 000070 10      7   7  4
  [ 7] .strtab           STRTAB          00000000 000244 000007 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.
There are no program headers in this file.
There are no relocations in this file.
There are no unwind sections in this file.

Symbol table '.symtab' contains 7 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 SECTION LOCAL  DEFAULT    1 
     2: 00000000     0 SECTION LOCAL  DEFAULT    2 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 NOTYPE  LOCAL  DEFAULT    1 $a
     5: 00000008     0 NOTYPE  LOCAL  DEFAULT    1 $t
     6: 00000000     0 SECTION LOCAL  DEFAULT    4 

No version information found in this file.
Attribute Section: aeabi
File Attributes
  Tag_CPU_arch: v4T
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-1

SOLUTION (Ian Cook)

$ objdump --syms --special-syms ./arm32_mov

./arm32_mov:     file format elf32-littlearm

SYMBOL TABLE:
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l       .text  00000000 $a   <-- ARM code
00000008 l       .text  00000000 $t   <-- Thumb code
00000000 l    d  .ARM.attributes    00000000 .ARM.attributes

Just to be sure, I interleaved arm code, thumb code and arm code again. Here is the dump:

$> objdump --syms --special-syms ./arm32_mov

./arm32_mov:     file format elf32-littlearm

SYMBOL TABLE:
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l       .text  00000000 $a
00000008 l       .text  00000000 $t
0000000c l       .text  00000000 $a
00000000 l    d  .ARM.attributes    00000000 .ARM.attributes

You can see that the ARM symbol is present twice at 0x0 and 0xc and are surrounding the thumb symbol.

  • dump the symbol table – Igor Skochinsky Aug 15 '14 at 22:53
  • In fact, I don't have a lot of symbols because I just wrote directly in assembly and did not linked the final object file (I also did it, but with the exact same result. But, I took the shortest working example). – perror Aug 15 '14 at 23:11
up vote 7 down vote accepted

An ARM object file should contain symbols identifying the regions that are arm code ($a), thumb code ($t) and literal data ($d).

You can see these as symbols #4 and #5 in your read-elf output. i.e. offset 0 is arm, offset 8 is thumb

obj-dump will output these symbols too if you use the--special-symsoption.

Reading the ARM ELF ABI will help you understand what's going on.

If you are planning to try to disassemble ARM code in an Apple (Mach-O) executable or in a firmware blob then you'll have to use another technique as these symbols won't be present.

  • Excellent. I, indeed, found the symbols you are talking about in my example file. Thanks ! – perror Aug 16 '14 at 9:09
  • 1
    Just to know about the other executable formats, you said Mach-O doesn't have such a symbol, but what about Microsoft Windows PE format ? Does it store such information in the symbols as well ? – perror Aug 16 '14 at 10:26
  • 1
    @perror: IMAGE_FILE_MACHINE_ARMNT assumes Thumb-2. The older ones (for WinCE) use the bit 0, I think. – Igor Skochinsky Aug 16 '14 at 14:05

This problem is solved in the case when code mapping symbols ($a, $t, $d) are available. However, it's still possible to get (almost) perfect disassembly for Thumb-2 even when symbols are not available.

A promising technique is speculative disassembly which has been discussed in this paper. The tool, Spedi, is open sourced here. It outperforms IDA Pro at least on the limited set of benchmarks used. Spedi can also be instructed to use ARM code mapping symbols, if available, to get the ground truth.

This tool can be enhanced with the ideas discussed in this paper to address your case of mixed mode disassembly even when symbols are not available.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.