diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs index cee810569c..acc0c35369 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; @@ -12,6 +13,194 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { + public sealed class CrashInfoModuleService : ICrashInfoModuleService + { + private readonly IServiceProvider Services; + + public CrashInfoModuleService(IServiceProvider services) + { + Services = services ?? throw new ArgumentNullException(nameof(services)); + } + public ICrashInfoService Create(ModuleEnumerationScheme moduleEnumerationScheme) + { + return CreateCrashInfoServiceFromModule(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}"); + } + break; + } + return false; + } + + private static unsafe ICrashInfoService CreateCrashInfoServiceFromModule(IServiceProvider services, ModuleEnumerationScheme moduleEnumerationScheme) + { + if (moduleEnumerationScheme == ModuleEnumerationScheme.None) + { + return null; + } + + if (moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointModule || + moduleEnumerationScheme == ModuleEnumerationScheme.EntryPointAndEntryPointDllModule) + { + // if the above did not locate 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; + OSPlatform osPlatform = entryPointModule.Target.OperatingSystem; + if (fileName == null) + { + Trace.TraceError("CrashInfoService: Entry point module has no file name"); + return null; + } + if (osPlatform == OSPlatform.Windows) + { + if (fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + { + 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; + } + } + } + } + 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 { /// @@ -24,6 +213,13 @@ 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_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"; + private sealed class CrashInfoJson { [JsonPropertyName("version")] diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs index 75f9a087c3..cd2c8e2acd 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 virtual 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.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 8f92d215a5..fa4c6ce835 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs @@ -74,9 +74,13 @@ 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 + _serviceContainerFactory.AddServiceFactory((services) => new CrashInfoModuleService(services)); + OnFlushEvent.Register(() => FlushService()); + Finished(); } diff --git a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs index 143532cdc3..f395fbaf04 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 ICrashInfoModuleService + { + /// + /// 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.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/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs index 41ad047e6c..ff2273b71c 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs @@ -16,24 +16,31 @@ public class CrashInfoCommand : CommandBase [ServiceImport(Optional = true)] public ICrashInfoService CrashInfo { get; set; } + [ServiceImport(Optional = true)] + 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; + public override void Invoke() { - if (CrashInfo == null) + ICrashInfoService crashInfo = CrashInfo ?? CrashInfoFactory.Create(ModuleEnumerationScheme); + 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 +48,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 +60,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/Clrma/ClrmaServiceWrapper.cs b/src/SOS/SOS.Extensions/Clrma/ClrmaServiceWrapper.cs index dc47872586..c8d57a2adf 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); @@ -34,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"); @@ -104,7 +116,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(ModuleEnumerationScheme); // if the above fails, try to create a crash info service from the modules + return _crashInfoService; + } + } private IThreadService ThreadService => _serviceProvider.GetService(); @@ -132,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/SOS.Extensions/ModuleServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs index 4137ee1dfd..371de58dd3 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, assume 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 aac3731ff1..72e9108bdd 100644 --- a/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromDebuggerServices.cs @@ -100,9 +100,13 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos }); // Add optional crash info service (currently only for Native AOT). - _serviceContainerFactory.AddServiceFactory((services) => CreateCrashInfoService(services, debuggerServices)); + _serviceContainerFactory.AddServiceFactory((services) => CreateCrashInfoServiceFromException(services, debuggerServices)); OnFlushEvent.Register(() => FlushService()); + // Add the crash info service factory which lookup the DotNetRuntimeDebugHeader from modules + _serviceContainerFactory.AddServiceFactory((services) => new CrashInfoModuleService(services)); + OnFlushEvent.Register(() => FlushService()); + if (Host.HostType == HostType.DbgEng) { _serviceContainerFactory.AddServiceFactory((services) => new MemoryRegionServiceFromDebuggerServices(debuggerServices)); @@ -114,12 +118,12 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos targetWrapper?.ServiceWrapper.AddServiceWrapper(ClrmaServiceWrapper.IID_ICLRMAService, () => new ClrmaServiceWrapper(this, Services, targetWrapper.ServiceWrapper)); } - private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, DebuggerServices debuggerServices) + 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); + return SpecialDiagInfo.CreateCrashInfoServiceFromException(services); } HResult hr = debuggerServices.GetLastException(out uint processId, out int threadIndex, out EXCEPTION_RECORD64 exceptionRecord); if (hr.IsOK) @@ -143,7 +147,10 @@ private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider service } } } + return null; } + + } } 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