DLL重定向

因为DLL的搜索路径有先后次序,假设有这样的场景:
App1.exe使用MyDll1.0.dll, App2.exe使用MyDll2.0.dll, MyDll1.0 和 MyDll2.0是同一个DLL的两个版本,1.0为旧版本,2.0为新版本。

而如果MyDll2.0.dll的存放路径的优先次序比较靠前时,那么App1.exe就会去加载MyDll2.0.dll,这就可能引发

DLL地狱问题,因此DLL重定向可解决这个问题。

加载程序总是先检查应用程序目录,我们所要做的就是如下:

①在应用程序目录下,新建AppName.local的文件,文件内容无所谓,比如App1.exe,对应文件名为App1.exe.local。

②在应用程序目录下,新建AppName.local的目录,把用到的DLL都放在这个目录下,比如App1.exe,对应目录为App1.exe.local。

注意:Windows中这项特性默认是关闭的,需要在

HKML\Software\Microsoft\WindowsNT\CurrentVersionImag\Image File Execution Options注册表中建一个新项,DWORD  DevOverrideEnable,值为1.

模块的基地址重定位

每个exe和DLL都有一个【首选基地址】,当系统为exe创建进程地址空间的时候,将exe加载进进程地址空间的该地址,默认为0x004000000;

当加载器将DLL加载进进程地址空间的时候,见DLL加载到其首选及地址,DLL默认为0x10000000。

也可以在VS的项目属性中设置模块的【基地址】:项目属性--配置属性--链接器--高级--基地址;  基地址必须是分配粒度的整数倍。

可以用VS的命令dumpbin来查看,也可以用depend是来查看。

e.g.

dumpbin  /headers  ADll.dll

 编译器和连接器在生成代码的时候会将【全局变量】和【静态变量】的地址硬编码,比如有如代码

//全局变量
int g_num = ; void CUseADll2Dlg::OnBnClickedButton4()
{
g_num = 0x04030201; //局部变量
int num = ;
num = 0x01020304; //静态局部变量
int static s_num = ;
s_num = 0x02030401;
}

其对应的反汇编代码如下:

可以看到【全局变量】和【静态变量】的地址采用的是硬编码,而局部变量则不是。

DLL的导出段中有符号(变量、函数)的RVA(相对虚拟地址),当DLL被加载到进程地址空间的时候,

符号的在进程地址空间中的地址 = DLL基地址 + RVA;

A.DLL 和 B.DLL的首选基地址都为0x10000000,那么当A.DLL被加载到进程地址空间的此地址的时候,B.DLL就不能被加载到此地址,此时,B.DLL就需要被重定位,那么DLL中的全局变量和静态变量的的地址就需要使用新的基地址重新计算。

假设B.DLL被加载到0x20000000,那么DLL中的全局变量和静态变量的地址就需要重新计算,此时需要有下面的动作:

①加载程序必须遍历重定位段并修改模块中大量的代码,这个过程会带来性能上的损失

②当加载程序写入模块的代码页面中时,系统的【写时复制】(copy-on-wirte)机制就会强制这些页面以系统的【页交换文件】作为后备存储器,这会导致内存页面的换入换出,这对性能是个损失。

创建一个不包含【重定位段】的exe或DLL

在vs的连接器选项中执行【/FIXED】选项,这个开关可以让模块大小变小,但是如果不能被加载到其首选基地址,那么就会载入失败。

对于一个【资源DLL】,不包含任何代码,合理的做法是:使用/FIXED开关, 并在文件头中嵌入一些信息(书上也没说到底是是什么信息,怎么嵌入?)来表示不包含重定位段是合理的。

创建一个不包含任何重定位信息的映像

①/SUBSYSTEM:WINDOWS 或  /SUBSYSTEM:CONSOLE

②不指定/FIXED开关

③在文件头中关闭【IMAGE_FILE_RELOCS_STRIPPED】标志

 VS提供了一个工具来某个模块用到的DLL进行静态重定位,Rebase.exe

通过调用ImageHlp API提供的ReBaseImage函数也可以实现重定位工具。

e.g.

在UseDll3程序中用到了两个DLL, ADll.dll 和  BDll.dll

//UseDll3项目的stdafx.h

#pragma comment(lib, "ADll.lib")
#pragma comment(lib, "BDll.lib") extern "C" __declspec(dllimport) int __stdcall Add(int a, int b); extern "C" __declspec(dllimport) int __stdcall Multi(int a, int b);

在depends中看到的DLL依赖

两者的首选基地址

现在使用rebase工具来UseDll3模块进行重定位

//略,how to use  rebase order ??  未完待续

rebase所做的操作

①模拟创建一个进程地址空间

②打开应该被载入的的所有模块,得到每个模块的【大小】和【首选基地址】

③在模拟的地址空间中对模块重定位过程进程模拟,使个模块没有交叠

④对每个重定位过的模块,解析其【重定位段】,并修改模块在磁盘文件中的代码

⑤更改每个重定位过的模块的文件头,更新其【首选基地址】

模块的绑定

VS提供了bind.exe这一工具来进行绑定,也可以使用BindMageEx()函数来实现。

对模块重定位后,仅仅是修改了其【首选基地址】,加载器还需要在运行时将【导入符号】的虚拟地址写入exe模块的【导入段】中,这也会造成这些页面以【页交换文件】作为后备存储器,也会带来内存换页的性能损失。

而模块的绑定,就是将每个DLL的【导出段】的符号的 【基地址+RVA】,写入exe模块的【导入段】,并且更新DLL的文件头信息,告诉系统这个模块已经被绑定过了, 当为exe创建进程地址空间时,就不需要再动态的获取符号的虚拟地址写入exe导入段, 而是直接从exe的【导入段】中读取即可。

NOTE:

bind.exe是基于两个假设的:

①DLL是被加载到他们的【首选基地址】的,可通过rebase来保证

②绑定完成后,DLL【导出段】中所引用的符号位置没发生变化。加载程序会通过检查每个DLL的时间戳来验证。

当加载程序检测到任意假设不成立的时候, 这时加载器就必须按照以前的样子来手的修正exe的【导入段】,如果加载器检测到两个假设都成立,那么就不再重新定位,也不再查看导入函数的虚拟地址。

【windows核心编程】DLL相关(3)的更多相关文章

  1. windows核心编程 DLL技术 【转】

    注:本文章转载于网络,源地址为:http://blog.csdn.net/ithzhang/article/details/7051558 本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑 ...

  2. 《Windows核心编程系列》二十谈谈DLL高级技术

    本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...

  3. 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。

    windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...

  4. 《windows核心编程系列》十七谈谈dll

    DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...

  5. 《Windows核心编程》读书笔记 上

    [C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对 ...

  6. C++Windows核心编程读书笔记

    转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...

  7. 【转】《windows核心编程》读书笔记

    这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...

  8. 《windows核心编程系列》二十一谈谈基址重定位和模块绑定

    每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...

  9. 《windows核心编程系列》十六谈谈内存映射文件

    内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...

  10. windows核心编程 - 线程同步机制

    线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...

随机推荐

  1. java retention注解

    Retention注解 Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值:1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源 ...

  2. 使用PowerDesigner进行数据库建模入门

    阅读目录 两种重要模型 创建表和主外键 创建视图和存储过程 生成数据库 PowerDesigner(简称PD)是一种强大的数据库建模工具,使用PD可以创建业务模型,UML类图等,当然最主要的功能是数据 ...

  3. IOS 开发中判断字符串是否为空字符的方法

    NSUInteger是无符号的整型, NSInteger是有符号的整型,在表视图应用中常见 NSUInteger row= [indexPath row];因为这是显示tableViewCell有多少 ...

  4. Hibernate逍遥游记-第10章 映射继承关系-002继承关系树中的根类对应一个表(discriminator、subclass)

    1. 2. <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate ...

  5. js错误:对象不支持此属性或方法

    对象不支持此属性或方法 错误原因: 可能是js的文件名和另外一个文件重复. 也有可能是js里的function和另外一个function名字重复. 也有可能是js里的function和页面的某一元素重 ...

  6. 直接拿来用 九个超实用的PHP代码片段(二)

    每位程序员和开发者都喜欢讨论他们最爱的代码片段,尤其是当PHP开发者花费数个小时为网页编码或创建应用时,他们更知道这些代码的重要性.为了节约编码时间,笔者收集了一些较为实用的代码片段,帮助开发者提高工 ...

  7. 异常:Caused by: java.lang.NoClassDefFoundError: Could not initialize class net.sf.log4jdbc.Properties

    参考文章: 使用Log4jdbc-log4j2监听MyBatis中运行的SQL和Connection 使用 log4jdbc格式化输出SQL,maven配置如下: <dependency> ...

  8. subsets-ii(需要思考,包括了子数组的求法)

    还是有一定难度的. 基本方法,就是用队列,然后不断累加新的数.这是为了不重复而量身定制的. 如果运行重复,是有更简单清晰的方法,就是每次增加考虑一个数字,然后加到本来每一个结果的后面.如下: publ ...

  9. 函数xdes_init

    /**********************************************************************//** Inits an extent descript ...

  10. 4010: [HNOI2015]菜肴制作

    拓扑排序+堆. 转自popoqqq神犇. 反向建图跑拓扑排序然后逆序输出. 为什么不能正的来呢,因为不知道选当前菜要先制作哪种菜. 逆序过来跑拓扑的话,也能保证满足限制条件编号小的在前面. 题外话:我 ...