Linux ELF文件格式
ELF文件有三种类型: 可重定位文件:也就是通常称的目标文件,后缀为.o。 共享文件:也就是通常称的库文件,后缀为.so。 可执行文件:可执行文件的格式与上述两种文件的格式之间的区别主要在于观察的角度不同:一种称为连接视图(Linking View),一种称为执行视图(Execution View)。
段(Segment)由若干个节(Section)构成,节头表(Section header table)对每一个节的信息有相关描述。
ELF头(ELF header)
程序头表(Program header table)
它是一个结构数组,包含了ELF头表中字段e_phnum定义的条目,结构描述一个段或其他系统准备执行该程序所需要的信息。
对一个ELF可执行程序而言,一个基本的段是标记p_type为PT_INTERP的段,它表明了运行此程序所需要的程序解释器(/lib64/ld-linux-x86-64.so.2),实际上也就是动态连接器(dynamic linker)。最重要的段是标记p_type为PT_LOAD的段,它表明了为运行程序而需要加载到内存的数据。查看上面实际输入,可以看见有两个可LOAD段,第一个为只读可执行(FLg为R E),第二个为可读可写(Flg为RW)。
.text 段:存储只读程序
.data 段:存储已经初始化的全局变量和静态变量
.bss 段:存储未初始化的全局变量和静态变量,因为这些变量的值为0,所以这个段在文件当中不占据空间
.rodata 段:存储只读数据,比如字符串常量
PIC
ELF可以生成一种特殊的代码——与位置无关的代码(position-independent code,PIC)。用户对gcc使用-fPIC指示GNU编译系统生成PIC代码。它是实现共享库或共享可执行代码的基础.这种代码的特殊性在于它可以加载到内存地址空间的任何地址执行.这也是加载器可以很方便的在进程中动态链接共享库。 PIC的实现运用了一个事实,就是代码段中任何指令和数据段中的任何变量之间的距离都是一个与代码段和数据段的绝对存储器位置无关的常量。
因此,编译器在数据段开始的地方创建了一个表.叫做全局偏移量表(global offset table.GOT)。GOT包含每个被这个目标模块引用的全局数据目标的表目。编译器还为GOT中每个表目生成一个重定位记录。在加载时,动态链接器会重定位GOT中的每个表目,使得它包含正确的绝对地址。PIC代码在代码中实现通过GOT间接的引用每个全局变量,这样,代码中本来简单的数据引用就变得复杂,必须加入得到GOT适当表目内容的指令。对只读数据的引用也根据同样的道理,所以,加上 IC编译成的代码比一般的代码开销大。
如果一个elf可执行文件需要调用定义在共享库中的任何函数,那么它就有自己的GOT和PLT(procedure linkage table,过程链接表).这两个节之间的交互可以实现延迟绑定(lazy binging),这种方法将过程地址的绑定推迟到第一次调用该函数。为了实现延迟绑定,GOT的头三条表目是特殊的:GOT[0]包含.dynamic 段的地址,.dynamic段包含了动态链接器用来绑定过程地址的信息,比如符号的位置和重定位信息;GOT[1]包含动态链接器的标识;GOT[2]包含动态链接器的延迟绑定代码的入口点。GOT的其他表目为本模块要引用的一个全局变量或函数的地址。PLT是一个以16字节(32位平台中)表目的数组形式出现的代码序列。其中PLT[0]是一个特殊的表目,它跳转到动态链接器中执行;每个定义在共享库中并被本模块调用的函数在PLT中都有一个表目,从 PLT[1]开始.模块对函数的调用会转到相应PLT表目中执行,这些表目由三条指令构成。第一条指令是跳转到相应的GOT存储的地址值中.第二条指令把函数相应的ID压入栈中,第三条指令跳转到PLT[O]中调用动态链接器解析函数地址,并把函数真正地址存入相应的GOT表目中。被调用函数GOT相应表目中存储的最初地址为相应PLT表目中第二条指令的地址值,函数第一次被调用后.GOT表目中的值就为函数的真正地址。因此,第一次调用函数时开销比较大.但是其后的每次调用都只会花费一条指令和一个间接的存储器引用。
关键节(section)详解
.got
这是我们常说的GOT, 即Global Offset Table, 全局偏移表. 这是链接器在执行链接时
实际上要填充的部分, 保存了所有外部符号的地址信息.
不过值得注意的是, 在i386架构下, 除了每个函数占用一个GOT表项外,GOT表项还保留了
3个公共表项, 每项32位(4字节), 保存在前三个位置, 分别是:
got[0]: 本ELF动态段(.dynamic段)的装载地址
got[1]: 本ELF的link_map数据结构描述符地址
got[2]: _dl_runtime_resolve函数的地址
.plt
这也是我们常说的PLT, 即Procedure Linkage Table, 进程链接表. 这个表里包含了一些代码, 用来
(1)调用链接器来解析某个外部函数的地址, 并填充到.got.plt中, 然后跳转到该函数; 或者
(2)直接在.got.plt中查找并跳转到对应外部函数(如果已经填充过).
.got.plt
.got.plt相当于.plt的GOT全局偏移表, 其内容有两种情况,
1)如果在之前查找过该符号,内容为外部函数的具体地址.
2)如果没查找过, 则内容为跳转回.plt的代码, 并执行查找.
.plt.got
不知何用
函数调用过程
第一次call:func -> jump to .plt[0] -> jump to .got.plt 这时地址内容尚未更新,为.plt[1] -> jump to .plt[1],push函数idx -> jump to .plt初始地址 -> jump to .got.plt got[2], 相当于call _dl_runtime_resolve ->修改 .got.plt 地址。
第二次call:func -> jump to .plt[0] -> jump to .got.plt, 这时内容有效
这个过程叫延时加载或Lazy加载。因此需要.plt可执行,.got.plt可写
反编译工具
readelf
objcopy
objdump
XED: https://intelxed.github.io/
./xed -ir xxxx.bin
引用:
https://www.ibm.com/developerworks/cn/linux/l-excutff/index.html
https://vaqeteart.iteye.com/blog/1118753
https://www.cnblogs.com/pannengzhi/p/2018-04-09-about-got-plt.html