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.
4649 lines
127 KiB
4649 lines
127 KiB
//----------------------------------------------------------------------------
|
|
//
|
|
// Breakpoint handling functions.
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1997-2002.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "ntsdp.hpp"
|
|
|
|
// Currently used only to watch for list changes when
|
|
// doing callbacks for breakpoint hit notifications.
|
|
BOOL g_BreakpointListChanged;
|
|
|
|
// Always update data breakpoints the very first time in
|
|
// order to flush out any stale data breakpoints.
|
|
BOOL g_UpdateDataBreakpoints = TRUE;
|
|
BOOL g_DataBreakpointsChanged;
|
|
BOOL g_BreakpointsSuspended;
|
|
|
|
Breakpoint* g_StepTraceBp; // Trace breakpoint.
|
|
CHAR g_StepTraceCmdState;
|
|
Breakpoint* g_DeferBp; // Deferred breakpoint.
|
|
BOOL g_DeferDefined; // TRUE if deferred breakpoint is active.
|
|
|
|
Breakpoint* g_LastBreakpointHit;
|
|
ADDR g_LastBreakpointHitPc;
|
|
|
|
HRESULT
|
|
BreakpointInit(void)
|
|
{
|
|
// These breakpoints are never put in any list so their
|
|
// IDs can be anything. Pick unusual numbers to make them
|
|
// easy to identify when debugging the debugger.
|
|
g_StepTraceBp = new
|
|
CodeBreakpoint(NULL, 0xffff0000, IMAGE_FILE_MACHINE_UNKNOWN);
|
|
g_StepTraceCmdState = 't';
|
|
g_DeferBp = new
|
|
CodeBreakpoint(NULL, 0xffff0001, IMAGE_FILE_MACHINE_UNKNOWN);
|
|
if (g_StepTraceBp == NULL ||
|
|
g_DeferBp == NULL)
|
|
{
|
|
delete g_StepTraceBp;
|
|
g_StepTraceBp = NULL;
|
|
delete g_DeferBp;
|
|
g_DeferBp = NULL;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Breakpoint.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
Breakpoint::Breakpoint(DebugClient* Adder, ULONG Id, ULONG Type,
|
|
ULONG ProcType)
|
|
{
|
|
m_Next = NULL;
|
|
m_Prev = NULL;
|
|
m_Refs = 1;
|
|
m_Id = Id;
|
|
m_BreakType = Type;
|
|
// Breakpoints are always created disabled since they
|
|
// are not initialized at the time of creation.
|
|
m_Flags = 0;
|
|
m_CodeFlags = IBI_DEFAULT;
|
|
ADDRFLAT(&m_Addr, 0);
|
|
// Initial data parameters must be set to something
|
|
// valid so that Validate calls will allow the offset
|
|
// to be changed.
|
|
m_DataSize = 1;
|
|
m_DataAccessType = DEBUG_BREAK_EXECUTE;
|
|
m_PassCount = 1;
|
|
m_CurPassCount = 1;
|
|
m_CommandLen = 0;
|
|
m_Command = NULL;
|
|
m_MatchThread = NULL;
|
|
m_Process = g_Process;
|
|
m_OffsetExprLen = 0;
|
|
m_OffsetExpr = NULL;
|
|
m_Adder = Adder;
|
|
m_MatchThreadData = 0;
|
|
m_MatchProcessData = 0;
|
|
|
|
SetProcType(ProcType);
|
|
|
|
if (m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
g_DataBreakpointsChanged = TRUE;
|
|
}
|
|
}
|
|
|
|
Breakpoint::~Breakpoint(void)
|
|
{
|
|
ULONG i;
|
|
|
|
// There used to be an assert here checking that
|
|
// the inserted flag wasn't set before a breakpoint
|
|
// structure was deleted. However, the inserted flag
|
|
// might still be set at this point if a breakpoint
|
|
// restore failed, so the assert is not valid.
|
|
|
|
if (m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
g_DataBreakpointsChanged = TRUE;
|
|
}
|
|
|
|
// Make sure stale pointers aren't left in the
|
|
// go breakpoints array. This can happen if
|
|
// a process exits or the target reboots while
|
|
// go breakpoints are active.
|
|
for (i = 0; i < g_NumGoBreakpoints; i++)
|
|
{
|
|
if (g_GoBreakpoints[i] == this)
|
|
{
|
|
g_GoBreakpoints[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (this == g_LastBreakpointHit)
|
|
{
|
|
g_LastBreakpointHit = NULL;
|
|
}
|
|
|
|
// Take this item out of the list if necessary.
|
|
if (m_Flags & BREAKPOINT_IN_LIST)
|
|
{
|
|
UnlinkFromList();
|
|
}
|
|
|
|
delete [] (PSTR)m_Command;
|
|
delete [] (PSTR)m_OffsetExpr;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::QueryInterface(
|
|
THIS_
|
|
IN REFIID InterfaceId,
|
|
OUT PVOID* Interface
|
|
)
|
|
{
|
|
*Interface = NULL;
|
|
|
|
// Interface specific casts are necessary in order to
|
|
// get the right vtable pointer in our multiple
|
|
// inheritance scheme.
|
|
if (DbgIsEqualIID(InterfaceId, IID_IUnknown) ||
|
|
DbgIsEqualIID(InterfaceId, IID_IDebugBreakpoint))
|
|
{
|
|
*Interface = (IDebugBreakpoint *)this;
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
Breakpoint::AddRef(
|
|
THIS
|
|
)
|
|
{
|
|
// This object's lifetime is not controlled by
|
|
// the interface.
|
|
return 1;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
Breakpoint::Release(
|
|
THIS
|
|
)
|
|
{
|
|
// This object's lifetime is not controlled by
|
|
// the interface.
|
|
return 0;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetId(
|
|
THIS_
|
|
OUT PULONG Id
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
*Id = m_Id;
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetType(
|
|
THIS_
|
|
OUT PULONG BreakType,
|
|
OUT PULONG ProcType
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
*BreakType = m_BreakType;
|
|
*ProcType = m_ProcType;
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetAdder(
|
|
THIS_
|
|
OUT PDEBUG_CLIENT* Adder
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
*Adder = (PDEBUG_CLIENT)m_Adder;
|
|
m_Adder->AddRef();
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetFlags(
|
|
THIS_
|
|
OUT PULONG Flags
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
*Flags = m_Flags & BREAKPOINT_EXTERNAL_FLAGS;
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::AddFlags(
|
|
THIS_
|
|
IN ULONG Flags
|
|
)
|
|
{
|
|
if (Flags & ~BREAKPOINT_EXTERNAL_MODIFY_FLAGS)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
m_Flags |= Flags;
|
|
|
|
if (m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
g_DataBreakpointsChanged = TRUE;
|
|
}
|
|
|
|
UpdateInternal();
|
|
NotifyChanged();
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::RemoveFlags(
|
|
THIS_
|
|
IN ULONG Flags
|
|
)
|
|
{
|
|
if (Flags & ~BREAKPOINT_EXTERNAL_MODIFY_FLAGS)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
m_Flags &= ~Flags;
|
|
|
|
if (m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
g_DataBreakpointsChanged = TRUE;
|
|
}
|
|
|
|
UpdateInternal();
|
|
NotifyChanged();
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetFlags(
|
|
THIS_
|
|
IN ULONG Flags
|
|
)
|
|
{
|
|
if (Flags & ~BREAKPOINT_EXTERNAL_MODIFY_FLAGS)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
m_Flags = (m_Flags & ~BREAKPOINT_EXTERNAL_MODIFY_FLAGS) |
|
|
(Flags & BREAKPOINT_EXTERNAL_MODIFY_FLAGS);
|
|
|
|
if (m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
g_DataBreakpointsChanged = TRUE;
|
|
}
|
|
|
|
UpdateInternal();
|
|
NotifyChanged();
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetOffset(
|
|
THIS_
|
|
OUT PULONG64 Offset
|
|
)
|
|
{
|
|
if (m_Flags & DEBUG_BREAKPOINT_DEFERRED)
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
*Offset = Flat(m_Addr);
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetOffset(
|
|
THIS_
|
|
IN ULONG64 Offset
|
|
)
|
|
{
|
|
if (m_Flags & DEBUG_BREAKPOINT_DEFERRED)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
ADDR Addr;
|
|
HRESULT Status;
|
|
|
|
ADDRFLAT(&Addr, Offset);
|
|
Status = SetAddr(&Addr, BREAKPOINT_WARN_MATCH);
|
|
if (Status == S_OK)
|
|
{
|
|
NotifyChanged();
|
|
}
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetDataParameters(
|
|
THIS_
|
|
OUT PULONG Size,
|
|
OUT PULONG AccessType
|
|
)
|
|
{
|
|
if (m_BreakType != DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
*Size = m_DataSize;
|
|
*AccessType = m_DataAccessType;
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetDataParameters(
|
|
THIS_
|
|
IN ULONG Size,
|
|
IN ULONG AccessType
|
|
)
|
|
{
|
|
if (m_BreakType != DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
ULONG OldSize = m_DataSize;
|
|
ULONG OldAccess = m_DataAccessType;
|
|
HRESULT Status;
|
|
|
|
m_DataSize = Size;
|
|
m_DataAccessType = AccessType;
|
|
Status = Validate();
|
|
if (Status != S_OK)
|
|
{
|
|
m_DataSize = OldSize;
|
|
m_DataAccessType = OldAccess;
|
|
}
|
|
else
|
|
{
|
|
g_DataBreakpointsChanged = TRUE;
|
|
NotifyChanged();
|
|
}
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetPassCount(
|
|
THIS_
|
|
OUT PULONG Count
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
*Count = m_PassCount;
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetPassCount(
|
|
THIS_
|
|
IN ULONG Count
|
|
)
|
|
{
|
|
if (Count < 1)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
m_PassCount = Count;
|
|
m_CurPassCount = Count;
|
|
NotifyChanged();
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetCurrentPassCount(
|
|
THIS_
|
|
OUT PULONG Count
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
*Count = m_CurPassCount;
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetMatchThreadId(
|
|
THIS_
|
|
OUT PULONG Id
|
|
)
|
|
{
|
|
HRESULT Status;
|
|
|
|
ENTER_ENGINE();
|
|
|
|
if (m_MatchThread)
|
|
{
|
|
*Id = m_MatchThread->m_UserId;
|
|
Status = S_OK;
|
|
}
|
|
else
|
|
{
|
|
Status = E_NOINTERFACE;
|
|
}
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetMatchThreadId(
|
|
THIS_
|
|
IN ULONG Id
|
|
)
|
|
{
|
|
HRESULT Status;
|
|
|
|
ENTER_ENGINE();
|
|
|
|
if (IS_KERNEL_TARGET(m_Process->m_Target) &&
|
|
m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
ErrOut("Kernel data breakpoints cannot be limited to a processor\n");
|
|
Status = E_INVALIDARG;
|
|
}
|
|
else
|
|
{
|
|
ThreadInfo* Thread = FindAnyThreadByUserId(Id);
|
|
if (Thread != NULL)
|
|
{
|
|
m_MatchThread = Thread;
|
|
NotifyChanged();
|
|
Status = S_OK;
|
|
}
|
|
else
|
|
{
|
|
Status = E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetCommand(
|
|
THIS_
|
|
OUT OPTIONAL PSTR Buffer,
|
|
IN ULONG BufferSize,
|
|
OUT OPTIONAL PULONG CommandSize
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
HRESULT Status = FillStringBuffer(m_Command, m_CommandLen,
|
|
Buffer, BufferSize, CommandSize);
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetCommand(
|
|
THIS_
|
|
IN PCSTR Command
|
|
)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (strlen(Command) >= MAX_COMMAND)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
Status = ChangeString((PSTR*)&m_Command, &m_CommandLen, Command);
|
|
if (Status == S_OK)
|
|
{
|
|
NotifyChanged();
|
|
}
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetOffsetExpression(
|
|
THIS_
|
|
OUT OPTIONAL PSTR Buffer,
|
|
IN ULONG BufferSize,
|
|
OUT OPTIONAL PULONG ExpressionSize
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
HRESULT Status = FillStringBuffer(m_OffsetExpr, m_OffsetExprLen,
|
|
Buffer, BufferSize, ExpressionSize);
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
Breakpoint::SetEvaluatedOffsetExpression(PCSTR Expr,
|
|
BreakpointEvalResult Valid,
|
|
PADDR Addr)
|
|
{
|
|
HRESULT Status =
|
|
ChangeString((PSTR*)&m_OffsetExpr, &m_OffsetExprLen, Expr);
|
|
if (Status != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if (Expr != NULL)
|
|
{
|
|
// Do initial evaluation in case the expression can be
|
|
// resolved right away. This will also set the deferred
|
|
// flag if the expression can't be evaluated.
|
|
EvalOffsetExpr(Valid, Addr);
|
|
}
|
|
else
|
|
{
|
|
// This breakpoint is no longer deferred since there's
|
|
// no way to activate it later any more.
|
|
m_Flags &= ~DEBUG_BREAKPOINT_DEFERRED;
|
|
UpdateInternal();
|
|
}
|
|
|
|
NotifyChanged();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetOffsetExpression(
|
|
THIS_
|
|
IN PCSTR Expression
|
|
)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (strlen(Expression) >= MAX_COMMAND)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ENTER_ENGINE();
|
|
|
|
ADDR Addr;
|
|
|
|
Status = SetEvaluatedOffsetExpression(Expression, BPEVAL_UNKNOWN, &Addr);
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::GetParameters(
|
|
THIS_
|
|
OUT PDEBUG_BREAKPOINT_PARAMETERS Params
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
if (m_Flags & DEBUG_BREAKPOINT_DEFERRED)
|
|
{
|
|
Params->Offset = DEBUG_INVALID_OFFSET;
|
|
}
|
|
else
|
|
{
|
|
Params->Offset = Flat(m_Addr);
|
|
}
|
|
Params->Id = m_Id;
|
|
Params->BreakType = m_BreakType;
|
|
Params->ProcType = m_ProcType;
|
|
Params->Flags = m_Flags & BREAKPOINT_EXTERNAL_FLAGS;
|
|
if (m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
Params->DataSize = m_DataSize;
|
|
Params->DataAccessType = m_DataAccessType;
|
|
}
|
|
else
|
|
{
|
|
Params->DataSize = 0;
|
|
Params->DataAccessType = 0;
|
|
}
|
|
Params->PassCount = m_PassCount;
|
|
Params->CurrentPassCount = m_CurPassCount;
|
|
Params->MatchThread = m_MatchThread != NULL ?
|
|
m_MatchThread->m_UserId : DEBUG_ANY_ID;
|
|
Params->CommandSize = m_CommandLen;
|
|
Params->OffsetExpressionSize = m_OffsetExprLen;
|
|
|
|
LEAVE_ENGINE();
|
|
return S_OK;
|
|
}
|
|
|
|
void
|
|
Breakpoint::LinkIntoList(void)
|
|
{
|
|
Breakpoint* NextBp;
|
|
Breakpoint* PrevBp;
|
|
|
|
DBG_ASSERT((m_Flags & BREAKPOINT_IN_LIST) == 0);
|
|
|
|
// Link into list sorted by ID.
|
|
PrevBp = NULL;
|
|
for (NextBp = m_Process->m_Breakpoints;
|
|
NextBp != NULL;
|
|
NextBp = NextBp->m_Next)
|
|
{
|
|
if (m_Id < NextBp->m_Id)
|
|
{
|
|
break;
|
|
}
|
|
|
|
PrevBp = NextBp;
|
|
}
|
|
|
|
m_Prev = PrevBp;
|
|
if (PrevBp == NULL)
|
|
{
|
|
m_Process->m_Breakpoints = this;
|
|
}
|
|
else
|
|
{
|
|
PrevBp->m_Next = this;
|
|
}
|
|
m_Next = NextBp;
|
|
if (NextBp == NULL)
|
|
{
|
|
m_Process->m_BreakpointsTail = this;
|
|
}
|
|
else
|
|
{
|
|
NextBp->m_Prev = this;
|
|
}
|
|
|
|
m_Flags |= BREAKPOINT_IN_LIST;
|
|
m_Process->m_NumBreakpoints++;
|
|
g_BreakpointListChanged = TRUE;
|
|
}
|
|
|
|
void
|
|
Breakpoint::UnlinkFromList(void)
|
|
{
|
|
DBG_ASSERT(m_Flags & BREAKPOINT_IN_LIST);
|
|
|
|
if (m_Prev == NULL)
|
|
{
|
|
m_Process->m_Breakpoints = m_Next;
|
|
}
|
|
else
|
|
{
|
|
m_Prev->m_Next = m_Next;
|
|
}
|
|
if (m_Next == NULL)
|
|
{
|
|
m_Process->m_BreakpointsTail = m_Prev;
|
|
}
|
|
else
|
|
{
|
|
m_Next->m_Prev = m_Prev;
|
|
}
|
|
|
|
m_Flags &= ~BREAKPOINT_IN_LIST;
|
|
m_Process->m_NumBreakpoints--;
|
|
g_BreakpointListChanged = TRUE;
|
|
}
|
|
|
|
void
|
|
Breakpoint::UpdateInternal(void)
|
|
{
|
|
// This only has an effect with internal breakpoints.
|
|
if ((m_Flags & BREAKPOINT_KD_INTERNAL) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the breakpoint is ready turn it on, otherwise
|
|
// turn it off.
|
|
ULONG Flags;
|
|
|
|
if ((m_Flags & (DEBUG_BREAKPOINT_ENABLED |
|
|
DEBUG_BREAKPOINT_DEFERRED)) == DEBUG_BREAKPOINT_ENABLED)
|
|
{
|
|
Flags = (m_Flags & BREAKPOINT_KD_COUNT_ONLY) ?
|
|
DBGKD_INTERNAL_BP_FLAG_COUNTONLY : 0;
|
|
}
|
|
else
|
|
{
|
|
Flags = DBGKD_INTERNAL_BP_FLAG_INVALID;
|
|
}
|
|
|
|
BpOut("Set internal bp at %s to %X\n",
|
|
FormatAddr64(Flat(m_Addr)), Flags);
|
|
|
|
if (Flags != DBGKD_INTERNAL_BP_FLAG_INVALID)
|
|
{
|
|
m_Process->m_Target->
|
|
InsertTargetCountBreakpoint(&m_Addr, Flags);
|
|
}
|
|
else
|
|
{
|
|
m_Process->m_Target->
|
|
RemoveTargetCountBreakpoint(&m_Addr);
|
|
}
|
|
}
|
|
|
|
BreakpointEvalResult
|
|
EvalAddrExpression(ProcessInfo* Process, ULONG Machine, PADDR Addr)
|
|
{
|
|
BOOL Error = FALSE;
|
|
ULONG NumUn;
|
|
StackSaveLayers Save;
|
|
EvalExpression* Eval;
|
|
EvalExpression* RelChain;
|
|
|
|
//
|
|
// This function can be reentered if evaluating an
|
|
// expression causes symbol changes which provoke
|
|
// reevaluation of existing address expressions.
|
|
// Save away current settings to support nesting.
|
|
//
|
|
|
|
// Evaluate the expression in the context of the breakpoint's
|
|
// machine type so that registers and such are available.
|
|
ULONG OldMachine = Process->m_Target->m_EffMachineType;
|
|
Process->m_Target->SetEffMachine(Machine, FALSE);
|
|
|
|
SetLayersFromProcess(Process);
|
|
|
|
RelChain = g_EvalReleaseChain;
|
|
g_EvalReleaseChain = NULL;
|
|
|
|
__try
|
|
{
|
|
Eval = GetCurEvaluator();
|
|
Eval->m_AllowUnresolvedSymbols++;
|
|
Eval->EvalCurAddr(SEGREG_CODE, Addr);
|
|
NumUn = Eval->m_NumUnresolvedSymbols;
|
|
ReleaseEvaluator(Eval);
|
|
}
|
|
__except(CommandExceptionFilter(GetExceptionInformation()))
|
|
{
|
|
// Skip the remainder of the command as there
|
|
// was an error during processing.
|
|
g_CurCmd += strlen(g_CurCmd);
|
|
Error = TRUE;
|
|
}
|
|
|
|
g_EvalReleaseChain = RelChain;
|
|
Process->m_Target->SetEffMachine(OldMachine, FALSE);
|
|
|
|
if (Error)
|
|
{
|
|
return BPEVAL_ERROR;
|
|
}
|
|
else if (NumUn > 0)
|
|
{
|
|
return BPEVAL_UNRESOLVED;
|
|
}
|
|
else
|
|
{
|
|
ImageInfo* Image;
|
|
|
|
// Check if this address falls within an existing module.
|
|
for (Image = Process->m_ImageHead;
|
|
Image != NULL;
|
|
Image = Image->m_Next)
|
|
{
|
|
if (Flat(*Addr) >= Image->m_BaseOfImage &&
|
|
Flat(*Addr) < Image->m_BaseOfImage + Image->m_SizeOfImage)
|
|
{
|
|
return BPEVAL_RESOLVED;
|
|
}
|
|
}
|
|
|
|
return BPEVAL_RESOLVED_NO_MODULE;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
Breakpoint::EvalOffsetExpr(BreakpointEvalResult Valid, PADDR Addr)
|
|
{
|
|
ULONG OldFlags = m_Flags;
|
|
|
|
DBG_ASSERT(m_OffsetExpr != NULL);
|
|
|
|
if (Valid == BPEVAL_UNKNOWN)
|
|
{
|
|
PSTR CurCommand = g_CurCmd;
|
|
|
|
g_CurCmd = (PSTR)m_OffsetExpr;
|
|
g_DisableErrorPrint++;
|
|
g_PrefixSymbols = TRUE;
|
|
|
|
Valid = EvalAddrExpression(m_Process, m_ProcType, Addr);
|
|
|
|
g_PrefixSymbols = FALSE;
|
|
g_DisableErrorPrint--;
|
|
g_CurCmd = CurCommand;
|
|
}
|
|
|
|
// Silently allow matching breakpoints when resolving
|
|
// as it is difficult for the expression setter to know
|
|
// whether there'll be matches or not at the time
|
|
// the expression is set.
|
|
if (Valid == BPEVAL_RESOLVED)
|
|
{
|
|
m_Flags &= ~DEBUG_BREAKPOINT_DEFERRED;
|
|
|
|
if (SetAddr(Addr, BREAKPOINT_ALLOW_MATCH) != S_OK)
|
|
{
|
|
m_Flags |= DEBUG_BREAKPOINT_DEFERRED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Flags |= DEBUG_BREAKPOINT_DEFERRED;
|
|
// The module containing the breakpoint is being
|
|
// unloaded so just mark this breakpoint as not-inserted.
|
|
m_Flags &= ~BREAKPOINT_INSERTED;
|
|
}
|
|
|
|
if ((OldFlags ^ m_Flags) & DEBUG_BREAKPOINT_DEFERRED)
|
|
{
|
|
// Update internal BP status.
|
|
UpdateInternal();
|
|
|
|
if (m_Flags & DEBUG_BREAKPOINT_DEFERRED)
|
|
{
|
|
BpOut("Deferring %u '%s'\n", m_Id, m_OffsetExpr);
|
|
}
|
|
else
|
|
{
|
|
BpOut("Enabling deferred %u '%s' at %s\n",
|
|
m_Id, m_OffsetExpr, FormatAddr64(Flat(m_Addr)));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
HRESULT
|
|
Breakpoint::CheckAddr(PADDR Addr)
|
|
{
|
|
ULONG AddrSpace, AddrFlags;
|
|
|
|
if (m_Process->m_Target->
|
|
QueryAddressInformation(m_Process,
|
|
Flat(*Addr), DBGKD_QUERY_MEMORY_VIRTUAL,
|
|
&AddrSpace, &AddrFlags) != S_OK)
|
|
{
|
|
ErrOut("Invalid breakpoint address\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (m_BreakType != DEBUG_BREAKPOINT_DATA &&
|
|
!(AddrFlags & DBGKD_QUERY_MEMORY_WRITE) ||
|
|
(AddrFlags & DBGKD_QUERY_MEMORY_FIXED))
|
|
{
|
|
ErrOut("Software breakpoints cannot be used on ROM code or\n"
|
|
"other read-only memory. "
|
|
"Use hardware execution breakpoints (ba e) instead.\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (m_BreakType != DEBUG_BREAKPOINT_DATA &&
|
|
AddrSpace == DBGKD_QUERY_MEMORY_SESSION)
|
|
{
|
|
WarnOut("WARNING: Software breakpoints on session "
|
|
"addresses can cause bugchecks.\n"
|
|
"Use hardware execution breakpoints (ba e) "
|
|
"if possible.\n");
|
|
return S_FALSE;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
Breakpoint::SetAddr(PADDR Addr, BreakpointMatchAction MatchAction)
|
|
{
|
|
if (m_Flags & DEBUG_BREAKPOINT_DEFERRED)
|
|
{
|
|
// Address is unknown.
|
|
return S_OK;
|
|
}
|
|
|
|
// Lock the breakpoint processor type to the
|
|
// type of the module containing it.
|
|
ULONG ProcType = m_ProcType;
|
|
if (m_BreakType == DEBUG_BREAKPOINT_CODE)
|
|
{
|
|
ImageInfo* Image = m_Process->FindImageByOffset(Flat(*Addr), FALSE);
|
|
if (Image)
|
|
{
|
|
ProcType = Image->GetMachineType();
|
|
if (ProcType == IMAGE_FILE_MACHINE_UNKNOWN)
|
|
{
|
|
ProcType = m_ProcType;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ProcType = m_ProcType;
|
|
}
|
|
}
|
|
|
|
if (m_Flags & BREAKPOINT_VIRT_ADDR)
|
|
{
|
|
// Old flag used on ALPHA
|
|
}
|
|
|
|
ADDR OldAddr = m_Addr;
|
|
HRESULT Valid;
|
|
|
|
m_Addr = *Addr;
|
|
|
|
Valid = Validate();
|
|
if (Valid != S_OK)
|
|
{
|
|
m_Addr = OldAddr;
|
|
return Valid;
|
|
}
|
|
|
|
if (ProcType != m_ProcType)
|
|
{
|
|
SetProcType(ProcType);
|
|
}
|
|
|
|
if (m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
g_DataBreakpointsChanged = TRUE;
|
|
}
|
|
|
|
if (MatchAction == BREAKPOINT_ALLOW_MATCH)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
Breakpoint* MatchBp;
|
|
|
|
MatchBp = CheckMatchingBreakpoints(this, TRUE, 0xffffffff);
|
|
if (MatchBp == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (MatchAction == BREAKPOINT_REMOVE_MATCH)
|
|
{
|
|
ULONG MoveId;
|
|
|
|
WarnOut("breakpoint %u redefined\n", MatchBp->m_Id);
|
|
// Move breakpoint towards lower IDs.
|
|
if (MatchBp->m_Id < m_Id)
|
|
{
|
|
MoveId = MatchBp->m_Id;
|
|
}
|
|
else
|
|
{
|
|
MoveId = DEBUG_ANY_ID;
|
|
}
|
|
|
|
RemoveBreakpoint(MatchBp);
|
|
|
|
if (MoveId != DEBUG_ANY_ID)
|
|
{
|
|
// Take over the removed ID.
|
|
UnlinkFromList();
|
|
m_Id = MoveId;
|
|
LinkIntoList();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WarnOut("Breakpoints %u and %u match\n",
|
|
m_Id, MatchBp->m_Id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
#define INSERTION_MATCH_FLAGS \
|
|
(BREAKPOINT_KD_INTERNAL | BREAKPOINT_VIRT_ADDR)
|
|
|
|
BOOL
|
|
Breakpoint::IsInsertionMatch(Breakpoint* Match)
|
|
{
|
|
if ((m_Flags & DEBUG_BREAKPOINT_DEFERRED) ||
|
|
(Match->m_Flags & DEBUG_BREAKPOINT_DEFERRED) ||
|
|
m_BreakType != Match->m_BreakType ||
|
|
((m_Flags ^ Match->m_Flags) & INSERTION_MATCH_FLAGS) ||
|
|
!AddrEqu(m_Addr, Match->m_Addr) ||
|
|
m_Process != Match->m_Process ||
|
|
(m_BreakType == DEBUG_BREAKPOINT_DATA &&
|
|
m_MatchThread != Match->m_MatchThread))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
#define PUBLIC_MATCH_FLAGS \
|
|
(BREAKPOINT_HIDDEN | DEBUG_BREAKPOINT_ADDER_ONLY)
|
|
|
|
BOOL
|
|
Breakpoint::IsPublicMatch(Breakpoint* Match)
|
|
{
|
|
if (!IsInsertionMatch(Match) ||
|
|
m_ProcType != Match->m_ProcType ||
|
|
((m_Flags ^ Match->m_Flags) & PUBLIC_MATCH_FLAGS) ||
|
|
((m_Flags & DEBUG_BREAKPOINT_ADDER_ONLY) &&
|
|
m_Adder != Match->m_Adder) ||
|
|
m_MatchThread != Match->m_MatchThread ||
|
|
m_MatchThreadData != Match->m_MatchThreadData ||
|
|
m_MatchProcessData != Match->m_MatchProcessData)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
Breakpoint::MatchesCurrentState(void)
|
|
{
|
|
HRESULT Status;
|
|
ULONG64 ThreadData = 0, ProcData = 0;
|
|
|
|
// If querying the current state fails go ahead
|
|
// and return a match so that the breakpoint will
|
|
// break as often as possible.
|
|
if (m_MatchThreadData)
|
|
{
|
|
if ((Status = g_EventTarget->
|
|
GetThreadInfoDataOffset(g_EventThread, 0, &ThreadData)) != S_OK)
|
|
{
|
|
ErrOut("Unable to determine current thread data, %s\n",
|
|
FormatStatusCode(Status));
|
|
return TRUE;
|
|
}
|
|
}
|
|
if (m_MatchProcessData)
|
|
{
|
|
if ((Status = g_EventTarget->
|
|
GetProcessInfoDataOffset(g_EventThread, 0, 0, &ProcData)) != S_OK)
|
|
{
|
|
ErrOut("Unable to determine current process data, %s\n",
|
|
FormatStatusCode(Status));
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return
|
|
(m_MatchThread == NULL ||
|
|
m_MatchThread == g_EventThread) &&
|
|
m_MatchThreadData == ThreadData &&
|
|
m_MatchProcessData == ProcData;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CodeBreakpoint.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CodeBreakpoint::Validate(void)
|
|
{
|
|
// No easy way to check for validity of offset.
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
CodeBreakpoint::Insert(void)
|
|
{
|
|
if (m_Flags & BREAKPOINT_INSERTED)
|
|
{
|
|
// Nothing to insert. This can happen in cases where
|
|
// the breakpoint remove failed.
|
|
return S_OK;
|
|
}
|
|
|
|
if (!m_Process)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
HRESULT Status;
|
|
|
|
DBG_ASSERT((m_Flags & (DEBUG_BREAKPOINT_DEFERRED |
|
|
BREAKPOINT_KD_INTERNAL)) == 0);
|
|
|
|
// Force recomputation of flat address.
|
|
NotFlat(m_Addr);
|
|
ComputeFlatAddress(&m_Addr, NULL);
|
|
|
|
Status = m_Process->m_Target->
|
|
InsertCodeBreakpoint(m_Process,
|
|
m_Process->m_Target->
|
|
m_Machines[m_ProcIndex],
|
|
&m_Addr,
|
|
m_CodeFlags,
|
|
m_InsertStorage);
|
|
if (Status == S_OK)
|
|
{
|
|
BpOut(" inserted bp %u at %s\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)));
|
|
|
|
m_Flags |= BREAKPOINT_INSERTED;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
ErrOut("Unable to insert breakpoint %u at %s, %s\n \"%s\"\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)),
|
|
FormatStatusCode(Status), FormatStatus(Status));
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
CodeBreakpoint::Remove(void)
|
|
{
|
|
if ((m_Flags & BREAKPOINT_INSERTED) == 0)
|
|
{
|
|
// Nothing to remove. This can happen in cases where
|
|
// the breakpoint insertion failed.
|
|
return S_OK;
|
|
}
|
|
|
|
if (!m_Process)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
HRESULT Status;
|
|
|
|
DBG_ASSERT((m_Flags & (DEBUG_BREAKPOINT_DEFERRED |
|
|
BREAKPOINT_KD_INTERNAL)) == 0);
|
|
|
|
// Force recomputation of flat address.
|
|
NotFlat(m_Addr);
|
|
ComputeFlatAddress(&m_Addr, NULL);
|
|
|
|
Status = m_Process->m_Target->
|
|
RemoveCodeBreakpoint(m_Process,
|
|
m_Process->m_Target->
|
|
m_Machines[m_ProcIndex],
|
|
&m_Addr,
|
|
m_CodeFlags,
|
|
m_InsertStorage);
|
|
if (Status == S_OK)
|
|
{
|
|
BpOut(" removed bp %u from %s\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)));
|
|
|
|
m_Flags &= ~BREAKPOINT_INSERTED;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
ErrOut("Unable to remove breakpoint %u at %s, %s\n \"%s\"\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)),
|
|
FormatStatusCode(Status), FormatStatus(Status));
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
ULONG
|
|
CodeBreakpoint::IsHit(PADDR Addr)
|
|
{
|
|
// Code breakpoints are code modifications and
|
|
// therefore aren't restricted to a particular
|
|
// thread.
|
|
// If this breakpoint can only match hits on
|
|
// a particular thread this is a partial hit
|
|
// because the exception occurred but it's
|
|
// being ignored.
|
|
if (AddrEqu(m_Addr, *Addr))
|
|
{
|
|
if (MatchesCurrentState())
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
else
|
|
{
|
|
return BREAKPOINT_HIT_IGNORED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DataBreakpoint.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
DataBreakpoint::Insert(void)
|
|
{
|
|
HRESULT Status;
|
|
ThreadInfo* Thread;
|
|
|
|
DBG_ASSERT((m_Flags & (BREAKPOINT_INSERTED |
|
|
DEBUG_BREAKPOINT_DEFERRED)) == 0);
|
|
|
|
// Force recomputation of flat address for non-I/O breakpoints.
|
|
if (m_Flags & BREAKPOINT_VIRT_ADDR)
|
|
{
|
|
NotFlat(m_Addr);
|
|
ComputeFlatAddress(&m_Addr, NULL);
|
|
}
|
|
|
|
Status = m_Process->m_Target->
|
|
InsertDataBreakpoint(m_Process,
|
|
m_MatchThread,
|
|
m_Process->m_Target->
|
|
m_Machines[m_ProcIndex],
|
|
&m_Addr,
|
|
m_DataSize,
|
|
m_DataAccessType,
|
|
m_InsertStorage);
|
|
// If the target returns S_FALSE it wants
|
|
// the generic insertion processing to occur.
|
|
if (Status == S_OK)
|
|
{
|
|
BpOut(" service inserted dbp %u at %s\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)));
|
|
|
|
m_Flags |= BREAKPOINT_INSERTED;
|
|
return Status;
|
|
}
|
|
else if (Status != S_FALSE)
|
|
{
|
|
ErrOut("Unable to insert breakpoint %u at %s, %s\n \"%s\"\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)),
|
|
FormatStatusCode(Status), FormatStatus(Status));
|
|
return Status;
|
|
}
|
|
|
|
// If this breakpoint is restricted to a thread
|
|
// only modify that thread's state. Otherwise
|
|
// update all threads in the process.
|
|
Thread = m_Process->m_ThreadHead;
|
|
while (Thread)
|
|
{
|
|
if (Thread->m_NumDataBreaks >=
|
|
m_Process->m_Target->m_Machine->m_MaxDataBreakpoints)
|
|
{
|
|
ErrOut("Too many data breakpoints for %s %d\n",
|
|
IS_KERNEL_TARGET(m_Process->m_Target) ?
|
|
"processor" : "thread", Thread->m_UserId);
|
|
return HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
|
|
}
|
|
else if (m_MatchThread == NULL || m_MatchThread == Thread)
|
|
{
|
|
BpOut("Add %s data bp %u to thread %u\n",
|
|
m_Process->m_Target->m_Machines[m_ProcIndex]->m_AbbrevName,
|
|
m_Id, Thread->m_UserId);
|
|
|
|
AddToThread(Thread);
|
|
}
|
|
|
|
Thread = Thread->m_Next;
|
|
}
|
|
|
|
g_UpdateDataBreakpoints = TRUE;
|
|
m_Flags |= BREAKPOINT_INSERTED;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
DataBreakpoint::Remove(void)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if ((m_Flags & BREAKPOINT_INSERTED) == 0)
|
|
{
|
|
// Nothing to remove. This can happen in cases where
|
|
// the breakpoint insertion failed.
|
|
return S_OK;
|
|
}
|
|
|
|
DBG_ASSERT((m_Flags & DEBUG_BREAKPOINT_DEFERRED) == 0);
|
|
|
|
Status = m_Process->m_Target->
|
|
RemoveDataBreakpoint(m_Process,
|
|
m_MatchThread,
|
|
m_Process->m_Target->
|
|
m_Machines[m_ProcIndex],
|
|
&m_Addr,
|
|
m_DataSize,
|
|
m_DataAccessType,
|
|
m_InsertStorage);
|
|
// If the target returns S_FALSE it wants
|
|
// the generic removal processing to occur.
|
|
if (Status == S_OK)
|
|
{
|
|
BpOut(" service removed dbp %u at %s\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)));
|
|
|
|
m_Flags &= ~BREAKPOINT_INSERTED;
|
|
return Status;
|
|
}
|
|
else if (Status != S_FALSE)
|
|
{
|
|
ErrOut("Unable to remove breakpoint %u at %s, %s\n \"%s\"\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)),
|
|
FormatStatusCode(Status), FormatStatus(Status));
|
|
return Status;
|
|
}
|
|
|
|
// When breakpoints are inserted the data breakpoint state
|
|
// is always started completely empty so no special
|
|
// work needs to be done when removing.
|
|
g_UpdateDataBreakpoints = TRUE;
|
|
m_Flags &= ~BREAKPOINT_INSERTED;
|
|
return S_OK;
|
|
}
|
|
|
|
void
|
|
DataBreakpoint::ClearThreadDataBreaks(ThreadInfo* Thread)
|
|
{
|
|
Thread->m_NumDataBreaks = 0;
|
|
memset(Thread->m_DataBreakBps, 0, sizeof(Thread->m_DataBreakBps));
|
|
}
|
|
|
|
void
|
|
DataBreakpoint::AddToThread(ThreadInfo* Thread)
|
|
{
|
|
DBG_ASSERT(Thread->m_NumDataBreaks <
|
|
Thread->m_Process->m_Target->m_Machine->m_MaxDataBreakpoints);
|
|
|
|
Thread->m_DataBreakBps[Thread->m_NumDataBreaks] = this;
|
|
Thread->m_NumDataBreaks++;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// X86DataBreakpoint.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
X86DataBreakpoint::Validate(void)
|
|
{
|
|
ULONG Dr7Bits;
|
|
ULONG Align;
|
|
|
|
if (!IsPow2(m_DataSize) || m_DataSize == 0 ||
|
|
(m_ProcType == IMAGE_FILE_MACHINE_AMD64 && m_DataSize > 8) ||
|
|
(m_ProcType != IMAGE_FILE_MACHINE_AMD64 && m_DataSize > 4))
|
|
{
|
|
ErrOut("Unsupported data breakpoint size\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
Align = (ULONG)(Flat(m_Addr) & (m_DataSize - 1));
|
|
if (Align != 0)
|
|
{
|
|
ErrOut("Data breakpoint must be aligned\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (m_DataSize < 8)
|
|
{
|
|
Dr7Bits = (m_DataSize - 1) << X86_DR7_LEN0_SHIFT;
|
|
}
|
|
else
|
|
{
|
|
Dr7Bits = 2 << X86_DR7_LEN0_SHIFT;
|
|
}
|
|
|
|
switch(m_DataAccessType)
|
|
{
|
|
case DEBUG_BREAK_EXECUTE:
|
|
Dr7Bits |= X86_DR7_RW0_EXECUTE;
|
|
// Code execution breakpoints must have a
|
|
// size of one.
|
|
// They must also be at the beginning
|
|
// of an instruction. This could be checked via
|
|
// examining the instructions but it doesn't seem
|
|
// that worth the trouble.
|
|
if (m_DataSize > 1)
|
|
{
|
|
ErrOut("Execution data breakpoint too large\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
break;
|
|
case DEBUG_BREAK_WRITE:
|
|
Dr7Bits |= X86_DR7_RW0_WRITE;
|
|
break;
|
|
case DEBUG_BREAK_IO:
|
|
if (IS_USER_TARGET(m_Process->m_Target) ||
|
|
!(m_Process->m_Target->m_Machines[m_ProcIndex]->
|
|
GetReg32(m_Cr4Reg) & X86_CR4_DEBUG_EXTENSIONS))
|
|
{
|
|
ErrOut("I/O breakpoints not enabled\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
if (Flat(m_Addr) > 0xffff)
|
|
{
|
|
ErrOut("I/O breakpoint port too large\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
Dr7Bits |= X86_DR7_RW0_IO;
|
|
break;
|
|
case DEBUG_BREAK_READ:
|
|
case DEBUG_BREAK_READ | DEBUG_BREAK_WRITE:
|
|
// There is no pure read-only option so
|
|
// lump it in with read-write.
|
|
Dr7Bits |= X86_DR7_RW0_READ_WRITE;
|
|
break;
|
|
default:
|
|
ErrOut("Unsupported data breakpoint access type\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_Dr7Bits = Dr7Bits | X86_DR7_L0_ENABLE;
|
|
if (m_DataAccessType == DEBUG_BREAK_IO)
|
|
{
|
|
m_Flags &= ~BREAKPOINT_VIRT_ADDR;
|
|
}
|
|
else
|
|
{
|
|
m_Flags |= BREAKPOINT_VIRT_ADDR;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG
|
|
X86DataBreakpoint::IsHit(PADDR Addr)
|
|
{
|
|
ULONG i;
|
|
ThreadInfo* Thread = g_EventThread;
|
|
HRESULT Status;
|
|
|
|
// Data breakpoints are only inserted in the contexts
|
|
// of matching threads so if the event thread doesn't match
|
|
// the breakpoint can't be hit.
|
|
if (m_MatchThread && m_MatchThread != Thread)
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
Status = m_Process->m_Target->
|
|
IsDataBreakpointHit(Thread, &m_Addr,
|
|
m_DataSize, m_DataAccessType,
|
|
m_InsertStorage);
|
|
// If the target returns S_FALSE it wants
|
|
// the generic processing to occur.
|
|
if (Status == S_OK)
|
|
{
|
|
if (MatchesCurrentState())
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
else
|
|
{
|
|
return BREAKPOINT_HIT_IGNORED;
|
|
}
|
|
}
|
|
else if (Status != S_FALSE)
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
// Locate this breakpoint in the thread's data breakpoints
|
|
// if possible.
|
|
for (i = 0; i < Thread->m_NumDataBreaks; i++)
|
|
{
|
|
// Check for match in addition to equality to handle
|
|
// multiple identical data breakpoints.
|
|
if (Thread->m_DataBreakBps[i] == this ||
|
|
IsInsertionMatch(Thread->m_DataBreakBps[i]))
|
|
{
|
|
// Is this breakpoint's index set in the debug status register?
|
|
// Address is not meaningful so this is the only way to check.
|
|
if ((m_Process->m_Target->m_Machines[m_ProcIndex]->
|
|
GetReg32(m_Dr6Reg) >> i) & 1)
|
|
{
|
|
if (MatchesCurrentState())
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
else
|
|
{
|
|
return BREAKPOINT_HIT_IGNORED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Breakpoint can't be listed in more than one slot
|
|
// so there's no need to finish the loop.
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Ia64DataBreakpoint.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
Ia64DataBreakpoint::Validate(void)
|
|
{
|
|
if (!IsPow2(m_DataSize))
|
|
{
|
|
ErrOut("Hardware breakpoint size must be power of 2\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (Flat(m_Addr) & (m_DataSize - 1))
|
|
{
|
|
ErrOut("Hardware breakpoint must be size aligned\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
switch (m_DataAccessType)
|
|
{
|
|
case DEBUG_BREAK_WRITE:
|
|
case DEBUG_BREAK_READ:
|
|
case DEBUG_BREAK_READ | DEBUG_BREAK_WRITE:
|
|
break;
|
|
case DEBUG_BREAK_EXECUTE:
|
|
if (m_DataSize & 0xf)
|
|
{
|
|
if (m_DataSize > 0xf)
|
|
{
|
|
ErrOut("Execution breakpoint size must be bundle aligned.\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
else
|
|
{
|
|
WarnOut("Execution breakpoint size extended to bundle size "
|
|
"(16 bytes).\n");
|
|
m_DataSize = 0x10;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
ErrOut("Unsupported data breakpoint access type\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_Control = GetControl(m_DataAccessType, m_DataSize);
|
|
m_Flags |= BREAKPOINT_VIRT_ADDR;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG
|
|
Ia64DataBreakpoint::IsHit(PADDR Addr)
|
|
{
|
|
HRESULT Status;
|
|
ULONG i;
|
|
ThreadInfo* Thread = g_EventThread;
|
|
|
|
// Data breakpoints are only inserted in the contexts
|
|
// of matching threads so if the event thread doesn't match
|
|
// the breakpoint can't be hit.
|
|
if (m_MatchThread && m_MatchThread != Thread)
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
Status = m_Process->m_Target->
|
|
IsDataBreakpointHit(Thread, &m_Addr,
|
|
m_DataSize, m_DataAccessType,
|
|
m_InsertStorage);
|
|
// If the target returns S_FALSE it wants
|
|
// the generic processing to occur.
|
|
if (Status == S_OK)
|
|
{
|
|
if (MatchesCurrentState())
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
else
|
|
{
|
|
return BREAKPOINT_HIT_IGNORED;
|
|
}
|
|
}
|
|
else if (Status != S_FALSE)
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
// Locate this breakpoint in the thread's data breakpoints
|
|
// if possible.
|
|
for (i = 0; i < Thread->m_NumDataBreaks; i++)
|
|
{
|
|
// Check for match in addition to equality to handle
|
|
// multiple identical data breakpoints.
|
|
if (Thread->m_DataBreakBps[i] == this ||
|
|
IsInsertionMatch(Thread->m_DataBreakBps[i]))
|
|
{
|
|
if ((Flat(*Thread->m_DataBreakBps[i]->GetAddr()) ^
|
|
Flat(*Addr)) &
|
|
(m_Control & IA64_DBG_MASK_MASK))
|
|
{
|
|
// Breakpoint can't be listed in more than one slot
|
|
// so there's no need to finish the loop.
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
else
|
|
{
|
|
if (MatchesCurrentState())
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
else
|
|
{
|
|
return BREAKPOINT_HIT_IGNORED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
ULONG64
|
|
Ia64DataBreakpoint::GetControl(ULONG AccessType, ULONG Size)
|
|
{
|
|
ULONG64 Control = (ULONG64(IA64_DBG_REG_PLM_ALL) |
|
|
ULONG64(IA64_DBG_MASK_MASK)) &
|
|
~ULONG64(Size - 1);
|
|
|
|
switch (AccessType)
|
|
{
|
|
case DEBUG_BREAK_WRITE:
|
|
Control |= IA64_DBR_WR;
|
|
break;
|
|
case DEBUG_BREAK_READ:
|
|
Control |= IA64_DBR_RD;
|
|
break;
|
|
case DEBUG_BREAK_READ | DEBUG_BREAK_WRITE:
|
|
Control |= IA64_DBR_RDWR;
|
|
break;
|
|
case DEBUG_BREAK_EXECUTE:
|
|
Control |= IA64_DBR_EXEC;
|
|
break;
|
|
}
|
|
|
|
return Control;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// X86OnIa64DataBreakpoint.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
X86OnIa64DataBreakpoint::X86OnIa64DataBreakpoint(DebugClient* Adder, ULONG Id)
|
|
: X86DataBreakpoint(Adder, Id, X86_CR4, X86_DR6, IMAGE_FILE_MACHINE_I386)
|
|
{
|
|
m_Control = 0;
|
|
}
|
|
|
|
HRESULT
|
|
X86OnIa64DataBreakpoint::Validate(void)
|
|
{
|
|
HRESULT Status = X86DataBreakpoint::Validate();
|
|
if (Status != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
switch (m_DataAccessType)
|
|
{
|
|
case DEBUG_BREAK_WRITE:
|
|
case DEBUG_BREAK_READ:
|
|
case DEBUG_BREAK_READ | DEBUG_BREAK_WRITE:
|
|
case DEBUG_BREAK_EXECUTE:
|
|
break;
|
|
default:
|
|
ErrOut("Unsupported data breakpoint access type\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_Control = Ia64DataBreakpoint::GetControl(m_DataAccessType, m_DataSize);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// XXX olegk -This is pure hack
|
|
// (see X86OnIa64MachineInfo::IsBreakpointOrStepException implementation
|
|
// for more info)
|
|
|
|
ULONG
|
|
X86OnIa64DataBreakpoint::IsHit(PADDR Addr)
|
|
{
|
|
HRESULT Status;
|
|
ULONG i;
|
|
ThreadInfo* Thread = g_EventThread;
|
|
|
|
// Data breakpoints are only inserted in the contexts
|
|
// of matching threads so if the event thread doesn't match
|
|
// the breakpoint can't be hit.
|
|
if (m_MatchThread && m_MatchThread != Thread)
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
Status = m_Process->m_Target->
|
|
IsDataBreakpointHit(Thread, &m_Addr,
|
|
m_DataSize, m_DataAccessType,
|
|
m_InsertStorage);
|
|
// If the target returns S_FALSE it wants
|
|
// the generic processing to occur.
|
|
if (Status == S_OK)
|
|
{
|
|
if (MatchesCurrentState())
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
else
|
|
{
|
|
return BREAKPOINT_HIT_IGNORED;
|
|
}
|
|
}
|
|
else if (Status != S_FALSE)
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
// Locate this breakpoint in the thread's data breakpoints
|
|
// if possible.
|
|
for (i = 0; i < Thread->m_NumDataBreaks; i++)
|
|
{
|
|
// Check for match in addition to equality to handle
|
|
// multiple identical data breakpoints.
|
|
if (Thread->m_DataBreakBps[i] == this ||
|
|
IsInsertionMatch(Thread->m_DataBreakBps[i]))
|
|
{
|
|
if (((ULONG)Flat(*Thread->m_DataBreakBps[i]->GetAddr()) ^
|
|
(ULONG)Flat(*Addr)) &
|
|
(ULONG)(m_Control & IA64_DBG_MASK_MASK))
|
|
{
|
|
// Breakpoint can't be listed in more than one slot
|
|
// so there's no need to finish the loop.
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
else
|
|
{
|
|
if (MatchesCurrentState())
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
else
|
|
{
|
|
return BREAKPOINT_HIT_IGNORED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Functions.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL
|
|
BreakpointNeedsToBeDeferred(Breakpoint* Bp,
|
|
ProcessInfo* Process, PADDR PcAddr)
|
|
{
|
|
if (Process && IS_CONTEXT_POSSIBLE(Process->m_Target) &&
|
|
(Bp->m_Process == Process))
|
|
{
|
|
// If this breakpoint matches the current IP and
|
|
// the current thread is going to be running
|
|
// we can't insert the breakpoint as the thread
|
|
// needs to run the real code.
|
|
if ((Bp->m_Flags & BREAKPOINT_VIRT_ADDR) &&
|
|
AddrEqu(*Bp->GetAddr(), *PcAddr) &&
|
|
ThreadWillResume(g_EventThread))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if ((Bp == g_LastBreakpointHit) && Bp->PcAtHit() &&
|
|
AddrEqu(g_LastBreakpointHitPc, *PcAddr))
|
|
{
|
|
if (g_ContextChanged)
|
|
{
|
|
WarnOut("Breakpoint %u will not be deferred because of "
|
|
"changes in the context. Breakpoint may hit again.\n",
|
|
Bp->m_Id);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Modify debuggee to activate current breakpoints.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
InsertBreakpoints(void)
|
|
{
|
|
HRESULT Status = S_OK;
|
|
ADDR PcAddr;
|
|
BOOL DeferredData = FALSE;
|
|
ThreadInfo* OldThread;
|
|
|
|
if (g_Thread != NULL)
|
|
{
|
|
// Aggressively clear this flag always in order to be
|
|
// as conservative as possible when recognizing
|
|
// trace events. We would rather misrecognize
|
|
// single-step events and break in instead of
|
|
// misrecognizing an app-generated single-step and
|
|
// ignoring it.
|
|
g_Thread->m_Flags &= ~ENG_THREAD_DEFER_BP_TRACE;
|
|
}
|
|
|
|
if ((g_EngStatus & ENG_STATUS_BREAKPOINTS_INSERTED) ||
|
|
(g_EngStatus & ENG_STATUS_SUSPENDED) == 0)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
g_DeferDefined = FALSE;
|
|
ADDRFLAT(&PcAddr, 0);
|
|
|
|
// Switch to the event thread to get the event thread's
|
|
// PC so we can see if we need to defer breakpoints in
|
|
// order to allow the event thread to keep running.
|
|
if (g_EventTarget)
|
|
{
|
|
OldThread = g_EventTarget->m_RegContextThread;
|
|
g_EventTarget->ChangeRegContext(g_EventThread);
|
|
}
|
|
|
|
if (g_BreakpointsSuspended)
|
|
{
|
|
goto StepTraceOnly;
|
|
}
|
|
|
|
//
|
|
// Turn off all data breakpoints. (We will turn the enabled ones back
|
|
// on when we restart execution).
|
|
//
|
|
|
|
TargetInfo* Target;
|
|
|
|
// Allow target to prepare for breakpoint insertion.
|
|
ForAllLayersToTarget()
|
|
{
|
|
if ((Status = Target->BeginInsertingBreakpoints()) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
if (IS_CONTEXT_POSSIBLE(g_EventTarget))
|
|
{
|
|
g_EventTarget->m_EffMachine->GetPC(&PcAddr);
|
|
}
|
|
|
|
BpOut("InsertBreakpoints PC ");
|
|
if (IS_CONTEXT_POSSIBLE(g_EventTarget))
|
|
{
|
|
MaskOutAddr(DEBUG_IOUTPUT_BREAKPOINT, &PcAddr);
|
|
BpOut("\n");
|
|
}
|
|
else
|
|
{
|
|
BpOut("?\n");
|
|
}
|
|
|
|
//
|
|
// Set any appropriate permanent breakpoints.
|
|
//
|
|
|
|
Breakpoint* Bp;
|
|
ProcessInfo* Process;
|
|
|
|
ForAllLayersToProcess()
|
|
{
|
|
BpOut(" Process %d with %d bps\n", Process->m_UserId,
|
|
Process->m_NumBreakpoints);
|
|
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->IsNormalEnabled() &&
|
|
(g_CmdState == 'g' ||
|
|
(Bp->m_Flags & DEBUG_BREAKPOINT_GO_ONLY) == 0))
|
|
{
|
|
Bp->ForceFlatAddr();
|
|
|
|
// Check if this breakpoint matches a previously
|
|
// inserted breakpoint. If so there's no need
|
|
// to insert this one.
|
|
Breakpoint* MatchBp;
|
|
|
|
for (MatchBp = Bp->m_Prev;
|
|
MatchBp != NULL;
|
|
MatchBp = MatchBp->m_Prev)
|
|
{
|
|
if ((MatchBp->m_Flags & BREAKPOINT_INSERTED) &&
|
|
Bp->IsInsertionMatch(MatchBp))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (MatchBp != NULL)
|
|
{
|
|
// Skip this breakpoint. It won't be marked as
|
|
// inserted so Remove is automatically handled.
|
|
continue;
|
|
}
|
|
|
|
if (BreakpointNeedsToBeDeferred(Bp,
|
|
g_EventProcess, &PcAddr))
|
|
{
|
|
g_DeferDefined = TRUE;
|
|
if (Bp->m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
DeferredData = TRUE;
|
|
// Force data breakpoint addresses to
|
|
// get updated because a dbp will now
|
|
// be missing.
|
|
g_DataBreakpointsChanged = TRUE;
|
|
}
|
|
BpOut(" deferred bp %u, dd %d\n",
|
|
Bp->m_Id, DeferredData);
|
|
}
|
|
else
|
|
{
|
|
HRESULT InsertStatus;
|
|
|
|
InsertStatus = Bp->Insert();
|
|
if (InsertStatus != S_OK)
|
|
{
|
|
if (Bp->m_Flags & DEBUG_BREAKPOINT_GO_ONLY)
|
|
{
|
|
ErrOut("go ");
|
|
}
|
|
ErrOut("bp%u at ", Bp->m_Id);
|
|
MaskOutAddr(DEBUG_OUTPUT_ERROR, Bp->GetAddr());
|
|
ErrOut("failed\n");
|
|
|
|
Status = InsertStatus;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ForAllLayersToTarget()
|
|
{
|
|
Target->EndInsertingBreakpoints();
|
|
}
|
|
|
|
// If we deferred a data breakpoint we haven't
|
|
// fully updated the data breakpoint state
|
|
// so leave the update flags set.
|
|
if (g_UpdateDataBreakpoints && !DeferredData)
|
|
{
|
|
g_UpdateDataBreakpoints = FALSE;
|
|
g_DataBreakpointsChanged = FALSE;
|
|
}
|
|
BpOut(" after insert udb %d, dbc %d\n",
|
|
g_UpdateDataBreakpoints, g_DataBreakpointsChanged);
|
|
|
|
StepTraceOnly:
|
|
|
|
// set the step/trace breakpoint if appropriate
|
|
|
|
if (g_StepTraceBp->m_Flags & DEBUG_BREAKPOINT_ENABLED)
|
|
{
|
|
BpOut("Step/trace addr = ");
|
|
MaskOutAddr(DEBUG_IOUTPUT_BREAKPOINT, g_StepTraceBp->GetAddr());
|
|
BpOut("\n");
|
|
|
|
if (Flat(*g_StepTraceBp->GetAddr()) == OFFSET_TRACE)
|
|
{
|
|
ThreadInfo* StepRegThread;
|
|
|
|
Target = g_StepTraceBp->m_Process->m_Target;
|
|
|
|
if (g_StepTraceBp->m_MatchThread &&
|
|
IS_USER_TARGET(Target))
|
|
{
|
|
StepRegThread = Target->m_RegContextThread;
|
|
Target->ChangeRegContext(g_StepTraceBp->m_MatchThread);
|
|
}
|
|
|
|
BpOut("Setting trace flag for step/trace thread %d:%x\n",
|
|
Target->m_RegContextThread ?
|
|
Target->m_RegContextThread->m_UserId : 0,
|
|
Target->m_RegContextThread ?
|
|
Target->m_RegContextThread->m_SystemId : 0);
|
|
Target->m_EffMachine->
|
|
QuietSetTraceMode(g_StepTraceCmdState == 'b' ?
|
|
TRACE_TAKEN_BRANCH :
|
|
TRACE_INSTRUCTION);
|
|
|
|
if (g_StepTraceBp->m_MatchThread &&
|
|
IS_USER_TARGET(Target))
|
|
{
|
|
Target->ChangeRegContext(StepRegThread);
|
|
}
|
|
}
|
|
else if (IS_CONTEXT_POSSIBLE(g_EventTarget) &&
|
|
AddrEqu(*g_StepTraceBp->GetAddr(), PcAddr))
|
|
{
|
|
BpOut("Setting defer flag for step/trace\n");
|
|
|
|
g_DeferDefined = TRUE;
|
|
}
|
|
else if (CheckMatchingBreakpoints(g_StepTraceBp, FALSE,
|
|
BREAKPOINT_INSERTED))
|
|
{
|
|
// There's already a breakpoint inserted at the
|
|
// step/trace address so we don't need to set another.
|
|
BpOut("Trace bp matches existing bp\n");
|
|
}
|
|
else
|
|
{
|
|
if (g_StepTraceBp->Insert() != S_OK)
|
|
{
|
|
ErrOut("Trace bp at addr ");
|
|
MaskOutAddr(DEBUG_OUTPUT_ERROR, g_StepTraceBp->GetAddr());
|
|
ErrOut("failed\n");
|
|
|
|
Status = E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
BpOut("Trace bp at addr ");
|
|
MaskOutAddr(DEBUG_IOUTPUT_BREAKPOINT,
|
|
g_StepTraceBp->GetAddr());
|
|
BpOut("succeeded\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process deferred breakpoint.
|
|
// If a deferred breakpoint is active it means that
|
|
// the debugger needs to do some work on the current instruction
|
|
// so it wants to step forward one instruction and then
|
|
// get control back. The deferred breakpoint forces a break
|
|
// back to the debugger as soon as possible so that it
|
|
// can carry out any deferred work.
|
|
|
|
if (g_DeferDefined)
|
|
{
|
|
ULONG NextMachine;
|
|
|
|
g_DeferBp->m_Process = g_EventProcess;
|
|
g_EventTarget->m_EffMachine->
|
|
GetNextOffset(g_EventProcess, FALSE,
|
|
g_DeferBp->GetAddr(), &NextMachine);
|
|
g_DeferBp->SetProcType(NextMachine);
|
|
|
|
BpOut("Defer addr = ");
|
|
MaskOutAddr(DEBUG_IOUTPUT_BREAKPOINT, g_DeferBp->GetAddr());
|
|
BpOut("\n");
|
|
|
|
if ((g_EngOptions & DEBUG_ENGOPT_SYNCHRONIZE_BREAKPOINTS) &&
|
|
IS_USER_TARGET(g_Target) &&
|
|
!IsSelectedExecutionThread(NULL, SELTHREAD_ANY))
|
|
{
|
|
// The user wants breakpoint management to occur
|
|
// precisely in order to properly handle breakpoints
|
|
// in code executed by multiple threads. Force
|
|
// the defer thread to be the only thread executing
|
|
// in order to avoid other threads running through
|
|
// the breakpoint location or generating events.
|
|
SelectExecutionThread(g_EventThread, SELTHREAD_INTERNAL_THREAD);
|
|
}
|
|
|
|
if (Flat(*g_DeferBp->GetAddr()) == OFFSET_TRACE)
|
|
{
|
|
BpOut("Setting trace flag for defer thread %d:%x\n",
|
|
g_EventTarget->m_RegContextThread ?
|
|
g_EventTarget->m_RegContextThread->m_UserId : 0,
|
|
g_EventTarget->m_RegContextThread ?
|
|
g_EventTarget->m_RegContextThread->m_SystemId : 0);
|
|
g_EventTarget->m_EffMachine->
|
|
QuietSetTraceMode(TRACE_INSTRUCTION);
|
|
|
|
if (IS_USER_TARGET(g_EventTarget) &&
|
|
g_EventThread != NULL)
|
|
{
|
|
// If the debugger is setting the trace flag
|
|
// for the current thread remember that it
|
|
// did so in order to properly recognize
|
|
// debugger-provoked single-step events even
|
|
// when events occur on other threads before
|
|
// the single-step event comes back.
|
|
g_EventThread->m_Flags |=
|
|
ENG_THREAD_DEFER_BP_TRACE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If an existing breakpoint or the step/trace breakpoint
|
|
// isn't already set on the next offset, insert the deferred
|
|
// breakpoint.
|
|
if (CheckMatchingBreakpoints(g_DeferBp, FALSE,
|
|
BREAKPOINT_INSERTED) == NULL &&
|
|
((g_StepTraceBp->m_Flags & BREAKPOINT_INSERTED) == 0 ||
|
|
!AddrEqu(*g_StepTraceBp->GetAddr(), *g_DeferBp->GetAddr())))
|
|
{
|
|
if (g_DeferBp->Insert() != S_OK)
|
|
{
|
|
ErrOut("Deferred bp at addr ");
|
|
MaskOutAddr(DEBUG_OUTPUT_ERROR, g_DeferBp->GetAddr());
|
|
ErrOut("failed\n");
|
|
|
|
Status = E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
BpOut("Deferred bp at addr ");
|
|
MaskOutAddr(DEBUG_IOUTPUT_BREAKPOINT,
|
|
g_DeferBp->GetAddr());
|
|
BpOut("succeeded\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BpOut("Defer bp matches existing bp\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_EventTarget)
|
|
{
|
|
g_EventTarget->ChangeRegContext(OldThread);
|
|
}
|
|
|
|
// Always consider breakpoints inserted since some
|
|
// of them may have been inserted even if some failed.
|
|
g_EngStatus |= ENG_STATUS_BREAKPOINTS_INSERTED;
|
|
|
|
return Status;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Reverse any debuggee changes caused by breakpoint insertion.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
RemoveBreakpoints(void)
|
|
{
|
|
if ((g_EngStatus & ENG_STATUS_BREAKPOINTS_INSERTED) == 0 ||
|
|
(g_EngStatus & ENG_STATUS_SUSPENDED) == 0)
|
|
{
|
|
return S_FALSE; // do nothing
|
|
}
|
|
|
|
BpOut("RemoveBreakpoints\n");
|
|
|
|
// restore the deferred breakpoint if set
|
|
g_DeferBp->Remove();
|
|
|
|
// restore the step/trace breakpoint if set
|
|
g_StepTraceBp->Remove();
|
|
|
|
if (!g_BreakpointsSuspended)
|
|
{
|
|
//
|
|
// Restore any appropriate permanent breakpoints (reverse order).
|
|
//
|
|
|
|
TargetInfo* Target;
|
|
ProcessInfo* Process;
|
|
Breakpoint* Bp;
|
|
|
|
ForAllLayersToTarget()
|
|
{
|
|
Target->BeginRemovingBreakpoints();
|
|
}
|
|
|
|
ForAllLayersToProcess()
|
|
{
|
|
BpOut(" Process %d with %d bps\n", Process->m_UserId,
|
|
Process->m_NumBreakpoints);
|
|
|
|
for (Bp = Process->m_BreakpointsTail;
|
|
Bp != NULL;
|
|
Bp = Bp->m_Prev)
|
|
{
|
|
Bp->Remove();
|
|
}
|
|
}
|
|
|
|
ForAllLayersToTarget()
|
|
{
|
|
Target->EndRemovingBreakpoints();
|
|
}
|
|
}
|
|
|
|
g_EngStatus &= ~ENG_STATUS_BREAKPOINTS_INSERTED;
|
|
return S_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Create a new breakpoint object.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
AddBreakpoint(DebugClient* Client,
|
|
MachineInfo* Machine,
|
|
ULONG Type,
|
|
ULONG DesiredId,
|
|
Breakpoint** RetBp)
|
|
{
|
|
Breakpoint* Bp;
|
|
ULONG Id;
|
|
TargetInfo* Target;
|
|
ProcessInfo* Process;
|
|
|
|
if (DesiredId == DEBUG_ANY_ID)
|
|
{
|
|
// Find the lowest unused ID across all processes.
|
|
// Breakpoint IDs are kept unique across all
|
|
// breakpoints to prevent user confusion and also
|
|
// to give extensions a unique ID for breakpoints.
|
|
Id = 0;
|
|
|
|
Restart:
|
|
// Search all bps to see if the current ID is in use.
|
|
ForAllLayersToProcess()
|
|
{
|
|
for (Bp = Process->m_Breakpoints; Bp; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Id == Id)
|
|
{
|
|
// A breakpoint is already using the current ID.
|
|
// Try the next one.
|
|
Id++;
|
|
goto Restart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check to see if the desired ID is in use.
|
|
ForAllLayersToProcess()
|
|
{
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Id == DesiredId)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
}
|
|
}
|
|
|
|
Id = DesiredId;
|
|
}
|
|
|
|
HRESULT Status = Machine->NewBreakpoint(Client, Type, Id, &Bp);
|
|
if (Status != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
*RetBp = Bp;
|
|
Bp->LinkIntoList();
|
|
|
|
// If this is an internal, hidden breakpoint set
|
|
// the flag immediately and do not notify.
|
|
if (Type & BREAKPOINT_HIDDEN)
|
|
{
|
|
Bp->m_Flags |= BREAKPOINT_HIDDEN;
|
|
}
|
|
else
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, Id, TRUE);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Delete a breakpoint object.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
RemoveBreakpoint(Breakpoint* Bp)
|
|
{
|
|
ULONG Id = Bp->m_Id;
|
|
ULONG Flags = Bp->m_Flags;
|
|
|
|
Bp->Relinquish();
|
|
|
|
if ((Flags & BREAKPOINT_HIDDEN) == 0)
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, Id, TRUE);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Clean up breakpoints owned by a particular process or thread.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
RemoveProcessBreakpoints(ProcessInfo* Process)
|
|
{
|
|
g_EngNotify++;
|
|
|
|
Breakpoint* Bp;
|
|
Breakpoint* NextBp;
|
|
BOOL NeedNotify = FALSE;
|
|
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = NextBp)
|
|
{
|
|
NextBp = Bp->m_Next;
|
|
|
|
DBG_ASSERT(Bp->m_Process == Process);
|
|
|
|
RemoveBreakpoint(Bp);
|
|
NeedNotify = TRUE;
|
|
}
|
|
|
|
g_EngNotify--;
|
|
if (NeedNotify)
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, DEBUG_ANY_ID, TRUE);
|
|
}
|
|
}
|
|
|
|
void
|
|
RemoveThreadBreakpoints(ThreadInfo* Thread)
|
|
{
|
|
g_EngNotify++;
|
|
|
|
Breakpoint* Bp;
|
|
Breakpoint* NextBp;
|
|
BOOL NeedNotify = FALSE;
|
|
|
|
DBG_ASSERT(Thread->m_Process);
|
|
|
|
for (Bp = Thread->m_Process->m_Breakpoints; Bp != NULL; Bp = NextBp)
|
|
{
|
|
NextBp = Bp->m_Next;
|
|
|
|
DBG_ASSERT(Bp->m_Process == Thread->m_Process);
|
|
|
|
if (Bp->m_MatchThread == Thread)
|
|
{
|
|
RemoveBreakpoint(Bp);
|
|
NeedNotify = TRUE;
|
|
}
|
|
}
|
|
|
|
g_EngNotify--;
|
|
if (NeedNotify)
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, DEBUG_ANY_ID, TRUE);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Remove all breakpoints and reset breakpoint state.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
RemoveAllBreakpoints(ULONG Reason)
|
|
{
|
|
TargetInfo* Target;
|
|
ProcessInfo* Process;
|
|
|
|
g_EngNotify++;
|
|
|
|
ForAllLayersToProcess()
|
|
{
|
|
while (Process->m_Breakpoints != NULL)
|
|
{
|
|
RemoveBreakpoint(Process->m_Breakpoints);
|
|
}
|
|
}
|
|
|
|
g_EngNotify--;
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, DEBUG_ANY_ID, TRUE);
|
|
|
|
g_NumGoBreakpoints = 0;
|
|
|
|
// If the machine is not waiting for commands we can't
|
|
// remove breakpoints. This happens when rebooting or
|
|
// when a wait doesn't successfully receive a state change.
|
|
if (Reason != DEBUG_SESSION_REBOOT &&
|
|
Reason != DEBUG_SESSION_HIBERNATE &&
|
|
Reason != DEBUG_SESSION_FAILURE &&
|
|
(g_EngStatus & ENG_STATUS_WAIT_SUCCESSFUL))
|
|
{
|
|
ForAllLayersToTarget()
|
|
{
|
|
Target->RemoveAllTargetBreakpoints();
|
|
ForTargetProcesses(Target)
|
|
{
|
|
Target->RemoveAllDataBreakpoints(Process);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Always update data breakpoints the very first time in
|
|
// order to flush out any stale data breakpoints.
|
|
g_UpdateDataBreakpoints = TRUE;
|
|
|
|
g_DataBreakpointsChanged = FALSE;
|
|
g_BreakpointsSuspended = FALSE;
|
|
|
|
g_DeferDefined = FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Look up breakpoints.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
Breakpoint*
|
|
GetBreakpointByIndex(DebugClient* Client, ULONG Index)
|
|
{
|
|
Breakpoint* Bp;
|
|
|
|
DBG_ASSERT(g_Process);
|
|
|
|
for (Bp = g_Process->m_Breakpoints;
|
|
Bp != NULL && Index > 0;
|
|
Bp = Bp->m_Next)
|
|
{
|
|
Index--;
|
|
}
|
|
|
|
if (Bp != NULL &&
|
|
(Bp->m_Flags & BREAKPOINT_HIDDEN) == 0 &&
|
|
((Bp->m_Flags & DEBUG_BREAKPOINT_ADDER_ONLY) == 0 ||
|
|
Bp->m_Adder == Client))
|
|
{
|
|
return Bp;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Breakpoint*
|
|
GetBreakpointById(DebugClient* Client, ULONG Id)
|
|
{
|
|
Breakpoint* Bp;
|
|
|
|
DBG_ASSERT(g_Process);
|
|
|
|
for (Bp = g_Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Id == Id)
|
|
{
|
|
if ((Bp->m_Flags & BREAKPOINT_HIDDEN) == 0 &&
|
|
((Bp->m_Flags & DEBUG_BREAKPOINT_ADDER_ONLY) == 0 ||
|
|
Bp->m_Adder == Client))
|
|
{
|
|
return Bp;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Check to see if two breakpoints refer to the same breakpoint
|
|
// conditions.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
Breakpoint*
|
|
CheckMatchingBreakpoints(Breakpoint* Match, BOOL Public, ULONG IncFlags)
|
|
{
|
|
Breakpoint* Bp;
|
|
|
|
for (Bp = Match->m_Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp == Match || (Bp->m_Flags & IncFlags) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((Public && Bp->IsPublicMatch(Match)) ||
|
|
(!Public && Bp->IsInsertionMatch(Match)))
|
|
{
|
|
return Bp;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Starting at the given breakpoint, check to see if a breakpoint
|
|
// is hit with the current processor state. Breakpoint types
|
|
// can be included or excluded by flags.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
Breakpoint*
|
|
CheckBreakpointHit(ProcessInfo* Process, Breakpoint* Start, PADDR Addr,
|
|
ULONG ExbsType, ULONG IncFlags, ULONG ExcFlags,
|
|
PULONG HitType,
|
|
BOOL SetLastBreakpointHit)
|
|
{
|
|
DBG_ASSERT(ExbsType & EXBS_BREAKPOINT_ANY);
|
|
|
|
ULONG BreakType;
|
|
|
|
switch(ExbsType)
|
|
{
|
|
case EXBS_BREAKPOINT_CODE:
|
|
BreakType = DEBUG_BREAKPOINT_CODE;
|
|
break;
|
|
case EXBS_BREAKPOINT_DATA:
|
|
BreakType = DEBUG_BREAKPOINT_DATA;
|
|
break;
|
|
default:
|
|
ExbsType = EXBS_BREAKPOINT_ANY;
|
|
break;
|
|
}
|
|
|
|
Breakpoint* Bp;
|
|
|
|
BpOut("CheckBp addr ");
|
|
MaskOutAddr(DEBUG_IOUTPUT_BREAKPOINT, Addr);
|
|
BpOut("\n");
|
|
|
|
for (Bp = (Start == NULL ? Process->m_Breakpoints : Start);
|
|
Bp != NULL;
|
|
Bp = Bp->m_Next)
|
|
{
|
|
// Allow different kinds of breakpoints to be scanned
|
|
// separately if desired.
|
|
|
|
if ((ExbsType != EXBS_BREAKPOINT_ANY &&
|
|
Bp->m_BreakType != BreakType) ||
|
|
(Bp->m_Flags & IncFlags) == 0 ||
|
|
(Bp->m_Flags & ExcFlags) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Common code is inlined here rather than in the
|
|
// base class because both pre- and post-derived
|
|
// checks are necessary.
|
|
|
|
// Force recomputation of flat address.
|
|
if (Bp->m_Flags & BREAKPOINT_VIRT_ADDR)
|
|
{
|
|
NotFlat(*Bp->GetAddr());
|
|
ComputeFlatAddress(Bp->GetAddr(), NULL);
|
|
}
|
|
|
|
if (Bp->IsNormalEnabled())
|
|
{
|
|
// We've got a partial match. Further checks
|
|
// depend on what kind of breakpoint it is.
|
|
*HitType = Bp->IsHit(Addr);
|
|
if (*HitType != BREAKPOINT_NOT_HIT)
|
|
{
|
|
// Do a final check for the pass count. If the
|
|
// pass count is nonzero this will become a partial hit.
|
|
if (*HitType == BREAKPOINT_HIT &&
|
|
!Bp->PassHit())
|
|
{
|
|
*HitType = BREAKPOINT_HIT_IGNORED;
|
|
}
|
|
|
|
BpOut(" hit %u\n", Bp->m_Id);
|
|
|
|
if (SetLastBreakpointHit)
|
|
{
|
|
g_LastBreakpointHit = Bp;
|
|
g_Target->m_EffMachine->GetPC(&g_LastBreakpointHitPc);
|
|
}
|
|
|
|
return Bp;
|
|
}
|
|
}
|
|
}
|
|
|
|
BpOut(" no hit\n");
|
|
|
|
*HitType = BREAKPOINT_NOT_HIT;
|
|
|
|
if (SetLastBreakpointHit)
|
|
{
|
|
g_LastBreakpointHit = NULL;
|
|
ZeroMemory(&g_LastBreakpointHitPc, sizeof(g_LastBreakpointHitPc));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Walk the breakpoint list and invoke event callbacks for
|
|
// any breakpoints that need it. Watch for and handle list changes
|
|
// caused by callbacks.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
NotifyHitBreakpoints(ULONG EventStatus)
|
|
{
|
|
Breakpoint* Bp;
|
|
TargetInfo* Target;
|
|
ProcessInfo* Process;
|
|
|
|
Restart:
|
|
g_BreakpointListChanged = FALSE;
|
|
|
|
ForAllLayersToProcess()
|
|
{
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Flags & BREAKPOINT_NOTIFY)
|
|
{
|
|
// Ensure the breakpoint isn't cleaned up before
|
|
// we're done with it.
|
|
Bp->Preserve();
|
|
|
|
Bp->m_Flags &= ~BREAKPOINT_NOTIFY;
|
|
EventStatus = NotifyBreakpointEvent(EventStatus, Bp);
|
|
|
|
if (Bp->m_Flags & DEBUG_BREAKPOINT_ONE_SHOT)
|
|
{
|
|
RemoveBreakpoint(Bp);
|
|
}
|
|
|
|
Bp->Relinquish();
|
|
|
|
// If the callback caused the breakpoint list to
|
|
// change we can no longer rely on the pointer
|
|
// we have and we need to restart the iteration.
|
|
if (g_BreakpointListChanged)
|
|
{
|
|
goto Restart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// A module load/unload event has occurred so go through every
|
|
// breakpoint with an offset expression and reevaluate it.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
EvaluateOffsetExpressions(ProcessInfo* Process, ULONG Flags)
|
|
{
|
|
static BOOL s_Evaluating;
|
|
|
|
// Don't reevaluate when not notifying because
|
|
// lack of notification usually means that a group
|
|
// of operations is being done and that notify/reevaluate
|
|
// will be done later after all of them are finished.
|
|
// It is also possible to have nested evaluations as
|
|
// evaluation may provoke symbol loads on deferred
|
|
// modules, which leads to a symbol notification and
|
|
// thus another evaluation. If we're already evaluating
|
|
// there's no need to evaluate again.
|
|
if (g_EngNotify > 0 || s_Evaluating)
|
|
{
|
|
return;
|
|
}
|
|
s_Evaluating = TRUE;
|
|
|
|
Breakpoint* Bp;
|
|
BOOL AnyEnabled = FALSE;
|
|
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
// Optimize evaluation somewhat.
|
|
// If a module is added then deferred breakpoints
|
|
// can become active. If a module is removed then
|
|
// active breakpoints can become deferred.
|
|
// XXX drewb - This doesn't hold up with general
|
|
// conditional expressions but currently the
|
|
// only thing that is officially supported is a simple symbol.
|
|
if (Bp->m_OffsetExpr != NULL &&
|
|
(((Flags & DEBUG_CSS_LOADS) &&
|
|
(Bp->m_Flags & DEBUG_BREAKPOINT_DEFERRED)) ||
|
|
((Flags & DEBUG_CSS_UNLOADS) &&
|
|
(Bp->m_Flags & DEBUG_BREAKPOINT_DEFERRED) == 0)))
|
|
{
|
|
ADDR Addr;
|
|
|
|
if (Bp->EvalOffsetExpr(BPEVAL_UNKNOWN, &Addr) &&
|
|
(Bp->m_Flags & DEBUG_BREAKPOINT_DEFERRED) == 0)
|
|
{
|
|
// No need to update on newly disabled breakpoints
|
|
// as the module is being unloaded so they'll
|
|
// go away anyway. The disabled breakpoint
|
|
// is simply marked not-inserted in EvalOffsetExpr.
|
|
AnyEnabled = TRUE;
|
|
}
|
|
}
|
|
|
|
if (PollUserInterrupt(TRUE))
|
|
{
|
|
// Leave the interrupt set as this may be
|
|
// called in the middle of a symbol operation
|
|
// and we want the interrupt to interrupt
|
|
// the entire symbol operation.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (AnyEnabled)
|
|
{
|
|
// A deferred breakpoint has become enabled.
|
|
// Force a refresh of the breakpoints so
|
|
// that the newly enabled breakpoints get inserted.
|
|
SuspendExecution();
|
|
RemoveBreakpoints();
|
|
}
|
|
|
|
s_Evaluating = FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Alters breakpoint state for b[cde]<idlist>.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
ChangeBreakpointState(DebugClient* Client, ProcessInfo* ForProcess,
|
|
ULONG Id, UCHAR StateChange)
|
|
{
|
|
Breakpoint* Bp;
|
|
Breakpoint* NextBp;
|
|
TargetInfo* Target;
|
|
ProcessInfo* Process;
|
|
|
|
ForAllLayersToProcess()
|
|
{
|
|
if (ForProcess != NULL && Process != ForProcess)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = NextBp)
|
|
{
|
|
// Prefetch the next breakpoint in case we remove
|
|
// the current breakpoint from the list.
|
|
NextBp = Bp->m_Next;
|
|
|
|
if ((Id == ALL_ID_LIST || Bp->m_Id == Id) &&
|
|
(Bp->m_Flags & BREAKPOINT_HIDDEN) == 0 &&
|
|
((Bp->m_Flags & DEBUG_BREAKPOINT_ADDER_ONLY) == 0 ||
|
|
Bp->m_Adder == Client))
|
|
{
|
|
if (StateChange == 'c')
|
|
{
|
|
RemoveBreakpoint(Bp);
|
|
}
|
|
else
|
|
{
|
|
if (StateChange == 'e')
|
|
{
|
|
Bp->AddFlags(DEBUG_BREAKPOINT_ENABLED);
|
|
}
|
|
else
|
|
{
|
|
Bp->RemoveFlags(DEBUG_BREAKPOINT_ENABLED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Lists current breakpoints for bl.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
ListBreakpoints(DebugClient* Client, ProcessInfo* ForProcess,
|
|
ULONG Id)
|
|
{
|
|
StackSaveLayers Save;
|
|
Breakpoint* Bp;
|
|
TargetInfo* Target;
|
|
ProcessInfo* Process;
|
|
|
|
ForAllLayersToProcess()
|
|
{
|
|
if (ForProcess != NULL && Process != ForProcess)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SetLayersFromProcess(Process);
|
|
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
char StatusChar;
|
|
|
|
if ((Bp->m_Flags & BREAKPOINT_HIDDEN) ||
|
|
((Bp->m_Flags & DEBUG_BREAKPOINT_ADDER_ONLY) &&
|
|
Client != Bp->m_Adder) ||
|
|
(Id != ALL_ID_LIST && Bp->m_Id != Id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (Bp->m_Flags & DEBUG_BREAKPOINT_ENABLED)
|
|
{
|
|
if (Bp->m_Flags & BREAKPOINT_KD_INTERNAL)
|
|
{
|
|
StatusChar = (Bp->m_Flags & BREAKPOINT_KD_COUNT_ONLY) ?
|
|
'i' : 'w';
|
|
}
|
|
else
|
|
{
|
|
StatusChar = 'e';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StatusChar = 'd';
|
|
}
|
|
|
|
dprintf("%2u %c", Bp->m_Id, StatusChar);
|
|
|
|
if (Bp->GetProcType() != g_Target->m_MachineType)
|
|
{
|
|
dprintf("%s ",
|
|
Bp->m_Process->m_Target->
|
|
m_Machines[Bp->GetProcIndex()]->m_AbbrevName);
|
|
}
|
|
|
|
if ((Bp->m_Flags & DEBUG_BREAKPOINT_DEFERRED) == 0)
|
|
{
|
|
dprintf(" ");
|
|
if (Bp->m_BreakType == DEBUG_BREAKPOINT_CODE &&
|
|
(g_SrcOptions & SRCOPT_STEP_SOURCE))
|
|
{
|
|
if (!OutputLineAddr(Flat(*Bp->GetAddr()), "[%s @ %d]"))
|
|
{
|
|
dprintAddr(Bp->GetAddr());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dprintAddr(Bp->GetAddr());
|
|
}
|
|
}
|
|
else if (g_Target->m_Machine->m_Ptr64)
|
|
{
|
|
dprintf("u ");
|
|
}
|
|
else
|
|
{
|
|
dprintf("u ");
|
|
}
|
|
|
|
char OptionChar;
|
|
|
|
if (Bp->m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
switch(Bp->m_DataAccessType)
|
|
{
|
|
case DEBUG_BREAK_EXECUTE:
|
|
OptionChar = 'e';
|
|
break;
|
|
case DEBUG_BREAK_WRITE:
|
|
OptionChar = 'w';
|
|
break;
|
|
case DEBUG_BREAK_IO:
|
|
OptionChar = 'i';
|
|
break;
|
|
case DEBUG_BREAK_READ:
|
|
case DEBUG_BREAK_READ | DEBUG_BREAK_WRITE:
|
|
OptionChar = 'r';
|
|
break;
|
|
default:
|
|
OptionChar = '?';
|
|
break;
|
|
}
|
|
dprintf("%c %d", OptionChar, Bp->m_DataSize);
|
|
}
|
|
else
|
|
{
|
|
dprintf(" ");
|
|
}
|
|
|
|
if (Bp->m_Flags & DEBUG_BREAKPOINT_ONE_SHOT)
|
|
{
|
|
dprintf("/1 ");
|
|
}
|
|
|
|
dprintf(" %04lx (%04lx) ",
|
|
Bp->m_CurPassCount, Bp->m_PassCount);
|
|
|
|
if ((Bp->m_Flags & DEBUG_BREAKPOINT_DEFERRED) == 0)
|
|
{
|
|
if (IS_USER_TARGET(Bp->m_Process->m_Target))
|
|
{
|
|
dprintf("%2ld:", Bp->m_Process->m_UserId);
|
|
if (Bp->m_MatchThread != NULL)
|
|
{
|
|
dprintf("~%03ld ", Bp->m_MatchThread->m_UserId);
|
|
}
|
|
else
|
|
{
|
|
dprintf("*** ");
|
|
}
|
|
}
|
|
|
|
OutputSymAddr(Flat(*Bp->GetAddr()), SYMADDR_FORCE, NULL);
|
|
|
|
if (Bp->m_Command != NULL)
|
|
{
|
|
dprintf("\"%s\"", Bp->m_Command);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dprintf(" (%s)", Bp->m_OffsetExpr);
|
|
}
|
|
|
|
dprintf("\n");
|
|
|
|
if (Bp->m_MatchThreadData || Bp->m_MatchProcessData)
|
|
{
|
|
dprintf(" ");
|
|
if (Bp->m_MatchThreadData)
|
|
{
|
|
dprintf(" Match thread data %s",
|
|
FormatAddr64(Bp->m_MatchThreadData));
|
|
}
|
|
if (Bp->m_MatchProcessData)
|
|
{
|
|
dprintf(" Match process data %s",
|
|
FormatAddr64(Bp->m_MatchProcessData));
|
|
}
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IS_KERNEL_TARGET(g_Target))
|
|
{
|
|
dprintf("\n");
|
|
|
|
ForAllLayersToProcess()
|
|
{
|
|
if (ForProcess != NULL && Process != ForProcess)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SetLayersFromProcess(Process);
|
|
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Flags & BREAKPOINT_KD_INTERNAL)
|
|
{
|
|
ULONG Flags, Calls, MinInst, MaxInst, TotInst, MaxCps;
|
|
|
|
g_Target->QueryTargetCountBreakpoint(Bp->GetAddr(),
|
|
&Flags,
|
|
&Calls,
|
|
&MinInst,
|
|
&MaxInst,
|
|
&TotInst,
|
|
&MaxCps);
|
|
dprintf("%s %6d %8d %8d %8d %2x %4d",
|
|
FormatAddr64(Flat(*Bp->GetAddr())),
|
|
Calls, MinInst, MaxInst,
|
|
TotInst, Flags, MaxCps);
|
|
OutputSymAddr(Flat(*Bp->GetAddr()), SYMADDR_FORCE, " ");
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Outputs commands necessary to recreate current breakpoints.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
ListBreakpointsAsCommands(DebugClient* Client, ProcessInfo* Process,
|
|
ULONG Flags)
|
|
{
|
|
Breakpoint* Bp;
|
|
|
|
if (Process == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if ((Bp->m_Flags & BREAKPOINT_HIDDEN) ||
|
|
((Bp->m_Flags & DEBUG_BREAKPOINT_ADDER_ONLY) &&
|
|
Client != Bp->m_Adder) ||
|
|
((Flags & BPCMDS_EXPR_ONLY && Bp->m_OffsetExpr == NULL)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (IS_USER_TARGET(Bp->m_Process->m_Target))
|
|
{
|
|
if (Bp->m_MatchThread != NULL ||
|
|
Bp->m_MatchThreadData ||
|
|
Bp->m_MatchProcessData)
|
|
{
|
|
// Ignore thread- and data-specific breakpoints
|
|
// as the things they are specific to may
|
|
// not exist in a new session.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (Bp->GetProcType() != Process->m_Target->m_MachineType)
|
|
{
|
|
dprintf(".effmach %s;%c",
|
|
Bp->m_Process->m_Target->
|
|
m_Machines[Bp->GetProcIndex()]->m_AbbrevName,
|
|
(Flags & BPCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
|
|
if ((Flags & BPCMDS_MODULE_HINT) &&
|
|
(Bp->m_Flags & (DEBUG_BREAKPOINT_DEFERRED |
|
|
BREAKPOINT_VIRT_ADDR)) == BREAKPOINT_VIRT_ADDR)
|
|
{
|
|
ImageInfo* Image =
|
|
Bp->m_Process->FindImageByOffset(Flat(*Bp->GetAddr()), FALSE);
|
|
if (Image != NULL)
|
|
{
|
|
dprintf("ld %s;%c", Image->m_ModuleName,
|
|
(Flags & BPCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
}
|
|
|
|
char TypeChar;
|
|
|
|
if (Bp->m_Flags & BREAKPOINT_KD_INTERNAL)
|
|
{
|
|
TypeChar = (Bp->m_Flags & BREAKPOINT_KD_COUNT_ONLY) ? 'i' : 'w';
|
|
}
|
|
else if (Bp->m_BreakType == DEBUG_BREAKPOINT_CODE)
|
|
{
|
|
TypeChar = Bp->m_OffsetExpr != NULL ? 'u' : 'p';
|
|
}
|
|
else
|
|
{
|
|
TypeChar = 'a';
|
|
}
|
|
|
|
dprintf("b%c%u", TypeChar, Bp->m_Id);
|
|
|
|
char OptionChar;
|
|
|
|
if (Bp->m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
switch(Bp->m_DataAccessType)
|
|
{
|
|
case DEBUG_BREAK_EXECUTE:
|
|
OptionChar = 'e';
|
|
break;
|
|
case DEBUG_BREAK_WRITE:
|
|
OptionChar = 'w';
|
|
break;
|
|
case DEBUG_BREAK_IO:
|
|
OptionChar = 'i';
|
|
break;
|
|
case DEBUG_BREAK_READ:
|
|
case DEBUG_BREAK_READ | DEBUG_BREAK_WRITE:
|
|
OptionChar = 'r';
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
dprintf(" %c%d", OptionChar, Bp->m_DataSize);
|
|
}
|
|
|
|
if (Bp->m_Flags & DEBUG_BREAKPOINT_ONE_SHOT)
|
|
{
|
|
dprintf(" /1");
|
|
}
|
|
|
|
if (Bp->m_OffsetExpr != NULL)
|
|
{
|
|
dprintf(" %s", Bp->m_OffsetExpr);
|
|
}
|
|
else
|
|
{
|
|
dprintf(" 0x");
|
|
dprintAddr(Bp->GetAddr());
|
|
}
|
|
|
|
if (Bp->m_PassCount > 1)
|
|
{
|
|
dprintf(" 0x%x", Bp->m_PassCount);
|
|
}
|
|
|
|
if (Bp->m_Command != NULL)
|
|
{
|
|
dprintf(" \"%s\"", Bp->m_Command);
|
|
}
|
|
|
|
dprintf(";%c", (Flags & BPCMDS_ONE_LINE) ? ' ' : '\n');
|
|
|
|
if ((Flags & BPCMDS_FORCE_DISABLE) ||
|
|
(Bp->m_Flags & DEBUG_BREAKPOINT_ENABLED) == 0)
|
|
{
|
|
dprintf("bd %u;%c", Bp->m_Id,
|
|
(Flags & BPCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
|
|
if (Bp->GetProcType() != Process->m_Target->m_MachineType)
|
|
{
|
|
dprintf(".effmach .;%c",
|
|
(Flags & BPCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
}
|
|
|
|
if (Flags & BPCMDS_ONE_LINE)
|
|
{
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Parses command-line breakpoint commands for b[aimpw].
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
struct SET_SYMBOL_MATCH_BP
|
|
{
|
|
DebugClient* Client;
|
|
ProcessInfo* Process;
|
|
PSTR MatchString;
|
|
Breakpoint* ProtoBp;
|
|
ULONG Matches;
|
|
ULONG Error;
|
|
};
|
|
|
|
BOOL CALLBACK
|
|
SetSymbolMatchBp(PSYMBOL_INFO SymInfo,
|
|
ULONG Size,
|
|
PVOID UserContext)
|
|
{
|
|
SET_SYMBOL_MATCH_BP* Context =
|
|
(SET_SYMBOL_MATCH_BP*)UserContext;
|
|
|
|
ImageInfo* Image = Context->Process->
|
|
FindImageByOffset(SymInfo->Address, FALSE);
|
|
if (!Image)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
MachineInfo* Machine = MachineTypeInfo(Context->Process->m_Target,
|
|
Image->GetMachineType());
|
|
|
|
if (IgnoreEnumeratedSymbol(Context->Process, Context->MatchString,
|
|
Machine, SymInfo) ||
|
|
!ForceSymbolCodeAddress(Context->Process, SymInfo, Machine))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
ADDR Addr;
|
|
|
|
ADDRFLAT(&Addr, SymInfo->Address);
|
|
if (FAILED(Context->ProtoBp->CheckAddr(&Addr)))
|
|
{
|
|
Context->Error = MEMORY;
|
|
return FALSE;
|
|
}
|
|
|
|
Breakpoint* Bp;
|
|
|
|
if (AddBreakpoint(Context->Client, Machine, DEBUG_BREAKPOINT_CODE,
|
|
DEBUG_ANY_ID, &Bp) != S_OK)
|
|
{
|
|
Context->Error = BPLISTFULL;
|
|
return FALSE;
|
|
}
|
|
|
|
if (Context->ProtoBp->m_Flags & DEBUG_BREAKPOINT_ONE_SHOT)
|
|
{
|
|
Bp->AddFlags(DEBUG_BREAKPOINT_ONE_SHOT);
|
|
}
|
|
|
|
Bp->m_CodeFlags = Context->ProtoBp->m_CodeFlags;
|
|
Bp->m_MatchProcessData = Context->ProtoBp->m_MatchProcessData;
|
|
Bp->m_MatchThreadData = Context->ProtoBp->m_MatchThreadData;
|
|
Bp->SetPassCount(Context->ProtoBp->m_PassCount);
|
|
if (Context->ProtoBp->m_MatchThread)
|
|
{
|
|
Bp->SetMatchThreadId(Context->ProtoBp->m_MatchThread->m_UserId);
|
|
}
|
|
|
|
if (Bp->SetAddr(&Addr, BREAKPOINT_REMOVE_MATCH) != S_OK ||
|
|
(Context->ProtoBp->m_Command &&
|
|
Bp->SetCommand(Context->ProtoBp->m_Command) != S_OK))
|
|
{
|
|
Bp->Relinquish();
|
|
Context->Error = NOMEMORY;
|
|
return FALSE;
|
|
}
|
|
|
|
Bp->AddFlags(DEBUG_BREAKPOINT_ENABLED);
|
|
|
|
dprintf("%3d: %s %s!%s\n",
|
|
Bp->m_Id, FormatAddr64(SymInfo->Address),
|
|
Image->m_ModuleName, SymInfo->Name);
|
|
|
|
Context->Matches++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PDEBUG_BREAKPOINT
|
|
ParseBpCmd(DebugClient* Client,
|
|
UCHAR Type,
|
|
ThreadInfo* Thread)
|
|
{
|
|
ULONG UserId = DEBUG_ANY_ID;
|
|
char Ch;
|
|
ADDR Addr;
|
|
Breakpoint* Bp;
|
|
|
|
if (IS_LOCAL_KERNEL_TARGET(g_Target) || IS_DUMP_TARGET(g_Target))
|
|
{
|
|
error(SESSIONNOTSUP);
|
|
}
|
|
if (!IS_CUR_CONTEXT_ACCESSIBLE())
|
|
{
|
|
error(BADTHREAD);
|
|
}
|
|
|
|
if (IS_LIVE_USER_TARGET(g_Target) && Type == 'a' &&
|
|
(g_EngStatus & ENG_STATUS_AT_INITIAL_BREAK))
|
|
{
|
|
ErrOut("The system resets thread contexts after the process\n");
|
|
ErrOut("breakpoint so hardware breakpoints cannot be set.\n");
|
|
ErrOut("Go to the executable's entry point and set it then.\n");
|
|
*g_CurCmd = 0;
|
|
return NULL;
|
|
}
|
|
|
|
// get the breakpoint number if given
|
|
|
|
Ch = *g_CurCmd;
|
|
if (Ch == '[')
|
|
{
|
|
UserId = (ULONG)GetTermExpression("Breakpoint ID missing from");
|
|
}
|
|
else if (Ch >= '0' && Ch <= '9')
|
|
{
|
|
UserId = Ch - '0';
|
|
Ch = *++g_CurCmd;
|
|
while (Ch >= '0' && Ch <= '9')
|
|
{
|
|
UserId = UserId * 10 + Ch - '0';
|
|
Ch = *++g_CurCmd;
|
|
}
|
|
|
|
if (Ch != ' ' && Ch != '\t' && Ch != '\0')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
}
|
|
|
|
if (UserId != DEBUG_ANY_ID)
|
|
{
|
|
// Remove any existing breakpoint with the given ID.
|
|
Breakpoint* IdBp;
|
|
|
|
if (Type == 'm')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
|
|
if ((IdBp = GetBreakpointById(Client, UserId)) != NULL)
|
|
{
|
|
WarnOut("breakpoint %ld exists, redefining\n", UserId);
|
|
RemoveBreakpoint(IdBp);
|
|
}
|
|
}
|
|
|
|
// Create a new breakpoint.
|
|
if (AddBreakpoint(Client, g_Machine, Type == 'a' ?
|
|
DEBUG_BREAKPOINT_DATA : DEBUG_BREAKPOINT_CODE,
|
|
UserId, &Bp) != S_OK)
|
|
{
|
|
error(BPLISTFULL);
|
|
}
|
|
|
|
// Add in KD internal flags if necessary.
|
|
if (Type == 'i' || Type == 'w')
|
|
{
|
|
if (IS_KERNEL_TARGET(g_Target))
|
|
{
|
|
Bp->m_Flags = Bp->m_Flags | BREAKPOINT_KD_INTERNAL |
|
|
(Type == 'i' ? BREAKPOINT_KD_COUNT_ONLY : 0);
|
|
if (Type == 'w')
|
|
{
|
|
g_Target->InitializeTargetControlledStepping();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// KD internal breakpoints are only supported in
|
|
// kernel debugging.
|
|
Bp->Relinquish();
|
|
error(SYNTAX);
|
|
}
|
|
}
|
|
|
|
// If data breakpoint, get option and size values.
|
|
if (Type == 'a')
|
|
{
|
|
ULONG64 Size;
|
|
ULONG AccessType;
|
|
|
|
Ch = PeekChar();
|
|
Ch = (char)tolower(Ch);
|
|
|
|
if (Ch == 'e')
|
|
{
|
|
AccessType = DEBUG_BREAK_EXECUTE;
|
|
}
|
|
else if (Ch == 'w')
|
|
{
|
|
AccessType = DEBUG_BREAK_WRITE;
|
|
}
|
|
else if (Ch == 'i')
|
|
{
|
|
AccessType = DEBUG_BREAK_IO;
|
|
}
|
|
else if (Ch == 'r')
|
|
{
|
|
AccessType = DEBUG_BREAK_READ;
|
|
}
|
|
else
|
|
{
|
|
Bp->Relinquish();
|
|
error(SYNTAX);
|
|
}
|
|
|
|
g_CurCmd++;
|
|
Size = GetTermExpression("Hardware breakpoint length missing from");
|
|
if (Size & ~ULONG(-1))
|
|
{
|
|
ErrOut("Breakpoint length too big\n");
|
|
Bp->Relinquish();
|
|
error(SYNTAX);
|
|
}
|
|
|
|
// Validate the selections. This assumes that
|
|
// the default offset of zero won't cause problems.
|
|
if (Bp->SetDataParameters((ULONG)Size, AccessType) != S_OK)
|
|
{
|
|
Bp->Relinquish();
|
|
error(SYNTAX);
|
|
}
|
|
|
|
g_CurCmd++;
|
|
}
|
|
|
|
//
|
|
// Parse breakpoint options.
|
|
//
|
|
|
|
while (PeekChar() == '/')
|
|
{
|
|
g_CurCmd++;
|
|
switch(*g_CurCmd++)
|
|
{
|
|
case '1':
|
|
Bp->AddFlags(DEBUG_BREAKPOINT_ONE_SHOT);
|
|
break;
|
|
case 'f':
|
|
Bp->m_CodeFlags = (ULONG)GetTermExpression(NULL);
|
|
break;
|
|
case 'p':
|
|
Bp->m_MatchProcessData = GetTermExpression(NULL);
|
|
break;
|
|
case 't':
|
|
Bp->m_MatchThreadData = GetTermExpression(NULL);
|
|
break;
|
|
default:
|
|
ErrOut("Unknown option '%c'\n", *g_CurCmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
PSTR ExprStart, ExprEnd;
|
|
|
|
Ch = PeekChar();
|
|
|
|
if (Type == 'm')
|
|
{
|
|
char Save;
|
|
|
|
ExprStart = StringValue(STRV_SPACE_IS_SEPARATOR |
|
|
STRV_TRIM_TRAILING_SPACE, &Save);
|
|
ExprEnd = g_CurCmd;
|
|
*g_CurCmd = Save;
|
|
Ch = PeekChar();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Get the breakpoint address, if given, otherwise
|
|
// default to the current IP.
|
|
//
|
|
|
|
BreakpointEvalResult AddrValid = BPEVAL_RESOLVED;
|
|
|
|
g_Target->m_EffMachine->GetPC(&Addr);
|
|
|
|
if (Ch != '"' && Ch != '\0')
|
|
{
|
|
ExprStart = g_CurCmd;
|
|
|
|
g_PrefixSymbols = Type == 'p' || Type == 'u';
|
|
|
|
AddrValid = EvalAddrExpression(g_Process,
|
|
g_Target->m_EffMachineType,
|
|
&Addr);
|
|
|
|
g_PrefixSymbols = FALSE;
|
|
|
|
if (AddrValid == BPEVAL_ERROR)
|
|
{
|
|
Bp->Relinquish();
|
|
return NULL;
|
|
}
|
|
|
|
// If an unresolved symbol was encountered this
|
|
// breakpoint will be deferred. Users can also force
|
|
// breakpoints to use expressions for cases where the
|
|
// address could be resolved but also may become invalid
|
|
// later.
|
|
if (Type == 'u' || AddrValid == BPEVAL_UNRESOLVED)
|
|
{
|
|
HRESULT Status;
|
|
UCHAR Save = *g_CurCmd;
|
|
*g_CurCmd = 0;
|
|
|
|
Status = Bp->SetEvaluatedOffsetExpression(ExprStart,
|
|
AddrValid,
|
|
&Addr);
|
|
|
|
if (Type != 'u' && Status == S_OK)
|
|
{
|
|
WarnOut("Bp expression '%s' could not be resolved, "
|
|
"adding deferred bp\n", ExprStart);
|
|
}
|
|
|
|
*g_CurCmd = Save;
|
|
|
|
if (Status != S_OK)
|
|
{
|
|
Bp->Relinquish();
|
|
error(NOMEMORY);
|
|
}
|
|
}
|
|
|
|
Ch = PeekChar();
|
|
}
|
|
|
|
if (AddrValid != BPEVAL_UNRESOLVED &&
|
|
FAILED(Bp->CheckAddr(&Addr)))
|
|
{
|
|
Bp->Relinquish();
|
|
error(MEMORY);
|
|
}
|
|
|
|
// The public interface only supports flat addresses
|
|
// so use an internal method to set the true address.
|
|
// Do not allow matching breakpoints through the parsing
|
|
// interface as that was the previous behavior.
|
|
if (Bp->SetAddr(&Addr, BREAKPOINT_REMOVE_MATCH) != S_OK)
|
|
{
|
|
Bp->Relinquish();
|
|
error(SYNTAX);
|
|
}
|
|
}
|
|
|
|
// Get the pass count, if given.
|
|
if (Ch != '"' && Ch != ';' && Ch != '\0')
|
|
{
|
|
ULONG64 PassCount = GetExpression();
|
|
if (PassCount < 1 || PassCount > 0xffffffff)
|
|
{
|
|
error(BADRANGE);
|
|
}
|
|
Bp->SetPassCount((ULONG)PassCount);
|
|
Ch = PeekChar();
|
|
}
|
|
|
|
// If next character is double quote, get the command string.
|
|
if (Ch == '"')
|
|
{
|
|
PSTR Str;
|
|
CHAR Save;
|
|
|
|
Str = StringValue(STRV_ESCAPED_CHARACTERS, &Save);
|
|
|
|
if (Bp->SetCommand(Str) != S_OK)
|
|
{
|
|
Bp->Relinquish();
|
|
error(NOMEMORY);
|
|
}
|
|
|
|
*g_CurCmd = Save;
|
|
}
|
|
|
|
// Set some final information.
|
|
if (Thread != NULL)
|
|
{
|
|
Bp->SetMatchThreadId(Thread->m_UserId);
|
|
}
|
|
|
|
// Turn breakpoint on.
|
|
Bp->AddFlags(DEBUG_BREAKPOINT_ENABLED);
|
|
|
|
if (Type == 'm')
|
|
{
|
|
SET_SYMBOL_MATCH_BP Context;
|
|
|
|
// Now that we have the prototype breakpoint created,
|
|
// enumerate all symbol matches for the symbol
|
|
// expression and create specific breakpoints for
|
|
// each hit from the prototype breakpoint.
|
|
*ExprEnd = 0;
|
|
Context.Client = Client;
|
|
Context.Process = g_Process;
|
|
Context.MatchString = ExprStart;
|
|
Context.ProtoBp = Bp;
|
|
Context.Matches = 0;
|
|
Context.Error = 0;
|
|
|
|
SymEnumSymbols(g_Process->m_SymHandle, 0, ExprStart,
|
|
SetSymbolMatchBp, &Context);
|
|
if (Context.Error)
|
|
{
|
|
error(Context.Error);
|
|
}
|
|
if (!Context.Matches)
|
|
{
|
|
ErrOut("No matching symbols found, no breakpoints set\n");
|
|
}
|
|
|
|
Bp->Relinquish();
|
|
Bp = NULL;
|
|
}
|
|
|
|
return Bp;
|
|
}
|
|
|
|
inline BOOL
|
|
IsCodeBreakpointInsertedInRange(Breakpoint* Bp,
|
|
ULONG64 Start, ULONG64 End)
|
|
{
|
|
return (Bp->m_Flags & BREAKPOINT_INSERTED) &&
|
|
Bp->m_BreakType == DEBUG_BREAKPOINT_CODE &&
|
|
Flat(*Bp->GetAddr()) >= Start &&
|
|
Flat(*Bp->GetAddr()) <= End;
|
|
}
|
|
|
|
BOOL
|
|
CheckBreakpointInsertedInRange(ProcessInfo* Process,
|
|
ULONG64 Start, ULONG64 End)
|
|
{
|
|
if ((g_EngStatus & ENG_STATUS_BREAKPOINTS_INSERTED) == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check for a breakpoint that might have caused
|
|
// a break instruction to be inserted in the given
|
|
// offset range. Data breakpoints don't count
|
|
// as they don't actually modify the address they
|
|
// break on.
|
|
//
|
|
|
|
Breakpoint* Bp;
|
|
|
|
for (Bp = Process->m_Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (IsCodeBreakpointInsertedInRange(Bp, Start, End))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if ((g_DeferBp->m_Process == Process &&
|
|
IsCodeBreakpointInsertedInRange(g_DeferBp, Start, End)) ||
|
|
(g_StepTraceBp->m_Process == Process &&
|
|
IsCodeBreakpointInsertedInRange(g_StepTraceBp, Start, End)))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// TargetInfo methods.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
TargetInfo::BeginInsertingBreakpoints(void)
|
|
{
|
|
ProcessInfo* Process;
|
|
ThreadInfo* Thread;
|
|
|
|
ForTargetProcesses(this)
|
|
{
|
|
ForProcessThreads(Process)
|
|
{
|
|
DataBreakpoint::ClearThreadDataBreaks(Thread);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
TargetInfo::InsertDataBreakpoint(ProcessInfo* Process,
|
|
ThreadInfo* Thread,
|
|
class MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
// Ask for default processing.
|
|
return S_FALSE;
|
|
}
|
|
|
|
void
|
|
TargetInfo::EndInsertingBreakpoints(void)
|
|
{
|
|
ProcessInfo* Process;
|
|
ThreadInfo* Thread;
|
|
|
|
if (!g_UpdateDataBreakpoints ||
|
|
!IS_CONTEXT_POSSIBLE(this))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// It's the target machine's responsibility to manage
|
|
// all data breakpoints for all machines, so always
|
|
// force the usage of the target machine here.
|
|
//
|
|
|
|
ThreadInfo* SaveThread = m_RegContextThread;
|
|
|
|
ForTargetProcesses(this)
|
|
{
|
|
ForProcessThreads(Process)
|
|
{
|
|
SetLayersFromThread(Thread);
|
|
ChangeRegContext(Thread);
|
|
m_Machine->InsertThreadDataBreakpoints();
|
|
}
|
|
}
|
|
|
|
ChangeRegContext(SaveThread);
|
|
}
|
|
|
|
void
|
|
TargetInfo::BeginRemovingBreakpoints(void)
|
|
{
|
|
// No work necessary.
|
|
}
|
|
|
|
HRESULT
|
|
TargetInfo::RemoveDataBreakpoint(ProcessInfo* Process,
|
|
ThreadInfo* Thread,
|
|
class MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
// Ask for default processing.
|
|
return S_FALSE;
|
|
}
|
|
|
|
void
|
|
TargetInfo::EndRemovingBreakpoints(void)
|
|
{
|
|
// No work necessary.
|
|
}
|
|
|
|
HRESULT
|
|
TargetInfo::RemoveAllDataBreakpoints(ProcessInfo* Process)
|
|
{
|
|
// No work necessary.
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
TargetInfo::RemoveAllTargetBreakpoints(void)
|
|
{
|
|
// No work necessary.
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
TargetInfo::IsDataBreakpointHit(ThreadInfo* Thread,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
// Ask for default processing.
|
|
return S_FALSE;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::InsertCodeBreakpoint(ProcessInfo* Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG InstrFlags,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
if (InstrFlags != IBI_DEFAULT)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
PDBGKD_MANIPULATE_STATE64 Reply;
|
|
PDBGKD_WRITE_BREAKPOINT64 a = &m.u.WriteBreakPoint;
|
|
NTSTATUS st;
|
|
ULONG rc;
|
|
|
|
//
|
|
// Format state manipulate message
|
|
//
|
|
|
|
m.ApiNumber = DbgKdWriteBreakPointApi;
|
|
m.ReturnStatus = STATUS_PENDING;
|
|
a->BreakPointAddress = Flat(*Addr);
|
|
|
|
//
|
|
// Send the message and context and then wait for reply
|
|
//
|
|
|
|
do
|
|
{
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
rc = m_Transport->
|
|
WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply);
|
|
} while (rc != DBGKD_WAIT_PACKET ||
|
|
Reply->ApiNumber != DbgKdWriteBreakPointApi);
|
|
|
|
st = Reply->ReturnStatus;
|
|
|
|
*(PULONG)StorageSpace = Reply->u.WriteBreakPoint.BreakPointHandle;
|
|
|
|
KdOut("DbgKdWriteBreakPoint(%s) returns %08lx, %x\n",
|
|
FormatAddr64(Flat(*Addr)), st,
|
|
Reply->u.WriteBreakPoint.BreakPointHandle);
|
|
|
|
return CONV_NT_STATUS(st);
|
|
}
|
|
|
|
NTSTATUS
|
|
ConnLiveKernelTargetInfo::KdRestoreBreakPoint(ULONG BreakPointHandle)
|
|
{
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
PDBGKD_MANIPULATE_STATE64 Reply;
|
|
PDBGKD_RESTORE_BREAKPOINT a = &m.u.RestoreBreakPoint;
|
|
NTSTATUS st;
|
|
ULONG rc;
|
|
|
|
//
|
|
// Format state manipulate message
|
|
//
|
|
|
|
m.ApiNumber = DbgKdRestoreBreakPointApi;
|
|
m.ReturnStatus = STATUS_PENDING;
|
|
a->BreakPointHandle = BreakPointHandle;
|
|
|
|
//
|
|
// Send the message and context and then wait for reply
|
|
//
|
|
|
|
do
|
|
{
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
rc = m_Transport->
|
|
WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply);
|
|
} while (rc != DBGKD_WAIT_PACKET ||
|
|
Reply->ApiNumber != DbgKdRestoreBreakPointApi);
|
|
|
|
st = Reply->ReturnStatus;
|
|
|
|
KdOut("DbgKdRestoreBreakPoint(%x) returns %08lx\n",
|
|
BreakPointHandle, st);
|
|
|
|
return st;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::RemoveCodeBreakpoint(ProcessInfo* Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG InstrFlags,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
// When the kernel fills out the CONTROL_REPORT.InstructionStream
|
|
// array it clears any breakpoints that might fall within the
|
|
// array. This means that some breakpoints may already be
|
|
// restored, so the restore call will fail. We could do some
|
|
// checks to try and figure out which ones might be affected
|
|
// but it doesn't seem worthwhile. Just ignore the return
|
|
// value from the restore.
|
|
KdRestoreBreakPoint(*(PULONG)StorageSpace);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::RemoveAllDataBreakpoints(ProcessInfo* Process)
|
|
{
|
|
if (m_Transport->m_WaitingThread)
|
|
{
|
|
// A thread is waiting so we can't communicate
|
|
// with the target machine.
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// If there were any data breakpoints active
|
|
// remove them from all processors. This can't be in
|
|
// RemoveAllKernelBreakpoints as that
|
|
// code is called in the middle of state
|
|
// change processing when the context hasn't
|
|
// been initialized.
|
|
if (g_UpdateDataBreakpoints)
|
|
{
|
|
ULONG Proc;
|
|
|
|
SetEffMachine(m_MachineType, FALSE);
|
|
|
|
g_EngNotify++;
|
|
for (Proc = 0; Proc < m_NumProcessors; Proc++)
|
|
{
|
|
SetCurrentProcessorThread(this, Proc, TRUE);
|
|
|
|
// Force the context to be dirty so it
|
|
// gets written back.
|
|
m_Machine->GetContextState(MCTX_DIRTY);
|
|
m_Machine->RemoveThreadDataBreakpoints();
|
|
}
|
|
g_EngNotify--;
|
|
|
|
// Flush final context.
|
|
ChangeRegContext(NULL);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::RemoveAllTargetBreakpoints(void)
|
|
{
|
|
ULONG i;
|
|
|
|
if (m_Transport->m_WaitingThread)
|
|
{
|
|
// A thread is waiting so we can't communicate
|
|
// with the target machine.
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Indices are array index plus one.
|
|
for (i = 1; i <= BREAKPOINT_TABLE_SIZE; i++)
|
|
{
|
|
KdRestoreBreakPoint(i);
|
|
}
|
|
|
|
// ClearAllInternalBreakpointsApi was added for XP
|
|
// so it fails against any previous OS.
|
|
if (m_KdMaxManipulate > DbgKdClearAllInternalBreakpointsApi)
|
|
{
|
|
DBGKD_MANIPULATE_STATE64 Request;
|
|
|
|
Request.ApiNumber = DbgKdClearAllInternalBreakpointsApi;
|
|
Request.ReturnStatus = STATUS_PENDING;
|
|
|
|
m_Transport->WritePacket(&Request, sizeof(Request),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::InsertTargetCountBreakpoint(PADDR Addr,
|
|
ULONG Flags)
|
|
{
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
ULONG64 Offset = Flat(*Addr);
|
|
|
|
m.ApiNumber = DbgKdSetInternalBreakPointApi;
|
|
m.ReturnStatus = STATUS_PENDING;
|
|
|
|
#ifdef IBP_WORKAROUND
|
|
// The kernel code keeps a ULONG64 for an internal breakpoint
|
|
// address but older kernels did not sign-extend the current IP
|
|
// when comparing against them. In order to work with both
|
|
// broken and fixed kernels send down zero-extended addresses.
|
|
// Don't actually enable this workaround right now as other
|
|
// internal breakpoint bugs can cause the machine to bugcheck.
|
|
Offset = m_Machine->m_Ptr64 ? Offset : (ULONG)Offset;
|
|
#endif
|
|
|
|
m.u.SetInternalBreakpoint.BreakpointAddress = Offset;
|
|
m.u.SetInternalBreakpoint.Flags = Flags;
|
|
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
|
|
KdOut("DbgKdSetInternalBp returns 0x00000000\n");
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::RemoveTargetCountBreakpoint(PADDR Addr)
|
|
{
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
ULONG64 Offset = Flat(*Addr);
|
|
|
|
m.ApiNumber = DbgKdSetInternalBreakPointApi;
|
|
m.ReturnStatus = STATUS_PENDING;
|
|
|
|
#ifdef IBP_WORKAROUND
|
|
// The kernel code keeps a ULONG64 for an internal breakpoint
|
|
// address but older kernels did not sign-extend the current IP
|
|
// when comparing against them. In order to work with both
|
|
// broken and fixed kernels send down zero-extended addresses.
|
|
// Don't actually enable this workaround right now as other
|
|
// internal breakpoint bugs can cause the machine to bugcheck.
|
|
Offset = m_Machine->m_Ptr64 ? Offset : (ULONG)Offset;
|
|
#endif
|
|
|
|
m.u.SetInternalBreakpoint.BreakpointAddress = Offset;
|
|
m.u.SetInternalBreakpoint.Flags = DBGKD_INTERNAL_BP_FLAG_INVALID;
|
|
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
|
|
KdOut("DbgKdSetInternalBp returns 0x00000000\n");
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::QueryTargetCountBreakpoint(PADDR Addr,
|
|
PULONG Flags,
|
|
PULONG Calls,
|
|
PULONG MinInstr,
|
|
PULONG MaxInstr,
|
|
PULONG TotInstr,
|
|
PULONG MaxCps)
|
|
{
|
|
DBGKD_MANIPULATE_STATE64 m;
|
|
PDBGKD_MANIPULATE_STATE64 Reply;
|
|
ULONG rc;
|
|
ULONG64 Offset = Flat(*Addr);
|
|
|
|
m.ApiNumber = DbgKdGetInternalBreakPointApi;
|
|
m.ReturnStatus = STATUS_PENDING;
|
|
|
|
#ifdef IBP_WORKAROUND
|
|
// The kernel code keeps a ULONG64 for an internal breakpoint
|
|
// address but older kernels did not sign-extend the current IP
|
|
// when comparing against them. In order to work with both
|
|
// broken and fixed kernels send down zero-extended addresses.
|
|
// Don't actually enable this workaround right now as other
|
|
// internal breakpoint bugs can cause the machine to bugcheck.
|
|
Offset = m_Machine->m_Ptr64 ? Offset : (ULONG)Offset;
|
|
#endif
|
|
|
|
m.u.GetInternalBreakpoint.BreakpointAddress = Offset;
|
|
|
|
do
|
|
{
|
|
m_Transport->WritePacket(&m, sizeof(m),
|
|
PACKET_TYPE_KD_STATE_MANIPULATE,
|
|
NULL, 0);
|
|
rc = m_Transport->
|
|
WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply);
|
|
}
|
|
while (rc != DBGKD_WAIT_PACKET);
|
|
|
|
*Flags = Reply->u.GetInternalBreakpoint.Flags;
|
|
*Calls = Reply->u.GetInternalBreakpoint.Calls;
|
|
*MaxInstr = Reply->u.GetInternalBreakpoint.MaxInstructions;
|
|
*MinInstr = Reply->u.GetInternalBreakpoint.MinInstructions;
|
|
*TotInstr = Reply->u.GetInternalBreakpoint.TotalInstructions;
|
|
*MaxCps = Reply->u.GetInternalBreakpoint.MaxCallsPerPeriod;
|
|
|
|
KdOut("DbgKdGetInternalBp returns 0x00000000\n");
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::BeginInsertingBreakpoints(void)
|
|
{
|
|
if (!m_ExdiDataBreaks)
|
|
{
|
|
return TargetInfo::BeginInsertingBreakpoints();
|
|
}
|
|
else
|
|
{
|
|
// If direct eXDI support for data breakpoints is
|
|
// used this method doesn't need to do anything.
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::InsertCodeBreakpoint(ProcessInfo* Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG InstrFlags,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
if (InstrFlags != IBI_DEFAULT)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
IeXdiCodeBreakpoint** BpStorage = (IeXdiCodeBreakpoint**)StorageSpace;
|
|
HRESULT Status = m_Server->
|
|
AddCodeBreakpoint(Flat(*Addr), m_CodeBpType, mtVirtual, 0, 0,
|
|
BpStorage);
|
|
if (Status == S_OK)
|
|
{
|
|
// Breakpoints are created disabled so enable it.
|
|
Status = (*BpStorage)->SetState(TRUE, TRUE);
|
|
if (Status != S_OK)
|
|
{
|
|
m_Server->DelCodeBreakpoint(*BpStorage);
|
|
RELEASE(*BpStorage);
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::InsertDataBreakpoint(ProcessInfo* Process,
|
|
ThreadInfo* Thread,
|
|
class MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
if (!m_ExdiDataBreaks)
|
|
{
|
|
return TargetInfo::InsertDataBreakpoint(Process, Thread, Machine,
|
|
Addr, Size, AccessType,
|
|
StorageSpace);
|
|
}
|
|
|
|
DATA_ACCESS_TYPE ExdiAccess;
|
|
|
|
if (AccessType & (DEBUG_BREAK_IO | DEBUG_BREAK_EXECUTE))
|
|
{
|
|
// Not supported.
|
|
return E_NOTIMPL;
|
|
}
|
|
switch(AccessType)
|
|
{
|
|
case 0:
|
|
return E_INVALIDARG;
|
|
case DEBUG_BREAK_READ:
|
|
ExdiAccess = daRead;
|
|
break;
|
|
case DEBUG_BREAK_WRITE:
|
|
ExdiAccess = daWrite;
|
|
break;
|
|
case DEBUG_BREAK_READ | DEBUG_BREAK_WRITE:
|
|
ExdiAccess = daBoth;
|
|
break;
|
|
}
|
|
|
|
IeXdiDataBreakpoint** BpStorage = (IeXdiDataBreakpoint**)StorageSpace;
|
|
HRESULT Status = m_Server->
|
|
AddDataBreakpoint(Flat(*Addr), -1, 0, 0, (BYTE)(Size * 8),
|
|
mtVirtual, 0, ExdiAccess, 0, BpStorage);
|
|
if (Status == S_OK)
|
|
{
|
|
// Breakpoints are created disabled so enable it.
|
|
Status = (*BpStorage)->SetState(TRUE, TRUE);
|
|
if (Status != S_OK)
|
|
{
|
|
m_Server->DelDataBreakpoint(*BpStorage);
|
|
RELEASE(*BpStorage);
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
void
|
|
ExdiLiveKernelTargetInfo::EndInsertingBreakpoints(void)
|
|
{
|
|
if (!m_ExdiDataBreaks)
|
|
{
|
|
TargetInfo::EndInsertingBreakpoints();
|
|
}
|
|
}
|
|
|
|
void
|
|
ExdiLiveKernelTargetInfo::BeginRemovingBreakpoints(void)
|
|
{
|
|
if (!m_ExdiDataBreaks)
|
|
{
|
|
TargetInfo::BeginRemovingBreakpoints();
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::RemoveCodeBreakpoint(ProcessInfo* Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG InstrFlags,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
IeXdiCodeBreakpoint** BpStorage = (IeXdiCodeBreakpoint**)StorageSpace;
|
|
HRESULT Status = m_Server->
|
|
DelCodeBreakpoint(*BpStorage);
|
|
if (Status == S_OK)
|
|
{
|
|
RELEASE(*BpStorage);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::RemoveDataBreakpoint(ProcessInfo* Process,
|
|
ThreadInfo* Thread,
|
|
class MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
if (!m_ExdiDataBreaks)
|
|
{
|
|
return TargetInfo::RemoveDataBreakpoint(Process, Thread, Machine,
|
|
Addr, Size, AccessType,
|
|
StorageSpace);
|
|
}
|
|
|
|
IeXdiDataBreakpoint** BpStorage = (IeXdiDataBreakpoint**)StorageSpace;
|
|
HRESULT Status = m_Server->
|
|
DelDataBreakpoint(*BpStorage);
|
|
if (Status == S_OK)
|
|
{
|
|
RELEASE(*BpStorage);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
void
|
|
ExdiLiveKernelTargetInfo::EndRemovingBreakpoints(void)
|
|
{
|
|
if (!m_ExdiDataBreaks)
|
|
{
|
|
TargetInfo::EndRemovingBreakpoints();
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::IsDataBreakpointHit(ThreadInfo* Thread,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
if (!m_ExdiDataBreaks)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
if (m_BpHit.Type != DBGENG_EXDI_IOCTL_BREAKPOINT_DATA ||
|
|
m_BpHit.Address != Flat(*Addr) ||
|
|
m_BpHit.AccessWidth != Size)
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::BeginInsertingBreakpoints(void)
|
|
{
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_DATA_BREAKPOINTS)
|
|
{
|
|
return TargetInfo::BeginInsertingBreakpoints();
|
|
}
|
|
|
|
// Services handle everything so there's no preparation.
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::InsertCodeBreakpoint(ProcessInfo* Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG InstrFlags,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_CODE_BREAKPOINTS)
|
|
{
|
|
ULONG64 ChangeStart;
|
|
ULONG ChangeLen;
|
|
|
|
Status = Machine->
|
|
InsertBreakpointInstruction(m_Services,
|
|
Process->m_SysHandle,
|
|
Flat(*Addr), InstrFlags, StorageSpace,
|
|
&ChangeStart, &ChangeLen);
|
|
if ((Status == HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY) ||
|
|
Status == HRESULT_FROM_WIN32(ERROR_NOACCESS) ||
|
|
Status == HRESULT_FROM_WIN32(ERROR_WRITE_FAULT)) &&
|
|
(g_EngOptions & DEBUG_ENGOPT_ALLOW_READ_ONLY_BREAKPOINTS))
|
|
{
|
|
HRESULT NewStatus;
|
|
ULONG OldProtect;
|
|
|
|
// Change the page protections to read-write and try again.
|
|
NewStatus = m_Services->
|
|
ProtectVirtual(Process->m_SysHandle, ChangeStart, ChangeLen,
|
|
PAGE_READWRITE, &OldProtect);
|
|
if (NewStatus == S_OK)
|
|
{
|
|
// If the page was already writable there's no point in
|
|
// retrying
|
|
if ((OldProtect & (PAGE_READWRITE |
|
|
PAGE_WRITECOPY |
|
|
PAGE_EXECUTE_READWRITE |
|
|
PAGE_EXECUTE_WRITECOPY)) == 0)
|
|
{
|
|
NewStatus = Machine->
|
|
InsertBreakpointInstruction(m_Services,
|
|
Process->m_SysHandle,
|
|
Flat(*Addr), InstrFlags,
|
|
StorageSpace,
|
|
&ChangeStart, &ChangeLen);
|
|
if (NewStatus == S_OK)
|
|
{
|
|
Status = S_OK;
|
|
}
|
|
}
|
|
|
|
NewStatus = m_Services->
|
|
ProtectVirtual(Process->m_SysHandle,
|
|
ChangeStart, ChangeLen,
|
|
OldProtect, &OldProtect);
|
|
if (NewStatus != S_OK)
|
|
{
|
|
// Couldn't restore page permissions so fail.
|
|
if (Status == S_OK)
|
|
{
|
|
Machine->
|
|
RemoveBreakpointInstruction(m_Services,
|
|
Process->m_SysHandle,
|
|
Flat(*Addr),
|
|
StorageSpace,
|
|
&ChangeStart,
|
|
&ChangeLen);
|
|
}
|
|
|
|
Status = NewStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
if (InstrFlags != IBI_DEFAULT)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
return m_Services->
|
|
InsertCodeBreakpoint(Process->m_SysHandle,
|
|
Flat(*Addr), Machine->m_ExecTypes[0],
|
|
StorageSpace, MAX_BREAKPOINT_LENGTH);
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::InsertDataBreakpoint(ProcessInfo* Process,
|
|
ThreadInfo* Thread,
|
|
class MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_DATA_BREAKPOINTS)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
return m_Services->
|
|
InsertDataBreakpoint(Process->m_SysHandle,
|
|
Thread ? Thread->m_Handle : 0,
|
|
Flat(*Addr), Size, AccessType,
|
|
Machine->m_ExecTypes[0]);
|
|
}
|
|
|
|
void
|
|
LiveUserTargetInfo::EndInsertingBreakpoints(void)
|
|
{
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_DATA_BREAKPOINTS)
|
|
{
|
|
return TargetInfo::EndInsertingBreakpoints();
|
|
}
|
|
|
|
// Services handle everything so there's no preparation.
|
|
}
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::RemoveCodeBreakpoint(ProcessInfo* Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG InstrFlags,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_CODE_BREAKPOINTS)
|
|
{
|
|
ULONG64 ChangeStart;
|
|
ULONG ChangeLen;
|
|
|
|
Status = Machine->
|
|
RemoveBreakpointInstruction(m_Services,
|
|
Process->m_SysHandle,
|
|
Flat(*Addr), StorageSpace,
|
|
&ChangeStart, &ChangeLen);
|
|
if ((Status == HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY) ||
|
|
Status == HRESULT_FROM_WIN32(ERROR_NOACCESS) ||
|
|
Status == HRESULT_FROM_WIN32(ERROR_WRITE_FAULT)) &&
|
|
(g_EngOptions & DEBUG_ENGOPT_ALLOW_READ_ONLY_BREAKPOINTS))
|
|
{
|
|
HRESULT NewStatus;
|
|
ULONG OldProtect;
|
|
|
|
// Change the page protections to read-write and try again.
|
|
NewStatus = m_Services->
|
|
ProtectVirtual(Process->m_SysHandle, ChangeStart, ChangeLen,
|
|
PAGE_READWRITE, &OldProtect);
|
|
if (NewStatus == S_OK)
|
|
{
|
|
// If the page was already writable there's no point in
|
|
// retrying
|
|
if ((OldProtect & (PAGE_READWRITE |
|
|
PAGE_WRITECOPY |
|
|
PAGE_EXECUTE_READWRITE |
|
|
PAGE_EXECUTE_WRITECOPY)) == 0)
|
|
{
|
|
NewStatus = Machine->
|
|
RemoveBreakpointInstruction(m_Services,
|
|
Process->m_SysHandle,
|
|
Flat(*Addr), StorageSpace,
|
|
&ChangeStart, &ChangeLen);
|
|
if (NewStatus == S_OK)
|
|
{
|
|
Status = S_OK;
|
|
}
|
|
}
|
|
|
|
NewStatus = m_Services->
|
|
ProtectVirtual(Process->m_SysHandle, ChangeStart,
|
|
ChangeLen, OldProtect, &OldProtect);
|
|
if (NewStatus != S_OK)
|
|
{
|
|
// Couldn't restore page permissions so fail.
|
|
if (Status == S_OK)
|
|
{
|
|
Machine->
|
|
InsertBreakpointInstruction(m_Services,
|
|
Process->m_SysHandle,
|
|
Flat(*Addr),
|
|
InstrFlags,
|
|
StorageSpace,
|
|
&ChangeStart,
|
|
&ChangeLen);
|
|
}
|
|
|
|
Status = NewStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
return m_Services->
|
|
RemoveCodeBreakpoint(Process->m_SysHandle,
|
|
Flat(*Addr), Machine->m_ExecTypes[0],
|
|
StorageSpace, MAX_BREAKPOINT_LENGTH);
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::RemoveDataBreakpoint(ProcessInfo* Process,
|
|
ThreadInfo* Thread,
|
|
class MachineInfo* Machine,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_DATA_BREAKPOINTS)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
return m_Services->
|
|
RemoveDataBreakpoint(Process->m_SysHandle,
|
|
Thread ? Thread->m_Handle : 0,
|
|
Flat(*Addr), Size, AccessType,
|
|
Machine->m_ExecTypes[0]);
|
|
}
|
|
|
|
HRESULT
|
|
LiveUserTargetInfo::IsDataBreakpointHit(ThreadInfo* Thread,
|
|
PADDR Addr,
|
|
ULONG Size,
|
|
ULONG AccessType,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_DATA_BREAKPOINTS)
|
|
{
|
|
// Ask for default processing.
|
|
return S_FALSE;
|
|
}
|
|
|
|
if (!m_DataBpAddrValid)
|
|
{
|
|
m_DataBpAddrStatus = m_Services->
|
|
GetLastDataBreakpointHit(Thread->m_Process->m_SysHandle,
|
|
Thread->m_Handle,
|
|
&m_DataBpAddr, &m_DataBpAccess);
|
|
m_DataBpAddrValid = TRUE;
|
|
}
|
|
|
|
if (m_DataBpAddrStatus != S_OK ||
|
|
m_DataBpAddr < Flat(*Addr) ||
|
|
m_DataBpAddr >= Flat(*Addr) + Size ||
|
|
!(m_DataBpAccess & AccessType))
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
else
|
|
{
|
|
return S_OK;
|
|
}
|
|
}
|