Linux中的目标文件、可执行文件、链接库、核心转储文件主要以ELF文件格式来存储。所以linux中ELF文件主要有四种类型:
可重定位文件
该类文件包含了代码和数据,可以被用来接连称为可执行文件或共享文件,静态链接库也可以归为该类。该类别的文件也就是平常所说的目标文件,由于静态链接库只是多个目标文件的压缩整合,并建立索引来管理这些目标文件,所以静态链接库也属于该类别。该类别的文件里面有重定位表,用来描述如何修改相应的段里的内容。如果一个源文件引用了其他文件定义的变量或是函数,那么在编译成目标文件的时候,对于这些未定义的符号,ELF文件只是使用一个临时地址来代替这些符号的实际地址,所以需要一个可重定位表来记录文件中那些符号需要重新定位,以及该符号在ELF文件中的地址。在链接的时候,其中一个很重要的步骤就是对符号地址的重定位,所以符号为未定义以及符号的重定义都是在链接的该步骤判断的。
Linux中的.o和.a文件以及windows中的.obj文件就是这中类型的文件。
可执行文件
该类文件包含了可以直接执行的程序,他的代表就是ELF可执行文件,他们一般都没有扩展名。Linux中的/bin/bash文件和windows的.exe文件就是这种类型的文件。
共享目标文件
这种文件包括了代码和数据段,可以在一下的两种情况下使用。一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件连接,产生新的目标文件。第二种就是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来运行。
Linux 的.so文件和windows中的.dll文件就是这种类型文件
核心转储文件
当进程意外终止时,系统可以将该进程的地址空间的内容以及终止时的一些其他信息转储到核心转储文件。
Linux的core dump文件。
ELF文件的格式
ELF文件使用段的形式来管理,从上到下主要有:
- 文件头:记录生成该ELF的系统信息,以及该ELF文件的信息,如:ELF文件版本、目标机器型号、程序入口地址、段表位置、段数量、入口地址、程序头入口和长度等。
- .text段:也就是所说的代码段,代码段存储的就是程序编译后所生成的机器指令。
- .data段:数据段,用来存储程序中的初始化的全局变量以及初始化的局部静态变量。
- .bss段:记录未初始化的全局变量和局部静态变量,在ELF文件里面,不会为这些为初始的变量分配空间,因为这些未初始的变量的默认值为0,所以只需要记录总体的未初始变量的大小就好,不需要实际的分配磁盘空间来存储他们,但是在程序装载到虚拟内存的时候,需要为其分配空间。
- .rodata: 只读数据段,该段用来存储程序中的字符串常量以及其他类型的常量(const修饰的),如:printf(“%d\n”, n),其中的”%d\n”就是字符串常量,
- .comment:存放编译器的版本信息,比如字符串:“GCC:(GUN)4.2.0”
- .debug:储存调试信息。
- Session header table:段表,用来保存ELF文件中所有段的基本属性结构,段表是ELF文件中除了文件头意外最重要的结构,他描述了ELF的各个段信息,比如每一个段段名、段的长度、在文件中的偏移、读写权限以及段的其他信息。也就是说,ELF文件的段结构就是由短表决定的,编译器、连接器和装载器都是依据段表来定位和访问各个段的属性的。
- .symtab:符号表,记录该文件中的所有符号信息(全局变量和函数符号等)。
- .rel.txt/.rel.data:重定位代码段和重定位数据段,对于每一个需要重定位的代码段和数据段,都会有一个相应的重定位表。对于代码中的对其他文件定义的函数的调用,就需要一个重定位代码段,用来记录需要重新定位的函数。数据段也是一样的道理。
总的来说,程序被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,数据段以及.bss段属于程序数据。
将程序指令和程序数据分来存储的主要好处有三个:
1:程序指令和程序数据被装载后分来存储,由于程序指令属于只读,程序数据可读可写,所以可以对装载后的内存实现不同的权限管理。防止程序指令被改写。
2:现代CPU都被设置为数据缓存和指令缓存分离,主要就是利用程序以及数据的局部性,将程序指令和程序数据分离有利于提高程序的局部性,可以充分的利用缓存,从而提高缓存的命中率。
3:节省内存。但系统运行着多个该版本的副本时,由于指令都是一样的,所以内存只需要存储一份程序指令就可以了,这样可以节省很多的内存空间。