读书-程序员的自我修养-链接、封装与库(8: 第四章:静态链接(1)空间与地址分配:静态链接,地址空间,链接步骤)
1. 什么是静态链接?
1.1 定义
当有两个目标文件时,如何将它们链接起来形成一个可执行文件?
这就是静态链接。
1.2 代码例子说明
a.c
extern int iShared;
int main()
{
int a=1;
swap(&a, &iShared);
}
b.c
int iShared = 2;
void swap(int* a, int* b)
{
*a ^= *b ^= *a ^= *b;
}
root@ubuntu-admin-a1:/home/4Chapter# gcc -c a.c b.c
root@ubuntu-admin-a1:/home/4Chapter# ls
a.c a.o b.c b.o
root@ubuntu-admin-a1:/home/4Chapter# gcc -o ab a.o b.o
root@ubuntu-admin-a1:/home/4Chapter# ls
ab a.c a.o b.c b.o
root@ubuntu-admin-a1:/home/4Chapter#
2. 空间与地址分配
例子 a.c,b.c 的目标文件就是 a.o b.o,输出可执行文件就是ab。
那么对于多个输入目标文件,链接器是如何将它们的各个段合并到输出文件的?
2.1 按序叠加
一个简单的方案就是将输入的目标文件按照次序叠加起来。
缺点:
输出文件有很多零散的段。
每个段都需要对齐,浪费空间。
2.2 相似段合并
就是将输入文件的 .text合并到输出文件的.text段……等等
其中,.bss段在目标文件和可执行文件中并不占用文件的空间,
但是在装载时占用地址空间。
2.3 地址和空间什么意思?
其中,链接器为目标文件分配地址和空间,这里的地址和空间什么意思?
- 第一个是在输出的可执行文件中的空间;
- 第二个是在装载后的虚拟地址中的虚拟地址空间
解释:
.text段和.data段来说,他们在文件和虚拟地址中都要分配空间。
.bss段只需要虚拟地址空间。
事实上,我们这里谈到的空间分配只关注于虚拟地址空间的分配
因为这个关系到链接器后面关于地址计算的步骤,而可执行文件本身的空间分配与链接过程关系不大。
2.3 两步链接
现在的链接器都采用相似段合并的方法,这种方法一般叫做两步链接。
也就是链接过程分两步。
2.3.1 第一步: 空间与地址分配
扫描所有的输入目标文件,并且获得它们的各个段的长度,属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符合引用收集起来,统一放到一个全局符号表。
收集到所有目标文件信息之后,将它们合并,并建立映射关系。
2.3.2 第二步: 符号解析与重定位
使用第一步收集到得所有信息,并且进行符号解析与重定位、调整代码中的地址等。其中,核心步骤是重定位过程。
objdump -h a.o
objdump -h b.o
objdump -h ab
root@ubuntu-admin-a1:/home/4Chapter# objdump -h a.o
a.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000004f 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 0000008f 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 0000008f 2**0
ALLOC
……
root@ubuntu-admin-a1:/home/4Chapter# objdump -h b.o
b.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000004b 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000004 0000000000000000 0000000000000000 0000008c 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000090 2**0
ALLOC
……
root@ubuntu-admin-a1:/home/4Chapter# objdump -h ab
ab: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
……
13 .text 00000202 0000000000400450 0000000000400450 00000450 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
24 .data 00000014 0000000000601028 0000000000601028 00001028 2**3
CONTENTS, ALLOC, LOAD, DATA
25 .bss 00000004 000000000060103c 000000000060103c 0000103c 2**0
ALLOC
26 .comment 00000034 0000000000000000 0000000000000000 0000103c 2**0
CONTENTS, READONLY
root@ubuntu-admin-a1:/home/4Chapter#
解释:
由代码中可以看到,VMA(Virtual Memory Address) 即表示虚拟地址
可以看到,链接之前的 a.o b.o的vma都是0,因为虚拟地址空间还没有分配,所以是0;
等到链接之后,可执行文件ab中的各个段都分配到了相应的虚拟地址。
其中,linux下 ELF可执行文件默认从地址 0x08048000开始分配。
2.3.3 符号地址的确定
因为,链接后的虚拟地址已经确定了。
如上面的 .text VMA为 0000000000400450,.data VMA为 0000000000601028。
因为各个符号在段内的相对位置是固定的,所以符号 main iShared swap等的地址已经是确定的。
只不过给每个符号加上一个偏移量,使它们能够调整到正确的虚拟地址。
比如,a.o 的起始地址是0000000000400450
那么 main 的地址是 0000000000400450 + X //X为main的相对偏移量
swap 的地址是 0000000000400450 + Y //X为 swap 的相对偏移量