目标文件
- 源代码编译后但是没有进行链接的那些中间文件,比如win下的.obj文件、linux下的.o文件,与可执行文件的内容以及格式很类似。
- 目标文件中的内容至少有编译后的机器指令代码、数据。还包括连接时所需要的一些信息,比如符号表、调试信息、字符串等。一般,目标文件会将这些信息按照不同的属性进行分段(其实就是多个一定长度的区域)。
下面主要对linux下的可执行文件的ELF格式进行分析
ELF文件的结构
-
ELF文件主要由文件头(ELF header)、代码段(.text)、数据段(.data)、.bss段、只读数据段(.rodata)、段表(section table)、符号表(symtab)、字符串表()、重定位表(.rel.text)如下图所示:
代码段与数据段分开的原因:
1.对进程来说,数据段是可读写的,指令段是只读的。这样可以防止程序指令被改写。
2.指令区与数据区的分离有助于提高程序的局部性,有助于对CPU缓存命中率的提高。
3.当系统运行多个改程序的副本的时候,他们对应的指令都是一样的,此时内存只需要保留一份改程序的指令即可。当然,每个副本进程的数据区域是不一样的,他们是进程私有的 -
结合下图进行分析
-
代码段
如上图所示,一般C语言编译后的执行语句都编译成机器代码,保存在.text段。 -
.data段
- 已经初始化的全局变量和局部静态变量(虽然默认会初始化为0,或者手动初始化为0,都没有必要在数据段分配空间,直接放在.bss段,就默认值为0了)都保存在.data段。
- 大体来说,该section包含了在内存中的程序的初始化数据;data段包含三个部分:heap(堆)、stack(栈)和静态数据区。即.data还会存放其他类型的数据,比如局部变量。
- 数据段只是存放数据,变量名存放在字符串表中。
-
.bss段
- 未初始化的全局变量和局部静态变量都保存在.bss段。
- 大体来说该section包含了在内存中的程序的未初始化的数据。
- 由于程序加载(一般是指main之前)时,bss会被操作系统清零,所以未赋初值或初值为0的全局变量都在bss。.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中也不占据空间,这样可减少目标文件体积。
- 但程序运行时需为变量分配内存空间,故目标文件必须记录所有未初始化的静态分配变量大小总和(通过start_bss和end_bss地址写入机器代码)。当加载器(loader)加载程序时,将为BSS段分配的内存初始化为0。
-
.rodata段
- 存放只读数据,一般是程序里面的只读变量(如const修饰的变量),以及字符串常量(不一定,也可能放在.data中)。
-
自定义段
程序员可以指定变量所处的段。
__attribute__((section("FOO"))) int global = 42;
则将变量放入名字为FOO的段中。 -
ELF文件头
- ELF魔数:文件头最开始的四个字节,第一个字节是DEL控制符的ASCII码,后三个字节是ELF字母的ASCII码。这种魔数用来确认文件的类型,操作系统加载可执行文件时会确认魔数是否正确。
- 文件类型:枚举表示
- 段表偏移:段表在文件中的偏移。
- 等
-
段表
- 描述ELF文件各个段的信息,你如每个段的段名、段长度、在文件中的偏移、读写权限以及段的其他属性。是用一个数据结构来描述的。
-
重定位表
- 链接器在处理目标文件的时候,需要对目标文件的某些部位进行重定位操作,即代码段数据段中那些绝对地址的引用位置。
-
字符串表
- 字符串表中包含若干以 null 结尾的字符串,这些字符串通常是 symbol 或 section 的名字。当 ELF 文件的其它部分需要引用字符串时,只需提供该字符串在字符串表中的位置索引即可。
- 字符串表中首先是一个空串,用于表示一个空名字,所以字符串表的第一个字节是“\0”。
-
一个字符串表可能涉及该 section 中的任意字节。一个字符串可能引用不止一次;引用子串的情况是可能存在的;一个字符串也可能被引用若干次;而不被引用的字符串也是允许存在的。
如下图所示
-
符号表
- 在链接的过程中,将函数和变量统称为符号。
- 每一个目标文件都有一个对应的符号表。符号表主要包括:
1.符号名,也即该符号名在符号串表中的下标。
2.符号值:一般是函数或者是变量的地址(.data段)。
3.该符号所在的段。
4.等等
网友评论