mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1242 lines
39 KiB
1242 lines
39 KiB
#include "master.hxx"
|
|
#pragma hdrstop
|
|
|
|
LIST_ENTRY ExclusiveRunList;
|
|
//LIST_ENTRY DeferredExclusiveRunList;
|
|
|
|
#define TRUSTED_THRESHHOLD 0
|
|
|
|
DWORD
|
|
GuardPageHandler
|
|
(
|
|
IN DEBUG_EVENT DebugEvent,
|
|
IN PCHILD_PROCESS_INFO pProcessInfo,
|
|
IN PCHILD_THREAD_INFO pThreadInfo
|
|
);
|
|
|
|
DWORD
|
|
LoadDllHandler
|
|
(
|
|
IN DEBUG_EVENT DebugEvent,
|
|
IN PCHILD_PROCESS_INFO pProcess,
|
|
IN PCHILD_THREAD_INFO pThread
|
|
);
|
|
|
|
VOID
|
|
RunThreadExclusively
|
|
(
|
|
IN PCHILD_PROCESS_INFO pProcessInfo,
|
|
IN PCHILD_THREAD_INFO pThreadInfo
|
|
)
|
|
{
|
|
if ( pThreadInfo->cRunExclusiveLevel == 0 )
|
|
{
|
|
InsertTailList( &ExclusiveRunList, &pThreadInfo->ExclusiveRunLinkage );
|
|
|
|
if ( pThreadInfo->pParentProcess->cThreadsInExclusion == 0 )
|
|
{
|
|
SuspendAllProcessThreads( pProcessInfo );
|
|
ResumeThread( pThreadInfo->hThread );
|
|
}
|
|
|
|
pThreadInfo->pParentProcess->cThreadsInExclusion ++;
|
|
}
|
|
else
|
|
{
|
|
if ( pThreadInfo->pParentProcess->HeapState == HEAP_GUARDED )
|
|
{
|
|
DebugPrintf( "Process has %d threads in exclusion, but the heap is guarded!\n", pThreadInfo->pParentProcess->cThreadsInExclusion );
|
|
}
|
|
ASSERT( pThreadInfo->pParentProcess->HeapState != HEAP_GUARDED );
|
|
}
|
|
pThreadInfo->cRunExclusiveLevel++;
|
|
|
|
ASSERT( pThreadInfo->cRunExclusiveLevel < 10 );
|
|
}
|
|
|
|
VOID
|
|
RunThreadNormally
|
|
(
|
|
IN PCHILD_PROCESS_INFO pProcessInfo,
|
|
IN PCHILD_THREAD_INFO pThreadInfo
|
|
)
|
|
{
|
|
pThreadInfo->cRunExclusiveLevel--;
|
|
|
|
if ( pThreadInfo->cRunExclusiveLevel==0 )
|
|
{
|
|
pThreadInfo->pParentProcess->cThreadsInExclusion --;
|
|
|
|
if ( pThreadInfo->pParentProcess->cThreadsInExclusion == 0 )
|
|
{
|
|
SuspendThread( pThreadInfo->hThread );
|
|
ResumeAllProcessThreads( pProcessInfo );
|
|
}
|
|
|
|
RemoveEntryList( &pThreadInfo->ExclusiveRunLinkage );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
CreditProcessForThreadReentrance
|
|
(
|
|
IN PCHILD_THREAD_INFO pThreadInfo
|
|
)
|
|
/*++
|
|
|
|
Called when a thread that is in trusted land is being put on hold to see if
|
|
some other thread can get things going. Also useful when a thread is killed
|
|
or exits.
|
|
|
|
--*/
|
|
{
|
|
PCHILD_PROCESS_INFO pProcessInfo = pThreadInfo->pParentProcess;
|
|
|
|
if ( pThreadInfo->cTrustedReentranceCharge>0 )
|
|
{
|
|
ASSERT( pThreadInfo->cRunExclusiveLevel > 0 );
|
|
ASSERT( pProcessInfo->cThreadsInExclusion > 0 );
|
|
|
|
pProcessInfo->cThreadsInExclusion --;
|
|
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "Thread %d was charged with %d entries into trusted routines.\n",
|
|
pThreadInfo->dwThreadId,
|
|
pThreadInfo->cTrustedReentranceCharge );
|
|
}
|
|
pProcessInfo->cTrustedReentrance -= pThreadInfo->cTrustedReentranceCharge;
|
|
|
|
if ( pProcessInfo->cTrustedReentrance <= TRUSTED_THRESHHOLD )
|
|
{
|
|
ASSERT( pProcessInfo->cTrustedReentrance == TRUSTED_THRESHHOLD );
|
|
GuardRemoteHeap( pProcessInfo );
|
|
//
|
|
// In addition, the principle of fewest assumptions has resulted in
|
|
// my discarding all the fast heap walk data at this point. "Better
|
|
// slow than borken."
|
|
//
|
|
// (The assumption would be that the trusted routine didn't change
|
|
// the heap.)
|
|
//
|
|
|
|
DiscardHeapValidAreasData( pProcessInfo );
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
DebitProcessForThreadReentrance
|
|
(
|
|
IN PCHILD_THREAD_INFO pThreadInfo
|
|
)
|
|
/*++
|
|
|
|
Called when a thread that is in trusted land is "waking up"
|
|
|
|
--*/
|
|
{
|
|
PCHILD_PROCESS_INFO pProcessInfo = pThreadInfo->pParentProcess;
|
|
|
|
if ( pThreadInfo->cTrustedReentranceCharge>0 )
|
|
{
|
|
ASSERT( pThreadInfo->cRunExclusiveLevel > 0 );
|
|
pProcessInfo->cThreadsInExclusion ++;
|
|
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "Thread %d is charged with %d entries into trusted routines.\n",
|
|
pThreadInfo->dwThreadId,
|
|
pThreadInfo->cTrustedReentranceCharge );
|
|
}
|
|
pProcessInfo->cTrustedReentrance += pThreadInfo->cTrustedReentranceCharge;
|
|
|
|
if ( pProcessInfo->cTrustedReentrance == TRUSTED_THRESHHOLD+1 )
|
|
{
|
|
CHKPT();
|
|
UnGuardRemoteHeap( pProcessInfo );
|
|
DiscardHeapValidAreasData( pProcessInfo );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
DebugLoop
|
|
(
|
|
IN BOOLEAN fVerifyReadAccess
|
|
)
|
|
{
|
|
DEBUG_EVENT DebugEvent;
|
|
DWORD dwContinueStatus;
|
|
BOOL fSuccess;
|
|
LIST_ENTRY listChildProcesses;
|
|
PCHILD_PROCESS_INFO pChildProcessInfo;
|
|
PCHILD_THREAD_INFO pChildThreadInfo;
|
|
BOOL fWaitOnDebugEvent;
|
|
|
|
InitializeListHead( &listChildProcesses );
|
|
|
|
InitializeListHead( &ExclusiveRunList );
|
|
// InitializeListHead( &DeferredExclusiveRunList );
|
|
|
|
fWaitOnDebugEvent = TRUE;
|
|
|
|
while ( fWaitOnDebugEvent )
|
|
{
|
|
if ( !IsListEmpty( &ExclusiveRunList ))
|
|
{
|
|
fSuccess = WaitForDebugEvent( &DebugEvent, 2000 );
|
|
|
|
if ( !fSuccess )
|
|
{
|
|
DWORD dwRetval;
|
|
PCHILD_THREAD_INFO pExclusiveRunThread;
|
|
PLIST_ENTRY pEntry;
|
|
|
|
DebugPrintf( "Exclusive run thread seems to have stalled...hoping it doesn't need to touch the heap, which is now guarded!\n" );
|
|
|
|
pEntry = RemoveHeadList( &ExclusiveRunList );
|
|
|
|
pExclusiveRunThread = CONTAINING_RECORD( pEntry, CHILD_THREAD_INFO, ExclusiveRunLinkage );
|
|
|
|
//
|
|
// Since this thread was in the kernel, we had charged the
|
|
// process for the number of calls it was in.
|
|
//
|
|
|
|
pExclusiveRunThread->bStalledInKernel = TRUE;
|
|
|
|
CreditProcessForThreadReentrance( pExclusiveRunThread );
|
|
RunThreadNormally( pExclusiveRunThread->pParentProcess, pExclusiveRunThread );
|
|
continue;
|
|
}
|
|
}
|
|
/* else if ( !IsListEmpty( &DeferredExclusiveRunList ))
|
|
{
|
|
fSuccess = WaitForDebugEvent( &DebugEvent, 2000 );
|
|
|
|
if ( !fSuccess )
|
|
{
|
|
PCHILD_THREAD_INFO pExclusiveRunThread;
|
|
PLIST_ENTRY pEntry;
|
|
|
|
DebugPrintf( "Waking up exclusive run thread...\n" );
|
|
|
|
pEntry = RemoveHeadList( &DeferredExclusiveRunList );
|
|
|
|
pExclusiveRunThread = CONTAINING_RECORD( pEntry, CHILD_THREAD_INFO, ExclusiveRunLinkage );
|
|
|
|
ResumeThread( pExclusiveRunThread );
|
|
SuspendAllProcessThreads( pExclusiveRunThread->pParentProcess );
|
|
ResumeThread( pExclusiveRunThread );
|
|
|
|
InsertTailList( &ExclusiveRunList, pEntry );
|
|
|
|
DebitProcessForThreadReentrance( pExclusiveRunThread );
|
|
continue;
|
|
}
|
|
}
|
|
*/ else
|
|
{
|
|
fSuccess = WaitForDebugEvent( &DebugEvent, INFINITE );
|
|
}
|
|
|
|
if ( !fSuccess )
|
|
{
|
|
DebugPrintf( "WaitForDebugEvent failed: %d\n", GetLastError() );
|
|
break;
|
|
}
|
|
|
|
pChildProcessInfo = GetProcessRecord( &listChildProcesses, DebugEvent.dwProcessId );
|
|
|
|
if ( DebugEvent.dwDebugEventCode!=CREATE_PROCESS_DEBUG_EVENT &&
|
|
pChildProcessInfo == NULL )
|
|
{
|
|
DebugPrintf( "Warning: got an unknown PROCESS's debugevent %d->%d.\n",
|
|
DebugEvent.dwProcessId,
|
|
DebugEvent.dwThreadId );
|
|
continue;
|
|
}
|
|
|
|
if ( pChildProcessInfo != NULL )
|
|
{
|
|
pChildThreadInfo = GetThreadRecord( &pChildProcessInfo->listChildThreads,
|
|
DebugEvent.dwThreadId );
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( pChildProcessInfo->fVerifyReadAccess==fVerifyReadAccess );
|
|
}
|
|
}
|
|
|
|
|
|
if ( DebugEvent.dwDebugEventCode!=CREATE_PROCESS_DEBUG_EVENT &&
|
|
DebugEvent.dwDebugEventCode!=CREATE_THREAD_DEBUG_EVENT &&
|
|
pChildThreadInfo == NULL )
|
|
{
|
|
DebugPrintf( "Warning: got an unknown THREAD's debugevent %d->%d.\n",
|
|
DebugEvent.dwProcessId,
|
|
DebugEvent.dwThreadId );
|
|
continue;
|
|
}
|
|
|
|
|
|
dwContinueStatus = DBG_CONTINUE;
|
|
|
|
switch ( DebugEvent.dwDebugEventCode )
|
|
{
|
|
case CREATE_PROCESS_DEBUG_EVENT:
|
|
DebugPrintf( "Process %d starting:\n",
|
|
DebugEvent.dwProcessId );
|
|
|
|
if ( pChildProcessInfo==NULL )
|
|
{
|
|
ProcessBirth( &listChildProcesses,
|
|
DebugEvent.dwProcessId,
|
|
DebugEvent.u.CreateProcessInfo.hProcess );
|
|
|
|
pChildProcessInfo = GetProcessRecord( &listChildProcesses, DebugEvent.dwProcessId );
|
|
|
|
pChildProcessInfo->fVerifyReadAccess = fVerifyReadAccess;
|
|
|
|
pChildProcessInfo->fProcessWasOpenedAfterCreation =
|
|
( DebugEvent.u.CreateProcessInfo.lpStartAddress == NULL ) ?
|
|
TRUE : FALSE;
|
|
|
|
|
|
if ( pChildProcessInfo->fProcessWasOpenedAfterCreation )
|
|
{
|
|
pChildProcessInfo->fFirstBreakpointSeen = TRUE;
|
|
pChildProcessInfo->fLdrpHackBreakpointSeen = FALSE;
|
|
}
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
DebugPrintf( "Read checking is %s.\n", fVerifyReadAccess ? "ON" : "OFF" );
|
|
}
|
|
|
|
ThreadBirth( pChildProcessInfo,
|
|
&pChildProcessInfo->listChildThreads,
|
|
DebugEvent.dwThreadId,
|
|
DebugEvent.u.CreateProcessInfo.hThread );
|
|
|
|
DEBUG_EVENT FakeDllLoadForApplication;
|
|
|
|
FakeDllLoadForApplication.dwProcessId = DebugEvent.dwProcessId;
|
|
FakeDllLoadForApplication.u.LoadDll.hFile = DebugEvent.u.CreateProcessInfo.hFile;
|
|
FakeDllLoadForApplication.u.LoadDll.lpBaseOfDll = DebugEvent.u.CreateProcessInfo.lpBaseOfImage;
|
|
FakeDllLoadForApplication.u.LoadDll.dwDebugInfoFileOffset = DebugEvent.u.CreateProcessInfo.dwDebugInfoFileOffset;
|
|
FakeDllLoadForApplication.u.LoadDll.nDebugInfoSize = DebugEvent.u.CreateProcessInfo.nDebugInfoSize;
|
|
FakeDllLoadForApplication.u.LoadDll.lpImageName = DebugEvent.u.CreateProcessInfo.lpImageName;
|
|
FakeDllLoadForApplication.u.LoadDll.fUnicode = DebugEvent.u.CreateProcessInfo.fUnicode;
|
|
|
|
LoadDllHandler( FakeDllLoadForApplication,
|
|
pChildProcessInfo,
|
|
pChildThreadInfo );
|
|
}
|
|
break;
|
|
|
|
case CREATE_THREAD_DEBUG_EVENT:
|
|
if ( pChildThreadInfo==NULL )
|
|
{
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "NEW Thread %d\n", DebugEvent.dwThreadId );
|
|
}
|
|
ThreadBirth( pChildProcessInfo,
|
|
&pChildProcessInfo->listChildThreads,
|
|
DebugEvent.dwThreadId,
|
|
DebugEvent.u.CreateThread.hThread );
|
|
}
|
|
|
|
//
|
|
// If this thread is new, and the process is running in trusted code,
|
|
// this thread must be suspended.
|
|
//
|
|
|
|
if ( fSerializeDebugeeThreads &&
|
|
pChildProcessInfo->cTrustedReentrance > TRUSTED_THRESHHOLD )
|
|
{
|
|
SuspendThread( DebugEvent.u.CreateThread.hThread );
|
|
}
|
|
break;
|
|
case EXIT_THREAD_DEBUG_EVENT:
|
|
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "Thread %d exiting.\n", DebugEvent.dwThreadId );
|
|
}
|
|
|
|
//
|
|
// If this thread exited from within trusted code land, make
|
|
// sure we reduce the number of time we expect the process to
|
|
// exit therefrom.
|
|
//
|
|
// It's possible ( likely, infact ) that this could remove the
|
|
// process from trusted land. In this case, it is required that
|
|
// we re-guard the heap.
|
|
//
|
|
//
|
|
|
|
CreditProcessForThreadReentrance( pChildThreadInfo );
|
|
|
|
//
|
|
// If this thread was the only unsuspended thread, resume all
|
|
// the other threads. This condition implies that the heap was
|
|
// unguarded, or there'd be no good reason to have the other
|
|
// threads suspended. Therefore, reguard the heap.
|
|
//
|
|
|
|
while ( pChildThreadInfo->cRunExclusiveLevel )
|
|
{
|
|
RunThreadNormally( pChildProcessInfo, pChildThreadInfo );
|
|
}
|
|
|
|
|
|
// ThreadDeath( pChildThreadInfo );
|
|
break;
|
|
case EXIT_PROCESS_DEBUG_EVENT:
|
|
|
|
DebugPrintf( "Process %d exiting:\n",
|
|
pChildProcessInfo->dwProcessId );
|
|
|
|
if ( pChildProcessInfo->fVerifyReadAccess )
|
|
{
|
|
DebugPrintf( "Read violations: %d\n", pChildProcessInfo->cReadViolations );
|
|
}
|
|
else
|
|
{
|
|
DebugPrintf( "Reads not checked.\n" );
|
|
}
|
|
|
|
DebugPrintf( "Write violations: %d\n", pChildProcessInfo->cWriteViolations );
|
|
|
|
ProcessDeath( pChildProcessInfo );
|
|
|
|
if ( IsListEmpty( &listChildProcesses ) )
|
|
{
|
|
fWaitOnDebugEvent = FALSE;
|
|
}
|
|
break;
|
|
case LOAD_DLL_DEBUG_EVENT:
|
|
dwContinueStatus = LoadDllHandler( DebugEvent,
|
|
pChildProcessInfo,
|
|
pChildThreadInfo );
|
|
break;
|
|
case OUTPUT_DEBUG_STRING_EVENT:
|
|
PVOID Buf;
|
|
Buf = malloc( DebugEvent.u.DebugString.nDebugStringLength );
|
|
|
|
ReadProcessMemory( pChildProcessInfo->hProcess,
|
|
DebugEvent.u.DebugString.lpDebugStringData,
|
|
Buf,
|
|
DebugEvent.u.DebugString.nDebugStringLength,
|
|
NULL );
|
|
|
|
if ( DebugEvent.u.DebugString.fUnicode )
|
|
{
|
|
DebugPrintf( "DbgOutput (pid %3d) \"%S\"\n",
|
|
pChildProcessInfo->dwProcessId,
|
|
Buf );
|
|
}
|
|
else
|
|
{
|
|
DebugPrintf( "DbgOutput (pid %3d) \"%s\"\n",
|
|
pChildProcessInfo->dwProcessId,
|
|
Buf );
|
|
}
|
|
|
|
free( Buf );
|
|
|
|
dwContinueStatus = DBG_CONTINUE;
|
|
break;
|
|
case EXCEPTION_DEBUG_EVENT:
|
|
dwContinueStatus = ExceptionHandler( DebugEvent,
|
|
pChildProcessInfo,
|
|
pChildThreadInfo );
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
if ( !DebugEvent.u.Exception.dwFirstChance )
|
|
{
|
|
DebugPrintf( "Second chance for %d.\n", DebugEvent.u.Exception.ExceptionRecord.ExceptionCode );
|
|
}
|
|
|
|
if ( DebugEvent.u.Exception.ExceptionRecord.ExceptionFlags & EXCEPTION_NONCONTINUABLE )
|
|
{
|
|
DebugPrintf( "NON-CONTINUABLE.\n" );
|
|
}
|
|
else
|
|
{
|
|
if ( Verbosity > 1 || Debug > 1 )
|
|
{
|
|
switch ( dwContinueStatus )
|
|
{
|
|
case DBG_EXCEPTION_HANDLED:
|
|
DebugPrintf( "Continuing exception with dwContinueStatus = DBG_EXCEPTION_HANDLED\n" );
|
|
break;
|
|
case DBG_EXCEPTION_NOT_HANDLED:
|
|
DebugPrintf( "Continuing exception with dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED\n" );
|
|
break;
|
|
default:
|
|
DebugPrintf( "Continuing exception with dwContinueStatus = %lu (0x%08X)\n",
|
|
dwContinueStatus,
|
|
dwContinueStatus );
|
|
break;
|
|
}
|
|
}
|
|
|
|
fSuccess = ContinueDebugEvent( DebugEvent.dwProcessId,
|
|
DebugEvent.dwThreadId,
|
|
dwContinueStatus );
|
|
|
|
if ( !fSuccess )
|
|
{
|
|
DebugPrintf( "Cannot continue from debug event: %0x08X\n", GetLastError() );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL RemoteProcessAllStacksBacktrace
|
|
(
|
|
IN PCHILD_PROCESS_INFO pProcessInfo
|
|
)
|
|
{
|
|
CONTEXT Context;
|
|
PLIST_ENTRY pEntry;
|
|
PCHILD_THREAD_INFO pThreadInfo;
|
|
|
|
pEntry = pProcessInfo->listChildThreads.Flink;
|
|
|
|
while ( pEntry != &pProcessInfo->listChildThreads )
|
|
{
|
|
pThreadInfo = CONTAINING_RECORD( pEntry, CHILD_THREAD_INFO, Linkage );
|
|
|
|
DebugPrintf( "Thread %d (h%08X)\n", pThreadInfo->dwThreadId, pThreadInfo->hThread );
|
|
|
|
Context.ContextFlags = CONTEXT_FULL;
|
|
|
|
GetThreadContext( pThreadInfo->hThread, &Context );
|
|
DebugPrintf( "at %08X\n", Context.Eip );
|
|
RemoteStackBacktrace( pThreadInfo );
|
|
|
|
pEntry = pEntry -> Flink;
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
DWORD
|
|
ExceptionHandler
|
|
(
|
|
IN DEBUG_EVENT DebugEvent,
|
|
IN PCHILD_PROCESS_INFO pProcessInfo,
|
|
IN PCHILD_THREAD_INFO pThreadInfo
|
|
)
|
|
{
|
|
switch ( EXCEPTION_CODE( DebugEvent ) )
|
|
{
|
|
case DBG_CONTROL_C:
|
|
DebugPrintf( "Control-C\n" );
|
|
RemoteProcessAllStacksBacktrace( pProcessInfo );
|
|
|
|
return( DBG_CONTINUE );
|
|
|
|
case EXCEPTION_BREAKPOINT:
|
|
return( BreakpointHandler( DebugEvent, pProcessInfo, pThreadInfo ) );
|
|
case EXCEPTION_SINGLE_STEP:
|
|
pThreadInfo->bWaitingForSingleStep = FALSE;
|
|
|
|
if ( Verbosity>1 )
|
|
{
|
|
DebugPrintf("Single-step ");
|
|
}
|
|
|
|
if ( pThreadInfo->bContinuingPastBreakpoint )
|
|
{
|
|
if ( Verbosity > 1 )
|
|
{
|
|
DebugPrintf( "-- Continuing past bp to reenable it.\n");
|
|
}
|
|
|
|
pThreadInfo->bContinuingPastBreakpoint = FALSE;
|
|
EnableRemoteBreakpoint( pThreadInfo->pDisabledBreakpoint );
|
|
pThreadInfo->pDisabledBreakpoint = NULL;
|
|
}
|
|
else
|
|
{
|
|
if ( Verbosity > 1 )
|
|
{
|
|
DebugPrintf( "-- Re-guarding heap after an access.\n");
|
|
}
|
|
/*
|
|
if ( pThreadInfo->bComingOutOfSystemCall )
|
|
{
|
|
if ( Verbosity > 1 )
|
|
{
|
|
DebugPrintf( "Thread %d is leaving its system call.\n", pThreadInfo->dwThreadId );
|
|
}
|
|
DebitProcessForThreadReentrance( pThreadInfo );
|
|
}
|
|
else
|
|
*/
|
|
if ( fSerializeDebugeeThreads )
|
|
{
|
|
RunThreadNormally( pProcessInfo, pThreadInfo );
|
|
GuardRemoteHeap( pProcessInfo );
|
|
}
|
|
|
|
// pThreadInfo->bComingOutOfSystemCall = FALSE;
|
|
}
|
|
|
|
GoThread( pThreadInfo->hThread );
|
|
|
|
return( DBG_EXCEPTION_HANDLED );
|
|
|
|
case EXCEPTION_GUARD_PAGE:
|
|
return( GuardPageHandler( DebugEvent, pProcessInfo, pThreadInfo ) );
|
|
|
|
case EXCEPTION_ACCESS_VIOLATION:
|
|
DebugPrintf( "ACCESS VIOLATION: %s location %08X\n",
|
|
EXCEPTION_PARAMETER( DebugEvent, 0 ) ? "writing" : "reading",
|
|
EXCEPTION_PARAMETER( DebugEvent, 1 ) );
|
|
|
|
RemoteStackBacktrace( pThreadInfo );
|
|
|
|
DebugPrintf( "\n" );
|
|
|
|
return( (DWORD) DBG_EXCEPTION_NOT_HANDLED );
|
|
|
|
default:
|
|
DebugPrintf(" [unhandled type %lu]\n ", EXCEPTION_CODE( DebugEvent ) );
|
|
}
|
|
|
|
return( (DWORD) DBG_EXCEPTION_NOT_HANDLED );
|
|
}
|
|
|
|
DWORD
|
|
GuardPageHandler
|
|
(
|
|
IN DEBUG_EVENT DebugEvent,
|
|
IN PCHILD_PROCESS_INFO pProcessInfo,
|
|
IN PCHILD_THREAD_INFO pThreadInfo
|
|
)
|
|
/*++
|
|
|
|
Looks into guard page exceptions in the debugee. The most likely cause of such
|
|
exceptions is that a location in the heap has been accessed after we have
|
|
guarded the heap.
|
|
|
|
--*/
|
|
{
|
|
CONTEXT Context;
|
|
ULONG cbAccessLength;
|
|
PCHAR pszOpcodeName;
|
|
CHAR pszModuleFileName[MAX_PATH];
|
|
PBYTE pbAccessAddress;
|
|
BOOL bWriteAccess;
|
|
BOOL bSerializeThisThread;
|
|
|
|
bWriteAccess = ( EXCEPTION_PARAMETER( DebugEvent, 0 ) == 1 );
|
|
pbAccessAddress = (PBYTE)EXCEPTION_PARAMETER( DebugEvent, 1 );
|
|
|
|
//
|
|
// Find out how long the access is so we can determine the optimal pages to unguard
|
|
// on the other side.
|
|
//
|
|
|
|
if ( !Platform.CalculateOpcodeAccessLength( pProcessInfo->hProcess,
|
|
pThreadInfo->hThread,
|
|
&cbAccessLength,
|
|
&pszOpcodeName ) )
|
|
{
|
|
DebugPrintf( "Could not decode operation which caused access, assuming BYTE access.\n" );
|
|
cbAccessLength = 1;
|
|
}
|
|
|
|
if ( Verbosity>1 )
|
|
{
|
|
Context.ContextFlags = CONTEXT_FULL;
|
|
|
|
GetThreadContext( pThreadInfo->hThread, &Context );
|
|
|
|
DebugPrintf( "Checking out %s( %08X ) by '%s' at %08X\n",
|
|
bWriteAccess ? "write" : "read",
|
|
pbAccessAddress,
|
|
pszOpcodeName,
|
|
Context.Eip );
|
|
|
|
if ( Verbosity>2 )
|
|
{
|
|
RemoteStackBacktrace( pThreadInfo );
|
|
}
|
|
}
|
|
|
|
UnguardPartialRemoteHeap( pProcessInfo, pbAccessAddress, cbAccessLength );
|
|
|
|
//
|
|
// Instructions that cause mem-to-mem moves ( e.g. movs ) may fault twice.
|
|
// When they do, we DON'T want to suspend their brethren to wait for the
|
|
// single-step handler to reguard the heap. We have already done that the
|
|
// first time we faulted here, and each instruction only generates one
|
|
// single-step fault when we leave it, obviously.
|
|
//
|
|
|
|
bSerializeThisThread = fSerializeDebugeeThreads
|
|
&& !pThreadInfo->bWaitingForSingleStep;
|
|
|
|
SingleStepThread( pThreadInfo->hThread );
|
|
|
|
pThreadInfo->bWaitingForSingleStep = TRUE;
|
|
pThreadInfo->bContinuingPastBreakpoint = FALSE;
|
|
// pThreadInfo->bComingOutOfSystemCall = FALSE;
|
|
|
|
if ( pProcessInfo->fVerifyReadAccess || bWriteAccess )
|
|
{
|
|
if ( pProcessInfo->pdwHeapValidAreas == NULL )
|
|
{
|
|
CHKPT();
|
|
UnGuardRemoteHeap( pProcessInfo );
|
|
DetermineHeapValidAreas( pProcessInfo );
|
|
}
|
|
|
|
if ( !IsAccessInHeapValidAreas( pProcessInfo,
|
|
pThreadInfo,
|
|
pbAccessAddress,
|
|
cbAccessLength ) )
|
|
{
|
|
CHKPT();
|
|
UnGuardRemoteHeap( pProcessInfo );
|
|
if ( !VerifyRemoteHeapAccess( pProcessInfo,
|
|
pbAccessAddress,
|
|
cbAccessLength ) )
|
|
{
|
|
DebugPrintf( "\n"
|
|
"ERROR: " );
|
|
|
|
if ( EXCEPTION_PARAMETER( DebugEvent, 0 ) == 1 )
|
|
{
|
|
pProcessInfo->cWriteViolations++;
|
|
DebugPrintf( "Write access " );
|
|
}
|
|
else
|
|
{
|
|
pProcessInfo->cReadViolations++;
|
|
DebugPrintf( "Read access " );
|
|
}
|
|
|
|
DebugPrintf( "(%s)\n",pszOpcodeName );
|
|
RemoteStackBacktrace( pThreadInfo );
|
|
|
|
DebugPrintf( "\n" );
|
|
|
|
if ( fHardErrors)
|
|
{
|
|
DebugBreak();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugPrintf( "Internal: Fast walk data disagreed with actual heap walk, flushing.\n" );
|
|
DiscardHeapValidAreasData( pProcessInfo );
|
|
}
|
|
}
|
|
}
|
|
else if ( Debug>0 )
|
|
{
|
|
DebugPrintf( "Read access not checked.\n" );
|
|
|
|
ASSERT( !pProcessInfo->fVerifyReadAccess );
|
|
}
|
|
|
|
if ( bSerializeThisThread )
|
|
{
|
|
RunThreadExclusively( pProcessInfo, pThreadInfo );
|
|
}
|
|
|
|
return( DBG_EXCEPTION_HANDLED );
|
|
}
|
|
|
|
|
|
DWORD
|
|
BreakpointHandler
|
|
(
|
|
IN DEBUG_EVENT DebugEvent,
|
|
IN PCHILD_PROCESS_INFO pProcessInfo,
|
|
IN PCHILD_THREAD_INFO pThreadInfo
|
|
)
|
|
{
|
|
PBREAKPOINT_RECORD pBreakpointEntry;
|
|
BOOLEAN fTrustedTransition;
|
|
|
|
if ( !pProcessInfo->fFirstBreakpointSeen )
|
|
{
|
|
pProcessInfo->fFirstBreakpointSeen = TRUE;
|
|
|
|
//
|
|
// fLdrpHackBreakpointSeen
|
|
//
|
|
// Ldrp calls the process's first breakpoint, supposedly after
|
|
// all initialization is complete and LdrpInititializeProcess is
|
|
// ready to return. Since the Ldrp call is in a try(){}except()
|
|
// block that fails process initialization if we throw an exception,
|
|
// we have to wait for this breakpoint to guard pages and set bps.
|
|
// Unfortunately, ldrp does some more initialization AFTER this bp,
|
|
// so we cause processes to die. The hack is to set a breakpoint
|
|
// on the Ldrp return address and wait for that bp to fire before
|
|
// guarding the heap.
|
|
//
|
|
//
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
|
|
SetRemoteBreakpoint( pProcessInfo -> hProcess,
|
|
GetRemoteReturnAddress( pProcessInfo -> hProcess,
|
|
pThreadInfo -> hThread ),
|
|
&pProcessInfo -> listFunctionReturnBreakpoints );
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
|
|
pProcessInfo -> fLdrpHackBreakpointSeen = FALSE;
|
|
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "FIRST BREAKPOINT!\n");
|
|
RemoteStackBacktrace( pThreadInfo );
|
|
}
|
|
}
|
|
else if ( !pProcessInfo->fLdrpHackBreakpointSeen )
|
|
{
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
|
|
if ( !pProcessInfo->fProcessWasOpenedAfterCreation )
|
|
{
|
|
pBreakpointEntry = GetBreakpointRecord( pProcessInfo->hProcess,
|
|
EXCEPTION_ADDRESS( DebugEvent ),
|
|
&pProcessInfo -> listFunctionReturnBreakpoints );
|
|
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
|
|
RemoveRemoteBreakpoint( pBreakpointEntry );
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
|
|
ContinuePastBreakpoint( pThreadInfo->hThread );
|
|
}
|
|
|
|
CopyProcessPeb( pProcessInfo->hProcess, &pProcessInfo->Peb );
|
|
|
|
GuardRemoteHeap( pProcessInfo );
|
|
|
|
SetTrustedBreakpoints( pProcessInfo );
|
|
pProcessInfo->fLdrpHackBreakpointSeen = TRUE;
|
|
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "LDRP HACK BREAKPOINT!\n");
|
|
RemoteStackBacktrace( pThreadInfo );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// STRATEGY:
|
|
//
|
|
// if ( Breakpoint is within RTL )
|
|
// {
|
|
// SuspendAllThreads( pProcessInfo->hProcess );
|
|
// ResumeCurrentThread();
|
|
// UnguardRemoteHeap();
|
|
// SetReturnBreakpoint();
|
|
// ContinueRemoteExecution();
|
|
// }
|
|
// else if ( Breakpoint is for an RTL return )
|
|
// {
|
|
// GuardRemoteHeap();
|
|
// ResumeAllThreads( pProcessInfo->hProcess );
|
|
// ContinueRemoteExecution();
|
|
// }
|
|
//
|
|
|
|
pBreakpointEntry = GetBreakpointRecord( pProcessInfo->hProcess,
|
|
EXCEPTION_ADDRESS( DebugEvent ),
|
|
&pProcessInfo->listTrustedBreakpoints );
|
|
|
|
if ( pBreakpointEntry != NULL )
|
|
{
|
|
|
|
PTRUSTED_ENTRY_POINT pEntryPoint;
|
|
|
|
pEntryPoint = (PTRUSTED_ENTRY_POINT)pBreakpointEntry->pAssociatedBreakpoint;
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( pEntryPoint != NULL );
|
|
}
|
|
|
|
fTrustedTransition = !pEntryPoint || pEntryPoint->fCanChangeHeap || fTrustAllNtdll;
|
|
|
|
if ( fTrustedTransition )
|
|
{
|
|
pProcessInfo->cTrustedReentrance++;
|
|
pThreadInfo ->cTrustedReentranceCharge++;
|
|
}
|
|
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "[%d->%d] Thread %d (%d->%d) Entering ntdll!_",
|
|
pProcessInfo->cTrustedReentrance - (fTrustedTransition ? 1 : 0),
|
|
pProcessInfo->cTrustedReentrance,
|
|
pThreadInfo->dwThreadId,
|
|
pThreadInfo ->cTrustedReentranceCharge - (fTrustedTransition ? 1 : 0),
|
|
pThreadInfo ->cTrustedReentranceCharge );
|
|
|
|
if ( pEntryPoint == NULL )
|
|
{
|
|
DebugPrintf( "<<unknown>>\n" );
|
|
}
|
|
else
|
|
{
|
|
DebugPrintf( "%s\n", pEntryPoint->pszEntryName );
|
|
}
|
|
|
|
if ( Verbosity>1 )
|
|
{
|
|
RemoteStackBacktrace( pThreadInfo );
|
|
}
|
|
}
|
|
|
|
if ( fTrustedTransition )
|
|
{
|
|
if ( pProcessInfo->cTrustedReentrance > TRUSTED_THRESHHOLD
|
|
&& fSerializeDebugeeThreads )
|
|
{
|
|
RunThreadExclusively( pProcessInfo, pThreadInfo );
|
|
}
|
|
|
|
if ( pProcessInfo->cTrustedReentrance==TRUSTED_THRESHHOLD+1 )
|
|
{
|
|
CHKPT();
|
|
UnGuardRemoteHeap( pProcessInfo );
|
|
DiscardHeapValidAreasData( pProcessInfo );
|
|
}
|
|
}
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
|
|
SetRemoteBreakpointOnFunctionReturn( pProcessInfo->hProcess,
|
|
pThreadInfo->hThread,
|
|
&pProcessInfo->listFunctionReturnBreakpoints,
|
|
pBreakpointEntry );
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
|
|
DisableRemoteBreakpoint( pBreakpointEntry );
|
|
|
|
pThreadInfo->bContinuingPastBreakpoint = TRUE;
|
|
pThreadInfo->pDisabledBreakpoint = pBreakpointEntry;
|
|
|
|
if ( !SingleStepThread( pThreadInfo->hThread ) )
|
|
{
|
|
ASSERT( !"SingleStepThread() failed!\n" );
|
|
}
|
|
|
|
ContinuePastBreakpoint( pThreadInfo->hThread );
|
|
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "Done entering\n" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
|
|
pBreakpointEntry = GetBreakpointRecord( pProcessInfo->hProcess,
|
|
EXCEPTION_ADDRESS( DebugEvent ),
|
|
&pProcessInfo->listFunctionReturnBreakpoints );
|
|
|
|
if ( pBreakpointEntry != NULL )
|
|
{
|
|
PTRUSTED_ENTRY_POINT pEntryPoint;
|
|
|
|
pEntryPoint = (PTRUSTED_ENTRY_POINT)pBreakpointEntry->pAssociatedBreakpoint->pAssociatedBreakpoint;
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( pEntryPoint != NULL );
|
|
}
|
|
|
|
fTrustedTransition = !pEntryPoint || pEntryPoint->fCanChangeHeap || fTrustAllNtdll;
|
|
|
|
if ( pThreadInfo->bStalledInKernel )
|
|
{
|
|
DebugPrintf( "Thread %d is no longer stuck!\n", pThreadInfo->dwThreadId );
|
|
RunThreadExclusively( pProcessInfo, pThreadInfo );
|
|
DebitProcessForThreadReentrance( pThreadInfo );
|
|
}
|
|
|
|
pThreadInfo->bStalledInKernel = FALSE;
|
|
|
|
if ( fTrustedTransition )
|
|
{
|
|
pProcessInfo->cTrustedReentrance--;
|
|
pThreadInfo->cTrustedReentranceCharge--;
|
|
}
|
|
|
|
|
|
if ( Verbosity>0 )
|
|
{
|
|
DebugPrintf( "[%d->%d] Thread %d (%d->%d) Leaving from ntddl!_",
|
|
pProcessInfo->cTrustedReentrance + ((fTrustedTransition && !pThreadInfo->bStalledInKernel )? 1 : 0),
|
|
pProcessInfo->cTrustedReentrance,
|
|
pThreadInfo->dwThreadId,
|
|
pThreadInfo ->cTrustedReentranceCharge + (fTrustedTransition ? 1 : 0),
|
|
pThreadInfo ->cTrustedReentranceCharge );
|
|
|
|
if ( pEntryPoint == NULL )
|
|
{
|
|
DebugPrintf( "<<unknown>>...\n" );
|
|
}
|
|
else
|
|
{
|
|
DebugPrintf( "%s...\n", pEntryPoint->pszEntryName );
|
|
}
|
|
|
|
DebugPrintf( "DWORD return value = %08X\n",
|
|
GetContextReturnValue( pThreadInfo->hThread ) );
|
|
}
|
|
|
|
if ( fTrustedTransition )
|
|
{
|
|
if ( pProcessInfo->cTrustedReentrance == TRUSTED_THRESHHOLD )
|
|
{
|
|
GuardRemoteHeap( pProcessInfo );
|
|
}
|
|
|
|
if ( fSerializeDebugeeThreads )
|
|
{
|
|
RunThreadNormally( pProcessInfo, pThreadInfo );
|
|
}
|
|
}
|
|
|
|
if ( !RemoveRemoteBreakpoint( pBreakpointEntry ))
|
|
{
|
|
pThreadInfo->bContinuingPastBreakpoint = TRUE;
|
|
pThreadInfo->pDisabledBreakpoint = pBreakpointEntry;
|
|
|
|
SingleStepThread( pThreadInfo );
|
|
}
|
|
|
|
ContinuePastBreakpoint( pThreadInfo->hThread );
|
|
|
|
if ( Debug>0 )
|
|
{
|
|
ASSERT( SanityCheckListEntry( &pProcessInfo->listFunctionReturnBreakpoints ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugPrintf( "Encountered unexpected breakpoint at %08X.\n",
|
|
EXCEPTION_ADDRESS( DebugEvent ) );
|
|
|
|
if ( pProcessInfo->cWriteViolations )
|
|
{
|
|
DebugPrintf( "Since the heap has been touched with stray writes, it's probably an assert() firing in the RTL, continuing.\n" );
|
|
}
|
|
else
|
|
{
|
|
DebugBreak();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return( DBG_EXCEPTION_HANDLED );
|
|
}
|
|
|
|
DWORD
|
|
LoadDllHandler
|
|
(
|
|
IN DEBUG_EVENT DebugEvent,
|
|
IN PCHILD_PROCESS_INFO pProcess,
|
|
IN PCHILD_THREAD_INFO pThread
|
|
)
|
|
{
|
|
PIMAGE_INFO pImageNew, *pp;
|
|
UCHAR index;
|
|
WCHAR ImageName[ MAX_PATH ];
|
|
DWORD cbRead;
|
|
BOOL fOk;
|
|
|
|
if ( DebugEvent.u.LoadDll.lpImageName )
|
|
{
|
|
if (!ReadProcessMemory(pProcess->hProcess,
|
|
DebugEvent.u.LoadDll.lpImageName,
|
|
&DebugEvent.u.LoadDll.lpImageName,
|
|
sizeof(DebugEvent.u.LoadDll.lpImageName),
|
|
&cbRead
|
|
))
|
|
{
|
|
DebugEvent.u.LoadDll.lpImageName = NULL;
|
|
}
|
|
}
|
|
|
|
if (DebugEvent.u.LoadDll.lpImageName)
|
|
{
|
|
|
|
fOk = ReadProcessMemory(pProcess->hProcess,
|
|
DebugEvent.u.LoadDll.lpImageName,
|
|
ImageName,
|
|
sizeof(ImageName),
|
|
&cbRead
|
|
);
|
|
if ( fOk )
|
|
{
|
|
DebugPrintf("%s: %x -> '%ws' loaded at %x\n",
|
|
DebuggerName,
|
|
DebugEvent.u.LoadDll.lpImageName,
|
|
ImageName,
|
|
DebugEvent.u.LoadDll.lpBaseOfDll
|
|
);
|
|
}
|
|
|
|
if ( !fOk )
|
|
{
|
|
DebugEvent.u.LoadDll.lpImageName = NULL;
|
|
}
|
|
}
|
|
|
|
if (DebugEvent.u.LoadDll.lpImageName == NULL)
|
|
{
|
|
swprintf(ImageName,L"Image@%08x",DebugEvent.u.LoadDll.lpBaseOfDll);
|
|
}
|
|
|
|
// search for existing image at same base address
|
|
// if found, remove symbols, but leave image structure intact
|
|
|
|
pp = &pProcess->pImageHead;
|
|
|
|
while (pImageNew = *pp)
|
|
{
|
|
if (pImageNew->lpBaseOfImage == DebugEvent.u.LoadDll.lpBaseOfDll)
|
|
{
|
|
if (pImageNew->fSymbolsLoaded)
|
|
{
|
|
if ( Verbosity>0 )
|
|
DebugPrintf("%s: force unload of %s\n", DebuggerName, pImageNew->szImagePath);
|
|
// Symbols
|
|
// UnloadSymbols(pImageNew);
|
|
}
|
|
|
|
break;
|
|
}
|
|
else if (pImageNew->lpBaseOfImage > DebugEvent.u.LoadDll.lpBaseOfDll)
|
|
{
|
|
pImageNew = NULL;
|
|
break;
|
|
}
|
|
|
|
pp = &pImageNew->pImageNext;
|
|
}
|
|
|
|
// if not found, allocate and fill new image structure
|
|
|
|
if (!pImageNew)
|
|
{
|
|
for (index=0; index<pProcess->MaxIndex; index++)
|
|
{
|
|
if (pProcess->pImageByIndex[ index ] == NULL)
|
|
{
|
|
pImageNew = (PIMAGE_INFO)calloc( 1, sizeof(IMAGE_INFO) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!pImageNew)
|
|
{
|
|
DWORD NewMaxIndex;
|
|
PIMAGE_INFO *NewImageByIndex;
|
|
|
|
NewMaxIndex = pProcess->MaxIndex + 32;
|
|
if (NewMaxIndex < 0x100)
|
|
{
|
|
NewImageByIndex = (PIMAGE_INFO *)calloc( NewMaxIndex, sizeof( *NewImageByIndex ) );
|
|
}
|
|
else
|
|
{
|
|
NewImageByIndex = NULL;
|
|
}
|
|
|
|
if (NewImageByIndex == NULL)
|
|
{
|
|
DebugPrintf("%s: No room for %ws image record.\n", DebuggerName, ImageName );
|
|
return( DBG_CONTINUE );
|
|
}
|
|
|
|
if (pProcess->pImageByIndex)
|
|
{
|
|
memcpy( NewImageByIndex,
|
|
pProcess->pImageByIndex,
|
|
pProcess->MaxIndex * sizeof( *NewImageByIndex )
|
|
);
|
|
free( pProcess->pImageByIndex );
|
|
}
|
|
|
|
pProcess->pImageByIndex = NewImageByIndex;
|
|
index = (UCHAR)pProcess->MaxIndex;
|
|
pProcess->MaxIndex = NewMaxIndex;
|
|
pImageNew = (PIMAGE_INFO)calloc( 1, sizeof(IMAGE_INFO));
|
|
}
|
|
|
|
pImageNew->pImageNext = *pp;
|
|
*pp = pImageNew;
|
|
pImageNew->index = index;
|
|
pProcess->pImageByIndex[ index ] = pImageNew;
|
|
}
|
|
|
|
// pImageNew has either the unloaded structure or the newly created one
|
|
|
|
pImageNew->hFile = DebugEvent.u.LoadDll.hFile;
|
|
pImageNew->lpBaseOfImage = DebugEvent.u.LoadDll.lpBaseOfDll;
|
|
|
|
DeferSymbolLoad( pProcess, pImageNew );
|
|
if (!fLazyLoad)
|
|
{
|
|
LoadSymbols( pProcess, pImageNew );
|
|
}
|
|
|
|
return( DBG_CONTINUE );
|
|
}
|