【1】预处理编译汇编过程
预处理器(预处理,生成ASCII码中间文件)——》编译器(编译,生成ASCII码汇编语言文件)——》汇编器(汇编,生成可重定位目标文件)——》链接器(链接,生成可执行目标文件)
【2】ELF格式的可重定位目标文件格式:
——————
| ELF头 |
——————
| .text | —— 已经编译程序的机器代码
——————
| .rodata | —— 只读数据
——————
| .data | —— 已经初始化的全局和静态C变量
——————
| .bss | —— 未初始化的全局和静态C变量
——————
| .symtab | —— 符号表,存放在程序中定义和引用的函数和全局变量信息。
——————
| .rel.text | ——
——————
| .rel.data | —— 被模块引用或重定位的所有全局变量的重定位信息
——————
| .debug | ——
——————
| .line | ——
——————
| .strtab | ——
——————
【3】链接器如何解析多重定义的全局符号
编译时,汇编器想链接器输出每个全局符号,分强弱。函数和已经初始化的全局变量是强符号,未初始化的全局变量是若符号。
连接规则1:不允许有多个同名强符号;规则2:一个强符号,多个若符号,选择强符号;规则3:多个弱符号,任选一个(很容易引起程序运行错误)。
【4】静态链接库
静态链接库(.a)是一系列可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置。当链接器运行的时候,就只会复制 .a 中需要被用到的 .o 模块到可执行文件中。
如果库之间不是相互独立的,存在依赖关系,那么命令行方式中需要左边放依赖的库,右边放被依赖的库,也就是越独立的、越基本的库越在右边。
【5】重定位
分两步组成:
-
重定位节和符号定义。比如,把所有来自输入模块的节全部合并成一个聚合节。这一步完成之后,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
-
重定位节中的符号引用。修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行地址。
【6】可执行目标文件
可分为以下三段:只读内存段(代码段)、读/写内存段(数据段)、不加载到内存的符号表和调试信息。
【7】加载器
当在shell中输入 > ./a.out 的时候,shell会认为这是一个可执行目标文件,于是便调用一段称为“加载器(loader)”的代码来运行它。
【8】共享库(动态链接库)
-
首先,在任何给定的文件系统中,所有引用该库的可执行目标文件共享这个.so,而不是像静态库那样把内容复制和嵌入到可执行文件中。其次,内存中,一个共享库的.text节的一个副本可以被不同的正在运行的继承共享。
-
因此静态库更新之后,所有使用了该静态库的软件需要重新连接(因为是内嵌在可执行文件中的),而动态库更新了之后,允许应用程序在运行时连接新的共享库。
-
位置无关代码
【9】linux链接器支持打桩机制