转载于:http://www.cnblogs.com/xingyun/archive/2011/12/10/2283149.html

 

我们知道函数名就是一个内存地址,这个地址指向函数的入口。调用函数就是压入参数,保存返回地址,然后跳转到函数名指向的代码。问题是,如果函数在共享库中,共享库加载的地址本身就不确定,函数地址也就不确定了,那如何调用共享库中的函数呢?这就是本文要回答的。

我们先来看一小段代码(test.c):

#include <stdio.h>
void hello_world(void) { printf("Hello world!\n");
return; }
int main(int argc, char* argv[]) { hello_world();
return 0; }

编译并反汇编:

gcc -g test.c -o test objdump -S test

void hello_world(void) {  80483b4:       55                      push   %ebp  80483b5:       89 e5                   mov    %esp,%ebp  80483b7:       83 ec 08                sub    $0x8,%esp         printf("Hello world!\n");  80483ba:       c7 04 24 b4 84 04 08    movl   $0x80484b4,(%esp)  80483c1:       e8 2a ff ff ff          call   80482f0 <puts@plt>
return; } 80483c6: c9 leave 80483c7: c3 ret
080483c8 <main>:
int main(int argc, char* argv[]) { 80483c8: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483cc: 83 e4 f0 and $0xfffffff0,%esp 80483cf: ff 71 fc pushl -0x4(%ecx) 80483d2: 55 push %ebp 80483d3: 89 e5 mov %esp,%ebp 80483d5: 51 push %ecx 80483d6: 83 ec 04 sub $0x4,%esp hello_world(); 80483d9: e8 d6 ff ff ff call 80483b4 <hello_world>
return 0; 80483de: b8 00 00 00 00 mov $0x0,%eax }

调用hello_world时,汇编代码对应于call   80483b4 <hello_world>,这是个绝对地址。hello_world是在可执行文件中,可执行文件是加载到一个固定地址的,因此hello_world的地址是确定的。

调用printf时,汇编代码对应于call   80482f0 <puts@plt>,这是个绝对地址。但函数名却是puts@plt,这是怎么回事呢?puts@plt显然是编译器加的一个中间函数,我们看一下这个函数对应的汇编代码:

080482f0 <puts@plt>:  80482f0:   ff 25 2c 96 04 08       jmp    *0x804962c  80482f6:   68 10 00 00 00          push   $0x10  80482fb:   e9 c0 ff ff ff          jmp     <_init+0x30>

现在我们用调试器分析一下:

gdb test

(gdb) b main Breakpoint 1 at 0×80483d9: file test.c, line 12. (gdb) r Starting program: /root/test/plt/test

Breakpoint 1, main () at test.c:12 12 hello_world();

puts@plt先跳到*0×804962c,我们看看*0×804962c里有什么? (gdb) x 0×804962c 0×804962c <_GLOBAL_OFFSET_TABLE_+20>:   0×080482f6

*0×804962c等于0×080482f6,这正是puts@plt中的第二行汇编代码的地址。也就是说puts@plt整个函数会顺序执行,直到跳转到0×80482c0.

再来看看0×80482c0处有什么,通过汇编可以看到: ff 25 20 96 04 08 jmp    *0×8049620

又跳到了*0×8049620,转的弯真多,没关系,我们再看*0×8049620: (gdb) x 0×8049620 0×8049620 <_GLOBAL_OFFSET_TABLE_+8>:    0×009ce4c0 (gdb) x /wa 0×009ce4c0 0×9ce4c0 <_dl_runtime_resolve>: 0×8b525150

原来转来转去就是为了调用函数_dl_runtime_resolve, _dl_runtime_resolve的功能就是找到要调用函数(puts)的地址。

为什么不直接调用_dl_runtime_resolve,而要转这么多圈子呢?

先执行完这个函数hello_world: (gdb) n

再回头来看看puts@plt的第一行代码:

80482f0:   ff 25 2c 96 04 08 jmp    *0×804962c

(gdb) x 0×804962c 0×804962c <_GLOBAL_OFFSET_TABLE_+20>:   0xa39a60 <puts> 对比前面的: (gdb) x 0×804962c 0×804962c <_GLOBAL_OFFSET_TABLE_+20>:   0×080482f6

也就是说第一次执行时,通过_dl_runtime_resolve解析到函数地址,并保存puts的地址到0×804962c里,以后执行时就直接调用了。

转自:http://apps.hi.baidu.com/share/detail/24654313

--------------------------------------------

/*如果是第一次的函数调用,它所走的路线就是我在上图中用红线标出的,而要是在第二次以后调用,那就是蓝线所标明的。*/

最后我们讨论ELF文件的动态连接机制。每一个外部定义的符号在全局偏移表 (Global Offset Table GOT)中有相应的条目,如果符号是函数则在过程连接表(Procedure Linkage Table PLT)中也有相应的条目,且一个PLT条目对应一个GOT条目。对外部定义函数解析可能是整个ELF文件规范中最复杂的,下面是函数符号解析过程的一个 描述。

1:代码中调用外部函数func,语句形式为call 0xaabbccdd,地址0xaabbccdd实际上就是符号func在PLT表中对应的条目地址(假设地址为标号.PLT2)。

2:PLT表的形式如下

.PLT0: pushl          4(%ebx)           /* GOT表的地址保存在寄存器ebx中 */ jmp            *8(%ebx) nop; nop nop; nop .PLT1: jmp            *name1@GOT(%ebx) pushl          $offset jmp            .PLT0@PC .PLT2: jmp            *func@GOT(%ebx) pushl          $offset jmp            .PLT0@PC

3:查看标号.PLT2的语句,实际上是跳转到符号func在GOT表中对应的条目。

4:在符号没有重定位前,GOT表中此符号对应的地址为标号.PLT2的下一条语句,即是pushl offset,其中 offset,其中
offset是符号func的重定位偏移量。注意到这是一个二次跳转。

5:在符号func的重定位偏移量压栈后,控制跳到PLT表的第一条目(.PLT0),把GOT[1]的内容(放置了用来标识特定库的代码)压栈,并跳转到GOT[2]对应的地址。

6:GOT[2]对应的实际上是动态符号解析函数的代码,在对符号func的地址解析后,会把func在内存中的地址设置到GOT表中此符号对应的条目中。

7:当第二次调用此符号时,GOT表中对应的条目已经包含了此符号的地址,就可直接调用而不需要利用PLT表进行跳转。

动态连接是比较复杂的,但为了获得灵活性的代价通常就是复杂性。其最终目的是把GOT表中条目的值修改为符号的真实地址,这也可解释节.got包含在可读可写段中。

Linux动态链接之GOT与PLT的更多相关文章

  1. 再探Linux动态链接 -- 关于动态库的基础知识

      在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考. 编译时和运行时 纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台 ...

  2. 再探Linux动态链接 -- 关于动态库的基础知识(Dynamic Linking on Linux Revisited)

      在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考. 编译时和运行时 纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台 ...

  3. 【转】Linux动态链接(4)ldd与ldconfig

    原文网址:http://tsecer.blog.163.com/blog/static/15018172012414105551345/ 一.动态链接工具ldd和ldconfig是动态链接的两个重要辅 ...

  4. 实例分析ELF文件动态链接

    参考文献: <ELF V1.2> <程序员的自我修养---链接.装载与库>第6章 可执行文件的装载与进程 第7章 动态链接 <Linux GOT与PLT> 开发平台 ...

  5. Linux 动态库剖析

    进程与 API 动态链接的共享库是 GNU/Linux® 的一个重要方面.该种库允许可执行文件在运行时动态访问外部函数,从而(通过在需要时才会引入函数的方式)减少它们对内存的总体占用.本文研究了创建和 ...

  6. Linux动态连接器

    转自:Chapter 9. Dynamic Linking 参考:Linux动态链接器 Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程 Linux加载启动可执行程序的过程(二)解释器 ...

  7. 程序的链接和装入及Linux下动态链接的实现

    http://www.ibm.com/developerworks/cn/linux/l-dynlink/ 程序的链接和装入及Linux下动态链接的实现 程序的链接和装入存在着多种方法,而如今最为流行 ...

  8. 深入了解GOT,PLT和动态链接

    之前几篇介绍exploit的文章, 有提到return-to-plt的技术. 当时只简单介绍了 GOT和PLT表的基本作用和他们之间的关系, 所以今天就来详细分析下其具体的工作过程. 本文所用的依然是 ...

  9. Linux Debugging(七): 使用反汇编理解动态库函数调用方式GOT/PLT

    本文主要讲解动态库函数的地址是如何在运行时被定位的.首先介绍一下PIC和Relocatable的动态库的区别.然后讲解一下GOT和PLT的理论知识.GOT是Global Offset Table,是保 ...

随机推荐

  1. memcpy 与strcpy的区别

      C/C++中mencpy的代码实现:https://www.cnblogs.com/goul/p/10191705.html C/C++中strcpy的代码实现:https://www.cnblo ...

  2. 关于windows 设备驱动重要的事实

    1. windows采用设备树描述所有挂在系统总线上的设备,每个设备对应一个节点. 2.每个设备有自己的device object stack/driver stack. 一个物理上的设备对应多个de ...

  3. Vue_(组件通讯)单项数据流

    Vue单项数据流 传送门 单向数据流:父组件值的更新,会影响到子组件,反之则不行 修改子组件的值: 局部数据:在子组件中定义新的数据,将父组件传过来的值赋值给新定义的数据,之后操作这个新数据 如果对数 ...

  4. ssh以及双机互信

    当我们要远程到其他主机上面时就需要使用ssh服务了. 我们就来安装一下sshd服务以及ssh命令的使用方法. 服务安装: 需要安装OpenSSH 四个安装包: 安装包: openssh-5.3p1-1 ...

  5. JETSON TK1 ~ 安装Cuda和OpenCV3

    一:安装Cuda6.5 1:下载安装包 Cuda6.5 2.在TK1上安装软件包: cd ~/Downloads sudo dpkg -i cuda-repo-l4t-r21.3-6-5-prod_6 ...

  6. mysql基础知识语法汇总整理(一)

    mysql基础知识语法汇总整理(二)   连接数据库操作 /*连接mysql*/ mysql -h 地址 -P 端口 -u 用户名 -p 密码 例如: mysql -u root -p **** /* ...

  7. 了解dubbo+zookeeper

    一.Dubbo是什么? Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,分布式服务框架(SOA),致力于提供高性能和透明化的RPC远程 ...

  8. oracle 中使用 pl/sql代码块

    1.写匿名块,输入三角形三个表的长度.在控制台打印三角形的面积. declare -- (p=(a+b+c)/2) --声明三角形的面积 三条边 的 v_a number (10,2):=&n ...

  9. leetcode题目10.正则表达式匹配(困难)

    题目描述: 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配. '.' 匹配任意单个字符'*' 匹配零个或多个前面的那一个元素所谓匹配,是要涵盖 整个  ...

  10. (转载)深入理解Java:内省(Introspector)

    本文转载自:https://www.cnblogs.com/peida/archive/2013/06/03/3090842.html 一些概念: 内省(Introspector) 是Java 语言对 ...