//=============================================================================
// 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 )
{
}
}