Home / Alt manpages / llvm-nm-20(1)

  • llvm-nm-20(1)
  • User command
  • linux

Inspect LLVM Symbols Safely with llvm-nm-20

A filter that returns nothing can look exactly like a broken command, and llvm-nm-20 is full of moments like that. Point it at an object, archive, executable or LLVM bitcode file and it tells you which symbols the file provides, requires, or hides behind a filter you asked for. It only ever reads its input, so ordinary user privileges are enough for anything you can already read. Allow about 10 minutes for a first inspection.

Before you start

This guide targets Ubuntu's llvm-20 package. The installed executable here reports LLVM 20.1.8. You need a readable object, archive or bitcode file; the examples create temporary input with clang-20, so substitute your own path when inspecting a real build artefact. No command below needs sudo.

Checkpoint

Confirm the versioned executable is the one on your path.

$ llvm-nm-20 --version
Ubuntu LLVM version 20.1.8

1. Inspect an object file

An object file is the clearest starting point. Compile a small source file into an object, then ask for its default symbol table. This test creates files under /tmp, not in your project.

$ work=$(mktemp -d /tmp/llvm-nm.XXXXXX)
$ cat > "$work/sample.c" <<'EOF'
static int local_value = 7;
int exported_value = 11;
int add_value(int x) { return x + local_value + exported_value; }
EOF
$ clang-20 -g -c "$work/sample.c" -o "$work/sample.o"
$ llvm-nm-20 "$work/sample.o"
0000000000000000 T add_value
0000000000000000 D exported_value
0000000000000004 d local_value

The default is BSD format. Each record has an address when one is meaningful, a type letter and a name. Uppercase letters normally identify global or external symbols; lowercase letters identify local ones. Here T is code, D is writable data and d is local writable data. Addresses in an object file are not final load addresses.

2. Narrow the list for a specific question

Filtering beats scanning a long table by eye. Use --defined-only when checking what the file supplies, --undefined-only when hunting for unresolved references. The sample has no undefined symbols, so the second command below prints nothing and still finishes successfully, that is the surprising bit worth remembering.

$ llvm-nm-20 --defined-only "$work/sample.o"
0000000000000000 T add_value
0000000000000000 D exported_value
0000000000000004 d local_value
$ llvm-nm-20 --undefined-only "$work/sample.o"
$ echo $?
0

For scripts or a quick membership check, print names only. That avoids treating addresses as stable data:

$ llvm-nm-20 --format=just-symbols "$work/sample.o"
add_value
exported_value
local_value

Use --extern-only for symbols reachable from other files, --no-sort when the input order matters, or --numeric-sort when address order is the useful view. Do not assume a missing name proves the code is absent until you have checked whether one of these filters is what changed the view.

3. Make output useful in a build log

When several inputs are involved, --print-file-name puts the source filename before each record, usually the least confusing form for a CI log or a directory-wide comparison.

$ llvm-nm-20 --defined-only --print-file-name "$work/sample.o"
/tmp/llvm-nm.XXXXXX/sample.o: 0000000000000000 T add_value
/tmp/llvm-nm.XXXXXX/sample.o: 0000000000000000 D exported_value
/tmp/llvm-nm.XXXXXX/sample.o: 0000000000000004 d local_value

That directory suffix is generated, so your own path will differ. For C++ or other mangled names, add --demangle to show human-readable names. Demangling only changes presentation, it does not prove that two differently named source-level functions have compatible types.

If addresses are the wrong unit, --print-size adds symbol sizes where the object format supports them. --radix=d, --radix=x and --radix=o pick decimal, hexadecimal or octal addresses. These are reporting choices, not transformations of the input.

4. Inspect archives without confusing the index

Static libraries are archives of object files. Build one from the sample, then ask for both its archive map and its member symbols.

$ llvm-ar-20 rcs "$work/libsample.a" "$work/sample.o"
$ llvm-nm-20 --print-armap "$work/libsample.a"
Archive map
add_value in sample.o
exported_value in sample.o


sample.o:
0000000000000000 T add_value
0000000000000000 D exported_value
0000000000000004 d local_value

The archive map is the index used to find members for externally requested symbols, separate from the member records printed below it. Omit --print-armap if you only need the symbols themselves. When a link cannot find a symbol in a library, this view helps you tell an absent member apart from an index or link-order problem.

5. Read LLVM bitcode and standard input

LLVM bitcode is accepted directly. Bitcode symbols have no final addresses, so this output prints a row of dashes in the address column even for definitions.

$ clang-20 -emit-llvm -c "$work/sample.c" -o "$work/sample.bc"
$ llvm-nm-20 "$work/sample.bc"
---------------- T add_value
---------------- D exported_value
---------------- d local_value

That dash is expected, not evidence the symbol is undefined. You can also use - as the filename to read one input from standard input.

$ llvm-nm-20 - < "$work/sample.o" | head -n 3
0000000000000000 T add_value
0000000000000000 D exported_value
0000000000000004 d local_value

The no-argument default is different again: without a filename, llvm-nm-20 quietly tries to inspect a.out. Pass the filename explicitly in any command you plan to repeat, so an old executable or a missing file never distracts you.

Common traps and safe recovery

Do not mistake a quiet command for a failed inspection. An empty result from an intentional filter can be entirely valid. Re-run without the filter, then check the exit status. A missing or unreadable input, by contrast, is a genuine error and returns a non-zero status.

$ llvm-nm-20 "$work/no-such-file"
llvm-nm-20: error: ...: No such file or directory
$ echo $?
1

Do not pass GNU nm options by habit. LLVM documents that it does not support the full GNU option set. Check llvm-nm-20 --help before copying a command from a different toolchain. The versioned command also supports response files with @FILE when a generated argument list is easier to manage.

Clean up only the temporary test data. The inspection commands change nothing themselves. If you created the sample directory, remove that exact directory once you have checked its contents. Never point a recursive removal command at a project path just because the examples here used mktemp.

Done means

  • Version confirmed. You confirmed the installed llvm-nm-20 version.
  • Records read correctly. You can read BSD records and tell global type letters from local ones.
  • Filters applied. You can filter defined, undefined, external, sorted and name-only output.
  • Archives and bitcode understood. You can identify archive maps and understand dash-filled bitcode addresses.
  • Exit status checked. You checked the exit status whenever output was empty or an input was missing.