12

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.

2
  • dump the symbol table
    – Igor Skochinsky
    Aug 15, 2014 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, 2014 at 23:11

3 Answers 3

11

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.

3
  • Excellent. I, indeed, found the symbols you are talking about in my example file. Thanks !
    – perror
    Aug 16, 2014 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, 2014 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, 2014 at 14:05
4

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.

0

When compiling C/C++ code, you can also detect the ARM/thumb status for a function by looking at the symbol table. The lowest bit of the address is set to 1 for Thumb and 0 for ARM instructions. To see this, you can use readelf --symWhen usingobjdump -tandreadelf`, though.

$ readelf --syms foo.elf |grep strlen$
  4460: 08000195    16 FUNC    GLOBAL DEFAULT    2 strlen
$ objdump -t foo.elf |grep strlen$
  08000194 g     F .text  00000010 strlen

Note that the actual address is 8000194 (32-bit aligned), but the symbol table entry (as shown by readelf) says 9000195 (with the LSB set to indicate thumb).

This approach will only tell you the ARM/thumb status at the start of a function, to see any transitions inside a function you will still need to look at the $t and $a symbols as documented in other answers.

The meaning of this LSB is defined in the ARM ELF ABI:

5.5.3 Symbol Values

In addition to the normal rules for symbol values the following rules shall also apply to symbols of type STT_FUNC:

  • If the symbol addresses an Arm instruction, its value is the address of the instruction (in a relocatable object, the offset of the instruction from the start of the section containing it).
  • If the symbol addresses a Thumb instruction, its value is the address of the instruction with bit zero set (in a relocatable object, the section offset with bit zero set).
  • For the purposes of relocation the value used shall be the address of the instruction (st_value & ~1).

Note: This allows a linker to distinguish Arm and Thumb code symbols without having to refer to the map. An Arm symbol will always have an even value, while a Thumb symbol will always have an odd value. However, a linker should strip the discriminating bit from the value before using it for relocation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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