目标文件的内容
ELF文件
linux下,使用file命令可以看到一个文件的类型,对于elf文件来说,一共有四个类型,分别是:
- 可重定位文件:linux下的.o文件和windows下面的.obj文件,包含代码和数据,可以被链接成共享目标文件;(静态链接库也属于这一类,即.so文件)
- 可执行文件:可以直接执行,例如/bin/bash,windows的exe;
- 共享目标文件:linux下的so文件,windows下的dll文件;包含数据和代码,(1)可以作为其他可重定位文件或共享目标文件,一起链接生成目标文件;(2)动态链接器可以将几个这种文件与可执行文件结合,作为进程映像的一部分来运行;
- 核心转储文件:进程意外终止时,转存相关信息的文件。
ELF文件中的段类别
下图是一个标准的elf文件结构示意图,主要包括图中几个部分:
- ELF Header:使用readelf -h xxx.o来查看目标文件的elf header信息; 此外,从elf的头信息中,我们可以得到elf文件基本属性,类型,目标机器号,程序入口地址等。具体信息如下:
- 几个重要的段的解释,可以使用objcopy命令自定义段,并插入内容;详情可以百度gcc的扩展运用;
- 整体来说,程序经过编译后,通过段实现了抽象。将源代码分为两部分:程序指令以及程序数据,优点主要是:(1)读写数据分离,防止程序被修改;(2)程序和数据的缓存区域单独设计,在cache里面,实现指令缓存和数据缓存分离,提高了指令缓存命中率;(3) 多个程序或者进程时候,实现指令共享。
- 分析指令: 用gcc -c将cc文件编译成.o文件,然后用下列命令查看;(linux下有很多实用的小命令,参考:GNU binutils 里的九种武器 - Linux中国的文章 - 知乎 https://zhuanlan.zhihu.com/p/85913402)
ELF中的三剑客
ELF文件中,除了一些基本段,有三个比较重要的部分,分别是段表,重定位表,字符串表。
段表
该表描叙了elf文件包含的所有段的信息,例如每个段的段名,长度,在文件中的偏移,读写权限以及其他权限等。段表是一个数组(每个元素是一个结构体,该结构体定义在 /usr/include/elf.h文件中,需要根据不同的硬件参数,找到对应的宏定义),数组的长度等于ELF文件中段的长度加一,第一个是保留的null结构体。所以,知道了段表数组中,每个结构体的信息,就知道了所有段的信息。但是描述段的信息的结构体,比较复杂。例如ELF32_Shdr段描述符结构:
- sh_type可以告诉我们一个段是无效段,符号表,或者程序段/代码段/数据段;
- sh_flags告诉我们该段在进程虚拟地址空间的属性,是否可写,可执行;
- sh_link和sh_info:如果段的类型和链接相关(重定位表,符号表)的,这两个成员则表明需要使用的数据的地址;
重定位表
在段表中.rel.text是针对.text段的重定位表,所谓重定位,是对代码段中某些函数或者变量的地址进行重新分配(例如对当前代码文件引用别的代码文件的函数或者变量等)。对于重定位表,sh_type的数值为宏定义的”重定位表“类型。sh_link表示符号表的下标,sh_info表示需要重定位的段(地址偏移)。
字符串表
ELF文件中用到了很多的字符串,便于存储, 专门开辟段来存在字符串,在引用字符串的地方,直接用字符串表的偏移即可。字符串表分为两种:
- 字符串表(.strtab):保存普通的字符串。例如符号的名字
- 段表字符串表(shstrtab):保存段表中用到的字符串,例如段名;在elf_header的结构体中,e_shrtrndx。
链接的接口——符号
符号:将代码文件中的函数和变量地址,根据编译器的规则,表示为符号(一定格式的字母名称);链接——跨文件,模块,将这些符号,替代成实际的地址。每个目标文件都有一个符号表,该表记录文件的所有符号,每个定义的符号有个value,表示该符号的地址。函数和变量名最重要,以及:全局符号(函数名,全局变量),外部符号(引用外部文件的函数名,变量名),段名,局部符号,符号信息;前两者对于链接需要关心(nm xxx.o,命令查看)。
ELF符号表结构:符号有一个名为.symtab的段表示;定义可在elf.h文件找到
st_info 低4位表示符号的类型,高28位表示符号的绑定信息;
st_shndx:如果符号在本目标文件中,则是符号所在段的在段表中的下标;如果符号不是在本目标文件中,则如下:
st_value:结合《程序员自我修养》p84
特殊符号——在代码中直接声明和引用,可以被ld识别;
符号修饰和函数签名——说白了就是符号名的生成规则,和具体编译器规则相关
extern C——用这个语句概括起来的语句,会被当做c语言规则处理,例如:
强符号和弱符号
初始化的全局变量为强符号;未初始化的全局变量为弱符号;(可以通过gcc关键字在源码中修饰。)强符号和弱符号的处理规则:
gcc中可以在源码中设置某个引用为弱引用,强引用(链接找不到符号定义就报错)和弱引用(有定义就用,没有就默认为0或特殊值)。这种设定,可以用于代码库的灵活裁剪和组合,例如在库文件定义一个弱引用,可以在用户依赖的代码被覆盖,而不报错。
其他
目标文件在debug模式下,-g 参数编译,有debug的信息参数;