windows-nt/Source/XPSP1/NT/sdktools/debuggers/ntsd64/stepgo.cpp
2020-09-26 16:20:57 +08:00

1625 lines
40 KiB
C++

//----------------------------------------------------------------------------
//
// 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 "[~<thread>]p|t[b] [=<addr>][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 "[~[<thrd>]g[=<startaddr>][<bpaddr>]*" 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 <bpaddr>, 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);
}