nm -D 是查看动态符号表的首选命令,仅显示 STB_GLOBAL 且类型为 FUNC/OBJECT 的导出符号;误用 nm -g 会包含未进入 .dynsym 的全局符号,导致误判。

用 nm -D 快速列出动态符号表
生成 .so 文件后,最直接的方法是查它的动态符号表(.dynsym),它决定了运行时其他模块能“看到”哪些函数或变量。静态符号(.symtab)不参与动态链接,不能代表实际导出内容。
nm -D libmylib.so 是首选命令,它只显示动态链接可见的符号(即 STB_GLOBAL + STT_FUNC/STT_OBJECT)。常见错误是误用 nm -g ——它查的是全局符号,但其中很多并未进入动态符号表,会导致误判“导出了却调不到”。
- 输出中带
T(text)的是导出函数,D或B是导出数据变量 - 如果某函数名没出现在
nm -D结果里,那它根本不会被dlopen或链接器找到 - 加
-C可自动 demangle C++ 符号(如_Z12calculate_sumii→calculate_sum(int, int))
用 objdump -T 验证符号是否带 DF 标志
objdump -T libmylib.so 显示更底层信息:每个符号的值、大小、绑定(Binding)和类型(Type)。关键看第二列标志,导出符号必须有 DF(Dynamic Function)或 DO(Dynamic Object)。
例如输出中这一行:
00000000000011a0 g DF .text 0000000000000015 Base calculate_sum
说明 calculate_sum 确实被标记为动态可链接函数;而如果只看到 00000000000011a0 g F .text ...(缺 D),就表示它没进动态符号表——哪怕加了 __attribute__((visibility("default"))),也可能被链接脚本或版本脚本过滤掉了。
- 对比
nm -D和objdump -T输出,能快速定位“声明了却没导出”的问题 -
objdump -T还能发现符号地址异常(比如为 0),这往往意味着未定义引用未解决
为什么 readelf -s 的结果不能直接信?
readelf -s libmylib.so 会同时打印 .symtab(全量符号)和 .dynsym(动态符号)两个节。新手常犯的错是只扫一眼 .symtab 就以为函数已导出——但 .symtab 是调试/分析用的,对运行时链接毫无影响。
真正起作用的只有 .dynsym 节里的条目。你可以用 readelf -s libmylib.so | grep " dyn " 过滤(注意空格),或直接指定节:readelf -s --section=.dynsym libmylib.so。
-
.dynsym条目数通常远少于.symtab,这是正常现象 - 若
.dynsym为空,说明编译或链接时完全没导出任何符号(比如忘了-fvisibility=hidden配合显式标记,或 Windows 下漏了__declspec(dllexport)) - macOS 上对应命令是
nm -m或otool -Iv,行为逻辑一致但输出格式不同
导出但调用时报 undefined symbol?再查三件事
即使 nm -D 和 objdump -T 都显示符号存在,运行时仍可能报错。这时不是符号没导出,而是加载路径或 ABI 不匹配。
- 确认程序链接时用了
-lmylib且LD_LIBRARY_PATH包含库所在目录,否则dlopen根本没加载该库 - 检查符号名是否被 C++ name mangling 搞乱:C++ 函数必须用
extern "C"包裹,否则nm -D看到的是_Z...形式,调用方也得用同样 mangled 名(几乎没人手动写) - 验证架构和 ABI 一致性:
file libmylib.so看是x86_64还是aarch64,是否与主程序匹配;readelf -h查ELFCLASS和ELFDATA
符号导出只是第一步,动态库能被正确加载、解析、调用,依赖一整条工具链的协同。最容易被忽略的是:你看到的符号名,未必是链接器 runtime 实际查找的那个名字。











