这是一个有趣的现象,可能是因为 nm
工具默认只会列出共享库中 公开的符号(即对外可见的符号),而 addr2line
是通过地址来定位代码的,它直接从符号地址中获取调试信息。所以,即使 nm
没能列出某个符号,addr2line
仍然能够通过地址找到相关的代码行。
可能的原因如下:
1. 符号表被剥离(Stripped Symbols)
-
共享库可能在构建时被剥离了符号表,或者符号被隐藏了。通常情况下,使用
-s
或strip
工具来去除符号表会减小库文件的大小,但这也意味着你无法使用nm
等工具查看符号。 -
如果使用
addr2line
能解出代码行,说明库在编译时带有调试信息(比如使用-g
选项编译),或者符号没有完全剥离,依然包含了部分调试信息。如何确认是否剥离符号:
你可以使用file
命令查看共享库的详细信息。例如:file libc.so
如果输出中包含类似
stripped
的信息,表示符号已被剥离。举个例子:libc.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), stripped
如果没有被剥离,你应该能够使用
nm
查看到公开的符号。
2. 符号的可见性问题
-
即使是非
static
的函数,也有可能被gcc
或clang
编译器处理为 局部符号,这通常是由于编译器优化或者特定的符号可见性设置(如__attribute__((visibility("hidden")))
)导致的。这意味着符号在共享库内是 “局部” 的,不会被nm
工具列出。如何查看符号可见性:
你可以使用objdump
来检查符号表和符号的可见性,例如:objdump -T libc.so
这将显示共享库中所有符号,并且会标明每个符号的可见性。
3. 函数地址未对齐
- 如果你的代码中使用了内联函数(
inline
),编译器可能会将这些函数的实现直接嵌入到调用它们的地方,而不是作为独立的符号存在。这也可能导致nm
无法列出这些符号。
4. 编译优化
-
编译器优化(如
-O2
,-O3
)可能会将某些函数内联化或移除,从而导致nm
看不到这些函数。即使你写的是一个非static
函数,如果编译器认为它未被使用,它可能会完全优化掉。你可以尝试在编译时禁用优化,例如使用
-O0
来避免优化,并查看是否能看到符号。
为什么 addr2line
可以工作?
addr2line
是基于地址来解析代码的,它并不依赖于符号表。即使函数被编译器优化,或符号被隐藏,addr2line
依然可以通过地址信息查找源代码行,前提是该库还包含了调试信息。调试信息通常包含了从机器码地址到源代码行号的映射,即使符号表已被剥离,addr2line
仍然可以利用这些映射信息。
总结
-
如果你不能在
nm
中找到符号,可能是因为:- 库的符号被剥离或隐藏。
- 编译器优化导致符号被内联或移除。
- 使用了符号可见性属性,导致符号是局部的。
-
使用
addr2line
可以通过地址找到源代码行,即使符号不可见,这表明调试信息还存在于库中。 -
要确认符号是否被剥离或隐藏,使用
file
或objdump
检查库的符号表和符号可见性。