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.
5246 lines
159 KiB
5246 lines
159 KiB
//----------------------------------------------------------------------------
|
|
//
|
|
// Event waiting and processing.
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1999-2002.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "ntsdp.hpp"
|
|
|
|
// Special exception code used by the system when generating dumps
|
|
// from apps which seem to be hung.
|
|
#define STATUS_APPLICATION_HANG 0xcfffffff
|
|
|
|
// An event can be signalled on certain events for
|
|
// synchronizing other programs with the debugger.
|
|
HANDLE g_EventToSignal;
|
|
|
|
// When both creating a debuggee process and attaching
|
|
// the debuggee is left suspended until the attach
|
|
// succeeds. At that point the created process's thread
|
|
// is resumed.
|
|
ULONG64 g_ThreadToResume;
|
|
PUSER_DEBUG_SERVICES g_ThreadToResumeServices;
|
|
|
|
ULONG g_ExecutionStatusRequest = DEBUG_STATUS_NO_CHANGE;
|
|
// Currently in seconds.
|
|
ULONG g_PendingBreakInTimeoutLimit = 30;
|
|
|
|
char g_OutputCommandRedirectPrefix[MAX_PATH];
|
|
ULONG g_OutputCommandRedirectPrefixLen;
|
|
|
|
// Set when events occur. Can't always be retrieved from
|
|
// g_Event{Process|Thread}->SystemId since the events may be creation events
|
|
// where the info structures haven't been created yet.
|
|
ULONG g_EventThreadSysId;
|
|
ULONG g_EventProcessSysId;
|
|
|
|
ULONG g_LastEventType;
|
|
char g_LastEventDesc[MAX_IMAGE_PATH + 64];
|
|
PVOID g_LastEventExtraData;
|
|
ULONG g_LastEventExtraDataSize;
|
|
LAST_EVENT_INFO g_LastEventInfo;
|
|
|
|
// Set when lookups are done during event handling.
|
|
TargetInfo* g_EventTarget;
|
|
ProcessInfo* g_EventProcess;
|
|
ThreadInfo* g_EventThread;
|
|
MachineInfo* g_EventMachine;
|
|
// This is zero for events without a PC.
|
|
ULONG64 g_TargetEventPc;
|
|
|
|
// PC for current suspended event.
|
|
ADDR g_EventPc;
|
|
// Stored PC from the last resumed event.
|
|
ADDR g_PrevEventPc;
|
|
// An interesting related PC for the current event, such
|
|
// as the source of a branch when branch tracing.
|
|
ADDR g_PrevRelatedPc;
|
|
|
|
PDEBUG_EXCEPTION_FILTER_PARAMETERS g_EventExceptionFilter;
|
|
ULONG g_ExceptionFirstChance;
|
|
|
|
ULONG g_SystemErrorOutput = SLE_ERROR;
|
|
ULONG g_SystemErrorBreak = SLE_ERROR;
|
|
|
|
ULONG g_SuspendedExecutionStatus;
|
|
CHAR g_SuspendedCmdState;
|
|
PDBGKD_ANY_CONTROL_REPORT g_ControlReport;
|
|
PCHAR g_StateChangeData;
|
|
CHAR g_StateChangeBuffer[2 * PACKET_MAX_SIZE];
|
|
DBGKD_ANY_WAIT_STATE_CHANGE g_StateChange;
|
|
DBGKD_ANY_CONTROL_SET g_ControlSet;
|
|
|
|
char g_CreateProcessBreakName[FILTER_MAX_ARGUMENT];
|
|
char g_ExitProcessBreakName[FILTER_MAX_ARGUMENT];
|
|
char g_LoadDllBreakName[FILTER_MAX_ARGUMENT];
|
|
char g_UnloadDllBaseName[FILTER_MAX_ARGUMENT];
|
|
ULONG64 g_UnloadDllBase;
|
|
char g_OutEventFilterPattern[FILTER_MAX_ARGUMENT];
|
|
|
|
DEBUG_EXCEPTION_FILTER_PARAMETERS
|
|
g_OtherExceptionList[OTHER_EXCEPTION_LIST_MAX];
|
|
EVENT_COMMAND g_OtherExceptionCommands[OTHER_EXCEPTION_LIST_MAX];
|
|
ULONG g_NumOtherExceptions;
|
|
|
|
char g_EventLog[1024];
|
|
PSTR g_EventLogEnd = g_EventLog;
|
|
|
|
EVENT_FILTER g_EventFilters[] =
|
|
{
|
|
//
|
|
// Debug events.
|
|
//
|
|
|
|
"Create thread", "ct", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Exit thread", "et", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Create process", "cpr", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, g_CreateProcessBreakName, 0,
|
|
"Exit process", "epr", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, g_ExitProcessBreakName, 0,
|
|
"Load module", "ld", NULL, NULL, 0, DEBUG_FILTER_OUTPUT,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, g_LoadDllBreakName, 0,
|
|
"Unload module", "ud", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, g_UnloadDllBaseName, 0,
|
|
"System error", "ser", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Initial breakpoint", "ibp", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Initial module load", "iml", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Debuggee output", "out", NULL, NULL, 0, DEBUG_FILTER_OUTPUT,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, g_OutEventFilterPattern, 0,
|
|
|
|
// Default exception filter.
|
|
"Unknown exception", NULL, NULL, NULL, 0, DEBUG_FILTER_SECOND_CHANCE_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
|
|
//
|
|
// Specific exceptions.
|
|
//
|
|
|
|
"Access violation", "av", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_ACCESS_VIOLATION,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Application hang", "aph", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_APPLICATION_HANG,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Break instruction exception", "bpe", "bpec", NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, STATUS_BREAKPOINT,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"C++ EH exception", "eh", NULL, NULL, 0, DEBUG_FILTER_SECOND_CHANCE_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_CPP_EH_EXCEPTION,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Control-Break exception", "cce", "cc", NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, DBG_CONTROL_BREAK,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Control-C exception", "cce", "cc", NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, DBG_CONTROL_C,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Data misaligned", "dm", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_DATATYPE_MISALIGNMENT,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Debugger command exception", "dbce", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, DBG_COMMAND_EXCEPTION,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Guard page violation", "gp", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_GUARD_PAGE_VIOLATION,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Illegal instruction", "ii", NULL, NULL, 0, DEBUG_FILTER_SECOND_CHANCE_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_ILLEGAL_INSTRUCTION,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"In-page I/O error", "ip", NULL, " %I64x", 2, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_IN_PAGE_ERROR,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Integer divide-by-zero", "dz", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_INTEGER_DIVIDE_BY_ZERO,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Integer overflow", "iov", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_INTEGER_OVERFLOW,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Invalid handle", "ch", "hc", NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_INVALID_HANDLE,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Invalid lock sequence", "lsq", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_INVALID_LOCK_SEQUENCE,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Invalid system call", "isc", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_INVALID_SYSTEM_SERVICE,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Port disconnected", "3c", NULL, NULL, 0, DEBUG_FILTER_SECOND_CHANCE_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_PORT_DISCONNECTED,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Single step exception", "sse", "ssec", NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, STATUS_SINGLE_STEP,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Stack buffer overflow", "sbo", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_STACK_BUFFER_OVERRUN,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Stack overflow", "sov", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_STACK_OVERFLOW,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Visual C++ exception", "vcpp", NULL, NULL, 0, DEBUG_FILTER_IGNORE,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, STATUS_VCPP_EXCEPTION,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"Wake debugger", "wkd", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_NOT_HANDLED, 0, 0, 0, STATUS_WAKE_SYSTEM_DEBUGGER,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"WOW64 breakpoint", "wob", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, STATUS_WX86_BREAKPOINT,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
"WOW64 single step exception", "wos", NULL, NULL, 0, DEBUG_FILTER_BREAK,
|
|
DEBUG_FILTER_GO_HANDLED, 0, 0, 0, STATUS_WX86_SINGLE_STEP,
|
|
NULL, NULL, NULL, 0, 0, NULL, 0,
|
|
};
|
|
|
|
void
|
|
ClearEventLog(void)
|
|
{
|
|
g_EventLogEnd = g_EventLog;
|
|
*g_EventLogEnd = 0;
|
|
}
|
|
|
|
void
|
|
DotEventLog(PDOT_COMMAND Cmd, DebugClient* Client)
|
|
{
|
|
if (g_EventLogEnd > g_EventLog)
|
|
{
|
|
dprintf("%s", g_EventLog);
|
|
}
|
|
else
|
|
{
|
|
dprintf("Event log is empty\n");
|
|
}
|
|
|
|
dprintf("Last event: %s\n", g_LastEventDesc);
|
|
}
|
|
|
|
void
|
|
LogEventDesc(PSTR Desc, ULONG ProcId, ULONG ThreadId)
|
|
{
|
|
// Extra space for newline and terminator.
|
|
int Len = strlen(Desc) + 2;
|
|
if (IS_USER_TARGET(g_EventTarget))
|
|
{
|
|
// Space for process and thread IDs.
|
|
Len += 16;
|
|
}
|
|
if (Len > sizeof(g_EventLog))
|
|
{
|
|
Len = sizeof(g_EventLog);
|
|
}
|
|
|
|
int Avail = (int)(sizeof(g_EventLog) - (g_EventLogEnd - g_EventLog));
|
|
if (g_EventLogEnd > g_EventLog && Len > Avail)
|
|
{
|
|
PSTR Save = g_EventLog;
|
|
int Need = Len - Avail;
|
|
|
|
while (Need > 0)
|
|
{
|
|
PSTR Scan = strchr(Save, '\n');
|
|
if (Scan == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Scan++;
|
|
Need -= (int)(Scan - Save);
|
|
Save = Scan;
|
|
}
|
|
|
|
if (Need > 0)
|
|
{
|
|
// Couldn't make enough space so throw
|
|
// everything away.
|
|
g_EventLogEnd = g_EventLog;
|
|
*g_EventLogEnd = 0;
|
|
}
|
|
else
|
|
{
|
|
Need = strlen(Save);
|
|
memmove(g_EventLog, Save, Need + 1);
|
|
g_EventLogEnd = g_EventLog + Need;
|
|
}
|
|
}
|
|
|
|
Avail = (int)(sizeof(g_EventLog) - (g_EventLogEnd - g_EventLog));
|
|
|
|
if (IS_USER_TARGET(g_EventTarget))
|
|
{
|
|
sprintf(g_EventLogEnd, "%04x.%04x: ", ProcId, ThreadId);
|
|
Avail -= strlen(g_EventLogEnd);
|
|
g_EventLogEnd += strlen(g_EventLogEnd);
|
|
}
|
|
|
|
CopyString(g_EventLogEnd, Desc, Avail - 2);
|
|
g_EventLogEnd += strlen(g_EventLogEnd);
|
|
*g_EventLogEnd++ = '\n';
|
|
*g_EventLogEnd = 0;
|
|
}
|
|
|
|
void
|
|
DiscardLastEventInfo(void)
|
|
{
|
|
if (g_EventThread)
|
|
{
|
|
g_EventThread->ClearEventStrings();
|
|
}
|
|
|
|
if (g_LastEventDesc[0])
|
|
{
|
|
LogEventDesc(g_LastEventDesc, g_EventProcessSysId, g_EventThreadSysId);
|
|
}
|
|
|
|
g_LastEventType = 0;
|
|
g_LastEventDesc[0] = 0;
|
|
g_LastEventExtraData = NULL;
|
|
g_LastEventExtraDataSize = 0;
|
|
}
|
|
|
|
void
|
|
DiscardLastEvent(void)
|
|
{
|
|
// Do this before clearing the other information so
|
|
// it's available for the log.
|
|
DiscardLastEventInfo();
|
|
g_EventProcessSysId = 0;
|
|
g_EventThreadSysId = 0;
|
|
g_TargetEventPc = 0;
|
|
|
|
// Clear any cached memory read during the last event.
|
|
InvalidateAllMemoryCaches();
|
|
}
|
|
|
|
BOOL
|
|
AnyEventsPossible(void)
|
|
{
|
|
TargetInfo* Target;
|
|
|
|
ForAllLayersToTarget()
|
|
{
|
|
ULONG DesiredTimeout;
|
|
|
|
if (Target->
|
|
WaitInitialize(DEBUG_WAIT_DEFAULT, INFINITE,
|
|
WINIT_TEST, &DesiredTimeout) == S_OK &&
|
|
Target->m_EventPossible)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Target != NULL;
|
|
}
|
|
|
|
void
|
|
NotifyDebuggeeActivation(void)
|
|
{
|
|
StackSaveLayers Save;
|
|
|
|
//
|
|
// Now that all initialization is done, send initial
|
|
// notification that a debuggee exists. Make sure
|
|
// that the basic target, system and machine globals
|
|
// are set up so that queries can be made during
|
|
// the callbacks.
|
|
//
|
|
|
|
SetLayersFromTarget(g_EventTarget);
|
|
|
|
g_EventTarget->AddSpecificExtensions();
|
|
|
|
NotifySessionStatus(DEBUG_SESSION_ACTIVE);
|
|
NotifyChangeDebuggeeState(DEBUG_CDS_ALL, 0);
|
|
NotifyExtensions(DEBUG_NOTIFY_SESSION_ACTIVE, 0);
|
|
}
|
|
|
|
ULONG
|
|
EventStatusToContinue(ULONG EventStatus)
|
|
{
|
|
switch(EventStatus)
|
|
{
|
|
case DEBUG_STATUS_GO_NOT_HANDLED:
|
|
return DBG_EXCEPTION_NOT_HANDLED;
|
|
case DEBUG_STATUS_GO_HANDLED:
|
|
return DBG_EXCEPTION_HANDLED;
|
|
case DEBUG_STATUS_NO_CHANGE:
|
|
case DEBUG_STATUS_IGNORE_EVENT:
|
|
case DEBUG_STATUS_GO:
|
|
case DEBUG_STATUS_STEP_OVER:
|
|
case DEBUG_STATUS_STEP_INTO:
|
|
case DEBUG_STATUS_STEP_BRANCH:
|
|
return DBG_CONTINUE;
|
|
default:
|
|
DBG_ASSERT(FALSE);
|
|
return DBG_CONTINUE;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
PrepareForWait(ULONG Flags, PULONG ContinueStatus)
|
|
{
|
|
HRESULT Status;
|
|
|
|
Status = PrepareForExecution(g_ExecutionStatusRequest);
|
|
if (Status != S_OK)
|
|
{
|
|
// If S_FALSE, we're at a hard breakpoint so the only thing that
|
|
// happens is that the PC is adjusted and the "wait"
|
|
// can succeed immediately.
|
|
// Otherwise we failed execution preparation. Either way
|
|
// we need to try and prepare for calls.
|
|
PrepareForCalls(0);
|
|
|
|
return FAILED(Status) ? Status : S_OK;
|
|
}
|
|
|
|
*ContinueStatus = EventStatusToContinue(g_ExecutionStatusRequest);
|
|
g_EngStatus |= ENG_STATUS_WAITING;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
DWORD
|
|
GetContinueStatus(ULONG FirstChance, ULONG Continue)
|
|
{
|
|
if (!FirstChance || Continue == DEBUG_FILTER_GO_HANDLED)
|
|
{
|
|
return DBG_EXCEPTION_HANDLED;
|
|
}
|
|
else
|
|
{
|
|
return DBG_EXCEPTION_NOT_HANDLED;
|
|
}
|
|
}
|
|
|
|
void
|
|
ProcessDeferredWork(PULONG ContinueStatus)
|
|
{
|
|
if (g_EngDefer & ENG_DEFER_SET_EVENT)
|
|
{
|
|
// This event signalling is used by the system
|
|
// to synchronize with the debugger when starting
|
|
// the debugger via AeDebug. The -e parameter
|
|
// to ntsd sets this value.
|
|
// It could potentially be used in other situations.
|
|
if (g_EventToSignal != NULL)
|
|
{
|
|
SetEvent(g_EventToSignal);
|
|
g_EventToSignal = NULL;
|
|
}
|
|
|
|
g_EngDefer &= ~ENG_DEFER_SET_EVENT;
|
|
}
|
|
|
|
if (g_EngDefer & ENG_DEFER_RESUME_THREAD)
|
|
{
|
|
DBG_ASSERT(g_ThreadToResumeServices);
|
|
|
|
g_ThreadToResumeServices->
|
|
ResumeThreads(1, &g_ThreadToResume, NULL);
|
|
g_ThreadToResume = 0;
|
|
g_ThreadToResumeServices = NULL;
|
|
g_EngDefer &= ~ENG_DEFER_RESUME_THREAD;
|
|
}
|
|
|
|
if (g_EngDefer & ENG_DEFER_EXCEPTION_HANDLING)
|
|
{
|
|
if (*ContinueStatus == DBG_CONTINUE)
|
|
{
|
|
if (g_EventExceptionFilter != NULL)
|
|
{
|
|
// A user-visible exception occurred so check on how it
|
|
// should be handled.
|
|
*ContinueStatus =
|
|
GetContinueStatus(g_ExceptionFirstChance,
|
|
g_EventExceptionFilter->ContinueOption);
|
|
|
|
}
|
|
else
|
|
{
|
|
// An internal exception occurred, such as a single-step.
|
|
// Force the continue status.
|
|
*ContinueStatus = g_ExceptionFirstChance;
|
|
}
|
|
}
|
|
|
|
g_EngDefer &= ~ENG_DEFER_EXCEPTION_HANDLING;
|
|
}
|
|
|
|
// If output was deferred but the wait was exited anyway
|
|
// a stale defer flag will be left. Make sure it's cleared.
|
|
g_EngDefer &= ~ENG_DEFER_OUTPUT_CURRENT_INFO;
|
|
|
|
// Clear at-initial flags. If the incoming event
|
|
// turns out to be one of them it'll turn on the flag.
|
|
g_EngStatus &= ~(ENG_STATUS_AT_INITIAL_BREAK |
|
|
ENG_STATUS_AT_INITIAL_MODULE_LOAD);
|
|
}
|
|
|
|
BOOL
|
|
SuspendExecution(void)
|
|
{
|
|
if (g_EngStatus & ENG_STATUS_SUSPENDED)
|
|
{
|
|
// Nothing to do.
|
|
return FALSE;
|
|
}
|
|
|
|
g_LastSelector = -1; // Prevent stale selector values
|
|
|
|
SuspendAllThreads();
|
|
|
|
// Don't notify on any state changes as
|
|
// PrepareForCalls will do a blanket notify later.
|
|
g_EngNotify++;
|
|
|
|
// If we have an event thread select it.
|
|
if (g_EventThread != NULL)
|
|
{
|
|
DBG_ASSERT(g_EventTarget->m_RegContextThread == NULL);
|
|
g_EventTarget->ChangeRegContext(g_EventThread);
|
|
}
|
|
|
|
// First set the effective machine to the true
|
|
// processor type so that real processor information
|
|
// can be examined to determine any possible
|
|
// alternate execution states.
|
|
// No need to notify here as another SetEffMachine
|
|
// is coming up.
|
|
g_EventTarget->SetEffMachine(g_EventTarget->m_MachineType, FALSE);
|
|
if (g_EngStatus & ENG_STATUS_STATE_CHANGED)
|
|
{
|
|
g_EventTarget->m_EffMachine->
|
|
InitializeContext(g_TargetEventPc, g_ControlReport);
|
|
g_EngStatus &= ~ENG_STATUS_STATE_CHANGED;
|
|
}
|
|
|
|
// If this is a live user target that's being examined
|
|
// instead of truly debugged we do not want to set the
|
|
// trace mode as we can't track events. If we did
|
|
// set the trace mode here it could cause context writeback
|
|
// which could generate an exception that nobody expects.
|
|
if (g_EventProcess &&
|
|
!IS_DUMP_TARGET(g_EventTarget) &&
|
|
(!IS_LIVE_USER_TARGET(g_EventTarget) ||
|
|
!(g_EventProcess->m_Flags & ENG_PROC_EXAMINED)))
|
|
{
|
|
g_EventTarget->m_EffMachine->QuietSetTraceMode(TRACE_NONE);
|
|
}
|
|
|
|
// Now determine the executing code type and
|
|
// make that the effective machine.
|
|
if (IS_CONTEXT_POSSIBLE(g_EventTarget))
|
|
{
|
|
g_EventMachine = MachineTypeInfo(g_EventTarget,
|
|
g_EventTarget->m_EffMachine->
|
|
ExecutingMachine());
|
|
}
|
|
else
|
|
{
|
|
// Local kernel debugging doesn't deal with contexts
|
|
// as everything would be in the context of the debugger.
|
|
// It's safe to just assume the executing machine
|
|
// is the target machine, plus this avoids unwanted
|
|
// context access.
|
|
g_EventMachine = g_EventTarget->m_Machine;
|
|
}
|
|
g_EventTarget->SetEffMachine(g_EventMachine->m_ExecTypes[0], TRUE);
|
|
g_Machine = g_EventMachine;
|
|
|
|
// Trace flag should always be clear at this point.
|
|
g_EngDefer &= ~ENG_DEFER_HARDWARE_TRACING;
|
|
|
|
g_EngNotify--;
|
|
|
|
g_EngStatus |= ENG_STATUS_SUSPENDED;
|
|
g_SuspendedExecutionStatus = GetExecutionStatus();
|
|
g_SuspendedCmdState = g_CmdState;
|
|
|
|
g_ContextChanged = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HRESULT
|
|
ResumeExecution(void)
|
|
{
|
|
TargetInfo* Target;
|
|
|
|
if ((g_EngStatus & ENG_STATUS_SUSPENDED) == 0)
|
|
{
|
|
// Nothing to do.
|
|
return S_OK;
|
|
}
|
|
|
|
if (g_EventTarget &&
|
|
g_EventTarget->m_EffMachine->GetTraceMode() != TRACE_NONE)
|
|
{
|
|
g_EngDefer |= ENG_DEFER_HARDWARE_TRACING;
|
|
}
|
|
|
|
if (!SPECIAL_EXECUTION(g_CmdState) &&
|
|
IS_REMOTE_KERNEL_TARGET(g_EventTarget))
|
|
{
|
|
g_EventTarget->m_Machine->KdUpdateControlSet(&g_ControlSet);
|
|
g_EngDefer |= ENG_DEFER_UPDATE_CONTROL_SET;
|
|
}
|
|
|
|
ForAllLayersToTarget()
|
|
{
|
|
Target->PrepareForExecution();
|
|
}
|
|
|
|
if (!ResumeAllThreads())
|
|
{
|
|
if (g_EventTarget)
|
|
{
|
|
g_EventTarget->ChangeRegContext(g_EventThread);
|
|
}
|
|
return E_FAIL;
|
|
}
|
|
|
|
g_EngStatus &= ~ENG_STATUS_SUSPENDED;
|
|
return S_OK;
|
|
}
|
|
|
|
void
|
|
PrepareForCalls(ULONG64 ExtraStatusFlags)
|
|
{
|
|
BOOL HardBrkpt = FALSE;
|
|
ADDR PcAddr;
|
|
BOOL Changed = FALSE;
|
|
|
|
// If there's no event then execution didn't really
|
|
// occur so there's no need to suspend. This will happen
|
|
// when a debuggee exits or during errors on execution
|
|
// preparation.
|
|
if (g_EventThreadSysId != 0)
|
|
{
|
|
if (SuspendExecution())
|
|
{
|
|
Changed = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_CmdState = 'c';
|
|
|
|
// Force notification in this case to ensure
|
|
// that clients know the engine is not running.
|
|
Changed = TRUE;
|
|
}
|
|
|
|
if (RemoveBreakpoints() == S_OK)
|
|
{
|
|
Changed = TRUE;
|
|
}
|
|
|
|
if (!IS_EVENT_CONTEXT_ACCESSIBLE())
|
|
{
|
|
ADDRFLAT(&PcAddr, 0);
|
|
ClearAddr(&g_EventPc);
|
|
}
|
|
else
|
|
{
|
|
g_EventMachine->GetPC(&PcAddr);
|
|
g_EventPc = PcAddr;
|
|
}
|
|
|
|
if (g_CmdState != 'c')
|
|
{
|
|
g_CmdState = 'c';
|
|
Changed = TRUE;
|
|
|
|
g_DumpDefault = g_UnasmDefault = g_AssemDefault = PcAddr;
|
|
|
|
if (IS_EVENT_CONTEXT_ACCESSIBLE() &&
|
|
IS_KERNEL_TARGET(g_EventTarget))
|
|
{
|
|
HardBrkpt = g_EventMachine->
|
|
IsBreakpointInstruction(g_EventProcess, &PcAddr);
|
|
}
|
|
}
|
|
|
|
g_EngStatus |= ENG_STATUS_PREPARED_FOR_CALLS;
|
|
|
|
if (Changed)
|
|
{
|
|
if (IS_EVENT_CONTEXT_ACCESSIBLE())
|
|
{
|
|
ResetCurrentScopeLazy();
|
|
}
|
|
|
|
// This can produce many notifications. Callers should
|
|
// suppress notification when they can to avoid multiple
|
|
// notifications during a single operation.
|
|
NotifyChangeEngineState(DEBUG_CES_EXECUTION_STATUS,
|
|
DEBUG_STATUS_BREAK | ExtraStatusFlags, TRUE);
|
|
NotifyChangeEngineState(DEBUG_CES_CURRENT_THREAD,
|
|
g_Thread ? g_Thread->m_UserId : DEBUG_ANY_ID,
|
|
TRUE);
|
|
NotifyChangeDebuggeeState(DEBUG_CDS_ALL, 0);
|
|
NotifyExtensions(DEBUG_NOTIFY_SESSION_ACCESSIBLE, 0);
|
|
}
|
|
else if (ExtraStatusFlags == 0)
|
|
{
|
|
// We're exiting a wait so force the current execution
|
|
// status to be sent to let everybody know that a
|
|
// wait is finishing.
|
|
NotifyChangeEngineState(DEBUG_CES_EXECUTION_STATUS,
|
|
DEBUG_STATUS_BREAK, TRUE);
|
|
}
|
|
|
|
// IA64 reports a plabel in the structure, so we need to compare
|
|
// against the real function address.
|
|
if (HardBrkpt)
|
|
{
|
|
ULONG64 Address = Flat(PcAddr);
|
|
ULONG64 StatusRoutine =
|
|
g_EventTarget->m_KdDebuggerData.BreakpointWithStatus;
|
|
|
|
if (g_EventMachine->m_ExecTypes[0] == IMAGE_FILE_MACHINE_IA64)
|
|
{
|
|
g_EventTarget->ReadPointer(g_EventProcess, g_EventMachine,
|
|
StatusRoutine, &StatusRoutine);
|
|
StatusRoutine &= ~0xf;
|
|
Address &= ~0xf;
|
|
}
|
|
|
|
if (Address && Address == StatusRoutine)
|
|
{
|
|
HandleBPWithStatus();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Some kernel dumps don't show up as hard breakpoints
|
|
// Call !analyze if we have kernel dump target
|
|
if (IS_EVENT_CONTEXT_ACCESSIBLE() &&
|
|
g_EventTarget->m_Class == DEBUG_CLASS_KERNEL &&
|
|
(g_EventTarget->m_ClassQualifier == DEBUG_DUMP_SMALL ||
|
|
g_EventTarget->m_ClassQualifier == DEBUG_DUMP_DEFAULT ||
|
|
g_EventTarget->m_ClassQualifier == DEBUG_DUMP_FULL))
|
|
{
|
|
CallBugCheckExtension(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
PrepareForExecution(ULONG NewStatus)
|
|
{
|
|
ADDR PcAddr;
|
|
BOOL AtHardBrkpt = FALSE;
|
|
ThreadInfo* StepThread = NULL;
|
|
|
|
ClearAddr(&g_PrevRelatedPc);
|
|
|
|
StepAgain:
|
|
// Remember the event PC for later.
|
|
g_PrevEventPc = g_EventPc;
|
|
|
|
// Display current information on intermediate steps where
|
|
// the debugger UI isn't even invoked.
|
|
if ((g_EngDefer & ENG_DEFER_OUTPUT_CURRENT_INFO) &&
|
|
(g_EngStatus & ENG_STATUS_STOP_SESSION) == 0)
|
|
{
|
|
OutCurInfo(OCI_SYMBOL | OCI_DISASM | OCI_ALLOW_EA |
|
|
OCI_ALLOW_REG | OCI_ALLOW_SOURCE | OCI_IGNORE_STATE,
|
|
g_Machine->m_AllMask, DEBUG_OUTPUT_PROMPT_REGISTERS);
|
|
g_EngDefer &= ~ENG_DEFER_OUTPUT_CURRENT_INFO;
|
|
}
|
|
|
|
// Don't notify on any state changes as
|
|
// PrepareForCalls will do a blanket notify later.
|
|
g_EngNotify++;
|
|
|
|
if (g_EventTarget && (g_EngStatus & ENG_STATUS_SUSPENDED))
|
|
{
|
|
if (!SPECIAL_EXECUTION(g_CmdState))
|
|
{
|
|
if (NewStatus != DEBUG_STATUS_IGNORE_EVENT)
|
|
{
|
|
SetExecutionStatus(NewStatus);
|
|
DBG_ASSERT((g_EngStatus & ENG_STATUS_STOP_SESSION) ||
|
|
IS_RUNNING(g_CmdState));
|
|
}
|
|
else
|
|
{
|
|
NewStatus = g_SuspendedExecutionStatus;
|
|
g_CmdState = g_SuspendedCmdState;
|
|
}
|
|
}
|
|
|
|
if (!(g_EngStatus & ENG_STATUS_STOP_SESSION) &&
|
|
!SPECIAL_EXECUTION(g_CmdState) &&
|
|
(g_StepTraceBp->m_Flags & DEBUG_BREAKPOINT_ENABLED) &&
|
|
g_StepTraceBp->m_MatchThread)
|
|
{
|
|
StepThread = g_StepTraceBp->m_MatchThread;
|
|
|
|
// Check and see if we need to fake a step/trace
|
|
// event when artificially moving beyond a hard-coded
|
|
// break instruction.
|
|
if (!StepThread->m_Process->m_Exited)
|
|
{
|
|
StackSaveLayers SaveLayers;
|
|
MachineInfo* Machine =
|
|
MachineTypeInfo(StepThread->m_Process->m_Target,
|
|
g_Machine->m_ExecTypes[0]);
|
|
|
|
SetLayersFromThread(StepThread);
|
|
StepThread->m_Process->m_Target->ChangeRegContext(StepThread);
|
|
Machine->GetPC(&PcAddr);
|
|
AtHardBrkpt = Machine->
|
|
IsBreakpointInstruction(g_Process, &PcAddr);
|
|
if (AtHardBrkpt)
|
|
{
|
|
g_WatchBeginCurFunc = 1;
|
|
|
|
Machine->AdjustPCPastBreakpointInstruction
|
|
(&PcAddr, DEBUG_BREAKPOINT_CODE);
|
|
if (Flat(*g_StepTraceBp->GetAddr()) != OFFSET_TRACE)
|
|
{
|
|
ULONG NextMachine;
|
|
|
|
Machine->GetNextOffset(g_Process,
|
|
g_StepTraceCmdState == 'p',
|
|
g_StepTraceBp->GetAddr(),
|
|
&NextMachine);
|
|
g_StepTraceBp->SetProcType(NextMachine);
|
|
}
|
|
GetCurrentMemoryOffsets(&g_StepTraceInRangeStart,
|
|
&g_StepTraceInRangeEnd);
|
|
|
|
if (StepTracePass(&PcAddr))
|
|
{
|
|
// If the step was passed over go back
|
|
// and update things based on the adjusted PC.
|
|
g_EngNotify--;
|
|
g_EventPc = PcAddr;
|
|
goto StepAgain;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the last event was a hard-coded breakpoint exception
|
|
// we need to move the event thread beyond the break instruction.
|
|
// Note that if we continued stepping on that thread it was
|
|
// handled above, so we only do this if it's a different
|
|
// thread or we're not stepping.
|
|
// If the continuation status is not-handled then
|
|
// we need to let the int3 get hit again. If we're
|
|
// exiting, though, we don't want to do this.
|
|
if (g_EventThread != NULL &&
|
|
!g_EventThread->m_Process->m_Exited &&
|
|
g_EventTarget->m_DynamicEvents &&
|
|
!SPECIAL_EXECUTION(g_CmdState) &&
|
|
g_EventThread != StepThread &&
|
|
(NewStatus != DEBUG_STATUS_GO_NOT_HANDLED ||
|
|
(g_EngStatus & ENG_STATUS_STOP_SESSION)))
|
|
{
|
|
StackSaveLayers SaveLayers;
|
|
|
|
SetLayersFromThread(g_EventThread);
|
|
g_EventTarget->ChangeRegContext(g_EventThread);
|
|
|
|
g_EventMachine->GetPC(&PcAddr);
|
|
if (g_EventMachine->
|
|
IsBreakpointInstruction(g_EventProcess, &PcAddr))
|
|
{
|
|
g_EventMachine->AdjustPCPastBreakpointInstruction
|
|
(&PcAddr, DEBUG_BREAKPOINT_CODE);
|
|
}
|
|
|
|
if (StepThread != NULL)
|
|
{
|
|
StepThread->m_Process->m_Target->
|
|
ChangeRegContext(StepThread);
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT Status;
|
|
|
|
if ((g_EngStatus & ENG_STATUS_STOP_SESSION) ||
|
|
SPECIAL_EXECUTION(g_CmdState))
|
|
{
|
|
// If we're stopping don't insert breakpoints in
|
|
// case we're detaching from the process. In
|
|
// that case we want threads to run normally.
|
|
Status = S_OK;
|
|
}
|
|
else
|
|
{
|
|
Status = InsertBreakpoints();
|
|
}
|
|
|
|
// Resume notification now that modifications are done.
|
|
g_EngNotify--;
|
|
|
|
if (Status != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if ((Status = ResumeExecution()) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
g_EngStatus &= ~ENG_STATUS_PREPARED_FOR_CALLS;
|
|
|
|
if (!SPECIAL_EXECUTION(g_CmdState))
|
|
{
|
|
// Now that we've resumed execution notify about the change.
|
|
NotifyChangeEngineState(DEBUG_CES_EXECUTION_STATUS,
|
|
NewStatus, TRUE);
|
|
NotifyExtensions(DEBUG_NOTIFY_SESSION_INACCESSIBLE, 0);
|
|
}
|
|
|
|
if (AtHardBrkpt && StepThread != NULL)
|
|
{
|
|
// We're stepping over a hard breakpoint. This is
|
|
// done entirely by the debugger so no debug event
|
|
// is associated with it. Instead we simply update
|
|
// the PC and return from the Wait without actually waiting.
|
|
|
|
// Step/trace events have empty event info.
|
|
DiscardLastEventInfo();
|
|
g_EventThreadSysId = StepThread->m_SystemId;
|
|
g_EventProcessSysId = StepThread->m_Process->m_SystemId;
|
|
FindEventProcessThread();
|
|
|
|
// Clear left-overs from the true event so they're
|
|
// not used during initialization.
|
|
g_TargetEventPc = 0;
|
|
g_ControlReport = NULL;
|
|
|
|
SuspendExecution();
|
|
if (IS_EVENT_CONTEXT_ACCESSIBLE())
|
|
{
|
|
g_EventMachine->GetPC(&g_EventPc);
|
|
}
|
|
else
|
|
{
|
|
ClearAddr(&g_EventPc);
|
|
}
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
// Once we resume execution the processes and threads
|
|
// can change so we must flush our notion of what's current.
|
|
g_Process = NULL;
|
|
g_Thread = NULL;
|
|
g_EventProcess = NULL;
|
|
g_EventThread = NULL;
|
|
g_EventMachine = NULL;
|
|
|
|
if (g_EngDefer & ENG_DEFER_DELETE_EXITED)
|
|
{
|
|
// Reap any threads and processes that have terminated since
|
|
// we last executed.
|
|
DeleteAllExitedInfos();
|
|
g_EngDefer &= ~ENG_DEFER_DELETE_EXITED;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
PrepareForSeparation(void)
|
|
{
|
|
HRESULT Status;
|
|
ULONG OldStop = g_EngStatus & ENG_STATUS_STOP_SESSION;
|
|
|
|
//
|
|
// The debugger is going to separate from the
|
|
// debuggee, such as during a detach operation.
|
|
// Get the debuggee running again so that it
|
|
// will go on without the debugger.
|
|
//
|
|
|
|
g_EngStatus |= ENG_STATUS_STOP_SESSION;
|
|
|
|
Status = PrepareForExecution(DEBUG_STATUS_GO_HANDLED);
|
|
|
|
g_EngStatus = (g_EngStatus & ~ENG_STATUS_STOP_SESSION) | OldStop;
|
|
return Status;
|
|
}
|
|
|
|
void
|
|
FindEventProcessThread(void)
|
|
{
|
|
//
|
|
// If these lookups fail other processes and
|
|
// threads cannot be substituted for the correct
|
|
// ones as that may cause modifications to the
|
|
// wrong data structures. For example, if a
|
|
// thread exit comes in it cannot be processed
|
|
// with any other process or thread as that would
|
|
// delete the wrong thread.
|
|
//
|
|
|
|
g_EventProcess = g_EventTarget->FindProcessBySystemId(g_EventProcessSysId);
|
|
if (g_EventProcess == NULL)
|
|
{
|
|
ErrOut("ERROR: Unable to find system process %X\n",
|
|
g_EventProcessSysId);
|
|
ErrOut("ERROR: The process being debugged has either exited "
|
|
"or cannot be accessed\n");
|
|
ErrOut("ERROR: Many commands will not work properly\n");
|
|
}
|
|
else
|
|
{
|
|
g_EventThread = g_EventProcess->
|
|
FindThreadBySystemId(g_EventThreadSysId);
|
|
if (g_EventThread == NULL)
|
|
{
|
|
ErrOut("ERROR: Unable to find system thread %X\n",
|
|
g_EventThreadSysId);
|
|
ErrOut("ERROR: The thread being debugged has either exited "
|
|
"or cannot be accessed\n");
|
|
ErrOut("ERROR: Many commands will not work properly\n");
|
|
}
|
|
}
|
|
|
|
DBG_ASSERT((g_EventThread == NULL ||
|
|
g_EventThread->m_Process == g_EventProcess) &&
|
|
(g_EventProcess == NULL ||
|
|
g_EventProcess->m_Target == g_EventTarget));
|
|
|
|
g_Thread = g_EventThread;
|
|
g_Process = g_EventProcess;
|
|
if (g_Process)
|
|
{
|
|
g_Process->m_CurrentThread = g_Thread;
|
|
}
|
|
g_Target = g_EventTarget;
|
|
if (g_Target)
|
|
{
|
|
g_Target->m_CurrentProcess = g_Process;
|
|
}
|
|
}
|
|
|
|
static int VoteWeight[] =
|
|
{
|
|
0, // DEBUG_STATUS_NO_CHANGE
|
|
2, // DEBUG_STATUS_GO
|
|
3, // DEBUG_STATUS_GO_HANDLED
|
|
4, // DEBUG_STATUS_GO_NOT_HANDLED
|
|
6, // DEBUG_STATUS_STEP_OVER
|
|
7, // DEBUG_STATUS_STEP_INTO
|
|
8, // DEBUG_STATUS_BREAK
|
|
9, // DEBUG_STATUS_NO_DEBUGGEE
|
|
5, // DEBUG_STATUS_STEP_BRANCH
|
|
1, // DEBUG_STATUS_IGNORE_EVENT
|
|
};
|
|
|
|
ULONG
|
|
MergeVotes(ULONG Cur, ULONG Vote)
|
|
{
|
|
// If the vote is actually an error code display a message.
|
|
if (FAILED(Vote))
|
|
{
|
|
ErrOut("Callback failed with %X\n", Vote);
|
|
return Cur;
|
|
}
|
|
|
|
// Ignore invalid votes.
|
|
if (
|
|
(
|
|
#if DEBUG_STATUS_NO_CHANGE > 0
|
|
Vote < DEBUG_STATUS_NO_CHANGE ||
|
|
#endif
|
|
Vote > DEBUG_STATUS_BREAK) &&
|
|
(Vote < DEBUG_STATUS_STEP_BRANCH ||
|
|
Vote > DEBUG_STATUS_IGNORE_EVENT))
|
|
{
|
|
ErrOut("Callback returned invalid vote %X\n", Vote);
|
|
return Cur;
|
|
}
|
|
|
|
// Votes are biased towards executing as little
|
|
// as possible.
|
|
// Break overrides all other votes.
|
|
// Step into overrides step over.
|
|
// Step over overrides step branch.
|
|
// Step branch overrides go.
|
|
// Go not-handled overrides go handled.
|
|
// Go handled overrides plain go.
|
|
// Plain go overrides ignore event.
|
|
// Anything overrides no change.
|
|
if (VoteWeight[Vote] > VoteWeight[Cur])
|
|
{
|
|
Cur = Vote;
|
|
}
|
|
|
|
return Cur;
|
|
}
|
|
|
|
ULONG
|
|
ProcessBreakpointOrStepException(PEXCEPTION_RECORD64 Record,
|
|
ULONG FirstChance)
|
|
{
|
|
ADDR BpAddr;
|
|
ULONG BreakType;
|
|
ULONG EventStatus;
|
|
|
|
SuspendExecution();
|
|
// Default breakpoint address to the current PC as that's
|
|
// where the majority are at.
|
|
g_EventMachine->GetPC(&BpAddr);
|
|
|
|
// Check whether the exception is a breakpoint.
|
|
BreakType = g_EventMachine->
|
|
IsBreakpointOrStepException(Record, FirstChance,
|
|
&BpAddr, &g_PrevRelatedPc);
|
|
if (BreakType & EXBS_BREAKPOINT_ANY)
|
|
{
|
|
// It's a breakpoint of some kind.
|
|
EventOut("*** breakpoint exception\n");
|
|
EventStatus = CheckBreakpointOrStepTrace(&BpAddr, BreakType);
|
|
}
|
|
else
|
|
{
|
|
// It's a true single step or taken branch exception.
|
|
// We still need to check breakpoints as we may have stepped
|
|
// to an instruction which has a breakpoint.
|
|
EventOut("*** single step or taken branch exception\n");
|
|
EventStatus = CheckBreakpointOrStepTrace(&BpAddr, EXBS_BREAKPOINT_ANY);
|
|
}
|
|
|
|
if (EventStatus == DEBUG_STATUS_NO_CHANGE)
|
|
{
|
|
// The break/step exception wasn't recognized
|
|
// as a debugger-specific event so handle it as
|
|
// a regular exception. The default states for
|
|
// break/step exceptions are to break in so
|
|
// this will do the right thing, plus it allows
|
|
// people to ignore or notify for them if they want.
|
|
EventStatus = NotifyExceptionEvent(Record, FirstChance, FALSE);
|
|
}
|
|
else
|
|
{
|
|
// Force the exception to be handled.
|
|
g_EngDefer |= ENG_DEFER_EXCEPTION_HANDLING;
|
|
g_EventExceptionFilter = NULL;
|
|
g_ExceptionFirstChance = DBG_EXCEPTION_HANDLED;
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
ULONG
|
|
CheckBreakpointOrStepTrace(PADDR BpAddr, ULONG BreakType)
|
|
{
|
|
ULONG EventStatus;
|
|
Breakpoint* Bp;
|
|
ULONG BreakHitType;
|
|
BOOL BpHit;
|
|
|
|
BpHit = FALSE;
|
|
Bp = NULL;
|
|
EventStatus = DEBUG_STATUS_NO_CHANGE;
|
|
|
|
// Multiple breakpoints can be hit at the same address.
|
|
// Process all possible hits. Do not do notifications
|
|
// while walking the list as the callbacks may modify
|
|
// the list. Instead just mark the breakpoint as
|
|
// needing notification in the next pass.
|
|
for (;;)
|
|
{
|
|
Bp = CheckBreakpointHit(g_EventProcess, Bp, BpAddr, BreakType, -1,
|
|
g_CmdState != 'g' ?
|
|
DEBUG_BREAKPOINT_GO_ONLY : 0,
|
|
&BreakHitType, TRUE);
|
|
if (Bp == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (BreakHitType == BREAKPOINT_HIT)
|
|
{
|
|
Bp->m_Flags |= BREAKPOINT_NOTIFY;
|
|
}
|
|
else
|
|
{
|
|
// This breakpoint was hit but the hit was ignored.
|
|
// Vote to continue execution.
|
|
EventStatus = MergeVotes(EventStatus, DEBUG_STATUS_IGNORE_EVENT);
|
|
}
|
|
|
|
BpHit = TRUE;
|
|
Bp = Bp->m_Next;
|
|
if (Bp == NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!BpHit)
|
|
{
|
|
// If no breakpoints were recognized check for an internal
|
|
// breakpoint.
|
|
EventStatus = CheckStepTrace(BpAddr, EventStatus);
|
|
|
|
//
|
|
// If the breakpoint wasn't for a step/trace
|
|
// it's a hard breakpoint and should be
|
|
// handled as a normal exception.
|
|
//
|
|
|
|
if (!g_EventProcess->m_InitialBreakDone)
|
|
{
|
|
g_EngStatus |= ENG_STATUS_AT_INITIAL_BREAK;
|
|
}
|
|
|
|
// We've seen the initial break for this process.
|
|
g_EventProcess->m_InitialBreakDone = TRUE;
|
|
// If we were waiting for a break-in exception we've got it.
|
|
g_EngStatus &= ~ENG_STATUS_PENDING_BREAK_IN;
|
|
|
|
if (EventStatus == DEBUG_STATUS_NO_CHANGE)
|
|
{
|
|
if (!g_EventProcess->m_InitialBreak)
|
|
{
|
|
// Refresh breakpoints even though we're not
|
|
// stopping. This gives saved breakpoints
|
|
// a chance to become active.
|
|
RemoveBreakpoints();
|
|
|
|
EventStatus = DEBUG_STATUS_GO;
|
|
g_EventProcess->m_InitialBreak = TRUE;
|
|
}
|
|
else if (IS_USER_TARGET(g_EventTarget) &&
|
|
(!g_EventProcess->m_InitialBreakWx86) &&
|
|
(g_EventTarget->m_MachineType !=
|
|
g_EventTarget->m_EffMachineType) &&
|
|
(g_EventTarget->
|
|
m_EffMachineType == IMAGE_FILE_MACHINE_I386))
|
|
{
|
|
// Allow skipping of both the target machine
|
|
// initial break and emulated machine initial breaks.
|
|
RemoveBreakpoints();
|
|
EventStatus = DEBUG_STATUS_GO;
|
|
g_EventProcess->m_InitialBreakWx86 = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// A breakpoint was recognized. We need to
|
|
// refresh the breakpoint status since we'll
|
|
// probably need to defer the reinsertion of
|
|
// the breakpoint we're sitting on.
|
|
RemoveBreakpoints();
|
|
|
|
// Now do event callbacks for any breakpoints that need it.
|
|
EventStatus = NotifyHitBreakpoints(EventStatus);
|
|
}
|
|
|
|
if (g_ThreadToResume != 0)
|
|
{
|
|
g_EngDefer |= ENG_DEFER_RESUME_THREAD;
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
ULONG
|
|
CheckStepTrace(PADDR PcAddr, ULONG DefaultStatus)
|
|
{
|
|
BOOL WatchStepOver = FALSE;
|
|
ULONG uOciFlags;
|
|
ULONG NextMachine;
|
|
|
|
if ((g_StepTraceBp->m_Flags & DEBUG_BREAKPOINT_ENABLED) &&
|
|
Flat(*g_StepTraceBp->GetAddr()) != OFFSET_TRACE)
|
|
{
|
|
NotFlat(*g_StepTraceBp->GetAddr());
|
|
ComputeFlatAddress(g_StepTraceBp->GetAddr(), NULL);
|
|
}
|
|
|
|
// We do not check ENG_THREAD_TRACE_SET here because
|
|
// this event detection is only for proper user-initiated
|
|
// step/trace events. Such an event must occur immediately
|
|
// after the t/p/b, otherwise we cannot be sure that
|
|
// it's actually a debugger event and not an app-generated
|
|
// single-step exception.
|
|
// In user mode we restrict the step/trace state
|
|
// to a single thread to try and be as precise
|
|
// as possible. This isn't done in kernel mode
|
|
// since kernel mode "threads" are currently
|
|
// just placeholders for processors. It is
|
|
// possible for a context switch to occur at any
|
|
// time while stepping, meaning a true system
|
|
// thread could move from one processor to another.
|
|
// The processor state, including the single-step
|
|
// flag, will be moved with the thread so single
|
|
// step exceptions will come from the new processor
|
|
// rather than this one, meaning we would ignore
|
|
// it if we used "thread" restrictions. Instead,
|
|
// just assume any single-step exception while in
|
|
// p/t mode is a debugger step.
|
|
if ((g_StepTraceBp->m_Flags & DEBUG_BREAKPOINT_ENABLED) &&
|
|
g_StepTraceBp->m_Process == g_EventProcess &&
|
|
((IS_KERNEL_TARGET(g_EventTarget) && IS_STEP_TRACE(g_CmdState)) ||
|
|
g_StepTraceBp->m_MatchThread == g_EventThread) &&
|
|
(Flat(*g_StepTraceBp->GetAddr()) == OFFSET_TRACE ||
|
|
AddrEqu(*g_StepTraceBp->GetAddr(), *PcAddr)))
|
|
{
|
|
ADDR CurrentSP;
|
|
|
|
// step/trace event occurred
|
|
|
|
// Update breakpoint status since we may need to step
|
|
// again and step/trace is updated when breakpoints
|
|
// are inserted.
|
|
RemoveBreakpoints();
|
|
|
|
uOciFlags = OCI_DISASM | OCI_ALLOW_REG | OCI_ALLOW_SOURCE |
|
|
OCI_ALLOW_EA;
|
|
|
|
if (g_EngStatus & (ENG_STATUS_PENDING_BREAK_IN |
|
|
ENG_STATUS_USER_INTERRUPT))
|
|
{
|
|
g_WatchFunctions.End(PcAddr);
|
|
return DEBUG_STATUS_BREAK;
|
|
}
|
|
|
|
if (IS_KERNEL_TARGET(g_EventTarget) && g_WatchInitialSP)
|
|
{
|
|
g_EventMachine->GetSP(&CurrentSP);
|
|
|
|
if ((Flat(CurrentSP) + 0x1500 < g_WatchInitialSP) ||
|
|
(g_WatchInitialSP + 0x1500 < Flat(CurrentSP)))
|
|
{
|
|
return DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
}
|
|
|
|
if (g_StepTraceInRangeStart != -1 &&
|
|
Flat(*PcAddr) >= g_StepTraceInRangeStart &&
|
|
Flat(*PcAddr) < g_StepTraceInRangeEnd)
|
|
{
|
|
// test if step/trace range active
|
|
// if so, compute the next offset and pass through
|
|
|
|
g_EventMachine->GetNextOffset(g_EventProcess,
|
|
g_StepTraceCmdState == 'p',
|
|
g_StepTraceBp->GetAddr(),
|
|
&NextMachine);
|
|
g_StepTraceBp->SetProcType(NextMachine);
|
|
if (g_WatchWhole)
|
|
{
|
|
g_WatchBeginCurFunc = Flat(*g_StepTraceBp->GetAddr());
|
|
g_WatchEndCurFunc = 0;
|
|
}
|
|
|
|
return DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
|
|
// active step/trace event - note event if count is zero
|
|
|
|
if (!StepTracePass(PcAddr) ||
|
|
(g_WatchFunctions.IsStarted() && AddrEqu(g_WatchTarget, *PcAddr) &&
|
|
(!IS_KERNEL_TARGET(g_EventTarget) ||
|
|
Flat(CurrentSP) >= g_WatchInitialSP)))
|
|
{
|
|
g_WatchFunctions.End(PcAddr);
|
|
return DEBUG_STATUS_BREAK;
|
|
}
|
|
|
|
if (g_WatchFunctions.IsStarted())
|
|
{
|
|
if (g_WatchTrace)
|
|
{
|
|
g_EventTarget->
|
|
ProcessWatchTraceEvent((PDBGKD_TRACE_DATA)
|
|
g_StateChangeData,
|
|
PcAddr,
|
|
&WatchStepOver);
|
|
}
|
|
goto skipit;
|
|
}
|
|
|
|
if (g_SrcOptions & SRCOPT_STEP_SOURCE)
|
|
{
|
|
goto skipit;
|
|
}
|
|
|
|
// more remaining events to occur, but output
|
|
// the instruction (optionally with registers)
|
|
// compute the step/trace address for next event
|
|
|
|
OutCurInfo(uOciFlags, g_EventMachine->m_AllMask,
|
|
DEBUG_OUTPUT_PROMPT_REGISTERS);
|
|
|
|
skipit:
|
|
g_EventMachine->
|
|
GetNextOffset(g_EventProcess,
|
|
g_StepTraceCmdState == 'p' || WatchStepOver,
|
|
g_StepTraceBp->GetAddr(),
|
|
&NextMachine);
|
|
g_StepTraceBp->SetProcType(NextMachine);
|
|
GetCurrentMemoryOffsets(&g_StepTraceInRangeStart,
|
|
&g_StepTraceInRangeEnd);
|
|
|
|
return DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
|
|
// Carry out deferred breakpoint work if necessary.
|
|
// We need to check the thread deferred-bp flag here as
|
|
// other events may occur before the thread with deferred
|
|
// work gets to execute again, in which case the setting
|
|
// of g_DeferDefined may have changed.
|
|
if ((g_EventThread != NULL &&
|
|
(g_EventThread->m_Flags & ENG_THREAD_DEFER_BP_TRACE)) ||
|
|
(g_DeferDefined &&
|
|
g_DeferBp->m_Process == g_EventProcess &&
|
|
(Flat(*g_DeferBp->GetAddr()) == OFFSET_TRACE ||
|
|
AddrEqu(*g_DeferBp->GetAddr(), *PcAddr))))
|
|
{
|
|
if ((g_EngOptions & DEBUG_ENGOPT_SYNCHRONIZE_BREAKPOINTS) &&
|
|
IS_USER_TARGET(g_EventTarget) &&
|
|
IsSelectedExecutionThread(g_EventThread,
|
|
SELTHREAD_INTERNAL_THREAD))
|
|
{
|
|
// The engine internally restricted execution to
|
|
// this particular thread in order to manage
|
|
// breakpoints in multithreaded conditions.
|
|
// The deferred work will be finished before
|
|
// we resume so we can drop the lock.
|
|
SelectExecutionThread(NULL, SELTHREAD_ANY);
|
|
}
|
|
|
|
// Deferred breakpoints are refreshed on breakpoint
|
|
// insertion so make sure that insertion happens
|
|
// when things restart.
|
|
RemoveBreakpoints();
|
|
return DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
|
|
// If the event was unrecognized return the default status.
|
|
return DefaultStatus;
|
|
}
|
|
|
|
void
|
|
AnalyzeDeadlock(PEXCEPTION_RECORD64 Record, ULONG FirstChance)
|
|
{
|
|
CHAR Symbol[MAX_SYMBOL_LEN];
|
|
DWORD64 Displacement;
|
|
ThreadInfo* ThreadOwner = NULL;
|
|
DWORD Tid = 0;
|
|
RTL_CRITICAL_SECTION CritSec;
|
|
|
|
// poking around inside NT's user-mode RTL_CRITICAL_SECTION and
|
|
// RTL_RESOURCE structures.
|
|
|
|
//
|
|
// Get the symbolic name of the routine which
|
|
// raised the exception to see if it matches
|
|
// one of the expected ones in ntdll.
|
|
//
|
|
|
|
GetSymbol((ULONG64)Record->ExceptionAddress,
|
|
Symbol, DIMA(Symbol), &Displacement);
|
|
|
|
if (!_stricmp("ntdll!RtlpWaitForCriticalSection", Symbol))
|
|
{
|
|
//
|
|
// If the first parameter is a pointer to the critsect as it
|
|
// should be, switch to the owning thread before bringing
|
|
// up the prompt. This way it's obvious where the problem
|
|
// is.
|
|
//
|
|
|
|
if (Record->ExceptionInformation[0])
|
|
{
|
|
if (g_EventTarget->
|
|
ReadAllVirtual(g_EventProcess, Record->ExceptionInformation[0],
|
|
&CritSec, sizeof(CritSec)) == S_OK)
|
|
{
|
|
if (NULL == CritSec.DebugInfo)
|
|
{
|
|
dprintf("Critsec %s was deleted or "
|
|
"was never initialized.\n",
|
|
FormatAddr64(Record->ExceptionInformation[0]));
|
|
}
|
|
else if (CritSec.LockCount < -1)
|
|
{
|
|
dprintf("Critsec %s was left when not owned, corrupted.\n",
|
|
FormatAddr64(Record->ExceptionInformation[0]));
|
|
}
|
|
else
|
|
{
|
|
Tid = (DWORD)((ULONG_PTR)CritSec.OwningThread);
|
|
ThreadOwner = g_Process->FindThreadBySystemId(Tid);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ThreadOwner)
|
|
{
|
|
dprintf("Critsec %s owned by thread %d (.%x) "
|
|
"caused thread %d (.%x)\n"
|
|
" to timeout entering it. "
|
|
"Breaking in on owner thread, ask\n"
|
|
" yourself why it has held this "
|
|
"critsec long enough to deadlock.\n"
|
|
" Use `~%ds` to switch back to timeout thread.\n",
|
|
FormatAddr64(Record->ExceptionInformation[0]),
|
|
ThreadOwner->m_UserId,
|
|
ThreadOwner->m_SystemId,
|
|
g_Thread->m_UserId,
|
|
g_Thread->m_SystemId,
|
|
g_Thread->m_UserId);
|
|
|
|
g_EventThread = ThreadOwner;
|
|
|
|
SetPromptThread(ThreadOwner, 0);
|
|
}
|
|
else if (Tid)
|
|
{
|
|
dprintf("Critsec %s ABANDONED owner thread ID is .%x, "
|
|
"no such thread.\n",
|
|
FormatAddr64(Record->ExceptionInformation[0]),
|
|
Tid);
|
|
}
|
|
|
|
if (!FirstChance)
|
|
{
|
|
dprintf("!!! second chance !!!\n");
|
|
}
|
|
|
|
//
|
|
// do a !critsec for them
|
|
//
|
|
|
|
if (Record->ExceptionInformation[0])
|
|
{
|
|
char CritsecAddr[64];
|
|
HRESULT Status;
|
|
|
|
sprintf(CritsecAddr, "%s",
|
|
FormatAddr64(Record->ExceptionInformation[0]));
|
|
dprintf("!critsec %s\n", CritsecAddr);
|
|
CallAnyExtension(NULL, NULL, "critsec", CritsecAddr,
|
|
FALSE, FALSE, &Status);
|
|
}
|
|
}
|
|
else if (!_stricmp("ntdll!RtlAcquireResourceShared", Symbol) ||
|
|
!_stricmp("ntdll!RtlAcquireResourceExclusive", Symbol) ||
|
|
!_stricmp("ntdll!RtlConvertSharedToExclusive", Symbol))
|
|
{
|
|
dprintf("deadlock in %s ", 1 + strstr(Symbol, "!"));
|
|
|
|
GetSymbol(Record->ExceptionInformation[0],
|
|
Symbol, sizeof(Symbol), &Displacement);
|
|
|
|
dprintf("Resource %s", Symbol);
|
|
if (Displacement)
|
|
{
|
|
dprintf("+%s", FormatDisp64(Displacement));
|
|
}
|
|
dprintf(" (%s)\n",
|
|
FormatAddr64(Record->ExceptionInformation[0]));
|
|
if (!FirstChance)
|
|
{
|
|
dprintf("!!! second chance !!!\n");
|
|
}
|
|
|
|
// Someone who uses RTL_RESOURCEs might write a !resource
|
|
// for ntsdexts.dll like !critsec.
|
|
}
|
|
else
|
|
{
|
|
dprintf("Possible Deadlock in %s ", Symbol);
|
|
|
|
GetSymbol(Record->ExceptionInformation[0],
|
|
Symbol, sizeof(Symbol), &Displacement);
|
|
|
|
dprintf("Lock %s", Symbol);
|
|
if (Displacement)
|
|
{
|
|
dprintf("+%s", FormatDisp64(Displacement));
|
|
}
|
|
dprintf(" (%s)\n",
|
|
FormatAddr64(Record->ExceptionInformation[0]));
|
|
if (!FirstChance)
|
|
{
|
|
dprintf("!!! second chance !!!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
OutputDeadlock(PEXCEPTION_RECORD64 Record, ULONG FirstChance)
|
|
{
|
|
CHAR Symbol[MAX_SYMBOL_LEN];
|
|
DWORD64 Displacement;
|
|
|
|
GetSymbol(Record->ExceptionInformation[0],
|
|
Symbol, sizeof(Symbol), &Displacement);
|
|
|
|
dprintf("Possible Deadlock Lock %s+%s at %s\n",
|
|
Symbol,
|
|
FormatDisp64(Displacement),
|
|
FormatAddr64(Record->ExceptionInformation[0]));
|
|
if (!FirstChance)
|
|
{
|
|
dprintf("!!! second chance !!!\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
GetEventName(ULONG64 ImageFile, ULONG64 ImageBase,
|
|
ULONG64 NamePtr, WORD Unicode,
|
|
PSTR NameBuffer, ULONG BufferSize)
|
|
{
|
|
char TempName[MAX_IMAGE_PATH];
|
|
|
|
if (!g_EventProcess)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (NamePtr != 0)
|
|
{
|
|
if (g_EventTarget->ReadPointer(g_EventProcess,
|
|
g_EventTarget->m_Machine,
|
|
NamePtr, &NamePtr) != S_OK)
|
|
{
|
|
NamePtr = 0;
|
|
}
|
|
}
|
|
|
|
if (NamePtr != 0)
|
|
{
|
|
ULONG Done;
|
|
|
|
if (g_EventTarget->ReadVirtual(g_EventProcess,
|
|
NamePtr, TempName, sizeof(TempName),
|
|
&Done) != S_OK ||
|
|
Done < (Unicode ? 2 * sizeof(WCHAR) : 2))
|
|
{
|
|
NamePtr = 0;
|
|
}
|
|
else
|
|
{
|
|
TempName[sizeof(TempName) - 1] = 0;
|
|
TempName[sizeof(TempName) - 2] = 0;
|
|
}
|
|
}
|
|
|
|
if (NamePtr != 0)
|
|
{
|
|
//
|
|
// We have a name.
|
|
//
|
|
if (Unicode)
|
|
{
|
|
if (!WideCharToMultiByte(
|
|
CP_ACP,
|
|
WC_COMPOSITECHECK,
|
|
(LPWSTR)TempName,
|
|
-1,
|
|
NameBuffer,
|
|
BufferSize,
|
|
NULL,
|
|
NULL
|
|
))
|
|
{
|
|
//
|
|
// Unicode -> ANSI conversion failed.
|
|
//
|
|
NameBuffer[0] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CopyString(NameBuffer, TempName, BufferSize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We don't have a name, so look in the image.
|
|
// A file handle will only be provided here in the
|
|
// local case so it's safe to case to HANDLE.
|
|
//
|
|
if (!GetModnameFromImage(g_EventProcess,
|
|
ImageBase, OS_HANDLE(ImageFile),
|
|
NameBuffer, BufferSize, TRUE))
|
|
{
|
|
NameBuffer[0] = 0;
|
|
}
|
|
}
|
|
|
|
if (!NameBuffer[0])
|
|
{
|
|
if (!GetModNameFromLoaderList(g_EventThread,
|
|
g_EventTarget->m_Machine, 0,
|
|
ImageBase, NameBuffer, BufferSize,
|
|
TRUE))
|
|
{
|
|
PrintString(NameBuffer, BufferSize,
|
|
"image%p", (PVOID)(ULONG_PTR)ImageBase);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the name given doesn't have a full path try
|
|
// and locate a full path in the loader list.
|
|
if ((((NameBuffer[0] < 'a' || NameBuffer[0] > 'z') &&
|
|
(NameBuffer[0] < 'A' || NameBuffer[0] > 'Z')) ||
|
|
NameBuffer[1] != ':') &&
|
|
(NameBuffer[0] != '\\' || NameBuffer[1] != '\\'))
|
|
{
|
|
GetModNameFromLoaderList(g_EventThread,
|
|
g_EventTarget->m_Machine, 0,
|
|
ImageBase, NameBuffer, BufferSize,
|
|
TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ConnLiveKernelTargetInfo wait methods.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
ConnLiveKernelTargetInfo::KdContinue(ULONG ContinueStatus,
|
|
PDBGKD_ANY_CONTROL_SET ControlSet)
|
|
{
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
|
|
DBG_ASSERT(ContinueStatus == DBG_EXCEPTION_HANDLED ||
|
|
ContinueStatus == DBG_EXCEPTION_NOT_HANDLED ||
|
|
ContinueStatus == DBG_CONTINUE);
|
|
|
|
if (ControlSet)
|
|
{
|
|
EventOut(">>> DbgKdContinue2\n");
|
|
|
|
m.ApiNumber = DbgKdContinueApi2;
|
|
m.u.Continue2.ContinueStatus = ContinueStatus;
|
|
m.u.Continue2.AnyControlSet = *ControlSet;
|
|
}
|
|
else
|
|
{
|
|
EventOut(">>> DbgKdContinue\n");
|
|
|
|
m.ApiNumber = DbgKdContinueApi;
|
|
m.u.Continue.ContinueStatus = ContinueStatus;
|
|
}
|
|
|
|
m.ReturnStatus = ContinueStatus;
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::WaitInitialize(ULONG Flags,
|
|
ULONG Timeout,
|
|
WAIT_INIT_TYPE Type,
|
|
PULONG DesiredTimeout)
|
|
{
|
|
// Timeouts can't easily be supported at the moment and
|
|
// aren't really necessary.
|
|
if (Timeout != INFINITE)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
*DesiredTimeout = Timeout;
|
|
m_EventPossible = m_CurrentPartition;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::ReleaseLastEvent(ULONG ContinueStatus)
|
|
{
|
|
HRESULT Status = S_OK;
|
|
NTSTATUS NtStatus;
|
|
|
|
if (!g_EventProcessSysId)
|
|
{
|
|
// No event to release.
|
|
return S_OK;
|
|
}
|
|
|
|
m_CurrentPartition = FALSE;
|
|
|
|
if (SPECIAL_EXECUTION(g_CmdState))
|
|
{
|
|
if (m_SwitchTarget)
|
|
{
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
|
|
EventOut(">>> Switch to system %d\n", m_SwitchTarget->m_UserId);
|
|
|
|
if (m_KdMaxManipulate <= DbgKdSwitchPartition)
|
|
{
|
|
ErrOut("System doesn't support partition switching\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
m.ApiNumber = (USHORT)DbgKdSwitchPartition;
|
|
m.Processor = 0;
|
|
m.ProcessorLevel = 0;
|
|
m.u.SwitchPartition.Partition = m_SwitchTarget->m_SystemId;
|
|
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
|
|
KdOut("DbgKdSwitchPartition returns 0x00000000\n");
|
|
|
|
m_SwitchTarget = NULL;
|
|
g_EngStatus |= ENG_STATUS_SPECIAL_EXECUTION;
|
|
}
|
|
else
|
|
{
|
|
// This can either be a real processor switch or
|
|
// a rewait for state change. Check the switch
|
|
// processor to be sure.
|
|
if (m_SwitchProcessor)
|
|
{
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
|
|
EventOut(">>> Switch to processor %d\n",
|
|
m_SwitchProcessor - 1);
|
|
|
|
m.ApiNumber = (USHORT)DbgKdSwitchProcessor;
|
|
m.Processor = (USHORT)(m_SwitchProcessor - 1);
|
|
|
|
// Quiet PREfix warnings.
|
|
m.ProcessorLevel = 0;
|
|
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
|
|
KdOut("DbgKdSwitchActiveProcessor returns 0x00000000\n");
|
|
|
|
m_SwitchProcessor = 0;
|
|
g_EngStatus |= ENG_STATUS_SPECIAL_EXECUTION;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NtStatus = KdContinue(ContinueStatus,
|
|
(g_EngDefer & ENG_DEFER_UPDATE_CONTROL_SET) ?
|
|
&g_ControlSet : NULL);
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
ErrOut("KdContinue failed, 0x%08x\n", NtStatus);
|
|
Status = HRESULT_FROM_NT(NtStatus);
|
|
}
|
|
else
|
|
{
|
|
g_EngDefer &= ~ENG_DEFER_UPDATE_CONTROL_SET;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout,
|
|
ULONG ElapsedTime, PULONG EventStatus)
|
|
{
|
|
NTSTATUS NtStatus;
|
|
|
|
if (!IS_MACHINE_SET(this))
|
|
{
|
|
dprintf("Waiting to reconnect...\n");
|
|
|
|
if ((g_EngOptions & DEBUG_ENGOPT_INITIAL_BREAK) &&
|
|
IS_CONN_KERNEL_TARGET(this))
|
|
{
|
|
// Ask for a breakin to be sent once the
|
|
// code gets into resync.
|
|
m_Transport->m_SyncBreakIn = TRUE;
|
|
}
|
|
}
|
|
|
|
// When waiting for confirmation of a processor switch don't
|
|
// yield the engine lock in order to prevent other clients
|
|
// from trying to do things with the target while it's
|
|
// switching.
|
|
NtStatus = WaitStateChange(&g_StateChange, g_StateChangeBuffer,
|
|
sizeof(g_StateChangeBuffer) - 2,
|
|
(g_EngStatus &
|
|
ENG_STATUS_SPECIAL_EXECUTION) == 0);
|
|
if (NtStatus == STATUS_PENDING)
|
|
{
|
|
// A caller interrupted the current wait so exit
|
|
// without an error message.
|
|
return E_PENDING;
|
|
}
|
|
else if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
ErrOut("DbgKdWaitStateChange failed: %08lx\n", NtStatus);
|
|
return HRESULT_FROM_NT(NtStatus);
|
|
}
|
|
|
|
g_EngStatus |= ENG_STATUS_STATE_CHANGED;
|
|
|
|
g_StateChangeData = g_StateChangeBuffer;
|
|
|
|
*EventStatus = ((ConnLiveKernelTargetInfo*)g_EventTarget)->
|
|
ProcessStateChange(&g_StateChange, g_StateChangeData);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
NTSTATUS
|
|
ConnLiveKernelTargetInfo::WaitStateChange
|
|
(OUT PDBGKD_ANY_WAIT_STATE_CHANGE StateChange,
|
|
OUT PVOID Buffer,
|
|
IN ULONG BufferLength,
|
|
IN BOOL SuspendEngine)
|
|
{
|
|
PVOID LocalStateChange;
|
|
NTSTATUS Status;
|
|
PUCHAR Data;
|
|
ULONG SizeofStateChange;
|
|
ULONG WaitStatus;
|
|
|
|
//
|
|
// Waiting for a state change message. Copy the message to the callers
|
|
// buffer.
|
|
//
|
|
|
|
DBG_ASSERT(m_Transport->m_WaitingThread == 0);
|
|
m_Transport->m_WaitingThread = GetCurrentThreadId();
|
|
|
|
if (SuspendEngine)
|
|
{
|
|
SUSPEND_ENGINE();
|
|
}
|
|
|
|
do
|
|
{
|
|
WaitStatus = m_Transport->
|
|
WaitForPacket(PACKET_TYPE_KD_STATE_CHANGE64, &LocalStateChange);
|
|
} while (WaitStatus != DBGKD_WAIT_PACKET &&
|
|
WaitStatus != DBGKD_WAIT_INTERRUPTED);
|
|
|
|
if (SuspendEngine)
|
|
{
|
|
RESUME_ENGINE();
|
|
}
|
|
m_Transport->m_WaitingThread = 0;
|
|
|
|
if (WaitStatus == DBGKD_WAIT_INTERRUPTED)
|
|
{
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
// If this is the very first wait we don't know what system
|
|
// we've connected to. Update the version information
|
|
// right away.
|
|
if (!IS_MACHINE_SET(this))
|
|
{
|
|
m_Transport->SaveReadPacket();
|
|
|
|
// Failures will be detected by checking the machine
|
|
// state later so don't worry about the return value.
|
|
InitFromKdVersion();
|
|
|
|
m_Transport->RestoreReadPacket();
|
|
|
|
if (!IS_MACHINE_SET(this))
|
|
{
|
|
//
|
|
// We were unable to determine what kind of machine
|
|
// has connected so we cannot properly communicate with it.
|
|
//
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
//
|
|
// Trusted Windows systems have two OS's running, one
|
|
// regular NT and the other a trusted NT-like OS.
|
|
// If this is a Trusted Windows system set up systems
|
|
// for both the regular and trusted OSs.
|
|
//
|
|
|
|
if ((m_KdVersion.Flags & DBGKD_VERS_FLAG_PARTITIONS) &&
|
|
!FindTargetBySystemId(DBGKD_PARTITION_ALTERNATE))
|
|
{
|
|
ConnLiveKernelTargetInfo* AltTarg = new ConnLiveKernelTargetInfo;
|
|
if (!AltTarg)
|
|
{
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
// Avoid the ConnLiveKernelTargetInfo Initialize as
|
|
// we don't want a new transport created.
|
|
if (AltTarg->LiveKernelTargetInfo::Initialize() != S_OK)
|
|
{
|
|
delete AltTarg;
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
m_Transport->Ref();
|
|
AltTarg->m_Transport = m_Transport;
|
|
|
|
m_Transport->SaveReadPacket();
|
|
AltTarg->InitFromKdVersion();
|
|
m_Transport->RestoreReadPacket();
|
|
if (!IS_MACHINE_SET(AltTarg))
|
|
{
|
|
delete AltTarg;
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if (DBGKD_MAJOR_TYPE(m_KdVersion.MajorVersion) == DBGKD_MAJOR_TNT)
|
|
{
|
|
// This is the trusted partition.
|
|
m_SystemId = DBGKD_PARTITION_ALTERNATE;
|
|
AltTarg->m_SystemId = DBGKD_PARTITION_DEFAULT;
|
|
}
|
|
else
|
|
{
|
|
// This is the regular partition.
|
|
m_SystemId = DBGKD_PARTITION_DEFAULT;
|
|
AltTarg->m_SystemId = DBGKD_PARTITION_ALTERNATE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_KdApi64)
|
|
{
|
|
if (m_KdVersion.ProtocolVersion < DBGKD_64BIT_PROTOCOL_VERSION2)
|
|
{
|
|
PDBGKD_WAIT_STATE_CHANGE64 Ws64 =
|
|
(PDBGKD_WAIT_STATE_CHANGE64)LocalStateChange;
|
|
ULONG Offset, Align, Pad;
|
|
|
|
//
|
|
// The 64-bit structures contain 64-bit quantities and
|
|
// therefore the compiler rounds the total size up to
|
|
// an even multiple of 64 bits (or even more, the IA64
|
|
// structures are 16-byte aligned). Internal structures
|
|
// are also aligned, so make sure that we account for any
|
|
// padding. Knowledge of which structures need which
|
|
// padding pretty much has to be hard-coded in.
|
|
//
|
|
|
|
C_ASSERT((sizeof(DBGKD_WAIT_STATE_CHANGE64) & 15) == 0);
|
|
|
|
SizeofStateChange =
|
|
sizeof(DBGKD_WAIT_STATE_CHANGE64) +
|
|
m_TypeInfo.SizeControlReport +
|
|
m_TypeInfo.SizeTargetContext;
|
|
|
|
// We shouldn't need to align the base of the control report
|
|
// so copy the base data and control report.
|
|
Offset = sizeof(DBGKD_WAIT_STATE_CHANGE64) +
|
|
m_TypeInfo.SizeControlReport;
|
|
memcpy(StateChange, Ws64, Offset);
|
|
|
|
//
|
|
// Add alignment padding before the context.
|
|
//
|
|
|
|
switch(m_MachineType)
|
|
{
|
|
case IMAGE_FILE_MACHINE_IA64:
|
|
Align = 15;
|
|
break;
|
|
default:
|
|
Align = 7;
|
|
break;
|
|
}
|
|
|
|
Pad = ((Offset + Align) & ~Align) - Offset;
|
|
Offset += Pad;
|
|
SizeofStateChange += Pad;
|
|
|
|
//
|
|
// Add alignment padding after the context.
|
|
//
|
|
|
|
Offset += m_TypeInfo.SizeTargetContext;
|
|
Pad = ((Offset + Align) & ~Align) - Offset;
|
|
SizeofStateChange += Pad;
|
|
}
|
|
else
|
|
{
|
|
PDBGKD_ANY_WAIT_STATE_CHANGE WsAny =
|
|
(PDBGKD_ANY_WAIT_STATE_CHANGE)LocalStateChange;
|
|
SizeofStateChange = sizeof(*WsAny);
|
|
*StateChange = *WsAny;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SizeofStateChange =
|
|
sizeof(DBGKD_WAIT_STATE_CHANGE32) +
|
|
m_TypeInfo.SizeControlReport +
|
|
m_TypeInfo.SizeTargetContext;
|
|
WaitStateChange32ToAny((PDBGKD_WAIT_STATE_CHANGE32)LocalStateChange,
|
|
m_TypeInfo.SizeControlReport,
|
|
StateChange);
|
|
}
|
|
|
|
if (StateChange->NewState & DbgKdAlternateStateChange)
|
|
{
|
|
// This state change came from the alternate partition.
|
|
g_EventTarget = FindTargetBySystemId(DBGKD_PARTITION_ALTERNATE);
|
|
|
|
StateChange->NewState &= ~DbgKdAlternateStateChange;
|
|
}
|
|
else
|
|
{
|
|
// Default partition state change.
|
|
g_EventTarget = FindTargetBySystemId(DBGKD_PARTITION_DEFAULT);
|
|
}
|
|
|
|
if (!g_EventTarget)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
((ConnLiveKernelTargetInfo*)g_EventTarget)->m_CurrentPartition = TRUE;
|
|
|
|
switch(StateChange->NewState)
|
|
{
|
|
case DbgKdExceptionStateChange:
|
|
case DbgKdCommandStringStateChange:
|
|
if (BufferLength <
|
|
(m_Transport->s_PacketHeader.ByteCount - SizeofStateChange))
|
|
{
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
else
|
|
{
|
|
Data = (UCHAR *)LocalStateChange + SizeofStateChange;
|
|
memcpy(Buffer, Data,
|
|
m_Transport->s_PacketHeader.ByteCount -
|
|
SizeofStateChange);
|
|
}
|
|
break;
|
|
case DbgKdLoadSymbolsStateChange:
|
|
if ( BufferLength < StateChange->u.LoadSymbols.PathNameLength )
|
|
{
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
else
|
|
{
|
|
Data = ((UCHAR *) LocalStateChange) +
|
|
m_Transport->s_PacketHeader.ByteCount -
|
|
(int)StateChange->u.LoadSymbols.PathNameLength;
|
|
memcpy(Buffer, Data,
|
|
(int)StateChange->u.LoadSymbols.PathNameLength);
|
|
}
|
|
break;
|
|
default:
|
|
ErrOut("Unknown state change type %X\n", StateChange->NewState);
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
#define EXCEPTION_CODE StateChange->u.Exception.ExceptionRecord.ExceptionCode
|
|
#define FIRST_CHANCE StateChange->u.Exception.FirstChance
|
|
|
|
ULONG
|
|
ConnLiveKernelTargetInfo::ProcessStateChange(PDBGKD_ANY_WAIT_STATE_CHANGE StateChange,
|
|
PCHAR StateChangeData)
|
|
{
|
|
ULONG EventStatus;
|
|
|
|
EventOut(">>> State change event %X, proc %d of %d\n",
|
|
StateChange->NewState, StateChange->Processor,
|
|
StateChange->NumberProcessors);
|
|
|
|
if (!m_NumProcessors)
|
|
{
|
|
dprintf("Kernel Debugger connection established.%s\n",
|
|
(g_EngOptions & DEBUG_ENGOPT_INITIAL_BREAK) ?
|
|
" (Initial Breakpoint requested)" : "");
|
|
|
|
// Initial connection after a fresh boot may only report
|
|
// a single processor as the others haven't started yet.
|
|
m_NumProcessors = StateChange->NumberProcessors;
|
|
|
|
CreateVirtualProcess(m_NumProcessors);
|
|
|
|
g_EventProcessSysId = m_ProcessHead->m_SystemId;
|
|
g_EventThreadSysId = VIRTUAL_THREAD_ID(StateChange->Processor);
|
|
FindEventProcessThread();
|
|
|
|
QueryKernelInfo(g_EventThread, TRUE);
|
|
|
|
// Now that we have the data block
|
|
// we can retrieve processor information.
|
|
InitializeForProcessor();
|
|
|
|
RemoveAllTargetBreakpoints();
|
|
|
|
OutputVersion();
|
|
|
|
NotifyDebuggeeActivation();
|
|
}
|
|
else
|
|
{
|
|
// Initial connection after a fresh boot may only report
|
|
// a single processor as the others haven't started yet.
|
|
// Pick up any additional processors.
|
|
if (StateChange->NumberProcessors > m_NumProcessors)
|
|
{
|
|
m_ProcessHead->
|
|
CreateVirtualThreads(m_NumProcessors,
|
|
StateChange->NumberProcessors -
|
|
m_NumProcessors);
|
|
m_NumProcessors = StateChange->NumberProcessors;
|
|
}
|
|
|
|
g_EventProcessSysId = m_ProcessHead->m_SystemId;
|
|
g_EventThreadSysId = VIRTUAL_THREAD_ID(StateChange->Processor);
|
|
FindEventProcessThread();
|
|
}
|
|
|
|
g_TargetEventPc = StateChange->ProgramCounter;
|
|
g_ControlReport = &StateChange->AnyControlReport;
|
|
if (g_EventThread)
|
|
{
|
|
g_EventThread->m_DataOffset = StateChange->Thread;
|
|
}
|
|
|
|
//
|
|
// If the reported instruction stream contained breakpoints
|
|
// the kernel automatically removed them. We need to
|
|
// ensure that breakpoints get reinserted properly if
|
|
// that's the case.
|
|
//
|
|
|
|
ULONG Count;
|
|
|
|
switch(m_MachineType)
|
|
{
|
|
case IMAGE_FILE_MACHINE_IA64:
|
|
Count = g_ControlReport->IA64ControlReport.InstructionCount;
|
|
break;
|
|
case IMAGE_FILE_MACHINE_I386:
|
|
Count = g_ControlReport->X86ControlReport.InstructionCount;
|
|
break;
|
|
case IMAGE_FILE_MACHINE_AMD64:
|
|
Count = g_ControlReport->Amd64ControlReport.InstructionCount;
|
|
break;
|
|
}
|
|
|
|
if (CheckBreakpointInsertedInRange(g_EventProcess,
|
|
g_TargetEventPc,
|
|
g_TargetEventPc + Count - 1))
|
|
{
|
|
SuspendExecution();
|
|
RemoveBreakpoints();
|
|
}
|
|
|
|
if (StateChange->NewState == DbgKdExceptionStateChange)
|
|
{
|
|
//
|
|
// Read the system range start address from the target system.
|
|
//
|
|
|
|
if (m_SystemRangeStart == 0)
|
|
{
|
|
QueryKernelInfo(g_EventThread, FALSE);
|
|
}
|
|
|
|
EventOut("Exception %X at %p\n", EXCEPTION_CODE, g_TargetEventPc);
|
|
|
|
if (EXCEPTION_CODE == STATUS_BREAKPOINT ||
|
|
EXCEPTION_CODE == STATUS_SINGLE_STEP ||
|
|
EXCEPTION_CODE == STATUS_WX86_BREAKPOINT ||
|
|
EXCEPTION_CODE == STATUS_WX86_SINGLE_STEP)
|
|
{
|
|
EventStatus = ProcessBreakpointOrStepException
|
|
(&StateChange->u.Exception.ExceptionRecord,
|
|
StateChange->u.Exception.FirstChance);
|
|
}
|
|
else if (EXCEPTION_CODE == STATUS_WAKE_SYSTEM_DEBUGGER)
|
|
{
|
|
// The target has requested that the debugger
|
|
// become active so just break in.
|
|
EventStatus = DEBUG_STATUS_BREAK;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The interlocked SList code has a by-design faulting
|
|
// case, so ignore AVs at that particular symbol.
|
|
//
|
|
|
|
if (EXCEPTION_CODE == STATUS_ACCESS_VIOLATION &&
|
|
StateChange->u.Exception.FirstChance)
|
|
{
|
|
CHAR ExSym[MAX_SYMBOL_LEN];
|
|
ULONG64 ExDisp;
|
|
|
|
GetSymbol(StateChange->
|
|
u.Exception.ExceptionRecord.ExceptionAddress,
|
|
ExSym, sizeof(ExSym), &ExDisp);
|
|
if (ExDisp == 0 &&
|
|
!_stricmp(ExSym, "nt!ExpInterlockedPopEntrySListFault"))
|
|
{
|
|
return DEBUG_STATUS_GO_NOT_HANDLED;
|
|
}
|
|
}
|
|
|
|
EventStatus =
|
|
NotifyExceptionEvent(&StateChange->u.Exception.ExceptionRecord,
|
|
StateChange->u.Exception.FirstChance,
|
|
FALSE);
|
|
}
|
|
}
|
|
else if (StateChange->NewState == DbgKdLoadSymbolsStateChange)
|
|
{
|
|
if (StateChange->u.LoadSymbols.UnloadSymbols)
|
|
{
|
|
if (StateChange->u.LoadSymbols.PathNameLength == 0 &&
|
|
StateChange->u.LoadSymbols.ProcessId == 0)
|
|
{
|
|
if (StateChange->u.LoadSymbols.BaseOfDll == (ULONG64)KD_REBOOT ||
|
|
StateChange->u.LoadSymbols.BaseOfDll == (ULONG64)KD_HIBERNATE)
|
|
{
|
|
KdContinue(DBG_CONTINUE, NULL);
|
|
DebuggeeReset(StateChange->u.LoadSymbols.BaseOfDll ==
|
|
KD_REBOOT ?
|
|
DEBUG_SESSION_REBOOT :
|
|
DEBUG_SESSION_HIBERNATE,
|
|
TRUE);
|
|
EventStatus = DEBUG_STATUS_NO_DEBUGGEE;
|
|
}
|
|
else
|
|
{
|
|
ErrOut("Invalid module unload state change\n");
|
|
EventStatus = DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EventStatus = NotifyUnloadModuleEvent
|
|
(StateChangeData, StateChange->u.LoadSymbols.BaseOfDll);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImageInfo* Image;
|
|
CHAR FileName[_MAX_FNAME];
|
|
CHAR Ext[_MAX_EXT];
|
|
CHAR ImageName[_MAX_FNAME + _MAX_EXT];
|
|
CHAR ModNameBuf[_MAX_FNAME + _MAX_EXT + 1];
|
|
PSTR ModName = ModNameBuf;
|
|
|
|
ModName[0] = '\0';
|
|
_splitpath( StateChangeData, NULL, NULL, FileName, Ext );
|
|
sprintf( ImageName, "%s%s", FileName, Ext );
|
|
if (_stricmp(Ext, ".sys") == 0)
|
|
{
|
|
Image = g_EventProcess ? g_EventProcess->m_ImageHead : NULL;
|
|
while (Image)
|
|
{
|
|
if (_stricmp(ImageName, Image->m_ImagePath) == 0)
|
|
{
|
|
PSTR Dot;
|
|
|
|
ModName[0] = 'c';
|
|
strcpy( &ModName[1], ImageName );
|
|
Dot = strchr( ModName, '.' );
|
|
if (Dot)
|
|
{
|
|
*Dot = '\0';
|
|
}
|
|
|
|
ModName[8] = '\0';
|
|
break;
|
|
}
|
|
|
|
Image = Image->m_Next;
|
|
}
|
|
}
|
|
else if (StateChange->u.LoadSymbols.BaseOfDll ==
|
|
m_KdDebuggerData.KernBase)
|
|
{
|
|
//
|
|
// Recognize the kernel module.
|
|
//
|
|
ModName = KERNEL_MODULE_NAME;
|
|
}
|
|
|
|
EventStatus = NotifyLoadModuleEvent(
|
|
0, StateChange->u.LoadSymbols.BaseOfDll,
|
|
StateChange->u.LoadSymbols.SizeOfImage,
|
|
ModName[0] ? ModName : NULL, ImageName,
|
|
StateChange->u.LoadSymbols.CheckSum, 0,
|
|
StateChange->u.LoadSymbols.BaseOfDll < m_SystemRangeStart);
|
|
|
|
//
|
|
// Attempt to preload the machine type of the image
|
|
// as we expect the headers to be available at this
|
|
// point, whereas they may be paged out later.
|
|
//
|
|
|
|
Image = g_EventProcess ? g_EventProcess->
|
|
FindImageByOffset(StateChange->u.LoadSymbols.BaseOfDll,
|
|
FALSE) : NULL;
|
|
if (Image)
|
|
{
|
|
Image->GetMachineType();
|
|
}
|
|
}
|
|
}
|
|
else if (StateChange->NewState == DbgKdCommandStringStateChange)
|
|
{
|
|
PSTR Command;
|
|
|
|
//
|
|
// The state change data has two strings one after
|
|
// the other. The first is a name string identifying
|
|
// the originator of the command. The second is
|
|
// the command itself.
|
|
//
|
|
|
|
Command = StateChangeData + strlen(StateChangeData) + 1;
|
|
_snprintf(g_LastEventDesc, sizeof(g_LastEventDesc) - 1,
|
|
"%.48s command: '%.192s'",
|
|
StateChangeData, Command);
|
|
EventStatus = ExecuteEventCommand(DEBUG_STATUS_NO_CHANGE, NULL,
|
|
Command);
|
|
|
|
// Break in if the command didn't explicitly continue.
|
|
if (EventStatus == DEBUG_STATUS_NO_CHANGE)
|
|
{
|
|
EventStatus = DEBUG_STATUS_BREAK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Invalid NewState in state change record.
|
|
//
|
|
ErrOut("\nUNEXPECTED STATE CHANGE %08lx\n\n",
|
|
StateChange->NewState);
|
|
|
|
EventStatus = DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
#undef EXCEPTION_CODE
|
|
#undef FIRST_CHANCE
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// LocalLiveKernelTargetInfo wait methods.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
LocalLiveKernelTargetInfo::WaitInitialize(ULONG Flags,
|
|
ULONG Timeout,
|
|
WAIT_INIT_TYPE Type,
|
|
PULONG DesiredTimeout)
|
|
{
|
|
*DesiredTimeout = Timeout;
|
|
m_EventPossible = m_FirstWait;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
LocalLiveKernelTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout,
|
|
ULONG ElapsedTime, PULONG EventStatus)
|
|
{
|
|
HRESULT Status;
|
|
SYSTEM_INFO SysInfo;
|
|
|
|
if (!m_FirstWait)
|
|
{
|
|
// A wait has already been done. Local kernels
|
|
// can only generate a single event so further
|
|
// waiting is not possible.
|
|
return S_FALSE;
|
|
}
|
|
|
|
g_EventTarget = this;
|
|
|
|
GetSystemInfo(&SysInfo);
|
|
m_NumProcessors = SysInfo.dwNumberOfProcessors;
|
|
|
|
// Set this right here since we know kernel debugging only works on
|
|
// recent systems using the 64 bit protocol.
|
|
m_KdApi64 = TRUE;
|
|
|
|
if ((Status = InitFromKdVersion()) != S_OK)
|
|
{
|
|
delete g_EventTarget;
|
|
g_EventTarget = NULL;
|
|
return Status;
|
|
}
|
|
|
|
// This is the first wait. Simulate any
|
|
// necessary events such as process and thread
|
|
// creations and image loads.
|
|
|
|
CreateVirtualProcess(m_NumProcessors);
|
|
|
|
g_EventProcessSysId = m_ProcessHead->m_SystemId;
|
|
// Current processor always starts at zero.
|
|
g_EventThreadSysId = VIRTUAL_THREAD_ID(0);
|
|
FindEventProcessThread();
|
|
|
|
QueryKernelInfo(g_EventThread, TRUE);
|
|
|
|
// Now that we have the data block
|
|
// we can retrieve processor information.
|
|
InitializeForProcessor();
|
|
|
|
// Clear the global state change just in case somebody's
|
|
// directly accessing it somewhere.
|
|
ZeroMemory(&g_StateChange, sizeof(g_StateChange));
|
|
g_StateChangeData = g_StateChangeBuffer;
|
|
g_StateChangeBuffer[0] = 0;
|
|
|
|
g_EngStatus |= ENG_STATUS_STATE_CHANGED;
|
|
|
|
// Do not provide a control report; this will force
|
|
// such information to come from context retrieval.
|
|
g_ControlReport = NULL;
|
|
|
|
// There isn't a current PC, let it be discovered.
|
|
g_TargetEventPc = 0;
|
|
|
|
// Warn if the kernel debugging code isn't available
|
|
// as that often causes problems.
|
|
if (g_NtDllCalls.NtQuerySystemInformation)
|
|
{
|
|
SYSTEM_KERNEL_DEBUGGER_INFORMATION KdInfo;
|
|
|
|
if (!NT_SUCCESS(g_NtDllCalls.NtQuerySystemInformation
|
|
(SystemKernelDebuggerInformation,
|
|
&KdInfo, sizeof(KdInfo), NULL)) ||
|
|
!KdInfo.KernelDebuggerEnabled)
|
|
{
|
|
WarnOut("*****************************************"
|
|
"**************************************\n");
|
|
WarnOut("WARNING: Local kernel debugging requires "
|
|
"booting with /debug to work optimally.\n");
|
|
WarnOut("*****************************************"
|
|
"**************************************\n");
|
|
}
|
|
}
|
|
|
|
OutputVersion();
|
|
|
|
NotifyDebuggeeActivation();
|
|
|
|
*EventStatus = DEBUG_STATUS_BREAK;
|
|
return S_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ExdiLiveKernelTargetInfo wait methods.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::WaitInitialize(ULONG Flags,
|
|
ULONG Timeout,
|
|
WAIT_INIT_TYPE Type,
|
|
PULONG DesiredTimeout)
|
|
{
|
|
*DesiredTimeout = Timeout;
|
|
m_EventPossible = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::ReleaseLastEvent(ULONG ContinueStatus)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (!g_EventProcessSysId)
|
|
{
|
|
// No event to release.
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// eXDI deals with hardware exceptions, not software
|
|
// exceptions, so there's no concept of handled/not-handled
|
|
// and first/second-chance.
|
|
//
|
|
|
|
if (g_EngDefer & ENG_DEFER_HARDWARE_TRACING)
|
|
{
|
|
// Processor trace flag was set. eXDI can change
|
|
// the trace flag itself, though, so use the
|
|
// official eXDI stepping methods rather than
|
|
// rely on the trace flag. This will result
|
|
// in a single instruction execution, after
|
|
// which the trace flag will be clear so
|
|
// go ahead and clear the defer flag.
|
|
Status = m_Server->DoSingleStep();
|
|
if (Status == S_OK)
|
|
{
|
|
g_EngDefer &= ~ENG_DEFER_HARDWARE_TRACING;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = m_Server->Run();
|
|
}
|
|
if (Status != S_OK)
|
|
{
|
|
ErrOut("IeXdiServer::Run failed, 0x%X\n", Status);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout,
|
|
ULONG ElapsedTime, PULONG EventStatus)
|
|
{
|
|
HRESULT Status;
|
|
DWORD Cookie;
|
|
|
|
if ((Status = m_Server->
|
|
StartNotifyingRunChg(&m_RunChange, &Cookie)) != S_OK)
|
|
{
|
|
ErrOut("IeXdiServer::StartNotifyingRunChg failed, 0x%X\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
RUN_STATUS_TYPE RunStatus;
|
|
|
|
if ((Status = m_Server->
|
|
GetRunStatus(&RunStatus, &m_RunChange.m_HaltReason,
|
|
&m_RunChange.m_ExecAddress,
|
|
&m_RunChange.m_ExceptionCode)) != S_OK)
|
|
{
|
|
m_Server->StopNotifyingRunChg(Cookie);
|
|
ErrOut("IeXdiServer::GetRunStatus failed, 0x%X\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
DWORD WaitStatus;
|
|
|
|
if (RunStatus == rsRunning)
|
|
{
|
|
SUSPEND_ENGINE();
|
|
|
|
// We need to run a message pump so COM
|
|
// can deliver calls properly.
|
|
for (;;)
|
|
{
|
|
if (g_EngStatus & ENG_STATUS_EXIT_CURRENT_WAIT)
|
|
{
|
|
WaitStatus = WAIT_FAILED;
|
|
SetLastError(ERROR_IO_PENDING);
|
|
break;
|
|
}
|
|
|
|
WaitStatus = MsgWaitForMultipleObjects(1, &m_RunChange.m_Event,
|
|
FALSE, Timeout,
|
|
QS_ALLEVENTS);
|
|
if (WaitStatus == WAIT_OBJECT_0 + 1)
|
|
{
|
|
MSG Msg;
|
|
|
|
if (GetMessage(&Msg, NULL, 0, 0))
|
|
{
|
|
TranslateMessage(&Msg);
|
|
DispatchMessage(&Msg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We either successfully waited, timed-out or failed.
|
|
// Break out to handle it.
|
|
break;
|
|
}
|
|
}
|
|
|
|
RESUME_ENGINE();
|
|
}
|
|
else
|
|
{
|
|
WaitStatus = WAIT_OBJECT_0;
|
|
}
|
|
|
|
m_Server->StopNotifyingRunChg(Cookie);
|
|
// Make sure we're not leaving the event set.
|
|
ResetEvent(m_RunChange.m_Event);
|
|
|
|
if (WaitStatus == WAIT_TIMEOUT)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
else if (WaitStatus != WAIT_OBJECT_0)
|
|
{
|
|
Status = WIN32_LAST_STATUS();
|
|
ErrOut("WaitForSingleObject failed, 0x%X\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
EventOut(">>> RunChange halt reason %d\n",
|
|
m_RunChange.m_HaltReason);
|
|
|
|
if (!IS_MACHINE_SET(this))
|
|
{
|
|
dprintf("Kernel Debugger connection established\n");
|
|
|
|
g_EventTarget = this;
|
|
|
|
//
|
|
// Try to figure out the processor configuration
|
|
// via the defined Ioctl.
|
|
//
|
|
|
|
// Default to one.
|
|
m_NumProcessors = 1;
|
|
|
|
if (DBGENG_EXDI_IOC_IDENTIFY_PROCESSORS > m_IoctlMin &&
|
|
DBGENG_EXDI_IOC_IDENTIFY_PROCESSORS < m_IoctlMax)
|
|
{
|
|
DBGENG_EXDI_IOCTL_BASE_IN IoctlIn;
|
|
DBGENG_EXDI_IOCTL_IDENTIFY_PROCESSORS_OUT IoctlOut;
|
|
ULONG OutUsed;
|
|
|
|
IoctlIn.Code = DBGENG_EXDI_IOC_IDENTIFY_PROCESSORS;
|
|
if (m_Server->
|
|
Ioctl(sizeof(IoctlIn), (PBYTE)&IoctlIn,
|
|
sizeof(IoctlOut), &OutUsed, (PBYTE)&IoctlOut) == S_OK)
|
|
{
|
|
m_NumProcessors = IoctlOut.NumberProcessors;
|
|
}
|
|
}
|
|
|
|
// eXDI kernels are always treated as Win2K so
|
|
// it's assumed it uses the 64-bit API.
|
|
if (m_KdSupport == EXDI_KD_NONE)
|
|
{
|
|
m_KdApi64 = TRUE;
|
|
m_SystemVersion = NT_SVER_W2K;
|
|
}
|
|
|
|
if ((Status = InitFromKdVersion()) != S_OK)
|
|
{
|
|
DeleteSystemInfo();
|
|
ResetSystemInfo();
|
|
g_EventTarget = NULL;
|
|
return Status;
|
|
}
|
|
|
|
CreateVirtualProcess(m_NumProcessors);
|
|
|
|
g_EventProcessSysId = m_ProcessHead->m_SystemId;
|
|
g_EventThreadSysId = VIRTUAL_THREAD_ID(GetCurrentProcessor());
|
|
FindEventProcessThread();
|
|
|
|
//
|
|
// Load kernel symbols.
|
|
//
|
|
|
|
if (m_ActualSystemVersion > NT_SVER_START &&
|
|
m_ActualSystemVersion < NT_SVER_END)
|
|
{
|
|
QueryKernelInfo(g_EventThread, TRUE);
|
|
}
|
|
else
|
|
{
|
|
// Initialize some debugger data fields from known
|
|
// information as there isn't a real data block.
|
|
m_KdDebuggerData.MmPageSize =
|
|
m_Machine->m_PageSize;
|
|
|
|
if (m_MachineType == IMAGE_FILE_MACHINE_AMD64)
|
|
{
|
|
// AMD64 always operates in PAE mode.
|
|
m_KdDebuggerData.PaeEnabled = TRUE;
|
|
}
|
|
}
|
|
|
|
// Now that we have the data block
|
|
// we can retrieve processor information.
|
|
InitializeForProcessor();
|
|
|
|
OutputVersion();
|
|
|
|
NotifyDebuggeeActivation();
|
|
}
|
|
else
|
|
{
|
|
g_EventTarget = this;
|
|
g_EventProcessSysId = m_ProcessHead->m_SystemId;
|
|
g_EventThreadSysId = VIRTUAL_THREAD_ID(GetCurrentProcessor());
|
|
FindEventProcessThread();
|
|
}
|
|
|
|
g_TargetEventPc = m_RunChange.m_ExecAddress;
|
|
g_ControlReport = NULL;
|
|
g_StateChangeData = NULL;
|
|
|
|
g_EngStatus |= ENG_STATUS_STATE_CHANGED;
|
|
|
|
*EventStatus = ProcessRunChange(m_RunChange.m_HaltReason,
|
|
m_RunChange.m_ExceptionCode);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG
|
|
ExdiLiveKernelTargetInfo::ProcessRunChange(ULONG HaltReason,
|
|
ULONG ExceptionCode)
|
|
{
|
|
ULONG EventStatus;
|
|
EXCEPTION_RECORD64 Record;
|
|
DBGENG_EXDI_IOCTL_BASE_IN IoctlBaseIn;
|
|
ULONG OutUsed;
|
|
|
|
// Assume no breakpoint information.
|
|
m_BpHit.Type = DBGENG_EXDI_IOCTL_BREAKPOINT_NONE;
|
|
|
|
switch(HaltReason)
|
|
{
|
|
case hrUser:
|
|
case hrUnknown:
|
|
// User requested break in.
|
|
// Unknown breakin also seems to be the status at power-up.
|
|
EventStatus = DEBUG_STATUS_BREAK;
|
|
break;
|
|
|
|
case hrException:
|
|
// Fake an exception record.
|
|
ZeroMemory(&Record, sizeof(Record));
|
|
// The exceptions reported are hardware exceptions so
|
|
// there's no easy mapping to NT exception codes.
|
|
// Just report them as access violations.
|
|
Record.ExceptionCode = STATUS_ACCESS_VIOLATION;
|
|
Record.ExceptionAddress = g_TargetEventPc;
|
|
// Hardware exceptions are always severe so always
|
|
// report them as second-chance.
|
|
EventStatus = NotifyExceptionEvent(&Record, FALSE, FALSE);
|
|
break;
|
|
|
|
case hrBp:
|
|
//
|
|
// Try and get which breakpoint it was.
|
|
//
|
|
|
|
if (DBGENG_EXDI_IOC_GET_BREAKPOINT_HIT > m_IoctlMin &&
|
|
DBGENG_EXDI_IOC_GET_BREAKPOINT_HIT < m_IoctlMax)
|
|
{
|
|
DBGENG_EXDI_IOCTL_GET_BREAKPOINT_HIT_OUT IoctlOut;
|
|
|
|
IoctlBaseIn.Code = DBGENG_EXDI_IOC_GET_BREAKPOINT_HIT;
|
|
if (m_Server->
|
|
Ioctl(sizeof(IoctlBaseIn), (PBYTE)&IoctlBaseIn,
|
|
sizeof(IoctlOut), &OutUsed, (PBYTE)&IoctlOut) != S_OK)
|
|
{
|
|
m_BpHit.Type = DBGENG_EXDI_IOCTL_BREAKPOINT_NONE;
|
|
}
|
|
}
|
|
|
|
// Fake a breakpoint exception record.
|
|
ZeroMemory(&Record, sizeof(Record));
|
|
Record.ExceptionCode = STATUS_BREAKPOINT;
|
|
Record.ExceptionAddress = g_TargetEventPc;
|
|
EventStatus = ProcessBreakpointOrStepException(&Record, TRUE);
|
|
break;
|
|
|
|
case hrStep:
|
|
// Fake a single-step exception record.
|
|
ZeroMemory(&Record, sizeof(Record));
|
|
Record.ExceptionCode = STATUS_SINGLE_STEP;
|
|
Record.ExceptionAddress = g_TargetEventPc;
|
|
EventStatus = ProcessBreakpointOrStepException(&Record, TRUE);
|
|
break;
|
|
|
|
default:
|
|
ErrOut("Unknown HALT_REASON %d\n", HaltReason);
|
|
EventStatus = DEBUG_STATUS_BREAK;
|
|
break;
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// UserTargetInfo wait methods.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
SynthesizeWakeEvent(LPDEBUG_EVENT64 Event,
|
|
ULONG ProcessId, ULONG ThreadId)
|
|
{
|
|
// Fake up an event.
|
|
ZeroMemory(Event, sizeof(*Event));
|
|
Event->dwDebugEventCode = EXCEPTION_DEBUG_EVENT;
|
|
Event->dwProcessId = ProcessId;
|
|
Event->dwThreadId = ThreadId;
|
|
Event->u.Exception.ExceptionRecord.ExceptionCode =
|
|
STATUS_WAKE_SYSTEM_DEBUGGER;
|
|
Event->u.Exception.dwFirstChance = TRUE;
|
|
}
|
|
|
|
#define THREADS_ALLOC 256
|
|
|
|
HRESULT
|
|
CreateNonInvasiveProcessAndThreads(PUSER_DEBUG_SERVICES Services,
|
|
ULONG ProcessId, ULONG Flags, ULONG Options,
|
|
PULONG InitialThreadId)
|
|
{
|
|
ULONG64 Process;
|
|
PUSER_THREAD_INFO Threads, ThreadBuffer;
|
|
ULONG ThreadsAlloc = 0;
|
|
ULONG ThreadCount;
|
|
HRESULT Status;
|
|
ULONG i;
|
|
ULONG ProcInfoFlags = (Flags & ENG_PROC_NO_SUSPEND_RESUME) ?
|
|
DBGSVC_PROC_INFO_NO_SUSPEND : 0;
|
|
|
|
//
|
|
// Retrieve process and thread information. This
|
|
// requires a thread buffer of unknown size and
|
|
// so involves a bit of trial and error.
|
|
//
|
|
|
|
for (;;)
|
|
{
|
|
ThreadsAlloc += THREADS_ALLOC;
|
|
ThreadBuffer = new USER_THREAD_INFO[ThreadsAlloc];
|
|
if (ThreadBuffer == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
if ((Status = Services->GetProcessInfo(ProcessId, ProcInfoFlags,
|
|
&Process, ThreadBuffer,
|
|
ThreadsAlloc,
|
|
&ThreadCount)) != S_OK &&
|
|
Status != S_FALSE)
|
|
{
|
|
delete [] ThreadBuffer;
|
|
return Status;
|
|
}
|
|
|
|
if (ThreadCount <= ThreadsAlloc)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// The threads retrieved were suspended so resume them
|
|
// and close handles.
|
|
for (i = 0; i < ThreadsAlloc; i++)
|
|
{
|
|
if (!(Flags & ENG_PROC_NO_SUSPEND_RESUME))
|
|
{
|
|
Services->ResumeThreads(1, &ThreadBuffer[i].Handle, NULL);
|
|
}
|
|
Services->CloseHandle(ThreadBuffer[i].Handle);
|
|
}
|
|
delete [] ThreadBuffer;
|
|
|
|
// Set the allocation request size to what the
|
|
// reported count of threads was. The count may
|
|
// change between now and the next call so let
|
|
// the normal allocation extension get added in
|
|
// also to provide extra space for new threads.
|
|
ThreadsAlloc = ThreadCount;
|
|
}
|
|
|
|
//
|
|
// Create the process and thread structures from
|
|
// the retrieved data.
|
|
//
|
|
|
|
Threads = ThreadBuffer;
|
|
|
|
g_EngNotify++;
|
|
|
|
// Create the fake kernel process and initial thread.
|
|
g_EventProcessSysId = ProcessId;
|
|
g_EventThreadSysId = Threads->Id;
|
|
*InitialThreadId = Threads->Id;
|
|
NotifyCreateProcessEvent(0, GloballyUniqueProcessHandle(g_EventTarget,
|
|
Process),
|
|
Process, 0, 0, NULL, NULL, 0, 0,
|
|
Threads->Handle, 0, 0,
|
|
Flags | ENG_PROC_THREAD_CLOSE_HANDLE,
|
|
Options, ENG_PROC_THREAD_CLOSE_HANDLE,
|
|
FALSE, 0, FALSE);
|
|
|
|
// Create any remaining threads.
|
|
while (--ThreadCount > 0)
|
|
{
|
|
Threads++;
|
|
g_EventThreadSysId = Threads->Id;
|
|
NotifyCreateThreadEvent(Threads->Handle, 0, 0,
|
|
ENG_PROC_THREAD_CLOSE_HANDLE);
|
|
}
|
|
|
|
g_EngNotify--;
|
|
|
|
delete [] ThreadBuffer;
|
|
|
|
// Don't leave event variables set as these
|
|
// weren't true events.
|
|
g_EventProcessSysId = 0;
|
|
g_EventThreadSysId = 0;
|
|
g_EventProcess = NULL;
|
|
g_EventThread = NULL;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ExamineActiveProcess(PUSER_DEBUG_SERVICES Services,
|
|
ULONG ProcessId, ULONG Flags, ULONG Options,
|
|
LPDEBUG_EVENT64 Event)
|
|
{
|
|
HRESULT Status;
|
|
ULONG InitialThreadId;
|
|
|
|
if ((Status = CreateNonInvasiveProcessAndThreads
|
|
(Services, ProcessId, Flags, Options, &InitialThreadId)) != S_OK)
|
|
{
|
|
ErrOut("Unable to examine process id %d, %s\n",
|
|
ProcessId, FormatStatusCode(Status));
|
|
return Status;
|
|
}
|
|
|
|
if (Flags & ENG_PROC_EXAMINED)
|
|
{
|
|
WarnOut("WARNING: Process %d is not attached as a debuggee\n",
|
|
ProcessId);
|
|
WarnOut(" The process can be examined but debug "
|
|
"events will not be received\n");
|
|
}
|
|
|
|
SynthesizeWakeEvent(Event, ProcessId, InitialThreadId);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// When waiting for an attach we check process status relatively
|
|
// frequently. The overall timeout limit is also hard-coded
|
|
// as we expect some sort of debug event to always be delivered
|
|
// quickly.
|
|
#define ATTACH_PENDING_TIMEOUT 100
|
|
#define ATTACH_PENDING_TIMEOUT_LIMIT 60000
|
|
|
|
// When not waiting for an attach the wait only waits one second,
|
|
// then checks to see if things have changed in a way that
|
|
// affects the wait. All timeouts are given in multiples of
|
|
// this interval.
|
|
#define DEFAULT_WAIT_TIMEOUT 1000
|
|
|
|
// A message is printed after this timeout interval to
|
|
// let the user know a break-in is pending.
|
|
#define PENDING_BREAK_IN_MESSAGE_TIMEOUT_LIMIT 3000
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::WaitInitialize(ULONG Flags,
|
|
ULONG Timeout,
|
|
WAIT_INIT_TYPE Type,
|
|
PULONG DesiredTimeout)
|
|
{
|
|
ULONG AllPend = m_AllPendingFlags;
|
|
|
|
if (AllPend & ENG_PROC_ANY_ATTACH)
|
|
{
|
|
if (Type == WINIT_FIRST)
|
|
{
|
|
dprintf("*** wait with pending attach\n");
|
|
}
|
|
|
|
// While waiting for an attach we need to periodically
|
|
// check and see if the process has exited so we
|
|
// need to force a reasonably small timeout.
|
|
*DesiredTimeout = ATTACH_PENDING_TIMEOUT;
|
|
|
|
// Check and see if any of our pending processes
|
|
// has died unexpectedly.
|
|
VerifyPendingProcesses();
|
|
}
|
|
else
|
|
{
|
|
// We might be waiting on a break-in. Keep timeouts moderate
|
|
// to deal with apps hung with a lock that prevents
|
|
// the break from happening. The timeout is
|
|
// still long enough so that no substantial amount
|
|
// of CPU time is consumed.
|
|
*DesiredTimeout = DEFAULT_WAIT_TIMEOUT;
|
|
}
|
|
|
|
m_DataBpAddrValid = FALSE;
|
|
|
|
if (Type != WINIT_NOT_FIRST)
|
|
{
|
|
m_BreakInMessage = FALSE;
|
|
}
|
|
|
|
m_EventPossible = m_ProcessHead || m_ProcessPending;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::ReleaseLastEvent(ULONG ContinueStatus)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (!g_EventTarget || !m_DeferContinueEvent)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if ((Status = m_Services->
|
|
ContinueEvent(ContinueStatus)) == S_OK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we got an out of memory error, wait again
|
|
//
|
|
|
|
if (Status != E_OUTOFMEMORY)
|
|
{
|
|
ErrOut("IUserDebugServices::ContinueEvent failed "
|
|
"with status 0x%X\n", Status);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
m_DeferContinueEvent = FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout,
|
|
ULONG ElapsedTime, PULONG EventStatus)
|
|
{
|
|
DEBUG_EVENT64 Event;
|
|
HRESULT Status;
|
|
ULONG EventUsed;
|
|
BOOL ContinueDefer;
|
|
BOOL BreakInTimeout;
|
|
PPENDING_PROCESS Pending;
|
|
ULONG PendingFlags = 0;
|
|
ULONG PendingOptions = DEBUG_PROCESS_ONLY_THIS_PROCESS;
|
|
ULONG ResumeProcId = 0;
|
|
ULONG AllPend;
|
|
BOOL InitSystem = FALSE;
|
|
|
|
//
|
|
// Check for partially initialized systems
|
|
// and query for all the system information that is needed.
|
|
// This needs to be done before the actual wait
|
|
// or examine so that the information is available
|
|
// for constructing processes and threads.
|
|
//
|
|
|
|
if (!m_MachinesInitialized)
|
|
{
|
|
if ((Status = InitFromServices()) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
InitSystem = TRUE;
|
|
}
|
|
|
|
AllPend = m_AllPendingFlags;
|
|
|
|
//
|
|
// There are two cases we want to handle timeouts for:
|
|
// 1. Timeout for a basic attach.
|
|
// 2. Timeout for a pending breakin.
|
|
// If neither of these things can happen we don't
|
|
// need to track delay time. Once one of those two
|
|
// things can happen we need to track delay time from
|
|
// the first time they became possible.
|
|
//
|
|
|
|
if ((AllPend & ENG_PROC_ANY_ATTACH) ||
|
|
(g_EngStatus & ENG_STATUS_PENDING_BREAK_IN))
|
|
{
|
|
if (m_WaitTimeBase == 0)
|
|
{
|
|
// Add one to avoid the case of ElapsedTime == 0
|
|
// causing a reinit the next time around.
|
|
m_WaitTimeBase = ElapsedTime + 1;
|
|
ElapsedTime = 0;
|
|
}
|
|
else
|
|
{
|
|
// Adjust for + 1 in time base above.
|
|
ElapsedTime++;
|
|
}
|
|
|
|
EventOut(">>> User elapsed time %d\n", ElapsedTime);
|
|
}
|
|
|
|
if ((AllPend & ENG_PROC_ANY_ATTACH) &&
|
|
ElapsedTime >= ATTACH_PENDING_TIMEOUT_LIMIT)
|
|
{
|
|
// Assume that the process has some kind
|
|
// of lock that's preventing the attach
|
|
// from succeeding and just do a soft attach.
|
|
AddExamineToPendingAttach();
|
|
}
|
|
|
|
// Refresh result of pending flags as they may have
|
|
// changed above due to AddAllExamineToPendingAttach.
|
|
if (m_AllPendingFlags & ENG_PROC_ANY_EXAMINE)
|
|
{
|
|
// If we're attaching noninvasively or reattaching
|
|
// and still haven't done the work go ahead and do it now.
|
|
Pending = FindPendingProcessByFlags(ENG_PROC_ANY_EXAMINE);
|
|
if (Pending == NULL)
|
|
{
|
|
DBG_ASSERT(FALSE);
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
g_EventTarget = this;
|
|
|
|
if ((Status = ExamineActiveProcess
|
|
(m_Services, Pending->Id, Pending->Flags,
|
|
Pending->Options, &Event)) != S_OK)
|
|
{
|
|
g_EventTarget = NULL;
|
|
g_EventTarget = NULL;
|
|
return Status;
|
|
}
|
|
|
|
// If we just started examining a process we
|
|
// suspended all the threads during enumeration.
|
|
// We need to resume them after the normal
|
|
// SuspendExecution suspend to get the suspend
|
|
// count back to normal.
|
|
ResumeProcId = Pending->Id;
|
|
|
|
PendingFlags = Pending->Flags;
|
|
PendingOptions = Pending->Options;
|
|
RemovePendingProcess(Pending);
|
|
EventUsed = sizeof(Event);
|
|
// This event is not a real continuable event.
|
|
ContinueDefer = FALSE;
|
|
BreakInTimeout = FALSE;
|
|
goto WaitDone;
|
|
}
|
|
|
|
if (g_EngStatus & ENG_STATUS_PENDING_BREAK_IN)
|
|
{
|
|
if (!m_BreakInMessage &&
|
|
ElapsedTime >= PENDING_BREAK_IN_MESSAGE_TIMEOUT_LIMIT)
|
|
{
|
|
dprintf("Break-in sent, waiting %d seconds...\n",
|
|
g_PendingBreakInTimeoutLimit);
|
|
m_BreakInMessage = TRUE;
|
|
}
|
|
|
|
if (ElapsedTime >= g_PendingBreakInTimeoutLimit * 1000)
|
|
{
|
|
// Assume that the process has some kind
|
|
// of lock that's preventing the break-in
|
|
// exception from coming through and
|
|
// just suspend to let the user look at things.
|
|
if (!m_ProcessHead ||
|
|
!m_ProcessHead->m_ThreadHead)
|
|
{
|
|
WarnOut("WARNING: Break-in timed out without "
|
|
"an available thread. Rewaiting.\n");
|
|
m_WaitTimeBase = 0;
|
|
}
|
|
else
|
|
{
|
|
WarnOut("WARNING: Break-in timed out, suspending.\n");
|
|
WarnOut(" This is usually caused by "
|
|
"another thread holding the loader lock\n");
|
|
SynthesizeWakeEvent(&Event,
|
|
m_ProcessHead->m_SystemId,
|
|
m_ProcessHead->m_ThreadHead->
|
|
m_SystemId);
|
|
EventUsed = sizeof(Event);
|
|
ContinueDefer = FALSE;
|
|
BreakInTimeout = TRUE;
|
|
g_EngStatus &= ~ENG_STATUS_PENDING_BREAK_IN;
|
|
Status = S_OK;
|
|
g_EventTarget = this;
|
|
goto WaitDone;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_EngStatus & ENG_STATUS_EXIT_CURRENT_WAIT)
|
|
{
|
|
return E_PENDING;
|
|
}
|
|
else
|
|
{
|
|
SUSPEND_ENGINE();
|
|
|
|
for (;;)
|
|
{
|
|
Status = m_Services->
|
|
WaitForEvent(Timeout, &Event, sizeof(Event), &EventUsed);
|
|
if (Status == E_OUTOFMEMORY)
|
|
{
|
|
// Allow memory pressure to ease and rewait.
|
|
Sleep(50);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
RESUME_ENGINE();
|
|
|
|
if (Status == S_FALSE)
|
|
{
|
|
return Status;
|
|
}
|
|
else if (Status != S_OK)
|
|
{
|
|
ErrOut("IUserDebugServices::WaitForEvent failed "
|
|
"with status 0x%X\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
g_EventTarget = this;
|
|
}
|
|
|
|
ContinueDefer = TRUE;
|
|
BreakInTimeout = FALSE;
|
|
|
|
WaitDone:
|
|
|
|
if (EventUsed == sizeof(DEBUG_EVENT32))
|
|
{
|
|
DEBUG_EVENT32 Event32 = *(DEBUG_EVENT32*)&Event;
|
|
DebugEvent32To64(&Event32, &Event);
|
|
}
|
|
else if (EventUsed != sizeof(DEBUG_EVENT64))
|
|
{
|
|
ErrOut("Event data corrupt\n");
|
|
return E_FAIL;
|
|
}
|
|
|
|
g_Target = g_EventTarget;
|
|
|
|
m_DeferContinueEvent = ContinueDefer;
|
|
m_BreakInTimeout = BreakInTimeout;
|
|
|
|
EventOut(">>> Debug event %u for %X.%X\n",
|
|
Event.dwDebugEventCode, Event.dwProcessId,
|
|
Event.dwThreadId);
|
|
|
|
g_EventProcessSysId = Event.dwProcessId;
|
|
g_EventThreadSysId = Event.dwThreadId;
|
|
|
|
// Look up the process and thread infos in the cases
|
|
// where they already exist.
|
|
if (Event.dwDebugEventCode != CREATE_PROCESS_DEBUG_EVENT &&
|
|
Event.dwDebugEventCode != CREATE_THREAD_DEBUG_EVENT)
|
|
{
|
|
FindEventProcessThread();
|
|
}
|
|
|
|
if (Event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
|
|
{
|
|
// If we're being notified of a new process take
|
|
// out the pending record for the process.
|
|
Pending = FindPendingProcessById(g_EventProcessSysId);
|
|
if (Pending == NULL &&
|
|
(m_AllPendingFlags & ENG_PROC_SYSTEM))
|
|
{
|
|
// Assume that this is the system process
|
|
// as we attached under a fake process ID so
|
|
// we can't check for a true match.
|
|
Pending = FindPendingProcessById(CSRSS_PROCESS_ID);
|
|
}
|
|
|
|
if (Pending != NULL)
|
|
{
|
|
PendingFlags = Pending->Flags;
|
|
PendingOptions = Pending->Options;
|
|
|
|
if (Pending->Flags & ENG_PROC_ATTACHED)
|
|
{
|
|
VerbOut("*** attach succeeded\n");
|
|
|
|
// If we're completing a full attach
|
|
// we are now a fully active debugger.
|
|
PendingFlags &= ~ENG_PROC_EXAMINED;
|
|
|
|
// Expect a break-in if a break thread
|
|
// was injected.
|
|
if (!(PendingFlags & ENG_PROC_NO_INITIAL_BREAK))
|
|
{
|
|
g_EngStatus |= ENG_STATUS_PENDING_BREAK_IN;
|
|
}
|
|
|
|
// If the process should be resumed
|
|
// mark that and it will happen just
|
|
// before leaving this routine.
|
|
if (PendingFlags & ENG_PROC_RESUME_AT_ATTACH)
|
|
{
|
|
ResumeProcId = Pending->Id;
|
|
}
|
|
}
|
|
|
|
RemovePendingProcess(Pending);
|
|
}
|
|
}
|
|
|
|
if (PendingFlags & ENG_PROC_ANY_EXAMINE)
|
|
{
|
|
PCSTR ArgsRet;
|
|
|
|
// We're examining the process rather than
|
|
// debugging it, so no module load events
|
|
// are going to come through. Reload from
|
|
// the system module list. This needs
|
|
// to work even if there isn't a path.
|
|
Reload(g_EventThread, "-s -P", &ArgsRet);
|
|
}
|
|
|
|
if (InitSystem)
|
|
{
|
|
NotifyDebuggeeActivation();
|
|
}
|
|
|
|
if ((Event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT ||
|
|
Event.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT) &&
|
|
(m_ServiceFlags & DBGSVC_CLOSE_PROC_THREAD_HANDLES))
|
|
{
|
|
PendingFlags |= ENG_PROC_THREAD_CLOSE_HANDLE;
|
|
}
|
|
|
|
g_EngStatus |= ENG_STATUS_STATE_CHANGED;
|
|
|
|
*EventStatus = ProcessDebugEvent(&Event, PendingFlags, PendingOptions);
|
|
|
|
// If we have an extra suspend count resume it out now
|
|
// that any normal suspends have been done and it's safe
|
|
// to remove the excess suspend.
|
|
if (ResumeProcId)
|
|
{
|
|
ProcessInfo* ExamProc =
|
|
FindProcessBySystemId(ResumeProcId);
|
|
|
|
// If we did a no-suspend examine we don't have the
|
|
// extra count, so suspend first and then resume to
|
|
// get accurate suspend counts for the threads.
|
|
// Be careful not to do this in the self-attach case
|
|
// as the suspend will hang things.
|
|
if (PendingFlags & ENG_PROC_NO_SUSPEND_RESUME)
|
|
{
|
|
if (ResumeProcId != GetCurrentProcessId())
|
|
{
|
|
SuspendResumeThreads(ExamProc, TRUE, NULL);
|
|
SuspendResumeThreads(ExamProc, FALSE, NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SuspendResumeThreads(ExamProc, FALSE, NULL);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG
|
|
LiveUserTargetInfo::ProcessDebugEvent(DEBUG_EVENT64* Event,
|
|
ULONG PendingFlags,
|
|
ULONG PendingOptions)
|
|
{
|
|
ULONG EventStatus;
|
|
CHAR NameBuffer[MAX_IMAGE_PATH];
|
|
ULONG ModuleSize, CheckSum, TimeDateStamp;
|
|
char ModuleName[MAX_MODULE];
|
|
|
|
switch(Event->dwDebugEventCode)
|
|
{
|
|
case CREATE_PROCESS_DEBUG_EVENT:
|
|
LoadWow64ExtsIfNeeded(Event->u.CreateProcessInfo.hProcess);
|
|
|
|
EventStatus = NotifyCreateProcessEvent(
|
|
(ULONG64)Event->u.CreateProcessInfo.hFile,
|
|
GloballyUniqueProcessHandle(g_EventTarget,
|
|
(ULONG64)
|
|
Event->u.CreateProcessInfo.hProcess),
|
|
(ULONG64)Event->u.CreateProcessInfo.hProcess,
|
|
(ULONG64)Event->u.CreateProcessInfo.lpBaseOfImage,
|
|
0, NULL, NULL, 0, 0,
|
|
(ULONG64)Event->u.CreateProcessInfo.hThread,
|
|
(ULONG64)Event->u.CreateProcessInfo.lpThreadLocalBase,
|
|
(ULONG64)Event->u.CreateProcessInfo.lpStartAddress,
|
|
PendingFlags, PendingOptions,
|
|
(PendingFlags & ENG_PROC_THREAD_CLOSE_HANDLE) ?
|
|
ENG_PROC_THREAD_CLOSE_HANDLE : 0,
|
|
TRUE,
|
|
Event->u.CreateProcessInfo.lpImageName,
|
|
Event->u.CreateProcessInfo.fUnicode);
|
|
break;
|
|
|
|
case EXIT_PROCESS_DEBUG_EVENT:
|
|
if (g_EventProcess == NULL)
|
|
{
|
|
// Assume that this unmatched exit process event is a leftover
|
|
// from a previous restart and just ignore it.
|
|
WarnOut("Ignoring unknown process exit for %X\n",
|
|
g_EventProcessSysId);
|
|
EventStatus = DEBUG_STATUS_IGNORE_EVENT;
|
|
}
|
|
else
|
|
{
|
|
EventStatus =
|
|
NotifyExitProcessEvent(Event->u.ExitProcess.dwExitCode);
|
|
}
|
|
break;
|
|
|
|
case CREATE_THREAD_DEBUG_EVENT:
|
|
EventStatus = NotifyCreateThreadEvent(
|
|
(ULONG64)Event->u.CreateThread.hThread,
|
|
(ULONG64)Event->u.CreateThread.lpThreadLocalBase,
|
|
(ULONG64)Event->u.CreateThread.lpStartAddress,
|
|
PendingFlags);
|
|
break;
|
|
|
|
case EXIT_THREAD_DEBUG_EVENT:
|
|
EventStatus = NotifyExitThreadEvent(Event->u.ExitThread.dwExitCode);
|
|
break;
|
|
|
|
case LOAD_DLL_DEBUG_EVENT:
|
|
strcpy(NameBuffer, "no_process");
|
|
GetEventName(Event->u.LoadDll.hFile,
|
|
Event->u.LoadDll.lpBaseOfDll,
|
|
Event->u.LoadDll.lpImageName,
|
|
Event->u.LoadDll.fUnicode,
|
|
NameBuffer, sizeof(NameBuffer));
|
|
|
|
GetHeaderInfo(g_EventProcess,
|
|
(ULONG64)Event->u.LoadDll.lpBaseOfDll,
|
|
&CheckSum, &TimeDateStamp, &ModuleSize);
|
|
CreateModuleNameFromPath(NameBuffer, ModuleName);
|
|
|
|
EventStatus = NotifyLoadModuleEvent(
|
|
(ULONG64)Event->u.LoadDll.hFile,
|
|
(ULONG64)Event->u.LoadDll.lpBaseOfDll,
|
|
ModuleSize, ModuleName, NameBuffer, CheckSum, TimeDateStamp,
|
|
TRUE);
|
|
break;
|
|
|
|
case UNLOAD_DLL_DEBUG_EVENT:
|
|
EventStatus = NotifyUnloadModuleEvent(
|
|
NULL, (ULONG64)Event->u.UnloadDll.lpBaseOfDll);
|
|
break;
|
|
|
|
case OUTPUT_DEBUG_STRING_EVENT:
|
|
EventStatus = OutputEventDebugString(&Event->u.DebugString);
|
|
break;
|
|
|
|
case RIP_EVENT:
|
|
EventStatus = NotifySystemErrorEvent(Event->u.RipInfo.dwError,
|
|
Event->u.RipInfo.dwType);
|
|
break;
|
|
|
|
case EXCEPTION_DEBUG_EVENT:
|
|
EventStatus = ProcessEventException(Event);
|
|
break;
|
|
|
|
default:
|
|
WarnOut("Unknown event number 0x%08lx\n",
|
|
Event->dwDebugEventCode);
|
|
EventStatus = DEBUG_STATUS_BREAK;
|
|
break;
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
#define ISTS() (!!(USER_SHARED_DATA->SuiteMask & (1 << TerminalServer)))
|
|
#define FIRST_CHANCE Event->u.Exception.dwFirstChance
|
|
|
|
ULONG
|
|
LiveUserTargetInfo::ProcessEventException(DEBUG_EVENT64* Event)
|
|
{
|
|
ULONG ExceptionCode;
|
|
ULONG EventStatus;
|
|
BOOL OutputDone = FALSE;
|
|
ImageInfo* Image;
|
|
|
|
ExceptionCode = Event->u.Exception.ExceptionRecord.ExceptionCode;
|
|
g_TargetEventPc = (ULONG64)
|
|
Event->u.Exception.ExceptionRecord.ExceptionAddress;
|
|
|
|
EventOut("Exception %X at %p\n", ExceptionCode, g_TargetEventPc);
|
|
|
|
//
|
|
// If we are debugging a crashed process, force the
|
|
// desktop we are on to the front so the user will know
|
|
// what happened.
|
|
//
|
|
if (g_EventToSignal != NULL &&
|
|
!ISTS() &&
|
|
!AnySystemProcesses(FALSE))
|
|
{
|
|
if (InitDynamicCalls(&g_User32CallsDesc) == S_OK &&
|
|
g_User32Calls.SwitchDesktop != NULL &&
|
|
g_User32Calls.GetThreadDesktop != NULL &&
|
|
g_User32Calls.CloseDesktop != NULL)
|
|
{
|
|
HDESK hDesk;
|
|
|
|
hDesk = g_User32Calls.GetThreadDesktop(::GetCurrentThreadId());
|
|
g_User32Calls.SwitchDesktop(hDesk);
|
|
g_User32Calls.CloseDesktop(hDesk);
|
|
}
|
|
}
|
|
|
|
if (g_EventThread && ExceptionCode == STATUS_VDM_EVENT)
|
|
{
|
|
ULONG ulRet = VDMEvent(Event);
|
|
|
|
switch(ulRet)
|
|
{
|
|
case VDMEVENT_NOT_HANDLED:
|
|
EventStatus = DEBUG_STATUS_GO_NOT_HANDLED;
|
|
break;
|
|
case VDMEVENT_HANDLED:
|
|
EventStatus = DEBUG_STATUS_GO_HANDLED;
|
|
break;
|
|
default:
|
|
// Give vdm code the option of mutating this into
|
|
// a standard exception (like STATUS_BREAKPOINT)
|
|
ExceptionCode = ulRet;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch(ExceptionCode)
|
|
{
|
|
case STATUS_BREAKPOINT:
|
|
case STATUS_SINGLE_STEP:
|
|
case STATUS_WX86_BREAKPOINT:
|
|
case STATUS_WX86_SINGLE_STEP:
|
|
if (!g_EventThread)
|
|
{
|
|
goto NotifyException;
|
|
}
|
|
EventStatus = ProcessBreakpointOrStepException
|
|
(&Event->u.Exception.ExceptionRecord, FIRST_CHANCE);
|
|
break;
|
|
|
|
case STATUS_VDM_EVENT:
|
|
if (!g_EventThread)
|
|
{
|
|
goto NotifyException;
|
|
}
|
|
// do nothing, it's already handled
|
|
EventStatus = DEBUG_STATUS_IGNORE_EVENT;
|
|
break;
|
|
|
|
case STATUS_ACCESS_VIOLATION:
|
|
if (FIRST_CHANCE &&
|
|
g_EventProcess &&
|
|
(Image = g_EventProcess->
|
|
FindImageByOffset(Event->u.Exception.
|
|
ExceptionRecord.ExceptionAddress, FALSE)) &&
|
|
!_stricmp(Image->m_ModuleName, "ntdll"))
|
|
{
|
|
CHAR ExSym[MAX_SYMBOL_LEN];
|
|
LPSTR Scan;
|
|
ULONG64 ExDisp;
|
|
|
|
//
|
|
// Ignore AVs that are expected in system code.
|
|
//
|
|
|
|
GetSymbol(Event->u.Exception.ExceptionRecord.ExceptionAddress,
|
|
ExSym, sizeof(ExSym), &ExDisp);
|
|
|
|
Scan = ExSym;
|
|
if (!_strnicmp(Scan, "ntdll!", 6))
|
|
{
|
|
Scan += 6;
|
|
if (*Scan == '_')
|
|
{
|
|
Scan += 1;
|
|
}
|
|
|
|
// This option allows new 3.51 binaries to run under
|
|
// this debugger on old 3.1, 3.5 systems and avoid stopping
|
|
// at access violations inside LDR that will be handled
|
|
// by the LDR anyway.
|
|
if ((g_EngOptions & DEBUG_ENGOPT_IGNORE_LOADER_EXCEPTIONS) &&
|
|
(!_stricmp(Scan, "LdrpSnapThunk") ||
|
|
!_stricmp(Scan, "LdrpWalkImportDescriptor")))
|
|
{
|
|
EventStatus = DEBUG_STATUS_GO_NOT_HANDLED;
|
|
break;
|
|
}
|
|
|
|
// The interlocked SList code has a by-design faulting
|
|
// case, so ignore AVs at that particular symbol.
|
|
if ((ExDisp == 0 &&
|
|
!_stricmp(Scan, "ExpInterlockedPopEntrySListFault")) ||
|
|
(m_ActualSystemVersion == NT_SVER_W2K &&
|
|
!_stricmp(Scan, "RtlpInterlockedPopEntrySList")))
|
|
{
|
|
EventStatus = DEBUG_STATUS_GO_NOT_HANDLED;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
goto NotifyException;
|
|
|
|
case STATUS_POSSIBLE_DEADLOCK:
|
|
if (m_PlatformId == VER_PLATFORM_WIN32_NT)
|
|
{
|
|
DBG_ASSERT(IS_USER_TARGET(g_EventTarget));
|
|
AnalyzeDeadlock(&Event->u.Exception.ExceptionRecord,
|
|
FIRST_CHANCE);
|
|
}
|
|
else
|
|
{
|
|
OutputDeadlock(&Event->u.Exception.ExceptionRecord,
|
|
FIRST_CHANCE);
|
|
}
|
|
|
|
OutputDone = TRUE;
|
|
goto NotifyException;
|
|
|
|
default:
|
|
{
|
|
NotifyException:
|
|
EventStatus =
|
|
NotifyExceptionEvent(&Event->u.Exception.ExceptionRecord,
|
|
Event->u.Exception.dwFirstChance,
|
|
OutputDone);
|
|
}
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Do this for all exceptions, just in case some other
|
|
// thread caused an exception before we get around to
|
|
// handling the breakpoint event.
|
|
//
|
|
g_EngDefer |= ENG_DEFER_SET_EVENT;
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
#undef FIRST_CHANCE
|
|
|
|
#define INPUT_API_SIG 0xdefaced
|
|
|
|
typedef struct _hdi
|
|
{
|
|
DWORD dwSignature;
|
|
BYTE cLength;
|
|
BYTE cStatus;
|
|
} HDI;
|
|
|
|
ULONG
|
|
LiveUserTargetInfo::OutputEventDebugString(OUTPUT_DEBUG_STRING_INFO64* Info)
|
|
{
|
|
LPSTR Str, Str2;
|
|
ULONG dwNumberOfBytesRead;
|
|
HDI hdi;
|
|
ULONG EventStatus = DEBUG_STATUS_IGNORE_EVENT;
|
|
|
|
if (Info->nDebugStringLength == 0)
|
|
{
|
|
return EventStatus;
|
|
}
|
|
|
|
Str = (PSTR)calloc(1, Info->nDebugStringLength);
|
|
if (Str == NULL)
|
|
{
|
|
ErrOut("Unable to allocate debug output buffer\n");
|
|
return EventStatus;
|
|
}
|
|
|
|
if (ReadVirtual(g_EventProcess,
|
|
Info->lpDebugStringData, Str,
|
|
Info->nDebugStringLength,
|
|
&dwNumberOfBytesRead) == S_OK &&
|
|
(dwNumberOfBytesRead == (SIZE_T)Info->nDebugStringLength))
|
|
{
|
|
//
|
|
// Special processing for hacky debug input string
|
|
//
|
|
|
|
if (ReadVirtual(g_EventProcess,
|
|
Info->lpDebugStringData +
|
|
Info->nDebugStringLength,
|
|
&hdi, sizeof(hdi),
|
|
&dwNumberOfBytesRead) == S_OK &&
|
|
dwNumberOfBytesRead == sizeof(hdi) &&
|
|
hdi.dwSignature == INPUT_API_SIG)
|
|
{
|
|
StartOutLine(DEBUG_OUTPUT_DEBUGGEE_PROMPT, OUT_LINE_NO_PREFIX);
|
|
MaskOut(DEBUG_OUTPUT_DEBUGGEE_PROMPT, "%s", Str);
|
|
|
|
Str2 = (PSTR)calloc(1, hdi.cLength + 1);
|
|
if (Str2)
|
|
{
|
|
GetInput(NULL, Str2, hdi.cLength, GETIN_DEFAULT);
|
|
WriteVirtual(g_EventProcess,
|
|
Info->lpDebugStringData + 6,
|
|
Str2, (DWORD)hdi.cLength, NULL);
|
|
free(Str2);
|
|
}
|
|
else
|
|
{
|
|
ErrOut("Unable to allocate prompt buffer\n");
|
|
}
|
|
}
|
|
else if (g_OutputCommandRedirectPrefixLen &&
|
|
!_strnicmp(g_OutputCommandRedirectPrefix, Str,
|
|
g_OutputCommandRedirectPrefixLen))
|
|
{
|
|
PSTR Command = Str + g_OutputCommandRedirectPrefixLen;
|
|
_snprintf(g_LastEventDesc, sizeof(g_LastEventDesc) - 1,
|
|
"%.48s command: '%.192s'",
|
|
g_OutputCommandRedirectPrefix, Command);
|
|
EventStatus = ExecuteEventCommand(DEBUG_STATUS_NO_CHANGE, NULL,
|
|
Command);
|
|
|
|
// Break in if the command didn't explicitly continue.
|
|
if (EventStatus == DEBUG_STATUS_NO_CHANGE)
|
|
{
|
|
EventStatus = DEBUG_STATUS_BREAK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StartOutLine(DEBUG_OUTPUT_DEBUGGEE, OUT_LINE_NO_PREFIX);
|
|
MaskOut(DEBUG_OUTPUT_DEBUGGEE, "%s", Str);
|
|
|
|
EVENT_FILTER* Filter =
|
|
&g_EventFilters[DEBUG_FILTER_DEBUGGEE_OUTPUT];
|
|
if (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
|
|
BreakOnThisOutString(Str))
|
|
{
|
|
EventStatus = DEBUG_STATUS_BREAK;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrOut("Unable to read debug output string, %d\n",
|
|
GetLastError());
|
|
}
|
|
|
|
free(Str);
|
|
return EventStatus;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DumpTargetInfo wait methods.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
KernelDumpTargetInfo::FirstEvent(void)
|
|
{
|
|
HRESULT Status;
|
|
ULONG i;
|
|
|
|
CreateVirtualProcess(m_NumProcessors);
|
|
|
|
QueryKernelInfo(m_ProcessHead->m_ThreadHead, TRUE);
|
|
OutputVersion();
|
|
|
|
if (!IS_KERNEL_TRIAGE_DUMP(this))
|
|
{
|
|
if (m_KdDebuggerData.KiProcessorBlock)
|
|
{
|
|
ULONG PtrSize = m_Machine->m_Ptr64 ?
|
|
sizeof(ULONG64) : sizeof(ULONG);
|
|
|
|
for (i = 0; i < m_NumProcessors; i++)
|
|
{
|
|
Status =
|
|
ReadPointer(g_EventProcess, m_Machine,
|
|
m_KdDebuggerData.KiProcessorBlock +
|
|
i * PtrSize, &m_KiProcessors[i]);
|
|
if (Status != S_OK || !m_KiProcessors[i])
|
|
{
|
|
ErrOut("KiProcessorBlock[%d] could not be read\n", i);
|
|
return Status != S_OK ? Status : E_FAIL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear the global state change just in case somebody's
|
|
// directly accessing it somewhere.
|
|
ZeroMemory(&g_StateChange, sizeof(g_StateChange));
|
|
g_StateChangeData = g_StateChangeBuffer;
|
|
g_StateChangeBuffer[0] = 0;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
UserDumpTargetInfo::FirstEvent(void)
|
|
{
|
|
ULONG i;
|
|
ULONG Suspend;
|
|
ULONG64 Teb;
|
|
|
|
if (GetProductInfo(&m_ProductType, &m_SuiteMask) != S_OK)
|
|
{
|
|
m_ProductType = INVALID_PRODUCT_TYPE;
|
|
m_SuiteMask = 0;
|
|
}
|
|
|
|
OutputVersion();
|
|
|
|
// Create the process.
|
|
g_EventProcessSysId = m_EventProcessId;
|
|
if (GetThreadInfo(0, &g_EventThreadSysId,
|
|
&Suspend, &Teb) != S_OK)
|
|
{
|
|
// Dump doesn't contain thread information so
|
|
// fake it.
|
|
g_EventThreadSysId = VIRTUAL_THREAD_ID(0);
|
|
Suspend = 0;
|
|
Teb = 0;
|
|
}
|
|
|
|
EventOut("User dump process %x.%x with %u threads\n",
|
|
g_EventProcessSysId, g_EventThreadSysId,
|
|
m_ThreadCount);
|
|
|
|
NotifyCreateProcessEvent(0,
|
|
m_EventProcessSymHandle,
|
|
(ULONG64)
|
|
VIRTUAL_PROCESS_HANDLE(g_EventProcessSysId),
|
|
0, 0, NULL, NULL, 0, 0,
|
|
(ULONG64)VIRTUAL_THREAD_HANDLE(0),
|
|
Teb, 0, 0, DEBUG_PROCESS_ONLY_THIS_PROCESS,
|
|
0, FALSE, 0, FALSE);
|
|
// Update thread suspend count from dump info.
|
|
g_EventThread->m_SuspendCount = Suspend;
|
|
|
|
// Create any remaining threads.
|
|
for (i = 1; i < m_ThreadCount; i++)
|
|
{
|
|
GetThreadInfo(i, &g_EventThreadSysId, &Suspend, &Teb);
|
|
|
|
EventOut("User dump thread %d: %x\n", i, g_EventThreadSysId);
|
|
|
|
NotifyCreateThreadEvent((ULONG64)VIRTUAL_THREAD_HANDLE(i),
|
|
Teb, 0, 0);
|
|
// Update thread suspend count from dump info.
|
|
g_EventThread->m_SuspendCount = Suspend;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
DumpTargetInfo::WaitInitialize(ULONG Flags,
|
|
ULONG Timeout,
|
|
WAIT_INIT_TYPE Type,
|
|
PULONG DesiredTimeout)
|
|
{
|
|
*DesiredTimeout = Timeout;
|
|
m_EventPossible = m_FirstWait;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
DumpTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout,
|
|
ULONG ElapsedTime, PULONG EventStatus)
|
|
{
|
|
HRESULT Status;
|
|
BOOL HaveContext = FALSE;
|
|
|
|
if (m_NumEvents == 1 && !m_FirstWait)
|
|
{
|
|
// A wait has already been done. Most crash dumps
|
|
// can only generate a single event so further
|
|
// waiting is not possible.
|
|
return S_FALSE;
|
|
}
|
|
|
|
g_EventTarget = this;
|
|
|
|
if (m_FirstWait)
|
|
{
|
|
//
|
|
// This is the first wait. Simulate any
|
|
// necessary events such as process and thread
|
|
// creations and image loads.
|
|
//
|
|
|
|
// Don't give real callbacks for processes/threads as
|
|
// they're just faked in the dump case.
|
|
g_EngNotify++;
|
|
|
|
if ((Status = FirstEvent()) != S_OK)
|
|
{
|
|
g_EngNotify--;
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
if (IS_KERNEL_TARGET(this))
|
|
{
|
|
ULONG CurProc = ((KernelDumpTargetInfo*)this)->GetCurrentProcessor();
|
|
if (CurProc == (ULONG)-1)
|
|
{
|
|
WarnOut("Could not determine the current processor, "
|
|
"using zero\n");
|
|
CurProc = 0;
|
|
}
|
|
|
|
// Always set up an event so that the debugger
|
|
// initializes to the point of having a process
|
|
// and thread so commands can be used.
|
|
g_EventProcessSysId =
|
|
g_EventTarget->m_ProcessHead->m_SystemId;
|
|
g_EventThreadSysId = VIRTUAL_THREAD_ID(CurProc);
|
|
|
|
HaveContext = IS_KERNEL_TRIAGE_DUMP(this) ||
|
|
g_EventTarget->m_KdDebuggerData.KiProcessorBlock;
|
|
|
|
if (HaveContext &&
|
|
(g_EventTarget->m_MachineType == IMAGE_FILE_MACHINE_I386 ||
|
|
g_EventTarget->m_MachineType == IMAGE_FILE_MACHINE_IA64) &&
|
|
!IS_KERNEL_TRIAGE_DUMP(g_EventTarget))
|
|
{
|
|
//
|
|
// Reset the page directory correctly since NT 4 stores
|
|
// the wrong CR3 value in the context.
|
|
//
|
|
// IA64 dumps start out with just the kernel page
|
|
// directory set so update everything.
|
|
//
|
|
|
|
FindEventProcessThread();
|
|
g_EventTarget->ChangeRegContext(g_EventThread);
|
|
if (g_EventTarget->m_Machine->
|
|
SetDefaultPageDirectories(g_EventThread, PAGE_DIR_ALL) != S_OK)
|
|
{
|
|
WarnOut("WARNING: Unable to reset page directories\n");
|
|
}
|
|
g_EventTarget->ChangeRegContext(NULL);
|
|
// Flush the cache just in case as vmem mappings changed.
|
|
g_EventProcess->m_VirtualCache.Empty();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UserDumpTargetInfo* UserDump = (UserDumpTargetInfo*)g_EventTarget;
|
|
g_EventProcessSysId = UserDump->m_EventProcessId;
|
|
g_EventThreadSysId = UserDump->m_EventThreadId;
|
|
|
|
HaveContext = TRUE;
|
|
|
|
EventOut("User dump event on %x.%x\n",
|
|
g_EventProcessSysId, g_EventThreadSysId);
|
|
}
|
|
|
|
// Do not provide a control report; this will force
|
|
// such information to come from context retrieval.
|
|
g_ControlReport = NULL;
|
|
|
|
g_TargetEventPc = (ULONG64)m_ExceptionRecord.ExceptionAddress;
|
|
|
|
g_EngStatus |= ENG_STATUS_STATE_CHANGED;
|
|
|
|
FindEventProcessThread();
|
|
if (HaveContext)
|
|
{
|
|
g_EventTarget->ChangeRegContext(g_EventThread);
|
|
}
|
|
|
|
//
|
|
// Go ahead and reload all the symbols.
|
|
// This is especially important for minidumps because without
|
|
// symbols and the executable image, we can't unassemble the
|
|
// current instruction.
|
|
//
|
|
// If we don't have any context information we need to try
|
|
// and load symbols with whatever we've got, so skip any
|
|
// path checks. Also, if we're on XP or newer there's enough
|
|
// information in the dump to get things running even without
|
|
// symbols, so don't fail on paths checks then either.
|
|
//
|
|
|
|
BOOL CheckPaths = TRUE;
|
|
|
|
if (!HaveContext ||
|
|
IS_USER_DUMP(this) ||
|
|
(g_EventTarget->m_ActualSystemVersion >= NT_SVER_XP &&
|
|
g_EventTarget->m_ActualSystemVersion < NT_SVER_END))
|
|
{
|
|
CheckPaths = FALSE;
|
|
}
|
|
|
|
PCSTR ArgsRet;
|
|
|
|
Status = g_EventTarget->Reload(g_EventThread, CheckPaths ? "" : "-P",
|
|
&ArgsRet);
|
|
|
|
g_EventTarget->ChangeRegContext(NULL);
|
|
|
|
// The engine is now initialized so a real event
|
|
// can be generated.
|
|
g_EngNotify--;
|
|
|
|
if (HaveContext && Status != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if (m_FirstWait)
|
|
{
|
|
if (IS_USER_TARGET(this))
|
|
{
|
|
SetKernel32BuildString(m_ProcessHead);
|
|
if (IS_USER_FULL_DUMP(this))
|
|
{
|
|
m_ProcessHead->VerifyKernel32Version();
|
|
}
|
|
}
|
|
|
|
NotifyDebuggeeActivation();
|
|
}
|
|
|
|
NotifyExceptionEvent(&m_ExceptionRecord, m_ExceptionFirstChance,
|
|
m_ExceptionRecord.ExceptionCode ==
|
|
STATUS_BREAKPOINT ||
|
|
m_ExceptionRecord.ExceptionCode ==
|
|
STATUS_WX86_BREAKPOINT);
|
|
|
|
*EventStatus = DEBUG_STATUS_BREAK;
|
|
return S_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Event filters.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
ParseImageTail(PSTR Buffer, ULONG BufferSize)
|
|
{
|
|
int i;
|
|
char ch;
|
|
|
|
Buffer[0] = '\0';
|
|
|
|
i = 0;
|
|
while (ch = (char)tolower(*g_CurCmd))
|
|
{
|
|
if (ch == ' ' || ch == '\t' || ch == ';')
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Only capture the path tail.
|
|
if (IS_SLASH(ch) || ch == ':')
|
|
{
|
|
i = 0;
|
|
}
|
|
else
|
|
{
|
|
Buffer[i++] = ch;
|
|
if (i == BufferSize - 1)
|
|
{
|
|
// don't overrun the buffer
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_CurCmd++;
|
|
}
|
|
|
|
Buffer[i] = '\0';
|
|
}
|
|
|
|
void
|
|
ParseUnloadDllBreakAddr(void)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called after 'sxe ud' has been parsed. This routine detects an
|
|
optional DLL base address after the 'sxe ud', which tells the debugger
|
|
to run until that specific DLL is unloaded, not just the next DLL.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
UCHAR ch;
|
|
|
|
g_UnloadDllBase = 0;
|
|
g_UnloadDllBaseName[0] = 0;
|
|
|
|
while (ch = (UCHAR)tolower(*g_CurCmd))
|
|
{
|
|
if (ch == ' ')
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Skip leading ':'
|
|
if (ch != ':')
|
|
{
|
|
// Get the base address
|
|
g_UnloadDllBase = GetExpression();
|
|
sprintf(g_UnloadDllBaseName, "0x%s",
|
|
FormatAddr64(g_UnloadDllBase));
|
|
break;
|
|
}
|
|
g_CurCmd++;
|
|
}
|
|
}
|
|
|
|
void
|
|
ParseOutFilterPattern(void)
|
|
{
|
|
int i;
|
|
char ch;
|
|
|
|
i = 0;
|
|
while (ch = (char)tolower(*g_CurCmd))
|
|
{
|
|
if (ch == ' ')
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (ch == ':')
|
|
{
|
|
i = 0;
|
|
}
|
|
else
|
|
{
|
|
g_OutEventFilterPattern[i++] = (char)toupper(ch);
|
|
if (i == sizeof(g_OutEventFilterPattern) - 1)
|
|
{
|
|
// Don't overrun the buffer.
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_CurCmd++;
|
|
}
|
|
|
|
g_OutEventFilterPattern[i] = 0;
|
|
}
|
|
|
|
BOOL
|
|
BreakOnThisImageTail(PCSTR ImagePath, PCSTR FilterArg)
|
|
{
|
|
//
|
|
// No filter specified so break on all events.
|
|
//
|
|
if (!FilterArg || !FilterArg[0])
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Some kind of error looking up the image path. Break anyhow.
|
|
//
|
|
if (!ImagePath || !ImagePath[0])
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
PCSTR Tail = PathTail(ImagePath);
|
|
|
|
//
|
|
// Specified name may not have an extension. Break
|
|
// on the first event whose name matches regardless of its extension if
|
|
// the break name did not have one.
|
|
//
|
|
if (_strnicmp(Tail, FilterArg, strlen(FilterArg)) == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
else if (MatchPattern((PSTR)Tail, (PSTR)FilterArg))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
BreakOnThisDllUnload(
|
|
ULONG64 DllBase
|
|
)
|
|
{
|
|
// 'sxe ud' with no base address specified. Break on all DLL unloads
|
|
if (g_UnloadDllBase == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// 'sxe ud' with base address specified. Break if this
|
|
// DLL's base address matches the one specified
|
|
return g_UnloadDllBase == DllBase;
|
|
}
|
|
|
|
BOOL
|
|
BreakOnThisOutString(PCSTR OutString)
|
|
{
|
|
if (!g_OutEventFilterPattern[0])
|
|
{
|
|
// No pattern means always break.
|
|
return TRUE;
|
|
}
|
|
|
|
return MatchPattern((PSTR)OutString, g_OutEventFilterPattern);
|
|
}
|
|
|
|
EVENT_FILTER*
|
|
GetSpecificExceptionFilter(ULONG Code)
|
|
{
|
|
ULONG i;
|
|
EVENT_FILTER* Filter;
|
|
|
|
Filter = g_EventFilters + FILTER_EXCEPTION_FIRST;
|
|
for (i = FILTER_EXCEPTION_FIRST; i <= FILTER_EXCEPTION_LAST; i++)
|
|
{
|
|
if (i != FILTER_DEFAULT_EXCEPTION &&
|
|
Filter->Params.ExceptionCode == Code)
|
|
{
|
|
return Filter;
|
|
}
|
|
|
|
Filter++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ULONG
|
|
GetOtherExceptionParameters(ULONG Code, BOOL DefaultOnNotFound,
|
|
PDEBUG_EXCEPTION_FILTER_PARAMETERS* Params,
|
|
EVENT_COMMAND** Command)
|
|
{
|
|
ULONG Index;
|
|
|
|
for (Index = 0; Index < g_NumOtherExceptions; Index++)
|
|
{
|
|
if (Code == g_OtherExceptionList[Index].ExceptionCode)
|
|
{
|
|
*Params = g_OtherExceptionList + Index;
|
|
*Command = g_OtherExceptionCommands + Index;
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
if (DefaultOnNotFound)
|
|
{
|
|
*Params = &g_EventFilters[FILTER_DEFAULT_EXCEPTION].Params;
|
|
*Command = &g_EventFilters[FILTER_DEFAULT_EXCEPTION].Command;
|
|
return NO_ERROR;
|
|
}
|
|
else if (g_NumOtherExceptions == OTHER_EXCEPTION_LIST_MAX)
|
|
{
|
|
return LISTSIZE;
|
|
}
|
|
else
|
|
{
|
|
Index = g_NumOtherExceptions++;
|
|
g_OtherExceptionList[Index] =
|
|
g_EventFilters[FILTER_DEFAULT_EXCEPTION].Params;
|
|
g_OtherExceptionList[Index].ExceptionCode = Code;
|
|
ZeroMemory(&g_OtherExceptionCommands[Index],
|
|
sizeof(g_OtherExceptionCommands[Index]));
|
|
*Params = g_OtherExceptionList + Index;
|
|
*Command = g_OtherExceptionCommands + Index;
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
ULONG
|
|
SetOtherExceptionParameters(PDEBUG_EXCEPTION_FILTER_PARAMETERS Params,
|
|
EVENT_COMMAND* Command)
|
|
{
|
|
ULONG Index;
|
|
|
|
if (g_EventFilters[FILTER_DEFAULT_EXCEPTION].
|
|
Params.ExecutionOption == Params->ExecutionOption &&
|
|
g_EventFilters[FILTER_DEFAULT_EXCEPTION].
|
|
Params.ContinueOption == Params->ContinueOption &&
|
|
!memcmp(&g_EventFilters[FILTER_DEFAULT_EXCEPTION].Command,
|
|
Command, sizeof(*Command)))
|
|
{
|
|
// Exception state same as global state clears entry
|
|
// in list if there.
|
|
|
|
for (Index = 0; Index < g_NumOtherExceptions; Index++)
|
|
{
|
|
if (Params->ExceptionCode ==
|
|
g_OtherExceptionList[Index].ExceptionCode)
|
|
{
|
|
RemoveOtherException(Index);
|
|
NotifyChangeEngineState(DEBUG_CES_EVENT_FILTERS,
|
|
DEBUG_ANY_ID, TRUE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Exception state different from global state is added
|
|
// to list if not already there.
|
|
|
|
for (Index = 0; Index < g_NumOtherExceptions; Index++)
|
|
{
|
|
if (Params->ExceptionCode ==
|
|
g_OtherExceptionList[Index].ExceptionCode)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (Index == g_NumOtherExceptions)
|
|
{
|
|
if (g_NumOtherExceptions == OTHER_EXCEPTION_LIST_MAX)
|
|
{
|
|
return LISTSIZE;
|
|
}
|
|
|
|
Index = g_NumOtherExceptions++;
|
|
}
|
|
|
|
g_OtherExceptionList[Index] = *Params;
|
|
g_OtherExceptionCommands[Index] = *Command;
|
|
NotifyChangeEngineState(DEBUG_CES_EVENT_FILTERS, Index, TRUE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
RemoveOtherException(ULONG Index)
|
|
{
|
|
g_NumOtherExceptions--;
|
|
memmove(g_OtherExceptionList + Index,
|
|
g_OtherExceptionList + Index + 1,
|
|
(g_NumOtherExceptions - Index) *
|
|
sizeof(g_OtherExceptionList[0]));
|
|
delete [] g_OtherExceptionCommands[Index].Command[0];
|
|
delete [] g_OtherExceptionCommands[Index].Command[1];
|
|
memmove(g_OtherExceptionCommands + Index,
|
|
g_OtherExceptionCommands + Index + 1,
|
|
(g_NumOtherExceptions - Index) *
|
|
sizeof(g_OtherExceptionCommands[0]));
|
|
}
|
|
|
|
ULONG
|
|
SetEventFilterExecution(EVENT_FILTER* Filter, ULONG Execution)
|
|
{
|
|
ULONG Index = (ULONG)(Filter - g_EventFilters);
|
|
|
|
// Non-exception events don't have second chances so
|
|
// demote second-chance break to output. This matches
|
|
// the intuitive expectation that sxd will disable
|
|
// the break.
|
|
if (
|
|
#if DEBUG_FILTER_CREATE_THREAD > 0
|
|
Index >= DEBUG_FILTER_CREATE_THREAD &&
|
|
#endif
|
|
Index <= DEBUG_FILTER_SYSTEM_ERROR &&
|
|
Execution == DEBUG_FILTER_SECOND_CHANCE_BREAK)
|
|
{
|
|
Execution = DEBUG_FILTER_OUTPUT;
|
|
}
|
|
|
|
Filter->Params.ExecutionOption = Execution;
|
|
Filter->Flags |= FILTER_CHANGED_EXECUTION;
|
|
|
|
// Collect any additional arguments.
|
|
switch(Index)
|
|
{
|
|
case DEBUG_FILTER_CREATE_PROCESS:
|
|
case DEBUG_FILTER_EXIT_PROCESS:
|
|
case DEBUG_FILTER_LOAD_MODULE:
|
|
ParseImageTail(Filter->Argument, FILTER_MAX_ARGUMENT);
|
|
break;
|
|
|
|
case DEBUG_FILTER_UNLOAD_MODULE:
|
|
ParseUnloadDllBreakAddr();
|
|
break;
|
|
|
|
case DEBUG_FILTER_DEBUGGEE_OUTPUT:
|
|
ParseOutFilterPattern();
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ULONG
|
|
SetEventFilterContinue(EVENT_FILTER* Filter, ULONG Continue)
|
|
{
|
|
Filter->Params.ContinueOption = Continue;
|
|
Filter->Flags |= FILTER_CHANGED_CONTINUE;
|
|
return 0;
|
|
}
|
|
|
|
ULONG
|
|
SetEventFilterCommand(DebugClient* Client, EVENT_FILTER* Filter,
|
|
EVENT_COMMAND* EventCommand, PSTR* Command)
|
|
{
|
|
if (Command[0] != NULL)
|
|
{
|
|
if (strlen(Command[0]) >= MAX_COMMAND)
|
|
{
|
|
return STRINGSIZE;
|
|
}
|
|
|
|
if (ChangeString(&EventCommand->Command[0],
|
|
&EventCommand->CommandSize[0],
|
|
Command[0][0] ? Command[0] : NULL) != S_OK)
|
|
{
|
|
return MEMORY;
|
|
}
|
|
}
|
|
if (Command[1] != NULL)
|
|
{
|
|
if (strlen(Command[1]) >= MAX_COMMAND)
|
|
{
|
|
return STRINGSIZE;
|
|
}
|
|
|
|
if (Filter != NULL &&
|
|
#if FILTER_SPECIFIC_FIRST > 0
|
|
(ULONG)(Filter - g_EventFilters) >= FILTER_SPECIFIC_FIRST &&
|
|
#endif
|
|
(ULONG)(Filter - g_EventFilters) <= FILTER_SPECIFIC_LAST)
|
|
{
|
|
WarnOut("Second-chance command for specific event ignored\n");
|
|
}
|
|
else if (ChangeString(&EventCommand->Command[1],
|
|
&EventCommand->CommandSize[1],
|
|
Command[1][0] ? Command[1] : NULL) != S_OK)
|
|
{
|
|
return MEMORY;
|
|
}
|
|
}
|
|
|
|
if (Command[0] != NULL || Command[1] != NULL)
|
|
{
|
|
if (Filter != NULL)
|
|
{
|
|
Filter->Flags |= FILTER_CHANGED_COMMAND;
|
|
}
|
|
EventCommand->Client = Client;
|
|
}
|
|
else
|
|
{
|
|
EventCommand->Client = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define EXEC_TO_CONT(Option) \
|
|
((Option) == DEBUG_FILTER_BREAK ? \
|
|
DEBUG_FILTER_GO_HANDLED : DEBUG_FILTER_GO_NOT_HANDLED)
|
|
|
|
ULONG
|
|
SetEventFilterEither(DebugClient* Client, EVENT_FILTER* Filter,
|
|
ULONG Option, BOOL ContinueOption,
|
|
PSTR* Command)
|
|
{
|
|
ULONG Status;
|
|
|
|
if (Option != DEBUG_FILTER_REMOVE)
|
|
{
|
|
if (ContinueOption)
|
|
{
|
|
Status = SetEventFilterContinue(Filter, EXEC_TO_CONT(Option));
|
|
}
|
|
else
|
|
{
|
|
Status = SetEventFilterExecution(Filter, Option);
|
|
}
|
|
if (Status != 0)
|
|
{
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
return SetEventFilterCommand(Client, Filter, &Filter->Command, Command);
|
|
}
|
|
|
|
ULONG
|
|
SetEventFilterByName(DebugClient* Client,
|
|
ULONG Option, BOOL ForceContinue, PSTR* Command)
|
|
{
|
|
PSTR Start = g_CurCmd;
|
|
char Name[8];
|
|
int i;
|
|
char Ch;
|
|
|
|
// Collect name.
|
|
i = 0;
|
|
while (i < sizeof(Name) - 1)
|
|
{
|
|
Ch = *g_CurCmd++;
|
|
if (!__iscsym(Ch))
|
|
{
|
|
g_CurCmd--;
|
|
break;
|
|
}
|
|
|
|
Name[i++] = (CHAR)tolower(Ch);
|
|
}
|
|
Name[i] = 0;
|
|
|
|
// Skip any whitespace after the name.
|
|
while (isspace(*g_CurCmd))
|
|
{
|
|
g_CurCmd++;
|
|
}
|
|
|
|
EVENT_FILTER* Filter;
|
|
BOOL Match = FALSE;
|
|
ULONG MatchIndex = DEBUG_ANY_ID;
|
|
ULONG Status = 0;
|
|
|
|
// Multiple filters can be altered if they share names.
|
|
Filter = g_EventFilters;
|
|
for (i = 0; i < FILTER_COUNT; i++)
|
|
{
|
|
if (Filter->ExecutionAbbrev != NULL &&
|
|
!strcmp(Name, Filter->ExecutionAbbrev))
|
|
{
|
|
Status = SetEventFilterEither(Client,
|
|
Filter, Option, ForceContinue,
|
|
Command);
|
|
if (Status != 0)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!Match)
|
|
{
|
|
MatchIndex = i;
|
|
Match = TRUE;
|
|
}
|
|
else if (MatchIndex != (ULONG)i)
|
|
{
|
|
// Multiple matches.
|
|
MatchIndex = DEBUG_ANY_ID;
|
|
}
|
|
}
|
|
|
|
if (Filter->ContinueAbbrev != NULL &&
|
|
!strcmp(Name, Filter->ContinueAbbrev))
|
|
{
|
|
// Translate execution-style option to continue-style option.
|
|
Status = SetEventFilterEither(Client,
|
|
Filter, Option, TRUE, Command);
|
|
if (Status != 0)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!Match)
|
|
{
|
|
MatchIndex = i;
|
|
Match = TRUE;
|
|
}
|
|
else if (MatchIndex != (ULONG)i)
|
|
{
|
|
// Multiple matches.
|
|
MatchIndex = DEBUG_ANY_ID;
|
|
}
|
|
}
|
|
|
|
Filter++;
|
|
}
|
|
|
|
if (!Match)
|
|
{
|
|
ULONG64 ExceptionCode;
|
|
|
|
// Name is unrecognized. Assume it's an exception code.
|
|
g_CurCmd = Start;
|
|
ExceptionCode = GetExpression();
|
|
if (NeedUpper(ExceptionCode))
|
|
{
|
|
return OVERFLOW;
|
|
}
|
|
|
|
DEBUG_EXCEPTION_FILTER_PARAMETERS Params, *CurParams;
|
|
EVENT_COMMAND EventCommand, *CurEventCommand;
|
|
|
|
if (Status = GetOtherExceptionParameters((ULONG)ExceptionCode, FALSE,
|
|
&CurParams, &CurEventCommand))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
Params = *CurParams;
|
|
if (Option != DEBUG_FILTER_REMOVE)
|
|
{
|
|
if (ForceContinue)
|
|
{
|
|
Params.ContinueOption = EXEC_TO_CONT(Option);
|
|
}
|
|
else
|
|
{
|
|
Params.ExecutionOption = Option;
|
|
}
|
|
}
|
|
Params.ExceptionCode = (ULONG)ExceptionCode;
|
|
|
|
EventCommand = *CurEventCommand;
|
|
Status = SetEventFilterCommand(Client, NULL, &EventCommand, Command);
|
|
if (Status != 0)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
return SetOtherExceptionParameters(&Params, &EventCommand);
|
|
}
|
|
|
|
Exit:
|
|
if (Match)
|
|
{
|
|
if (SyncOptionsWithFilters())
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_EVENT_FILTERS |
|
|
DEBUG_CES_ENGINE_OPTIONS,
|
|
DEBUG_ANY_ID, TRUE);
|
|
}
|
|
else
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_EVENT_FILTERS, MatchIndex, TRUE);
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
char* g_EfExecutionNames[] =
|
|
{
|
|
"break", "second-chance break", "output", "ignore",
|
|
};
|
|
|
|
char* g_EfContinueNames[] =
|
|
{
|
|
"handled", "not handled",
|
|
};
|
|
|
|
void
|
|
ListEventFilters(void)
|
|
{
|
|
EVENT_FILTER* Filter;
|
|
ULONG i;
|
|
BOOL SetOption = TRUE;
|
|
|
|
Filter = g_EventFilters;
|
|
for (i = 0; i < FILTER_COUNT; i++)
|
|
{
|
|
if (Filter->ExecutionAbbrev != NULL)
|
|
{
|
|
dprintf("%4s - %s - %s",
|
|
Filter->ExecutionAbbrev, Filter->Name,
|
|
g_EfExecutionNames[Filter->Params.ExecutionOption]);
|
|
if (i >= FILTER_EXCEPTION_FIRST &&
|
|
Filter->ContinueAbbrev == NULL)
|
|
{
|
|
dprintf(" - %s\n",
|
|
g_EfContinueNames[Filter->Params.ContinueOption]);
|
|
}
|
|
else
|
|
{
|
|
dprintf("\n");
|
|
}
|
|
|
|
if (Filter->Command.Command[0] != NULL)
|
|
{
|
|
dprintf(" Command: \"%s\"\n",
|
|
Filter->Command.Command[0]);
|
|
}
|
|
if (Filter->Command.Command[1] != NULL)
|
|
{
|
|
dprintf(" Second command: \"%s\"\n",
|
|
Filter->Command.Command[1]);
|
|
}
|
|
}
|
|
|
|
if (Filter->ContinueAbbrev != NULL)
|
|
{
|
|
dprintf("%4s - %s continue - %s\n",
|
|
Filter->ContinueAbbrev, Filter->Name,
|
|
g_EfContinueNames[Filter->Params.ContinueOption]);
|
|
}
|
|
|
|
switch(i)
|
|
{
|
|
case DEBUG_FILTER_CREATE_PROCESS:
|
|
case DEBUG_FILTER_EXIT_PROCESS:
|
|
case DEBUG_FILTER_LOAD_MODULE:
|
|
case DEBUG_FILTER_UNLOAD_MODULE:
|
|
if (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
|
|
Filter->Argument[0])
|
|
{
|
|
dprintf(" (only break for %s)\n", Filter->Argument);
|
|
}
|
|
break;
|
|
case DEBUG_FILTER_DEBUGGEE_OUTPUT:
|
|
if (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
|
|
g_OutEventFilterPattern[0])
|
|
{
|
|
dprintf(" (only break for %s matches)\n",
|
|
g_OutEventFilterPattern);
|
|
}
|
|
break;
|
|
}
|
|
|
|
Filter++;
|
|
}
|
|
|
|
Filter = &g_EventFilters[FILTER_DEFAULT_EXCEPTION];
|
|
dprintf("\n * - Other exception - %s - %s\n",
|
|
g_EfExecutionNames[Filter->Params.ExecutionOption],
|
|
g_EfContinueNames[Filter->Params.ContinueOption]);
|
|
if (Filter->Command.Command[0] != NULL)
|
|
{
|
|
dprintf(" Command: \"%s\"\n",
|
|
Filter->Command.Command[0]);
|
|
}
|
|
if (Filter->Command.Command[1] != NULL)
|
|
{
|
|
dprintf(" Second command: \"%s\"\n",
|
|
Filter->Command.Command[1]);
|
|
}
|
|
|
|
if (g_NumOtherExceptions > 0)
|
|
{
|
|
dprintf(" Exception option for:\n");
|
|
for (i = 0; i < g_NumOtherExceptions; i++)
|
|
{
|
|
dprintf(" %08lx - %s - %s\n",
|
|
g_OtherExceptionList[i].ExceptionCode,
|
|
g_EfExecutionNames[g_OtherExceptionList[i].
|
|
ExecutionOption],
|
|
g_EfContinueNames[g_OtherExceptionList[i].
|
|
ContinueOption]);
|
|
if (g_OtherExceptionCommands[i].Command[0] != NULL)
|
|
{
|
|
dprintf(" Command: \"%s\"\n",
|
|
g_OtherExceptionCommands[i].Command[0]);
|
|
}
|
|
if (g_OtherExceptionCommands[i].Command[1] != NULL)
|
|
{
|
|
dprintf(" Second command: \"%s\"\n",
|
|
g_OtherExceptionCommands[i].Command[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ParseSetEventFilter(DebugClient* Client)
|
|
{
|
|
UCHAR Ch;
|
|
|
|
// Verify that exception constants are properly updated.
|
|
DBG_ASSERT(!strcmp(g_EventFilters[FILTER_EXCEPTION_FIRST - 1].Name,
|
|
"Debuggee output"));
|
|
C_ASSERT(DIMA(g_EventFilters) == FILTER_COUNT);
|
|
|
|
Ch = PeekChar();
|
|
if (Ch == '\0')
|
|
{
|
|
ListEventFilters();
|
|
}
|
|
else
|
|
{
|
|
ULONG Option;
|
|
|
|
Ch = (UCHAR)tolower(Ch);
|
|
g_CurCmd++;
|
|
|
|
switch(Ch)
|
|
{
|
|
case 'd':
|
|
Option = DEBUG_FILTER_SECOND_CHANCE_BREAK;
|
|
break;
|
|
case 'e':
|
|
Option = DEBUG_FILTER_BREAK;
|
|
break;
|
|
case 'i':
|
|
Option = DEBUG_FILTER_IGNORE;
|
|
break;
|
|
case 'n':
|
|
Option = DEBUG_FILTER_OUTPUT;
|
|
break;
|
|
case '-':
|
|
// Special value to indicate "don't change the option".
|
|
// Used for just changing commands.
|
|
Option = DEBUG_FILTER_REMOVE;
|
|
break;
|
|
default:
|
|
error(SYNTAX);
|
|
break;
|
|
}
|
|
|
|
BOOL ForceContinue;
|
|
PSTR Command[2];
|
|
ULONG Which;
|
|
|
|
ForceContinue = FALSE;
|
|
Command[0] = NULL;
|
|
Command[1] = NULL;
|
|
|
|
for (;;)
|
|
{
|
|
while (isspace(PeekChar()))
|
|
{
|
|
g_CurCmd++;
|
|
}
|
|
|
|
if (*g_CurCmd == '-' || *g_CurCmd == '/')
|
|
{
|
|
switch(tolower(*(++g_CurCmd)))
|
|
{
|
|
case 'c':
|
|
if (*(++g_CurCmd) == '2')
|
|
{
|
|
Which = 1;
|
|
g_CurCmd++;
|
|
}
|
|
else
|
|
{
|
|
Which = 0;
|
|
}
|
|
if (PeekChar() != '"')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
if (Command[Which] != NULL)
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
Command[Which] = ++g_CurCmd;
|
|
while (*g_CurCmd && *g_CurCmd != '"')
|
|
{
|
|
g_CurCmd++;
|
|
}
|
|
if (*g_CurCmd != '"')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
*g_CurCmd = 0;
|
|
break;
|
|
|
|
case 'h':
|
|
ForceContinue = TRUE;
|
|
break;
|
|
|
|
default:
|
|
error(SYNTAX);
|
|
}
|
|
|
|
g_CurCmd++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
ULONG Status;
|
|
|
|
if (*g_CurCmd == '*')
|
|
{
|
|
g_CurCmd++;
|
|
|
|
Status = SetEventFilterEither
|
|
(Client, &g_EventFilters[FILTER_DEFAULT_EXCEPTION],
|
|
Option, ForceContinue, Command);
|
|
if (Status == 0)
|
|
{
|
|
while (g_NumOtherExceptions)
|
|
{
|
|
RemoveOtherException(0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = SetEventFilterByName(Client,
|
|
Option, ForceContinue, Command);
|
|
}
|
|
|
|
if (Status != 0)
|
|
{
|
|
error(Status);
|
|
}
|
|
}
|
|
}
|
|
|
|
char
|
|
ExecutionChar(ULONG Execution)
|
|
{
|
|
switch(Execution)
|
|
{
|
|
case DEBUG_FILTER_BREAK:
|
|
return 'e';
|
|
case DEBUG_FILTER_SECOND_CHANCE_BREAK:
|
|
return 'd';
|
|
case DEBUG_FILTER_OUTPUT:
|
|
return 'n';
|
|
case DEBUG_FILTER_IGNORE:
|
|
return 'i';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char
|
|
ContinueChar(ULONG Continue)
|
|
{
|
|
switch(Continue)
|
|
{
|
|
case DEBUG_FILTER_GO_HANDLED:
|
|
return 'e';
|
|
case DEBUG_FILTER_GO_NOT_HANDLED:
|
|
return 'd';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
ListFiltersAsCommands(DebugClient* Client, ULONG Flags)
|
|
{
|
|
ULONG i;
|
|
|
|
EVENT_FILTER* Filter = g_EventFilters;
|
|
for (i = 0; i < FILTER_COUNT; i++)
|
|
{
|
|
if (Filter->Flags & FILTER_CHANGED_EXECUTION)
|
|
{
|
|
PCSTR Abbrev = Filter->ExecutionAbbrev != NULL ?
|
|
Filter->ExecutionAbbrev : "*";
|
|
dprintf("sx%c %s",
|
|
ExecutionChar(Filter->Params.ExecutionOption), Abbrev);
|
|
|
|
switch(i)
|
|
{
|
|
case DEBUG_FILTER_CREATE_PROCESS:
|
|
case DEBUG_FILTER_EXIT_PROCESS:
|
|
case DEBUG_FILTER_LOAD_MODULE:
|
|
case DEBUG_FILTER_UNLOAD_MODULE:
|
|
case DEBUG_FILTER_DEBUGGEE_OUTPUT:
|
|
if (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
|
|
Filter->Argument[0])
|
|
{
|
|
dprintf(":%s", Filter->Argument);
|
|
}
|
|
break;
|
|
}
|
|
|
|
dprintf(" ;%c", (Flags & SXCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
|
|
if (Filter->Flags & FILTER_CHANGED_CONTINUE)
|
|
{
|
|
PCSTR Abbrev = Filter->ContinueAbbrev;
|
|
if (Abbrev == NULL)
|
|
{
|
|
Abbrev = Filter->ExecutionAbbrev != NULL ?
|
|
Filter->ExecutionAbbrev : "*";
|
|
}
|
|
|
|
dprintf("sx%c -h %s ;%c",
|
|
ContinueChar(Filter->Params.ContinueOption), Abbrev,
|
|
(Flags & SXCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
|
|
if (Filter->Flags & FILTER_CHANGED_COMMAND)
|
|
{
|
|
PCSTR Abbrev = Filter->ExecutionAbbrev != NULL ?
|
|
Filter->ExecutionAbbrev : "*";
|
|
|
|
dprintf("sx-");
|
|
if (Filter->Command.Command[0] != NULL)
|
|
{
|
|
dprintf(" -c \"%s\"", Filter->Command.Command[0]);
|
|
}
|
|
if (Filter->Command.Command[1] != NULL)
|
|
{
|
|
dprintf(" -c2 \"%s\"", Filter->Command.Command[1]);
|
|
}
|
|
dprintf(" %s ;%c", Abbrev,
|
|
(Flags & SXCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
|
|
Filter++;
|
|
}
|
|
|
|
PDEBUG_EXCEPTION_FILTER_PARAMETERS Other = g_OtherExceptionList;
|
|
EVENT_COMMAND* EventCommand = g_OtherExceptionCommands;
|
|
for (i = 0; i < g_NumOtherExceptions; i++)
|
|
{
|
|
dprintf("sx%c 0x%x ;%c",
|
|
ExecutionChar(Other->ExecutionOption), Other->ExceptionCode,
|
|
(Flags & SXCMDS_ONE_LINE) ? ' ' : '\n');
|
|
dprintf("sx%c -h 0x%x ;%c",
|
|
ContinueChar(Other->ContinueOption), Other->ExceptionCode,
|
|
(Flags & SXCMDS_ONE_LINE) ? ' ' : '\n');
|
|
|
|
if (EventCommand->Command[0] != NULL ||
|
|
EventCommand->Command[1] != NULL)
|
|
{
|
|
dprintf("sx-");
|
|
if (EventCommand->Command[0] != NULL)
|
|
{
|
|
dprintf(" -c \"%s\"", EventCommand->Command[0]);
|
|
}
|
|
if (EventCommand->Command[1] != NULL)
|
|
{
|
|
dprintf(" -c2 \"%s\"", EventCommand->Command[1]);
|
|
}
|
|
dprintf(" 0x%x ;%c", Other->ExceptionCode,
|
|
(Flags & SXCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
|
|
Other++;
|
|
EventCommand++;
|
|
}
|
|
|
|
if (Flags & SXCMDS_ONE_LINE)
|
|
{
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
|
|
struct SHARED_FILTER_AND_OPTION
|
|
{
|
|
ULONG FilterIndex;
|
|
ULONG OptionBit;
|
|
};
|
|
|
|
SHARED_FILTER_AND_OPTION g_SharedFilterOptions[] =
|
|
{
|
|
DEBUG_FILTER_INITIAL_BREAKPOINT, DEBUG_ENGOPT_INITIAL_BREAK,
|
|
DEBUG_FILTER_INITIAL_MODULE_LOAD, DEBUG_ENGOPT_INITIAL_MODULE_BREAK,
|
|
DEBUG_FILTER_EXIT_PROCESS, DEBUG_ENGOPT_FINAL_BREAK,
|
|
};
|
|
|
|
BOOL
|
|
SyncFiltersWithOptions(void)
|
|
{
|
|
ULONG ExOption;
|
|
BOOL Changed = FALSE;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < DIMA(g_SharedFilterOptions); i++)
|
|
{
|
|
ExOption = (g_EngOptions & g_SharedFilterOptions[i].OptionBit) ?
|
|
DEBUG_FILTER_BREAK : DEBUG_FILTER_IGNORE;
|
|
if (g_EventFilters[g_SharedFilterOptions[i].FilterIndex].
|
|
Params.ExecutionOption != ExOption)
|
|
{
|
|
g_EventFilters[g_SharedFilterOptions[i].FilterIndex].
|
|
Params.ExecutionOption = ExOption;
|
|
Changed = TRUE;
|
|
}
|
|
}
|
|
|
|
return Changed;
|
|
}
|
|
|
|
BOOL
|
|
SyncOptionsWithFilters(void)
|
|
{
|
|
ULONG Bit;
|
|
BOOL Changed = FALSE;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < DIMA(g_SharedFilterOptions); i++)
|
|
{
|
|
Bit = IS_EFEXECUTION_BREAK
|
|
(g_EventFilters[g_SharedFilterOptions[i].FilterIndex].
|
|
Params.ExecutionOption) ?
|
|
g_SharedFilterOptions[i].OptionBit : 0;
|
|
if ((g_EngOptions & g_SharedFilterOptions[i].OptionBit) ^ Bit)
|
|
{
|
|
g_EngOptions =
|
|
(g_EngOptions & ~g_SharedFilterOptions[i].OptionBit) | Bit;
|
|
Changed = TRUE;
|
|
}
|
|
}
|
|
|
|
return Changed;
|
|
}
|