mirror of https://github.com/tongzx/nt5src
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.
3687 lines
96 KiB
3687 lines
96 KiB
//----------------------------------------------------------------------------
|
|
//
|
|
// Breakpoint handling functions.
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1997-2001.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#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;
|
|
ADDR g_PrevRelatedPc;
|
|
|
|
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)
|
|
{
|
|
ErrOut("Unable to allocate private breakpoints\n");
|
|
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_Id = Id;
|
|
m_BreakType = Type;
|
|
// Breakpoints are always created disabled since they
|
|
// are not initialized at the time of creation.
|
|
m_Flags = 0;
|
|
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_CurrentProcess;
|
|
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)
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
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();
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
|
|
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();
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
|
|
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();
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
|
|
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)
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
}
|
|
|
|
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;
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
}
|
|
|
|
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
|
|
)
|
|
{
|
|
ENTER_ENGINE();
|
|
|
|
m_PassCount = Count;
|
|
m_CurPassCount = Count;
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
|
|
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->UserId;
|
|
Status = S_OK;
|
|
}
|
|
else
|
|
{
|
|
Status = E_NOINTERFACE;
|
|
}
|
|
|
|
LEAVE_ENGINE();
|
|
return Status;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetMatchThreadId(
|
|
THIS_
|
|
IN ULONG Id
|
|
)
|
|
{
|
|
if (IS_KERNEL_TARGET() &&
|
|
m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
ErrOut("Kernel data breakpoints cannot be limited to a processor\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT Status;
|
|
|
|
ENTER_ENGINE();
|
|
|
|
PTHREAD_INFO Thread = FindThreadByUserId(NULL, Id);
|
|
if (Thread != NULL)
|
|
{
|
|
m_MatchThread = Thread;
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
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;
|
|
|
|
ENTER_ENGINE();
|
|
|
|
Status = ChangeString((PSTR*)&m_Command, &m_CommandLen, Command);
|
|
if (Status == S_OK)
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
Breakpoint::SetOffsetExpression(
|
|
THIS_
|
|
IN PCSTR Expression
|
|
)
|
|
{
|
|
HRESULT Status;
|
|
|
|
ENTER_ENGINE();
|
|
|
|
Status = ChangeString((PSTR*)&m_OffsetExpr, &m_OffsetExprLen, Expression);
|
|
if (Status == S_OK)
|
|
{
|
|
if (Expression != 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();
|
|
}
|
|
else
|
|
{
|
|
// This breakpoint is no longer deferred since there's
|
|
// no way to activate it later any more.
|
|
m_Flags &= ~DEBUG_BREAKPOINT_DEFERRED;
|
|
UpdateInternal();
|
|
}
|
|
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, m_Id, TRUE);
|
|
}
|
|
|
|
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->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->Breakpoints;
|
|
NextBp != NULL;
|
|
NextBp = NextBp->m_Next)
|
|
{
|
|
if (m_Id < NextBp->m_Id)
|
|
{
|
|
break;
|
|
}
|
|
|
|
PrevBp = NextBp;
|
|
}
|
|
|
|
m_Prev = PrevBp;
|
|
if (PrevBp == NULL)
|
|
{
|
|
m_Process->Breakpoints = this;
|
|
}
|
|
else
|
|
{
|
|
PrevBp->m_Next = this;
|
|
}
|
|
m_Next = NextBp;
|
|
if (NextBp == NULL)
|
|
{
|
|
m_Process->BreakpointsTail = this;
|
|
}
|
|
else
|
|
{
|
|
NextBp->m_Prev = this;
|
|
}
|
|
|
|
m_Flags |= BREAKPOINT_IN_LIST;
|
|
m_Process->NumBreakpoints++;
|
|
g_BreakpointListChanged = TRUE;
|
|
}
|
|
|
|
void
|
|
Breakpoint::UnlinkFromList(void)
|
|
{
|
|
DBG_ASSERT(m_Flags & BREAKPOINT_IN_LIST);
|
|
|
|
if (m_Prev == NULL)
|
|
{
|
|
m_Process->Breakpoints = m_Next;
|
|
}
|
|
else
|
|
{
|
|
m_Prev->m_Next = m_Next;
|
|
}
|
|
if (m_Next == NULL)
|
|
{
|
|
m_Process->BreakpointsTail = m_Prev;
|
|
}
|
|
else
|
|
{
|
|
m_Next->m_Prev = m_Prev;
|
|
}
|
|
|
|
m_Flags &= ~BREAKPOINT_IN_LIST;
|
|
m_Process->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);
|
|
|
|
DbgKdSetInternalBp(Flat(m_Addr), Flags);
|
|
}
|
|
|
|
enum
|
|
{
|
|
EVAL_RESOLVED,
|
|
EVAL_RESOLVED_NO_MODULE,
|
|
EVAL_UNRESOLVED,
|
|
EVAL_ERROR
|
|
};
|
|
|
|
ULONG
|
|
EvalAddrExpression(PPROCESS_INFO Process, ULONG Machine, PADDR Addr)
|
|
{
|
|
BOOL Error = FALSE;
|
|
|
|
// 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.
|
|
BOOL OldAllow = g_AllowUnresolvedSymbols;
|
|
ULONG OldNum = g_NumUnresolvedSymbols;
|
|
ULONG NumUn;
|
|
PPROCESS_INFO OldProcess = g_CurrentProcess;
|
|
|
|
// Evaluate the expression in the context of the breakpoint's
|
|
// machine type so that registers and such are available.
|
|
ULONG OldMachine = g_EffMachine;
|
|
SetEffMachine(Machine, FALSE);
|
|
|
|
g_AllowUnresolvedSymbols = TRUE;
|
|
g_NumUnresolvedSymbols = 0;
|
|
g_CurrentProcess = Process;
|
|
|
|
__try
|
|
{
|
|
GetAddrExpression(SEGREG_CODE, Addr);
|
|
}
|
|
__except(CommandExceptionFilter(GetExceptionInformation()))
|
|
{
|
|
Error = TRUE;
|
|
}
|
|
|
|
NumUn = g_NumUnresolvedSymbols;
|
|
SetEffMachine(OldMachine, FALSE);
|
|
g_AllowUnresolvedSymbols = OldAllow;
|
|
g_NumUnresolvedSymbols = OldNum;
|
|
g_CurrentProcess = OldProcess;
|
|
|
|
if (Error)
|
|
{
|
|
return EVAL_ERROR;
|
|
}
|
|
else if (NumUn > 0)
|
|
{
|
|
return EVAL_UNRESOLVED;
|
|
}
|
|
else
|
|
{
|
|
PDEBUG_IMAGE_INFO Image;
|
|
|
|
// Check if this address falls within an existing module.
|
|
for (Image = Process->ImageHead;
|
|
Image != NULL;
|
|
Image = Image->Next)
|
|
{
|
|
if (Flat(*Addr) >= Image->BaseOfImage &&
|
|
Flat(*Addr) < Image->BaseOfImage + Image->SizeOfImage)
|
|
{
|
|
return EVAL_RESOLVED;
|
|
}
|
|
}
|
|
|
|
return EVAL_RESOLVED_NO_MODULE;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
Breakpoint::EvalOffsetExpr(void)
|
|
{
|
|
ADDR Addr;
|
|
PSTR CurCommand = g_CurCmd;
|
|
ULONG OldFlags = m_Flags;
|
|
ULONG Valid;
|
|
|
|
DBG_ASSERT(m_OffsetExpr != NULL);
|
|
|
|
g_CurCmd = (PSTR)m_OffsetExpr;
|
|
g_DisableErrorPrint = TRUE;
|
|
g_PrefixSymbols = TRUE;
|
|
|
|
Valid = EvalAddrExpression(m_Process, m_ProcType, &Addr);
|
|
|
|
g_PrefixSymbols = FALSE;
|
|
g_DisableErrorPrint = FALSE;
|
|
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 == EVAL_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 %d '%s'\n", m_Id, m_OffsetExpr);
|
|
}
|
|
else
|
|
{
|
|
BpOut("Enabling deferred %d '%s' at %s\n",
|
|
m_Id, m_OffsetExpr, FormatAddr64(Flat(m_Addr)));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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)
|
|
{
|
|
ProcType = ModuleMachineType(m_Process, Flat(*Addr));
|
|
if (ProcType == IMAGE_FILE_MACHINE_UNKNOWN)
|
|
{
|
|
ProcType = m_ProcType;
|
|
}
|
|
}
|
|
|
|
if (m_Flags & BREAKPOINT_VIRT_ADDR)
|
|
{
|
|
if (ProcType == IMAGE_FILE_MACHINE_AXP64)
|
|
{
|
|
PIMAGE_FUNCTION_ENTRY64 FunctionEntry;
|
|
FunctionEntry = (PIMAGE_FUNCTION_ENTRY64)
|
|
SymFunctionTableAccess64( m_Process->Handle,
|
|
Flat(*Addr) );
|
|
if (FunctionEntry != NULL)
|
|
{
|
|
if ( (Flat(*Addr) >= FunctionEntry->StartingAddress) &&
|
|
(Flat(*Addr) < FunctionEntry->EndOfPrologue))
|
|
{
|
|
ADDRFLAT(Addr, FunctionEntry->EndOfPrologue & -4I64 );
|
|
}
|
|
}
|
|
}
|
|
else if (ProcType == IMAGE_FILE_MACHINE_ALPHA)
|
|
{
|
|
PIMAGE_FUNCTION_ENTRY FunctionEntry;
|
|
FunctionEntry = (PIMAGE_FUNCTION_ENTRY)
|
|
SymFunctionTableAccess64( m_Process->Handle,
|
|
Flat(*Addr) );
|
|
if (FunctionEntry != NULL)
|
|
{
|
|
if ( (Flat(*Addr) >= FunctionEntry->StartingAddress) &&
|
|
(Flat(*Addr) < FunctionEntry->EndOfPrologue))
|
|
{
|
|
ADDRFLAT(Addr, FunctionEntry->EndOfPrologue & -4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 %ld 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 %d and %d 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_Target->
|
|
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_Target->
|
|
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;
|
|
}
|
|
|
|
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 = g_Target->InsertCodeBreakpoint(m_Process,
|
|
g_AllMachines[m_ProcIndex],
|
|
&m_Addr,
|
|
m_InsertStorage);
|
|
if (Status == S_OK)
|
|
{
|
|
BpOut(" inserted bp %d at %s\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)));
|
|
|
|
m_Flags |= BREAKPOINT_INSERTED;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
ErrOut("Unable to write breakpoint %d at %s, 0x%X\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)), 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;
|
|
}
|
|
|
|
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 = g_Target->RemoveCodeBreakpoint(m_Process,
|
|
g_AllMachines[m_ProcIndex],
|
|
&m_Addr,
|
|
m_InsertStorage);
|
|
if (Status == S_OK)
|
|
{
|
|
BpOut(" removed bp %d from %s\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)));
|
|
|
|
m_Flags &= ~BREAKPOINT_INSERTED;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
ErrOut("Unable to restore breakpoint %d at %s, 0x%X\n",
|
|
m_Id, FormatAddr64(Flat(m_Addr)), 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)
|
|
{
|
|
PTHREAD_INFO 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);
|
|
}
|
|
|
|
// If this breakpoint is restricted to a thread
|
|
// only modify that thread's state. Otherwise
|
|
// update all threads in the process.
|
|
Thread = m_Process->ThreadHead;
|
|
while (Thread)
|
|
{
|
|
if (Thread->NumDataBreaks >= g_Machine->m_MaxDataBreakpoints)
|
|
{
|
|
ErrOut("Too many data breakpoints for %s %d\n",
|
|
IS_KERNEL_TARGET() ? "processor" : "thread",
|
|
Thread->UserId);
|
|
return HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
|
|
}
|
|
else if (m_MatchThread == NULL || m_MatchThread == Thread)
|
|
{
|
|
BpOut("Add %s data bp %d to thread %d\n",
|
|
g_AllMachines[m_ProcIndex]->m_AbbrevName,
|
|
m_Id, Thread->UserId);
|
|
|
|
AddToThread(Thread);
|
|
}
|
|
|
|
Thread = Thread->Next;
|
|
}
|
|
|
|
g_UpdateDataBreakpoints = TRUE;
|
|
m_Flags |= BREAKPOINT_INSERTED;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
DataBreakpoint::Remove(void)
|
|
{
|
|
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);
|
|
|
|
// When breakpoints are inserted the data breakpoint state
|
|
// is always started completely empty to no special
|
|
// work needs to be done when removing.
|
|
g_UpdateDataBreakpoints = TRUE;
|
|
m_Flags &= ~BREAKPOINT_INSERTED;
|
|
return S_OK;
|
|
}
|
|
|
|
void
|
|
DataBreakpoint::ClearThreadDataBreaks(PTHREAD_INFO Thread)
|
|
{
|
|
Thread->NumDataBreaks = 0;
|
|
memset(Thread->DataBreakBps, 0, sizeof(Thread->DataBreakBps));
|
|
}
|
|
|
|
void
|
|
DataBreakpoint::AddToThread(PTHREAD_INFO Thread)
|
|
{
|
|
DBG_ASSERT(Thread->NumDataBreaks < g_Machine->m_MaxDataBreakpoints);
|
|
|
|
Thread->DataBreakBps[Thread->NumDataBreaks] = this;
|
|
Thread->NumDataBreaks++;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// X86DataBreakpoint.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
X86DataBreakpoint::Validate(void)
|
|
{
|
|
ULONG Dr7Bits;
|
|
ULONG Align;
|
|
|
|
if (!IsPow2(m_DataSize) || m_DataSize == 0 || 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;
|
|
}
|
|
|
|
Dr7Bits = (m_DataSize - 1) << 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() ||
|
|
!(GetRegVal32(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;
|
|
PTHREAD_INFO Thread = g_EventThread;
|
|
|
|
// Data breakpoints are only active on particular
|
|
// threads so if the event thread doesn't match
|
|
// the breakpoint can't be hit.
|
|
if (!MatchesCurrentState())
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
// Locate this breakpoint in the thread's data breakpoints
|
|
// if possible.
|
|
for (i = 0; i < Thread->NumDataBreaks; i++)
|
|
{
|
|
// Check for match in addition to equality to handle
|
|
// multiple identical data breakpoints.
|
|
if (Thread->DataBreakBps[i] == this ||
|
|
IsInsertionMatch(Thread->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 ((GetRegVal32(m_Dr6Reg) >> i) & 1)
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
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)
|
|
{
|
|
ULONG i;
|
|
PTHREAD_INFO Thread = g_EventThread;
|
|
|
|
// Data breakpoints are only active on particular
|
|
// threads so if the event thread doesn't match
|
|
// the breakpoint can't be hit.
|
|
if (m_MatchThread != NULL && m_MatchThread != Thread)
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
// Locate this breakpoint in the thread's data breakpoints
|
|
// if possible.
|
|
for (i = 0; i < Thread->NumDataBreaks; i++)
|
|
{
|
|
// Check for match in addition to equality to handle
|
|
// multiple identical data breakpoints.
|
|
if (Thread->DataBreakBps[i] == this ||
|
|
IsInsertionMatch(Thread->DataBreakBps[i]))
|
|
{
|
|
if ((Flat(*Thread->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
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
ULONG i;
|
|
PTHREAD_INFO Thread = g_EventThread;
|
|
|
|
// Data breakpoints are only active on particular
|
|
// threads so if the event thread doesn't match
|
|
// the breakpoint can't be hit.
|
|
if (m_MatchThread != NULL && m_MatchThread != Thread)
|
|
{
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
// Locate this breakpoint in the thread's data breakpoints
|
|
// if possible.
|
|
for (i = 0; i < Thread->NumDataBreaks; i++)
|
|
{
|
|
// Check for match in addition to equality to handle
|
|
// multiple identical data breakpoints.
|
|
if (Thread->DataBreakBps[i] == this ||
|
|
IsInsertionMatch(Thread->DataBreakBps[i]))
|
|
{
|
|
if (((ULONG)Flat(*Thread->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
|
|
{
|
|
return BREAKPOINT_HIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
return BREAKPOINT_NOT_HIT;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Functions.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
BOOL
|
|
BreakpointNeedsToBeDeferred(Breakpoint* Bp, PADDR PcAddr)
|
|
{
|
|
if (IS_CONTEXT_POSSIBLE() &&
|
|
(Bp->m_Process == g_CurrentProcess))
|
|
{
|
|
if (AddrEqu(*Bp->GetAddr(), *PcAddr) &&
|
|
(Bp->m_Flags & BREAKPOINT_VIRT_ADDR))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if ((Bp == g_LastBreakpointHit) && Bp->PcAtHit() &&
|
|
AddrEqu(g_LastBreakpointHitPc, *PcAddr))
|
|
{
|
|
if (g_ContextChanged)
|
|
{
|
|
WarnOut("Breakpoint %ld will not be deferred because "
|
|
"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;
|
|
PTHREAD_INFO OldThread;
|
|
MachineInfo* Machine;
|
|
|
|
if (g_CurrentProcess != NULL &&
|
|
g_CurrentProcess->CurrentThread != 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_CurrentProcess->CurrentThread->Flags &= ~ENG_THREAD_DEFER_BP_TRACE;
|
|
}
|
|
|
|
if ((g_EngStatus & ENG_STATUS_BREAKPOINTS_INSERTED) ||
|
|
(g_EngStatus & ENG_STATUS_SUSPENDED) == 0 ||
|
|
IS_DUMP_TARGET())
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
g_DeferDefined = FALSE;
|
|
|
|
// 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.
|
|
OldThread = g_RegContextThread;
|
|
ChangeRegContext(g_EventThread);
|
|
|
|
Machine = g_Machine;
|
|
|
|
if (g_BreakpointsSuspended)
|
|
{
|
|
goto StepTraceOnly;
|
|
}
|
|
|
|
//
|
|
// Turn off all data breakpoints. (We will turn the enabled ones back
|
|
// on when we restart execution).
|
|
//
|
|
|
|
PTHREAD_INFO Thread;
|
|
PPROCESS_INFO Process;
|
|
|
|
// Clear each thread in each process.
|
|
|
|
Process = g_ProcessHead;
|
|
while (Process)
|
|
{
|
|
Thread = Process->ThreadHead;
|
|
while (Thread)
|
|
{
|
|
DataBreakpoint::ClearThreadDataBreaks(Thread);
|
|
Thread = Thread->Next;
|
|
}
|
|
Process = Process->Next;
|
|
}
|
|
|
|
if (IS_CONTEXT_POSSIBLE())
|
|
{
|
|
Machine->GetPC(&PcAddr);
|
|
}
|
|
|
|
BpOut("InsertBreakpoints PC ");
|
|
if (IS_CONTEXT_POSSIBLE())
|
|
{
|
|
MaskOutAddr(DEBUG_IOUTPUT_BREAKPOINT, &PcAddr);
|
|
BpOut("\n");
|
|
}
|
|
else
|
|
{
|
|
BpOut("?\n");
|
|
}
|
|
|
|
//
|
|
// Set any appropriate permanent breakpoints.
|
|
//
|
|
|
|
Breakpoint* Bp;
|
|
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
BpOut(" Process %d with %d bps\n", Process->UserId,
|
|
Process->NumBreakpoints);
|
|
|
|
for (Bp = Process->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, &PcAddr))
|
|
{
|
|
g_DeferDefined = TRUE;
|
|
if (Bp->m_BreakType == DEBUG_BREAKPOINT_DATA)
|
|
{
|
|
DeferredData = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HRESULT InsertStatus;
|
|
|
|
InsertStatus = Bp->Insert();
|
|
if (InsertStatus != S_OK)
|
|
{
|
|
if (Bp->m_Flags & DEBUG_BREAKPOINT_GO_ONLY)
|
|
{
|
|
ErrOut("go ");
|
|
}
|
|
ErrOut("bp%d at ", Bp->m_Id);
|
|
MaskOutAddr(DEBUG_OUTPUT_ERROR, Bp->GetAddr());
|
|
ErrOut("failed\n");
|
|
|
|
Status = InsertStatus;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enable data breakpoints if necessary.
|
|
if (g_UpdateDataBreakpoints)
|
|
{
|
|
// 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.
|
|
g_TargetMachine->InsertAllDataBreakpoints();
|
|
|
|
// If we deferred a data breakpoint we haven't
|
|
// fully updated the data breakpoint state
|
|
// so leave the update flags set.
|
|
if (!DeferredData)
|
|
{
|
|
g_UpdateDataBreakpoints = FALSE;
|
|
g_DataBreakpointsChanged = FALSE;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (IS_USER_TARGET())
|
|
{
|
|
ChangeRegContext(g_StepTraceBp->m_MatchThread);
|
|
}
|
|
|
|
BpOut("Setting trace flag for step/trace thread %d:%x\n",
|
|
g_RegContextThread ? g_RegContextThread->UserId : 0,
|
|
g_RegContextThread ? g_RegContextThread->SystemId : 0);
|
|
Machine->QuietSetTraceMode(g_StepTraceCmdState == 'b' ?
|
|
TRACE_TAKEN_BRANCH :
|
|
TRACE_INSTRUCTION);
|
|
|
|
if (IS_USER_TARGET())
|
|
{
|
|
ChangeRegContext(g_EventThread);
|
|
}
|
|
}
|
|
else if (IS_CONTEXT_POSSIBLE() &&
|
|
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_CurrentProcess;
|
|
Machine->GetNextOffset(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_SelectExecutionThread == SELTHREAD_ANY &&
|
|
g_SelectedThread == NULL)
|
|
{
|
|
// 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.
|
|
g_SelectExecutionThread = SELTHREAD_INTERNAL_THREAD;
|
|
g_SelectedThread = g_EventThread;
|
|
}
|
|
|
|
if (Flat(*g_DeferBp->GetAddr()) == OFFSET_TRACE)
|
|
{
|
|
BpOut("Setting trace flag for defer thread %d:%x\n",
|
|
g_RegContextThread ? g_RegContextThread->UserId : 0,
|
|
g_RegContextThread ? g_RegContextThread->SystemId : 0);
|
|
Machine->QuietSetTraceMode(TRACE_INSTRUCTION);
|
|
|
|
if (IS_USER_TARGET() &&
|
|
g_CurrentProcess != NULL &&
|
|
g_CurrentProcess->CurrentThread != 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_CurrentProcess->CurrentThread->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");
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL
|
|
RemoveBreakpoints(void)
|
|
{
|
|
if ((g_EngStatus & ENG_STATUS_BREAKPOINTS_INSERTED) == 0 ||
|
|
(g_EngStatus & ENG_STATUS_SUSPENDED) == 0 ||
|
|
IS_DUMP_TARGET())
|
|
{
|
|
return 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).
|
|
//
|
|
|
|
PPROCESS_INFO Process;
|
|
Breakpoint* Bp;
|
|
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
BpOut(" Process %d with %d bps\n", Process->UserId,
|
|
Process->NumBreakpoints);
|
|
|
|
for (Bp = Process->BreakpointsTail; Bp != NULL; Bp = Bp->m_Prev)
|
|
{
|
|
Bp->Remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
g_EngStatus &= ~ENG_STATUS_BREAKPOINTS_INSERTED;
|
|
return TRUE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Create a new breakpoint object.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
AddBreakpoint(DebugClient* Client,
|
|
ULONG Type,
|
|
ULONG DesiredId,
|
|
Breakpoint** RetBp)
|
|
{
|
|
Breakpoint* Bp;
|
|
ULONG Id;
|
|
PPROCESS_INFO Process;
|
|
|
|
if (!IS_MACHINE_SET() || !g_CurrentProcess)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
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;
|
|
for (;;)
|
|
{
|
|
// Search all bps to see if the current ID is in use.
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
for (Bp = Process->Breakpoints; Bp; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Id == Id)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Bp != NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Process != NULL)
|
|
{
|
|
// A breakpoint is already using the current ID.
|
|
// Try the next one.
|
|
Id++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check to see if the desired ID is in use.
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
for (Bp = Process->Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Id == DesiredId)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
}
|
|
}
|
|
|
|
Id = DesiredId;
|
|
}
|
|
|
|
HRESULT Status = g_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
|
|
DiscardBreakpoint(Breakpoint* Bp)
|
|
{
|
|
ULONG i;
|
|
|
|
// 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] == Bp)
|
|
{
|
|
g_GoBreakpoints[i] = NULL;
|
|
}
|
|
}
|
|
|
|
delete Bp;
|
|
}
|
|
|
|
void
|
|
RemoveBreakpoint(Breakpoint* Bp)
|
|
{
|
|
ULONG Id = Bp->m_Id;
|
|
ULONG Flags = Bp->m_Flags;
|
|
|
|
DiscardBreakpoint(Bp);
|
|
|
|
if ((Flags & BREAKPOINT_HIDDEN) == 0)
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, Id, TRUE);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Clean up breakpoints owned by a particular process or thread.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
RemoveProcessBreakpoints(PPROCESS_INFO Process)
|
|
{
|
|
g_EngNotify++;
|
|
|
|
Breakpoint* Bp;
|
|
Breakpoint* NextBp;
|
|
BOOL NeedNotify = FALSE;
|
|
|
|
for (Bp = Process->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(PTHREAD_INFO Thread)
|
|
{
|
|
g_EngNotify++;
|
|
|
|
Breakpoint* Bp;
|
|
Breakpoint* NextBp;
|
|
BOOL NeedNotify = FALSE;
|
|
|
|
DBG_ASSERT(Thread->Process);
|
|
|
|
for (Bp = Thread->Process->Breakpoints; Bp != NULL; Bp = NextBp)
|
|
{
|
|
NextBp = Bp->m_Next;
|
|
|
|
DBG_ASSERT(Bp->m_Process == Thread->Process);
|
|
|
|
if (Bp->m_MatchThread == Thread)
|
|
{
|
|
RemoveBreakpoint(Bp);
|
|
NeedNotify = TRUE;
|
|
}
|
|
}
|
|
|
|
g_EngNotify--;
|
|
if (NeedNotify)
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, DEBUG_ANY_ID, TRUE);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Forces the target machine to remove all kernel breakpoints.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
RemoveAllKernelBreakpoints(void)
|
|
{
|
|
ULONG i;
|
|
|
|
// Indices are array index plus one.
|
|
for (i = 1; i <= BREAKPOINT_TABLE_SIZE; i++)
|
|
{
|
|
DbgKdRestoreBreakPoint(i);
|
|
}
|
|
|
|
// This API was added for Whistler so it fails against
|
|
// any previous OS. Ignore any failure return.
|
|
DbgKdClearAllInternalBreakpoints();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Remove all breakpoints and reset breakpoint state.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
RemoveAllBreakpoints(ULONG Reason)
|
|
{
|
|
PPROCESS_INFO Process;
|
|
|
|
g_EngNotify++;
|
|
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
while (Process->Breakpoints != NULL)
|
|
{
|
|
RemoveBreakpoint(Process->Breakpoints);
|
|
}
|
|
}
|
|
|
|
g_EngNotify--;
|
|
NotifyChangeEngineState(DEBUG_CES_BREAKPOINTS, DEBUG_ANY_ID, TRUE);
|
|
|
|
g_NumGoBreakpoints = 0;
|
|
|
|
// If the machine is shutting down we can't
|
|
// remove breakpoints.
|
|
if (Reason != DEBUG_SESSION_REBOOT &&
|
|
Reason != DEBUG_SESSION_HIBERNATE &&
|
|
Reason != DEBUG_SESSION_FAILURE &&
|
|
IS_CONN_KERNEL_TARGET() &&
|
|
g_DbgKdTransport->m_WaitingThread == 0)
|
|
{
|
|
RemoveAllKernelBreakpoints();
|
|
|
|
// 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(g_TargetMachineType, FALSE);
|
|
|
|
g_EngNotify++;
|
|
for (Proc = 0; Proc < g_TargetNumberProcessors; Proc++)
|
|
{
|
|
SetCurrentProcessorThread(Proc, TRUE);
|
|
|
|
// Force the context to be dirty so it
|
|
// gets written back.
|
|
g_Machine->GetContextState(MCTX_DIRTY);
|
|
g_Machine->RemoveAllDataBreakpoints();
|
|
}
|
|
g_EngNotify--;
|
|
|
|
// Flush final context.
|
|
ChangeRegContext(NULL);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
delete g_StepTraceBp;
|
|
g_StepTraceBp = NULL;
|
|
delete g_DeferBp;
|
|
g_DeferBp = NULL;
|
|
g_DeferDefined = FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Look up breakpoints.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
Breakpoint*
|
|
GetBreakpointByIndex(DebugClient* Client, ULONG Index)
|
|
{
|
|
Breakpoint* Bp;
|
|
|
|
DBG_ASSERT(g_CurrentProcess);
|
|
|
|
for (Bp = g_CurrentProcess->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_CurrentProcess);
|
|
|
|
for (Bp = g_CurrentProcess->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->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(PPROCESS_INFO 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->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 %d\n", Bp->m_Id);
|
|
|
|
if (SetLastBreakpointHit)
|
|
{
|
|
g_LastBreakpointHit = Bp;
|
|
g_Machine->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;
|
|
PPROCESS_INFO Process;
|
|
|
|
for (;;)
|
|
{
|
|
g_BreakpointListChanged = FALSE;
|
|
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
for (Bp = Process->Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Flags & BREAKPOINT_NOTIFY)
|
|
{
|
|
Bp->m_Flags &= ~BREAKPOINT_NOTIFY;
|
|
EventStatus = NotifyBreakpointEvent(EventStatus, Bp);
|
|
|
|
// 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)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Bp)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Process == NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// A module load/unload event has occurred so go through every
|
|
// breakpoint with an offset expression and reevaluate it.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
EvaluateOffsetExpressions(PPROCESS_INFO 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->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)))
|
|
{
|
|
if (Bp->EvalOffsetExpr() &&
|
|
(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 (g_EngStatus & (ENG_STATUS_USER_INTERRUPT |
|
|
ENG_STATUS_PENDING_BREAK_IN))
|
|
{
|
|
// 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, PPROCESS_INFO ForProcess,
|
|
ULONG Id, UCHAR StateChange)
|
|
{
|
|
Breakpoint* Bp;
|
|
Breakpoint* NextBp;
|
|
PPROCESS_INFO Process;
|
|
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
if (ForProcess != NULL && Process != ForProcess)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (Bp = Process->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, PPROCESS_INFO ForProcess,
|
|
ULONG Id)
|
|
{
|
|
PPROCESS_INFO ProcessSaved = g_CurrentProcess;
|
|
Breakpoint* Bp;
|
|
PPROCESS_INFO Process;
|
|
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
if (ForProcess != NULL && Process != ForProcess)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (Bp = Process->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("%2ld %c", Bp->m_Id, StatusChar);
|
|
|
|
if (Bp->GetProcType() != g_TargetMachineType)
|
|
{
|
|
dprintf("%s ",
|
|
g_AllMachines[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))
|
|
{
|
|
IMAGEHLP_LINE Line;
|
|
DWORD Disp;
|
|
|
|
Line.SizeOfStruct = sizeof(Line);
|
|
if (g_CurrentProcess != NULL &&
|
|
SymGetLineFromAddr(g_CurrentProcess->Handle,
|
|
Flat(*Bp->GetAddr()),
|
|
&Disp, &Line))
|
|
{
|
|
dprintf("[%s @ %d]", Line.FileName, Line.LineNumber);
|
|
}
|
|
else
|
|
{
|
|
dprintAddr(Bp->GetAddr());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dprintAddr(Bp->GetAddr());
|
|
}
|
|
}
|
|
else if (g_TargetMachine->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(" ");
|
|
}
|
|
|
|
dprintf(" %04lx (%04lx) ", Bp->m_CurPassCount, Bp->m_PassCount);
|
|
|
|
if ((Bp->m_Flags & DEBUG_BREAKPOINT_DEFERRED) == 0)
|
|
{
|
|
if (IS_USER_TARGET())
|
|
{
|
|
dprintf("%2ld:", Bp->m_Process->UserId);
|
|
if (Bp->m_MatchThread != NULL)
|
|
{
|
|
dprintf("~%03ld ", Bp->m_MatchThread->UserId);
|
|
}
|
|
else
|
|
{
|
|
dprintf("*** ");
|
|
}
|
|
}
|
|
|
|
g_CurrentProcess = Bp->m_Process;
|
|
OutputSymAddr(Flat(*Bp->GetAddr()), SYMADDR_FORCE);
|
|
|
|
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())
|
|
{
|
|
dprintf("\n");
|
|
|
|
for (Process = g_ProcessHead; Process; Process = Process->Next)
|
|
{
|
|
if (ForProcess != NULL && Process != ForProcess)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (Bp = Process->Breakpoints; Bp != NULL; Bp = Bp->m_Next)
|
|
{
|
|
if (Bp->m_Flags & BREAKPOINT_KD_INTERNAL)
|
|
{
|
|
ULONG flags, calls, minInst, maxInst, totInst, maxCPS;
|
|
DbgKdGetInternalBp(Flat(*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);
|
|
g_CurrentProcess = Bp->m_Process;
|
|
OutputSymAddr(Flat(*Bp->GetAddr()), SYMADDR_FORCE);
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
g_CurrentProcess = ProcessSaved;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Outputs commands necessary to recreate current breakpoints.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
ListBreakpointsAsCommands(DebugClient* Client, PPROCESS_INFO Process,
|
|
ULONG Flags)
|
|
{
|
|
Breakpoint* Bp;
|
|
|
|
if (Process == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (Bp = Process->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())
|
|
{
|
|
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() != g_TargetMachineType)
|
|
{
|
|
dprintf(".effmach %s;%c",
|
|
g_AllMachines[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)
|
|
{
|
|
PDEBUG_IMAGE_INFO Image =
|
|
GetImageByOffset(Bp->m_Process, Flat(*Bp->GetAddr()));
|
|
if (Image != NULL)
|
|
{
|
|
dprintf("ld %s;%c", Image->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%d", 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_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 %d;%c", Bp->m_Id,
|
|
(Flags & BPCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
|
|
if (Bp->GetProcType() != g_TargetMachineType)
|
|
{
|
|
dprintf(".effmach .;%c",
|
|
(Flags & BPCMDS_ONE_LINE) ? ' ' : '\n');
|
|
}
|
|
}
|
|
|
|
if (Flags & BPCMDS_ONE_LINE)
|
|
{
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Parses command-line breakpoint commands for b[aipw].
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
PDEBUG_BREAKPOINT
|
|
ParseBpCmd(DebugClient* Client,
|
|
UCHAR Type,
|
|
PTHREAD_INFO Thread)
|
|
{
|
|
ULONG UserId = DEBUG_ANY_ID;
|
|
UCHAR ch;
|
|
ADDR Addr;
|
|
Breakpoint* Bp;
|
|
|
|
if (IS_LOCAL_KERNEL_TARGET() || IS_DUMP_TARGET())
|
|
{
|
|
error(SESSIONNOTSUP);
|
|
}
|
|
if (!IS_CONTEXT_ACCESSIBLE())
|
|
{
|
|
error(BADTHREAD);
|
|
}
|
|
|
|
if (IS_LIVE_USER_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)GetTermExprDesc("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 ((IdBp = GetBreakpointById(Client, UserId)) != NULL)
|
|
{
|
|
WarnOut("breakpoint %ld exists, redefining\n", UserId);
|
|
RemoveBreakpoint(IdBp);
|
|
}
|
|
}
|
|
|
|
// Create a new breakpoint.
|
|
if (AddBreakpoint(Client, 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())
|
|
{
|
|
Bp->m_Flags = Bp->m_Flags | BREAKPOINT_KD_INTERNAL |
|
|
(Type == 'i' ? BREAKPOINT_KD_COUNT_ONLY : 0);
|
|
if (Type == 'w')
|
|
{
|
|
SetupSpecialCalls();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// KD internal breakpoints are only supported in
|
|
// kernel debugging.
|
|
DiscardBreakpoint(Bp);
|
|
error(SYNTAX);
|
|
}
|
|
}
|
|
|
|
// if data breakpoint, get option and size values
|
|
|
|
if (Type == 'a')
|
|
{
|
|
ULONG64 Size;
|
|
ULONG AccessType;
|
|
|
|
ch = PeekChar();
|
|
ch = (UCHAR)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
|
|
{
|
|
DiscardBreakpoint(Bp);
|
|
error(SYNTAX);
|
|
}
|
|
|
|
g_CurCmd++;
|
|
Size = GetTermExprDesc("Hardware breakpoint length missing from");
|
|
if (Size & ~ULONG(-1))
|
|
{
|
|
ErrOut("Breakpoint length too big\n");
|
|
DiscardBreakpoint(Bp);
|
|
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)
|
|
{
|
|
DiscardBreakpoint(Bp);
|
|
error(SYNTAX);
|
|
}
|
|
|
|
g_CurCmd++;
|
|
}
|
|
|
|
//
|
|
// Parse breakpoint options.
|
|
//
|
|
|
|
while (PeekChar() == '/')
|
|
{
|
|
g_CurCmd++;
|
|
switch(*g_CurCmd++)
|
|
{
|
|
case 'p':
|
|
Bp->m_MatchProcessData = GetTermExprDesc(NULL);
|
|
break;
|
|
case 't':
|
|
Bp->m_MatchThreadData = GetTermExprDesc(NULL);
|
|
break;
|
|
default:
|
|
ErrOut("Unknown option '%c'\n", *g_CurCmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// get the breakpoint address, if given, in addr
|
|
// default to PC
|
|
|
|
ULONG AddrValid = EVAL_RESOLVED;
|
|
|
|
g_Machine->GetPC(&Addr);
|
|
|
|
ch = PeekChar();
|
|
if (ch != '"' && ch != '\0')
|
|
{
|
|
PCHAR ExprStart = (PCHAR)g_CurCmd;
|
|
|
|
g_PrefixSymbols = Type == 'p' || Type == 'u';
|
|
|
|
AddrValid = EvalAddrExpression(g_CurrentProcess, g_EffMachine,
|
|
&Addr);
|
|
|
|
g_PrefixSymbols = FALSE;
|
|
|
|
if (AddrValid == EVAL_ERROR)
|
|
{
|
|
DiscardBreakpoint(Bp);
|
|
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 == EVAL_UNRESOLVED)
|
|
{
|
|
HRESULT Status;
|
|
UCHAR Save = *g_CurCmd;
|
|
*g_CurCmd = 0;
|
|
|
|
Status = Bp->SetOffsetExpression(ExprStart);
|
|
|
|
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)
|
|
{
|
|
DiscardBreakpoint(Bp);
|
|
error(BPLISTFULL);
|
|
}
|
|
}
|
|
|
|
ch = PeekChar();
|
|
}
|
|
|
|
if (AddrValid != EVAL_UNRESOLVED)
|
|
{
|
|
ULONG AddrSpace, AddrFlags;
|
|
|
|
if (g_Target->
|
|
QueryAddressInformation(Flat(Addr), DBGKD_QUERY_MEMORY_VIRTUAL,
|
|
&AddrSpace, &AddrFlags) != S_OK)
|
|
{
|
|
ErrOut("Invalid breakpoint address\n");
|
|
DiscardBreakpoint(Bp);
|
|
error(MEMORY);
|
|
}
|
|
|
|
if (Type != 'a' &&
|
|
!(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");
|
|
DiscardBreakpoint(Bp);
|
|
error(MEMORY);
|
|
}
|
|
|
|
if (Type != 'a' && 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");
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
DiscardBreakpoint(Bp);
|
|
error(SYNTAX);
|
|
}
|
|
|
|
// get the pass count, if given
|
|
|
|
if (ch != '"' && ch != ';' && ch != '\0')
|
|
{
|
|
Bp->SetPassCount((ULONG)GetExpression());
|
|
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)
|
|
{
|
|
DiscardBreakpoint(Bp);
|
|
error(BPLISTFULL);
|
|
}
|
|
|
|
*g_CurCmd = Save;
|
|
}
|
|
|
|
// Set some final information.
|
|
if (Thread != NULL)
|
|
{
|
|
Bp->SetMatchThreadId(Thread->UserId);
|
|
}
|
|
|
|
// Turn breakpoint on.
|
|
Bp->AddFlags(DEBUG_BREAKPOINT_ENABLED);
|
|
|
|
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(PPROCESS_INFO 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->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;
|
|
}
|
|
|
|
void
|
|
DbgKdpAcquireHardwareBp(PDBGKD_CONTROL_REQUEST BpRequest)
|
|
{
|
|
BpRequest->u.RequestBreakpoint.Available = FALSE;
|
|
|
|
g_DbgKdTransport->WritePacket(BpRequest,
|
|
sizeof(*BpRequest),
|
|
PACKET_TYPE_KD_CONTROL_REQUEST,
|
|
NULL,
|
|
0);
|
|
}
|
|
|
|
void
|
|
DbgKdpReleaseHardwareBp(PDBGKD_CONTROL_REQUEST BpRequest)
|
|
{
|
|
BpRequest->u.ReleaseBreakpoint.Released = TRUE;
|
|
|
|
g_DbgKdTransport->WritePacket(BpRequest,
|
|
sizeof(*BpRequest),
|
|
PACKET_TYPE_KD_CONTROL_REQUEST,
|
|
NULL,
|
|
0);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// TargetInfo methods.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::InsertCodeBreakpoint(PPROCESS_INFO Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
NTSTATUS Status = DbgKdWriteBreakPoint(Flat(*Addr),
|
|
(PULONG_PTR)StorageSpace);
|
|
return CONV_NT_STATUS(Status);
|
|
}
|
|
|
|
HRESULT
|
|
ConnLiveKernelTargetInfo::RemoveCodeBreakpoint(PPROCESS_INFO Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
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.
|
|
DbgKdRestoreBreakPoint(*(PULONG_PTR)StorageSpace);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::InsertCodeBreakpoint(PPROCESS_INFO Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
HRESULT Status = m_Server->
|
|
AddCodeBreakpoint(Flat(*Addr), m_CodeBpType, mtVirtual, 0, 0,
|
|
(IeXdiCodeBreakpoint**)StorageSpace);
|
|
if (Status == S_OK)
|
|
{
|
|
// Breakpoints are created disabled so enable it.
|
|
Status = (*(IeXdiCodeBreakpoint**)StorageSpace)->SetState(TRUE, TRUE);
|
|
if (Status != S_OK)
|
|
{
|
|
m_Server->DelCodeBreakpoint(*(IeXdiCodeBreakpoint**)StorageSpace);
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
ExdiLiveKernelTargetInfo::RemoveCodeBreakpoint(PPROCESS_INFO Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
HRESULT Status = m_Server->
|
|
DelCodeBreakpoint(*(IeXdiCodeBreakpoint**)StorageSpace);
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
UserTargetInfo::InsertCodeBreakpoint(PPROCESS_INFO Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_CODE_BREAKPOINTS)
|
|
{
|
|
ULONG64 ChangeStart;
|
|
ULONG ChangeLen;
|
|
|
|
Status = Machine->
|
|
InsertBreakpointInstruction(m_Services, Process->FullHandle,
|
|
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->FullHandle, 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->FullHandle,
|
|
Flat(*Addr), StorageSpace,
|
|
&ChangeStart, &ChangeLen);
|
|
if (NewStatus == S_OK)
|
|
{
|
|
Status = S_OK;
|
|
}
|
|
}
|
|
|
|
NewStatus = m_Services->
|
|
ProtectVirtual(Process->FullHandle, ChangeStart, ChangeLen,
|
|
OldProtect, &OldProtect);
|
|
if (NewStatus != S_OK)
|
|
{
|
|
// Couldn't restore page permissions so fail.
|
|
if (Status == S_OK)
|
|
{
|
|
Machine->
|
|
RemoveBreakpointInstruction(m_Services,
|
|
Process->FullHandle,
|
|
Flat(*Addr),
|
|
StorageSpace,
|
|
&ChangeStart,
|
|
&ChangeLen);
|
|
}
|
|
|
|
Status = NewStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
return m_Services->
|
|
InsertCodeBreakpoint(Process->FullHandle,
|
|
Flat(*Addr), Machine->m_ExecTypes[0],
|
|
StorageSpace, MAX_BREAKPOINT_LENGTH);
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
UserTargetInfo::RemoveCodeBreakpoint(PPROCESS_INFO Process,
|
|
MachineInfo* Machine,
|
|
PADDR Addr,
|
|
PUCHAR StorageSpace)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if (m_ServiceFlags & DBGSVC_GENERIC_CODE_BREAKPOINTS)
|
|
{
|
|
ULONG64 ChangeStart;
|
|
ULONG ChangeLen;
|
|
|
|
Status = Machine->
|
|
RemoveBreakpointInstruction(m_Services, Process->FullHandle,
|
|
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->FullHandle, 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->FullHandle,
|
|
Flat(*Addr), StorageSpace,
|
|
&ChangeStart, &ChangeLen);
|
|
if (NewStatus == S_OK)
|
|
{
|
|
Status = S_OK;
|
|
}
|
|
}
|
|
|
|
NewStatus = m_Services->
|
|
ProtectVirtual(Process->FullHandle, ChangeStart, ChangeLen,
|
|
OldProtect, &OldProtect);
|
|
if (NewStatus != S_OK)
|
|
{
|
|
// Couldn't restore page permissions so fail.
|
|
if (Status == S_OK)
|
|
{
|
|
Machine->
|
|
InsertBreakpointInstruction(m_Services,
|
|
Process->FullHandle,
|
|
Flat(*Addr),
|
|
StorageSpace,
|
|
&ChangeStart,
|
|
&ChangeLen);
|
|
}
|
|
|
|
Status = NewStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
return m_Services->
|
|
RemoveCodeBreakpoint(Process->FullHandle,
|
|
Flat(*Addr), Machine->m_ExecTypes[0],
|
|
StorageSpace, MAX_BREAKPOINT_LENGTH);
|
|
}
|
|
}
|