Home / Alt manpages / nm(1)

  • nm(1)
  • User command
  • linux

Read Object Symbols with nm

A link error naming a missing symbol stops being cryptic once you point nm at the object file. This guide gets you 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 come from the matching AArch64 and x86-64 Binutils packages.

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 you left out the object path, not that the toolchain has failed.

Checkpoint

Keep the exact path to the object you mean to inspect. Do not substitute an executable or a source file and assume the symbol view means 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 needing 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 one.

  • T is code in the text section.
  • D is initialised data.
  • d is local initialised data.
  • U means undefined: this object refers to missing_symbol, but another object or library must supply 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 a final link will succeed. A symbol can also be supplied by a library that has not yet been added to the link command.

For a set of objects, include the file name on every row so a result never gets 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 will 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 you are hunting 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 alone 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 hexadecimal field before it 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 all sit outside this table. Size sorting also 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 strips out address and type information. Record the format you chose alongside your 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 this build does not support.

Done means

  • Version and path checked: you confirmed the installed GNU nm version and the object path.
  • Symbol types understood: you can distinguish code, data, local symbols and undefined references in BSD output.
  • Dependencies isolated: you used -u to find unresolved dependencies and -C to read C++ names.
  • Size checked: you used -S --size-sort for a quick symbol-size comparison, with its limits in mind.
  • Format chosen deliberately: you selected a stable output format before automating a parser.
  • Target matched: you matched a cross-target alias to the file's actual object format.