## CSDN博客

               -- 中文翻译, by snake.(http://snake12.top263.net)

在 1998年12月的MSJ出版刊物中, Jeffrey和我

通常的,当调用一个dll中的函数时,连接器

当加载该程序的时候,win32程序加载器会扫描

上面的是简化的步骤.实际上,根区是一小段代码，

DLLNAME是相关的DLL文件名。你还需要增加DELAYIMP.LIB到

很不幸，Visual Studio 6.0 IDE 不提供一个简单的方法

"Project Options"中。

然而，我想找到一个简单的，自动的方法来跟踪。于是，

exe文件对dll的调用，直到你的exe结束。它打印出dll被调

第一，你必须决定，要在哪里运行你的代码，实现对exe的IAT

接着的艰苦工作是要在和目的exe相同的进程空间里完成。几

当确认到需要在目的进程中，加载我的代码的时候，下个问

在这点上，我想到了加载程序，然后，插入我的

很久以前的MSJ读者可能记得我5年前写的一个叫APISPY32的

完成所有的工作，包括 监视 一个程序的引入的dll,叫

当DllMain获得DLL_PROCESS_ATTACH的消息的时候，

RedirectIAT是比较复杂的函数。如果你理解了 winini.h中

IAT和相关的引入名字表，然后，计算有多少个IAT入口，扫描

DLPD_IAT_STUB根区，每个根区对应一个IAT入口。
最后，代码重新扫描IAT，获取每个IAT入口的地址，用根区

DLPD_IAT_STUB根区。我在后面将还会继续解析。
在重定向IAT入口的根区中，有2个值得提起：1，IAT常常

另外一个要注意的地方，就是在重定向IAT的时候，有数据引

那么，如何判断一个IAT是数据呢？一个商业的软件，应该用

DLPD_IAT_STUB结构包含着代码和数据。简单来说，就是如下：
JMP xxxxxxxx //original IAT 地址
DWORD count
DWORD pssNameOrOrdinal

当exe调用其中一个重定向的函数时，控制权被转到根区的CALL

5个字节，根据这些算法，可以确定COUNT字段的地址。

当你的进程停止的时候，系统对所有加载的DLL发送一个

ReportProfileResults用到这个指针来再次遍历引入段。如果这个
IAT是被重定向的，那么，第一个IAT的指针应该指到第一个

总的说来，所有的都很正常，并且，第一个IAT入口指到我的根区

ReportProfileResults格式化一个字符串，输出该dll的名字，和调

CDebugInjector类。我将简单的介绍它。函数主要包含了目的exe

在运行目标程序之前，最后的步骤是调用

描述3（hoodtextfigs.htm#fig3）说明了CDebugInjector类。这是

WaitForDebugEvent返回，都有些东西发生在调试程序身上。可能是一个

CDebugInjector::Run过程包含这个循环的代码。
那么，如何让目的进程作为一个被调试进程，帮助你注射一个dll呢？

在某些特定场合，CDebugInjector合成了一小段代码根区来调用
CDebugInjector写那个根区（和相关联的dll名字）到被调试者的地址

如果你不想那么多，这个注射进程不会觉得太难，但是，一些有兴趣的

最后，我决定让被调试者运行，直到碰到了第一个断点。我在程序入口

顺便说一下：断点从哪里来呢？通过定义，一个被调试的win32的进程，

DebugBreak在Kernel32.dll初始化之前，被调用，那么，CDebugInjector

CDebugInjector要处理的断点，所有的处理不同断点的技巧，可以参考
CDebugInjector::HandleException。

DelayLoadProfile: SHELL32.dll was called 0 times
DelayLoadProfile: MSVCRT.dll was called 9 times
DelayLoadProfile: GDI32.dll was called 60 times
DelayLoadProfile: USER32.dll was called 691 times

你将回觉得奇怪，为什么calc调用shell32.dll，你没有调用它。如果你

在你的测试过程中，你的测试的程序的个数，也是值得考虑的。如果你

OL3AUT32.DLL。一个多态的程序，在小容器中，用到COM和OLE，那么，

IsModuleOKToHook函数，我放了MFC42.DLL，MFC42U.DLL和KERNEL32.DLL进

matt@wheaty.net，或者 http://www.wheaty.net

In the December 1998 issue of MSJ, Jeffrey Richter and I wrote dueling columns on the DelayLoad feature of the Microsoft® Visual C++® 6.0 linker. The fact that both Jeff and I jumped on this topic is testimony to how cool this feature is. Unfortunately, I still find people who don't know anything about DelayLoad or they think it's some feature that's available only in the latest version of Windows NT®. For starters, let me scream from the highest rooftop that DelayLoad is not an operating system feature. It works on any Win32®-based system. With that off my chest, I'll demonstrate this month's utility, DelayLoadProfile, which makes it almost trivial to determine whether your program can benefit from DelayLoad. As I'll show, even some of Microsoft's own programs can benefit from it.A Quick Review If you're wondering "What's this thing Matt's gone off the deep end over?" a quick recap of DelayLoad is in order. Here's how it works. Normally, when calling an imported function in a DLL, the linker adds information about the imported DLL and function to your executable. Collectively, the information for all the imported functions is known as the imports section. The Win32 loader scans through the imports section at load time and loads each DLL. For each DLL loaded, the loader iterates through all the imported functions and locates their addresses in the imported DLL. These addresses are written back to the imports section in a location known as the Import Address Table (IAT). A simple way to think of an IAT is as an array of function pointers. When calling an imported function, the call uses one of the function pointers from the IAT. How does the picture change with DelayLoad? When you specify DelayLoad for a DLL, the linker doesn't emit the usual data it would put in the imports section. Instead, it generates a small stub for each DelayLoad imported function. This stub points to the imported DLL and function name. Upon calling an imported function for the first time, the stub calls LoadLibrary to load the DLL. Next, it calls GetProcAddress to get the address of the called function. Finally, the stub overwrites part of itself so that subsequent calls to the function go directly to the target code. What I've just described is a slight simplification. In reality, the stub is a small bit of code that calls a routine statically linked into your executable. This routine resides in DELAYIMP.LIB, which must be included in the list of libraries that the linker uses. Also, the stubs and DELAYIMP.LIB code are smart enough to call LoadLibrary only the first time a function in the DLL is used. Subsequent calls to other functions in the same DelayLoad imported DLL don't call LoadLibrary.All things considered, DelayLoad doesn't add much time or space overhead compared to importing the DLL the usual way. Calling LoadLibrary is only slightly less efficient than letting the Win32 loader load the DLL. Likewise, calling GetProcAddress once for each DelayLoad imported function is only slightly slower than having the Win32 loader locate the imported functions at startup. However, the benefits of DelayLoad can easily make up for these small speed penalties. For starters, if you never call a function in a DelayLoad imported DLL, the DLL isn't loaded in the first place. This comes in handy more often than you may think. Consider the situation in which you have printing code in your program. If the user doesn't print something during a program session, you've loaded WINSPOOL.DRV for no reason. In this case, using DelayLoad is actually faster since you never loaded and initialized WINSPOOL.DRV. Another benefit of using DelayLoad is that you avoid calling APIs that are not available on one of your target platforms. For instance, say you want to call AnimateWindow, which is supported in Windows® 98 and Windows 2000, but not Windows 95 or Windows NT 4.0. If you were to call AnimateWindow the usual way, your code wouldn't load on the earlier platforms. However, with DelayLoad you can make a runtime check of which operating system you're on and only call AnimateWindow if it's supported. There's no need for you to muck up your code with calls to LoadLibrary and GetProcAddress. Using DelayLoad is incredibly easy. Once you know which DLLs you want to use DelayLoad with, simply add /DELAYLOAD:DLLNAME, where DLLNAME is the name of the DLL. You'll also need to add DELAYIMP.LIB to the linker's library list, and you'll still need the original import library, for example, SHELL32.LIB. Putting everything together, to DelayLoad against SHELL32.DLL your linker line would need the following:
JMP     XXXXXXXX // original IAT address
DWORD   count
DWORD   pszNameOrOrdinal
I simply started CALC and immediately shut it down. Note that SHELL32.DLL and ADVAPI32.DLL both had no calls to them. These two DLLs are prime candidates for CALC to DelayLoad. You may be wondering why CALC loads SHELL32.DLL, yet doesn't call it. It would be easy enough to run DumpBin /IMPORTS or Depends.EXE against CALC. In doing so, you'd see that the only function CALC imports from SHELL32.DLL is ShellAboutW. Simply put, unless you select the Help | About Calculator menu item in CALC, it's a complete waste of time and memory to load SHELL32.DLL. This is a fabulous example of where /DELAYLOAD can really show its worth. Incidentally, SHELL32.DLL implicitly links against SHLWAPI.DLL and COMCTL32.DLL—two additional DLLs that are brought into memory and initialized for no reason. Just because DelayLoadProfile reports that a DLL is receiving few or no calls at all doesn't mean you should automatically DelayLoad it. Be sure to consider whether one of your implicitly linked DLLs also links against the DLL you're considering using DelayLoad with. If this is the case, it's not worth using /DELAYLOAD in your EXE since the DLL is still going to be loaded and initialized because of some other dependency. Depends.EXE from the Platform SDK is a great tool for quickly determining the scope of a DLL's usage. Another thing to consider when using DelayLoadProfile is how much of your app you'll exercise during your test. Obviously, if you exercise all aspects of your app, all the DLLs you import in the EXE will be invoked. Personally, I think minimal load time is a good target to shoot for. This might mean just starting your program and then closing it down. By spreading the work of loading and initializing your DLLs throughout your application as it runs, you can speed the initial load sequence. Users often subjectively judge the speed of your application by its startup time. I've found a few DLLs that will benefit from using /DELAYLOAD. As you saw earlier, SHELL32.DLL is one of them. Another is WINSPOOL.DRV, which is used for printing support. Since most users don't print frequently, it's a good candidate, as are OLE32.DLL and OLEAUT32.DLL. In addition, a variety of programs use COM and OLE in some minimal capacity, making those DLLs possible candidates, too. For example, the Windows 2000 CDPLAYER.EXE links against OLE32.DLL and the CreateStreamOnHGlobal API. Yet in ordinary usage, I didn't observe this function being called. DelayLoadProfile is not without its faults (literally). While I've tested it successfully with a large number of applications, you may still run into the occasional program that doesn't work so well when DelayLoadProfileDLL interfaces with its IAT. Trying to find and locate all these odd scenarios is beyond the scope of this column. However, if you locate and fix one of these problems, please let me know. I may update DelayLoadProfile at some future date. I know that programs that import MFC42.DLL and MFC42U.DLL can crash with DelayLoadProfile. For that reason I've provided an escape hatch. In DelayLoadProfileDLL.CPP it's the IsModuleOKToHook function. I've placed MFC42.DLL, MFC42U.DLL, and KERNEL32.DLL in it. (You can't use /DELAYLOAD with KERNEL32.DLL anyhow, so it's no loss.) If a particular DLL seems to be giving you problems, first try adding it to IsModuleOKToHook. I hope DelayLoadProfile's ease of use will inspire you to tune your applications to make use of /DELAYLOAD. I certainly had a good time updating some classic code, and I'd enjoy hearing your success stories, too. Have a suggestion for Under The Hood? Send it to Matt at matt@wheaty.net or http://www.wheaty.net.