Linux系统ELF文件二进制格式分析(四)

来源:互联网 发布:windows管理员权限cmd 编辑:程序博客网 时间:2024/06/06 00:17

六、重定位项

重定位是将ELF文件中未定义的符号关联到有效位置的过程,特别是目标文件中这一项尤为重要。本例中引用了C语言库函数printf和exit,链接时必须替换为该进程的虚拟地址空间中机器代码所在位置。

每个ELF中,都有专门的类型为REL的节包含重定位项,标识了需要进行重定位的位置。每一项都是用相同的数据结构表示的。

1.      数据结构

由于历史原因,有两种类型的重定位信息,由两种不同数据结构。第一种重定位结构称为普通重定位,对应的节的类型为SHT_REL,结构如下:

typedef struct elf32_rel {  Elf32_Addrr_offset;  Elf32_Wordr_info;} Elf32_Rel;

r_offset指定需要重定位项的位置,r_info不仅提供了符号表中的位置,还包括重定位类型的信息。其中高8位中指示符号表中位置,低8位指示重定位类型。在计算重定位位置时,将根据重定位类型,对该值进行不同处理。获取相应值的宏定义如下:

#define ELF32_R_SYM(x) ((x) >> 8)#define ELF32_R_TYPE(x) ((x) & 0xff)

第二种重定位结构称为添加常数的重定位,对应节的类型为SHT_RELA,结构如下:

typedef struct elf32_rela{  Elf32_Addrr_offset;  Elf32_Wordr_info;  Elf32_Swordr_addend;} Elf32_Rela;

需要注意的是,第一种重定位结构也存在这个加数值,该加数值不在结构中存储,而是链接器根据该值应该出现的位置获取的。

目标文件中这个加数值在链接成可执行文件时会参与运算得到一个新的值,后面将会用例子进行说明如何计算得出新结果。

查看本例中目标文件的重定位项,结果如下:

 

2.      重定位类型

Linux存在两种重定位类型:相对重定位(对应类型为上图中R_386_PC32)和绝对重定位(对应类型为上图中R_386_32)。相对重定位主要用于子例程调用。绝对重定位的重定位项指向内存中编译时就已知的数据,例如字符串常数。

调用子过程是通过汇编指令call进行,X86架构32位系统中,call命令机器码为0xe8,紧跟在call命令机器码后的是4个字节的地址信息,但这个信息不是被调用子过程的准确地址,而是上一节提到的“加数”,这个加数是一个相对位置,可以根据这个相对值确定准确地址。目标文件和可执行文件都存在这个加数,但由于目标文件链接到可执行文件时子过程地址会改变,这个加数也会改变,因此需要通过计算得到改变后的值,根据重定位类型不同,计算方法也不同。

当定位类型为相对重定位时:

           E = S – P +A

当定位类型为绝对重定位时:

           E = S + A

其中:

S表示“重定位符号位置”,P表示“重定位项的位置”,A表示“目标文件加数值”。

E表示“可执行文件加数值”。

下面以本例中main函数中调用add子过程为例说明计算方法。

汇编方式查看目标文件代码如下,命令为objdump --disassemble ELF文件:


可以看到,main函数在文件中偏移为0x1C,0x44处调用了子过程add,0x45开始的4字节值为0xFFFFFFFC,也就是加数,他是以补码形式表示的,换算成十进制为-4,即A=-4。对照上一小节目标文件重定位表可知,偏移为0x45的重定位项名字是add,汇编代码“call 45”即表示调用了该重定位项。

汇编方式查看可执行文件代码如下:


可执行文件中,add子过程入口为0x8048434,即S=0x08048434;重定位项位置为0x08048479,即P=0x08048479。所以可得E为:

E=S –P + A

= 0x08048434 - 0x08048479 + (-4)

= 134513716 – 134513785 – 4

= -73

-73的二进制补码为0xFFFFFFB7,正如可执行文件0x08048479处的4个字节的值一样。

-73实际上是一个偏移量,指明了重定位符号跟当前位置的差值。但有的读者会注意到,add子过程入口0x08048434和重定位项偏移0x08048479差值为-69,而不是-73,两者相差4,正好是“目标文件加数”的绝对值。这样的结果是跟IA-32处理器工作方式有关的。在执行可执行文件时,0x08048478处call add指令被执行后,CPU的IP寄存器即指向下一条指令的地址,即0x0804847D,前后两条指令差值正好是4。但由于是调用了子过程,IP指令需要跳转到子过程的入口,那么如何从现在IP=0x0804847D这个位置跳转到add指令的正确位置呢?add子过程入口0x08048434和重定位项偏移0x08048479差值为-69,而重定位项偏移和IP=0x0804847D差值为-4,则add子过程入口和IP差值为-69-4=-73,0x0804847D加上十进制-73,为0x08048434,正好是add子过程的入口地址。


七、动态链接

ELF文件中,以下两个节用于保存动态链接器所需要的数据:

.dynsym保存了有关符号表,包含了所有需要通过外部引用解决的符号。本例中引用外部符号如下:

 

.dynamic保存了一个数组,数组类型为Elf32_Dyn类型,以下内容都是关于这个节的。

1.      数据结构

typedef struct dynamic{  Elf32_Sword d_tag;  union{    Elf32_Swordd_val;    Elf32_Addrd_ptr;  } d_un;} Elf32_Dyn;

d_tag用于区分各种指定信息类型的标记,该结构中的联合根据该标记进行解释。d_un或者保存一个虚拟地址,或者保存一个整数。

最重要的标记如下:

DT_NEEDED指定改程序执行所需的一个动态库,d_un指向一个字符串表项,给出库的名称。本例当中只用到标准库。

DT_STRTAB保存了字符表的位置,其中包括了dynamic节所需的所有动态链接库和符号名称。

DT_SYMTAB保存了符号表的位置,其中包含了dynamic节所需的所有信息。

DT_INIT和DT_FINT保存了用于初始化和结束程序的函数。

2.      动态链接信息

用命令查看本例动态链接信息如下:



至此,关于Linux系统中ELF文件的格式已基本分析完毕。

0 0
原创粉丝点击