//---------------------------------------------------------------------------- // // Handles stepping, tracing and go. // // Copyright (C) Microsoft Corporation, 1997-2001. // //---------------------------------------------------------------------------- #include "ntsdp.hpp" #define DBG_KWT 0 #define DBG_UWT 0 Breakpoint* g_GoBreakpoints[MAX_GO_BPS]; ULONG g_NumGoBreakpoints; // Pass count of trace breakpoint. ULONG g_StepTracePassCount; ULONG64 g_StepTraceInRangeStart = (ULONG64)-1; ULONG64 g_StepTraceInRangeEnd; IMAGEHLP_LINE64 g_SrcLine; // Current source line for step/trace BOOL g_SrcLineValid; // Validity of SrcLine information BOOL g_WatchTrace; BOOL g_WatchWhole; ADDR g_WatchTarget; ULONG64 g_WatchInitialSP; ULONG64 g_WatchBeginCurFunc = 1; ULONG64 g_WatchEndCurFunc; WatchFunctions g_WatchFunctions; //---------------------------------------------------------------------------- // // WatchFunctions. // //---------------------------------------------------------------------------- WatchFunctions::WatchFunctions(void) { m_Started = FALSE; } void WatchFunctions::Start(void) { ULONG i; m_TotalInstr = 0; m_TotalWatchTraceEvents = 0; m_TotalWatchThreadMismatches = 0; for (i = 0; i < WF_BUCKETS; i++) { m_Funcs[i] = NULL; } m_Sorted = NULL; m_CallTop = NULL; m_CallBot = NULL; m_CallLevel = 0; m_Started = TRUE; } void WatchFunctions::End(PADDR PcAddr) { g_StepTracePassCount = 0; g_EngStatus &= ~ENG_STATUS_USER_INTERRUPT; if (IS_CONN_KERNEL_TARGET()) { g_DbgKdTransport->m_BreakIn = FALSE; } if (!m_Started) { return; } ULONG TotalInstr; if (IS_KERNEL_TARGET()) { PDBGKD_TRACE_DATA td = (PDBGKD_TRACE_DATA)g_StateChangeData; if (g_WatchWhole) { if (td[1].s.Instructions == TRACE_DATA_INSTRUCTIONS_BIG) { TotalInstr = td[2].LongNumber; } else { TotalInstr = td[1].s.Instructions; } } else { if (PcAddr != NULL) { g_Target->ProcessWatchTraceEvent(td, *PcAddr); } while (m_CallTop != NULL) { PopCall(); } TotalInstr = m_TotalInstr; } g_BreakpointsSuspended = FALSE; g_WatchInitialSP = 0; } else { if (m_CallTop != NULL) { OutputCall(m_CallTop); } while (m_CallTop != NULL) { PopCall(); } TotalInstr = m_TotalInstr; dprintf("\n"); } m_Started = FALSE; dprintf("%d instructions were executed in %d events " "(%d from other threads)\n", TotalInstr, m_TotalWatchTraceEvents, m_TotalWatchThreadMismatches); if (!g_WatchWhole) { OutputFunctions(); } if (!IS_KERNEL_TARGET()) { OutputSysCallFunctions(); } dprintf("\n"); Clear(); } void WatchFunctions::OutputFunctions(void) { WatchFunction* Func; dprintf("\n%-43.43s Invocations MinInst MaxInst AvgInst\n", "Function Name"); for (Func = m_Sorted; Func != NULL; Func = Func->Sort) { dprintf("%-47.47s%8d%8d%8d%8d\n", Func->Symbol, Func->Calls, Func->MinInstr, Func->MaxInstr, Func->Calls ? Func->TotalInstr / Func->Calls : 0); } } void WatchFunctions::OutputSysCallFunctions(void) { WatchFunction* Func; ULONG TotalSysCalls = 0; for (Func = m_Sorted; Func != NULL; Func = Func->Sort) { TotalSysCalls += Func->SystemCalls; } if (TotalSysCalls == 1) { dprintf("\n%d system call was executed\n", TotalSysCalls); } else { dprintf("\n%d system calls were executed\n", TotalSysCalls); } if (TotalSysCalls == 0) { return; } dprintf("\nCalls System Call\n"); for (Func = m_Sorted; Func != NULL; Func = Func->Sort) { if (Func->SystemCalls > 0) { dprintf("%5d %s\n", Func->SystemCalls, Func->Symbol); } } } WatchFunction* WatchFunctions::FindAlways(PSTR Sym, ULONG64 Start) { WatchFunction* Func = Find(Sym); if (Func == NULL) { Func = Add(Sym, Start); } return Func; } WatchCallStack* WatchFunctions::PushCall(WatchFunction* Func) { WatchCallStack* Call = new WatchCallStack; if (Call != NULL) { ZeroMemory(Call, sizeof(*Call)); Call->Prev = m_CallTop; Call->Next = NULL; if (m_CallTop == NULL) { m_CallBot = Call; } else { m_CallTop->Next = Call; m_CallLevel++; } m_CallTop = Call; Call->Func = Func; Call->Level = m_CallLevel; } return Call; } void WatchFunctions::PopCall(void) { if (m_CallTop == NULL) { return; } WatchCallStack* Call = m_CallTop; if (Call->Prev != NULL) { Call->Prev->Next = Call->Next; } else { m_CallBot = Call->Next; } if (Call->Next != NULL) { Call->Next->Prev = Call->Prev; } else { m_CallTop = Call->Prev; } m_CallLevel = m_CallTop != NULL ? m_CallTop->Level : 0; ReuseCall(Call, NULL); delete Call; } #define MAXPCOFFSET 10 WatchCallStack* WatchFunctions::PopCallsToCallSite(PADDR Pc) { WatchCallStack* Call = m_CallTop; while (Call != NULL) { if ((Flat(*Pc) - Flat(Call->CallSite)) < MAXPCOFFSET) { break; } Call = Call->Prev; } if (Call == NULL) { // No matching call site found. return NULL; } // Pop off calls above the call site. while (m_CallTop != Call) { PopCall(); } return m_CallTop; } WatchCallStack* WatchFunctions::PopCallsToFunctionStart(ULONG64 Start) { WatchCallStack* Call = m_CallTop; while (Call != NULL) { if (Start == Call->Func->StartOffset) { break; } Call = Call->Prev; } if (Call == NULL) { // No matching calling function found. return NULL; } // Pop off calls above the calling function. while (m_CallTop != Call) { PopCall(); } return m_CallTop; } void WatchFunctions::ReuseCall(WatchCallStack* Call, WatchFunction* ReinitFunc) { if (Call->Prev != NULL) { Call->Prev->ChildInstrCount += Call->InstrCount + Call->ChildInstrCount; } WatchFunction* Func = Call->Func; if (Func != NULL) { Func->Calls++; Func->TotalInstr += Call->InstrCount; m_TotalInstr += Call->InstrCount; if (Func->MinInstr > Call->InstrCount) { Func->MinInstr = Call->InstrCount; } if (Func->MaxInstr < Call->InstrCount) { Func->MaxInstr = Call->InstrCount; } } ZeroMemory(&Call->CallSite, sizeof(Call->CallSite)); Call->Func = ReinitFunc; Call->Level = m_CallLevel; Call->InstrCount = 0; Call->ChildInstrCount = 0; } #define MAX_INDENT_LEVEL 50 void WatchFunctions::OutputCall(WatchCallStack* Call) { LONG i; dprintf("%5ld %5ld [%3ld]", Call->InstrCount, Call->ChildInstrCount, Call->Level); if (Call->Level < MAX_INDENT_LEVEL) { for (i = 0; i < Call->Level; i++) { dprintf(" "); } } else { for (i = 0; i < MAX_INDENT_LEVEL + 1; i++) { dprintf(" "); } } dprintf(" %s\n", Call->Func->Symbol); } WatchFunction* WatchFunctions::Add(PSTR Sym, ULONG64 Start) { WatchFunction* Func = new WatchFunction; if (Func == NULL) { return NULL; } ZeroMemory(Func, sizeof(*Func)); Func->StartOffset = Start; Func->MinInstr = -1; Func->SymbolLength = strlen(Sym); strncat(Func->Symbol, Sym, sizeof(Func->Symbol) - 1); // // Add into appropriate hash bucket. // // Hash under full name as that's what searches will // hash with. int Bucket = Hash(Sym, Func->SymbolLength); Func->Next = m_Funcs[Bucket]; m_Funcs[Bucket] = Func; // // Add into sorted list. // WatchFunction* Cur, *Prev; Prev = NULL; for (Cur = m_Sorted; Cur != NULL; Cur = Cur->Sort) { if (strcmp(Func->Symbol, Cur->Symbol) <= 0) { break; } Prev = Cur; } Func->Sort = Cur; if (Prev == NULL) { m_Sorted = Func; } else { Prev->Sort = Func; } return Func; } WatchFunction* WatchFunctions::Find(PSTR Sym) { int SymLen = strlen(Sym); int Bucket = Hash(Sym, SymLen); WatchFunction* Func = m_Funcs[Bucket]; while (Func != NULL) { if (SymLen == Func->SymbolLength && !strncmp(Sym, Func->Symbol, sizeof(Func->Symbol) - 1)) { break; } Func = Func->Next; } return Func; } void WatchFunctions::Clear(void) { ULONG i; for (i = 0; i < WF_BUCKETS; i++) { WatchFunction* Func; while (m_Funcs[i] != NULL) { Func = m_Funcs[i]->Next; delete m_Funcs[i]; m_Funcs[i] = Func; } } m_Sorted = NULL; } //---------------------------------------------------------------------------- // // ConnLiveKernelTargetInfo watch trace methods. // //---------------------------------------------------------------------------- typedef struct _TRACE_DATA_SYM { ULONG64 SymMin; ULONG64 SymMax; } TRACE_DATA_SYM, *PTRACE_DATA_SYM; TRACE_DATA_SYM TraceDataSyms[256]; UCHAR NextTraceDataSym = 0; // what's the next one to be replaced UCHAR NumTraceDataSyms = 0; // how many are valid? void ConnLiveKernelTargetInfo::InitializeWatchTrace(void) { ADDR SpAddr; g_Machine->GetSP(&SpAddr); g_WatchInitialSP = Flat(SpAddr); g_BreakpointsSuspended = TRUE; NextTraceDataSym = 0; NumTraceDataSyms = 0; } LONG SymNumFor ( ULONG64 Pc ) { long index; for ( index = 0; index < NumTraceDataSyms; index++ ) { if ( (TraceDataSyms[index].SymMin <= Pc) && (TraceDataSyms[index].SymMax > Pc) ) { return index; } } return -1; } VOID PotentialNewSymbol ( ULONG64 Pc ) { if ( -1 != SymNumFor(Pc) ) { return; // we've already seen this one } TraceDataSyms[NextTraceDataSym].SymMin = g_WatchBeginCurFunc; TraceDataSyms[NextTraceDataSym].SymMax = g_WatchEndCurFunc; // // Bump the "next" pointer, wrapping if necessary. Also bump the // "valid" pointer if we need to. // NextTraceDataSym = (NextTraceDataSym + 1) % (sizeof(TraceDataSyms) / sizeof(TraceDataSyms[0]));; if ( NumTraceDataSyms < NextTraceDataSym ) { NumTraceDataSyms = NextTraceDataSym; } } void ConnLiveKernelTargetInfo::ProcessWatchTraceEvent( PDBGKD_TRACE_DATA TraceData, ADDR PcAddr ) { // // All of the real information is captured in the TraceData unions // sent to us by the kernel. Here we have two main jobs: // // 1) Print out the data in the TraceData record. // 2) See if we need up update the SymNum table before // returning to the kernel. // char SymName[MAX_SYMBOL_LEN]; ULONG index; ULONG64 qw; ADDR CurSP; g_WatchFunctions.RecordEvent(); g_Machine->GetSP(&CurSP); if ( AddrEqu(g_WatchTarget, PcAddr) && (Flat(CurSP) >= g_WatchInitialSP) ) { // // HACK HACK HACK // // fix up the last trace entry. // ULONG lastEntry = TraceData[0].LongNumber; if (lastEntry != 0) { TraceData[lastEntry].s.LevelChange = -1; // this is wrong if we // filled the symbol table! TraceData[lastEntry].s.SymbolNumber = 0; } } for ( index = 1; index < TraceData[0].LongNumber; index++ ) { WatchFunction* Func; WatchCallStack* Call; ULONG64 SymOff = TraceDataSyms[TraceData[index].s.SymbolNumber].SymMin; GetSymbolStdCall(SymOff, SymName, sizeof(SymName), &qw, NULL); if (!SymName[0]) { SymName[0] = '0'; SymName[1] = 'x'; strcpy(SymName + 2, FormatAddr64(SymOff)); qw = 0; } #if DBG_KWT dprintf("!%2d: lev %2d instr %4u %s %s\n", index, TraceData[index].s.LevelChange, TraceData[index].s.Instructions == TRACE_DATA_INSTRUCTIONS_BIG ? TraceData[index + 1].LongNumber : TraceData[index].s.Instructions, FormatAddr64(SymOff), SymName); #endif Func = g_WatchFunctions.FindAlways(SymName, SymOff - qw); if (Func == NULL) { ErrOut("Unable to allocate watch function\n"); goto Flush; } Call = g_WatchFunctions.GetTopCall(); if (Call == NULL || TraceData[index].s.LevelChange > 0) { if (Call == NULL) { // Treat the initial entry as a pseudo-call to // get it pushed. TraceData[index].s.LevelChange = 1; } while (TraceData[index].s.LevelChange != 0) { Call = g_WatchFunctions.PushCall(Func); if (Call == NULL) { ErrOut("Unable to allocate watch call level\n"); goto Flush; } TraceData[index].s.LevelChange--; } } else if (TraceData[index].s.LevelChange < 0) { while (TraceData[index].s.LevelChange != 0) { g_WatchFunctions.PopCall(); TraceData[index].s.LevelChange++; } // The level change may not actually be accurate, so // attempt to match up the current symbol offset with // some level of the call stack. Call = g_WatchFunctions.PopCallsToFunctionStart(SymOff); if (Call == NULL) { WarnOut(">> Unable to match return to %s\n", SymName); Call = g_WatchFunctions.GetTopCall(); } } else { // We just made a horizontal call. g_WatchFunctions.ReuseCall(Call, Func); } ULONG InstrCount; if (TraceData[index].s.Instructions == TRACE_DATA_INSTRUCTIONS_BIG) { InstrCount = TraceData[++index].LongNumber; } else { InstrCount = TraceData[index].s.Instructions; } if (Call != NULL) { Call->InstrCount += InstrCount; g_WatchFunctions.OutputCall(Call); } } // // now see if we need to add a new symbol // index = SymNumFor(Flat(PcAddr)); if (-1 == index) { /* yup, add the symbol */ GetAdjacentSymOffsets(Flat(PcAddr), &g_WatchBeginCurFunc, &g_WatchEndCurFunc); if ((g_WatchBeginCurFunc == 0) || (g_WatchEndCurFunc == (ULONG64)-1)) { // Couldn't determine function, fake up // a single-byte function. g_WatchBeginCurFunc = g_WatchEndCurFunc = Flat(PcAddr); } PotentialNewSymbol(Flat(PcAddr)); } else { g_WatchBeginCurFunc = TraceDataSyms[index].SymMin; g_WatchEndCurFunc = TraceDataSyms[index].SymMax; } if ((g_WatchBeginCurFunc <= Flat(g_WatchTarget)) && (Flat(g_WatchTarget) < g_WatchEndCurFunc)) { // The "exit" address is in the symbol range; // fix it so this isn't the case. if (Flat(PcAddr) < Flat(g_WatchTarget)) { g_WatchEndCurFunc = Flat(g_WatchTarget); } else { g_WatchBeginCurFunc = Flat(g_WatchTarget) + 1; } } Flush: FlushCallbacks(); } //---------------------------------------------------------------------------- // // UserTargetInfo watch trace methods. // //---------------------------------------------------------------------------- LONG g_DeferredLevelChange; void UserTargetInfo::InitializeWatchTrace(void) { g_DeferredLevelChange = 0; } void UserTargetInfo::ProcessWatchTraceEvent( PDBGKD_TRACE_DATA TraceData, ADDR PcAddr ) { WatchFunction* Func; WatchCallStack* Call; ULONG64 Disp64; CHAR Disasm[MAX_DISASM_LEN]; g_WatchFunctions.RecordEvent(); // // Get current function and see if it matches current. If so, bump // count in current, otherwise, update to new level // GetSymbolStdCall(Flat(PcAddr), Disasm, sizeof(Disasm), &Disp64, NULL); // If there's no symbol for the current address create a // fake symbol for the instruction address. if (!Disasm[0]) { Disasm[0] = '0'; Disasm[1] = 'x'; strcpy(Disasm + 2, FormatAddr64(Flat(PcAddr))); Disp64 = 0; } Func = g_WatchFunctions.FindAlways(Disasm, Flat(PcAddr) - Disp64); if (Func == NULL) { ErrOut("Unable to allocate watch symbol\n"); goto Flush; } g_Machine->Disassemble(&PcAddr, Disasm, FALSE); Call = g_WatchFunctions.GetTopCall(); if (Call == NULL) { // // First symbol in the list // Call = g_WatchFunctions.PushCall(Func); if (Call == NULL) { ErrOut("Unable to allocate watch symbol\n"); goto Flush; } // At least one instruction must have executed // in this call to register it so initialize to one. // Also, one instruction was executed to get to the // first trace point so count it here. Call->InstrCount += 2; } else { if (g_DeferredLevelChange < 0) { g_DeferredLevelChange = 0; g_WatchFunctions.OutputCall(Call); // We have to see if this is really returning to a call site. // We do this because of try-finally funnies LONG OldLevel = g_WatchFunctions.GetCallLevel(); WatchCallStack* CallSite = g_WatchFunctions.PopCallsToCallSite(&PcAddr); if (CallSite == NULL) { WarnOut(">> No match on ret %s\n", Disasm); } else { if (OldLevel - 1 != CallSite->Level) { WarnOut(">> More than one level popped %d -> %d\n", OldLevel, CallSite->Level); } ZeroMemory(&CallSite->CallSite, sizeof(CallSite->CallSite)); Call = CallSite; } } if (Call->Func == Func && g_DeferredLevelChange == 0) { Call->InstrCount++; } else { g_WatchFunctions.OutputCall(Call); if (g_DeferredLevelChange > 0) { g_DeferredLevelChange = 0; Call = g_WatchFunctions.PushCall(Func); if (Call == NULL) { ErrOut("Unable to allocate watch symbol\n"); goto Flush; } } else { g_WatchFunctions.ReuseCall(Call, Func); } // At least one instruction must have executed // in this call to register it so initialize to one. Call->InstrCount++; } } #if DBG_UWT dprintf("! %3d %s", Call != NULL ? Call->InstrCount : -1, Disasm); #endif // // Adjust watch level to compensate for kernel-mode callbacks // if (Call->InstrCount == 1) { if (!_stricmp(Call->Func->Symbol, "ntdll!_KiUserCallBackDispatcher")) { g_WatchFunctions.ChangeCallLevel(1); Call->Level = g_WatchFunctions.GetCallLevel(); } else if (!_stricmp(Call->Func->Symbol, "ntdll!_ZwCallbackReturn")) { g_WatchFunctions.ChangeCallLevel(-2); Call->Level = g_WatchFunctions.GetCallLevel(); } } if (g_Machine->IsCallDisasm(Disasm)) { Call->CallSite = PcAddr; g_DeferredLevelChange = 1; } else if (g_Machine->IsReturnDisasm(Disasm)) { g_DeferredLevelChange = -1; } else if (g_Machine->IsSystemCallDisasm(Disasm)) { PSTR CallName; ULONG i; WatchCallStack* SysCall = Call; CallName = strchr(Call->Func->Symbol, '!'); if (!CallName) { CallName = Call->Func->Symbol; } else { CallName++; } if (!strcmp(CallName, "*SharedUserSystemCall")) { // We're in a Whistler system call thunk // and the interesting system call symbol // is the previous level. SysCall = Call->Prev; } if (SysCall != NULL) { SysCall->Func->SystemCalls++; // ZwRaiseException returns out two levels after the call. if (!_stricmp(SysCall->Func->Symbol, "ntdll!ZwRaiseException") || !_stricmp(SysCall->Func->Symbol, "ntdll!_ZwRaiseException")) { g_WatchFunctions.ChangeCallLevel(-1); } } g_WatchFunctions.ChangeCallLevel(-1); } Flush: FlushCallbacks(); } //---------------------------------------------------------------------------- // // Support functions. // //---------------------------------------------------------------------------- /*** parseStepTrace - parse step or trace * * Purpose: * Parse the step ("p") or trace ("t") command. Command * syntax is "[~]p|t[b] [=][count]. Once parsed, * call fnStepTrace to step or trace the program. * * Input: * pThread - nonNULL for breakpoint on specific thread * fThreadFreeze - TRUE if all threads except pThread are frozen * *g_CurCmd - pointer to operands in command string * chcmd - 'p' for step, 't' for trace, 'w' for watch * * Output: * None. * * Exceptions: * error exit: * SYNTAX - indirectly through GetExpression * *************************************************************************/ VOID parseStepTrace ( PTHREAD_INFO pThread, BOOL fThreadFreeze, UCHAR chcmd ) { ADDR addr1; ULONG64 value2; UCHAR ch; CHAR chAddrBuffer[MAX_SYMBOL_LEN]; ULONG64 displacement; if (!IS_EXECUTION_POSSIBLE()) { error(SESSIONNOTSUP); } if (!IS_MACHINE_ACCESSIBLE()) { error(BADTHREAD); } if (IS_LIVE_USER_TARGET()) { if (g_AllProcessFlags & ENG_PROC_EXAMINED) { ErrOut("The debugger is not attached so " "process execution cannot be monitored\n"); return; } else if ((g_EngDefer & ENG_DEFER_CONTINUE_EVENT) == 0) { ErrOut("Due to the break-in timeout the debugger " "cannot step or trace\n"); return; } } if (chcmd == 'w') { if (IS_KERNEL_TARGET() && g_TargetMachineType != IMAGE_FILE_MACHINE_I386) { error(UNIMPLEMENT); } if ((PeekChar() == 't') || (IS_KERNEL_TARGET() && PeekChar() == 'w')) { g_WatchTrace = TRUE; g_WatchWhole = *g_CurCmd == 'w'; g_WatchBeginCurFunc = g_WatchEndCurFunc = 0; g_CurCmd++; } else { error(SYNTAX); } } else { g_WatchTrace = FALSE; // // if next character is 'b' and command is 't' perform branch trace // ch = PeekChar(); if ((chcmd == 't') && (tolower(ch) == 'b')) { if (!g_Machine->IsStepStatusSupported(DEBUG_STATUS_STEP_BRANCH)) { error(SESSIONNOTSUP); } chcmd = 'b'; g_CurCmd++; } // // if next character is 'r', toggle flag to output registers // on display on breakpoint. // ch = PeekChar(); if (tolower(ch) == 'r') { g_CurCmd++; g_OciOutputRegs = !g_OciOutputRegs; } } g_Machine->GetPC(&addr1); // default to current PC if (PeekChar() == '=') { g_CurCmd++; GetAddrExpression(SEGREG_CODE, &addr1); } value2 = 1; if ((ch = PeekChar()) != '\0' && ch != ';') { value2 = GetExpression(); } else if (chcmd == 'w') { GetSymbolStdCall(Flat(addr1), chAddrBuffer, sizeof(chAddrBuffer), &displacement, NULL); if (displacement == 0 && chAddrBuffer[ 0 ] != '\0') { ADDR Addr2; g_Machine->GetRetAddr(&Addr2); value2 = Flat(Addr2); dprintf("Tracing %s to return address %s\n", chAddrBuffer, FormatAddr64(value2)); if (g_WatchWhole) { g_WatchBeginCurFunc = value2; g_WatchEndCurFunc = 0; } } } if (((LONG)value2 <= 0) && (!g_WatchTrace)) { error(SYNTAX); } fnStepTrace(&addr1, value2, // count or watch end address pThread, fThreadFreeze, chcmd); } // // Returns TRUE if the current step/trace should be passed over. // BOOL StepTracePass( PADDR pcaddr ) { // If we have valid source line information and we're stepping // by source line, check and see if we moved from one line to another. if ((g_SrcOptions & SRCOPT_STEP_SOURCE) && g_SrcLineValid) { IMAGEHLP_LINE64 Line; DWORD Disp; Line.SizeOfStruct = sizeof(Line); if (SymGetLineFromAddr64(g_CurrentProcess->Handle, Flat(*pcaddr), &Disp, &Line)) { if (Line.LineNumber == g_SrcLine.LineNumber) { // The common case is that we're still in the same line, // so check for a name match by pointer as a very quick // trivial accept. If there's a mismatch we need to // do the hard comparison. if (Line.FileName == g_SrcLine.FileName || _strcmpi(Line.FileName, g_SrcLine.FileName) == 0) { // We're still on the same line so don't treat // this as motion. return TRUE; } } // We've changed lines so we drop one from the pass count. // SrcLine also needs to be updated. g_SrcLine = Line; } else { // If we can't get line number information for the current // address we treat it as a transition on the theory that // it's better to stop than to skip interesting code. g_SrcLineValid = FALSE; } } if (--g_StepTracePassCount > 0) { if (!g_WatchFunctions.IsStarted()) { // If the engine doesn't break for some other reason // on this intermediate step it should output the // step information to show the user the stepping // path. g_EngDefer |= ENG_DEFER_OUTPUT_CURRENT_INFO; } return TRUE; } return FALSE; } /*** fnStepTrace - step or trace the program * * Purpose: * To continue execution of the program with a temporary * breakpoint set to stop after the next instruction * executed (trace - 't') or the instruction in the next * memory location (step - 'p'). The PC is also set * as well as a pass count variable. * * Input: * addr - new value of PC * count - passcount for step or trace * pThread - thread pointer to qualify step/trace, NULL for all * chStepType - 't' for trace; 'p' for step * * Output: * cmdState - set to 't' for trace; 'p' for step * g_StepTracePassCount - pass count for step/trace * *************************************************************************/ void fnStepTrace ( PADDR Addr, ULONG64 Count, PTHREAD_INFO Thread, BOOL ThreadFreeze, UCHAR StepType ) { // If we're stepping a particular thread it better // be the current context thread so that the machine // activity occurs on the appropriate thread. DBG_ASSERT(Thread == NULL || Thread == g_RegContextThread); if ((g_SrcOptions & SRCOPT_STEP_SOURCE) && fFlat(*Addr)) { ULONG Disp; // Get the current line information so it's possible to // tell when the line changes. g_SrcLine.SizeOfStruct = sizeof(g_SrcLine); g_SrcLineValid = SymGetLineFromAddr64(g_CurrentProcess->Handle, Flat(*Addr), &Disp, &g_SrcLine); } g_Machine->SetPC(Addr); g_StepTracePassCount = (ULONG)Count; g_SelectedThread = Thread; g_SelectExecutionThread = ThreadFreeze ? SELTHREAD_THREAD : SELTHREAD_ANY; if (StepType == 'w') { ULONG NextMachine; g_Target->InitializeWatchTrace(); g_WatchFunctions.Start(); g_WatchTarget = *Addr; g_Machine->GetNextOffset(TRUE, &g_WatchTarget, &NextMachine); if ( Flat(g_WatchTarget) != OFFSET_TRACE || Count != 1) { if (IS_KERNEL_TARGET()) { SetupSpecialCalls(); } g_StepTracePassCount = 0xfffffff; if ( Count != 1 ) { Flat(g_WatchTarget) = Count; } } StepType = 't'; } g_CmdState = StepType; switch(StepType) { case 'b': g_ExecutionStatusRequest = DEBUG_STATUS_STEP_BRANCH; break; case 't': g_ExecutionStatusRequest = DEBUG_STATUS_STEP_INTO; break; case 'p': default: g_ExecutionStatusRequest = DEBUG_STATUS_STEP_OVER; break; } if (Thread) { g_StepTraceBp->m_Process = Thread->Process; g_StepTraceBp->m_MatchThread = Thread; } else { g_StepTraceBp->m_Process = g_CurrentProcess; g_StepTraceBp->m_MatchThread = g_CurrentProcess->CurrentThread; } if (StepType == 'b') { // Assume that taken branch trace is always performed by // hardware so set the g_StepTraceBp address to OFFSET_TRACE // (the value returned by GetNextOffset to signal the // hardware stepping mode). DBG_ASSERT(g_Machine-> IsStepStatusSupported(DEBUG_STATUS_STEP_BRANCH)); ADDRFLAT(g_StepTraceBp->GetAddr(), OFFSET_TRACE); } else { ULONG NextMachine; g_Machine->GetNextOffset(g_CmdState == 'p', g_StepTraceBp->GetAddr(), &NextMachine); g_StepTraceBp->SetProcType(NextMachine); } GetCurrentMemoryOffsets(&g_StepTraceInRangeStart, &g_StepTraceInRangeEnd); g_StepTraceBp->m_Flags |= DEBUG_BREAKPOINT_ENABLED; g_StepTraceCmdState = g_CmdState; g_EngStatus &= ~ENG_STATUS_USER_INTERRUPT; NotifyChangeEngineState(DEBUG_CES_EXECUTION_STATUS, g_ExecutionStatusRequest, TRUE); } /*** fnGoExecution - continue execution with temporary breakpoints * * Purpose: * Function of the "[~[]g[=][]*" command. * * Set the PC to startaddr. Set the temporary breakpoints in * golist with the addresses pointed by *bparray and the count * in gocnt to bpcount. Set cmdState to exit the command processor * and continue program execution. * * Input: * startaddr - new starting address * bpcount - number of temporary breakpoints * pThread - thread pointer to qualify , NULL for all * fThreadFreeze - TRUE if freezing all but pThread thread * bpaddr - pointer to array of temporary breakpoints * * Output: * cmdState - set to continue execution * *************************************************************************/ void fnGoExecution ( ULONG ExecStatus, PADDR StartAddr, ULONG BpCount, PTHREAD_INFO Thread, BOOL ThreadFreeze, PADDR BpArray ) { ULONG Count; // If we're resuming a particular thread it better // be the current context thread so that the machine // activity occurs on the appropriate thread. DBG_ASSERT(Thread == NULL || Thread == g_RegContextThread); if (IS_CONTEXT_ACCESSIBLE()) { g_Machine->SetPC(StartAddr); } // Remove old go breakpoints. for (Count = 0; Count < g_NumGoBreakpoints; Count++) { if (g_GoBreakpoints[Count] != NULL) { RemoveBreakpoint(g_GoBreakpoints[Count]); g_GoBreakpoints[Count] = NULL; } } DBG_ASSERT(BpCount <= MAX_GO_BPS); g_NumGoBreakpoints = BpCount; // Add new go breakpoints. for (Count = 0; Count < g_NumGoBreakpoints; Count++) { HRESULT Status; // First try to put the breakpoint at an ID up // and out of the way of user breakpoints. Status = AddBreakpoint(NULL, DEBUG_BREAKPOINT_CODE | BREAKPOINT_HIDDEN, 10000 + Count, &g_GoBreakpoints[Count]); if (Status != S_OK) { // That didn't work so try letting the engine // pick an ID. Status = AddBreakpoint(NULL, DEBUG_BREAKPOINT_CODE | BREAKPOINT_HIDDEN, DEBUG_ANY_ID, &g_GoBreakpoints[Count]); } if (Status != S_OK) { WarnOut("Temp bp at "); MaskOutAddr(DEBUG_OUTPUT_WARNING, BpArray); WarnOut("failed.\n"); } else { // Matches must be allowed so that temporary breakpoints // don't interfere with permanent breakpoints. g_GoBreakpoints[Count]->SetAddr(BpArray, BREAKPOINT_ALLOW_MATCH); g_GoBreakpoints[Count]->m_Flags |= (DEBUG_BREAKPOINT_GO_ONLY | DEBUG_BREAKPOINT_ENABLED); } BpArray++; } g_CmdState = 'g'; if (Thread == NULL || Thread == g_StepTraceBp->m_MatchThread) { g_StepTraceBp->m_Flags &= ~DEBUG_BREAKPOINT_ENABLED; } g_ExecutionStatusRequest = ExecStatus; g_SelectExecutionThread = ThreadFreeze ? SELTHREAD_THREAD : SELTHREAD_ANY; g_SelectedThread = Thread; NotifyChangeEngineState(DEBUG_CES_EXECUTION_STATUS, ExecStatus, TRUE); } /*** parseGoCmd - parse go command * * Purpose: * Parse the go ("g") command. Once parsed, call fnGoExecution * restart program execution. * * Input: * pThread - nonNULL for breakpoint on specific thread * fThreadFreeze - TRUE if all threads except pThread are frozen * *g_CurCmd - pointer to operands in command string * * Output: * None. * * Exceptions: * error exit: * LISTSIZE - breakpoint address list too large * *************************************************************************/ void parseGoCmd ( PTHREAD_INFO pThread, BOOL fThreadFreeze ) { ULONG count; ADDR addr[MAX_GO_BPS]; CHAR ch; ADDR pcaddr; CHAR ch2; ULONG ExecStatus; if (!IS_EXECUTION_POSSIBLE()) { error(SESSIONNOTSUP); } if (IS_LIVE_USER_TARGET() && (g_AllProcessFlags & ENG_PROC_EXAMINED)) { ErrOut("The debugger is not attached so " "process execution cannot be monitored\n"); return; } if (!IS_MACHINE_SET() || IS_RUNNING(g_CmdState)) { ErrOut("Debuggee is busy, cannot go\n"); return; } ExecStatus = DEBUG_STATUS_GO; ch = (CHAR)tolower(*g_CurCmd); if (ch == 'h' || ch == 'n') { ch2 = *(g_CurCmd + 1); if (ch2 == ' ' || ch2 == '\t' || ch2 == '\0') { g_CurCmd++; ExecStatus = ch == 'h' ? DEBUG_STATUS_GO_HANDLED : DEBUG_STATUS_GO_NOT_HANDLED; } } g_PrefixSymbols = TRUE; if (IS_CONTEXT_ACCESSIBLE()) { g_Machine->GetPC(&pcaddr); // default to current PC } else { ZeroMemory(&pcaddr, sizeof(pcaddr)); } if (PeekChar() == '=') { g_CurCmd++; GetAddrExpression(SEGREG_CODE, &pcaddr); } count = 0; while ((ch = PeekChar()) != '\0' && ch != ';') { ULONG AddrSpace, AddrFlags; if (count == DIMA(addr)) { error(LISTSIZE); } GetAddrExpression(SEGREG_CODE, addr + (count++)); if (g_Target-> QueryAddressInformation(Flat(addr[count - 1]), DBGKD_QUERY_MEMORY_VIRTUAL, &AddrSpace, &AddrFlags) != S_OK) { ErrOut("Invalid breakpoint address\n"); error(MEMORY); } if (AddrSpace == DBGKD_QUERY_MEMORY_SESSION || !(AddrFlags & DBGKD_QUERY_MEMORY_WRITE) || (AddrFlags & DBGKD_QUERY_MEMORY_FIXED)) { ErrOut("Software breakpoints cannot be used on session code, " "ROM code or other\nread-only memory. " "Use hardware execution breakpoints (ba e) instead.\n"); error(MEMORY); } } g_PrefixSymbols = FALSE; if (IS_USER_TARGET()) { g_LastCommand[0] = '\0'; // null out g command } fnGoExecution(ExecStatus, &pcaddr, count, pThread, fThreadFreeze, addr); } VOID SetupSpecialCalls( VOID ) { ULONG64 SpecialCalls[10]; PULONG64 Call = SpecialCalls; g_SrcLineValid = FALSE; g_StepTracePassCount = 0xfffffffe; // Set the special calls (overkill, once per boot // would be enough, but this is easier). if (!GetOffsetFromSym("hal!KfLowerIrql", Call, NULL) && !GetOffsetFromSym("hal!KeLowerIrql", Call, NULL)) { dprintf( "Cannot find hal!KfLowerIrql/KeLowerIrql\n" ); } else { Call++; } if (!GetOffsetFromSym("hal!KfReleaseSpinLock", Call, NULL) && !GetOffsetFromSym("hal!KeReleaseSpinLock", Call, NULL)) { dprintf( "Cannot find hal!KfReleaseSpinLock/KeReleaseSpinLock\n" ); } else { Call++; } #define GetSymWithErr(s) \ if (!GetOffsetFromSym(s, Call, NULL)) \ { \ dprintf("Cannot find " s "\n"); \ } \ else \ { \ Call++; \ } GetSymWithErr("hal!HalRequestSoftwareInterrupt"); if (g_SystemVersion >= NT_SVER_W2K) { GetSymWithErr("hal!ExReleaseFastMutex"); GetSymWithErr("hal!KeReleaseQueuedSpinLock"); if (g_SystemVersion >= NT_SVER_W2K_WHISTLER) { GetSymWithErr("hal!KeReleaseInStackQueuedSpinLock"); } } GetSymWithErr("nt!SwapContext"); GetSymWithErr("nt!KiUnlockDispatcherDatabase"); GetSymWithErr("nt!KiCallUserMode"); DBG_ASSERT((ULONG)(Call - SpecialCalls) <= sizeof(SpecialCalls) / sizeof(SpecialCalls[0])); DbgKdSetSpecialCalls((ULONG)(Call - SpecialCalls), SpecialCalls); }