基于PowerPC Linux的ELF格式分析 二
标签:
elfpowerpclinuxit |
分类: PowerPC平台 |
第四部分重定位项Relocation Entries
重定位是将ELF文件中为定义符号关联到有效值的处理过程,在我们的例子test.o中,这意味着对printf、exit、puts的未定义引用必须替换为该进程的虚拟地址空间中相应的机器代码所在的空间,在目标文件中用到的符号之处都将被替换。
对用户空间程序的替换,内核并不会牵涉其中。因为所有的替换操作都是由外面工具完成的。当对于Linux内核模块来说,情况有所不同,因为内核所接受到得模块裸数据与存储在二进制文件中的数据完全相同,内核负责重定位操作。
在每个目标文件中都有一个专门的表,包含了重定位表项,标识了需要进行重定位的地方。每个表项都包含了下列信息:
第一:一个偏移量,用来指定所要修改的项的位置
第二:对符号的引用(符号表的索引),提供了所要插入的重定位位置的数据
为了说明如何使用重定位信息,我们来看一下此前test.c测试程序,我们用readelf显示的所有重定位项如下图所示:
http://s5/middle/70dd1691gbdcc3bc379b4&690Linux的ELF格式分析
在程序运行时或者test.o产生可执行文件时,如果某些机器代码使用了虚拟地址空间中位置尚不明确的符号或者函数,则会使用offset列的信息。main函数的汇编语言代码调用了若干函数,分别位于偏移量0x 7c,0x98,0xa0如下所示:
00000050 <main>:
备注:
我们可以用objdump工具查看,示意图如下:
http://s8/middle/70dd1691gbdcc478c1877&690Linux的ELF格式分析
在printf和add函数的地址已经确定后,必须将它们插入指定的偏移量处,以便可以生成正确运行的可执行代码。
4.1 Linux内核中相关的数据结构
由于技术原因,有两种类型的重定位信息,有两种稍有不同的数据结构表示:
第一种:普通重定位,SHT_REL类型的节中的重定位表项由以下的数据结构表示:
typedef struct elf32_rel {
} Elf32_Rel;
解释:
r_offset:指定需要重定位的项的位置
r_info:不仅提供了符合表中的一个位置,还包括重定位类型的有关信息。这是通过把值划分为两个部分来达到的。
第二种:需要添加常数的重定位项只出现在SHT_RELA类型的节中,这类结构由下列数据结构定义:
typedef struct elf32_rela{
} Elf32_Rela;
解释:
这里除了有第一种重定位类型提供的r_offset和r_info字段之外,还补充了r_addend字段,其中存放一个称之为加数(addend)的值。在计算重定位值时,将根据重定位类型,对该值进行不同的处理。
请注意:
在使用elf32_rel时也会出现加数这个值,尽管在数据结构中没有明确的保存,但链接器根据该值应该在内存中出现的位置,将计算出的重定位长度作为加数填入,该值的用途将在下面的例子中说明。
对两种重定位类型,都有功能等效的64位数据结构:
typedef struct elf64_rel {
} Elf64_Rel;
typedef struct elf64_rela {
} Elf64_Rela;
解释:这两个数据结构和32位的对应类型非常相似,这里就不在讨论
4.2 重定位类型
ELF标准定义了很多重定位类型,对于每种支持的体系结构,都有一个独立的集合,这些类型大部分用于生成动态库或者与装载位置无关的代码。Linux内核只对模块的重定位感兴趣,因此使用下面的两种重定位类型:相对重定位和绝对重定位。
我们通过一个例子介绍一下相对从定位:
1000058c <add>:
1000058c:
100005d8:
100005dc <main>:
100005dc:
...........................
10000608:
..........................
10000624:
10000628:
1000062c:
Disassembly of section .plt:
10010900 <__register_frame_info@plt-0x48>:
10010948 <__register_frame_info@plt>:
10010950 <__deregister_frame_info@plt>:
10010958 <__nldbl_printf@plt>:
10010960 <puts@plt>:
10010968 <__gmon_start__@plt>:
10010970 <exit@plt>:
解释:
10000608:
我们首先介绍一下bl指令的格式:
0-5
OPCD
AA=0,表示LI中存放的是相对地址LI*4,基址是当前指令的地址
AA=1,表示LI中存放的是绝对地址LI*4
LK=1,表示转移到目的地址的同时,将当前指令的下一条指令存入LR寄存器
LK=0,仅仅表示跳转到目的地址,而不用修改LR寄存器
当前bl指令的机器码是0x4b ff ff 85
所以4*LI的值为:0b11 1111 1111 1111 1111 1000 0100符号扩展为0xffff ff84对应的真值为:-124(十进制)=-0x7c
因为当前指令的地址为0x1000 0608
所以跳转的目标地址为0x1000 0608+(-0x7c)= 1000058c,即为add函数的入口地址1000058c
定义指令10000624:
同理可以得到4*LI=0x10334
所以跳转到得目的地址就是0x1000 0624+0x10334=0x1001 0958,即为.plt表中__nldbl_printf的入口地址。
4.3 动态链接
内核对必须与库动态链接产生的ELF文件不感兴趣,模块中的所有引用都可以通过重定位解决,而用户空间程序的动态链接则完全由用户空间中的ld.so进行。以下两个节用来保存动态链接器所需要的数据。
.dynsym保存了有关符号表,包含了所有需要外部引用解决的符号
.dynamic保存了一个数组,数组项为Elf32_Dyn类型,这些选项提供了以下几个段落所描述的数据。
dynamic的内容可以使用readelf查询,示意图如下:
http://s1/middle/70dd1691g79613b613d50&690Linux的ELF格式分析
输出的内容不仅包含若干生成可执行文件时自动添加的符号,还包括机器代码使用的__nldbl_printf和exit函数,@GLIBC_2.0指出至少使用GNU标准库中的2.0版本才能解决这些引用。
dynamic节中数组项的数据类型在内核中定义如下:
typedef struct dynamic{
} Elf32_Dyn;
解释:
d_tag用于区分各种指定信息类型的标记,该结构中的共用体根据该标志进行解释:
d_un或者保存一个虚拟地址,或者保存一个整数,可以根据特定的标志进行解释。
最重要的标记如下所示:
DT_NEEDED指定该执行程序所需要的一个动态库,d_un指向一个字符串表项,给出了库的名称,对于test.c测试程序来说只需要C标准库,如下图所示:
http://s16/middle/70dd1691gbdcc55a5c9cf&690Linux的ELF格式分析
DT_STRTAB保存了字符串表的位置,其中包括了dynamic节所需要的所有的动态库和符号的名称。
DT_SYMTAB保存了符号表的位置,其中包含了dynamic节所需要的所有的信息
DT_INIT和DT_FINI保存了用于初始化和结束程序的地址。
参考资料:
Professional Linux Kernel Architecture.Wolfgang Mauerer. Page1273-Page1267
http://www.ibm.com/developerworks/cn/linux/l-excutff/
http://blog.chinaunix.net/space.php?uid=16329656&do=blog&id=2747482

加载中…