动态链接库中函数的地址确定---PLT和GOT

源博客封了,转载备份下

前面写过动态链接库 延迟绑定的一篇博文,那篇文章我非常喜欢,但是当时刚搞清楚,自己写的比较凌乱,我最近学习了Ulrich Drepper的How to write share library,学习了几篇其他的讲述动态链接的文章,再次整理了这篇文章。

 

    有一个问题是我们调用了动态链接库里面的函数,我们怎么知道动态链接库里面的函数的地址呢?事实上,直到我们第一次调用这个函数,我们并不知道这个函数的地址,这个功能要做延迟绑定 lazy bind。 因为程序的分支很多,并不是所有的分支都能跑到,想想我们的异常处理,异常处理分支的动态链接库里面的函数也许永远跑不到,所以,一上来就解析所有出现过的动态库里面的函数是个浪费的办法,降低性能并且没有必要。

    下面我们看下延迟绑定的效果。我写了个程序,先睡15s,然后pthread_create 一个线程。我们用LD_DEBUG观察符号的解析。

 

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<pthread.h>
  4.  
  5.  
  6. void* myfunc()
  7. {
  8.         while(1)
  9.         {
  10.          sleep(10);
  11.         }
  12.         return NULL;
  13. }
  14.  
  15.  
  16.  
  17. int main()
  18. {
  19.         sleep(15);
  20.         pthread_t tid = 0;
  21.         int ret = pthread_create(&tid,NULL,myfunc,NULL);
  22.         if(ret)
  23.         {
  24.                 fprintf(stderr,"pthread create failed %m \n");
  25.                 return -1;
  26.         }
  27.  
  28.         ret = pthread_join(tid,NULL);
  29.         if(ret)
  30.         {
  31.                 fprintf(stderr,"pthread join failed %m\n");
  32.                 return -2;
  33.         }
  34.  
  35.         return 0;
  36. }

 

  1.  
  2. root@libin:~/program/C/plt_got# LD_DEBUG=symbols ./test
  3. 2849: symbol=_res;  lookup in file=./test [0]
  4. 2849: symbol=_res;  lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  5. 2849: symbol=_res;  lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]
  6. 2849: symbol=_IO_file_close;  lookup in file=./test [0]
  7. 2849: symbol=_IO_file_close;  lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  8. 2849: symbol=_IO_file_close;  lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]
  9. 2849: symbol=rpc_createerr;  lookup in file=./test [0]
  10. 2849: symbol=rpc_createerr;  lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  11. 2849: symbol=rpc_createerr;  lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]
  12.  

...................

 2849:    transferring control: ./test

 2849:

 2849:    symbol=sleep;  lookup in file=./test [0]

 2849:    symbol=sleep;  lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]

 2849:    symbol=sleep;  lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]

===================================================================================

    然后停了15s,才解析出pthread_create的地址,由此可见,得确是运行时重定位,知道用到这个函数pthread_create才真正去找这个函数的地址。

 

  1. 2849:   
  2. 2849:    symbol=sleep; lookup in file=./test [0]
  3. 2849:    symbol=sleep; lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  4. 2849:    symbol=sleep; lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]
  5. ===================================================================================
  6. 2849:    symbol=pthread_create; lookup in file=./test [0]
  7. 2849:    symbol=pthread_create; lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  8. 2849:    symbol=__getpagesize; lookup in file=./test [0]
  9. 2849:    symbol=__getpagesize; lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  10. 2849:    symbol=__getpagesize; lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]
  11. 2849:    symbol=mmap; lookup in file=./test [0]
  12. 2849:    symbol=mmap; lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  13. 2849:    symbol=mmap; lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]

    真正动态库中函数地址的解析是第一次调用的时候做的,然后如果再次用到动态库的解析过的函数,就直接用第一次解析的结果。很自然的想法就是,一定有地方存储函数的地址,否则第一次解析出来的结果,第二次调用也没法利用。 这个存储动态库函数的地方就要GOT,Global Offset Table。 OK,我们可以想象,如果我的程序里面用到了6个动态库里面的函数,那个这个GOT里面就应该存有6个条目,每个条目里面存储着对应函数的地址。事实的确是这样:

 

  1. root@libin:~/program/C/plt_got# readelf -r test
  2.  
  3. Relocation section '.rel.dyn' at offset 0x394 contains 2 entries:
  4. Offset Info Type Sym.Value Sym. Name
  5. 08049ff0 00000206 R_386_GLOB_DAT 00000000 __gmon_start__
  6. 0804a020 00000905 R_386_COPY 0804a020 stderr
  7.  
  8. Relocation section '.rel.plt' at offset 0x3a4 contains 6 entries:
  9. Offset Info Type Sym.Value Sym. Name
  10. 0804a000 00000107 R_386_JUMP_SLOT 00000000 pthread_join
  11. 0804a004 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__
  12. 0804a008 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main
  13. 0804a00c 00000507 R_386_JUMP_SLOT 00000000 fprintf
  14. 0804a010 00000607 R_386_JUMP_SLOT 00000000 pthread_create
  15. 0804a014 00000707 R_386_JUMP_SLOT 00000000 sleep

    我们看到了有全局变量stderr和__gmon_start__需要重定位,这些本文并不关心。下面是需要重定位的函数,可以看出,我们调用动态库里面的函数都在这了,fprintf是Glibc库的,pthread_create是pthread库的等等。

 
   动态库里面需要重定位的函数在.got.plt这个段里面,我们看下:

 

    .got.plt这个段的起始地址是0x8049ff4。 .got.plt这个section大小为0x24 = 36,可是我们只有6个需要解析地址的function,4*6=24个字节,只需要24个字节就能存放这6个函数指针。多出来的12个字节是dynamic段地址,ModuleID 和 _dl_runtime_resolve的地址,如下图所示

 

    OK 。我们看一下:

 

  1. (gdb) b main
  2. Breakpoint 1 at 0x8048551: file test.c, line 19.
  3. (gdb) r
  4. Starting program: /home/libin/program/C/plt_got/test
  5. [Thread debugging using libthread_db enabled]
  6.  
  7. Breakpoint 1, main () at test.c:19
  8. 19 sleep(15);
  9. (gdb) x/24x 0x8049ff4
  10. 0x8049ff4 <_GLOBAL_OFFSET_TABLE_>: 0x08049f18 0x0012c8f8 0x00123270 0x0804841a
  11. 0x804a004 <_GLOBAL_OFFSET_TABLE_+16>: 0x0804842a 0x0015daf0 0x0804844a 0x0804845a
  12. 0x804a014 <_GLOBAL_OFFSET_TABLE_+32>: 0x0804846a 0x00000000 0x00000000 0x0029c580
  13. 0x804a024 <completed.7021>: 0x00000000 0x00000000 0x00000000 0x00000000
  14. 0x804a034: 0x00000000 0x00000000 0x00000000 0x00000000
  15. 0x804a044: 0x00000000 0x00000000 0x00000000 0x00000000

蓝色的0x0849f18是dynamic段的地址

 

  1. [21] .dynamic DYNAMIC 08049f18 000f18 0000d8 08 WA 7 0 4

 

接下来,我们要分析PLT 和GOT的关系了。

 

  1. (gdb) disas main
  2. ....
  3.  
  4. 0x0804857e <+54>: lea 0x1c(%esp),%eax
  5. 0x08048582 <+58>: mov %eax,(%esp)
  6. 0x08048585 <+61>: call 0x8048454 <pthread_create@plt>
  7. 0x0804858a <+66>: mov %eax,0x18(%esp)
  8. 0x0804858e <+70>: cmpl $0x0,0x18(%esp)
  9. .....

 
    要执行pthread_create 函数,跳到PLT部分。

 

  1. libin@libin:~/program/C/plt_got$ objdump -dj .plt test
  2.  
  3. test: file format elf32-i386
  4.  
  5.  
  6. Disassembly of section .plt:
  7.  
  8. 08048404 <pthread_join@plt-0x10>:
  9. 8048404: ff 35 f8 9f 04 08 pushl 0x8049ff8
  10. 804840a: ff 25 fc 9f 04 08 jmp *0x8049ffc
  11. 8048410: 00 00 add %al,(%eax)
  12. ...
  13.  
  14. 08048414 <pthread_join@plt>:
  15. 8048414: ff 25 00 a0 04 08 jmp *0x804a000
  16. 804841a: 68 00 00 00 00 push $0x0
  17. 804841f: e9 e0 ff ff ff jmp 8048404 <_init+0x30>
  18.  
  19. 08048424 <__gmon_start__@plt>:
  20. 8048424: ff 25 04 a0 04 08 jmp *0x804a004
  21. 804842a: 68 08 00 00 00 push $0x8
  22. 804842f: e9 d0 ff ff ff jmp 8048404 <_init+0x30>
  23.  
  24. 08048434 <__libc_start_main@plt>:
  25. 8048434: ff 25 08 a0 04 08 jmp *0x804a008
  26. 804843a: 68 10 00 00 00 push $0x10
  27. 804843f: e9 c0 ff ff ff jmp 8048404 <_init+0x30>
  28.  
  29. 08048444 <fprintf@plt>:
  30. 8048444: ff 25 0c a0 04 08 jmp *0x804a00c
  31. 804844a: 68 18 00 00 00 push $0x18
  32. 804844f: e9 b0 ff ff ff jmp 8048404 <_init+0x30>
  33.  
  34. 08048454 <pthread_create@plt>:
  35. 8048454: ff 25 10 a0 04 08 jmp *0x804a010
  36. 804845a: 68 20 00 00 00 push $0x20
  37. 804845f: e9 a0 ff ff ff jmp 8048404 <_init+0x30>
  38.  
  39. 08048464 <sleep@plt>:
  40. 8048464: ff 25 14 a0 04 08 jmp *0x804a014
  41. 804846a: 68 28 00 00 00 push $0x28
  42. 804846f: e9 90 ff ff ff jmp 8048404 <_init+0x30>

    PLT部分认为pthread_create函数存放在GOT,0x804a010是GOT里面的一个条目,这个条目存储着pthread_create函数的地址。当第二次以至于第N次调用pthead_create的时候,的的确确存放着pthread_create的地址,但是第一次不行,第一次这个条目里面还没记录这个地址。那么这个条目记录的是什么呢?

 

  1. (gdb) x/10i 0x8048454
  2. 0x8048454 <pthread_create@plt>: jmp *0x804a010
  3. 0x804845a <pthread_create@plt+6>: push $0x20
  4. 0x804845f <pthread_create@plt+11>: jmp 0x8048404
  5. 0x8048464 <sleep@plt>: jmp *0x804a014
  6. 0x804846a <sleep@plt+6>: push $0x28
  7. 0x804846f <sleep@plt+11>: jmp 0x8048404
  8. 0x8048474: add %al,(%eax)
  9. 0x8048476: add %al,(%eax)
  10. 0x8048478: add %al,(%eax)
  11. 0x804847a: add %al,(%eax)
  12. (gdb) x/10x 0x804a010
  13. 0x804a010 <_GLOBAL_OFFSET_TABLE_+28>: 0x0804845a 0x0804846a 0x00000000 0x00000000
  14. 0x804a020 <stderr@@GLIBC_2.0>: 0x0029c580 0x00000000 0x00000000 0x00000000
  15. 0x804a030: 0x00000000 0x00000000

    0x804a010这个地址最终应该记录的是pthread_create的地址,但是目前还不是,记录的是0x084845a

 

  1.  
  2.  08048454 <pthread_create@plt>:
  3.  8048454: ff 25 10 a0 04 08     jmp    *0x804a010
  4.  804845a: 68 20 00 00 00       push   $0x20
  5.  804845f: e9 a0 ff ff ff       jmp    8048404 <_init+0x30>

    从PLT跳到GOT 找地址,但是第一次找的时候,并不是pthread_create的地址,而是又跳回来PLT,我们看到push了0x20之后,跳到了0x8048404。 每一个PLT的代码段,都是push了一个值之后,跳到了0x8048404。大家可以去上面的图验证。

 

 


    接下来,我们看0x8048404存放的是啥指令:

 

  1. (gdb) x/10i 0x8048404
  2.    0x8048404:    pushl 0x8049ff8
  3.    0x804840a:    jmp *0x8049ffc
  4.    0x8048410:    add %al,(%eax)
  5.    0x8048412:    add %al,(%eax)
  6.    0x8048414 <pthread_join@plt>:    jmp *0x804a000
  7.    0x804841a <pthread_join@plt+6>:    push $0x0
  8.    0x804841f <pthread_join@plt+11>:    jmp 0x8048404
  9.    0x8048424 <__gmon_start__@plt>:    jmp *0x804a004
  10.    0x804842a <__gmon_start__@plt+6>:    push $0x8
  11.    0x804842f <__gmon_start__@plt+11>:    jmp 0x8048404

 

  1. (gdb) x/10x 0x8049ffc
  2. 0x8049ffc <_GLOBAL_OFFSET_TABLE_+8>:    0x00123270    0x0804841a    0x0804842a    0x0015daf0
  3. 0x804a00c <_GLOBAL_OFFSET_TABLE_+24>:    0x0804844a    0x0804845a    0x0804846a    0x00000000
  4. 0x804a01c <__dso_handle>:    0x00000000    0x0029c580
  5. (gdb) x/10i 0x00123270
  6.    0x123270 <_dl_runtime_resolve>:    push %eax
  7.    0x123271 <_dl_runtime_resolve+1>:    push %ecx
  8.    0x123272 <_dl_runtime_resolve+2>:    push %edx
  9.    0x123273 <_dl_runtime_resolve+3>:    mov 0x10(%esp),%edx
  10.    0x123277 <_dl_runtime_resolve+7>:    mov 0xc(%esp),%eax
  11.    0x12327b <_dl_runtime_resolve+11>:    call 0x11d5a0 <_dl_fixup>
  12.    0x123280 <_dl_runtime_resolve+16>:    pop %edx
  13.    0x123281 <_dl_runtime_resolve+17>:    mov (%esp),%ecx
  14.    0x123284 <_dl_runtime_resolve+20>:    mov %eax,(%esp)
  15.    0x123287 <_dl_runtime_resolve+23>:    mov 0x4(%esp),%eax

    我们看到0x8049ffc就是GOT的第三项,前文提到的dl_runtime_resolve的地址。这个函数将帮助我们将pthread_create函数地址定位,并且填入GOT表的相应位置 0x804a010。

 

    我们watch下GOT pthread_create对应条目,看下这个条目啥时候变化:

 

  1. (gdb) b main
  2. Breakpoint 1 at 0x8048551: file test.c, line 19.
  3. (gdb) r
  4. Starting program: /home/libin/program/C/plt_got/test
  5. [Thread debugging using libthread_db enabled]
  6.  
  7. Breakpoint 1, main () at test.c:19
  8. 19     sleep(15);
  9. (gdb) watch *0x804a010
  10. Hardware watchpoint 2: *0x804a010
  11. (gdb) c
  12. Continuing.
  13. Hardware watchpoint 2: *0x804a010
  14.  
  15. Old value = 134513754
  16. New value = 1260912
  17. _dl_fixup (l=<value optimized out>, reloc_arg=<value optimized out>) at dl-runtime.c:155
  18. 155    dl-runtime.c: 没有那个文件或目录.
  19.     in dl-runtime.c
  20. (gdb) bt
  21. #0 _dl_fixup (l=<value optimized out>, reloc_arg=<value optimized out>) at dl-runtime.c:155
  22. #1 0x00123280 in _dl_runtime_resolve () at ../sysdeps/i386/dl-trampoline.S:37
  23. #2 0x0804858a in main () at test.c:21
  24. (gdb)

 

    看到了,是_dl_runtime_resolve调用了_dl_fixup修改了GOT的对应条目。

 

  1. (gdb) x/10i 1260912
  2.    0x133d70 <__pthread_create_2_1>:    push %ebp
  3.    0x133d71 <__pthread_create_2_1+1>:    mov %esp,%ebp
  4.    0x133d73 <__pthread_create_2_1+3>:    push %edi
  5.    0x133d74 <__pthread_create_2_1+4>:    push %esi
  6.    0x133d75 <__pthread_create_2_1+5>:    push %ebx
  7.    0x133d76 <__pthread_create_2_1+6>:    call 0x132340 <__i686.get_pc_thunk.bx>
  8.    0x133d7b <__pthread_create_2_1+11>:    add $0x10279,%ebx
  9.    0x133d81 <__pthread_create_2_1+17>:    sub $0x4c,%esp
  10.    0x133d84 <__pthread_create_2_1+20>:    mov 0xc(%ebp),%edx
  11.    0x133d87 <__pthread_create_2_1+23>:    test %edx,%edx

    这是第一次。第二次就比较简单了,因为GOT里面有一个条目已经有了pthread_create函数的地址。

 

 

    

    本文里面两个PLT图来自http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/。这个博客内容相当的好,我学到了很多的东西。

 

参考文献:

 1 Position Independent Code (PIC) in shared libraries

 2 How to write share library.

--------------------- 本文来自 hulihong 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/hulihong/article/details/44224521?utm_source=copy

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值