Home / Alt manpages / readelf(1)

  • readelf(1)
  • User command
  • linux

Inspect ELF Binaries with readelf Without Guessing

You will finish with a small set of readelf commands for answering the usual questions about an ELF executable: what architecture it targets, how it is laid out, which libraries it needs, and which symbols it exposes. 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 to inspect. The commands only read their input, so they do not need elevated privileges and do not alter the binary. Use an input file you trust when dumping debug or string data: readelf reports bytes from the file, but that output can still contain misleading or sensitive text.

1. Confirm the binary and the tool version

Use an absolute path for a repeatable check. This avoids accidentally calling another 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

Replace /usr/bin/readelf below if you intentionally need a cross-target alias. The alias changes the target defaults, while the inspection options remain the same. If you want to identify the package that supplied the command, run:

$ 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 can change formatting.

2. Read the ELF header first

Start with the file header. It answers the questions most likely to prevent 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

The Class is 32-bit or 64-bit, Data gives the byte order, and Machine identifies the target architecture. A DYN type is normal for a position-independent executable on a modern Linux system. It is not the same thing as a shared library, so use the program headers and dynamic section for more context.

Do not infer the architecture from the filename. A file called tool can target a different machine, and a cross-compiled output can be perfectly valid while being unusable by the host kernel.

3. Compare sections with loadable segments

Sections describe the linker's view of an 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 was placed in the file. Use --section-details when you also need flags and the relationships between sections. It implies --sections.

Segments describe what the loader maps into memory. Show them with --program-headers, also available as --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 useful distinction when investigating loader behaviour: a section can exist in the file without being part of a loadable segment. Look at 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 symbol table used at runtime. Add --wide so long names and columns 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, such as name@@VERSION, marks the default version used for an unversioned reference. C++ names are deliberately left mangled by default; add --demangle when human-readable function names matter.

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]

The NEEDED entries are the libraries named by the ELF file. They are not a complete guarantee that 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 commonly 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 to display relocation entries, especially when diagnosing an object file or link failure. Use --lint to ask readelf to report possible problems. With no other dumping option, lint examines the file contents and still prints the relevant information:

$ /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 that a program is safe or correct. A non-zero status or diagnostic needs investigation 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 when it no longer contains useful information. That removal is the only state-changing command in this guide, and it is irreversible for that temporary file.

6. Avoid the common output 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.
  • On 64-bit files, readelf normally wraps long section and segment lines at 80 columns. Add --wide when copying output into a ticket or comparing columns.
  • Dump selectors such as --hex-dump=.rodata and --string-dump=.rodata accept either a section number or a name. A name can match more than one section, so confirm the section table first.
  • Debug dumps can be very large. Begin with a focused selector such as --debug-dump=info, and remember that linked debug files or debuginfod may be consulted by the installed build. Avoid unexpected network access by using --debug-dump=do-not-use-debuginfod where supported.

If an example fails with "Not an ELF file" or a similar diagnostic, check the input with file -- /path/to/input. A shell script, compressed archive or text file needs a different tool. If a file is an archive containing ELF objects, readelf can inspect archives, but use the archive index option, --archive-index, when your immediate question is which symbols the archive advertises.

Done means

  • You confirmed the exact readelf executable and version.
  • You used the ELF header to verify class, byte order and architecture.
  • You separated sections, loadable segments, symbols and dynamic dependencies.
  • You used --wide for stable tabular output and treated warnings as evidence to investigate.
  • You removed any temporary report that is no longer needed.