zoukankan      html  css  js  c++  java
  • 【windows核心编程】DLL相关(3)

     

    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中这项特性默认是关闭的,需要在

    HKMLSoftwareMicrosoftWindowsNTCurrentVersionImagImage 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 = 0;
    
    void CUseADll2Dlg::OnBnClickedButton4()
    {
        g_num = 0x04030201;
    
        //局部变量
        int num = 0;
        num = 0x01020304;
    
        //静态局部变量
        int static s_num = 0;
        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的【导入段】,如果加载器检测到两个假设都成立,那么就不再重新定位,也不再查看导入函数的虚拟地址。

     

     

      

  • 相关阅读:
    Putty·Network error:Software caused connection abort
    VSCode·搭建Java开发环境
    MSSQL·将一对多的数据合并为以指定分隔符的数据
    技能Get·Windows10将任何格式文件固定到开始屏幕
    MSSQL·查询TSQL语句执行时间的三种方法
    Javac·编码GBK的不可映射字符
    傅里叶变换、拉氏变换、z变换的含义
    宏、内联函数和普通函数的区别
    OpenGL中创建GLUT菜单
    在PC安裝Android系統+軟體
  • 原文地址:https://www.cnblogs.com/cuish/p/3762565.html
Copyright © 2011-2022 走看看