动态链接 Surager

动态链接

渊源和冤缘愿怨

静态链接虽然很大程度地促进了程序的开发效率,也暴露出了很多的缺点,比如浪费内存和磁盘空间、模块更新困难等很多问题。

在内存和磁盘空间方面,静态链接将重复的模块重复地储存和加载。C语言静态库就是一个很典型的例子。

在程序开发和发布方面,一旦程序中有任一模块需要更新,整个程序就需要重新链接、发布给用户。每次更新包都很大。

动态链接的思想

解决以上两个问题的最简单的办法,就是把程序的模块相互分割开来。就是不对那些组成程序的目标文件进行链接,等到程序运行时再进行链接。也就是说,把链接的整个过程推迟到运行时进行,这就是动态链接的基本思想。

比如有两个程序Program1和Program2,我们有Program1.o、Program2.o、Lib.o三个目标文件。当我们运行Program1时,系统首先加载Program1.o,当系统发现Program1.o中用到了Lib.o,那么系统就加载Lib.o。依次加载其他目标文件。加载完毕之后,系统开始链接工作。完成之后系统将控制权交到Program1.o程序入口,程序开始运行。

这时如果我们运行Program2,我们就不用加载Lib.o,用到时直接链接即可。

此外,程序的动态链接还增加了程序的可扩展性和兼容性。

动态链接的基本实现

动态链接涉及到运行时的链接和多个文件的装载,必须有操作系统的支持。目前的主流系统几乎都支持动态链接这种方式。在Linux系统中,ELF动态链接文件被称为动态共享对象(DSO,Dynamic Shared Objects),他们一般以”.so”为扩展名。在Windows系统中,动态链接文件被称为动态链接库(Dynamic Linking Library),他们一般以”.dll”为扩展名。

在Linux中,常用的C语言库的运行库glibc,它的动态链接形式的版本保存在”/lib”目录下,文件名叫”libc.so”。整个系统只保留一份。而所有的C语言编写的、动态链接的程序都可以在运行时使用它。当程序被装载时,系统的动态链接器就会将程序所需要的所有动态链接库装载到进程的地址空间,并且将程序中所有未决议的符号绑定到相应的动态链接库中,并进行重定位工作。

简单的动态链接例子

下面准备四个文件源文件。

/* Program1.c */
#include "Lib.h"

int main()
{
    foobar(1);
    return 0;
}

/* Program2.c */
#include "Lib.h"

int main()
{
    foobar(2);
    return 0;
}

/* Lib.c */
#include <stdio.h>

void foobar(int i)
{
    printf("Printing from Lib.so %d\n", i);
}

/* Lib.h */
#ifndef LIB_H
#define LIB_H

void foobar(int i);

#endif

我们用gcc将Lib.c编译成一个共享文件:

gcc -fPIC -shared -o Lib.so Lib.c

-shared:生成共享文件

-fPIC:敬请期待

然后分别编译链接Program1.c和Program2.c

gcc -o Program1 Program1.c ./Lib.so

gcc -o Program2 Program2.c ./Lib.so

从Program1的角度来看,整个编译链接的过程是这样的:

动态链接过程

在这里Program1.c被编译成Program1.o时,编译器还不知道foobar()的地址。当链接时,链接器必须要确定foobar()函数的性质。如果foobar()是个动态共享对象中的函数,那么链接器就会将这个符号的引用标记为一个动态链接符号,不对它进行重定位,把整个过程留到装载时进行。

因为Lib.so中保存了完整的符号信息,把Lib.so作为输入文件之一,那么链接器在解析符号时就可以知道foobar是定义在Lib.so中的动态符号了。

动态链接程序运行时地址空间分布

对于动态链接的映射来说,除了可执行文件本身,还有它所依赖的共享目标文件。那么此时进程的地址空间分布是怎么样的呢?

我们在Lib.c中加入sleep函数:

#include <stdio.h>

void foobar(int i)
{
    printf("Printing from Lib.so %d\n", i);
    sleep(-1);
}

然后就可以这样查看虚拟空间分布。

abc@Blue-Whale:~/work/linkers_loaders$ ./Program1 &
[3] 3007
Printing from Lib.so 1
abc@Blue-Whale:~/work/linkers_loaders$cat /proc/3007/maps
558b80117000-558b80118000 r-xp 00000000 08:01 1967839                    /home/abc/work/linkers_loaders/Program1
558b80317000-558b80318000 r--p 00000000 08:01 1967839                    /home/abc/work/linkers_loaders/Program1
558b80318000-558b80319000 rw-p 00001000 08:01 1967839                    /home/abc/work/linkers_loaders/Program1
558b80c4b000-558b80c6c000 rw-p 00000000 00:00 0                          [heap]
7f12b636d000-7f12b6554000 r-xp 00000000 08:01 1054087                    /lib/x86_64-linux-gnu/libc-2.27.so
7f12b6554000-7f12b6754000 ---p 001e7000 08:01 1054087                    /lib/x86_64-linux-gnu/libc-2.27.so
7f12b6754000-7f12b6758000 r--p 001e7000 08:01 1054087                    /lib/x86_64-linux-gnu/libc-2.27.so
7f12b6758000-7f12b675a000 rw-p 001eb000 08:01 1054087                    /lib/x86_64-linux-gnu/libc-2.27.so
7f12b675a000-7f12b675e000 rw-p 00000000 00:00 0 
7f12b675e000-7f12b675f000 r-xp 00000000 08:01 1967831                    /home/abc/work/linkers_loaders/Lib.so
7f12b675f000-7f12b695e000 ---p 00001000 08:01 1967831                    /home/abc/work/linkers_loaders/Lib.so
7f12b695e000-7f12b695f000 r--p 00000000 08:01 1967831                    /home/abc/work/linkers_loaders/Lib.so
7f12b695f000-7f12b6960000 rw-p 00001000 08:01 1967831                    /home/abc/work/linkers_loaders/Lib.so
7f12b6960000-7f12b6987000 r-xp 00000000 08:01 1054059                    /lib/x86_64-linux-gnu/ld-2.27.so
7f12b6b62000-7f12b6b65000 rw-p 00000000 00:00 0 
7f12b6b85000-7f12b6b87000 rw-p 00000000 00:00 0 
7f12b6b87000-7f12b6b88000 r--p 00027000 08:01 1054059                    /lib/x86_64-linux-gnu/ld-2.27.so
7f12b6b88000-7f12b6b89000 rw-p 00028000 08:01 1054059                    /lib/x86_64-linux-gnu/ld-2.27.so
7f12b6b89000-7f12b6b8a000 rw-p 00000000 00:00 0 
7ffc2b241000-7ffc2b262000 rw-p 00000000 00:00 0                          [stack]
7ffc2b272000-7ffc2b275000 r--p 00000000 00:00 0                          [vvar]
7ffc2b275000-7ffc2b276000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
abc@Blue-Whale:~/work/linkers_loaders$ kill 3007
[3]   Terminated              ./Program1

Program1除了使用Lib.so之外,还用到了C语言运行库libc-2.27.so。另外还用到了ld-2.27.so,这是Linux下的动态链接器。动态链接器被映射到进程的地址空间,在运行Program1之前,系统首先将控制权交给动态链接器,由它来完成动态链接工作,之后把控制权交给Program1,然后开始执行。

我们查看一下Lib.so的装载属性:

abc@Blue-Whale:~/work/linkers_loaders$ readelf -l Lib.so

Elf file type is DYN (Shared object file)
Entry point 0x580
There are 7 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000754 0x0000000000000754  R E    0x200000
  LOAD           0x0000000000000e10 0x0000000000200e10 0x0000000000200e10
                 0x0000000000000220 0x0000000000000228  RW     0x200000
  DYNAMIC        0x0000000000000e20 0x0000000000200e20 0x0000000000200e20
                 0x00000000000001c0 0x00000000000001c0  RW     0x8
  NOTE           0x00000000000001c8 0x00000000000001c8 0x00000000000001c8
                 0x0000000000000024 0x0000000000000024  R      0x4
  GNU_EH_FRAME   0x00000000000006b4 0x00000000000006b4 0x00000000000006b4
                 0x0000000000000024 0x0000000000000024  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000000e10 0x0000000000200e10 0x0000000000200e10
                 0x00000000000001f0 0x00000000000001f0  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
   01     .init_array .fini_array .dynamic .got .got.plt .data .bss 
   02     .dynamic 
   03     .note.gnu.build-id 
   04     .eh_frame_hdr 
   05     
   06     .init_array .fini_array .dynamic .got

动态链接模块的装载地址是从0x00000000开始的。我们知道这个地址是无效地址,Lib.so 的最终装载地址也并不是0x00000000。从这一点可以判断,共享文件的最终装载地址在编译时是不确定的。

地址无关代码

固定装载地址的困扰

我们假设把0x1000到0x2000分配给模块A,把0x2000到0x3000分配给模块B,当有人制造一个程序该程序用到模块B但不用模块A的时候,他可能认为0x1000到0x2000是空闲的。于是分配给另外一个模块C,于是就产生了冲突。

早期确实有这样的做法,叫做静态共享库,就是将程序的各种模块统一交给操作系统来管理,操作系统在特定位置划分出一些地址块,为已知的模块预留足够的空间。

为了解决这个问题,我们设想让共享对象在任意地址加载。换一种表述即共享对象在编译时不能假设自己在进程虚拟地址空间中的位置。

装载时重定位

在装载时,对所有绝对地址的引用不作重定位,而把这一步推迟到装载时再完成。一旦模块装载地址确定,即目标地址确定,那么系统就对程序中所有的绝对地址引用进行重定位。这种情况叫做装载时重定位。

地址无关代码

上面的-fPIC敬请期待就是在这里。

装载时重定位的方法使得指令部分无法在多个进程之间共享。一种更好的解决方法就是把指令中那些需要被修改的部分分离出来,跟数据放在一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本,这种方案就是目前被称作地址无关代码的技术。

这里我们可以把共享模块中的地址引用按照是否为跨模块分为两类:模块内部引用和模块外部引用;按照不同引用方式又可以分成指令引用和数据访问,这样就得到了四种情况。

  • 模块内部的函数调用、跳转
  • 模块内部的数据访问(全局变量、静态变量)
  • 模块外部的函数调用、跳转
  • 模块外部的数据访问(其他模块定义的全局变量)

4种寻址方式

类型一 模块内部调用或跳转

以上foo对bar的调用会产生如下代码:

8048344 <bar>:
8048344:    55                push  %ebp
8048345:	89 e5			  mov   %esp,%ebp
8048347:	5d				  pop   %ebp
8048348:	c3				  ret
8048349 <foo>:
...
8048357:	e8 e8 ff ff ff    call  8048344 <bar>
804835c:	b8 00 00 00 00    mov   $0x0,%eax
...

这实际上是相对偏移调用指令,bar的地址为0x804835c+(-24)=0x8048344。只要bar和foo的相对位置不变,这条指令就是地址无关的。无论模块被装载到什么位置,这条指令都是有效的。

类型二 模块内部数据访问

现代的体系中,数据的相对寻址往往没有相对于当前指令地址(PC)的寻址方式,所以ELF用了一个比较巧妙的办法来获取当前的PC值,然后再加上一个偏移量就可以访问相应变量了。

粗体表示的是bar()函数中访问模块内部变量a的相应汇编指令。它先调用了一个叫”__i686.get_pc_thunk.cx”的函数,这个函数的作用就是把返回地址的值放到ecx寄存器中,即把call的下一条指令地址放到ecx寄存器中。

接着执行add指令和一条mov指令,可以看到a的地址是add指令地址加上两个偏移量0x118c和0x28。

模块内部数据访问示意

类型三 模块间数据访问

基本思想是把跟地址相关的部分放到数据段里面。ELF的做法是在数据段里面建立一个指向这些变量的指针数组,称为全局变量表(Global Offset Table)。

模块间数据访问

当指令访问变量b时,程序先找到GOT,然后根据变量所对应的项找到变量的目标地址。

类型四 模块间调用、跳转

调用ext()函数的方法和上面访问变量b的方法基本类似。先得到当前指令地址PC,然后加上一个偏移得到函数地址在GOT中的偏移,然后得到一个间接调用。

call	494 <__i686.get_pc_thunk.cx>
add 	$0x118c,%ecx
mov 	0xfffffffc(%ecx),%eax
call	*(%eax)

模块间调用、跳转

如何区分一个DSO是否为PIC

readelf -d foo.so | grep REXTREL

有输出就不是PIC,否则则是。

PIC与PIE

一个以地址无关方式编译的可执行文件被称作地址无关可执行文件(PIE,Position-Independent Executable),产生PIE的参数为-fPIE。

共享模块的全局变量问题

上述情况中没有定义再模块内部的全局变量的情况。

以下情景:

extern int global;
int foo()
{
	global = 1;
}

当编译器编译module.c时,它无法根据上下文判断global是定义在同一个模块的其他目标文件还是定义在另外一个共享对象之中。

假设module.c是程序可执行文件的一部分,那么再这种情况下,由于程序主模块不是地址无关代码,他引用这个全局变量方式跟普通数据访问方式一样。为了能够使链接过程正常进行,链接器会在创建可执行文件时在”.bss”段创建一个global变量的副本,而在global变量定义的原共享对象中也有,一个变量存在于多个位置。

解决办法只有一个,就是把定义在模块内部的全局变量当作定义在其他模块的全局变量,通过GOT来实现变量的访问。

延迟绑定(PLT)

GOT寻址和运行时的链接工作使得动态链接的性能不是很好。

在动态链接下,程序模块之间包含了大量的函数引用,所以在程序开始执行之前,动态链接会耗费不少时间解决符号查找和重定位等一系列工作。所以ELF采取当函数第一次被用到时才进行绑定的办法,称为延迟绑定。

实现方法并不麻烦。我们假设liba.so需要调用libc.so中的bar()函数。PLT为了实现延时绑定,在访问GOT之间又增加了一层间接跳转。每个外部函数在PLT中都有一个相应的项,比如bar()函数在PLT中的项的地址我们称之为bar@plt。让我们来看看bar@plt的实现:

bar@plt:

jmp 	*(bar@GOT)
push	n
push	moduleID
jump	_dl_runtime_resolve

第一条指令是通过GOT间接跳转的指令。bar@GOT表示GOT中保存bar()这个函数相应的项。链接器在初始化阶段并没有将bar()的地址填入,而是将push n的地址填入该项,后面两条push指令负责传递参数,n代表bar符号引用在重定位表”.rel.plt”中的下标。接着调用_dl_runtime_resolve,这是动态链接器的一个函数,它在进行一系列工作后将bar()的真正地址填入到bar@GOT中。

bar()函数解析完毕之后,我们再次调用bar@plt时,第一条jmp会直接跳转到真正的bar()函数中,bar()函数返回时会会根据堆栈里保存的EIP直接返回到调用者,而不再进行bar@plt第二条以下的指令了。

ELF将GOT拆分成了两个表叫做”.got”和”.got.plt”,前者保存全局变量引用地址,后者保存函数引用地址。另外,”.got.plt”前三项有特殊意义。

第一项保存”.dynamic”段的地址。第二项保存本模块的ID。第三项保存_dl_runtime_resolve()的地址。

GOT中的PLT数据结构

动态链接相关结构

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

“.interp”段

在动态链接的ELF可执行文件中,有一个专门的段叫”.interp”段。我们可以用objdump查看其内容。

surager@LAPTOP-0H9RPTHA:~/linkers_loaders$ objdump -s a.out

a.out:     file format elf32-i386

Contents of section .interp:
 0194 2f6c6962 2f6c642d 6c696e75 782e736f  /lib/ld-linux.so
 01a4 2e3200                               .2.

里面就保存了一个字符串,这个字符串就是动态链接器的路径。

“.dynamic”段

动态链接ELF中最重要的结构。定义在elf.h中:

typedef struct {
	Elf32_Sword d_tag;
	union {
		Elf32_word d_val;
        Elf32_Addr d_ptr;
	} d_un;
} Elf32_Dyn;

比较常见的类型值

从上面看,”.dynamic”段里面保存的信息有点像ELF文件头,这里只是换成了动态链接下所需要的相应信息而已。

我们可以用readelf查看”.dynamic”段的内容:

abc@Blue-Whale:~/work/linkers_loaders$ readelf -d Lib.so

Dynamic section at offset 0xe20 contains 24 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [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

另外使用ldd还可以查看依赖关系:

abc@Blue-Whale:~/work/linkers_loaders$ ldd Program1
	linux-vdso.so.1 (0x00007ffe4d3ee000)
	./Lib.so (0x00007f0a83c06000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0a83815000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0a8400a000)

动态符号表

为了表示动态链接模块之间的符号导入导出关系,ELF专门有一个动态符号表(.dynsym)保存这些信息。

动态符号表也需要一些辅助的表。比如动态符号字符串表(.dynstr),符号哈希表(.hash)

我们可以用readelf查看ELF的动态符号表和哈希表:

abc@Blue-Whale:~/work/linkers_loaders$ readelf -sD Lib.so

Symbol table '.dynsym' contains 13 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.2.5 (2)
     6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
     7: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   22 _edata
     8: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT   23 _end
     9: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   23 __bss_start
    10: 0000000000000520     0 FUNC    GLOBAL DEFAULT    9 _init
    11: 0000000000000690     0 FUNC    GLOBAL DEFAULT   13 _fini
    12: 000000000000065a    51 FUNC    GLOBAL DEFAULT   12 foobar
    
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.dyn”是对数据引用的修正,它所修正的位置位于”.got”以及数据段;而”.rel.plt”是对函数引用的修正,它所修正的位置位于”.got.plt”。

我们可以用readelf来查看一个动态链接文件的重定位表:

abc@Blue-Whale:~/work/linkers_loaders$ readelf -r Lib.so

Relocation section '.rela.dyn' at offset 0x448 contains 7 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
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

Relocation section '.rela.plt' at offset 0x4f0 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
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

abc@Blue-Whale:~/work/linkers_loaders$ readelf -S Lib.so
...
[20] .got              PROGBITS         0000000000200fe0  00000fe0
       0000000000000020  0000000000000008  WA       0     0     8
 [21] .got.plt          PROGBITS         0000000000201000  00001000
       0000000000000028  0000000000000008  WA       0     0     8
 [22] .data             PROGBITS         0000000000201028  00001028
       0000000000000008  0000000000000000  WA       0     0     8
...

演示环境问题,上述信息跟学习内容有偏差。有几种新的重定位入口类型:R_386_RELATIVE、R_386_GLOB_DAT和R_386_JUMP_SLOT。

printf的重定位入口类型为R_386_JUMP_SLOT,偏移为0x201018,它实际位于”.got.plt”中。我们知道,前三项被系统占用,第四项刚好是0x201000+8*3=0x201018,第五项是”sleep”。

当动态链接器需要进行重定位时,它先查找”printf”的地址,”printf”位于libc-2.6.1.so。假设链接器在全局变量表找到了”printf”的地址为0x8801234,那么链接器就会把这个地址填入到”.got.plt”中偏移为0x201018的位置去,从而实现地址重定位。

R_386_GLOB_DAT是对”.got”的重定位。它跟R_386_JUMP_SLOT一摸一样。

有点麻烦的是R_386_RELATIVE,这种类型实际上就是基址重置。比如有以下定义:

static int a;
static int* p = &a;

在编译时,共享对象地址从0开始,我们假设静态变量a相对于起始地址的偏移为B,即p的值为B。一旦共享对象被装载到地址A,那么a的地址实际为A+B。R_386_RELATIVE类型重定位其实就是专门用来重定位指针变量p这种类型的。

那么导入函数的重定位入口是不是只会出现在”.rel.plt”而不会出现在”.rel.dyn”呢?不用猜,肯定不是。是我还问吗如果ELF文件不是以PIC模式编译,则bar将出现在”.rel.dyn”中。

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

进程初始化堆栈

动态链接的步骤和实现

基本上分三步:

  1. 启动动态链接器
  2. 装载所有需要的共享对象
  3. 重定位和初始化

动态链接器自举

对于普通共享对象,他的重定位由动态链接器来完成;它也可以依赖于其他共享对象,由动态链接器负责链接和装载。

对于这种“鸡和蛋”的问题,动态链接器必须有一定的特殊性。它本身不可以依赖于其他的共享对象,它本身所需要的全局变量和静态变量重定位需要自己完成。这种具有一定限制的启动代码叫做自举。

装载共享对象