diff --git a/src/coreclr/src/pal/src/exception/machexception.cpp b/src/coreclr/src/pal/src/exception/machexception.cpp index aa7d7018f51b26..4acafddbce74ed 100644 --- a/src/coreclr/src/pal/src/exception/machexception.cpp +++ b/src/coreclr/src/pal/src/exception/machexception.cpp @@ -840,6 +840,11 @@ HijackFaultingThread( ts64.__rflags &= ~EFL_TF; } + if (fIsStackOverflow) + { + exceptionRecord.ExceptionCode = EXCEPTION_STACK_OVERFLOW; + } + exceptionRecord.ExceptionFlags = EXCEPTION_IS_SIGNAL; exceptionRecord.ExceptionRecord = NULL; exceptionRecord.ExceptionAddress = (void *)ts64.__rip; diff --git a/src/coreclr/src/pal/src/exception/seh.cpp b/src/coreclr/src/pal/src/exception/seh.cpp index f194156509d49f..ee022b45fa7ce3 100644 --- a/src/coreclr/src/pal/src/exception/seh.cpp +++ b/src/coreclr/src/pal/src/exception/seh.cpp @@ -264,24 +264,6 @@ SEHProcessException(PAL_SEHException* exception) // or in a jitter helper or it is a debugger breakpoint) if (g_safeExceptionCheckFunction(contextRecord, exceptionRecord)) { - if (exceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) - { - // Check if the failed access has hit a stack guard page. In such case, it - // was a stack probe that detected that there is not enough stack left. - void* stackLimit = CPalThread::GetStackLimit(); - void* stackOverflowBottom = (void*)((size_t)stackLimit - GetVirtualPageSize()); - // On some versions of glibc / platforms the stackLimit is an address of the guard page, on some - // it is right above the guard page. - // So consider SIGSEGV in one page above and below stack limit to be stack overflow. - void* stackOverflowTop = (void*)((size_t)stackLimit + GetVirtualPageSize()); - void* violationAddr = (void*)exceptionRecord->ExceptionInformation[1]; - - if ((violationAddr >= stackOverflowBottom) && (violationAddr < stackOverflowTop)) - { - exceptionRecord->ExceptionCode = EXCEPTION_STACK_OVERFLOW; - } - } - EnsureExceptionRecordsOnHeap(exception); if (g_hardwareExceptionHandler(exception)) { diff --git a/src/coreclr/src/pal/src/exception/signal.cpp b/src/coreclr/src/pal/src/exception/signal.cpp index 27ea7c2df015d0..b142bbd82f77b9 100644 --- a/src/coreclr/src/pal/src/exception/signal.cpp +++ b/src/coreclr/src/pal/src/exception/signal.cpp @@ -116,6 +116,10 @@ int g_common_signal_handler_context_locvar_offset = 0; // TOP of special stack for handling stack overflow volatile void* g_stackOverflowHandlerStack = NULL; + +// Flag that is or-ed with SIGSEGV to indicate that the SIGSEGV was a stack overflow +const int StackOverflowFlag = 0x40000000; + #endif // !HAVE_MACH_EXCEPTIONS /* public function definitions ************************************************/ @@ -529,7 +533,7 @@ static void sigsegv_handler(int code, siginfo_t *siginfo, void *context) } } - if (SwitchStackAndExecuteHandler(code, siginfo, context, (size_t)handlerStackTop)) + if (SwitchStackAndExecuteHandler(code | StackOverflowFlag, siginfo, context, (size_t)handlerStackTop)) { PROCAbort(); } @@ -841,7 +845,15 @@ static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext ucontext = (native_context_t *)sigcontext; g_common_signal_handler_context_locvar_offset = (int)((char*)&signalContextRecord - (char*)__builtin_frame_address(0)); - exceptionRecord.ExceptionCode = CONTEXTGetExceptionCodeForSignal(siginfo, ucontext); + if (code == (SIGSEGV | StackOverflowFlag)) + { + exceptionRecord.ExceptionCode = EXCEPTION_STACK_OVERFLOW; + code &= ~StackOverflowFlag; + } + else + { + exceptionRecord.ExceptionCode = CONTEXTGetExceptionCodeForSignal(siginfo, ucontext); + } exceptionRecord.ExceptionFlags = EXCEPTION_IS_SIGNAL; exceptionRecord.ExceptionRecord = NULL; exceptionRecord.ExceptionAddress = GetNativeContextPC(ucontext); diff --git a/src/coreclr/src/vm/exceptionhandling.cpp b/src/coreclr/src/vm/exceptionhandling.cpp index e8f835a43b6c8c..1612f395b1750c 100644 --- a/src/coreclr/src/vm/exceptionhandling.cpp +++ b/src/coreclr/src/vm/exceptionhandling.cpp @@ -5157,6 +5157,7 @@ BOOL IsSafeToHandleHardwareException(PCONTEXT contextRecord, PEXCEPTION_RECORD e return g_fEEStarted && ( exceptionRecord->ExceptionCode == STATUS_BREAKPOINT || exceptionRecord->ExceptionCode == STATUS_SINGLE_STEP || + exceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW || (IsSafeToCallExecutionManager() && ExecutionManager::IsManagedCode(controlPc)) || IsIPinVirtualStub(controlPc) || // access violation comes from DispatchStub of Interface call IsIPInMarkedJitHelper(controlPc)); @@ -5192,6 +5193,7 @@ BOOL HandleHardwareException(PAL_SEHException* ex) if (ex->GetExceptionRecord()->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { GetThread()->SetExecutingOnAltStack(); + Thread::VirtualUnwindToFirstManagedCallFrame(ex->GetContextRecord()); EEPolicy::HandleFatalStackOverflow(&ex->ExceptionPointers, FALSE); UNREACHABLE(); } diff --git a/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow.cs b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow.cs new file mode 100644 index 00000000000000..2cb9aa6ae82f49 --- /dev/null +++ b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow.cs @@ -0,0 +1,152 @@ +// 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. +using System; +using System.Threading; + +namespace TestStackOverflow +{ + struct LargeStruct256 + { + Guid g0; + Guid g1; + Guid g2; + Guid g3; + Guid g4; + Guid g5; + Guid g6; + Guid g7; + Guid g8; + Guid g9; + Guid ga; + Guid gb; + Guid gc; + Guid gd; + Guid ge; + Guid gf; + } + + struct LargeStruct4096 + { + LargeStruct256 s0; + LargeStruct256 s1; + LargeStruct256 s2; + LargeStruct256 s3; + LargeStruct256 s4; + LargeStruct256 s5; + LargeStruct256 s6; + LargeStruct256 s7; + LargeStruct256 s8; + LargeStruct256 s9; + LargeStruct256 sa; + LargeStruct256 sb; + LargeStruct256 sc; + LargeStruct256 sd; + LargeStruct256 se; + LargeStruct256 sf; + } + + struct LargeStruct65536 + { + LargeStruct4096 s0; + LargeStruct4096 s1; + LargeStruct4096 s2; + LargeStruct4096 s3; + LargeStruct4096 s4; + LargeStruct4096 s5; + LargeStruct4096 s6; + LargeStruct4096 s7; + LargeStruct4096 s8; + LargeStruct4096 s9; + LargeStruct4096 sa; + LargeStruct4096 sb; + LargeStruct4096 sc; + LargeStruct4096 sd; + LargeStruct4096 se; + LargeStruct4096 sf; + } + class Program + { + static void InfiniteRecursionA() + { + InfiniteRecursionB(); + } + + static void InfiniteRecursionB() + { + InfiniteRecursionC(); + } + static void InfiniteRecursionC() + { + InfiniteRecursionA(); + } + + static void InfiniteRecursionA2() + { + LargeStruct65536 s; + InfiniteRecursionB2(); + } + + static void InfiniteRecursionB2() + { + LargeStruct65536 s; + InfiniteRecursionC2(); + } + + static void InfiniteRecursionC2() + { + LargeStruct65536 s; + InfiniteRecursionA2(); + } + + static void MainThreadTest(bool smallframe) + { + if (smallframe) + { + InfiniteRecursionA(); + } + else + { + InfiniteRecursionA2(); + } + } + + static void SecondaryThreadsTest(bool smallframe) + { + Thread[] threads = new Thread[32]; + for (int i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(() => { + if (smallframe) + { + InfiniteRecursionA(); + } + else + { + InfiniteRecursionA2(); + } + }); + threads[i].Start(); + } + + for (int i = 0; i < threads.Length; i++) + { + threads[i].Join(); + } + } + + static void Main(string[] args) + { + bool smallframe = (args[0] == "smallframe"); + if (args[1] == "secondary") + { + SecondaryThreadsTest(smallframe); + } + else if (args[1] == "main") + { + MainThreadTest(smallframe); + } + } + } +} + diff --git a/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow.csproj b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow.csproj new file mode 100644 index 00000000000000..119fceb14efc96 --- /dev/null +++ b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow.csproj @@ -0,0 +1,12 @@ + + + Exe + false + BuildOnly + 0 + + + + + + diff --git a/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow3.cs b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow3.cs new file mode 100644 index 00000000000000..bb53a0fb324c98 --- /dev/null +++ b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow3.cs @@ -0,0 +1,33 @@ +// 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. +using System; + +namespace TestStackOverflow3 +{ + class Program + { + private const int MAX_RECURSIVE_CALLS = 1000000; + static int ctr = 0; + + public static void Main() + { + Program ex = new Program(); + ex.Execute(); + } + + private unsafe void Execute(string arg1 = "") + { + long* bar = stackalloc long [1000]; + ctr++; + if (ctr % 50 == 0) + Console.WriteLine("Call number {0} to the Execute method", ctr); + + if (ctr <= MAX_RECURSIVE_CALLS) + Execute(string.Format("{0}", (IntPtr)bar)); + + ctr--; + } + } +} + diff --git a/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow3.csproj b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow3.csproj new file mode 100644 index 00000000000000..d75dd6ecbbe3d6 --- /dev/null +++ b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflow3.csproj @@ -0,0 +1,13 @@ + + + Exe + false + true + BuildOnly + 0 + + + + + + diff --git a/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflowtester.cs new file mode 100644 index 00000000000000..f30305658c1007 --- /dev/null +++ b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -0,0 +1,271 @@ +// 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. +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace TestStackOverflow +{ + class Program + { + static string s_corerunPath; + static string s_currentPath; + + static bool TestStackOverflow(string testName, string testArgs, out List stderrLines) + { + Console.WriteLine($"Running {testName} test({testArgs})"); + List lines = new List(); + + Process testProcess = new Process(); + + testProcess.StartInfo.FileName = s_corerunPath; + testProcess.StartInfo.Arguments = $"{Path.Combine(s_currentPath, "..", testName, $"{testName}.dll")} {testArgs}"; + testProcess.StartInfo.UseShellExecute = false; + testProcess.StartInfo.RedirectStandardError = true; + testProcess.ErrorDataReceived += (sender, line) => + { + Console.WriteLine($"\"{line.Data}\""); + if (!string.IsNullOrEmpty(line.Data)) + { + lines.Add(line.Data); + } + }; + + testProcess.Start(); + testProcess.BeginErrorReadLine(); + testProcess.WaitForExit(); + testProcess.CancelErrorRead(); + + stderrLines = lines; + + int expectedExitCode; + if ((Environment.OSVersion.Platform == PlatformID.Unix) || (Environment.OSVersion.Platform == PlatformID.MacOSX)) + { + expectedExitCode = 128 + 6; + } + else + { + expectedExitCode = unchecked((int)0xC00000FD); + } + + if (testProcess.ExitCode != expectedExitCode) + { + Console.WriteLine($"Exit code: 0x{testProcess.ExitCode:X8}, expected 0x{expectedExitCode:X8}"); + return false; + } + + if (lines[0] != "Stack overflow.") + { + Console.WriteLine("Missing \"Stack overflow.\" at the first line"); + return false; + } + + return true; + } + + static bool TestStackOverflowSmallFrameMainThread() + { + List lines; + if (TestStackOverflow("stackoverflow", "smallframe main", out lines)) + { + if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow.Program.Main(System.String[])")) + { + Console.WriteLine("Missing \"Main\" method frame at the last line"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionA\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionB\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionC\" method frame"); + return false; + } + + return true; + } + + return false; + } + + static bool TestStackOverflowLargeFrameMainThread() + { + List lines; + if (TestStackOverflow("stackoverflow", "largeframe main", out lines)) + { + if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow.Program.Main(System.String[])")) + { + Console.WriteLine("Missing \"Main\" method frame at the last line"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.MainThreadTest(Boolean)"))) + { + Console.WriteLine("Missing \"MainThreadTest\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA2()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionA2\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB2()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionB2\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC2()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionC2\" method frame"); + return false; + } + + return true; + } + + return false; + } + + static bool TestStackOverflowSmallFrameSecondaryThread() + { + List lines; + if (TestStackOverflow("stackoverflow", "smallframe secondary", out lines)) + { + if (!lines[lines.Count - 1].EndsWith("at System.Threading.ThreadHelper.ThreadStart()")) + { + Console.WriteLine("Missing \"System.Threading.ThreadHelper.ThreadStart\" method frame at the last line"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionA\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionB\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionC\" method frame"); + return false; + } + + return true; + } + + return false; + } + + static bool TestStackOverflowLargeFrameSecondaryThread() + { + List lines; + if (TestStackOverflow("stackoverflow", "largeframe secondary", out lines)) + { + if (!lines[lines.Count - 1].EndsWith("at System.Threading.ThreadHelper.ThreadStart()")) + { + Console.WriteLine("Missing \"System.Threading.ThreadHelper.ThreadStart\" method frame at the last line"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA2()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionA2\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.InfiniteRecursionB2()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionB2\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.InfiniteRecursionC2()"))) + { + Console.WriteLine("Missing \"InfiniteRecursionC2\" method frame"); + return false; + } + + return true; + } + + return false; + } + + static bool TestStackOverflow3() + { + List lines; + if (TestStackOverflow("stackoverflow3", "", out lines)) + { + if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow3.Program.Main()")) + { + Console.WriteLine("Missing \"Main\" method frame at the last line"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow3.Program.Execute(System.String)"))) + { + Console.WriteLine("Missing \"Execute\" method frame"); + return false; + } + + return true; + } + + return false; + } + + static int Main(string[] args) + { + s_currentPath = Directory.GetCurrentDirectory(); + s_corerunPath = Path.Combine(Environment.GetEnvironmentVariable("CORE_ROOT"), "corerun"); + + if (!TestStackOverflowSmallFrameMainThread()) + { + return 101; + } + + if (!TestStackOverflowLargeFrameMainThread()) + { + return 102; + } + + if (!TestStackOverflowSmallFrameSecondaryThread()) + { + return 103; + } + + if (!TestStackOverflowLargeFrameSecondaryThread()) + { + return 104; + } + + if (!TestStackOverflow3()) + { + return 105; + } + + return 100; + } + } +} diff --git a/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj new file mode 100644 index 00000000000000..5cb94c1225699b --- /dev/null +++ b/src/coreclr/tests/src/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj @@ -0,0 +1,12 @@ + + + Exe + false + BuildAndRun + 0 + + + + + +