You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1796 lines
46 KiB
1796 lines
46 KiB
//----------------------------------------------------------------------------
|
|
//
|
|
// Handles stepping, tracing and go.
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1997-2002.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#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;
|
|
BOOL g_StepTraceToCall;
|
|
|
|
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;
|
|
|
|
void
|
|
ResetStepTrace(void)
|
|
{
|
|
g_WatchBeginCurFunc = 1;
|
|
g_WatchEndCurFunc = 0;
|
|
g_WatchTrace = FALSE;
|
|
g_WatchInitialSP = 0;
|
|
g_StepTraceInRangeStart = (ULONG64)-1;
|
|
g_StepTraceInRangeEnd = 0;
|
|
g_StepTraceToCall = FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// WatchFunctions.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
WatchFunctions::WatchFunctions(void)
|
|
{
|
|
m_Started = FALSE;
|
|
SetDefaultParameters();
|
|
}
|
|
|
|
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;
|
|
g_Target->ClearBreakIn();
|
|
|
|
if (!m_Started)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ULONG TotalInstr;
|
|
|
|
if (IS_KERNEL_TARGET(g_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
|
|
{
|
|
BOOL StepOver;
|
|
|
|
if (PcAddr != NULL)
|
|
{
|
|
g_Target->ProcessWatchTraceEvent(td, PcAddr, &StepOver);
|
|
}
|
|
|
|
while (m_CallTop != NULL)
|
|
{
|
|
PopCall();
|
|
}
|
|
|
|
TotalInstr = m_TotalInstr;
|
|
}
|
|
|
|
g_BreakpointsSuspended = FALSE;
|
|
g_WatchInitialSP = 0;
|
|
}
|
|
else
|
|
{
|
|
if (m_CallTop != NULL)
|
|
{
|
|
OutputCall(m_CallTop, WCALL_OTHER);
|
|
}
|
|
|
|
while (m_CallTop != NULL)
|
|
{
|
|
PopCall();
|
|
}
|
|
|
|
TotalInstr = m_TotalInstr;
|
|
if (m_OutputCalls)
|
|
{
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
|
|
m_Started = FALSE;
|
|
|
|
if (m_OutputSummary)
|
|
{
|
|
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(g_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::IndentForCall(WatchCallStack* Call)
|
|
{
|
|
LONG i;
|
|
|
|
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(" ");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
WatchFunctions::OutputCall(WatchCallStack* Call, WATCH_CALL_TYPE Type)
|
|
{
|
|
if (!m_OutputCalls)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dprintf("%5ld %5ld [%3ld]", Call->InstrCount, Call->ChildInstrCount,
|
|
Call->Level);
|
|
IndentForCall(Call);
|
|
dprintf(" %s", Call->Func->Symbol);
|
|
|
|
if (Type == WCALL_RETURN && m_OutputReturnValues)
|
|
{
|
|
dprintf(" %s = %s",
|
|
RegNameFromIndex(g_Machine->m_RetRegIndex),
|
|
FormatDisp64(g_Machine->GetRetReg()));
|
|
}
|
|
else if (Type == WCALL_CALL && m_OutputCallAddrs)
|
|
{
|
|
dprintf("\n ");
|
|
IndentForCall(Call);
|
|
dprintf(" call at ");
|
|
dprintAddr(&Call->CallSite);
|
|
OutputLineAddr(Flat(Call->CallSite), "[%s @ %d]");
|
|
}
|
|
|
|
dprintf("\n");
|
|
}
|
|
|
|
void
|
|
WatchFunctions::SetDefaultParameters(void)
|
|
{
|
|
m_MaxCallLevelAllowed = 0x7fffffff;
|
|
m_OutputReturnValues = FALSE;
|
|
m_OutputCallAddrs = FALSE;
|
|
m_OutputCalls = TRUE;
|
|
m_OutputSummary = TRUE;
|
|
}
|
|
|
|
void
|
|
WatchFunctions::ParseParameters(void)
|
|
{
|
|
SetDefaultParameters();
|
|
|
|
while (PeekChar() == '-' || *g_CurCmd == '/')
|
|
{
|
|
g_CurCmd++;
|
|
switch(*g_CurCmd)
|
|
{
|
|
case 'l':
|
|
if (IS_KERNEL_TARGET(g_Target))
|
|
{
|
|
error(SESSIONNOTSUP);
|
|
}
|
|
g_CurCmd++;
|
|
m_MaxCallLevelAllowed = (LONG)GetExpression();
|
|
break;
|
|
|
|
case 'n':
|
|
g_CurCmd++;
|
|
switch(*g_CurCmd)
|
|
{
|
|
case 'c':
|
|
m_OutputCalls = FALSE;
|
|
break;
|
|
case 's':
|
|
m_OutputSummary = FALSE;
|
|
break;
|
|
default:
|
|
ErrOut("Unknown -n option '%c'\n", *g_CurCmd);
|
|
break;
|
|
}
|
|
g_CurCmd++;
|
|
break;
|
|
|
|
case 'o':
|
|
g_CurCmd++;
|
|
switch(*g_CurCmd)
|
|
{
|
|
case 'a':
|
|
if (IS_KERNEL_TARGET(g_Target))
|
|
{
|
|
error(SESSIONNOTSUP);
|
|
}
|
|
m_OutputCallAddrs = TRUE;
|
|
break;
|
|
case 'r':
|
|
if (IS_KERNEL_TARGET(g_Target))
|
|
{
|
|
error(SESSIONNOTSUP);
|
|
}
|
|
m_OutputReturnValues = TRUE;
|
|
break;
|
|
default:
|
|
ErrOut("Unknown -o option '%c'\n", *g_CurCmd);
|
|
break;
|
|
}
|
|
g_CurCmd++;
|
|
break;
|
|
|
|
default:
|
|
ErrOut("Unknown option '%c'\n", *g_CurCmd);
|
|
g_CurCmd++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
CopyString(Func->Symbol, Sym, DIMA(Func->Symbol));
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// TargetInfo watch trace methods.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
TargetInfo::InitializeTargetControlledStepping(void)
|
|
{
|
|
// Nothing to do.
|
|
return S_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// 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?
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::InitializeTargetControlledStepping(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(m_ProcessHead,
|
|
"hal!KfLowerIrql", Call, NULL) &&
|
|
!GetOffsetFromSym(m_ProcessHead,
|
|
"hal!KeLowerIrql", Call, NULL))
|
|
{
|
|
ErrOut("Cannot find hal!KfLowerIrql/KeLowerIrql\n");
|
|
}
|
|
else
|
|
{
|
|
Call++;
|
|
}
|
|
|
|
if (!GetOffsetFromSym(m_ProcessHead,
|
|
"hal!KfReleaseSpinLock", Call, NULL) &&
|
|
!GetOffsetFromSym(m_ProcessHead,
|
|
"hal!KeReleaseSpinLock", Call, NULL))
|
|
{
|
|
ErrOut("Cannot find hal!KfReleaseSpinLock/KeReleaseSpinLock\n");
|
|
}
|
|
else
|
|
{
|
|
Call++;
|
|
}
|
|
|
|
#define GetSymWithErr(s) \
|
|
if (!GetOffsetFromSym(m_ProcessHead, \
|
|
s, Call, NULL)) \
|
|
{ \
|
|
ErrOut("Cannot find " s "\n"); \
|
|
} \
|
|
else \
|
|
{ \
|
|
Call++; \
|
|
}
|
|
|
|
GetSymWithErr("hal!HalRequestSoftwareInterrupt");
|
|
if (g_Target->m_SystemVersion >= NT_SVER_W2K)
|
|
{
|
|
GetSymWithErr("hal!ExReleaseFastMutex");
|
|
GetSymWithErr("hal!KeReleaseQueuedSpinLock");
|
|
if (m_SystemVersion >= NT_SVER_XP)
|
|
{
|
|
GetSymWithErr("hal!KeReleaseInStackQueuedSpinLock");
|
|
}
|
|
}
|
|
|
|
GetSymWithErr("nt!SwapContext");
|
|
GetSymWithErr("nt!KiCallUserMode");
|
|
|
|
// Removed in .NET server
|
|
if (m_SystemVersion < NT_SVER_NET_SERVER)
|
|
{
|
|
GetSymWithErr("nt!KiUnlockDispatcherDatabase");
|
|
}
|
|
|
|
DBG_ASSERT((ULONG)(Call - SpecialCalls) <=
|
|
sizeof(SpecialCalls) / sizeof(SpecialCalls[0]));
|
|
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
|
|
m.ApiNumber = DbgKdClearSpecialCallsApi;
|
|
m.ReturnStatus = STATUS_PENDING;
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
InvalidateMemoryCaches(FALSE);
|
|
|
|
PULONG64 Send = SpecialCalls;
|
|
|
|
while (Send < Call)
|
|
{
|
|
m.ApiNumber = DbgKdSetSpecialCallApi;
|
|
m.ReturnStatus = STATUS_PENDING;
|
|
m.u.SetSpecialCall.SpecialCall = *Send++;
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
}
|
|
|
|
KdOut("DbgKdSetSpecialCalls returns 0x00000000\n");
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
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,
|
|
PADDR PcAddr,
|
|
PBOOL StepOver)
|
|
{
|
|
//
|
|
// 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;
|
|
|
|
// Kernel mode always traces.
|
|
*StepOver = FALSE;
|
|
|
|
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;
|
|
|
|
GetSymbol(SymOff, SymName, sizeof(SymName), &qw);
|
|
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, WCALL_OTHER);
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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
|
|
LiveUserTargetInfo::InitializeWatchTrace(void)
|
|
{
|
|
g_DeferredLevelChange = 0;
|
|
}
|
|
|
|
void
|
|
LiveUserTargetInfo::ProcessWatchTraceEvent(PDBGKD_TRACE_DATA TraceData,
|
|
PADDR PcAddr,
|
|
PBOOL StepOver)
|
|
{
|
|
WatchFunction* Func;
|
|
WatchCallStack* Call;
|
|
ULONG64 Disp64;
|
|
CHAR Disasm[MAX_DISASM_LEN];
|
|
|
|
// Default to tracing in.
|
|
*StepOver = FALSE;
|
|
|
|
g_WatchFunctions.RecordEvent();
|
|
|
|
//
|
|
// Get current function and see if it matches current. If so, bump
|
|
// count in current, otherwise, update to new level
|
|
//
|
|
|
|
GetSymbol(Flat(*PcAddr), Disasm, sizeof(Disasm), &Disp64);
|
|
|
|
// 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;
|
|
}
|
|
|
|
ADDR PcCopy;
|
|
|
|
PcCopy = *PcAddr;
|
|
g_Machine->Disassemble(g_Process, &PcCopy, 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, WCALL_RETURN);
|
|
|
|
// 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, g_DeferredLevelChange > 0 ?
|
|
WCALL_CALL : WCALL_OTHER);
|
|
|
|
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))
|
|
{
|
|
if (g_WatchFunctions.GetCallLevel() >=
|
|
g_WatchFunctions.m_MaxCallLevelAllowed)
|
|
{
|
|
// We're at the maximum allowed depth
|
|
// so just step over the call.
|
|
*StepOver = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Call->CallSite = *PcAddr;
|
|
g_DeferredLevelChange = 1;
|
|
}
|
|
}
|
|
else if (g_Machine->IsReturnDisasm(Disasm))
|
|
{
|
|
g_DeferredLevelChange = -1;
|
|
}
|
|
else if (g_Machine->IsSystemCallDisasm(Disasm))
|
|
{
|
|
PSTR CallName;
|
|
WatchCallStack* SysCall = Call;
|
|
|
|
CallName = strchr(Call->Func->Symbol, '!');
|
|
if (!CallName)
|
|
{
|
|
CallName = Call->Func->Symbol;
|
|
}
|
|
else
|
|
{
|
|
CallName++;
|
|
}
|
|
if (!strcmp(Call->Func->Symbol, "SharedUserData!SystemCallStub"))
|
|
{
|
|
// We're in a Windows XP 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.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
ParseStepTrace(ThreadInfo* Thread,
|
|
BOOL ThreadFreeze,
|
|
char StepType)
|
|
{
|
|
ADDR Addr1;
|
|
ULONG64 Value2;
|
|
char Ch;
|
|
CHAR AddrBuffer[MAX_SYMBOL_LEN];
|
|
ULONG64 Displacement;
|
|
BOOL ToCall = FALSE;
|
|
|
|
// If there's an outstanding request for input don't
|
|
// allow the execution status of the engine to change
|
|
// as it could lead to a wait which cannot
|
|
// be carried out in this situation. It's better to fail
|
|
// this call and have the caller try again.
|
|
if (g_InputNesting >= 1)
|
|
{
|
|
error(ENGBUSY);
|
|
}
|
|
|
|
if (!IS_CUR_MACHINE_ACCESSIBLE())
|
|
{
|
|
error(BADTHREAD);
|
|
}
|
|
|
|
if (!g_Target->m_DynamicEvents)
|
|
{
|
|
error(TARGETNOTSUP);
|
|
}
|
|
|
|
if (IS_LIVE_USER_TARGET(g_Target))
|
|
{
|
|
TargetInfo* Target;
|
|
|
|
ForAllLayersToTarget()
|
|
{
|
|
if (Target->m_AllProcessFlags & ENG_PROC_EXAMINED)
|
|
{
|
|
ErrOut("The debugger is not attached to some processes so\n"
|
|
"process execution cannot be monitored\n");
|
|
return;
|
|
}
|
|
else if (Target->m_BreakInTimeout)
|
|
{
|
|
ErrOut("Due to the break-in timeout the debugger "
|
|
"cannot step or trace\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (StepType == 'w')
|
|
{
|
|
if (IS_KERNEL_TARGET(g_Target) &&
|
|
g_Target->m_MachineType != IMAGE_FILE_MACHINE_I386)
|
|
{
|
|
error(UNIMPLEMENT);
|
|
}
|
|
|
|
if ((PeekChar() == 't') ||
|
|
(IS_KERNEL_TARGET(g_Target) && PeekChar() == 'w'))
|
|
{
|
|
g_WatchTrace = TRUE;
|
|
g_WatchWhole = *g_CurCmd == 'w';
|
|
g_WatchBeginCurFunc = g_WatchEndCurFunc = 0;
|
|
g_CurCmd++;
|
|
|
|
g_WatchFunctions.ParseParameters();
|
|
}
|
|
else
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_WatchTrace = FALSE;
|
|
|
|
//
|
|
// if next character is 'b' and command is 't' perform branch trace
|
|
//
|
|
|
|
Ch = PeekChar();
|
|
Ch = (char)tolower(Ch);
|
|
if (StepType == 't' && Ch == 'b')
|
|
{
|
|
if (!g_Machine->IsStepStatusSupported(DEBUG_STATUS_STEP_BRANCH))
|
|
{
|
|
error(TARGETNOTSUP);
|
|
}
|
|
|
|
StepType = 'b';
|
|
g_CurCmd++;
|
|
}
|
|
else if (Ch == 'c')
|
|
{
|
|
// Step/trace to next call.
|
|
ToCall = TRUE;
|
|
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 (StepType == 'w')
|
|
{
|
|
GetSymbol(Flat(Addr1),
|
|
AddrBuffer, sizeof(AddrBuffer), &Displacement);
|
|
if (Displacement == 0 && AddrBuffer[ 0 ] != '\0')
|
|
{
|
|
ADDR Addr2;
|
|
|
|
g_Machine->GetRetAddr(&Addr2);
|
|
Value2 = Flat(Addr2);
|
|
dprintf("Tracing %s to return address %s\n",
|
|
AddrBuffer,
|
|
FormatAddr64(Value2));
|
|
if (g_WatchWhole)
|
|
{
|
|
g_WatchBeginCurFunc = Value2;
|
|
g_WatchEndCurFunc = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (((LONG)Value2 <= 0) && (!g_WatchTrace))
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
|
|
SetExecStepTrace(&Addr1,
|
|
Value2, // count or watch end address
|
|
Thread,
|
|
ThreadFreeze,
|
|
ToCall,
|
|
StepType);
|
|
}
|
|
|
|
//
|
|
// Returns TRUE if the current step/trace should be passed over.
|
|
//
|
|
BOOL
|
|
StepTracePass(PADDR PcAddr)
|
|
{
|
|
if (g_StepTraceToCall)
|
|
{
|
|
char Disasm[MAX_DISASM_LEN];
|
|
ADDR Addr = *PcAddr;
|
|
|
|
// We're tracing by call instructions. Pass if
|
|
// the current instruction isn't a call. If we
|
|
// can't disassemble stop stepping as we don't
|
|
// want to miss something.
|
|
if (g_Machine->Disassemble(g_Process, &Addr, Disasm, FALSE) &&
|
|
!g_Machine->IsCallDisasm(Disasm) &&
|
|
!g_Machine->IsSystemCallDisasm(Disasm))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
ULONG Disp;
|
|
ULONG64 Disp64;
|
|
SYMBOL_INFO SymInfo = {0};
|
|
|
|
if (GetLineFromAddr(g_Process, Flat(*PcAddr), &Line, &Disp))
|
|
{
|
|
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 (SymFromAddr(g_Process->m_SymHandle,
|
|
Flat(*PcAddr),
|
|
&Disp64,
|
|
&SymInfo) &&
|
|
SymInfo.Tag == SymTagThunk)
|
|
{
|
|
// If we're on a thunk we just go ahead and keep
|
|
// stepping so that things don't stop in compiler-
|
|
// generated intermediaries like incremental compilation
|
|
// thunks.
|
|
return TRUE;
|
|
}
|
|
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;
|
|
}
|
|
|
|
void
|
|
SetExecStepTrace(PADDR StartAddr,
|
|
ULONG64 PassCount,
|
|
ThreadInfo* Thread,
|
|
BOOL ThreadFreeze,
|
|
BOOL ToCall,
|
|
char 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_Target->m_RegContextThread);
|
|
|
|
if ((g_SrcOptions & SRCOPT_STEP_SOURCE) && fFlat(*StartAddr))
|
|
{
|
|
ULONG Disp;
|
|
|
|
// Get the current line information so it's possible to
|
|
// tell when the line changes.
|
|
g_SrcLineValid = GetLineFromAddr(g_Process, Flat(*StartAddr),
|
|
&g_SrcLine, &Disp);
|
|
}
|
|
|
|
g_Machine->SetPC(StartAddr);
|
|
g_StepTracePassCount = (ULONG)PassCount;
|
|
g_StepTraceToCall = ToCall;
|
|
|
|
SelectExecutionThread(Thread,
|
|
ThreadFreeze ? SELTHREAD_THREAD : SELTHREAD_ANY);
|
|
|
|
if (StepType == 'w')
|
|
{
|
|
ULONG NextMachine;
|
|
|
|
g_Target->InitializeWatchTrace();
|
|
g_WatchFunctions.Start();
|
|
|
|
g_WatchTarget = *StartAddr;
|
|
g_Machine->GetNextOffset(g_Process, TRUE,
|
|
&g_WatchTarget, &NextMachine);
|
|
if (Flat(g_WatchTarget) != OFFSET_TRACE || PassCount != 1)
|
|
{
|
|
g_Target->InitializeTargetControlledStepping();
|
|
g_StepTracePassCount = 0xfffffff;
|
|
if (PassCount != 1)
|
|
{
|
|
Flat(g_WatchTarget) = PassCount;
|
|
}
|
|
}
|
|
|
|
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->m_Process;
|
|
g_StepTraceBp->m_MatchThread = Thread;
|
|
}
|
|
else
|
|
{
|
|
g_StepTraceBp->m_Process = g_Process;
|
|
g_StepTraceBp->m_MatchThread = g_Thread;
|
|
}
|
|
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_Process, 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);
|
|
}
|
|
|
|
void
|
|
SetExecGo(ULONG ExecStatus,
|
|
PADDR StartAddr,
|
|
ThreadInfo* Thread,
|
|
BOOL ThreadFreeze,
|
|
ULONG BpCount,
|
|
PADDR BpArray,
|
|
PCSTR BpCmd)
|
|
{
|
|
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_Target->m_RegContextThread);
|
|
|
|
if (IS_CUR_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, g_Machine, 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, g_Machine, 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);
|
|
}
|
|
|
|
if (BpCmd &&
|
|
g_GoBreakpoints[Count]->SetCommand(BpCmd) != S_OK)
|
|
{
|
|
WarnOut("Unable to set go breakpoint command\n");
|
|
}
|
|
|
|
BpArray++;
|
|
}
|
|
|
|
g_CmdState = 'g';
|
|
if (Thread == NULL || Thread == g_StepTraceBp->m_MatchThread)
|
|
{
|
|
g_StepTraceBp->m_Flags &= ~DEBUG_BREAKPOINT_ENABLED;
|
|
}
|
|
g_ExecutionStatusRequest = ExecStatus;
|
|
SelectExecutionThread(Thread,
|
|
ThreadFreeze ? SELTHREAD_THREAD : SELTHREAD_ANY);
|
|
NotifyChangeEngineState(DEBUG_CES_EXECUTION_STATUS, ExecStatus, TRUE);
|
|
}
|
|
|
|
void
|
|
ParseGoCmd(ThreadInfo* Thread,
|
|
BOOL ThreadFreeze)
|
|
{
|
|
ULONG BpCount;
|
|
ADDR BpAddr[MAX_GO_BPS];
|
|
CHAR Ch;
|
|
ADDR PcAddr;
|
|
CHAR Ch2;
|
|
ULONG ExecStatus;
|
|
|
|
if (!AnyEventsPossible())
|
|
{
|
|
error(NORUNNABLE);
|
|
}
|
|
|
|
// If there's an outstanding request for input don't
|
|
// allow the execution status of the engine to change
|
|
// as it could lead to a wait which cannot
|
|
// be carried out in this situation. It's better to fail
|
|
// this call and have the caller try again.
|
|
if (g_InputNesting >= 1)
|
|
{
|
|
error(ENGBUSY);
|
|
}
|
|
|
|
if (AllProcessFlags() & ENG_PROC_EXAMINED)
|
|
{
|
|
ErrOut("The debugger is not attached so "
|
|
"process execution cannot be monitored\n");
|
|
return;
|
|
}
|
|
|
|
if (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_CUR_CONTEXT_ACCESSIBLE())
|
|
{
|
|
g_Machine->GetPC(&PcAddr); // default to current PC
|
|
}
|
|
else
|
|
{
|
|
ZeroMemory(&PcAddr, sizeof(PcAddr));
|
|
}
|
|
|
|
if (PeekChar() == '=')
|
|
{
|
|
g_CurCmd++;
|
|
GetAddrExpression(SEGREG_CODE, &PcAddr);
|
|
}
|
|
|
|
BpCount = 0;
|
|
while ((Ch = PeekChar()) != '\0' && Ch != ';')
|
|
{
|
|
ULONG AddrSpace, AddrFlags;
|
|
|
|
if (BpCount == DIMA(BpAddr))
|
|
{
|
|
error(LISTSIZE);
|
|
}
|
|
|
|
GetAddrExpression(SEGREG_CODE, BpAddr + (BpCount++));
|
|
|
|
if (g_Target->
|
|
QueryAddressInformation(g_Process, Flat(BpAddr[BpCount - 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_Target))
|
|
{
|
|
g_LastCommand[0] = '\0'; // null out g command
|
|
}
|
|
|
|
//
|
|
// Check for trailing commands so that they can be attached
|
|
// to any go breakpoints that are being created.
|
|
//
|
|
|
|
PCSTR BpCmd = g_CurCmd;
|
|
|
|
if (g_EngStatus & ENG_STATUS_NO_AUTO_WAIT)
|
|
{
|
|
while (*BpCmd == ';' || *BpCmd == ' ' || *BpCmd == '\t')
|
|
{
|
|
BpCmd++;
|
|
}
|
|
if (!*BpCmd)
|
|
{
|
|
BpCmd = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Auto-waiting is enabled so just let the auto-wait
|
|
// handle trailing commands.
|
|
BpCmd = NULL;
|
|
}
|
|
|
|
SetExecGo(ExecStatus, &PcAddr, Thread, ThreadFreeze,
|
|
BpCount, BpAddr, BpCmd);
|
|
}
|