7.1 编译器驱动程序
1. 一般调用预处理,编译器,汇编器,链接器来完成一个程序的驱动
1.首先运行C预处理器cpp,将C源程序main.c翻译成一个ASCII码的中间文件main.i。
2.接下来,C编译器cc1将main.i翻译成一个ASCII汇编语言文件main.s。
3.最后,汇编器as将main.s翻译成一个可重定位目标文件main.o。
4.类似地,生成其他.o文件。最后,运行链接器程序ld,将main.o和其他.o以及一些必要的系统目标文件组合起来,创建一个可执行目标文件。
7.2 静态链接
在以上的这个过程中ld链接器的主要工作:
① 符号解析。目标文件定义和引用符号,符号解析的目的是将每个符号引用和一个符号定义联系起来。
(符号:一个函数,一个全局变量,一个静态变量)
②重定位:把每个符号定义与一个存储器位置联系起来,然后修改对这些符号的引用,是的他们指向这个存储器位置,从而实现重定位。
目标文件是字节块的集合,有的包含代码,有的包含程序数据,其他则是包含引导链接器和加载器的数据结构。
7.3 目标文件
目标文件共有三种形式:
1.可重定位目标文件。包含二进制代码和数据,其可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。(.o文件)
2.可重定位目标文件:包含二进制代码和数据,在运行的时候可以将和其他可重定位目标文件合并变成可执行文件。(类似a.out)
3.共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载到存储器并链接。(.so文件也称库文件)
编译器和汇编器生成可重定位目标文件(包括共享目标文件),链接器生成可执行目标文件。一般讨论可执行可链接格式(ELF)
7.4 可重定位目标文件
1.ELF的格式:
.text:已编译程序的机器代码。
.rodata:制只读数据,比如printf语句中格式串和开关语句的跳转表。
.data:已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不在.bss节中。
.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。
.symtab:符号表,存放在程序中定义和引用的函数和全局变量的信息。
.rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
.rel.data:被模块引用或定义的全局变量的重定位信息。
.debug:一个调试符号表,包含程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。
.line:原始C源程序中行号和.text节中机器指令之间的映射。
.strtab:一个字符串表,包含.symtab和.debug节中的符号表,以及节头部中的节名字。
这部分知识点看其他的文章摘下来的。
7.5 符号和符号表
每个可重定位目标模块都有一个符号表,它包含m所定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:
有m定义且能被其他模块引用的全局符号:全局链接器符号对应于非静态的C函数以及被定义为不带C static属性的全局变量
由其他模块定义并被模块m引用的全局符号:称之为外部符号(external),对应于定义在其他模块中的C函数和变量。
只被模块m定义和引用的本地符号:这些符号在m中随处可见但不能被其他模块引用。
符号表是由汇编器构造的,使用编译器输出到汇编语言.s文件中的符号。
其中.symtab节包含ELF符号表,这张符号表包含一个条目的数组,也可以说是一个结构体数组。
7.6 符号解析
对多重定义的全局符号的解析:
当我们遇到多重定义的全局符号怎么办呢?首先全局符号被分为强或弱,这个信息包含在符号表中,函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。根据强弱符号的定义,Unix链接器使用下面的规则来处理多重定义的符号:
规则1:不允许有多个强符号。
规则2:如果有一个强符号和多个弱符号,那么选择强符号。
规则3:如果有多个弱符号,那么从这些弱符号中任意选择一个。
如果链接的程序定义了多个强符号,则会报错。但是规则2和3会导致一些不易察觉的错误:
函数f将x的值由15213改为15212,这会给main函数的作者带来意外。
静态库:
编译系统提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库,其文件后缀为.a,里面包含多个.o文件。它可以用做链接器的输人,当链接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。
使用静态库可以减少可执行文件在磁盘和内存中的大小。
使用ar工具ar rcs .a .o .o 创建静态库
使用gcc -static -o prog(自己起名) .o ./.a 链接静态库,(注意,.a文件必须在要引用它的文件后面,否则会出错)