Inspect Object Symbols Safely with llvm-nm-18
You will use llvm-nm-18 to inspect symbols in an object file, executable or archive, then narrow the output to the names or definitions you actually need. The examples are read-only and take about ten minutes if the file is already on disk.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Confirm the installed command
- 2. Read a normal object or executable
- 3. Inspect an archive and keep the member names
- 4. Filter the report instead of searching a wall of text
- 5. Demangle, sort and add context when needed
- 6. Read from standard input without changing the file
- 7. Diagnose failures and leave the input untouched
This guide uses Ubuntu's llvm-18 package, version 18.1.3. The installed command and its manual page are the authority for this machine. Most commands need no elevated privileges. Do not add sudo just because a file came from a system directory: use it only when your account cannot read the input.
1. Confirm the installed command
Check that the versioned executable is the one your shell will run. This avoids silently using a different nm implementation with different options.
$ command -v llvm-nm-18
/usr/bin/llvm-nm-18
$ llvm-nm-18 --version
llvm-nm, compatible with GNU nm
Ubuntu LLVM version 18.1.3
Optimized build.
Checkpoint: if command -v prints nothing, install or enable the package through your normal system administration process. Do not substitute nm until you have checked its own manual page.
2. Read a normal object or executable
Pass one or more filenames after the options. The default output is BSD-style: an address when available, a one-character type code, and the symbol name. A type such as T represents a global code symbol, while U means that the name is undefined in that file. Lowercase and uppercase variants distinguish local and global symbols for several types.
$ llvm-nm-18 /path/to/program
/path/to/program: 0000000000001139 T main
/path/to/program: U puts
The exact names and addresses depend on the file. A stripped executable may contain no ordinary symbols, and that is not automatically a failure. For example, this installed binary has no symbols:
$ llvm-nm-18 /usr/bin/true
/usr/bin/true: no symbols
If you omit every filename, llvm-nm-18 looks for a.out. That default is easy to trigger accidentally, so make the input explicit in scripts and notes.
3. Inspect an archive and keep the member names
Archives contain several object files. The output inserts a member heading before that member's symbols. This is useful when a library exports a name but you need to find the object that provides it.
$ llvm-nm-18 /usr/lib/x86_64-linux-gnu/libz.a | head -n 6
adler32.o:
00000000000006c0 T adler32
00000000000006d0 T adler32_combine
00000000000007b0 T adler32_combine64
0000000000000000 T adler32_z
Use --print-file-name, also spelled -A or -o, when several standalone files are being compared. With an archive, member headings are already part of the normal report. Use --print-armap when you also need the archive's symbol index.
Do not treat an archive report as a link map. It tells you what symbol records are present; it does not explain every later linker decision or runtime lookup.
4. Filter the report instead of searching a wall of text
Use the narrowest filter that answers the question:
--defined-onlyor-Ukeeps symbols defined in the input.--undefined-onlyor-ukeeps unresolved names.--extern-onlyor-gkeeps externally accessible definitions.--debug-symsor-aincludes symbols normally suppressed.--no-weakor-Wremoves weak symbols from the report.
For a script that only needs names, use -j. It is an alias for the just-symbols format and is easier to parse than BSD output:
$ llvm-nm-18 -j /usr/lib/x86_64-linux-gnu/libz.a | head -n 5
adler32.o:
adler32
adler32_combine
adler32_combine64
The blank line and archive member heading still matter. If a machine-readable report is part of a larger tool, choose an explicit format such as --format=posix and test the parser against the actual file types you expect.
5. Demangle, sort and add context when needed
C++ names can be hard to recognise in mangled form. Add --demangle or -C when readable function and type names are more useful than the linker spelling. The default is no demangling, so a surprising name is not evidence that the symbol is absent.
By default, symbols are sorted. Use --no-sort or -p to preserve the order encountered in the file. Use --numeric-sort, -n or -v for address order, and --size-sort when symbol size is the useful measure. --reverse-sort or -r reverses the selected ordering. Add --print-size or -S to show sizes where the object format supports them.
For source locations, --line-numbers or -l needs usable debugging information. Undefined symbols are reported at their first relocation when that information is available; without debug data, do not expect source lines.
6. Read from standard input without changing the file
A filename of - tells llvm-nm-18 to read the object or bitcode stream from standard input. This is useful in a pipeline, but keep the producer's exit status in mind: a successful llvm-nm-18 status only says that it processed its input successfully.
$ cat /usr/bin/true | llvm-nm-18 -
<stdin>: no symbols
LLVM bitcode normally has no addresses before it is linked or just-in-time compiled, so its symbol records do not receive printed addresses. If you need to distinguish input formats while diagnosing a pipeline, save a copy in a temporary working directory and inspect that file directly. Do not overwrite the original stream source merely to make it easier to inspect.
7. Diagnose failures and leave the input untouched
llvm-nm-18 only reads its input. It does not modify object files, archives or executables, so there is no undo operation for these examples. Still, treat symbol output as potentially sensitive: names can reveal internal functions, build paths or interfaces. Avoid pasting full reports into public tickets when a filtered report will do.
A missing path produces a diagnostic and a non-zero status:
$ llvm-nm-18 /tmp/definitely-not-an-llvm-file
llvm-nm-18: error: /tmp/definitely-not-an-llvm-file: No such file or directory
$ printf 'exit status: %s\n' "$?"
exit status: 1
Check the path, permissions and file type before changing anything:
$ test -r /path/to/input.o && echo readable
$ file /path/to/input.o
If the tool says no symbols, check whether the file was stripped and whether you meant to inspect dynamic symbols. --dynamic or -D selects dynamic symbols instead of normal symbols. Do not infer that a stripped file has no callable code from this one report.
Done means
- You confirmed that
llvm-nm-18is version 18.1.3 from the installedllvm-18package. - You passed an explicit input and understand that an omitted filename means
a.out. - You can distinguish defined, undefined, weak and code symbols from the type column.
- You used filtering or
-jbefore handing output to another tool. - You checked missing-file errors and kept the original input unchanged.