在掌握了一门编程语言并学习了操作系统和机组后,我脑袋里形成了两条并行的知识线,搞不清我的代码到底是如何与进程、内存管理这些知识结合起来的。虽然很多书都有涉及编译、连接与加载的知识,但是讲的都不够全面,系统性也不强。直到看了《深入理解计算机系统》,之前整理的小知识点终于联系起来。以下内容只是本人的简单整理,更多内容请看原书。
一、编译
@过程
编译是由代码文件(.c)生成可重定位目标文件(.o)的过程,如下:
fredzzt.c/.h——Prepressing——>fredzzt.i ——Compilation——>fredzzt.s ——Assembly——>fredzzt.o
首先,对.c文件运行C预处理器(cpp),将fredzzt.c翻译成一个ASCII码的中间文件fredzzt.i ,你可以通过[cpp fredzzt.c fredzzt.i]命令得到.i文件。
接下来,运行C编译器(cc1),他将.i文件翻译成一个ASCII汇编语言文件fredzzt.s,[cc1 fredzzt.i -o fredzzt.s]。
最后,驱动程序运行汇编器(as),翻译出一个可重定位目标文件fredzzt.o,[as -o fredzzt.o fredzzt.s]
@可重定位目标文件的构成
目标文件有三种形式:可重定位目标文件(与其他文件连接成可执行)、可执行目标文件(直接拷贝到存储器执行)、共享目标文件(动态加载到内存并连接)。
根据系统的不同目标文件可分为COFF、PE、OMF和ELF等,书上以ELF为例
[readelf -S fredzzt.o]
.text section : 已编译程序的机器代码。
.rodata section : 只读数据节,放置全局const变量和字符串常量。
.data section : 用来存放初始化的全局变量和静态变量以及他们的值 。
.bss section : 为未初始化的全局变量和静态变量(C中默认值为0,有些初始化为0的全局变量和静态变量,也会被编译器放入BSS段)预留位置,不占磁盘空间,在程序加载时系统会根据BSS指定的大小,为程序分配相应的BSS段内存。
.symtab : 符号表,存放在程序中定义和引用的函数和全局变量的信息。
.rel.text : .text section 的位置列表,多个目标文件链接时需要修改位置。
.rel.data : 全局变量的重定位信息。
.strtab : 字符串表,包括.symtab和.debug中的符号表。
e_flags; // Read/Write/Execute bits
二、链接
@静态链接:
连接器(ld)将可重定位目标文件组合成可执行文件.out/.exe,[ld -o fredzzt fredzzt1.o fredzzt2.o]。
为了构造可执行文件,ld必须完成两个主要任务:符号解析和重定位。将不同可重定位目标文件的section修改并合并。
不只是可重定位目标文件,静态库也可以静态链接[gcc main.c lib.a]
@符号解析
链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来,对那些和引用定义在相同模块中的本地符号的引用,符号解析是非常简单明了的,编译器只允许每个模块中每个本地符号只有―个定义,编译器还确保静态本地变量,它们也会有本地链接器符号,拥有唯一的名字:不过,对全局符号的引用解析就棘手得多,当编译器遇到―个不是在当前模块中定义的符号(变量或函数名)时,它会假设该符号是在其他某个模块中定义的,生成了一个链接器符号表目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用的符号,它输出一条(通常很难阅读的)错误信息并终止。
对于重名全局符号,汇编器将函数和已初始化的全局变量定义为强符号,,未初始化的全局变量是弱符号,并使用以下规则来处理:
1、不许有多个强符号。
2、如果有一个强符号和多个弱符号,那么选择强符号。
3、如果有多个弱符号,那么从这些符号里任选一个。
@重定位
重定位分成两步:重定位节和符号定义(同类型节合并)、重定位节中的符号引用(修改符号引用)
@动态链接
为了进一步提高代码的可复用性和减少静态库定期维护的缺点,共享目标文件(.so/.dll)诞生了。在运行时,系统中只有一个共享库只有一个共享目标文件,可以加载到任意的存储器地址,并和任意一个在存储器中的程序链接。[gcc -o fredzzt fredzzt.c lib.so]
@可执行目标文件
.init segment: 定义了_init函数初始化时调用。
因为,已经执行过了链接,所以不再需要.rel节。
原文:http://www.cnblogs.com/fredzzt/p/4235040.html