Linux动态链接之GOT与PLT

转载于: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 <[email protected]>        return; }  80483c6:       c9                      leave  80483c7:       c3                      ret080483c8 <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 <[email protected]>,这是个绝对地址。但函数名却是[email protected],这是怎么回事呢?[email protected]显然是编译器加的一个中间函数,我们看一下这个函数对应的汇编代码:

080482f0 <[email protected]>:  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();

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

*0×804962c等于0×080482f6,这正是[email protected]中的第二行汇编代码的地址。也就是说[email protected]整个函数会顺序执行,直到跳转到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

再回头来看看[email protected]的第一行代码:

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            *[email protected](%ebx) pushl          $offset jmp            [email protected] .PLT2: jmp            *[email protected](%ebx) pushl          $offset jmp            [email protected]

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包含在可读可写段中。

时间: 2024-07-28 15:22:35

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

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

  在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考. 编译时和运行时 纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台相关目标文件这一层次)和链接(Linking,指目标文件到最终形成可执行文件这一层次),这个总的过程可称为编译时:就动态链接而言,还存在一个运行时,即程序在被操作系统加载的过程中,系统将该程序需要的动态库加载至内存到程序开始运行的这一段过程.明确这两个过程在一般linux开发中的地位,以及了解每个"

Linux动态链接(2)so初始化执行

一.so文件和exe文件这两种文件其实具有很多相似自出,或者说so文件是介于obj文件和exe文件的一种中间过渡形式,它虽然不能直接运行(但是经过特殊编写的so文件内核是支持加载运行的,例如ld.so),但是具有了自己的一些更为高级的内容,例如一些初始化节,got表等内容,虽然孱弱,但是它具有了更加完善的生物形态.但是大家不要用进化论的观点来认为so文件比exe文件出现的早,事实上so是比较新的一个概念.我们看一下这些文件类型的标识类型说明#define ET_NONE   0#define E

Linux动态链接(1)惰性链接

一.动态链接在Linux(unix族谱)下,共享目标文件称为so文件,它和windows下的DLL机制对应,该功能在节省物理内存使用量上有重要意义,但是更重要的它还是一种扩展框架,也就是很多所谓的“插件”的实现基础.从它的出现频率上来看,它和Linux下的多线程具有同等重要的地位,甚至更高.因为很多可执行文件都没有使用pthread库,但是几乎所有的Linux发行版本的可执行文件都是动态链接生成的可执行文件.从实现机制上看,两种机制也有相同之处,它们都有共享和隔离,而线程和共享库内的线程私有数据

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

原文网址:http://tsecer.blog.163.com/blog/static/15018172012414105551345/ 一.动态链接工具ldd和ldconfig是动态链接的两个重要辅助工具,所谓“辅助”,是相对于真正的主角动态链接器ld.so,说它是工具,是只它相对于配置文件/etc/ld.so.conf文件.ldd不直接参与链接过程,它依赖于ld.so,但是ld.so不依赖于这个工具,事实上,ldd只是一个脚本,它在调用ld.so的时候传递了一些约定好的环境变量,从而使ld.

linux动态链接

1, 编译,使用-shared和-fpic 生成动态链接库库源码:test.c #include <stdio.h> #include <string.h> #include <stdlib.h> static void printline(int len) { int i; for(i = 0;i<len;i++) { printf("="); } printf("\n"); } void print(char * s)

实例分析ELF文件动态链接

参考文献: <ELF V1.2> <程序员的自我修养---链接.装载与库>第6章 可执行文件的装载与进程 第7章 动态链接 <Linux GOT与PLT> 开发平台: [[email protected] dynamic_link]# uname -a Linux tanghuimin 2.6.32-358.el6.x86_64 #1 SMP Fri Feb 22 00:31:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux 实例讲解

装载与动态链接

装载与动态链接 1可执行文件的装载与进程 可执行文件只有装载到内存后才能被CPU执行.早期的程序装载十分简陋,装载的基本过程就是把程序从外部存储器中读取到内存中的某个位置. 历史有过的装载方式包括覆盖装载.页映射. 1.1 进程虚拟地址空间 程序是一个静态的概念,它就是一些预先编译好的指令和数据集合的一个文件:进程则是一个动态的概念,它是程序运行的一个过程. 每个程序被运行起来以后,都有自己的虚拟地址空间,这个虚拟地址空间的大小由计算机的硬件平台决定,具体地说是由CPU的位数决定的. 1.2 装

程序员的自我修养-链接、装载与库-7 动态链接

动态链接 静态链接的好处:使得不同部门的开发者能够相对独立的开发和测试自己的程序模块,促进了开发效率,原先限制程序的规模也随之扩大. 缺点:浪费内存空间和磁盘空间,模块更新困难 种种罪行: 空间浪费:想想一下每个程序内部除了printf, scanf, strlen等公用库函数,还有非常多的其他库函数以及他们所需的辅助数据结构.在Linux中一个普通的c程序需要的静态库至少1MB以上. 简而言之就是,相同的目标模块每一个程序都会保留一份obj文件. 更新困难:一旦程序中有任何模块更新,整个程序都

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

http://www.ibm.com/developerworks/cn/linux/l-dynlink/ 程序的链接和装入及Linux下动态链接的实现 程序的链接和装入存在着多种方法,而如今最为流行的当属动态链接.动态装入方法.本文首先回顾了链接器和装入器的基本工作原理及这一技术的发展历史,然后通过实际的例子剖析了Linux系统下动态链接的实现.了解底层关键技术的实现细节对系统分析和设计人员无疑是必须的,尤其当我们在面对实时系统,需要对程序执行时的时空效率有着精确的度量和把握时,这种知识更显重