Fixed mixed mode attach/JIT debugging.
The mixed mode debugging attach uses TLS slot to communicate between debugger break-in thread and the right side. Unfortunately, the __thread static variables cannot be used on debugger breakin
thread because of it does not have storage allocated for them.
The fix is to switch the storage for debugger word to classic TlsAlloc allocated slot that works
fine on debugger break-in thread.
There was also problem (that is also in 2.0) where the WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer was using the define for 64/32 bit and ended up always the 32 bit Windows value. This caused the right side GetEEThreadValue, GetEETlsDataBlock unmanaged thread functions to always fail.
m_runtimeOffsets.m_notifyRSOfSyncCompleteBPAddr));
LOG((LF_CORDB, LL_INFO10000, " m_raiseException= 0x%p\n",
m_runtimeOffsets.m_raiseExceptionAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_debuggerWordTLSIndex= 0x%08x\n",
+ m_runtimeOffsets.m_debuggerWordTLSIndex));
#endif // FEATURE_INTEROP_DEBUGGING
LOG((LF_CORDB, LL_INFO10000, " m_TLSIndex= 0x%08x\n",
m_runtimeOffsets.m_EEThreadPGCDisabledOffset));
LOG((LF_CORDB, LL_INFO10000, " m_EEThreadPGCDisabledValue= 0x%08x\n",
m_runtimeOffsets.m_EEThreadPGCDisabledValue));
- LOG((LF_CORDB, LL_INFO10000, " m_EEThreadDebuggerWordOffset= 0x%08x\n",
- m_runtimeOffsets.m_EEThreadDebuggerWordOffset));
LOG((LF_CORDB, LL_INFO10000, " m_EEThreadFrameOffset= 0x%08x\n",
m_runtimeOffsets.m_EEThreadFrameOffset));
LOG((LF_CORDB, LL_INFO10000, " m_EEThreadMaxNeededSize= 0x%08x\n",
DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+ LOG((LF_CORDB, LL_INFO1000, "CP::TE1stCAI: Enter\n"));
#ifdef _DEBUG
// Some Interop bugs involve threads that land at a crazy IP. Since we're interop-debugging, we can't
}
else
{
+ LOG((LF_CORDB, LL_INFO1000, "CP::TE1stCAI: Triage1stChanceNonSpecial\n"));
+
Reaction r(REACTION(cOOB));
HRESULT hrCheck = S_OK;;
EX_TRY
CorDebugInterfaceVersion GetDebuggerVersion() const;
#ifdef FEATURE_CORESYSTEM
- HMODULE GetTargetCLR() { return m_targetCLR; }
+ HMODULE GetTargetCLR() { return m_targetCLR; }
#endif
private:
//Note - this code could be useful outside coresystem, but keeping the change localized
// because we are late in the win8 release
#ifdef FEATURE_CORESYSTEM
- HMODULE m_targetCLR;
+ HMODULE m_targetCLR;
#endif
};
// ICorDebugAppDomain3 APIs
//-----------------------------------------------------------
COM_METHOD GetCachedWinRTTypesForIIDs(
- ULONG32 cGuids,
- GUID * guids,
- ICorDebugTypeEnum * * ppTypesEnum);
+ ULONG32 cGuids,
+ GUID * guids,
+ ICorDebugTypeEnum * * ppTypesEnum);
COM_METHOD GetCachedWinRTTypes(
- ICorDebugGuidToTypeEnum * * ppType);
+ ICorDebugGuidToTypeEnum * * ppType);
//-----------------------------------------------------------
// ICorDebugAppDomain4
{
case cInband: return "cInband";
case cInband_NotNewEvent: return "cInband_NotNewEvent";
+ case cFirstChanceHijackStarted: return "cFirstChanceHijackStarted";
case cInbandHijackComplete: return "cInbandHijackComplete";
case cInbandExceptionRetrigger: return "cInbandExceptionRetrigger";
case cBreakpointRequiringHijack: return "cBreakpointRequiringHijack";
HRESULT EnableSSAfterBP();
bool GetEEThreadCantStopHelper();
- DWORD_PTR GetTlsSlot(SIZE_T slot);
+ HRESULT GetTlsSlot(DWORD slot, REMOTE_PTR *pValue);
+ HRESULT SetTlsSlot(DWORD slot, REMOTE_PTR value);
REMOTE_PTR GetPreDefTlsSlot(SIZE_T slot, bool * pRead);
void * m_pPatchSkipAddress;
-
-
- /*
- * This abstracts away an overload of the OS thread's TLS slot. In
- * particular the runtime may or may not have created a thread object for
- * a particular OS thread at any point.
- *
- * If the runtime has created a thread object, then it stores a pointer to
- * that thread object in the thread's TLS slot.
- *
- * If not, then interop-debugging uses that TLS slot to store temporary
- * information.
- *
- * To determine this, interop-debugging will set the low bit. Thus when
- * we read the TLS slot, if it is non-NULL, anything w/o the low bit set
- * is an EE thread object ptr. Anything with the low bit set is an
- * interop-debugging value. Any NULL is null, and an indicator that
- * there does not exist a runtime thread object for this thread yet.
- *
- */
- REMOTE_PTR m_pEEThread;
- REMOTE_PTR m_pdwTlsValue;
- BOOL m_fValidTlsData;
-
UINT m_continueCountCached;
- void CacheEEDebuggerWord();
- HRESULT SetEEThreadValue(REMOTE_PTR EETlsValue);
-
DWORD_PTR GetEEThreadValue();
- REMOTE_PTR GetClrModuleTlsDataAddress();
REMOTE_PTR GetEETlsDataBlock();
+ HRESULT GetClrModuleTlsDataAddress(REMOTE_PTR* pAddress);
public:
HRESULT GetEEDebuggerWord(REMOTE_PTR *pValue);
return 0;
}
-// sets the value of gCurrentThreadInfo.m_pThread
-HRESULT CordbUnmanagedThread::SetEEThreadValue(REMOTE_PTR EETlsValue)
+// Read the contents from a LS threads's TLS slot.
+HRESULT CordbUnmanagedThread::GetTlsSlot(DWORD slot, REMOTE_PTR * pValue)
{
- FAIL_IF_NEUTERED(this);
+ // Compute the address of the necessary TLS value.
+ HRESULT hr = LoadTLSArrayPtr();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
- HRESULT hr = S_OK;
- _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+ void * pBase = NULL;
+ SIZE_T slotAdjusted = slot;
- REMOTE_PTR EEThreadAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_CurrentThread;
- if(EEThreadAddr == NULL)
- return E_FAIL;
+ if (slot < TLS_MINIMUM_AVAILABLE)
+ {
+ pBase = m_pTLSArray;
+ }
+ else if (slot < TLS_MINIMUM_AVAILABLE + TLS_EXPANSION_SLOTS)
+ {
+ pBase = m_pTLSExtendedArray;
+ slotAdjusted -= TLS_MINIMUM_AVAILABLE;
- // Write the thread's TLS value.
- hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &EETlsValue);
+ // Expansion slot is lazily allocated. If we're trying to read from it, but hasn't been allocated,
+ // then the TLS slot is still the default value, which is 0 (NULL).
+ if (pBase == NULL)
+ {
+ *pValue = NULL;
+ return S_OK;
+ }
+ }
+ else
+ {
+ // Slot is out of range. Shouldn't happen unless debuggee is corrupted.
+ _ASSERTE(!"Invalid TLS slot");
+ return E_UNEXPECTED;
+ }
+ void *pEEThreadTLS = (BYTE*)pBase + (slotAdjusted * sizeof(void*));
+
+ // Read the thread's TLS value.
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadTLS), pValue);
if (FAILED(hr))
{
- LOG((LF_CORDB, LL_INFO1000, "CUT::SEETV: failed to set TLS value: "
- "computed addr=0x%p index=%d, err=%x\n",
- EEThreadAddr, GetProcess()->m_runtimeOffsets.m_TLSIndex, hr));
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GTS: failed to read TLS value: computed addr=0x%p index=%d, err=%x\n",
+ pEEThreadTLS, slot, hr));
+ return hr;
+ }
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GTS: EE Thread TLS value is 0x%p for thread 0x%x, slot 0x%x\n", *pValue, m_id, slot));
+ return S_OK;
+}
+
+// This does a WriteProcessMemory to write to the debuggee's TLS slot
+//
+// Notes:
+// This is very brittle because the OS can lazily allocates storage for TLS slots.
+// In order to gaurantee the storage is available, it must have been written to by the debuggee.
+// For managed threads, that's easy because the Thread* is already written to the slot.
+// But for pure native threads where GetThread() == NULL, the storage may not yet be allocated.
+//
+// The saving grace is that the debuggee's hijack filters will force the TLS to be allocated before it
+// sends a flare.
+//
+// Therefore, this function can only be called:
+// 1) on a managed thread
+// 2) on a native thread after that thread has been hijacked and sent a flare.
+//
+// This is brittle reasoning, but so is the rest of interop-debugging.
+//
+HRESULT CordbUnmanagedThread::SetTlsSlot(DWORD slot, REMOTE_PTR value)
+{
+ FAIL_IF_NEUTERED(this);
+
+ // Compute the address of the necessary TLS value.
+ HRESULT hr = LoadTLSArrayPtr();
+ if (FAILED(hr))
+ {
return hr;
}
- LOG((LF_CORDB, LL_INFO1000000,
- "CUT::SEETV: EE Thread TLS value is now 0x%p for thread 0x%x\n",
- EETlsValue, m_id));
+ void * pBase = NULL;
+ SIZE_T slotAdjusted = slot;
+ if (slot < TLS_MINIMUM_AVAILABLE)
+ {
+ pBase = m_pTLSArray;
+ }
+ else if (slot < TLS_MINIMUM_AVAILABLE + TLS_EXPANSION_SLOTS)
+ {
+ pBase = m_pTLSExtendedArray;
+ slotAdjusted -= TLS_MINIMUM_AVAILABLE;
- return S_OK;
+ // Expansion slot is lazily allocated. If we're trying to read from it, but hasn't been allocated,
+ // then the TLS slot is still the default value, which is 0.
+ if (pBase == NULL)
+ {
+ // See reasoning in header for why this should succeed.
+ _ASSERTE(!"Can't set to expansion slots because they haven't been allocated");
+ return E_FAIL;
+ }
+ }
+ else
+ {
+ // Slot is out of range. Shouldn't happen unless debuggee is corrupted.
+ _ASSERTE(!"Invalid TLS slot");
+ return E_INVALIDARG;
+ }
+ void *pEEThreadTLS = (BYTE*)pBase + (slotAdjusted * sizeof(void*));
+
+ // Write the thread's TLS value.
+ hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pEEThreadTLS), &value);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEETV: failed to set TLS value: computed addr=0x%p slot=%d, err=%x\n", pEEThreadTLS, slot, hr));
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::SEETV: EE Thread TLS value is now 0x%p for 0x%x\n", value, m_id));
+ return S_OK;
}
// gets the value of gCurrentThreadInfo.m_pThread
{
DWORD_PTR ret = NULL;
- REMOTE_PTR EEThreadAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_CurrentThread;
- if(EEThreadAddr == NULL)
+ REMOTE_PTR tlsDataAddress;
+ HRESULT hr = GetClrModuleTlsDataAddress(&tlsDataAddress);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: GetClrModuleTlsDataAddress FAILED %x for 0x%x\n", hr, m_id));
return NULL;
+ }
// Read the thread's TLS value.
- HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &ret);
-
+ REMOTE_PTR EEThreadAddr = (BYTE*)tlsDataAddress + OFFSETOF__TLS__tls_CurrentThread;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &ret);
if (FAILED(hr))
{
- LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: failed to get TLS value: "
- "computed addr=0x%p index=%d, err=%x\n",
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: failed to get TLS value: computed addr=0x%p index=%d, err=%x\n",
EEThreadAddr, GetProcess()->m_runtimeOffsets.m_TLSIndex, hr));
-
return NULL;
}
- LOG((LF_CORDB, LL_INFO1000000,
- "CUT::GEETV: EE Thread TLS value is 0x%p for thread 0x%x\n",
- ret, m_id));
-
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETV: EE Thread TLS value is 0x%p for 0x%x\n", ret, m_id));
return ret;
}
// returns the remote address of gCurrentThreadInfo
-REMOTE_PTR CordbUnmanagedThread::GetClrModuleTlsDataAddress()
+HRESULT CordbUnmanagedThread::GetClrModuleTlsDataAddress(REMOTE_PTR* pAddress)
{
- HRESULT hr = S_OK;
+ *pAddress = NULL;
REMOTE_PTR tlsArrayAddr;
- hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_threadLocalBase + WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer), &tlsArrayAddr);
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_threadLocalBase + WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer), &tlsArrayAddr);
if (FAILED(hr))
{
- return NULL;
+ return hr;
}
+ // This is the special break-in thread case: TEB.ThreadLocalStoragePointer == NULL
if (tlsArrayAddr == NULL)
{
- _ASSERTE(!"ThreadLocalStoragePointer is NULL");
- return NULL;
+ return E_FAIL;
}
DWORD slot = (DWORD)(GetProcess()->m_runtimeOffsets.m_TLSIndex);
hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)tlsArrayAddr + (slot & 0xFFFF) * sizeof(void*)), &clrModuleTlsDataAddr);
if (FAILED(hr))
{
- return NULL;
+ return hr;
}
if (clrModuleTlsDataAddr == NULL)
{
_ASSERTE(!"No clr module data present at _tls_index for this thread");
- return NULL;
+ return E_FAIL;
}
- return (BYTE*) clrModuleTlsDataAddr + ((slot & 0x7FFF0000) >> 16);
+ *pAddress = (BYTE*) clrModuleTlsDataAddr + ((slot & 0x7FFF0000) >> 16);
+ return S_OK;
}
-// gets the value of gCurrentThreadInfo.m_EETlsData
+// Gets the value of gCurrentThreadInfo.m_EETlsData
REMOTE_PTR CordbUnmanagedThread::GetEETlsDataBlock()
{
REMOTE_PTR ret;
- REMOTE_PTR blockAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_EETlsData;
+ REMOTE_PTR tlsDataAddress;
+ HRESULT hr = GetClrModuleTlsDataAddress(&tlsDataAddress);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETDB: GetClrModuleTlsDataAddress FAILED %x for 0x%x\n", hr, m_id));
+ return NULL;
+ }
-
- HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(blockAddr), &ret);
+ REMOTE_PTR blockAddr = (BYTE*)tlsDataAddress + OFFSETOF__TLS__tls_EETlsData;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(blockAddr), &ret);
if (FAILED(hr))
{
LOG((LF_CORDB, LL_INFO1000, "CUT::GEETDB: failed to read EETlsData address: computed addr=0x%p offset=%d, err=%x\n",
blockAddr, OFFSETOF__TLS__tls_EETlsData, hr));
-
return NULL;
}
- LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETDB: EETlsData address value is 0x%p for thread 0x%x\n", ret, m_id));
-
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETDB: EETlsData address value is 0x%p for 0x%x\n", ret, m_id));
return ret;
}
/*
- * CacheEEDebuggerWord
- *
- * NOTE: This routine is inappropriately named at this time because we dont
- * actually cache any values. This is because we dont have a way to invalidate
- * the cache between purely-native continues.
- *
- * This routine grabs two pieces of information from the target process via
- * ReadProcessMemory. First, if the runtime does not have a thread object for
- * this thread it grabs the debugger's value from the TLS slot. If there is a
- * runtime thread object, then it saves that away and grabs the debugger's value
- * from the thread object.
- *
- * Parameters:
- * None.
- *
- * Returns:
- * None. If it fails, then the Get/Set functions will fail.
- */
-void CordbUnmanagedThread::CacheEEDebuggerWord()
-{
- LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Entered\n"));
-
- REMOTE_PTR value = (REMOTE_PTR)GetEEThreadValue();
-
- if ((((DWORD)value) & 0x1) == 1)
- {
- m_pEEThread = NULL;
- m_pdwTlsValue = (REMOTE_PTR)((BYTE*)value - 0x1);
- m_fValidTlsData = TRUE;
- }
- else if (value != NULL)
- {
- m_pEEThread = value;
-
- // Compute the address of the debugger word #2.
- void *pEEDebuggerWord = (BYTE*)m_pEEThread + GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset;
-
- // Update the word.
- HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEDebuggerWord), &m_pdwTlsValue);
- m_fValidTlsData = SUCCEEDED(hr);
-
- if (!m_fValidTlsData)
- {
- LOG((LF_CORDB, LL_INFO1000, "EEDW: failed to read debugger word: 0x%08x + 0x%x = 0x%p, err=%d\n",
- m_pEEThread, GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset, pEEDebuggerWord, GetLastError()));
- }
- else
- {
- LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Debugger word is 0x%p\n", m_pdwTlsValue));
- }
- }
- else
- {
- m_fValidTlsData = TRUE;
- m_pEEThread = NULL;
- m_pdwTlsValue = NULL;
- }
-
- LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Exited\n"));
-}
-
-/*
* GetEEDebuggerWord
*
* This routine returns the value read from the thread
*/
HRESULT CordbUnmanagedThread::GetEEDebuggerWord(REMOTE_PTR *pValue)
{
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEEDW: Entered\n"));
if (pValue == NULL)
{
return E_INVALIDARG;
}
-
- CacheEEDebuggerWord();
-
- if (!m_fValidTlsData)
- {
- *pValue = NULL;
- return E_FAIL;
- }
-
- *pValue = m_pdwTlsValue;
-
- return S_OK;
+ return GetTlsSlot(GetProcess()->m_runtimeOffsets.m_debuggerWordTLSIndex, pValue);
}
// SetEEDebuggerWord
HRESULT CordbUnmanagedThread::SetEEDebuggerWord(REMOTE_PTR value)
{
LOG((LF_CORDB, LL_INFO1000, "CUT::SEEDW: Entered - value is 0x%p\n", value));
-
- CacheEEDebuggerWord();
-
- if (!m_fValidTlsData)
- {
- return E_FAIL;
- }
-
- m_pdwTlsValue = value;
-
- //
- // If the thread is NULL, bit-or on a 1 and store that.
- //
- if (m_pEEThread == NULL)
- {
- REMOTE_PTR pdwTemp = m_pdwTlsValue;
-
- if (pdwTemp != 0)
- {
- // actually we add 1, but we only use it for pointers which are
- // 8 byte aligned so it is the same thing
- _ASSERTE( ((UINT_PTR)pdwTemp & 0x1) == 0);
- pdwTemp = (REMOTE_PTR) ((BYTE*)pdwTemp + 0x01);
- }
- // This will write to the TLS slot. It's only safe to do this after a Flare has been sent from the
- // LS (since that's what guarantees the slot is allocated).
- return SetEEThreadValue(pdwTemp);
- }
- else
- {
- // Compute the address of the debugger word #2.
- void *pEEDebuggerWord = (BYTE*)m_pEEThread + GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset;
-
- // Update the word.
- HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pEEDebuggerWord), &m_pdwTlsValue);
-
- if (FAILED(hr))
- {
- LOG((LF_CORDB, LL_INFO1000, "CUT::SEETDW: failed to write debugger word: 0x%08x + 0x%x = 0x%08x, err=%x\n",
- m_pEEThread, GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset, pEEDebuggerWord, hr));
-
- return hr;
- }
- }
-
- LOG((LF_CORDB, LL_INFO1000, "CUT::SEEDW: Exited\n"));
- return S_OK;
+ return SetTlsSlot(GetProcess()->m_runtimeOffsets.m_debuggerWordTLSIndex, value);
}
/*
return E_INVALIDARG;
}
- CacheEEDebuggerWord();
-
- if (!m_fValidTlsData)
- {
- *ppEEThread = NULL;
- return E_FAIL;
- }
-
- *ppEEThread = m_pEEThread;
+ *ppEEThread = (REMOTE_PTR)GetEEThreadValue();
return S_OK;
}
-
void CordbUnmanagedThread::GetEEState(bool *threadStepping, bool *specialManagedException)
{
REMOTE_PTR pEEThread;
// Grab the thread state out of the EE Thread.
DWORD EEThreadStateNC;
hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadStateNC), &EEThreadStateNC);
-
if (FAILED(hr))
{
LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread state NC: 0x%p + 0x%x = 0x%p, err=%d\n",
pEEThread, pRO->m_EEThreadStateNCOffset, pEEThreadStateNC, GetLastError()));
-
return;
}
}
_ASSERTE(GetProcess()->ThreadHoldsProcessLock());
- if(IsRaiseExceptionHijacked())
+ if (IsRaiseExceptionHijacked())
{
return true;
}
REMOTE_PTR pEEThread;
HRESULT hr = this->GetEEThreadPtr(&pEEThread);
-
if (FAILED(hr))
{
_ASSERTE(!"Failed to EEThreadPtr in IsCantStop");
SPEW(fprintf(stderr, "0x%x D::FCHF: code=0x%08x, addr=0x%08x, Eip=0x%08x, Esp=0x%08x, EFlags=0x%08x\n",
tid, pExceptionRecord->ExceptionCode, pExceptionRecord->ExceptionAddress, pContext->Eip, pContext->Esp,
pContext->EFlags));
-
#endif
-
// This memory is used as IPC during the hijack. We will place a pointer to this in
- // either the EEThreadPtr or the EEDebuggerWord and then the RS can write info into
- // the memory
+ // the EE debugger word (a TLS slot that works even on the debugger break-in thread)
+ // and then the RS can write info into the memory.
DebuggerIPCFirstChanceData fcd;
- // accessing through the volatile pointer to fend off some potential compiler optimizations.
+
+ // Accessing through the volatile pointer to fend off some potential compiler optimizations.
// If the debugger changes that data from OOP we need to see those updates
volatile DebuggerIPCFirstChanceData* pFcd = &fcd;
-
+ // The Windows native break in thread does not have TLS storage allocated.
+ bool debuggerBreakInThread = (NtCurrentTeb()->ThreadLocalStoragePointer == NULL);
{
// Hijack filters are always in the can't stop range.
// The RS knows this b/c it knows which threads it hijacked.
// Bump up the CS counter so that any further calls in the LS can see this too.
// (This makes places where we assert that we're in a CS region happy).
- CantStopHolder hCantStop;
+ CantStopHolder hCantStop(!debuggerBreakInThread);
// Get the current runtime thread. This is only an optimized TLS access.
- Thread *pEEThread = g_pEEInterface->GetThread();
-
- // Is that really a ptr to a Thread? If the low bit is set or it its NULL then we don't have an EE Thread. If we
- // have a EE Thread, then we know the original handler now. If not, we have to wait for the Right Side to fixup our
- // handler chain once we've notified it that the exception does not belong to the runtime. Note: if we don't have an
- // EE thread, then the exception never belongs to the Runtime.
- bool hasEEThread = false;
- if ((pEEThread != NULL) && !(((UINT_PTR)pEEThread) & 0x01))
- {
- SPEW(fprintf(stderr, "0x%x D::FCHF: Has EE thread.\n", tid));
- hasEEThread = true;
- }
-
+ Thread *pEEThread = debuggerBreakInThread ? NULL : g_pEEInterface->GetThread();
+
// Hook up the memory so RS can get to it
fcd.pLeftSideContext.Set((DT_CONTEXT*)pContext);
fcd.action = HIJACK_ACTION_EXIT_UNHANDLED;
fcd.debugCounter = 0;
- if(hasEEThread)
- {
- SPEW(fprintf(stderr, "0x%x D::FCHF: Set Debugger word to 0x%p.\n", tid, pFcd));
- g_pEEInterface->SetThreadDebuggerWord(pEEThread, (VOID*) pFcd);
- }
- else
- {
- // this shouldn't be re-entrant
- _ASSERTE(pEEThread == NULL);
- SPEW(fprintf(stderr, "0x%x D::FCHF: EEThreadPtr word to 0x%p.\n", tid, (BYTE*)pFcd + 1));
- g_pEEInterface->SetEEThreadPtr((void*) ((BYTE*)pFcd + 1));
- }
+ SPEW(fprintf(stderr, "0x%x D::FCHF: Set debugger word to 0x%p.\n", tid, pFcd));
+ g_pEEInterface->SetThreadDebuggerWord((VOID*)pFcd);
// Signal the RS to tell us what to do
SPEW(fprintf(stderr, "0x%x D::FCHF: Signaling hijack started.\n", tid));
SignalHijackStarted();
SPEW(fprintf(stderr, "0x%x D::FCHF: Signaling hijack started complete. DebugCounter=0x%x\n", tid, pFcd->debugCounter));
- if(pFcd->action == HIJACK_ACTION_WAIT)
+ if (pFcd->action == HIJACK_ACTION_WAIT)
{
// This exception does NOT belong to the CLR.
// If we belong to the CLR, then we either:
// - were a M2U transition, in which case we should be in a different Hijack
// - were a CLR exception in CLR code, in which case we should have continued and let the inproc handlers get it.
- SPEW(fprintf(stderr, "0x%x D::FCHF: exception does not belong to the Runtime, hasEEThread=%d, pContext=0x%p\n",
- tid, hasEEThread, pContext));
+ SPEW(fprintf(stderr, "0x%x D::FCHF: exception does not belong to the Runtime, pEEThread=0x%p, pContext=0x%p\n",
+ tid, pEEThread, pContext));
- if(hasEEThread)
+ if (pEEThread != NULL)
{
_ASSERTE(!pEEThread->GetInteropDebuggingHijacked()); // hijack is not re-entrant.
pEEThread->SetInteropDebuggingHijacked(TRUE);
// Wait for the continue. We may / may not have an EE Thread for this, (and we're definitely
// not doing fiber-mode debugging), so just use a raw win32 API, and not some fancy fiber-safe call.
SPEW(fprintf(stderr, "0x%x D::FCHF: waiting for continue.\n", tid));
-
- DWORD ret = WaitForSingleObject(g_pDebugger->m_pRCThread->GetDCB()->m_leftSideUnmanagedWaitEvent,
- INFINITE);
-
+ DWORD ret = WaitForSingleObject(g_pDebugger->m_pRCThread->GetDCB()->m_leftSideUnmanagedWaitEvent, INFINITE);
SPEW(fprintf(stderr, "0x%x D::FCHF: waiting for continue complete.\n", tid));
+
if (ret != WAIT_OBJECT_0)
{
SPEW(fprintf(stderr, "0x%x D::FCHF: wait failed!\n", tid));
}
- if(hasEEThread)
+ if (pEEThread != NULL)
{
_ASSERTE(pEEThread->GetInteropDebuggingHijacked());
pEEThread->SetInteropDebuggingHijacked(FALSE);
_ASSERTE(pFcd->action != HIJACK_ACTION_WAIT);
// cleanup from above
- if (hasEEThread)
- {
- SPEW(fprintf(stderr, "0x%x D::FCHF: set debugger word = NULL.\n", tid));
- g_pEEInterface->SetThreadDebuggerWord(pEEThread, (VOID*) NULL);
- }
- else
- {
- SPEW(fprintf(stderr, "0x%x D::FCHF: set EEThreadPtr = NULL.\n", tid));
- g_pEEInterface->SetEEThreadPtr(NULL);
- }
+ SPEW(fprintf(stderr, "0x%x D::FCHF: set debugger word = NULL.\n", tid));
+ g_pEEInterface->SetThreadDebuggerWord(NULL);
} // end can't stop region
- if(pFcd->action == HIJACK_ACTION_EXIT_HANDLED)
+ if (pFcd->action == HIJACK_ACTION_EXIT_HANDLED)
{
SPEW(fprintf(stderr, "0x%x D::FCHF: exiting with CONTINUE_EXECUTION\n", tid));
return EXCEPTION_CONTINUE_EXECUTION;
_ASSERTE(pFcd->action == HIJACK_ACTION_EXIT_UNHANDLED);
return EXCEPTION_CONTINUE_SEARCH;
}
-}
+}
#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
void GenericHijackFuncHelper()
#if DOSPEW
DWORD tid = GetCurrentThreadId();
#endif
+
+ // The Windows native break in thread does not have TLS storage allocated.
+ bool debuggerBreakInThread = (NtCurrentTeb()->ThreadLocalStoragePointer == NULL);
+
// Hijack filters are always in the can't stop range.
// The RS knows this b/c it knows which threads it hijacked.
// Bump up the CS counter so that any further calls in the LS can see this too.
// (This makes places where we assert that we're in a CS region happy).
- CantStopHolder hCantStop;
+ CantStopHolder hCantStop(!debuggerBreakInThread);
SPEW(fprintf(stderr, "0x%x D::GHF: in generic hijack.\n", tid));
// thread at an unsafe place and enable pgc. This will allow us to sync even with this thread hijacked.
bool disabled = false;
- Thread *pEEThread = g_pEEInterface->GetThread();
+ Thread *pEEThread = debuggerBreakInThread ? NULL : g_pEEInterface->GetThread();
- if ((pEEThread != NULL) && !(((UINT_PTR)pEEThread) & 0x01))
+ if (pEEThread != NULL)
{
disabled = g_pEEInterface->IsPreemptiveGCDisabled();
_ASSERTE(!disabled);
// thread's context before clearing the exception, so continuing will give a different result.)
DWORD continueType = 0;
- pEEThread = g_pEEInterface->GetThread();
+ void* threadDebuggerWord = g_pEEInterface->GetThreadDebuggerWord();
- if (((UINT_PTR)pEEThread) & 0x01)
- {
- // There is no EE Thread for this thread, so we null out the TLS word so we don't confuse the Runtime.
- continueType = 1;
- g_pEEInterface->SetEEThreadPtr(NULL);
- pEEThread = NULL;
- }
- else if (pEEThread)
+ if (pEEThread != NULL)
{
// We've got a Thread ptr, so get the continue type out of the thread's debugger word.
- continueType = (DWORD) g_pEEInterface->GetThreadDebuggerWord(pEEThread);
+ continueType = (DWORD)threadDebuggerWord;
_ASSERTE(pEEThread->GetInteropDebuggingHijacked());
pEEThread->SetInteropDebuggingHijacked(FALSE);
}
+ else if (threadDebuggerWord != NULL)
+ {
+ continueType = 1;
+ g_pEEInterface->SetThreadDebuggerWord(NULL);
+ }
SPEW(fprintf(stderr, "0x%x D::GHF: continued with %d.\n", tid, continueType));
pDebuggerRuntimeOffsets->m_signalHijackCompleteBPAddr = (void*) SignalHijackCompleteFlare;
pDebuggerRuntimeOffsets->m_excepNotForRuntimeBPAddr = (void*) ExceptionNotForRuntimeFlare;
pDebuggerRuntimeOffsets->m_notifyRSOfSyncCompleteBPAddr = (void*) NotifyRightSideOfSyncCompleteFlare;
+ pDebuggerRuntimeOffsets->m_debuggerWordTLSIndex = g_debuggerWordTLSIndex;
#if !defined(FEATURE_CORESYSTEM)
// Grab the address of RaiseException in kernel32 because we have to play some games with exceptions
&pDebuggerRuntimeOffsets->m_EEThreadStateNCOffset,
&pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledOffset,
&pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledValue,
- &pDebuggerRuntimeOffsets->m_EEThreadDebuggerWordOffset,
&pDebuggerRuntimeOffsets->m_EEThreadFrameOffset,
&pDebuggerRuntimeOffsets->m_EEThreadMaxNeededSize,
&pDebuggerRuntimeOffsets->m_EEThreadSteppingStateMask,
void *m_excepNotForRuntimeBPAddr;
void *m_notifyRSOfSyncCompleteBPAddr;
void *m_raiseExceptionAddr; // The address of kernel32!RaiseException in the debuggee
+ DWORD m_debuggerWordTLSIndex; // The TLS slot for the debugger word used in the debugger hijack functions
#endif // FEATURE_INTEROP_DEBUGGING
SIZE_T m_TLSIndex; // The TLS index the CLR is using to hold Thread objects
SIZE_T m_TLSIsSpecialIndex; // The index into the Predef block of the the "IsSpecial" status for a thread.
SIZE_T m_EEThreadStateNCOffset; // Offset of m_stateNC in a Thread
SIZE_T m_EEThreadPGCDisabledOffset; // Offset of the bit for whether PGC is disabled or not in a Thread
DWORD m_EEThreadPGCDisabledValue; // Value at m_EEThreadPGCDisabledOffset that equals "PGC disabled".
- SIZE_T m_EEThreadDebuggerWordOffset; // Offset of debugger word in a Thread
SIZE_T m_EEThreadFrameOffset; // Offset of the Frame ptr in a Thread
SIZE_T m_EEThreadMaxNeededSize; // Max memory to read to get what we need out of a Thread object
DWORD m_EEThreadSteppingStateMask; // Mask for Thread::TSNC_DebuggerIsStepping
DEFINE_DACVAR(ULONG, BOOL, SystemDomain__s_fForceInstrument, SystemDomain::s_fForceInstrument)
DEFINE_DACVAR(ULONG, PTR_SharedDomain, SharedDomain__m_pSharedDomain, SharedDomain::m_pSharedDomain)
-
+#ifdef FEATURE_INTEROP_DEBUGGING
+DEFINE_DACVAR(ULONG, DWORD, dac__g_debuggerWordTLSIndex, g_debuggerWordTLSIndex)
+#endif
DEFINE_DACVAR(ULONG, DWORD, dac__g_TlsIndex, g_TlsIndex)
#if defined(FEATURE_WINDOWSPHONE)
#define OFFSETOF__TLS__tls_CurrentThread (0x0)
#define OFFSETOF__TLS__tls_EETlsData (2*sizeof(void*))
-#ifdef _TARGET_WIN64_
+#ifdef DBG_TARGET_WIN64
#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x58
#else
#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x2c
#ifndef DACCESS_COMPILE
- virtual void SetEEThreadPtr(VOID* newPtr) = 0;
-
virtual StackWalkAction StackWalkFramesEx(Thread* pThread,
PREGDISPLAY pRD,
PSTACKWALKFRAMESCALLBACK pCallback,
virtual T_CONTEXT *GetThreadFilterContext(Thread *thread) = 0;
- virtual VOID *GetThreadDebuggerWord(Thread *thread) = 0;
+#ifdef FEATURE_INTEROP_DEBUGGING
+ virtual VOID *GetThreadDebuggerWord() = 0;
- virtual void SetThreadDebuggerWord(Thread *thread,
- VOID *dw) = 0;
+ virtual void SetThreadDebuggerWord(VOID *dw) = 0;
+#endif
virtual BOOL IsManagedNativeCode(const BYTE *address) = 0;
SIZE_T *pEEThreadStateNCOffset,
SIZE_T *pEEThreadPGCDisabledOffset,
DWORD *pEEThreadPGCDisabledValue,
- SIZE_T *pEEThreadDebuggerWordOffset,
SIZE_T *pEEThreadFrameOffset,
SIZE_T *pEEThreadMaxNeededSize,
DWORD *pEEThreadSteppingStateMask,
#ifndef DACCESS_COMPILE
-void EEDbgInterfaceImpl::SetEEThreadPtr(VOID* newPtr)
-{
- // Since this may be called from a Debugger Interop Hijack, the EEThread may be bogus.
- // Thus we can't use contracts. If we do fix that, then the contract below would be nice...
-#if 0
- CONTRACTL
- {
- NOTHROW;
- GC_NOTRIGGER;
-
- PRECONDITION(GetThread() == NULL); // shouldn't have an EE thread.
- }
- CONTRACTL_END;
-#endif
- // This should only be called by interop-debugging when we don't have an EE thread
- // object.
-
- // Normally the LS & RS can communicate a pointer value using the EE thread's
- // m_debuggerWord field. If we have no EE thread, then we can use the
- // TLS slot that the EE thread would have been in.
-
- SetThread((Thread*)newPtr);
-}
-
StackWalkAction EEDbgInterfaceImpl::StackWalkFramesEx(Thread* pThread,
PREGDISPLAY pRD,
PSTACKWALKFRAMESCALLBACK pCallback,
RETURN thread->GetFilterContext();
}
-VOID * EEDbgInterfaceImpl::GetThreadDebuggerWord(Thread *thread)
-{
- CONTRACTL
- {
- SO_NOT_MAINLINE;
- NOTHROW;
- GC_NOTRIGGER;
- PRECONDITION(CheckPointer(thread));
- }
- CONTRACTL_END;
+#ifdef FEATURE_INTEROP_DEBUGGING
- return thread->m_debuggerWord;
+VOID * EEDbgInterfaceImpl::GetThreadDebuggerWord()
+{
+ return UnsafeTlsGetValue(g_debuggerWordTLSIndex);
}
-void EEDbgInterfaceImpl::SetThreadDebuggerWord(Thread *thread,
- VOID *dw)
+void EEDbgInterfaceImpl::SetThreadDebuggerWord(VOID *dw)
{
- CONTRACTL
- {
- SO_NOT_MAINLINE;
- NOTHROW;
- GC_NOTRIGGER;
- PRECONDITION(CheckPointer(thread));
- }
- CONTRACTL_END;
-
- thread->m_debuggerWord = dw;
+ UnsafeTlsSetValue(g_debuggerWordTLSIndex, dw);
}
+#endif
+
BOOL EEDbgInterfaceImpl::IsManagedNativeCode(const BYTE *address)
{
WRAPPER_NO_CONTRACT;
SIZE_T *pEEThreadStateNCOffset,
SIZE_T *pEEThreadPGCDisabledOffset,
DWORD *pEEThreadPGCDisabledValue,
- SIZE_T *pEEThreadDebuggerWordOffset,
SIZE_T *pEEThreadFrameOffset,
SIZE_T *pEEThreadMaxNeededSize,
DWORD *pEEThreadSteppingStateMask,
PRECONDITION(CheckPointer(pEEThreadStateNCOffset));
PRECONDITION(CheckPointer(pEEThreadPGCDisabledOffset));
PRECONDITION(CheckPointer(pEEThreadPGCDisabledValue));
- PRECONDITION(CheckPointer(pEEThreadDebuggerWordOffset));
PRECONDITION(CheckPointer(pEEThreadFrameOffset));
PRECONDITION(CheckPointer(pEEThreadMaxNeededSize));
PRECONDITION(CheckPointer(pEEThreadSteppingStateMask));
*pEEThreadStateNCOffset = Thread::GetOffsetOfStateNC();
*pEEThreadPGCDisabledOffset = Thread::GetOffsetOfGCFlag();
*pEEThreadPGCDisabledValue = 1; // A little obvious, but just in case...
- *pEEThreadDebuggerWordOffset = Thread::GetOffsetOfDebuggerWord();
*pEEThreadFrameOffset = Thread::GetOffsetOfCurrentFrame();
*pEEThreadMaxNeededSize = sizeof(Thread);
*pEEThreadDebuggerFilterContextOffset = Thread::GetOffsetOfDebuggerFilterContext();
Thread* GetThread(void);
- void SetEEThreadPtr(VOID* newPtr);
-
StackWalkAction StackWalkFramesEx(Thread* pThread,
PREGDISPLAY pRD,
PSTACKWALKFRAMESCALLBACK pCallback,
T_CONTEXT *GetThreadFilterContext(Thread *thread);
- VOID *GetThreadDebuggerWord(Thread *thread);
+#ifdef FEATURE_INTEROP_DEBUGGING
+ VOID *GetThreadDebuggerWord();
- void SetThreadDebuggerWord(Thread *thread,
- VOID *dw);
+ VOID SetThreadDebuggerWord(VOID *dw);
+#endif
BOOL IsManagedNativeCode(const BYTE *address);
SIZE_T *pEEThreadStateNCOffset,
SIZE_T *pEEThreadPGCDisabledOffset,
DWORD *pEEThreadPGCDisabledValue,
- SIZE_T *pEEThreadDebuggerWordOffset,
SIZE_T *pEEThreadFrameOffset,
SIZE_T *pEEThreadMaxNeededSize,
DWORD *pEEThreadSteppingStateMask,
#ifndef DACCESS_COMPILE
-
-
BOOL Thread::s_fCleanFinalizedThread = FALSE;
Volatile<LONG> Thread::s_threadPoolCompletionCountOverflow = 0;
NULL, // m_EETlsData
};
} // extern "C"
+
// index into TLS Array. Definition added by compiler
EXTERN_C UINT32 _tls_index;
#ifndef DACCESS_COMPILE
+
BOOL SetThread(Thread* t)
{
LIMITED_METHOD_CONTRACT
fOK = SetAppDomain(pThread->GetDomain());
_ASSERTE (fOK);
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Ensure that debugger word slot is allocated
+ UnsafeTlsSetValue(g_debuggerWordTLSIndex, 0);
+#endif
+
// We now have a Thread object visable to the RS. unmark special status.
hCantStop.Release();
}
#ifndef FEATURE_PAL
-
_ASSERTE(GetThread() == NULL);
PTEB Teb = NtCurrentTeb();
_ASSERTE(g_TrapReturningThreads == 0);
#endif // !FEATURE_PAL
+#ifdef FEATURE_INTEROP_DEBUGGING
+ g_debuggerWordTLSIndex = UnsafeTlsAlloc();
+ if (g_debuggerWordTLSIndex == TLS_OUT_OF_INDEXES)
+ COMPlusThrowWin32();
+#endif
+
__ClrFlsGetBlock = CExecutionEngine::GetTlsData;
IfFailThrow(Thread::CLRSetThreadStackGuarantee(Thread::STSGuarantee_Force));
m_debuggerFilterContext = NULL;
m_debuggerCantStop = 0;
- m_debuggerWord = NULL;
m_fInteropDebuggingHijacked = FALSE;
m_profilerCallbackState = 0;
#ifdef FEATURE_PROFAPI_ATTACH_DETACH
}
//---------------------------------------------------------------
- // Expose offset of the debugger word for the debugger
- //---------------------------------------------------------------
- static SIZE_T GetOffsetOfDebuggerWord()
- {
- LIMITED_METHOD_CONTRACT;
- return (SIZE_T)(offsetof(class Thread, m_debuggerWord));
- }
-
- //---------------------------------------------------------------
// Expose offset of the debugger cant stop count for the debugger
//---------------------------------------------------------------
static SIZE_T GetOffsetOfCantStop()
DWORD m_debuggerCantStop;
//---------------------------------------------------------------
- // A word reserved for use by the CLR Debugging Services during
- // managed/unmanaged debugging.
- //---------------------------------------------------------------
- VOID* m_debuggerWord;
-
- //---------------------------------------------------------------
// The current custom notification data object (or NULL if none
// pending)
//---------------------------------------------------------------
GPTR_IMPL(RCWCleanupList,g_pRCWCleanupList);
#endif // FEATURE_COMINTEROP
+#ifdef FEATURE_INTEROP_DEBUGGING
+GVAL_IMPL_INIT(DWORD, g_debuggerWordTLSIndex, TLS_OUT_OF_INDEXES);
+#endif
GVAL_IMPL_INIT(DWORD, g_TlsIndex, TLS_OUT_OF_INDEXES);
#ifndef DACCESS_COMPILE
GPTR_DECL(MethodDesc, g_pObjectFinalizerMD);
+#ifdef FEATURE_INTEROP_DEBUGGING
+GVAL_DECL(DWORD, g_debuggerWordTLSIndex);
+#endif
GVAL_DECL(DWORD, g_TlsIndex);
// Global System Information