对于DLL,书上是这么说的:  “它们有助于节省内存。如果两个或多个应用程序使用同一个D L L,那么该D L L的页面只要放入R A M一次,所有的应用程序都可以共享它的各个页面。”
                         -摘自《windows核心编程》  但是实际操作中,却有些疑问:
  我用APIHook时,必须针对每个我感兴趣的进程、对我感兴趣的API进行hook,而若果按照书中的说法,实际上我只需要对我感兴趣的API进行一次Hook就好了,跟进程无关。  这么看起来,实际与书中的说法似乎有些矛盾。琢磨了半天,只想出一个可能性:copy-on-write机制(具体的机制《windows核心编程》里也有详细的说明)。  So,自己动手写了个修改Ntdll.dll代码段的DLL,代码如下:
/*
  WriteCopy_Test.cpp
*/
#include <windows.h>  BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to the DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpvReserved   // reserved
)
{
    if (DLL_PROCESS_ATTACH == fdwReason)
    {
        unsigned char ucBuf[1] = {0};
        HMODULE hModule = GetModuleHandle("ntdll.dll");        typedef VOID (NTAPI *_DbgBreakPoint)(VOID);
        _DbgBreakPoint DbgBreakPoint = (_DbgBreakPoint)GetProcAddress(hModule, "DbgBreakPoint");
        memcpy(&ucBuf, DbgBreakPoint, 1);
        DWORD dwOldProtect = 0;
        VirtualProtect(DbgBreakPoint, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect);
        memcpy(DbgBreakPoint, &ucBuf, 1);
    }
  //为了省内存,避免干扰,干脆连DLL_PROCESS_DETACH部分的代码也省略了
}  我机子上的NtDll.dll文件大小为578KB,启动了一个基本上什么事都不干的小程序,该程序初始占用内存442KB。
  将上面的WriteCopy_Test.dll注入该程序,发现内存占用达到1524KB。  测试的结果让我很迷惑,测试前后内存占用相差约1MB,除去NtDLL.dll的代码体积和一些WriteCopy.dll、注入程序申请的少许内存(不到1KB),还有500K去哪了?
是不是我分析的有问题?疑惑中。

解决方案 »

  1.   

    相差1MB是物理内存还是虚拟内存,要看虚拟内存.
    不知道你怎么注入DLL的,如果是远程线程,线程堆栈和线程信息需要内存.
    copy-on-write机制应该不会影响虚拟内存大小
      

  2.   

    只共用程序空间吧,DLL所用的堆栈,和变量空间还是得用内存.你可以借助一进程软件可以看,同一个dll的入口地址都是一样的(动态加载的除外)
      

  3.   

    除非有特别指定shared的segment,否则就只有code segment是共享的,也就是一个DLL只有一份,其它包括任何数据(特别处理除外)和堆栈都是非共享的。
      

  4.   

    回WM_JAWIN、unsigned:
      理论上,的确是只有代码段是共享的。我就是在测试代码段是否也有copy-on-write机制。
      

  5.   

    "我用APIHook时,必须针对每个我感兴趣的进程、对我感兴趣的API进行hook,而若果按照书中的说法,实际上我只需要对我感兴趣的API进行一次Hook就好了,跟进程无关。"不是这样的,你说的Copy On Write是对的。如果没有改动,Known DLLs是共享的,如果一个进程改动了,它就自己来一份儿了。
      

  6.   

    再说两句,这个Copy On Write 不只是进行Hook的时候才会发生,装载出现碰撞的时候也会发生,比如两个Dll都要装到一个位置,Loader只能搬走一个,被搬走的需要进行地址修复(全局变量,比如),这时候就会发生Copy On Write,这个DLL就自己占一份儿物理内存了。所以rebase好的话,可以减少物理内存使用。对于系统的KnownDlls,都是特殊处理,每个Module有固定的装载位置(Vista为了防止病毒在固定地址乱下钩子随机了,但系统也是特殊处理还是共享物理内存)。通常的情况KnownDlls是不需要Copy on Write的,除了你这种Hook。这时候你修改的Page就会单独另外分配空间。共享的通常只有代码段,数据段一写的瞬间就Copy on Write, fork出来另一个Page。
      

  7.   

    关于内存相查 500kb 的问题,因该和你的驻入方式有关,可能是你的驻入程序在驻入 DLL 后没有处理好善后工作的原因。
    此外,“它们有助于节省内存。如果两个或多个应用程序使用同一个D L L,那么该D L L的页面只要放入R A M一次,所有的应用程序都可以共享它的各个页面。”的说法是不正确的,估计你买的书说的是 Windows 9x 版本,而你使用的 却是 Windows NT/2000/XP/Vista 版本,在后者中, DLL 在内存中并不仅仅只有一份,实际上,在每个相关进程中都有一个该 DLL 的拷贝,这种拷贝不仅仅是数据段,还包括了代码段,当然,明确指定了的共享段是例外。
      

  8.   

    cdeee(亦难),是你错了。 :)  WinNT/2000/XP以后在物理内存里面也是一份儿。 和95/98的区别是当你改动DLL的时候,系统会另外做一个Copy。同样的代码一个进程里面一份儿拷贝没有任何意义。
      

  9.   

    我觉得DLL在内存里面只有一份,每个进程调用一次只是增加DLL的计数
      

  10.   

    感谢各位朋友的关注,特别是feimingbiao,
    我十分认同您的说法。
    这两天我也完善了我的测试环境,现在测试的结果多是4K,但是个别情况会出现8K的增长。
    这个我就不明白了。
     
    copy-on-write机制的单位是页,在我现在的机器上是4KB。所以我一旦修改了某个共享区某处,则copy-on-write机制就会把修改处所在的整个4KB都“搬”到进程私有空间里。可是为什么会出现8KB的情况?
    十分期待您的解答!
      

  11.   

    MagicMoon,你是如何测试内存增长的?
      

  12.   

    用VC6创建一个MFC APPWIZZARD工程,然后在某个按钮的OnOK函数里,加入修改共享代码段的代码。
    等该程序正常启动,并内存占用稳定后(因为这个程序什么具体的事都不做,所以内存很快就会稳定下来。),点击OK按钮。在任务管理器里看到的就是4KB。
    更细的测试环境我正在写,最近两天会再进行更细的测试。
      

  13.   

    MagicMoon, 任务管理器可能会有噪音,你用GetProcessMemoryInfo 看看。其实不用写加入修改共享代码段的代码,最简单是Debugger里面在Kernel 函数上设个断点就可以了,因为Debugger需要写 int 3 (cc)所以一样会造成copy on write. 对了,这个也证实了你最早问题的答案:
    "我用APIHook时,必须针对每个我感兴趣的进程、对我感兴趣的API进行hook,而若果按照书中的说法,实际上我只需要对我感兴趣的API进行一次Hook就好了,跟进程无关。"如果是这样的话,你在一个程序里面设个CreateFile的断点,所有的程序就都停下来了。
      

  14.   

    感谢feimingbiao()   
    我现在写的测试程序就是用GetProcessMemoryInfo    
    等我得出了测试结果,立刻就发上来,并且通知您,希望您能继续指导我  :)
      

  15.   

    MagicMoon,  千万别说指导,否则我不敢跟贴了。:) 我也是偷懒在这儿学习呢。挺佩服你的钻研精神的,不像我总不求甚解。下班路上想了一下8K的问题,觉得我说的TaskManager噪音不是特别靠谱。又想了想,可能是你在启动程序的时候,因为系统状况不同,MFC的Share Library(MFCxx.dll)没有完全被Page In(因为那个DLL比较大,没有执行的代码可能没有完全载入或者被Page Out了)。你点Button的时候处理WM_COMMAND的那段儿CDialog的程序可能加载,用了一页。以下简单的程序不知道会不会再有8K现象:main()
    {
       MessageBoxA(0,"start",0,0);
       //看TaskManager
       改DLL
       MessageBoxA(0,"take another look", 0,0)
       //看TaskManager
    }
      

  16.   

    feimingbiao,我用了你的方法,果然只有4KB的内存增量。多谢 :)
    到目前为止,我只是证明了这样改会有一个内存页的增量,看上去似乎是copy-on-write
    可是怎么拿出强有力的数据证据来证明我的问题的源头就是copy-on-write机制呢?
    我查了下资料,copy-on-write机制是操作系统和CPU合作完成的,所以我很郁闷。
    暂时还没想好怎么继续分析~
    如果你有好点子,分享(期待你的跟帖,所以就不用“指导”一词了 :) )下
      

  17.   

    MagicMoon,向你学习,我也埋头真正钻研一下,赶下时髦,平生头一次写Blog,我的实验报告:http://blog.csdn.net/feimingbiao/archive/2007/08/10/1736371.aspx我觉得可以定论了,你不满意我们再研究。
      

  18.   

    :)
    看了你的分析,很好,很强大~
    我最近也会做些分析,可能会碰到些问题,到时候找你讨论吧(在你BLOG上留言,然后给你发CSDN消息)