出现这种差异的原因在于:动态库中的符号分为两种类型:
- 常规符号表(regular symbol table):通常用于静态链接和调试,默认不包含在动态库中(除非显式保留)。
- 动态符号表(dynamic symbol table):专门用于动态链接,始终存在于动态库中,供运行时解析符号。
具体解释:
-
nm
默认查看的是常规符号表
如果动态库在编译时通过-fvisibility=hidden
或strip
命令移除了常规符号表(为减小体积或保护代码),nm
会显示"no symbols"。 -
nm -D
强制查看动态符号表(.dynsym
段)
动态链接器(如ld.so
)仅依赖动态符号表解析符号,因此即使常规符号表被剥离,动态符号表仍必须保留(除非库被完全strip -R .dynsym
,但这会导致库无法使用)。
如何验证?
-
检查动态符号表是否存在:
readelf -S libxxx.so | grep dynsym
若存在.dynsym
段,则nm -D
必然有输出。 -
检查是否被剥离:
file libxxx.so
输出若包含stripped
,说明常规符号表已被移除,但动态符号表可能仍存在。
总结:
nm
看不到符号 → 常规符号表被剥离(不影响动态链接)。nm -D
能看到符号 → 动态符号表正常(足够动态链接使用)。
这是动态库优化后的正常现象,无需担心功能问题。
二、常规符号表的作用
常规符号表(regular symbol table,通常是.symtab
和.strtab
段)只在静态链接和调试阶段有用,对动态库的正常运行完全没有作用。具体用途如下:
1. 调试(Debug)
- 作用:为调试器(如
gdb
)提供符号信息(函数名、变量名、行号等)。 - 例子:
如果动态库保留了常规符号表,调试时可以准确看到崩溃栈中的函数名:
如果符号表被剥离,只能看到地址:#0 0x00007ffff7a8b2c4 in my_function() from libxxx.so
#0 0x00007ffff7a8b2c4 in ?? from libxxx.so
2. 静态链接(Static Linking)
- 作用:如果某个**静态库(
.a
文件)**需要链接这个动态库中的符号,常规符号表是必需的。 - 例子:
假设libxxx.so
中有函数foo()
,如果另一个静态库libbar.a
需要在编译时链接foo()
,则需要libxxx.so
的常规符号表来解析foo()
的地址。
(但这种情况极少见,因为动态库通常不会用于静态链接。)
3. 分析工具(如nm
、objdump
)
- 作用:帮助开发者检查库的内部符号(如是否有未导出的全局符号)。
- 例子:
nm libxxx.so
可以查看所有符号(包括未导出的),而nm -D libxxx.so
只能看到动态符号表(导出的符号)。
4. 性能分析(Profiling)
- 作用:
perf
等性能分析工具依赖符号表将地址转换为函数名。 - 例子:
如果符号表被剥离,perf report
只能显示十六进制地址,无法直观看到热点函数。
常规符号表 vs 动态符号表
特性 | 常规符号表(.symtab ) | 动态符号表(.dynsym ) |
---|---|---|
作用 | 调试、静态链接 | 动态链接 |
是否必须存在 | 否(可剥离) | 是(必须存在) |
工具查看 | nm 、objdump -t | nm -D 、readelf -sD |
大小 | 较大(含所有符号) | 较小(仅导出符号) |
结论:
- 对最终用户:常规符号表完全无用,可以安全
strip
(如strip libxxx.so
)。 - 对开发者:建议保留调试版(带符号表)用于调试,发布版剥离符号表以减小体积。