1. 空间和地址分配
对于连接器来说,链接的过程实际上是把多个输入目标文件合并成一个输出文件,问题是这个过程是如何实现的,如何将符号重新定位?
/* a.c */
extern int shared;
void exit()
{
asm( "movq $66,%rdi \n\t"
"movq $60,%rax \n\t"
"syscall \n\t");
}
int main(){
int a=100;
swap(&a,&shared);
exit();
}
/* b.c */
int shared =1;
void swap(int* a,int* b){
*a^=*b^=*a^=*b;
}
合并方法:相似段合并
所有输入文件的相同性质的段合并再一起,例如所有文件的.text
段合并到输出文件的.text
段,之后是.data
、.bss
:::warning
空间分配的含义是什么?
空间:
- 输出可执行文件的空间
- 装载后的虚拟地址中的虚拟地址空间 ::: 空间分配的策略:两步链接
- 空间与地址分配:扫描所有输入的目标文件,获得他们各个段的长度属性位置,此时链接器能够计算出输出文件中各个段合并后的长度和位置,并且建立映射关系
符号解析与定位:利用上一步收集到的信息,读取输入文件中的段的数据、重定位信息,然后进行符号解析与重定位、调整代码中的地址。
符号地址的确定
符号的地址如何确定?
首先在第一步中,连接器会建立一个全局符号表。
- 在第一步完成后,段的偏移已经计算出来了,根据每个符号在段中的偏移,可以计算出其虚拟地址,这时可以完成全局符号表的填充。
2. 符号解析与重定位
在编译的时候,有些不在当前文件的函数名和变量名编译器并不知道,因此会用0 来代替其地址进行编译。
连接器在完成空间和地址的分配后,已经可以确定这些符号的地址了,因此在连接后这些空地址会被替代成真正的虚拟地址。重定位表
如何知道哪些符号是需要重定位的呢?答案是依靠重定位表:
在ELF文件中,有一个叫重定位表(Relocation Table)的结构专门用来保存这些与重定位相关的信息。
$objdump -r a.o
a.o: 文件格式 elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000035 R_X86_64_PC32 shared-0x0000000000000004
0000000000000042 R_X86_64_PLT32 swap-0x0000000000000004
000000000000004c R_X86_64_PLT32 exit-0x0000000000000004
重定位表的结构也很简单,它是一个Elf32_Rel
结构的数组,每个数组元素对应一个重定位入口。
typedef struct {
Elf32_Addr r_offset; //重定位入口的偏移.
Elf32_Word r_info; //重定位入口的类型和符号
} Elf32_Rel;
符号解析
符号解析可以说是占据了链接过程的主要内容;重定位的过程也伴随着符号解析。当链接器查找所有输入目标文件的符号表组成全局符号表是,会找到相应的符号表进行定位,如果有的符号在所有的目标文件的符号表中都查不到,那么他的type则是UND即未定义的,这时连接器会报错。