From f2472ee026b5d14f3fe45b2a60507237df269e32 Mon Sep 17 00:00:00 2001 From: Juan Hoyos <19413848+hoyosjs@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:53:20 -0700 Subject: [PATCH 1/2] Fix null-ref crash in CLRMA when querying nested exceptions on message-only fail-fast The runtime emit a triage JSON with no "exception" block when Environment.FailFast() is called without an exception (e.g. a garbage- collected delegate). CrashInfoService parsed this correctly but left _exception null, which caused a null-ref crash downstream in CLRMA when iterating nested exceptions. Adds a regression test covering the Delegate_GarbageCollected payload seen in a dump --- .../CrashInfoService.cs | 13 ++++++++ src/SOS/SOS.Extensions/Clrma/ThreadWrapper.cs | 9 +++++- .../CrashInfoServiceTests.cs | 30 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CrashInfoServiceTests.cs diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index acc0c35369..2dbf7c4d23 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -508,6 +508,11 @@ public IEnumerable GetNestedExceptions(uint threadId) } } + if (_exception is null) + { + return []; + } + List exceptions = new() { _exception }; @@ -516,8 +521,16 @@ public IEnumerable GetNestedExceptions(uint threadId) void AddExceptions(IEnumerable inner) { + if (inner is null) + { + return; + } foreach (IException exception in inner) { + if (exception is null) + { + continue; + } exceptions.Add(exception); AddExceptions(exception.InnerExceptions); } diff --git a/src/SOS/SOS.Extensions/Clrma/ThreadWrapper.cs b/src/SOS/SOS.Extensions/Clrma/ThreadWrapper.cs index cbfd2b1ed8..6432526b97 100644 --- a/src/SOS/SOS.Extensions/Clrma/ThreadWrapper.cs +++ b/src/SOS/SOS.Extensions/Clrma/ThreadWrapper.cs @@ -149,7 +149,14 @@ private ExceptionWrapper[] NestedExceptions { try { - _nestedExceptions = _crashInfoService.GetNestedExceptions(ThreadId).Select((exception) => new ExceptionWrapper(exception)).ToArray(); + if (_crashInfoService.GetThreadException(ThreadId) is null) + { + _nestedExceptions = Array.Empty(); + } + else + { + _nestedExceptions = _crashInfoService.GetNestedExceptions(ThreadId).Select((exception) => new ExceptionWrapper(exception)).ToArray(); + } } catch (ArgumentOutOfRangeException) { diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CrashInfoServiceTests.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CrashInfoServiceTests.cs new file mode 100644 index 0000000000..01c98eaf8b --- /dev/null +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CrashInfoServiceTests.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.DebugServices.Implementation; +using Xunit; + +namespace Microsoft.Diagnostics.DebugServices.UnitTests +{ + public class CrashInfoServiceTests + { + [Fact] + public void Create_AllowsMessageOnlyNativeAotCrashInfo() + { + string triageJson = """{"version":"1.0.0","runtime_base":"0x7FFF80270000","runtime_type":"4","runtime_version":"9.0.16","reason":"2","thread":"0x7050","message":"Delegate_GarbageCollected"}"""; + + ICrashInfoService crashInfo = CrashInfoService.Create(0, Encoding.UTF8.GetBytes(triageJson), null!); + + Assert.NotNull(crashInfo); + Assert.Equal(CrashReason.EnvironmentFailFast, crashInfo.CrashReason); + Assert.Equal(RuntimeType.NativeAOT, crashInfo.RuntimeType); + Assert.Equal((uint)0x7050, crashInfo.ThreadId); + Assert.Equal("Delegate_GarbageCollected", crashInfo.Message); + Assert.Null(crashInfo.GetException(0)); + Assert.Null(crashInfo.GetThreadException(crashInfo.ThreadId)); + Assert.Empty(crashInfo.GetNestedExceptions(crashInfo.ThreadId)); + } + } +} From e13bac8ebb7901714246696af8f4209ab443522c Mon Sep 17 00:00:00 2001 From: Juan Hoyos <19413848+hoyosjs@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:20:18 -0700 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../CrashInfoServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CrashInfoServiceTests.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CrashInfoServiceTests.cs index 01c98eaf8b..a95a1f6bcf 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CrashInfoServiceTests.cs +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CrashInfoServiceTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Diagnostics.DebugServices.UnitTests public class CrashInfoServiceTests { [Fact] - public void Create_AllowsMessageOnlyNativeAotCrashInfo() + public void CreateAllowsMessageOnlyNativeAotCrashInfo() { string triageJson = """{"version":"1.0.0","runtime_base":"0x7FFF80270000","runtime_type":"4","runtime_version":"9.0.16","reason":"2","thread":"0x7050","message":"Delegate_GarbageCollected"}""";