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