主流的可执行文件有:windows下的PE和linux下的ELF,他们都是COFF格式的变种。
目标文件:源代码编译以后但未进行链接的中间文件,它和可执行文件格式几乎一样。
各种命名
- 可重定位文件
.o
文件中包含代码和数据,可以被用来链接成可执行文件或是共享目标文件,静态链接库归为这一类。
比如我们编译未连接的目标文件也是这一类 - 可执行文件
包含了可执行程序,一般没有扩展名。都是链接以后的文件了。 - 共享目标文件
.so
包含代码和数据,可以和可重定向文件一起链接,产生新的目标文件(不一定是可执行文件)。或者是被动态链接器与可执行文件结合作为进程映像的一部分运行(在堆和栈之间,靠近栈) - 核心转存文件
core
当进程以外终止时,系统可以将进程地址空间的内容以及终止时的一些其他信息转存到核心转存文件。
目标文件格式
这个目标文件,可以是上面的4中文件中的一种哈
目标文件中的信息是分类存放的,该放数据的放数据,该放代码的放代码。一个分类成为节,或者是段。
将文件信息分段存放的目的在于:
- 运行时数据和指令分别被映射到不同的虚拟内存区域中。
在内存中:数据区可读写,代码区只读。可以防止程序指令被修改。 - 提高CPU缓存的命中率
数据和代码段分离有利于提高局部性。 - 使得代码区可供下个
节省内存。
File Header
该段,主要存储了,整个文件的属性,包括文件是否可以执行,动态链接还是静态连接,入口地址,目标硬件,目标操作系统等息息。
还有就是一个段表,保存了该文件中所有端的便宜位置,以及段的属性。
头部有一个对应的数据结构,定义在/usr/include/elf.h
中。
其中包括:
e_ident
:主要对应着Magic
,是该文件标识码。
第一位对应DEL控制符(0x7f),接下来三位是ELF的ASCII,第五位01表示32位,02表示63位,第六位表示字节序,第七位表示ELF文件的主版本号,主要是1,后面的一般用0填充了。
该字段又被解析为:
e_type
表示文件类型
ET_REL
:可重定向文件1
ET_EXEC
:可执行文件2
ET_DYN
:共享目标文件3
e_machine
机器类型
主要是cpu平台属性。
e_shoff
段表的起始位置
指出段表在ELF文件中的位置
段表
段又对应一个结构体,每个段一个,该结构体中主要包含,段名,段类型,段标志位,段的虚拟地址,段偏移,段长度,等等。
段名只是个名字,段类型才真正表示该段的类型。
类型包括,标号为值:
- SHT_NULL无效段
- SHU_PROGBITS程序段,代码段,数据段都是该类型
- SHT_SYMTAB符号表
- SHU_STRTAB字符串表
- SHT_RELA重定位表
等等
段标志指示该段在虚拟地址空间中的属性,可写,需要分配空间,可执行。
代码段 .code
或.text
一般为.text
,包含了程序中的代码部分。
已初始化全局变量和局部静态变量 .data
已初始化的全局变量和静态局部变量,存储在.data
段中。
也就是我们声明并定义了的那些变量。
但是一些字符串常量保存在.rodata
段中。
只读数据段.rodata
该段存放只读数据,一般是程序中的只读变量,const
修饰的变量和字符串常量。
该段的存在保证了程序的安全性。
上述两段中的数据,在文件中就存储了。
未初始化全局变量和局部静态变量.bss
未初始化的变量,因为没有值或是默认值,所以目标文件中不占位置。
但是.bss
段在虚拟内存中给这些变量留了空间
同时一些变量初始化为默认值,也有可能被编译器放入.bss
段。
注释信息段.comment
存放编译器的信息,版本号之类的。
同时我们也可以自己定义一个段:
__attribute__((section("FOO"))) int i = 42;
可以显示的声明将该变量放入.FOO
段中。
重定位表.rel.text
和.rel.data
段类型为SHT_REL
。
编译器在处理目标文件的时候,不许对某些不问进行重定位,即代码段和数据段中那些绝对地址的引用位置。这些息息都记录在ELF文件的重定位表里面。
每个需要重定位的代码段或数据段,都会有一个对应的重定位表。
.rel.text
是针对.text
段的重定位表。
其中段表的sh_info
对应的值就是所针对的段的下表。
字符串表.strtab
或.shstrtab
.strtab
字符串表用来保存普通字符串,后一个.shstrtab段表字符串表,用来保存段表中用到的字符串,常见的就是表名。我们自己定义的。 .shstrtab
其在段表中的下表就是File Head结构中e_shstrndx
。所以,系统读取了ELF文件的表头,就可以获得段表和段名,从而解析了整个ELF文件。
符号表.symtab
符号表中存储着程序所有的(全局的,可以被其他模块引用或引用其他模块)变量名,函数名,局部变量和函数,还有段名,行号信息(指令在源代码中对应位置),每一个符号对应一个符号值,变量和函数的符号值就是他们的地址。
符号表中的每一项也是一个结构体
对应的是:符号名,符号值,符号对应的类型的大小,符号类型,0,符号所在段。
st_info
:符号类型可选为:
-
STB_LOCAL
局部符号0,对于目标文件外部不可见 -
STB_GLOBAL
全度符号1,对外部可见 -
STB_WEAK
弱引用2
另外链接器还自己定义了几个符号。
符号修饰语函数签名
修饰机制是用来防止静态变量名字冲突的。
.debug
在编译的时候使用-g
,可以额外生成该段。