1. 空间和地址分配

对于连接器来说,链接的过程实际上是把多个输入目标文件合并成一个输出文件,问题是这个过程是如何实现的,如何将符号重新定位?

  1. /* a.c */
  2. extern int shared;
  3. void exit()
  4. {
  5. asm( "movq $66,%rdi \n\t"
  6. "movq $60,%rax \n\t"
  7. "syscall \n\t");
  8. }
  9. int main(){
  10. int a=100;
  11. swap(&a,&shared);
  12. exit();
  13. }
  14. /* b.c */
  15. int shared =1;
  16. void swap(int* a,int* b){
  17. *a^=*b^=*a^=*b;
  18. }

链接遇到的一些问题

合并方法:相似段合并

所有输入文件的相同性质的段合并再一起,例如所有文件的.text段合并到输出文件的.text段,之后是.data.bss
fig0301.svg :::warning 空间分配的含义是什么?
空间:

  • 输出可执行文件的空间
  • 装载后的虚拟地址中的虚拟地址空间 ::: 空间分配的策略:两步链接
  1. 空间与地址分配:扫描所有输入的目标文件,获得他们各个段的长度属性位置,此时链接器能够计算出输出文件中各个段合并后的长度和位置,并且建立映射关系
  2. 符号解析与定位:利用上一步收集到的信息,读取输入文件中的段的数据、重定位信息,然后进行符号解析与重定位调整代码中的地址

    符号地址的确定

    符号的地址如何确定?

  3. 首先在第一步中,连接器会建立一个全局符号表。

  4. 在第一步完成后,段的偏移已经计算出来了,根据每个符号在段中的偏移,可以计算出其虚拟地址,这时可以完成全局符号表的填充。

    2. 符号解析与重定位

    在编译的时候,有些不在当前文件的函数名和变量名编译器并不知道,因此会用0 来代替其地址进行编译。
    连接器在完成空间和地址的分配后,已经可以确定这些符号的地址了,因此在连接后这些空地址会被替代成真正的虚拟地址。

    重定位表

    如何知道哪些符号是需要重定位的呢?答案是依靠重定位表:
    在ELF文件中,有一个叫重定位表(Relocation Table)的结构专门用来保存这些与重定位相关的信息。
  1. $objdump -r a.o
  2. a.o 文件格式 elf64-x86-64
  3. RELOCATION RECORDS FOR [.text]:
  4. OFFSET TYPE VALUE
  5. 0000000000000035 R_X86_64_PC32 shared-0x0000000000000004
  6. 0000000000000042 R_X86_64_PLT32 swap-0x0000000000000004
  7. 000000000000004c R_X86_64_PLT32 exit-0x0000000000000004

重定位表的结构也很简单,它是一个Elf32_Rel结构的数组,每个数组元素对应一个重定位入口。

  1. typedef struct {
  2. Elf32_Addr r_offset; //重定位入口的偏移.
  3. Elf32_Word r_info; //重定位入口的类型和符号
  4. } Elf32_Rel;

符号解析

符号解析可以说是占据了链接过程的主要内容;重定位的过程也伴随着符号解析。当链接器查找所有输入目标文件的符号表组成全局符号表是,会找到相应的符号表进行定位,如果有的符号在所有的目标文件的符号表中都查不到,那么他的type则是UND即未定义的,这时连接器会报错。