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.
- You may need to inspect third-party
.sofiles when source code is unavailable.
About symbol source: a final .so file usually does not preserve a simple “this symbol came from this static library” label in a way these tools can always show directly. If symbols were pulled in from .a archives during linking, the linker usually copies the needed object code into the final shared library. After that, the resulting .so mainly contains the final symbol information, not a full origin history. In some cases, you can infer origin by:
- comparing symbols from the
.sowith symbols in candidate.afiles - examining linker map files generated during the build
- inspecting build logs or linker command lines
So, listing symbols is straightforward, but reliably identifying their original source is often a build-time investigation rather than something stored directly in the .so.
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 objdump
objdump -T libexample.so
This displays the dynamic symbol table and is useful for checking what the runtime linker can see.
Beginner-friendly example
Suppose you built a shared library:
int add(int a, int b) {
return a + b;
}
static int helper(int x) {
return x * 2;
}
int version = 1;
Compile it:
gcc -shared -fPIC -o libmathdemo.so mathdemo.c
Inspect exported symbols:
nm -D --defined-only libmathdemo.so
You would expect to see add and version, but not helper.
Why?
addhas external linkageversionis a global variablehelperisstatic, so it is private to that source file and not exported
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
0000000000001130= address or offsetT= text section, usually executable codeadd= symbol name
Why helper is missing
helper was declared static, so it has internal linkage. It is not part of the library's exported interface.
Why some symbols may still appear as undefined
If you run:
nm -D libmathdemo.so
without --defined-only, you may also see symbols that the library expects from elsewhere, such as libc functions:
U printf
Here:
Umeans undefined in this file- the symbol must be resolved by another shared library at link or load time
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:
gcc -shared ... -Wl,-Map,libexample.map
A map file is often much more useful than the final .so when you need to know which object file or archive member contributed code or data.
Debugging symbol collisions
When two libraries define similar names, developers inspect exported symbols to understand clashes and unexpected resolution behavior.
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.ofiles
Mistake 4: Forgetting that static functions are not exported
Broken assumption:
static int helper(void) {
return 1;
}
Then expecting helper to appear in exported symbols.
It will not be exported because static gives internal linkage.
Mistake 5: Ignoring symbol stripping
If debug or nonessential symbol information has been stripped, some tools will show less information than expected.
Mistake 6: Mixing up tools and their purpose
nm -D= quick exported/dynamic symbol listingreadelf -Ws= detailed ELF symbol informationobjdump -T= dynamic symbol table view
Use the tool that matches the question you are asking.
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 | Example indicator |
|---|---|---|
| Exported/defined | Implemented in the .so and available to others | T, D, B |
| Undefined | Referenced by the .so but provided elsewhere | U |
| Local/internal | Present internally, not part of the public interface | Often absent from -D output |
nm -D vs nm
| Command | What it focuses on |
|---|---|
nm libx.so | General symbol table information |
nm -D libx.so | Dynamic symbols used by the runtime linker |
nm -D --defined-only libx.so | Exported symbols actually defined by the library |
Can these tools tell where a symbol came from?
| Question | Answer |
|---|---|
| Can they show the symbol name? | Yes |
| Can they show whether it is defined or undefined? | Yes |
| Can they always show which static library archive contributed it? | No |
| What helps with origin tracking? | Linker map files and build-time inspection |
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
staticfunctions are not exported- undefined symbols are not exports
- stripped binaries may show fewer symbols
- dynamic symbols are what matter most for shared-library exports
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?
Start with nm -D --defined-only. If you need more details, use readelf -Ws.
Does GCC version matter for listing symbols?
Usually not much for inspection itself. The main tools are ELF/binutils tools such as nm, readelf, and objdump, not GCC directly.
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
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
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.