Source code of Windows XP (NT5)
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

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