程序运行之动态链接三

静态链接情况下,操作系统接着就把控制权交给可执行文件的入口地址,然后程序开始执行。但在动态链接情况下,操作系统还不能在装载完可执行文件后就把控制权交给程序。因为可执行文件依赖很多共享对象。这个时候可执行文件中对于很多外部符号的引用还处于无效地址的状态,也就是还没有跟相应的共享对象中的实际位置链接过来。所以在映射完可执行文件后,操作系统会先启动一个动态链接器。

在Linux下,动态链接器ld.so实际上是一个共享对象,操作系统通过映射的方式将它加载到进程的地址空间中。操作系统在加载动态链接器之后,就将控制权交给动态链接器的入口地址。当动态链接器得到控制权之后,它就开始自身的初始化操作,然后根据当前的环境参数,开始对可执行文件进行链接工作。当所有的动态链接都完成后,动态链接器会将控制权转交到可执行文件的入口地址,程序开始执行。

那么这个ld.so在什么位置呢。执行objdump -s program1可以看到在.interp段中有/lib64/ld-linux- x86-64.so.2

program1:     文件格式 elf64-x86-64

 

Contents of section .interp:

 0238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-

 0248 7838362d 36342e73 6f2e3200           x86-64.so.2

.interp段的内容其实就是保存一个字符串,这个字符串就是可执行文件所需要的动态链接器的路径。

 

.dynamic段

动态链接ELF中最重要的就是.dynamic段,这个段里面保存了动态链接器的所有信息,比如依赖哪些共享对象,动态链接符号表的位置,动态链接重定位表的位置,共享对象初始化代码的地址等等。结构定义在”elf.h”中

typedef struct {

     Elf32_Sword d_tag;

     union {

         Elf32_Word d_val;

         Elf32_Addr d_ptr;

     }

 } Elf32_Dyn

Elf32_Dyn结构由一个类型值加上 附加的数组和指针。对于不同的类型,后面附加的数值或者指针有着不同的含义。

使用readelf -d Lib.so可以查看.dynamic段的内容。

 

Dynamic section at offset 0xe20 contains 24 entries:

  标记        类型                         名称/值

 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]

 0x000000000000000c (INIT)               0x520

 0x000000000000000d (FINI)               0x690

 0x0000000000000019 (INIT_ARRAY)         0x200e10

 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)

 0x000000000000001a (FINI_ARRAY)         0x200e18

 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)

 0x000000006ffffef5 (GNU_HASH)           0x1f0

 0x0000000000000005 (STRTAB)             0x368

 0x0000000000000006 (SYMTAB)             0x230

 0x000000000000000a (STRSZ)              163 (bytes)

 0x000000000000000b (SYMENT)             24 (bytes)

 0x0000000000000003 (PLTGOT)             0x201000

 0x0000000000000002 (PLTRELSZ)           48 (bytes)

 0x0000000000000014 (PLTREL)             RELA

 0x0000000000000017 (JMPREL)             0x4f0

 0x0000000000000007 (RELA)               0x448

 0x0000000000000008 (RELASZ)             168 (bytes)

 0x0000000000000009 (RELAENT)            24 (bytes)

 0x000000006ffffffe (VERNEED)            0x428

 0x000000006fffffff (VERNEEDNUM)         1

 0x000000006ffffff0 (VERSYM)             0x40c

 0x000000006ffffff9 (RELACOUNT)          3

 0x0000000000000000 (NULL)               0x0

另外linux还提供了ldd命令用来查看一个程序主模块或一个共享库依赖于哪些共享库:ldd pro1

        linux-vdso.so.1 (0x00007fff3cef5000)

        ./Lib.so (0x00007fe6e267b000)

        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe6e228a000)

        /lib64/ld-linux-x86-64.so.2 (0x00007fe6e2a7f000)

 

动态符号表

为了完成动态链接,最关键的还是所依赖的符号和相关文件的信息。在静态链接中,有一个专门的段叫做.symtab,里面保存了所有关于该目标文件的符号的定义和引用。Pro1中也导入了foobar函数,并且提供给其他模块使用。为了表示动态链接这些模块之间的符号导入导出关系,ELF专门有一个叫做动态符号表的段用来保存这些信息。段名是.dynsym。里面只保存与动态链接相关的符号。为了加快符号的查找,往往还有辅助的符号哈希表(.hash)。我们可以用readelf工具来查看ELF文件的动态符号表及它的哈希表

readelf -sD Lib.so

Symbol table of `.gnu.hash' for image:

  Num Buc:    Value          Size   Type   Bind Vis      Ndx Name

    7   0: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT  22 _edata

    8   0: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT  23 _end

    9   1: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT  23 __bss_start

   10   1: 0000000000000520     0 FUNC    GLOBAL DEFAULT   9 _init

   11   2: 0000000000000690     0 FUNC    GLOBAL DEFAULT  13 _fini

   12   2: 000000000000065a    51 FUNC    GLOBAL DEFAULT  12 foobar

 

动态链接重定位表:

动态链接下,无论是可执行文件还是共享对象,一旦它依赖于其他的共享对象。也是有导入的符号,那么代码或者数据中就有对导入符号的引用。动态链接的时候,导入符号的地址需要运行的时候才能确定,所以需要在运行时将这些导入符号的引用修正,也就是重定位。静态链接中,.rel.text表示代码段的重定位表, .rel.data表示数据段的重定位表。动态链接中,类似的重定位表分别是.rel.dyn和.rel.plt。相当于.rel.text和.rel.data。.rel.dyn修正的位置位于.got以及数据段,.rel.plt是对函数引用的修正,修正位置位于.got.plt。可以用readelf来查看动态链接的文件的重定位表

readelf -r Lib.so

 

重定位节 '.rela.dyn' at offset 0x448 contains 7 entries:

  偏移量          信息           类型           符号值        符号名称 + 加数

000000200e10  000000000008 R_X86_64_RELATIVE                    650

000000200e18  000000000008 R_X86_64_RELATIVE                    610

000000201028  000000000008 R_X86_64_RELATIVE                    201028

000000200fe0  000100000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0

000000200fe8  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

000000200ff0  000400000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0

000000200ff8  000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0

 

重定位节 '.rela.plt' at offset 0x4f0 contains 2 entries:

  偏移量          信息           类型           符号值        符号名称 + 加数

000000201018  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0

000000201020  000500000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0

 

 

比如我们看printf的重定位入口,类型为R_X86_64_JUMP_SLOT,偏移为000000201018位于.got.plt中。当动态链接需要重定位的时候,先查找printf的地址,”printf”位于libc-2.6.1.so。假设链接在全局符号表里面找到printf的地址是0x01010101,那么连接器就将这个地址填入到.got.plt中的偏移为000000201018的位置中去实现地址重定位。

如果某个ELF文件是以PIC模式编译的,并调用了一个外部函数printf,则printf会出现在.rel.plt中。但是如果不以PIC模式编译,则printf将出现在.rel.dyn中。

 

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

当操作系统把控制权交给动态链接器的时候,它将开始链接工作。但是需要知道关于可执行文件和本进程的一些信息。比如可执行文件有几个段,每个段的属性,程序的入口地址。这些信息都保存在堆栈里面,用一个结构数组来表示。

typedef struct

{

        uint32_t a_type;

        union

        {

                 uint32_t a_val;

        }a_un;

}Elf32_auxv_t;

字段定义如下:

在栈中的分布如下:

 

posted @ 2019-06-24 14:17  red_leaf_412  阅读(495)  评论(0编辑  收藏  举报