涉及核心:ELF文件结构、符号表、动态链接、可见性、强弱符号、未定义符号、ABI影响。
.so是ELF格式 共享目标文件(Shared Object)
root@ser745692301841:/dev_dir/avant/build/external# file libavant-lua.so
libavant-lua.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=397134715c11e89b57a0138292983920f68ccbb5, with debug_info, not stripped可以看到 ELF 64-bit LSB shared object ,
它本质和可执行文件差不多,只是
在ELF里,符号就是:一个“名字”对应一个地址
包括:函数、全局变量、静态对象、vtable、模板实例
符号存放在:
重点是 .dynsym
root@ser745692301841:/dev_dir/avant/build/external# nm -D libavant-log.so
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U _Unwind_Resume@GCC_3.0
000000000001711e T _ZN5avant3log6logger15rotate_log_fileEl
0000000000016f62 T _ZN5avant3log6logger4openERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEi
00000000000170c2 T _ZN5avant3log6logger5closeEv
0000000000032f80 D _ZN5avant3log6logger6s_flagE
0000000000016e4a T _ZN5avant3log6loggerC1Ev
0000000000016e4a T _ZN5avant3log6loggerC2Ev
0000000000016f08 T _ZN5avant3log6loggerD1Ev
0000000000016f08 T _ZN5avant3log6loggerD2Ev
0000000000017ffb W _ZN9__gnu_cxx11char_traitsIcE2eqERKcS3_
0000000000018e9a W _ZN9__gnu_cxx11char_traitsIcE4copyEPcPKcm
000000000001879e W _ZN9__gnu_cxx11char_traitsIcE4findEPKcmRS2_
0000000000018e32 W _ZN9__gnu_cxx11char_traitsIcE4moveEPcPKcm
0000000000018f2c W _ZN9__gnu_cxx11char_traitsIcE6assignEPcmc
0000000000017f8e W _ZN9__gnu_cxx11char_traitsIcE6lengthEPKc
000000000001d8e2 W _ZN9__gnu_cxx17__normal_iteratorIPcSt4spanIcLm18446744073709551615EEEC1ERKS1_
000000000001d8e2 W _ZN9__gnu_cxx17__normal_iteratorIPcSt4spanIcLm18446744073709551615EEEC2ERKS1_
00000000000285e6 W _ZN9__gnu_cxx17__normal_iteratorIPcSt4spanIcLm18446744073709551615EEEpLEl
0000000000029b5e W _ZN9__gnu_cxx17__normal_iteratorIPcSt4spanIcLm18446744073709551615EEEppEi
000000000001cc78 W _ZN9__gnu_cxxmiIPcSt4spanIcLm18446744073709551615EEEENS_17__normal_iteratorIT_T0_E15difference_typeERKS7_SA_
000000000001d904 W _ZNK9__gnu_cxx17__normal_iteratorIPcSt4spanIcLm18446744073709551615EEE4baseEv
0000000000029bc4 W _ZNK9__gnu_cxx17__normal_iteratorIPcSt4spanIcLm18446744073709551615EEEdeEv
U _ZNKSt13runtime_error4whatEv@GLIBCXX_3.4
000000000001c35c W _ZNKSt16reverse_iteratorIPKNSt8__format6_Arg_tEE4baseEv
000000000001c166 W _ZNKSt16reverse_iteratorIPKNSt8__format6_Arg_tEEdeEv
0000000000026588 W _ZNKSt17basic_format_argsISt20basic_format_contextINSt8__format10_Sink_iterIcEEcEE7_M_typeEm
000000000002601e W _ZNKSt17basic_string_viewIcSt11char_traitsIcEE17find_first_not_ofEcm
0000000000018102 W _ZNKSt17basic_string_viewIcSt11char_traitsIcEE3endEv
0000000000028528 W _ZNKSt17basic_string_viewIcSt11char_traitsIcEE4copyEPcmm
0000000000018fd8 W _ZNKSt17basic_string_viewIcSt11char_traitsIcEE4dataEv
000000000001871a W _ZNKSt17basic_string_viewIcSt11char_traitsIcEE4findEcm
......比如
int add(int a, int b) { return a + b; }nm显示
T add含义:
比如
extern int foo();但库里没实现
nm 显示:
U foo意思:我需要foo,但我自己没实现,等别的库提供。
如果运行时找不到:
undefined symbol: foo程序直接崩。
static 函数:
static void helper() {}不会出现在动态符号表里,因为它不可见。
.so的重点,默认情况下
int add();在GCC下是 default visibility。
你可以控制:
__attribute__((visibility("default")))或者
__attribute__((visibility("hidden")))编译选项:
-fvisibility=hidden这是工程级实践:默认隐藏,只导出API
否则你的库会把一堆内部符号暴露出去。
nm里会看到:
T 强符号
W 弱符号规则:
弱符号常用于:
动态链接时发生什么
程序启动时:
如果找不到
symbol lookup error你就开始怀疑人生。
undefined reference to xxx链接阶段找不到实现
原因:
undefined symbol: xxx编译时找到了,运行时loader找不到
原因:
C++:
int add(int, int);实际符号可能是:
_Z3addii如果C调用C++,C++应该
extern "C" int add(int, int)否则符号对不上。
.so 一旦发布:
否则:表面链接成功、运行直接炸
这就是为什么生产环境里:API稳定性比功能还重要。
如果你写库:
-fvisibility=hiddenextern "C" 控制ABI查看动态符号
name -D libxxx.so只看已定义
nm -D --defined-only libxxx.so只看未定义
nm -u libxxx.so查看依赖
ldd libxxx.so查看ELF详细信息
readelf -Ws libxxx.so.so
├── .text
├── .data
├── .bss
├── .symtab
└── .dynsym
├── Defined
├── Undefined
├── Weak
└── Hidden写 .so 本质不是“编译出文件”,而是 设计一个稳定的二进制接口。