计算机系统基础答案第四章,计算机系统基础 -- 第四章 (程序的链接)
计算机系统基础 -- 第四章 (程序的链接)
什么是程序的链接?
通过gcc编译生成的一系列具有.c0后缀的目标文件与vc将其输出为一组具有.obj后缀的可再定位目标文件进行整合, 最终形成一个完整的可执行程序
程序链接的好处:
分层化的系统架构(能够拆分为多个独立模块,并且所有模块均编译生成.o文件后通过链接即可运行起来。不仅仅局限于单个.c文件,在大型工程项目中明确划分至关重要)
2, 高效性(该系统能够通过在同一时间段处理多个文档,并能同一时间操作多个模块从而显著提升了整体效能)
在链接先进行编译和汇编, 在第三章聊过
可执行文件与. o 文件的汇编差别在哪?
因为可执行文件将可重定位二进制文件重新组合, 所以它自身将虚拟地址转化为逻辑地址, 如下// test.c
intmain(inti,intj){
intx=i+j;
returnx;
}
// 使用 objdump -d test.o 反汇编出来的. o 文件
// test.o
Disassemblyofsection.text:
0000000000000000
:
0:55push%rbp
1:48 89 e5 mov %rsp,%rbp
4:89 7d ec mov %edi,-0x14(%rbp)
7:89 75 e8 mov %esi,-0x18(%rbp)
a:8b 55 ec mov -0x14(%rbp),%edx
d:8b 45 e8 mov -0x18(%rbp),%eax
10:01 d0 add %edx,%eax
12:89 45 fc mov %eax,-0x4(%rbp)
15:8b 45 fc mov -0x4(%rbp),%eax18:5dpop%rbp
19:c3 retq
每个. o 文件均以 0 开始,并未完成链接至可执行文件的过程,在其内存区域采用虚拟地址机制
// 使用 objdump -d test 反汇编出来的可执行文件
//test
00000000004004d6
:
4004d6:55push%rbp
4004d7:48 89 e5 mov %rsp,%rbp
4004da:89 7d ec mov %edi,-0x14(%rbp)
4004dd:89 75 e8 mov %esi,-0x18(%rbp)
4004e0:8b 55 ec mov -0x14(%rbp),%edx
4004e3:8b 45 e8 mov -0x18(%rbp),%eax
4004e6:01 d0 add %edx,%eax
4004e8:89 45 fc mov %eax,-0x4(%rbp)
4004eb:8b 45 fc mov -0x4(%rbp),%eax4004ee:5dpop%rbp
4004ef:c3 retq
// 由于我的机子是 8g 内存, 因此开头以 400... 开头
// test.o 与 test 相比, test 经过链接后, 将虚拟地址转化成为逻辑地址
目标文件格式
ELF 格式
每个可执行文件都有一个 ELF 头, 里面包括着可执行文件的信息.
ELF 头
夹在 ELF 头和节头部表之间的都是节. 其内容通常列举了以下几类标准的符号表:
.text: 已编译程序的机器代码.
.rodata: 可读性受限的数据, 例如,在printf语句中使用格式字符串以及switch语句中的转移表。
.data字段表示已赋值的全局变量;局部变量则存储于栈内存位置,并不包含在.data字段内或属于.bss字段部分。
.bss: 未被赋值的全局C变量在其目标文件中并不占用真实的存储空间只是一个占位区域目的是为了提高存储空间利用率在目标文件中这些未被赋值变量无需占用额外的存储磁盘空间
.........: 一个\texttt{symbol table}(\texttt{symbol table}),它存储了程序中被定义并被引用的所有函数及其全局变量的信息。\n\n一些程序员错误地认为他们必须借助-g 编译指令以获取程序的 symbol 表信息。\n但实际上,在每一个可重新定位的目标文件中都可以找到相应的 \texttt{symtab} 文件。\n\n然而,在编译器中的 \texttt{symtable}通常会包含局部变量的信息;而 \texttt{.symtab} 符号表则不包括这些。
当将目标文件与其他多个文件结合起来时,在.text节中存在多个需要调整的位置。一般而言,在编译过程中会遇到以下两种情况:一是任何调用外部函数或者引用全局变量的指令都需要进行相应的处理和调整;二是若涉及调用本地函数的指令,则无需做任何改动即可顺利运行。需要注意的是,在可执行的目标文件中,并不必要进行重定位操作……通常情况下会省略这些信息(除非用户明确要求包含)……
.rela数据: 被所使用的模块定义或被调用的对象所包含的全局变量信息。通常情况下, 已初始化的全局变量在其初始值设置为全局变量或外部定义函数地址时, 都需要经过相应的处理。
.debug 中包含了一个调试符号表。其中一些条目对应于程序中声明的局部变量和类型;另外一些条目则对应于程序中声明的全局变量及其引用;还有一部分条目直接来源于原始的 C 源文件。仅当使用带有 -g 选项的编译驱动程序时才能生成该符号表。
.line: 源代码中的具体行号与.text节中的机器指令对应关系仅当使用带有--g选项的编译器执行该操作时才能生成此对应关系表
.line: 源代码中的具体行号与.text节中的机器指令对应关系仅当使用带有--g选项的编译器执行该操作时才能生成此对应关系表
strtab: 由字符串表构成,其内容包含symtab和debug节中的符号表以及在节头部中还有节名字。字符串表即以null结尾的字符串序列。
旁注: 为什么未初始化的数据称为. bss?
常用术语bss来表示未初始化的数据是很常见的。它源自IBM 704汇编语言(大约于1957年)中"块存储开始(Block Storage Start)"指令的首字母缩写,并沿用至今。记住区分data和bss节的一个简便方法是将bss视为"更好地节省空间(Better Save Space)!"的缩写。
符号表和符号解析
符号表的类型
1, 在模块 m 中定义冰杯其他模块引用的全局符号
2, 在其他模块定义并且被 m 引用的外部符号
3, 在模块 m 中定义并在 m 中引用的本地符号
使用 readelf -s prog.o 查看符号表readelf-s main.o
Symboltable'.symtab'contains11entries:
Num:ValueSizeTypeBindVisNdxName
8:00000000000000008OBJECT GLOBAL DEFAULT3buf
9:000000000000000016FUNC GLOBAL DEFAULT1main
10:00000000000000000NOTYPE GLOBAL DEFAULT UND swap
readelf-s swap.o
Symboltable'.symtab'contains12entries:
Num:ValueSizeTypeBindVisNdxName
5:00000000000000008OBJECT LOCAL DEFAULT5bufp1
9:00000000000000008OBJECT GLOBAL DEFAULT3bufp0
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND buf
11: 0000000000000000 60 FUNC GLOBAL DEFAULT 1 swap
该全局变量被标记为 GLOBAL。
该局部变量被标记为 LOCAL。
该函数名为 swap。
该本地变量被标记为 Ndx。
该值被赋值为 undefined。
符号解析
全局符号的强弱特性
说明: 在函数命名空间中, 函数名称及其已被赋值过的全局变量将被视为强符号, 而那些尚未被赋值过的全球变量则被视为弱符号
比如前面提到的, main(buf) 函数, swap(buf) 操作, bufp0 是 强标志, bufp1 位本地变量, 本地变量没有强弱区分。
多重定义符号处理:
规则 1: 强符号不能多次定义, 也即强符号只能被定义一次, 否则链接错误
当一个数学公式被解释为一次主要的强化型标识符与多个次要的弱化型标识符时,则应按照主要强化型标识符的规定执行。
规则 3: 若有多个弱符号定义, 则任选其中一个
强弱符号链接错误解决方案:
1, 尽量避免使用全局符号
2, 把全局符号定义位 static, 这样就没有强弱之分
3, 尽量要给全局变量赋初值使其变成强符号
4, 外部全局变量尽量使用 extern
重定位
规则:
函数调用采用相对重定位
即使用 R_386_PC32:ADDR(r_sym)-((ADDR(.text)+ r_offset) - init)
全局变量采用绝对重定位
即使用 R_386_32 把 32 位的地址值直接代替
重定位的工作
1, 节和定义符号的重定位
2, 引用符号的重定位
链接
静态链接: 将用户程序中使用的库文件全部复制到目标环境中,并生成一个完整的可执行程序
优点: 可随时执行文件, 可执行文件不会因为库文件丢失而无法执行
缺点: 导致相同库文件多个备份
基于代码, 数据, 重定位和符号表信息, 动态链接的过程能够在执行目标文件时将被动态地装入内存并自动完成链接过程
优点: 减少库文件的多个备份
缺点: 缺少库文件无法运行
小结:
至此为止第四章也已圆满结束本章主要阐述了生成可执行文件的具体流程首先是进行代码编辑随后对其进行预处理接着完成编译流程进而开始汇编操作之后还需完成链接处理最终得以生成完整的可执行文件这一过程中我们不仅看到的是简单的黑色框框背后还有将代码转换为汇编建立全局变量及函数名称索引表并通过精确重定位整合出无缺的整体运行程序第四章的学习使我对于可执行文件的生成有了清晰的认识同时也成功解开了早期作为黑匣子存在的神秘面纱使我对其中的工作原理有了更加深刻的理解
