diff --git a/src/coreclr/debug/crashreport/CMakeLists.txt b/src/coreclr/debug/crashreport/CMakeLists.txt index f88699a4c6a464..f23dd004846df5 100644 --- a/src/coreclr/debug/crashreport/CMakeLists.txt +++ b/src/coreclr/debug/crashreport/CMakeLists.txt @@ -1,7 +1,9 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CRASHREPORT_SOURCES + signalsafeformat.cpp signalsafejsonwriter.cpp + signalsafeconsolewriter.cpp inproccrashreporter.cpp ) diff --git a/src/coreclr/debug/crashreport/inproccrashreporter.cpp b/src/coreclr/debug/crashreport/inproccrashreporter.cpp index fe771432eee5f4..b18088165aeadb 100644 --- a/src/coreclr/debug/crashreport/inproccrashreporter.cpp +++ b/src/coreclr/debug/crashreport/inproccrashreporter.cpp @@ -6,7 +6,9 @@ // Streams a createdump-shaped JSON skeleton to a crashreport.json file. #include "inproccrashreporter.h" +#include "signalsafeconsolewriter.h" #include "signalsafejsonwriter.h" +#include "signalsafeformat.h" #include "pal.h" @@ -17,12 +19,73 @@ #include #include #include +#include #include #ifdef __APPLE__ #include #include #endif +static const char CRASHREPORT_PROTOCOL_VERSION[] = "1.0.0"; +static constexpr uint32_t CRASHREPORT_COR_E_STACKOVERFLOW = 0x800703E9; +static const char CRASHREPORT_STACK_OVERFLOW_EXCEPTION_TYPE[] = "System.StackOverflowException"; +static const char CRASHREPORT_STACK_OVERFLOW_TRACE_UNAVAILABLE_REASON[] = "stack_overflow_trace_unavailable"; +static constexpr uint32_t CRASHREPORT_STACK_OVERFLOW_MAX_TRACE_FRAMES = 128; + +#if defined(__x86_64__) +static const char CRASHREPORT_ARCHITECTURE_NAME[] = "amd64"; +#elif defined(__aarch64__) +static const char CRASHREPORT_ARCHITECTURE_NAME[] = "arm64"; +#elif defined(__arm__) +static const char CRASHREPORT_ARCHITECTURE_NAME[] = "arm"; +#endif + +// Prescribed compact crash report log format. One logical line == one +// __android_log_write entry under tag "DOTNET_CRASH" on Android, one +// '\n'-terminated stderr write elsewhere. +// +// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** (BeginConsoleReport) +// .NET Crash Report v +// Build: (omitted if empty) +// ABI: amd64|arm64|arm +// Cmdline: (omitted if empty) +// pid: +// signal () +// (blank between sections) +// --- thread 0xTID [(crashed)] --- (BeginConsoleThreadBlock) +// managed exception: (0x) (only if EE provided one) +// #NN [M] Class.Method + 0xILOFFSET (token=0xTOKEN) (managed frame; WriteFrameToConsole) +// #NN (in ) Class.Method + 0xILOFFSET (token=0xTOKEN) (overflow form: module didn't fit the table) +// #NN [M] 0xIP (module + 0xOFFSET) (native frame; WriteFrameToConsole) +// #NN 0xIP (module + 0xOFFSET) (native frame not in module table) +// (no managed frames) | ... +N more frames (EndConsoleThreadBlock) +// (blank between threads) +// modules: (EndConsoleReport) +// [N] {} (one per ModuleTable entry) +// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** (closing separator) + +static SignalSafeConsoleWriter s_consoleWriter; +static volatile LONG s_crashKind = static_cast(InProcCrashReportCrashKind::Unknown); + +struct StackOverflowTraceFrame +{ + char methodName[CRASHREPORT_STRING_BUFFER_SIZE]; + uint32_t repeatCount; + uint32_t repeatSequenceLength; +}; + +struct StackOverflowTraceSnapshot +{ + uint64_t crashingTid; + uint32_t totalFrameCount; + uint32_t frameCount; + uint32_t truncatedFrameCount; + StackOverflowTraceFrame frames[CRASHREPORT_STACK_OVERFLOW_MAX_TRACE_FRAMES]; + volatile LONG available; +}; + +static StackOverflowTraceSnapshot s_stackOverflowTrace; + // Include the .NET version string instead of linking because it is "static". #if __has_include("_version.c") #include "_version.c" @@ -30,6 +93,47 @@ static char sccsid[] = "@(#)Version N/A"; #endif +static char s_versionScratch[sizeof(sccsid)]; +static char s_moduleGuidScratch[MINIPAL_GUID_BUFFER_LEN]; + +static const char* +GetSignalNameAscii(int signal) +{ + switch (signal) + { + case SIGSEGV: return "SIGSEGV"; + case SIGBUS: return "SIGBUS"; + case SIGFPE: return "SIGFPE"; + case SIGILL: return "SIGILL"; + case SIGABRT: return "SIGABRT"; + case SIGTRAP: return "SIGTRAP"; + case SIGTERM: return "SIGTERM"; + default: return "Unknown signal"; + } +} + +static void CopyStringToBuffer(char* buffer, size_t bufferSize, const char* value) +{ + if (buffer == nullptr || bufferSize == 0) + { + return; + } + + if (value == nullptr) + { + buffer[0] = '\0'; + return; + } + + size_t toCopy = strnlen(value, bufferSize - 1); + if (toCopy != 0) + { + memcpy(buffer, value, toCopy); + } + + buffer[toCopy] = '\0'; +} + #ifdef __APPLE__ // Query a sysctl by name into a caller-supplied buffer. Called from Initialize, NOT from the // signal handler -- sysctl/sysctlbyname is not on POSIX's async-signal-safe list, so the @@ -51,17 +155,77 @@ static void CacheSysctlString(const char* sysctlName, char* buffer, size_t buffe } #endif // __APPLE__ +// Bounded module table that deduplicates each unique module observed during a +// single crash report. Frames in the compact log refer to modules by short +// ``[N]`` indices instead of repeating module identity on every line; the +// matching ``modules:`` block resolves the module handles back to full data. +// +// Capacity is fixed at MAX_MODULES_IN_TABLE (no heap on the fatal-signal +// path). A managed frame whose module didn't fit (table full, missing handle, +// or unresolved module identity) renders the module identity inline as +// ``(in ) `` so the frame stays self-describing — overflow is lossless, +// just less compact for that frame. +// +// Single-instance because CreateReport is one-shot per process (guarded by +// the ``s_generating`` InterlockedCompareExchange in CreateReport). + +static constexpr size_t MAX_MODULES_IN_TABLE = 256; + +class ModuleTable +{ +public: + int GetOrAddIndex( + const void* moduleHandle) + { + if (moduleHandle == nullptr) + { + return -1; + } + + for (size_t i = 0; i < m_count; ++i) + { + if (m_moduleHandles[i] == moduleHandle) + { + return static_cast(i); + } + } + + if (m_count >= MAX_MODULES_IN_TABLE) + { + return -1; + } + + m_moduleHandles[m_count] = moduleHandle; + return static_cast(m_count++); + } + + size_t Count() const { return m_count; } + const void* ModuleHandle(size_t i) const { return m_moduleHandles[i]; } + +private: + const void* m_moduleHandles[MAX_MODULES_IN_TABLE]; + size_t m_count = 0; +}; + +static ModuleTable s_moduleTable; + class ThreadEnumerationContext { public: + ThreadEnumerationContext() + { + Init(nullptr, nullptr, nullptr, 0, 0, nullptr); + } + ThreadEnumerationContext( SignalSafeJsonWriter* writer, + SignalSafeConsoleWriter* consoleWriter, + InProcCrashReportModuleInfoCallback moduleInfoCallback, + uint64_t crashingTid, + uint32_t frameLimitPerThread, void* signalContext) - : m_writer(writer), - m_signalContext(signalContext), - m_threadCount(0), - m_sawCrashThread(false) { + Init(writer, consoleWriter, moduleInfoCallback, crashingTid, frameLimitPerThread, signalContext); } ThreadEnumerationContext(const ThreadEnumerationContext&) = delete; @@ -69,9 +233,31 @@ class ThreadEnumerationContext size_t ThreadCount() const { return m_threadCount; } bool SawCrashThread() const { return m_sawCrashThread; } - SignalSafeJsonWriter* Writer() const { return m_writer; } + SignalSafeJsonWriter* JsonWriter() const { return m_jsonWriter; } + SignalSafeConsoleWriter* ConsoleWriter() const { return m_consoleWriter; } + + void Init( + SignalSafeJsonWriter* writer, + SignalSafeConsoleWriter* consoleWriter, + InProcCrashReportModuleInfoCallback moduleInfoCallback, + uint64_t crashingTid, + uint32_t frameLimitPerThread, + void* signalContext) + { + m_jsonWriter = writer; + m_consoleWriter = consoleWriter; + m_moduleInfoCallback = moduleInfoCallback; + m_signalContext = signalContext; + m_threadCount = 0; + m_crashingTid = crashingTid; + m_currentThreadFrameCount = 0; + m_currentThreadDroppedCount = 0; + m_frameLimitPerThread = frameLimitPerThread; + m_sawCrashThread = false; + m_methodNameScratch[0] = '\0'; + } - void EnumerateThreads(InProcCrashReportEnumerateThreadsCallback callback, uint64_t crashingTid); + void EnumerateThreads(InProcCrashReportEnumerateThreadsCallback callback); static void ThreadCallback( uint64_t osThreadId, @@ -86,12 +272,13 @@ class ThreadEnumerationContext const char* methodName, const char* className, const char* moduleName, + const void* moduleHandle, uint32_t nativeOffset, uint32_t token, uint32_t ilOffset, uint32_t moduleTimestamp, uint32_t moduleSize, - const char* moduleGuid, + const GUID* moduleGuid, void* ctx); private: @@ -107,34 +294,58 @@ class ThreadEnumerationContext const char* methodName, const char* className, const char* moduleName, + const void* moduleHandle, uint32_t nativeOffset, uint32_t token, uint32_t ilOffset, uint32_t moduleTimestamp, uint32_t moduleSize, - const char* moduleGuid); + const GUID* moduleGuid); - SignalSafeJsonWriter* m_writer; + void EndCurrentConsoleThreadBlock(); + void EndCurrentJsonThreadBlock(); + + SignalSafeJsonWriter* m_jsonWriter; + SignalSafeConsoleWriter* m_consoleWriter; + InProcCrashReportModuleInfoCallback m_moduleInfoCallback; void* m_signalContext; size_t m_threadCount; + uint64_t m_crashingTid; + uint32_t m_currentThreadFrameCount; + uint32_t m_currentThreadDroppedCount; + uint32_t m_frameLimitPerThread; bool m_sawCrashThread; + char m_methodNameScratch[CRASHREPORT_STRING_BUFFER_SIZE]; }; +static ThreadEnumerationContext s_threadContext; + class CrashReportOutputContext { public: - explicit CrashReportOutputContext(int fd) - : m_fd(fd), + CrashReportOutputContext() + : m_fd(-1), m_writeFailed(false) { } + explicit CrashReportOutputContext(int fd) + { + Init(fd); + } + CrashReportOutputContext(const CrashReportOutputContext&) = delete; CrashReportOutputContext& operator=(const CrashReportOutputContext&) = delete; int Fd() const { return m_fd; } bool WriteFailed() const { return m_writeFailed; } + void Init(int fd) + { + m_fd = fd; + m_writeFailed = false; + } + static bool ChunkCallback(const char* buffer, size_t len, void* ctx); private: @@ -144,9 +355,23 @@ class CrashReportOutputContext bool m_writeFailed; }; +static CrashReportOutputContext s_outputContext; + class CrashReportHelpers { public: + struct FrameContext + { + SignalSafeJsonWriter* jsonWriter; + SignalSafeConsoleWriter* consoleWriter; + InProcCrashReportModuleInfoCallback moduleInfoCallback; + uint32_t* currentThreadFrameCount; + uint32_t* currentThreadDroppedCount; + uint32_t frameLimitPerThread; + char* methodNameBuffer; + size_t methodNameBufferSize; + }; + static void GetVersionString( char* buffer, size_t bufferSize); @@ -188,28 +413,130 @@ class CrashReportHelpers size_t bufferSize, const char* value); - static void JsonFrameCallback( + static void WriteFrameToJson( + SignalSafeJsonWriter* writer, + char* methodNameBuffer, + size_t methodNameBufferSize, + uint64_t ip, + uint64_t stackPointer, + const char* methodName, + const char* className, + const char* moduleName, + uint32_t nativeOffset, + uint32_t token, + uint32_t ilOffset, + uint32_t moduleTimestamp, + uint32_t moduleSize, + const GUID* moduleGuid); + + static void WriteFrameToConsole( + SignalSafeConsoleWriter* consoleWriter, + char* methodNameBuffer, + size_t methodNameBufferSize, + uint32_t frameIndex, + int moduleIndex, + uint64_t ip, + const char* methodName, + const char* className, + const char* fallbackModuleName, + uint32_t nativeOffset, + uint32_t token, + uint32_t ilOffset); + + static void WriteStackOverflowFrameToJson( + SignalSafeJsonWriter* writer, + const StackOverflowTraceFrame& frame, + bool includeRepeatMetadata); + + static void WriteStackOverflowFrameToConsole( + SignalSafeConsoleWriter* consoleWriter, + uint32_t frameIndex, + const StackOverflowTraceFrame& frame); + + static void BeginJsonThreadBlock( + SignalSafeJsonWriter* jsonWriter, + uint64_t osThreadId, + bool isManagedThread, + bool isCrashThread, + const char* exceptionType, + uint32_t exceptionHResult); + + static void BeginJsonStackFrames( + SignalSafeJsonWriter* jsonWriter, + bool writeCrashSiteFrame, + void* signalContext); + + static void EndJsonStackFrames( + SignalSafeJsonWriter* jsonWriter); + + static void EndJsonThreadBlock( + SignalSafeJsonWriter* jsonWriter); + + static void BeginConsoleThreadBlock( + SignalSafeConsoleWriter* consoleWriter, + uint64_t osThreadId, + bool isCrashThread); + + static void EndConsoleThreadBlock( + SignalSafeConsoleWriter* consoleWriter, + uint32_t frameCount, + uint32_t droppedCount); + + static void WriteFrame( uint64_t ip, uint64_t stackPointer, const char* methodName, const char* className, const char* moduleName, + const void* moduleHandle, uint32_t nativeOffset, uint32_t token, uint32_t ilOffset, uint32_t moduleTimestamp, uint32_t moduleSize, - const char* moduleGuid, + const GUID* moduleGuid, void* ctx); + static void WriteFrameToReport( + SignalSafeJsonWriter* jsonWriter, + SignalSafeConsoleWriter* consoleWriter, + InProcCrashReportModuleInfoCallback moduleInfoCallback, + char* methodNameBuffer, + size_t methodNameBufferSize, + uint32_t* currentThreadFrameCount, + uint32_t* currentThreadDroppedCount, + uint32_t frameLimitPerThread, + uint64_t ip, + uint64_t stackPointer, + const char* methodName, + const char* className, + const char* moduleName, + const void* moduleHandle, + uint32_t nativeOffset, + uint32_t token, + uint32_t ilOffset, + uint32_t moduleTimestamp, + uint32_t moduleSize, + const GUID* moduleGuid); + static bool WriteToFile( int fd, const char* buffer, size_t len); + // SignalSafeJsonWriter callback that drops everything: used when the + // crash report is running in compact-log-only mode (no DbgMiniDumpName) + // so the JSON formatter still keeps its bookkeeping consistent without + // emitting bytes anywhere. + static bool DiscardOutputCallback(const char* buffer, size_t len, void* ctx); + static bool BuildReportPath( char* buffer, size_t bufferSize, + char* expandedBuffer, + size_t expandedBufferSize, + char* numberBuffer, + size_t numberBufferSize, const char* dumpPath, const char* processName, const char* hostName); @@ -217,6 +544,8 @@ class CrashReportHelpers static size_t ExpandDumpTemplate( char* buffer, size_t bufferSize, + char* numberBuffer, + size_t numberBufferSize, const char* pattern, const char* processName, const char* hostName); @@ -225,7 +554,6 @@ class CrashReportHelpers void InProcCrashReporter::CreateReport( int signal, - siginfo_t* siginfo, void* context) { static LONG s_generating = 0; @@ -234,59 +562,73 @@ InProcCrashReporter::CreateReport( return; } - char reportPath[CRASHREPORT_PATH_BUFFER_SIZE]; - reportPath[0] = '\0'; - - if (m_reportPath[0] == '\0' || !CrashReportHelpers::BuildReportPath(reportPath, sizeof(reportPath), m_reportPath, m_processName, m_hostName)) + m_reportFilePathScratch[0] = '\0'; + + // The JSON file sink is only enabled when DbgMiniDumpName supplied a + // template AND the template expanded to a valid path. Otherwise the + // crash report runs in compact-log-only mode: the JSON emitter still + // executes (so it can keep its bookkeeping consistent) but writes go + // to a no-op DiscardOutputCallback instead of an open fd. + bool jsonEnabled = m_reportPath[0] != '\0' && + CrashReportHelpers::BuildReportPath( + m_reportFilePathScratch, + sizeof(m_reportFilePathScratch), + m_expandedReportPathScratch, + sizeof(m_expandedReportPathScratch), + m_numberScratch, + sizeof(m_numberScratch), + m_reportPath, + m_processName, + m_hostName); + + int fd = -1; + if (jsonEnabled) { - return; + fd = open(m_reportFilePathScratch, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd == -1) + { + jsonEnabled = false; + } } - int fd = open(reportPath, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd == -1) + InProcCrashReportCrashKind crashKind = static_cast( + InterlockedExchange(&s_crashKind, static_cast(InProcCrashReportCrashKind::Unknown))); + + s_outputContext.Init(fd); + if (jsonEnabled) { - return; + m_jsonWriter.Init(&CrashReportOutputContext::ChunkCallback, &s_outputContext); } - - (void)siginfo; - - CrashReportOutputContext outputContext(fd); - - m_jsonWriter.Init(&CrashReportOutputContext::ChunkCallback, &outputContext); - - m_jsonWriter.OpenObject(); - m_jsonWriter.OpenObject("payload"); - m_jsonWriter.WriteString("protocol_version", "1.0.0"); - - m_jsonWriter.OpenObject("configuration"); -#if defined(__x86_64__) - m_jsonWriter.WriteString("architecture", "amd64"); -#elif defined(__aarch64__) - m_jsonWriter.WriteString("architecture", "arm64"); -#elif defined(__arm__) - m_jsonWriter.WriteString("architecture", "arm"); -#endif - char version[sizeof(sccsid)]; - CrashReportHelpers::GetVersionString(version, sizeof(version)); - m_jsonWriter.WriteString("version", version); - m_jsonWriter.CloseObject(); // configuration - - if (m_processName[0] != '\0') + else { - m_jsonWriter.WriteString("process_name", m_processName); + m_jsonWriter.Init(&CrashReportHelpers::DiscardOutputCallback, nullptr); } - m_jsonWriter.WriteDecimalAsString("pid", static_cast(GetCurrentProcessId())); + BeginConsoleReport(signal); + BeginJsonReport(); + EmitThreads(crashKind, context); + EndJsonReport(signal, jsonEnabled, fd); + EndConsoleReport(); +} +void +InProcCrashReporter::EmitThreads( + InProcCrashReportCrashKind crashKind, + void* context) +{ m_jsonWriter.OpenArray("threads"); - if (m_enumerateThreadsCallback != nullptr) + if (crashKind == InProcCrashReportCrashKind::StackOverflow) + { + EmitStackOverflowCrashThread(); + } + else if (m_enumerateThreadsCallback != nullptr) { - ThreadEnumerationContext threadContext(&m_jsonWriter, context); uint64_t crashingTid = static_cast(minipal_get_current_thread_id()); + s_threadContext.Init(&m_jsonWriter, &s_consoleWriter, m_moduleInfoCallback, crashingTid, m_frameLimitPerThread, context); - threadContext.EnumerateThreads(m_enumerateThreadsCallback, crashingTid); + s_threadContext.EnumerateThreads(m_enumerateThreadsCallback); - if (threadContext.ThreadCount() == 0 || !threadContext.SawCrashThread()) + if (s_threadContext.ThreadCount() == 0 || !s_threadContext.SawCrashThread()) { EmitSynthesizedCrashThread(context, /*walkStack*/ false); } @@ -296,37 +638,6 @@ InProcCrashReporter::CreateReport( EmitSynthesizedCrashThread(context, /*walkStack*/ true); } m_jsonWriter.CloseArray(); // threads - - m_jsonWriter.CloseObject(); // payload - - m_jsonWriter.OpenObject("parameters"); - m_jsonWriter.WriteSignedDecimalAsString("signal", static_cast(signal)); -#ifdef __APPLE__ - if (m_osVersion[0] != '\0') - { - m_jsonWriter.WriteString("OSVersion", m_osVersion); - } - if (m_systemModel[0] != '\0') - { - m_jsonWriter.WriteString("SystemModel", m_systemModel); - } - m_jsonWriter.WriteString("SystemManufacturer", "apple"); -#endif - m_jsonWriter.CloseObject(); // parameters - - m_jsonWriter.CloseObject(); // root - - if (fd != -1) - { - bool writeSucceeded = m_jsonWriter.Finish() && - !outputContext.WriteFailed() && - CrashReportHelpers::WriteToFile(fd, "\n", 1); - - if (close(fd) != 0 || !writeSucceeded) - { - unlink(reportPath); - } - } } InProcCrashReporter& @@ -343,6 +654,8 @@ InProcCrashReporter::Initialize( m_isManagedThreadCallback = settings.isManagedThreadCallback; m_walkStackCallback = settings.walkStackCallback; m_enumerateThreadsCallback = settings.enumerateThreadsCallback; + m_moduleInfoCallback = settings.moduleInfoCallback; + m_frameLimitPerThread = settings.frameLimitPerThread; CrashReportHelpers::CopyString(m_reportPath, sizeof(m_reportPath), settings.reportPath); m_processName[0] = '\0'; @@ -354,13 +667,12 @@ InProcCrashReporter::Initialize( int cmdlineFd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC); if (cmdlineFd >= 0) { - char buf[CRASHREPORT_STRING_BUFFER_SIZE]; - ssize_t n = read(cmdlineFd, buf, sizeof(buf) - 1); + ssize_t n = read(cmdlineFd, m_processNameScratch, sizeof(m_processNameScratch) - 1); close(cmdlineFd); if (n > 0) { - buf[n] = '\0'; - CrashReportHelpers::CopyString(m_processName, sizeof(m_processName), CrashReportHelpers::GetFilename(buf)); + m_processNameScratch[n] = '\0'; + CrashReportHelpers::CopyString(m_processName, sizeof(m_processName), CrashReportHelpers::GetFilename(m_processNameScratch)); } } #endif @@ -397,8 +709,10 @@ InProcCrashReporter::Initialize( void InProcCrashReportSignalDispatcher(int signal, void* siginfo, void* context) { + (void)siginfo; + InProcCrashReporter& reporter = InProcCrashReporter::GetInstance(); - reporter.CreateReport(signal, static_cast(siginfo), context); + reporter.CreateReport(signal, context); } void @@ -412,6 +726,49 @@ InProcCrashReportInitialize(const InProcCrashReporterSettings& settings) PAL_SetInProcCrashReportCallback(&InProcCrashReportSignalDispatcher); } +void +InProcCrashReportSetCrashKind(InProcCrashReportCrashKind crashKind) +{ + InterlockedExchange(&s_crashKind, static_cast(crashKind)); +} + +void +InProcCrashReportBeginStackOverflowTrace( + uint64_t crashingTid, + uint32_t totalFrameCount) +{ + InterlockedExchange(&s_stackOverflowTrace.available, 0); + s_stackOverflowTrace.crashingTid = crashingTid; + s_stackOverflowTrace.totalFrameCount = totalFrameCount; + s_stackOverflowTrace.frameCount = 0; + s_stackOverflowTrace.truncatedFrameCount = 0; +} + +void +InProcCrashReportAddStackOverflowTraceFrame( + const char* methodName, + uint32_t repeatCount, + uint32_t repeatSequenceLength) +{ + if (s_stackOverflowTrace.frameCount >= CRASHREPORT_STACK_OVERFLOW_MAX_TRACE_FRAMES) + { + ++s_stackOverflowTrace.truncatedFrameCount; + return; + } + + StackOverflowTraceFrame& frame = s_stackOverflowTrace.frames[s_stackOverflowTrace.frameCount++]; + CopyStringToBuffer(frame.methodName, sizeof(frame.methodName), methodName); + frame.repeatCount = repeatCount; + frame.repeatSequenceLength = repeatSequenceLength; +} + +void +InProcCrashReportCompleteStackOverflowTrace(uint32_t truncatedFrameCount) +{ + s_stackOverflowTrace.truncatedFrameCount += truncatedFrameCount; + InterlockedExchange(&s_stackOverflowTrace.available, 1); +} + bool CrashReportHelpers::WriteToFile( int fd, @@ -444,6 +801,15 @@ CrashReportHelpers::WriteToFile( return true; } +bool +CrashReportHelpers::DiscardOutputCallback( + const char* /*buffer*/, + size_t /*len*/, + void* /*ctx*/) +{ + return true; +} + bool CrashReportOutputContext::HandleChunk( const char* buffer, @@ -488,11 +854,15 @@ size_t CrashReportHelpers::ExpandDumpTemplate( char* buffer, size_t bufferSize, + char* numberBuffer, + size_t numberBufferSize, const char* pattern, const char* processName, const char* hostName) { - if (buffer == nullptr || bufferSize == 0 || pattern == nullptr) + if (buffer == nullptr || bufferSize == 0 || + numberBuffer == nullptr || numberBufferSize == 0 || + pattern == nullptr) { return 0; } @@ -512,7 +882,7 @@ CrashReportHelpers::ExpandDumpTemplate( char specifier = *pattern; const char* substitution = nullptr; - char numberBuf[CRASHREPORT_NUMBER_BUFFER_SIZE]; + numberBuffer[0] = '\0'; switch (specifier) { @@ -526,11 +896,11 @@ CrashReportHelpers::ExpandDumpTemplate( case 'p': case 'd': - if (SignalSafeJsonWriter::FormatUnsignedDecimal(numberBuf, sizeof(numberBuf), pid) == 0) + if (SignalSafeFormat::FormatUnsignedDecimal(numberBuffer, numberBufferSize, pid) == 0) { return 0; } - substitution = numberBuf; + substitution = numberBuffer; break; case 'e': @@ -542,12 +912,12 @@ CrashReportHelpers::ExpandDumpTemplate( break; case 't': - if (SignalSafeJsonWriter::FormatUnsignedDecimal( - numberBuf, sizeof(numberBuf), static_cast(time(nullptr))) == 0) + if (SignalSafeFormat::FormatUnsignedDecimal( + numberBuffer, numberBufferSize, static_cast(time(nullptr))) == 0) { return 0; } - substitution = numberBuf; + substitution = numberBuffer; break; default: @@ -594,24 +964,37 @@ bool CrashReportHelpers::BuildReportPath( char* buffer, size_t bufferSize, + char* expandedBuffer, + size_t expandedBufferSize, + char* numberBuffer, + size_t numberBufferSize, const char* dumpPath, const char* processName, const char* hostName) { - if (buffer == nullptr || bufferSize == 0 || dumpPath == nullptr || dumpPath[0] == '\0') + if (buffer == nullptr || bufferSize == 0 || + expandedBuffer == nullptr || expandedBufferSize == 0 || + numberBuffer == nullptr || numberBufferSize == 0 || + dumpPath == nullptr || dumpPath[0] == '\0') { return false; } - char expanded[CRASHREPORT_PATH_BUFFER_SIZE]; - size_t expandedLen = ExpandDumpTemplate(expanded, sizeof(expanded), dumpPath, processName, hostName); + size_t expandedLen = ExpandDumpTemplate( + expandedBuffer, + expandedBufferSize, + numberBuffer, + numberBufferSize, + dumpPath, + processName, + hostName); if (expandedLen == 0) { return false; } size_t pos = 0; - if (!AppendString(buffer, bufferSize, &pos, expanded)) + if (!AppendString(buffer, bufferSize, &pos, expandedBuffer)) { return false; } @@ -863,34 +1246,36 @@ CrashReportHelpers::GetFilename( return path; } +static bool +HasModuleName(const char* moduleName) +{ + return moduleName != nullptr && moduleName[0] != '\0'; +} + +static bool +HasManagedIdentity( + const char* methodName, + const char* moduleName, + uint32_t token) +{ + return methodName != nullptr || + (token != 0 && HasModuleName(moduleName)); +} + void CrashReportHelpers::CopyString( char* buffer, size_t bufferSize, const char* value) { - if (buffer == nullptr || bufferSize == 0) - { - return; - } - - if (value == nullptr) - { - buffer[0] = '\0'; - return; - } - - size_t toCopy = strnlen(value, bufferSize - 1); - if (toCopy != 0) - { - memcpy(buffer, value, toCopy); - } - - buffer[toCopy] = '\0'; + CopyStringToBuffer(buffer, bufferSize, value); } void -CrashReportHelpers::JsonFrameCallback( +CrashReportHelpers::WriteFrameToJson( + SignalSafeJsonWriter* writer, + char* methodNameBuffer, + size_t methodNameBufferSize, uint64_t ip, uint64_t stackPointer, const char* methodName, @@ -901,10 +1286,8 @@ CrashReportHelpers::JsonFrameCallback( uint32_t ilOffset, uint32_t moduleTimestamp, uint32_t moduleSize, - const char* moduleGuid, - void* ctx) + const GUID* moduleGuid) { - SignalSafeJsonWriter* writer = reinterpret_cast(ctx); if (writer == nullptr) { return; @@ -915,15 +1298,25 @@ CrashReportHelpers::JsonFrameCallback( writer->WriteHexAsString("native_address", ip); writer->WriteHexAsString("native_offset", nativeOffset); - if (methodName != nullptr) + if (HasManagedIdentity(methodName, moduleName, token)) { - char fullName[CRASHREPORT_STRING_BUFFER_SIZE]; - BuildMethodName(fullName, sizeof(fullName), className, methodName); - writer->WriteString("method_name", fullName); writer->WriteString("is_managed", "true"); - writer->WriteHexAsString("token", token); - writer->WriteHexAsString("il_offset", ilOffset); - if (moduleName != nullptr) + if (methodName != nullptr) + { + const char* fullMethodName = methodName; + if (methodNameBuffer != nullptr && methodNameBufferSize != 0) + { + BuildMethodName(methodNameBuffer, methodNameBufferSize, className, methodName); + fullMethodName = methodNameBuffer; + } + writer->WriteString("method_name", fullMethodName); + } + if (methodName != nullptr || token != 0) + { + writer->WriteHexAsString("token", token); + writer->WriteHexAsString("il_offset", ilOffset); + } + if (HasModuleName(moduleName)) { writer->WriteString("filename", moduleName); } @@ -935,15 +1328,16 @@ CrashReportHelpers::JsonFrameCallback( { writer->WriteHexAsString("sizeofimage", moduleSize); } - if (moduleGuid != nullptr && moduleGuid[0] != '\0') + if (moduleGuid != nullptr) { - writer->WriteString("guid", moduleGuid); + minipal_guid_as_string(*moduleGuid, s_moduleGuidScratch, sizeof(s_moduleGuidScratch)); + writer->WriteString("guid", s_moduleGuidScratch); } } else { writer->WriteString("is_managed", "false"); - if (moduleName != nullptr) + if (HasModuleName(moduleName)) { writer->WriteString("native_module", moduleName); } @@ -953,42 +1347,431 @@ CrashReportHelpers::JsonFrameCallback( } void -ThreadEnumerationContext::OnFrame( +CrashReportHelpers::WriteFrameToConsole( + SignalSafeConsoleWriter* consoleWriter, + char* methodNameBuffer, + size_t methodNameBufferSize, + uint32_t frameIndex, + int moduleIndex, uint64_t ip, - uint64_t stackPointer, const char* methodName, const char* className, - const char* moduleName, + const char* fallbackModuleName, uint32_t nativeOffset, uint32_t token, - uint32_t ilOffset, - uint32_t moduleTimestamp, - uint32_t moduleSize, - const char* moduleGuid) + uint32_t ilOffset) { - CrashReportHelpers::JsonFrameCallback(ip, stackPointer, methodName, className, moduleName, nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid, m_writer); -} + if (consoleWriter == nullptr) + { + return; + } -void + consoleWriter->AppendStr(" #"); + if (frameIndex < 10) + { + consoleWriter->AppendChar('0'); + } + consoleWriter->AppendDecimal(static_cast(frameIndex)); + consoleWriter->AppendChar(' '); + + if (moduleIndex >= 0) + { + consoleWriter->AppendChar('['); + consoleWriter->AppendDecimal(static_cast(moduleIndex)); + consoleWriter->AppendStr("] "); + } + else if ((methodName != nullptr || (token != 0 && HasModuleName(fallbackModuleName))) && HasModuleName(fallbackModuleName)) + { + consoleWriter->AppendStr("(in "); + consoleWriter->AppendStr(GetFilename(fallbackModuleName)); + consoleWriter->AppendStr(") "); + } + + if (methodName != nullptr) + { + const char* fullMethodName = methodName; + if (methodNameBuffer != nullptr && methodNameBufferSize != 0) + { + BuildMethodName(methodNameBuffer, methodNameBufferSize, className, methodName); + fullMethodName = methodNameBuffer; + } + consoleWriter->AppendStr(fullMethodName); + consoleWriter->AppendStr(" + 0x"); + consoleWriter->AppendHex(static_cast(ilOffset)); + consoleWriter->AppendStr(" (token=0x"); + consoleWriter->AppendHex(static_cast(token)); + consoleWriter->AppendChar(')'); + } + else if (token != 0 && HasModuleName(fallbackModuleName)) + { + consoleWriter->AppendStr("token=0x"); + consoleWriter->AppendHex(static_cast(token)); + consoleWriter->AppendStr(" + 0x"); + consoleWriter->AppendHex(static_cast(ilOffset)); + } + else + { + consoleWriter->AppendStr("0x"); + consoleWriter->AppendHex(ip); + if (HasModuleName(fallbackModuleName)) + { + consoleWriter->AppendStr(" ("); + consoleWriter->AppendStr(GetFilename(fallbackModuleName)); + consoleWriter->AppendStr(" + 0x"); + consoleWriter->AppendHex(static_cast(nativeOffset)); + consoleWriter->AppendChar(')'); + } + } + consoleWriter->EndLine(); +} + +void +CrashReportHelpers::WriteStackOverflowFrameToJson( + SignalSafeJsonWriter* writer, + const StackOverflowTraceFrame& frame, + bool includeRepeatMetadata) +{ + if (writer == nullptr) + { + return; + } + + writer->OpenObject(); + writer->WriteString("method_name", frame.methodName); + writer->WriteString("is_managed", "true"); + if (includeRepeatMetadata) + { + writer->WriteDecimalAsString("stack_overflow_repeat_count", frame.repeatCount); + writer->WriteDecimalAsString("stack_overflow_repeat_sequence_length", frame.repeatSequenceLength); + } + writer->CloseObject(); // frame +} + +void +CrashReportHelpers::WriteStackOverflowFrameToConsole( + SignalSafeConsoleWriter* consoleWriter, + uint32_t frameIndex, + const StackOverflowTraceFrame& frame) +{ + if (consoleWriter == nullptr) + { + return; + } + + consoleWriter->AppendStr(" #"); + if (frameIndex < 10) + { + consoleWriter->AppendChar('0'); + } + consoleWriter->AppendDecimal(static_cast(frameIndex)); + consoleWriter->AppendChar(' '); + consoleWriter->AppendStr(frame.methodName); + consoleWriter->EndLine(); +} + +void +CrashReportHelpers::BeginJsonThreadBlock( + SignalSafeJsonWriter* jsonWriter, + uint64_t osThreadId, + bool isManagedThread, + bool isCrashThread, + const char* exceptionType, + uint32_t exceptionHResult) +{ + if (jsonWriter == nullptr) + { + return; + } + + jsonWriter->OpenObject(); + jsonWriter->WriteString("is_managed", isManagedThread ? "true" : "false"); + jsonWriter->WriteString("crashed", isCrashThread ? "true" : "false"); + jsonWriter->WriteHexAsString("native_thread_id", osThreadId); + + if (exceptionType != nullptr && exceptionType[0] != '\0') + { + jsonWriter->WriteString("managed_exception_type", exceptionType); + jsonWriter->WriteHexAsString("managed_exception_hresult", exceptionHResult); + } +} + +void +CrashReportHelpers::BeginJsonStackFrames( + SignalSafeJsonWriter* jsonWriter, + bool writeCrashSiteFrame, + void* signalContext) +{ + if (jsonWriter == nullptr) + { + return; + } + + jsonWriter->OpenArray("stack_frames"); + if (writeCrashSiteFrame) + { + WriteCrashSiteFrameToJson(jsonWriter, signalContext); + } +} + +void +CrashReportHelpers::EndJsonStackFrames( + SignalSafeJsonWriter* jsonWriter) +{ + if (jsonWriter == nullptr) + { + return; + } + + jsonWriter->CloseArray(); // stack_frames +} + +void +CrashReportHelpers::EndJsonThreadBlock( + SignalSafeJsonWriter* jsonWriter) +{ + if (jsonWriter == nullptr) + { + return; + } + + jsonWriter->CloseObject(); // thread +} + +void +CrashReportHelpers::BeginConsoleThreadBlock( + SignalSafeConsoleWriter* consoleWriter, + uint64_t osThreadId, + bool isCrashThread) +{ + if (consoleWriter == nullptr) + { + return; + } + + consoleWriter->WriteBlank(); + consoleWriter->AppendStr("--- thread 0x"); + consoleWriter->AppendHex(osThreadId); + if (isCrashThread) + { + consoleWriter->AppendStr(" (crashed)"); + } + consoleWriter->AppendStr(" ---"); + consoleWriter->EndLine(); +} + +void +CrashReportHelpers::EndConsoleThreadBlock( + SignalSafeConsoleWriter* consoleWriter, + uint32_t frameCount, + uint32_t droppedCount) +{ + if (consoleWriter == nullptr) + { + return; + } + + if (frameCount == 0) + { + consoleWriter->WriteLine(" (no managed frames)"); + } + else if (droppedCount != 0) + { + consoleWriter->AppendStr(" ... +"); + consoleWriter->AppendDecimal(static_cast(droppedCount)); + consoleWriter->AppendStr(" more frames"); + consoleWriter->EndLine(); + } +} + +void +CrashReportHelpers::WriteFrame( + uint64_t ip, + uint64_t stackPointer, + const char* methodName, + const char* className, + const char* moduleName, + const void* moduleHandle, + uint32_t nativeOffset, + uint32_t token, + uint32_t ilOffset, + uint32_t moduleTimestamp, + uint32_t moduleSize, + const GUID* moduleGuid, + void* ctx) +{ + FrameContext* frameContext = reinterpret_cast(ctx); + if (frameContext == nullptr) + { + return; + } + + WriteFrameToReport( + frameContext->jsonWriter, + frameContext->consoleWriter, + frameContext->moduleInfoCallback, + frameContext->methodNameBuffer, + frameContext->methodNameBufferSize, + frameContext->currentThreadFrameCount, + frameContext->currentThreadDroppedCount, + frameContext->frameLimitPerThread, + ip, + stackPointer, + methodName, + className, + moduleName, + moduleHandle, + nativeOffset, + token, + ilOffset, + moduleTimestamp, + moduleSize, + moduleGuid); +} + +void +CrashReportHelpers::WriteFrameToReport( + SignalSafeJsonWriter* jsonWriter, + SignalSafeConsoleWriter* consoleWriter, + InProcCrashReportModuleInfoCallback moduleInfoCallback, + char* methodNameBuffer, + size_t methodNameBufferSize, + uint32_t* currentThreadFrameCount, + uint32_t* currentThreadDroppedCount, + uint32_t frameLimitPerThread, + uint64_t ip, + uint64_t stackPointer, + const char* methodName, + const char* className, + const char* moduleName, + const void* moduleHandle, + uint32_t nativeOffset, + uint32_t token, + uint32_t ilOffset, + uint32_t moduleTimestamp, + uint32_t moduleSize, + const GUID* moduleGuid) +{ + uint32_t frameIndex = currentThreadFrameCount != nullptr + ? *currentThreadFrameCount + : 0; + + // Always feed the JSON sink: the file output is the authoritative, + // post-mortem data store and the cap is a compact-log triage knob. + WriteFrameToJson(jsonWriter, + methodNameBuffer, + methodNameBufferSize, + ip, stackPointer, methodName, className, moduleName, + nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid); + + bool consoleCapped = frameLimitPerThread != 0 && + frameIndex >= frameLimitPerThread; + if (!consoleCapped) + { + int moduleIndex = moduleInfoCallback != nullptr && moduleHandle != nullptr + ? s_moduleTable.GetOrAddIndex(moduleHandle) + : -1; + WriteFrameToConsole(consoleWriter, + methodNameBuffer, + methodNameBufferSize, + frameIndex, moduleIndex, ip, methodName, className, moduleName, + nativeOffset, token, ilOffset); + } + else if (currentThreadDroppedCount != nullptr) + { + (*currentThreadDroppedCount)++; + } + + if (currentThreadFrameCount != nullptr) + { + (*currentThreadFrameCount)++; + } +} + +void +ThreadEnumerationContext::OnFrame( + uint64_t ip, + uint64_t stackPointer, + const char* methodName, + const char* className, + const char* moduleName, + const void* moduleHandle, + uint32_t nativeOffset, + uint32_t token, + uint32_t ilOffset, + uint32_t moduleTimestamp, + uint32_t moduleSize, + const GUID* moduleGuid) +{ + CrashReportHelpers::WriteFrameToReport( + m_jsonWriter, + m_consoleWriter, + m_moduleInfoCallback, + m_methodNameScratch, + sizeof(m_methodNameScratch), + &m_currentThreadFrameCount, + &m_currentThreadDroppedCount, + m_frameLimitPerThread, + ip, + stackPointer, + methodName, + className, + moduleName, + moduleHandle, + nativeOffset, + token, + ilOffset, + moduleTimestamp, + moduleSize, + moduleGuid); +} + +void ThreadEnumerationContext::FrameCallback( uint64_t ip, uint64_t stackPointer, const char* methodName, const char* className, const char* moduleName, + const void* moduleHandle, uint32_t nativeOffset, uint32_t token, uint32_t ilOffset, uint32_t moduleTimestamp, uint32_t moduleSize, - const char* moduleGuid, + const GUID* moduleGuid, void* ctx) { if (ctx == nullptr) { return; } - reinterpret_cast(ctx)->OnFrame(ip, stackPointer, methodName, className, moduleName, nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid); + reinterpret_cast(ctx)->OnFrame(ip, stackPointer, methodName, className, moduleName, moduleHandle, nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid); +} + +void +ThreadEnumerationContext::EndCurrentConsoleThreadBlock() +{ + if (m_threadCount == 0) + { + return; + } + + CrashReportHelpers::EndConsoleThreadBlock(m_consoleWriter, + m_currentThreadFrameCount, m_currentThreadDroppedCount); +} + +void +ThreadEnumerationContext::EndCurrentJsonThreadBlock() +{ + if (m_threadCount == 0) + { + return; + } + + CrashReportHelpers::EndJsonStackFrames(m_jsonWriter); + CrashReportHelpers::EndJsonThreadBlock(m_jsonWriter); + + (void)m_jsonWriter->Flush(); } void @@ -1000,10 +1783,8 @@ ThreadEnumerationContext::OnThread( { if (m_threadCount > 0) { - m_writer->CloseArray(); // stack_frames - m_writer->CloseObject(); // thread - - (void)m_writer->Flush(); + EndCurrentConsoleThreadBlock(); + EndCurrentJsonThreadBlock(); } if (isCrashThread) @@ -1011,27 +1792,32 @@ ThreadEnumerationContext::OnThread( m_sawCrashThread = true; } m_threadCount++; + m_currentThreadFrameCount = 0; + m_currentThreadDroppedCount = 0; - m_writer->OpenObject(); - m_writer->WriteString("is_managed", "true"); - m_writer->WriteString("crashed", isCrashThread ? "true" : "false"); - m_writer->WriteHexAsString("native_thread_id", osThreadId); - - if (exceptionType != nullptr && exceptionType[0] != '\0') - { - m_writer->WriteString("managed_exception_type", exceptionType); - m_writer->WriteHexAsString("managed_exception_hresult", exceptionHResult); - } + CrashReportHelpers::BeginJsonThreadBlock(m_jsonWriter, + osThreadId, /*isManagedThread*/ true, isCrashThread, exceptionType, exceptionHResult); if (isCrashThread) { - CrashReportHelpers::WriteRegistersToJson(m_writer, m_signalContext); + CrashReportHelpers::WriteRegistersToJson(m_jsonWriter, m_signalContext); } - m_writer->OpenArray("stack_frames"); - if (isCrashThread) + CrashReportHelpers::BeginJsonStackFrames(m_jsonWriter, isCrashThread, m_signalContext); + + if (m_consoleWriter != nullptr) { - CrashReportHelpers::WriteCrashSiteFrameToJson(m_writer, m_signalContext); + CrashReportHelpers::BeginConsoleThreadBlock(m_consoleWriter, osThreadId, isCrashThread); + + if (exceptionType != nullptr && exceptionType[0] != '\0') + { + m_consoleWriter->AppendStr(" managed exception: "); + m_consoleWriter->AppendStr(exceptionType); + m_consoleWriter->AppendStr(" (0x"); + m_consoleWriter->AppendHex(static_cast(exceptionHResult)); + m_consoleWriter->AppendChar(')'); + m_consoleWriter->EndLine(); + } } } @@ -1052,28 +1838,22 @@ ThreadEnumerationContext::ThreadCallback( void ThreadEnumerationContext::EnumerateThreads( - InProcCrashReportEnumerateThreadsCallback callback, - uint64_t crashingTid) + InProcCrashReportEnumerateThreadsCallback callback) { if (callback == nullptr) { return; } - callback(crashingTid, &ThreadCallback, &FrameCallback, this); + callback(m_crashingTid, &ThreadCallback, &FrameCallback, this); if (m_threadCount == 0) { return; } - // Close the last thread's stack_frames + thread objects opened by OnThread. - m_writer->CloseArray(); // stack_frames - m_writer->CloseObject(); // thread - - // Flush the final thread so it reaches the crash report file even if any - // later work (e.g. synthesizing a crash thread fallback) hangs or faults. - (void)m_writer->Flush(); + EndCurrentConsoleThreadBlock(); + EndCurrentJsonThreadBlock(); } void @@ -1083,19 +1863,307 @@ InProcCrashReporter::EmitSynthesizedCrashThread( { uint64_t crashingTid = static_cast(minipal_get_current_thread_id()); - m_jsonWriter.OpenObject(); - m_jsonWriter.WriteString("is_managed", - m_isManagedThreadCallback != nullptr && m_isManagedThreadCallback() ? "true" : "false"); - m_jsonWriter.WriteString("crashed", "true"); - m_jsonWriter.WriteHexAsString("native_thread_id", crashingTid); + bool isManagedThread = m_isManagedThreadCallback != nullptr && m_isManagedThreadCallback(); + CrashReportHelpers::BeginJsonThreadBlock(&m_jsonWriter, + crashingTid, isManagedThread, /*isCrashThread*/ true, nullptr, 0); CrashReportHelpers::WriteRegistersToJson(&m_jsonWriter, context); - m_jsonWriter.OpenArray("stack_frames"); - CrashReportHelpers::WriteCrashSiteFrameToJson(&m_jsonWriter, context); + CrashReportHelpers::BeginJsonStackFrames(&m_jsonWriter, /*writeCrashSiteFrame*/ true, context); + + CrashReportHelpers::BeginConsoleThreadBlock(&s_consoleWriter, crashingTid, /*isCrashThread*/ true); + + uint32_t synthesizedFrameCount = 0; + uint32_t synthesizedDroppedCount = 0; if (walkStack && m_walkStackCallback != nullptr) { - m_walkStackCallback(&CrashReportHelpers::JsonFrameCallback, &m_jsonWriter); + CrashReportHelpers::FrameContext frameContext = + { + &m_jsonWriter, + &s_consoleWriter, + m_moduleInfoCallback, + &synthesizedFrameCount, + &synthesizedDroppedCount, + m_frameLimitPerThread, + m_methodNameScratch, + sizeof(m_methodNameScratch), + }; + m_walkStackCallback(&CrashReportHelpers::WriteFrame, &frameContext); + } + CrashReportHelpers::EndConsoleThreadBlock(&s_consoleWriter, + synthesizedFrameCount, synthesizedDroppedCount); + + CrashReportHelpers::EndJsonStackFrames(&m_jsonWriter); + CrashReportHelpers::EndJsonThreadBlock(&m_jsonWriter); +} + +void +InProcCrashReporter::EmitStackOverflowCrashThread() +{ + bool stackOverflowTraceAvailable = s_stackOverflowTrace.available != 0; + uint64_t crashingTid = stackOverflowTraceAvailable && s_stackOverflowTrace.crashingTid != 0 + ? s_stackOverflowTrace.crashingTid + : static_cast(minipal_get_current_thread_id()); + + CrashReportHelpers::BeginJsonThreadBlock(&m_jsonWriter, + crashingTid, + /*isManagedThread*/ true, + /*isCrashThread*/ true, + CRASHREPORT_STACK_OVERFLOW_EXCEPTION_TYPE, + CRASHREPORT_COR_E_STACKOVERFLOW); + if (stackOverflowTraceAvailable) + { + m_jsonWriter.WriteDecimalAsString("stack_overflow_total_frames", s_stackOverflowTrace.totalFrameCount); + if (s_stackOverflowTrace.truncatedFrameCount != 0) + { + m_jsonWriter.WriteDecimalAsString("stack_overflow_trace_truncated_frames", s_stackOverflowTrace.truncatedFrameCount); + } + } + else + { + m_jsonWriter.WriteString("stack_frames_unavailable_reason", CRASHREPORT_STACK_OVERFLOW_TRACE_UNAVAILABLE_REASON); + } + + CrashReportHelpers::BeginJsonStackFrames(&m_jsonWriter, /*writeCrashSiteFrame*/ false, nullptr); + if (stackOverflowTraceAvailable) + { + for (uint32_t i = 0; i < s_stackOverflowTrace.frameCount;) + { + StackOverflowTraceFrame& frame = s_stackOverflowTrace.frames[i]; + uint32_t repeatSequenceLength = frame.repeatSequenceLength; + bool isRepeatSequence = frame.repeatCount > 1 && repeatSequenceLength != 0; + CrashReportHelpers::WriteStackOverflowFrameToJson( + &m_jsonWriter, frame, isRepeatSequence); + ++i; + + if (!isRepeatSequence) + { + continue; + } + + uint32_t sequenceEnd = i + repeatSequenceLength - 1; + if (sequenceEnd > s_stackOverflowTrace.frameCount) + { + sequenceEnd = s_stackOverflowTrace.frameCount; + } + + for (; i < sequenceEnd; ++i) + { + CrashReportHelpers::WriteStackOverflowFrameToJson( + &m_jsonWriter, s_stackOverflowTrace.frames[i], false); + } + } + } + CrashReportHelpers::EndJsonStackFrames(&m_jsonWriter); + CrashReportHelpers::EndJsonThreadBlock(&m_jsonWriter); + + CrashReportHelpers::BeginConsoleThreadBlock(&s_consoleWriter, crashingTid, /*isCrashThread*/ true); + s_consoleWriter.AppendStr(" managed exception: "); + s_consoleWriter.AppendStr(CRASHREPORT_STACK_OVERFLOW_EXCEPTION_TYPE); + s_consoleWriter.AppendStr(" (0x"); + s_consoleWriter.AppendHex(static_cast(CRASHREPORT_COR_E_STACKOVERFLOW)); + s_consoleWriter.AppendChar(')'); + s_consoleWriter.EndLine(); + + if (!stackOverflowTraceAvailable) + { + s_consoleWriter.WriteLine(" stack overflow trace unavailable"); + CrashReportHelpers::EndConsoleThreadBlock(&s_consoleWriter, 0, 0); + return; + } + + s_consoleWriter.AppendStr(" stack overflow frames: "); + s_consoleWriter.AppendDecimal(static_cast(s_stackOverflowTrace.totalFrameCount)); + s_consoleWriter.EndLine(); + + uint32_t consoleFrameCount = 0; + uint32_t consoleDroppedCount = s_stackOverflowTrace.truncatedFrameCount; + for (uint32_t i = 0; i < s_stackOverflowTrace.frameCount;) + { + StackOverflowTraceFrame& frame = s_stackOverflowTrace.frames[i]; + uint32_t repeatSequenceLength = frame.repeatSequenceLength; + if (frame.repeatCount > 1 && repeatSequenceLength != 0) + { + uint32_t sequenceEnd = i + repeatSequenceLength; + if (sequenceEnd > s_stackOverflowTrace.frameCount) + { + sequenceEnd = s_stackOverflowTrace.frameCount; + } + + if (m_frameLimitPerThread != 0 && consoleFrameCount >= m_frameLimitPerThread) + { + consoleDroppedCount += sequenceEnd - i; + i = sequenceEnd; + continue; + } + + s_consoleWriter.AppendStr(" repeated "); + s_consoleWriter.AppendDecimal(static_cast(frame.repeatCount)); + s_consoleWriter.AppendStr(" times:"); + s_consoleWriter.EndLine(); + + for (; i < sequenceEnd; ++i) + { + if (m_frameLimitPerThread != 0 && consoleFrameCount >= m_frameLimitPerThread) + { + consoleDroppedCount++; + continue; + } + + CrashReportHelpers::WriteStackOverflowFrameToConsole( + &s_consoleWriter, consoleFrameCount, s_stackOverflowTrace.frames[i]); + consoleFrameCount++; + } + + continue; + } + + if (m_frameLimitPerThread != 0 && consoleFrameCount >= m_frameLimitPerThread) + { + consoleDroppedCount++; + } + else + { + CrashReportHelpers::WriteStackOverflowFrameToConsole(&s_consoleWriter, consoleFrameCount, frame); + consoleFrameCount++; + } + ++i; + } + + CrashReportHelpers::EndConsoleThreadBlock(&s_consoleWriter, + consoleFrameCount, consoleDroppedCount); +} + +// --- InProcCrashReporter: console report lifecycle ------------------------- + +void +InProcCrashReporter::BeginConsoleReport(int signal) +{ + s_consoleWriter.WriteSeparator(); + s_consoleWriter.AppendStr(".NET Crash Report v"); + s_consoleWriter.AppendStr(CRASHREPORT_PROTOCOL_VERSION); + s_consoleWriter.EndLine(); + + CrashReportHelpers::GetVersionString(s_versionScratch, sizeof(s_versionScratch)); + if (s_versionScratch[0] != '\0') + { + s_consoleWriter.WriteKeyValueStr("Build", s_versionScratch); + } + + s_consoleWriter.WriteKeyValueStr("ABI", CRASHREPORT_ARCHITECTURE_NAME); + + if (m_processName[0] != '\0') + { + s_consoleWriter.WriteKeyValueStr("Cmdline", m_processName); + } + + s_consoleWriter.WriteKeyValueDecimal("pid", static_cast(GetCurrentProcessId())); + + s_consoleWriter.AppendStr("signal "); + s_consoleWriter.AppendSignedDecimal(signal); + s_consoleWriter.AppendStr(" ("); + s_consoleWriter.AppendStr(GetSignalNameAscii(signal)); + s_consoleWriter.AppendChar(')'); + s_consoleWriter.EndLine(); +} + +void +InProcCrashReporter::EndConsoleReport() +{ + if (s_moduleTable.Count() != 0) + { + s_consoleWriter.WriteBlank(); + s_consoleWriter.WriteLine("modules:"); + for (size_t i = 0; i < s_moduleTable.Count(); ++i) + { + s_consoleWriter.AppendStr(" ["); + s_consoleWriter.AppendDecimal(static_cast(i)); + s_consoleWriter.AppendStr("] "); + const char* moduleName = nullptr; + GUID moduleGuid; + if (m_moduleInfoCallback != nullptr && + m_moduleInfoCallback(s_moduleTable.ModuleHandle(i), &moduleName, &moduleGuid) && + HasModuleName(moduleName)) + { + s_consoleWriter.AppendStr(CrashReportHelpers::GetFilename(moduleName)); + s_consoleWriter.AppendChar(' '); + minipal_guid_as_string(moduleGuid, s_moduleGuidScratch, sizeof(s_moduleGuidScratch)); + s_consoleWriter.AppendStr(s_moduleGuidScratch); + } + else + { + s_consoleWriter.AppendStr(""); + } + s_consoleWriter.EndLine(); + } + } + + s_consoleWriter.WriteSeparator(); +} + +// --- InProcCrashReporter: JSON report lifecycle ---------------------------- + +void +InProcCrashReporter::BeginJsonReport() +{ + m_jsonWriter.OpenObject(); + m_jsonWriter.OpenObject("payload"); + m_jsonWriter.WriteString("protocol_version", CRASHREPORT_PROTOCOL_VERSION); + + m_jsonWriter.OpenObject("configuration"); + m_jsonWriter.WriteString("architecture", CRASHREPORT_ARCHITECTURE_NAME); + CrashReportHelpers::GetVersionString(s_versionScratch, sizeof(s_versionScratch)); + m_jsonWriter.WriteString("version", s_versionScratch); + m_jsonWriter.CloseObject(); // configuration + + if (m_processName[0] != '\0') + { + m_jsonWriter.WriteString("process_name", m_processName); + } + + m_jsonWriter.WriteDecimalAsString("pid", static_cast(GetCurrentProcessId())); +} + +void +InProcCrashReporter::EndJsonReport( + int signal, + bool jsonEnabled, + int fd) +{ + m_jsonWriter.CloseObject(); // payload + + m_jsonWriter.OpenObject("parameters"); + m_jsonWriter.WriteSignedDecimalAsString("signal", static_cast(signal)); +#ifdef __APPLE__ + if (m_osVersion[0] != '\0') + { + m_jsonWriter.WriteString("OSVersion", m_osVersion); + } + if (m_systemModel[0] != '\0') + { + m_jsonWriter.WriteString("SystemModel", m_systemModel); + } + m_jsonWriter.WriteString("SystemManufacturer", "apple"); +#endif + m_jsonWriter.CloseObject(); // parameters + + m_jsonWriter.CloseObject(); // root + + if (jsonEnabled) + { + bool finishSucceeded = m_jsonWriter.Finish(); + bool writeFailed = s_outputContext.WriteFailed(); + if (!CrashReportHelpers::WriteToFile(fd, "\n", 1)) + { + writeFailed = true; + } + + if (close(fd) != 0 || !finishSucceeded || writeFailed) + { + unlink(m_reportFilePathScratch); + } + } + else + { + (void)m_jsonWriter.Finish(); } - m_jsonWriter.CloseArray(); // stack_frames - m_jsonWriter.CloseObject(); // thread } diff --git a/src/coreclr/debug/crashreport/inproccrashreporter.h b/src/coreclr/debug/crashreport/inproccrashreporter.h index 5018f3b0d10793..3b051f21d60e1d 100644 --- a/src/coreclr/debug/crashreport/inproccrashreporter.h +++ b/src/coreclr/debug/crashreport/inproccrashreporter.h @@ -11,6 +11,8 @@ #include #include +#include + #include "signalsafejsonwriter.h" // Scratch-buffer sizes used throughout the in-proc crash reporter: @@ -24,6 +26,12 @@ static constexpr size_t CRASHREPORT_PATH_BUFFER_SIZE = 1024; static constexpr size_t CRASHREPORT_STRING_BUFFER_SIZE = 256; static constexpr size_t CRASHREPORT_NUMBER_BUFFER_SIZE = 32; +enum class InProcCrashReportCrashKind : uint32_t +{ + Unknown = 0, + StackOverflow = 1, +}; + using InProcCrashReportIsManagedThreadCallback = bool (*)(); using InProcCrashReportFrameCallback = void (*)( @@ -32,12 +40,13 @@ using InProcCrashReportFrameCallback = void (*)( const char* methodName, const char* className, const char* moduleName, + const void* moduleHandle, uint32_t nativeOffset, uint32_t token, uint32_t ilOffset, uint32_t moduleTimestamp, uint32_t moduleSize, - const char* moduleGuid, + const GUID* moduleGuid, void* ctx); using InProcCrashReportWalkStackCallback = void (*)( @@ -57,12 +66,19 @@ using InProcCrashReportEnumerateThreadsCallback = void (*)( InProcCrashReportFrameCallback frameCallback, void* ctx); +using InProcCrashReportModuleInfoCallback = bool (*)( + const void* moduleHandle, + const char** moduleName, + GUID* moduleGuid); + struct InProcCrashReporterSettings { const char* reportPath; InProcCrashReportIsManagedThreadCallback isManagedThreadCallback; InProcCrashReportWalkStackCallback walkStackCallback; InProcCrashReportEnumerateThreadsCallback enumerateThreadsCallback; + InProcCrashReportModuleInfoCallback moduleInfoCallback; + uint32_t frameLimitPerThread; }; class InProcCrashReporter @@ -76,7 +92,6 @@ class InProcCrashReporter void CreateReport( int signal, - siginfo_t* siginfo, void* context); private: @@ -88,17 +103,39 @@ class InProcCrashReporter void* context, bool walkStack); + void EmitStackOverflowCrashThread(); + + void EmitThreads( + InProcCrashReportCrashKind crashKind, + void* context); + + void BeginConsoleReport(int signal); + void EndConsoleReport(); + + void BeginJsonReport(); + void EndJsonReport( + int signal, + bool jsonEnabled, + int fd); + SignalSafeJsonWriter m_jsonWriter; InProcCrashReportIsManagedThreadCallback m_isManagedThreadCallback = nullptr; InProcCrashReportWalkStackCallback m_walkStackCallback = nullptr; InProcCrashReportEnumerateThreadsCallback m_enumerateThreadsCallback = nullptr; + InProcCrashReportModuleInfoCallback m_moduleInfoCallback = nullptr; char m_reportPath[CRASHREPORT_PATH_BUFFER_SIZE] = {}; + char m_reportFilePathScratch[CRASHREPORT_PATH_BUFFER_SIZE] = {}; + char m_expandedReportPathScratch[CRASHREPORT_PATH_BUFFER_SIZE] = {}; + char m_numberScratch[CRASHREPORT_NUMBER_BUFFER_SIZE] = {}; + char m_methodNameScratch[CRASHREPORT_STRING_BUFFER_SIZE] = {}; char m_processName[CRASHREPORT_STRING_BUFFER_SIZE] = {}; + char m_processNameScratch[CRASHREPORT_STRING_BUFFER_SIZE] = {}; char m_hostName[CRASHREPORT_STRING_BUFFER_SIZE] = {}; #ifdef __APPLE__ char m_osVersion[CRASHREPORT_STRING_BUFFER_SIZE] = {}; char m_systemModel[CRASHREPORT_STRING_BUFFER_SIZE] = {}; #endif + uint32_t m_frameLimitPerThread = 0; }; // Free-function entry point used by the runtime to wire the in-proc crash @@ -107,3 +144,17 @@ class InProcCrashReporter // PAL_SetInProcCrashReportCallback. PAL has no direct dependency on the // reporter; the only coupling is through this registered callback. void InProcCrashReportInitialize(const InProcCrashReporterSettings& settings); + +// Records crash kind hints from VM fatal paths that later terminate through PAL +// as a generic signal (for example stack overflow -> SIGABRT). +void InProcCrashReportSetCrashKind(InProcCrashReportCrashKind crashKind); + +// Captures the compressed stack-overflow trace built by the runtime SO helper +// thread so the later in-proc crash reporter can include the same managed stack +// without trying to walk from the exhausted crashing stack. +void InProcCrashReportBeginStackOverflowTrace(uint64_t crashingTid, uint32_t totalFrameCount); +void InProcCrashReportAddStackOverflowTraceFrame( + const char* methodName, + uint32_t repeatCount, + uint32_t repeatSequenceLength); +void InProcCrashReportCompleteStackOverflowTrace(uint32_t truncatedFrameCount); diff --git a/src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp b/src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp new file mode 100644 index 00000000000000..ccb9bd7fc983ae --- /dev/null +++ b/src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "signalsafeconsolewriter.h" +#include "signalsafeformat.h" + +#include +#include + +#if defined(__ANDROID__) +#include +static const char CRASHREPORT_LOG_TAG[] = "DOTNET_CRASH"; +#endif + +static const char CRASHREPORT_LINE_SEPARATOR[] = "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***"; + +void +SignalSafeConsoleWriter::AppendStr(const char* s) +{ + if (s == nullptr || m_pos + 1 >= sizeof(m_buffer)) + { + return; + } + + size_t available = sizeof(m_buffer) - 1 - m_pos; + size_t toCopy = strnlen(s, available); + if (toCopy != 0) + { + memcpy(m_buffer + m_pos, s, toCopy); + m_pos += toCopy; + } +} + +void +SignalSafeConsoleWriter::AppendChar(char c) +{ + if (m_pos + 1 < sizeof(m_buffer)) + { + m_buffer[m_pos++] = c; + } +} + +void +SignalSafeConsoleWriter::AppendHex(uint64_t v) +{ + char buf[SignalSafeFormat::MAX_HEX_BUFFER_SIZE]; + SignalSafeFormat::FormatHex(buf, sizeof(buf), v); + // Skip the leading "0x" so callers control whether the prefix appears + // (the compact format inserts it verbatim around the value). + const char* p = buf; + if (p[0] == '0' && p[1] == 'x') + { + p += 2; + } + AppendStr(p); +} + +void +SignalSafeConsoleWriter::AppendDecimal(uint64_t v) +{ + char buf[SignalSafeFormat::MAX_UNSIGNED_DECIMAL_BUFFER_SIZE]; + SignalSafeFormat::FormatUnsignedDecimal(buf, sizeof(buf), v); + AppendStr(buf); +} + +void +SignalSafeConsoleWriter::AppendSignedDecimal(int64_t v) +{ + char buf[SignalSafeFormat::MAX_SIGNED_DECIMAL_BUFFER_SIZE]; + SignalSafeFormat::FormatSignedDecimal(buf, sizeof(buf), v); + AppendStr(buf); +} + +void +SignalSafeConsoleWriter::EndLine() +{ + Flush(); +} + +void +SignalSafeConsoleWriter::WriteLine(const char* s) +{ + AppendStr(s); + EndLine(); +} + +void +SignalSafeConsoleWriter::WriteKeyValueStr(const char* key, const char* value) +{ + AppendStr(key); + AppendStr(": "); + AppendStr(value != nullptr ? value : ""); + EndLine(); +} + +void +SignalSafeConsoleWriter::WriteKeyValueDecimal(const char* key, uint64_t value) +{ + AppendStr(key); + AppendStr(": "); + AppendDecimal(value); + EndLine(); +} + +void +SignalSafeConsoleWriter::WriteSeparator() +{ + WriteLine(CRASHREPORT_LINE_SEPARATOR); +} + +void +SignalSafeConsoleWriter::Flush() +{ + // Always null-terminate so the platform write APIs see a proper C string. + if (m_pos < sizeof(m_buffer)) + { + m_buffer[m_pos] = '\0'; + } + else + { + m_buffer[sizeof(m_buffer) - 1] = '\0'; + } + +#if defined(__ANDROID__) + // __android_log_write expects a tag + null-terminated message; it adds its + // own line discipline so we deliberately do not append '\n'. Each call + // becomes one logcat entry, which is what makes per-line filtering useful. + __android_log_write(ANDROID_LOG_FATAL, CRASHREPORT_LOG_TAG, m_buffer); +#else + // On Apple/Linux the report goes to stderr; explicitly newline-terminate + // each line so log readers split entries the same way logcat would. + size_t newlinePos = m_pos < sizeof(m_buffer) - 1 ? m_pos : sizeof(m_buffer) - 2; + m_buffer[newlinePos++] = '\n'; + m_buffer[newlinePos] = '\0'; + minipal_log_write_error(m_buffer); +#endif + + m_pos = 0; + m_buffer[0] = '\0'; +} diff --git a/src/coreclr/debug/crashreport/signalsafeconsolewriter.h b/src/coreclr/debug/crashreport/signalsafeconsolewriter.h new file mode 100644 index 00000000000000..0a66d2b6bf76cb --- /dev/null +++ b/src/coreclr/debug/crashreport/signalsafeconsolewriter.h @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Bounded, signal-safe line-oriented console writer. Paired with +// SignalSafeJsonWriter as the second crash-report output sink: +// SignalSafeJsonWriter streams JSON to a file callback (compact, no +// line concept); SignalSafeConsoleWriter emits one logical line at a +// time to the platform console (Android logcat under the "DOTNET_CRASH" +// tag, stderr elsewhere). All public members are async-signal-safe: no +// heap allocation, no stdio, no locale or variadic formatting. +// +// Design choices below are driven by the prescribed compact crash report +// log format (specified at the top of inproccrashreporter.cpp): +// +// * One Flush per logical line (triggered by EndLine() / WriteLine()) +// instead of stream-buffer-fill flushing. Each call becomes exactly one +// __android_log_write entry on Android, so the format's line-oriented +// "header / per-thread block / modules / footer" structure maps 1:1 +// to logcat entries that filter cleanly under a single tag (`adb +// logcat *:S DOTNET_CRASH:F`) without cutting fields in half. On +// Apple/Linux each Flush adds an explicit '\n' for the same reason. +// +// * Unique "DOTNET_CRASH" logcat tag (distinct from the runtime's +// general "DOTNET" tag) so consumers can isolate the crash report from +// an otherwise noisy logcat with a single per-tag filter. +// +// * Best-effort silent truncation on per-line buffer overflow (Append* +// helpers all guard with `m_pos + 1 < sizeof(m_buffer)`). 512 bytes +// leaves comfortable headroom over the longest line the format +// produces (a fully-qualified Class.Method line at roughly +// CRASHREPORT_STRING_BUFFER_SIZE + line decoration), so truncation is +// reserved for unforeseen overrun and never fails any other +// crash-report output. + +#pragma once + +#include +#include + +static constexpr size_t SIGNAL_SAFE_CONSOLE_BUFFER_SIZE = 512; + +class SignalSafeConsoleWriter +{ +public: + SignalSafeConsoleWriter() + : m_pos(0) + { + m_buffer[0] = '\0'; + } + + SignalSafeConsoleWriter(const SignalSafeConsoleWriter&) = delete; + SignalSafeConsoleWriter& operator=(const SignalSafeConsoleWriter&) = delete; + + void AppendStr(const char* s); + void AppendChar(char c); + void AppendHex(uint64_t v); + void AppendDecimal(uint64_t v); + void AppendSignedDecimal(int64_t v); + void EndLine(); + + // Convenience for the many fixed strings emitted during the report. + void WriteLine(const char* s); + // "key: value" line shortcut (no string-escaping; values are trusted CLR strings). + void WriteKeyValueStr(const char* key, const char* value); + void WriteKeyValueDecimal(const char* key, uint64_t value); + + void WriteSeparator(); + void WriteBlank() { WriteLine(""); } + +private: + void Flush(); + + char m_buffer[SIGNAL_SAFE_CONSOLE_BUFFER_SIZE]; + size_t m_pos; +}; diff --git a/src/coreclr/debug/crashreport/signalsafeformat.cpp b/src/coreclr/debug/crashreport/signalsafeformat.cpp new file mode 100644 index 00000000000000..9efed80e982a22 --- /dev/null +++ b/src/coreclr/debug/crashreport/signalsafeformat.cpp @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "signalsafeformat.h" + +namespace SignalSafeFormat +{ + +void +FormatHex( + char* buffer, + size_t bufferSize, + uint64_t value) +{ + if (buffer == nullptr || bufferSize == 0) + { + return; + } + + char reverse[MAX_HEX_DIGITS_UINT64]; + size_t reverseLength = 0; + do + { + unsigned digit = static_cast(value & 0xf); + reverse[reverseLength++] = static_cast(digit < 10 ? ('0' + digit) : ('a' + digit - 10)); + value >>= 4; + } while (value != 0 && reverseLength < sizeof(reverse)); + + if (bufferSize < HEX_PREFIX_LEN + reverseLength + NULL_TERMINATOR_LEN) + { + buffer[0] = '\0'; + return; + } + + buffer[0] = '0'; + buffer[1] = 'x'; + + size_t index = HEX_PREFIX_LEN; + while (reverseLength > 0) + { + buffer[index++] = reverse[--reverseLength]; + } + buffer[index] = '\0'; +} + +size_t +FormatUnsignedDecimal( + char* buffer, + size_t bufferSize, + uint64_t value) +{ + if (buffer == nullptr || bufferSize == 0) + { + return 0; + } + + char reverse[MAX_DECIMAL_DIGITS_UINT64]; + size_t reverseLength = 0; + do + { + reverse[reverseLength++] = static_cast('0' + (value % 10)); + value /= 10; + } while (value != 0 && reverseLength < sizeof(reverse)); + + if (bufferSize < reverseLength + NULL_TERMINATOR_LEN) + { + buffer[0] = '\0'; + return 0; + } + + size_t pos = 0; + while (reverseLength > 0) + { + buffer[pos++] = reverse[--reverseLength]; + } + buffer[pos] = '\0'; + return pos; +} + +size_t +FormatSignedDecimal( + char* buffer, + size_t bufferSize, + int64_t value) +{ + if (buffer == nullptr || bufferSize == 0) + { + return 0; + } + + if (value >= 0) + { + return FormatUnsignedDecimal(buffer, bufferSize, static_cast(value)); + } + + if (bufferSize < SIGN_LEN + NULL_TERMINATOR_LEN) + { + buffer[0] = '\0'; + return 0; + } + + buffer[0] = '-'; + // Cast to unsigned first to handle INT64_MIN without signed overflow. + uint64_t absValue = static_cast(-(value + 1)) + 1; + size_t written = FormatUnsignedDecimal(buffer + 1, bufferSize - 1, absValue); + if (written == 0) + { + buffer[0] = '\0'; + return 0; + } + return written + 1; +} + +} // namespace SignalSafeFormat diff --git a/src/coreclr/debug/crashreport/signalsafeformat.h b/src/coreclr/debug/crashreport/signalsafeformat.h new file mode 100644 index 00000000000000..2bf40cf963dfe1 --- /dev/null +++ b/src/coreclr/debug/crashreport/signalsafeformat.h @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Async-signal-safe integer-to-string format primitives shared across the +// signal-safe writer family (SignalSafeJsonWriter, SignalSafeConsoleWriter, +// and any other consumer that needs to render integers without stdio, +// locale, or heap allocation). Bounded buffer-size constants document the +// minimum buffer required for each formatter. + +#pragma once + +#include +#include + +namespace SignalSafeFormat +{ + constexpr size_t MAX_HEX_DIGITS_UINT64 = 16; + constexpr size_t MAX_DECIMAL_DIGITS_UINT64 = 20; + constexpr size_t HEX_PREFIX_LEN = 2; // "0x" + constexpr size_t SIGN_LEN = 1; // '-' for signed decimals + constexpr size_t NULL_TERMINATOR_LEN = 1; + + constexpr size_t MAX_HEX_BUFFER_SIZE = HEX_PREFIX_LEN + MAX_HEX_DIGITS_UINT64 + NULL_TERMINATOR_LEN; + constexpr size_t MAX_UNSIGNED_DECIMAL_BUFFER_SIZE = MAX_DECIMAL_DIGITS_UINT64 + NULL_TERMINATOR_LEN; + constexpr size_t MAX_SIGNED_DECIMAL_BUFFER_SIZE = SIGN_LEN + MAX_DECIMAL_DIGITS_UINT64 + NULL_TERMINATOR_LEN; + + // Writes "0x"-prefixed hex (lowercase) of `value` into `buffer`. On + // success the buffer is null-terminated. If `buffer` is null, `bufferSize` + // is zero, or the buffer is too small to hold the formatted value, the + // buffer is left empty (or null-terminated at index 0 when possible). + void FormatHex(char* buffer, size_t bufferSize, uint64_t value); + + // Writes the unsigned-decimal representation of `value` into `buffer` and + // returns the number of bytes written (excluding the null terminator). + // Returns 0 on failure with the same buffer-state guarantees as FormatHex. + size_t FormatUnsignedDecimal(char* buffer, size_t bufferSize, uint64_t value); + + // Writes the signed-decimal representation of `value` into `buffer` and + // returns the number of bytes written (excluding the null terminator). + // Returns 0 on failure. Handles INT64_MIN without signed overflow. + size_t FormatSignedDecimal(char* buffer, size_t bufferSize, int64_t value); +} diff --git a/src/coreclr/debug/crashreport/signalsafejsonwriter.cpp b/src/coreclr/debug/crashreport/signalsafejsonwriter.cpp index 2cd858ab544564..44adea0af9b321 100644 --- a/src/coreclr/debug/crashreport/signalsafejsonwriter.cpp +++ b/src/coreclr/debug/crashreport/signalsafejsonwriter.cpp @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "signalsafejsonwriter.h" +#include "signalsafeformat.h" #include #include @@ -257,124 +258,13 @@ SignalSafeJsonWriter::WriteEscapedString( AppendChar('"'); } -// Bounded, async-signal-safe integer-to-string formatters. They write into the -// caller-supplied buffer and never allocate or call into stdio/locale code. -// If the buffer is too small to hold the maximum-width output (per the -// MAX_*_BUFFER_SIZE constants on SignalSafeJsonWriter), they leave only a null -// terminator and return early. - -void -SignalSafeJsonWriter::FormatHexValue( - char* buffer, - size_t bufferSize, - uint64_t value) -{ - if (buffer == nullptr || bufferSize == 0) - { - return; - } - - char reverse[MAX_HEX_DIGITS_UINT64]; - size_t reverseLength = 0; - do - { - unsigned digit = static_cast(value & 0xf); - reverse[reverseLength++] = static_cast(digit < 10 ? ('0' + digit) : ('a' + digit - 10)); - value >>= 4; - } while (value != 0 && reverseLength < sizeof(reverse)); - - if (bufferSize < HEX_PREFIX_LEN + reverseLength + NULL_TERMINATOR_LEN) - { - buffer[0] = '\0'; - return; - } - - buffer[0] = '0'; - buffer[1] = 'x'; - - size_t index = HEX_PREFIX_LEN; - while (reverseLength > 0) - { - buffer[index++] = reverse[--reverseLength]; - } - buffer[index] = '\0'; -} - -size_t -SignalSafeJsonWriter::FormatUnsignedDecimal( - char* buffer, - size_t bufferSize, - uint64_t value) -{ - if (buffer == nullptr || bufferSize == 0) - { - return 0; - } - - char reverse[MAX_DECIMAL_DIGITS_UINT64]; - size_t reverseLength = 0; - do - { - reverse[reverseLength++] = static_cast('0' + (value % 10)); - value /= 10; - } while (value != 0 && reverseLength < sizeof(reverse)); - - if (bufferSize < reverseLength + NULL_TERMINATOR_LEN) - { - buffer[0] = '\0'; - return 0; - } - - size_t pos = 0; - while (reverseLength > 0) - { - buffer[pos++] = reverse[--reverseLength]; - } - buffer[pos] = '\0'; - return pos; -} - -size_t -SignalSafeJsonWriter::FormatSignedDecimal( - char* buffer, - size_t bufferSize, - int64_t value) -{ - if (buffer == nullptr || bufferSize == 0) - { - return 0; - } - - if (value >= 0) - { - return FormatUnsignedDecimal(buffer, bufferSize, static_cast(value)); - } - - if (bufferSize < SIGN_LEN + NULL_TERMINATOR_LEN) - { - buffer[0] = '\0'; - return 0; - } - - buffer[0] = '-'; - // Cast to unsigned first to handle INT64_MIN without signed overflow. - uint64_t absValue = static_cast(-(value + 1)) + 1; - size_t written = FormatUnsignedDecimal(buffer + 1, bufferSize - 1, absValue); - if (written == 0) - { - buffer[0] = '\0'; - return 0; - } - return written + 1; -} - bool SignalSafeJsonWriter::WriteHexAsString( const char* key, uint64_t value) { - char scratch[MAX_HEX_FORMAT_BUFFER_SIZE]; - FormatHexValue(scratch, sizeof(scratch), value); + char scratch[SignalSafeFormat::MAX_HEX_BUFFER_SIZE]; + SignalSafeFormat::FormatHex(scratch, sizeof(scratch), value); return WriteString(key, scratch); } @@ -383,8 +273,8 @@ SignalSafeJsonWriter::WriteDecimalAsString( const char* key, uint64_t value) { - char scratch[MAX_UNSIGNED_DECIMAL_BUFFER_SIZE]; - (void)FormatUnsignedDecimal(scratch, sizeof(scratch), value); + char scratch[SignalSafeFormat::MAX_UNSIGNED_DECIMAL_BUFFER_SIZE]; + (void)SignalSafeFormat::FormatUnsignedDecimal(scratch, sizeof(scratch), value); return WriteString(key, scratch); } @@ -393,7 +283,7 @@ SignalSafeJsonWriter::WriteSignedDecimalAsString( const char* key, int64_t value) { - char scratch[MAX_SIGNED_DECIMAL_BUFFER_SIZE]; - (void)FormatSignedDecimal(scratch, sizeof(scratch), value); + char scratch[SignalSafeFormat::MAX_SIGNED_DECIMAL_BUFFER_SIZE]; + (void)SignalSafeFormat::FormatSignedDecimal(scratch, sizeof(scratch), value); return WriteString(key, scratch); } diff --git a/src/coreclr/debug/crashreport/signalsafejsonwriter.h b/src/coreclr/debug/crashreport/signalsafejsonwriter.h index 54eac5dbf6d30d..650e1edcb82802 100644 --- a/src/coreclr/debug/crashreport/signalsafejsonwriter.h +++ b/src/coreclr/debug/crashreport/signalsafejsonwriter.h @@ -19,16 +19,6 @@ static constexpr size_t SIGNAL_SAFE_JSON_BUFFER_SIZE = 4 * 1024; class SignalSafeJsonWriter { public: - // Maximum digit counts and required buffer sizes for the static format helpers below. - static constexpr size_t MAX_HEX_DIGITS_UINT64 = 16; - static constexpr size_t MAX_DECIMAL_DIGITS_UINT64 = 20; - static constexpr size_t HEX_PREFIX_LEN = 2; // "0x" - static constexpr size_t SIGN_LEN = 1; // '-' for signed decimals - static constexpr size_t NULL_TERMINATOR_LEN = 1; - static constexpr size_t MAX_HEX_FORMAT_BUFFER_SIZE = HEX_PREFIX_LEN + MAX_HEX_DIGITS_UINT64 + NULL_TERMINATOR_LEN; - static constexpr size_t MAX_UNSIGNED_DECIMAL_BUFFER_SIZE = MAX_DECIMAL_DIGITS_UINT64 + NULL_TERMINATOR_LEN; - static constexpr size_t MAX_SIGNED_DECIMAL_BUFFER_SIZE = SIGN_LEN + MAX_DECIMAL_DIGITS_UINT64 + NULL_TERMINATOR_LEN; - SignalSafeJsonWriter() : m_pos(0), m_commaNeeded(false), @@ -55,13 +45,6 @@ class SignalSafeJsonWriter bool Finish(); bool Flush(); - // Async-signal-safe integer-to-string formatters used by the Write* members - // above and by the few non-writer call sites that need the raw text (e.g. - // dump-name pattern expansion). All are bounded and never allocate. - static void FormatHexValue(char* buffer, size_t bufferSize, uint64_t value); - static size_t FormatUnsignedDecimal(char* buffer, size_t bufferSize, uint64_t value); - static size_t FormatSignedDecimal(char* buffer, size_t bufferSize, int64_t value); - private: bool Append(const char* str, size_t len); bool AppendChar(char c); diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index c9dd7c485c99c0..d37be482afd0c1 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -578,6 +578,7 @@ RETAIL_CONFIG_STRING_INFO(INTERNAL_DbgMiniDumpName, W("DbgMiniDumpName"), "Crash RETAIL_CONFIG_DWORD_INFO(INTERNAL_DbgMiniDumpType, W("DbgMiniDumpType"), 0, "Crash dump type: 1 normal, 2 withheap, 3 triage, 4 full") RETAIL_CONFIG_DWORD_INFO(INTERNAL_CreateDumpDiagnostics, W("CreateDumpDiagnostics"), 0, "Enable crash dump generation diagnostic logging") RETAIL_CONFIG_DWORD_INFO(INTERNAL_CrashReportBeforeSignalChaining, W("CrashReportBeforeSignalChaining"), 0, "Enable crash report generation before chaining to previous signal handler") +RETAIL_CONFIG_DWORD_INFO_EX(INTERNAL_CrashReportFrameLimitPerThread, W("CrashReportFrameLimitPerThread"), 32, "Maximum number of managed stack frames per thread to emit in the in-proc crash report's compact log; 0 disables the limit; remaining frames are summarized as '... +N more frames'", CLRConfig::LookupOptions::ParseIntegerAsBase10) /// /// R2R diff --git a/src/coreclr/vm/crashreportstackwalker.cpp b/src/coreclr/vm/crashreportstackwalker.cpp index 1670ec970d91ff..f1c20201097d81 100644 --- a/src/coreclr/vm/crashreportstackwalker.cpp +++ b/src/coreclr/vm/crashreportstackwalker.cpp @@ -23,8 +23,132 @@ struct WalkContext void* userCtx; }; +struct CrashReportStackWalkerScratch +{ + char crashExceptionType[CRASHREPORT_STRING_BUFFER_SIZE]; + char className[CRASHREPORT_STRING_BUFFER_SIZE]; + GUID moduleGuid; + bool hasModuleGuid; +}; + +static CrashReportStackWalkerScratch s_crashReportScratch; +static WalkContext s_walkContext; + static void BuildTypeName(LPUTF8 buffer, size_t bufferSize, LPCUTF8 namespaceName, LPCUTF8 className); +static +void +CrashReportGetModuleDetails( + Module* pModule, + LPCUTF8* moduleName, + GUID* moduleGuid, + bool* hasModuleGuid, + uint32_t* moduleTimestamp, + uint32_t* moduleSize) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + MODE_ANY; + } + CONTRACTL_END; + + if (moduleName != nullptr) + { + *moduleName = nullptr; + } + if (hasModuleGuid != nullptr) + { + *hasModuleGuid = false; + } + if (moduleTimestamp != nullptr) + { + *moduleTimestamp = 0; + } + if (moduleSize != nullptr) + { + *moduleSize = 0; + } + + if (pModule == nullptr) + { + return; + } + + if (moduleName != nullptr) + { + Assembly* pAssembly = pModule->GetAssembly(); + if (pAssembly != nullptr) + { + *moduleName = pAssembly->GetSimpleName(); + } + } + + if (moduleTimestamp != nullptr || moduleSize != nullptr) + { + PEAssembly* pPEAssembly = pModule->GetPEAssembly(); + if (pPEAssembly != nullptr && pPEAssembly->HasLoadedPEImage()) + { + if (moduleTimestamp != nullptr) + { + *moduleTimestamp = pPEAssembly->GetLoadedLayout()->GetTimeDateStamp(); + } + if (moduleSize != nullptr) + { + *moduleSize = static_cast(pPEAssembly->GetLoadedLayout()->GetSize()); + } + } + } + + if (moduleGuid != nullptr) + { + IMDInternalImport* pImport = pModule->GetMDImport(); + if (pImport != nullptr && SUCCEEDED(pImport->GetScopeProps(nullptr, moduleGuid))) + { + if (hasModuleGuid != nullptr) + { + *hasModuleGuid = true; + } + } + } +} + +static +bool +CrashReportGetModuleInfo( + const void* moduleHandle, + const char** moduleName, + GUID* moduleGuid) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + MODE_ANY; + } + CONTRACTL_END; + + if (moduleName == nullptr || moduleGuid == nullptr || moduleHandle == nullptr) + { + return false; + } + + LPCUTF8 resolvedModuleName = nullptr; + bool hasModuleGuid = false; + Module* pModule = reinterpret_cast(const_cast(moduleHandle)); + CrashReportGetModuleDetails(pModule, &resolvedModuleName, moduleGuid, &hasModuleGuid, nullptr, nullptr); + if (resolvedModuleName == nullptr || resolvedModuleName[0] == '\0' || !hasModuleGuid) + { + return false; + } + + *moduleName = resolvedModuleName; + return true; +} + static StackWalkAction FrameCallbackAdapter( @@ -68,19 +192,10 @@ FrameCallbackAdapter( } } - char classNameBuf[CRASHREPORT_STRING_BUFFER_SIZE]; - BuildTypeName(classNameBuf, sizeof(classNameBuf), namespaceName, className); + s_crashReportScratch.className[0] = '\0'; + BuildTypeName(s_crashReportScratch.className, sizeof(s_crashReportScratch.className), namespaceName, className); - LPCUTF8 moduleName = nullptr; Module* pModule = pMD->GetModule(); - if (pModule != nullptr) - { - Assembly* pAssembly = pModule->GetAssembly(); - if (pAssembly != nullptr) - { - moduleName = pAssembly->GetSimpleName(); - } - } uint32_t nativeOffset = pCF->HasFaulted() ? 0 : pCF->GetRelOffset(); uint32_t ilOffset = 0; @@ -122,33 +237,20 @@ FrameCallbackAdapter( } } + LPCUTF8 moduleName = nullptr; uint32_t moduleTimestamp = 0; uint32_t moduleSize = 0; - char moduleGuid[MINIPAL_GUID_BUFFER_LEN]; - moduleGuid[0] = '\0'; - - if (pModule != nullptr) - { - PEAssembly* pPEAssembly = pModule->GetPEAssembly(); - if (pPEAssembly != nullptr && pPEAssembly->HasLoadedPEImage()) - { - moduleTimestamp = pPEAssembly->GetLoadedLayout()->GetTimeDateStamp(); - moduleSize = static_cast(pPEAssembly->GetLoadedLayout()->GetSize()); - } - - IMDInternalImport* pImport = pModule->GetMDImport(); - if (pImport != nullptr) - { - GUID mvid; - if (SUCCEEDED(pImport->GetScopeProps(nullptr, &mvid))) - { - minipal_guid_as_string(mvid, moduleGuid, MINIPAL_GUID_BUFFER_LEN); - } - } - } - - className = classNameBuf[0] == '\0' ? nullptr : classNameBuf; - ctx->callback(static_cast(ip), static_cast(stackPointer), methodName, className, moduleName, nativeOffset, static_cast(token), ilOffset, moduleTimestamp, moduleSize, moduleGuid, ctx->userCtx); + s_crashReportScratch.hasModuleGuid = false; + CrashReportGetModuleDetails( + pModule, + &moduleName, + &s_crashReportScratch.moduleGuid, + &s_crashReportScratch.hasModuleGuid, + &moduleTimestamp, + &moduleSize); + + className = s_crashReportScratch.className[0] == '\0' ? nullptr : s_crashReportScratch.className; + ctx->callback(static_cast(ip), static_cast(stackPointer), methodName, className, moduleName, pModule, nativeOffset, static_cast(token), ilOffset, moduleTimestamp, moduleSize, s_crashReportScratch.hasModuleGuid ? &s_crashReportScratch.moduleGuid : nullptr, ctx->userCtx); return SWA_CONTINUE; } @@ -164,8 +266,9 @@ CrashReportWalkThread( return; } - WalkContext walkContext = { frameCallback, ctx }; - pThread->StackWalkFrames(FrameCallbackAdapter, &walkContext, + s_walkContext.callback = frameCallback; + s_walkContext.userCtx = ctx; + pThread->StackWalkFrames(FrameCallbackAdapter, &s_walkContext, QUICKUNWIND | FUNCTIONSONLY | ALLOW_ASYNC_STACK_WALK); } @@ -359,8 +462,7 @@ CrashReportEnumerateThreads( // so the throwable inspection runs in the thread's natural EE-live context, // outside the suspended window which exists for safe-point operations on // other threads. - char crashExceptionType[CRASHREPORT_STRING_BUFFER_SIZE]; - crashExceptionType[0] = '\0'; + s_crashReportScratch.crashExceptionType[0] = '\0'; uint32_t crashHresult = 0; bool crashHasException = false; bool isCrashingThread = pCrashThread != nullptr @@ -368,7 +470,10 @@ CrashReportEnumerateThreads( if (isCrashingThread) { crashHasException = CrashReportGetExceptionForThread( - pCrashThread, crashExceptionType, sizeof(crashExceptionType), &crashHresult); + pCrashThread, + s_crashReportScratch.crashExceptionType, + sizeof(s_crashReportScratch.crashExceptionType), + &crashHresult); } bool runtimeSuspended = CrashReportSuspendThreads(pCrashThread); @@ -378,7 +483,7 @@ CrashReportEnumerateThreads( if (isCrashingThread) { uint64_t crashOsId = static_cast(pCrashThread->GetOSThreadId()); - threadCallback(crashOsId, true, crashHasException ? crashExceptionType : "", crashHresult, ctx); + threadCallback(crashOsId, true, crashHasException ? s_crashReportScratch.crashExceptionType : "", crashHresult, ctx); CrashReportWalkThread(pCrashThread, frameCallback, ctx); } @@ -427,16 +532,14 @@ CrashReportConfigure() CLRConfigNoCache dmpNameCfg = CLRConfigNoCache::Get("DbgMiniDumpName", /*noprefix*/ false, &getenv); const char* dumpName = dmpNameCfg.IsSet() ? dmpNameCfg.AsString() : nullptr; - if (dumpName == nullptr || dumpName[0] == '\0') - { - return; - } InProcCrashReporterSettings settings = {}; settings.reportPath = dumpName; settings.isManagedThreadCallback = CrashReportIsCurrentThreadManaged; settings.walkStackCallback = CrashReportWalkStack; settings.enumerateThreadsCallback = CrashReportEnumerateThreads; + settings.moduleInfoCallback = CrashReportGetModuleInfo; + settings.frameLimitPerThread = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CrashReportFrameLimitPerThread); // Initialize the reporter and register the PAL signal-path callback last // so PAL only observes the reporter after all VM callbacks are wired in. diff --git a/src/coreclr/vm/eepolicy.cpp b/src/coreclr/vm/eepolicy.cpp index 1462aeb827f681..19b23bf3c9a927 100644 --- a/src/coreclr/vm/eepolicy.cpp +++ b/src/coreclr/vm/eepolicy.cpp @@ -16,6 +16,10 @@ #include "typestring.h" +#ifdef FEATURE_INPROC_CRASHREPORT +#include "inproccrashreporter.h" +#endif + #ifndef TARGET_UNIX #include "dwreport.h" #endif // !TARGET_UNIX @@ -210,6 +214,20 @@ class CallStackLogger PrintToStdErrW(str.GetUnicode()); } +#ifdef FEATURE_INPROC_CRASHREPORT + void CaptureFrameForCrashReport(int index, uint32_t repeatCount, uint32_t repeatSequenceLength) + { + WRAPPER_NO_CONTRACT; + + SString str; + + MethodDesc* pMD = m_frames[index]; + TypeString::AppendMethodInternal(str, pMD, TypeString::FormatNamespace|TypeString::FormatFullInst|TypeString::FormatSignature); + + InProcCrashReportAddStackOverflowTraceFrame(str.GetUTF8(), repeatCount, repeatSequenceLength); + } +#endif // FEATURE_INPROC_CRASHREPORT + public: CallStackLogger(PEXCEPTION_POINTERS pExceptionInfo) @@ -228,7 +246,7 @@ class CallStackLogger return logger->LogCallstackForLogCallbackWorker(pCF); } - void PrintStackTrace(const WCHAR* pWordAt) + void PrintStackTrace(const WCHAR* pWordAt, uint64_t crashingTid) { WRAPPER_NO_CONTRACT; @@ -302,8 +320,15 @@ class CallStackLogger largestCommonLength = 0; } +#ifdef FEATURE_INPROC_CRASHREPORT + InProcCrashReportBeginStackOverflowTrace(crashingTid, static_cast(m_frames.Count())); +#endif // FEATURE_INPROC_CRASHREPORT + for (int i = 0; i < largestCommonStartOffset; i++) { +#ifdef FEATURE_INPROC_CRASHREPORT + CaptureFrameForCrashReport(i, 0, 0); +#endif // FEATURE_INPROC_CRASHREPORT PrintFrame(i, pWordAt); } @@ -317,6 +342,11 @@ class CallStackLogger PrintToStdErrA("--------------------------------\n"); for (int i = largestCommonStartOffset; i < largestCommonStartOffset + largestCommonLength; i++) { +#ifdef FEATURE_INPROC_CRASHREPORT + CaptureFrameForCrashReport(i, + static_cast(largestCommonRepeat), + static_cast(largestCommonLength)); +#endif // FEATURE_INPROC_CRASHREPORT PrintFrame(i, pWordAt); } PrintToStdErrA("--------------------------------\n"); @@ -324,8 +354,15 @@ class CallStackLogger for (int i = largestCommonLength * largestCommonRepeat + largestCommonStartOffset; i < m_frames.Count(); i++) { +#ifdef FEATURE_INPROC_CRASHREPORT + CaptureFrameForCrashReport(i, 0, 0); +#endif // FEATURE_INPROC_CRASHREPORT PrintFrame(i, pWordAt); } + +#ifdef FEATURE_INPROC_CRASHREPORT + InProcCrashReportCompleteStackOverflowTrace(0); +#endif // FEATURE_INPROC_CRASHREPORT } }; @@ -365,7 +402,7 @@ inline void LogCallstackForLogWorker(Thread* pThread, PEXCEPTION_POINTERS pExcep pThread->StackWalkFrames(&CallStackLogger::LogCallstackForLogCallback, &logger, QUICKUNWIND | FUNCTIONSONLY | ALLOW_ASYNC_STACK_WALK); - logger.PrintStackTrace(WordAt.GetUnicode()); + logger.PrintStackTrace(WordAt.GetUnicode(), static_cast(pThread->GetOSThreadId())); #ifdef _DEBUG if (g_LogStackOverflowExit) PrintToStdErrA("@Exiting stack trace printing thread.\n"); @@ -819,6 +856,9 @@ void DECLSPEC_NORETURN EEPolicy::HandleFatalStackOverflow(EXCEPTION_POINTERS *pE #ifdef _DEBUG if (g_LogStackOverflowExit) PrintToStdErrA("@Terminating the process.\n"); +#endif +#ifdef FEATURE_INPROC_CRASHREPORT + InProcCrashReportSetCrashKind(InProcCrashReportCrashKind::StackOverflow); #endif CrashDumpAndTerminateProcess(COR_E_STACKOVERFLOW); UNREACHABLE();