Home / Alt manpages / readelf(1)

  • readelf(1)
  • User command
  • linux

Read ELF Binaries Properly with readelf

A binary refuses to run and the error gives you nothing useful to go on: readelf answers the real questions. It tells you what architecture the binary targets, how it is laid out, which libraries it needs and which symbols it exposes, all without touching the file. The examples use the packaged /usr/bin/readelf from GNU Binutils 2.42, package version 2.42-4ubuntu2.10. The installed manpages are shared by readelf, aarch64-linux-gnu-readelf and x86_64-linux-gnu-readelf.

Allow about fifteen minutes. You need a shell and an ELF file. These commands only read their input, so no elevated privileges and no changes to the binary. Use a file you trust when dumping debug or string data: readelf reports raw bytes, and raw bytes can still be misleading or sensitive.

1. Confirm the binary and the tool version

Use an absolute path so you are not accidentally calling a different readelf earlier in your PATH:

$ command -v /usr/bin/readelf
/usr/bin/readelf
$ /usr/bin/readelf --version
GNU readelf (GNU Binutils for Ubuntu) 2.42

Swap in a different /usr/bin/readelf path only if you deliberately need a cross-target alias; the alias changes target defaults, not the inspection options. To find which package supplied the command:

$ dpkg-query -S /usr/bin/readelf
binutils-common:amd64: /usr/bin/readelf

Checkpoint

Record both the executable path and its version before comparing output from another machine. Binutils releases add options and change formatting between versions.

2. Read the ELF header first

The file header answers the question most likely to save you a wrong diagnosis:

$ /usr/bin/readelf --file-header /usr/bin/readelf
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 ...
  Class:                             ELF64
  Data:                              2's complement, little endian
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64

Class is 32-bit or 64-bit, Data is the byte order, and Machine is the target architecture. A DYN type is normal for a position-independent executable on a modern Linux system, but it is not the same thing as a shared library: check the program headers and dynamic section for more context. Do not infer architecture from the filename. A file called tool can target a completely different machine, and a valid cross-compiled binary can still be unusable by the host kernel.

3. Compare sections with loadable segments

Sections are the linker's view of the object file. List them with --sections:

$ /usr/bin/readelf --sections /usr/bin/readelf
There are 30 section headers, starting at offset ...

Section Headers:
  [Nr] Name              Type             Address           Offset
  [ 1] .interp           PROGBITS         ...
  [ 2] .note.gnu.property NOTE           ...

Names such as .text, .rodata, .data, .bss and .debug_info tell you what content landed in the file. Use --section-details when you also need flags and the relationships between sections; it implies --sections.

Segments are what the loader actually maps into memory. Show them with --program-headers, also spelled --segments:

$ /usr/bin/readelf --program-headers /usr/bin/readelf
Elf file type is DYN (Position-Independent Executable file)
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr   PhysAddr
  LOAD           ...      ...        ...       R E

This is the distinction that matters for loader behaviour: a section can exist in the file without being part of a loadable segment. Check segment permissions and the requested interpreter, rather than treating a section listing as a memory map.

4. Inspect symbols and shared-library requirements

Use --symbols for the ordinary symbol table and --dyn-syms for the dynamic one used at runtime. Add --wide so long names stay on one line:

$ /usr/bin/readelf --dyn-syms --wide /usr/bin/readelf
Symbol table '.dynsym' contains 104 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __strcat_chk@GLIBC_2.3.4

Names ending in @VERSION carry symbol-version information; a double at sign, name@@VERSION, marks the default version for an unversioned reference. C++ names stay mangled by default, so add --demangle when you need readable function names.

Show the dynamic section with --dynamic:

$ /usr/bin/readelf --dynamic /usr/bin/readelf
Dynamic section at offset ... contains 31 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

NEEDED entries are the libraries named by the ELF file. That is not a guarantee the program will start: the dynamic loader still has to find compatible files, resolve symbols and satisfy the host architecture.

5. Check notes, relocations and file health

Notes usually contain build IDs, ABI markers and hardening properties:

$ /usr/bin/readelf --notes /usr/bin/readelf
Displaying notes found in: .note.gnu.build-id
  Owner                Data size  Description
  GNU                  0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: ...

Use --relocs for relocation entries, especially when chasing an object file or link failure. Use --lint to have readelf flag possible problems; with no other dump option, it still examines the file and prints what it finds:

$ /usr/bin/readelf --lint /usr/bin/readelf >/tmp/readelf-lint.txt
$ printf 'readelf status: %s\n' "$?"
readelf status: 0

An empty lint report with status 0 is a useful result, not proof the program is safe or correct. Investigate any non-zero status or diagnostic against the exact file and tool version. Keep the temporary report if you need to share the warning, then remove it with rm -- /tmp/readelf-lint.txt once it has served its purpose. That removal is the only state-changing command in this guide, and it is irreversible for that temporary file.

Common traps

  • --all is broad, not magic. It combines many report types but does not enable --use-dynamic, so dynamic symbols and relocations are not automatically substituted for their ordinary counterparts.
  • 64-bit files wrap at 80 columns by default. Add --wide when you are pasting output into a ticket or comparing columns.
  • Section names are not unique. --hex-dump=.rodata and --string-dump=.rodata take a section number or a name, and a name can match more than one section, so check the section table first.
  • Debug dumps get huge fast. Start with a focused selector such as --debug-dump=info. Linked debug files or debuginfod may be consulted by the installed build; use --debug-dump=do-not-use-debuginfod where supported to avoid the unexpected network access.

"Not an ELF file" or similar? Check the input with file -- /path/to/input: a shell script, compressed archive or plain text file needs a different tool. readelf can inspect an archive of ELF objects too; use --archive-index when the immediate question is which symbols the archive advertises.

Done means

  • Executable and version confirmed: you know the exact readelf binary and its version.
  • Header checked first: you used the ELF header to verify class, byte order and architecture.
  • Layers kept separate: sections, loadable segments, symbols and dynamic dependencies were not conflated.
  • Output stayed stable: you used --wide for tabular output and treated warnings as evidence to chase down.
  • No mess left behind: any temporary report was removed once it was no longer needed.