Read Object Symbols with nm Without Guessing
You will finish with a small, repeatable workflow for answering three useful questions about a compiled object: which symbols it defines, which symbols it still needs, and which names or functions are taking the most space. The examples use GNU nm from GNU Binutils 2.42, installed here as binutils-common:amd64 version 2.42-4ubuntu2.5. The architecture-qualified aliases on this host are provided by the corresponding AArch64 and x86-64 Binutils packages.
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, a compiler that can produce an object file, and read access to that file. nm only reads its inputs in the examples below. It does not rewrite an object, relink a program or change a service, so no elevated privileges are needed unless the file itself is unreadable.
1. Check the installed command
Start with the executable that will do the inspection:
$ command -v nm
/usr/bin/nm
$ nm --version
GNU nm (GNU Binutils for Ubuntu) 2.42
The command accepts one or more object files. With no file argument it assumes a.out, which is an easy distraction: an error about a missing a.out usually means the object path was omitted, not that the toolchain has failed.
Checkpoint: keep the exact path to the object you intend to inspect. Do not substitute an executable or a source file and assume the symbol view will mean the same thing.
2. Make a small object to inspect
If you already have an object file, skip to the next step. Otherwise, create a harmless test source in a temporary directory and compile it without linking:
$ mkdir -p /tmp/nm-demo
$ cat > /tmp/nm-demo/example.cpp <<'EOF'
extern int missing_symbol;
int global_value = 7;
static int local_value = 3;
int add_values(int a, int b) {
return a + b + global_value + local_value + missing_symbol;
}
EOF
$ g++ -g -c /tmp/nm-demo/example.cpp -o /tmp/nm-demo/example.o
The -c option stops after compilation, leaving an ELF relocatable object. The declaration of missing_symbol is deliberate: it gives nm an undefined symbol to report without requiring a failed link. The source and object are disposable. When you are finished, remove only this known temporary directory with rm -rf /tmp/nm-demo; do not broaden that path.
3. Read the default symbol table
Run nm against the object:
$ nm /tmp/nm-demo/example.o
0000000000000000 T _Z10add_valuesii
0000000000000004 d _ZL11local_value
0000000000000000 D global_value
U missing_symbol
In the default BSD format, a defined symbol has an address, a one-character type and a name. An uppercase type normally denotes a global or external symbol; lowercase normally denotes a local symbol. Here T is code in the text section, D is initialised data, and d is local initialised data. U means undefined: this object refers to missing_symbol, but another object or library must provide it during linking.
The address is relative to this relocatable object, not a promise about the final address in a running program. Treat it as an object-file value until the linker has produced an executable or shared library.
4. Isolate unresolved references
Use -u, also spelled --undefined-only, when you are diagnosing a link failure or checking what a component expects from its dependencies:
$ nm --undefined-only /tmp/nm-demo/example.o
U missing_symbol
The inverse, -U or --defined-only, hides unresolved references. These filters apply to each input object. They do not resolve anything and they do not prove that a final link will succeed. A symbol can also be supplied by a library that has not yet been included in the link command.
For a set of objects, include the file name on every row so that a result is not detached from its input:
$ nm --print-file-name --undefined-only build/*.o
build/worker.o: U pthread_create
build/worker.o: U queue_push
build/main.o: U worker_start
File names and symbol names vary. The useful check is that every unresolved name is associated with the object that refers to it.
5. Demangle C++ names only when it helps
GNU C++ compilers encode function signatures in object symbols. Add -C or --demangle for a readable view:
$ nm --demangle /tmp/nm-demo/example.o
0000000000000000 T add_values(int, int)
0000000000000004 d local_value
0000000000000000 D global_value
U missing_symbol
Demangling changes the display, not the object. It is useful when searching for an overloaded C++ function, but keep the unmangled output when you need the exact linker name. The installed build enables a recursion limit during demangling. The manpage warns that disabling it with --no-recurse-limit can exhaust the stack on hostile or unusually nested names, so leave the default in place unless you have a specific, controlled reason to change it.
6. Find large symbols
For ELF objects, --size-sort orders symbols by recorded size. Add -S or --print-size if you want both the value and size in the usual BSD output:
$ nm --print-size --size-sort /tmp/nm-demo/example.o
0000000000000004 0000000000000004 d _ZL11local_value
0000000000000000 0000000000000004 D global_value
0000000000000000 0000000000000030 T _Z10add_valuesii
The final column is the symbol name and the preceding hexadecimal field is its size. This is a quick way to spot unexpectedly large functions or data, but it is not a complete binary-size report. Padding, section alignment, discarded sections and shared-library layout are outside this table. Also, size sorting does not work with --undefined-only, because an unresolved symbol has no size.
7. Choose a format for scripts
The default BSD format is convenient for people. For a script, choose deliberately rather than parsing columns that may move:
$ nm --format=posix /tmp/nm-demo/example.o
_Z10add_valuesii T 0000000000000000
_ZL11local_value d 0000000000000004
global_value D 0000000000000000
missing_symbol U
--format=posix is also available as -P. Other documented formats are bsd, sysv and just-symbols. If you only need names, -j or --format=just-symbols removes address and type information. Record the chosen format alongside a parser, and test it against the exact Binutils version used in your build.
8. Inspect a different target carefully
Architecture-qualified aliases select a target-specific nm:
$ command -v aarch64-linux-gnu-nm
/usr/bin/aarch64-linux-gnu-nm
$ aarch64-linux-gnu-nm --demangle /tmp/nm-demo/example.o | sed -n '1,4p'
0000000000000000 T add_values(int, int)
0000000000000004 d local_value
0000000000000000 D global_value
U missing_symbol
The example object is still x86-64, so the output happens to be readable here. That does not make it an AArch64 object. For a cross-built file, use the nm whose target matches the file and confirm the result with a second tool such as file or readelf -h. If nm reports an unsupported format or no symbols, stop and check the input path, architecture and whether the file is stripped or generated for a format that this build does not support.
Done means
- You checked the installed GNU nm version and the object path.
- You can distinguish code, data, local symbols and undefined references in BSD output.
- You used
-uto isolate unresolved dependencies and-Cto read C++ names. - You used
-S --size-sortfor a quick symbol-size comparison, with its limits understood. - You selected a stable output format before automating a parser.
- You matched a cross-target alias to the file's actual object format.