From 60daaf973cd611df39eaaf6ccbf9c5c22f38b875 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Tue, 22 Jul 2025 19:19:47 -0400 Subject: [PATCH 01/16] Support reading native aot crashinfo export --- .../CrashInfoService.cs | 2 ++ .../TargetFromDebuggerServices.cs | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index cee810569c..4652e69473 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -24,6 +24,8 @@ public class CrashInfoService : ICrashInfoService /// public const uint FAST_FAIL_EXCEPTION_DOTNET_AOT = 0x48; + public const uint MAX_CRASHINFOBUFFER_SIZE = 8192; + private sealed class CrashInfoJson { [JsonPropertyName("version")] diff --git a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs index aac3731ff1..3229660702 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -142,6 +142,42 @@ private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider service Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed"); } } + else + { + IModuleService foo = services.GetService();//.TryGetSymbolAddress("g_CrashInfoBuffer", out ulong triageBufferAddress); + IModule m = foo.GetModuleFromIndex(0); // the zero'th module is the EXE module + //foreach (IModule m in foo.EnumerateModules()) + // If the module is managed, this won't have the g_CrashInfoBuffer symbol + if (!m.IsManaged && m.FileName != null && m.FileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + { + if ((m as IModuleSymbols)?.TryGetSymbolAddress("g_CrashInfoBuffer", out ulong triageBufferAddress) == true) + { + // If the crash info buffer is available, read it + Span buffer = new byte[CrashInfoService.MAX_CRASHINFOBUFFER_SIZE]; + if (services.GetService().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead > 0 && bytesRead <= CrashInfoService.MAX_CRASHINFOBUFFER_SIZE) + { + // truncate the buffer to the null terminated string in the buffer + for (int i = 0; i < bytesRead - 1; i += 2) + { + if (buffer[i] == 0 && buffer[i + 1] == 0) + { + buffer = buffer.Slice(0, (int)i); + break; + } + } + return CrashInfoService.Create(0, buffer, services.GetService()); + } + else + { + Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed"); + } + } + else + { + Trace.TraceInformation($"CrashInfoService: g_CrashInfoBuffer symbol not found in module {m.FileName}"); + } + } + } } return null; } From 71739bed1133c894ef98f4bdcde0f73d1e94a63b Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 12:23:10 -0400 Subject: [PATCH 02/16] Refactoring --- .../CrashInfoService.cs | 4 + .../ModuleService.cs | 14 +++ .../IModuleService.cs | 8 ++ .../MemoryServiceExtensions.cs | 57 +++++++++ .../TargetFromDebuggerServices.cs | 113 ++++++++++++------ 5 files changed, 160 insertions(+), 36 deletions(-) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index 4652e69473..77e9c046e9 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -25,6 +25,10 @@ public class CrashInfoService : ICrashInfoService public const uint FAST_FAIL_EXCEPTION_DOTNET_AOT = 0x48; public const uint MAX_CRASHINFOBUFFER_SIZE = 8192; + public const uint MAX_GLOBAL_ENTRIES_ARRAY_SIZE = 8; + public const uint MAX_GLOBAL_ENTRY_NAME_CHARS = 32; + public const uint DOTNET_RUNTIME_DEBUG_HEADER_COOKIE = 0x48444e44; // 'DNDH' + public const string DOTNET_RUNTIME_DEBUG_HEADER_NAME = "DotNetRuntimeDebugHeader"; private sealed class CrashInfoJson { diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs index 75f9a087c3..afaf4ea31a 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs @@ -194,6 +194,20 @@ public IModule CreateModule(int moduleIndex, ulong imageBase, ulong imageSize, s return new ModuleFromAddress(this, moduleIndex, imageBase, imageSize, imageName); } + /// + /// Gets the entrypoint module for the target. This is the first module in the sorted list of modules. + /// + public IModule EntryPointModule + { + get + { + // The entry point module is the first module in the sorted list of modules. + // This is not necessarily the same as the entry point module in the target. + IModule[] modules = GetSortedModules(); + return modules.Length > 0 ? modules[0] : null; + } + } + #endregion /// diff --git a/src/Microsoft.Diagnostics.DebugServices/IModuleService.cs b/src/Microsoft.Diagnostics.DebugServices/IModuleService.cs index 7d65d8e796..0b3f7ca6c0 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IModuleService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IModuleService.cs @@ -57,5 +57,13 @@ public interface IModuleService /// IModule /// thrown if imageBase or imageSize is 0 IModule CreateModule(int moduleIndex, ulong imageBase, ulong imageSize, string imageName); + + /// + /// Gets the entrypoint module for the target. + /// + IModule EntryPointModule + { + get; + } } } diff --git a/src/Microsoft.Diagnostics.DebugServices/MemoryServiceExtensions.cs b/src/Microsoft.Diagnostics.DebugServices/MemoryServiceExtensions.cs index 7cb5dbabc1..fd394f7bbd 100644 --- a/src/Microsoft.Diagnostics.DebugServices/MemoryServiceExtensions.cs +++ b/src/Microsoft.Diagnostics.DebugServices/MemoryServiceExtensions.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Text; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace Microsoft.Diagnostics.DebugServices { @@ -71,6 +73,22 @@ public static bool ReadDword(this IMemoryService memoryService, ulong address, o return false; } + public static bool Read(this IMemoryService memoryService, ref ulong address, out T value) where T : unmanaged + { + Span buffer = stackalloc byte[Unsafe.SizeOf()]; + if (memoryService.ReadMemory(address, buffer, out int bytesRead)) + { + if (bytesRead == Unsafe.SizeOf()) + { + value = MemoryMarshal.Read(buffer); + address += (ulong)Unsafe.SizeOf(); + return true; + } + } + value = default; + return false; + } + /// /// Return a pointer sized value from the address. /// @@ -98,6 +116,45 @@ public static bool ReadPointer(this IMemoryService memoryService, ulong address, return false; } + public static bool ReadPointer(this IMemoryService memoryService, ref ulong address, out ulong value) + { + bool ret = memoryService.ReadPointer(address, out value); + if (ret) + { + address += (ulong)memoryService.PointerSize; + } + return ret; + } + + public static bool ReadAnsiString(this IMemoryService memoryService, uint maxLength, ulong address, out string value) + { + StringBuilder sb = new(); + byte[] buffer = new byte[maxLength]; + value = null; + if (memoryService.ReadMemory(address, buffer, out int bytesRead) && bytesRead > 0) + { + // convert null terminated ANSI char array to a string + for (int i = 0; i < buffer.Length; i++) + { + // Read the string one character at a time + char c = (char)buffer[i]; + if (buffer[i] == 0) // Stop at null terminator + { + value = sb.ToString(); + break; // Stop reading at null terminator + } + if (c < 0x20 || c > 0x7E) // Unexpected characters + { + break; + } + // Append the character to the string + sb.Append(c); + } + } + return !string.IsNullOrEmpty(value); + } + + /// /// Create a stream for all of memory. /// diff --git a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs index 3229660702..592b798dd0 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -114,6 +114,75 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos targetWrapper?.ServiceWrapper.AddServiceWrapper(ClrmaServiceWrapper.IID_ICLRMAService, () => new ClrmaServiceWrapper(this, Services, targetWrapper.ServiceWrapper)); } + private static bool CreateCrashInfoServiceForModule(IServiceProvider services, IModule module, out ICrashInfoService crashInfoService) + { + crashInfoService = null; + if (module?.IsManaged == true) + { + return false; + } + if ((module as IExportSymbols)?.TryGetSymbolAddress(CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME, out ulong headerAddr) != true) + { + Trace.TraceInformation($"CrashInfoService: {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} export not found in module {module.FileName}"); + return false; + } + IMemoryService memoryService = services.GetService(); + if (!memoryService.Read(ref headerAddr, out uint headerValue) || + headerValue != CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_COOKIE || + !memoryService.Read(ref headerAddr, out ushort majorVersion) || majorVersion < 5 || // .NET 10 and later + !memoryService.Read(ref headerAddr, out ushort _)) + { + Trace.TraceInformation($"CrashInfoService: .NET 10+ {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} not found in module {module.FileName}"); + return false; + } + + if (!memoryService.Read(ref headerAddr, out uint flags) || + memoryService.PointerSize != (flags == 0x1 ? 8 : 4) || + !memoryService.Read(ref headerAddr, out uint _)) // padding + { + Trace.TraceError($"CrashInfoService: Failed to read DotNetRuntimeDebugHeader flags or padding in module {module.FileName}"); + return false; + } + + headerAddr += (uint)memoryService.PointerSize; // skip DebugEntries array + + // read GlobalEntries array + if (!memoryService.ReadPointer(ref headerAddr, out ulong globalEntryArrayAddress) || globalEntryArrayAddress == 0) + { + Trace.TraceError($"CrashInfoService: Unable to read GlobalEntry array"); + return false; + } + for (int i = 0; i < CrashInfoService.MAX_GLOBAL_ENTRIES_ARRAY_SIZE; i++) + { + if (!memoryService.ReadPointer(ref globalEntryArrayAddress, out ulong globalNameAddress) || globalNameAddress == 0 || + !memoryService.ReadPointer(ref globalEntryArrayAddress, out ulong globalValueAddress) || globalValueAddress == 0 || + !memoryService.ReadAnsiString(CrashInfoService.MAX_GLOBAL_ENTRY_NAME_CHARS, globalNameAddress, out string globalName)) + { + break; // no more global entries + } + + if (!string.Equals(globalName, "g_CrashInfoBuffer", StringComparison.Ordinal)) + { + continue; // not the crash info buffer + } + + ulong triageBufferAddress = globalValueAddress; + Span buffer = new byte[CrashInfoService.MAX_CRASHINFOBUFFER_SIZE]; + if (memoryService.ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead > 0 && bytesRead <= CrashInfoService.MAX_CRASHINFOBUFFER_SIZE) + { + // truncate the buffer to the null terminated string in the buffer + int nullTerminatorIndex = buffer.IndexOf((byte)0); + if (nullTerminatorIndex >= 0) + { + buffer = buffer.Slice(0, nullTerminatorIndex); + crashInfoService = CrashInfoService.Create(0, buffer, services.GetService()); + return true; + } + } + } + return false; + } + private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, DebuggerServices debuggerServices) { // For Linux/OSX dumps loaded under dbgeng the GetLastException API doesn't return the necessary information @@ -142,43 +211,15 @@ private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider service Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed"); } } - else - { - IModuleService foo = services.GetService();//.TryGetSymbolAddress("g_CrashInfoBuffer", out ulong triageBufferAddress); - IModule m = foo.GetModuleFromIndex(0); // the zero'th module is the EXE module - //foreach (IModule m in foo.EnumerateModules()) - // If the module is managed, this won't have the g_CrashInfoBuffer symbol - if (!m.IsManaged && m.FileName != null && m.FileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) - { - if ((m as IModuleSymbols)?.TryGetSymbolAddress("g_CrashInfoBuffer", out ulong triageBufferAddress) == true) - { - // If the crash info buffer is available, read it - Span buffer = new byte[CrashInfoService.MAX_CRASHINFOBUFFER_SIZE]; - if (services.GetService().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead > 0 && bytesRead <= CrashInfoService.MAX_CRASHINFOBUFFER_SIZE) - { - // truncate the buffer to the null terminated string in the buffer - for (int i = 0; i < bytesRead - 1; i += 2) - { - if (buffer[i] == 0 && buffer[i + 1] == 0) - { - buffer = buffer.Slice(0, (int)i); - break; - } - } - return CrashInfoService.Create(0, buffer, services.GetService()); - } - else - { - Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed"); - } - } - else - { - Trace.TraceInformation($"CrashInfoService: g_CrashInfoBuffer symbol not found in module {m.FileName}"); - } - } - } } + + // if the above did not located the crash info service, then look for the DotNetRuntimeDebugHeader + IModule entryPointModule = services.GetService().EntryPointModule; + if (CreateCrashInfoServiceForModule(services, entryPointModule, out ICrashInfoService crashInfoService)) + { + return crashInfoService; + } + return null; } } From b0c787843a960d49a0823cddc03b6a8ea5ca0dea Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 15:25:24 -0400 Subject: [PATCH 03/16] Refactor CrashInfoService and ModuleService to improve entry point module handling and crash info service creation --- .../CrashInfoService.cs | 3 +- .../ModuleService.cs | 2 +- .../ModuleServiceFromDebuggerServices.cs | 25 ++++++++ .../TargetFromDebuggerServices.cs | 57 ++++++++++++++++--- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index 77e9c046e9..f8bd71904d 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -25,7 +25,8 @@ public class CrashInfoService : ICrashInfoService public const uint FAST_FAIL_EXCEPTION_DOTNET_AOT = 0x48; public const uint MAX_CRASHINFOBUFFER_SIZE = 8192; - public const uint MAX_GLOBAL_ENTRIES_ARRAY_SIZE = 8; + public const uint MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET8 = 6; + public const uint MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET9_PLUS = 8; public const uint MAX_GLOBAL_ENTRY_NAME_CHARS = 32; public const uint DOTNET_RUNTIME_DEBUG_HEADER_COOKIE = 0x48444e44; // 'DNDH' public const string DOTNET_RUNTIME_DEBUG_HEADER_NAME = "DotNetRuntimeDebugHeader"; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs index afaf4ea31a..cd2c8e2acd 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs @@ -197,7 +197,7 @@ public IModule CreateModule(int moduleIndex, ulong imageBase, ulong imageSize, s /// /// Gets the entrypoint module for the target. This is the first module in the sorted list of modules. /// - public IModule EntryPointModule + public virtual IModule EntryPointModule { get { diff --git a/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs index 4137ee1dfd..11431ce1d4 100644 --- a/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using Microsoft.Diagnostics.DebugServices; using Microsoft.Diagnostics.DebugServices.Implementation; using Microsoft.Diagnostics.Runtime.Utilities; @@ -262,6 +263,30 @@ internal ModuleServiceFromDebuggerServices(IServiceProvider services, DebuggerSe _debuggerServices = debuggerServices; } + public override IModule EntryPointModule + { + get + { + foreach (IModule module in ((IModuleService)this).EnumerateModules()) + { + if (Target.OperatingSystem == OSPlatform.Windows) + { + // The entry point module is not necessarily the first module in the sorted list of modules. + if (module.FileName?.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) == true) + { + return module; + } + } + else + { + // On non-Windows, the entry point module is the first module. + return module; + } + } + return null; + } + } + /// /// Get/create the modules dictionary. /// diff --git a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs index 592b798dd0..ce1fcd612f 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -114,10 +114,10 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos targetWrapper?.ServiceWrapper.AddServiceWrapper(ClrmaServiceWrapper.IID_ICLRMAService, () => new ClrmaServiceWrapper(this, Services, targetWrapper.ServiceWrapper)); } - private static bool CreateCrashInfoServiceForModule(IServiceProvider services, IModule module, out ICrashInfoService crashInfoService) + private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashInfoService crashInfoService) { crashInfoService = null; - if (module?.IsManaged == true) + if (module == null || module.IsManaged) { return false; } @@ -126,13 +126,13 @@ private static bool CreateCrashInfoServiceForModule(IServiceProvider services, I Trace.TraceInformation($"CrashInfoService: {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} export not found in module {module.FileName}"); return false; } - IMemoryService memoryService = services.GetService(); + IMemoryService memoryService = module.Services.GetService(); if (!memoryService.Read(ref headerAddr, out uint headerValue) || headerValue != CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_COOKIE || - !memoryService.Read(ref headerAddr, out ushort majorVersion) || majorVersion < 5 || // .NET 10 and later + !memoryService.Read(ref headerAddr, out ushort majorVersion) || majorVersion < 3 || // .NET 8 and later !memoryService.Read(ref headerAddr, out ushort _)) { - Trace.TraceInformation($"CrashInfoService: .NET 10+ {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} not found in module {module.FileName}"); + Trace.TraceInformation($"CrashInfoService: .NET 8+ {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} not found in module {module.FileName}"); return false; } @@ -152,7 +152,8 @@ private static bool CreateCrashInfoServiceForModule(IServiceProvider services, I Trace.TraceError($"CrashInfoService: Unable to read GlobalEntry array"); return false; } - for (int i = 0; i < CrashInfoService.MAX_GLOBAL_ENTRIES_ARRAY_SIZE; i++) + uint maxGlobalEntries = (majorVersion >= 4 /*.NET 9 or later*/) ? CrashInfoService.MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET9_PLUS : CrashInfoService.MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET8; + for (int i = 0; i < maxGlobalEntries; i++) { if (!memoryService.ReadPointer(ref globalEntryArrayAddress, out ulong globalNameAddress) || globalNameAddress == 0 || !memoryService.ReadPointer(ref globalEntryArrayAddress, out ulong globalValueAddress) || globalValueAddress == 0 || @@ -175,9 +176,24 @@ private static bool CreateCrashInfoServiceForModule(IServiceProvider services, I if (nullTerminatorIndex >= 0) { buffer = buffer.Slice(0, nullTerminatorIndex); - crashInfoService = CrashInfoService.Create(0, buffer, services.GetService()); - return true; + if (buffer.Length > 0) + { + crashInfoService = CrashInfoService.Create(0, buffer, module.Services.GetService()); + return true; + } + else + { + Trace.TraceError($"CrashInfoService: g_CrashInfoBuffer is empty in module {module.FileName}"); + } } + else + { + Trace.TraceError($"CrashInfoService: g_CrashInfoBuffer is not null terminated in module {module.FileName}"); + } + } + else + { + Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed in module {module.FileName}"); } } return false; @@ -215,10 +231,33 @@ private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider service // if the above did not located the crash info service, then look for the DotNetRuntimeDebugHeader IModule entryPointModule = services.GetService().EntryPointModule; - if (CreateCrashInfoServiceForModule(services, entryPointModule, out ICrashInfoService crashInfoService)) + if (entryPointModule == null) + { + Trace.TraceError("CrashInfoService: No entry point module found"); + return null; + } + if (CreateCrashInfoServiceForModule(entryPointModule, out ICrashInfoService crashInfoService)) { return crashInfoService; } + // if the entry point module did not have the crash info service, then look for a library with the same name as the entry point + string fileName = entryPointModule.FileName; + if (fileName == null) + { + Trace.TraceError("CrashInfoService: Entry point module has no file name"); + return null; + } + if (fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + { + fileName = fileName.Substring(0, fileName.Length - 4) + ".dll"; // look for a dll with the same name + foreach (IModule speculativeAppModule in services.GetService().GetModuleFromModuleName(fileName)) + { + if (CreateCrashInfoServiceForModule(speculativeAppModule, out crashInfoService)) + { + return crashInfoService; + } + } + } return null; } From 05f28e774783013d0c9393f68c4748dfdc55bbb0 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 16:51:38 -0400 Subject: [PATCH 04/16] Add more informational CrashInfoService tracing logging --- src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs index ce1fcd612f..a0fada58f6 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -130,12 +130,14 @@ private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashIn if (!memoryService.Read(ref headerAddr, out uint headerValue) || headerValue != CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_COOKIE || !memoryService.Read(ref headerAddr, out ushort majorVersion) || majorVersion < 3 || // .NET 8 and later - !memoryService.Read(ref headerAddr, out ushort _)) + !memoryService.Read(ref headerAddr, out ushort minorVersion)) { Trace.TraceInformation($"CrashInfoService: .NET 8+ {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} not found in module {module.FileName}"); return false; } + Trace.TraceInformation($"CrashInfoService: Found {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} in module {module.FileName} with version {majorVersion}.{minorVersion}"); + if (!memoryService.Read(ref headerAddr, out uint flags) || memoryService.PointerSize != (flags == 0x1 ? 8 : 4) || !memoryService.Read(ref headerAddr, out uint _)) // padding @@ -144,6 +146,8 @@ private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashIn return false; } + Trace.TraceInformation($"CrashInfoService: Target is {memoryService.PointerSize * 8}-bit"); + headerAddr += (uint)memoryService.PointerSize; // skip DebugEntries array // read GlobalEntries array @@ -176,6 +180,7 @@ private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashIn if (nullTerminatorIndex >= 0) { buffer = buffer.Slice(0, nullTerminatorIndex); + Trace.TraceInformation($"CrashInfoService: Found g_CrashInfoBuffer in module {module.FileName} with size {buffer.Length} bytes"); if (buffer.Length > 0) { crashInfoService = CrashInfoService.Create(0, buffer, module.Services.GetService()); From bda8e6a7ad271c7876e2d0d71532555ea39176a1 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 20:20:14 -0400 Subject: [PATCH 05/16] Initial iteration making carshingo module enumeration configurable --- .../ICrashInfoService.cs | 33 ++ .../CrashInfoCommand.cs | 49 ++- .../TargetFromDebuggerServices.cs | 282 +++++++++++------- 3 files changed, 237 insertions(+), 127 deletions(-) diff --git a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs index 143532cdc3..0c87841a45 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs @@ -17,6 +17,39 @@ public enum CrashReason InternalFailFast = 3, } + public enum ModuleEnumerationScheme + { + /// + /// Enumerate no modules + /// + None, + + /// + /// Enumerate only the entry point module + /// + EntryPointModule, + + /// + /// Enumerate only the entry point module and the entry point DLL module + /// + EntryPointAndEntryPointDllModule, + + /// + /// Enumerate all modules in the target + /// + All + } + + public interface ICrashInfoServiceFactory + { + /// + /// Create a crash info service + /// + /// module enumeration scheme + /// ICrashInfoService + ICrashInfoService Create(ModuleEnumerationScheme moduleEnumerationScheme); + } + /// /// Crash information service. Details about the unhandled exception or crash. /// diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs index 41ad047e6c..63f8c0f668 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs @@ -13,27 +13,50 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Command(Name = "crashinfo", Help = "Displays the crash details that created the dump.")] public class CrashInfoCommand : CommandBase { + // [ServiceImport(Optional = true)] + // public ICrashInfoService CrashInfo { get; set; } + [ServiceImport(Optional = true)] - public ICrashInfoService CrashInfo { get; set; } + public ICrashInfoServiceFactory CrashInfoFactory { get; set; } + + [Option(Name = "--enumerateAllModules", Aliases = new string[] { "-a" }, Help = "Enables enumerating all modules to find a Native AOT module.")] + public bool EnumerateAllModules { get; set; } + + [Option(Name = "--entryPoint", Aliases = new string[] { "-e" }, Help = "Enables enumerating the entry point module to find a Native AOT module.")] + public bool EntryPointOnly { get; set; } + + // [Option(Name = "--moduleName", Aliases = new string[] { "-m" }, Help = "Specifies the module name to find a Native AOT module.")] + // public string ModuleName { get; set; } + + // [Option(Name = "--moduleIndex", Aliases = new string[] { "-i" }, Help = "Specifies the module index to find a Native AOT module.")] + // public int ModuleIndex { get; set; } + + // [Option(Name = "--moduleBase", Aliases = new string[] { "-b" }, Help = "Specifies the module base address to find a Native AOT module.")] + // public ulong ModuleBase { get; set; } + + [Option(Name = "--entryPointDll", Aliases = new string[] { "-d" }, Help = "Enables enumerating the entry point module and corresponding entry point library to find a Native AOT module.")] + public bool EntryPointDll { get; set; } public override void Invoke() { - if (CrashInfo == null) + ICrashInfoService crashInfo = CrashInfoFactory.Create(ModuleEnumerationScheme.All); + + if (crashInfo == null) { throw new DiagnosticsException("No crash info to display"); } - WriteLine($"CrashReason: {CrashInfo.CrashReason}"); - WriteLine($"ThreadId: {CrashInfo.ThreadId:X4}"); - WriteLine($"HResult: {CrashInfo.HResult:X4}"); - WriteLine($"RuntimeType: {CrashInfo.RuntimeType}"); - WriteLine($"RuntimeBaseAddress: {CrashInfo.RuntimeBaseAddress:X16}"); - WriteLine($"RuntimeVersion: {CrashInfo.RuntimeVersion}"); - WriteLine($"Message: {CrashInfo.Message}"); + WriteLine($"CrashReason: {crashInfo.CrashReason}"); + WriteLine($"ThreadId: {crashInfo.ThreadId:X4}"); + WriteLine($"HResult: {crashInfo.HResult:X4}"); + WriteLine($"RuntimeType: {crashInfo.RuntimeType}"); + WriteLine($"RuntimeBaseAddress: {crashInfo.RuntimeBaseAddress:X16}"); + WriteLine($"RuntimeVersion: {crashInfo.RuntimeVersion}"); + WriteLine($"Message: {crashInfo.Message}"); WriteLine(); WriteLine("** Current Exception **"); WriteLine(); - IException exception = CrashInfo.GetException(0); + IException exception = crashInfo.GetException(0); if (exception != null) { WriteLine("-----------------------------------------------"); @@ -41,9 +64,9 @@ public override void Invoke() } WriteLine(); - WriteLine($"** Thread {CrashInfo.ThreadId} Exception **"); + WriteLine($"** Thread {crashInfo.ThreadId} Exception **"); WriteLine(); - exception = CrashInfo.GetThreadException(CrashInfo.ThreadId); + exception = crashInfo.GetThreadException(crashInfo.ThreadId); if (exception != null) { WriteLine("-----------------------------------------------"); @@ -53,7 +76,7 @@ public override void Invoke() WriteLine(); WriteLine("** Nested Exceptions **"); WriteLine(); - IEnumerable exceptions = CrashInfo.GetNestedExceptions(CrashInfo.ThreadId); + IEnumerable exceptions = crashInfo.GetNestedExceptions(crashInfo.ThreadId); foreach (IException ex in exceptions) { WriteLine("-----------------------------------------------"); diff --git a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs index a0fada58f6..51bd497519 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -14,104 +14,23 @@ namespace SOS.Extensions { - /// - /// ITarget implementation for the ClrMD IDataReader - /// - internal sealed class TargetFromDebuggerServices : Target + internal sealed class CrashInfoServiceFactory : ICrashInfoServiceFactory { - /// - /// Build a target instance from IDataReader - /// - internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost host) - : base(host, dumpPath: null) - { - Debug.Assert(debuggerServices != null); - - HResult hr = debuggerServices.GetOperatingSystem(out DebuggerServices.OperatingSystem operatingSystem); - Debug.Assert(hr == HResult.S_OK); - OperatingSystem = operatingSystem switch - { - DebuggerServices.OperatingSystem.Windows => OSPlatform.Windows, - DebuggerServices.OperatingSystem.Linux => OSPlatform.Linux, - DebuggerServices.OperatingSystem.OSX => OSPlatform.OSX, - _ => throw new PlatformNotSupportedException($"Operating system not supported: {operatingSystem}"), - }; - - hr = debuggerServices.GetDebuggeeType(out DEBUG_CLASS debugClass, out DEBUG_CLASS_QUALIFIER qualifier); - Debug.Assert(hr == HResult.S_OK); - if (qualifier >= DEBUG_CLASS_QUALIFIER.USER_WINDOWS_SMALL_DUMP) - { - IsDump = true; - } + private readonly IServiceProvider Services; + private readonly DebuggerServices DebuggerServices; + private readonly IHost Host; + private readonly OSPlatform OperatingSystem; - hr = debuggerServices.GetProcessorType(out IMAGE_FILE_MACHINE type); - if (hr == HResult.S_OK) - { - Debug.Assert(type is not IMAGE_FILE_MACHINE.ARM64X and not IMAGE_FILE_MACHINE.ARM64EC); - Architecture = type switch - { - IMAGE_FILE_MACHINE.I386 => Architecture.X86, - IMAGE_FILE_MACHINE.ARM => Architecture.Arm, - IMAGE_FILE_MACHINE.THUMB => Architecture.Arm, - IMAGE_FILE_MACHINE.ARMNT => Architecture.Arm, - IMAGE_FILE_MACHINE.AMD64 => Architecture.X64, - IMAGE_FILE_MACHINE.ARM64 => Architecture.Arm64, - IMAGE_FILE_MACHINE.LOONGARCH64 => (Architecture)6 /* Architecture.LoongArch64 */, - IMAGE_FILE_MACHINE.RISCV64 => (Architecture)9 /* Architecture.RiscV64 */, - _ => throw new PlatformNotSupportedException($"Machine type not supported: {type}"), - }; - } - else - { - throw new PlatformNotSupportedException($"GetProcessorType() FAILED {hr:X8}"); - } - - hr = debuggerServices.GetCurrentProcessId(out uint processId); - if (hr == HResult.S_OK) - { - ProcessId = processId; - } - else - { - Trace.TraceError("GetCurrentThreadId() FAILED {0:X8}", hr); - } - - // Add the thread, memory, and module services - _serviceContainerFactory.AddServiceFactory((services) => new ModuleServiceFromDebuggerServices(services, debuggerServices)); - _serviceContainerFactory.AddServiceFactory((services) => new ThreadServiceFromDebuggerServices(services, debuggerServices)); - _serviceContainerFactory.AddServiceFactory((_) => { - Debug.Assert(Host.HostType != HostType.DotnetDump); - IMemoryService memoryService = new MemoryServiceFromDebuggerServices(this, debuggerServices); - if (IsDump && Host.HostType == HostType.Lldb) - { - ServiceContainerFactory clone = _serviceContainerFactory.Clone(); - clone.RemoveServiceFactory(); - - // lldb doesn't map managed modules into the address space - memoryService = new ImageMappingMemoryService(clone.Build(), memoryService, managed: true); - - // This is a special memory service that maps the managed assemblies' metadata into the address - // space. The lldb debugger returns zero's (instead of failing the memory read) for missing pages - // in core dumps that older (< 5.0) createdumps generate so it needs this special metadata mapping - // memory service. dotnet-dump needs this logic for clrstack -i (uses ICorDebug data targets). - memoryService = new MetadataMappingMemoryService(clone.Build(), memoryService); - } - return memoryService; - }); - - // Add optional crash info service (currently only for Native AOT). - _serviceContainerFactory.AddServiceFactory((services) => CreateCrashInfoService(services, debuggerServices)); - OnFlushEvent.Register(() => FlushService()); - - if (Host.HostType == HostType.DbgEng) - { - _serviceContainerFactory.AddServiceFactory((services) => new MemoryRegionServiceFromDebuggerServices(debuggerServices)); - } - - Finished(); - - TargetWrapper targetWrapper = Services.GetService(); - targetWrapper?.ServiceWrapper.AddServiceWrapper(ClrmaServiceWrapper.IID_ICLRMAService, () => new ClrmaServiceWrapper(this, Services, targetWrapper.ServiceWrapper)); + public CrashInfoServiceFactory(IServiceProvider services, DebuggerServices debuggerServices, IHost host, OSPlatform operatingSystem) + { + Services = services ?? throw new ArgumentNullException(nameof(services)); + DebuggerServices = debuggerServices ?? throw new ArgumentNullException(nameof(debuggerServices)); + Host = host ?? throw new ArgumentNullException(nameof(host)); + OperatingSystem = operatingSystem; + } + public ICrashInfoService Create(ModuleEnumerationScheme moduleEnumerationScheme) + { + return CreateCrashInfoService(Services, DebuggerServices, moduleEnumerationScheme); } private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashInfoService crashInfoService) @@ -204,7 +123,7 @@ private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashIn return false; } - private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, DebuggerServices debuggerServices) + private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, DebuggerServices debuggerServices, ModuleEnumerationScheme moduleEnumerationScheme) { // For Linux/OSX dumps loaded under dbgeng the GetLastException API doesn't return the necessary information if (Host.HostType == HostType.DbgEng && (OperatingSystem == OSPlatform.Linux || OperatingSystem == OSPlatform.OSX)) @@ -234,37 +153,172 @@ private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider service } } - // if the above did not located the crash info service, then look for the DotNetRuntimeDebugHeader - IModule entryPointModule = services.GetService().EntryPointModule; - if (entryPointModule == null) + if (moduleEnumerationScheme == ModuleEnumerationScheme.None) { - Trace.TraceError("CrashInfoService: No entry point module found"); return null; } - if (CreateCrashInfoServiceForModule(entryPointModule, out ICrashInfoService crashInfoService)) - { - return crashInfoService; - } - // if the entry point module did not have the crash info service, then look for a library with the same name as the entry point - string fileName = entryPointModule.FileName; - if (fileName == null) + + if (moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointModule || + moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointAndEntryPointDllModule) { - Trace.TraceError("CrashInfoService: Entry point module has no file name"); - return null; + // if the above did not located the crash info service, then look for the DotNetRuntimeDebugHeader + IModule entryPointModule = services.GetService().EntryPointModule; + if (entryPointModule == null) + { + Trace.TraceError("CrashInfoService: No entry point module found"); + return null; + } + if (CreateCrashInfoServiceForModule(entryPointModule, out ICrashInfoService crashInfoService)) + { + return crashInfoService; + } + if (moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointAndEntryPointDllModule) + { + // if the entry point module did not have the crash info service, then look for a library with the same name as the entry point + string fileName = entryPointModule.FileName; + if (fileName == null) + { + Trace.TraceError("CrashInfoService: Entry point module has no file name"); + return null; + } + if (fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + { + fileName = fileName.Substring(0, fileName.Length - 4) + ".dll"; // look for a dll with the same name + foreach (IModule speculativeAppModule in services.GetService().GetModuleFromModuleName(fileName)) + { + if (CreateCrashInfoServiceForModule(speculativeAppModule, out crashInfoService)) + { + return crashInfoService; + } + } + } + } } - if (fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + else if (moduleEnumerationScheme == ModuleEnumerationScheme.All) { - fileName = fileName.Substring(0, fileName.Length - 4) + ".dll"; // look for a dll with the same name - foreach (IModule speculativeAppModule in services.GetService().GetModuleFromModuleName(fileName)) + // enumerate all modules and look for the crash info service + foreach (IModule module in services.GetService().EnumerateModules()) { - if (CreateCrashInfoServiceForModule(speculativeAppModule, out crashInfoService)) + if (CreateCrashInfoServiceForModule(module, out ICrashInfoService crashInfoService)) { return crashInfoService; } } } + else + { + Trace.TraceError($"CrashInfoService: Unknown module enumeration scheme {moduleEnumerationScheme}"); + } return null; } + + + } + + /// + /// ITarget implementation for the ClrMD IDataReader + /// + internal sealed class TargetFromDebuggerServices : Target + { + /// + /// Build a target instance from IDataReader + /// + internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost host) + : base(host, dumpPath: null) + { + Debug.Assert(debuggerServices != null); + + HResult hr = debuggerServices.GetOperatingSystem(out DebuggerServices.OperatingSystem operatingSystem); + Debug.Assert(hr == HResult.S_OK); + OperatingSystem = operatingSystem switch + { + DebuggerServices.OperatingSystem.Windows => OSPlatform.Windows, + DebuggerServices.OperatingSystem.Linux => OSPlatform.Linux, + DebuggerServices.OperatingSystem.OSX => OSPlatform.OSX, + _ => throw new PlatformNotSupportedException($"Operating system not supported: {operatingSystem}"), + }; + + hr = debuggerServices.GetDebuggeeType(out DEBUG_CLASS debugClass, out DEBUG_CLASS_QUALIFIER qualifier); + Debug.Assert(hr == HResult.S_OK); + if (qualifier >= DEBUG_CLASS_QUALIFIER.USER_WINDOWS_SMALL_DUMP) + { + IsDump = true; + } + + hr = debuggerServices.GetProcessorType(out IMAGE_FILE_MACHINE type); + if (hr == HResult.S_OK) + { + Debug.Assert(type is not IMAGE_FILE_MACHINE.ARM64X and not IMAGE_FILE_MACHINE.ARM64EC); + Architecture = type switch + { + IMAGE_FILE_MACHINE.I386 => Architecture.X86, + IMAGE_FILE_MACHINE.ARM => Architecture.Arm, + IMAGE_FILE_MACHINE.THUMB => Architecture.Arm, + IMAGE_FILE_MACHINE.ARMNT => Architecture.Arm, + IMAGE_FILE_MACHINE.AMD64 => Architecture.X64, + IMAGE_FILE_MACHINE.ARM64 => Architecture.Arm64, + IMAGE_FILE_MACHINE.LOONGARCH64 => (Architecture)6 /* Architecture.LoongArch64 */, + IMAGE_FILE_MACHINE.RISCV64 => (Architecture)9 /* Architecture.RiscV64 */, + _ => throw new PlatformNotSupportedException($"Machine type not supported: {type}"), + }; + } + else + { + throw new PlatformNotSupportedException($"GetProcessorType() FAILED {hr:X8}"); + } + + hr = debuggerServices.GetCurrentProcessId(out uint processId); + if (hr == HResult.S_OK) + { + ProcessId = processId; + } + else + { + Trace.TraceError("GetCurrentThreadId() FAILED {0:X8}", hr); + } + + // Add the thread, memory, and module services + _serviceContainerFactory.AddServiceFactory((services) => new ModuleServiceFromDebuggerServices(services, debuggerServices)); + _serviceContainerFactory.AddServiceFactory((services) => new ThreadServiceFromDebuggerServices(services, debuggerServices)); + _serviceContainerFactory.AddServiceFactory((_) => + { + Debug.Assert(Host.HostType != HostType.DotnetDump); + IMemoryService memoryService = new MemoryServiceFromDebuggerServices(this, debuggerServices); + if (IsDump && Host.HostType == HostType.Lldb) + { + ServiceContainerFactory clone = _serviceContainerFactory.Clone(); + clone.RemoveServiceFactory(); + + // lldb doesn't map managed modules into the address space + memoryService = new ImageMappingMemoryService(clone.Build(), memoryService, managed: true); + + // This is a special memory service that maps the managed assemblies' metadata into the address + // space. The lldb debugger returns zero's (instead of failing the memory read) for missing pages + // in core dumps that older (< 5.0) createdumps generate so it needs this special metadata mapping + // memory service. dotnet-dump needs this logic for clrstack -i (uses ICorDebug data targets). + memoryService = new MetadataMappingMemoryService(clone.Build(), memoryService); + } + return memoryService; + }); + + // Add optional crash info service (currently only for Native AOT). + // _serviceContainerFactory.AddServiceFactory((services) => CreateCrashInfoService(services, debuggerServices, ModuleEnumerationScheme.None)); + // OnFlushEvent.Register(() => FlushService()); + + _serviceContainerFactory.AddServiceFactory((services) => new CrashInfoServiceFactory(services, debuggerServices, host, OperatingSystem)); + OnFlushEvent.Register(() => FlushService()); + + if (Host.HostType == HostType.DbgEng) + { + _serviceContainerFactory.AddServiceFactory((services) => new MemoryRegionServiceFromDebuggerServices(debuggerServices)); + } + + Finished(); + + TargetWrapper targetWrapper = Services.GetService(); + targetWrapper?.ServiceWrapper.AddServiceWrapper(ClrmaServiceWrapper.IID_ICLRMAService, () => new ClrmaServiceWrapper(this, Services, targetWrapper.ServiceWrapper)); + } + } } From 0540bc66a719ee47669b2fb5cf4a7eb44cc1e01c Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 20:37:54 -0400 Subject: [PATCH 06/16] Separate exception based crashinfo from module enumeration based implementation --- .../CrashInfoService.cs | 166 ++++++++++++ .../CrashInfoCommand.cs | 26 +- .../TargetFromDebuggerServices.cs | 243 +++--------------- 3 files changed, 209 insertions(+), 226 deletions(-) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index f8bd71904d..d7b5947f2f 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -12,6 +12,172 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { + public sealed class CrashInfoServiceFactory : ICrashInfoServiceFactory + { + private readonly IServiceProvider Services; + + public CrashInfoServiceFactory(IServiceProvider services) + { + Services = services ?? throw new ArgumentNullException(nameof(services)); + } + public ICrashInfoService Create(ModuleEnumerationScheme moduleEnumerationScheme) + { + return CreateCrashInfoService(Services, moduleEnumerationScheme); + } + + private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashInfoService crashInfoService) + { + crashInfoService = null; + if (module == null || module.IsManaged) + { + return false; + } + if ((module as IExportSymbols)?.TryGetSymbolAddress(CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME, out ulong headerAddr) != true) + { + Trace.TraceInformation($"CrashInfoService: {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} export not found in module {module.FileName}"); + return false; + } + IMemoryService memoryService = module.Services.GetService(); + if (!memoryService.Read(ref headerAddr, out uint headerValue) || + headerValue != CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_COOKIE || + !memoryService.Read(ref headerAddr, out ushort majorVersion) || majorVersion < 3 || // .NET 8 and later + !memoryService.Read(ref headerAddr, out ushort minorVersion)) + { + Trace.TraceInformation($"CrashInfoService: .NET 8+ {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} not found in module {module.FileName}"); + return false; + } + + Trace.TraceInformation($"CrashInfoService: Found {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} in module {module.FileName} with version {majorVersion}.{minorVersion}"); + + if (!memoryService.Read(ref headerAddr, out uint flags) || + memoryService.PointerSize != (flags == 0x1 ? 8 : 4) || + !memoryService.Read(ref headerAddr, out uint _)) // padding + { + Trace.TraceError($"CrashInfoService: Failed to read DotNetRuntimeDebugHeader flags or padding in module {module.FileName}"); + return false; + } + + Trace.TraceInformation($"CrashInfoService: Target is {memoryService.PointerSize * 8}-bit"); + + headerAddr += (uint)memoryService.PointerSize; // skip DebugEntries array + + // read GlobalEntries array + if (!memoryService.ReadPointer(ref headerAddr, out ulong globalEntryArrayAddress) || globalEntryArrayAddress == 0) + { + Trace.TraceError($"CrashInfoService: Unable to read GlobalEntry array"); + return false; + } + uint maxGlobalEntries = (majorVersion >= 4 /*.NET 9 or later*/) ? CrashInfoService.MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET9_PLUS : CrashInfoService.MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET8; + for (int i = 0; i < maxGlobalEntries; i++) + { + if (!memoryService.ReadPointer(ref globalEntryArrayAddress, out ulong globalNameAddress) || globalNameAddress == 0 || + !memoryService.ReadPointer(ref globalEntryArrayAddress, out ulong globalValueAddress) || globalValueAddress == 0 || + !memoryService.ReadAnsiString(CrashInfoService.MAX_GLOBAL_ENTRY_NAME_CHARS, globalNameAddress, out string globalName)) + { + break; // no more global entries + } + + if (!string.Equals(globalName, "g_CrashInfoBuffer", StringComparison.Ordinal)) + { + continue; // not the crash info buffer + } + + ulong triageBufferAddress = globalValueAddress; + Span buffer = new byte[CrashInfoService.MAX_CRASHINFOBUFFER_SIZE]; + if (memoryService.ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead > 0 && bytesRead <= CrashInfoService.MAX_CRASHINFOBUFFER_SIZE) + { + // truncate the buffer to the null terminated string in the buffer + int nullTerminatorIndex = buffer.IndexOf((byte)0); + if (nullTerminatorIndex >= 0) + { + buffer = buffer.Slice(0, nullTerminatorIndex); + Trace.TraceInformation($"CrashInfoService: Found g_CrashInfoBuffer in module {module.FileName} with size {buffer.Length} bytes"); + if (buffer.Length > 0) + { + crashInfoService = CrashInfoService.Create(0, buffer, module.Services.GetService()); + return true; + } + else + { + Trace.TraceError($"CrashInfoService: g_CrashInfoBuffer is empty in module {module.FileName}"); + } + } + else + { + Trace.TraceError($"CrashInfoService: g_CrashInfoBuffer is not null terminated in module {module.FileName}"); + } + } + else + { + Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed in module {module.FileName}"); + } + } + return false; + } + + private static unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, ModuleEnumerationScheme moduleEnumerationScheme) + { + if (moduleEnumerationScheme == ModuleEnumerationScheme.None) + { + return null; + } + + if (moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointModule || + moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointAndEntryPointDllModule) + { + // if the above did not located the crash info service, then look for the DotNetRuntimeDebugHeader + IModule entryPointModule = services.GetService().EntryPointModule; + if (entryPointModule == null) + { + Trace.TraceError("CrashInfoService: No entry point module found"); + return null; + } + if (CreateCrashInfoServiceForModule(entryPointModule, out ICrashInfoService crashInfoService)) + { + return crashInfoService; + } + if (moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointAndEntryPointDllModule) + { + // if the entry point module did not have the crash info service, then look for a library with the same name as the entry point + string fileName = entryPointModule.FileName; + if (fileName == null) + { + Trace.TraceError("CrashInfoService: Entry point module has no file name"); + return null; + } + if (fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + { + fileName = fileName.Substring(0, fileName.Length - 4) + ".dll"; // look for a dll with the same name + foreach (IModule speculativeAppModule in services.GetService().GetModuleFromModuleName(fileName)) + { + if (CreateCrashInfoServiceForModule(speculativeAppModule, out crashInfoService)) + { + return crashInfoService; + } + } + } + } + } + else if (moduleEnumerationScheme == ModuleEnumerationScheme.All) + { + // enumerate all modules and look for the crash info service + foreach (IModule module in services.GetService().EnumerateModules()) + { + if (CreateCrashInfoServiceForModule(module, out ICrashInfoService crashInfoService)) + { + return crashInfoService; + } + } + } + else + { + Trace.TraceError($"CrashInfoService: Unknown module enumeration scheme {moduleEnumerationScheme}"); + } + + return null; + } + } + public class CrashInfoService : ICrashInfoService { /// diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs index 63f8c0f668..2b49aa5492 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs @@ -13,34 +13,18 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Command(Name = "crashinfo", Help = "Displays the crash details that created the dump.")] public class CrashInfoCommand : CommandBase { - // [ServiceImport(Optional = true)] - // public ICrashInfoService CrashInfo { get; set; } + [ServiceImport(Optional = true)] + public ICrashInfoService CrashInfo { get; set; } [ServiceImport(Optional = true)] public ICrashInfoServiceFactory CrashInfoFactory { get; set; } - [Option(Name = "--enumerateAllModules", Aliases = new string[] { "-a" }, Help = "Enables enumerating all modules to find a Native AOT module.")] - public bool EnumerateAllModules { get; set; } - - [Option(Name = "--entryPoint", Aliases = new string[] { "-e" }, Help = "Enables enumerating the entry point module to find a Native AOT module.")] - public bool EntryPointOnly { get; set; } - - // [Option(Name = "--moduleName", Aliases = new string[] { "-m" }, Help = "Specifies the module name to find a Native AOT module.")] - // public string ModuleName { get; set; } - - // [Option(Name = "--moduleIndex", Aliases = new string[] { "-i" }, Help = "Specifies the module index to find a Native AOT module.")] - // public int ModuleIndex { get; set; } - - // [Option(Name = "--moduleBase", Aliases = new string[] { "-b" }, Help = "Specifies the module base address to find a Native AOT module.")] - // public ulong ModuleBase { get; set; } - - [Option(Name = "--entryPointDll", Aliases = new string[] { "-d" }, Help = "Enables enumerating the entry point module and corresponding entry point library to find a Native AOT module.")] - public bool EntryPointDll { get; set; } + [Option(Name = "--moduleEnumerationScheme", Aliases = new string[] { "-e" }, Help = "Enables searching modules for the NativeAOT crashinfo data.")] + public ModuleEnumerationScheme ModuleEnumerationScheme { get; set; } public override void Invoke() { - ICrashInfoService crashInfo = CrashInfoFactory.Create(ModuleEnumerationScheme.All); - + ICrashInfoService crashInfo = CrashInfo ?? CrashInfoFactory.Create(ModuleEnumerationScheme); if (crashInfo == null) { throw new DiagnosticsException("No crash info to display"); diff --git a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs index 51bd497519..4af78f1949 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -14,208 +14,6 @@ namespace SOS.Extensions { - internal sealed class CrashInfoServiceFactory : ICrashInfoServiceFactory - { - private readonly IServiceProvider Services; - private readonly DebuggerServices DebuggerServices; - private readonly IHost Host; - private readonly OSPlatform OperatingSystem; - - public CrashInfoServiceFactory(IServiceProvider services, DebuggerServices debuggerServices, IHost host, OSPlatform operatingSystem) - { - Services = services ?? throw new ArgumentNullException(nameof(services)); - DebuggerServices = debuggerServices ?? throw new ArgumentNullException(nameof(debuggerServices)); - Host = host ?? throw new ArgumentNullException(nameof(host)); - OperatingSystem = operatingSystem; - } - public ICrashInfoService Create(ModuleEnumerationScheme moduleEnumerationScheme) - { - return CreateCrashInfoService(Services, DebuggerServices, moduleEnumerationScheme); - } - - private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashInfoService crashInfoService) - { - crashInfoService = null; - if (module == null || module.IsManaged) - { - return false; - } - if ((module as IExportSymbols)?.TryGetSymbolAddress(CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME, out ulong headerAddr) != true) - { - Trace.TraceInformation($"CrashInfoService: {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} export not found in module {module.FileName}"); - return false; - } - IMemoryService memoryService = module.Services.GetService(); - if (!memoryService.Read(ref headerAddr, out uint headerValue) || - headerValue != CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_COOKIE || - !memoryService.Read(ref headerAddr, out ushort majorVersion) || majorVersion < 3 || // .NET 8 and later - !memoryService.Read(ref headerAddr, out ushort minorVersion)) - { - Trace.TraceInformation($"CrashInfoService: .NET 8+ {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} not found in module {module.FileName}"); - return false; - } - - Trace.TraceInformation($"CrashInfoService: Found {CrashInfoService.DOTNET_RUNTIME_DEBUG_HEADER_NAME} in module {module.FileName} with version {majorVersion}.{minorVersion}"); - - if (!memoryService.Read(ref headerAddr, out uint flags) || - memoryService.PointerSize != (flags == 0x1 ? 8 : 4) || - !memoryService.Read(ref headerAddr, out uint _)) // padding - { - Trace.TraceError($"CrashInfoService: Failed to read DotNetRuntimeDebugHeader flags or padding in module {module.FileName}"); - return false; - } - - Trace.TraceInformation($"CrashInfoService: Target is {memoryService.PointerSize * 8}-bit"); - - headerAddr += (uint)memoryService.PointerSize; // skip DebugEntries array - - // read GlobalEntries array - if (!memoryService.ReadPointer(ref headerAddr, out ulong globalEntryArrayAddress) || globalEntryArrayAddress == 0) - { - Trace.TraceError($"CrashInfoService: Unable to read GlobalEntry array"); - return false; - } - uint maxGlobalEntries = (majorVersion >= 4 /*.NET 9 or later*/) ? CrashInfoService.MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET9_PLUS : CrashInfoService.MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET8; - for (int i = 0; i < maxGlobalEntries; i++) - { - if (!memoryService.ReadPointer(ref globalEntryArrayAddress, out ulong globalNameAddress) || globalNameAddress == 0 || - !memoryService.ReadPointer(ref globalEntryArrayAddress, out ulong globalValueAddress) || globalValueAddress == 0 || - !memoryService.ReadAnsiString(CrashInfoService.MAX_GLOBAL_ENTRY_NAME_CHARS, globalNameAddress, out string globalName)) - { - break; // no more global entries - } - - if (!string.Equals(globalName, "g_CrashInfoBuffer", StringComparison.Ordinal)) - { - continue; // not the crash info buffer - } - - ulong triageBufferAddress = globalValueAddress; - Span buffer = new byte[CrashInfoService.MAX_CRASHINFOBUFFER_SIZE]; - if (memoryService.ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead > 0 && bytesRead <= CrashInfoService.MAX_CRASHINFOBUFFER_SIZE) - { - // truncate the buffer to the null terminated string in the buffer - int nullTerminatorIndex = buffer.IndexOf((byte)0); - if (nullTerminatorIndex >= 0) - { - buffer = buffer.Slice(0, nullTerminatorIndex); - Trace.TraceInformation($"CrashInfoService: Found g_CrashInfoBuffer in module {module.FileName} with size {buffer.Length} bytes"); - if (buffer.Length > 0) - { - crashInfoService = CrashInfoService.Create(0, buffer, module.Services.GetService()); - return true; - } - else - { - Trace.TraceError($"CrashInfoService: g_CrashInfoBuffer is empty in module {module.FileName}"); - } - } - else - { - Trace.TraceError($"CrashInfoService: g_CrashInfoBuffer is not null terminated in module {module.FileName}"); - } - } - else - { - Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed in module {module.FileName}"); - } - } - return false; - } - - private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, DebuggerServices debuggerServices, ModuleEnumerationScheme moduleEnumerationScheme) - { - // For Linux/OSX dumps loaded under dbgeng the GetLastException API doesn't return the necessary information - if (Host.HostType == HostType.DbgEng && (OperatingSystem == OSPlatform.Linux || OperatingSystem == OSPlatform.OSX)) - { - return SpecialDiagInfo.CreateCrashInfoService(services); - } - HResult hr = debuggerServices.GetLastException(out uint processId, out int threadIndex, out EXCEPTION_RECORD64 exceptionRecord); - if (hr.IsOK) - { - if (exceptionRecord.ExceptionCode == CrashInfoService.STATUS_STACK_BUFFER_OVERRUN && - exceptionRecord.NumberParameters >= 4 && - exceptionRecord.ExceptionInformation[0] == CrashInfoService.FAST_FAIL_EXCEPTION_DOTNET_AOT) - { - uint hresult = (uint)exceptionRecord.ExceptionInformation[1]; - ulong triageBufferAddress = exceptionRecord.ExceptionInformation[2]; - int triageBufferSize = (int)exceptionRecord.ExceptionInformation[3]; - - Span buffer = new byte[triageBufferSize]; - if (services.GetService().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead == triageBufferSize) - { - return CrashInfoService.Create(hresult, buffer, services.GetService()); - } - else - { - Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed"); - } - } - } - - if (moduleEnumerationScheme == ModuleEnumerationScheme.None) - { - return null; - } - - if (moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointModule || - moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointAndEntryPointDllModule) - { - // if the above did not located the crash info service, then look for the DotNetRuntimeDebugHeader - IModule entryPointModule = services.GetService().EntryPointModule; - if (entryPointModule == null) - { - Trace.TraceError("CrashInfoService: No entry point module found"); - return null; - } - if (CreateCrashInfoServiceForModule(entryPointModule, out ICrashInfoService crashInfoService)) - { - return crashInfoService; - } - if (moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointAndEntryPointDllModule) - { - // if the entry point module did not have the crash info service, then look for a library with the same name as the entry point - string fileName = entryPointModule.FileName; - if (fileName == null) - { - Trace.TraceError("CrashInfoService: Entry point module has no file name"); - return null; - } - if (fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) - { - fileName = fileName.Substring(0, fileName.Length - 4) + ".dll"; // look for a dll with the same name - foreach (IModule speculativeAppModule in services.GetService().GetModuleFromModuleName(fileName)) - { - if (CreateCrashInfoServiceForModule(speculativeAppModule, out crashInfoService)) - { - return crashInfoService; - } - } - } - } - } - else if (moduleEnumerationScheme == ModuleEnumerationScheme.All) - { - // enumerate all modules and look for the crash info service - foreach (IModule module in services.GetService().EnumerateModules()) - { - if (CreateCrashInfoServiceForModule(module, out ICrashInfoService crashInfoService)) - { - return crashInfoService; - } - } - } - else - { - Trace.TraceError($"CrashInfoService: Unknown module enumeration scheme {moduleEnumerationScheme}"); - } - - return null; - } - - - } - /// /// ITarget implementation for the ClrMD IDataReader /// @@ -303,10 +101,11 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos }); // Add optional crash info service (currently only for Native AOT). - // _serviceContainerFactory.AddServiceFactory((services) => CreateCrashInfoService(services, debuggerServices, ModuleEnumerationScheme.None)); - // OnFlushEvent.Register(() => FlushService()); + _serviceContainerFactory.AddServiceFactory((services) => CreateCrashInfoServiceFromException(services, debuggerServices)); + OnFlushEvent.Register(() => FlushService()); - _serviceContainerFactory.AddServiceFactory((services) => new CrashInfoServiceFactory(services, debuggerServices, host, OperatingSystem)); + // Add the crash info service factory which lookup the DotNetRuntimeDebugHeader from modules + _serviceContainerFactory.AddServiceFactory((services) => new CrashInfoServiceFactory(services)); OnFlushEvent.Register(() => FlushService()); if (Host.HostType == HostType.DbgEng) @@ -320,5 +119,39 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos targetWrapper?.ServiceWrapper.AddServiceWrapper(ClrmaServiceWrapper.IID_ICLRMAService, () => new ClrmaServiceWrapper(this, Services, targetWrapper.ServiceWrapper)); } + private unsafe ICrashInfoService CreateCrashInfoServiceFromException(IServiceProvider services, DebuggerServices debuggerServices) + { + // For Linux/OSX dumps loaded under dbgeng the GetLastException API doesn't return the necessary information + if (Host.HostType == HostType.DbgEng && (OperatingSystem == OSPlatform.Linux || OperatingSystem == OSPlatform.OSX)) + { + return SpecialDiagInfo.CreateCrashInfoService(services); + } + HResult hr = debuggerServices.GetLastException(out uint processId, out int threadIndex, out EXCEPTION_RECORD64 exceptionRecord); + if (hr.IsOK) + { + if (exceptionRecord.ExceptionCode == CrashInfoService.STATUS_STACK_BUFFER_OVERRUN && + exceptionRecord.NumberParameters >= 4 && + exceptionRecord.ExceptionInformation[0] == CrashInfoService.FAST_FAIL_EXCEPTION_DOTNET_AOT) + { + uint hresult = (uint)exceptionRecord.ExceptionInformation[1]; + ulong triageBufferAddress = exceptionRecord.ExceptionInformation[2]; + int triageBufferSize = (int)exceptionRecord.ExceptionInformation[3]; + + Span buffer = new byte[triageBufferSize]; + if (services.GetService().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead == triageBufferSize) + { + return CrashInfoService.Create(hresult, buffer, services.GetService()); + } + else + { + Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed"); + } + } + } + + return null; // no crash info service found + } + + } } From 13e2f0bc7870b7ef8be090a52235eb0690ebc354 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 21:35:32 -0400 Subject: [PATCH 07/16] Set crashinfo ModuleEnumerationScheme default to none --- .../CrashInfoCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs index 2b49aa5492..72668ebe3a 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs @@ -19,8 +19,8 @@ public class CrashInfoCommand : CommandBase [ServiceImport(Optional = true)] public ICrashInfoServiceFactory CrashInfoFactory { get; set; } - [Option(Name = "--moduleEnumerationScheme", Aliases = new string[] { "-e" }, Help = "Enables searching modules for the NativeAOT crashinfo data.")] - public ModuleEnumerationScheme ModuleEnumerationScheme { get; set; } + [Option(Name = "--moduleEnumerationScheme", Aliases = new string[] { "-e" }, Help = "Enables searching modules for the NativeAOT crashinfo data. Default is None")] + public ModuleEnumerationScheme ModuleEnumerationScheme { get; set; } = ModuleEnumerationScheme.None; public override void Invoke() { From 011abda5e0c6921cad5f96bce6dcdabe1ee87c20 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 21:35:59 -0400 Subject: [PATCH 08/16] Make EntryPointAndEntryPointDllModule cross platform --- .../CrashInfoService.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index d7b5947f2f..4883733e46 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Linq; using System.Text; using System.Text.Json; @@ -140,20 +141,41 @@ private static unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider { // if the entry point module did not have the crash info service, then look for a library with the same name as the entry point string fileName = entryPointModule.FileName; + OSPlatform osPlatform = entryPointModule.Target.OperatingSystem; if (fileName == null) { Trace.TraceError("CrashInfoService: Entry point module has no file name"); return null; } - if (fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + if (osPlatform == OSPlatform.Windows) { - fileName = fileName.Substring(0, fileName.Length - 4) + ".dll"; // look for a dll with the same name - foreach (IModule speculativeAppModule in services.GetService().GetModuleFromModuleName(fileName)) + if (fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { - if (CreateCrashInfoServiceForModule(speculativeAppModule, out crashInfoService)) - { - return crashInfoService; - } + fileName = fileName.Substring(0, fileName.Length - 4) + ".dll"; // look for a dll with the same name + } + else + { + Trace.TraceError($"CrashInfoService: Unexpected entry point module file name {fileName}"); + } + } + else if (osPlatform == OSPlatform.Linux) + { + fileName += ".so"; // look for a .so with the same name + } + else if (osPlatform == OSPlatform.OSX) + { + fileName += ".dylib"; // look for a .dylib with the same name + } + else + { + Trace.TraceError($"CrashInfoService: Unsupported operating system {osPlatform}"); + return null; + } + foreach (IModule speculativeAppModule in services.GetService().GetModuleFromModuleName(fileName)) + { + if (CreateCrashInfoServiceForModule(speculativeAppModule, out crashInfoService)) + { + return crashInfoService; } } } From 4013261b3cccd6f9ad8808c52ab22804ff6196ba Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 21:51:41 -0400 Subject: [PATCH 09/16] Break out of GlobalEntry loop after finding g_CrashInfoBuffer --- .../CrashInfoService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index 4883733e46..802cad0253 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -112,6 +112,7 @@ private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashIn { Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed in module {module.FileName}"); } + break; } return false; } From 4553d613300107d9fa75ec0e1ba6c14cedc5dd22 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 21:54:22 -0400 Subject: [PATCH 10/16] update comment --- src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs index 11431ce1d4..371de58dd3 100644 --- a/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs @@ -279,7 +279,7 @@ public override IModule EntryPointModule } else { - // On non-Windows, the entry point module is the first module. + // On non-Windows, assume the entry point module is the first module. return module; } } From 4098cf9d41b1d59f21054978b166cef9f8f6936b Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 22:12:02 -0400 Subject: [PATCH 11/16] Rename ICrashInfoService related interfaces --- .../CrashInfoService.cs | 8 ++++---- .../ICrashInfoService.cs | 2 +- .../CrashInfoCommand.cs | 2 +- src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs | 9 ++++----- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index 802cad0253..2fd78f06a0 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -13,17 +13,17 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { - public sealed class CrashInfoServiceFactory : ICrashInfoServiceFactory + public sealed class CrashInfoModuleService : ICrashInfoModuleService { private readonly IServiceProvider Services; - public CrashInfoServiceFactory(IServiceProvider services) + public CrashInfoModuleService(IServiceProvider services) { Services = services ?? throw new ArgumentNullException(nameof(services)); } public ICrashInfoService Create(ModuleEnumerationScheme moduleEnumerationScheme) { - return CreateCrashInfoService(Services, moduleEnumerationScheme); + return CreateCrashInfoServiceFromModule(Services, moduleEnumerationScheme); } private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashInfoService crashInfoService) @@ -117,7 +117,7 @@ private static bool CreateCrashInfoServiceForModule(IModule module, out ICrashIn return false; } - private static unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, ModuleEnumerationScheme moduleEnumerationScheme) + private static unsafe ICrashInfoService CreateCrashInfoServiceFromModule(IServiceProvider services, ModuleEnumerationScheme moduleEnumerationScheme) { if (moduleEnumerationScheme == ModuleEnumerationScheme.None) { diff --git a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs index 0c87841a45..f395fbaf04 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs @@ -40,7 +40,7 @@ public enum ModuleEnumerationScheme All } - public interface ICrashInfoServiceFactory + public interface ICrashInfoModuleService { /// /// Create a crash info service diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs index 72668ebe3a..ff2273b71c 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs @@ -17,7 +17,7 @@ public class CrashInfoCommand : CommandBase public ICrashInfoService CrashInfo { get; set; } [ServiceImport(Optional = true)] - public ICrashInfoServiceFactory CrashInfoFactory { get; set; } + public ICrashInfoModuleService CrashInfoFactory { get; set; } [Option(Name = "--moduleEnumerationScheme", Aliases = new string[] { "-e" }, Help = "Enables searching modules for the NativeAOT crashinfo data. Default is None")] public ModuleEnumerationScheme ModuleEnumerationScheme { get; set; } = ModuleEnumerationScheme.None; diff --git a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs index 4af78f1949..a918ca7dda 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -79,8 +79,7 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos // Add the thread, memory, and module services _serviceContainerFactory.AddServiceFactory((services) => new ModuleServiceFromDebuggerServices(services, debuggerServices)); _serviceContainerFactory.AddServiceFactory((services) => new ThreadServiceFromDebuggerServices(services, debuggerServices)); - _serviceContainerFactory.AddServiceFactory((_) => - { + _serviceContainerFactory.AddServiceFactory((_) => { Debug.Assert(Host.HostType != HostType.DotnetDump); IMemoryService memoryService = new MemoryServiceFromDebuggerServices(this, debuggerServices); if (IsDump && Host.HostType == HostType.Lldb) @@ -105,8 +104,8 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos OnFlushEvent.Register(() => FlushService()); // Add the crash info service factory which lookup the DotNetRuntimeDebugHeader from modules - _serviceContainerFactory.AddServiceFactory((services) => new CrashInfoServiceFactory(services)); - OnFlushEvent.Register(() => FlushService()); + _serviceContainerFactory.AddServiceFactory((services) => new CrashInfoModuleService(services)); + OnFlushEvent.Register(() => FlushService()); if (Host.HostType == HostType.DbgEng) { @@ -149,7 +148,7 @@ private unsafe ICrashInfoService CreateCrashInfoServiceFromException(IServicePro } } - return null; // no crash info service found + return null; } From 8b14cdbffc1cb529dcbf77217bdfd55f22159421 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 22:14:00 -0400 Subject: [PATCH 12/16] Add ICrashInfoModuleService to TargetFromDataReader --- .../TargetFromDataReader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs index 8f92d215a5..7fd23526df 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs @@ -77,6 +77,10 @@ public TargetFromDataReader(DataTarget dataTarget, OSPlatform targetOS, IHost ho _serviceContainerFactory.AddServiceFactory((services) => SpecialDiagInfo.CreateCrashInfoService(services)); OnFlushEvent.Register(() => FlushService()); + // Add the crash info service factory which lookup the DotNetRuntimeDebugHeader from modules + _serviceContainerFactory.AddServiceFactory((services) => new CrashInfoModuleService(services)); + OnFlushEvent.Register(() => FlushService()); + Finished(); } From 0f1add1f9bcded60dec342be066e1fa0555108e0 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 24 Jul 2025 22:16:08 -0400 Subject: [PATCH 13/16] Rename CreateCrashInfoService to CreateCrashInfoServiceFromException --- .../SpecialDiagInfo.cs | 2 +- .../TargetFromDataReader.cs | 2 +- src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs index 65b3621825..7784a51736 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs @@ -84,7 +84,7 @@ private ulong SpecialDiagInfoAddress } } - public static ICrashInfoService CreateCrashInfoService(IServiceProvider services) + public static ICrashInfoService CreateCrashInfoServiceFromException(IServiceProvider services) { EXCEPTION_RECORD64 exceptionRecord; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs index 7fd23526df..fa4c6ce835 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs @@ -74,7 +74,7 @@ public TargetFromDataReader(DataTarget dataTarget, OSPlatform targetOS, IHost ho }); // Add optional crash info service (currently only for Native AOT on Linux/MacOS). - _serviceContainerFactory.AddServiceFactory((services) => SpecialDiagInfo.CreateCrashInfoService(services)); + _serviceContainerFactory.AddServiceFactory((services) => SpecialDiagInfo.CreateCrashInfoServiceFromException(services)); OnFlushEvent.Register(() => FlushService()); // Add the crash info service factory which lookup the DotNetRuntimeDebugHeader from modules diff --git a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs index a918ca7dda..72e9108bdd 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -123,7 +123,7 @@ private unsafe ICrashInfoService CreateCrashInfoServiceFromException(IServicePro // For Linux/OSX dumps loaded under dbgeng the GetLastException API doesn't return the necessary information if (Host.HostType == HostType.DbgEng && (OperatingSystem == OSPlatform.Linux || OperatingSystem == OSPlatform.OSX)) { - return SpecialDiagInfo.CreateCrashInfoService(services); + return SpecialDiagInfo.CreateCrashInfoServiceFromException(services); } HResult hr = debuggerServices.GetLastException(out uint processId, out int threadIndex, out EXCEPTION_RECORD64 exceptionRecord); if (hr.IsOK) From 77f4165031b267ca1d712f83336e94aa1eb68cea Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 25 Jul 2025 00:20:14 -0400 Subject: [PATCH 14/16] Add CrashInfoService module service to clrma implementation --- src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs b/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs index dc47872586..865e57bb45 100644 --- a/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs +++ b/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs @@ -13,6 +13,7 @@ namespace SOS.Extensions.Clrma { public sealed class ClrmaServiceWrapper : COMCallableIUnknown { + public const ModuleEnumerationScheme DefaultModuleEnumerationScheme = ModuleEnumerationScheme.EntryPointAndEntryPointDllModule; public static readonly Guid IID_ICLRMAService = new("1FCF4C14-60C1-44E6-84ED-20506EF3DC60"); public const int E_BOUNDS = unchecked((int)0x8000000B); @@ -104,7 +105,15 @@ private int GetObjectInspection( return HResult.E_NOTIMPL; } - private ICrashInfoService CrashInfoService => _crashInfoService ??= _serviceProvider.GetService(); + private ICrashInfoService CrashInfoService + { + get + { + _crashInfoService ??= _serviceProvider.GetService(); // Use the default exception-based mechanism for obtaining crash info service + _crashInfoService ??= _serviceProvider.GetService()?.Create(DefaultModuleEnumerationScheme); // if the above fails, try to create a crash info service from the modules + return _crashInfoService; + } + } private IThreadService ThreadService => _serviceProvider.GetService(); From b98d8cdd2631bd14482dd6b708cfeeca830a46a3 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Tue, 29 Jul 2025 11:44:51 -0400 Subject: [PATCH 15/16] Fix comment typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Strehovský --- .../CrashInfoService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index 2fd78f06a0..acc0c35369 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -127,7 +127,7 @@ private static unsafe ICrashInfoService CreateCrashInfoServiceFromModule(IServic if (moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointModule || moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointAndEntryPointDllModule) { - // if the above did not located the crash info service, then look for the DotNetRuntimeDebugHeader + // if the above did not locate the crash info service, then look for the DotNetRuntimeDebugHeader IModule entryPointModule = services.GetService().EntryPointModule; if (entryPointModule == null) { From a5debf87f7389d09e98ecaa072148f8866e9fbae Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Tue, 29 Jul 2025 12:19:00 -0400 Subject: [PATCH 16/16] Add clrconfig -enumScheme command to change clrma module load default behavior --- .../Clrma/ClrmaServiceWrapper.cs | 18 ++++++++++++- src/SOS/Strike/clrma/clrma.cpp | 25 ++++++++++++++++++- src/SOS/Strike/clrma/managedanalysis.cpp | 4 +++ src/SOS/Strike/clrma/managedanalysis.h | 3 +++ src/SOS/inc/clrmaservice.h | 3 +++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs b/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs index 865e57bb45..c8d57a2adf 100644 --- a/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs +++ b/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs @@ -35,10 +35,21 @@ public ClrmaServiceWrapper(ITarget target, IServiceProvider serviceProvider, Ser builder.AddMethod(new GetThreadDelegate(GetThread)); builder.AddMethod(new GetExceptionDelegate(GetException)); builder.AddMethod(new GetObjectInspectionDelegate(GetObjectInspection)); + builder.AddMethod(new SetModuleEnumerationPolicyDelegate((self, moduleEnumerationPolicy) => + { + if (moduleEnumerationPolicy < 0 || moduleEnumerationPolicy > (int)ModuleEnumerationScheme.All) + { + return HResult.E_INVALIDARG; + } + ModuleEnumerationScheme = (ModuleEnumerationScheme)moduleEnumerationPolicy; + return HResult.S_OK; + })); builder.Complete(); // Since this wrapper is only created through a ServiceWrapper factory, no AddRef() is needed. } + public ModuleEnumerationScheme ModuleEnumerationScheme { get; private set; } = ModuleEnumerationScheme.None; + protected override void Destroy() { Trace.TraceInformation("ClrmaServiceWrapper.Destroy"); @@ -110,7 +121,7 @@ private ICrashInfoService CrashInfoService get { _crashInfoService ??= _serviceProvider.GetService(); // Use the default exception-based mechanism for obtaining crash info service - _crashInfoService ??= _serviceProvider.GetService()?.Create(DefaultModuleEnumerationScheme); // if the above fails, try to create a crash info service from the modules + _crashInfoService ??= _serviceProvider.GetService()?.Create(ModuleEnumerationScheme); // if the above fails, try to create a crash info service from the modules return _crashInfoService; } } @@ -141,6 +152,11 @@ private delegate int GetObjectInspectionDelegate( [In] IntPtr self, [Out] out IntPtr objectInspection); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate int SetModuleEnumerationPolicyDelegate( + [In] IntPtr self, + [In] uint moduleEnumerationPolicy); + #endregion } } diff --git a/src/SOS/Strike/clrma/clrma.cpp b/src/SOS/Strike/clrma/clrma.cpp index c62aae06a1..aae001aba6 100644 --- a/src/SOS/Strike/clrma/clrma.cpp +++ b/src/SOS/Strike/clrma/clrma.cpp @@ -9,7 +9,7 @@ HRESULT CLRMAReleaseInstance(); ICLRManagedAnalysis* g_managedAnalysis = nullptr; -int g_clrmaGlobalFlags = ClrmaGlobalFlags::LoggingEnabled | ClrmaGlobalFlags::DacClrmaEnabled | ClrmaGlobalFlags::ManagedClrmaEnabled; +int g_clrmaGlobalFlags = ClrmaGlobalFlags::LoggingEnabled | ClrmaGlobalFlags::DacClrmaEnabled | ClrmaGlobalFlags::ManagedClrmaEnabled | ClrmaGlobalFlags::ModuleEnumeration_EntryPointAndDllModule; // // Exports @@ -63,6 +63,7 @@ DECLARE_API(clrmaconfig) BOOL bDacClrma = FALSE; BOOL bManagedClrma = FALSE; BOOL bLogging = FALSE; + SIZE_T moduleEnumerationScheme = -1; // None=0, EntryPointModule=1, EntryPointAndEntryPointDllModule=2, AllModules=3 CMDOption option[] = { // name, vptr, type, hasValue @@ -71,6 +72,7 @@ DECLARE_API(clrmaconfig) {"-dac", &bDacClrma, COBOOL, FALSE}, {"-managed", &bManagedClrma, COBOOL, FALSE}, {"-logging", &bLogging, COBOOL, FALSE}, + {"-enumScheme", &moduleEnumerationScheme, COSIZE_T, TRUE}, }; if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) @@ -107,11 +109,32 @@ DECLARE_API(clrmaconfig) { g_clrmaGlobalFlags &= ~ClrmaGlobalFlags::LoggingEnabled; } + if (moduleEnumerationScheme != 0) + { + g_clrmaGlobalFlags &= ~(ClrmaGlobalFlags::ModuleEnumeration_EntryPointModule | + ClrmaGlobalFlags::ModuleEnumeration_EntryPointAndDllModule | + ClrmaGlobalFlags::ModuleEnumeration_AllModules); + } + } + + if (moduleEnumerationScheme != -1) + { + g_clrmaGlobalFlags &= ~(ClrmaGlobalFlags::ModuleEnumeration_EntryPointModule | + ClrmaGlobalFlags::ModuleEnumeration_EntryPointAndDllModule | + ClrmaGlobalFlags::ModuleEnumeration_AllModules); + g_clrmaGlobalFlags |= moduleEnumerationScheme == 1 ? ClrmaGlobalFlags::ModuleEnumeration_EntryPointModule : + moduleEnumerationScheme == 2 ? ClrmaGlobalFlags::ModuleEnumeration_EntryPointAndDllModule : + moduleEnumerationScheme == 3 ? ClrmaGlobalFlags::ModuleEnumeration_AllModules : + 0; } ExtOut("CLRMA logging: %s\n", (g_clrmaGlobalFlags & ClrmaGlobalFlags::LoggingEnabled) ? "enabled (disable with '-disable -logging')" : "disabled (enable with '-enable -logging')"); ExtOut("CLRMA direct DAC support: %s\n", (g_clrmaGlobalFlags & ClrmaGlobalFlags::DacClrmaEnabled) ? "enabled (disable with '-disable -dac')" : "disabled (enable with '-enable -dac')"); ExtOut("CLRMA managed support: %s\n", (g_clrmaGlobalFlags & ClrmaGlobalFlags::ManagedClrmaEnabled) ? "enabled (disable with '-disable -managed')" : "disabled (enable with '-enable -managed')"); + ExtOut("CLRMA module enumeration: %s\n", (g_clrmaGlobalFlags & ClrmaGlobalFlags::ModuleEnumeration_EntryPointModule) ? "Search for crashinfo on EntryPoint module (-enumScheme 1)" : + (g_clrmaGlobalFlags & ClrmaGlobalFlags::ModuleEnumeration_EntryPointAndDllModule) ? "Search for crash info on both EntryPoint and DLL with same name (-enumScheme 2)" : + (g_clrmaGlobalFlags & ClrmaGlobalFlags::ModuleEnumeration_AllModules) ? "Search for crash info on all modules (-enumScheme 3)" : + "Only read crashinfo from Exception if present (use -enumScheme 0)"); return Status; } diff --git a/src/SOS/Strike/clrma/managedanalysis.cpp b/src/SOS/Strike/clrma/managedanalysis.cpp index 24250b4de6..365cc39c4d 100644 --- a/src/SOS/Strike/clrma/managedanalysis.cpp +++ b/src/SOS/Strike/clrma/managedanalysis.cpp @@ -241,6 +241,10 @@ ClrmaManagedAnalysis::AssociateClient( ReleaseHolder clrmaService; if (SUCCEEDED(hr = target->GetService(__uuidof(ICLRMAService), (void**)&clrmaService))) { + TraceInformation("AssociateClient got managed CLRMA service\n"); + clrmaService->SetModuleEnumerationPolicy(g_clrmaGlobalFlags & ClrmaGlobalFlags::ModuleEnumeration_EntryPointModule ? 1 : + g_clrmaGlobalFlags & ClrmaGlobalFlags::ModuleEnumeration_EntryPointAndDllModule ? 2 : + g_clrmaGlobalFlags & ClrmaGlobalFlags::ModuleEnumeration_AllModules ? 3 : 0); if (SUCCEEDED(hr = clrmaService->AssociateClient(m_debugClient))) { m_clrmaService = clrmaService.Detach(); diff --git a/src/SOS/Strike/clrma/managedanalysis.h b/src/SOS/Strike/clrma/managedanalysis.h index 02a80a3304..f108452b29 100644 --- a/src/SOS/Strike/clrma/managedanalysis.h +++ b/src/SOS/Strike/clrma/managedanalysis.h @@ -27,6 +27,9 @@ enum ClrmaGlobalFlags LoggingEnabled = 0x01, // CLRMA logging enabled DacClrmaEnabled = 0x02, // Direct DAC CLRMA code enabled ManagedClrmaEnabled = 0x04, // Native AOT managed support enabled + ModuleEnumeration_EntryPointModule = 0x08, // Use entry point module enumeration scheme + ModuleEnumeration_EntryPointAndDllModule = 0x10, // Use entry point and DLL module enumeration scheme + ModuleEnumeration_AllModules = 0x20, // Use all modules enumeration scheme }; #define MAX_STACK_FRAMES 1000 // Max number of stack frames returned from thread stackwalk diff --git a/src/SOS/inc/clrmaservice.h b/src/SOS/inc/clrmaservice.h index 4887d035f0..8236ad548e 100644 --- a/src/SOS/inc/clrmaservice.h +++ b/src/SOS/inc/clrmaservice.h @@ -31,6 +31,9 @@ ICLRMAService : public IUnknown virtual HRESULT STDMETHODCALLTYPE GetObjectInspection( ICLRMAObjectInspection **ppObjectInspection) = 0; + + virtual HRESULT STDMETHODCALLTYPE SetModuleEnumerationPolicy( + ULONG moduleEnumerationPolicy) = 0; }; #ifdef __cplusplus