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