前面写过动态链接库 延迟绑定的一篇博文,那篇文章我非常喜欢,但是当时刚搞清楚,自己写的比较凌乱,我最近学习了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. void* myfunc()
  5. {
  6. while(1)
  7. {
  8. sleep(10);
  9. }
  10. return NULL;
  11. }
  12. int main()
  13. {
  14. sleep(15);
  15. pthread_t tid = 0;
  16. int ret = pthread_create(&tid,NULL,myfunc,NULL);
  17. if(ret)
  18. {
  19. fprintf(stderr,"pthread create failed %m \n");
  20. return -1;
  21. }
  22. ret = pthread_join(tid,NULL);
  23. if(ret)
  24. {
  25. fprintf(stderr,"pthread join failed %m\n");
  26. return -2;
  27. }
  28. return 0;
  29. }
  1. root@libin:~/program/C/plt_got# LD_DEBUG=symbols ./test
  2. 2849: symbol=_res;  lookup in file=./test [0]
  3. 2849: symbol=_res;  lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  4. 2849: symbol=_res;  lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]
  5. 2849: symbol=_IO_file_close;  lookup in file=./test [0]
  6. 2849: symbol=_IO_file_close;  lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  7. 2849: symbol=_IO_file_close;  lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]
  8. 2849: symbol=rpc_createerr;  lookup in file=./test [0]
  9. 2849: symbol=rpc_createerr;  lookup in file=/lib/tls/i686/cmov/libpthread.so.0 [0]
  10. 2849: symbol=rpc_createerr;  lookup in file=/lib/tls/i686/cmov/libc.so.6 [0]
...................
 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. Relocation section '.rel.dyn' at offset 0x394 contains 2 entries:
  3. Offset Info Type Sym.Value Sym. Name
  4. 08049ff0 00000206 R_386_GLOB_DAT 00000000 __gmon_start__
  5. 0804a020 00000905 R_386_COPY 0804a020 stderr
  6. Relocation section '.rel.plt' at offset 0x3a4 contains 6 entries:
  7. Offset Info Type Sym.Value Sym. Name
  8. 0804a000 00000107 R_386_JUMP_SLOT 00000000 pthread_join
  9. 0804a004 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__
  10. 0804a008 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main
  11. 0804a00c 00000507 R_386_JUMP_SLOT 00000000 fprintf
  12. 0804a010 00000607 R_386_JUMP_SLOT 00000000 pthread_create
  13. 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. Breakpoint 1, main () at test.c:19
  7. 19 sleep(15);
  8. (gdb) x/24x 0x8049ff4
  9. 0x8049ff4 <_GLOBAL_OFFSET_TABLE_>: 0x08049f18 0x0012c8f8 0x00123270 0x0804841a
  10. 0x804a004 <_GLOBAL_OFFSET_TABLE_+16>: 0x0804842a 0x0015daf0 0x0804844a 0x0804845a
  11. 0x804a014 <_GLOBAL_OFFSET_TABLE_+32>: 0x0804846a 0x00000000 0x00000000 0x0029c580
  12. 0x804a024 : 0x00000000 0x00000000 0x00000000 0x00000000
  13. 0x804a034: 0x00000000 0x00000000 0x00000000 0x00000000
  14. 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. 0x0804857e <+54>: lea 0x1c(%esp),%eax
  4. 0x08048582 <+58>: mov %eax,(%esp)
  5. 0x08048585 <+61>: call 0x8048454<pthread_create@plt>
  6. 0x0804858a <+66>: mov %eax,0x18(%esp)
  7. 0x0804858e <+70>: cmpl $0x0,0x18(%esp)
  8. .....

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

  1. libin@libin:~/program/C/plt_got$ objdump -dj .plt test
  2. test: file format elf32-i386
  3. Disassembly of section .plt:
  4. 08048404 <pthread_join@plt-0x10>:
  5. 8048404: ff 35 f8 9f 04 08 pushl 0x8049ff8
  6. 804840a: ff 25 fc 9f 04 08 jmp *0x8049ffc
  7. 8048410: 00 00 add %al,(%eax)
  8. ...
  9. 08048414 <pthread_join@plt>:
  10. 8048414: ff 25 00 a0 04 08 jmp *0x804a000
  11. 804841a: 68 00 00 00 00 push $0x0
  12. 804841f: e9 e0 ff ff ff jmp 8048404 <_init+0x30>
  13. 08048424 <__gmon_start__@plt>:
  14. 8048424: ff 25 04 a0 04 08 jmp *0x804a004
  15. 804842a: 68 08 00 00 00 push $0x8
  16. 804842f: e9 d0 ff ff ff jmp 8048404 <_init+0x30>
  17. 08048434 <__libc_start_main@plt>:
  18. 8048434: ff 25 08 a0 04 08 jmp *0x804a008
  19. 804843a: 68 10 00 00 00 push $0x10
  20. 804843f: e9 c0 ff ff ff jmp 8048404 <_init+0x30>
  21. 08048444 <fprintf@plt>:
  22. 8048444: ff 25 0c a0 04 08 jmp *0x804a00c
  23. 804844a: 68 18 00 00 00 push $0x18
  24. 804844f: e9 b0 ff ff ff jmp 8048404 <_init+0x30>
  25. 08048454 <pthread_create@plt>:
  26. 8048454: ff 25 10 a0 04 08 jmp *0x804a010
  27. 804845a: 68 20 00 00 00 push $0x20
  28. 804845f: e9 a0 ff ff ff jmp 8048404 <_init+0x30>
  29. 08048464 <sleep@plt>:
  30. 8048464: ff 25 14 a0 04 08 jmp *0x804a014
  31. 804846a: 68 28 00 00 00 push $0x28
  32. 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. 08048454 <pthread_create@plt>:
  2. 8048454: ff 25 10 a0 04 08     jmp    *0x804a010
  3. 804845a: 68 20 00 00 00        push   $0x20
  4. 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. Breakpoint 1, main () at test.c:19
  7. 19     sleep(15);
  8. (gdb) watch *0x804a010
  9. Hardware watchpoint 2: *0x804a010
  10. (gdb) c
  11. Continuing.
  12. Hardware watchpoint 2: *0x804a010
  13. Old value = 134513754
  14. New value = 1260912
  15. _dl_fixup (l=<value optimized out>, reloc_arg=<value optimized out>) at dl-runtime.c:155
  16. 155    dl-runtime.c: 没有那个文件或目录.
  17. in dl-runtime.c
  18. (gdb) bt
  19. #0 _dl_fixup (l=<value optimized out>, reloc_arg=<value optimized out>) at dl-runtime.c:155
  20. #1 0x00123280 in _dl_runtime_resolve () at ../sysdeps/i386/dl-trampoline.S:37
  21. #2 0x0804858a in main () at test.c:21
  22. (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/。这个博客内容相当的好,我学到了很多的东西。
 
参考文献:
 2 How to write share library.

来源:http://blog.chinaunix.net/uid-24774106-id-3349549.html

动态链接库中函数的地址确定---PLT和GOT [转]的更多相关文章

  1. 如何识别IDA反汇编中遇到的动态链接库中的函数

    在使用IDA静态反汇编时,如果正在逆向的文件中有动态链接库函数(比如调用了程序自定义so库中的函数),IDA只会显示一个地址,跟进去会发现是延迟绑定中关于plt的代码,无法知道具体调用了哪个函数,对于 ...

  2. JavaScript中函数参数的按值传递与按引用传递(即按地址传递)

    首先声明一句:JavaScript中所有函数的参数都是按值传递的!不存在按引用传递! 在讲传递参数之前我们先来讲一下指针. 学过C指针的应该都知道,指针变量中保存的是一个地址,程序可以根据所保存的地址 ...

  3. VC 使用msxml6.dll动态链接库中的函数读写XML文件

    VC 使用msxml6.dll动态链接库中的函数读写XML文件 目录 1 引言 2 .dll使用方法 3 常用函数总结 4 实例应用 5 运行效果预览 6 补充说明 7 不足之处 8 更新   引言: ...

  4. 在DLL动态链接库中封装VCL的MDI子窗体

    在DLL动态链接库中封装VCL的MDI子窗体不多说了,看代码就应该明白了,曾经我遇到的问题,现在放出来大家共享! 这里是工程文件的部分: 在DLL中封装MDI子窗体需要重写DLL入口函数,具体代码如下 ...

  5. 【C/C++开发】C语言 DLL(动态链接库)中申请动态内存释放的问题

    参考:首先,声明一点,凡是使用malloc之类命令动态申请的内存,必须进行释放操作,否则就会发生内存泄漏问题. DLL中申请的内存释放,如果没有做过,很可能会认为是直接在调用程序中释放就可以了,其实不 ...

  6. 如何理解javaSript中函数的参数是按值传递

    本文是我基于红宝书<Javascript高级程序设计>中的第四章,4.1.3传递参数小节P70,进一步理解javaSript中函数的参数,当传递的参数是对象时的传递方式. (结合资料的个人 ...

  7. JS 中没有按地址(引用)传递,只有按值传递

    很多人,包括我,受书本知识消化不彻底的影响,认为 JS 中参数有两种传递方式:数字.字符串等按值传递:数组.对象等按地址(引用)传递.对此种观点,我们要谨慎. var v1 = [] var v2 = ...

  8. string.h文件中函数用法

    下面为string.h文件中函数的详细用法: strcpy函数名:strcpy功 能: 拷贝一个字符串到另一个用 法: char *strcpy(char *destin, char *source) ...

  9. 三种语言(c++、as、lua)中函数的差异性

    对于不同的语言, 尤其是静态语言和动态语言, 对于函数的定义(即如何看待一个函数)和处理截然不同.具体来说可以分为两类: 1.将函数视为第一类型值, 即函数和其他的对象一样, 都是语言中一个普通的对象 ...

随机推荐

  1. lib静态链接库,dll动态链接库,h文件

    最近在弄摄像头,发现我在调用摄像头自带的函数的时候,库没连接上,于是经过高人指点,学习了一下lib静态链接库,dll动态链接库来补充一下自己的基础知识. 一.首先我们来介绍一下lib静态链接库. li ...

  2. 3D中的切线空间简介

    转自:http://www.cnblogs.com/cxrs/archive/2009/10/25/1589515.html 1. 什么是Tangent space? Tangent space和wo ...

  3. System.out.println()输出到指定文件里

    public static void main(String[] args) throws Exception{ String str = "abcd"; PrintStream ...

  4. hdu 2041

    ps:这道题之前一直没思路,有大神提醒我用递推,但当时没搞清...今天做了那个小蜜蜂..才懂得用递推做这道题.. 代码: #include "stdio.h"long long d ...

  5. python03函数、递归

    本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 1.函数基本语法及特性 函数是什么? 函数一词来源于数学 ...

  6. Reason we use Camel

    Camel is mainly for integration purpose, in our project we also use it inside the single component t ...

  7. HDU 1721

    http://acm.hdu.edu.cn/showproblem.php?pid=1721 非常有趣的一道水题,注意到相隔一个点的粒子数是可以相互转移的,所以只要判红点的和与蓝点的和是否相等 #in ...

  8. Smart210学习记录-------文件操作

    一.linux文件操作(只能在linux系统上用) 创建:int creat(const char* filename, mode_t mode) filename 表示要创建的文件名,mode表示对 ...

  9. C++学习笔记15:操作符重载的函数原型列表(推荐)

    //普通四则运算 friend A operator +(const A & lhs, const A & rhs); friend A operator -(const A & ...

  10. LeetCode First Bad Version (二分查找)

    题意: 有一个bool序列表示对应下标的版本是否出问题(下标从1开始),如果一个版本出了问题,那么其后面全部版本必定出问题.现在给出判断任意版本是否出问题的API,请找到第一个出问题的版本. 思路: ...