操作系统-动态链接

动态链接

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

动态链接的过程中,链接的并不是存储在硬盘上额目标文件代码,而是加载到内存中的共享库(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_H

void 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.so
 gcc -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   0x0
  40055b:       e9 e0 ff ff ff          jmp    400540 <_init+0x28>
……
0000000000400676 <main>:
  400676:       55                      push   rbp
  400677:       48 89 e5                mov    rbp,rsp
  40067a:       48 83 ec 10             sub    rsp,0x10
  40067e:       c7 45 fc 05 00 00 00    mov    DWORD PTR [rbp-0x4],0x5
  400685:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  400688:       89 c7                   mov    edi,eax
  40068a:       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表,来找到它需要调用到的外部变量和函数的地址。

举个例子:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值