本文共 20332 字,大约阅读时间需要 67 分钟。
导读: by falcon 2008-02-26 Linux支持动态连接库,不仅节省了磁盘、内存空间,而且可以提高程序运行效率[1]。不过引入动态连接库也可能会带来很多问题,例如动态连接库的调试[4]、升级更新[5]和潜在的安全威胁[6][7]。这里主要讨论符号的动态链接过程,即程序在执行过程中,对其中包含的一些未确定地址的符号进行重定位的过程[3][8]。 本篇主要参考资料[3]和[8],前者侧重实践,后者侧重原理,把两者结合起来就方便理解程序的动态链接过程了。另外,动态连接库的创建、使用以及调用动态连接库的部分参考了资料[1][2]。 下面先来看看几个基本概念,接着就介绍动态连接库的创建、隐式和显示调用,最后介绍符号的动态链接细节。 1、基本概念 1.1 ELF ELF是Linux支持的一种程序文件格式,本身包含重定位、执行、共享(动态连接库)三种类型。(man elf) 代码: Code: [Ctrl+A Select All] 演示: $ gcc -c test.c #通过-c生成可重定位文件test.o,这里不会进行链接$ file test.otest.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped$ gcc -o test test.o #链接后才可以执行$ file test test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped//也可链接成动态连接库,不过一般不会把main函数链接成动态连接库,后面再介绍$ gcc -fpic -shared -W1,-soname,libtest.so.0 -o libtest.so.0.0 test.o $ file libtest.so.0.0 libtest.so.0.0: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped |
$ gcc -c test.c #生成可重定位文件test.o//包含全局变量、自定义函数以及动态连接库中的函数,但不包含局部变量;发现这个三个符号的地址都没有确定$ nm test.o #nm可以用来查看ELF文件的符号表信息 00000000 B global00000000 T main U printf$ gcc -o test test.o #生成可执行文件//经过链接后,global和main的地址都已经确定了,但是printf却还没有,因为它是动态连接库glibc中定义函数,需要动态链接,而不是这里的“静态”链接$ nm test | egrep "main$| printf|global$"080495a0 B global08048354 T main U printf@@GLIBC_2.0 |
$ nm /lib/libc.so.6 | grep "/ printf$"000457b0 T printf |
$ readelf -d test | grep NEEDED 0x00000001 (NEEDED) Shared library: [libc.so.6] |
$ ldd test linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/libc.so.6 (0xb7da2000) /lib/ld-linux.so.2 (0xb7efc000) |
$ readelf -x .interp testHex dump of section '.interp': 0x08048114 2f6c6962 2f6c642d 6c696e75 782e736f /lib/ld-linux.so 0x08048124 2e3200 .2. |
$ readelf -h test | grep Entry Entry point address: 0x80482b0 |
$ objdump -d -s -j .text test | grep printf 804837c: e8 1f ff ff ff call 80482a0 |
$ objdump -D test | grep "80482a0" | grep -v call080482a0 : 80482a0: ff 25 8c 95 04 08 jmp *0x804958c |
$ readelf -d testDynamic section at offset 0x4ac contains 20 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000c (INIT) 0x8048258 0x0000000d (FINI) 0x8048454 0x00000004 (HASH) 0x8048148 0x00000005 (STRTAB) 0x80481c0 0x00000006 (SYMTAB) 0x8048170 0x0000000a (STRSZ) 76 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000015 (DEBUG) 0x0 0x00000003 (PLTGOT) 0x8049578 0x00000002 (PLTRELSZ) 24 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x8048240 0x00000011 (REL) 0x8048238 0x00000012 (RELSZ) 8 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x6ffffffe (VERNEED) 0x8048218 0x6fffffff (VERNEEDNUM) 1 0x6ffffff0 (VERSYM) 0x804820c 0x00000000 (NULL) 0x0 |
$ readelf -x .got.plt testHex dump of section '.got.plt': 0x08049578 ac940408 00000000 00000000 86820408 ................ 0x08049588 96820408 a6820408 ........ |
$ objdump -d -d -s -j .plt test | grep "080482a0 :" -A 3 080482a0 : 80482a0: ff 25 8c 95 04 08 jmp *0x804958c 80482a6: 68 10 00 00 00 push $0x10 80482ab: e9 c0 ff ff ff jmp 8048270 <_init+0x18> |
$ objdump -d -d -s -j .plt test | grep -v "jmp 8048270 <_init+0x18>" | grep "08048270" -A 208048270 <__gmon_start__@plt-0x10>: 8048270: ff 35 7c 95 04 08 pushl 0x804957c 8048276: ff 25 80 95 04 08 jmp *0x8049580 |
$ readelf -x .got.plt testHex dump of section '.got.plt': 0x08049578 ac940408 00000000 00000000 86820408 ................ 0x08049588 96820408 a6820408 ........ |
$ objdump -d -d -s -j .plt test08048270 <__gmon_start__@plt-0x10>: 8048270: ff 35 7c 95 04 08 pushl 0x804957c 8048276: ff 25 80 95 04 08 jmp *0x8049580 804827c: 00 00 add %al,(%eax) ...08048280 <__gmon_start__@plt>: 8048280: ff 25 84 95 04 08 jmp *0x8049584 8048286: 68 00 00 00 00 push $0x0 804828b: e9 e0 ff ff ff jmp 8048270 <_init+0x18>08048290 <__libc_start_main@plt>: 8048290: ff 25 88 95 04 08 jmp *0x8049588 8048296: 68 08 00 00 00 push $0x8 804829b: e9 d0 ff ff ff jmp 8048270 <_init+0x18>080482a0 : 80482a0: ff 25 8c 95 04 08 jmp *0x804958c 80482a6: 68 10 00 00 00 push $0x10 80482ab: e9 c0 ff ff ff jmp 8048270 <_init+0x18> |
$ readelf -x .got.plt testHex dump of section '.got.plt': 0x08049578 ac940408 00000000 00000000 86820408 ................ 0x08049588 96820408 a6820408 ........ |
$ objdump -D test | grep 080494ac080494ac <_DYNAMIC>: |
$ readelf -r testRelocation section '.rel.dyn' at offset 0x238 contains 1 entries: Offset Info Type Sym.Value Sym. Name08049574 00000106 R_386_GLOB_DAT 00000000 __gmon_start__Relocation section '.rel.plt' at offset 0x240 contains 3 entries: Offset Info Type Sym.Value Sym. Name08049584 00000107 R_386_JUMP_SLOT 00000000 __gmon_start__08049588 00000207 R_386_JUMP_SLOT 00000000 __libc_start_main0804958c 00000407 R_386_JUMP_SLOT 00000000 printf |
$ readelf -s test | grep ".dynsym" -A 6Symbol table '.dynsym' contains 5 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 2: 00000000 410 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2) 3: 08048474 4 OBJECT GLOBAL DEFAULT 14 _IO_stdin_used 4: 00000000 57 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.0 (2) |
链接编辑器创建这种重定位类型主要是为了支持动态链接。其偏移地址成员给出过程链接表项的位置。动态链接器修改全局偏移表项的内容,把控制传输给指定符号的地址。 |
$ gcc -c myprintf.c$ gcc -shared -W1,-soname,libmyprintf.so.0 -o libmyprintf.so.0.0 myprintf.o$ ln -sf libmyprintf.so.0.0 libmyprintf.so.0$ ln -fs libmyprintf.so.0 libmyprintf.so$ lslibmyprintf.so libmyprintf.so.0 libmyprintf.so.0.0 myprintf.c myprintf.h myprintf.o |
$ gcc -o test test.c -lmyprintf -L./ -I./$ ./test #直接运行test,提示找不到该库,因为库的默认搜索路径里头没有包含当前目录./test: error while loading shared libraries: libmyprintf.so: cannot open shared object file: No such file or directory$ LD_LIBRARY_PATH=$PWD ./test #如果指定库的搜索路径,则可以运行Hello World |
$ gcc -o test1 test1.c -ldl |
$ make got$ readelf -d got | grep PLTGOT 0x00000003 (PLTGOT) 0x8049614 |
$ make got$ Hello Worldgot2: 0xb7f376d8, got3: 0xb7f2ef10, old_addr: 0x80482da, new_addr: 0xb7e19a20$ ./got Hello Worldgot2: 0xb7f1e6d8, got3: 0xb7f15f10, old_addr: 0x80482da, new_addr: 0xb7e00a20 |
$ gcc -g -o got got.c$ gdb ./got(gdb) l5 #include 6 7 #define GOT 0x8049614 8 9 int main(int argc, char *argv[]) 10 { 11 long got2, got3; 12 long old_addr, new_addr; 13 14 got2=*(long *)(GOT+4); (gdb) l 15 got3=*(long *)(GOT+8); 16 old_addr=*(long *)(GOT+24); 17 18 printf("Hello World/n"); 19 20 new_addr=*(long *)(GOT+24); 21 22 printf("got2: 0x%0x, got3: 0x%0x, old_addr: 0x%0x, new_addr: 0x%0x/n", 23 got2, got3, old_addr, new_addr); 24 (gdb) break 18 #在第一个printf处设置一个断点 Breakpoint 1 at 0x80483c3: file got.c, line 18. (gdb) break 22 #在第二个printf处设置一个断点 Breakpoint 2 at 0x80483dd: file got.c, line 22. (gdb) r #运行到第一个printf之前会停止 Starting program: /mnt/hda8/Temp/c/program/got Breakpoint 1, main () at got.c:18 18 printf("Hello World/n"); (gdb) x/8x 0x8049614 #查看执行printf之前的全局偏移表内容 0x8049614 <_GLOBAL_OFFSET_TABLE_>: 0x08049548 0xb7f3c6d8 0xb7f33f10 0x080482aa 0x8049624 <_GLOBAL_OFFSET_TABLE_+16>: 0xb7ddbd20 0x080482ca 0x080482da 0x00000000 (gdb) disassemble 0x080482da #查看GOT表项的最有一项,发现刚好是PLT表中push指令的地址,说明此时还没有进行进行符号的重定位,不过发现并非printf,而是puts(1) Dump of assembler code for function puts@plt: 0x080482d4 : jmp *0x804962c 0x080482da : push $0x18 0x080482df : jmp 0x8048294 <_init+24> (gdb) disassemble 0xb7f33f10 #查看GOT第三项的内容,刚好是dl-linux对应的代码, #可通过nm /lib/ld-linux.so.2 | grep _dl_runtime_resolve进行确认 Dump of assembler code for function _dl_runtime_resolve: 0xb7f33f10 <_dl_runtime_resolve+0>: push %eax 0xb7f33f11 <_dl_runtime_resolve+1>: push %ecx 0xb7f33f12 <_dl_runtime_resolve+2>: push %edx (gdb) x/8x 0xb7f3c6d8 #查看GOT表第二项处的内容,看不出什么特别的信息,反编译时提示无法反编译 0xb7f3c6d8: 0x00000000 0xb7f39c3d 0x08049548 0xb7f3c9b8 0xb7f3c6e8: 0x00000000 0xb7f3c6d8 0x00000000 0xb7f3c9a4 (gdb) break *(0xb7f33f10) #在*(0xb7f33f10)指向的代码处设置一个断点,确认它是否被执行 break *(0xb7f33f10) Breakpoint 3 at 0xb7f3cf10 (gdb) c Continuing. Breakpoint 3, 0xb7f3cf10 in _dl_runtime_resolve () from /lib/ld-linux.so.2 (gdb) c #继续运行,直到第二次调用printf Continuing. Hello World Breakpoint 2, main () at got.c:22 22 printf("got2: 0x%0x, got3: 0x%0x, old_addr: 0x%0x, new_addr: 0x%0x/n", (gdb) x/8x 0x8049614 #再次查看GOT表项,发现GOT表的最后一项的值应该被修改 0x8049614 <_GLOBAL_OFFSET_TABLE_>: 0x08049548 0xb7f3c6d8 0xb7f33f10 0x080482aa 0x8049624 <_GLOBAL_OFFSET_TABLE_+16>: 0xb7ddbd20 0x080482ca 0xb7e1ea20 0x00000000 (gdb) disassemble 0xb7e1ea20 #查看GOT表最后一项,发现变成了puts函数的代码,说明进行了符号puts的重定位(2) Dump of assembler code for function puts: 0xb7e1ea20 : push %ebp 0xb7e1ea21 : mov %esp,%ebp 0xb7e1ea23 : sub $0x1c,%esp |
$ LD_BIND_NOW=1 ./got #设置LD_BIND_NOW环境变量的运行结果Hello Worldgot2: 0x0, got3: 0x0, old_addr: 0xb7e61a20, new_addr: 0xb7e61a20$ ./got #默认情况下的运行结果Hello Worldgot2: 0xb7f806d8, got3: 0xb7f77f10, old_addr: 0x80482da, new_addr: 0xb7e62a20 |
转载地址:http://prnob.baihongyu.com/