From 12ce8e9124f989cc8d1706d1e0781b51903fbe4d Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 22 Aug 2025 11:13:02 -0400 Subject: [PATCH 1/2] implement getassemblydata --- docs/design/datacontracts/Loader.md | 10 ++- src/coreclr/vm/assembly.hpp | 1 + .../vm/datadescriptor/datadescriptor.inc | 1 + .../Contracts/ILoader.cs | 1 + .../Contracts/Loader_1.cs | 10 ++- .../Data/Assembly.cs | 2 + .../Legacy/ISOSDacInterface.cs | 17 +++++- .../Legacy/SOSDacImpl.cs | 61 ++++++++++++++++++- 8 files changed, 97 insertions(+), 6 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 46fa6b716fb651..39d2fec84a1cb6 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -108,7 +108,8 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); | `ModuleLookupMap` | `Count` | Number of TargetPointer sized entries in this section of the map | | `ModuleLookupMap` | `Next` | Pointer to next ModuleLookupMap segment for this map | | `Assembly` | `Module` | Pointer to the Assemblies module | -| `Assembly` | `IsCollectible` | Flag indicating if this is module may be collected | +| `Assembly` | `IsCollectible` | Flag indicating if this module may be collected | +| `Assembly` | `IsDynamic` | Flag indicating if this module is dynamic | | `Assembly` | `Error` | Pointer to exception. No error if nullptr | | `Assembly` | `NotifyFlags` | Flags relating to the debugger/profiler notification state of the assembly | | `Assembly` | `Level` | File load level of the assembly | @@ -481,6 +482,13 @@ bool IsCollectible(ModuleHandle handle) return isCollectible != 0; } +bool IsDynamic(ModuleHandle handle) +{ + TargetPointer assembly = target.ReadPointer(handle.Address + /*Module::Assembly*/); + byte isDynamic = target.Read(assembly + /* Assembly::IsDynamic*/); + return isDynamic != 0; +} + bool IsAssemblyLoaded(ModuleHandle handle) { TargetPointer assembly = target.ReadPointer(handle.Address + /*Module::Assembly*/); diff --git a/src/coreclr/vm/assembly.hpp b/src/coreclr/vm/assembly.hpp index 58e63b72a4d91e..e73b5ae8bda5cf 100644 --- a/src/coreclr/vm/assembly.hpp +++ b/src/coreclr/vm/assembly.hpp @@ -546,6 +546,7 @@ struct cdac_data #ifdef FEATURE_COLLECTIBLE_TYPES static constexpr size_t IsCollectible = offsetof(Assembly, m_isCollectible); #endif + static constexpr size_t IsDynamic = offsetof(Assembly, m_isDynamic); static constexpr size_t Module = offsetof(Assembly, m_pModule); static constexpr size_t Error = offsetof(Assembly, m_pError); static constexpr size_t NotifyFlags = offsetof(Assembly, m_notifyFlags); diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 5a5755306a4ebd..ec8d93dece461c 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -187,6 +187,7 @@ CDAC_TYPE_INDETERMINATE(Assembly) #ifdef FEATURE_COLLECTIBLE_TYPES CDAC_TYPE_FIELD(Assembly, /*uint8*/, IsCollectible, cdac_data::IsCollectible) #endif +CDAC_TYPE_FIELD(Assembly, /*bool*/, IsDynamic, cdac_data::IsDynamic) CDAC_TYPE_FIELD(Assembly, /*pointer*/, Module, cdac_data::Module) CDAC_TYPE_FIELD(Assembly, /*pointer*/, Error, cdac_data::Error) CDAC_TYPE_FIELD(Assembly, /*uint32*/, NotifyFlags, cdac_data::NotifyFlags) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 8822dfee7ba384..1f79707f2a74b0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -98,6 +98,7 @@ public interface ILoader : IContract ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags) => throw new NotImplementedException(); bool IsCollectible(ModuleHandle handle) => throw new NotImplementedException(); + bool IsDynamic(ModuleHandle handle) => throw new NotImplementedException(); bool IsAssemblyLoaded(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetGlobalLoaderAllocator() => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 2d2562625e4688..947ae07eb82e0d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -386,11 +386,17 @@ TargetPointer ILoader.GetModuleLookupMapElement(TargetPointer table, uint token, bool ILoader.IsCollectible(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - TargetPointer assembly = module.Assembly; - Data.Assembly la = _target.ProcessedData.GetOrAdd(assembly); + Data.Assembly la = _target.ProcessedData.GetOrAdd(module.Assembly); return la.IsCollectible != 0; } + bool ILoader.IsDynamic(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + Data.Assembly assembly = _target.ProcessedData.GetOrAdd(module.Assembly); + return assembly.IsDynamic; + } + bool ILoader.IsAssemblyLoaded(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Assembly.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Assembly.cs index 2815223c7a7306..e8d7b1d1260c1d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Assembly.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Assembly.cs @@ -14,6 +14,7 @@ public Assembly(Target target, TargetPointer address) Module = target.ReadPointer(address + (ulong)type.Fields[nameof(Module)].Offset); IsCollectible = target.Read(address + (ulong)type.Fields[nameof(IsCollectible)].Offset); + IsDynamic = target.Read(address + (ulong)type.Fields[nameof(IsDynamic)].Offset) != 0; Error = target.ReadPointer(address + (ulong)type.Fields[nameof(Error)].Offset); NotifyFlags = target.Read(address + (ulong)type.Fields[nameof(NotifyFlags)].Offset); Level = target.Read(address + (ulong)type.Fields[nameof(Level)].Offset); @@ -21,6 +22,7 @@ public Assembly(Target target, TargetPointer address) public TargetPointer Module { get; init; } public byte IsCollectible { get; init; } + public bool IsDynamic { get; init; } public TargetPointer Error { get; init; } public uint NotifyFlags { get; init; } public uint Level { get; init; } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs index f2663d4363cce4..fe17b1d510aac5 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs @@ -74,6 +74,21 @@ internal struct DacpAppDomainStoreData public ClrDataAddress systemDomain; public int DomainCount; }; + +internal struct DacpAssemblyData +{ + public ClrDataAddress AssemblyPtr; + public ClrDataAddress ClassLoader; + public ClrDataAddress ParentDomain; + public ClrDataAddress DomainPtr; + public ClrDataAddress AssemblySecDesc; + public int isDynamic; + public uint ModuleCount; + public uint LoadContext; + public int isDomainNeutral; // Always false, preserved for backward compatibility + public uint dwLocationFlags; +} + internal struct DacpThreadData { public int corThreadId; @@ -270,7 +285,7 @@ internal unsafe partial interface ISOSDacInterface [PreserveSig] int GetAssemblyList(ClrDataAddress appDomain, int count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ClrDataAddress[]? values, int* pNeeded); [PreserveSig] - int GetAssemblyData(ClrDataAddress baseDomainPtr, ClrDataAddress assembly, /*struct DacpAssemblyData*/ void* data); + int GetAssemblyData(ClrDataAddress domain, ClrDataAddress assembly, DacpAssemblyData* data); [PreserveSig] int GetAssemblyName(ClrDataAddress assembly, uint count, char* name, uint* pNeeded); diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index f1637e56838983..870a11ae9dfb61 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -338,8 +338,65 @@ int ISOSDacInterface.GetApplicationBase(ClrDataAddress appDomain, int count, cha return hr; } - int ISOSDacInterface.GetAssemblyData(ClrDataAddress baseDomainPtr, ClrDataAddress assembly, void* data) - => _legacyImpl is not null ? _legacyImpl.GetAssemblyData(baseDomainPtr, assembly, data) : HResults.E_NOTIMPL; + + int ISOSDacInterface.GetAssemblyData(ClrDataAddress domain, ClrDataAddress assembly, DacpAssemblyData* data) + { + int hr = HResults.S_OK; + + try + { + if (assembly == 0 && domain == 0) + throw new ArgumentException(); + + // Zero out data structure + *data = default; + + data->AssemblyPtr = assembly; + data->DomainPtr = domain; + + TargetPointer ppAppDomain = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + TargetPointer pAppDomain = _target.ReadPointer(ppAppDomain); + data->ParentDomain = pAppDomain.ToClrDataAddress(_target); + + ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(assembly.ToTargetPointer(_target)); + data->isDynamic = loader.IsDynamic(moduleHandle) ? 1 : 0; + + // The DAC increments ModuleCount to 1 if assembly->GetModule() is valid, + // the cDAC assumes that all assemblies will have a module and the above logic relies on that. + // Therefore we always set ModuleCount to 1. + data->ModuleCount = 1; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + DacpAssemblyData dataLocal = default; + int hrLocal = _legacyImpl.GetAssemblyData(domain, assembly, &dataLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(data->AssemblyPtr == dataLocal.AssemblyPtr, $"cDAC: {data->AssemblyPtr:x}, DAC: {dataLocal.AssemblyPtr:x}"); + Debug.Assert(data->ClassLoader == dataLocal.ClassLoader, $"cDAC: {data->ClassLoader:x}, DAC: {dataLocal.ClassLoader:x}"); + Debug.Assert(data->ParentDomain == dataLocal.ParentDomain, $"cDAC: {data->ParentDomain:x}, DAC: {dataLocal.ParentDomain:x}"); + Debug.Assert(data->DomainPtr == dataLocal.DomainPtr, $"cDAC: {data->DomainPtr:x}, DAC: {dataLocal.DomainPtr:x}"); + Debug.Assert(data->AssemblySecDesc == dataLocal.AssemblySecDesc, $"cDAC: {data->AssemblySecDesc:x}, DAC: {dataLocal.AssemblySecDesc:x}"); + Debug.Assert(data->isDynamic == dataLocal.isDynamic, $"cDAC: {data->isDynamic}, DAC: {dataLocal.isDynamic}"); + Debug.Assert(data->ModuleCount == dataLocal.ModuleCount, $"cDAC: {data->ModuleCount}, DAC: {dataLocal.ModuleCount}"); + Debug.Assert(data->LoadContext == dataLocal.LoadContext, $"cDAC: {data->LoadContext:x}, DAC: {dataLocal.LoadContext:x}"); + Debug.Assert(data->isDomainNeutral == dataLocal.isDomainNeutral, $"cDAC: {data->isDomainNeutral}, DAC: {dataLocal.isDomainNeutral}"); + Debug.Assert(data->dwLocationFlags == dataLocal.dwLocationFlags, $"cDAC: {data->dwLocationFlags:x}, DAC: {dataLocal.dwLocationFlags:x}"); + } + } +#endif + + return hr; + } + int ISOSDacInterface.GetAssemblyList(ClrDataAddress addr, int count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[]? values, int* pNeeded) { if (addr == 0) From d29f8752fec7831e12be87eba352a847ca886105 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 22 Aug 2025 13:45:56 -0400 Subject: [PATCH 2/2] update mock descriptors --- src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index 2b4a634b46fe4c..73d301858691c3 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs @@ -163,6 +163,7 @@ internal record TypeFields [ new(nameof(Data.Assembly.Module), DataType.pointer), new(nameof(Data.Assembly.IsCollectible), DataType.uint8), + new(nameof(Data.Assembly.IsDynamic), DataType.uint8), new(nameof(Data.Assembly.Error), DataType.pointer), new(nameof(Data.Assembly.NotifyFlags), DataType.uint32), new(nameof(Data.Assembly.Level), DataType.uint32),