WINDOWS 2K Dll 加载过程

来源:互联网 发布:unity3d 动画 编辑:程序博客网 时间:2024/06/06 02:26
这片文章是我在阅读完MSJSeptember 1999 Under the Hood后的总结。
在windows中exe可执行程序运行时都会调用一些DLL,例如KERNEL32.DLL和USER32.DLL等系统的dll。但是dll是怎么被加载的呢?通常,大家都知道在编写dll时会有一个DLLMain的入口函数,但是实际上这个函数并不是调用dll时最先的工作。首先dll需要被加载,然后要进行初始化分配,再之后才进入DLLMain。还有可能你的一个dll中还会调用另一各dll。那么dll到底是怎样加载和初始化的呢,我们来参考一下PlatformSDK中的“Dynamic-Link Library Entry-Point Function”。
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
?当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
?在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
?接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windowsNT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
//=============================================================================
// Matt Pietrek, September 1999 Microsoft Systems Journal
// 中文注释部分为jefong翻译
//
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4,SP3)
//
// 当LdrpRunInitializeRoutines在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad参数是零;
//=============================================================================
#include<ntexapi.h>??? // For HardError defines near theend
// Global symbols (name isaccurate, and comes from NTDLL.DBG)
//? _NtdllBaseTag
//? _ShowSnaps
//? _SaveSp
//? _CurSp
//? _LdrpInLdrInit
//? _LdrpFatalHardErrorCount
//? _LdrpImageHasTls
NTSTATUS
LdrpRunInitializeRoutines( DWORD bImplicitLoad )
{
??? //第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了
??? unsigned nRoutinesToRun =_LdrpClearLoadInProgress();
??? if ( nRoutinesToRun)
??? {
??????? //如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
??????? pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
??????????????????????????????????????????? _NtdllBaseTag +0x60000,
??????????????????????????????????????????? nRoutinesToRun * 4);
???????????????????????????
??????? if ( 0 == pInitNodeArray )??? // Make sure allocationworked
??????????? return STATUS_NO_MEMORY;
??? }
??? else
??????? pInitNodeArray = 0;
??? //第二部分;
???//进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
??? pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);
??? ModuleLoaderInfoHead =pCurrentPeb->ModuleLoaderInfoHead;
???????
??? if ( _ShowSnaps )
??? {
??????? _DbgPrint( "LDR: Real INIT LIST\n" );
??? }
??? nModulesInitedSoFar =0;
??? if ( pCurrNode !=ModuleLoaderInfoHead ) //判断是否有新加载的模块
??? {
???????
??????? while ( pCurrNode != ModuleLoaderInfoHead )//遍历所有新加载的模块
??????? {
??????????? ModuleLoaderInfo? pModuleLoaderInfo;
???????????
??????????? //
??????????? //一个ModuleLoaderInfo结构节点的大小为0X10字节
??????????? pModuleLoaderInfo = &NextNode - 0x10;
???????????
??????????? localVar3C = pModuleLoaderInfo;????????
??????????? //
??????????? // 如果模块已经被初始化,就忽略
??????????? // X_LOADER_SAW_MODULE = 0x40 已被初始化
??????????? if ( !(pModuleLoaderInfo->Flags35 &X_LOADER_SAW_MODULE) )
??????????? {
??????????????? //
??????????????? // 模块没有被初始化,判断是否具有入口函数
??????????????? //
??????????????? if ( pModuleLoaderInfo->EntryPoint )
??????????????? {
??????????????????? //
??????????????????? //具有初始化函数,添加到模块列表中,等待进行初始化
??????????????????? pInitNodeArray[nModulesInitedSoFar]=pModuleLoaderInfo;
??????????????????? //如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
????? // 例如:
??????????????????? // C:\WINNT\system32\KERNEL32.dll init routine77f01000
??????????????????? if ( _ShowSnaps )
??????????????????? {
??????????????????????? _DbgPrint(? "%wZ init routine %x\n",
???????????????????????????????????&pModuleLoaderInfo->24,
???????????????????????????????????pModuleLoaderInfo->EntryPoint );
??????????????????? }
???????????????????nModulesInitedSoFar++;
??????????????? }
??????????? }
//设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
??????????? pModuleLoaderInfo->Flags35 &=X_LOADER_SAW_MODULE;
??????????? //处理下一个模块节点
??????????? pCurrNode = pCurrNode->pNext
??????? }
??? }
??? else
??? {
??????? pModuleLoaderInfo = localVar3C;???? // May not beinitialized???
??? }
???
??? if ( 0 == pInitNodeArray )
??????? return STATUS_SUCCESS;
??? //************************* MSJ Layout! *****************
??? // If you’re going to split this code across pages, this is agreat
??? // spot to split the code.? Just be sure to remove thiscomment
??? // ************************* MSJ Layout!*****************
???
??? //
??? // pInitNodeArray指针包含一个模块指针队列,这些模块还没有DLL_PROCESS_ATTACH
??? // 第三部分,调用初始化部分
??? try???? // Wrap all this in a try block, in case the initroutine faults
??? {
??????? nModulesInitedSoFar = 0;? // Start at array element0
??????? //
??????? // 遍历模块队列
??????? //
??????? while ( nModulesInitedSoFar < nRoutinesToRun )
??????? {
??????????? // 获得模块指针
??????????? pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar];
??????????? // Thisdoesn’t seem to do anything...
??????????? localVar3C = pModuleLoaderInfo;
???????????
??????????? nModulesInitedSoFar++;
???????????????
??????????? // 保存初始化程序入口指针
??????????? pfnInitRoutine =pModuleLoaderInfo->EntryPoint;
???????????
??????????? fBreakOnDllLoad = 0;??? // Default is to not break onload
??????????? // 调试用
??????????? // If this process is a debuggee, check to see if theloader
??????????? // should break into a debugger before calling theinitialization.
??????????? //
??????????? // DebuggerPresent (offset 2 in PEB) is whatIsDebuggerPresent()
??????????? // returns. IsDebuggerPresent is an NT only API.
??????????? //
??????????? if ( pCurrentPeb->DebuggerPresent ||pCurrentPeb->1 )
??????????? {
??????????????? LONG retCode;
???????????????//?????????????
??????????????? // Query the"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
??????????????? // Windows NT\CurrentVersion\Image File ExecutionOptions"
??????????????? // registry key.? If a a subkey entry with the nameof
??????????????? // the executable exists, check for theBreakOnDllLoad value.
??????????????? //
??????????????? retCode =
??????????????????? _LdrQueryImageFileExecutionOptions(
???????????????????????????????pModuleLoaderInfo->pwszDllName,
???????????????????????????????"BreakOnDllLoad",pInitNodeArray
??????????????????????????????? REG_DWORD,
??????????????????????????????? &fBreakOnDllLoad,
??????????????????????????????? sizeof(DWORD),
??????????????????????????????? 0 );
??????????????? // If regvalue not found (usually the case), then don’t
??????????????? // break on this DLL init
??????????????? if ( retCode <= STATUS_SUCCESS )
??????????????????? fBreakOnDllLoad = 0;pInitNodeArray
??????????? }
???????????
??????????? if ( fBreakOnDllLoad )
??????????? {??????????
??????????????? if ( _ShowSnaps )
??????????????? {
??????????????????? // Inform the debug output stream of the modulename
??????????????????? // and the init routine address before actuallybreaking
??????????????????? // into the debugger
???????????????????_DbgPrint(? "LDR: %wZ loaded.",
???????????????????????????????&pModuleLoaderInfo->pModuleLoaderInfo );
???????????????????
??????????????????? _DbgPrint(? "- About to call init routine at%lx\n",
??????????????????????????????? pfnInitRoutine )
??????????????? }
???????????????
??????????????? // Break into thedebugger?????????????????????????????
??????????????? _DbgBreakPoint();?? // An INT 3, followed by aRET
??????????? }
??????????? else if ( _ShowSnaps && pfnInitRoutine )
??????????? {
??????????????? // Inform the debug output stream of the modulename
??????????????? // and the init routine address before callingit??????????????
??????????????? _DbgPrint(? "LDR: %wZ loaded.",
??????????????????????????? pModuleLoaderInfo->pModuleLoaderInfo);
???????????????_DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
??????????? }
???????????????????
??????????? if ( pfnInitRoutine )
??????????? {
??????????????? // 设置DLL_PROCESS_ATTACH标志
??????????????? //
??????????????? // (Shouldn’t this come *after* the actualcall?)
??????????????? //
??????????????? // X_LOADER_CALLED_PROCESS_ATTACH =0x8????????????
??????????????? pModuleLoaderInfo->Flags36 |=X_LOADER_CALLED_PROCESS_ATTACH;
??????????????? //
??????????????? // If there’s Thread Local Storage (TLS) for thismodule,
??????????????? // call the TLS init functions.? *** NOTE *** Thisonly
??????????????? // occurs during the first time this code is called(when
??????????????? // implicitly loaded DLLs are initialized).?Dynamically
??????????????? // loaded DLLs shouldn’t use TLS declared vars, asper the
??????????????? // SDK documentation
??????????????? // 如果模块需要分配TLS,调用TLS初始化函数
? //注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
? // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
??????????????? if ( pModuleLoaderInfo->bHasTLS &&bImplicitLoad )
??????????????? {
??????????????????? _LdrpCallTlsInitializers(??pModuleLoaderInfo->hModDLL,
??????????????????????????????????????????????? DLL_PROCESS_ATTACH);
??????????????? }
???????????????
??????????????? hModDLL =pModuleLoaderInfo->hModDLL
??????????????? MOV????ESI,ESP // Save off the ESP register into ESI
?
? // 设置入口函数指针???????????????
??????????????? MOV???? EDI,DWORD PTR[pfnInitRoutine]????????????????????
??????????????? // In C++code, the following ASM would look like:
??????????????? //
??????????????? // initRetValue =
??????????????? //pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
??????????????? //
??????????????? PUSH???DWORD PTR [bImplicitLoad]
???????????????
??????????????? PUSH??? DLL_PROCESS_ATTACH
???????????????
??????????????? PUSH??? DWORD PTR [hModDLL]
???????????????
??????????????? CALL??? EDI???? // 调用入口函数
???????????????
??????????????? MOV???? BYTE PTR [initRetValue],AL? //保存入口函数返回值
??????????????? MOV????DWORD PTR [_SaveSp],ESI // Save stack values after the
??????????????? MOV???? DWORD PTR [_CurSp],ESP? // entry point codereturns
??????????????? MOV????ESP,ESI???? // Restore ESP to value before the call
// 检查调用前后的ESP值是否一至
? //
??????????????? if ( _CurSP != _SavSP )
??????????????? {
??????????????????? hardErrorParam =pModuleLoaderInfo->FullDllPath;
???????????????????hardErrorRetCode =
??????????????????????? _NtRaiseHardError(
??????????????????????????? STATUS_BAD_DLL_ENTRYPOINT |0x10000000,
??????????????????????????? 1,? // Number of parameters
??????????????????????????? 1,? //UnicodeStringParametersMask,
??????????????????????????? &hardErrorParam,
??????????????????????????? OptionYesNo,??? // Let userdecide
??????????????????????????? &hardErrorResponse );
???????????????????????????????????????????
??????????????????? if ( _LdrpInLdrInit )
??????????????????????? _LdrpFatalHardErrorCount++;
??????????????????? if (???(hardErrorRetCode >= STATUS_SUCCESS)
??????????????????????? &&? (ResponseYes ==hardErrorResponse) )
??????????????????? {
??????????????????????? return STATUS_DLL_INIT_FAILED;
??????????????????? }
??????????????? }
??????????????? //
??????????????? // 入口函数返回0,错误
??????????????? //
??????????????? if ( 0 == initRetValue )
??????????????? {
??????????????????? DWORD hardErrorParam2;
??????????????????? DWORD hardErrorResponse2;
???????????????????????????????????????
??????????????????? hardErrorParam2 =pModuleLoaderInfo->FullDllPath;
???????????????????
??????????????????? _NtRaiseHardError(?STATUS_DLL_INIT_FAILED,
??????????????????????????????????????? 1,? // Number ofparameters
??????????????????????????????????????? 1,? //UnicodeStringParametersMask
??????????????????????????????????????? &hardErrorParam2,
??????????????????????????????????????? OptionOk,?? // OK is onlyresponse
??????????????????????????????????????? &hardErrorResponse2);
???????????????????????????????????????????????????????????
??????????????????? if ( _LdrpInLdrInit )
??????????????????????? _LdrpFatalHardErrorCount++;
??????????????????? returnSTATUS_DLL_INIT_FAILED;
??????????????? }
??????????? }
??????? }
??????? //
??????? //如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
??????? //?????
??????? if ( _LdrpImageHasTls && bImplicitLoad )
??????? {
??????????? _LdrpCallTlsInitializers(??pCurrentPeb->ProcessImageBase,
??????????????????????????????????????? DLL_PROCESS_ATTACH );
??????? }
??? }
??? __finally
??? {
??????? //
??????? // 第四部分;
??????? // 清除分配的内存
??????? _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );
??? }
??? returnSTATUS_SUCCESS;
}??
这个函数分为四个主要部分:
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
另外,在调用入口函数时还会对TLS进行初始化,当用__declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALLEDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。
四:第四部分代码是清理代码,象_RtlAllocateHeap分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap