//---------------------------------------------------------------------------- // // Event waiting and processing. // // Copyright (C) Microsoft Corporation, 1999-2001. // //---------------------------------------------------------------------------- #include "ntsdp.hpp" #include // 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; ULONG g_ExecutionStatusRequest = DEBUG_STATUS_NO_CHANGE; // Currently in seconds. ULONG g_PendingBreakInTimeoutLimit = 30; // 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. PTHREAD_INFO g_EventThread; PPROCESS_INFO g_EventProcess; // This is zero for events without a PC. ULONG64 g_EventPc; 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; ULONG64 g_SystemRangeStart; ULONG64 g_SystemCallVirtualAddress; ULONG g_SwitchProcessor; KDDEBUGGER_DATA64 KdDebuggerData; ULONG64 g_KdDebuggerDataBlock; 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[512]; 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, "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, "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 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 OutputEventLog(void) { 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()) { // 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()) { sprintf(g_EventLogEnd, "%04x.%04x: ", ProcId, ThreadId); Avail -= strlen(g_EventLogEnd); g_EventLogEnd += strlen(g_EventLogEnd); } strncat(g_EventLogEnd, Desc, Avail); g_EventLogEnd += strlen(g_EventLogEnd); *g_EventLogEnd++ = '\n'; *g_EventLogEnd = 0; } void DiscardLastEventInfo(void) { 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_EngDefer &= ~ENG_DEFER_CONTINUE_EVENT; g_EventProcessSysId = 0; g_EventThreadSysId = 0; g_EventPc = 0; // Clear any cached memory read during the last event. InvalidateMemoryCaches(); } 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(IS_LIVE_USER_TARGET()); ((UserTargetInfo*)g_Target)->m_Services-> ResumeThreads(1, &g_ThreadToResume, NULL); g_ThreadToResume = 0; 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_RegContextThread == NULL); 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. SetEffMachine(g_TargetMachineType, FALSE); if (g_EngStatus & ENG_STATUS_STATE_CHANGED) { g_Machine->InitializeContext(g_EventPc, g_ControlReport); g_EngStatus &= ~ENG_STATUS_STATE_CHANGED; } if (!IS_DUMP_TARGET()) { g_Machine->QuietSetTraceMode(TRACE_NONE); } // Now determine the executing code type and // make that the effective machine. if (IS_CONTEXT_POSSIBLE()) { g_TargetExecMachine = g_Machine->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_TargetExecMachine = g_TargetMachineType; } SetEffMachine(g_TargetExecMachine, TRUE); // 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) { if ((g_EngStatus & ENG_STATUS_SUSPENDED) == 0) { // Nothing to do. return S_OK; } if (g_Machine->GetTraceMode() != TRACE_NONE) { g_EngDefer |= ENG_DEFER_HARDWARE_TRACING; } if (IS_REMOTE_KERNEL_TARGET()) { g_Machine->KdUpdateControlSet(&g_ControlSet); g_EngDefer |= ENG_DEFER_UPDATE_CONTROL_SET; } // Flush context. ChangeRegContext(NULL); // Make sure stale values aren't held across // executions. ResetImplicitData(); FlushMachinePerExecutionCaches(); if (!ResumeAllThreads()) { 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()) { Changed = TRUE; } if (g_CmdState != 'c') { g_CmdState = 'c'; Changed = TRUE; if (!IS_CONTEXT_ACCESSIBLE()) { ADDRFLAT(&PcAddr, 0); } else { g_Machine->GetPC(&PcAddr); if (IS_KERNEL_TARGET()) { HardBrkpt = g_Machine->IsBreakpointInstruction(&PcAddr); } } g_DumpDefault = g_UnasmDefault = g_AssemDefault = PcAddr; } g_EngStatus |= ENG_STATUS_PREPARED_FOR_CALLS; if (HardBrkpt && Flat(PcAddr) == KdDebuggerData.BreakpointWithStatus) { HandleBPWithStatus(); } if (Changed) { if (IS_MACHINE_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); 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); } } HRESULT PrepareForExecution(ULONG NewStatus) { ADDR PcAddr; BOOL fHardBrkpt = FALSE; PTHREAD_INFO StepThread = NULL; ZeroMemory(&g_PrevRelatedPc, sizeof(g_PrevRelatedPc)); // If all processes have exited we don't have any way // to manipulate the debuggee so we must fail immediately. if ((g_EngStatus & ENG_STATUS_PROCESSES_ADDED) && !ANY_PROCESSES()) { return E_UNEXPECTED; } StepAgain: // 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_EngStatus & ENG_STATUS_SUSPENDED) { if (g_CmdState != 's') { if (NewStatus != DEBUG_STATUS_IGNORE_EVENT) { SetExecutionStatus(NewStatus); DBG_ASSERT(IS_RUNNING(g_CmdState)); } else { NewStatus = g_SuspendedExecutionStatus; g_CmdState = g_SuspendedCmdState; } } if ((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->Process->Exited) { MachineInfo* Machine = g_Machine; ChangeRegContext(StepThread); Machine->GetPC(&PcAddr); fHardBrkpt = Machine->IsBreakpointInstruction(&PcAddr); if (fHardBrkpt) { g_WatchBeginCurFunc = 1; Machine->AdjustPCPastBreakpointInstruction (&PcAddr, DEBUG_BREAKPOINT_CODE); if (Flat(*g_StepTraceBp->GetAddr()) != OFFSET_TRACE) { ULONG NextMachine; Machine->GetNextOffset(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--; 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->Process->Exited && !IS_LOCAL_KERNEL_TARGET() && g_CmdState != 's' && g_EventThread != StepThread && (NewStatus != DEBUG_STATUS_GO_NOT_HANDLED || (g_EngStatus & ENG_STATUS_STOP_SESSION))) { ChangeRegContext(g_EventThread); g_Machine->GetPC(&PcAddr); if (g_Machine->IsBreakpointInstruction(&PcAddr)) { g_Machine->AdjustPCPastBreakpointInstruction (&PcAddr, DEBUG_BREAKPOINT_CODE); } if (StepThread != NULL) { ChangeRegContext(StepThread); } } } HRESULT Status; if (g_EngStatus & ENG_STATUS_STOP_SESSION) { // 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_TargetExecMachine = IMAGE_FILE_MACHINE_UNKNOWN; g_EngStatus &= ~ENG_STATUS_PREPARED_FOR_CALLS; if (g_CmdState != 's') { // Now that we've resumed execution notify about the change. NotifyChangeEngineState(DEBUG_CES_EXECUTION_STATUS, NewStatus, TRUE); NotifyExtensions(DEBUG_NOTIFY_SESSION_INACCESSIBLE, 0); } if (fHardBrkpt && 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->SystemId; g_EventProcessSysId = StepThread->Process->SystemId; FindEventProcessThread(); return S_FALSE; } // Once we resume execution the processes and threads // can change so we must flush our notion of what's current. g_CurrentProcess = NULL; g_EventProcess = NULL; g_EventThread = NULL; if (g_EngDefer & ENG_DEFER_DELETE_EXITED) { // Reap any threads and processes that have terminated since // we last executed. if (DeleteExitedInfos()) { OutputProcessInfo("*** exit cleanup ***"); } g_EngDefer &= ~ENG_DEFER_DELETE_EXITED; // If all processes have exited we're done. if (!ANY_PROCESSES()) { if (IS_LIVE_USER_TARGET()) { // If there's an outstanding event continue it. if (g_EngDefer & ENG_DEFER_CONTINUE_EVENT) { ((UserTargetInfo*)g_Target)->m_Services-> ContinueEvent(DBG_CONTINUE); DiscardLastEvent(); } } DiscardMachine(DEBUG_SESSION_END); return E_UNEXPECTED; } } 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); if (g_ProcessHead == NULL) { // All processes are gone so don't consider // it an error if we couldn't resume execution. Status = S_OK; } 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 = 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 = FindThreadBySystemId(g_EventProcess, 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"); } } g_CurrentProcess = g_EventProcess; if (g_CurrentProcess != NULL) { g_CurrentProcess->CurrentThread = g_EventThread; DBG_ASSERT(g_EventThread == NULL || g_EventThread->Process == g_CurrentProcess); } } 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_Machine->GetPC(&BpAddr); // Check whether the exception is a breakpoint. BreakType = g_Machine->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->InitialBreakDone) { g_EngStatus |= ENG_STATUS_AT_INITIAL_BREAK; } // We've seen the initial break for this process. g_EventProcess->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->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->InitialBreak = TRUE; } else if ((!g_EventProcess->InitialBreakWx86) && (g_TargetMachineType != g_EffMachine) && (g_EffMachine == 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->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() && 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_WatchInitialSP) { g_Machine->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_Machine->GetNextOffset(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() || Flat(CurrentSP) >= g_WatchInitialSP))) { g_WatchFunctions.End(PcAddr); return DEBUG_STATUS_BREAK; } if (g_WatchFunctions.IsStarted()) { if (g_WatchTrace) { g_Target-> ProcessWatchTraceEvent((PDBGKD_TRACE_DATA) g_StateChangeData, *PcAddr); } 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_Machine->m_AllMask, DEBUG_OUTPUT_PROMPT_REGISTERS); skipit: g_Machine->GetNextOffset(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->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_SelectExecutionThread == SELTHREAD_INTERNAL_THREAD && g_SelectedThread == g_EventThread) { // 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. g_SelectExecutionThread = SELTHREAD_ANY; g_SelectedThread = NULL; } // 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; PTHREAD_INFO pThreadOwner = NULL; DWORD TID = 0; ADDR addrCritSec; RTL_CRITICAL_SECTION CritSec; char szBangCritsec[32]; // 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. // GetSymbolStdCall((ULONG64)Record->ExceptionAddress, Symbol, sizeof(Symbol), &Displacement, NULL ); 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]) { ADDRFLAT(&addrCritSec, Record->ExceptionInformation[0]); if (GetMemString(&addrCritSec, (PUCHAR)&CritSec, sizeof(CritSec))) { 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); pThreadOwner = FindThreadBySystemId(g_CurrentProcess, TID); } } } if (pThreadOwner) { 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]), pThreadOwner->UserId, pThreadOwner->SystemId, g_CurrentProcess->CurrentThread->UserId, g_CurrentProcess->CurrentThread->SystemId, g_CurrentProcess->CurrentThread->UserId); g_EventThread = pThreadOwner; SetPromptThread(pThreadOwner, 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]) { sprintf(szBangCritsec, "critsec %s", FormatAddr64(Record->ExceptionInformation[0])); dprintf("!%s\n", szBangCritsec); fnBangCmd(NULL, szBangCritsec, NULL, FALSE); } } else if ( !_stricmp("ntdll!RtlAcquireResourceShared", Symbol) || !_stricmp("ntdll!RtlAcquireResourceExclusive", Symbol) || !_stricmp("ntdll!RtlConvertSharedToExclusive", Symbol) ) { dprintf("deadlock in %s ", 1 + strstr(Symbol, "!") ); GetSymbolStdCall(Record->ExceptionInformation[0], Symbol, sizeof(Symbol), &Displacement, NULL); 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 ); GetSymbolStdCall(Record->ExceptionInformation[0], Symbol, sizeof(Symbol), &Displacement, NULL); 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; GetSymbolStdCall(Record->ExceptionInformation[0], Symbol, sizeof(Symbol), &Displacement, NULL); 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) { SIZE_T BytesRead; char TempName[MAX_IMAGE_PATH]; if (NamePtr != 0) { if (g_Target->ReadPointer(g_TargetMachine, NamePtr, &NamePtr) != S_OK) { NamePtr = 0; } } if (NamePtr != 0) { ULONG Done; if (g_Target->ReadVirtual(NamePtr, TempName, sizeof(TempName), &Done) != S_OK || Done != sizeof(TempName)) { NamePtr = 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 { strncpy(NameBuffer, TempName, BufferSize); NameBuffer[BufferSize - 1] = 0; } } 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(ImageBase, OS_HANDLE(ImageFile), NameBuffer, BufferSize)) { NameBuffer[0] = 0; } } if (!NameBuffer[0]) { if (!GetModNameFromLoaderList(g_TargetMachine, 0, ImageBase, NameBuffer, BufferSize, TRUE)) { // Buffer should be big enough for this simple name. DBG_ASSERT(BufferSize >= 32); sprintf(NameBuffer, "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_TargetMachine, 0, ImageBase, NameBuffer, BufferSize, TRUE); } } } //---------------------------------------------------------------------------- // // ConnLiveKernelTargetInfo::WaitForEvent. // //---------------------------------------------------------------------------- HRESULT ConnLiveKernelTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout) { HRESULT Status; ULONG EventStatus; NTSTATUS NtStatus; ULONG ContinueStatus; BOOL SwitchedProcessors; // Timeouts can't easily be supported at the moment and // aren't really necessary. if (Timeout != INFINITE) { return E_NOTIMPL; } Status = PrepareForWait(Flags, &ContinueStatus); if ((g_EngStatus & ENG_STATUS_WAITING) == 0) { return Status; } EventOut("> Executing\n"); for (;;) { EventOut(">> Waiting\n"); SwitchedProcessors = FALSE; // Don't process deferred work if this is // just a processor switch. if (g_CmdState != 's') { ProcessDeferredWork(&ContinueStatus); } if (g_EventProcessSysId) { EventOut(">> Continue with %X\n", ContinueStatus); if (g_CmdState == 's') { // This can either be a real processor switch or // a rewait for state change. Check the switch // processor to be sure. if (g_SwitchProcessor) { DbgKdSwitchActiveProcessor(g_SwitchProcessor - 1); g_SwitchProcessor = 0; SwitchedProcessors = TRUE; } } else { if (g_EngDefer & ENG_DEFER_UPDATE_CONTROL_SET) { NtStatus = DbgKdContinue2(ContinueStatus, g_ControlSet); g_EngDefer &= ~ENG_DEFER_UPDATE_CONTROL_SET; } else { NtStatus = DbgKdContinue(ContinueStatus); } if (!NT_SUCCESS(NtStatus)) { ErrOut("DbgKdContinue failed with status %X\n", NtStatus); Status = HRESULT_FROM_NT(NtStatus); goto Exit; } } } DiscardLastEvent(); if (!IS_MACHINE_SET() && (g_EngOptions & DEBUG_ENGOPT_INITIAL_BREAK) && IS_CONN_KERNEL_TARGET()) { // Ask for a breakin to be sent once the // code gets into resync. g_DbgKdTransport->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 = DbgKdWaitStateChange(&g_StateChange, g_StateChangeBuffer, sizeof(g_StateChangeBuffer) - 2, !SwitchedProcessors); if (!NT_SUCCESS(NtStatus)) { ErrOut("DbgKdWaitStateChange failed: %08lx\n", NtStatus); Status = HRESULT_FROM_NT(NtStatus); goto Exit; } EventOut(">>> State change event %X, proc %d of %d\n", g_StateChange.NewState, g_StateChange.Processor, g_StateChange.NumberProcessors); g_EngStatus |= ENG_STATUS_STATE_CHANGED; if (!IS_MACHINE_SET()) { 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. g_TargetNumberProcessors = g_StateChange.NumberProcessors; Status = InitializeMachine(g_TargetMachineType); if (Status != S_OK) { ErrOut("Unable to initialize target machine information\n"); goto Exit; } CreateKernelProcessAndThreads(); g_EventProcessSysId = VIRTUAL_PROCESS_ID; g_EventThreadSysId = VIRTUAL_THREAD_ID(g_StateChange.Processor); FindEventProcessThread(); // // Load kernel symbols. // VerifyKernelBase(TRUE); g_Target->OutputVersion(); RemoveAllKernelBreakpoints(); } 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 (g_StateChange.NumberProcessors > g_TargetNumberProcessors) { AddKernelThreads(g_TargetNumberProcessors, g_StateChange.NumberProcessors - g_TargetNumberProcessors); g_TargetNumberProcessors = g_StateChange.NumberProcessors; } g_EventProcessSysId = VIRTUAL_PROCESS_ID; g_EventThreadSysId = VIRTUAL_THREAD_ID(g_StateChange.Processor); FindEventProcessThread(); } g_EventPc = g_StateChange.ProgramCounter; g_ControlReport = &g_StateChange.AnyControlReport; g_StateChangeData = g_StateChangeBuffer; if (g_EventThread) { g_EventThread->DataOffset = g_StateChange.Thread; } EventStatus = ProcessStateChange(&g_StateChange, g_StateChangeData); EventOut(">>> StateChange event status %X\n", EventStatus); if (EventStatus == DEBUG_STATUS_NO_DEBUGGEE) { // Machine has rebooted or something else // which breaks the connection. Forget the // connection and go back to waiting. ContinueStatus = DBG_CONTINUE; } else if (EventStatus == DEBUG_STATUS_BREAK || SwitchedProcessors) { // If the event handlers requested a break return // to the caller. This path is also taken // when switching processors to guarantee that // the target doesn't start running when the // user just wanted a processor switch. Status = S_OK; goto Exit; } else { // We're resuming execution so reverse any // command preparation that may have occurred // while processing the event. if ((Status = PrepareForExecution(EventStatus)) != S_OK) { goto Exit; } ContinueStatus = EventStatusToContinue(EventStatus); } } Exit: g_EngStatus &= ~ENG_STATUS_WAITING; // Control is passing back to the caller so the engine must // be ready for command processing. PrepareForCalls(0); // If we did switch processors automatically // update the page directory for the new processor. if (SwitchedProcessors) { if (g_TargetMachine->SetDefaultPageDirectories(PAGE_DIR_ALL) != S_OK) { WarnOut("WARNING: Unable to reset page directories\n"); } } EventOut("> Wait returning %X\n", Status); return Status; } DWORD64 GetKernelModuleBase( ULONG64 Address ) { LIST_ENTRY64 List64; ULONG64 Next64; ULONG64 ListHead; NTSTATUS Status; KLDR_DATA_TABLE_ENTRY64 DataTableBuffer; if ((ListHead = KdDebuggerData.PsLoadedModuleList) == 0) { return 0; } if (g_Target->ReadListEntry(g_TargetMachine, KdDebuggerData.PsLoadedModuleList, &List64) != S_OK) { return 0; } if ((Next64 = List64.Flink) == 0) { return 0; } while (Next64 != ListHead) { if (g_Target->ReadLoaderEntry(g_TargetMachine, Next64, &DataTableBuffer) != S_OK) { break; } Next64 = DataTableBuffer.InLoadOrderLinks.Flink; if ((Address >= (ULONG64)DataTableBuffer.DllBase) && (Address < (ULONG64)DataTableBuffer.DllBase + DataTableBuffer.SizeOfImage) ) { return (DWORD64)DataTableBuffer.DllBase; } } return 0; } HRESULT LoadKdDataBlock( BOOL NotLive ) /*++ Routine Description: This routine get the kernel's debugger data block. Arguments: UseSymbol - Instread of reading the information over the wire, use the symbol information to get the data. This is less reliable, but is necessary for crash dumps. --*/ { HRESULT Status; LIST_ENTRY64 List64; ULONG64 DataList; ULONG Result; ULONG Size = 0; KDDEBUGGER_DATA32 LocalData32; KDDEBUGGER_DATA64 LocalData64; g_TargetBuildLabName[0] = 0; if (NotLive) { if (!g_KdDebuggerDataBlock) { if ( (GetOffsetFromSym( "nt!KdDebuggerDataBlock", (PULONG64) &List64, NULL)) && (List64.Flink) ) { g_KdDebuggerDataBlock = List64.Flink; } else { if (g_SystemVersion > NT_SVER_NT4) { // Only print an error for win2k and above to avoid // error output in "normal" circumstances. ErrOut("KdDebuggerDataBlock not available !\n"); } return E_FAIL; } } } else { if (!g_KdVersion.DebuggerDataList) { // This is always the case in early loader situations // so suppress duplicate error messages. if ((g_EngErr & ENG_ERR_DEBUGGER_DATA) == 0) { ErrOut("Debugger data list address is NULL\n"); } return E_FAIL; } if ((Status = g_Target->ReadListEntry(g_TargetMachine, g_KdVersion.DebuggerDataList, &List64)) != S_OK) { ErrOut("Unable to get address of debugger data list\n"); return Status; } g_KdDebuggerDataBlock = List64.Flink; } // // Get the Size of the KDDEBUGGER_DATA block // if (DbgKdApi64) { DBGKD_DEBUG_DATA_HEADER64 Header; Status = g_Target->ReadVirtual(g_KdDebuggerDataBlock, &Header, sizeof(Header), &Result); if (Status == S_OK && Result == sizeof(Header)) { Size = Header.Size; } } else { DBGKD_DEBUG_DATA_HEADER32 Header; Status = g_Target->ReadVirtual(g_KdDebuggerDataBlock, &Header, sizeof(Header), &Result); if (Status == S_OK && Result == sizeof(Header)) { Size = Header.Size; } } // // Only read as much of the data block as we can hold in the debugger. // if (Size == 0) { // The data block is not present in older triage dumps // so don't give an error message for an expected // condition. if (!IS_KERNEL_TRIAGE_DUMP()) { ErrOut("KdDebuggerDataBlock Size field is 0 - " "can not read datablock further\n"); } return E_FAIL; } if (Size > sizeof(KDDEBUGGER_DATA64)) { Size = sizeof(KDDEBUGGER_DATA64); } // // Now read the data // if (DbgKdApi64) { Status = g_Target->ReadVirtual(g_KdDebuggerDataBlock, &LocalData64, Size, &Result); if (Status != S_OK || Result != Size) { ErrOut("KdDebuggerDataBlock Could not be read\n"); return Status == S_OK ? E_FAIL : Status; } if (g_TargetMachine->m_Ptr64) { memcpy(&KdDebuggerData, &LocalData64, Size); } else { // // Sign extended for X86 // // // Extend the header so it doesn't get whacked // ListEntry32To64((PLIST_ENTRY32)(&LocalData64.Header.List), &(KdDebuggerData.Header.List)); KdDebuggerData.Header.OwnerTag = LocalData64.Header.OwnerTag; KdDebuggerData.Header.Size = LocalData64.Header.Size; // // Sign extend all the 32 bits values to 64 bit // #define UIP(f) if (FIELD_OFFSET(KDDEBUGGER_DATA64, f) < Size) \ { \ KdDebuggerData.f = \ (ULONG64)(LONG64)(LONG)(LocalData64.f); \ } #define CP(f) KdDebuggerData.f = LocalData64.f; UIP(KernBase); UIP(BreakpointWithStatus); UIP(SavedContext); CP(ThCallbackStack); CP(NextCallback); CP(FramePointer); CP(PaeEnabled); UIP(KiCallUserMode); UIP(KeUserCallbackDispatcher); UIP(PsLoadedModuleList); UIP(PsActiveProcessHead); UIP(PspCidTable); UIP(ExpSystemResourcesList); UIP(ExpPagedPoolDescriptor); UIP(ExpNumberOfPagedPools); UIP(KeTimeIncrement); UIP(KeBugCheckCallbackListHead); UIP(KiBugcheckData); UIP(IopErrorLogListHead); UIP(ObpRootDirectoryObject); UIP(ObpTypeObjectType); UIP(MmSystemCacheStart); UIP(MmSystemCacheEnd); UIP(MmSystemCacheWs); UIP(MmPfnDatabase); UIP(MmSystemPtesStart); UIP(MmSystemPtesEnd); UIP(MmSubsectionBase); UIP(MmNumberOfPagingFiles); UIP(MmLowestPhysicalPage); UIP(MmHighestPhysicalPage); UIP(MmNumberOfPhysicalPages); UIP(MmMaximumNonPagedPoolInBytes); UIP(MmNonPagedSystemStart); UIP(MmNonPagedPoolStart); UIP(MmNonPagedPoolEnd); UIP(MmPagedPoolStart); UIP(MmPagedPoolEnd); UIP(MmPagedPoolInformation); CP(MmPageSize); UIP(MmSizeOfPagedPoolInBytes); UIP(MmTotalCommitLimit); UIP(MmTotalCommittedPages); UIP(MmSharedCommit); UIP(MmDriverCommit); UIP(MmProcessCommit); UIP(MmPagedPoolCommit); UIP(MmExtendedCommit); UIP(MmZeroedPageListHead); UIP(MmFreePageListHead); UIP(MmStandbyPageListHead); UIP(MmModifiedPageListHead); UIP(MmModifiedNoWritePageListHead); UIP(MmAvailablePages); UIP(MmResidentAvailablePages); UIP(PoolTrackTable); UIP(NonPagedPoolDescriptor); UIP(MmHighestUserAddress); UIP(MmSystemRangeStart); UIP(MmUserProbeAddress); UIP(KdPrintCircularBuffer); UIP(KdPrintCircularBufferEnd); UIP(KdPrintWritePointer); UIP(KdPrintRolloverCount); UIP(MmLoadedUserImageList); // NT 5.1 additions UIP(NtBuildLab); UIP(KiNormalSystemCall); // NT 5.0 QFE additions UIP(KiProcessorBlock); UIP(MmUnloadedDrivers); UIP(MmLastUnloadedDriver); UIP(MmTriageActionTaken); UIP(MmSpecialPoolTag); UIP(KernelVerifier); UIP(MmVerifierData); UIP(MmAllocatedNonPagedPool); UIP(MmPeakCommitment); UIP(MmTotalCommitLimitMaximum); UIP(CmNtCSDVersion); // NT 5.1 additions UIP(MmPhysicalMemoryBlock); UIP(MmSessionBase); UIP(MmSessionSize); UIP(MmSystemParentTablePage); } } else { if (Size != sizeof(LocalData32)) { ErrOut("Someone changed the definition of KDDEBUGGER_DATA32 - " "please fix\n"); return E_FAIL; } Status = g_Target->ReadVirtual(g_KdDebuggerDataBlock, &LocalData32, sizeof(LocalData32), &Result); if (Status != S_OK || Result != sizeof(LocalData32)) { ErrOut("KdDebuggerDataBlock Could not be read\n"); return Status == S_OK ? E_FAIL : Status; } else { // // Convert all the 32 bits fields to 64 bit // #undef UIP #undef CP #define UIP(f) KdDebuggerData.f = EXTEND64(LocalData32.f) #define CP(f) KdDebuggerData.f = (LocalData32.f) // // Extend the header so it doesn't get whacked // ListEntry32To64((PLIST_ENTRY32)(&LocalData32.Header.List), &(KdDebuggerData.Header.List)); KdDebuggerData.Header.OwnerTag = LocalData32.Header.OwnerTag; KdDebuggerData.Header.Size = LocalData32.Header.Size; UIP(KernBase); UIP(BreakpointWithStatus); UIP(SavedContext); CP(ThCallbackStack); CP(NextCallback); CP(FramePointer); CP(PaeEnabled); UIP(KiCallUserMode); UIP(KeUserCallbackDispatcher); UIP(PsLoadedModuleList); UIP(PsActiveProcessHead); UIP(PspCidTable); UIP(ExpSystemResourcesList); UIP(ExpPagedPoolDescriptor); UIP(ExpNumberOfPagedPools); UIP(KeTimeIncrement); UIP(KeBugCheckCallbackListHead); UIP(KiBugcheckData); UIP(IopErrorLogListHead); UIP(ObpRootDirectoryObject); UIP(ObpTypeObjectType); UIP(MmSystemCacheStart); UIP(MmSystemCacheEnd); UIP(MmSystemCacheWs); UIP(MmPfnDatabase); UIP(MmSystemPtesStart); UIP(MmSystemPtesEnd); UIP(MmSubsectionBase); UIP(MmNumberOfPagingFiles); UIP(MmLowestPhysicalPage); UIP(MmHighestPhysicalPage); UIP(MmNumberOfPhysicalPages); UIP(MmMaximumNonPagedPoolInBytes); UIP(MmNonPagedSystemStart); UIP(MmNonPagedPoolStart); UIP(MmNonPagedPoolEnd); UIP(MmPagedPoolStart); UIP(MmPagedPoolEnd); UIP(MmPagedPoolInformation); CP(MmPageSize); UIP(MmSizeOfPagedPoolInBytes); UIP(MmTotalCommitLimit); UIP(MmTotalCommittedPages); UIP(MmSharedCommit); UIP(MmDriverCommit); UIP(MmProcessCommit); UIP(MmPagedPoolCommit); UIP(MmExtendedCommit); UIP(MmZeroedPageListHead); UIP(MmFreePageListHead); UIP(MmStandbyPageListHead); UIP(MmModifiedPageListHead); UIP(MmModifiedNoWritePageListHead); UIP(MmAvailablePages); UIP(MmResidentAvailablePages); UIP(PoolTrackTable); UIP(NonPagedPoolDescriptor); UIP(MmHighestUserAddress); UIP(MmSystemRangeStart); UIP(MmUserProbeAddress); UIP(KdPrintCircularBuffer); UIP(KdPrintCircularBufferEnd); UIP(KdPrintWritePointer); UIP(KdPrintRolloverCount); UIP(MmLoadedUserImageList); // // DO NOT ADD ANY FIELDS HERE // The 32 bit structure should not be changed // } } // Read build lab information if possible. Result = 0; if (KdDebuggerData.NtBuildLab != 0) { ULONG PreLen; strcpy(g_TargetBuildLabName, "Built by: "); PreLen = strlen(g_TargetBuildLabName); if (g_Target->ReadVirtual(KdDebuggerData.NtBuildLab, g_TargetBuildLabName + PreLen, sizeof(g_TargetBuildLabName) - PreLen - 1, &Result) == S_OK && Result >= 2) { Result += PreLen; } } DBG_ASSERT(Result < sizeof(g_TargetBuildLabName)); g_TargetBuildLabName[Result] = 0; // // Reset specific fields base on the build lab names // NOTE: lab names can be of different cases, so you must do case // insensitive compares. // char BuildLabName[272]; strcpy(BuildLabName, g_TargetBuildLabName); _strlwr(BuildLabName); if (strstr(BuildLabName, "lab01")) { if ((g_TargetBuildNumber > 2405) && (g_TargetMachineType == IMAGE_FILE_MACHINE_I386)) { g_TargetMachine->m_OffsetKThreadNextProcessor = X86_NT51_KTHREAD_NEXTPROCESSOR_OFFSET; } } // // Sanity check and Debug output. // if (KdDebuggerData.Header.OwnerTag != KDBG_TAG) { dprintf("\nKdDebuggerData.Header.OwnerTag is wrong !!!\n"); } KdOut("LoadKdDataBlock %08lx\n", Status); KdOut("KernBase %s\n", FormatAddr64(KdDebuggerData.KernBase)); KdOut("BreakpointWithStatus %s\n", FormatAddr64(KdDebuggerData.BreakpointWithStatus)); KdOut("SavedContext %s\n", FormatAddr64(KdDebuggerData.SavedContext)); KdOut("ThCallbackStack %08lx\n", KdDebuggerData.ThCallbackStack); KdOut("NextCallback %08lx\n", KdDebuggerData.NextCallback); KdOut("FramePointer %08lx\n", KdDebuggerData.FramePointer); KdOut("PaeEnabled %08lx\n", KdDebuggerData.PaeEnabled); KdOut("KiCallUserMode %s\n", FormatAddr64(KdDebuggerData.KiCallUserMode)); KdOut("KeUserCallbackDispatcher %s\n", FormatAddr64(KdDebuggerData.KeUserCallbackDispatcher)); KdOut("PsLoadedModuleList %s\n", FormatAddr64(KdDebuggerData.PsLoadedModuleList)); KdOut("PsActiveProcessHead %s\n", FormatAddr64(KdDebuggerData.PsActiveProcessHead)); KdOut("MmPageSize %s\n", FormatAddr64(KdDebuggerData.MmPageSize)); KdOut("MmLoadedUserImageList %s\n", FormatAddr64(KdDebuggerData.MmLoadedUserImageList)); KdOut("MmSystemRangeStart %s\n", FormatAddr64(KdDebuggerData.MmSystemRangeStart)); KdOut("KiProcessorBlock %s\n", FormatAddr64(KdDebuggerData.KiProcessorBlock)); return Status; } BOOL VerifyKernelBase ( IN BOOL LoadImage ) { PDEBUG_IMAGE_INFO p; // // User mode dumps have no kernel information. // if (IS_USER_TARGET()) { return FALSE; } // // Ask host for version information // if (!IS_DUMP_TARGET()) { // // Force load of KD data block // if (g_SystemVersion <= NT_SVER_NT4) { KdDebuggerData.PsLoadedModuleList = EXTEND64(g_KdVersion.PsLoadedModuleList); KdDebuggerData.KernBase = g_KdVersion.KernBase; } else { if (LoadKdDataBlock(FALSE) != STATUS_SUCCESS) { goto VerifyError; } // // The version and KdDebuggerData blocks should agree on // the version ! // if ((g_KdVersion.KernBase != KdDebuggerData.KernBase) || (g_KdVersion.PsLoadedModuleList != KdDebuggerData.PsLoadedModuleList)) { ErrOut("Debugger can not determine kernel base address\n"); } } } if (LoadImage) { // // Verify only one kernel image loaded & it's at the correct base // For crashdump we may not have the KernBase at this point. // for (p = g_ProcessHead->ImageHead; p && KdDebuggerData.KernBase; p = p->Next) { if (p->BaseOfImage == KdDebuggerData.KernBase) { // // Already loaded with current base address // DelImageByBase(g_CurrentProcess, p->BaseOfImage); break; } } // // If acceptable kernel image was not found load one now // // Wow ! possible recursive call to bangReload - that's OK though :-) // ... as long as we only call with "NT" // if (g_Target->Reload(KERNEL_MODULE_NAME) == E_INVALIDARG) { // The most likely cause of this is missing paths. // We don't necessarily need a path to load // the kernel, so try again and ignore path problems. g_Target->Reload("-P "KERNEL_MODULE_NAME); } } // // After the kernel mode symbols are loaded, we can now try to load // the DataBlock from the dump file. // if (IS_DUMP_TARGET() && (!IS_KERNEL_TRIAGE_DUMP() || g_TriageDumpHasDebuggerData)) { LoadKdDataBlock(TRUE); } if (g_TargetMachineType == IMAGE_FILE_MACHINE_IA64) { // // Try to determine the kernel base virtual mapping address // for IA64. This should be done as early as possible // to enable later virtual translations to work. // if (!IS_KERNEL_TRIAGE_DUMP()) { if (!KdDebuggerData.MmSystemParentTablePage) { GetOffsetFromSym("nt!MmSystemParentTablePage", &KdDebuggerData.MmSystemParentTablePage, NULL); } if (KdDebuggerData.MmSystemParentTablePage) { ADDR Addr; ULONG64 SysPtp; ADDRFLAT(&Addr, KdDebuggerData.MmSystemParentTablePage); if (GetMemQword(&Addr, &SysPtp)) { g_Ia64Machine. SetKernelPageDirectory(SysPtp << IA64_VALID_PFN_SHIFT); } } } // // Get the system call address from the debugger data block // Added around build 2204. // Default to symbols otherwise. // g_SystemCallVirtualAddress = 0; if (KdDebuggerData.KiNormalSystemCall) { g_Target->ReadPointer(g_TargetMachine, KdDebuggerData.KiNormalSystemCall, &g_SystemCallVirtualAddress); } if (!g_SystemCallVirtualAddress) { g_SystemCallVirtualAddress = ExtGetExpression( "nt!KiNormalSystemCall" ); } if (!g_SystemCallVirtualAddress) { g_SystemCallVirtualAddress = ExtGetExpression( "nt!.KiNormalSystemCall" ); } if (!g_SystemCallVirtualAddress) { WarnOut("Could not get KiNormalSystemCall address\n"); } } // // Now that we have symbols and a data block try to // get CmNtCSDVersion, first from the data block and // then from the symbols if necessary. // // If we didn't get unloaded driver information try to // get it from symbols. // // if (!IS_KERNEL_TRIAGE_DUMP()) { if (!KdDebuggerData.CmNtCSDVersion) { GetOffsetFromSym("nt!CmNtCSDVersion", &KdDebuggerData.CmNtCSDVersion, NULL); } if (KdDebuggerData.CmNtCSDVersion) { ADDR Addr; ULONG CmNtCSDVersion; ADDRFLAT(&Addr, KdDebuggerData.CmNtCSDVersion); if (GetMemDword(&Addr, &CmNtCSDVersion)) { SetTargetNtCsdVersion(CmNtCSDVersion); } } if (KdDebuggerData.MmUnloadedDrivers == 0) { GetOffsetFromSym("nt!MmUnloadedDrivers", &KdDebuggerData.MmUnloadedDrivers, NULL); } if (KdDebuggerData.MmLastUnloadedDriver == 0) { GetOffsetFromSym("nt!MmLastUnloadedDriver", &KdDebuggerData.MmLastUnloadedDriver, NULL); } if (KdDebuggerData.KiProcessorBlock == 0) { GetOffsetFromSym("nt!KiProcessorBlock", &KdDebuggerData.KiProcessorBlock, NULL); } if (KdDebuggerData.MmPhysicalMemoryBlock == 0) { GetOffsetFromSym("nt!MmPhysicalMemoryBlock", &KdDebuggerData.MmPhysicalMemoryBlock, NULL); } } // // Try to get the start of system memory. // This may be zero because we are looking at an NT 4 system, so try // looking it up using symbols. // if (!KdDebuggerData.MmSystemRangeStart) { GetOffsetFromSym("nt!MmSystemRangeStart", &KdDebuggerData.MmSystemRangeStart, NULL); } if (KdDebuggerData.MmSystemRangeStart) { g_Target->ReadPointer(g_TargetMachine, KdDebuggerData.MmSystemRangeStart, &g_SystemRangeStart); } // // If we did not have symbols, at least pick a default value. // if (!g_SystemRangeStart) { g_SystemRangeStart = 0xFFFFFFFF80000000; } if (KdDebuggerData.KernBase < g_SystemRangeStart) { ErrOut("KdDebuggerData.KernBase < g_SystemRangeStart\n"); } return TRUE; VerifyError: // This is always the case in early loader situations // so suppress duplicate error messages. if ((g_EngErr & ENG_ERR_DEBUGGER_DATA) == 0) { WarnOut("Kernel base address could not be determined. " "Please try to reconnect with .reload\n"); g_EngErr |= ENG_ERR_DEBUGGER_DATA; } return FALSE; } #define EXCEPTION_CODE StateChange->u.Exception.ExceptionRecord.ExceptionCode #define FIRST_CHANCE StateChange->u.Exception.FirstChance ULONG ProcessStateChange(PDBGKD_ANY_WAIT_STATE_CHANGE StateChange, PCHAR StateChangeData) { ULONG EventStatus; // // 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(g_TargetMachineType) { case IMAGE_FILE_MACHINE_IA64: Count = g_ControlReport->IA64ControlReport.InstructionCount; break; case IMAGE_FILE_MACHINE_ALPHA: case IMAGE_FILE_MACHINE_AXP64: Count = g_ControlReport->AlphaControlReport.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_ProcessHead, g_EventPc, g_EventPc + Count - 1)) { SuspendExecution(); RemoveBreakpoints(); } if (StateChange->NewState == DbgKdExceptionStateChange) { // // Read the system range start address from the target system. // if (g_SystemRangeStart == 0) { VerifyKernelBase(FALSE); } EventOut("Exception %X at %p\n", EXCEPTION_CODE, g_EventPc); 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 { 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) { DbgKdContinue(DBG_CONTINUE); ResetConnection(StateChange->u.LoadSymbols.BaseOfDll == KD_REBOOT ? DEBUG_SESSION_REBOOT : DEBUG_SESSION_HIBERNATE); 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 { PDEBUG_IMAGE_INFO pImage; CHAR fname[_MAX_FNAME]; CHAR ext[_MAX_EXT]; CHAR ImageName[256]; CHAR ModName[256]; LPSTR pModName = ModName; LPSTR p; ModName[0] = '\0'; _splitpath( StateChangeData, NULL, NULL, fname, ext ); sprintf( ImageName, "%s%s", fname, ext ); if (_stricmp(ext, ".sys") == 0) { pImage = g_EventProcess ? g_EventProcess->ImageHead : NULL; while (pImage) { if (_stricmp(ImageName, pImage->ImagePath) == 0) { ModName[0] = 'c'; strcpy( &ModName[1], ImageName ); p = strchr( ModName, '.' ); if (p) { *p = '\0'; } ModName[8] = '\0'; break; } pImage = pImage->Next; } } else if (StateChange->u.LoadSymbols.BaseOfDll == KdDebuggerData.KernBase) { // // Recognize the kernel module. // pModName = KERNEL_MODULE_NAME; } EventStatus = NotifyLoadModuleEvent( 0, StateChange->u.LoadSymbols.BaseOfDll, StateChange->u.LoadSymbols.SizeOfImage, *pModName ? pModName : NULL, ImageName, StateChange->u.LoadSymbols.CheckSum, 0); } } 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 continuation. 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 void ResetConnection(ULONG Reason) { if (Reason == DEBUG_SESSION_REBOOT) { dprintf("Shutdown occurred...unloading all symbol tables.\n"); } else { dprintf("Hibernate occurred\n"); } DiscardMachine(Reason); } void CreateKernelProcessAndThreads(void) { g_EngNotify++; // Create the fake kernel process. g_EventProcessSysId = VIRTUAL_PROCESS_ID; g_EventThreadSysId = VIRTUAL_THREAD_ID(0); NotifyCreateProcessEvent(0, (ULONG64)VIRTUAL_PROCESS_HANDLE, 0, 0, NULL, NULL, 0, 0, (ULONG64)VIRTUAL_THREAD_HANDLE(0), 0, 0, 0, DEBUG_PROCESS_ONLY_THIS_PROCESS, 0); // Create any remaining threads. AddKernelThreads(1, g_TargetNumberProcessors - 1); g_EngNotify--; // Don't leave event variables set as these // weren't true events. g_EventProcessSysId = 0; g_EventThreadSysId = 0; g_EventProcess = NULL; g_EventThread = NULL; } void AddKernelThreads(ULONG Start, ULONG Count) { g_EngNotify++; g_EventProcessSysId = VIRTUAL_PROCESS_ID; while (Count-- > 0) { g_EventThreadSysId = VIRTUAL_THREAD_ID(Start); NotifyCreateThreadEvent((ULONG64)VIRTUAL_THREAD_HANDLE(Start), 0, 0, 0); Start++; } g_EngNotify--; } //---------------------------------------------------------------------------- // // LocalLiveKernelTargetInfo::WaitForEvent. // //---------------------------------------------------------------------------- HRESULT LocalLiveKernelTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout) { HRESULT Status; ULONG i; ULONG ContinueStatus; SYSTEM_INFO SystemInfo; if (g_EngStatus & ENG_STATUS_PROCESSES_ADDED) { // A wait has already been done. Local kernels // can only generate a single event so further // waiting is not possible. return E_UNEXPECTED; } // Even though local kernels don't really wait the standard // preparation still needs to be done to set things // up and generate the usual callbacks. The continuation // part of things is simply ignored. Status = PrepareForWait(Flags, &ContinueStatus); if (FAILED(Status)) { return Status; } GetSystemInfo(&SystemInfo); g_TargetNumberProcessors = SystemInfo.dwNumberOfProcessors; g_Target->GetKdVersion(); // This is the first wait. Simulate any // necessary events such as process and thread // creations and image loads. Status = InitializeMachine(g_TargetMachineType); if (Status != S_OK) { ErrOut("Unable to initialize target machine information\n"); return Status; } // Don't give real callbacks for processes/threads as // they're just faked in the kernel case. g_EngNotify++; CreateKernelProcessAndThreads(); // Load kernel version information and symbols. VerifyKernelBase(TRUE); g_Target->OutputVersion(); g_EventProcessSysId = VIRTUAL_PROCESS_ID; // Current process always starts at zero. g_EventThreadSysId = VIRTUAL_THREAD_ID(0); // 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; // 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_EventPc = 0; g_EngStatus |= ENG_STATUS_STATE_CHANGED; // The engine is now initialized so a real event // can be generated. g_EngNotify--; FindEventProcessThread(); Status = S_OK; g_EngStatus &= ~ENG_STATUS_WAITING; // Control is passing back to the caller so the engine must // be ready for command processing. PrepareForCalls(0); EventOut("> Wait returning %X\n", Status); return Status; } //---------------------------------------------------------------------------- // // ExdiLiveKernelTargetInfo::WaitForEvent. // //---------------------------------------------------------------------------- HRESULT ExdiLiveKernelTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout) { HRESULT Status; ULONG EventStatus; ULONG ContinueStatus; // eXDI deals with hardware exceptions, not software // exceptions, so there's no concept of handled/not-handled // and first/second-chance. Status = PrepareForWait(Flags, &ContinueStatus); if ((g_EngStatus & ENG_STATUS_WAITING) == 0) { return Status; } EventOut("> Executing\n"); for (;;) { EventOut(">> Waiting\n"); ProcessDeferredWork(&ContinueStatus); if (g_EventProcessSysId) { EventOut(">> Continue with %X\n", ContinueStatus); 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); goto Exit; } } DiscardLastEvent(); DWORD Cookie; if ((Status = m_Server->StartNotifyingRunChg(&m_RunChange, &Cookie)) != S_OK) { ErrOut("IeXdiServer::StartNotifyingRunChg failed, 0x%X\n", Status); goto Exit; } RUN_STATUS_TYPE RunStatus; if ((Status = m_Server-> GetRunStatus(&RunStatus, &m_RunChange.m_HaltReason, &m_RunChange.m_ExecAddress, &m_RunChange.m_ExceptionCode)) != S_OK) { ErrOut("IeXdiServer::GetRunStatus failed, 0x%X\n", Status); goto Exit; } 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) { Status = S_FALSE; goto Exit; } else if (WaitStatus != WAIT_OBJECT_0) { Status = WIN32_LAST_STATUS(); ErrOut("WaitForSingleObject failed, 0x%X\n", Status); goto Exit; } EventOut(">>> RunChange halt reason %d\n", m_RunChange.m_HaltReason); g_EngStatus |= ENG_STATUS_STATE_CHANGED; if (!IS_MACHINE_SET()) { dprintf("Kernel Debugger connection established\n"); g_TargetNumberProcessors = 1; // eXDI kernels are always treated as Win2K so // it's assumed it uses the 64-bit API. if (m_KdSupport == EXDI_KD_NONE) { DbgKdApi64 = TRUE; g_SystemVersion = NT_SVER_W2K; } g_Target->GetKdVersion(); Status = InitializeMachine(m_ExpectedMachine); if (Status != S_OK) { ErrOut("Unable to initialize target machine information\n"); goto Exit; } CreateKernelProcessAndThreads(); g_EventProcessSysId = VIRTUAL_PROCESS_ID; g_EventThreadSysId = VIRTUAL_THREAD_ID(0); FindEventProcessThread(); // // Load kernel symbols. // if (g_ActualSystemVersion > NT_SVER_START && g_ActualSystemVersion < NT_SVER_END) { VerifyKernelBase(TRUE); } else { // Initialize some debugger data fields from known // information as there isn't a real data block. KdDebuggerData.MmPageSize = g_Machine->m_PageSize; if (g_TargetMachineType == IMAGE_FILE_MACHINE_AMD64) { // AMD64 always operates in PAE mode. KdDebuggerData.PaeEnabled = TRUE; } } g_Target->OutputVersion(); } else { g_EventProcessSysId = VIRTUAL_PROCESS_ID; g_EventThreadSysId = VIRTUAL_THREAD_ID(0); FindEventProcessThread(); } g_EventPc = m_RunChange.m_ExecAddress; g_ControlReport = NULL; g_StateChangeData = NULL; EventStatus = ProcessRunChange(m_RunChange.m_HaltReason, m_RunChange.m_ExceptionCode); EventOut(">>> RunChange event status %X\n", EventStatus); if (EventStatus == DEBUG_STATUS_NO_DEBUGGEE) { // Machine has rebooted or something else // which breaks the connection. Forget the // connection and go back to waiting. ContinueStatus = DBG_CONTINUE; } else if (EventStatus == DEBUG_STATUS_BREAK) { // If the event handlers requested a break return // to the caller. Status = S_OK; goto Exit; } else { // We're resuming execution so reverse any // command preparation that may have occurred // while processing the event. if ((Status = PrepareForExecution(EventStatus)) != S_OK) { goto Exit; } ContinueStatus = EventStatusToContinue(EventStatus); } } Exit: g_EngStatus &= ~ENG_STATUS_WAITING; // Control is passing back to the caller so the engine must // be ready for command processing. PrepareForCalls(0); EventOut("> Wait returning %X\n", Status); return Status; } ULONG ProcessRunChange(ULONG HaltReason, ULONG ExceptionCode) { ULONG EventStatus; EXCEPTION_RECORD64 Record; 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_EventPc; // Hardware exceptions are always severe so always // report them as second-chance. EventStatus = NotifyExceptionEvent(&Record, FALSE, FALSE); break; case hrBp: // Fake a breakpoint exception record. ZeroMemory(&Record, sizeof(Record)); Record.ExceptionCode = STATUS_BREAKPOINT; Record.ExceptionAddress = g_EventPc; 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_EventPc; EventStatus = ProcessBreakpointOrStepException(&Record, TRUE); break; default: ErrOut("Unknown HALT_REASON %d\n", HaltReason); EventStatus = DEBUG_STATUS_BREAK; break; } return EventStatus; } //---------------------------------------------------------------------------- // // UserTargetInfo::WaitForEvent. // //---------------------------------------------------------------------------- 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; // // 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, &Process, ThreadBuffer, ThreadsAlloc, &ThreadCount)) != S_OK) { delete ThreadBuffer; return Status; } if (ThreadCount <= ThreadsAlloc) { break; } } // // 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, Process, 0, 0, NULL, NULL, 0, 0, Threads->Handle, 0, 0, Flags | ENG_PROC_THREAD_CLOSE_HANDLE, Options, ENG_PROC_THREAD_CLOSE_HANDLE); // 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 600 // 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 3 HRESULT UserTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout) { DEBUG_EVENT64 Event; DWORD ContinueStatus; HRESULT Status; ULONG EventStatus; ULONG UseTimeout = Timeout; ULONG TimeoutCount = 0; ULONG EventUsed; ULONG ContinueDefer; PPENDING_PROCESS Pending; ULONG PendingFlags, PendingOptions; ULONG ExamineResumeProcId = 0; // Fail if there isn't a debuggee. if (!ANY_PROCESSES()) { return E_UNEXPECTED; } Status = PrepareForWait(Flags, &ContinueStatus); if ((g_EngStatus & ENG_STATUS_WAITING) == 0) { return Status; } if (g_AllPendingFlags & ENG_PROC_ANY_ATTACH) { dprintf("*** wait with pending attach\n"); } EventOut("> Executing\n"); for (;;) { EventOut(">> Waiting\n"); ProcessDeferredWork(&ContinueStatus); if (g_EngDefer & ENG_DEFER_CONTINUE_EVENT) { for (;;) { EventOut(">> Continue with %X\n", ContinueStatus); 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); goto Exit; } } } DiscardLastEvent(); PendingFlags = 0; PendingOptions = DEBUG_PROCESS_ONLY_THIS_PROCESS; if (g_AllPendingFlags & ENG_PROC_ANY_ATTACH) { // If we're attaching noninvasively or reattaching // and still haven't done the work go ahead and do it now. if (g_AllPendingFlags & ENG_PROC_ANY_EXAMINE) { Pending = FindPendingProcessByFlags(ENG_PROC_ANY_EXAMINE); if (Pending == NULL) { DBG_ASSERT(FALSE); goto Exit; } if ((Status = ExamineActiveProcess (m_Services, Pending->Id, Pending->Flags, Pending->Options, &Event)) != S_OK) { goto Exit; } // 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. ExamineResumeProcId = Pending->Id; PendingFlags = Pending->Flags; PendingOptions = Pending->Options; RemovePendingProcess(Pending); EventUsed = sizeof(Event); // This event is not a real continuable event. ContinueDefer = 0; goto WaitDone; } // 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. UseTimeout = min(Timeout, ATTACH_PENDING_TIMEOUT); } 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. UseTimeout = min(Timeout, DEFAULT_WAIT_TIMEOUT); } SUSPEND_ENGINE(); if (g_EngStatus & ENG_STATUS_EXIT_CURRENT_WAIT) { Status = E_PENDING; } else { Status = m_Services-> WaitForEvent(UseTimeout, &Event, sizeof(Event), &EventUsed); } RESUME_ENGINE(); ContinueDefer = ENG_DEFER_CONTINUE_EVENT; WaitDone: if (Status == S_OK) { 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"); Status = E_FAIL; goto Exit; } g_EngDefer |= ContinueDefer; 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(); } g_EngStatus |= ENG_STATUS_STATE_CHANGED; 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 && (g_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"); UseTimeout = Timeout; // If we're completing a full attach // we are now a fully active debugger. PendingFlags &= ~ENG_PROC_EXAMINED; // Expect a break-in. g_EngStatus |= ENG_STATUS_PENDING_BREAK_IN; TimeoutCount = 0; } RemovePendingProcess(Pending); } } if (!IS_MACHINE_SET()) { ULONG Machine; if ((Status = m_Services-> GetTargetInfo(&Machine, &g_TargetNumberProcessors, &g_TargetPlatformId, &g_TargetBuildNumber, &g_TargetCheckedBuild, g_TargetServicePackString, sizeof(g_TargetServicePackString), g_TargetBuildLabName, sizeof(g_TargetBuildLabName))) != S_OK) { ErrOut("Unable to retrieve target machine " "information\n"); goto Exit; } SetTargetSystemVersionAndBuild(g_TargetBuildNumber, g_TargetPlatformId); DbgKdApi64 = g_SystemVersion > NT_SVER_NT4; if ((Status = InitializeMachine(Machine)) != S_OK) { ErrOut("Unable to initialize target machine " "information\n"); goto Exit; } } if (PendingFlags & ENG_PROC_ANY_EXAMINE) { // 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. g_Target->Reload("-s -P"); } 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; } EventStatus = ProcessDebugEvent(&Event, PendingFlags, PendingOptions); EventOut(">>> DebugEvent status %X\n", EventStatus); // If the event handlers requested a break return // to the caller. if (EventStatus == DEBUG_STATUS_BREAK) { Status = S_OK; goto Exit; } else { // We're resuming execution so reverse any // command preparation that may have occurred // while processing the event. // If we just started examining a process we // can resume things as it's OK to execute. if (ExamineResumeProcId) { SuspendResumeThreads(FindProcessBySystemId (ExamineResumeProcId), FALSE, NULL); ExamineResumeProcId = 0; } if ((Status = PrepareForExecution(EventStatus)) != S_OK) { goto Exit; } ContinueStatus = EventStatusToContinue(EventStatus); } } else { if (Status == S_FALSE) { TimeoutCount++; if (g_AllPendingFlags & ENG_PROC_ATTACHED) { VerifyPendingProcesses(); if (!ANY_PROCESSES()) { Status = E_FAIL; goto Exit; } if (TimeoutCount == 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(); TimeoutCount = 0; } } else if (g_EngStatus & ENG_STATUS_PENDING_BREAK_IN) { if (TimeoutCount == PENDING_BREAK_IN_MESSAGE_TIMEOUT_LIMIT) { dprintf("Break-in sent, waiting %d seconds...\n", (g_PendingBreakInTimeoutLimit * DEFAULT_WAIT_TIMEOUT) / 1000); } else if (TimeoutCount >= g_PendingBreakInTimeoutLimit) { // 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. WarnOut("WARNING: Break-in timed out, suspending.\n"); WarnOut(" This is usually caused by another " "thread holding the loader lock\n"); SynthesizeWakeEvent(&Event, g_ProcessHead->SystemId, g_ProcessHead->ThreadHead-> SystemId); EventUsed = sizeof(Event); ContinueDefer = 0; TimeoutCount = 0; g_EngStatus &= ~ENG_STATUS_PENDING_BREAK_IN; Status = S_OK; goto WaitDone; } } else if (Timeout != INFINITE && Timeout <= UseTimeout) { Status = S_FALSE; goto Exit; } if (Timeout != INFINITE) { // Update overall timeout. This // isn't incredibly accurate but it // doesn't really need to be. Timeout -= UseTimeout; } // Loop back to waiting. } else if (Status == E_OUTOFMEMORY) { // If we got an out of memory error, wait again } else { ErrOut("IUserDebugServices::WaitForEvent failed " "with status 0x%X\n", Status); goto Exit; } } } Exit: g_EngStatus &= ~ENG_STATUS_WAITING; // Control is passing back to the caller so the engine must // be ready for command processing. PrepareForCalls(0); // If we have an extra suspend count from examining resume it out now // that any normal suspends have been done and it's safe // to remove the excess suspend. if (ExamineResumeProcId) { SuspendResumeThreads(FindProcessBySystemId(ExamineResumeProcId), FALSE, NULL); } EventOut("> Wait returning %X\n", Status); return Status; } /*** ProcessDebugEvent - main dispatch table * * Purpose: * As debug events come in, they each have to be handled in a unique * manner. This routine does all of the processing required for each * event. * * Also this routine serves the callback mechanism for VDM debug events * using the VDMDBG.DLL apis (they require the ability of calling back * into the debugger). * * Significant events include creation and termination of processes * and threads, and events such as breakpoints. * * Data structures for processes and threads are created and * maintained for use by the program. * *************************************************************************/ ULONG ProcessDebugEvent(DEBUG_EVENT64* Event, ULONG PendingFlags, ULONG PendingOptions) { ULONG EventStatus; PSTR ImagePath; CHAR NameBuffer[MAX_IMAGE_PATH]; ULONG ModuleSize, CheckSum, TimeDateStamp; char ModuleName[MAX_MODULE]; switch(Event->dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: // We don't have a process yet but // getting the name can involve reading process // memory, which in some cases currently wants // g_CurrentProcess set. Hack up a temporary // process with just the handle. PROCESS_INFO TempProcess; THREAD_INFO TempThread; memset(&TempProcess, 0, sizeof(TempProcess)); memset(&TempThread, 0, sizeof(TempThread)); TempProcess.FullHandle = Event->u.CreateProcessInfo.hProcess; TempProcess.Handle = (HANDLE)(ULONG_PTR)TempProcess.FullHandle; TempThread.Handle = Event->u.CreateProcessInfo.hThread; TempThread.Process = &TempProcess; g_CurrentProcess = &TempProcess; g_CurrentProcess->ThreadHead = &TempThread; g_CurrentProcess->CurrentThread = &TempThread; GetEventName(Event->u.CreateProcessInfo.hFile, Event->u.CreateProcessInfo.lpBaseOfImage, Event->u.CreateProcessInfo.lpImageName, Event->u.CreateProcessInfo.fUnicode, NameBuffer, sizeof(NameBuffer)); GetHeaderInfo((ULONG64)Event->u.CreateProcessInfo.lpBaseOfImage, &CheckSum, &TimeDateStamp, &ModuleSize); CreateModuleNameFromPath(NameBuffer, ModuleName); LoadWow64ExtsIfNeeded(); // Clear the temporary process setting. g_CurrentProcess = NULL; EventStatus = NotifyCreateProcessEvent( (ULONG64)Event->u.CreateProcessInfo.hFile, (ULONG64)Event->u.CreateProcessInfo.hProcess, (ULONG64)Event->u.CreateProcessInfo.lpBaseOfImage, ModuleSize, ModuleName, NameBuffer, CheckSum, TimeDateStamp, (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); 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: GetEventName(Event->u.LoadDll.hFile, Event->u.LoadDll.lpBaseOfDll, Event->u.LoadDll.lpImageName, Event->u.LoadDll.fUnicode, NameBuffer, sizeof(NameBuffer)); GetHeaderInfo((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); 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; } typedef BOOL (__stdcall * PFN_SWITCHDESKTOP)(HDESK hDesktop); typedef HDESK (__stdcall *PFN_GETTHREADDESKTOP)(DWORD dwThreadId); typedef BOOL (__stdcall *PFN_CLOSEDESKTOP)(HDESK hDesktop); PFN_SWITCHDESKTOP g_SwitchDesktop; PFN_GETTHREADDESKTOP g_GetThreadDesktop; PFN_CLOSEDESKTOP g_CloseDesktop; BOOL g_InitUserApis; BOOL g_UserApisAvailable; #define ISTS() (!!(USER_SHARED_DATA->SuiteMask & (1 << TerminalServer))) #define FIRST_CHANCE Event->u.Exception.dwFirstChance ULONG ProcessEventException(DEBUG_EVENT64* Event) { ULONG ExceptionCode; ULONG EventStatus; BOOL OutputDone = FALSE; ExceptionCode = Event->u.Exception.ExceptionRecord.ExceptionCode; g_EventPc = (ULONG64) Event->u.Exception.ExceptionRecord.ExceptionAddress; EventOut("Exception %X at %p\n", ExceptionCode, g_EventPc); // // 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() && !SYSTEM_PROCESSES()) { if (!g_InitUserApis) { HINSTANCE hUser = LoadLibrary("user32.dll"); if (hUser) { g_SwitchDesktop = (PFN_SWITCHDESKTOP) GetProcAddress(hUser, "SwitchDesktop"); g_GetThreadDesktop = (PFN_GETTHREADDESKTOP) GetProcAddress(hUser, "GetThreadDesktop"); g_CloseDesktop = (PFN_CLOSEDESKTOP) GetProcAddress(hUser, "CloseDesktop"); if (g_SwitchDesktop && g_GetThreadDesktop && g_CloseDesktop) { g_UserApisAvailable = TRUE; } } g_InitUserApis = TRUE; } if (g_UserApisAvailable) { HDESK hDesk; hDesk = g_GetThreadDesktop(::GetCurrentThreadId()); g_SwitchDesktop(hDesk); g_CloseDesktop(hDesk); } } if (g_EventThread == NULL) { ErrOut("ERROR: Exception %X occurred on unknown thread\n", ExceptionCode); return DEBUG_STATUS_BREAK; } if (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: EventStatus = ProcessBreakpointOrStepException (&Event->u.Exception.ExceptionRecord, FIRST_CHANCE); break; case STATUS_VDM_EVENT: // do nothing, it's already handled EventStatus = DEBUG_STATUS_IGNORE_EVENT; break; case STATUS_ACCESS_VIOLATION: if (FIRST_CHANCE && (g_EngOptions & DEBUG_ENGOPT_IGNORE_LOADER_EXCEPTIONS)) { CHAR chSymBuffer[MAX_SYMBOL_LEN]; LPSTR s; ULONG64 displacement; // // 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. // GetSymbolStdCall((ULONG64)(Event->u.Exception.ExceptionRecord. ExceptionAddress), chSymBuffer, sizeof(chSymBuffer), &displacement, NULL); s = (LPSTR)chSymBuffer; if (!_strnicmp( s, "ntdll!", 6 )) { s += 6; if (*s == '_') s += 1; if (!_stricmp( s, "LdrpSnapThunk" ) || !_stricmp( s, "LdrpWalkImportDescriptor" )) { EventStatus = DEBUG_STATUS_GO_NOT_HANDLED; break; } } } goto NotifyException; case STATUS_POSSIBLE_DEADLOCK: if (g_TargetPlatformId == VER_PLATFORM_WIN32_NT) { DBG_ASSERT(IS_USER_TARGET()); 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 OutputEventDebugString(OUTPUT_DEBUG_STRING_INFO64* Info) { LPSTR Str, StrOffset, Str2; BOOL b; ULONG dwNumberOfBytesRead; HDI hdi; DWORD dwInputSig; 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 (g_Target->ReadVirtual(Info->lpDebugStringData, Str, Info->nDebugStringLength, &dwNumberOfBytesRead) == S_OK && (dwNumberOfBytesRead == (SIZE_T)Info->nDebugStringLength)) { // // Special processing for hacky debug input string // if (g_Target->ReadVirtual(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); g_Target->WriteVirtual(Info->lpDebugStringData + 6, Str2, (DWORD)hdi.cLength, NULL); free(Str2); } else { ErrOut("Unable to allocate prompt buffer\n"); } } 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::WaitForEvent. // //---------------------------------------------------------------------------- HRESULT DumpTargetInfo::WaitForEvent(ULONG Flags, ULONG Timeout) { HRESULT Status; ULONG i; ULONG ContinueStatus; BOOL HaveContext = FALSE; if (g_EngStatus & ENG_STATUS_PROCESSES_ADDED) { // A wait has already been done. Crash dumps // can only generate a single event so further // waiting is not possible. return E_UNEXPECTED; } // Even though dumps don't really wait the standard // preparation still needs to be done to set things // up and generate the usual callbacks. The continuation // part of things is simply ignored. Status = PrepareForWait(Flags, &ContinueStatus); if (FAILED(Status)) { return Status; } // This is the first wait. Simulate any // necessary events such as process and thread // creations and image loads. Status = InitializeMachine(g_TargetMachineType); if (Status != S_OK) { ErrOut("Unable to initialize target machine information\n"); return Status; } // Don't give real callbacks for processes/threads as // they're just faked in the dump case. g_EngNotify++; if (IS_KERNEL_TARGET()) { ULONG CurProc; CreateKernelProcessAndThreads(); // Load kernel symbols. VerifyKernelBase(TRUE); g_Target->OutputVersion(); if (!IS_KERNEL_TRIAGE_DUMP()) { if (g_TargetMachineType == IMAGE_FILE_MACHINE_ALPHA || g_TargetMachineType == IMAGE_FILE_MACHINE_AXP64) { // Try and get the symbol for KiPcrBaseAddress. if (!GetOffsetFromSym("nt!KiPcrBaseAddress", &g_DumpKiPcrBaseAddress, NULL)) { // Not a critical failure. g_DumpKiPcrBaseAddress = 0; } } if (KdDebuggerData.KiProcessorBlock) { ULONG PtrSize = g_TargetMachine->m_Ptr64 ? sizeof(ULONG64) : sizeof(ULONG); for (i = 0; i < g_TargetNumberProcessors; i++) { Status = ReadPointer(g_TargetMachine, KdDebuggerData.KiProcessorBlock + i * PtrSize, &(g_DumpKiProcessors[i])); if ((Status != S_OK) || !g_DumpKiProcessors[i]) { ErrOut("KiProcessorBlock[%d] could not be read\n", i); Status = E_FAIL; g_EngNotify--; goto Exit; } } HaveContext = TRUE; } } else { HaveContext = TRUE; } CurProc = ((KernelDumpTargetInfo*)this)->GetCurrentProcessor(); if (CurProc == (ULONG)-1) { WarnOut("Could not determine the current processor, " "using zero\n"); CurProc = 0; } if (HaveContext) { g_EventProcessSysId = VIRTUAL_PROCESS_ID; g_EventThreadSysId = VIRTUAL_THREAD_ID(CurProc); } // 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; if ((g_TargetMachineType == IMAGE_FILE_MACHINE_I386 || g_TargetMachineType == IMAGE_FILE_MACHINE_IA64) && !IS_KERNEL_TRIAGE_DUMP() && HaveContext) { // // 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(); ChangeRegContext(g_EventThread); if (g_TargetMachine-> SetDefaultPageDirectories(PAGE_DIR_ALL) != S_OK) { WarnOut("WARNING: Unable to reset page directories\n"); } ChangeRegContext(NULL); // Flush the cache just in case as vmem mappings changed. g_VirtualCache.Empty(); } } else { ULONG Suspend; ULONG64 Teb; UserDumpTargetInfo* UserDump = (UserDumpTargetInfo*)g_Target; g_Target->OutputVersion(); // Create the process. g_EventProcessSysId = UserDump->m_EventProcess; if (UserDump->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, UserDump->m_ThreadCount); NotifyCreateProcessEvent(0, (ULONG64)VIRTUAL_PROCESS_HANDLE, 0, 0, NULL, NULL, 0, 0, (ULONG64)VIRTUAL_THREAD_HANDLE(0), Teb, 0, 0, DEBUG_PROCESS_ONLY_THIS_PROCESS, 0); // Update thread suspend count from dump info. g_EventThread->SuspendCount = Suspend; // Create any remaining threads. for (i = 1; i < UserDump->m_ThreadCount; i++) { UserDump->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->SuspendCount = Suspend; } g_EventThreadSysId = UserDump->m_EventThread; 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_EventPc = (ULONG64)g_DumpException.ExceptionAddress; if (HaveContext) { FindEventProcessThread(); 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. // Status = g_Target->Reload(HaveContext ? "" : "-P"); ChangeRegContext(NULL); if (HaveContext && Status != S_OK) { g_EngNotify--; goto Exit; } if (IS_USER_TARGET()) { PDEBUG_IMAGE_INFO Image; // Try and look up the build lab information from kernel32. Image = GetImageByName(g_ProcessHead, "kernel32", INAME_MODULE); if (Image != NULL) { char Item[64]; ULONG PreLen; sprintf(Item, "\\StringFileInfo\\%04x%04x\\FileVersion", VER_VERSION_TRANSLATION); strcpy(g_TargetBuildLabName, "kernel32.dll version: "); PreLen = strlen(g_TargetBuildLabName); if (FAILED(GetImageVersionInformation (Image->ImagePath, Image->BaseOfImage, Item, g_TargetBuildLabName + PreLen, sizeof(g_TargetBuildLabName) - PreLen, NULL))) { g_TargetBuildLabName[0] = 0; } } } g_EngStatus |= ENG_STATUS_STATE_CHANGED; // The engine is now initialized so a real event // can be generated. g_EngNotify--; NotifyExceptionEvent(&g_DumpException, g_DumpExceptionFirstChance, g_DumpException.ExceptionCode == STATUS_BREAKPOINT || g_DumpException.ExceptionCode == STATUS_WX86_BREAKPOINT); Status = S_OK; Exit: g_EngStatus &= ~ENG_STATUS_WAITING; // Control is passing back to the caller so the engine must // be ready for command processing. PrepareForCalls(0); #if 0 // this is now doe in condbg/windbg // Run the bugcheck analyzers if this dump has a bugcheck. if (Status == S_OK && HaveContext && IS_KERNEL_TARGET()) { ULONG Code; ULONG64 Args[4]; if (ReadBugCheckData(&Code, Args) == S_OK && Code != 0) { CallBugCheckExtension(NULL); } } #endif EventOut("> Wait returning %X\n", Status); return Status; } //---------------------------------------------------------------------------- // // 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; } void GetOtherExceptionParameters(ULONG Code, 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; } } *Params = &g_EventFilters[FILTER_DEFAULT_EXCEPTION].Params; *Command = &g_EventFilters[FILTER_DEFAULT_EXCEPTION].Command; } 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) { g_NumOtherExceptions--; memmove(g_OtherExceptionList + Index, g_OtherExceptionList + Index + 1, (g_NumOtherExceptions - Index) * sizeof(g_OtherExceptionList[0])); memmove(g_OtherExceptionCommands + Index, g_OtherExceptionCommands + Index + 1, (g_NumOtherExceptions - Index) * sizeof(g_OtherExceptionCommands[0])); 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; } 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 && ChangeString(&EventCommand->Command[0], &EventCommand->CommandSize[0], Command[0][0] ? Command[0] : NULL) != S_OK) { return MEMORY; } if (Command[1] != NULL) { 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; } 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; GetOtherExceptionParameters((ULONG)ExceptionCode, &CurParams, &CurEventCommand); 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")); DBG_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) { g_NumOtherExceptions = 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; }