Inspect ELF Binaries Safely with llvm-objdump-20

You will use llvm-objdump-20 to identify an ELF file, inspect its sections and symbols, and produce a small, readable disassembly. The commands are read-only: they examine the binary and do not rewrite it. Allow about fifteen minutes if you have a binary ready to inspect.

This guide describes the installed LLVM package, llvm-20 version 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139. Output varies with the file, architecture and build options. The examples below use the system's /bin/ls ELF executable, so substitute a file you are allowed to read.

1. Confirm the installed command

Start with a version and path check. This needs no elevated privileges:

$ command -v llvm-objdump-20
/usr/bin/llvm-objdump-20
$ llvm-objdump-20 --version
Ubuntu LLVM version 20.1.8
  Optimized build.

The manual calls the tool an LLVM object file dumper. Its general shape is llvm-objdump-20 [options] <input object files>. It can read object files, executables and archives. Do not assume that the command's name means the input must have an .o extension.

Checkpoint: Record the exact binary path you intend to inspect. Keep untrusted files in a suitable analysis directory and do not execute them merely because you are examining them.

2. Identify the file format and entry point

Use -f, the alias for --file-headers, for a compact first look:

$ llvm-objdump-20 -f /bin/ls

/bin/ls:  file format elf64-x86-64
architecture: x86_64
start address: 0x0000000000006d30

The format line confirms that this file is a 64-bit x86 ELF binary. The start address is the entry point recorded in the file. It is not necessarily the address of the first function you care about.

If you get an error instead, check the path and permissions first:

$ test -r /path/to/input-file && echo readable
$ file /path/to/input-file

llvm-objdump-20 is not a general text-file viewer. A script, compressed file or damaged input can produce an error or unhelpful output. Preserve the original while diagnosing the problem.

3. List sections before disassembling

Use -h, the alias for --section-headers, to see named sections, sizes, virtual addresses and types:

$ llvm-objdump-20 -h /bin/ls | head -18

/bin/ls:  file format elf64-x86-64

Sections:
Idx Name               Size     VMA              Type
  0                    00000000 0000000000000000
  1 .interp            0000001c 0000000000000318 DATA
  2 .note.gnu.property 00000030 0000000000000338 DATA
  3 .note.gnu.build-id 00000024 0000000000000368 DATA
  4 .note.ABI-tag      00000020 000000000000038c DATA

Look for the section that matches the question. .text normally contains executable code, while sections such as .rodata contain read-only data. A section's VMA is useful when you later narrow a disassembly by address. Do not treat every bytes-looking section as instructions.

To restrict later operations to one section, use --section=NAME, or its -j alias. For example:

$ llvm-objdump-20 --section=.init -h /bin/ls
/bin/ls:  file format elf64-x86-64

Sections:
Idx Name               Size     VMA              Type
 12 .init              0000001b 0000000000004000 TEXT

4. Inspect symbols and relocations

Use -t for the symbol table. The long form is --syms:

$ llvm-objdump-20 -t /bin/ls | head -8

/bin/ls:  file format elf64-x86-64

SYMBOL TABLE:

A stripped executable may have few or no useful regular symbols. That is a property of the input, not evidence that the command failed. Dynamic symbols can be requested with -T or --dynamic-syms. Relocation entries use -r or --reloc; dynamic relocations use -R or --dynamic-reloc.

When you need the broadest format-specific overview, -x or --all-headers includes headers, relocations and the symbol table. It can be long, so prefer the narrower command while investigating a specific question.

5. Disassemble a small, known section

Start with one section and suppress instruction bytes if you want a compact listing. The installed file's .init section begins at 0x4000:

$ llvm-objdump-20 -d --section=.init --no-show-raw-insn --disassembler-color=off /bin/ls

/bin/ls:  file format elf64-x86-64

Disassembly of section .init:

0000000000004000 <.init>:
    4000:       endbr64
    4004:       subq    $0x8, %rsp
    4008:       movq    0x1efb9(%rip), %rax
    400f:       testq   %rax, %rax
    4012:       je      0x4016 <.init+0x16>
    4014:       callq   *%rax
    4016:       addq    $0x8, %rsp
    401a:       retq

-d disassembles executable sections. -D disassembles all sections, including ones that may contain data, so use it only when that broader interpretation is intentional. --no-show-raw-insn hides instruction bytes; remove it when you need to compare the encoded bytes with another tool. The explicit colour setting keeps captured output predictable.

For source-backed binaries, -S or --source interleaves source with disassembly and implies --disassemble. -l or --line-numbers adds source line numbers and also implies disassembly. These options depend on usable debug information and source paths, so an absent source listing is not automatically a tool failure.

6. Focus on an address range

After checking section addresses, use --start-address and --stop-address to limit disassembly, relocations and symbols. The range must overlap the selected content:

$ llvm-objdump-20 -d --section=.init --no-show-raw-insn --disassembler-color=off \
    --start-address=0x4000 --stop-address=0x401b /bin/ls | head -16

/bin/ls:  file format elf64-x86-64

Disassembly of section .init:

0000000000004000 <.init>:
    4000:       endbr64
    4004:       subq    $0x8, %rsp
    4008:       movq    0x1efb9(%rip), %rax

An empty result or a warning about no section overlap usually means the addresses came from the wrong file, were copied as decimal rather than hexadecimal, or do not belong to the selected section. Re-run -h and use the VMA shown there. Address limits are filters, not a way to repair an invalid or truncated binary.

7. Choose the disassembly syntax deliberately

For x86 input, LLVM emits AT&T syntax by default. Select Intel syntax explicitly when comparing output with Intel-style documentation or another tool:

$ llvm-objdump-20 -d --section=.init --no-show-raw-insn \
    --x86-asm-syntax=intel /bin/ls | head -14

The manual also accepts --x86-asm-syntax=att. These settings change presentation, not the bytes in the input. For another target, use --arch-name or --triple only when the file and the intended target require it. The available target list is printed by --version.

Common traps and safe boundaries

Done means