nm -D libxxx.so 可直接查看动态库导出的运行时符号(即.dynsym节内容),仅显示STB_GLOBAL绑定的FUNC/OBJECT类型符号,配合c++filt可解码C++修饰名;readelf -s --dyn-syms提供更完整字段,而ldd仅查依赖不查导出。

用 nm -D 快速看动态库对外暴露的函数
直接运行 nm -D libxxx.so 就能看到该库真正导出给外部调用的符号(即 .dynsym 节里的内容)。它只显示运行时可见的符号,不包含内部静态函数或未标记为 default 的符号。
常见错误现象:有人用 nm libxxx.so(不带 -D)看到一堆 T 和 t,误以为全都能被外部调用 —— 其实那些小写 t 是静态函数,根本不会出现在动态符号表里。
-
nm -D输出中,T表示全局函数(可被外部链接),D表示全局已初始化变量 - 如果什么都没输出,说明这个库可能用了
-fvisibility=hidden且没显式标记任何default符号 - 对 C++ 函数,名字会被 mangling,比如
_ZN3foo1aEv,这时建议配合c++filt解码:nm -D libxxx.so | c++filt
readelf -s --dyn-syms 查看完整动态符号节
比 nm -D 更底层、更精确,它直接读取 ELF 文件的 .dynsym 节,结果字段更全(含符号值、大小、类型、绑定属性等),适合排查符号是否真的“导出”了。
容易踩的坑:很多人用 readelf -s libxxx.so(不加 --dyn-syms),结果看到的是全部符号(包括静态和调试符号),信息过载且误导性强。
- 必须加
--dyn-syms才能过滤出运行时链接器实际使用的那部分符号 - 关注
STB_GLOBAL(全局绑定)且STT_FUNC或STT_OBJECT类型的条目,才是有效导出项 - 如果某函数在源码里加了
__attribute__((visibility("hidden"))),它就不会出现在这个列表里
为什么 ldd 看不到符号,只能看依赖?
ldd 只解析 ELF 的 DT_NEEDED 段,告诉你这个库依赖哪些其他库(比如 libc.so.6),完全不涉及它自己导出了什么。把它当“符号查看工具”是典型误用。
使用场景错位举例:你想确认 libmath.so 是否导出了 fast_sin,却跑 ldd libmath.so —— 它只会打印依赖,哪怕 fast_sin 根本没导出,ldd 也照常输出。
- 正确链路:导出检查 →
nm -D或readelf -s --dyn-syms - 依赖检查 →
ldd - 加载路径诊断 →
LD_DEBUG=libs ./your_app
符号没导出?先查编译时有没有开 -fvisibility=hidden
很多团队默认加 -fvisibility=hidden 编译动态库,这是好习惯,但代价是:所有函数/类成员默认不导出,除非显式加 __attribute__((visibility("default"))) 或宏包装。
性能影响很实在:一个导出 500 个符号的库,启动时 linker 要挨个字符串匹配;砍到 20 个后,进程冷启动快 10%~15% 是常见收益。
- 检查编译命令里是否含
-fvisibility=hidden(Makefile 或 CMakeLists.txt 中) - 检查头文件里是否有类似
#define API_EXPORT __attribute__((visibility("default"))),且目标函数是否用了这个宏 - 没加
default属性的 C++ 成员函数,即使 public,也不会导出 —— 这点和 Windows 的__declspec(dllexport)直接修饰类不同
真正的麻烦往往不在“怎么查”,而在“查出来为空时该翻哪几行代码、哪个编译选项、哪处宏定义”。符号导出是编译期决策,运行时不可更改,漏标一个 visibility("default") 就得重编整个库。










