When working with embedded Linux systems, in ARM-based boards, sometimes you need to determine whether a binary was compiled with ARM or THUMB2 instruction sets. This is a quick reference guide for checking this without relying on heavy tools like readelf or objdump.

The Core Concept

ARM uses a clever trick to distinguish between ARM and THUMB mode: the least significant bit (LSB) of the entry point address. If it’s odd (LSB = 1), the binary uses THUMB. If it’s even (LSB = 0), it uses ARM. This is because THUMB instructions are 2-byte aligned, while ARM instructions are 4-byte aligned.

Quick Decision Tree

  • Entry point is odd? → THUMB2 mode
  • Entry point is even? → ARM mode

Three Methods to Check

… depending on the tools you have in system.

Method 1: Using od (Octal Dump)

check_with_od() {
  local file="$1"
  echo "Verifying: $(basename "$file")"

  # Show first bytes
  od -t x1 -N 32 "$file" | head -3

  # Extract entry point using od
  entry=$(od -t x4 -j 24 -N 4 -A n "$file" | tr -d ' ')
  entry_dec=$((0x$entry))

  echo "Entry point: 0x$entry"
  [ $((entry_dec % 2)) -eq 1 ] && echo "THUMB" || echo "ARM"
}

How it works:

  • od -t x4 -j 24 -N 4 reads 4 bytes starting at offset 24 (the entry point field in the ELF header)
  • The modulo check (% 2) determines if the address is odd or even

Example output (THUMB2 binary):

root@nxp-imx6qp-sdb:~# check_with_od /usr/libexec/wpe-webkit-2.0/WPEWebProcess 
Verifying: WPEWebProcess
0000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
0000020 03 00 28 00 01 00 00 00 b1 04 00 00 34 00 00 00
0000040
Entry point: 0x000004b1
THUMB

Method 2: Using dd and od

check_with_dd() {
  local file="$1"
  echo "Verifying: $(basename "$file")"

  # Extract entry point (4 bytes, offset 24)
  entry_hex=$(dd if="$file" skip=24 count=4 bs=1 2>/dev/null | od -t x4 -A n | tr -d ' ')

  if [ ! -z "$entry_hex" ]; then
    entry_dec=$((0x$entry_hex))
    echo "Entry point: 0x$entry_hex ($entry_dec)"

    if [ $((entry_dec % 2)) -eq 1 ]; then
      echo "Result: THUMB"
    else
      echo "Result: ARM"
    fi
  fi
}

How it works:

  • dd extracts raw bytes from the binary
  • skip=24 positions to the entry point offset
  • count=4 bs=1 reads exactly 4 bytes
  • Pipes to od for hex formatting

Example output (THUMB2 binary):

root@nxp-imx6qp-sdb:~# check_with_dd /usr/libexec/wpe-webkit-2.0/WPEWebProcess 
Verifying: WPEWebProcess
Entry point: 0x000004b1 (1201)
Result: THUMB

Example output (ARM binary):

root@nxp-imx6qp-sdb:~# check_with_dd /usr/libexec/wpe-webkit-2.0/WPEWebProcess
Verifying: WPEWebProcess
Entry point: 0x000004b0 (1200)
Result: ARM

Method 3: Using hexdump (Most Readable)

check_entry_hexdump() {
  local file="$1"
  echo "Verifying: $file"

  # Show header ELF (first 64 bytes)
  hexdump -C "$file" | head -4

  # entry point (offset 0x18, 4 bytes)
  echo -n "Entry point: "
  hexdump -s 24 -n 4 -e '1/4 "0x%08x" "\n"' "$file" 2>/dev/null

  # ARM vs Thumb
  entry=$(hexdump -s 24 -n 4 -e '1/4 "%u" "\n"' "$file" 2>/dev/null)
  if [ $((entry % 2)) -eq 1 ]; then
    echo "Result: THUMB (entry point odd)"
  else
    echo "Result: ARM (entry point even)"
  fi
}

How it works:

  • hexdump -C provides the most human-readable output
  • -s 24 seeks to offset 24 (entry point)
  • -n 4 limits to 4 bytes
  • -e '1/4 "0x%08x"' formats as a 32-bit hex value

Example output (THUMB2 binary):

root@nxp-imx6qp-sdb:~# check_entry_hexdump /usr/libexec/wpe-webkit-2.0/WPEWebProcess 
Verifying: /usr/libexec/wpe-webkit-2.0/WPEWebProcess
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  03 00 28 00 01 00 00 00  b1 04 00 00 34 00 00 00  |..(.........4...|
00000020  4c 11 00 00 00 04 00 05  34 00 20 00 09 00 28 00  |L.......4. ...(.|
00000030  1a 00 19 00 01 00 00 70  b8 05 00 00 b8 05 00 00  |.......p........|
Entry point: 0x000004b1
Result: THUMB (entry point impar)

Example output (ARM binary):

root@nxp-imx6qp-sdb:~# check_entry_hexdump /usr/libexec/wpe-webkit-2.0/WPEWebProcess
Verifying: /usr/libexec/wpe-webkit-2.0/WPEWebProcess
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  03 00 28 00 01 00 00 00  b0 04 00 00 34 00 00 00  |..(.........4...|
00000020  4c 11 00 00 00 04 00 05  34 00 20 00 09 00 28 00  |L.......4. ...(.|
00000030  1a 00 19 00 01 00 00 70  24 06 00 00 24 06 00 00  |.......p$...$...|
Entry point: 0x000004b0
Result: ARM (entry point par)

Example from libc:

root@nxp-imx6qp-sdb:~# check_entry_hexdump /usr/lib/libc.so.6
Verifying: /usr/lib/libc.so.6
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  03 00 28 00 01 00 00 00  39 f4 01 00 34 00 00 00  |..(.....9...4...|
00000020  8c 17 10 00 00 04 00 05  34 00 20 00 0a 00 28 00  |........4. ...(.|
00000030  3a 00 39 00 01 00 00 70  c8 c2 0f 00 c8 c2 0f 00  |:.9....p........|
Entry point: 0x0001f439
Result: THUMB (entry point impar)

Real-World Comparison: THUMB2 vs ARM

Standard Scarthgap THUMB2 Image

Check CPU capabilities:

root@nxp-imx6qp-sdb:~# cat /proc/cpuinfo | grep -i 'model\|features'
model name      : ARMv7 Processor rev 10 (v7l)
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpd32 
model name      : ARMv7 Processor rev 10 (v7l)
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpd32 

Check binaries:

root@nxp-imx6qp-sdb:~# check_with_od /usr/libexec/wpe-webkit-2.0/WPEWebProcess 
Verifying: WPEWebProcess
0000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
0000020 03 00 28 00 01 00 00 00 b1 04 00 00 34 00 00 00
0000040
Entry point: 0x000004b1
THUMB

root@nxp-imx6qp-sdb:~# check_with_dd /usr/libexec/wpe-webkit-2.0/WPEWebProcess 
Verifying: WPEWebProcess
Entry point: 0x000004b1 (1201)
Result: THUMB

root@nxp-imx6qp-sdb:~# check_entry_hexdump /usr/lib/libc.so.6
Verifying: /usr/lib/libc.so.6
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  03 00 28 00 01 00 00 00  39 f4 01 00 34 00 00 00  |..(.....9..4...|
00000020  8c 17 10 00 00 04 00 05  34 00 20 00 0a 00 28 00  |........4. ...(.|
00000030  3a 00 39 00 01 00 00 70  c8 c2 0f 00 c8 c2 0f 00  |:.9....p........|
Entry point: 0x0001f439
Result: THUMB (entry point odd)

Disabling THUMB2 in Yocto

Add to local.conf:

ARM_INSTRUCTION_SET = "arm"
ARM_INSTRUCTION_SET:pn-wpewebkit:armv7a = "arm"
ARM_INSTRUCTION_SET:pn-wpewebkit:armv7r = "arm"
ARM_INSTRUCTION_SET:pn-wpewebkit:armv7m = "arm"
ARM_INSTRUCTION_SET:pn-wpewebkit:armv7ve = "arm"
ARM_INSTRUCTION_SET:pn-wpewebkit = "arm"
TUNE_FEATURES:remove = "thumb"

Verification After Disabling THUMB2

CPU capabilities remain the same (CPU supports both):

root@nxp-imx6qp-sdb:~# cat /proc/cpuinfo | grep -i 'model\|features'
model name      : ARMv7 Processor rev 10 (v7l)
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpd32 
model name      : ARMv7 Processor rev 10 (v7l)
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpd32 

But binaries now compile in ARM mode:

root@nxp-imx6qp-sdb:~# check_entry_hexdump /usr/libexec/wpe-webkit-2.0/WPEWebProcess
Verifying: /usr/libexec/wpe-webkit-2.0/WPEWebProcess
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  03 00 28 00 01 00 00 00  b0 04 00 00 34 00 00 00  |..(.........4...|
00000020  4c 11 00 00 00 04 00 05  34 00 20 00 09 00 28 00  |L.......4. ...(.|
00000030  1a 00 19 00 01 00 00 70  24 06 00 00 24 06 00 00  |.......p$...$...|
Entry point: 0x000004b0
Result: ARM (entry point even)

root@nxp-imx6qp-sdb:~# check_with_dd /usr/libexec/wpe-webkit-2.0/WPEWebProcess
Verifying: WPEWebProcess
Entry point: 0x000004b0 (1200)
Result: ARM

root@nxp-imx6qp-sdb:~# check_with_od /usr/libexec/wpe-webkit-2.0/WPEWebProcess
Verifying: WPEWebProcess
0000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
0000020 03 00 28 00 01 00 00 00 b0 04 00 00 34 00 00 00
0000040
Entry point: 0x000004b0
ARM

root@nxp-imx6qp-sdb:~# check_entry_hexdump /usr/lib/libc.so.6
Verifying: /usr/lib/libc.so.6
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  03 00 28 00 01 00 00 00  1c f7 01 00 34 00 00 00  |..(.....4...|
00000020  8c c7 15 00 00 04 00 05  34 00 20 00 0a 00 28 00  |........4. ...(.|
00000030  3a 00 39 00 01 00 00 70  64 76 15 00 64 76 15 00  |:.9....pdv..dv..|
Entry point: 0x0001f71c
Result: ARM (entry point even)

Notice the entry point changed from 0x1f439 (odd) to 0x1f71c (even).

That’s it. Bookmark this for the next time you’re debugging embedded Linux builds!

Leave a comment

:-)

I’m Pablo Saavedra, a former Unix systems administrator turned embedded software developer, now dedicated to squashing bugs and optimizing performance on embedded devices..

I’m degree  in Computer Science by Universade da Coruña (Spain).

Of course, my hobbies are anything similar to computers, but also boxing, fitness, good beers, … You can follow me on twitter or my linkedin profile,