Windows 2K DLL load process JEFONG BY 2005/03/30 This article is a summary after I have read MSJ September 1999 under the hood. Some DLLs such as kernel32.dll and user32.dll are called in Windows. But how did the DLL are loaded? Often, everyone knows that there will be a DLLMain's entry function when writing a DLL, but in fact this function is not the first job when calling the DLL. First of all, the DLL needs to be loaded, and then the initialization assignment is initialized, and then it will enter the DLLMAIN. It is also possible that you will also call another DLL in your DLL. So how do DLL loads and initializes, let's refer to "Dynamic-Link Library Entry-Point Function" in Platform SDK. Your function is performing an initialization task, such as setting TLS, create a synchronization object, or open a file. Then you must call the LoadLibrary function in the function, because the DLL load command creates a dependency loop. This will result in a function of DLL that has been called before the system executing the DLL initialization code. For example, you can't call the FreeElibrary function in the entrance function, as this will cause the system to call the DLL in the DLL that has ended the DLL, causing serious errors. Calling the Win32 function while initializing the task will also cause errors, such as calling USER, Shell, and COM functions may cause a storage invalid error, because some functions in the DLL call LoadLibrary to load other system components. When you read a registry key value in your dllmain function, you will be restricted because the advapi32.dll is not initialized when you execute the DLLMAIN code, so the function of the read registry you call. failure. The initialization part in the document is strictly limited, but there is special case, and the user32.dll in Windows is ignored above. This seems like the opposite to the above, and the part of the USER32.DLL appears to call the LoadLibrary load DLL, but there is no problem. This is because Appinit_DLLS, Appinit_DLLS can call a DLL list for any process. So if your user32.dll calls the problem, it must be appinit_dlls without work. Next, let's take a look at how the DLL load and initialization are completed. The operating system has a loader that loads a module usually there are two steps: 1. Put the EXE or DLL image to the memory, at this time, the loader checks the module's import address table (IAT), see if the module depends on additional DLL. If the DLL has not been loaded into the process, the loader will put the DLL image to memory. Until all unloaded modules are imaged to memory. 2. Initialize all DLLs. In Windows NT, the system calls the Exe and DLL entry functions will call the LDRPRUNITILIZEROUTINES function first, that is, when you call LoadLibrary, you will call LDRPRunInitializeroutines when calling LDRUNInInitializerout, first check whether the DLL that has been mapped to memory has been initialized.
Let's look at the following code (Matt's LDRUNITILALIZEROUTINES pseudo code): / / ================================== =================================================== // matt pietrek, september 1999 Microsoft Systems Journal // Chinese Comment section is jefong translation /// pseudocode for ldrpruninitializeroutines in ntdll.dll (NT 4, SP3) / / / / When LDRPRUNInInIzitiUTINES is called in a process (this process implicit The link module has been initialized), the BIMPLICITLOAD parameter is non-zero. When using LoadLibrary calls DLL, the BIMPLICITLOAD parameter is zero; // ==================================== ================================================== # include
// Global Symbols (Name IS ACCURATE, AND COMES from NTDLL.DBG) // _NTDLLBASETAG / / / _SHOWSNAPS / / _SAVESP / / _CURSP / / _LDRPINLDRINIT / / _LDRPFATALHARDERRORCOUNT / / _LDRPIMAGEHASTLS
NTSTATUSLDRUNITIALIZERIZEROUTIOUTINES (DWORD BIMPLICIAD) {// The number of modules that may be initialized can be obtained. Some modules may have been initialized with unsigned nroutinestorun = _ldrpclearloadinprogress ();
If (nroutinestorun) {// If there is a module that needs to be initialized, assign a queue to load each module information. pInitNodeArray = _RtlAllocateHeap (GetProcessHeap (), _NtdllBaseTag 0x60000, nRoutinesToRun * 4); if (0 == pInitNodeArray) // Make sure allocation worked return STATUS_NO_MEMORY;} else pInitNodeArray = 0; // second portion; // Process Environment Block (PEB) contains a pointer to a link list to a new load module. PCurrNode = * (pcurrentpeb-> moduleLoaderinfohead); ModuleLoaderinfoHead = pcurrentpeb-> moduleLoaderinfohead; if (_showsnaps) {_dbgprint ("ldr: real init list / n");}
nmodulesInitedSofar = 0;
if (! pCurrNode = ModuleLoaderInfoHead) // determines whether the newly loaded module {while (! pCurrNode = ModuleLoaderInfoHead) // iterate through all the newly loaded module {ModuleLoaderInfo pModuleLoaderInfo; // // ModuleLoaderInfo a node configuration byte size of 0X10 PModuleLoaderInfo = & nextNode - 0x10; Localvar3c = PModuleLoaderInfo;
// // If the module has been initialized, ignore // x_loader_saw_module = 0x40 has been initialized if (! (PModuleLoader_SAW_Module) {// // The module is not initialized, determined whether there is an entry function // if (PModuleLoaderInfo-> EntryPoint) {// has an initialization function, add to the module list, wait for the initialization pinitnodeaRray [nmodulesinitedsofar] = PModuleLoaderinfo;
// If Showsnaps are non-zero, then print the path of the module and the address of the entrance function //, for example: // c: /winnt/system32/kernel32.dll init routine 77f01000 if (_showsnaps) {_dbgprint ("% WZ init routine % X / N ", & PModuleLoaderinfo-> 24, PModuleLoaderinfo-> EntryPoint);} nModulesInitedSofar ;}}
// Set the X_Loader_SAW_Module flag of the module. Note This module has not been initialized. PModuleLoaderInfo-> Flags35 & = x_loader_saw_module;
// Handling the next module node pCurrnode = pCurrnode-> pnext}} else {pmoduleLoaderinfo = localival3c; // may not be initialized ???} if (0 == pinitnodeaRray) Return status_success;
// ************************ MSJ Layout! ***************** / / IF You're going to split this code across pages, this is a great // spot to split the code. Just Be Sure To Remove this Comment // ***************************** ******* MSJ Layout! **************** / / / PinitNodeArray pointer contains a module pointer queue, and these modules have no DLL_Process_attach // Part III, Call Initialization Some Try // Wrap All this in a try block, in case the init routine faults {nmodulesinitedsofar = 0; // start at array element 0
// // Traverse module queue // while (nmodulesinitedsofar // This does not seem to do anything ... localVar3C = pModuleLoaderInfo; nModulesInitedSoFar ; // save the initialization procedure entry point pfnInitRoutine = pModuleLoaderInfo-> EntryPoint; fBreakOnDllLoad = 0; // Default is to not break on load // debugging // If this process is a debuggee, check to see if the loader // should break into a debugger before calling the initialization. // // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent () // returns. IsDebuggerPresent is an NT Only API. // IF (pcurrentpeb-> debuggerpresent || pcurrentpeb-> 1) {long return; // // Query the "HKEY_LOCAL_MACHINE / SOFTWARE / Microsoft / // Windows NT / CurrentVersion / Image File Execution Options" // registry key. If aa subkey entry with the name of // the executable exists, check for the BreakOnDllLoad value. // Retcode = _LDRQueryImageFileExecutionOptions (PModuleLoaderInfo-> PwszdllName, "BreakondllLoad", PinitnodeaRray Reg_dword, & fbreakondlload, Sizeof (DWORD), 0); // If reg value not found (usually the case), then do not // break on this DLL init if (retCode <= STATUS_SUCCESS) fBreakOnDllLoad = 0; pInitNodeArray} if (fBreakOnDllLoad) {if (_ShowSnaps) {// Inform the debug output stream of the module name // and the init routine address before actually breaking // into the debugger_DbgPrint ( "LDR:.% wZ loaded", & pModuleLoaderInfo-> pModuleLoaderInfo); _DbgPrint ( "- About to call init routine at% LX / N ", PFNITROUTINE)} // Break Into the debugger _dbgbreakpoint (); // an int 3, followed by a ret} else if (_showsnaps && pfninitroutine) {// inform The debug output stream of the module name // and the init routine address before calling it _dbgprint ("LDR:% wz loaded.", PModuleLoaderinfo-> PModuleLoaderInfo; _Dbgprint ("- Calling Init Routine AT% LX / N", PFNITROUTINE);} IF (PFNITROUTINE) {// Setting DLL_PROCESS_ATTACH SOL / / / (SHOULDN 'THIS COME * AFTER * THE ACTUAL CALL?) //// X_LOADER_CALLED_PROCESS_ATTACH = 0x8 pModuleLoaderInfo-> Flags36 | = X_LOADER_CALLED_PROCESS_ATTACH; // // If there's Thread Local Storage (TLS) for this module, // call the TLS init functions *** NOTE *** This only // occurs during the first. Time this code is called (when //). Dynamically // loaded Dlls Shouldn't Use TLS DECLARED VARS, AS PER TLS Documentation // If the module needs to be assigned TLS, call TLS initialization functions // Note that only TLS is allocated in the first time (BIMPLICITLOAD! = 0), it is an implicit DLL load // When dynamically loaded (BIMPLICITLOAD == 0), there is no need to declare the TLS variable IF (PModuleLoaderInfo-> Bhastls && BimplicitLoad) {_ldrpcalltlsinitializers (PModuleLoaderInfo-> HMODDLL, DLL_PROCESS_ATTACH); HMODDLL = PModuleLoaderInfo-> HMODDLL MOV ESI, ESP // Save Off The ESP Register INTO ESI / / Setting Inlet Function Pointer MOV EDI, DWORD PTR [PFNITROUTINE] // In C Code, The Following ASM Would Look Like: // // InitretValue = // PFNITROUTINE (Hinstdll, DLL_PROCESS_ATTACH, BIMPLICITLOAD; // PUSH DWORD PTR [bImplicitLoad] PUSH DLL_PROCESS_ATTACH PUSH DWORD PTR [hModDLL] CALL EDI // calls associated with the MOV BYTE PTR [initRetValue], AL // entry function returns the value stored MOV DWORD PTR [_SaveSp], ESI // Save stack values after THE MOV DWORD PTR [_CURSP], ESP // Entry Point Code Returns Mov ESP, ESI // RESTORE ESP To Value Before The Call // // Check if the ESP value before and after the call is from one to // if (_cursp! = _Savsp) {harderrorparam = pmoduleLoaderinfo-> fulldllpath; hardErrorRetCode = _NtRaiseHardError (STATUS_BAD_DLL_ENTRYPOINT | 0x10000000, 1, // Number of parameters 1, // UnicodeStringParametersMask, & hardErrorParam, OptionYesNo, // Let user decide & hardErrorResponse); if (_LdrpInLdrInit) _LdrpFatalHardErrorCount ; IF ((Harden_Success) && (Responseyes == Harden)) {return status_dll_init_failed;}} // // entry function returns 0, an error // if (0 == initRetValue) {DWORD hardErrorParam2; DWORD hardErrorResponse2; hardErrorParam2 = pModuleLoaderInfo-> FullDllPath; _NtRaiseHardError (STATUS_DLL_INIT_FAILED, 1, // Number of parameters 1, // UnicodeStringParametersMask & hardErrorParam2 , Optionok, // ok is only response & harderrorresponse2; if (_ldrpinldrinit) _ldrpfactalharderrorcount ; returnction_dll_init_failed;}}} // // If you already have a EXE TLS, then calls the TLS initialization function, but also in the process of initializing the first time when the dll // if (_LdrpImageHasTls && bImplicitLoad) {_LdrpCallTlsInitializers (pCurrentPeb-> ProcessImageBase, DLL_PROCESS_ATTACH);}} __finally {/ / / / Part 4; // Clear allocated memory _rtlfreeheap (getProcessHeap (), 0, pinitnodeaRray;} Return status_success;}