考虑一下你对EXE、DLL以及它们是如何被加载的和初始化的到底知道多少。你可能知道当一个用C++写成的DLL被加载时,它的DllMain函数会被调用。想一想当你的EXE隐含链接到一些DLL(例如,KERNEL32.DLL和USER32.DLL)时到底发生了什么。这些DLL是以什么顺序被初始化的?某个DLL将要被初始化,而它所依赖的其它DLL还未被初始化,这可能吗?Platform SDK在“Dynamic Link Library Entry Point Function(动态链接库入口点函数)”一节中对此描述如下:
如果你碰巧知道_LdrpRunInitializeRoutines@4的地址(在Intel平台的Windows NT 4.0 SP3上这个地址为0x77F63242),你可以键入那个地址,汇编窗口很容易就会显示它。IDE甚至会显示这个函数的名称为_LdrpRunInitializeRoutines@4。如果你不是调试器老手,符号名识别失败让人很困惑。如果你和我一样是个调试器爱好者,这是非常讨厌的,因为你不知道到底问题出在哪里。
在图6中,注意所有从NTDLL中输出的内容前面都加了LDR:前缀。其它行(例如“Loaded symbols for XXX”)是由MSDEV进程插入的。在查看带有LDR:的行时会发现一些有价值的信息。例如在进程启动时给出了EXE文件的完整路径以及当前目录和搜索路径。
由于NTDLL加载各个DLL并修正导入函数的地址,因此你会看到类似下面的信息:
LDR: ntdll.dll used by SHELL32.dll
LDR: Snapping imports for SHELL32.dll from ntdll.dll
第一行表明SHELL32.DLL链接到了NTDLL中的API上。第二行表明了从NTDLL导入的API正常被“snapped(快照)”。当可执行模块从其它DLL导入函数时,在它里面就有一个函数指针数组。这个函数指针数组就是IAT。加载器的工作之一就是定位导入函数的地址并把它们填入IAT中。因此,术语“snapping”就出现在了LDR:输出中。
输出内容中另一个引起我注意的是正在被处理的DLL的绑定信息。
LDR: COMCTL32.dll bound to KERNEL32.dll
LDR: COMCTL32.dll has correct binding to KERNEL32.dll
在以前的专栏中,我曾经讲过使用BIND.EXE程序或IMAGEHLP.DLL导出的BindImageEx这个API来绑定程序。将一个可执行文件绑定到某个DLL上实际就是查找导入函数的地址并把它们写入到磁盘上的可执行文件中。这可以加速加载过程,因为加载时不再需要查找导入函数的地址了。
return STATUS_SUCCESS;
}
返回
图6 CALC.EXE的ShowSnaps输出信息
## 以##开头的是我的注释
Loaded 'C:\WINNT\system32\CALC.EXE', no matching symbolic information found.
Loaded symbols for 'C:\WINNT\system32\ntdll.dll'
LDR: PID: 0x3a started - '"C:\WINNT\system32\CALC.EXE"'
LDR: NEW PROCESS
Image Path: C:\WINNT\system32\CALC.EXE (CALC.EXE)
Current Directory: C:\WINNT\system32
Search Path: C:\WINNT\system32;.;C:\WINNT\System32;C:\WINNT\system;...
LDR: SHELL32.dll used by CALC.EXE
Loaded 'C:\WINNT\system32\SHELL32.DLL', no matching symbolic information found.
LDR: ntdll.dll used by SHELL32.dll
LDR: Snapping imports for SHELL32.dll from ntdll.dll
LDR: KERNEL32.dll used by SHELL32.dll
Loaded symbols for 'C:\WINNT\system32\KERNEL32.DLL'
LDR: ntdll.dll used by KERNEL32.dll
LDR: Snapping imports for KERNEL32.dll from ntdll.dll
LDR: Snapping imports for SHELL32.dll from KERNEL32.dll
LDR: LdrLoadDll, loading NTDLL.dll from
LDR: LdrGetProcedureAddress by NAME - RtlEnterCriticalSection
LDR: LdrLoadDll, loading NTDLL.dll from
LDR: LdrGetProcedureAddress by NAME - RtlDeleteCriticalSection//其余部分省略....
LDR: GDI32.dll used by SHELL32.dll
Loaded symbols for 'C:\WINNT\system32\GDI32.DLL'
LDR: ntdll.dll used by GDI32.dll
LDR: Snapping imports for GDI32.dll from ntdll.dll
LDR: KERNEL32.dll used by GDI32.dll
LDR: Snapping imports for GDI32.dll from KERNEL32.dll
LDR: USER32.dll used by GDI32.dll
Loaded symbols for 'C:\WINNT\system32\USER32.DLL'
LDR: ntdll.dll used by USER32.dll
LDR: Snapping imports for USER32.dll from ntdll.dll
LDR: KERNEL32.dll used by USER32.dll
LDR: Snapping imports for USER32.dll from KERNEL32.dll
LDR: LdrLoadDll, loading NTDLL.dll from
LDR: LdrGetProcedureAddress by NAME - RtlSizeHeap
LDR: LdrLoadDll, loading NTDLL.dll from
LDR: LdrGetProcedureAddress by NAME - RtlReAllocateHeap
LDR: LdrLoadDll, loading NTDLL.dll from
LDR: LdrGetProcedureAddress by NAME - RtlFreeHeap
LDR: LdrLoadDll, loading NTDLL.dll from
LDR: LdrGetProcedureAddress by NAME - RtlAllocateHeap//其余部分省略....
## 注意加载器开始查找并校验COMCTL32导入并绑定的DLL
Loaded 'C:\WINNT\system32\COMCTL32.DLL', no matching symbolic information found.
LDR: COMCTL32.dll bound to ntdll.dll
LDR: COMCTL32.dll has correct binding to ntdll.dll
LDR: COMCTL32.dll bound to GDI32.dll
LDR: COMCTL32.dll has correct binding to GDI32.dll
LDR: COMCTL32.dll bound to KERNEL32.dll
LDR: COMCTL32.dll has correct binding to KERNEL32.dll
LDR: COMCTL32.dll bound to ntdll.dll via forwarder(s) from KERNEL32.dll
LDR: COMCTL32.dll has correct binding to ntdll.dll
LDR: COMCTL32.dll bound to USER32.dll
LDR: COMCTL32.dll has correct binding to USER32.dll
LDR: COMCTL32.dll bound to ADVAPI32.dll
LDR: COMCTL32.dll has correct binding to ADVAPI32.dll//其余部分省略....
LDR: Refcount COMCTL32.dll (1)
LDR: Refcount GDI32.dll (3)
LDR: Refcount KERNEL32.dll (6)
LDR: Refcount USER32.dll (4)
LDR: Refcount ADVAPI32.dll (5)
LDR: Refcount KERNEL32.dll (7)
LDR: Refcount GDI32.dll (4)
LDR: Refcount USER32.dll (5)## List of implicit link DLLs to be init'ed.
LDR: Real INIT LIST
C:\WINNT\system32\KERNEL32.dll init routine 77f01000
C:\WINNT\system32\RPCRT4.dll init routine 77e1b6d5
C:\WINNT\system32\ADVAPI32.dll init routine 77dc1000
C:\WINNT\system32\USER32.dll init routine 77e78037
C:\WINNT\system32\COMCTL32.dll init routine 71031a18
C:\WINNT\system32\SHELL32.dll init routine 77c41094
## 开始实际调用隐含链接的DLL的初始化例程
LDR: KERNEL32.dll loaded. - Calling init routine at 77f01000
LDR: RPCRT4.dll loaded. - Calling init routine at 77e1b6d5
LDR: ADVAPI32.dll loaded. - Calling init routine at 77dc1000
LDR: USER32.dll loaded. - Calling init routine at 77e78037
## USER32开始做与AppInit_DLLs有关的工作,因此静态初始化被暂时中断
## 这个例子中,“globaldll.dll”是在USER32的初始化代码中由LoadLibrary加载的
LDR: LdrLoadDll, loading c:\temp\globaldll.dll from C:\WINNT\system32;.;
LDR: Loading (DYNAMIC) c:\temp\globaldll.dll
Loaded 'C:\TEMP\GlobalDLL.dll', no matching symbolic information found.
LDR: KERNEL32.dll used by globaldll.dll//其余部分省略....
LDR: Real INIT LIST
c:\temp\globaldll.dll init routine 10001310
LDR: globaldll.dll loaded. - Calling init routine at 10001310
## 现在接着初始化隐含链接的DLL
LDR: COMCTL32.dll loaded. - Calling init routine at 71031a18
LDR: LdrGetDllHandle, searching for USER32.dll from
LDR: LdrGetProcedureAddress by NAME - GetSystemMetrics
LDR: LdrGetProcedureAddress by NAME - MonitorFromWindow
LDR: SHELL32.dll loaded. - Calling init routine at 77c41094
//其余部分省略....
返回
图7 TLSInit.cpp
void _LdrpCallTlsInitializers( HMODULE hModule, DWORD fdwReason )
{
PIMAGE_TLS_DIRECTORY pTlsDir;
DWORD size