《程序员的自我修养》学习笔记(八)————动态链接(3):动态链接相关结构

        动态链接在不同的系统上有不同的实现方式,ELF的动态链接实现方式比PE稍微简单一点。动态链接情况下,可执行文件的装载与静态链接情况基本一样。首先操作系统会读取可执行文件的头部,检查文件的合法性,然后从头部中的”Program Header”中读取每个”Segment”的虚拟地址、文件地址和属性,并将它们映射到进程虚拟空间的相应位置,这些步骤跟前面的静态链接情况下的装载基本无异。在静态链接情况下,操作系统接着就可以把控制权转交给可执行文件的入口地址,然后程序开始执行,一切看起来非常直观。
        但是在动态链接情况下,操作系统还不能在装载完可执行文件之后就把控制权交给可执行文件,因为我们知道可执行文件依赖于很多共享对象。这时候,可执行文件里对于很多外部符号的引用还处于无效地址的状态,即还没有跟相应的共享对象中的实际位置链接起来。所以在映射完可执行文件之后,操作系统会先启动一个动态链接器(Dynamic Linker)
        在Linux下,动态链接器ld.so实际上是一个共享对象,操作系统同样通过映射的方式将它加载到进程的地址空间中。操作系统在加载完动态链接器之后,就将控制权交给动态链接器的入口地址(与可执行文件一样,共享对象也有入口地址)。当动态链接器得到控制权之后,它开始执行一系列自身的初始化操作,然后根据当前的环境参数,开始对可执行文件进行动态链接工作。当所有动态链接工作完成以后,动态链接器会将控制权转交给可执行文件的入口地址,程序开始正式执行。

1. “.interp”段

        动态链接器的位置既不是由系统配置指定,也不是由环境参数决定,而是由ELF可执行文件决定。在动态链接的ELF可执行文件中,有一个专门的段叫做“.interp”段(“.interp”是”interpreter”(解释器)的缩写)。如果我们使用objdump工具来查看,可以看到“.interp”内容,如下图所示。

        “.interp”的内容很简单,里面保存的就是一个字符串,这个字符串就是可执行文件所需要的动态链接器的路径,在Linux下,可执行文件所需要的动态链接器的路径几乎都是“/lib/ld-linux.so.2”,其它的*nix操作系统可能会有不同的路径。在Linux的系统中,/lib/ld-linux.so.2通常是一个软链接,比如在我的机器上,它指向/lib/ld-linux.so.2,这个才是真正的动态链接器。在Linux中,操作系统在对可执行文件进行加载的时候,它会去寻找装载该可执行文件所需要相应的动态链接器,即”.interp”段指定的路径的共享对象。

        动态链接器在Linux下是Glibc的一部分,也就是属于系统库级别的,它的版本号往往跟系统中的Glibc库版本号是一样的。当系统中的Glibc库更新或者安装其它版本的时候,/lib/ld-linux.so.2这个软链接就会指向到新的动态链接器,而可执行文件本身不需要修改”.interp”中的动态链接器路径来适应系统的升级。
        我们也可以使用”readelf -l Program1 | grep interpreter”命令来查看一个可执行文件所需要的动态链接器的路径。

2. “.dynamic”段

         动态链接ELF中最重要的结构应该是”.dynamic”段,这个段里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。”.dynamic”段的结构Elf64_Dyn或Elf32_Dyn定义在/usr/include/elf.h文件中,如下:

/* Dynamic section entry.  */
typedef struct
{
  Elf32_Sword	d_tag;			/* Dynamic entry type */
  union
    {
      Elf32_Word d_val;			/* Integer value */
      Elf32_Addr d_ptr;			/* Address value */
    } d_un;
} Elf32_Dyn;

typedef struct
{
  Elf64_Sxword	d_tag;			/* Dynamic entry type */
  union
    {
      Elf64_Xword d_val;		/* Integer value */
      Elf64_Addr d_ptr;			/* Address value */
    } d_un;
} Elf64_Dyn;

        使用readelf工具可以查看”.dynamic”段的内容,另外,Linux还提供了一个ldd命令用来查看程序主模块或一个共享库依赖于哪些共享库,如下图所示

3. 动态符号表

        在静态链接中,有一个专门的段叫做符号表”.symtab”(Symbol Table),里面保存了所有关于该目标文件的符号的定义和引用。动态链接的符号表示实际上它跟静态链接十分相似。比如前面例子中的Program1程序依赖于Lib.so,引用到了里面的foobar()函数。那么对于Program1来说,我们往往称Program1导入(Import)了foobar函数,foobar是Program1的导入函数(Import Function);而站在Lib.so的角度来看,它实际上定义了foobar()函数,并且提供给其它模块使用,我们往往称Lib.so导出(Export)了foobar()函数,foobar是Lib.so的导出函数(Export Function)。把这种导入导出关系放到静态链接的情形下,我们可以把它们看作普通的函数定义和引用。为了表示动态链接这些模块之间的符号导入导出关系,ELF专门有一个叫做动态符号表(Dynamic Symbol Table)的段来保存这些信息,这个段的段名通常叫做”.dynsym”(Dynamic Symbol)。与”.symtab”不同的是, ”.dynsym”只保存了与动态链接相关的符号,对于那些模块内部的符号,比如模块私有变量则不保存。很多时候动态链接的模块同时拥有”.dynsym”和”.symtab”两个表,”.symtab”中往往保存了所有符号,包括”.dynsym”中的符号。
         与”.symtab”类似,动态符号表也需要一些辅助的表,比如用于保存符号名的字符串表。静态链接时叫做符号字符串表”.strtab”(String Table),在这里就是动态符号字符串表”.dynstr”(String Table),在这里就是动态符号字符串表”.dynstr”(Dynamic String Table);由于动态链接下,我们需要在程序运行时查找符号,为了加快符号的查找过程,往往还有辅助的符号哈希表(“.hash”)。我们可以用readelf工具来查看ELF文件的动态符号表及它的哈希表,如下图所示:

        动态链接符号表的结构与静态链接的符号表几乎一样,我们可以简单地将导入函数看作是对其它目标文件中函数的引用;把导出函数看作是在本目标文件定义的函数就可以了。

4. 动态链接重定位表

       共享对象需要重定位的主要原因是导入符号的存在。动态链接下,无论是可执行文件或共享对象,一旦它依赖于其它共享对象,也就是说有导入的符号时,那么它的代码或数据中就会有对于导入符号的引用。在编译时这些导入符号的地址未知,在静态链接中,这些未知的地址引用在最终链接时被修正。但是在动态链接中,导入符号的地址在运行时才确定,所以需要在运行时将这些导入符号的引用修正,即需要重定位。动态链接的可执行文件使用的是PIC方法,但这不能改变它需要重定位的本质。对于动态链接来说,如果一个共享对象不是以PIC模式编译的,那么毫无疑问,它是需要在装载时被重定位的;如果一个共享对象是PIC模式编译的,也需要重定位。对于使用PIC技术的可执行文件或共享对象来说,虽然它们的代码段不需要重定位(因为地址无关),但是数据段还包含了绝对地址的引用,因为代码段中绝对地址相关的部分被分离了出来,变成了GOT,而GOT实际上是数据段的一部分。除了GOT以外,数据段还可能包含绝对地址引用。

        动态链接重定位相关结构共享对象的重定位与前面”静态链接”中分析的目标文件的重定位是否类似,唯一有区别的是目标文件的重定位是在静态链接时完成的,而共享对象的重定位是在装载时完成的。在静态链接中,目标文件里面包含有专门用于表示重定位信息的重定位表,比如”.rel.text”表示是代码段的重定位表,”.rel.data”是数据段的重定位表。动态链接的文件中,也有类似的重定位表分别叫做”.rel.dyn”(“.rela.dyn”)和”.rel.plt”(“.rela.plt”),它们分别相当于”.rel.text”和”.rel.data”。”.rel.dyn”实际上是对数据引用的修正,它所修正的位置位于”.got”以及数据段;而”.rel.plt”是对函数引用的修正,它所修正的位置位于”.got.plt”。我们可以使用readelf来查看一个动态链接的文件的重定位表,如下图所示:

5. 动态链接时进程堆栈初始化信息

        站在动态链接器的角度看,当操作系统把控制权交给它的时候,它将开始做链接工作,那么至少它需要知道关于可执行文件和本进程的一些信息,比如可执行文件有几个段(“Segment”)、每个段的属性、程序的入口地址(因为动态链接器到时候需要把控制权交给可执行文件)等。这些信息往往由操作系统传递给动态链接器,保存在进程的堆栈里面。进程初始化的时候,堆栈里面保存了关于进程执行环境和命令行参数等信息。事实上,堆栈里面还保存了动态链接器所需要的一些辅助信息数组(Auxiliary Vector)。辅助信息的格式也是一个结构数组,它的结构定义在” /usr/include/elf.h”:

/* Auxiliary vector.  */

/* This vector is normally only used by the program interpreter.  The
   usual definition in an ABI supplement uses the name auxv_t.  The
   vector is not usually defined in a standard <elf.h> file, but it
   can't hurt.  We rename it to avoid conflicts.  The sizes of these
   types are an arrangement between the exec server and the program
   interpreter, so we don't fully specify them here.  */

typedef struct
{
  uint32_t a_type;		/* Entry type */
  union
    {
      uint32_t a_val;		/* Integer value */
      /* We use to have pointer elements added here.  We cannot do that,
	 though, since it does not work when using 32-bit definitions
	 on 64-bit platforms and vice versa.  */
    } a_un;
} Elf32_auxv_t;

typedef struct
{
  uint64_t a_type;		/* Entry type */
  union
    {
      uint64_t a_val;		/* Integer value */
      /* We use to have pointer elements added here.  We cannot do that,
	 though, since it does not work when using 32-bit definitions
	 on 64-bit platforms and vice versa.  */
    } a_un;
} Elf64_auxv_t;

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值