静态编译和链接
静态编译和链接
0x1 编译过程
一个源代码文件也就是.c文件在被编译成可执行文件的时候经历了那些过程呢?一般有四个步骤:预处理(Prepressing)、编译(Compilation)、汇编(Assembly)、链接(Linking)。如下图所示(这里的操作系统是linux):
-
预处理过程的工作是将一些预处理指令(以#号开头的命令)进行处理,最终生成.i文件。
-
然后由编译器对.i文件进行编译,将里面的源代码指令转换为汇编指令,生成.s文件(有的系统是.asm文件)。
-
之后由汇编器对.s文件进行处理生成二进制文件.o(.obj),也是目标文件。
-
然后由链接器对多个.o文件或静态运行库.a文件进行链接生成可执行文件.out。
可执行文件的种类linux下的.elf、动态链接库.so、静态链接库.a。windows下的.exe、动态链接库.dll、静态链接库.lib
0x2 静态链接
多个目标文件和库的链接过程,其实就是各个模块之间函数和变量的引用,地址是不确定的。搞清楚地址是如何计算的是链接过程最重要的一个过程。在链接的过程中我们将函数和变量统称为符号,每个目标文件都会有一个符号表,这个表记录了这个目标文件所用到的所有符号,每个符号还有一个对应的值,叫做符号值也就是他们的地址(绝对地址和相对地址)。符号表是一个结构体数组,结构定义如下。
typedef struct {
Elf32_Word st_name, //符号名称
Elf32_Addr st_value,//符号值(相对地址或绝对地址)
Elf32_Word st_size,//符号所占的字节数
unsigned char st_info,//符号类型和绑定信息
unsigned char st_other,//0,无用
Elf32_Half st_shndx,//符号所在的段
} Elf32_Sym;
这里符号的值加上符号所在的段就可以确定符号在目标文件中的地址,此时符号的值是绝对地址,也就是相对于所在段的偏移。
在可执行文件中,符号的值是该符号在文件中的虚拟地址,可以草率的认为是绝对地址。
链接器怎么知道那些文件需要重定位呢?这里有一个重定位表,如果是text段的重定位表则是.rel.text、data段的为.rel.data。重定位表的结构体如下:
typedef {
Elf32_Addr r_offset,//符号相对于所在段的偏移
Elf32_Word r_info,//重定位的类型和符号,低8位是重定位入口的类型,高24位是符号在符号表中的下标
} Elf32_Rel;
在目标文件中r_offest是符号相对与所在段的偏移,在可执行文件中这个是符号所在的虚拟地址。两个目标文件进行链接的时候会按照相似段进行合并,这里是指段的权限(读,写,可执行)来划分的,具体步骤如下。
- 空间和地址的分配。这个过程会将所有的符号全部收集起来统一放在全局符号表。然后对其中的值进行调整。
- 符号解析和重定位。按照重定位表对各个符号进行重定位。
符号地址的确定。在这里因为每个符号的段地址知道,偏移知道。所以每个符号在文件中的虚拟地址就知道,这时计算每个符号的虚拟地址然后写入全局符号表。这也就是为什么可执行文件里符号表存放的是虚拟地址而不是相对偏移。
之后根据重定位表对里面的每个符号进行地址的修改,这个地址的修改策略有两种。绝对寻址和相对寻址,因为在汇编指令里面有绝对地址引用和相对地址引用所以在修正的时候就得按照不同的方式,这个方式被记录在r_info字段里面。方式尽管不同,但是最后的结果就是各个符号都能按照虚拟地址进行引用。
当文件按照重定位做完重定位工作之后链接工作就完成了,生成了可执行文件。静态库的链接也是如此。