出現這種差異的原因在于:動態庫中的符號分為兩種類型:
- 常規符號表(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
)。 - 對開發者:建議保留調試版(帶符號表)用于調試,發布版剝離符號表以減小體積。