From bda0c3226cf3fbbb0b2c89b0e33fa5b815684ec5 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:23:04 -0400 Subject: [PATCH] Forward ICLRSymbolProvider calls to a host-supplied symbol resolver Adds an IClrSymbolProvider adapter (HostSymbolProvider) over the host's IModuleService/IModuleSymbols, registered as a per-target service. Wires it into ClrMD via DataTargetOptions.SymbolProvider and exposes it through SOS's legacy-DAC data target CCW (DataTargetWrapper.ICLRSymbolProvider) so both data access paths resolve symbols through one implementation. Also refactors the ClrFlavor -> RuntimeType mapping into a single GetRuntimeType helper and adds NativeAOT to the mapping. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../HostSymbolProvider.cs | 104 ++++++++++++++ .../Runtime.cs | 19 +-- .../RuntimeProvider.cs | 3 +- src/SOS/SOS.Hosting/DataTargetWrapper.cs | 135 ++++++++++++++++++ 4 files changed, 251 insertions(+), 10 deletions(-) create mode 100644 src/Microsoft.Diagnostics.DebugServices.Implementation/HostSymbolProvider.cs diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/HostSymbolProvider.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/HostSymbolProvider.cs new file mode 100644 index 0000000000..e5f337b57d --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/HostSymbolProvider.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.Runtime; + +namespace Microsoft.Diagnostics.DebugServices.Implementation +{ + /// + /// Adapter exposing the host's / + /// through ClrMD's + /// contract. + /// + [ServiceExport(Type = typeof(IClrSymbolProvider), Scope = ServiceScope.Target)] + public sealed class HostSymbolProvider : IClrSymbolProvider + { + private readonly IModuleService _moduleService; + private readonly ulong _signExtensionMask; + + public HostSymbolProvider(IModuleService moduleService, IMemoryService memoryService) + { + _moduleService = moduleService ?? throw new ArgumentNullException(nameof(moduleService)); + _signExtensionMask = memoryService?.SignExtensionMask() ?? ulong.MaxValue; + } + + public bool TryGetSymbolName(ulong address, out string symbolName, out ulong displacement) + { + symbolName = null; + displacement = 0; + + address &= _signExtensionMask; + + IModule module; + try + { + module = _moduleService.GetModuleFromAddress(address); + } + catch (DiagnosticsException) + { + return false; + } + if (module is null) + { + return false; + } + + IModuleSymbols symbols = module.Services.GetService(); + if (symbols is null) + { + return false; + } + + if (!symbols.TryGetSymbolName(address, out string bareName, out displacement) + || string.IsNullOrEmpty(bareName)) + { + return false; + } + + // Strip any module! qualifier the lower-level service might have + // prepended — the new contract returns bare names only. + int bang = bareName.IndexOf('!'); + symbolName = bang >= 0 && bang + 1 < bareName.Length ? bareName.Substring(bang + 1) : bareName; + return true; + } + + public bool TryGetSymbolAddress(ulong moduleBase, string name, out ulong address) + { + address = 0; + if (string.IsNullOrEmpty(name)) + { + return false; + } + + moduleBase &= _signExtensionMask; + + IModule scopedModule; + try + { + scopedModule = _moduleService.GetModuleFromBaseAddress(moduleBase); + } + catch (DiagnosticsException) + { + return false; + } + if (scopedModule is null) + { + return false; + } + + IModuleSymbols scopedSymbols = scopedModule.Services.GetService(); + if (scopedSymbols is null) + { + return false; + } + + if (scopedSymbols.TryGetSymbolAddress(name, out ulong scopedAddr) && scopedAddr != 0) + { + address = scopedAddr; + return true; + } + return false; + } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs index 4c362a4de8..28c444bb62 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs @@ -44,15 +44,7 @@ public Runtime(IServiceProvider services, int id, ClrInfo clrInfo) _settingsService = services.GetService() ?? throw new ArgumentException("ISettingsService required"); _symbolService = services.GetService() ?? throw new ArgumentException("ISymbolService required"); - RuntimeType = RuntimeType.Unknown; - if (clrInfo.Flavor == ClrFlavor.Core) - { - RuntimeType = RuntimeType.NetCore; - } - else if (clrInfo.Flavor == ClrFlavor.Desktop) - { - RuntimeType = RuntimeType.Desktop; - } + RuntimeType = GetRuntimeType(clrInfo.Flavor); RuntimeModule = services.GetService().GetModuleFromBaseAddress(clrInfo.ModuleInfo.ImageBase); ServiceContainerFactory containerFactory = services.GetService().CreateServiceContainerFactory(ServiceScope.Runtime, services); @@ -382,9 +374,18 @@ public override int GetHashCode() "Desktop .NET Framework", ".NET Core", ".NET Core (single-file)", + "Native AOT", "Other" }; + private static RuntimeType GetRuntimeType(ClrFlavor flavor) => flavor switch + { + ClrFlavor.Core => RuntimeType.NetCore, + ClrFlavor.Desktop => RuntimeType.Desktop, + ClrFlavor.NativeAOT => RuntimeType.NativeAOT, + _ => RuntimeType.Unknown, + }; + public override string ToString() { StringBuilder sb = new(); diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs index 84572786c1..3d077346df 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs @@ -61,7 +61,8 @@ public IEnumerable EnumerateRuntimes(int startingRuntimeId, RuntimeEnu return false; } return verifyDac; - } + }, + SymbolProvider = _services.GetService(), }); for (int i = 0; i < dataTarget.ClrVersions.Length; i++) { diff --git a/src/SOS/SOS.Hosting/DataTargetWrapper.cs b/src/SOS/SOS.Hosting/DataTargetWrapper.cs index 0a01ceea22..0a6690a9b2 100644 --- a/src/SOS/SOS.Hosting/DataTargetWrapper.cs +++ b/src/SOS/SOS.Hosting/DataTargetWrapper.cs @@ -19,6 +19,7 @@ internal sealed unsafe class DataTargetWrapper : COMCallableIUnknown private static readonly Guid IID_ICLRMetadataLocator = new("aa8fa804-bc05-4642-b2c5-c353ed22fc63"); private static readonly Guid IID_ICLRRuntimeLocator = new("b760bf44-9377-4597-8be7-58083bdc5146"); private static readonly Guid IID_ICLRContractLocator = new("17d5b8c6-34a9-407f-af4f-a930201d4e02"); + private static readonly Guid IID_ICLRSymbolProvider = new("c4f8b7e2-9d3a-4f6c-b1e5-8a2d7c3f9b1e"); // For ClrMD's magic hand shake private const ulong MagicCallbackConstant = 0x43; @@ -31,6 +32,7 @@ internal sealed unsafe class DataTargetWrapper : COMCallableIUnknown private readonly IModuleService _moduleService; private readonly IThreadUnwindService _threadUnwindService; private readonly IRemoteMemoryService _remoteMemoryService; + private readonly IClrSymbolProvider _symbolProvider; private readonly ulong _ignoreAddressBitsMask; public IntPtr IDataTarget { get; } @@ -47,6 +49,7 @@ public DataTargetWrapper(IServiceProvider services, IRuntime runtime) _threadUnwindService = services.GetService(); _moduleService = services.GetService(); _remoteMemoryService = services.GetService(); + _symbolProvider = services.GetService(); _ignoreAddressBitsMask = _memoryService.SignExtensionMask(); VTableBuilder builder = AddInterface(IID_ICLRDataTarget, false); @@ -73,6 +76,11 @@ public DataTargetWrapper(IServiceProvider services, IRuntime runtime) builder.AddMethod(new GetContractDescriptorDelegate(GetContractDescriptor)); builder.Complete(); + builder = AddInterface(IID_ICLRSymbolProvider, false); + builder.AddMethod(new TryGetSymbolNameDelegate(TryGetSymbolName)); + builder.AddMethod(new TryGetSymbolAddressDelegate(TryGetSymbolAddress)); + builder.Complete(); + AddRef(); } @@ -378,6 +386,113 @@ private int GetContractDescriptor( #endregion + #region ICLRSymbolProvider + + private int TryGetSymbolName( + IntPtr self, + ulong address, + uint cchName, + char* pName, + uint* pcchNameActual, + ulong* pDisplacement) + { + if (cchName > int.MaxValue) + { + return HResult.E_INVALIDARG; + } + + address &= _ignoreAddressBitsMask; + + try + { + if (_symbolProvider is null) + { + return HResult.E_NOTIMPL; + } + + if (!_symbolProvider.TryGetSymbolName(address, out string symbolName, out ulong displacement) + || string.IsNullOrEmpty(symbolName)) + { + return HResult.E_FAIL; + } + + if (pcchNameActual != null) + { + *pcchNameActual = (uint)symbolName.Length + 1; + } + if (pDisplacement != null) + { + *pDisplacement = displacement; + } + + if (cchName == 0 || pName == null) + { + return HResult.S_OK; + } + + int copy = Math.Min(symbolName.Length, (int)cchName - 1); + for (int i = 0; i < copy; i++) + { + pName[i] = symbolName[i]; + } + pName[copy] = '\0'; + return copy < symbolName.Length ? HResult.S_FALSE : HResult.S_OK; + } + catch + { + return HResult.E_FAIL; + } + } + + private int TryGetSymbolAddress( + IntPtr self, + ulong moduleBase, + string name, + ulong* pAddress) + { + if (pAddress == null) + { + return HResult.E_INVALIDARG; + } + *pAddress = 0; + + moduleBase &= _ignoreAddressBitsMask; + + try + { + if (_symbolProvider is null) + { + return HResult.E_NOTIMPL; + } + + if (string.IsNullOrEmpty(name)) + { + return HResult.E_INVALIDARG; + } + + // Bare symbol names only — '!' is reserved as the SOS module + // separator and is not produced by any of the mangling toolchains + // we target. + if (name.IndexOf('!') >= 0) + { + return HResult.E_INVALIDARG; + } + + if (_symbolProvider.TryGetSymbolAddress(moduleBase, name, out ulong address) && address != 0) + { + *pAddress = address; + return HResult.S_OK; + } + return HResult.E_FAIL; + } + catch + { + return HResult.E_FAIL; + } + } + + #endregion + #region ICLRDataTarget delegates [UnmanagedFunctionPointer(CallingConvention.Winapi)] @@ -522,5 +637,25 @@ private delegate int GetContractDescriptorDelegate( [Out] out ulong address); #endregion + + #region ICLRSymbolProvider delegates + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate int TryGetSymbolNameDelegate( + [In] IntPtr self, + [In] ulong address, + [In] uint cchName, + [Out] char* pName, + [Out] uint* pcchNameActual, + [Out] ulong* pDisplacement); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate int TryGetSymbolAddressDelegate( + [In] IntPtr self, + [In] ulong moduleBase, + [In][MarshalAs(UnmanagedType.LPWStr)] string name, + [Out] ulong* pAddress); + + #endregion } }