so文件中重要的Section--符号表、字符串表、重定位表

转:https://blog.csdn.net/feglass/article/details/51469511

下面我们分析一些so文件中重要的Section,包括符号表、重定位表、GOT表等。

-符号表(.dynsym,.symtab)

函数和变量作为符号被存在可执行文件中, 不同类型的符号又聚合在一起, 称为符号表. 有两种类型的符号表, 一种是常规的(.symtab和.strtab), 另一种是动态的(.dynsym和.dynstr), 他们都在对应的section中。常规的符号表通常只在调试时用到. 我们平时用的strip命令删除的就是该符号表; 而动态符号表则是程序执行时候真正会查找的目标.

 

符号是对某种类型的变量和代码(例如全局变量和函数)的符号引用。例如,printf() 函数会在动态符号表.dynsym 中有一个指向该函数的符号条目。在大多数共享库和动态链接可执行文件中,存在两个符号表:.dynsym 和 .symtab。.dynsym 保存了来自外部文件符号的全局符号。例如printf() 此类的库函数。.dynsym保存的符号是.symtab所保存的符号的子集。后者还保存了可执行文件本地的全局符号,例如全局变量或者是本地函数。因此.symtab保存了所有的符号,而.dynsym只保存了动态/全局符号。.dynsym被标记为ALLOC(A),而.symtab没有标记。有A标记意味着运行时分配并装入内存。而.symtab 不是运行是必需的,因此不会被装载到内存中。.dynsym 保存的符号是运行时动态链接器所需要的唯一符号,是必需的。而.symtab符号表只是用来进行调试和链接,因此有时为了节省空间,会将.symtab 符号表从生产二进制文件中删除。

符号表项的格式如下:

typedef struct {  
     Elf32_Word st_name;      //符号表项名称。如果该值非0,则表示符号名的字
                                             //符串表索引(offset),否则符号表项没有名称。
     Elf32_Addr st_value;       //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。
     Elf32_Word st_size;         //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。
     unsigned char st_info;    //符号的类型和绑定属性。
     unsigned char st_other;  //未定义。
     Elf32_Half st_shndx;        //每个符号表项都以和其他节区的关系的方式给出定义。
             //此成员给出相关的节区头部表索引。
} Elf32_sym; 

符号的源码定义中

st_name

st_name 变量指向符号表中字符串表(.dynstr 或者 .strtab)的字符串偏移。偏移位置保存的是符号的名称字符串,例如 printf。

st_info

st_info 定义了符号的类型和绑定属性。

符号类型

  • STT_NOTYPE: 符号类型未定义
  • STT_FUNC: 该符号与函数或者其他可执行代码关联
  • STT_OBJECT: 该符号与数据目标文件关联

 符号绑定

  • STB_LOCAL: 本地符号在目标文件之外是不可见的。目标文件包含了符号的定义,例如声明一个为static的函数
  • STB_GLOBAL: 全局符号对于所有要合并的目标文件来说都是可见的。一个全局符号在一个文件中进行定义后,另一个文件可以对这个符号进行引用。类比源码不同 .c 文件编译为 .o 文件,各个文件之间可能存在函数调用关系,不同的 .o 目标文件链接打包在一起。
  • STB_WEAK: 与全局绑定类似。不过比 STB_GLOBAL 的优先级低。被标记为 STB_WEAK 的符号有可能被同名的未被标记为 STB_WEAK 的符号覆盖。类似源码中的符号名作用域和优先级。

下面是通过010Editor解析出的符号表.dynsym的section header表项:
这里写图片描述

再来看一下符号表的具体内容:
这里写图片描述

符号的存在可以大大方便调试、反编译等工作。但是符号也可以被去掉。

一个二进制文件的符号表 .symtab 可以很容易地被去掉,但是动态链接可执行文件会保留.dynsym,因此文件中会显示导入库的符号。如果一个文件通过静态编译,或者没有使用libc进行链接,然后使用strip命令清理。这个文件就不会有符号表,此时动态符号表也不存在,因为不是必需的。符号删除后,函数名会被替换成sub_xxxx的形式,增加辨识难度。

-字符串表(.dynstr .shstrtab .strtab)

上面我们提到,符号表的st_name是符号名的字符串表中的索引,那么字符串表中肯定存放着所有符号的名称字符串。下面,我们先来看一看字符串表的section header表项:
这里写图片描述

再看一下下图中字符串表的具体内容,我们可以看出,.dynstr和.shstrtab结构完全相同,不过一个存储的是符号名称的字符串,而另一个是Section名称的字符串
这里写图片描述

-重定位表

重定位表在ELF文件中扮演很重要的角色,首先我们得理解重定位的概念,程序从代码到可执行文件这个过程中,要经历编译器,汇编器和链接器对代码的处理。然而编译器和汇编器通常为每个文件创建程序地址从0开始的目标代码,但是几乎没有计算机会允许从地址0加载你的程序。如果一个程序是由多个子程序组成的,那么所有的子程序必需要加载到互不重叠的地址上。重定位就是为程序不同部分分配加载地址,调整程序中的数据和代码以反映所分配地址的过程。简单的言之,则是将程序中的各个部分映射到合理的地址上来。
换句话来说,重定位是将符号引用与符号定义进行连接的过程。例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。
具体来说,就是把符号的value进行重新定位。

可重定位文件必须包含如何修改其节区内容的信息,从而允许可执行文件和共享目标文件保存进程的程序映象的正确信息。这就是重定位表项做的工作。重定位表项的格式如下:

typedef struct {  
    Elf32_Addr r_offset;     //重定位动作所适用的位置(受影响的存储单位的第一个字节的偏移或者虚拟地址)
    Elf32_Word r_info;       //要进行重定位的符号表索引,以及将实施的重定位类型(哪些位需要修改,以及如何计算它们的取值)
                                         //其中 .rel.dyn 重定位类型一般为R_386_GLOB_DAT和R_386_COPY;.rel.plt为R_386_JUMP_SLOT
} Elf32_Rel; 
 ```
 ```
typedef struct {  
    Elf32_Addr r_offset;  
    Elf32_Word r_info;  
    Elf32_Word r_addend;
 } Elf32_Rela; 

对 r_info 成员使用 ELF32_R_TYPE 宏运算可得到重定位类型,使用 ELF32_R_SYM 宏运算可得到符号在符号表里的索引值。 三种宏的具体定义如下:

#define ELF32_R_SYM(i) ((i)>>8) 
#define ELF32_R_TYPE(i) ((unsigned char)(i)) 
#define ELF32_R_INFO(s, t) (((s)

再看一下重定位表中的内容。

这里写图片描述

以下是.rel.plt表的具体内容:
这里写图片描述

我们可以看到,每8个字节(s_entsize)一个表项。第一个表项中的r_offset值为0xc7660,r_info为0xa16。其中r_offset指向下图中GOT表中第一项__imp_clock_gettime外部函数地址。那么我们如何利用r_info值来找到其对应的符号呢?如上所述,进行 ELF32_R_SYM宏运算实际上就是将r_info右移8位,0xa16右移8位得到0xa,因此这就是其在符号表中的索引。

这里写图片描述

从下图中可以看见符号表的s_entsize值为10h,即16个字节每条目。因此我们可以找到其索引为0xa的条目的st_name值为0x9ea。那么怎么证明我们确实找到的是clock_gettime函数的符号呢?我们再来看一下st_name值是不是正确的。

这里写图片描述

st_name值表示的是符号名字符串中的第一个字符在字符串表中的偏移量,因此我们用0x9ea加上字符串表的起始位置(0x7548)就能得到该字符串在‭0x7F32位置。如下图所示。

这里写图片描述
这里写图片描述

-常见的重定位表类型:

  • .rel.text:重定位的地方在.text段内,以offset指定具体要定位位置。在链接时候由链接器完成。.rel.text属于普通重定位辅助段 ,他由编译器编译产生,存在于obj文件内。连接器连接时,他用于最终可执行文件或者动态库的重定位。通过它修改原obj文件的.text段后,合并到最终可执行文件或者动态文件的.text段。其类型一般为R_386_32和R_386_PC32。

  • .rel.dyn:重定位的地方在.got段内。主要是针对外部数据变量符号。例如全局数据。重定位在程序运行时定位,一般是在.init段内。定位过程:获得符号对应value后,根据rel.dyn表中对应的offset,修改.got表对应位置的value。另外,.rel.dyn 含义是指和dyn有关,一般是指在程序运行时候,动态加载。区别于rel.plt,rel.plt是指和plt相关,具体是指在某个函数被调用时候加载。我个人理解这个Section的作用是,在重定位过程中,动态链接器根据r_offset找到.got对应表项,来完成对.got表项值的修改。

.rel.dyn和.rel.plt是动态定位辅助段。由连接器产生,存在于可执行文件或者动态库文件内。借助这两个辅助段可以动态修改对应.got和.got.plt段,从而实现运行时重定位。

  • .rel.plt:重定位的地方在.got.plt段内(注意也是.got内,具体区分而已)。 主要是针对外部函数符号。一般是函数首次被调用时候重定位。首次调用时会重定位函数地址,把最终函数地址放到.got内,以后读取该.got就直接得到最终函数地址。我个人理解这个Section的作用是,在重定位过程中,动态链接器根据r_offset找到.got对应表项,来完成对.got表项值的修改。

  • .plt段(过程链接表):所有外部函数调用都是经过一个对应桩函数,这些桩函数都在.plt段内。具体调用外部函数过程是:
    调用对应桩函数—>桩函数取出.got表表内地址—>然后跳转到这个地址.如果是第一次,这个跳转地址默认是桩函数本身跳转处地址的下一个指令地址(目的是通过桩函数统一集中取地址和加载地址),后续接着把对应函数的真实地址加载进来放到.got表对应处,同样跳转执行该地址指令.以后桩函数从.got取得地址都是真实函数地址了。
    下图是.plt某表项,它包含了取.got表地址和跳转执行两条指令。
    这里写图片描述

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值