静态链接

空间地址分配

按序叠加

相似合并

  1. 空间地址分配:扫描、收集(符号定义和引用到全局符号表)、合并、建立映射
  2. 符号解析与重定位:读取段信息、重定位信息,进行符号解析和重定位
// a.c
extern int shared;
int main(){
int a = 100;
swap(&a, &shared);
}

// b.c
int shared = 1;
void swap(int *a, int *b){
*a ^= *b ^= *a ^= *b;
}

生成目标文件;

gcc -c a.c b.c

进行链接;

ld a.o b.o -e main -o ab

链接后各个段的虚拟地址就确定了;

接着开始计算各个符号的虚拟地址;

例如main函数,原本在a.o中与.text的偏移offsetx,链接后,.text的虚拟地址假设为0x08048094,则main的地址为0x08048094 + x

符号解析与重定位

符号解析

a.c中引用了shared变量,并且调用了swap外部函数;

a.c编译成a.o时,编译器并不知道sharedswap的地址,因此shared用地址0x0暂时代替,而函数swap则用main+offset来表示(下一条指令的地址,与本行指令机器码指定的offset相加,决定调用的地址);

链接器通过重定位表Relocation Table获取哪些部分需要重定位;

例如当.text中有部分需要重定位,将会有一个.rel.text段保存代码段的重定位信息,.data段类似;

查看重定位信息,所有引用外部符号的地址;

objdump -r a.o

每个要重定位的地方有一个重定位入口Relocation Entry,在a.o中就有两个:sharedswap

重定位入口的偏移offset代表其在段中的偏移;

接着,查全局符号表,没查到就是undefined状态,查到就修正地址;

重定位修正

relocation

假设链接完成后,最终可执行文件中,main的虚拟地址为0x1000swap的虚拟地址为0x2000shared的虚拟地址为0x3000

绝对寻址修正

对于shared变量,重定位前,a.o中使用了movl $0x0, 0x4(%esp)来暂时代表shared的地址,即0x0

这里的重定位使用了R_386_32的绝对寻址修正,则S = 0x3000,而A = 0x0

那么最终重定位入口地址修正为S + A = 0x3000,修正后的指令为movl $0x3000, 0x4(%esp)

绝对寻址修正

对于swap函数,重定位前,a.o中使用了

这里的重定位使用了R_386_PC32的相对寻址修正,则S=0x2000A = 0xfffffffc = -4A即为机器码0xfffffffc,而P = 0x1000 + 0x27,是被修正的虚拟地址;

最后重定位入口地址修正为S + P - A = 0x2000 + (-4) - 0x1027 = 0xfd5,即最后call 0xfd5

下一条指令的地址加上当前call的地址,正好就是swap函数的地址,即:102b + fd5 = 0x2000

静态库链接

交互使用操作系统提供的API;

开发环境中的语言库Language Library就是对操作系统API的封装;

查看一个文件包含的目标文件

ar -t libc.a

将这些目标文件都提取出来

ar -x libc.a

查看某个函数在那个目标文件中

objdump -t libc.a | grep func

打印链接的过程

gcc -static --verbose -fno-builtin hello.c
  1. cc1编译成.s文件
  2. as汇编成.o文件
  3. collect2链接