Question
How to List Exported Symbols in a .so File with GCC and Linux Tools
Question
How can I list the symbols exported from a .so shared library file? If possible, I would also like to identify where those symbols come from, such as whether they were pulled in from a static library.
I am using GCC 4.0.2, if that affects the available tools or commands.
Short Answer
By the end of this page, you will know how to inspect a Linux .so file to see which symbols it exports, which tools are commonly used for this task, and how to interpret their output. You will also understand the limits of symbol inspection when trying to determine whether a symbol originated from a static library or another object file.
Concept
Shared libraries (.so files) contain compiled machine code plus metadata that linkers and loaders use. One important part of that metadata is the symbol table.
A symbol is a named item in compiled code, such as:
- a function
- a global variable
- a weak symbol
- an undefined external reference
When people ask for the symbols in a .so file, they usually mean one of these:
- Exported symbols: names the shared library makes available to other programs or libraries
- Dynamic symbols: symbols used by the dynamic linker at runtime
- All symbols: includes internal and non-exported symbols, if they have not been stripped
On Linux systems, the most common tools for inspecting symbols are:
nmreadelfobjdump
These tools help you answer slightly different questions:
nm -Dshows dynamic symbols in a shared objectreadelf -Wsshows symbol tables with more structured detailsobjdump -Tshows the dynamic symbol table in a readable format
Why this matters in real programming:
- You may need to confirm that your library exports the expected API.
- You may be debugging linker errors such as
undefined referenceor runtime loader errors. - You may want to reduce your public ABI by hiding internal symbols.
Mental Model
Think of a .so file like a workshop with labeled tools.
- Exported symbols are the tools placed near the front desk for others to borrow.
- Internal symbols are tools kept inside the workshop for private use.
- Undefined symbols are tools the workshop expects someone else to provide.
Tools like nm, readelf, and objdump let you walk through the workshop and read the labels.
However, if you ask, “Which factory originally manufactured this hammer?” the workshop may not keep that information. If the hammer was brought in from a static library during linking, the final workshop may only show that the hammer exists now, not its original shipping box.
Syntax and Examples
Common commands
1. List dynamic/exported symbols with nm
nm -D --defined-only libexample.so
What this does:
-Dshows dynamic symbols--defined-onlyhides undefined references and shows symbols actually defined in the.so
Example output:
0000000000001130 T add
0000000000001150 T subtract
0000000000004010 D version
Meaning:
T= symbol is in the text section, usually a functionD= initialized global data
2. Show detailed symbol information with readelf
readelf -Ws libexample.so
This shows:
- symbol name
- address
- size
- type
- binding
- visibility
- section index
This is often the best tool when you want structured information.
3. Show exported dynamic symbols with
Step by Step Execution
Consider this command:
nm -D --defined-only libmathdemo.so
Assume the library contains these symbols:
int add(int a, int b) { return a + b; }
static int helper(int x) { return x * 2; }
int version = 1;
Step-by-step
nmopenslibmathdemo.so.- The
-Dflag tells it to read the dynamic symbol table. - The
--defined-onlyflag removes symbols that are only referenced but not implemented in this library. nmprints matching symbols line by line.
Example output:
0000000000001130 T add
0000000000004010 D version
How to read one line
0000000000001130 T add
Real World Use Cases
Checking a library's public API
If you maintain a shared library, you can verify that only intended functions are exported.
Example:
- a database client library should export
db_connect - it should not accidentally export internal helper functions
Debugging linker or loader problems
If an application fails with a missing symbol error, you can inspect the .so to confirm whether that symbol is actually exported.
Example:
nm -D --defined-only libplugin.so | grep initialize_plugin
Comparing library versions
When upgrading a third-party .so, you can compare symbol lists to detect API or ABI changes.
Investigating dependency issues
If a library unexpectedly depends on another library, inspecting undefined dynamic symbols can help explain why.
Example:
nm -D libexample.so | grep ' U '
Auditing symbol visibility
Large codebases often try to keep exported symbols small to reduce ABI complexity and avoid name collisions.
Real Codebase Usage
In real projects, developers rarely inspect symbols manually just once. They usually combine symbol inspection with build-system practices.
Common patterns
Guarding the public API
Teams often use visibility controls so only intended functions are exported.
Example pattern:
__attribute__((visibility("default")))
int public_api(void);
and compile with hidden visibility by default:
gcc -fvisibility=hidden -shared -fPIC ...
Then nm -D shows a cleaner exported API.
Early validation in CI
A build script may fail if an expected symbol is missing:
nm -D --defined-only libexample.so | grep -q '^.* my_api$' || exit 1
Investigating static library contributions
If a shared library was linked with a static archive, developers often inspect both files:
nm libhelper.a
nm -D --defined-only libfinal.so
Then they compare names to infer what was pulled in.
Using linker map files
For true origin tracking, many projects generate a linker map during the build:
Common Mistakes
Mistake 1: Using nm without -D on a shared library when you only want exported symbols
Broken expectation:
nm libexample.so
This may show more than just exported runtime symbols, or less useful information if the binary is stripped.
Better:
nm -D --defined-only libexample.so
Mistake 2: Confusing undefined symbols with exported symbols
Example output:
U malloc
Beginners sometimes think this means the library exports malloc. It does not.
Umeans the symbol is used but not defined in this library.
Mistake 3: Assuming every symbol's original source is stored in the .so
A final shared library usually does not contain a simple record like:
add came from libmath.a(member.o)
To find origins, use:
- linker map files
- build logs
- inspection of original
.aand files
Comparisons
| Tool | Best for | Typical command | Notes |
|---|---|---|---|
nm | Quick symbol listing | nm -D --defined-only libx.so | Simple and fast |
readelf | Detailed ELF symbol info | readelf -Ws libx.so | Very structured output |
objdump | Dynamic symbol inspection | objdump -T libx.so | Helpful for runtime-visible symbols |
Exported vs undefined vs all symbols
| Symbol kind | Meaning |
|---|
Cheat Sheet
Quick commands
nm -D --defined-only libexample.so
readelf -Ws libexample.so
objdump -T libexample.so
Common symbol letters in nm
T= function/code in text sectionD= initialized global dataB= uninitialized global dataU= undefined symbolW= weak symbol
Most useful command for exported symbols
nm -D --defined-only yourlib.so
Show undefined dependencies too
nm -D yourlib.so
Better detail for ELF fields
readelf -Ws yourlib.so
If you need symbol origin
The final .so often does not fully record where a symbol came from.
Use:
gcc ... -Wl,-Map,output.map
Then inspect the map file.
Rules to remember
FAQ
How do I list only exported symbols from a .so file?
Use:
nm -D --defined-only libexample.so
This is the most common quick command for exported symbols.
What is the difference between nm -D and readelf -Ws?
nm -D is shorter and easier for quick checks. readelf -Ws gives more detailed ELF symbol information such as binding and visibility.
Can I tell whether a symbol came from a static library?
Not reliably from the final .so alone in all cases. Usually you need a linker map file, build logs, or comparisons against the original .a file.
Why do I see symbols marked with U?
U means undefined in this library. The symbol is referenced here but must be provided by another library at link or runtime.
Why is my function missing from the exported symbol list?
Common reasons:
- it was declared
static - symbol visibility hides it
- it was optimized away
- the binary was stripped
Which tool should I use first?
Mini Project
Description
Create a tiny shared library in C, export a couple of symbols, keep one helper function private, and inspect the resulting .so with nm and readelf. This demonstrates the difference between exported symbols, internal symbols, and undefined references.
Goal
Build a shared library and verify exactly which symbols it exports.
Requirements
- Create a C source file with at least one public function, one
statichelper function, and one global variable. - Compile the file into a shared library using GCC.
- Use
nm -D --defined-onlyto list exported symbols. - Use
readelf -Wsto inspect the symbol table in more detail. - Confirm that the
statichelper does not appear as an exported symbol.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C printf Format Specifier for bool: How to Print Boolean Values
Learn how to print bool values in C with printf, why no %b/%B specifier exists, and the common patterns to print true/false or 0/1.
Calling C or C++ from Python: Building Python Bindings
Learn the quickest ways to call C or C++ from Python, including ctypes, C extensions, Cython, and binding tools with practical examples.