Improve stack overflow stack trace dumping (#33281)
authorJan Vorlicek <janvorli@microsoft.com>
Fri, 6 Mar 2020 22:48:35 +0000 (23:48 +0100)
committerGitHub <noreply@github.com>
Fri, 6 Mar 2020 22:48:35 +0000 (23:48 +0100)
It was discovered that the stack overflow stack trace dumping to console
doesn't work properly in some cases due to the fact that there was not
enough stack space left to display more complex method signatures on the
call stack.

To remove such fragility, this change updates the stack trace dumping by
running the actual dumping and stack walking on a new thread and
waiting for its completion. That means that we should be able to dump
any stack trace reliably.

src/coreclr/src/vm/eepolicy.cpp
src/coreclr/src/vm/threads.cpp

index c2a472ed766461cfcf43e82fee37d2a41feada50..d01c98883e53903dba3a741f4a963c7057848fb0 100644 (file)
@@ -851,16 +851,13 @@ public:
 // Return Value:
 //    None
 //
-inline void LogCallstackForLogWorker(bool isStackOverflow = false)
+inline void LogCallstackForLogWorker(Thread* pThread)
 {
     WRAPPER_NO_CONTRACT;
 
-    Thread* pThread = GetThread();
-    _ASSERTE (pThread);
-
     SmallStackSString WordAt;
 
-    if (isStackOverflow || !WordAt.LoadResource(CCompRC::Optional, IDS_ER_WORDAT))
+    if (!WordAt.LoadResource(CCompRC::Optional, IDS_ER_WORDAT))
     {
         WordAt.Set(W("   at"));
     }
@@ -872,7 +869,7 @@ inline void LogCallstackForLogWorker(bool isStackOverflow = false)
 
     CallStackLogger logger;
 
-    pThread->StackWalkFrames(&CallStackLogger::LogCallstackForLogCallback, &logger, QUICKUNWIND | FUNCTIONSONLY);
+    pThread->StackWalkFrames(&CallStackLogger::LogCallstackForLogCallback, &logger, QUICKUNWIND | FUNCTIONSONLY | ALLOW_ASYNC_STACK_WALK);
 
     logger.PrintStackTrace(WordAt.GetUnicode());
 
@@ -929,7 +926,7 @@ void LogInfoForFatalError(UINT exitCode, LPCWSTR pszMessage, LPCWSTR errorSource
 
         if (pThread && errorSource == NULL)
         {
-            LogCallstackForLogWorker();
+            LogCallstackForLogWorker(GetThread());
 
             if (argExceptionString != NULL) {
                 PrintToStdErrW(argExceptionString);
@@ -1127,6 +1124,13 @@ void DisplayStackOverflowException()
     PrintToStdErrA("Stack overflow.\n");
 }
 
+DWORD LogStackOverflowStackTraceThread(void* arg)
+{
+    LogCallstackForLogWorker((Thread*)arg);
+
+    return 0;
+}
+
 void DECLSPEC_NORETURN EEPolicy::HandleFatalStackOverflow(EXCEPTION_POINTERS *pExceptionInfo, BOOL fSkipDebugger)
 {
     // This is fatal error.  We do not care about SO mode any more.
@@ -1155,7 +1159,15 @@ void DECLSPEC_NORETURN EEPolicy::HandleFatalStackOverflow(EXCEPTION_POINTERS *pE
     if (InterlockedCompareExchange(&g_stackOverflowCallStackLogged, 1, 0) == 0)
     {
         DisplayStackOverflowException();
-        LogCallstackForLogWorker(true /* isStackOverflow */);
+
+        HandleHolder stackDumpThreadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, LogStackOverflowStackTraceThread, GetThread(), W(".NET Stack overflow trace logger"));
+        if (stackDumpThreadHandle != INVALID_HANDLE_VALUE)
+        {
+            // Wait for the stack trace logging completion
+            DWORD res = WaitForSingleObject(stackDumpThreadHandle, INFINITE);
+            _ASSERTE(res == WAIT_OBJECT_0);
+        }
+
         g_stackOverflowCallStackLogged = 2;
     }
     else
index 13f1a379d779c5091a3445e2e0519eec6ba6b23e..5c1814024e8ec809e3d352101ace4f030a30df89 100644 (file)
@@ -6475,7 +6475,7 @@ HRESULT Thread::CLRSetThreadStackGuarantee(SetThreadStackGuaranteeScope fScope)
         // -additionally, we need to provide some region to hosts to allow for lock acquisition in a hosted scenario
         //
         EXTRA_PAGES = 3;
-        INDEBUG(EXTRA_PAGES += 3);
+        INDEBUG(EXTRA_PAGES += 1);
 
         int ThreadGuardPages = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ThreadGuardPages);
         if (ThreadGuardPages == 0)
@@ -6489,7 +6489,7 @@ HRESULT Thread::CLRSetThreadStackGuarantee(SetThreadStackGuaranteeScope fScope)
 
 #else // HOST_64BIT
 #ifdef _DEBUG
-        uGuardSize += (3 * GetOsPageSize());    // three extra pages for debug infrastructure
+        uGuardSize += (1 * GetOsPageSize());    // one extra page for debug infrastructure
 #endif // _DEBUG
 #endif // HOST_64BIT