链接器如何在链接时解析共享库中数据对象的引用?实例疑问
链接器相关疑问解答
问题背景
现有以下代码文件及编译流程:
main.c
#include "other.h" extern int i; int main() { ++i; inci(); return 0; }
other.c
int i = 0; void inci() { ++i; }
编译命令
gcc -c main.c gcc -shared -fpic other.c -o libother.so gcc -o main main.o ./libother.so
反汇编内容
- main.o的部分反汇编:
f: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 15 <main+0x15> 15: 83 c0 01 add $0x1,%eax 18: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 1e <main+0x1e> 1e: b8 00 00 00 00 mov $0x0,%eax 23: e8 00 00 00 00 call 28 <main+0x28>
- 可执行文件main的部分反汇编:
1148: 8b 05 ca 2e 00 00 mov 0x2eca(%rip),%eax # 4018 <i@@Base> 114e: 83 c0 01 add $0x1,%eax 1151: 89 05 c1 2e 00 00 mov %eax,0x2ec1(%rip) # 4018 <i@@Base> 1157: b8 00 00 00 00 mov $0x0,%eax 115c: e8 cf fe ff ff call 1030 <inci@plt>
对应C语句++i;的汇编显示,链接器直接用PC相对地址引用变量i,而非通过GOT(全局偏移表)。但共享库是运行时加载的,可执行文件链接时应该不知道i的地址,核心疑问:
- 链接器如何确定
i位于0x4018? - 注释中的
i@@Base是什么意思?
解答
1. 链接器确定i地址的原因
这是GCC链接器的默认优化行为:将共享库中的全局变量i复制到了可执行文件的全局数据段,也就是俗称的COMDAT折叠或共享库变量预绑定。
具体逻辑是:
- 变量
i在other.c中是带初始化值的强符号(int i=0),链接器在处理可执行文件与共享库的符号关联时,会识别这类变量。 - 为了减少GOT间接访问的开销,链接器会把这个变量从共享库中提取出来,放到可执行文件自身的
.data段里,分配一个固定地址(也就是反汇编里的0x4018)。 - 运行时,共享库
libother.so中的i会被重定向到可执行文件里的这个副本,确保主程序和共享库访问的是同一个变量。
如果要强制让变量通过GOT访问,可以编译共享库时加-fno-semantic-interposition选项,或者链接时用-Bsymbolic参数。
2. i@@Base的含义
i@@Base里的Base指的是可执行文件自身的符号表(Base Symbol Table),也就是主程序的全局符号空间。这个注释明确表示:当前访问的i是属于可执行文件自己的符号,而非共享库中的原始符号。
对比反汇编里的inci@plt,@plt表示通过PLT(过程链接表)间接访问共享库中的函数;而i@@Base直接指向主程序数据段的固定地址,说明这个变量已经被链接器绑定到了主程序的地址空间中,不需要运行时再通过GOT解析。
内容的提问来源于stack exchange,提问作者drcxd
相关产品推荐
相关产品推荐

