elf-inspection · git:20260905.fd53e99 · 2026-09-05 · sha256 25a26cb86348511f

elf-inspection git:20260905.fd53e99A

Immutable. This exact content is served forever at /api/v1/blob/25a26cb86348511f.

---
name: elf-inspection
description: 'Use when examining ELF binaries with readelf, objdump, nm, or ldd: dependencies, symbols, sections, relocations, build IDs, or hardening. Not for modifying binaries: use binutils.'
---

# ELF inspection

An ELF binary reports its own structure. Every question about dependencies, symbols, sections, or hardening is one `readelf`, `objdump`, `nm`, or `ldd` query away. This skill is read-only; transformation of binaries belongs to `binutils`.

## Contract

| Field | Bound contract |
|---|---|
| Trigger | The task inspects what a binary depends on, why it is large, which symbols it exports or needs, whether it is PIE or RELRO-hardened, or why a symbol is undefined at link time or load time. |
| Authority | Read-only. The commands read the named binaries and print to stdout; no file is written, so no rollback applies. No remote mutation. |
| Side effect | None. Output goes to the chat or the terminal. |
| Done | The question about the binary is answered with the matching tool output quoted, and every quoted fact comes from the binary itself. |

## Inputs

- The binary or library to inspect: required.
- The question: required. Dependency, symbol, size, hardening, relocation, or build identity.
- A symbol name or address: optional, narrows the query.

## Procedure

1. Classify the file. `file` reports architecture, linkage, and stripped state; `size` reports text, data, and bss. Done when: the type and the stripped state are known, because they select the tools for later steps.

```bash
file prog
size --format=sysv prog
```

2. List dynamic dependencies with `ldd`. A `not found` row names the deployment gap. Done when: every `DT_NEEDED` entry resolves, or the missing one is named.

```bash
ldd ./prog
ldd -v ./prog          # include version requirements
```

`ldd` executes the loader against the binary. Never run it on an untrusted binary; use `readelf -d` for those.

3. Query symbols with `nm`. `-D` reads the dynamic table, `-C` demangles, `-u` lists what the binary needs. Done when: the symbol is found with its type, or its absence is proven.

```bash
nm -D ./libfoo.so            # exported dynamic symbols
nm -C prog                   # demangled
nm -u prog                   # undefined symbols
nm -S --defined-only prog    # with sizes
```

Type codes: `T`/`t` code, `D`/`d` initialized data, `B`/`b` bss, `R`/`r` read-only data, upper for global and lower for local, `U` undefined, `W`/`w` weak, `V` weak object.

4. Read structure with `readelf`. It needs no execution and parses every ELF. Done when: the requested section, segment, or table is printed.

```bash
readelf -h prog    # header: class, machine, type, entry
readelf -S prog    # sections
readelf -l prog    # program headers, segments
readelf -d prog    # dynamic section, raw form of ldd
readelf -s prog    # symbol tables
readelf -r prog    # relocations
readelf -n prog    # notes, build ID
readelf --debug-dump=info prog   # DWARF
```

5. Disassemble with `objdump`. `-S` interleaves source when the binary carries `-g`. Done when: the code around the address or symbol is shown in the requested syntax.

```bash
objdump -d -M intel prog
objdump -d -S prog
objdump -s -j .rodata prog   # hex dump of one section
objdump -p prog              # private headers, DT_NEEDED entries
```

6. Check hardening state. PIE means `ET_DYN` on an executable; full RELRO needs `GNU_RELRO` plus `BIND_NOW`; a non-executable stack means `GNU_STACK` flags `RW`, not `RWE`. Done when: each property is reported present or absent from the binary's own headers.

```bash
readelf -h prog | grep 'Type:'
readelf -l prog | grep GNU_RELRO
readelf -d prog | grep BIND_NOW
readelf -l prog | grep GNU_STACK
nm prog | grep __stack_chk_fail     # stack protector
```

`checksec --file=prog` runs the same checks in one call and is a separate install.

7. Analyze size. Rank symbols by size, then rank sections. For per-object contribution, rebuild the link with `-Wl,--print-map` or run `bloaty`, a separate install. Done when: the largest contributors are named with numbers.

```bash
size --format=sysv prog | sort -k2 -nr | head
nm -S --defined-only prog | sort -k2 -nr | head -20
```

8. Read the build ID. It identifies the exact build for `debuginfod` lookups and pairs the binary with its `.debug` file. Done when: the ID is quoted.

```bash
readelf -n prog | grep 'Build ID'
```

9. Run the diagnosis flows. Done when: the reported error traces to its cause in the binary.

- Undefined symbol at load time: `nm -D libfoo.so | grep mysymbol` to see whether the expected provider exports it, then `ldd ./prog | grep libfoo` to see whether the loader found that provider.
- Binary too large: steps 1 and 7, then decide between stripping debug info (`binutils`), removing sections, or restructuring data.
- Unexpected dependency: `readelf -d prog | grep NEEDED`, then trace who pulls it in with the link map.

## Failure and recovery

| Failure class | Behavior |
|---|---|
| `nm` prints `no symbols` | The binary is stripped. Inspect the dynamic table with `nm -D`, or point the tools at the `.debug` file or unstripped build. |
| `ldd` reports `not a dynamic executable` | The binary is static or for another architecture. Confirm with `file`, and use the triplet-prefixed tools for foreign objects. |
| Section names absent | The binary may be stripped of the section header table. Read it through program headers with `readelf -l`. |
| Two builds disagree | Compare build IDs from `readelf -n` before comparing anything else; a mismatch means the inputs differ. |

## Output

A quoted-answer report: the tool command, its relevant output lines, and the conclusion drawn from them. The command table, symbol type list, and section map are in `references/cheatsheet.md`.