summaryrefslogtreecommitdiff
path: root/src/debug/di/breakpoint.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/breakpoint.cpp')
-rw-r--r--src/debug/di/breakpoint.cpp723
1 files changed, 723 insertions, 0 deletions
diff --git a/src/debug/di/breakpoint.cpp b/src/debug/di/breakpoint.cpp
new file mode 100644
index 0000000000..1722be793f
--- /dev/null
+++ b/src/debug/di/breakpoint.cpp
@@ -0,0 +1,723 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+//*****************************************************************************
+// File: breakpoint.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+/* ------------------------------------------------------------------------- *
+ * Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+CordbBreakpoint::CordbBreakpoint(CordbProcess * pProcess, CordbBreakpointType bpType)
+ : CordbBase(pProcess, 0, enumCordbBreakpoint),
+ m_active(false), m_type(bpType)
+{
+}
+
+// Neutered by CordbAppDomain
+void CordbBreakpoint::Neuter()
+{
+ m_pAppDomain = NULL; // clear ref
+ CordbBase::Neuter();
+}
+
+HRESULT CordbBreakpoint::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugBreakpoint)
+ {
+ *pInterface = static_cast<ICorDebugBreakpoint*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugBreakpoint*>(this));
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbBreakpoint::BaseIsActive(BOOL *pbActive)
+{
+ *pbActive = m_active ? TRUE : FALSE;
+
+ return S_OK;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Function Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+CordbFunctionBreakpoint::CordbFunctionBreakpoint(CordbCode *code,
+ SIZE_T offset)
+ : CordbBreakpoint(code->GetProcess(), CBT_FUNCTION),
+ m_code(code), m_offset(offset)
+{
+ // Remember the app domain we came from so that breakpoints can be
+ // deactivated from within the ExitAppdomain callback.
+ m_pAppDomain = m_code->GetAppDomain();
+ _ASSERTE(m_pAppDomain != NULL);
+}
+
+CordbFunctionBreakpoint::~CordbFunctionBreakpoint()
+{
+ // @todo- eventually get CordbFunctionBreakpoint rooted and enable this.
+ //_ASSERTE(this->IsNeutered());
+ //_ASSERTE(m_code == NULL);
+}
+
+void CordbFunctionBreakpoint::Neuter()
+{
+ Disconnect();
+ CordbBreakpoint::Neuter();
+}
+
+HRESULT CordbFunctionBreakpoint::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFunctionBreakpoint)
+ {
+ *pInterface = static_cast<ICorDebugFunctionBreakpoint*>(this);
+ }
+ else
+ {
+ // Not looking for a function breakpoint? See if the base class handles
+ // this interface. (issue 143976)
+ return CordbBreakpoint::QueryInterface(id, pInterface);
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbFunctionBreakpoint::GetFunction(ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ if (m_code == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ if (m_code->IsNeutered())
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+
+ *ppFunction = static_cast<ICorDebugFunction *> (m_code->GetFunction());
+ (*ppFunction)->AddRef();
+
+ return S_OK;
+}
+
+// m_id is actually a LSPTR_BREAKPOINT. Get it as a type-safe member.
+LSPTR_BREAKPOINT CordbFunctionBreakpoint::GetLsPtrBP()
+{
+ LSPTR_BREAKPOINT p;
+ p.Set((void*) m_id);
+ return p;
+}
+
+HRESULT CordbFunctionBreakpoint::GetOffset(ULONG32 *pnOffset)
+{
+ //REVISIT_TODO: is this casting correct for ia64?
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnOffset, SIZE_T *);
+
+ *pnOffset = (ULONG32)m_offset;
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Activates or removes a breakpoint
+//
+// Arguments:
+// fActivate - TRUE if to activate the breakpoint, else FALSE.
+//
+// Return Value:
+// S_OK if successful, else a specific error code detailing the type of failure.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbFunctionBreakpoint::Activate(BOOL fActivate)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ OK_IF_NEUTERED(this); // we'll check again later
+
+ if (fActivate == (m_active == true) )
+ {
+ return S_OK;
+ }
+
+ // For backwards compat w/ everett, we let the other error codes
+ // take precedence over neutering error codes.
+ if ((m_code == NULL) || this->IsNeutered())
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr;
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ // For legacy, check this error condition. We must do this under the stop-go lock to ensure
+ // that the m_code object was not deleted out from underneath us.
+ //
+ // 6/23/09 - This isn't just for legacy anymore, collectible types should be able to hit this
+ // by unloading the module containing the code this breakpoint is bound to.
+ if (m_code->IsNeutered())
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+
+
+ //
+ // <REVISIT_TODO>@todo: when we implement module and value breakpoints, then
+ // we'll want to factor some of this code out.</REVISIT_TODO>
+ //
+ CordbProcess * pProcess = GetProcess();
+
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->ClearPatchTable(); // if we add something, then the right side
+ // view of the patch table is no longer valid
+
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ CordbAppDomain * pAppDomain = GetAppDomain();
+ _ASSERTE (pAppDomain != NULL);
+
+ if (fActivate)
+ {
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_BREAKPOINT_ADD, true, pAppDomain->GetADToken());
+
+ pEvent->BreakpointData.funcMetadataToken = m_code->GetMetadataToken();
+ pEvent->BreakpointData.vmDomainFile = m_code->GetModule()->GetRuntimeDomainFile();
+ pEvent->BreakpointData.encVersion = m_code->GetVersion();
+
+ BOOL fIsIL = m_code->IsIL();
+
+ pEvent->BreakpointData.isIL = fIsIL ? true : false;
+ pEvent->BreakpointData.offset = m_offset;
+ if (fIsIL)
+ {
+ pEvent->BreakpointData.nativeCodeMethodDescToken = pEvent->BreakpointData.nativeCodeMethodDescToken.NullPtr();
+ }
+ else
+ {
+ pEvent->BreakpointData.nativeCodeMethodDescToken =
+ (m_code.GetValue()->AsNativeCode())->GetVMNativeCodeMethodDescToken().ToLsPtr();
+ }
+
+ // Note: we're sending a two-way event, so it blocks here
+ // until the breakpoint is really added and the reply event is
+ // copied over the event we sent.
+ lockHolder.Release();
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+ lockHolder.Acquire();
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ m_id = LsPtrToCookie(pEvent->BreakpointData.breakpointToken);
+
+ // If we weren't able to allocate the BP, we should have set the
+ // hr on the left side.
+ _ASSERTE(m_id != 0);
+
+
+ pAppDomain->m_breakpoints.AddBase(this);
+ m_active = true;
+
+ // Continue called automatically by StopContinueHolder
+ }
+ else
+ {
+ _ASSERTE (pAppDomain != NULL);
+
+ if (pProcess->IsSafeToSendEvents())
+ {
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_BREAKPOINT_REMOVE, false, pAppDomain->GetADToken());
+
+ pEvent->BreakpointData.breakpointToken = GetLsPtrBP();
+
+ lockHolder.Release();
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+ lockHolder.Acquire();
+
+ hr = WORST_HR(hr, pEvent->hr);
+ }
+ else
+ {
+ hr = CORDBHRFromProcessState(pProcess, pAppDomain);
+ }
+
+ pAppDomain->m_breakpoints.RemoveBase(LsPtrToCookie(GetLsPtrBP()));
+ m_active = false;
+ }
+
+ return hr;
+}
+
+void CordbFunctionBreakpoint::Disconnect()
+{
+ m_code.Clear();
+}
+
+/* ------------------------------------------------------------------------- *
+ * Stepper class
+ * ------------------------------------------------------------------------- */
+
+CordbStepper::CordbStepper(CordbThread *thread, CordbFrame *frame)
+ : CordbBase(thread->GetProcess(), 0, enumCordbStepper),
+ m_thread(thread), m_frame(frame),
+ m_stepperToken(0), m_active(false),
+ m_rangeIL(TRUE),
+ m_fIsJMCStepper(false),
+ m_rgfMappingStop(STOP_OTHER_UNMAPPED),
+ m_rgfInterceptStop(INTERCEPT_NONE)
+{
+}
+
+HRESULT CordbStepper::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugStepper)
+ *pInterface = static_cast<ICorDebugStepper *>(this);
+ else if (id == IID_ICorDebugStepper2)
+ *pInterface = static_cast<ICorDebugStepper2 *>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugStepper *>(this));
+ else
+ return E_NOINTERFACE;
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetRangeIL(BOOL bIL)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ m_rangeIL = (bIL != FALSE);
+
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetJMC(BOOL fIsJMCStepper)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ // Can't have JMC and stopping with anything else.
+ if (m_rgfMappingStop & STOP_ALL)
+ return E_INVALIDARG;
+
+ m_fIsJMCStepper = (fIsJMCStepper != FALSE);
+ return S_OK;
+}
+
+HRESULT CordbStepper::IsActive(BOOL *pbActive)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ *pbActive = m_active;
+
+ return S_OK;
+}
+
+// M_id is a ptr to the stepper in the LS process.
+LSPTR_STEPPER CordbStepper::GetLsPtrStepper()
+{
+ LSPTR_STEPPER p;
+ p.Set((void*) m_id);
+ return p;
+}
+
+HRESULT CordbStepper::Deactivate()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ if (!m_active)
+ return S_OK;
+
+ FAIL_IF_NEUTERED(this);
+
+ if (m_thread == NULL)
+ return CORDBG_E_PROCESS_TERMINATED;
+
+ HRESULT hr;
+ CordbProcess *process = GetProcess();
+ ATT_ALLOW_LIVE_DO_STOPGO(process);
+
+ process->Lock();
+
+ if (!m_active) // another thread may be deactivating (e.g. step complete event)
+ {
+ process->Unlock();
+ return S_OK;
+ }
+
+ CordbAppDomain *pAppDomain = GetAppDomain();
+ _ASSERTE (pAppDomain != NULL);
+
+ DebuggerIPCEvent event;
+ process->InitIPCEvent(&event,
+ DB_IPCE_STEP_CANCEL,
+ false,
+ pAppDomain->GetADToken());
+
+ event.StepData.stepperToken = GetLsPtrStepper();
+
+ process->Unlock();
+ hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+ hr = WORST_HR(hr, event.hr);
+ process->Lock();
+
+
+ process->m_steppers.RemoveBase((ULONG_PTR)m_id);
+ m_active = false;
+
+ process->Unlock();
+
+ return hr;
+}
+
+HRESULT CordbStepper::SetInterceptMask(CorDebugIntercept mask)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ m_rgfInterceptStop = mask;
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetUnmappedStopMask(CorDebugUnmappedStop mask)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // You must be Win32 attached to stop in unmanaged code.
+ if ((mask & STOP_UNMANAGED) && !GetProcess()->IsInteropDebugging())
+ return E_INVALIDARG;
+
+ // Limitations on JMC Stepping - if JMC stepping is active,
+ // all other stop masks must be disabled.
+ // The jit can't place JMC probes before the prolog, so if we're
+ // we're JMC stepping, we'll stop after the prolog.
+ // The implementation for JMC stepping also doesn't let us stop in
+ // unmanaged code. (because there are no probes there).
+ // So enforce those implementation limitations here.
+ if (m_fIsJMCStepper)
+ {
+ if (mask & STOP_ALL)
+ return E_INVALIDARG;
+ }
+
+ // @todo- Ensure that we only set valid bits.
+
+
+ m_rgfMappingStop = mask;
+ return S_OK;
+}
+
+HRESULT CordbStepper::Step(BOOL bStepIn)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ return CORDBG_E_PROCESS_TERMINATED;
+
+ return StepRange(bStepIn, NULL, 0);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Ships off a step-range command to the left-side. On the next continue the LS will
+// step across one range at a time.
+//
+// Arguments:
+// fStepIn - TRUE if this stepper should execute a step-in, else FALSE
+// rgRanges - Array of ranges that define a single step.
+// cRanges - Count of number of elements in rgRanges.
+//
+// Returns:
+// S_OK if the stepper is successfully set-up, else an appropriate error code.
+//
+HRESULT CordbStepper::StepRange(BOOL fStepIn,
+ COR_DEBUG_STEP_RANGE rgRanges[],
+ ULONG32 cRanges)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(rgRanges, COR_DEBUG_STEP_RANGE, cRanges, true, true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr = S_OK;
+
+ if (m_active)
+ {
+ //
+ // Deactivate the current stepping.
+ // or return an error???
+ //
+ hr = Deactivate();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ // Validate step-ranges. Ranges are exclusive, so end offset
+ // should always be greater than start offset.
+ // Ranges don't have to be sorted.
+ // Zero ranges is ok; though they ought to just call Step() in that case.
+ for (ULONG32 i = 0; i < cRanges; i++)
+ {
+ if (rgRanges[i].startOffset >= rgRanges[i].endOffset)
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO10, "Illegal step range. 0x%x-0x%x\n", rgRanges[i].startOffset, rgRanges[i].endOffset);
+ return ErrWrapper(E_INVALIDARG);
+ }
+ }
+
+ CordbProcess * pProcess = GetProcess();
+
+ //
+ // Build step event
+ //
+
+ DebuggerIPCEvent * pEvent = reinterpret_cast<DebuggerIPCEvent *>(_alloca(CorDBIPC_BUFFER_SIZE));
+
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_STEP, true, GetAppDomain()->GetADToken());
+
+ pEvent->StepData.vmThreadToken = m_thread->m_vmThreadToken;
+ pEvent->StepData.rgfMappingStop = m_rgfMappingStop;
+ pEvent->StepData.rgfInterceptStop = m_rgfInterceptStop;
+ pEvent->StepData.IsJMCStop = !!m_fIsJMCStepper;
+
+
+ if (m_frame == NULL)
+ {
+ pEvent->StepData.frameToken = LEAF_MOST_FRAME;
+ }
+ else
+ {
+ pEvent->StepData.frameToken = m_frame->GetFramePointer();
+ }
+
+ pEvent->StepData.stepIn = (fStepIn != 0);
+ pEvent->StepData.totalRangeCount = cRanges;
+ pEvent->StepData.rangeIL = m_rangeIL;
+
+ //
+ // Send ranges. We may have to send > 1 message.
+ //
+
+ COR_DEBUG_STEP_RANGE * pRangeStart = &(pEvent->StepData.range);
+ COR_DEBUG_STEP_RANGE * pRangeEnd = (reinterpret_cast<COR_DEBUG_STEP_RANGE *> (((BYTE *)pEvent) + CorDBIPC_BUFFER_SIZE)) - 1;
+
+ int cRangesToGo = cRanges;
+
+ if (cRangesToGo > 0)
+ {
+ while (cRangesToGo > 0)
+ {
+ //
+ // Find the number of ranges we can copy this time thru the loop
+ //
+ int cRangesToCopy;
+
+ if (cRangesToGo < (pRangeEnd - pRangeStart))
+ {
+ cRangesToCopy = cRangesToGo;
+ }
+ else
+ {
+ cRangesToCopy = (unsigned int)(pRangeEnd - pRangeStart);
+ }
+
+ //
+ // Copy the ranges into the IPC block now, 1-by-1
+ //
+ int cRangesCopied = 0;
+
+ while (cRangesCopied != cRangesToCopy)
+ {
+ pRangeStart[cRangesCopied] = rgRanges[cRanges - cRangesToGo + cRangesCopied];
+ cRangesCopied++;
+ }
+
+ pEvent->StepData.rangeCount = cRangesCopied;
+
+ cRangesToGo -= cRangesCopied;
+
+ //
+ // Send step event (two-way event here...)
+ //
+
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+ }
+ else
+ {
+ //
+ // Send step event without any ranges (two-way event here...)
+ //
+
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ m_id = LsPtrToCookie(pEvent->StepData.stepperToken);
+
+ LOG((LF_CORDB,LL_INFO10000, "CS::SR: m_id:0x%x | 0x%x \n",
+ m_id,
+ LsPtrToCookie(pEvent->StepData.stepperToken)));
+
+#ifdef _DEBUG
+ CordbAppDomain *pAppDomain = GetAppDomain();
+#endif
+ _ASSERTE (pAppDomain != NULL);
+
+ pProcess->Lock();
+
+ pProcess->m_steppers.AddBase(this);
+ m_active = true;
+
+ pProcess->Unlock();
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Ships off a step-out command to the left-side. On the next continue the LS will
+// execute a step-out
+//
+// Returns:
+// S_OK if the stepper is successfully set-up, else an appropriate error code.
+//
+HRESULT CordbStepper::StepOut()
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr;
+
+ if (m_active)
+ {
+ //
+ // Deactivate the current stepping.
+ // or return an error???
+ //
+
+ hr = Deactivate();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ CordbProcess * pProcess = GetProcess();
+
+ // We don't do native step-out.
+ if (pProcess->SupportsVersion(ver_ICorDebugProcess2))
+ {
+ if ((m_rgfMappingStop & STOP_UNMANAGED) != 0)
+ {
+ return ErrWrapper(CORDBG_E_CANT_INTEROP_STEP_OUT);
+ }
+ }
+
+ //
+ // Build step event
+ //
+
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_STEP_OUT, true, GetAppDomain()->GetADToken());
+
+ pEvent->StepData.vmThreadToken = m_thread->m_vmThreadToken;
+ pEvent->StepData.rgfMappingStop = m_rgfMappingStop;
+ pEvent->StepData.rgfInterceptStop = m_rgfInterceptStop;
+ pEvent->StepData.IsJMCStop = !!m_fIsJMCStepper;
+
+ if (m_frame == NULL)
+ {
+ pEvent->StepData.frameToken = LEAF_MOST_FRAME;
+ }
+ else
+ {
+ pEvent->StepData.frameToken = m_frame->GetFramePointer();
+ }
+
+ pEvent->StepData.totalRangeCount = 0;
+
+ // Note: two-way event here...
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ m_id = LsPtrToCookie(pEvent->StepData.stepperToken);
+
+#ifdef _DEBUG
+ CordbAppDomain * pAppDomain = GetAppDomain();
+#endif
+ _ASSERTE (pAppDomain != NULL);
+
+ pProcess->Lock();
+
+ pProcess->m_steppers.AddBase(this);
+ m_active = true;
+
+ pProcess->Unlock();
+
+ return S_OK;
+}