修改 DLL 文件系列的基地址
生成的 DLL 基地址确保给定 DLL 系列中每个 DLL 的连续内存空间。
介绍
在 Microsoft Visual C++ 中创建的动态链接库(DLL)的默认基地址为 0x10000000(Visual C# 的默认基地址为 0x400000)。这意味着,当进程启动时,系统加载器会尝试将 DLL 加载到进程内存中的此地址。但是,如果多个 DLL 具有相同的基地址,则只有一个 DLL 会加载到其默认地址。对于所有其他 DLL,加载器会进行重定位:根据 DLL 加载的新基地址更改 DLL 命令中的内存地址。此过程也称为 DLL 冲突。
DLL 重定位的主要缺点是
- 这会花费额外的时间。
- 保存在进程内存中的 DLL 代码会被更改。这意味着,当内存管理器需要内存页面用于其他用途时,它会将包含 DLL 代码的页面保存到系统分页文件中。这可能会降低大型程序的运行速度。
- 更改 DLL 的加载基地址可能导致当前内存段发生更改。对于非托管 DLL,这会影响近类型指针变量的地址计算。近类型指针以零偏移量引用当前内存段的第一个地址位置。
对于给定的应用程序,通常使用的 DLL 数量是恒定的。一种避免在应用程序运行时发生 DLL 冲突的解决方案是更改每个 DLL 的编译器默认基地址(首选基地址)。
RebaseDlls
应用程序是一个 Win32 MFC 控制台程序,它修改工作文件夹中或应用程序命令行中给定的每个 DLL 文件的首选基地址。RebaseDlls
应用程序在给定的地址范围 [Min, Max] 内计算首选 DLL 基地址。生成的 DLL 基地址确保工作文件夹中或命令行中给定的每个 DLL 文件的连续 DLL 内存空间。2 个连续地址之间的内存大小根据 DLL 所需的加载内存空间(64K 倍数)计算。
Min A1 A2 A3 A4 ……………….. Max
|-------|---------------|-------------|-------------------|-----------------------------|
Dll1 Dll2 Dll3 Dll4 ……………….
背景
有关 DLL 重基化的更多基本信息,请参阅 Code Project 链接:链接 1 和 链接 2。在这些文章中,还介绍了如何检查 DLL 基地址的值(Dependency Walker - Microsoft Visual Studio 6.0 工具)。
一篇关于 DLL 冲突的有趣文章可以在 链接 3 中找到。这篇文章是 RebaseDlls
应用程序的灵感来源。
Using the Code
应用程序用法
RebaseDlls
应用程序的命令行选项为
-folder:
此选项包含要重基化的 DLL 文件所在的文件夹的绝对路径。
-rec:
此选项启用在给定工作文件夹内的递归文件夹搜索(布尔值:0 / 1)。默认值为 0(不递归搜索)。
-log:
此选项启用创建一个日志文件,其中存储有关 DLL 重基化操作的详细信息(布尔值 0 / 1)。默认值为 1(创建日志文件)。日志文件在当前 RebaseDlls 应用程序文件夹中创建。
-files:
此选项包含要重基化的 DLL 文件的绝对路径。文件系列必须用分号分隔。-folder:
和 -files:
选项不能同时省略。至少要填写一个。
可以省略 -rec:
和/或 -log:
选项。
命令行示例
RebaseDlls -folder:C:\Temp -rec:1 -log:0
此 RebaseDlls
命令行处理 C:\Temp 文件夹及其内部所有文件夹中包含的所有 DLL 文件。不生成日志文件。
RebaseDlls -files:C:\File1.dll;C:\File2.dll
此 RebaseDlls
命令行处理命令行中提到的 DLL 文件(“-files:
”选项):C:\File1.dll 和 C:\File2.dll。会生成日志文件(默认)。
RebaseDlls -files:C:\File1.dll;C:\File2.dll -folder:C:\Temp
此 RebaseDlls
命令行处理命令行中提到的 DLL 文件(“-files:
”选项):C:\File1.dll 和 C:\File2.dll 以及 C:\Temp 文件夹中包含的所有 DLL 文件。不递归文件夹搜索(默认)。创建日志文件(默认)。
RebaseDlls
应用程序以报告结束,该报告包含有关整个重基化过程的简短有用信息。RebaseDlls
日志文件包含有关执行的每次 DLL 重基化操作的信息。
应用程序详情
对于未被任何 Windows 版本保留的应用程序,地址范围为 0x00400000 到 0x80000000。Windows 的系统 DLL 目前在 Intel 处理器上的内存中基于 0x70000000 到 0x78000000 的范围,在 MIPS 处理器上的内存中基于 0x68000000 到 0x78000000 的范围。其他标准 DLL(用于 OLE 支持)显然位于 0x50000000 到 0x5f000000 的范围内。在选择 DLL 的基地址时,Microsoft 建议您从允许的地址范围的顶部向下选择,以避免与应用程序动态分配的内存(从底部向上分配)发生冲突。
总之,最适合 DLL 的地址范围是 0x60000000 到 0x6f000000。Microsoft 建议进一步将范围缩小到 0x60000000 到 0x68000000,以便也容纳 MIPS 处理器,但这并不是 RebaseDlls
应用程序所考虑的情况。RebaseDlls
应用程序在 0x60000000 到 0x70000000 的地址范围内计算首选 DLL 基地址(大于 Microsoft 建议的范围)。该范围允许为重基化的 DLL 使用多达 256 MB 的内存空间。地址范围可以在源代码中修改。
#define STARTBASE 0x60000000L
#define MAXADDRESS 0x70000000L
考虑到 RebaseDlls
应用程序已应用于给定应用程序通常使用的多个 DLL 文件,因此在要加载这些 DLL 的模块中,可能存在另一个第三方 DLL 已加载到同一内存地址的情况。这种情况意味着存在首选基地址位于 RebaseDlls
重基化范围:0x60000000 至 0x70000000 的第三方(非 Windows)DLL。这种情况的发生概率非常低。
Microsoft Platform SDK 中提供的 ReBaseImage
函数完成了整个 DLL 重基化工作。由于并非所有人都安装了 SDK,因此我在项目 zip 文件中包含了 RebaseDll
C++ 项目正确编译所需的 ImageHlp.lib。
测试场景
检查 RebaseDlls
应用程序运行**之前**和**之后**的 DLL 基地址。您将使用 Dependency Walker 工具看到每个 DLL 文件基地址的差异。
在运行 RebaseDll
**之前**,检查每个 DLL 在工作模块中加载的地址。您将看到(使用 Iarsn TaskInfo 应用程序之类的系统视图工具)某些 DLL 发生了冲突,并且它们已被重定位到与首选地址不同的地址。运行 RebaseDlls
**之后**执行相同的操作。您将看到您的 DLL 在工作模块中加载的地址与 RebaseDlls
日志文件中提到的地址相对应。在这种情况下没有冲突。
关注点
对于非托管和托管 DLL,通过重基化 DLL 提高了 DLL 加载操作的速度。对于非托管 DLL,还有一个好处:避免了由于可能的 DLL 重定位而导致的近指针基地址修改。
历史
- 2009 年 4 月 23 日 - 文章创建
- 2009 年 4 月 27 日 - 文章文本更新 - 进行了一些小的修改和更正