ELF文件分析之3 - 动态链接

概述

相对于静态链接,动态链接和加载机制更加复杂,但对于系统来说,动态加载能更节省内存和其它计算资源

这里面涉及到的主要技术点为:

  1. GOT表
  2. PLT表
  3. 延迟绑定

基本过程为

  1. 调用系统加载程序
  2. 调用程序所需要的动态库,并分配程序段地址
  3. 进行初始化,构建运行时环境(Run Time)
  4. 运行程序,第一次遇到动态未定义符号时,调用动态解析函数,找到符号地址,将信息写入绑定表
  5. 第二次调用符号时,直接进入查找表,调用对应函数

所以,下面,分别介绍GOT和PLT,然后用实例来分析函数符号动态加载机制

特别注意:

  1. 本文只讲述动态函数的加载机制,至于动态变量的加载机制,暂时没有深入研究,不做深入讨论,免得误导别人
  2. 对于动态加载机制,本文属于初级水平,仅供参考

GOT

Global Offset Table, 主要用于放置动态的符号地址信息,具体包括变量符号和函数符号,每个元素为32个字节大小,元素0用于保存动态结构体的didididi地址xixi信息,元素1和元素2保留,其它元素是提供给动态符号来用的

PLT

Procedure Linkage Table,用于保存函数跳转表,因为GOT没有足够的空间保存指令,所以用PLT来控制函数跳转,每个条目包含3条指令

动态链接

  1. 在创建程序的内存映像时,动态链接器设置GOT表中的第2,3项为特殊值,下文详述
  2. 如果PLT是位置无关代码,则必须将GOT的地址放置于EBX寄存器中,每个共享文件都有自己的PLT,只对自己的部分负责。所以,调用函数在调用动态函数之前,必须设置好GOT的基地址寄存器
  3. 下面,我们假设调用name1函数,函数将跳转到PLT的PLT1表项
  4. 首先,程序会跳转到GOT的name1表项,在刚开始时,GOT保存的是push指令,而不是函数地址
  5. 所以,程序压入重定向偏移量(32位非负整数),然后跳转到PLT0,再次压入一个数据,然后交给动态加载器
  6. 动态加载器接管后,根据压入的函数索引号,找到对应的字符串名称,然后找到对应的绝对地址
  7. 修改GOT name1中的表项位为name1的真实地址
  8. 再次执行时,程序会直接通过jmp指令,间接跳转到name1函数

对应的汇编代码如下所示

1
00010610 <main>:
   10610:	e92d4800 	push	{fp, lr}
   10614:	e28db004 	add	fp, sp, #4
   10618:	e3a00001 	mov	r0, #1
   1061c:	e3a01002 	mov	r1, #2
   10620:	ebffffa4 	bl	104b8 <f_add@plt>          # 0 跳转到PLT表
   10624:	e1a02000 	mov	r2, r0
   10628:	e59f3020 	ldr	r3, [pc, #32]	; 10650 <main+0x40>
   1062c:	e5832000 	str	r2, [r3]
   10630:	e59f3018 	ldr	r3, [pc, #24]	; 10650 <main+0x40>
   10634:	e5933000 	ldr	r3, [r3]
   10638:	e59f0014 	ldr	r0, [pc, #20]	; 10654 <main+0x44>
   1063c:	e1a01003 	mov	r1, r3
   10640:	ebffff99 	bl	104ac <printf@plt>
   10644:	e3a03000 	mov	r3, #0
   10648:	e1a00003 	mov	r0, r3
   1064c:	e8bd8800 	pop	{fp, pc}
   10650:	0002080c 	andeq	r0, r2, ip, lsl #16
   10654:	000106cc 	andeq	r0, r1, ip, asr #13

然后查看PLT表的汇编代码

1
000104b8 <f_add@plt>:
   104b8:	e28fc600 	add	ip, pc, #0, 12
   104bc:	e28cca10 	add	ip, ip, #16, 20	; 0x10000
   104c0:	e5bcf330 	ldr	pc, [ip, #816]!	; 0x330

最后一句是跳转到IP+0x330处的数据所指示的地址,经GDB调试时,我们知道IP+0x330 =
0x207f0,找到这个GOT表项,发现内存数据为0x00010498,就像上文说的一样,跳转到PLT0表项中,开始执行下面的函数

1
00010498 <printf@plt-0x14>:
   10498:	e52de004 	push	{lr}		; (str lr, [sp, #-4]!)
   1049c:	e59fe004 	ldr	lr, [pc, #4]	; 104a8 <_init+0x1c>
   104a0:	e08fe00e 	add	lr, pc, lr
   104a4:	e5bef008 	ldr	pc, [lr, #8]!
   104a8:	00010338 	andeq	r0, r1, r8, lsr r3

这部分函数,负责计算得到对应的函数绝对地址,然后修改PLT表项和GOT表项,我们可以在GDB中验证

完整的GDB调试操作过程如下所示

1
root@raspberrypi:/home/pi/tmp# readelf -S -r -d a.out
There are 36 section headers, starting at offset 0x16e8:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        00010134 000134 000019 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            00010150 000150 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            00010170 000170 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        00010194 000194 000064 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          000101f8 0001f8 000130 10   A  6   1  4
  [ 6] .dynstr           STRTAB          00010328 000328 0000e5 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0001040e 00040e 000026 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         00010434 000434 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             00010454 000454 000010 08   A  5   0  4
  [10] .rel.plt          REL             00010464 000464 000028 08  AI  5  12  4
  [11] .init             PROGBITS        0001048c 00048c 00000c 00  AX  0   0  4
  [12] .plt              PROGBITS        00010498 000498 000050 04  AX  0   0  4
  [13] .text             PROGBITS        000104e8 0004e8 0001d8 00  AX  0   0  4
  [14] .fini             PROGBITS        000106c0 0006c0 000008 00  AX  0   0  4
  [15] .rodata           PROGBITS        000106c8 0006c8 000010 00   A  0   0  4
  [16] .ARM.exidx        ARM_EXIDX       000106d8 0006d8 000008 00  AL 13   0  4
  [17] .eh_frame         PROGBITS        000106e0 0006e0 000004 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      000206e4 0006e4 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      000206e8 0006e8 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        000206ec 0006ec 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         000206f0 0006f0 0000f0 08  WA  6   0  4
  [22] .got              PROGBITS        000207e0 0007e0 000024 04  WA  0   0  4
  [23] .data             PROGBITS        00020804 000804 000008 00  WA  0   0  4
  [24] .bss              NOBITS          0002080c 00080c 000008 00  WA  0   0  4
  [25] .comment          PROGBITS        00000000 00080c 00003d 01  MS  0   0  1
  [26] .ARM.attributes   ARM_ATTRIBUTES  00000000 000849 000031 00      0   0  1
  [27] .debug_aranges    PROGBITS        00000000 00087a 000020 00      0   0  1
  [28] .debug_info       PROGBITS        00000000 00089a 000098 00      0   0  1
  [29] .debug_abbrev     PROGBITS        00000000 000932 000055 00      0   0  1
  [30] .debug_line       PROGBITS        00000000 000987 000038 00      0   0  1
  [31] .debug_frame      PROGBITS        00000000 0009c0 00002c 00      0   0  4
  [32] .debug_str        PROGBITS        00000000 0009ec 0000bf 01  MS  0   0  1
  [33] .shstrtab         STRTAB          00000000 000aab 000157 00      0   0  1
  [34] .symtab           SYMTAB          00000000 000c04 000770 10     35  92  4
  [35] .strtab           STRTAB          00000000 001374 000374 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

Dynamic section at offset 0x6f0 contains 25 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libfun.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000c (INIT)                       0x1048c
 0x0000000d (FINI)                       0x106c0
 0x00000019 (INIT_ARRAY)                 0x206e4
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x206e8
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x10194
 0x00000005 (STRTAB)                     0x10328
 0x00000006 (SYMTAB)                     0x101f8
 0x0000000a (STRSZ)                      229 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x207e0
 0x00000002 (PLTRELSZ)                   40 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x10464
 0x00000011 (REL)                        0x10454
 0x00000012 (RELSZ)                      16 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x10434
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x1040e
 0x00000000 (NULL)                       0x0

Relocation section '.rel.dyn' at offset 0x454 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00020800  00000215 R_ARM_GLOB_DAT    00000000   __gmon_start__
0002080c  00000b14 R_ARM_COPY        0002080c   m

Relocation section '.rel.plt' at offset 0x464 contains 5 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
000207ec  00000916 R_ARM_JUMP_SLOT   00000000   printf
000207f0  00000716 R_ARM_JUMP_SLOT   00000000   f_add
000207f4  00001116 R_ARM_JUMP_SLOT   00000000   __libc_start_main
000207f8  00000216 R_ARM_JUMP_SLOT   00000000   __gmon_start__
000207fc  00000d16 R_ARM_JUMP_SLOT   00000000   abort

root@raspberrypi:/home/pi/tmp# gdb a.out

GNU gdb (Raspbian 7.7.1+dfsg-5) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...done.
(gdb) b main
Breakpoint 1 at 0x10618: file main.c, line 8.
(gdb) display /i $pc
(gdb) r
Starting program: /home/pi/tmp/a.out 

Breakpoint 1, main () at main.c:8
8	    m = f_add(1, 2);
1: x/i $pc
=> 0x10618 <main+8>:	mov	r0, #1
(gdb) disass
Dump of assembler code for function main:
   0x00010610 <+0>:	push	{r11, lr}
   0x00010614 <+4>:	add	r11, sp, #4
=> 0x00010618 <+8>:	mov	r0, #1
   0x0001061c <+12>:	mov	r1, #2
   0x00010620 <+16>:	bl	0x104b8
   0x00010624 <+20>:	mov	r2, r0
   0x00010628 <+24>:	ldr	r3, [pc, #32]	; 0x10650 <main+64>
   0x0001062c <+28>:	str	r2, [r3]
   0x00010630 <+32>:	ldr	r3, [pc, #24]	; 0x10650 <main+64>
   0x00010634 <+36>:	ldr	r3, [r3]
   0x00010638 <+40>:	ldr	r0, [pc, #20]	; 0x10654 <main+68>
   0x0001063c <+44>:	mov	r1, r3
   0x00010640 <+48>:	bl	0x104ac
   0x00010644 <+52>:	mov	r3, #0
   0x00010648 <+56>:	mov	r0, r3
   0x0001064c <+60>:	pop	{r11, pc}
   0x00010650 <+64>:	andeq	r0, r2, r12, lsl #16
   0x00010654 <+68>:	andeq	r0, r1, r12, asr #13
End of assembler dump.
(gdb) disassemble 0x10498, 0x104d0
Dump of assembler code from 0x10498 to 0x104d0:
   0x00010498:	push	{lr}		; (str lr, [sp, #-4]!)
   0x0001049c:	ldr	lr, [pc, #4]	; 0x104a8
   0x000104a0:	add	lr, pc, lr
   0x000104a4:	ldr	pc, [lr, #8]!
   0x000104a8:	andeq	r0, r1, r8, lsr r3
   0x000104ac:	add	r12, pc, #0, 12
   0x000104b0:	add	r12, r12, #16, 20	; 0x10000
   0x000104b4:	ldr	pc, [r12, #824]!	; 0x338
   0x000104b8:	add	r12, pc, #0, 12
   0x000104bc:	add	r12, r12, #16, 20	; 0x10000
   0x000104c0:	ldr	pc, [r12, #816]!	; 0x330
   0x000104c4:	add	r12, pc, #0, 12
   0x000104c8:	add	r12, r12, #16, 20	; 0x10000
   0x000104cc:	ldr	pc, [r12, #808]!	; 0x328
End of assembler dump.
(gdb) x /12 0x207e0
0x207e0:	0x000206f0	0x76fff958	0x76fe4f94	0x00010498
0x207f0:	0x00010498	0x76e6d180	0x00010498	0x00010498
0x20800:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) si
0x0001061c	8	    m = f_add(1, 2);
1: x/i $pc
=> 0x1061c <main+12>:	mov	r1, #2
(gdb) si
0x00010620	8	    m = f_add(1, 2);
1: x/i $pc
=> 0x10620 <main+16>:	bl	0x104b8
(gdb) si
0x000104b8 in ?? ()
1: x/i $pc
=> 0x104b8:	add	r12, pc, #0, 12
(gdb) si
0x000104bc in ?? ()
1: x/i $pc
=> 0x104bc:	add	r12, r12, #16, 20	; 0x10000
(gdb) si
0x000104c0 in ?? ()
1: x/i $pc
=> 0x104c0:	ldr	pc, [r12, #816]!	; 0x330
(gdb) info registers
r0             0x1	1
r1             0x2	2
r2             0x7efff7ec	2130704364
r3             0x10610	67088
r4             0x0	0
r5             0x0	0
r6             0x104e8	66792
r7             0x0	0
r8             0x0	0
r9             0x0	0
r10            0x76fff000	1996484608
r11            0x7efff68c	2130704012
r12            0x204c0	132288
sp             0x7efff688	0x7efff688
lr             0x10624	67108
pc             0x104c0	0x104c0
cpsr           0x60000010	1610612752
(gdb) x /12 0x207e0
0x207e0:	0x000206f0	0x76fff958	0x76fe4f94	0x00010498
0x207f0:	0x00010498	0x76e6d180	0x00010498	0x00010498
0x20800:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) p /x  0x204c0+0x330
$2 = 0x207f0
(gdb) si
0x00010498 in ?? ()
1: x/i $pc
=> 0x10498:	push	{lr}		; (str lr, [sp, #-4]!)
(gdb) ni
0x0001049c in ?? ()
1: x/i $pc
=> 0x1049c:	ldr	lr, [pc, #4]	; 0x104a8
(gdb) ni
0x000104a0 in ?? ()
1: x/i $pc
=> 0x104a0:	add	lr, pc, lr
(gdb) ni
0x000104a4 in ?? ()
1: x/i $pc
=> 0x104a4:	ldr	pc, [lr, #8]!
(gdb) ni
_dl_runtime_resolve () at ../ports/sysdeps/arm/dl-trampoline.S:40
40	../ports/sysdeps/arm/dl-trampoline.S: No such file or directory.
1: x/i $pc
=> 0x76fe4f94 <_dl_runtime_resolve>:	push	{r0, r1, r2, r3, r4}
(gdb) ni
48	in ../ports/sysdeps/arm/dl-trampoline.S
1: x/i $pc
=> 0x76fe4f98 <_dl_runtime_resolve+4>:	ldr	r0, [lr, #-4]
(gdb) ni
52	in ../ports/sysdeps/arm/dl-trampoline.S
1: x/i $pc
=> 0x76fe4f9c <_dl_runtime_resolve+8>:	sub	r1, r12, lr
(gdb) ni
53	in ../ports/sysdeps/arm/dl-trampoline.S
1: x/i $pc
=> 0x76fe4fa0 <_dl_runtime_resolve+12>:	sub	r1, r1, #4
(gdb) ni
54	in ../ports/sysdeps/arm/dl-trampoline.S
1: x/i $pc
=> 0x76fe4fa4 <_dl_runtime_resolve+16>:	add	r1, r1, r1
(gdb) ni
57	in ../ports/sysdeps/arm/dl-trampoline.S
1: x/i $pc
=> 0x76fe4fa8 <_dl_runtime_resolve+20>:	bl	0x76fde344 <_dl_fixup>
(gdb) ni
60	in ../ports/sysdeps/arm/dl-trampoline.S
1: x/i $pc
=> 0x76fe4fac <_dl_runtime_resolve+24>:	mov	r12, r0
(gdb) ni
64	in ../ports/sysdeps/arm/dl-trampoline.S
1: x/i $pc
=> 0x76fe4fb0 <_dl_runtime_resolve+28>:	pop	{r0, r1, r2, r3, r4, lr}
(gdb) ni
68	in ../ports/sysdeps/arm/dl-trampoline.S
1: x/i $pc
=> 0x76fe4fb4 <_dl_runtime_resolve+32>:	bx	r12
(gdb) ni
f_add (a=67088, b=2130704364) at fun.c:4
4	{
1: x/i $pc
=> 0x76f92588 <f_add>:	push	{r11}		; (str r11, [sp, #-4]!)
(gdb) ni
0x76f9258c	4	{
1: x/i $pc
=> 0x76f9258c <f_add+4>:	add	r11, sp, #0
(gdb) disassemble
Dump of assembler code for function f_add:
   0x76f92588 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
=> 0x76f9258c <+4>:	add	r11, sp, #0
   0x76f92590 <+8>:	sub	sp, sp, #12
   0x76f92594 <+12>:	str	r0, [r11, #-8]
   0x76f92598 <+16>:	str	r1, [r11, #-12]
   0x76f9259c <+20>:	ldr	r2, [r11, #-8]
   0x76f925a0 <+24>:	ldr	r3, [r11, #-12]
   0x76f925a4 <+28>:	add	r3, r2, r3
   0x76f925a8 <+32>:	mov	r0, r3
   0x76f925ac <+36>:	sub	sp, r11, #0
   0x76f925b0 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x76f925b4 <+44>:	bx	lr
End of assembler dump.
(gdb) x /12 0x207e0
0x207e0:	0x000206f0	0x76fff958	0x76fe4f94	0x00010498
0x207f0:	0x76f92588	0x76e6d180	0x00010498	0x00010498
0x20800:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) disassemble 0x10498, 0x104d0
Dump of assembler code from 0x10498 to 0x104d0:
   0x00010498:	push	{lr}		; (str lr, [sp, #-4]!)
   0x0001049c:	ldr	lr, [pc, #4]	; 0x104a8
   0x000104a0:	add	lr, pc, lr
   0x000104a4:	ldr	pc, [lr, #8]!
   0x000104a8:	andeq	r0, r1, r8, lsr r3
   0x000104ac:	add	r12, pc, #0, 12
   0x000104b0:	add	r12, r12, #16, 20	; 0x10000
   0x000104b4:	ldr	pc, [r12, #824]!	; 0x338
   0x000104b8:	add	r12, pc, #0, 12
   0x000104bc:	add	r12, r12, #16, 20	; 0x10000
   0x000104c0:	ldr	pc, [r12, #816]!	; 0x330
   0x000104c4:	add	r12, pc, #0, 12
   0x000104c8:	add	r12, r12, #16, 20	; 0x10000
   0x000104cc:	ldr	pc, [r12, #808]!	; 0x328
End of assembler dump.
(gdb) q
A debugging session is active.

	Inferior 1 [process 3290] will be killed.

Quit anyway? (y or n) y

参考资料

  1. ELF规范1.1
  2. ELF规范1.2
  3. ARM ELF 规范