静态链接
地址空间分配
两个或者多个不同的目标文件如何组成一个可执行文件,需要进行链接(linking)。链接由链接器(linker)完成,根据发生的时间不同,可分为编译时链接(compile time)、加载时链接(load time)和运行时链接(run time)。
下面由两个c语言文件main.c和func.c
main.c | func.c |
---|---|
extern int shared; extern void func(int *a,int *b); int main() { int a=100; func(&a,&shared); return 0; } | int shared=1; int tmp=0; void func(int *a,int *b) { tmp = *a; *a = *b; *b = tmp; } |
![]() |
执行此命令,结果如下:
在将main.o和func.o这两个文件链接成一个exec文件,按序叠加
另一种是相似节合并,将不同目标文件相同属性的节合并为一个节
静态链接的详细过程
为了构造可执行文件,链接器必须完成两个重要工作:符号解析(symbol resolution)和重定位(relocation)。
符号解析是将每个符号(函数、全局变量、静态变量)的引用与其定义进行关联。
重定位是将每个符号的定义与一个内存地址进行关联,然后修改这些符号的引用,使其指向这个内存地址。
先查看静态链接前后文件的区别,这里以func.ELF和main.o两者进行比较
VMA(virtual memory address)是虚拟地址,LMA(load memory address)是加载地址
观察上述,得出未经链接的main.o文件的VMA和LMA的值都是0,而链接以后的文件中,完成了虚拟地址的分配
在查看两个文件的反汇编代码
可以看到main()函数的地址从0开始。其中,对func()函数的调用在偏移0x22处,0xe8是CALL指令的操作码,后四个字节是被调用函数相对于调用指令的下一条指令的偏移量。
可以看到,调用func函数的指令CALL位于0x400b8a,其下一条指令MOV位于0x400b8f,call行操作码中的偏移量位0x07,也就是相对于MOV的偏移量为0x07,:0x400b8f+0x07=0x400b96。正好是func函数的地址。同时,shared的地址也变成了6b90f0。
可重定位文件中最重要的就是要包括重定位表,用于告诉链接器如何修改节的内容。
静态链接库
后缀名为.a的文件是静态链接库文件,如常见的libc.a。一个动态链接库可以视为一组目标文件经过压缩打包后形成的集合。