Inspect ELF Binaries with llvm-readelf-18

An unfamiliar ELF binary lands on your desk, and llvm-readelf-18 pulls its headers, sections, symbols and relocations apart to show you what it is. The same workflow asks for JSON when another tool needs to consume the result. The examples use llvm-readelf-18 from Ubuntu package llvm-18, version 18.1.3-1ubuntu1, whose executable reports LLVM version 18.1.3.

Allow about fifteen minutes. You need a shell, the LLVM 18 package and a readable ELF object such as an executable or shared library. These commands do not alter the input and do not need elevated privileges. Use sudo only if ordinary file permissions prevent you reading a binary, and prefer copying it to a permitted working directory over broadening permissions.

1. Check the installed command

Confirm that the command in your path is the one you intend to use:

$ command -v llvm-readelf-18
/usr/bin/llvm-readelf-18
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-18
llvm-18 1:18.1.3-1ubuntu1
$ llvm-readelf-18 --version
Ubuntu LLVM version 18.1.3
  Optimized build.

The manpage describes this program as a low-level object-file reader. It accepts one or more input files, and a single hyphen means that it reads an object from standard input. Keep the version check in bug reports because output details and supported formats can differ between LLVM releases.

Checkpoint: set a shell variable to a real file and make sure it is readable. This is an ordinary command, not a privileged operation:

ELF_FILE=/bin/ls
test -r "$ELF_FILE" && printf 'readable: %s\n' "$ELF_FILE"

Do not substitute an untrusted filename into an unquoted command. Quoting the variable keeps whitespace and shell metacharacters in the filename from changing the command line.

2. Read the ELF file header

Start with -h, also spelled --file-header. It reports the file class, byte order, machine, entry point and the locations and counts of the program and section headers:

$ llvm-readelf-18 -h "$ELF_FILE"
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Entry point address:               0x6D30

The exact addresses and some descriptive text belong to the file, so your output will not necessarily match this example. For a first compatibility check, pay attention to Class, Data and Machine. A 32-bit object and a little-endian 64-bit object are not interchangeable merely because both use ELF.

Use --headers or -e when you want the file header, program headers and section headers together. This is a display choice only. It does not execute the input file.

3. Inspect loadable segments and sections

Program headers describe how a loader maps an ELF object into memory. Section headers describe the linker's and tools' view of the file. Request them separately when you are narrowing down a problem:

$ llvm-readelf-18 --program-headers "$ELF_FILE" | head -18
Elf file type is DYN (Shared object file)
Entry point 0x6d30
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x00000040 0x00000040 0x0002d8 0x0002d8 R   0x8

--program-headers, --segments and -l are equivalent names. Piping to head is safe for this read-only report, but it can hide a diagnostic printed after the part you kept. When diagnosing a failure, run the command without the pipe and check its status.

List all sections with -S or --sections:

$ llvm-readelf-18 -S "$ELF_FILE" | head -14
There are 31 section headers, starting at offset 0x22428:

Section Headers:
  [Nr] Name              Type            Address          Off    Size ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000318 000318 00001c 00   A  0   0  1
  [ 5] .gnu.hash         GNU_HASH        00000000000003b0 0003b0 000050 00   A  6   0  8

Use -t or --section-details as an alternative when you need more detail about section flags and relationships. Use --section-mapping to see how sections are assigned to segments. These views answer different questions: a section can exist in the file without being loaded into a process.

Checkpoint: record the section name, offset and size before extracting or comparing content. A section name alone is not enough to identify its bytes in a script.

4. Dump strings or bytes from a named section

Use -p for strings and -x for hexadecimal bytes. Both options accept a section index or section name. The following reads the dynamic string table from the example file:

$ llvm-readelf-18 --string-dump=.dynstr "$ELF_FILE" | head -12

String dump of section '.dynstr':
  [     0]  
  [     1]  __ctype_toupper_loc
  [    16]  getenv
  [    23]  fgetfilecon
  [    34]  sigprocmask

For bytes, specify a section and keep the output manageable:

$ llvm-readelf-18 --hex-dump=.interp "$ELF_FILE"

Hex dump of section '.interp':
  0x00000318 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/linux-
  0x00000328 7838362d 36342e73 6f2e3200          x86-64.so.2.

Compressed sections need -z or --decompress together with -x or -p. Without that combination, a compressed section is not automatically expanded. Never treat a string dump as proof that the string is used at runtime; it only shows bytes present in the selected section.

5. Inspect symbols and relocations

Use -s or --symbols for the symbol table. With the default GNU output style on ELF, the dynamic symbol table is included as well:

$ llvm-readelf-18 --symbols "$ELF_FILE" | head -14

Symbol table '.dynsym' contains 127 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_toupper_loc
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTable

Names may be difficult to read in C++ programs. Add -C or --demangle to request demangled names. The installed manpage says demangling is on by default for this command's normal output, while --no-demangle disables it. If a script needs stable names, choose the setting explicitly.

Use -r or --relocations to display relocation entries. Add --expand-relocs when the compact table is too dense. A relocation report describes records in the file; it does not apply them or rewrite the object.

6. Produce JSON for a script

LLVM 18 supports --elf-output-style=JSON for machine consumption. Start with the header so the output is small enough to inspect:

$ llvm-readelf-18 --elf-output-style=JSON -h "$ELF_FILE" | head -c 220
[{"FileSummary":{"File":"/bin/ls","Format":"elf64-x86-64","Arch":"x86_64","AddressSize":"64bit","LoadName":"<Not found>"},"ElfHeader":{"Ident":{

JSON output is a format selection, not a general conversion of every text report. The option accepts LLVM, GNU and JSON; GNU is the default, while LLVM is an expanded structured text style. Add --pretty-print with JSON when a person needs to read the result. For automation, send the complete command output to a file and parse it with a JSON parser rather than matching columns:

llvm-readelf-18 --elf-output-style=JSON -h "$ELF_FILE" > elf-header.json
test -s elf-header.json && jq . elf-header.json > /dev/null

The redirection creates or truncates elf-header.json. If that file is valuable, choose a new name first, or write to a temporary file and rename it only after the command and parser succeed. The input binary is never modified by this workflow.

7. Handle failures without guessing

The command returns zero under normal operation and non-zero when it encounters an error. Capture the status immediately:

if llvm-readelf-18 -h "$ELF_FILE" > header.txt; then
    printf '%s\n' 'ELF header read successfully'
else
    status=$?
    printf 'llvm-readelf-18 failed with status %s\n' "$status" >&2
    rm -f header.txt
    exit "$status"
fi

A missing file, a non-ELF input and a damaged object can all produce failure, but they are not the same diagnosis. Check the path and type first:

$ test -r "$ELF_FILE" && file "$ELF_FILE"
/bin/ls: ELF 64-bit LSB pie executable, x86-64, ...

Do not repair a failed read by running as root. First confirm that you selected the right file, then inspect its permissions and copy it to a controlled directory if access is the only problem. Keep an original copy when investigating possible corruption. No llvm-readelf option in this guide changes a binary, so there is no command-specific rollback to perform.

Done means