Permalink
Fetching contributors…
Cannot retrieve contributors at this time
16967 lines (14093 sloc) 558 KB
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//*****************************************************************************
// File: debugger.cpp
//
//
// Debugger runtime controller routines.
//
//*****************************************************************************
#include "stdafx.h"
#include "debugdebugger.h"
#include "ipcmanagerinterface.h"
#include "../inc/common.h"
#include "perflog.h"
#include "eeconfig.h" // This is here even for retail & free builds...
#include "../../dlls/mscorrc/resource.h"
#include "context.h"
#include "vars.hpp"
#include <limits.h>
#include "ilformatter.h"
#include "typeparse.h"
#include "debuginfostore.h"
#include "generics.h"
#include "../../vm/methoditer.h"
#include "../../vm/encee.h"
#include "../../vm/dwreport.h"
#include "../../vm/eepolicy.h"
#include "../../vm/excep.h"
#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
#include "dbgtransportsession.h"
#endif // FEATURE_DBGIPC_TRANSPORT_VM
#ifdef TEST_DATA_CONSISTENCY
#include "datatest.h"
#endif // TEST_DATA_CONSISTENCY
#include "dbgenginemetrics.h"
#include "../../vm/rejit.h"
#include "threadsuspend.h"
class CCLRSecurityAttributeManager;
extern CCLRSecurityAttributeManager s_CLRSecurityAttributeManager;
#ifdef DEBUGGING_SUPPORTED
#ifdef _DEBUG
// Reg key. We can set this and then any debugger-lazy-init code will assert.
// This helps track down places where we're caching in debugger stuff in a
// non-debugger scenario.
bool g_DbgShouldntUseDebugger = false;
#endif
/* ------------------------------------------------------------------------ *
* Global variables
* ------------------------------------------------------------------------ */
GPTR_IMPL(Debugger, g_pDebugger);
GPTR_IMPL(EEDebugInterface, g_pEEInterface);
SVAL_IMPL_INIT(BOOL, Debugger, s_fCanChangeNgenFlags, TRUE);
// This is a public export so debuggers can read and determine if the coreclr
// process is waiting for JIT debugging attach.
GVAL_IMPL_INIT(ULONG, CLRJitAttachState, 0);
bool g_EnableSIS = false;
// The following instances are used for invoking overloaded new/delete
InteropSafe interopsafe;
InteropSafeExecutable interopsafeEXEC;
#ifndef DACCESS_COMPILE
DebuggerRCThread *g_pRCThread = NULL;
#ifndef _PREFAST_
// Do some compile time checking on the events in DbgIpcEventTypes.h
// No one ever calls this. But the compiler should still compile it,
// and that should be sufficient.
void DoCompileTimeCheckOnDbgIpcEventTypes()
{
_ASSERTE(!"Don't call this function. It just does compile time checking\n");
// We use the C_ASSERT macro here to get a compile-time assert.
// Make sure we don't have any duplicate numbers.
// The switch statements in the main loops won't always catch this
// since we may not switch on all events.
// store Type-0 in const local vars, so we can use them for bounds checking
// Create local vars with the val from Type1 & Type2. If there are any
// collisions, then the variables' names will collide at compile time.
#define IPC_EVENT_TYPE0(type, val) const int e_##type = val;
#define IPC_EVENT_TYPE1(type, val) int T_##val; T_##val = 0;
#define IPC_EVENT_TYPE2(type, val) int T_##val; T_##val = 0;
#include "dbgipceventtypes.h"
#undef IPC_EVENT_TYPE2
#undef IPC_EVENT_TYPE1
#undef IPC_EVENT_TYPE0
// Ensure that all identifiers are unique and are matched with
// integer values.
#define IPC_EVENT_TYPE0(type, val) int T2_##type; T2_##type = val;
#define IPC_EVENT_TYPE1(type, val) int T2_##type; T2_##type = val;
#define IPC_EVENT_TYPE2(type, val) int T2_##type; T2_##type = val;
#include "dbgipceventtypes.h"
#undef IPC_EVENT_TYPE2
#undef IPC_EVENT_TYPE1
#undef IPC_EVENT_TYPE0
// Make sure all values are subset of the bits specified by DB_IPCE_TYPE_MASK
#define IPC_EVENT_TYPE0(type, val)
#define IPC_EVENT_TYPE1(type, val) C_ASSERT((val & e_DB_IPCE_TYPE_MASK) == val);
#define IPC_EVENT_TYPE2(type, val) C_ASSERT((val & e_DB_IPCE_TYPE_MASK) == val);
#include "dbgipceventtypes.h"
#undef IPC_EVENT_TYPE2
#undef IPC_EVENT_TYPE1
#undef IPC_EVENT_TYPE0
// Make sure that no value is DB_IPCE_INVALID_EVENT
#define IPC_EVENT_TYPE0(type, val)
#define IPC_EVENT_TYPE1(type, val) C_ASSERT(val != e_DB_IPCE_INVALID_EVENT);
#define IPC_EVENT_TYPE2(type, val) C_ASSERT(val != e_DB_IPCE_INVALID_EVENT);
#include "dbgipceventtypes.h"
#undef IPC_EVENT_TYPE2
#undef IPC_EVENT_TYPE1
#undef IPC_EVENT_TYPE0
// Make sure first-last values are well structured.
static_assert_no_msg(e_DB_IPCE_RUNTIME_FIRST < e_DB_IPCE_RUNTIME_LAST);
static_assert_no_msg(e_DB_IPCE_DEBUGGER_FIRST < e_DB_IPCE_DEBUGGER_LAST);
// Make sure that event ranges don't overlap.
// This check is simplified because L->R events come before R<-L
static_assert_no_msg(e_DB_IPCE_RUNTIME_LAST < e_DB_IPCE_DEBUGGER_FIRST);
// Make sure values are in the proper ranges
// Type1 should be in the Runtime range, Type2 in the Debugger range.
#define IPC_EVENT_TYPE0(type, val)
#define IPC_EVENT_TYPE1(type, val) C_ASSERT((e_DB_IPCE_RUNTIME_FIRST <= val) && (val < e_DB_IPCE_RUNTIME_LAST));
#define IPC_EVENT_TYPE2(type, val) C_ASSERT((e_DB_IPCE_DEBUGGER_FIRST <= val) && (val < e_DB_IPCE_DEBUGGER_LAST));
#include "dbgipceventtypes.h"
#undef IPC_EVENT_TYPE2
#undef IPC_EVENT_TYPE1
#undef IPC_EVENT_TYPE0
// Make sure that events are in increasing order
// It's ok if the events skip numbers.
// This is a more specific check than the range check above.
/* Expands to look like this:
const bool f = (
first <=
10) && (10 <
11) && (11 <
12) && (12 <
last)
static_assert_no_msg(f);
*/
const bool f1 = (
(e_DB_IPCE_RUNTIME_FIRST <=
#define IPC_EVENT_TYPE0(type, val)
#define IPC_EVENT_TYPE1(type, val) val) && (val <
#define IPC_EVENT_TYPE2(type, val)
#include "dbgipceventtypes.h"
#undef IPC_EVENT_TYPE2
#undef IPC_EVENT_TYPE1
#undef IPC_EVENT_TYPE0
e_DB_IPCE_RUNTIME_LAST)
);
static_assert_no_msg(f1);
const bool f2 = (
(e_DB_IPCE_DEBUGGER_FIRST <=
#define IPC_EVENT_TYPE0(type, val)
#define IPC_EVENT_TYPE1(type, val)
#define IPC_EVENT_TYPE2(type, val) val) && (val <
#include "dbgipceventtypes.h"
#undef IPC_EVENT_TYPE2
#undef IPC_EVENT_TYPE1
#undef IPC_EVENT_TYPE0
e_DB_IPCE_DEBUGGER_LAST)
);
static_assert_no_msg(f2);
} // end checks
#endif // _PREFAST_
//-----------------------------------------------------------------------------
// Ctor for AtSafePlaceHolder
AtSafePlaceHolder::AtSafePlaceHolder(Thread * pThread)
{
_ASSERTE(pThread != NULL);
if (!g_pDebugger->IsThreadAtSafePlace(pThread))
{
m_pThreadAtUnsafePlace = pThread;
g_pDebugger->IncThreadsAtUnsafePlaces();
}
else
{
m_pThreadAtUnsafePlace = NULL;
}
}
//-----------------------------------------------------------------------------
// Dtor for AtSafePlaceHolder
AtSafePlaceHolder::~AtSafePlaceHolder()
{
Clear();
}
//-----------------------------------------------------------------------------
// Returns true if this adjusted the unsafe counter
bool AtSafePlaceHolder::IsAtUnsafePlace()
{
return m_pThreadAtUnsafePlace != NULL;
}
//-----------------------------------------------------------------------------
// Clear the holder.
// Notes:
// This can be called multiple times.
// Calling this makes the dtor a nop.
void AtSafePlaceHolder::Clear()
{
if (m_pThreadAtUnsafePlace != NULL)
{
// The thread is still at an unsafe place.
// We're clearing the flag to avoid the Dtor() calling DecThreads again.
m_pThreadAtUnsafePlace = NULL;
g_pDebugger->DecThreadsAtUnsafePlaces();
}
}
//-----------------------------------------------------------------------------
// Is the guard page missing on this thread?
// Should only be called for managed threads handling a managed exception.
// If we're handling a stack overflow (ie, missing guard page), then another
// stack overflow will instantly terminate the process. In that case, do stack
// intensive stuff on the helper thread (which has lots of stack space). Only
// problem is that if the faulting thread has a lock, the helper thread may
// get stuck.
// Serves as a hint whether we want to do a favor on the
// faulting thread (preferred) or the helper thread (if low stack).
// See whidbey issue 127436.
//-----------------------------------------------------------------------------
bool IsGuardPageGone()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
Thread * pThread = g_pEEInterface->GetThread();
// We're not going to be called for a unmanaged exception.
// Should always have a managed thread, but just in case something really
// crazy happens, it's not worth an AV. (since this is just being used as a hint)
if (pThread == NULL)
{
return false;
}
// Don't use pThread->IsGuardPageGone(), it's not accurate here.
bool fGuardPageGone = (pThread->DetermineIfGuardPagePresent() == FALSE);
LOG((LF_CORDB, LL_INFO1000000, "D::IsGuardPageGone=%d\n", fGuardPageGone));
return fGuardPageGone;
}
//-----------------------------------------------------------------------------
// LSPTR_XYZ is a type-safe wrapper around an opaque reference type XYZ in the left-side.
// But TypeHandles are value-types that can't be directly converted into a pointer.
// Thus converting between LSPTR_XYZ and TypeHandles requires some extra glue.
// The following conversions are valid:
// LSPTR_XYZ <--> XYZ* (via Set/UnWrap methods)
// TypeHandle <--> void* (via AsPtr() and FromPtr()).
// so we can't directly convert between LSPTR_TYPEHANDLE and TypeHandle.
// We must do: TypeHandle <--> void* <--> XYZ <--> LSPTR_XYZ
// So LSPTR_TYPEHANDLE is actually for TypeHandleDummyPtr, and then we unsafe cast
// that to a void* to use w/ AsPtr() and FromPtr() to convert to TypeHandles.
// @todo- it would be nice to have these happen automatically w/ Set & UnWrap.
//-----------------------------------------------------------------------------
// helper class to do conversion above.
class TypeHandleDummyPtr
{
private:
TypeHandleDummyPtr() { }; // should never actually create this.
void * data;
};
// Convert: VMPTR_TYPEHANDLE --> TypeHandle
TypeHandle GetTypeHandle(VMPTR_TypeHandle ptr)
{
return TypeHandle::FromPtr(ptr.GetRawPtr());
}
// Convert: TypeHandle --> LSPTR_TYPEHANDLE
VMPTR_TypeHandle WrapTypeHandle(TypeHandle th)
{
return VMPTR_TypeHandle::MakePtr(reinterpret_cast<TypeHandle *> (th.AsPtr()));
}
extern void WaitForEndOfShutdown();
// Get the Canary structure which can sniff if the helper thread is safe to run.
HelperCanary * Debugger::GetCanary()
{
return g_pRCThread->GetCanary();
}
// IMPORTANT!!!!!
// Do not call Lock and Unlock directly. Because you might not unlock
// if exception takes place. Use DebuggerLockHolder instead!!!
// Only AcquireDebuggerLock can call directly.
//
void Debugger::DoNotCallDirectlyPrivateLock(void)
{
WRAPPER_NO_CONTRACT;
LOG((LF_CORDB,LL_INFO10000, "D::Lock acquire attempt by 0x%x\n",
GetCurrentThreadId()));
// Debugger lock is larger than both Controller & debugger-data locks.
// So we should never try to take the D lock if we hold either of the others.
// Lock becomes no-op in late shutdown.
if (g_fProcessDetach)
{
return;
}
//
// If the debugger has been disabled by the runtime, this means that it should block
// all threads that are trying to travel thru the debugger. We do this by blocking
// threads as they try and take the debugger lock.
//
if (m_fDisabled)
{
__SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING);
_ASSERTE (!"Can not reach here");
}
m_mutex.Enter();
//
// If we were blocked on the lock and the debugging facilities got disabled
// while we were waiting, release the lock and park this thread.
//
if (m_fDisabled)
{
m_mutex.Leave();
__SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING);
_ASSERTE (!"Can not reach here");
}
//
// Now check if we are in a shutdown case...
//
Thread * pThread;
bool fIsCooperative;
BEGIN_GETTHREAD_ALLOWED;
pThread = g_pEEInterface->GetThread();
fIsCooperative = (pThread != NULL) && (pThread->PreemptiveGCDisabled());
END_GETTHREAD_ALLOWED;
if (m_fShutdownMode && !fIsCooperative)
{
// The big fear is that some other random thread will take the debugger-lock and then block on something else,
// and thus prevent the helper/finalizer threads from taking the debugger-lock in shutdown scenarios.
//
// If we're in shutdown mode, then some locks (like the Thread-Store-Lock) get special semantics.
// Only helper / finalizer / shutdown threads can actually take these locks.
// Other threads that try to take them will just get parked and block forever.
// This is ok b/c the only threads that need to run at this point are the Finalizer and Helper threads.
//
// We need to be in preemptive to block for shutdown, so we don't do this block in Coop mode.
// Fortunately, it's safe to take this lock in coop mode because we know the thread can't block
// on anything interesting because we're in a GC-forbid region (see crst flags).
m_mutex.ReleaseAndBlockForShutdownIfNotSpecialThread();
}
#ifdef _DEBUG
_ASSERTE(m_mutexCount >= 0);
if (m_mutexCount>0)
{
if (pThread)
{
// mamaged thread
_ASSERTE(m_mutexOwner == GetThreadIdHelper(pThread));
}
else
{
// unmanaged thread
_ASSERTE(m_mutexOwner == GetCurrentThreadId());
}
}
m_mutexCount++;
if (pThread)
{
m_mutexOwner = GetThreadIdHelper(pThread);
}
else
{
// unmanaged thread
m_mutexOwner = GetCurrentThreadId();
}
if (m_mutexCount == 1)
{
LOG((LF_CORDB,LL_INFO10000, "D::Lock acquired by 0x%x\n", m_mutexOwner));
}
#endif
}
// See comment above.
// Only ReleaseDebuggerLock can call directly.
void Debugger::DoNotCallDirectlyPrivateUnlock(void)
{
WRAPPER_NO_CONTRACT;
// Controller lock is "smaller" than debugger lock.
if (!g_fProcessDetach)
{
#ifdef _DEBUG
if (m_mutexCount == 1)
LOG((LF_CORDB,LL_INFO10000, "D::Unlock released by 0x%x\n",
m_mutexOwner));
if(0 == --m_mutexCount)
m_mutexOwner = 0;
_ASSERTE( m_mutexCount >= 0);
#endif
m_mutex.Leave();
//
// If the debugger has been disabled by the runtime, this means that it should block
// all threads that are trying to travel thru the debugger. We do this by blocking
// threads also as they leave the debugger lock.
//
if (m_fDisabled)
{
__SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING);
_ASSERTE (!"Can not reach here");
}
}
}
#ifdef TEST_DATA_CONSISTENCY
// ---------------------------------------------------------------------------------
// Implementations for DataTest member functions
// ---------------------------------------------------------------------------------
// Send an event to the RS to signal that it should test to determine if a crst is held.
// This is for testing purposes only.
// Arguments:
// input: pCrst - the lock to test
// fOkToTake - true iff the LS does NOT currently hold the lock
// output: none
// Notes: The RS will throw if the lock is held. The code that tests the lock will catch the
// exception and assert if throwing was not the correct thing to do (determined via the
// boolean). See the case for DB_IPCE_TEST_CRST in code:CordbProcess::RawDispatchEvent.
//
void DataTest::SendDbgCrstEvent(Crst * pCrst, bool fOkToTake)
{
DebuggerIPCEvent * pLockEvent = g_pDebugger->m_pRCThread->GetIPCEventSendBuffer();
g_pDebugger->InitIPCEvent(pLockEvent, DB_IPCE_TEST_CRST);
pLockEvent->TestCrstData.vmCrst.SetRawPtr(pCrst);
pLockEvent->TestCrstData.fOkToTake = fOkToTake;
g_pDebugger->SendRawEvent(pLockEvent);
} // DataTest::SendDbgCrstEvent
// Send an event to the RS to signal that it should test to determine if a SimpleRWLock is held.
// This is for testing purposes only.
// Arguments:
// input: pRWLock - the lock to test
// fOkToTake - true iff the LS does NOT currently hold the lock
// output: none
// Note: The RS will throw if the lock is held. The code that tests the lock will catch the
// exception and assert if throwing was not the correct thing to do (determined via the
// boolean). See the case for DB_IPCE_TEST_RWLOCK in code:CordbProcess::RawDispatchEvent.
//
void DataTest::SendDbgRWLockEvent(SimpleRWLock * pRWLock, bool okToTake)
{
DebuggerIPCEvent * pLockEvent = g_pDebugger->m_pRCThread->GetIPCEventSendBuffer();
g_pDebugger->InitIPCEvent(pLockEvent, DB_IPCE_TEST_RWLOCK);
pLockEvent->TestRWLockData.vmRWLock.SetRawPtr(pRWLock);
pLockEvent->TestRWLockData.fOkToTake = okToTake;
g_pDebugger->SendRawEvent(pLockEvent);
} // DataTest::SendDbgRWLockEvent
// Takes a series of locks in various ways and signals the RS to test the locks at interesting
// points to ensure we reliably detect when the LS holds a lock. If in the course of inspection, the
// DAC needs to execute a code path where the LS holds a lock, we assume that the locked data is in
// an inconsistent state. In this situation, we don't want to report information about this data, so
// we throw an exception.
// This is for testing purposes only.
//
// Arguments: none
// Return Value: none
// Notes: See code:CordbProcess::RawDispatchEvent for the RS part of this test and code:Debugger::Startup
// for the LS invocation of the test.
// The environment variable TestDataConsistency must be set to 1 to make this test run.
void DataTest::TestDataSafety()
{
const bool okToTake = true;
SendDbgCrstEvent(&m_crst1, okToTake);
{
CrstHolder ch1(&m_crst1);
SendDbgCrstEvent(&m_crst1, !okToTake);
{
CrstHolder ch2(&m_crst2);
SendDbgCrstEvent(&m_crst2, !okToTake);
SendDbgCrstEvent(&m_crst1, !okToTake);
}
SendDbgCrstEvent(&m_crst2, okToTake);
SendDbgCrstEvent(&m_crst1, !okToTake);
}
SendDbgCrstEvent(&m_crst1, okToTake);
{
SendDbgRWLockEvent(&m_rwLock, okToTake);
SimpleReadLockHolder readLock(&m_rwLock);
SendDbgRWLockEvent(&m_rwLock, okToTake);
}
SendDbgRWLockEvent(&m_rwLock, okToTake);
{
SimpleWriteLockHolder readLock(&m_rwLock);
SendDbgRWLockEvent(&m_rwLock, !okToTake);
}
} // DataTest::TestDataSafety
#endif // TEST_DATA_CONSISTENCY
#if _DEBUG
static DebugEventCounter g_debugEventCounter;
static int g_iDbgRuntimeCounter[DBG_RUNTIME_MAX];
static int g_iDbgDebuggerCounter[DBG_DEBUGGER_MAX];
void DoAssertOnType(DebuggerIPCEventType event, int count)
{
WRAPPER_NO_CONTRACT;
// check to see if we need fire the assertion or not.
if ((event & 0x0300) == 0x0100)
{
// use the Runtime array
if (g_iDbgRuntimeCounter[event & 0x00ff] == count)
{
char tmpStr[256];
_snprintf_s(tmpStr, _countof(tmpStr), _TRUNCATE, "%s == %d, break now!",
IPCENames::GetName(event), count);
// fire the assertion
DbgAssertDialog(__FILE__, __LINE__, tmpStr);
}
}
// check to see if we need fire the assertion or not.
else if ((event & 0x0300) == 0x0200)
{
// use the Runtime array
if (g_iDbgDebuggerCounter[event & 0x00ff] == count)
{
char tmpStr[256];
_snprintf_s(tmpStr, _countof(tmpStr), _TRUNCATE, "%s == %d, break now!",
IPCENames::GetName(event), count);
// fire the assertion
DbgAssertDialog(__FILE__, __LINE__, tmpStr);
}
}
}
void DbgLogHelper(DebuggerIPCEventType event)
{
WRAPPER_NO_CONTRACT;
switch (event)
{
// we don't need to handle event type 0
#define IPC_EVENT_TYPE0(type, val)
#define IPC_EVENT_TYPE1(type, val) case type: {\
g_debugEventCounter.m_iDebugCount_##type++; \
DoAssertOnType(type, g_debugEventCounter.m_iDebugCount_##type); \
break; \
}
#define IPC_EVENT_TYPE2(type, val) case type: { \
g_debugEventCounter.m_iDebugCount_##type++; \
DoAssertOnType(type, g_debugEventCounter.m_iDebugCount_##type); \
break; \
}
#include "dbgipceventtypes.h"
#undef IPC_EVENT_TYPE2
#undef IPC_EVENT_TYPE1
#undef IPC_EVENT_TYPE0
default:
break;
}
}
#endif // _DEBUG
/* ------------------------------------------------------------------------ *
* DLL export routine
* ------------------------------------------------------------------------ */
Debugger *CreateDebugger(void)
{
Debugger *pDebugger = NULL;
EX_TRY
{
pDebugger = new (nothrow) Debugger();
}
EX_CATCH
{
if (pDebugger != NULL)
{
delete pDebugger;
pDebugger = NULL;
}
}
EX_END_CATCH(RethrowTerminalExceptions);
return pDebugger;
}
//
// CorDBGetInterface is exported to the Runtime so that it can call
// the Runtime Controller.
//
extern "C"{
HRESULT __cdecl CorDBGetInterface(DebugInterface** rcInterface)
{
CONTRACT(HRESULT)
{
NOTHROW; // use HRESULTS instead
GC_NOTRIGGER;
POSTCONDITION(FAILED(RETVAL) || (rcInterface == NULL) || (*rcInterface != NULL));
}
CONTRACT_END;
HRESULT hr = S_OK;
if (rcInterface != NULL)
{
if (g_pDebugger == NULL)
{
LOG((LF_CORDB, LL_INFO10,
"CorDBGetInterface: initializing debugger.\n"));
g_pDebugger = CreateDebugger();
TRACE_ALLOC(g_pDebugger);
if (g_pDebugger == NULL)
hr = E_OUTOFMEMORY;
}
*rcInterface = g_pDebugger;
}
RETURN hr;
}
}
//-----------------------------------------------------------------------------
// Send a pre-init IPC event and block.
// We assume the IPC event has already been initialized. There's nothing special
// here; it just used the standard formula for sending an IPC event to the RS.
// This should match up w/ the description in SENDIPCEVENT_BEGIN.
//-----------------------------------------------------------------------------
void Debugger::SendSimpleIPCEventAndBlock()
{
CONTRACTL
{
SO_NOT_MAINLINE;
MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
}
CONTRACTL_END;
// BEGIN will acquire the lock (END will release it). While blocking, the
// debugger may have detached though, so we need to check for that.
_ASSERTE(ThreadHoldsLock());
if (CORDebuggerAttached())
{
m_pRCThread->SendIPCEvent();
// Stop all Runtime threads
this->TrapAllRuntimeThreads();
}
}
//-----------------------------------------------------------------------------
// Get context from a thread in managed code.
// See header for exact semantics.
//-----------------------------------------------------------------------------
CONTEXT * GetManagedStoppedCtx(Thread * pThread)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(pThread != NULL);
// We may be stopped or live.
// If we're stopped at an interop-hijack, we'll have a filter context,
// but we'd better not be redirected for a managed-suspension hijack.
if (pThread->GetInteropDebuggingHijacked())
{
_ASSERTE(!ISREDIRECTEDTHREAD(pThread));
return NULL;
}
// Check if we have a filter ctx. This should only be for managed-code.
// We're stopped at some exception (likely an int3 or single-step).
// Can't have both filter ctx + redirected ctx.
CONTEXT *pCtx = g_pEEInterface->GetThreadFilterContext(pThread);
if (pCtx != NULL)
{
_ASSERTE(!ISREDIRECTEDTHREAD(pThread));
return pCtx;
}
if (ISREDIRECTEDTHREAD(pThread))
{
pCtx = GETREDIRECTEDCONTEXT(pThread);
_ASSERTE(pCtx != NULL);
return pCtx;
}
// Not stopped somewhere in managed code.
return NULL;
}
//-----------------------------------------------------------------------------
// See header for exact semantics.
// Never NULL. (Caller guarantees this is active.)
//-----------------------------------------------------------------------------
CONTEXT * GetManagedLiveCtx(Thread * pThread)
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(pThread != NULL);
// We should never be on the helper thread, we should only be inspecting our own thread.
// We're in some Controller's Filter after hitting an exception.
// We're not stopped.
//_ASSERTE(!g_pDebugger->IsStopped()); <-- @todo - this fires, need to find out why.
_ASSERTE(GetThread() == pThread);
CONTEXT *pCtx = g_pEEInterface->GetThreadFilterContext(pThread);
// Note that we may be in a M2U hijack. So we can't assert !pThread->GetInteropDebuggingHijacked()
_ASSERTE(!ISREDIRECTEDTHREAD(pThread));
_ASSERTE(pCtx);
return pCtx;
}
// Attempt to validate a GC handle.
HRESULT ValidateGCHandle(OBJECTHANDLE oh)
{
// The only real way to do this is to Enumerate all GC handles in the handle table.
// That's too expensive. So we'll use a similar workaround that we use in ValidateObject.
// This will err on the side off returning True for invalid handles.
CONTRACTL
{
SO_NOT_MAINLINE;
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
HRESULT hr = S_OK;
EX_TRY
{
// Use AVInRuntimeImplOkHolder.
AVInRuntimeImplOkayHolder AVOkay;
// This may throw if the Object Handle is invalid.
Object * objPtr = *((Object**) oh);
// NULL is certinally valid...
if (objPtr != NULL)
{
if (!objPtr->ValidateObjectWithPossibleAV())
{
LOG((LF_CORDB, LL_INFO10000, "GAV: object methodtable-class invariant doesn't hold.\n"));
hr = E_INVALIDARG;
goto LExit;
}
}
LExit: ;
}
EX_CATCH
{
LOG((LF_CORDB, LL_INFO10000, "GAV: exception indicated ref is bad.\n"));
hr = E_INVALIDARG;
}
EX_END_CATCH(SwallowAllExceptions);
return hr;
}
// Validate an object. Returns E_INVALIDARG or S_OK.
HRESULT ValidateObject(Object *objPtr)
{
CONTRACTL
{
SO_NOT_MAINLINE;
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
HRESULT hr = S_OK;
EX_TRY
{
// Use AVInRuntimeImplOkHolder.
AVInRuntimeImplOkayHolder AVOkay;
// NULL is certinally valid...
if (objPtr != NULL)
{
if (!objPtr->ValidateObjectWithPossibleAV())
{
LOG((LF_CORDB, LL_INFO10000, "GAV: object methodtable-class invariant doesn't hold.\n"));
hr = E_INVALIDARG;
goto LExit;
}
}
LExit: ;
}
EX_CATCH
{
LOG((LF_CORDB, LL_INFO10000, "GAV: exception indicated ref is bad.\n"));
hr = E_INVALIDARG;
}
EX_END_CATCH(SwallowAllExceptions);
return hr;
} // ValidateObject
#ifdef FEATURE_DBGIPC_TRANSPORT_VM
void
ShutdownTransport()
{
if (g_pDbgTransport != NULL)
{
g_pDbgTransport->Shutdown();
g_pDbgTransport = NULL;
}
}
void
AbortTransport()
{
if (g_pDbgTransport != NULL)
{
g_pDbgTransport->AbortConnection();
}
}
#endif // FEATURE_DBGIPC_TRANSPORT_VM
/* ------------------------------------------------------------------------ *
* Debugger routines
* ------------------------------------------------------------------------ */
//
// a Debugger object represents the global state of the debugger program.
//
//
// Constructor & Destructor
//
/******************************************************************************
*
******************************************************************************/
Debugger::Debugger()
:
m_fLeftSideInitialized(FALSE),
#ifdef _DEBUG
m_mutexCount(0),
#endif //_DEBUG
m_pRCThread(NULL),
m_trappingRuntimeThreads(FALSE),
m_stopped(FALSE),
m_unrecoverableError(FALSE),
m_ignoreThreadDetach(FALSE),
m_pMethodInfos(NULL),
m_mutex(CrstDebuggerMutex, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD)),
#ifdef _DEBUG
m_mutexOwner(0),
m_tidLockedForEventSending(0),
#endif //_DEBUG
m_threadsAtUnsafePlaces(0),
m_jitAttachInProgress(FALSE),
m_launchingDebugger(FALSE),
m_LoggingEnabled(TRUE),
m_pAppDomainCB(NULL),
m_dClassLoadCallbackCount(0),
m_pModules(NULL),
m_RSRequestedSync(FALSE),
m_sendExceptionsOutsideOfJMC(TRUE),
m_pIDbgThreadControl(NULL),
m_forceNonInterceptable(FALSE),
m_pLazyData(NULL),
m_defines(_defines)
{
CONTRACTL
{
SO_INTOLERANT;
WRAPPER(THROWS);
WRAPPER(GC_TRIGGERS);
CONSTRUCTOR_CHECK;
}
CONTRACTL_END;
m_fShutdownMode = false;
m_fDisabled = false;
m_rgHijackFunction = NULL;
#ifdef _DEBUG
InitDebugEventCounting();
#endif
m_processId = GetCurrentProcessId();
// Initialize these in ctor because we free them in dtor.
// And we can't set them to some safe uninited value (like NULL).
//------------------------------------------------------------------------------
// Metadata data structure version numbers
//
// 1 - initial state of the layouts ( .Net 4.5.2 )
//
// as data structure layouts change, add a new version number
// and comment the changes
m_mdDataStructureVersion = 1;
}
/******************************************************************************
*
******************************************************************************/
Debugger::~Debugger()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
DESTRUCTOR_CHECK;
SO_INTOLERANT;
}
CONTRACTL_END;
// We explicitly leak the debugger object on shutdown. See Debugger::StopDebugger for details.
_ASSERTE(!"Debugger dtor should not be called.");
}
#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX)
typedef void (*PFN_HIJACK_FUNCTION) (void);
// Given the start address and the end address of a function, return a MemoryRange for the function.
inline MemoryRange GetMemoryRangeForFunction(PFN_HIJACK_FUNCTION pfnStart, PFN_HIJACK_FUNCTION pfnEnd)
{
PCODE pfnStartAddress = (PCODE)GetEEFuncEntryPoint(pfnStart);
PCODE pfnEndAddress = (PCODE)GetEEFuncEntryPoint(pfnEnd);
return MemoryRange(dac_cast<PTR_VOID>(pfnStartAddress), (pfnEndAddress - pfnStartAddress));
}
// static
MemoryRange Debugger::s_hijackFunction[kMaxHijackFunctions] =
{GetMemoryRangeForFunction(ExceptionHijack, ExceptionHijackEnd),
GetMemoryRangeForFunction(RedirectedHandledJITCaseForGCThreadControl_Stub,
RedirectedHandledJITCaseForGCThreadControl_StubEnd),
GetMemoryRangeForFunction(RedirectedHandledJITCaseForDbgThreadControl_Stub,
RedirectedHandledJITCaseForDbgThreadControl_StubEnd),
GetMemoryRangeForFunction(RedirectedHandledJITCaseForUserSuspend_Stub,
RedirectedHandledJITCaseForUserSuspend_StubEnd)
#if defined(HAVE_GCCOVER) && defined(_TARGET_AMD64_)
,
GetMemoryRangeForFunction(RedirectedHandledJITCaseForGCStress_Stub,
RedirectedHandledJITCaseForGCStress_StubEnd)
#endif // HAVE_GCCOVER && _TARGET_AMD64_
};
#endif // FEATURE_HIJACK && !PLATFORM_UNIX
// Save the necessary information for the debugger to recognize an IP in one of the thread redirection
// functions.
void Debugger::InitializeHijackFunctionAddress()
{
#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX)
// Advertise hijack address for the DD Hijack primitive
m_rgHijackFunction = Debugger::s_hijackFunction;
#endif // FEATURE_HIJACK && !PLATFORM_UNIX
}
// For debug-only builds, we'll have a debugging feature to count
// the number of ipc events and break on a specific number.
// Initialize the stuff to do that.
void Debugger::InitDebugEventCounting()
{
CONTRACTL
{
SO_INTOLERANT;
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
#ifdef _DEBUG
// initialize the debug event counter structure to zero
memset(&g_debugEventCounter, 0, sizeof(DebugEventCounter));
memset(&g_iDbgRuntimeCounter, 0, DBG_RUNTIME_MAX*sizeof(int));
memset(&g_iDbgDebuggerCounter, 0, DBG_DEBUGGER_MAX*sizeof(int));
// retrieve the possible counter for break point
LPWSTR wstrValue = NULL;
// The string value is of the following format
// <Event Name>=Count;<Event Name>=Count;....;
// The string must end with ;
if ((wstrValue = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DebuggerBreakPoint)) != NULL)
{
LPSTR strValue;
int cbReq;
cbReq = WszWideCharToMultiByte(CP_UTF8, 0, wstrValue,-1, 0,0, 0,0);
strValue = new (nothrow) char[cbReq+1];
// This is a debug only thingy, if it fails, not worth taking
// down the process.
if (strValue == NULL)
return;
// now translate the unicode to ansi string
WszWideCharToMultiByte(CP_UTF8, 0, wstrValue, -1, strValue, cbReq+1, 0,0);
char *szEnd = (char *)strchr(strValue, ';');
char *szStart = strValue;
while (szEnd != NULL)
{
// Found a key value
char *szNameEnd = strchr(szStart, '=');
int iCount;
DebuggerIPCEventType eventType;
if (szNameEnd != NULL)
{
// This is a well form key
*szNameEnd = '\0';
*szEnd = '\0';
// now szStart is the key name null terminated. Translate the counter into integer.
iCount = atoi(szNameEnd+1);
if (iCount != 0)
{
eventType = IPCENames::GetEventType(szStart);
if (eventType < DB_IPCE_DEBUGGER_FIRST)
{
// use the runtime one
g_iDbgRuntimeCounter[eventType & 0x00ff] = iCount;
}
else if (eventType < DB_IPCE_DEBUGGER_LAST)
{
// use the debugger one
g_iDbgDebuggerCounter[eventType & 0x00ff] = iCount;
}
else
_ASSERTE(!"Unknown Event Type");
}
}
szStart = szEnd + 1;
// try to find next key value
szEnd = (char *)strchr(szStart, ';');
}
// free the ansi buffer
delete [] strValue;
REGUTIL::FreeConfigString(wstrValue);
}
#endif // _DEBUG
}
// This is a notification from the EE it's about to go to fiber mode.
// This is given *before* it actually goes to fiber mode.
HRESULT Debugger::SetFiberMode(bool isFiberMode)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
// Notifications from EE never come on helper worker.
PRECONDITION(!ThisIsHelperThreadWorker());
}
CONTRACTL_END;
Thread * pThread = ::GetThread();
m_pRCThread->m_pDCB->m_bHostingInFiber = isFiberMode;
// If there is a debugger already attached, then we have a big problem. As of V2.0, the debugger
// does not support debugging processes with fibers in them. We set the unrecoverable state to
// indicate that we're in a bad state now. The debugger will notice this, and take appropiate action.
if (isFiberMode && CORDebuggerAttached())
{
LOG((LF_CORDB, LL_INFO10, "Thread has entered fiber mode while debugger attached.\n"));
EX_TRY
{
// We send up a MDA for two reasons: 1) we want to give the user some chance to see what went wrong,
// and 2) we want to get the Right Side to notice that we're in an unrecoverable error state now.
SString szName(W("DebuggerFiberModeNotSupported"));
SString szDescription;
szDescription.LoadResource(CCompRC::Debugging, MDARC_DEBUGGER_FIBER_MODE_NOT_SUPPORTED);
SString szXML(W(""));
// Sending any debug event will be a GC violation.
// However, if we're enabling fiber-mode while a debugger is attached, we're already doomed.
// Deadlocks and AVs are just around the corner. A Gc-violation is the least of our worries.
// We want to at least notify the debugger at all costs.
CONTRACT_VIOLATION(GCViolation);
// As soon as we set unrecoverable error in the LS, the RS will pick it up and basically shut down.
// It won't dispatch any events. So we fire the MDA first, and then set unrecoverable error.
SendMDANotification(pThread, &szName, &szDescription, &szXML, (CorDebugMDAFlags) 0, FALSE);
CORDBDebuggerSetUnrecoverableError(this, CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS, false);
// Fire the MDA again just to force the RS to sniff the LS and pick up that we're in an unrecoverable error.
// No harm done from dispatching an MDA twice. And
SendMDANotification(pThread, &szName, &szDescription, &szXML, (CorDebugMDAFlags) 0, FALSE);
}
EX_CATCH
{
LOG((LF_CORDB, LL_INFO10, "Error sending MDA regarding fiber mode.\n"));
}
EX_END_CATCH(SwallowAllExceptions);
}
return S_OK;
}
// Checks if the MethodInfos table has been allocated, and if not does so.
// Throw on failure, so we always return
HRESULT Debugger::CheckInitMethodInfoTable()
{
CONTRACTL
{
SO_INTOLERANT;
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
if (m_pMethodInfos == NULL)
{
DebuggerMethodInfoTable *pMethodInfos = NULL;
EX_TRY
{
pMethodInfos = new (interopsafe) DebuggerMethodInfoTable();
}
EX_CATCH
{
pMethodInfos = NULL;
}
EX_END_CATCH(RethrowTerminalExceptions);
if (pMethodInfos == NULL)
{
return E_OUTOFMEMORY;
}
if (InterlockedCompareExchangeT(&m_pMethodInfos, pMethodInfos, NULL) != NULL)
{
DeleteInteropSafe(pMethodInfos);
}
}
return S_OK;
}
// Checks if the m_pModules table has been allocated, and if not does so.
HRESULT Debugger::CheckInitModuleTable()
{
CONTRACT(HRESULT)
{
NOTHROW;
GC_NOTRIGGER;
POSTCONDITION(m_pModules != NULL);
}
CONTRACT_END;
if (m_pModules == NULL)
{
DebuggerModuleTable *pModules = new (interopsafe, nothrow) DebuggerModuleTable();
if (pModules == NULL)
{
RETURN (E_OUTOFMEMORY);
}
if (InterlockedCompareExchangeT(&m_pModules, pModules, NULL) != NULL)
{
DeleteInteropSafe(pModules);
}
}
RETURN (S_OK);
}
// Checks if the m_pModules table has been allocated, and if not does so.
HRESULT Debugger::CheckInitPendingFuncEvalTable()
{
CONTRACT(HRESULT)
{
NOTHROW;
GC_NOTRIGGER;
POSTCONDITION(GetPendingEvals() != NULL);
}
CONTRACT_END;
#ifndef DACCESS_COMPILE
if (GetPendingEvals() == NULL)
{
DebuggerPendingFuncEvalTable *pPendingEvals = new (interopsafe, nothrow) DebuggerPendingFuncEvalTable();
if (pPendingEvals == NULL)
{
RETURN(E_OUTOFMEMORY);
}
// Since we're setting, we need an LValue and not just an accessor.
if (InterlockedCompareExchangeT(&(GetLazyData()->m_pPendingEvals), pPendingEvals, NULL) != NULL)
{
DeleteInteropSafe(pPendingEvals);
}
}
#endif
RETURN (S_OK);
}
#ifdef _DEBUG_DMI_TABLE
// Returns the number of (official) entries in the table
ULONG DebuggerMethodInfoTable::CheckDmiTable(void)
{
LIMITED_METHOD_CONTRACT;
ULONG cApparent = 0;
ULONG cOfficial = 0;
if (NULL != m_pcEntries)
{
DebuggerMethodInfoEntry *dcp;
int i = 0;
while (i++ <m_iEntries)
{
dcp = (DebuggerMethodInfoEntry*)&(((DebuggerMethodInfoEntry *)m_pcEntries)[i]);
if(dcp->pFD != 0 &&
dcp->pFD != (MethodDesc*)0xcdcdcdcd &&
dcp->mi != NULL)
{
cApparent++;
_ASSERTE( dcp->pFD == dcp->mi->m_fd );
LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Entry:0x%p mi:0x%p\nPrevs:\n",
dcp, dcp->mi));
DebuggerMethodInfo *dmi = dcp->mi->m_prevMethodInfo;
while(dmi != NULL)
{
LOG((LF_CORDB, LL_INFO1000, "\t0x%p\n", dmi));
dmi = dmi->m_prevMethodInfo;
}
dmi = dcp->mi->m_nextMethodInfo;
LOG((LF_CORDB, LL_INFO1000, "Nexts:\n", dmi));
while(dmi != NULL)
{
LOG((LF_CORDB, LL_INFO1000, "\t0x%p\n", dmi));
dmi = dmi->m_nextMethodInfo;
}
LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:DONE\n",
dcp, dcp->mi));
}
}
if (m_piBuckets == 0)
{
LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT: The table is officially empty!\n"));
return cOfficial;
}
LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Looking for official entries:\n"));
ULONG iNext = m_piBuckets[0];
ULONG iBucket = 1;
HASHENTRY *psEntry = NULL;
while (TRUE)
{
while (iNext != UINT32_MAX)
{
cOfficial++;
psEntry = EntryPtr(iNext);
dcp = ((DebuggerMethodInfoEntry *)psEntry);
LOG((LF_CORDB, LL_INFO1000, "\tEntry:0x%p mi:0x%p @idx:0x%x @bucket:0x%x\n",
dcp, dcp->mi, iNext, iBucket));
iNext = psEntry->iNext;
}
// Advance to the next bucket.
if (iBucket < m_iBuckets)
iNext = m_piBuckets[iBucket++];
else
break;
}
LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Finished official entries: ****************"));
}
return cOfficial;
}
#endif // _DEBUG_DMI_TABLE
//---------------------------------------------------------------------------------------
//
// Class constructor for DebuggerEval. This is the supporting data structure for
// func-eval tracking.
//
// Arguments:
// pContext - The context to return to when done with this eval.
// pEvalInfo - Contains all the important information, such as parameters, type args, method.
// fInException - TRUE if the thread for the eval is currently in an exception notification.
//
DebuggerEval::DebuggerEval(CONTEXT * pContext, DebuggerIPCE_FuncEvalInfo * pEvalInfo, bool fInException)
{
WRAPPER_NO_CONTRACT;
// Allocate the breakpoint instruction info in executable memory.
m_bpInfoSegment = new (interopsafeEXEC, nothrow) DebuggerEvalBreakpointInfoSegment(this);
// This must be non-zero so that the saved opcode is non-zero, and on IA64 we want it to be 0x16
// so that we can have a breakpoint instruction in any slot in the bundle.
m_bpInfoSegment->m_breakpointInstruction[0] = 0x16;
#if defined(_TARGET_ARM_)
USHORT *bp = (USHORT*)&m_bpInfoSegment->m_breakpointInstruction;
*bp = CORDbg_BREAK_INSTRUCTION;
#endif // _TARGET_ARM_
m_thread = pEvalInfo->vmThreadToken.GetRawPtr();
m_evalType = pEvalInfo->funcEvalType;
m_methodToken = pEvalInfo->funcMetadataToken;
m_classToken = pEvalInfo->funcClassMetadataToken;
// Note: we can't rely on just the DebuggerModule* or AppDomain* because the AppDomain
// could get unloaded between now and when the funceval actually starts. So we stash an
// AppDomain ID which is safe to use after the AD is unloaded. It's only safe to
// use the DebuggerModule* after we've verified the ADID is still valid (i.e. by entering that domain).
m_debuggerModule = g_pDebugger->LookupOrCreateModule(pEvalInfo->vmDomainFile);
if (m_debuggerModule == NULL)
{
// We have no associated code.
_ASSERTE((m_evalType == DB_IPCE_FET_NEW_STRING) || (m_evalType == DB_IPCE_FET_NEW_ARRAY));
// We'll just do the creation in whatever domain the thread is already in.
// It's conceivable that we might want to allow the caller to specify a specific domain, but
// ICorDebug provides the debugger with no was to specify the domain.
m_appDomainId = m_thread->GetDomain()->GetId();
}
else
{
m_appDomainId = m_debuggerModule->GetAppDomain()->GetId();
}
m_funcEvalKey = pEvalInfo->funcEvalKey;
m_argCount = pEvalInfo->argCount;
m_targetCodeAddr = NULL;
m_stringSize = pEvalInfo->stringSize;
m_arrayRank = pEvalInfo->arrayRank;
m_genericArgsCount = pEvalInfo->genericArgsCount;
m_genericArgsNodeCount = pEvalInfo->genericArgsNodeCount;
m_successful = false;
m_argData = NULL;
memset(m_result, 0, sizeof(m_result));
m_md = NULL;
m_resultType = TypeHandle();
m_aborting = FE_ABORT_NONE;
m_aborted = false;
m_completed = false;
m_evalDuringException = fInException;
m_rethrowAbortException = false;
m_retValueBoxing = Debugger::NoValueTypeBoxing;
m_requester = (Thread::ThreadAbortRequester)0;
m_vmObjectHandle = VMPTR_OBJECTHANDLE::NullPtr();
// Copy the thread's context.
if (pContext == NULL)
{
memset(&m_context, 0, sizeof(m_context));
}
else
{
memcpy(&m_context, pContext, sizeof(m_context));
}
}
//---------------------------------------------------------------------------------------
//
// This constructor is only used when setting up an eval to re-abort a thread.
//
// Arguments:
// pContext - The context to return to when done with this eval.
// pThread - The thread to re-abort.
// requester - The type of abort to throw.
//
DebuggerEval::DebuggerEval(CONTEXT * pContext, Thread * pThread, Thread::ThreadAbortRequester requester)
{
WRAPPER_NO_CONTRACT;
// Allocate the breakpoint instruction info in executable memory.
m_bpInfoSegment = new (interopsafeEXEC, nothrow) DebuggerEvalBreakpointInfoSegment(this);
// This must be non-zero so that the saved opcode is non-zero, and on IA64 we want it to be 0x16
// so that we can have a breakpoint instruction in any slot in the bundle.
m_bpInfoSegment->m_breakpointInstruction[0] = 0x16;
m_thread = pThread;
m_evalType = DB_IPCE_FET_RE_ABORT;
m_methodToken = mdMethodDefNil;
m_classToken = mdTypeDefNil;
m_debuggerModule = NULL;
m_funcEvalKey = RSPTR_CORDBEVAL::NullPtr();
m_argCount = 0;
m_stringSize = 0;
m_arrayRank = 0;
m_genericArgsCount = 0;
m_genericArgsNodeCount = 0;
m_successful = false;
m_argData = NULL;
m_targetCodeAddr = NULL;
memset(m_result, 0, sizeof(m_result));
m_md = NULL;
m_resultType = TypeHandle();
m_aborting = FE_ABORT_NONE;
m_aborted = false;
m_completed = false;
m_evalDuringException = false;
m_rethrowAbortException = false;
m_retValueBoxing = Debugger::NoValueTypeBoxing;
m_requester = requester;
if (pContext == NULL)
{
memset(&m_context, 0, sizeof(m_context));
}
else
{
memcpy(&m_context, pContext, sizeof(m_context));
}
}
#ifdef _DEBUG
// Thread proc for interop stress coverage. Have an unmanaged thread
// that just loops throwing native exceptions. This can test corner cases
// such as getting an native exception while the runtime is synced.
DWORD WINAPI DbgInteropStressProc(void * lpParameter)
{
LIMITED_METHOD_CONTRACT;
int i = 0;
int zero = 0;
// This will ensure that the compiler doesn't flag our 1/0 exception below at compile-time.
if (lpParameter != NULL)
{
zero = 1;
}
// Note that this thread is a non-runtime thread. So it can't take any CLR locks
// or do anything else that may block the helper thread.
// (Log statements take CLR locks).
while(true)
{
i++;
if ((i % 10) != 0)
{
// Generate an in-band event.
PAL_CPP_TRY
{
// Throw a handled exception. Don't use an AV since that's pretty special.
*(int*)lpParameter = 1 / zero;
}
PAL_CPP_CATCH_ALL
{
}
PAL_CPP_ENDTRY
}
else
{
// Generate the occasional oob-event.
WszOutputDebugString(W("Ping from DbgInteropStressProc"));
}
// This helps parallelize if we have a lot of threads, and keeps us from
// chewing too much CPU time.
ClrSleepEx(2000,FALSE);
ClrSleepEx(GetRandomInt(1000), FALSE);
}
return 0;
}
// ThreadProc that does everything in a can't stop region.
DWORD WINAPI DbgInteropCantStopStressProc(void * lpParameter)
{
WRAPPER_NO_CONTRACT;
// This will mark us as a can't stop region.
ClrFlsSetThreadType (ThreadType_DbgHelper);
return DbgInteropStressProc(lpParameter);
}
// Generate lots of OOB events.
DWORD WINAPI DbgInteropDummyStressProc(void * lpParameter)
{
LIMITED_METHOD_CONTRACT;
ClrSleepEx(1,FALSE);
return 0;
}
DWORD WINAPI DbgInteropOOBStressProc(void * lpParameter)
{
WRAPPER_NO_CONTRACT;
int i = 0;
while(true)
{
i++;
if (i % 10 == 1)
{
// Create a dummy thread. That generates 2 oob events
// (1 for create, 1 for destroy)
DWORD id;
::CreateThread(NULL, 0, DbgInteropDummyStressProc, NULL, 0, &id);
}
else
{
// Generate the occasional oob-event.
WszOutputDebugString(W("OOB ping from "));
}
ClrSleepEx(3000, FALSE);
}
return 0;
}
// List of the different possible stress procs.
LPTHREAD_START_ROUTINE g_pStressProcs[] =
{
DbgInteropOOBStressProc,
DbgInteropCantStopStressProc,
DbgInteropStressProc
};
#endif
DebuggerHeap * Debugger::GetInteropSafeHeap()
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_NOTRIGGER;
}
CONTRACTL_END;
// Lazily initialize our heap.
if (!m_heap.IsInit())
{
_ASSERTE(!"InteropSafe Heap should have already been initialized in LazyInit");
// Just in case we miss it in retail, convert to OOM here:
ThrowOutOfMemory();
}
return &m_heap;
}
DebuggerHeap * Debugger::GetInteropSafeHeap_NoThrow()
{
CONTRACTL
{
SO_INTOLERANT;
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
// Lazily initialize our heap.
if (!m_heap.IsInit())
{
_ASSERTE(!"InteropSafe Heap should have already been initialized in LazyInit");
// Just in case we miss it in retail, convert to OOM here:
return NULL;
}
return &m_heap;
}
DebuggerHeap * Debugger::GetInteropSafeExecutableHeap()
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_NOTRIGGER;
}
CONTRACTL_END;
// Lazily initialize our heap.
if (!m_executableHeap.IsInit())
{
_ASSERTE(!"InteropSafe Executable Heap should have already been initialized in LazyInit");
// Just in case we miss it in retail, convert to OOM here:
ThrowOutOfMemory();
}
return &m_executableHeap;
}
DebuggerHeap * Debugger::GetInteropSafeExecutableHeap_NoThrow()
{
CONTRACTL
{
SO_INTOLERANT;
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
// Lazily initialize our heap.
if (!m_executableHeap.IsInit())
{
_ASSERTE(!"InteropSafe Executable Heap should have already been initialized in LazyInit");
// Just in case we miss it in retail, convert to OOM here:
return NULL;
}
return &m_executableHeap;
}
//---------------------------------------------------------------------------------------
//
// Notify potential debugger that the runtime has started up
//
//
// Assumptions:
// Called during startup path
//
// Notes:
// If no debugger is attached, this does nothing.
//
//---------------------------------------------------------------------------------------
void Debugger::RaiseStartupNotification()
{
// Right-side will read this field from OOP via DAC-primitive to determine attach or launch case.
// We do an interlocked increment to gaurantee this is an atomic memory write, and to ensure
// that it's flushed from any CPU cache into memory.
InterlockedIncrement(&m_fLeftSideInitialized);
#ifndef FEATURE_DBGIPC_TRANSPORT_VM
// If we are remote debugging, don't send the event now if a debugger is not attached. No one will be
// listening, and we will fail. However, we still want to initialize the variable above.
DebuggerIPCEvent startupEvent;
InitIPCEvent(&startupEvent, DB_IPCE_LEFTSIDE_STARTUP, NULL, VMPTR_AppDomain::NullPtr());
SendRawEvent(&startupEvent);
// RS will set flags from OOP while we're stopped at the event if it wants to attach.
#endif // FEATURE_DBGIPC_TRANSPORT_VM
}
//---------------------------------------------------------------------------------------
//
// Sends a raw managed debug event to the debugger.
//
// Arguments:
// pManagedEvent - managed debug event
//
//
// Notes:
// This can be called even if a debugger is not attached.
// The entire process will get frozen by the debugger once we send. The debugger
// needs to resume the process. It may detach as well.
// See code:IsEventDebuggerNotification for decoding this event. These methods must stay in sync.
// The debugger process reads the events via code:CordbProcess.CopyManagedEventFromTarget.
//
//---------------------------------------------------------------------------------------
void Debugger::SendRawEvent(const DebuggerIPCEvent * pManagedEvent)
{
#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
HRESULT hr = g_pDbgTransport->SendDebugEvent(const_cast<DebuggerIPCEvent *>(pManagedEvent));
if (FAILED(hr))
{
_ASSERTE(!"Failed to send debugger event");
STRESS_LOG1(LF_CORDB, LL_INFO1000, "D::SendIPCEvent Error on Send with 0x%x\n", hr);
UnrecoverableError(hr,
0,
FILE_DEBUG,
LINE_DEBUG,
false);
// @dbgtodo Mac - what can we do here?
}
#else
// We get to send an array of ULONG_PTRs as data with the notification.
// The debugger can then use ReadProcessMemory to read through this array.
ULONG_PTR rgData [] = {
CLRDBG_EXCEPTION_DATA_CHECKSUM,
(ULONG_PTR) g_pMSCorEE,
(ULONG_PTR) pManagedEvent
};
// If no debugger attached, then don't bother raising a 1st-chance exception because nobody will sniff it.
// @dbgtodo iDNA: in iDNA case, the recorder may sniff it.
if (!IsDebuggerPresent())
{
return;
}
//
// Physically send the event via an OS Exception. We're using exceptions as a notification
// mechanism on top of the OS native debugging pipeline.
// @dbgtodo cross-plat - this needs to be cross-plat.
//
EX_TRY
{
const DWORD dwFlags = 0; // continuable (eg, Debugger can continue GH)
RaiseException(CLRDBG_NOTIFICATION_EXCEPTION_CODE, dwFlags, NumItems(rgData), rgData);
// If debugger continues "GH" (DBG_CONTINUE), then we land here.
// This is the expected path for a well-behaved ICorDebug debugger.
}
EX_CATCH
{
// If no debugger is attached, or if the debugger continues "GN" (DBG_EXCEPTION_NOT_HANDLED), then we land here.
// A naive (not-ICorDebug aware) native-debugger won't handle the exception and so land us here.
// We may also get here if a debugger detaches at the Exception notification
// (and thus implicitly continues GN).
}
EX_END_CATCH(SwallowAllExceptions);
#endif // FEATURE_DBGIPC_TRANSPORT_VM
}
//---------------------------------------------------------------------------------------
// Send a createProcess event to give the RS a chance to do SetDesiredNGENFlags
//
// Arguments:
// pDbgLockHolder - lock holder.
//
// Assumptions:
// Lock is initially held. This will toggle the lock to send an IPC event.
// This will start a synchronization.
//
// Notes:
// In V2, this also gives the RS a chance to intialize the IPC protocol.
// Spefically, this needs to be sent before the LS can send a sync-complete.
//---------------------------------------------------------------------------------------
void Debugger::SendCreateProcess(DebuggerLockHolder * pDbgLockHolder)
{
pDbgLockHolder->Release();
// Encourage helper thread to spin up so that we're in a consistent state.
PollWaitingForHelper();
// we don't need to use SENDIPCEVENT_BEGIN/END macros that perform the debug-suspend aware checks,
// as this code executes on the startup path...
SENDIPCEVENT_RAW_BEGIN(pDbgLockHolder);
// Send a CreateProcess event.
// @dbgtodo pipeline - eliminate these reasons for needing a CreateProcess event (part of pipeline feature crew)
// This will let the RS know that the IPC block is up + ready, and then the RS can read it.
// The RS will then update the DCB with enough information so that we can send the sync-complete.
// (such as letting us know whether we're interop-debugging or not).
DebuggerIPCEvent event;
InitIPCEvent(&event, DB_IPCE_CREATE_PROCESS, NULL, VMPTR_AppDomain::NullPtr());
SendRawEvent(&event);
// @dbgtodo inspection- it doesn't really make sense to sync on a CreateProcess. We only have 1 thread
// in the CLR and we know exactly what state we're in and we can ensure that we're synchronized.
// For V3,RS should be able to treat a CreateProcess like a synchronized.
// Remove this in V3 as we make SetDesiredNgenFlags operate OOP.
TrapAllRuntimeThreads();
// Must have a thread object so that we ensure that we will actually block here.
// This ensures the debuggee is actually stopped at startup, and
// this gives the debugger a chance to call SetDesiredNGENFlags before we
// set s_fCanChangeNgenFlags to FALSE.
_ASSERTE(GetThread() != NULL);
SENDIPCEVENT_RAW_END;
pDbgLockHolder->Acquire();
}
#if !defined(FEATURE_PAL)
HANDLE g_hContinueStartupEvent = INVALID_HANDLE_VALUE;
CLR_ENGINE_METRICS g_CLREngineMetrics = {
sizeof(CLR_ENGINE_METRICS),
CorDebugVersion_4_0,
&g_hContinueStartupEvent};
#define StartupNotifyEventNamePrefix W("TelestoStartupEvent_")
const int cchEventNameBufferSize = sizeof(StartupNotifyEventNamePrefix)/sizeof(WCHAR) + 8; // + hex DWORD (8). NULL terminator is included in sizeof(StartupNotifyEventNamePrefix)
HANDLE OpenStartupNotificationEvent()
{
DWORD debuggeePID = GetCurrentProcessId();
WCHAR szEventName[cchEventNameBufferSize];
swprintf_s(szEventName, cchEventNameBufferSize, StartupNotifyEventNamePrefix W("%08x"), debuggeePID);
return WszOpenEvent(MAXIMUM_ALLOWED | SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, szEventName);
}
void NotifyDebuggerOfStartup()
{
// Create the continue event first so that we guarantee that any
// enumeration of this process will get back a valid continue event
// the instant we signal the startup notification event.
CONSISTENCY_CHECK(INVALID_HANDLE_VALUE == g_hContinueStartupEvent);
g_hContinueStartupEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
CONSISTENCY_CHECK(INVALID_HANDLE_VALUE != g_hContinueStartupEvent); // we reserve this value for error conditions in EnumerateCLRs
HANDLE startupEvent = OpenStartupNotificationEvent();
if (startupEvent != NULL)
{
// signal notification event
SetEvent(startupEvent);
CloseHandle(startupEvent);
startupEvent = NULL;
// wait on continue startup event
// The debugger may attach to us while we're blocked here.
WaitForSingleObject(g_hContinueStartupEvent, INFINITE);
}
CloseHandle(g_hContinueStartupEvent);
g_hContinueStartupEvent = NULL;
}
#endif // !FEATURE_PAL
//---------------------------------------------------------------------------------------
//
// Initialize Left-Side debugger object
//
// Return Value:
// S_OK on successs. May also throw.
//
// Assumptions:
// This is called in the startup path.
//
// Notes:
// Startup initializes any necessary debugger objects, including creating
// and starting the Runtime Controller thread. Once the RC thread is started
// and we return successfully, the Debugger object can expect to have its
// event handlers called.
//
//---------------------------------------------------------------------------------------
HRESULT Debugger::Startup(void)
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_TRIGGERS;
}
CONTRACTL_END;
HRESULT hr = S_OK;
_ASSERTE(g_pEEInterface != NULL);
#if !defined(FEATURE_PAL)
// This may block while an attach occurs.
NotifyDebuggerOfStartup();
#endif // !FEATURE_PAL
{
DebuggerLockHolder dbgLockHolder(this);
// Stubs in Stacktraces are always enabled.
g_EnableSIS = true;
// We can get extra Interop-debugging test coverage by having some auxillary unmanaged
// threads running and throwing debug events. Keep these stress procs separate so that
// we can focus on certain problem areas.
#ifdef _DEBUG
g_DbgShouldntUseDebugger = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNoDebugger) != 0;
// Creates random thread procs.
DWORD dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreads);
DWORD dwId;
DWORD i;
if (dwRegVal > 0)
{
for (i = 0; i < dwRegVal; i++)
{
int iProc = GetRandomInt(NumItems(g_pStressProcs));
LPTHREAD_START_ROUTINE pStartRoutine = g_pStressProcs[iProc];
::CreateThread(NULL, 0, pStartRoutine, NULL, 0, &dwId);
LOG((LF_CORDB, LL_INFO1000, "Created random thread (%d) with tid=0x%x\n", i, dwId));
}
}
dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsIB);
if (dwRegVal > 0)
{
for (i = 0; i < dwRegVal; i++)
{
::CreateThread(NULL, 0, DbgInteropStressProc, NULL, 0, &dwId);
LOG((LF_CORDB, LL_INFO1000, "Created extra thread (%d) with tid=0x%x\n", i, dwId));
}
}
dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsCantStop);
if (dwRegVal > 0)
{
for (i = 0; i < dwRegVal; i++)
{
::CreateThread(NULL, 0, DbgInteropCantStopStressProc, NULL, 0, &dwId);
LOG((LF_CORDB, LL_INFO1000, "Created extra thread 'can't-stop' (%d) with tid=0x%x\n", i, dwId));
}
}
dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsOOB);
if (dwRegVal > 0)
{
for (i = 0; i < dwRegVal; i++)
{
::CreateThread(NULL, 0, DbgInteropOOBStressProc, NULL, 0, &dwId);
LOG((LF_CORDB, LL_INFO1000, "Created extra thread OOB (%d) with tid=0x%x\n", i, dwId));
}
}
#endif
#ifdef FEATURE_PAL
PAL_InitializeDebug();
#endif // FEATURE_PAL
// Lazily initialize the interop-safe heap
// Must be done before the RC thread is initialized.
// @dbgtodo - In V2, LS was lazily initialized; but was eagerly pre-initialized if launched by debugger.
// (This was for perf reasons). But we don't want Launch vs. Attach checks in the LS, so we now always
// init. As we move more to OOP, this init will become cheaper.
{
LazyInit();
DebuggerController::Initialize();
}
InitializeHijackFunctionAddress();
// Also initialize the AppDomainEnumerationIPCBlock
#if !defined(FEATURE_IPCMAN) || defined(FEATURE_DBGIPC_TRANSPORT_VM)
m_pAppDomainCB = new (nothrow) AppDomainEnumerationIPCBlock();
#else
m_pAppDomainCB = g_pIPCManagerInterface->GetAppDomainBlock();
#endif
if (m_pAppDomainCB == NULL)
{
LOG((LF_CORDB, LL_INFO100, "D::S: Failed to get AppDomain IPC block from IPCManager.\n"));
ThrowHR(E_FAIL);
}
hr = InitAppDomainIPC();
_ASSERTE(SUCCEEDED(hr)); // throws on error.
// Allows the debugger (and profiler) diagnostics to be disabled so resources like
// the named pipes and semaphores are not created.
if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_EnableDiagnostics) == 0)
{
return S_OK;
}
// Create the runtime controller thread, a.k.a, the debug helper thread.
// Don't use the interop-safe heap b/c we don't want to lazily create it.
m_pRCThread = new DebuggerRCThread(this);
_ASSERTE(m_pRCThread != NULL); // throws on oom
TRACE_ALLOC(m_pRCThread);
hr = m_pRCThread->Init();
_ASSERTE(SUCCEEDED(hr)); // throws on error
#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
// Create transport session and initialize it.
g_pDbgTransport = new DbgTransportSession();
hr = g_pDbgTransport->Init(m_pRCThread->GetDCB(), m_pAppDomainCB);
if (FAILED(hr))
{
ShutdownTransport();
ThrowHR(hr);
}
#ifdef FEATURE_PAL
PAL_SetShutdownCallback(AbortTransport);
#endif // FEATURE_PAL
#endif // FEATURE_DBGIPC_TRANSPORT_VM
RaiseStartupNotification();
// See if we need to spin up the helper thread now, rather than later.
DebuggerIPCControlBlock* pIPCControlBlock = m_pRCThread->GetDCB();
(void)pIPCControlBlock; //prevent "unused variable" error from GCC
_ASSERTE(pIPCControlBlock != NULL);
_ASSERTE(!pIPCControlBlock->m_rightSideShouldCreateHelperThread);
{
// Create the win32 thread for the helper and let it run free.
hr = m_pRCThread->Start();
// convert failure to exception as with old contract
if (FAILED(hr))
{
ThrowHR(hr);
}
LOG((LF_CORDB, LL_EVERYTHING, "Start was successful\n"));
}
#ifdef TEST_DATA_CONSISTENCY
// if we have set the environment variable TestDataConsistency, run the data consistency test.
// See code:DataTest::TestDataSafety for more information
if ((g_pConfig != NULL) && (g_pConfig->TestDataConsistency() == true))
{
DataTest dt;
dt.TestDataSafety();
}
#endif
}
#ifdef FEATURE_PAL
// Signal the debugger (via dbgshim) and wait until it is ready for us to
// continue. This needs to be outside the lock and after the transport is
// initialized.
if (PAL_NotifyRuntimeStarted())
{
// The runtime was successfully launched and attached so mark it now
// so no notifications are missed especially the initial module load
// which would cause debuggers problems with reliable setting breakpoints
// in startup code or Main.
MarkDebuggerAttachedInternal();
}
#endif // FEATURE_PAL
// We don't bother changing this process's permission.
// A managed debugger will have the SE_DEBUG permission which will allow it to open our process handle,
// even if we're a guest account.
return hr;
}
//---------------------------------------------------------------------------------------
// Finishes startup once we have a Thread object.
//
// Arguments:
// pThread - the current thread. Must be non-null
//
// Notes:
// Most debugger initialization is done in code:Debugger.Startup,
// However, debugger can't block on synchronization without a Thread object,
// so sending IPC events must wait until after we have a thread object.
//---------------------------------------------------------------------------------------
HRESULT Debugger::StartupPhase2(Thread * pThread)
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_TRIGGERS;
}
CONTRACTL_END;
HRESULT hr = S_OK;
// Must have a thread so that we can block
_ASSERTE(pThread != NULL);
DebuggerLockHolder dbgLockHolder(this);
// @dbgtodo - This may need to change when we remove SetupSyncEvent...
// If we're launching, then sync now so that the RS gets an early chance to dispatch the CreateProcess event.
// This is especially important b/c certain portions of the ICorDebugAPI (like setting ngen flags) are only
// valid during the CreateProcess callback in the launch case.
// We need to send the callback early enough so those APIs can set the flags before they're actually used.
// We also ensure the debugger is actually attached.
if (SUCCEEDED(hr) && CORDebuggerAttached())
{
StartCanaryThread();
SendCreateProcess(&dbgLockHolder); // toggles lock
}
// After returning from debugger startup we assume that the runtime might start using the NGEN flags to make
// binding decisions. From now on the debugger can not influence NGEN binding policy
// Use volatile store to guarantee make the value visible to the DAC (the store can be optimized out otherwise)
VolatileStoreWithoutBarrier(&s_fCanChangeNgenFlags, FALSE);
// Must release the lock (which would be done at the end of this method anyways) so that
// the helper thread can do the jit-attach.
dbgLockHolder.Release();
#ifdef _DEBUG
// Give chance for stress harnesses to launch a managed debugger when a managed app starts up.
// This lets us run a set of managed apps under a debugger.
if (!CORDebuggerAttached())
{
#define DBG_ATTACH_ON_STARTUP_ENV_VAR W("COMPlus_DbgAttachOnStartup")
PathString temp;
// We explicitly just check the env because we don't want a switch this invasive to be global.
DWORD fAttach = WszGetEnvironmentVariable(DBG_ATTACH_ON_STARTUP_ENV_VAR, temp) > 0;
if (fAttach)
{
// Remove the env var from our process so that the debugger we spin up won't inherit it.
// Else, if the debugger is managed, we'll have an infinite recursion.
BOOL fOk = WszSetEnvironmentVariable(DBG_ATTACH_ON_STARTUP_ENV_VAR, NULL);
if (fOk)
{
// We've already created the helper thread (which can service the attach request)
// So just do a normal jit-attach now.
SString szName(W("DebuggerStressStartup"));
SString szDescription(W("MDA used for debugger-stress scenario. This is fired to trigger a jit-attach")
W("to allow us to attach a debugger to any managed app that starts up.")
W("This MDA is only fired when the 'DbgAttachOnStartup' COM+ knob/reg-key is set on checked builds."));
SString szXML(W("<xml>See the description</xml>"));
SendMDANotification(
NULL, // NULL b/c we don't have a thread yet
&szName,
&szDescription,
&szXML,
((CorDebugMDAFlags) 0 ),
TRUE // this will force the jit-attach
);
}
}
}
#endif
return hr;
}
//---------------------------------------------------------------------------------------
//
// Public entrypoint into the debugger to force the lazy data to be initialized at a
// controlled point in time. This is useful for those callers into the debugger (e.g.,
// ETW rundown) that know they will need the lazy data initialized but cannot afford to
// have it initialized unpredictably or inside a lock.
//
// This may be called more than once, and will know to initialize the lazy data only
// once.
//
void Debugger::InitializeLazyDataIfNecessary()
{
CONTRACTL
{
SO_NOT_MAINLINE;
THROWS;
GC_TRIGGERS;
}
CONTRACTL_END;
if (!HasLazyData())
{
DebuggerLockHolder lockHolder(this);
LazyInit(); // throws
}
}
/******************************************************************************
Lazy initialize stuff once we know we are debugging.
This reduces the startup cost in the non-debugging case.
We can do this at a bunch of random strategic places.
******************************************************************************/
HRESULT Debugger::LazyInitWrapper()
{
CONTRACTL
{
SO_INTOLERANT;
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(ThisMaybeHelperThread());
}
CONTRACTL_END;
HRESULT hr = S_OK;
// Do lazy initialization now.
EX_TRY
{
LazyInit(); // throws on errors.
}
EX_CATCH
{
Exception *_ex = GET_EXCEPTION();
hr = _ex->GetHR();
STRESS_LOG1(LF_CORDB, LL_ALWAYS, "LazyInit failed w/ hr:0x%08x\n", hr);
}
EX_END_CATCH(SwallowAllExceptions);
return hr;
}
void Debugger::LazyInit()
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_NOTRIGGER;
PRECONDITION(ThreadHoldsLock()); // ensure we're serialized, requires GC_NOTRIGGER
PRECONDITION(ThisMaybeHelperThread());
}
CONTRACTL_END;
// Have knob that catches places where we lazy init.
_ASSERTE(!g_DbgShouldntUseDebugger);
// If we're already init, then bail.
if (m_pLazyData != NULL)
{
return;
}
// Lazily create our heap.
HRESULT hr = m_heap.Init(FALSE);
IfFailThrow(hr);
hr = m_executableHeap.Init(TRUE);
IfFailThrow(hr);
m_pLazyData = new (interopsafe) DebuggerLazyInit();
_ASSERTE(m_pLazyData != NULL); // throws on oom.
m_pLazyData->Init();
}
HelperThreadFavor::HelperThreadFavor() :
m_fpFavor(NULL),
m_pFavorData(NULL),
m_FavorReadEvent(NULL),
m_FavorLock(CrstDebuggerFavorLock, CRST_DEFAULT),
m_FavorAvailableEvent(NULL)
{
}
void HelperThreadFavor::Init()
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_NOTRIGGER;
PRECONDITION(ThisMaybeHelperThread());
}
CONTRACTL_END;
// Create events for managing favors.
m_FavorReadEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE);
m_FavorAvailableEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE);
}
DebuggerLazyInit::DebuggerLazyInit() :
m_pPendingEvals(NULL),
// @TODO: a-meicht
// Major clean up needed for giving the right flag
// There are cases where DebuggerDataLock is taken by managed thread and unmanaged trhead is also trying to take it.
// It could cause deadlock if we toggle GC upon taking lock.
// Unfortunately UNSAFE_COOPGC is not enough. There is a code path in Jit comipling that we are in GC Preemptive
// enabled. workaround by orring the unsafe_anymode flag. But we really need to do proper clean up.
//
// NOTE: If this ever gets fixed, you should replace CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT
// with appropriate contracts at each site.
//
m_DebuggerDataLock(CrstDebuggerJitInfo, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD)),
m_CtrlCMutex(NULL),
m_exAttachEvent(NULL),
m_exUnmanagedAttachEvent(NULL),
m_DebuggerHandlingCtrlC(NULL)
{
}
void DebuggerLazyInit::Init()
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_NOTRIGGER;
PRECONDITION(ThisMaybeHelperThread());
}
CONTRACTL_END;
// Caller ensures this isn't double-called.
// This event is only used in the unmanaged attach case. We must mark this event handle as inheritable.
// Otherwise, the unmanaged debugger won't be able to notify us.
//
// Note that PAL currently doesn't support specifying the security attributes when creating an event, so
// unmanaged attach for unhandled exceptions is broken on PAL.
SECURITY_ATTRIBUTES* pSA = NULL;
SECURITY_ATTRIBUTES secAttrib;
secAttrib.nLength = sizeof(secAttrib);
secAttrib.lpSecurityDescriptor = NULL;
secAttrib.bInheritHandle = TRUE;
pSA = &secAttrib;
// Create some synchronization events...
// these events stay signaled all the time except when an attach is in progress
m_exAttachEvent = CreateWin32EventOrThrow(NULL, kManualResetEvent, TRUE);
m_exUnmanagedAttachEvent = CreateWin32EventOrThrow(pSA, kManualResetEvent, TRUE);
m_CtrlCMutex = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE);
m_DebuggerHandlingCtrlC = FALSE;
// Let the helper thread lazy init stuff too.
m_RCThread.Init();
}
DebuggerLazyInit::~DebuggerLazyInit()
{
{
USHORT cBlobs = m_pMemBlobs.Count();
void **rgpBlobs = m_pMemBlobs.Table();
for (int i = 0; i < cBlobs; i++)
{
g_pDebugger->ReleaseRemoteBuffer(rgpBlobs[i], false);
}
}
if (m_pPendingEvals)
{
DeleteInteropSafe(m_pPendingEvals);
m_pPendingEvals = NULL;
}
if (m_CtrlCMutex != NULL)
{
CloseHandle(m_CtrlCMutex);
}
if (m_exAttachEvent != NULL)
{
CloseHandle(m_exAttachEvent);
}
if (m_exUnmanagedAttachEvent != NULL)
{
CloseHandle(m_exUnmanagedAttachEvent);
}
}
//
// RequestFavor gets the debugger helper thread to call a function. It's
// typically called when the current thread can't call the function directly,
// e.g, there isn't enough stack space.
//
// RequestFavor can be called in stack-overflow scenarios and thus explicitly
// avoids any lazy initialization.
// It blocks until the favor callback completes.
//
// Parameters:
// fp - a non-null Favour callback function
// pData - the parameter passed to the favor callback function. This can be any value.
//
// Return values:
// S_OK if the function succeeds, else a failure HRESULT
//
HRESULT Debugger::RequestFavor(FAVORCALLBACK fp, void * pData)
{
CONTRACTL
{
SO_INTOLERANT;
NOTHROW;
GC_TRIGGERS;
PRECONDITION(fp != NULL);
}
CONTRACTL_END;
if (m_pRCThread == NULL ||
m_pRCThread->GetRCThreadId() == GetCurrentThreadId())
{
// Since favors are only used internally, we know that the helper should alway be up and ready
// to handle them. Also, since favors can be used in low-stack scenarios, there's not any
// extra initialization needed for them.
_ASSERTE(!"Helper not initialized for favors.");
return E_UNEXPECTED;
}
m_pRCThread->DoFavor(fp, pData);
return S_OK;
}
/******************************************************************************
// Called to set the interface that the Runtime exposes to us.
******************************************************************************/
void Debugger::SetEEInterface(EEDebugInterface* i)
{
LIMITED_METHOD_CONTRACT;
// @@@
// Implements DebugInterface API
g_pEEInterface = i;
}
/******************************************************************************
// Called to shut down the debugger. This stops the RC thread and cleans
// the object up.
******************************************************************************/
void Debugger::StopDebugger(void)
{
CONTRACTL
{
SO_INTOLERANT;
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
// Leak almost everything on process exit. The OS will clean it up anyways and trying to
// clean it up ourselves is just one more place we may AV / deadlock.
#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
ShutdownTransport();
#endif // FEATURE_DBGIPC_TRANSPORT_VM
// Ping the helper thread to exit. This will also prevent the helper from servicing new requests.
if (m_pRCThread != NULL)
{
m_pRCThread->AsyncStop();
}
// Also clean up the AppDomain stuff since this is cross-process.
TerminateAppDomainIPC ();
//
// Tell the VM to clear out all references to the debugger before we start cleaning up,
// so that nothing will reference (accidentally) through the partially cleaned up debugger.
//
// NOTE: we cannot clear out g_pDebugger before the delete call because the
// stuff in delete (particularly deleteinteropsafe) needs to look at it.
//
g_pEEInterface->ClearAllDebugInterfaceReferences();
g_pDebugger = NULL;
}
/* ------------------------------------------------------------------------ *
* JIT Interface routines
* ------------------------------------------------------------------------ */
/******************************************************************************
*
******************************************************************************/
DebuggerMethodInfo *Debugger::CreateMethodInfo(Module *module, mdMethodDef md)
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_NOTRIGGER;
PRECONDITION(HasDebuggerDataLock());
}
CONTRACTL_END;
// <TODO>@todo perf: creating these on the heap is slow. We should use a
// pool and create them out of there since we never free them
// until the AD is unloaded.</TODO>
//
DebuggerMethodInfo *mi = new (interopsafe) DebuggerMethodInfo(module, md);
_ASSERTE(mi != NULL); // throws on oom error
TRACE_ALLOC(mi);
LOG((LF_CORDB, LL_INFO100000, "D::CreateMethodInfo module=%p, token=0x%08x, info=%p\n",
module, md, mi));
//
// Lock a mutex when changing the table.
//
//@TODO : _ASSERTE(EnC);
HRESULT hr;
hr =InsertToMethodInfoList(mi);
if (FAILED(hr))
{
LOG((LF_CORDB, LL_EVERYTHING, "IAHOL Failed!!\n"));
DeleteInteropSafe(mi);
return NULL;
}
return mi;
}
/******************************************************************************
// void Debugger::JITComplete(): JITComplete is called by
// the jit interface when the JIT completes, successfully or not.
//
// MethodDesc* fd: MethodDesc of the code that's been JITted
// BYTE* newAddress: The address of that the method begins at.
// If newAddress is NULL then the JIT failed. Remember that this
// gets called before the start address of the MethodDesc gets set,
// and so methods like GetFunctionAddress & GetFunctionSize won't work.
//
// <TODO>@Todo If we're passed 0 for the newAddress param, the jit has been
// cancelled & should be undone.</TODO>
******************************************************************************/
void Debugger::JITComplete(MethodDesc* fd, TADDR newAddress)
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
PRECONDITION(!HasDebuggerDataLock());
PRECONDITION(newAddress != NULL);
CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT;
}
CONTRACTL_END;
LOG((LF_CORDB, LL_INFO100000, "D::JITComplete: md:0x%x (%s::%s), address:0x%x.\n",
fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName,
newAddress));
#ifdef _TARGET_ARM_
newAddress = newAddress|THUMB_CODE;
#endif
// @@@
// Can be called on managed thread only
// This API Implements DebugInterface
if (CORDebuggerAttached())
{
// Populate the debugger's cache of DJIs. Normally we can do this lazily,
// the only reason we do it here is b/c the MethodDesc is not yet officially marked as "jitted",
// and so we can't lazily create it yet. Furthermore, the binding operations may need the DJIs.
//
// This also gives the debugger a chance to know if new JMC methods are coming.
DebuggerMethodInfo * dmi = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef());
if (dmi == NULL)
{
goto Exit;
}
BOOL jiWasCreated = FALSE;
DebuggerJitInfo * ji = dmi->CreateInitAndAddJitInfo(fd, newAddress, &jiWasCreated);
if (!jiWasCreated)
{
// we've already been notified about this code, no work remains.
// The JIT is occasionally asked to generate code for the same
// method on two threads. When this occurs both threads will
// return the same code pointer and this callback is invoked
// multiple times.
LOG((LF_CORDB, LL_INFO1000000, "D::JITComplete: md:0x%x (%s::%s), address:0x%x. Already created\n",
fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName,
newAddress));
goto Exit;
}
LOG((LF_CORDB, LL_INFO1000000, "D::JITComplete: md:0x%x (%s::%s), address:0x%x. Created ji:0x%x\n",
fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName,
newAddress, ji));
// Bind any IL patches to the newly jitted native code.
HRESULT hr;
hr = MapAndBindFunctionPatches(ji, fd, (CORDB_ADDRESS_TYPE *)newAddress);
_ASSERTE(SUCCEEDED(hr));
}
LOG((LF_CORDB, LL_EVERYTHING, "JitComplete completed successfully\n"));
Exit:
;
}
/******************************************************************************
// Get the number of fixed arguments to a function, i.e., the explicit args and the "this" pointer.
// This does not include other implicit arguments or varargs. This is used to compute a variable ID
// (see comment in CordbJITILFrame::ILVariableToNative for more detail)
// fVarArg is not used when this is called by Debugger::GetAndSendJITInfo, thus it has a default value.
// The return value is not used when this is called by Debugger::getVars.
******************************************************************************/
SIZE_T Debugger::GetArgCount(MethodDesc *fd,BOOL *fVarArg /* = NULL */)
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_NOTRIGGER;
}
CONTRACTL_END;
// Create a MetaSig for the given method's sig. (Easier than
// picking the sig apart ourselves.)
PCCOR_SIGNATURE pCallSig;
DWORD cbCallSigSize;
fd->GetSig(&pCallSig, &cbCallSigSize);
if (pCallSig == NULL)
{
// Sig should only be null if the image is corrupted. (Even for lightweight-codegen)
// We expect the jit+verifier to catch this, so that we never land here.
// But just in case ...
CONSISTENCY_CHECK_MSGF(false, ("Corrupted image, null sig.(%s::%s)", fd->m_pszDebugClassName, fd->m_pszDebugMethodName));
return 0;
}
MetaSig msig(pCallSig, cbCallSigSize, g_pEEInterface->MethodDescGetModule(fd), NULL, MetaSig::sigMember);
// Get the arg count.
UINT32 NumArguments = msig.NumFixedArgs();
// Account for the 'this' argument.
if (!(g_pEEInterface->MethodDescIsStatic(fd)))
NumArguments++;
// Is this a VarArg's function?
if (msig.IsVarArg() && fVarArg != NULL)
{
*fVarArg = true;
}
return NumArguments;
}
#endif // #ifndef DACCESS_COMPILE
/******************************************************************************
DebuggerJitInfo * Debugger::GetJitInfo(): GetJitInfo
will return a pointer to a DebuggerJitInfo. If the DJI
doesn't exist, or it does exist, but the method has actually
been pitched (and the caller wants pitched methods filtered out),
then we'll return NULL.
Note: This will also create a DMI for if one does not exist for this DJI.
MethodDesc* fd: MethodDesc for the method we're interested in.
CORDB_ADDRESS_TYPE * pbAddr: Address within the code, to indicate which
version we want. If this is NULL, then we want the
head of the DebuggerJitInfo list, whether it's been
JITted or not.
******************************************************************************/
// Get a DJI from an address.
DebuggerJitInfo *Debugger::GetJitInfoFromAddr(TADDR addr)
{
WRAPPER_NO_CONTRACT;
MethodDesc *fd;
fd = g_pEEInterface->GetNativeCodeMethodDesc(addr);
_ASSERTE(fd);
return GetJitInfo(fd, (const BYTE*) addr, NULL);
}
// Get a DJI for a Native MD (MD for a native function).
// In the EnC scenario, the MethodDesc refers to the most recent method.
// This is very dangerous since there may be multiple versions alive at the same time.
// This will give back the wrong DJI if we're lookikng for a stale method desc.
// @todo - can a caller possibly use this correctly?
DebuggerJitInfo *Debugger::GetLatestJitInfoFromMethodDesc(MethodDesc * pMethodDesc)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(pMethodDesc != NULL);
// We'd love to assert that we're jitted; but since this may be in the JitComplete
// callback path, we can't be sure.
return GetJitInfoWorker(pMethodDesc, NULL, NULL);
}
DebuggerJitInfo *Debugger::GetJitInfo(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo )
{
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_NOTRIGGER;
PRECONDITION(!g_pDebugger->HasDebuggerDataLock());
}
CONTRACTL_END;
// Address should be non-null and in range of MethodDesc. This lets us tell which EnC version.
_ASSERTE(pbAddr != NULL);
return GetJitInfoWorker(fd, pbAddr, pMethInfo);
}
// Internal worker to GetJitInfo. Doesn't validate parameters.
DebuggerJitInfo *Debugger::GetJitInfoWorker(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo)
{
DebuggerMethodInfo *dmi = NULL;
DebuggerJitInfo *dji = NULL;
// If we have a null MethodDesc - we're not going to get a jit-info. Do this check once at the top
// rather than littered throughout the rest of this function.
if (fd == NULL)
{
LOG((LF_CORDB, LL_EVERYTHING, "Debugger::GetJitInfo, addr=0x%p - null fd - returning null\n", pbAddr));
return NULL;
}
else
{
CONSISTENCY_CHECK_MSGF(!fd->IsWrapperStub(), ("Can't get Jit-info for wrapper MDesc,'%s'", fd->m_pszDebugMethodName));
}
// The debugger doesn't track Lightweight-codegen methods b/c they have no metadata.
if (fd->IsDynamicMethod())
{
return NULL;
}
// initialize our out param
if (pMethInfo)
{
*pMethInfo = NULL;
}
LOG((LF_CORDB, LL_EVERYTHING, "Debugger::GetJitInfo called\n"));
// CHECK_DJI_TABLE_DEBUGGER;
// Find the DJI via the DMI
//
// One way to improve the perf, both in terms of memory usage, number of allocations
// and lookup speeds would be to have the first JitInfo inline in the MethodInfo
// struct. After all, we never want to have a MethodInfo in the table without an
// associated JitInfo, and this should bring us back very close to the old situation
// in terms of perf. But correctness comes first, and perf later...
// CHECK_DMI_TABLE;
dmi = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef());
if (dmi == NULL)
{
// If we can't create the DMI, we won't be able to create the DJI.
return NULL;
}
// TODO: Currently, this method does not handle code versioning properly (at least in some profiler scenarios), it may need
// to take pbAddr into account and lazily create a DJI for that particular version of the method.
// This may take the lock and lazily create an entry, so we do it up front.
dji = dmi->GetLatestJitInfo(fd);
DebuggerDataLockHolder debuggerDataLockHolder(this);
// Note the call to GetLatestJitInfo() will lazily create the first DJI if we don't already have one.
for (; dji != NULL; dji = dji->m_prevJitInfo)
{
if (PTR_TO_TADDR(dji->m_fd) == PTR_HOST_TO_TADDR(fd))
{
break;
}
}
LOG((LF_CORDB, LL_INFO1000, "D::GJI: for md:0x%x (%s::%s), got dmi:0x%x.\n",
fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName,
dmi));
// Log stuff - fd may be null; so we don't want to AV in the log.
LOG((LF_CORDB, LL_INFO1000, "D::GJI: for md:0x%x (%s::%s), got dmi:0x%x, dji:0x%x, latest dji:0x%x, latest fd:0x%x, prev dji:0x%x\n",
fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName,
dmi, dji, (dmi ? dmi->GetLatestJitInfo_NoCreate() : 0),
((dmi && dmi->GetLatestJitInfo_NoCreate()) ? dmi->GetLatestJitInfo_NoCreate()->m_fd:0),
(dji?dji->m_prevJitInfo:0)));
if ((dji != NULL) && (pbAddr != NULL))
{
dji = dji->GetJitInfoByAddress(pbAddr);
// XXX Microsoft - dac doesn't support stub tracing
// so this just results in not-impl exceptions.
#ifndef DACCESS_COMPILE
if (dji == NULL) //may have been given address of a thunk
{
LOG((LF_CORDB,LL_INFO1000,"Couldn't find a DJI by address 0x%p, "
"so it might be a stub or thunk\n", pbAddr));
TraceDestination trace;
g_pEEInterface->TraceStub((const BYTE *)pbAddr, &trace);
if ((trace.GetTraceType() == TRACE_MANAGED) && (pbAddr != (const BYTE *)trace.GetAddress()))
{
LOG((LF_CORDB,LL_INFO1000,"Address thru thunk"
": 0x%p\n", trace.GetAddress()));
dji = GetJitInfo(fd, dac_cast<PTR_CBYTE>(trace.GetAddress()));
}
#ifdef LOGGING
else
{
_ASSERTE(trace.GetTraceType() != TRACE_UNJITTED_METHOD ||
(fd == trace.GetMethodDesc()));
LOG((LF_CORDB,LL_INFO1000,"Address not thunked - "
"must be to unJITted method, or normal managed "
"method lacking a DJI!\n"));
}
#endif //LOGGING
}
#endif // #ifndef DACCESS_COMPILE
}
if (pMethInfo)
{
*pMethInfo = dmi;
}
// DebuggerDataLockHolder out of scope - release implied
return dji;
}
DebuggerMethodInfo *Debugger::GetOrCreateMethodInfo(Module *pModule, mdMethodDef token)
{
CONTRACTL
{
SO_INTOLERANT;
SUPPORTS_DAC;
THROWS;
GC_NOTRIGGER;
}
CONTRACTL_END;
DebuggerMethodInfo *info = NULL;
// When dump debugging, we don't expect to have a lock,
// nor would it be useful for anything.
ALLOW_DATATARGET_MISSING_MEMORY(
// In case we don't have already, take it now.
DebuggerDataLockHolder debuggerDataLockHolder(this);
);
if (m_pMethodInfos != NULL)
{
info = m_pMethodInfos->GetMethodInfo(pModule, token);
}
// dac checks ngen'ed image content first, so
// if we didn't find information it doesn't exist.
#ifndef DACCESS_COMPILE
if (info == NULL)
{
info = CreateMethodInfo(pModule, token);
LOG((LF_CORDB, LL_INFO1000, "D::GOCMI: created DMI for mdToken:0x%x, dmi:0x%x\n",
token, info));
}
#endif // #ifndef DACCESS_COMPILE
if (info == NULL)
{
// This should only happen in an oom scenario. It would be nice to throw here.
STRESS_LOG2(LF_CORDB, LL_EVERYTHING, "OOM - Failed to allocate DJI (0x%p, 0x%x)\n", pModule, token);
}
// DebuggerDataLockHolder out of scope - release implied
return info;
}
#ifndef DACCESS_COMPILE
/******************************************************************************
* GetILToNativeMapping returns a map from IL offsets to native
* offsets for this code. An array of COR_PROF_IL_TO_NATIVE_MAP
* structs will be returned, and some of the ilOffsets in this array
* may be the values specified in CorDebugIlToNativeMappingTypes.
******************************************************************************/
HRESULT Debugger::GetILToNativeMapping(PCODE pNativeCodeStartAddress, ULONG32 cMap,
ULONG32 *pcMap, COR_DEBUG_IL_TO_NATIVE_MAP map[])
{
CONTRACTL
{
SO_NOT_MAINLINE;
THROWS;
GC_TRIGGERS_FROM_GETJITINFO;
}
CONTRACTL_END;
#ifdef PROFILING_SUPPORTED
// At this point, we're pulling in the debugger.
if (!HasLazyData())
{
DebuggerLockHolder lockHolder(this);
LazyInit(); // throws
}
// Get the JIT info by functionId.
// This function is unsafe to use during EnC because the MethodDesc doesn't tell
// us which version is being requested.
// However, this function is only used by the profiler, and you can't profile with EnC,
// which means that getting the latest jit-info is still correct.
#if defined(PROFILING_SUPPORTED)
_ASSERTE(CORProfilerPresent());
#endif // PROFILING_SUPPORTED
MethodDesc *fd = g_pEEInterface->GetNativeCodeMethodDesc(pNativeCodeStartAddress);
if (fd == NULL || fd->IsWrapperStub() || fd->IsDynamicMethod())
{
return E_FAIL;
}
DebuggerMethodInfo *pDMI = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef());
if (pDMI == NULL)
{
return E_FAIL;
}
DebuggerJitInfo *pDJI = pDMI->FindOrCreateInitAndAddJitInfo(fd, pNativeCodeStartAddress);
// Dunno what went wrong
if (pDJI == NULL)
return (E_FAIL);
// If they gave us space to copy into...
if (map != NULL)
{
// Only copy as much as either they gave us or we have to copy.
ULONG32 cpyCount = min(cMap, pDJI->GetSequenceMapCount());
// Read the map right out of the Left Side.
if (cpyCount > 0)
ExportILToNativeMap(cpyCount,
map,
pDJI->GetSequenceMap(),
pDJI->m_sizeOfCode);
}
// Return the true count of entries
if (pcMap)
{
*pcMap = pDJI->GetSequenceMapCount();
}
return (S_OK);
#else
return E_NOTIMPL;
#endif
}
//---------------------------------------------------------------------------------------
//
// This is morally the same as GetILToNativeMapping, except the output is in a different
// format, to better facilitate sending the ETW ILToNativeMap events.
//
// Arguments:
// pMD - MethodDesc whose IL-to-native map will be returned
// cMapMax - Max number of map entries to return. Although
// this function handles the allocation of the returned
// array, the caller still wants to limit how big this
// can get, since ETW itself has limits on how big
// events can get
// pcMap - [out] Number of entries returned in each output parallel array (next
// two parameters).
// prguiILOffset - [out] Array of IL offsets. This function allocates, caller must free.
// prguiNativeOffset - [out] Array of the starting native offsets that correspond
// to each (*prguiILOffset)[i]. This function allocates,
// caller must free.
//
// Return Value:
// HRESULT indicating success or failure.
//
// Notes:
// * This function assumes lazy data has already been initialized (in order to
// ensure that this doesn't trigger or take the large debugger mutex). So
// callers must guarantee they call InitializeLazyDataIfNecessary() first.
// * Either this function fails, and (*prguiILOffset) & (*prguiNativeOffset) will be
// untouched OR this function succeeds and (*prguiILOffset) & (*prguiNativeOffset)
// will both be non-NULL, set to the parallel arrays this function allocated.
// * If this function returns success, then the caller must free (*prguiILOffset) and
// (*prguiNativeOffset)
// * (*prguiILOffset) and (*prguiNativeOffset) are parallel arrays, such that
// (*prguiILOffset)[i] corresponds to (*prguiNativeOffset)[i] for each 0 <= i < *pcMap
// * If EnC is enabled, this function will return the IL-to-native mapping for the latest
// EnC version of the function. This may not be what the profiler wants, but EnC
// + ETW-map events is not a typical combination, and this is consistent with
// other ETW events like JittingStarted or MethodLoad, which also fire multiple
// events for the same MethodDesc (each time it's EnC'd), with each event
// corresponding to the most recent EnC version at the time.
//
HRESULT Debugger::GetILToNativeMappingIntoArrays(
MethodDesc * pMethodDesc,
PCODE pCode,
USHORT cMapMax,
USHORT * pcMap,
UINT ** prguiILOffset,
UINT ** prguiNativeOffset)
{
CONTRACTL
{
SO_NOT_MAINLINE;
THROWS;
GC_NOTRIGGER;
}
CONTRACTL_END;
_ASSERTE(pcMap != NULL);
_ASSERTE(prguiILOffset != NULL);
_ASSERTE(prguiNativeOffset != NULL);
// Any caller of GetILToNativeMappingIntoArrays had better call
// InitializeLazyDataIfNecessary first!
_ASSERTE(HasLazyData());
// Get the JIT info by functionId.
DebuggerJitInfo * pDJI = GetJitInfo(pMethodDesc, (const BYTE *)pCode);
// Dunno what went wrong
if (pDJI == NULL)
return E_FAIL;
ULONG32 cMap = min(cMapMax, pDJI->GetSequenceMapCount());
DebuggerILToNativeMap * rgMapInt = pDJI->GetSequenceMap();
NewArrayHolder<UINT> rguiILOffsetTemp = new (nothrow) UINT[cMap];
if (rguiILOffsetTemp == NULL)
return E_OUTOFMEMORY;
NewArrayHolder<UINT> rguiNativeOffsetTemp = new (nothrow) UINT[cMap];
if (rguiNativeOffsetTemp == NULL)
return E_OUTOFMEMORY;
for (ULONG32 iMap=0; iMap < cMap; iMap++)
{
rguiILOffsetTemp[iMap] = rgMapInt[iMap].ilOffset;
rguiNativeOffsetTemp[iMap] = rgMapInt[iMap].nativeStartOffset;
}
// Since cMap is the min of cMapMax (and something else) and cMapMax is a USHORT,
// then cMap must fit in a USHORT as well
_ASSERTE(FitsIn<USHORT>(cMap));
*pcMap = (USHORT) cMap;
*prguiILOffset = rguiILOffsetTemp.Extract();
*prguiNativeOffset = rguiNativeOffsetTemp.Extract();
return S_OK;
}
#endif // #ifndef DACCESS_COMPILE
/******************************************************************************
*
******************************************************************************/
CodeRegionInfo CodeRegionInfo::GetCodeRegionInfo(DebuggerJitInfo *dji, MethodDesc *md, PTR_CORDB_ADDRESS_TYPE addr)
{
CONTRACTL
{
SO_INTOLERANT;
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
MODE_ANY;
}
CONTRACTL_END;
if (dji && dji->m_addrOfCode)
{
LOG((LF_CORDB, LL_EVERYTHING, "CRI::GCRI: simple case\n"));
return dji->m_codeRegionInfo;
}
else
{
LOG((LF_CORDB, LL_EVERYTHING, "CRI::GCRI: more complex case\n"));
CodeRegionInfo codeRegionInfo;
// Use method desc from dji if present
if (dji && dji->m_fd)
{
_ASSERTE(!md || md == dji->m_fd);
md = dji->m_fd;
}
if (!addr)
{
_ASSERTE(md);
addr = dac_cast<PTR_CORDB_ADDRESS_TYPE>(g_pEEInterface->GetFunctionAddress(md));
}
else
{
_ASSERTE(!md ||
(addr == dac_cast<PTR_CORDB_ADDRESS_TYPE>(g_pEEInterface->GetFunctionAddress(md))));
}
if (addr)
{
PCODE pCode = PINSTRToPCODE(dac_cast<TADDR>(addr));
codeRegionInfo.InitializeFromStartAddress(pCode);
}
return codeRegionInfo;
}
}
#ifndef DACCESS_COMPILE
/******************************************************************************
// Helper function for getBoundaries to get around AMD64 compiler and
// contract holders with PAL_TRY in the same function.
******************************************************************************/
void Debugger::getBoundariesHelper(MethodDesc * md,
unsigned int *cILOffsets,
DWORD **pILOffsets)
{
//
// CANNOT ADD A CONTRACT HERE. Contract is in getBoundaries
//
//
// Grab the JIT info struct for this method. Create if needed, as this
// may be called before JITComplete.
//
DebuggerMethodInfo *dmi = NULL;
dmi = GetOrCreateMethodInfo(md->GetModule(), md->GetMemberDef());
if (dmi != NULL)
{
LOG((LF_CORDB,LL_INFO10000,"De::NGB: Got dmi 0x%x\n",dmi));
#if defined(FEATURE_ISYM_READER)
// Note: we need to make sure to enable preemptive GC here just in case we block in the symbol reader.
GCX_PREEMP_EEINTERFACE();
Module *pModule = md->GetModule();
(void)pModule; //prevent "unused variable" error from GCC
_ASSERTE(pModule != NULL);
SafeComHolder<ISymUnmanagedReader> pReader(pModule->GetISymUnmanagedReader());
// If we got a reader, use it.
if (pReader != NULL)
{
// Grab the sym reader's method.
ISymUnmanagedMethod *pISymMethod;
HRESULT hr = pReader->GetMethod(md->GetMemberDef(),
&pISymMethod);
ULONG32 n = 0;
if (SUCCEEDED(hr))
{
// Get the count of sequence points.
hr = pISymMethod->GetSequencePointCount(&n);
_ASSERTE(SUCCEEDED(hr));
LOG((LF_CORDB, LL_INFO100000,
"D::NGB: Reader seq pt count is %d\n", n));
ULONG32 *p;
if (n > 0)
{
ULONG32 dummy;
p = new ULONG32[n];
_ASSERTE(p != NULL); // throws on oom errror
hr = pISymMethod->GetSequencePoints(n, &dummy,
p, NULL, NULL, NULL,
NULL, NULL);
_ASSERTE(SUCCEEDED(hr));
_ASSERTE(dummy == n);
*pILOffsets = (DWORD*)p;
// Translate the IL offets based on an
// instrumented IL map if one exists.
if (dmi->HasInstrumentedILMap())
{
InstrumentedILOffsetMapping mapping =
dmi->GetRuntimeModule()->GetInstrumentedILOffsetMapping(dmi->m_token);
for (SIZE_T i = 0; i < n; i++)
{
int origOffset = *p;
*p = dmi->TranslateToInstIL(
&mapping,
origOffset,
bOriginalToInstrumented);
LOG((LF_CORDB, LL_INFO100000,
"D::NGB: 0x%04x (Real IL:0x%x)\n",
origOffset, *p));
p++;
}
}
#ifdef LOGGING
else
{
for (SIZE_T i = 0; i < n; i++)
{
LOG((LF_CORDB, LL_INFO100000,
"D::NGB: 0x%04x \n", *p));
p++;
}
}
#endif
}
else
*pILOffsets = NULL;
pISymMethod->Release();
}
else
{
*pILOffsets = NULL;
LOG((LF_CORDB, LL_INFO10000,
"De::NGB: failed to find method 0x%x in sym reader.\n",
md->GetMemberDef()));
}
*cILOffsets = n;
}
else
{
LOG((LF_CORDB, LL_INFO100000, "D::NGB: no reader.\n"));
}
#else // FEATURE_ISYM_READER
// We don't have ISymUnmanagedReader. Pretend there are no sequence points.
*cILOffsets = 0;
#endif // FEATURE_ISYM_READER
}
LOG((LF_CORDB, LL_INFO100000, "D::NGB: cILOffsets=%d\n", *cILOffsets));
return;
}
#endif
/******************************************************************************
// Use an ISymUnmanagedReader to get method sequence points.
******************************************************************************/
void Debugger::getBoundaries(MethodDesc * md,
unsigned int *cILOffsets,
DWORD **pILOffsets,
ICorDebugInfo::BoundaryTypes *implicitBoundaries)
{
#ifndef DACCESS_COMPILE
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_TRIGGERS;
}
CONTRACTL_END;
// May be here even when a debugger is not attached.
// @@@
// Implements DebugInterface API
*cILOffsets = 0;
*pILOffsets = NULL;
*implicitBoundaries = ICorDebugInfo::DEFAULT_BOUNDARIES;
// If there has been an unrecoverable Left Side error, then we
// just pretend that there are no boundaries.
if (CORDBUnrecoverableError(this))
{
return;
}
// LCG methods have their own resolution scope that is seperate from a module
// so they shouldn't have their symbols looked up in the module PDB. Right now
// LCG methods have no symbols so we can just early out, but if they ever
// had some symbols attached we would need a different way of getting to them.
// See Dev10 issue 728519
if(md->IsLCGMethod())
{
return;
}
// If JIT optimizations are allowed for the module this function
// lives in, then don't grab specific boundaries from the symbol
// store since any boundaries we give the JIT will be pretty much
// ignored anyway.
if (!CORDisableJITOptimizations(md->GetModule()->GetDebuggerInfoBits()))
{
*implicitBoundaries = ICorDebugInfo::BoundaryTypes(ICorDebugInfo::STACK_EMPTY_BOUNDARIES |
ICorDebugInfo::CALL_SITE_BOUNDARIES);
return;
}
Module* pModule = md->GetModule();
DWORD dwBits = pModule->GetDebuggerInfoBits();
if ((dwBits & DACF_IGNORE_PDBS) != 0)
{
//
// If told to explicitly ignore PDBs for this function, then bail now.
//
return;
}
if( !pModule->IsSymbolReadingEnabled() )
{
// Symbol reading is disabled for this module, so bail out early (for efficiency only)
return;
}
if (pModule == SystemDomain::SystemModule())
{
// We don't look up PDBs for mscorlib. This is not quite right, but avoids
// a bootstrapping problem. When an EXE loads, it has the option of setting
// the COM apartment model to STA if we need to. It is important that no
// other Coinitialize happens before this. Since loading the PDB reader uses
// com we can not come first. However managed code IS run before the COM
// apartment model is set, and thus we have a problem since this code is
// called for when JITTing managed code. We avoid the problem by just
// bailing for mscorlib.
return;
}
// At this point, we're pulling in the debugger.
if (!HasLazyData())
{
DebuggerLockHolder lockHolder(this);
LazyInit(); // throws
}
getBoundariesHelper(md, cILOffsets, pILOffsets);
#else
DacNotImpl();
#endif // #ifndef DACCESS_COMPILE
}
/******************************************************************************
*
******************************************************************************/
void Debugger::getVars(MethodDesc * md, ULONG32 *cVars, ICorDebugInfo::ILVarInfo **vars,
bool *extendOthers)
{
#ifndef DACCESS_COMPILE
CONTRACTL
{
SO_INTOLERANT;
THROWS;
GC_TRIGGERS_FROM_GETJITINFO;
PRECONDITION(!ThisIsHelperThreadWorker());
}
CONTRACTL_END;
// At worst return no information
*cVars = 0;
*vars = NULL;
// Just tell the JIT to extend everything.
// Note that if optimizations are enabled, the native compilers are
// free to ingore *extendOthers
*extendOthers = true;
DWORD bits = md->GetModule()->GetDebuggerInfoBits();
if (CORDBUnrecoverableError(this))
goto Exit;
if (CORDisableJITOptimizations(bits))
// if (!CORDebuggerAllowJITOpts(bits))
{
//
// @TODO: Do we really need this code since *extendOthers==true?
//
// Is this a vararg function?
BOOL fVarArg = false;
GetArgCount(md, &fVarArg);
if (fVarArg)
{
COR_ILMETHOD *ilMethod = g_pEEInterface->MethodDescGetILHeader(md);
if (ilMethod)
{
// It is, so we need to tell the JIT to give us the
// varags handle.
ICorDebugInfo::ILVarInfo *p = new ICorDebugInfo::ILVarInfo[1];
_ASSERTE(p != NULL); // throws on oom error
COR_ILMETHOD_DECODER header(ilMethod);
unsigned int ilCodeSize = header.GetCodeSize();
p->startOffset = 0;
p->endOffset = ilCodeSize;
p->varNumber = (DWORD) ICorDebugInfo::VARARGS_HND_ILNUM;
*cVars = 1;
*vars = p;
}
}
}
LOG((LF_CORDB, LL_INFO100000, "D::gV: cVars=%d, extendOthers=%d\n",
*cVars, *extendOthers));
Exit:
;
#else
DacNotImpl();
#endif // #ifndef DACCESS_COMPILE
}
#ifndef DACCESS_COMPILE
// If we have a varargs function, we can't set the IP (we don't know how to pack/unpack the arguments), so if we
// call SetIP with fCanSetIPOnly = true, we need to check for that.
// Arguments:
// input: nEntries - number of entries in varNativeInfo
// varNativeInfo - array of entries describing the args and locals for the function
// output: true iff the function has varargs
BOOL Debugger::IsVarArgsFunction(unsigned int nEntries, PTR_NativeVarInfo varNativeInfo)
{
for (unsigned int i = 0; i < nEntries; ++i)
{
if (varNativeInfo[i].loc.vlType == ICorDebugInfo::VLT_FIXED_VA)
{
return TRUE;
}
}
return FALSE;
}
// We want to keep the 'worst' HRESULT - if one has failed (..._E_...) & the
// other hasn't, take the failing one. If they've both/neither failed, then
// it doesn't matter which we take.
// Note that this macro favors retaining the first argument
#define WORST_HR(hr1,hr2) (FAILED(hr1)?hr1:hr2)
/******************************************************************************
*
******************************************************************************/
HRESULT Debugger::SetIP( bool fCanSetIPOnly, Thread *thread,Module *module,
mdMethodDef mdMeth, DebuggerJitInfo* dji,
SIZE_T offsetILTo, BOOL fIsIL)
{
CONTRACTL
{
SO_NOT_MAINLINE;
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(CheckPointer(thread));
PRECONDITION(CheckPointer(module));
PRECONDITION(mdMeth != mdMethodDefNil);
}
CONTRACTL_END;
#ifdef _DEBUG
static ConfigDWORD breakOnSetIP;
if (breakOnSetIP.val(CLRConfig::INTERNAL_DbgBreakOnSetIP)) _ASSERTE(!"DbgBreakOnSetIP");
#endif
HRESULT hr = S_OK;
HRESULT hrAdvise = S_OK;
DWORD offsetILFrom;
CorDebugMappingResult map;
DWORD whichIgnore;
ControllerStackInfo csi;
BOOL exact;
SIZE_T offsetNatTo;
PCODE pbDest = NULL;
BYTE *pbBase = NULL;
CONTEXT *pCtx = NULL;
DWORD dwSize = 0;
SIZE_T *rgVal1 = NULL;
SIZE_T *rgVal2 = NULL;
BYTE **pVCs = NULL;
LOG((LF_CORDB, LL_INFO1000, "D::SIP: In SetIP ==> fCanSetIPOnly:0x%x <==!\n", fCanSetIPOnly));
if (ReJitManager::IsReJITEnabled())
{
return CORDBG_E_SET_IP_IMPOSSIBLE;
}
pCtx = GetManagedStoppedCtx(thread);
// If we can't get a context, then we can't possibly be a in a good place
// to do a setip.
if (pCtx == NULL)
{
return CORDBG_S_BAD_START_SEQUENCE_POINT;
}
// Implicit Caveat: We need to be the active frame.
// We can safely take a stack trace because the thread is synchronized.
StackTraceTicket ticket(thread);
csi.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL);
ULONG offsetNatFrom = csi.m_activeFrame.relOffset;
#if defined(WIN64EXCEPTIONS)
if (csi.m_activeFrame.IsFuncletFrame())
{
offsetNatFrom = (ULONG)((SIZE_T)GetControlPC(&(csi.m_activeFrame.registers)) -
(SIZE_T)(dji->m_addrOfCode));
}
#endif // WIN64EXCEPTIONS
_ASSERTE(dji != NULL);
// On WIN64 platforms, it's important to use the total size of the
// parent method and the funclets below (i.e. m_sizeOfCode). Don't use
// the size of the individual funclets or the parent method.
pbBase = (BYTE*)CORDB_ADDRESS_TO_PTR(dji->m_addrOfCode);
dwSize = (DWORD)dji->m_sizeOfCode;
#if defined(WIN64EXCEPTIONS)
// Currently, method offsets are not bigger than 4 bytes even on WIN64.
// Assert that it is so here.
_ASSERTE((SIZE_T)dwSize == dji->m_sizeOfCode);
#endif // WIN64EXCEPTIONS
// Create our structure for analyzing this.
// <TODO>@PERF: optimize - hold on to this so we don't rebuild it for both
// CanSetIP & SetIP.</TODO>
int cFunclet = 0;
const DWORD * rgFunclet = NULL;
#if defined(WIN64EXCEPTIONS)
cFunclet = dji->GetFuncletCount();
rgFunclet = dji->m_rgFunclet;
#endif // WIN64EXCEPTIONS
EHRangeTree* pEHRT = new (nothrow) EHRangeTree(csi.m_activeFrame.pIJM,
csi.m_activeFrame.MethodToken,
dwSize,
cFunclet,
rgFunclet);
// To maintain the current semantics, we will check the following right before SetIPFromSrcToDst() is called
// (instead of checking them now):
// 1) pEHRT == NULL
// 2) FAILED(pEHRT->m_hrInit)
{
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Got version info fine\n"));
// Caveat: we need to start from a sequence point
offsetILFrom = dji->MapNativeOffsetToIL(offsetNatFrom,
&map, &whichIgnore);
if ( !(map & MAPPING_EXACT) )
{
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Starting native offset is bad!\n"));
hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_START_SEQUENCE_POINT);
}
else
{ // exact IL mapping
if (!(dji->GetSrcTypeFromILOffset(offsetILFrom) & ICorDebugInfo::STACK_EMPTY))
{
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Starting offset isn't stack empty!\n"));
hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_START_SEQUENCE_POINT);
}
}
// Caveat: we need to go to a sequence point
if (fIsIL )
{
#if defined(WIN64EXCEPTIONS)
int funcletIndexFrom = dji->GetFuncletIndex((CORDB_ADDRESS)offsetNatFrom, DebuggerJitInfo::GFIM_BYOFFSET);
offsetNatTo = dji->MapILOffsetToNativeForSetIP(offsetILTo, funcletIndexFrom, pEHRT, &exact);
#else // WIN64EXCEPTIONS
DebuggerJitInfo::ILToNativeOffsetIterator it;
dji->InitILToNativeOffsetIterator(it, offsetILTo);
offsetNatTo = it.CurrentAssertOnlyOne(&exact);
#endif // WIN64EXCEPTIONS
if (!exact)
{
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest (via IL offset) is bad!\n"));
hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT);
}
}
else
{
offsetNatTo = offsetILTo;
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest of 0x%p (via native "
"offset) is fine!\n", offsetNatTo));
}
CorDebugMappingResult mapping;
DWORD which;
offsetILTo = dji->MapNativeOffsetToIL(offsetNatTo, &mapping, &which);
// We only want to perhaps return CORDBG_S_BAD_END_SEQUENCE_POINT if
// we're not already returning CORDBG_S_BAD_START_SEQUENCE_POINT.
if (hr != CORDBG_S_BAD_START_SEQUENCE_POINT)
{
if ( !(mapping & MAPPING_EXACT) )
{
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Ending native offset is bad!\n"));
hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT);
}
else
{
// <NOTE WIN64>
// All duplicate sequence points (ones with the same IL offset) should have the same SourceTypes.
// </NOTE WIN64>
if (!(dji->GetSrcTypeFromILOffset(offsetILTo) & ICorDebugInfo::STACK_EMPTY))
{
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Ending offset isn't a sequence"
" point, or not stack empty!\n"));
hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT);
}
}
}
// Once we finally have a native offset, it had better be in range.
if (offsetNatTo >= dwSize)
{
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Code out of range! offsetNatTo = 0x%x, dwSize=0x%x\n", offsetNatTo, dwSize));
hrAdvise = E_INVALIDARG;
goto LExit;
}
pbDest = CodeRegionInfo::GetCodeRegionInfo(dji).OffsetToAddress(offsetNatTo);
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest is 0x%p\n", pbDest));
// Don't allow SetIP if the source or target is cold (SetIPFromSrcToDst does not
// correctly handle this case).
if (!CodeRegionInfo::GetCodeRegionInfo(dji).IsOffsetHot(offsetNatTo) ||
!CodeRegionInfo::GetCodeRegionInfo(dji).IsOffsetHot(offsetNatFrom))
{
hrAdvise = WORST_HR(hrAdvise, CORDBG_E_SET_IP_IMPOSSIBLE);
goto LExit;
}
}
if (!fCanSetIPOnly)
{
hr = ShuffleVariablesGet(dji,
offsetNatFrom,
pCtx,
&rgVal1,
&rgVal2,
&pVCs);
LOG((LF_CORDB|LF_ENC,
LL_INFO10000,
"D::SIP: rgVal1 0x%X, rgVal2 0x%X\n",
rgVal1,
rgVal2));
if (FAILED(hr))
{
// This will only fail fatally, so exit.
hrAdvise = WORST_HR(hrAdvise, hr);
goto LExit;
}
}
else // fCanSetIPOnly
{
if (IsVarArgsFunction(dji->GetVarNativeInfoCount(), dji->GetVarNativeInfo()))
{
hrAdvise = E_INVALIDARG;
goto LExit;
}
}
if (pEHRT == NULL)
{
hr = E_OUTOFMEMORY;
}
else if (FAILED(pEHRT->m_hrInit))
{
hr = pEHRT->m_hrInit;
}
else
{
//
// This is a known, ok, violation. END_EXCEPTION_GLUE has a call to GetThrowable in it, but
// we will never hit it because we are passing in NULL below. This is to satisfy the static
// contract analyzer.
//
CONTRACT_VIOLATION(GCViolation);
EX_TRY
{
hr =g_pEEInterface->SetIPFromSrcToDst(thread,
pbBase,
offsetNatFrom,
(DWORD)offsetNatTo,
fCanSetIPOnly,
&(csi.m_activeFrame.registers),
pCtx,
(void *)dji,
pEHRT);
}
EX_CATCH
{
}
EX_END_CATCH(SwallowAllExceptions);
}
// Get the return code, if any
if (hr != S_OK)
{
hrAdvise = WORST_HR(hrAdvise, hr);
goto LExit;
}
// If we really want to do this, we'll have to put the
// variables into their new locations.
if (!fCanSetIPOnly && !FAILED(hrAdvise))
{
// TODO: We should zero out any registers which have now become live GC roots,
// but which aren't tracked variables (i.e. they are JIT temporaries). Such registers may
// have garbage left over in them, and we don't want the GC to try and dereference them
// as object references. However, we can't easily tell here which of the callee-saved regs
// are used in this method and therefore safe to clear.
//
hr = ShuffleVariablesSet(dji,
offsetNatTo,
pCtx,
&rgVal1,
&rgVal2,
pVCs);
if (hr != S_OK)
{
hrAdvise = WORST_HR(hrAdvise, hr);
goto LExit;
}
_ASSERTE(pbDest != NULL);
::SetIP(pCtx, pbDest);
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Set IP to be 0x%p\n", GetIP(pCtx)));
}
LExit:
if (rgVal1 != NULL)
{
DeleteInteropSafe(rgVal1);
}
if (rgVal2 != NULL)
{
DeleteInteropSafe(rgVal2);
}
if (pEHRT != NULL)
{
delete pEHRT;
}
LOG((LF_CORDB, LL_INFO1000, "D::SIP:Returning 0x%x\n", hr));
return hrAdvise;
}
#include "nativevaraccessors.h"
/******************************************************************************
*
******************************************************************************/
HRESULT Debugger::ShuffleVariablesGet(DebuggerJitInfo *dji,
SIZE_T offsetFrom,
CONTEXT *pCtx,
SIZE_T **prgVal1,
SIZE_T **prgVal2,
BYTE ***prgpVCs)
{
CONTRACTL
{
SO_NOT_MAINLINE;
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(CheckPointer(dji));
PRECONDITION(CheckPointer(pCtx));
PRECONDITION(CheckPointer(prgVal1));
PRECONDITION(CheckPointer(prgVal2));
PRECONDITION(dji->m_sizeOfCode >= offsetFrom);
}
CONTRACTL_END;
LONG cVariables = 0;
DWORD i;
//
// Find the largest variable number
//
for (i = 0; i < dji->GetVarNativeInfoCount(); i++)
{
if ((LONG)(dji->GetVarNativeInfo()[i].varNumber) > cVariables)
{
cVariables = (LONG)(dji->GetVarNativeInfo()[i].varNumber);
}
}
HRESULT hr = S_OK;
//
// cVariables is a zero-based count of the number of variables. Increment it.
//
cVariables++;
SIZE_T *rgVal1 = new (interopsafe, nothrow) SIZE_T[cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)];
SIZE_T *rgVal2 = NULL;
if (rgVal1 == NULL)
{
hr = E_OUTOFMEMORY;
goto LExit;
}
rgVal2 = new (interopsafe, nothrow) SIZE_T[cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)];
if (rgVal2 == NULL)
{
hr = E_OUTOFMEMORY;
goto LExit;
}
memset(rgVal1, 0, sizeof(SIZE_T) * (cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)));
memset(rgVal2, 0, sizeof(SIZE_T) * (cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)));
LOG((LF_CORDB|LF_ENC,
LL_INFO10000,
"D::SVG cVariables %d, hiddens %d, rgVal1 0x%X, rgVal2 0x%X\n