GCC的工作,到生成汇编代码为止。剩下的工作,交给了Binutils来完成:assembler和static linker。linker主要完成的是静态链接,目标文件合并的工作。例如,把多个.o文件合并成一个可执行文件。
两步链接
两步链接指的是:
-
空间与地址的分配
链接器会首先扫描所有的输入文件,获得各个段的长度、属性和位置,将段合并;并将输入目标文件中的符号表合并为全局符号表。 -
符号解析与重定位
使用上一步中收集到的信息,进行符号解析和重定位,调整代码中的地址等。这一步也是链接过程的核心,特别是重定位过程。
链接器首先获取各个段的虚拟地址;在确定段的虚拟地址之后,也就能确定各个符号的虚拟地址了。
重定位与符号解析
在完成空间和地址的分配之后,链接器开始进行符号解析和重定位的过程。在链接之前,各个段中的符号地址,都是以0为基地址的,对于未知的地址,也通通用0进行替代。编译器在编译时,对于不知道的符号地址,全部用一个假值替代,把真正的工作留给链接器去做。
而链接器在分配了虚拟地址之后,就可以修正每一个需要重定位的入口。这个工作是借助于重定位表来实现的。重定位表包括:重定位入口(也就是需要重定位的地方),偏移表示入口在被重定位的段中的位置。
在x86_64下,重定位表的结构也很简单(定义在elf.h当中):
typedef struct{
Elf64_Addr r_offset;
Elf64_Xword r_info;
}Elf64_Rel;
typedef struct{
Elf64_Addr r_offset;
Elf64_Xword r_info;
ELF64_Sxword r_addend;
}Elf64_Rela
这里,r_offset
指定应用重定位操作的位置;r_info
则指定必须对其进行重定位的符号表索引以及要应用的重定位类型。其低位表示重定位入口的类型;高位表示重定位入口的符号,在符号表中的下标。(不同处理器的格式不一样)
符号解析则是为符号的重定位提供帮助,根据多个目标文件中的符号表,生成全局符号表,找到相应的符号并进行重定位。对于未定义的符号,链接器都应该能在全局符号表中找到,否则就报出符号未定义的错误。
PS:x86_64只使用Elf64_Rela。
指令修正
在x86_64中,call、jmp、mov、lea等指令的寻址方式千差万别。对于重定位来说,修正指令的寻址方式定义在binutils/elfcpp/x86_64.h当中。这其中主要包括:R_X86_64_64
和R_X86_64_PC32
两种。这是因为X86_64上,相对寻址依然只支持32位(实际上这也很科学;因为一个可执行文件通常不会有4G那么大)。
两种寻址方式的修正方法分别为:符号地址 + 保存在被修正位置的值
和符号地址 + 保存在被修正位置的值 - 被修正的位置相对于段开始的偏移量
源码分析
初始化,parsing command line & script file
linker的入口,在ldmain.c当中(通常在链接的时候,通过编译器内部直接进行调用)。首先,linker会调用bfd库,识别二进制文件的格式,生成各个段的描述符,并且转换为canonical form
。(例如linker中的符号表识别工作,就是首先由BFD来进行分析和转化,然后linker直接在canonical form
上进行操作,再由BFD来进行输出)因此在ldmain.c的main
中,首先进行的也是bfd的初始化bfd_init
。随后linker进行了一系列的设置,包括路径,回调函数、初始化,加载插件、读取命令、linker script等。
文件和符号的加载
随后,lang_process
中,linker会对每个输入文件进行处理。对于每个输入文件,linker都会分配一个bfd,对输入文件进行扫描,识别出其中的符号。首先open_input_bfds
为所有文件建立了bfd,随后载入文件中的所有符号。每个符号对应一个bfd_link_hash_entry
,它们保存在bfd_link_hash_table
当中。
bfd_link_add_symbols
将符号添加到hash_table当中。
输入文件的分析和合并
在链接的第一部分完成后,第二部分开始前,链接器首先调用了ldctor_build_sets
函数,它主要为C++中的constructor/dectructor提供支持。随后链接器lang_do_memory_region
计算出内存区域(它们保存在lang_memory_region_list
当中)。再通过lang_common
处理全局符号,将它们添加到对应的section,移除没有被使用的sections等。随后链接器建立输入section和输出section之间的映射关系,并且将文件的section合并,以及设置段的属性等。
重定位
第四步是符号的重定位工作。这里lang_size_section
首先获取所有section的大小,然后lang_set_startof
会修正section的大小和位置。在确定了sections的信息之后,就可以对符号进行重定位了,这便是lang_do_assignments
和ldexp_finalize_syms
的工作。它们会按照前面提到的方法,对符号进行重定位。最后链接器还会检查符号和section的正确性。
交给bfd,输出文件
在完成重定位之后,如果没有出现异常,linker就把工作交给bfd了。ldwrite
负责把链接好的文件输出。完成一些清理工作后,整个链接过程就结束了。
The link of this page is https://blog.nooa.tech/articles/41311c08/ . Welcome to reproduce it!