0x05
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时、加载时、运行时。
链接器使得分离编译成为可能。当改变模块时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
编译器驱动程序
GCC驱动程序命令:
linux> gcc -Og -o prog main.c sum.c
驱动程序首先运行C预处理器(cpp),它将C的源程序main.c翻译成一个ASCII码的中间文件main.i:
cpp [other arguments] main.c /tmp/main.i
接下来,驱动程序运行C编译器(ccl),它将main.i翻译成一个ASCII汇编语言文件main.s:
ccl /tmp/main.i -Og [other auguments] -o /tmp/main.s
然后,驱动程序运行汇编器(as),它将main.s翻译成一个可重定位目标文件main.o:
as [other arguments] -o /tmp/main.o /tmp/main.s
驱动程序经过相同的过程生成sum.o。最后,它运行链接器程序ld,将main.o和sum.o以及一些必要的系统目标文件组合其阿里,创建一个可执行目标文件prog:
ld -o prog [system object files and args] /tmp/main.o /tmp/sum.o
要运行可执行文件prog,在shell的命令行上输入它的名字:
linux> ./prog
shell调用操作系统中一个叫做加载器的函数,它将可执行文件prog中的代码和数据复制到内存,然后将控制转移到这个程序的开头。
静态链接
像ld程序这样的静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节组成,每一节都是一个连续的字节序列。指令在一节中,初始化了的全局变量在另一节中,而未初始化的变量又在另外一节中。
为了构造可执行文件,链接器必须完成两个主要任务:
- 符号解析。目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量。符号解析的目的是将每个符号引用正好和一个符号定义关联起来。
- 重定位。编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得他们指向这个内存位置。
目标文件是字节块的集合。这些块中,有些包含程序代码,有些包含程序数据,而其他的则包含引导链接器和加载器的数据结构。链接器将这些块连接起来,确定被连接块的运行时位置,并且修改代码和数据块中的各种位置。
目标文件
目标文件有三种形式:
- 可重定位目标文件。包含二进制代码和数据。其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
- 可执行目标文件。包含二进制代码和数据,其形式可以被直接复制到内存并执行。
- 共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接。
现代x86-64Linux和Unix系统使用可执行可链接格式(Executable and Linkable Format, ELF)。
可重定位目标文件
图中展示了一个典型的ELF可重定位目标文件的格式。ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。
夹在ELF头和节头部表之间的都是节。一个典型的ELF可重定位目标文件包含以下几个节:
- .text:已编译程序的机器代码。
- .rodata:只读数据,如printf语句中的格式串和开关语句的跳转表。
- .data:已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中。
- .bss:未初始化的全局和静态变量,以及所有被初始化为0的全局或静态变量。
- .symtab:一个符号表,存放在程序中定义和引用的函数和全局变量的信息。和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
- .rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。
- .rel.data:被模块引用或定义的所有全局变量的重定位信息。一般而言,任何已初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义函数的地址,都需要被修改。
- .debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项编译时,才会得到这张表。
- .line:原始C源程序和.text节中机器指令之间的映射。-g选项编译时,才会得到这张表。
- .strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表是以null结尾的字符串的序列。
符号和符号表
每个可重定位目标模块m都有一个符号表,包含m定义和引用的符号的信息。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!