Leaked source code of windows server 2003
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

//----------------------------------------------------------------------------
//
// 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;
}
}