From 82606301e1c4f23bac9d8ebed015036cd99a58d1 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Thu, 19 Oct 2023 19:49:48 +0000 Subject: [PATCH 01/18] Report stack overflow in linux Use SA_ONSTACK for SIGSEGV handler Call sigaltstack() to set the alternate stack on the main thread only Check if faulting address is near the stack pointer, if so, report stack overflow --- .../Runtime/unix/HardwareExceptions.cpp | 78 +++++++++++++++---- .../nativeaot/Runtime/unix/UnixSignals.cpp | 4 +- .../nativeaot/Runtime/unix/UnixSignals.h | 2 +- 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp index 03ee5d514c3dc0..b27e11fafc7f1f 100644 --- a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp @@ -1,8 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#include "PalRedhawk.h" + #include "CommonTypes.h" #include "PalRedhawkCommon.h" +// #include "RhConfig.h" +// #include "rhassert.h" #include "CommonMacros.h" #include "config.h" #include "daccess.h" @@ -545,6 +549,37 @@ bool HardwareExceptionHandler(int code, siginfo_t *siginfo, void *context, void* // Handler for the SIGSEGV signal void SIGSEGVHandler(int code, siginfo_t *siginfo, void *context) { + const char StackOverflowMessage[] = "Stack overflow.\n"; + // First check if we have a stack overflow + size_t sp = ((UNIX_CONTEXT *)context)->GetSp(); + size_t failureAddress = (size_t)siginfo->si_addr; + + // If the failure address is at most one page above or below the stack pointer, + // we have a stack overflow. + if ((failureAddress - (sp - getpagesize())) < 2 * getpagesize()) + { + (void)!write(STDOUT_FILENO, StackOverflowMessage, sizeof(StackOverflowMessage) - 1); + // RhFailFast(); + // if (GetCurrentPalThread()) + // { + // size_t handlerStackTop = __sync_val_compare_and_swap((size_t*)&g_stackOverflowHandlerStack, (size_t)g_stackOverflowHandlerStack, 0); + // if (handlerStackTop == 0) + // { + // // We have only one stack for handling stack overflow preallocated. We let only the first thread that hits stack overflow to + // // run the exception handling code on that stack (which ends up just dumping the stack trace and aborting the process). + // // Other threads are held spinning and sleeping here until the process exits. + // while (true) + // { + // sleep(1); + // } + // } + + // if (SwitchStackAndExecuteHandler(code | StackOverflowFlag, siginfo, context, (size_t)handlerStackTop)) + // { + // PROCAbort(SIGSEGV, siginfo); + // } + // } + } bool isHandled = HardwareExceptionHandler(code, siginfo, context, siginfo->si_addr); if (isHandled) { @@ -589,7 +624,8 @@ void SIGFPEHandler(int code, siginfo_t *siginfo, void *context) // Initialize hardware exception handling bool InitializeHardwareExceptionHandling() { - if (!AddSignalHandler(SIGSEGV, SIGSEGVHandler, &g_previousSIGSEGV)) + // Run SIGSEGV handler on separate stack so we can handle stack overflow. Otherwise, the current (invalid) stack is used and another segfault is raised. + if (!AddSignalHandler(SIGSEGV, SIGSEGVHandler, &g_previousSIGSEGV, SA_ONSTACK)) { return false; } @@ -599,25 +635,39 @@ bool InitializeHardwareExceptionHandling() return false; } + stack_t oldstack; + stack_t newstack; + newstack.ss_size = 1024 * 1024 * 2 > SIGSTKSZ ? 1024 * 1024 * 2 : SIGSTKSZ; + newstack.ss_sp = malloc(newstack.ss_size); + if (newstack.ss_sp == NULL) + { + return false; + } + newstack.ss_flags = 0; + if (0 != sigaltstack(&newstack, &oldstack)) + { + return false; + } + #if defined(HOST_APPLE) #ifndef HOST_TVOS // task_set_exception_ports is not supported on tvOS - // LLDB installs task-wide Mach exception handlers. XNU dispatches Mach - // exceptions first to any registered "activation" handler and then to - // any registered task handler before dispatching the exception to a - // host-wide Mach exception handler that does translation to POSIX - // signals. This makes it impossible to use LLDB with implicit null + // LLDB installs task-wide Mach exception handlers. XNU dispatches Mach + // exceptions first to any registered "activation" handler and then to + // any registered task handler before dispatching the exception to a + // host-wide Mach exception handler that does translation to POSIX + // signals. This makes it impossible to use LLDB with implicit null // checks in NativeAOT; continuing execution after LLDB traps an // EXC_BAD_ACCESS will result in LLDB's EXC_BAD_ACCESS handler being // invoked again. This also interferes with the translation of SIGFPEs // to .NET-level ArithmeticExceptions. Work around this here by - // installing a no-op task-wide Mach exception handler for - // EXC_BAD_ACCESS and EXC_ARITHMETIC. - kern_return_t kr = task_set_exception_ports( - mach_task_self(), - EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC, /* SIGSEGV, SIGFPE */ - MACH_PORT_NULL, - EXCEPTION_STATE_IDENTITY, - MACHINE_THREAD_STATE); + // installing a no-op task-wide Mach exception handler for + // EXC_BAD_ACCESS and EXC_ARITHMETIC. + kern_return_t kr = task_set_exception_ports( + mach_task_self(), + EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC, /* SIGSEGV, SIGFPE */ + MACH_PORT_NULL, + EXCEPTION_STATE_IDENTITY, + MACHINE_THREAD_STATE); ASSERT(kr == KERN_SUCCESS); #endif #endif diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixSignals.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixSignals.cpp index 33852920653ff6..813c6704b6213c 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixSignals.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixSignals.cpp @@ -9,11 +9,11 @@ #include "UnixSignals.h" // Add handler for hardware exception signal -bool AddSignalHandler(int signal, SignalHandler handler, struct sigaction* previousAction) +bool AddSignalHandler(int signal, SignalHandler handler, struct sigaction* previousAction, int additionalFlags) { struct sigaction newAction; - newAction.sa_flags = SA_RESTART; + newAction.sa_flags = SA_RESTART | additionalFlags; newAction.sa_handler = NULL; newAction.sa_sigaction = handler; newAction.sa_flags |= SA_SIGINFO; diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixSignals.h b/src/coreclr/nativeaot/Runtime/unix/UnixSignals.h index 60e08d461e3830..9980ffb5df9e5e 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixSignals.h +++ b/src/coreclr/nativeaot/Runtime/unix/UnixSignals.h @@ -14,7 +14,7 @@ typedef void (*SignalHandler)(int code, siginfo_t* siginfo, void* context); -bool AddSignalHandler(int signal, SignalHandler handler, struct sigaction* previousAction); +bool AddSignalHandler(int signal, SignalHandler handler, struct sigaction* previousAction, int additionalFlags = 0); void RestoreSignalHandler(int signal_id, struct sigaction* previousAction); #endif // __UNIX_SIGNALS_H__ From 263262cc92f1de68f0e27c2487ada3b69b0e7023 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Mon, 23 Oct 2023 23:38:19 +0000 Subject: [PATCH 02/18] Set up signal alternate stack on all new threads --- .../nativeaot/Runtime/inc/CommonTypes.h | 1 + src/coreclr/nativeaot/Runtime/thread.cpp | 101 +++++++++++++++++- src/coreclr/nativeaot/Runtime/thread.h | 12 ++- .../Runtime/unix/HardwareExceptions.cpp | 51 ++------- 4 files changed, 118 insertions(+), 47 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h b/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h index fde2d9247e07b9..bd0675971c0a8d 100644 --- a/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h +++ b/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h @@ -4,6 +4,7 @@ #ifndef __COMMON_TYPES_H__ #define __COMMON_TYPES_H__ +#include #include #include #include diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index 4c924524fd4d73..9d044c10d66091 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -4,6 +4,7 @@ #include "common.h" #include "CommonTypes.h" #include "CommonMacros.h" +#include "CommonMacros.inl" #include "daccess.h" #include "PalRedhawkCommon.h" #include "PalRedhawk.h" @@ -26,6 +27,8 @@ #include "stressLog.h" #include "RhConfig.h" #include "RhVolatile.h" +#include +#include #ifndef DACCESS_COMPILE @@ -281,6 +284,9 @@ void Thread::Construct() if (StressLog::StressLogOn(~0u, 0)) m_pThreadStressLog = StressLog::CreateThreadStressLog(this); #endif // STRESS_LOG +#ifdef TARGET_UNIX + EnsureSignalAlternateStack(); +#endif // TARGET_UNIX // Everything else should be initialized to 0 via the static initialization of tls_CurrentThread. @@ -297,6 +303,89 @@ void Thread::Construct() ASSERT(m_interruptedContext == NULL); } +#ifdef TARGET_UNIX +void Thread::FreeSignalAlternateStack() +{ + void *altstack = m_alternateStack; + m_alternateStack = nullptr; + + if (altstack != nullptr) + { + stack_t ss, oss; + // The man page for sigaltstack says that when the ss.ss_flags is set to SS_DISABLE, + // all other ss fields are ignored. However, MUSL implementation checks that the + // ss_size is >= MINSIGSTKSZ even in this case. + ss.ss_size = MINSIGSTKSZ; + ss.ss_flags = SS_DISABLE; + ss.ss_sp = NULL; + int st = sigaltstack(&ss, &oss); + if ((st == 0) && (oss.ss_flags != SS_DISABLE)) + { + // Make sure this altstack is this PAL's before freeing. + if (oss.ss_sp == altstack) + { + int st = munmap(oss.ss_sp, oss.ss_size); + _ASSERTE(st == 0); + } + } + } +} + +bool Thread::EnsureSignalAlternateStack() +{ + int st = 0; + + stack_t oss; + + // Query the current alternate signal stack + st = sigaltstack(NULL, &oss); + if ((st == 0) && (oss.ss_flags == SS_DISABLE)) + { + // There is no alternate stack for SIGSEGV handling installed yet so allocate one + + // We include the size of the SignalHandlerWorkerReturnPoint in the alternate stack size since the + // context contained in it is large and the SIGSTKSZ was not sufficient on ARM64 during testing. + int altStackSize = SIGSTKSZ + ALIGN_UP(sizeof(CONTEXT), 16) + getpagesize(); +#ifdef HAS_ADDRESS_SANITIZER + // Asan also uses alternate stack so we increase its size on the SIGSTKSZ * 4 that enough for asan + // (see kAltStackSize in compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cc) + altStackSize += SIGSTKSZ * 4; +#endif + altStackSize = ALIGN_UP(altStackSize, getpagesize()); + int flags = MAP_ANONYMOUS | MAP_PRIVATE; +#ifdef MAP_STACK + flags |= MAP_STACK; +#endif + void* altStack = mmap(NULL, altStackSize, PROT_READ | PROT_WRITE, flags, -1, 0); + if (altStack != MAP_FAILED) + { + // create a guard page for the alternate stack + st = mprotect(altStack, getpagesize(), PROT_NONE); + if (st == 0) + { + stack_t ss; + ss.ss_sp = (char*)altStack; + ss.ss_size = altStackSize; + ss.ss_flags = 0; + st = sigaltstack(&ss, NULL); + } + + if (st == 0) + { + m_alternateStack = altStack; + } + else + { + int st2 = munmap(altStack, altStackSize); + _ASSERTE(st2 == 0); + } + } + } + + return (st == 0); +} +#endif // TARGET_UNIX + bool Thread::IsInitialized() { return (m_ThreadStateFlags != TSF_Unknown); @@ -361,6 +450,10 @@ void Thread::Destroy() } #endif //FEATURE_SUSPEND_REDIRECTION +#ifdef TARGET_UNIX + FreeSignalAlternateStack(); +#endif + ASSERT(m_pGCFrameRegistrations == NULL); } @@ -866,19 +959,19 @@ void Thread::Unhijack() } // This unhijack routine is called to undo a hijack, that is potentially on a different thread. -// +// // Although there are many code sequences (here and in asm) to // perform an unhijack operation, they will never execute concurrently: -// +// // - A thread may unhijack itself at any time so long as it does that from unmanaged code while in coop mode. // This ensures that coop thread can access its stack synchronously. // Unhijacking from unmanaged code ensures that another thread will not attempt to hijack it, // since we only hijack threads that are executing managed code. -// +// // - A GC thread may access a thread asynchronously, including unhijacking it. // Asynchronously accessed thread must be in preemptive mode and should not // access the managed portion of its stack. -// +// // - A thread that owns the suspension can access another thread as long as the other thread is // in preemptive mode or suspended in managed code. // Either way the other thread cannot be accessing its hijack. diff --git a/src/coreclr/nativeaot/Runtime/thread.h b/src/coreclr/nativeaot/Runtime/thread.h index 7aa473eb6763d4..f2cb41510bea09 100644 --- a/src/coreclr/nativeaot/Runtime/thread.h +++ b/src/coreclr/nativeaot/Runtime/thread.h @@ -94,7 +94,7 @@ struct ThreadBuffer HANDLE m_hPalThread; // WARNING: this may legitimately be INVALID_HANDLE_VALUE void ** m_ppvHijackedReturnAddressLocation; void * m_pvHijackedReturnAddress; - uintptr_t m_uHijackedReturnValueFlags; + uintptr_t m_uHijackedReturnValueFlags; PTR_ExInfo m_pExInfoStackHead; Object* m_threadAbortException; // ThreadAbortException instance -set only during thread abort Object* m_pThreadLocalStatics; @@ -112,6 +112,9 @@ struct ThreadBuffer #ifdef FEATURE_GC_STRESS uint32_t m_uRand; // current per-thread random number #endif // FEATURE_GC_STRESS +#ifdef TARGET_UNIX + void * m_alternateStack; // ptr to alternate signal stack +#endif // UNIX }; struct ReversePInvokeFrame @@ -152,7 +155,7 @@ class Thread : private ThreadBuffer // For suspension APCs it is mostly harmless, but wasteful and in extreme // cases may force the target thread into stack oveflow. // We use this flag to avoid sending another APC when one is still going through. - // + // // On Unix this is an optimization to not queue up more signals when one is // still being processed. }; @@ -312,6 +315,11 @@ class Thread : private ThreadBuffer bool IsActivationPending(); void SetActivationPending(bool isPending); + +#ifdef TARGET_UNIX + bool EnsureSignalAlternateStack(); + void FreeSignalAlternateStack(); +#endif // TARGET_UNIX }; #ifndef __GCENV_BASE_INCLUDED__ diff --git a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp index b27e11fafc7f1f..a5085b349dab16 100644 --- a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp @@ -1,12 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#include "PalRedhawk.h" - #include "CommonTypes.h" -#include "PalRedhawkCommon.h" -// #include "RhConfig.h" -// #include "rhassert.h" +#include "PalRedhawk.h" #include "CommonMacros.h" #include "config.h" #include "daccess.h" @@ -15,6 +11,9 @@ #include "HardwareExceptions.h" #include "UnixSignals.h" #include "PalCreateDump.h" +#include "thread.h" +#include "threadstore.h" +#include #if defined(HOST_APPLE) #include @@ -549,7 +548,6 @@ bool HardwareExceptionHandler(int code, siginfo_t *siginfo, void *context, void* // Handler for the SIGSEGV signal void SIGSEGVHandler(int code, siginfo_t *siginfo, void *context) { - const char StackOverflowMessage[] = "Stack overflow.\n"; // First check if we have a stack overflow size_t sp = ((UNIX_CONTEXT *)context)->GetSp(); size_t failureAddress = (size_t)siginfo->si_addr; @@ -558,27 +556,8 @@ void SIGSEGVHandler(int code, siginfo_t *siginfo, void *context) // we have a stack overflow. if ((failureAddress - (sp - getpagesize())) < 2 * getpagesize()) { - (void)!write(STDOUT_FILENO, StackOverflowMessage, sizeof(StackOverflowMessage) - 1); - // RhFailFast(); - // if (GetCurrentPalThread()) - // { - // size_t handlerStackTop = __sync_val_compare_and_swap((size_t*)&g_stackOverflowHandlerStack, (size_t)g_stackOverflowHandlerStack, 0); - // if (handlerStackTop == 0) - // { - // // We have only one stack for handling stack overflow preallocated. We let only the first thread that hits stack overflow to - // // run the exception handling code on that stack (which ends up just dumping the stack trace and aborting the process). - // // Other threads are held spinning and sleeping here until the process exits. - // while (true) - // { - // sleep(1); - // } - // } - - // if (SwitchStackAndExecuteHandler(code | StackOverflowFlag, siginfo, context, (size_t)handlerStackTop)) - // { - // PROCAbort(SIGSEGV, siginfo); - // } - // } + PalPrintFatalError("\nProcess is terminating due to StackOverflowException.\n"); + RhFailFast(); } bool isHandled = HardwareExceptionHandler(code, siginfo, context, siginfo->si_addr); if (isHandled) @@ -634,20 +613,10 @@ bool InitializeHardwareExceptionHandling() { return false; } - - stack_t oldstack; - stack_t newstack; - newstack.ss_size = 1024 * 1024 * 2 > SIGSTKSZ ? 1024 * 1024 * 2 : SIGSTKSZ; - newstack.ss_sp = malloc(newstack.ss_size); - if (newstack.ss_sp == NULL) - { - return false; - } - newstack.ss_flags = 0; - if (0 != sigaltstack(&newstack, &oldstack)) - { - return false; - } +#ifdef TARGET_UNIX + Thread* curThread = ThreadStore::RawGetCurrentThread(); + curThread->EnsureSignalAlternateStack(); +#endif // TARGET_UNIX #if defined(HOST_APPLE) #ifndef HOST_TVOS // task_set_exception_ports is not supported on tvOS From 666d2311ac6265bf6efee7b72ffd8c968a1fe853 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Tue, 24 Oct 2023 00:46:53 +0000 Subject: [PATCH 03/18] Add test to naot smoke tests --- src/coreclr/nativeaot/Runtime/thread.cpp | 2 +- .../Runtime/unix/HardwareExceptions.cpp | 2 +- .../StackOverflowReporting/Program.cs | 50 +++++++++++++++++++ .../StackOverflowReporting.csproj | 12 +++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/tests/nativeaot/SmokeTests/StackOverflowReporting/Program.cs create mode 100644 src/tests/nativeaot/SmokeTests/StackOverflowReporting/StackOverflowReporting.csproj diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index 9d044c10d66091..b07b6451c3dd7d 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -4,8 +4,8 @@ #include "common.h" #include "CommonTypes.h" #include "CommonMacros.h" -#include "CommonMacros.inl" #include "daccess.h" +#include "CommonMacros.inl" #include "PalRedhawkCommon.h" #include "PalRedhawk.h" #include "rhassert.h" diff --git a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp index a5085b349dab16..e79557d3114d67 100644 --- a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "CommonTypes.h" -#include "PalRedhawk.h" +#include "PalRedhawkCommon.h" #include "CommonMacros.h" #include "config.h" #include "daccess.h" diff --git a/src/tests/nativeaot/SmokeTests/StackOverflowReporting/Program.cs b/src/tests/nativeaot/SmokeTests/StackOverflowReporting/Program.cs new file mode 100644 index 00000000000000..8c09b669479c0e --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/StackOverflowReporting/Program.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +public static class StackOverflowReporting +{ + public static int Main(string[] args) + { + if (args.Length == 1 && args[0].ToLowerInvariant() == "recurse") + { + Recursion(0); + return 0; + } + else + { + var directory = System.AppContext.BaseDirectory; + var exe = System.IO.Path.Combine(directory, "StackOverflowReporting"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + exe += ".exe"; + } + exe = Process.GetCurrentProcess().MainModule.FileName; + using (Process p = new Process()) + { + p.StartInfo.FileName = exe; + p.StartInfo.Arguments = "recurse"; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + p.Start(); + p.WaitForExit(); + var output = p.StandardOutput.ReadToEnd(); + var error = p.StandardError.ReadToEnd(); + if (output.Contains("StackOverflowException") || error.Contains("StackOverflowException")) + { + return 100; + } + else + { + return 1; + } + } + } + } + + public static void Recursion(int i) + { + Recursion(i + 1); + Recursion(i + 1); + } +} diff --git a/src/tests/nativeaot/SmokeTests/StackOverflowReporting/StackOverflowReporting.csproj b/src/tests/nativeaot/SmokeTests/StackOverflowReporting/StackOverflowReporting.csproj new file mode 100644 index 00000000000000..f890930e365604 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/StackOverflowReporting/StackOverflowReporting.csproj @@ -0,0 +1,12 @@ + + + + Exe + 0 + + + + + + + From c44e631126dd713662e7497b9fb9e13b6a52e43d Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Tue, 24 Oct 2023 05:16:42 +0000 Subject: [PATCH 04/18] Only allocate sigaltstack in Thread::Construct --- src/coreclr/nativeaot/Runtime/thread.cpp | 9 ++++++--- .../nativeaot/Runtime/unix/HardwareExceptions.cpp | 4 ---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index b07b6451c3dd7d..74068895767b0e 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -27,8 +27,11 @@ #include "stressLog.h" #include "RhConfig.h" #include "RhVolatile.h" + +#ifdef TARGET_UNIX #include #include +#endif #ifndef DACCESS_COMPILE @@ -345,13 +348,13 @@ bool Thread::EnsureSignalAlternateStack() // We include the size of the SignalHandlerWorkerReturnPoint in the alternate stack size since the // context contained in it is large and the SIGSTKSZ was not sufficient on ARM64 during testing. - int altStackSize = SIGSTKSZ + ALIGN_UP(sizeof(CONTEXT), 16) + getpagesize(); + int altStackSize = SIGSTKSZ + ALIGN_UP(sizeof(CONTEXT), 16) + PalOsPageSize(); #ifdef HAS_ADDRESS_SANITIZER // Asan also uses alternate stack so we increase its size on the SIGSTKSZ * 4 that enough for asan // (see kAltStackSize in compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cc) altStackSize += SIGSTKSZ * 4; #endif - altStackSize = ALIGN_UP(altStackSize, getpagesize()); + altStackSize = ALIGN_UP(altStackSize, PalOsPageSize()); int flags = MAP_ANONYMOUS | MAP_PRIVATE; #ifdef MAP_STACK flags |= MAP_STACK; @@ -360,7 +363,7 @@ bool Thread::EnsureSignalAlternateStack() if (altStack != MAP_FAILED) { // create a guard page for the alternate stack - st = mprotect(altStack, getpagesize(), PROT_NONE); + st = mprotect(altStack, PalOsPageSize(), PROT_NONE); if (st == 0) { stack_t ss; diff --git a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp index e79557d3114d67..9ed4bbb649602a 100644 --- a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp @@ -613,10 +613,6 @@ bool InitializeHardwareExceptionHandling() { return false; } -#ifdef TARGET_UNIX - Thread* curThread = ThreadStore::RawGetCurrentThread(); - curThread->EnsureSignalAlternateStack(); -#endif // TARGET_UNIX #if defined(HOST_APPLE) #ifndef HOST_TVOS // task_set_exception_ports is not supported on tvOS From 1976a3863ab28db42953d814b54300c5396219f5 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Tue, 24 Oct 2023 16:32:23 +0000 Subject: [PATCH 05/18] Remove other getpagesize calls and guard sigaltstack when using mach exceptions --- src/coreclr/nativeaot/Runtime/thread.cpp | 16 ++++++++-------- .../Runtime/unix/HardwareExceptions.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index 74068895767b0e..3140cf863c9f10 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -28,10 +28,10 @@ #include "RhConfig.h" #include "RhVolatile.h" -#ifdef TARGET_UNIX +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS #include #include -#endif +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS #ifndef DACCESS_COMPILE @@ -287,9 +287,9 @@ void Thread::Construct() if (StressLog::StressLogOn(~0u, 0)) m_pThreadStressLog = StressLog::CreateThreadStressLog(this); #endif // STRESS_LOG -#ifdef TARGET_UNIX +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS EnsureSignalAlternateStack(); -#endif // TARGET_UNIX +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS // Everything else should be initialized to 0 via the static initialization of tls_CurrentThread. @@ -306,7 +306,7 @@ void Thread::Construct() ASSERT(m_interruptedContext == NULL); } -#ifdef TARGET_UNIX +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS void Thread::FreeSignalAlternateStack() { void *altstack = m_alternateStack; @@ -387,7 +387,7 @@ bool Thread::EnsureSignalAlternateStack() return (st == 0); } -#endif // TARGET_UNIX +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS bool Thread::IsInitialized() { @@ -453,9 +453,9 @@ void Thread::Destroy() } #endif //FEATURE_SUSPEND_REDIRECTION -#ifdef TARGET_UNIX +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS FreeSignalAlternateStack(); -#endif +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS ASSERT(m_pGCFrameRegistrations == NULL); } diff --git a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp index 9ed4bbb649602a..db4b2f2a091049 100644 --- a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp @@ -554,7 +554,7 @@ void SIGSEGVHandler(int code, siginfo_t *siginfo, void *context) // If the failure address is at most one page above or below the stack pointer, // we have a stack overflow. - if ((failureAddress - (sp - getpagesize())) < 2 * getpagesize()) + if ((failureAddress - (sp - PalOsPageSize())) < 2 * PalOsPageSize()) { PalPrintFatalError("\nProcess is terminating due to StackOverflowException.\n"); RhFailFast(); From fa0511c29be112918e403382916e1e7d65548848 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Tue, 24 Oct 2023 21:46:14 +0000 Subject: [PATCH 06/18] Use CLR stackoverflow test for NAOT, don't use sigaltstack on tvos --- src/coreclr/nativeaot/Runtime/thread.cpp | 18 ++-- src/coreclr/nativeaot/Runtime/thread.h | 8 +- src/tests/Directory.Build.targets | 2 +- .../stackoverflow/stackoverflowtester.cs | 82 +++++++++++++++---- .../stackoverflow/stackoverflowtester.csproj | 4 +- src/tests/issues.targets | 6 +- .../StackOverflowReporting/Program.cs | 50 ----------- .../StackOverflowReporting.csproj | 12 --- 8 files changed, 87 insertions(+), 95 deletions(-) delete mode 100644 src/tests/nativeaot/SmokeTests/StackOverflowReporting/Program.cs delete mode 100644 src/tests/nativeaot/SmokeTests/StackOverflowReporting/StackOverflowReporting.csproj diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index 3140cf863c9f10..8972cd46ac9cb3 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -28,10 +28,10 @@ #include "RhConfig.h" #include "RhVolatile.h" -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) #include #include -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) #ifndef DACCESS_COMPILE @@ -287,9 +287,9 @@ void Thread::Construct() if (StressLog::StressLogOn(~0u, 0)) m_pThreadStressLog = StressLog::CreateThreadStressLog(this); #endif // STRESS_LOG -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) EnsureSignalAlternateStack(); -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) // Everything else should be initialized to 0 via the static initialization of tls_CurrentThread. @@ -306,7 +306,7 @@ void Thread::Construct() ASSERT(m_interruptedContext == NULL); } -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) void Thread::FreeSignalAlternateStack() { void *altstack = m_alternateStack; @@ -348,7 +348,7 @@ bool Thread::EnsureSignalAlternateStack() // We include the size of the SignalHandlerWorkerReturnPoint in the alternate stack size since the // context contained in it is large and the SIGSTKSZ was not sufficient on ARM64 during testing. - int altStackSize = SIGSTKSZ + ALIGN_UP(sizeof(CONTEXT), 16) + PalOsPageSize(); + int altStackSize = SIGSTKSZ; #ifdef HAS_ADDRESS_SANITIZER // Asan also uses alternate stack so we increase its size on the SIGSTKSZ * 4 that enough for asan // (see kAltStackSize in compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cc) @@ -387,7 +387,7 @@ bool Thread::EnsureSignalAlternateStack() return (st == 0); } -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) bool Thread::IsInitialized() { @@ -453,9 +453,9 @@ void Thread::Destroy() } #endif //FEATURE_SUSPEND_REDIRECTION -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) FreeSignalAlternateStack(); -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) ASSERT(m_pGCFrameRegistrations == NULL); } diff --git a/src/coreclr/nativeaot/Runtime/thread.h b/src/coreclr/nativeaot/Runtime/thread.h index f2cb41510bea09..e6c5754537ea03 100644 --- a/src/coreclr/nativeaot/Runtime/thread.h +++ b/src/coreclr/nativeaot/Runtime/thread.h @@ -112,9 +112,9 @@ struct ThreadBuffer #ifdef FEATURE_GC_STRESS uint32_t m_uRand; // current per-thread random number #endif // FEATURE_GC_STRESS -#ifdef TARGET_UNIX +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) void * m_alternateStack; // ptr to alternate signal stack -#endif // UNIX +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) }; struct ReversePInvokeFrame @@ -316,10 +316,10 @@ class Thread : private ThreadBuffer bool IsActivationPending(); void SetActivationPending(bool isPending); -#ifdef TARGET_UNIX +#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) bool EnsureSignalAlternateStack(); void FreeSignalAlternateStack(); -#endif // TARGET_UNIX +#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) }; #ifndef __GCENV_BASE_INCLUDED__ diff --git a/src/tests/Directory.Build.targets b/src/tests/Directory.Build.targets index 75299527a79251..184d541507f2c6 100644 --- a/src/tests/Directory.Build.targets +++ b/src/tests/Directory.Build.targets @@ -603,7 +603,7 @@ DependsOnTargets="Build;LinkNativeIfBuildAndRun" /> stderrLines) + static Process GetNativeAotProcess(string testName, string testArgs) + { + Process testProcess = new Process(); + string executableExtension = TestLibrary.Utilities.IsWindows ? ".exe" : ""; + testProcess.StartInfo.FileName = Path.Combine(s_currentPath, "..", testName, "native", $"{testName}{executableExtension}"); + testProcess.StartInfo.Arguments = testArgs; + return testProcess; + } + + static bool TestStackOverflow(string testName, string testArgs, out List stderrLines, out bool checkStackFrame) { 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}"; + Process testProcess; + if (TestLibrary.Utilities.IsNativeAot) + { + testProcess = GetNativeAotProcess(testName, testArgs); + } + else { + 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) => + testProcess.ErrorDataReceived += (sender, line) => { Console.WriteLine($"\"{line.Data}\""); if (!string.IsNullOrEmpty(line.Data)) @@ -39,6 +54,8 @@ static bool TestStackOverflow(string testName, string testArgs, out List testProcess.CancelErrorRead(); stderrLines = lines; + // NativeAOT doesn't provide a stack trace on stack overflow + checkStackFrame = !TestLibrary.Utilities.IsNativeAot; int[] expectedExitCodes; if ((Environment.OSVersion.Platform == PlatformID.Unix) || (Environment.OSVersion.Platform == PlatformID.MacOSX)) @@ -62,9 +79,23 @@ static bool TestStackOverflow(string testName, string testArgs, out List return false; } - if (lines[0] != "Stack overflow.") + string expectedMessage; + if (TestLibrary.Utilities.IsNativeAot) + { + expectedMessage = "Process is terminating due to StackOverflowException."; + } + else + { + expectedMessage = "Stack overflow."; + } + + if (lines.Count > 0 && lines[0] == expectedMessage) + { + return true; + } + else { - Console.WriteLine("Missing \"Stack overflow.\" at the first line"); + Console.WriteLine($"Missing \"{expectedMessage}\" at the first line"); return false; } @@ -74,8 +105,12 @@ static bool TestStackOverflow(string testName, string testArgs, out List static bool TestStackOverflowSmallFrameMainThread() { List lines; - if (TestStackOverflow("stackoverflow", "smallframe main", out lines)) + if (TestStackOverflow("stackoverflow", "smallframe main", out lines, out bool checkStackFrame)) { + if (!checkStackFrame) + { + return true; + } if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow.Program.Main(System.String[])")) { Console.WriteLine("Missing \"Main\" method frame at the last line"); @@ -115,8 +150,12 @@ static bool TestStackOverflowSmallFrameMainThread() static bool TestStackOverflowLargeFrameMainThread() { List lines; - if (TestStackOverflow("stackoverflow", "largeframe main", out lines)) + if (TestStackOverflow("stackoverflow", "largeframe main", out lines, out bool checkStackFrame)) { + if (!checkStackFrame) + { + return true; + } if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow.Program.Main(System.String[])")) { Console.WriteLine("Missing \"Main\" method frame at the last line"); @@ -156,8 +195,13 @@ static bool TestStackOverflowLargeFrameMainThread() static bool TestStackOverflowSmallFrameSecondaryThread() { List lines; - if (TestStackOverflow("stackoverflow", "smallframe secondary", out lines)) + if (TestStackOverflow("stackoverflow", "smallframe secondary", out lines, out bool checkStackFrame)) { + if (!checkStackFrame) + { + return true; + } + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.Test(Boolean)"))) { Console.WriteLine("Missing \"TestStackOverflow.Program.Test\" method frame"); @@ -191,8 +235,13 @@ static bool TestStackOverflowSmallFrameSecondaryThread() static bool TestStackOverflowLargeFrameSecondaryThread() { List lines; - if (TestStackOverflow("stackoverflow", "largeframe secondary", out lines)) + if (TestStackOverflow("stackoverflow", "largeframe secondary", out lines, out bool checkStackFrame)) { + if (!checkStackFrame) + { + return true; + } + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.Test(Boolean)"))) { Console.WriteLine("Missing \"TestStackOverflow.Program.Test\" method frame"); @@ -226,8 +275,13 @@ static bool TestStackOverflowLargeFrameSecondaryThread() static bool TestStackOverflow3() { List lines; - if (TestStackOverflow("stackoverflow3", "", out lines)) + if (TestStackOverflow("stackoverflow3", "", out lines, out bool checkStackFrame)) { + if (!checkStackFrame) + { + return true; + } + if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow3.Program.Main()")) { Console.WriteLine("Missing \"Main\" method frame at the last line"); @@ -249,7 +303,7 @@ static bool TestStackOverflow3() static int Main() { s_currentPath = Directory.GetCurrentDirectory(); - s_corerunPath = Path.Combine(Environment.GetEnvironmentVariable("CORE_ROOT"), "corerun"); + // s_corerunPath = Path.Combine(Environment.GetEnvironmentVariable("CORE_ROOT"), "corerun"); if (!TestStackOverflowSmallFrameMainThread()) { diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj index afed1789170b53..caefa32b1eecea 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj @@ -8,5 +8,7 @@ + + + - diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 406616c50c5206..0564600b170dba 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -89,7 +89,8 @@ - + + https://github.com/dotnet/runtime/issues/46175 @@ -727,9 +728,6 @@ https://github.com/dotnet/runtimelab/issues/155: Reflection.Emit - - Specific to CoreCLR - No crossgen folder under Core_Root diff --git a/src/tests/nativeaot/SmokeTests/StackOverflowReporting/Program.cs b/src/tests/nativeaot/SmokeTests/StackOverflowReporting/Program.cs deleted file mode 100644 index 8c09b669479c0e..00000000000000 --- a/src/tests/nativeaot/SmokeTests/StackOverflowReporting/Program.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -public static class StackOverflowReporting -{ - public static int Main(string[] args) - { - if (args.Length == 1 && args[0].ToLowerInvariant() == "recurse") - { - Recursion(0); - return 0; - } - else - { - var directory = System.AppContext.BaseDirectory; - var exe = System.IO.Path.Combine(directory, "StackOverflowReporting"); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - exe += ".exe"; - } - exe = Process.GetCurrentProcess().MainModule.FileName; - using (Process p = new Process()) - { - p.StartInfo.FileName = exe; - p.StartInfo.Arguments = "recurse"; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.RedirectStandardError = true; - p.Start(); - p.WaitForExit(); - var output = p.StandardOutput.ReadToEnd(); - var error = p.StandardError.ReadToEnd(); - if (output.Contains("StackOverflowException") || error.Contains("StackOverflowException")) - { - return 100; - } - else - { - return 1; - } - } - } - } - - public static void Recursion(int i) - { - Recursion(i + 1); - Recursion(i + 1); - } -} diff --git a/src/tests/nativeaot/SmokeTests/StackOverflowReporting/StackOverflowReporting.csproj b/src/tests/nativeaot/SmokeTests/StackOverflowReporting/StackOverflowReporting.csproj deleted file mode 100644 index f890930e365604..00000000000000 --- a/src/tests/nativeaot/SmokeTests/StackOverflowReporting/StackOverflowReporting.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - 0 - - - - - - - From 3f5803972abbafc37b8fc2bb99583c2e54915463 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Tue, 24 Oct 2023 23:00:42 +0000 Subject: [PATCH 07/18] Cast to size_t before comparison --- src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp index db4b2f2a091049..99682c36362f2e 100644 --- a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp @@ -554,7 +554,7 @@ void SIGSEGVHandler(int code, siginfo_t *siginfo, void *context) // If the failure address is at most one page above or below the stack pointer, // we have a stack overflow. - if ((failureAddress - (sp - PalOsPageSize())) < 2 * PalOsPageSize()) + if ((failureAddress - (sp - PalOsPageSize())) < (size_t)PalOsPageSize() * 2) { PalPrintFatalError("\nProcess is terminating due to StackOverflowException.\n"); RhFailFast(); From bda88cbeb31155b511439e449827d06ce1a23b37 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Wed, 25 Oct 2023 16:40:58 +0000 Subject: [PATCH 08/18] Uncomment code and fix for NAOT --- .../exceptions/stackoverflow/stackoverflowtester.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index 1b5a6abc91fd3f..60a55b66e0a627 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -303,7 +303,15 @@ static bool TestStackOverflow3() static int Main() { s_currentPath = Directory.GetCurrentDirectory(); - // s_corerunPath = Path.Combine(Environment.GetEnvironmentVariable("CORE_ROOT"), "corerun"); + if (TestLibrary.Utilities.IsNativeAot) + { + // CORE_ROOT is not set when running native AOT tests + s_corerunPath = ""; + } + else + { + s_corerunPath = Path.Combine(Environment.GetEnvironmentVariable("CORE_ROOT"), "corerun"); + } if (!TestStackOverflowSmallFrameMainThread()) { From aa7354a23fa7db5826bf0eff9fb776ff5da21c19 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Thu, 26 Oct 2023 23:29:08 +0000 Subject: [PATCH 09/18] Merge stackoverflow tests into one assembly --- .../exceptions/stackoverflow/stackoverflow.cs | 5 +++-- .../stackoverflow/stackoverflow.csproj | 11 ---------- .../stackoverflow/stackoverflow3.cs | 8 ++++---- .../stackoverflow/stackoverflow3.csproj | 12 ----------- .../stackoverflow/stackoverflowtester.cs | 20 +++++++++++++++---- .../stackoverflow/stackoverflowtester.csproj | 3 +++ 6 files changed, 26 insertions(+), 33 deletions(-) delete mode 100644 src/tests/baseservices/exceptions/stackoverflow/stackoverflow.csproj delete mode 100644 src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.csproj diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.cs index 8249f4bbb44aca..64aafb1238a473 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.cs @@ -65,7 +65,8 @@ struct LargeStruct65536 LargeStruct4096 se; LargeStruct4096 sf; } - class Program + + internal class StackOverflow { [MethodImpl(MethodImplOptions.NoInlining)] static void InfiniteRecursionA() @@ -134,7 +135,7 @@ static void SecondaryThreadsTest(bool smallframe) } } - static void Main(string[] args) + internal static void Run(string[] args) { bool smallframe = (args[0] == "smallframe"); if (args[1] == "secondary") diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.csproj b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.csproj deleted file mode 100644 index 552eb026fae0d2..00000000000000 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - Exe - false - BuildOnly - - - - - - diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.cs index c833bae8afc15a..62f6afd06fd62f 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.cs @@ -2,16 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -namespace TestStackOverflow3 +namespace TestStackOverflow { - class Program + internal class StackOverflow3 { private const int MAX_RECURSIVE_CALLS = 1000000; static int ctr = 0; - public static void Main() + public static void Run() { - Program ex = new Program(); + StackOverflow3 ex = new StackOverflow3(); ex.Execute(); } diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.csproj b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.csproj deleted file mode 100644 index 343785e075ffde..00000000000000 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - Exe - false - true - BuildOnly - - - - - - diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index 60a55b66e0a627..260d06cbc0647f 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -4,12 +4,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; namespace TestStackOverflow { class Program { + const string ThisProjectName = "stackoverflowtester"; static string s_corerunPath; static string s_currentPath; @@ -17,14 +19,15 @@ static Process GetNativeAotProcess(string testName, string testArgs) { Process testProcess = new Process(); string executableExtension = TestLibrary.Utilities.IsWindows ? ".exe" : ""; - testProcess.StartInfo.FileName = Path.Combine(s_currentPath, "..", testName, "native", $"{testName}{executableExtension}"); + testProcess.StartInfo.FileName = Path.Combine(s_currentPath, "native", $"{ThisProjectName}{executableExtension}"); testProcess.StartInfo.Arguments = testArgs; return testProcess; } static bool TestStackOverflow(string testName, string testArgs, out List stderrLines, out bool checkStackFrame) { - Console.WriteLine($"Running {testName} test({testArgs})"); + Console.WriteLine($"Running with args: {testName} {testArgs}"); + testArgs = $"{testName} {testArgs}"; List lines = new List(); Process testProcess; @@ -35,7 +38,7 @@ static bool TestStackOverflow(string testName, string testArgs, out List else { testProcess = new Process(); testProcess.StartInfo.FileName = s_corerunPath; - testProcess.StartInfo.Arguments = $"{Path.Combine(s_currentPath, "..", testName, $"{testName}.dll")} {testArgs}"; + testProcess.StartInfo.Arguments = $"{Path.Combine(s_currentPath, $"{ThisProjectName}.dll")} {testArgs}"; } testProcess.StartInfo.UseShellExecute = false; testProcess.StartInfo.RedirectStandardError = true; @@ -300,8 +303,17 @@ static bool TestStackOverflow3() return false; } - static int Main() + static int Main(string[] args) { + if (args.Length > 0) + { + if (args[0] == "stackoverflow") + StackOverflow.Run(args[1..]); + + if (args[0] == "stackoverflow3") + StackOverflow3.Run(); + } + s_currentPath = Directory.GetCurrentDirectory(); if (TestLibrary.Utilities.IsNativeAot) { diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj index caefa32b1eecea..608825e15693ac 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj @@ -4,9 +4,12 @@ false true + true + + From ceff3680a6b4d526ad6c573319aac75b8884290c Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:34:30 -0700 Subject: [PATCH 10/18] Update src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Strehovský --- .../exceptions/stackoverflow/stackoverflowtester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index 260d06cbc0647f..b8f3a3be781a92 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -19,7 +19,7 @@ static Process GetNativeAotProcess(string testName, string testArgs) { Process testProcess = new Process(); string executableExtension = TestLibrary.Utilities.IsWindows ? ".exe" : ""; - testProcess.StartInfo.FileName = Path.Combine(s_currentPath, "native", $"{ThisProjectName}{executableExtension}"); + testProcess.StartInfo.FileName = Environment.ProcessPath; testProcess.StartInfo.Arguments = testArgs; return testProcess; } From 3e0b99acdbc8d9b55a83da8da765bb9ef0819d2c Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Fri, 27 Oct 2023 16:53:01 +0000 Subject: [PATCH 11/18] Revert msbuild change that's no longer needed --- src/tests/Directory.Build.targets | 2 +- .../exceptions/stackoverflow/stackoverflowtester.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tests/Directory.Build.targets b/src/tests/Directory.Build.targets index 184d541507f2c6..75299527a79251 100644 --- a/src/tests/Directory.Build.targets +++ b/src/tests/Directory.Build.targets @@ -603,7 +603,7 @@ DependsOnTargets="Build;LinkNativeIfBuildAndRun" /> Date: Fri, 27 Oct 2023 22:24:14 +0000 Subject: [PATCH 12/18] Fix namespace changes --- .../Common/CoreCLRTestLibrary/Utilities.cs | 2 +- .../stackoverflow/stackoverflowtester.cs | 58 ++++++++++++------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/tests/Common/CoreCLRTestLibrary/Utilities.cs b/src/tests/Common/CoreCLRTestLibrary/Utilities.cs index c298a46abca874..20b299b814fce2 100644 --- a/src/tests/Common/CoreCLRTestLibrary/Utilities.cs +++ b/src/tests/Common/CoreCLRTestLibrary/Utilities.cs @@ -94,7 +94,7 @@ public static bool IsWindowsIoTCore public static bool IsMonoRuntime => Type.GetType("Mono.RuntimeStructs") != null; public static bool IsNotMonoRuntime => !IsMonoRuntime; - public static bool IsNativeAot => IsNotMonoRuntime && !IsReflectionEmitSupported; + public static bool IsNativeAot => IsSingleFile && IsNotMonoRuntime && !IsReflectionEmitSupported; public static bool HasAssemblyFiles => !string.IsNullOrEmpty(typeof(Utilities).Assembly.Location); public static bool IsSingleFile => !HasAssemblyFiles; diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index a8ba6710ed907a..cae0a828d88a63 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -119,25 +119,31 @@ static bool TestStackOverflowSmallFrameMainThread() return false; } - if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.Test(Boolean)"))) + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.StackOverflow.Run(System.String[])"))) + { + Console.WriteLine("Missing \"Run\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.StackOverflow.Test(Boolean)"))) { Console.WriteLine("Missing \"Test\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionA()"))) { Console.WriteLine("Missing \"InfiniteRecursionA\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionB()"))) { Console.WriteLine("Missing \"InfiniteRecursionB\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionC()"))) { Console.WriteLine("Missing \"InfiniteRecursionC\" method frame"); return false; @@ -164,25 +170,31 @@ static bool TestStackOverflowLargeFrameMainThread() return false; } - if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.Test(Boolean)"))) + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.StackOverflow.Run(System.String[])"))) + { + Console.WriteLine("Missing \"Run\" method frame"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.StackOverflow.Test(Boolean)"))) { Console.WriteLine("Missing \"Test\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA2()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionA2()"))) { Console.WriteLine("Missing \"InfiniteRecursionA2\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB2()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionB2()"))) { Console.WriteLine("Missing \"InfiniteRecursionB2\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC2()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionC2()"))) { Console.WriteLine("Missing \"InfiniteRecursionC2\" method frame"); return false; @@ -204,25 +216,25 @@ static bool TestStackOverflowSmallFrameSecondaryThread() return true; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.Test(Boolean)"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.Test(Boolean)"))) { - Console.WriteLine("Missing \"TestStackOverflow.Program.Test\" method frame"); + Console.WriteLine("Missing \"TestStackOverflow.StackOverflow.Test\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionA()"))) { Console.WriteLine("Missing \"InfiniteRecursionA\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionB()"))) { Console.WriteLine("Missing \"InfiniteRecursionB\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionC()"))) { Console.WriteLine("Missing \"InfiniteRecursionC\" method frame"); return false; @@ -244,25 +256,25 @@ static bool TestStackOverflowLargeFrameSecondaryThread() return true; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.Test(Boolean)"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.Test(Boolean)"))) { - Console.WriteLine("Missing \"TestStackOverflow.Program.Test\" method frame"); + Console.WriteLine("Missing \"TestStackOverflow.StackOverflow.Test\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA2()"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow.InfiniteRecursionA2()"))) { Console.WriteLine("Missing \"InfiniteRecursionA2\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.InfiniteRecursionB2()"))) + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.StackOverflow.InfiniteRecursionB2()"))) { Console.WriteLine("Missing \"InfiniteRecursionB2\" method frame"); return false; } - if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.InfiniteRecursionC2()"))) + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.StackOverflow.InfiniteRecursionC2()"))) { Console.WriteLine("Missing \"InfiniteRecursionC2\" method frame"); return false; @@ -284,13 +296,19 @@ static bool TestStackOverflow3() return true; } - if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow3.Program.Main()")) + 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 TestStackOverflow3.Program.Execute(System.String)"))) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow3.Run()"))) + { + Console.WriteLine("Missing \"Run\" method frame at the last line"); + return false; + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.StackOverflow3.Execute(System.String)"))) { Console.WriteLine("Missing \"Execute\" method frame"); return false; From 07c98d17e87b9996b75b1f4f8ae262cb4f490501 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Mon, 30 Oct 2023 19:05:36 +0000 Subject: [PATCH 13/18] Set DOTNET_DbgEnableMiniDump=0 on subprocesses to avoid dumps on expected failures and remove messages about failing to find createdump --- .../baseservices/exceptions/stackoverflow/stackoverflowtester.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index cae0a828d88a63..81b3ebf1472b95 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -20,6 +20,7 @@ static Process GetNativeAotProcess(string testName, string testArgs) Process testProcess = new Process(); testProcess.StartInfo.FileName = Environment.ProcessPath; testProcess.StartInfo.Arguments = testArgs; + testProcess.StartInfo.Environment.Add("DOTNET_DbgEnableMiniDump", "0") return testProcess; } From eb59bb7c3ef59db4118dd9cf5f8f53a71f5eacc3 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Mon, 30 Oct 2023 19:11:35 +0000 Subject: [PATCH 14/18] Missed semicolon --- .../exceptions/stackoverflow/stackoverflowtester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index 81b3ebf1472b95..1f6c70859f8682 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -20,7 +20,7 @@ static Process GetNativeAotProcess(string testName, string testArgs) Process testProcess = new Process(); testProcess.StartInfo.FileName = Environment.ProcessPath; testProcess.StartInfo.Arguments = testArgs; - testProcess.StartInfo.Environment.Add("DOTNET_DbgEnableMiniDump", "0") + testProcess.StartInfo.Environment.Add("DOTNET_DbgEnableMiniDump", "0"); return testProcess; } From daf9cec0eced2009c6a9cb6b008c3a161c94c7ab Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Tue, 31 Oct 2023 22:03:25 +0000 Subject: [PATCH 15/18] Convert generated xunit runner test to work with NAOT --- .../stackoverflow/stackoverflowtester.cs | 210 ++++++++---------- 1 file changed, 88 insertions(+), 122 deletions(-) diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index 9f650fb2eadbba..b469ff9bbdf4a1 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; using System.IO; using System.Linq; using System.Text; @@ -10,57 +12,57 @@ namespace TestStackOverflow { - public class Program: IDisposable + public class Program : IDisposable { const string TestNameEnvVar = "STACKOVERFLOWTESTER_TESTNAME"; const string TestArgsEnvVar = "STACKOVERFLOWTESTER_TESTARGS"; + const string EnableMiniDumpEnvVar = "DOTNET_DbgEnableMiniDump"; - public Program() { - string? testName = Environment.GetEnviromentVariable(TestNameEnvVar); - string[]? testArgs = Environment.GetEnviromentVariable(TestArgsEnvVar)?.Split(' '); + static bool s_checkedEnv = false; + public Program() + { + // Called before every non-static [Fact] + if (s_checkedEnv) + { + return; + } + // Use the environment variable to determine if we should trigger a stack overflow + string? testName = Environment.GetEnvironmentVariable(TestNameEnvVar); + string? testArgs = Environment.GetEnvironmentVariable(TestArgsEnvVar); + Console.WriteLine(testName); if (testName != null) { - switch(testName) + switch (testName) { - "stackoverflow": + case "stackoverflow": Assert.NotNull(testArgs); - StackOverflow.Run(testArgs); + StackOverflow.Run(testArgs.Split(' ')); break; - "stackoverflow3": + case "stackoverflow3": StackOverflow3.Run(); break; } - throw new InvalidOperationException($"Invalid test. Test {testName} with arguments '{testArgs.Join(' ')}' should have thrown an exception."); + throw new InvalidOperationException($"Invalid test. Test {testName} with arguments '{testArgs}' should have thrown an exception."); } + s_checkedEnv = true; } - public void Dispose() {} - - const string ThisProjectName = "stackoverflowtester"; - - static Process GetNativeAotProcess(string testName, string testArgs) - { - Process testProcess = new Process(); - return testProcess; - } + public void Dispose() { } - static bool TestStackOverflow(string testName, string testArgs, out List stderrLines, out bool checkStackFrame) + [UnconditionalSuppressMessage("SingleFile", "IL3000", Justification = "We want an empty string for location if the test is running as single file")] + static bool TestStackOverflow(string testName, string testArgs, out string[] stderrLines, out bool checkStackFrame) { - Console.WriteLine($"Running with args: {testName} {testArgs}"); - testArgs = $"{testName} {testArgs}"; List lines = new List(); Process testProcess = new Process(); - testProcess.StartInfo.Environment.Add("DOTNET_DbgEnableMiniDump", "0"); - testProcess.StartInfo.Environment.Add(TestNameEnvVar, testName); - testProcess.StartInfo.Environment.Add(, testArgs); - // Always use whatever runner started this test + // Always use whatever runner started this test, or the exe if we're running single file / NativeAOT testProcess.StartInfo.FileName = Environment.ProcessPath; - if (!TestLibrary.Utilities.IsNativeAot) - { - testProcess.StartInfo.Arguments = $"{typeof(Program).Assembly.Location}"; - } - + // We want this assembly in CoreCLR and empty string for single file / NativeAOT + testProcess.StartInfo.Arguments = typeof(Program).Assembly.Location; + testProcess.StartInfo.Environment.Add(EnableMiniDumpEnvVar, "0"); + // Set the environment so the subprocess will trigger a stack overflow + testProcess.StartInfo.Environment.Add(TestNameEnvVar, testName); + testProcess.StartInfo.Environment.Add(TestArgsEnvVar, testArgs); testProcess.StartInfo.UseShellExecute = false; testProcess.StartInfo.RedirectStandardError = true; testProcess.ErrorDataReceived += (sender, line) => @@ -77,7 +79,8 @@ static bool TestStackOverflow(string testName, string testArgs, out List testProcess.WaitForExit(); testProcess.CancelErrorRead(); - stderrLines = lines; + stderrLines = lines.ToArray(); + // NativeAOT doesn't provide a stack trace on stack overflow checkStackFrame = !TestLibrary.Utilities.IsNativeAot; @@ -117,141 +120,104 @@ static bool TestStackOverflow(string testName, string testArgs, out List { return true; } - else - { - throw new Exception($"Missing \"{expectedMessage}\" at the first line"); - } + + throw new Exception($"Missing \"{expectedMessage}\" at the first line of stderr"); } - [Fact] - public static void TestStackOverflowSmallFrameMainThread() + public static void AssertStackFramePresent(string stackFrame, ReadOnlySpan lines) { - TestStackOverflow("stackoverflow", "smallframe main", out List lines); - - if (!lines[lines.Count - 1].EndsWith(".Main(System.String[])")) - { - throw new Exception("Missing \"Main\" method frame at the last line"); - } - - if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.Test(Boolean)"))) - { - throw new Exception("Missing \"Test\" method frame"); - } - - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA()"))) - { - throw new Exception("Missing \"InfiniteRecursionA\" method frame"); - } - - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB()"))) - { - throw new Exception("Missing \"InfiniteRecursionB\" method frame"); - } - - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC()"))) + foreach(var line in lines) { - throw new Exception("Missing \"InfiniteRecursionC\" method frame"); + if (line.EndsWith(stackFrame)) + return; } + throw new Exception($"Missing \"{stackFrame}\" from stack trace"); } [Fact] - public static void TestStackOverflowLargeFrameMainThread() + public void TestStackOverflowSmallFrameMainThread() { - TestStackOverflow("stackoverflow", "largeframe main", out List lines); + TestStackOverflow("stackoverflow", "smallframe main", out string[] lines, out bool checkStackFrame); - if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow.Program.Main(System.String[])")) + if (!checkStackFrame) { - throw new Exception("Missing \"Main\" method frame at the last line"); + return; } - if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.Test(Boolean)"))) - { - throw new Exception("Missing \"Test\" method frame"); - } + AssertStackFramePresent(".Main()", lines[(lines.Length-1)..]); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.Run(System.String[])", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionC()", lines); + } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA2()"))) - { - throw new Exception("Missing \"InfiniteRecursionA2\" method frame"); - } + [Fact] + public void TestStackOverflowLargeFrameMainThread() + { + TestStackOverflow("stackoverflow", "largeframe main", out string[] lines, out bool checkStackFrame); - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB2()"))) + if (!checkStackFrame) { - throw new Exception("Missing \"InfiniteRecursionB2\" method frame"); + return; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC2()"))) + if (!lines[lines.Length - 1].EndsWith(".Main()")) { - throw new Exception("Missing \"InfiniteRecursionC2\" method frame"); + throw new Exception("Missing \"Main\" method frame at the last line"); } + AssertStackFramePresent(".Main()", lines[(lines.Length-1)..]); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.Run(System.String[])", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionA2()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB2()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionC2()", lines); } [Fact] - public static void TestStackOverflowSmallFrameSecondaryThread() + public void TestStackOverflowSmallFrameSecondaryThread() { - TestStackOverflow("stackoverflow", "smallframe secondary", out List lines); - - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.Test(Boolean)"))) - { - throw new Exception("Missing \"TestStackOverflow.Program.Test\" method frame"); - } + TestStackOverflow("stackoverflow", "smallframe secondary", out string[] lines, out bool checkStackFrame); - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA()"))) + if (!checkStackFrame) { - throw new Exception("Missing \"InfiniteRecursionA\" method frame"); + return; } - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB()"))) - { - throw new Exception("Missing \"InfiniteRecursionB\" method frame"); - } - - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC()"))) - { - throw new Exception("Missing \"InfiniteRecursionC\" method frame"); - } + AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionA()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionC()", lines); } [Fact] - public static void TestStackOverflowLargeFrameSecondaryThread() + public void TestStackOverflowLargeFrameSecondaryThread() { - TestStackOverflow("stackoverflow", "largeframe secondary", out List lines); - - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.Test(Boolean)"))) - { - throw new Exception("Missing \"TestStackOverflow.Program.Test\" method frame"); - } + TestStackOverflow("stackoverflow", "largeframe secondary", out string[] lines, out bool checkStackFrame); - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA2()"))) + if (!checkStackFrame) { - throw new Exception("Missing \"InfiniteRecursionA2\" method frame"); + return; } - if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.InfiniteRecursionB2()"))) - { - throw new Exception("Missing \"InfiniteRecursionB2\" method frame"); - } - - if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.InfiniteRecursionC2()"))) - { - throw new Exception("Missing \"InfiniteRecursionC2\" method frame"); - } + AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionA2()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB2()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionC2()", lines); } [Fact] - public static void TestStackOverflow3() + public void TestStackOverflow3() { - TestStackOverflow("stackoverflow3", "", out List lines); - - if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow3.Program.Main()")) - { - throw new Exception("Missing \"Main\" method frame at the last line"); - } + TestStackOverflow("stackoverflow3", "", out string[] lines, out bool checkStackFrame); - if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow3.Program.Execute(System.String)"))) + if (!checkStackFrame) { - throw new Exception("Missing \"Execute\" method frame"); + return; } + AssertStackFramePresent(".Main()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow3.Run()", lines); + AssertStackFramePresent("at TestStackOverflow.StackOverflow3.Execute(System.String)", lines); } } } From 257b2dff0123b8de67b692319339ed9ac3cb1b49 Mon Sep 17 00:00:00 2001 From: Jackson Schuster Date: Thu, 2 Nov 2023 20:39:05 +0000 Subject: [PATCH 16/18] Don't use xunit generator for stackoverflowtester --- .../stackoverflow/stackoverflowtester.cs | 86 ++++++++++--------- .../stackoverflow/stackoverflowtester.csproj | 1 + 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index b469ff9bbdf4a1..da1e3eefb8bba8 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Reflection; +using System.Runtime.CompilerServices; using System.IO; using System.Linq; using System.Text; @@ -12,57 +12,70 @@ namespace TestStackOverflow { - public class Program : IDisposable + public class Program { - const string TestNameEnvVar = "STACKOVERFLOWTESTER_TESTNAME"; - const string TestArgsEnvVar = "STACKOVERFLOWTESTER_TESTARGS"; const string EnableMiniDumpEnvVar = "DOTNET_DbgEnableMiniDump"; - static bool s_checkedEnv = false; - public Program() + public static int Main(string[] args) { - // Called before every non-static [Fact] - if (s_checkedEnv) - { - return; - } - // Use the environment variable to determine if we should trigger a stack overflow - string? testName = Environment.GetEnvironmentVariable(TestNameEnvVar); - string? testArgs = Environment.GetEnvironmentVariable(TestArgsEnvVar); - Console.WriteLine(testName); - if (testName != null) + if (args.Length > 0) { + string testName = args[0]; + string[] testArgs = Array.Empty(); switch (testName) { case "stackoverflow": - Assert.NotNull(testArgs); - StackOverflow.Run(testArgs.Split(' ')); + Assert.True(args.Length >= 2); + testArgs = args[1..].ToArray(); + StackOverflow.Run(testArgs); break; case "stackoverflow3": StackOverflow3.Run(); break; + default: + throw new InvalidOperationException($"Invalid arguments to 'stackoverflowtester' process. Test '{testName}' does not exist"); } - throw new InvalidOperationException($"Invalid test. Test {testName} with arguments '{testArgs}' should have thrown an exception."); + throw new InvalidOperationException($"Invalid arguments to 'stackoverflowtester' process. Test '{testName}' with arguments '{testArgs}' should have thrown an exception."); + } + + int exitCode = 101; + try + { + TestStackOverflowSmallFrameMainThread(); + exitCode++; + TestStackOverflowLargeFrameMainThread(); + exitCode++; + TestStackOverflowSmallFrameSecondaryThread(); + exitCode++; + TestStackOverflowLargeFrameSecondaryThread(); + exitCode++; + TestStackOverflow3(); + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + return exitCode; } - s_checkedEnv = true; - } - public void Dispose() { } + return 100; + } [UnconditionalSuppressMessage("SingleFile", "IL3000", Justification = "We want an empty string for location if the test is running as single file")] static bool TestStackOverflow(string testName, string testArgs, out string[] stderrLines, out bool checkStackFrame) { List lines = new List(); + string thisAssemblyPath = typeof(Program).Assembly.Location; + // TestLibrary.Utilities.IsNativeAot brings in ~4mb on Linux, so we'll approximate with this for now. https://github.com/dotnet/runtime/issues/94313 + bool IsNativeAot = string.IsNullOrEmpty(thisAssemblyPath) && !RuntimeFeature.IsDynamicCodeSupported; + Process testProcess = new Process(); // Always use whatever runner started this test, or the exe if we're running single file / NativeAOT testProcess.StartInfo.FileName = Environment.ProcessPath; - // We want this assembly in CoreCLR and empty string for single file / NativeAOT - testProcess.StartInfo.Arguments = typeof(Program).Assembly.Location; + // We want the path to this assembly in CoreCLR and the empty string for single file / NativeAOT + testProcess.StartInfo.Arguments = $"{typeof(Program).Assembly.Location} {testName} {testArgs}"; + Console.WriteLine($"Running {testName} {testArgs}"); testProcess.StartInfo.Environment.Add(EnableMiniDumpEnvVar, "0"); - // Set the environment so the subprocess will trigger a stack overflow - testProcess.StartInfo.Environment.Add(TestNameEnvVar, testName); - testProcess.StartInfo.Environment.Add(TestArgsEnvVar, testArgs); testProcess.StartInfo.UseShellExecute = false; testProcess.StartInfo.RedirectStandardError = true; testProcess.ErrorDataReceived += (sender, line) => @@ -82,7 +95,7 @@ static bool TestStackOverflow(string testName, string testArgs, out string[] std stderrLines = lines.ToArray(); // NativeAOT doesn't provide a stack trace on stack overflow - checkStackFrame = !TestLibrary.Utilities.IsNativeAot; + checkStackFrame = !IsNativeAot; int[] expectedExitCodes; if ((Environment.OSVersion.Platform == PlatformID.Unix) || (Environment.OSVersion.Platform == PlatformID.MacOSX)) @@ -107,7 +120,7 @@ static bool TestStackOverflow(string testName, string testArgs, out string[] std } string expectedMessage; - if (TestLibrary.Utilities.IsNativeAot) + if (IsNativeAot) { expectedMessage = "Process is terminating due to StackOverflowException."; } @@ -134,8 +147,7 @@ public static void AssertStackFramePresent(string stackFrame, ReadOnlySpan true true + false From f8dfd67ed220caf2729e141f1516a27dff3cdd24 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 2 Nov 2023 13:39:59 -0700 Subject: [PATCH 17/18] Update src/coreclr/nativeaot/Runtime/thread.h Co-authored-by: Jan Vorlicek --- src/coreclr/nativeaot/Runtime/thread.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/Runtime/thread.h b/src/coreclr/nativeaot/Runtime/thread.h index e6c5754537ea03..aa3f370a8643d7 100644 --- a/src/coreclr/nativeaot/Runtime/thread.h +++ b/src/coreclr/nativeaot/Runtime/thread.h @@ -113,7 +113,7 @@ struct ThreadBuffer uint32_t m_uRand; // current per-thread random number #endif // FEATURE_GC_STRESS #if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) - void * m_alternateStack; // ptr to alternate signal stack + void * m_alternateStack; // ptr to alternate signal stack #endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) }; From 4aa99120e1630b84ba281f6c7c2eec96d0e0bbd2 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:51:24 -0800 Subject: [PATCH 18/18] Use IsSingleFile and update Main signature --- .../stackoverflow/stackoverflowtester.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index da1e3eefb8bba8..6e97fd7bb55161 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -16,6 +16,8 @@ public class Program { const string EnableMiniDumpEnvVar = "DOTNET_DbgEnableMiniDump"; + const string MainSignature = ".Main(System.String[])"; + public static int Main(string[] args) { if (args.Length > 0) @@ -66,8 +68,7 @@ static bool TestStackOverflow(string testName, string testArgs, out string[] std List lines = new List(); string thisAssemblyPath = typeof(Program).Assembly.Location; - // TestLibrary.Utilities.IsNativeAot brings in ~4mb on Linux, so we'll approximate with this for now. https://github.com/dotnet/runtime/issues/94313 - bool IsNativeAot = string.IsNullOrEmpty(thisAssemblyPath) && !RuntimeFeature.IsDynamicCodeSupported; + bool isSingleFile = TestLibrary.Utilities.IsSingleFile; Process testProcess = new Process(); // Always use whatever runner started this test, or the exe if we're running single file / NativeAOT @@ -95,7 +96,7 @@ static bool TestStackOverflow(string testName, string testArgs, out string[] std stderrLines = lines.ToArray(); // NativeAOT doesn't provide a stack trace on stack overflow - checkStackFrame = !IsNativeAot; + checkStackFrame = !isSingleFile; int[] expectedExitCodes; if ((Environment.OSVersion.Platform == PlatformID.Unix) || (Environment.OSVersion.Platform == PlatformID.MacOSX)) @@ -120,7 +121,7 @@ static bool TestStackOverflow(string testName, string testArgs, out string[] std } string expectedMessage; - if (IsNativeAot) + if (isSingleFile) { expectedMessage = "Process is terminating due to StackOverflowException."; } @@ -156,7 +157,7 @@ public static void TestStackOverflowSmallFrameMainThread() return; } - AssertStackFramePresent(".Main()", lines[(lines.Length-1)..]); + AssertStackFramePresent(MainSignature, lines[(lines.Length-1)..]); AssertStackFramePresent("at TestStackOverflow.StackOverflow.Run(System.String[])", lines); AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB()", lines); @@ -172,11 +173,11 @@ public static void TestStackOverflowLargeFrameMainThread() return; } - if (!lines[lines.Length - 1].EndsWith(".Main()")) + if (!lines[lines.Length - 1].EndsWith(MainSignature)) { throw new Exception("Missing \"Main\" method frame at the last line"); } - AssertStackFramePresent(".Main()", lines[(lines.Length-1)..]); + AssertStackFramePresent(MainSignature, lines[(lines.Length-1)..]); AssertStackFramePresent("at TestStackOverflow.StackOverflow.Run(System.String[])", lines); AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionA2()", lines); @@ -223,7 +224,7 @@ public static void TestStackOverflow3() return; } - AssertStackFramePresent(".Main()", lines); + AssertStackFramePresent(MainSignature, lines); AssertStackFramePresent("at TestStackOverflow.StackOverflow3.Run()", lines); AssertStackFramePresent("at TestStackOverflow.StackOverflow3.Execute(System.String)", lines); }