Skip to content

[Diagnostics] Add InProc CrashReport Logging#128105

Open
mdh1418 wants to merge 15 commits into
dotnet:mainfrom
mdh1418:inproc_crashreport_logging
Open

[Diagnostics] Add InProc CrashReport Logging#128105
mdh1418 wants to merge 15 commits into
dotnet:mainfrom
mdh1418:inproc_crashreport_logging

Conversation

@mdh1418
Copy link
Copy Markdown
Member

@mdh1418 mdh1418 commented May 12, 2026

This adds a compact text view of the in-proc crash report for mobile, while preserving the existing JSON crash report file output when DOTNET_DbgMiniDumpName is configured.

The compact output is intentionally tombstone-style: it prints process/signal metadata, per-thread frame stacks, a module table, and a footer. It omits data that platform crash reports already provide well, such as registers, memory-near dumps, and memory maps.

On Android, the compact report is emitted to logcat under a dedicated DOTNET_CRASH tag for easy filtering. On iOS, the same compact report is emitted through the existing stderr/minipal log path.

Details

  • Adds signal-safe formatting helpers shared by the JSON writer and compact log writer.
  • Adds a compact crash log writer: Android uses DOTNET_CRASH, while non-Android targets write newline-delimited compact report lines through the existing error log path.
  • Emits compact per-thread managed frame stacks alongside the JSON file writer.
  • Deduplicates managed modules into a fixed-size module table and references frames by module index.
  • Adds DOTNET_CrashReportFrameLimitPerThread; default is 32 frames per thread, and 0 disables the compact-log cap.
  • Keeps the JSON crash report file as the authoritative detailed artifact; the frame cap only limits compact log output.
  • Enables the in-proc crash reporter for Android and Apple app-model targets (iOS, tvOS, and MacCatalyst), including Apple ucontext_t register extraction.

Android Emulator Validation

Validated on Android x64 emulator with the same crash scenarios as #126916, comparing the console output with the json file.

  • Unhandled managed exception: compact output and JSON emitted; top frames matched.
  • Managed FailFast: compact output and JSON emitted; top frames matched.
  • P/Invoke abort: compact output and JSON emitted; top frames matched.
  • SIGSEGV: compact output and JSON emitted; top frames matched.
  • Interleaved managed/native frames via pthread_once: compact output and JSON emitted; top frames matched.

iOS simulator validation

Validated compact console crash-report emission on iossimulator-arm64

All scenarios emitted both:

  • the on-disk .crashreport.json
  • the compact .NET Crash Report v1.0.0 console report in the app log

nullref, sigsegv, abort,failfast, unhandled, interleaved, stackoverflow

For every scenario, the console report included the compact header, signal, per-thread blocks, managed frames, and a modules: table. Console thread counts matched the corresponding JSON crash report.

mdh1418 and others added 5 commits May 12, 2026 01:53
… namespace


Moves the async-signal-safe integer formatting helpers and buffer-size constants out of SignalSafeJsonWriter into a shared SignalSafeFormat namespace. The helpers are JSON-agnostic and are needed by both the JSON writer and later compact console output without introducing a sibling dependency.

This is intended as a behavior-preserving refactor: JSON writer call sites continue to use the same bounded fixed-buffer formatting logic, just through the shared helper namespace.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds SignalSafeConsoleWriter, a bounded line-oriented sink that writes DOTNET_CRASH entries through __android_log_write on Android and newline-terminated stderr lines elsewhere. CreateReport now emits a compact tombstone-style header/footer alongside the existing JSON report path.

The JSON header/footer emission is split into helpers, and DbgMiniDumpName becomes optional: when no JSON path is configured, the JSON writer uses a no-op sink while the compact log still runs. PROCGetSignalNameAscii exposes the existing signal-name table in the narrow form used by the compact log.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds shared frame-sink plumbing so each walked frame can feed both the JSON writer and the compact console writer. The compact log now emits per-thread headers, managed exception info, managed frame lines with IL offset/token, native frame lines with module offsets, and a marker when no managed frames were reported.

Both normal thread enumeration and the synthesized crash-thread fallback use the same console block helpers, keeping per-thread compact-log structure in one place.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…dule-index references


Adds ModuleTable, a 64-entry fixed-capacity table keyed by MVID for one crash report. Compact-log frames can refer to modules by short [N] indices, and the footer emits a modules block that maps each index back to the module filename and MVID.

If a managed frame's module cannot be stored because the table is full or the GUID is missing, the frame renders the module name inline as (in <name>) instead of using a lossy placeholder. JSON output is unchanged; module indices are only a compact-log representation detail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds DOTNET_CrashReportFrameLimitPerThread, parsed as base 10 with default 32, to cap the number of frames written per thread to the compact log. Setting the value to 0 disables the limit.

Frames past the cap are still emitted to the JSON report. The compact log skips only the console frame line, tracks how many frames were omitted for the current thread, and emits an "... +N more frames" summary in the thread footer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends CoreCLR’s in-process crash reporting path to emit a compact, signal-safe crash log for mobile scenarios while preserving JSON file output when DOTNET_DbgMiniDumpName is configured.

Changes:

  • Adds shared signal-safe formatting helpers and a line-oriented compact console/logcat writer.
  • Refactors in-proc crash report emission into JSON header/footer and compact log sections.
  • Adds frame limiting and module-table support for compact per-thread stack output.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/coreclr/vm/crashreportstackwalker.cpp Allows crash reporting without a dump path and wires frame-limit configuration.
src/coreclr/pal/src/thread/process.cpp Adds ASCII POSIX signal-name helper.
src/coreclr/pal/src/include/pal/process.h Declares the signal-name helper for crash reporting.
src/coreclr/inc/clrconfigvalues.h Adds CrashReportFrameLimitPerThread configuration.
src/coreclr/debug/crashreport/signalsafejsonwriter.h Removes formatter declarations now moved to shared helper.
src/coreclr/debug/crashreport/signalsafejsonwriter.cpp Uses shared signal-safe formatting helpers.
src/coreclr/debug/crashreport/signalsafeformat.h Adds shared bounded integer formatting declarations.
src/coreclr/debug/crashreport/signalsafeformat.cpp Implements shared bounded integer formatting.
src/coreclr/debug/crashreport/signalsafeconsolewriter.h Adds compact line-oriented crash log writer interface.
src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp Implements Android logcat / stderr compact crash log output.
src/coreclr/debug/crashreport/inproccrashreporter.h Adds compact output helpers and frame-limit state.
src/coreclr/debug/crashreport/inproccrashreporter.cpp Emits compact crash logs alongside JSON reporting.
src/coreclr/debug/crashreport/CMakeLists.txt Includes new crash-report helper source files.

Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.h
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/inproccrashreporter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/signalsafeconsolewriter.cpp Outdated
Comment thread src/coreclr/debug/crashreport/signalsafeformat.h Outdated
Comment thread src/coreclr/debug/crashreport/signalsafeformat.cpp Outdated
Comment thread src/coreclr/debug/crashreport/signalsafeformat.cpp Outdated
Comment thread src/coreclr/vm/crashreportstackwalker.cpp Outdated
Comment thread src/coreclr/vm/eepolicy.cpp Outdated
Comment thread src/coreclr/vm/eepolicy.cpp Outdated
Comment thread src/coreclr/vm/eepolicy.cpp Outdated
Move compact log line completion into EndLine, allocate crash report scratch state at initialization, group frame callback module fields, clean up stack-overflow capture plumbing, log initialization allocation failures, and scope Apple-specific reporter code to iOS/tvOS/MacCatalyst.

Also make init-time storage publication race-safe, share the Android crash-report log tag through the reporter header, and remove the redundant module-table reset plus the single-use stack-overflow frame-formatting helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 19, 2026 23:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Comment on lines +589 to 599
CLRConfigNoCache dmpNameCfg = CLRConfigNoCache::Get("DbgMiniDumpName", /*noprefix*/ false, &getenv);
const char* dumpName = dmpNameCfg.IsSet() ? dmpNameCfg.AsString() : nullptr;

InProcCrashReporterSettings settings = {};
settings.reportPath = dumpName;
settings.isManagedThreadCallback = CrashReportIsCurrentThreadManaged;
settings.walkStackCallback = CrashReportWalkStack;
settings.enumerateThreadsCallback = CrashReportEnumerateThreads;
settings.moduleInfoCallback = CrashReportGetModuleInfo;
settings.frameLimitPerThread = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CrashReportFrameLimitPerThread);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a CLRConfigNoCache::TryAsInteger that can be used.

#elif defined(__aarch64__)
static const char CRASHREPORT_ARCHITECTURE_NAME[] = "arm64";
#elif defined(__arm__)
static const char CRASHREPORT_ARCHITECTURE_NAME[] = "arm";
Comment on lines +1729 to +1737
InProcCrashReporterStorage* storage = s_storage;
int moduleIndex = storage != nullptr && moduleInfoCallback != nullptr && moduleHandle != nullptr
? storage->moduleTable.GetOrAddIndex(moduleHandle)
: -1;
WriteFrameToConsole(consoleWriter,
methodNameBuffer,
methodNameBufferSize,
frameIndex, moduleIndex, ip, methodName, className, moduleName,
nativeOffset, token, ilOffset);
Comment on lines +1986 to +1991
StackOverflowTraceSnapshot& trace = storage->stackOverflowTrace;
bool stackOverflowTraceAvailable = trace.available != 0;
uint64_t crashingTid = stackOverflowTraceAvailable && trace.crashingTid != 0
? trace.crashingTid
: static_cast<uint64_t>(minipal_get_current_thread_id());

Comment on lines +1386 to +1393
if (moduleGuid != nullptr)
{
writer->WriteString("guid", moduleGuid);
InProcCrashReporterStorage* storage = s_storage;
if (storage != nullptr)
{
minipal_guid_as_string(*moduleGuid, storage->moduleGuidScratch, sizeof(storage->moduleGuidScratch));
writer->WriteString("guid", storage->moduleGuidScratch);
}
Read frame-limit configuration through the late Android getenv path, keep architecture naming defined for every compiled target, guard compact module-table references with resolvable module identity, synchronize stack-overflow snapshot publication reads, and keep GUID formatting on the async-signal-safe formatter path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you mix this with int and this can't be larger than 256 entry, then you could just handle it as an int and remove type mismatch complexity.

#if defined(__ANDROID__)
__android_log_write(ANDROID_LOG_ERROR, CRASHREPORT_LOG_TAG, message);
#elif defined(TARGET_IOS) || defined(TARGET_TVOS) || defined(TARGET_MACCATALYST)
minipal_log_write_error(message);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess this will work on all platforms, so just do it in the else clause.

// InProcCrashReporter lets disabled processes avoid the large buffers entirely,
// while Initialize can allocate and publish the state before registering the PAL
// signal callback.
struct InProcCrashReporterStorage
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have this as state on the InProcCrashReporter instance? The whole InProcCrashReporter instance can be allocated when setting up crash reporter support, its already a singleton accessed through InProcCrashReporter::GetInstance(). That would simplify the code a lot since you don't need to go through storage-> for field access, its just regular class fields. In case you need this from none instance member functions, you have the singleton access function + you can add the methods performing work or simple access methods to get/set specific fields etc.

// 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"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should match file name to class name, so signalsafeformatter.h/cpp

volatile LONG crashKind = static_cast<LONG>(InProcCrashReportCrashKind::Unknown);
uint32_t frameLimitPerThread = 0;
char reportPath[CRASHREPORT_PATH_BUFFER_SIZE];
char reportFilePathScratch[CRASHREPORT_PATH_BUFFER_SIZE];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if we need all these different scratch buffers at the same time, normally you use one scratch buffer temporary while computing one of the others, could all be collapsed into just one scratch buffer saving some memory?

Rename the signal-safe formatter files, use compact module indexes in
crash report output, and route initialization failure logging through
portable platform logging.

Move crash reporter state onto the init-time allocated reporter and
minimize persistent scratch buffers by reusing temporary scratch storage
where lifetimes do not overlap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 21, 2026 02:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants