Inspect AArch64 Symbols with gcc-nm-13
aarch64-linux-gnu-gcc-nm-13 lists symbols from an AArch64 object, executable or shared library, with the right GCC linker plugin already wired in. Allow about ten minutes if the file is ready to hand. Every command below only reads the input and prints a report, so none of it normally needs elevated privileges.
The route
Jump straight to the step you need, or tick off Done means at the end.
1. Check the installed wrapper
The local manual pages describe the same command: gcc-nm wraps nm and adds the right --plugin option for GCC 13. The -13-suffixed name is the canonical installed form; aarch64-linux-gnu-gcc-nm is its unversioned alias.
$ command -v aarch64-linux-gnu-gcc-nm-13
/usr/bin/aarch64-linux-gnu-gcc-nm-13
$ aarch64-linux-gnu-gcc-nm-13 --version
GNU nm (GNU Binutils for Ubuntu) 2.42
On this machine the packages are gcc-13-aarch64-linux-gnu 13.3.0-6ubuntu2~24.04.1cross1 and binutils-aarch64-linux-gnu 2.42-4ubuntu2.10. The wrapper reports the nm implementation, so that version line is not a separate GCC compiler version.
Checkpoint
The command should resolve to the cross-toolchain wrapper and report GNU nm. If it's missing, install the distribution's AArch64 GCC and Binutils packages. Don't reach for sudo just to inspect a file you can already read.
2. List symbols from one input
Pass a file after the options. With no input, GNU nm quietly assumes a.out, an easy trap when you meant to inspect a specific build artefact:
$ aarch64-linux-gnu-gcc-nm-13 /path/to/program-or-library
The default BSD-style report is a value, a one-character type, and a symbol name. Uppercase types are usually global, lowercase usually local. T/t is code in a text section, D/d is initialised data, U is an undefined reference, though exact types depend on the object format.
For something more script-friendly, use POSIX format:
$ aarch64-linux-gnu-gcc-nm-13 -P /path/to/program-or-library | sed -n '1,12p'
symbol_name T 0000000000000000 0000000000000030
Address and size will differ for your file. The sed limit here just stops an exploratory command filling your terminal; drop it once you need the full list.
3. Narrow the report before you read it
Start with one filter, add a second only once the result is still too big. These are nm options passed through by the wrapper:
- -g, --extern-only shows external symbols.
- -u, --undefined-only shows unresolved references.
- -U, --defined-only shows defined symbols.
- -D, --dynamic selects the dynamic symbol table in a shared or other dynamic file.
- -C, --demangle makes C++ names readable.
- -A, --print-file-name prefixes symbols with their input file, handy for several files or archive members at once.
For example, checking exported dynamic symbols without dumping the whole static symbol table:
$ aarch64-linux-gnu-gcc-nm-13 -D -g -P /path/to/libexample.so | sed -n '1,20p'
exported_name T 0000000000000000 0000000000000040
Checkpoint
Reach for -u when hunting dependencies, -D when checking a shared library's runtime interface, and neither when you need the full normal table. An empty result can just mean the filter worked, not that the file is broken.
4. See what the wrapper actually changes
The useful difference isn't a new symbol format, it's that the wrapper invokes the target nm with a GCC linker plugin path already set. Tracing the help command on this install shows the extra argument as --plugin /usr/libexec/gcc-cross/aarch64-linux-gnu/13/liblto_plugin.so, which helps nm understand link-time-optimised inputs where the plugin supports them:
$ strace -f -e execve -o /tmp/gcc-nm-trace.log \
aarch64-linux-gnu-gcc-nm-13 -h
$ grep -- '--plugin' /tmp/gcc-nm-trace.log
execve(".../aarch64-linux-gnu/bin/nm", [..., "--plugin", ".../13/liblto_plugin.so", ...], ...)
That trace is optional and needs strace; the abbreviated paths are deliberate since the exact multiarch path is installation-specific. Don't confuse nm's --plugin with the linker's -plugin, and don't copy a plugin into a system directory to make this work, the wrapper already supplies the configured one.
Common traps
Start by identifying the input and its format:
$ file /path/to/program-or-library
$ test -r /path/to/program-or-library && echo readable
$ aarch64-linux-gnu-gcc-nm-13 -V
- "No symbols" reported: it may be a stripped executable, a file with only dynamic symbols, or simply the wrong input. Try
-Dfor a dynamic object, or check the unstripped artefact. - Unrecognised format: confirm it's an AArch64 ELF file rather than a source file, an archive built for another target, or plain text.
- You passed a manual --plugin: that replaces the wrapper's own plugin argument, so a missing or incompatible one can break a previously working command. Drop the override and retry with the wrapper's default:
$ aarch64-linux-gnu-gcc-nm-13 /path/to/program-or-library
Don't run nm as root to dodge a permissions error, fix the file permissions or get a readable copy instead. Nothing here changes the input, the linker plugin, or any symbol table, so there's no undo beyond deleting an optional trace file from /tmp.
Done means
- Wrapper confirmed: the GCC 13 AArch64 wrapper resolves and reports the installed GNU nm version.
- Right file targeted: you passed the intended ELF object, executable or library explicitly.
- Right symbol set: you chose normal, dynamic, defined, undefined or external symbols on purpose.
- C++ names handled: demangling was used only when readable names were actually needed.
- Nothing touched: the input, plugin installation and build tree are unchanged.