//============================================================================= // Matt Pietrek, September 1999 Microsoft Systems Journal // Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (Windows NT 4.0, SP3) // bImplicitLoad parameter is nonzero when LdrpRunInitializeRoutines is // called for the first time in a process (that is, when the implicitly // linked modules are initialized. // On subsequent invocations (caused by calls to LoadLibrary), bImplicitLoad // is 0. //============================================================================= #include <ntexapi.h> // For HardError defines near the end // Global symbols (name is accurate, and comes from NTDLL.DBG) // _NtdllBaseTag // _ShowSnaps // _SaveSp // _CurSp // _LdrpInLdrInit // _LdrpFatalHardErrorCount // _LdrpImageHasTls NTSTATUS LdrpRunInitializeRoutines( DWORD bImplicitLoad ) { // Get the number of modules that *might* need to be initialized. Some // of the modules may already have been initialized. unsigned nRoutinesToRun = _LdrpClearLoadInProgress(); if ( nRoutinesToRun ) { // If there are any init routines, allocate memory for an array // containing information about each module pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(), _NtdllBaseTag + 0x60000, nRoutinesToRun * 4 ); if ( 0 == pInitNodeArray ) // Make sure allocation worked return STATUS_NO_MEMORY; } else pInitNodeArray = 0; // The Process Environment Block (Peb), keeps a pointer to the linked // list of modules that have just been loaded. Get a pointer to this info // pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead); ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead; if ( _ShowSnaps ) { _DbgPrint( "LDR: Real INIT LIST\n" ); } nModulesInitedSoFar = 0; if ( pCurrNode != ModuleLoaderInfoHead ) { // Iterate through the linked list // while ( pCurrNode != ModuleLoaderInfoHead ) { ModuleLoaderInfo pModuleLoaderInfo; // Apparently the next node pointer is 0x10 bytes inside // the ModuleLoaderInfo structure // pModuleLoaderInfo = &NextNode - 0x10; // This doesn't seem to do anything... localVar3C = pModuleLoaderInfo; // Determine if the module has already been initialized. If so, // skip over it. // // X_LOADER_SAW_MODULE = 0x40 if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) ) { // This module hasn't previously been initialized. Check // to see if it has an EntryPoint to call. // if ( pModuleLoaderInfo->EntryPoint ) { // This previously uninitialized module has an entry // point. Add it to the array of modules that will // be called to initialize later in this routine. // pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo; // If ShowSnaps is nonzero, spit out the module path // and the entry point address for the module. 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 for this module. Note that // the module hasn't actually been initialized. Rather, it will // be by the time this routine returns. pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE; // Advance to the next node in the module list pCurrNode = pCurrNode->pNext } } else { pModuleLoaderInfo = localVar3C; // May not be initialized??? } if ( 0 == pInitNodeArray ) return STATUS_SUCCESS; // At this point, pInitNodeArray contains an array of pointers to // modules that haven't yet seen the DLL_PROCESS_ATTACH notification. // It's now time to start calling the initialization routines. // try // Wrap all this in a try block, in case the init routine faults { nModulesInitedSoFar = 0; // Start at array element 0 // Begin iterating through the module array // while ( nModulesInitedSoFar < nRoutinesToRun ) { // Get a pointer to the module's info out of the array pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ]; // This doesn't seem to do anything... localVar3C = pModuleLoaderInfo; nModulesInitedSoFar++; // Store init routine address in a local variable pfnInitRoutine = pModuleLoaderInfo->EntryPoint; fBreakOnDllLoad = 0; // Default is to not break on load // 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 a Windows NT-only API. if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 ) { LONG retCode; // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ // Windows NT\CurrentVersion\Image File Execution Options" // registry key. If a a subkey entry with the name of // the executable exists, check for the BreakOnDllLoad value. retCode = _LdrQueryImageFileExecutionOptions( pModuleLoaderInfo->pwszDllName, "BreakOnDllLoad", REG_DWORD, &fBreakOnDllLoad, sizeof(DWORD), 0 ); // If reg value not found (usually the case), then don't // break on this DLL init if ( retCode <= STATUS_SUCCESS ) fBreakOnDllLoad = 0; } 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", pfnInitRoutine ) } // 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", pfnInitRoutine); } if ( pfnInitRoutine ) { // Set flag indicating that the DLL_PROCESS_ATTACH notification // has been sent to this DLL // (Shouldn't 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 // implicitly loaded DLLs are initialized). Dynamically // loaded DLLs shouldn't use TLS declared vars, as per the // SDK documentation 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] // Load EDI with module's entry point // 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 // Call the init routine. Excellent point // to set a breakpoint. Stepping into this // call will take you to the DLL's entry point MOV BYTE PTR [initRetValue],AL // Save the return value // from the entry point 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 ESP (stack pointer) after the call. If it's not // the same as the ESP value before the call, the DLL's // init routine didn't clean up the stack properly. For // example, it's entry routine may have been defined // improperly. Although this rarely happens, if it does, // let the user know and ask if they want to continue. 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 ( (hardErrorRetCode >= STATUS_SUCCESS) && (ResponseYes == hardErrorResponse) ) { return STATUS_DLL_INIT_FAILED; } } // If the DLL's entry point returned 0 (failure), tell the user 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 ) _LdrpFatalHardErrorCount++; return STATUS_DLL_INIT_FAILED; } } } // If the EXE itself has TLS declared vars, call the init routines. // See the comment for the previous call to _LdrpCallTlsInitializers // for more details. // if ( _LdrpImageHasTls && bImplicitLoad ) { _LdrpCallTlsInitializers( pCurrentPeb->ProcessImageBase, DLL_PROCESS_ATTACH ); } } __finally { // Before exiting the routine, make sure that the memory allocated // at the beginning is freed _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray ); } return STATUS_SUCCESS; }
Figure
6 ShowSnaps Output from CALC.EXE
## Matt's comments denoted by ##'s 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 //.... additional output omitted 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 //.... additional output omitted ## Note loader looking for and verifying "bound" DLL imports in COMCTL32 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 //.... additional output omitted 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 ## Beginning of actual calls to implicitly linked DLL init routines 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 does AppInit_DLLs thing, so static inits temporarily interrupted ## In this case, "globaldll.dll" is LoadLibrary'ed from USER32 init code 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 //.... additional output omitted LDR: Real INIT LIST c:\temp\globaldll.dll init routine 10001310 LDR: globaldll.dll loaded. - Calling init routine at 10001310 ## Now back to calling the inits of the statically linked DLLs 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 //.... additional output omitted
Figure
7 TLSInit.cpp
void _LdrpCallTlsInitializers( HMODULE hModule, DWORD fdwReason ) { PIMAGE_TLS_DIRECTORY pTlsDir; DWORD size // Look up the TLS directory in the IMAGE_OPTIONAL_HEADER.DataDirectory pTlsDir = _RtlImageDirectoryEntryToData(hModule, 1, IMAGE_DIRECTORY_ENTRY_TLS, &size ); __try // Protect all this code with a try/catch block { if ( pTlsDir->AddressOfCallbacks ) { if ( _ShowSnaps ) // diagnostic output { _DbgPrint( "LDR: Tls Callbacks Found. " "Imagebase %lx Tls %lx CallBacks %lx\n", hModule, TlsDir, pTlsDir->AddressOfCallbacks ); } // Get pointer to beginning of array of TLS callback addresses PVOID * pCallbacks = pTlsDir->AddressOfCallbacks; while ( *pCallbacks ) // Iterate through each array entry { PIMAGE_TLS_CALLBACK pTlsCallback = *pCallbacks; pCallbacks++; if ( _ShowSnaps ) // More diagnostic output { _DbgPrint( "LDR: Calling Tls Callback " "Imagebase %lx Function %lx\n", hModule, pTlsCallback ); } // Make the actual call pTlsCallback( hModule, fdwReason, 0 ); } } } __except( EXCEPTION_EXECUTE_HANDLER ) { } }