summaryrefslogtreecommitdiff
path: root/src/debug
diff options
context:
space:
mode:
authorMike McLaughlin <mikem@microsoft.com>2018-02-26 15:19:00 -0800
committerGitHub <noreply@github.com>2018-02-26 15:19:00 -0800
commit759f0c84fcffa058e5d2ed9ba8a13e47fd28ee5c (patch)
treeb5b29228718c1145154fd7b60e8f674b522e7f80 /src/debug
parent3cb7c002e1325a7614961fd10e9a9f989528370f (diff)
downloadcoreclr-759f0c84fcffa058e5d2ed9ba8a13e47fd28ee5c.tar.gz
coreclr-759f0c84fcffa058e5d2ed9ba8a13e47fd28ee5c.tar.bz2
coreclr-759f0c84fcffa058e5d2ed9ba8a13e47fd28ee5c.zip
Fixed mixed mode attach/JIT debugging. (#16552)
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.
Diffstat (limited to 'src/debug')
-rw-r--r--src/debug/di/process.cpp7
-rw-r--r--src/debug/di/rspriv.h45
-rw-r--r--src/debug/di/rsthread.cpp322
-rw-r--r--src/debug/ee/debugger.cpp103
-rw-r--r--src/debug/ee/rcthread.cpp2
-rw-r--r--src/debug/inc/dbgipcevents.h2
6 files changed, 197 insertions, 284 deletions
diff --git a/src/debug/di/process.cpp b/src/debug/di/process.cpp
index b6d49c218d..533a420f47 100644
--- a/src/debug/di/process.cpp
+++ b/src/debug/di/process.cpp
@@ -7551,6 +7551,8 @@ HRESULT CordbProcess::GetRuntimeOffsets()
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",
@@ -7563,8 +7565,6 @@ HRESULT CordbProcess::GetRuntimeOffsets()
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",
@@ -12132,6 +12132,7 @@ Reaction CordbProcess::TriageExcep1stChanceAndInit(CordbUnmanagedThread * pUnman
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
@@ -12377,6 +12378,8 @@ Reaction CordbProcess::TriageExcep1stChanceAndInit(CordbUnmanagedThread * pUnman
}
else
{
+ LOG((LF_CORDB, LL_INFO1000, "CP::TE1stCAI: Triage1stChanceNonSpecial\n"));
+
Reaction r(REACTION(cOOB));
HRESULT hrCheck = S_OK;;
EX_TRY
diff --git a/src/debug/di/rspriv.h b/src/debug/di/rspriv.h
index 41a04f284e..5077f31144 100644
--- a/src/debug/di/rspriv.h
+++ b/src/debug/di/rspriv.h
@@ -2316,7 +2316,7 @@ public:
CorDebugInterfaceVersion GetDebuggerVersion() const;
#ifdef FEATURE_CORESYSTEM
- HMODULE GetTargetCLR() { return m_targetCLR; }
+ HMODULE GetTargetCLR() { return m_targetCLR; }
#endif
private:
@@ -2338,7 +2338,7 @@ 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
};
@@ -2480,12 +2480,12 @@ public:
// 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
@@ -2730,6 +2730,7 @@ public:
{
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";
@@ -10590,43 +10591,17 @@ private:
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);
diff --git a/src/debug/di/rsthread.cpp b/src/debug/di/rsthread.cpp
index cd5e62932a..fae679a55f 100644
--- a/src/debug/di/rsthread.cpp
+++ b/src/debug/di/rsthread.cpp
@@ -3031,36 +3031,126 @@ REMOTE_PTR CordbUnmanagedThread::GetPreDefTlsSlot(SIZE_T slot, bool * pRead)
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
@@ -3068,45 +3158,44 @@ DWORD_PTR CordbUnmanagedThread::GetEEThreadValue()
{
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);
@@ -3115,103 +3204,46 @@ REMOTE_PTR CordbUnmanagedThread::GetClrModuleTlsDataAddress()
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
@@ -3224,22 +3256,12 @@ void CordbUnmanagedThread::CacheEEDebuggerWord()
*/
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
@@ -3257,53 +3279,7 @@ HRESULT CordbUnmanagedThread::GetEEDebuggerWord(REMOTE_PTR *pValue)
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);
}
/*
@@ -3326,21 +3302,12 @@ HRESULT CordbUnmanagedThread::GetEEThreadPtr(REMOTE_PTR *ppEEThread)
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;
@@ -3360,12 +3327,10 @@ void CordbUnmanagedThread::GetEEState(bool *threadStepping, bool *specialManaged
// 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;
}
@@ -3448,14 +3413,13 @@ bool CordbUnmanagedThread::IsCantStop()
}
_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");
diff --git a/src/debug/ee/debugger.cpp b/src/debug/ee/debugger.cpp
index c5b8a633bd..1f70f17d1f 100644
--- a/src/debug/ee/debugger.cpp
+++ b/src/debug/ee/debugger.cpp
@@ -13754,73 +13754,52 @@ LONG Debugger::FirstChanceSuspendHijackWorker(CONTEXT *pContext,
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);
@@ -13837,17 +13816,15 @@ LONG Debugger::FirstChanceSuspendHijackWorker(CONTEXT *pContext,
// 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);
@@ -13868,20 +13845,12 @@ LONG Debugger::FirstChanceSuspendHijackWorker(CONTEXT *pContext,
_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;
@@ -13892,7 +13861,7 @@ LONG Debugger::FirstChanceSuspendHijackWorker(CONTEXT *pContext,
_ASSERTE(pFcd->action == HIJACK_ACTION_EXIT_UNHANDLED);
return EXCEPTION_CONTINUE_SEARCH;
}
-}
+}
#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
void GenericHijackFuncHelper()
@@ -13900,11 +13869,15 @@ 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));
@@ -13916,9 +13889,9 @@ void GenericHijackFuncHelper()
// 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);
@@ -13940,23 +13913,21 @@ void GenericHijackFuncHelper()
// 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));
diff --git a/src/debug/ee/rcthread.cpp b/src/debug/ee/rcthread.cpp
index 58547411c1..f40fe16544 100644
--- a/src/debug/ee/rcthread.cpp
+++ b/src/debug/ee/rcthread.cpp
@@ -733,6 +733,7 @@ HRESULT DebuggerRCThread::SetupRuntimeOffsets(DebuggerIPCControlBlock * pDebugge
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
@@ -767,7 +768,6 @@ HRESULT DebuggerRCThread::SetupRuntimeOffsets(DebuggerIPCControlBlock * pDebugge
&pDebuggerRuntimeOffsets->m_EEThreadStateNCOffset,
&pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledOffset,
&pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledValue,
- &pDebuggerRuntimeOffsets->m_EEThreadDebuggerWordOffset,
&pDebuggerRuntimeOffsets->m_EEThreadFrameOffset,
&pDebuggerRuntimeOffsets->m_EEThreadMaxNeededSize,
&pDebuggerRuntimeOffsets->m_EEThreadSteppingStateMask,
diff --git a/src/debug/inc/dbgipcevents.h b/src/debug/inc/dbgipcevents.h
index 8a6786a378..b91bb5b549 100644
--- a/src/debug/inc/dbgipcevents.h
+++ b/src/debug/inc/dbgipcevents.h
@@ -129,6 +129,7 @@ struct MSLAYOUT DebuggerIPCRuntimeOffsets
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.
@@ -137,7 +138,6 @@ struct MSLAYOUT DebuggerIPCRuntimeOffsets
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