diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/HostSymbolProvider.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/HostSymbolProvider.cs
new file mode 100644
index 0000000000..5ab9ffc3e7
--- /dev/null
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/HostSymbolProvider.cs
@@ -0,0 +1,125 @@
+// 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;
+
+ // moduleBase != 0 restricts the search to a single module.
+ if (moduleBase != 0)
+ {
+ 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;
+ }
+
+ foreach (IModule module in _moduleService.EnumerateModules())
+ {
+ IModuleSymbols symbols = module.Services.GetService();
+ if (symbols is null)
+ {
+ continue;
+ }
+
+ if (symbols.TryGetSymbolAddress(name, out ulong addr) && addr != 0)
+ {
+ address = addr;
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
index 05db19b715..9cdfc03022 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
@@ -39,15 +39,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);
@@ -129,6 +121,15 @@ public string GetDacFilePath(out bool verifySignature)
_verifySignature = _settingsService.DacSignatureVerificationEnabled;
}
}
+ if (_dacFilePath is null)
+ {
+ _cdacFilePath ??= GetLibraryPath(DebugLibraryKind.CDac);
+ if (_cdacFilePath is not null)
+ {
+ verifySignature = false;
+ return _cdacFilePath;
+ }
+ }
verifySignature = _verifySignature;
return _dacFilePath;
}
@@ -318,9 +319,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 00eb74c6cd..2994f71a18 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs
@@ -36,7 +36,8 @@ public IEnumerable EnumerateRuntimes(int startingRuntimeId, RuntimeEnu
DataTarget dataTarget = new(_services.GetService(), new DataTargetOptions()
{
ForceCompleteRuntimeEnumeration = (flags & RuntimeEnumerationFlags.All) != 0,
- VerifyDacOnWindows = settingsService?.DacSignatureVerificationEnabled ?? true
+ VerifyDacOnWindows = settingsService?.DacSignatureVerificationEnabled ?? true,
+ SymbolProvider = _services.GetService(),
});
for (int i = 0; i < dataTarget.ClrVersions.Length; i++)
{
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs
index ae69ea69ef..d4375b2f02 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs
@@ -121,6 +121,12 @@ public void RegisterExportedServices(Type serviceType)
{
throw new InvalidOperationException();
}
+
+ if (typeof(IHostExtension).IsAssignableFrom(serviceType))
+ {
+ InvokeHostExtension(serviceType);
+ }
+
for (Type currentType = serviceType; currentType is not null; currentType = currentType.BaseType)
{
if (currentType == typeof(object) || currentType == typeof(ValueType))
@@ -185,6 +191,27 @@ or FileLoadException
}
}
+ private static void InvokeHostExtension(Type type)
+ {
+ if (type.IsAbstract)
+ {
+ return;
+ }
+ if (type.GetConstructor(Type.EmptyTypes) is null)
+ {
+ throw new InvalidOperationException($"IHostExtension implementation '{type.FullName}' must have a public parameterless constructor.");
+ }
+ try
+ {
+ IHostExtension extension = (IHostExtension)Activator.CreateInstance(type);
+ extension.Initialize();
+ }
+ catch (Exception ex) when (ex is not DiagnosticsException)
+ {
+ throw new DiagnosticsException($"IHostExtension '{type.FullName}' initialization failed: {ex.Message}", ex);
+ }
+ }
+
///
/// Add service factory for the specific scope.
///
diff --git a/src/Microsoft.Diagnostics.DebugServices/IHostExtension.cs b/src/Microsoft.Diagnostics.DebugServices/IHostExtension.cs
new file mode 100644
index 0000000000..c6dedee9f6
--- /dev/null
+++ b/src/Microsoft.Diagnostics.DebugServices/IHostExtension.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ ///
+ /// Implemented by extension assemblies to perform process-wide initialization
+ /// (for example registering an IClrInfoProvider) when the extension
+ /// is loaded.
+ ///
+ /// Implementations must have a public parameterless constructor. Every
+ /// concrete implementation discovered in an
+ /// extension assembly is instantiated and its
+ /// method invoked once during assembly
+ /// registration, before any other service or provider exports from the
+ /// assembly are consumed. An assembly may contain multiple
+ /// implementations; invocation order across implementations is
+ /// unspecified.
+ ///
+ public interface IHostExtension
+ {
+ ///
+ /// Performs one-time initialization for the extension.
+ ///
+ void Initialize();
+ }
+}
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
}
}