动态链接库(DLL)重定址

来源:互联网 发布:编程语言的都有啥 编辑:程序博客网 时间:2024/06/05 18:48

动态链接库(DLL)重定址


一、概述

本文介绍了应用程序依赖多个DLL启动的情况下,如何通过DLL重定址来提升应用程序启动时的性能。它包括如何改变编译器的设置来重定址DLL,并且通过使用/FIXED改变重定址。

每个exe和dll模块有各自的首选基址,这个地址指定了该模块应该映射到内存地址空间的最适内存地址。当你建立一个exe模块时,连接器设置了模块的首选基址为0x00400000。对于一个DLL则首选基址为0x10000000。使用vs的dumpbin工具能看到首选基址。使用Dumpbin /option exename.exe或者使用vs的Dependency Walker工具,点击EXE文件,将会看到所有DLL的信息,同时还能看到它们加载的基址。

当exe模块执行,操作系统加载器为新进程创建了一个虚拟地址空间。然后加载器在内存地址0x00400000处加载exe模块、在0x10000000处加载dll。为什么首选基址如此重要?让我们看看如下代码:

1、1使用代码

看看下面代码段。我在一个函数中初始化整数i。
int i;void Func(){      int i = 5;}
当编译器处理Func函数,编译器和连接器产生可能想下面的机器代码:
MOV    [0x10014540], 5

换言之,编译器和连接器在变量“i”(0x10014540)地址处创建了硬编码地址变量。只要DLL加载了其首选基址,那么这个内存地址是绝对正确的。

好了,现在编写的应用程序需要2个DLL。默认情况下连接器设置exe模块的首选基址0x00400000,两个DLL的首选基址都是0x10000000。当运行exe时,加载器创建虚拟地址空间并且将exe模块地址映射到0x00400000。然后映射第一个DLL到内存0x10000000处,然而现在当加载器试图加载第二个DLL到内存地址空间时,它不可能映射到其首选基址(0x10000000),而必须从新为DLL模块寻址,放到其他地方。

下面图片展示了没有重定址的情况下使用Dependency Walker测试调用DLL1和DLL2的EXE。正如所看到的,2个DLL有相同的基址,只有一个会被在首选基址加载,另一个要重新分配。


重定址exe(或者dll)是一个十分痛苦的过程,你应该避免。假定加载器重定址DLL2的地址为0x20000000。这样,给 i 赋值5的汇编代码:
MOV    [0x20014540], 5

然而在文件映像中的代码却是这样:
MOV    [0x10014540], 5
如果文件映像中的代码并没有在exe加载时改变地址,DLL1中一些4字节的值会被重写为5。这是不被允许的。加载器必须修复这个问题。当连接器建立了模块时(obj文件),在生成的文件(obj文件)中嵌入一个重定址段。如果加载器能在首选地址被加载,则模块的重定址段不会被操作系统访问。这正是我们所想要的----你绝不会使用重定址段,看看下面的缘由。

如果模块未能映射到首选基址,加载器打开模块的重定址段并且遍历所有入口点。对于每一个入口点,加载器访问包含机器代码指示修改的存储页,然后保存当前正在使用的内存地址,加上首选基址和最终加载的差值就是最终模块重定址的地址。

所以,上面的例子中,DLL2映射到0x20000000,但是其首选基址是0x10000000。这改变了开始的地址0x10000000,然后再执行机器的汇编。

为了避免加载器重定址的麻烦,在编译的时候就可以改变基址。使用下面方法修改。


重新编译,使用Depends Walker来检测使用DLL1和DLL2的EXE。正如所看到的,两个DLL有不同的基址(DLL1:0x10000000   DLL2:0x20000000)。如果某种原因不能在指定的基址中加载,那么就重复以上重定址的过程。



模块在未能在首选基址加载有两个主要的缺点:
  • 加载器必须遍历重定址段并且更改模块很多代码。这影响到性能,应用程序初始化时间也就变长。
  • 随着加载程序写入模块的代码页,系统即写即拷机制迫使这些页面支持系统的分页文件,
第二个缺点十分糟糕。这意味着模块的代码页不能被丢弃,并且需要从磁盘重新加载模块的映像。然而,和系统进行交换的页面文件是很重要的。这也对性能有影响。更糟的是,当页面从代码页返回时,系统很少存储使用时需要用到的页。

顺便说一下,你也能创建不需要重定址的EXE或DLL。当建立模块时选择使用/FIXED来链接。用这种方式让模块所用空间更少,但却不能重定址。如果模块不能在首选基址上加载,它便不能被加载了。如果加载器必须加载这个模块而这个模块没有重定址段存在,那么加载器将杀死进程,同时发出“Abnormal Process Termination”信息给用户。


在下面的例子中我让两个DLL基址都是0x20000000,然后给它们加上/FIXED开关。因此就只能有一个DLL被加载,另一个不能重定址因此未能加载,你将得到下面信息:





翻译不好,加紧练习中。大家可以去原文查看。穿越。。






0 0
原创粉丝点击