静态链接就是将多个目标文件合并为一个可执行文件。
实例文件:
/* a.c */
extern int shared;
extern void swap(int *a, int *b);
int main()
{
int a = 100;
swap(&a, &shared);
}
/* b.c */
int shared = 1;
void swap(int *a, int *b)
{
*a ^= *b ^= *a ^= *b;
}
编译为目标文件:
/*
*需要加上-fno-stack-protector,否则当链接时会提示:
*a.o: In function `main':
*a.c:(.text+0x44): undefined reference to `__stack_chk_fail'
*/
$ gcc -c a.c b.c -fno-stack-protector
1 空间和地址分配
“链接器为目标文件分配地址和空间” 中的 “地址和空间” 有两个含义:
1、在输出的可执行文件中的空间
2、在装载后的虚拟地址中的虚拟地址空间
对于有实际数据的段,比如 .text 和 .data 来说,在可执行文件中和虚拟地址空间中都要分配空间;对于没有实际数据的段,比如 .bss 来说,分配空间的意义只局限于虚拟地址空间,因为它在文件中并没有内容。
注意:这里谈到的空间分配只关注于虚拟地址空间的分配。
可执行文件中的代码段和数据段都是由输入的目标文件合并而来的。
对于多个输入目标文件,链接器如何将它们的各个段合并到输出文件中呢?
1.1 按序叠加
这是最简单的方法:将输入文件按次序叠加起来,也就是将多个目标文件合并为一个大目标文件。
问题:在很多输入目标文件的情况下,输入文件会有很多零散的段。这样非常浪费空间,因为每个段都有一定的空间和地址对齐要求(生成可执行文件和装载运行时)。
1.2 相似段合并
这种方法就是讲输入文件的text
段合并到输入文件的text
段,其他段也是这样。
现在的链接器空间分配的策略基本上都采用这种方法,是用这种方法的链接器一般都采用两步链接的方法。
- 空间与地址分配: 扫描说有的输入目标文件,并且获得它们的各个段的长度,属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到全局符号表中。这样就能将输入目标文件中的段进行合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。
- **符号解析与重定位:**使用第一步中收集到的信息,读取输入文件中段的数据,重定位信息,进行符号解析与重定位,调整代码中的地址。这一步才是链接过程的核心,特别是重定位过程。
/*
* 将目标文件链接成可执行文件
* -e 指定 main 函数作为程序入口,ld 链接器的默认程序入口为 _start
* -o 指定输出的可执行文件名称,默认为 a.out
* /
$ ld a.o b.o -e main -o ab
/*
* 链接前后各个段的属性
* VMA(virtual memory address):虚拟地址;LMA(load memory address):加载地址。正常情况下这两个值应该是一样的,但是对于嵌入式系统,特别是将程序放入 ROM 的系统中 LMA 和 VMA 是不相同的。
*/
$ objdump -h a.o
a.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000027 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 00000067 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000067 2**0
ALLOC
3 .comment 00000036 0000000000000000 0000000000000000 00000067 2**0
CONTENTS, READONLY
4 .note.GNU-stack 00000000 0000000000000000 0000000000000000 0000009d 2**0
CONTENTS, READONLY
5 .eh_frame 00000038 0000000000000000 0000000000000000 000000a0 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
$ objdump -h b.o
b.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000001b 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000004 0000000000000000 0000000000000000 0000005c 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000060 2**0
ALLOC
3 .comment 00000036 0000000000000000 0000000000000000 00000060 2**0
CONTENTS, READONLY
4 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000096 2**0
CONTENTS, READONLY
5 .eh_frame 00000038 0000000000000000 0000000000000000 00000098 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
$ objdump -h ab
ab: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000042 00000000004000e8 00000000004000e8 000000e8 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .eh_frame 00000058 0000000000400130 0000000000400130 00000130 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .data 00000004 0000000000600188 0000000000600188 00000188 2**2
CONTENTS, ALLOC, LOAD, DATA
3 .comment 00000035 0000000000000000 0000000000000000 0000018c 2**0
CONTENTS, R