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.
The route
Jump straight to the step you need, or tick off Done means at the end.
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
--allis 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
--widewhen you are pasting output into a ticket or comparing columns. - Section names are not unique.
--hex-dump=.rodataand--string-dump=.rodatatake 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-debuginfodwhere 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
--widefor 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.