四个过程:预处理Preprocessing、编译Compile、汇编Assembly、链接Linking
Preprocessing:通常完成一些宏扩展、包含头文件、删除注视等,生成.i文件;
Compile:gcc -S hello.i -o hello.S生成汇编文件,把预处理的文件进行一系列的词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件;汇编过程也可以使用汇编器as hello.s -o hello.o来完成。
Assembly:gcc -c hello.s -o hello.o生成目标.o文件,汇编器是将汇编代码编程机器可以执行的指令;
最后再将不同的.o文件链接成可执行文件。简单来说,编译器就是将高级语言翻译成机器语言的一个工具,直接使用汇编代码或者使用机器指令效率比较低。
编译器生成的一些.o可重定位文件,里面引用了一些定义在其它模块的函数或者全局变量,这些函数或者变量的地址只有在链接的时候才能被确定下来。
在Linux下,.o可重定位文件,可执行文件/bin/bash,共享目标文件.so,核心存储文件,都可以称为目标文件,采用Elf格式存储。
.o目标文件的ELF格式大致如下
ELF Header |
.text |
.data |
.bss |
.rel.text |
.. other sections |
Section header Table |
String Tables Symbol Tables |
|
objdump也能查看ELF文件中包含的段,但它会省略一些符号表、重定位表、字符串表;如果使用readelf命令,则可以玩到ELF所有的信息,如ELF header或者Section Header Table,Symbol Tables。
一般来说,生成的.o文件都包括重定位表和符号表,这两个表也以段的形式表现。
重定位表:rel.text;rel.data等,如果引用了其余目标中间中的函数或全局变量,就会在这两个段中标明出来,通过指明段类型为SHT_REL,来标明该段是重定位段。
符号表:段名一般叫.symtab,每个符号都由如下结构体来表现:
typedef struct{
Elf32_Word st_name;//符号名字
Elf32_Addr st_value;//符号直,可能是绝对值,也可能是一个地址
Elf32_Word st_size;//符号大小
unsigned char st_info;//符号绑定信息以及符号类型
unsigned char st_other;
Elf32_Half st_shndx;
}Elf32_Sym;
熟悉单个目标文件的格式以后,就能更好的理解链接过程了。
编译器和汇编器生成从地址零开始的代码和数据段,连接器通过把每个符号定义与一个存储器位置联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些指令。
链接器在链接多个.o文件时,首先会合并各个.o文件的重定位表段以及符号表段,链接时先确定目标文件的虚拟地址,随后就能知道各个符号的绝对的虚拟地址。得知此地址后再去更新新的符号表段中各个符号的地址;符号确定后,再按照重定位表段,去更新指令中需要重定位的地址或数值。