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..51304b2a86a505 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,51 @@ #include #include #include +#include #include #ifdef __APPLE__ #include #include #endif +extern "C" const char* PROCGetSignalNameAscii(int signal); + +static const char CRASHREPORT_PROTOCOL_VERSION[] = "1.0.0"; + +#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. +// +// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** (EmitConsoleHeader) +// .NET Crash Report v +// Build: (omitted if empty) +// ABI: amd64|arm64|arm +// Cmdline: (omitted if empty) +// pid: +// signal () +// (blank between sections) +// --- thread 0xTID [(crashed)] --- (per thread; OnThread) +// 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 (FinishCurrentThreadCompactBlock) +// (blank between threads) +// modules: (EmitConsoleModulesAndFooter) +// [N] {} (one per ModuleTable entry) +// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** (closing separator) + +static SignalSafeConsoleWriter s_consoleWriter; + // Include the .NET version string instead of linking because it is "static". #if __has_include("_version.c") #include "_version.c" @@ -51,15 +92,90 @@ static void CacheSysctlString(const char* sysctlName, char* buffer, size_t buffe } #endif // __APPLE__ +// Bounded module name/GUID 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 the (often verbose) +// filename + GUID on every line; the matching ``modules:`` block at the end +// of the report maps each index back to the 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, or empty/null GUID) +// 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 = 64; + +class ModuleTable +{ +public: + int GetOrAddIndex(const char* moduleName, const char* moduleGuid) + { + if (moduleName == nullptr || moduleName[0] == '\0' || + moduleGuid == nullptr || moduleGuid[0] == '\0') + { + return -1; + } + + for (size_t i = 0; i < m_count; ++i) + { + if (strncmp(m_entries[i].guid, moduleGuid, MINIPAL_GUID_BUFFER_LEN) == 0) + { + return static_cast(i); + } + } + + if (m_count >= MAX_MODULES_IN_TABLE) + { + return -1; + } + + Entry& entry = m_entries[m_count]; + size_t nameLen = strnlen(moduleName, sizeof(entry.name) - 1); + memcpy(entry.name, moduleName, nameLen); + entry.name[nameLen] = '\0'; + size_t guidLen = strnlen(moduleGuid, sizeof(entry.guid) - 1); + memcpy(entry.guid, moduleGuid, guidLen); + entry.guid[guidLen] = '\0'; + return static_cast(m_count++); + } + + size_t Count() const { return m_count; } + const char* Name(size_t i) const { return m_entries[i].name; } + const char* Guid(size_t i) const { return m_entries[i].guid; } + +private: + struct Entry + { + char name[CRASHREPORT_STRING_BUFFER_SIZE]; + char guid[MINIPAL_GUID_BUFFER_LEN]; + }; + + Entry m_entries[MAX_MODULES_IN_TABLE]; + size_t m_count = 0; +}; + +static ModuleTable s_moduleTable; + class ThreadEnumerationContext { public: ThreadEnumerationContext( SignalSafeJsonWriter* writer, + SignalSafeConsoleWriter* consoleWriter, + uint64_t crashingTid, + uint32_t frameLimitPerThread, void* signalContext) : m_writer(writer), + m_consoleWriter(consoleWriter), m_signalContext(signalContext), m_threadCount(0), + m_crashingTid(crashingTid), + m_currentThreadFrameCount(0), + m_currentThreadDroppedCount(0), + m_frameLimitPerThread(frameLimitPerThread), m_sawCrashThread(false) { } @@ -70,8 +186,9 @@ class ThreadEnumerationContext size_t ThreadCount() const { return m_threadCount; } bool SawCrashThread() const { return m_sawCrashThread; } SignalSafeJsonWriter* Writer() const { return m_writer; } + SignalSafeConsoleWriter* ConsoleWriter() const { return m_consoleWriter; } - void EnumerateThreads(InProcCrashReportEnumerateThreadsCallback callback, uint64_t crashingTid); + void EnumerateThreads(InProcCrashReportEnumerateThreadsCallback callback); static void ThreadCallback( uint64_t osThreadId, @@ -114,9 +231,16 @@ class ThreadEnumerationContext uint32_t moduleSize, const char* moduleGuid); + void FinishCurrentThreadCompactBlock(); + SignalSafeJsonWriter* m_writer; + SignalSafeConsoleWriter* m_consoleWriter; 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; }; @@ -147,6 +271,15 @@ class CrashReportOutputContext class CrashReportHelpers { public: + struct FrameSinks + { + SignalSafeJsonWriter* writer; + SignalSafeConsoleWriter* consoleWriter; + uint32_t* currentThreadFrameCount; + uint32_t* currentThreadDroppedCount; + uint32_t frameLimitPerThread; + }; + static void GetVersionString( char* buffer, size_t bufferSize); @@ -202,11 +335,67 @@ class CrashReportHelpers const char* moduleGuid, void* ctx); + static void WriteFrameToJson( + SignalSafeJsonWriter* writer, + 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 char* moduleGuid); + + static void WriteFrameToConsole( + SignalSafeConsoleWriter* consoleWriter, + uint32_t frameIndex, + int moduleIndex, + uint64_t ip, + const char* methodName, + const char* className, + const char* moduleName, + uint32_t nativeOffset, + uint32_t token, + uint32_t ilOffset); + + static void WriteThreadBlockHeaderToConsole( + SignalSafeConsoleWriter* consoleWriter, + uint64_t osThreadId, + bool isCrashThread); + + static void WriteThreadBlockCloserToConsole( + SignalSafeConsoleWriter* consoleWriter, + uint32_t frameCount, + uint32_t droppedCount); + + static void FrameSinkCallback( + 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 char* moduleGuid, + void* ctx); + 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, @@ -237,54 +426,47 @@ InProcCrashReporter::CreateReport( 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)) - { - return; - } + // 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(reportPath, sizeof(reportPath), m_reportPath, m_processName, m_hostName); - int fd = open(reportPath, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd == -1) + int fd = -1; + if (jsonEnabled) { - return; + fd = open(reportPath, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd == -1) + { + jsonEnabled = false; + } } (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"); + EmitConsoleHeader(signal); - 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') + CrashReportOutputContext outputContext(fd); + if (jsonEnabled) { - m_jsonWriter.WriteString("process_name", m_processName); + m_jsonWriter.Init(&CrashReportOutputContext::ChunkCallback, &outputContext); + } + else + { + m_jsonWriter.Init(&CrashReportHelpers::DiscardOutputCallback, nullptr); } - m_jsonWriter.WriteDecimalAsString("pid", static_cast(GetCurrentProcessId())); + EmitJsonHeader(); m_jsonWriter.OpenArray("threads"); if (m_enumerateThreadsCallback != nullptr) { - ThreadEnumerationContext threadContext(&m_jsonWriter, context); uint64_t crashingTid = static_cast(minipal_get_current_thread_id()); + ThreadEnumerationContext threadContext(&m_jsonWriter, &s_consoleWriter, crashingTid, m_frameLimitPerThread, context); - threadContext.EnumerateThreads(m_enumerateThreadsCallback, crashingTid); + threadContext.EnumerateThreads(m_enumerateThreadsCallback); if (threadContext.ThreadCount() == 0 || !threadContext.SawCrashThread()) { @@ -297,26 +479,11 @@ InProcCrashReporter::CreateReport( } 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 + EmitJsonFooter(signal); - m_jsonWriter.CloseObject(); // root + EmitConsoleModulesAndFooter(); - if (fd != -1) + if (jsonEnabled) { bool writeSucceeded = m_jsonWriter.Finish() && !outputContext.WriteFailed() && @@ -327,6 +494,10 @@ InProcCrashReporter::CreateReport( unlink(reportPath); } } + else + { + (void)m_jsonWriter.Finish(); + } } InProcCrashReporter& @@ -343,6 +514,7 @@ InProcCrashReporter::Initialize( m_isManagedThreadCallback = settings.isManagedThreadCallback; m_walkStackCallback = settings.walkStackCallback; m_enumerateThreadsCallback = settings.enumerateThreadsCallback; + m_frameLimitPerThread = settings.frameLimitPerThread; CrashReportHelpers::CopyString(m_reportPath, sizeof(m_reportPath), settings.reportPath); m_processName[0] = '\0'; @@ -444,6 +616,15 @@ CrashReportHelpers::WriteToFile( return true; } +bool +CrashReportHelpers::DiscardOutputCallback( + const char* /*buffer*/, + size_t /*len*/, + void* /*ctx*/) +{ + return true; +} + bool CrashReportOutputContext::HandleChunk( const char* buffer, @@ -526,7 +707,7 @@ CrashReportHelpers::ExpandDumpTemplate( case 'p': case 'd': - if (SignalSafeJsonWriter::FormatUnsignedDecimal(numberBuf, sizeof(numberBuf), pid) == 0) + if (SignalSafeFormat::FormatUnsignedDecimal(numberBuf, sizeof(numberBuf), pid) == 0) { return 0; } @@ -542,7 +723,7 @@ CrashReportHelpers::ExpandDumpTemplate( break; case 't': - if (SignalSafeJsonWriter::FormatUnsignedDecimal( + if (SignalSafeFormat::FormatUnsignedDecimal( numberBuf, sizeof(numberBuf), static_cast(time(nullptr))) == 0) { return 0; @@ -910,6 +1091,30 @@ CrashReportHelpers::JsonFrameCallback( return; } + WriteFrameToJson(writer, ip, stackPointer, methodName, className, moduleName, + nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid); +} + +void +CrashReportHelpers::WriteFrameToJson( + SignalSafeJsonWriter* writer, + 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 char* moduleGuid) +{ + if (writer == nullptr) + { + return; + } + writer->OpenObject(); writer->WriteHexAsString("stack_pointer", stackPointer); writer->WriteHexAsString("native_address", ip); @@ -952,6 +1157,168 @@ CrashReportHelpers::JsonFrameCallback( writer->CloseObject(); // frame } +void +CrashReportHelpers::WriteFrameToConsole( + SignalSafeConsoleWriter* consoleWriter, + uint32_t frameIndex, + int moduleIndex, + uint64_t ip, + const char* methodName, + const char* className, + const char* moduleName, + uint32_t nativeOffset, + uint32_t token, + uint32_t ilOffset) +{ + if (consoleWriter == nullptr) + { + return; + } + + 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 && moduleName != nullptr && moduleName[0] != '\0') + { + consoleWriter->AppendStr("(in "); + consoleWriter->AppendStr(GetFilename(moduleName)); + consoleWriter->AppendStr(") "); + } + + if (methodName != nullptr) + { + char fullName[CRASHREPORT_STRING_BUFFER_SIZE]; + BuildMethodName(fullName, sizeof(fullName), className, methodName); + consoleWriter->AppendStr(fullName); + consoleWriter->AppendStr(" + 0x"); + consoleWriter->AppendHex(static_cast(ilOffset)); + consoleWriter->AppendStr(" (token=0x"); + consoleWriter->AppendHex(static_cast(token)); + consoleWriter->AppendChar(')'); + } + else + { + consoleWriter->AppendStr("0x"); + consoleWriter->AppendHex(ip); + if (moduleName != nullptr && moduleName[0] != '\0') + { + consoleWriter->AppendStr(" ("); + consoleWriter->AppendStr(GetFilename(moduleName)); + consoleWriter->AppendStr(" + 0x"); + consoleWriter->AppendHex(static_cast(nativeOffset)); + consoleWriter->AppendChar(')'); + } + } + consoleWriter->EndLine(); +} + +void +CrashReportHelpers::WriteThreadBlockHeaderToConsole( + 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::WriteThreadBlockCloserToConsole( + 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::FrameSinkCallback( + 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 char* moduleGuid, + void* ctx) +{ + FrameSinks* sinks = reinterpret_cast(ctx); + if (sinks == nullptr) + { + return; + } + + uint32_t frameIndex = sinks->currentThreadFrameCount != nullptr + ? *sinks->currentThreadFrameCount + : 0; + + int moduleIndex = s_moduleTable.GetOrAddIndex(moduleName, moduleGuid); + + // 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(sinks->writer, ip, stackPointer, methodName, className, moduleName, + nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid); + + bool consoleCapped = sinks->frameLimitPerThread != 0 && + frameIndex >= sinks->frameLimitPerThread; + if (!consoleCapped) + { + WriteFrameToConsole(sinks->consoleWriter, frameIndex, moduleIndex, ip, methodName, className, moduleName, + nativeOffset, token, ilOffset); + } + else if (sinks->currentThreadDroppedCount != nullptr) + { + ++*sinks->currentThreadDroppedCount; + } + + if (sinks->currentThreadFrameCount != nullptr) + { + ++*sinks->currentThreadFrameCount; + } +} + void ThreadEnumerationContext::OnFrame( uint64_t ip, @@ -966,7 +1333,16 @@ ThreadEnumerationContext::OnFrame( uint32_t moduleSize, const char* moduleGuid) { - CrashReportHelpers::JsonFrameCallback(ip, stackPointer, methodName, className, moduleName, nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid, m_writer); + CrashReportHelpers::FrameSinks sinks = + { + m_writer, + m_consoleWriter, + &m_currentThreadFrameCount, + &m_currentThreadDroppedCount, + m_frameLimitPerThread, + }; + CrashReportHelpers::FrameSinkCallback(ip, stackPointer, methodName, className, moduleName, + nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid, &sinks); } void @@ -991,6 +1367,18 @@ ThreadEnumerationContext::FrameCallback( reinterpret_cast(ctx)->OnFrame(ip, stackPointer, methodName, className, moduleName, nativeOffset, token, ilOffset, moduleTimestamp, moduleSize, moduleGuid); } +void +ThreadEnumerationContext::FinishCurrentThreadCompactBlock() +{ + if (m_threadCount == 0) + { + return; + } + + CrashReportHelpers::WriteThreadBlockCloserToConsole(m_consoleWriter, + m_currentThreadFrameCount, m_currentThreadDroppedCount); +} + void ThreadEnumerationContext::OnThread( uint64_t osThreadId, @@ -1000,6 +1388,8 @@ ThreadEnumerationContext::OnThread( { if (m_threadCount > 0) { + FinishCurrentThreadCompactBlock(); + m_writer->CloseArray(); // stack_frames m_writer->CloseObject(); // thread @@ -1011,6 +1401,8 @@ ThreadEnumerationContext::OnThread( m_sawCrashThread = true; } m_threadCount++; + m_currentThreadFrameCount = 0; + m_currentThreadDroppedCount = 0; m_writer->OpenObject(); m_writer->WriteString("is_managed", "true"); @@ -1033,6 +1425,21 @@ ThreadEnumerationContext::OnThread( { CrashReportHelpers::WriteCrashSiteFrameToJson(m_writer, m_signalContext); } + + if (m_consoleWriter != nullptr) + { + CrashReportHelpers::WriteThreadBlockHeaderToConsole(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(); + } + } } void @@ -1052,21 +1459,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; } + FinishCurrentThreadCompactBlock(); + // Close the last thread's stack_frames + thread objects opened by OnThread. m_writer->CloseArray(); // stack_frames m_writer->CloseObject(); // thread @@ -1092,10 +1500,129 @@ InProcCrashReporter::EmitSynthesizedCrashThread( CrashReportHelpers::WriteRegistersToJson(&m_jsonWriter, context); m_jsonWriter.OpenArray("stack_frames"); CrashReportHelpers::WriteCrashSiteFrameToJson(&m_jsonWriter, context); + + CrashReportHelpers::WriteThreadBlockHeaderToConsole(&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::FrameSinks sinks = + { + &m_jsonWriter, + &s_consoleWriter, + &synthesizedFrameCount, + &synthesizedDroppedCount, + m_frameLimitPerThread, + }; + m_walkStackCallback(&CrashReportHelpers::FrameSinkCallback, &sinks); } + CrashReportHelpers::WriteThreadBlockCloserToConsole(&s_consoleWriter, + synthesizedFrameCount, synthesizedDroppedCount); + m_jsonWriter.CloseArray(); // stack_frames m_jsonWriter.CloseObject(); // thread } + +// --- InProcCrashReporter: console header and footer ------------------------ + +void +InProcCrashReporter::EmitConsoleHeader(int signal) +{ + s_consoleWriter.WriteSeparator(); + s_consoleWriter.AppendStr(".NET Crash Report v"); + s_consoleWriter.AppendStr(CRASHREPORT_PROTOCOL_VERSION); + s_consoleWriter.EndLine(); + + char version[sizeof(sccsid)]; + CrashReportHelpers::GetVersionString(version, sizeof(version)); + if (version[0] != '\0') + { + s_consoleWriter.WriteKeyValueStr("Build", version); + } + + 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(PROCGetSignalNameAscii(signal)); + s_consoleWriter.AppendChar(')'); + s_consoleWriter.EndLine(); +} + +void +InProcCrashReporter::EmitConsoleModulesAndFooter() +{ + 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("] "); + s_consoleWriter.AppendStr(CrashReportHelpers::GetFilename(s_moduleTable.Name(i))); + s_consoleWriter.AppendChar(' '); + s_consoleWriter.AppendStr(s_moduleTable.Guid(i)); + s_consoleWriter.EndLine(); + } + } + + s_consoleWriter.WriteSeparator(); +} + +// --- InProcCrashReporter: JSON header and footer --------------------------- + +void +InProcCrashReporter::EmitJsonHeader() +{ + 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); + char version[sizeof(sccsid)]; + CrashReportHelpers::GetVersionString(version, sizeof(version)); + m_jsonWriter.WriteString("version", version); + 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::EmitJsonFooter(int signal) +{ + 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 +} diff --git a/src/coreclr/debug/crashreport/inproccrashreporter.h b/src/coreclr/debug/crashreport/inproccrashreporter.h index 5018f3b0d10793..e395a581856ecf 100644 --- a/src/coreclr/debug/crashreport/inproccrashreporter.h +++ b/src/coreclr/debug/crashreport/inproccrashreporter.h @@ -63,6 +63,7 @@ struct InProcCrashReporterSettings InProcCrashReportIsManagedThreadCallback isManagedThreadCallback; InProcCrashReportWalkStackCallback walkStackCallback; InProcCrashReportEnumerateThreadsCallback enumerateThreadsCallback; + uint32_t frameLimitPerThread; }; class InProcCrashReporter @@ -88,6 +89,12 @@ class InProcCrashReporter void* context, bool walkStack); + void EmitConsoleHeader(int signal); + void EmitConsoleModulesAndFooter(); + + void EmitJsonHeader(); + void EmitJsonFooter(int signal); + SignalSafeJsonWriter m_jsonWriter; InProcCrashReportIsManagedThreadCallback m_isManagedThreadCallback = nullptr; InProcCrashReportWalkStackCallback m_walkStackCallback = nullptr; @@ -99,6 +106,7 @@ class InProcCrashReporter 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 diff --git a/src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp b/src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp new file mode 100644 index 00000000000000..8d952c6b81017a --- /dev/null +++ b/src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp @@ -0,0 +1,142 @@ +// 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. + if (m_pos + 1 < sizeof(m_buffer)) + { + m_buffer[m_pos++] = '\n'; + m_buffer[m_pos] = '\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/pal/src/include/pal/process.h b/src/coreclr/pal/src/include/pal/process.h index e3f26bde875a03..6d4336a3a05720 100644 --- a/src/coreclr/pal/src/include/pal/process.h +++ b/src/coreclr/pal/src/include/pal/process.h @@ -172,6 +172,18 @@ VOID PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo, void* context, --*/ VOID PROCLogManagedCallstackForSignal(int signal); +/*++ +Function: + PROCGetSignalNameAscii + + Returns the ASCII name for the given POSIX signal (e.g. "SIGABRT"), or + "Unknown signal" if not recognized. Async-signal-safe. + +Parameters: + signal - POSIX signal number +--*/ +const char* PROCGetSignalNameAscii(int signal); + #ifdef __cplusplus } #endif // __cplusplus diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index 25902fcea08b8c..f150f54644003f 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -1882,6 +1882,21 @@ static LPCWSTR GetSignalName(int signal) } } +const char* PROCGetSignalNameAscii(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"; + } +} + /*++ Function: PROCLogManagedCallstackForSignal diff --git a/src/coreclr/vm/crashreportstackwalker.cpp b/src/coreclr/vm/crashreportstackwalker.cpp index 1670ec970d91ff..0509cc6e836c85 100644 --- a/src/coreclr/vm/crashreportstackwalker.cpp +++ b/src/coreclr/vm/crashreportstackwalker.cpp @@ -427,16 +427,13 @@ 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.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.