欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > 操作系统-动态链接

操作系统-动态链接

2025/2/24 14:43:20 来源:https://blog.csdn.net/qq_42506224/article/details/141607050  浏览:    关键词:操作系统-动态链接

动态链接

程序的链接,是把对应的不同文件内的代码段,合并到一起,成为最后可执行文件。这个链接的方式,让我们在写代码的时候做到了“复用”,同样的功能代码只要写一次,然后提供给很多不同的程序进行链接就行了。链接可以分为动、静,共享运行内存。

动态链接的过程中,链接的并不是存储在硬盘上额目标文件代码,而是加载到内存中的共享库(share libraries)。这个加载到内存的共享库会被很多个程序的指令调用到。Windows中共享库文件就是.dll文件,即Dynamic-Link Libary(DLL,动态链接库)。在Linux下,这些共享库文件就是.so文件,即Share Object。
在这里插入图片描述

在程序运行过程中共享代码,需要要求这些机器码必须“地址无关”,即编译出来的共享库文件的指令代码,是地址无关码,也就是说这段代码无论加载在哪个内存地址,都能够正常执行。

常见的地址相关的代码,eg:绝对地址代码、利用重定位表的代码等等,都是地址相关的代码。如果在程序链接的时候,把函数调用后的要跳转访问的地址确定下来,那么当这个函数加载到一个不同的内存地址,跳转就会失败。
在这里插入图片描述

对于所有动态链接共享库的程序来讲,需要要求它们在不同的应用程序里,所在的虚拟地址是不同的。所以需要使用相对地址。各种指令中使用到的内存地址,是一个相对当前指令的偏移量的内存地址。

PLT和GOT,动态链接解决方案。

有一段程序 lib.h 定一个动态链接库的一个函数 show_me_the_memory.

// lib.h
#ifndef LIB_H
#define LIB_Hvoid show_me_the_momory(int momory);#endif

lib.c包含了lib.h的实际实现

// lib.c
#include <stdio.h>void show_me_the_memory(int memory)
{printf("Show me USD %d from lib.c \n", memory);
}

然后,show_me_memory.c 调用了lib的函数。

// show_me_memory.c
#include "lib.h"
int main()
{int memory= 5;show_me_the_memory(memory);
}

最后把lib.c编译成一个动态链接库,即.so文件

 gcc lib.c -fPIC -shared -o lib.sogcc -o show_me_memory show_me_memory.c ./lib.so
  • fPIC 参数的含义:编译成一个地址无关代码

然后通过c编译 show_me_memory动态链接了lib.so的可执行文件。然后objdump这个文件

$ objdump -d -M intel -S show_me_memory……
0000000000400540 <show_me_the_memory@plt-0x10>:400540:       ff 35 12 05 20 00       push   QWORD PTR [rip+0x200512]        # 600a58 <_GLOBAL_OFFSET_TABLE_+0x8>400546:       ff 25 14 05 20 00       jmp    QWORD PTR [rip+0x200514]        # 600a60 <_GLOBAL_OFFSET_TABLE_+0x10>40054c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]0000000000400550 <show_me_the_memory@plt>:400550:       ff 25 12 05 20 00       jmp    QWORD PTR [rip+0x200512]        # 600a68 <_GLOBAL_OFFSET_TABLE_+0x18>400556:       68 00 00 00 00          push   0x040055b:       e9 e0 ff ff ff          jmp    400540 <_init+0x28>
……
0000000000400676 <main>:400676:       55                      push   rbp400677:       48 89 e5                mov    rbp,rsp40067a:       48 83 ec 10             sub    rsp,0x1040067e:       c7 45 fc 05 00 00 00    mov    DWORD PTR [rbp-0x4],0x5400685:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]400688:       89 c7                   mov    edi,eax40068a:       e8 c1 fe ff ff          call   400550 <show_me_the_memory@plt>40068f:       c9                      leave  400690:       c3                      ret    400691:       66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]400698:       00 00 00 40069b:       0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]
……

可以看到可执行文件中调用show me_the_memory函数,对应代码如下

call   400550 <show_me_the_memory@plt>

这里有一个@plt关键字,代表要从PLT,即程序链接表(Procedure Link Table)里面查找调用的函数。对应地址即400550这个地址。

查找400550这个地址,发现如下代码,又进行了一次跳转,这次跳转指定的跳转地址,注释写GLOBAL_OFFSET_TABLE+0x18,即全局偏移表

400550:       ff 25 12 05 20 00       jmp    QWORD PTR [rip+0x200512]        # 600a68 <_GLOBAL_OFFSET_TABLE_+0x18>

在动态链接对应的共享库的data section里面,保存了一张全局偏移表(GOT,Gloabl Offset Table)。虽然

虽然共享库的代码部分的物理内存是共享的,但是数据部分是各个动态链接它的应用程序里面各加载一份的。

所有需要引用当前共享库外部地址的指令,都会查询GOT,来找到当前运行程序的虚拟内存里对应位置,GOT表的数据,则是加载一个个共享库时写进去的。

不同的进程,调用同样的lib.so文件,各自的GOT里面指向最终加载的动态链接库里面的虚拟内存地址是不同的。这样,虽然不同的程序代用同样的动态库,各自的内存地址是独立的,调用的又是同一个动态库,但是不需要去修改动态库里面代码所使用的地址,而是各个程序维护好自己的GOT,能够找到对应的动态库即可。

在这里插入图片描述

GOT表位于共享库自己的数据段里,GOT表在内存里和对应的代码段位置之间的偏移量,始终是确定的,这样共享库就是地址无关的代码,对应的各个程序只需要在物理内存加载同一份代码,程序在加载时,生成各不相同的GOT表,来找到它需要调用到的外部变量和函数的地址。

举个例子:

比如一栋大楼,它有一个表,通过这个表可以找到每个房间,因为房间是相对于这栋大楼固定的。现在,你需要从你家去到这栋大楼的某个房间,这时候,你就需要复制一份这个表。但是这个表跟原来的那个表不完全一样,需要在原表的基础上加上你家到这这栋大楼的偏移。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词