概述
相对于静态链接,动态链接和加载机制更加复杂,但对于系统来说,动态加载能更节省内存和其它计算资源
这里面涉及到的主要技术点为:
- GOT表
- PLT表
- 延迟绑定
基本过程为
- 调用系统加载程序
- 调用程序所需要的动态库,并分配程序段地址
- 进行初始化,构建运行时环境(Run Time)
- 运行程序,第一次遇到动态未定义符号时,调用动态解析函数,找到符号地址,将信息写入绑定表
- 第二次调用符号时,直接进入查找表,调用对应函数
所以,下面,分别介绍GOT和PLT,然后用实例来分析函数符号动态加载机制
特别注意:
- 本文只讲述动态函数的加载机制,至于动态变量的加载机制,暂时没有深入研究,不做深入讨论,免得误导别人
- 对于动态加载机制,本文属于初级水平,仅供参考
GOT
Global Offset Table, 主要用于放置动态的符号地址信息,具体包括变量符号和函数符号,每个元素为32个字节大小,元素0用于保存动态结构体的didididi地址xixi信息,元素1和元素2保留,其它元素是提供给动态符号来用的
PLT
Procedure Linkage Table,用于保存函数跳转表,因为GOT没有足够的空间保存指令,所以用PLT来控制函数跳转,每个条目包含3条指令
动态链接
- 在创建程序的内存映像时,动态链接器设置GOT表中的第2,3项为特殊值,下文详述
- 如果PLT是位置无关代码,则必须将GOT的地址放置于EBX寄存器中,每个共享文件都有自己的PLT,只对自己的部分负责。所以,调用函数在调用动态函数之前,必须设置好GOT的基地址寄存器
- 下面,我们假设调用name1函数,函数将跳转到PLT的PLT1表项
- 首先,程序会跳转到GOT的name1表项,在刚开始时,GOT保存的是push指令,而不是函数地址
- 所以,程序压入重定向偏移量(32位非负整数),然后跳转到PLT0,再次压入一个数据,然后交给动态加载器
- 动态加载器接管后,根据压入的函数索引号,找到对应的字符串名称,然后找到对应的绝对地址
- 修改GOT name1中的表项位为name1的真实地址
- 再次执行时,程序会直接通过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