From d450d9c9ee4dd5a98812981dac06d2f92bdb8213 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 24 Oct 2024 13:04:05 -0700 Subject: [PATCH] [cdac] Implement `ISOSDacInterface::GetModule` and `IXCLRDataModule::GetFileName` (#109133) - Add `ClrDataModule` to cDAC as its implementation of `IXCLRDataModule` and `IXCLRDataModule2` - Delegates to legacy implementation for everything except for `GetFileName` - Implements `ICustomQueryInterface` to handle QI-ing the legacy DAC for `IMetaDataImport` - Implement `ISOSDacInterface::GetModule` - Gets a module from the legacy DAC and passes it to the cDAC `ClrDataModule` - Cache the module file name on `Module` - Update comments around how it is expected to be set - Add `GetFileName` to `Loader` contract - Implement `IXClrDataModule::GetFileName` --- docs/design/datacontracts/Loader.md | 9 + .../debug/runtimeinfo/datadescriptor.h | 3 +- src/coreclr/vm/ceeload.cpp | 1 + src/coreclr/vm/ceeload.h | 6 +- src/coreclr/vm/peassembly.h | 7 +- src/coreclr/vm/peassembly.inl | 2 - src/coreclr/vm/peimage.cpp | 5 +- src/coreclr/vm/peimage.h | 12 +- src/coreclr/vm/peimage.inl | 4 - .../Contracts/ILoader.cs | 1 + .../Contracts/Loader_1.cs | 12 +- .../Data/Module.cs | 2 + .../cdacreader/src/Legacy/ClrDataModule.cs | 188 ++++++++++++++++++ .../cdacreader/src/Legacy/IXCLRData.cs | 106 ++++++++++ .../src/Legacy/OutputBufferHelpers.cs | 25 +++ .../cdacreader/src/Legacy/SOSDacImpl.cs | 60 +++--- .../managed/cdacreader/tests/LoaderTests.cs | 85 ++++++++ .../cdacreader/tests/MockDescriptors.cs | 90 ++++++++- .../cdacreader/tests/MockMemorySpace.cs | 6 +- .../cdacreader/tests/TargetTestHelpers.cs | 20 +- .../managed/cdacreader/tests/TargetTests.cs | 45 ++++- 21 files changed, 634 insertions(+), 55 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Legacy/ClrDataModule.cs create mode 100644 src/native/managed/cdacreader/src/Legacy/OutputBufferHelpers.cs create mode 100644 src/native/managed/cdacreader/tests/LoaderTests.cs diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 8b96ed6c30caf..fce06060e280e 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -34,6 +34,7 @@ ModuleHandle GetModuleHandle(TargetPointer module); TargetPointer GetAssembly(ModuleHandle handle); ModuleFlags GetFlags(ModuleHandle handle); string GetPath(ModuleHandle handle); +string GetFileName(ModuleHandle handle); TargetPointer GetLoaderAllocator(ModuleHandle handle); TargetPointer GetThunkHeap(ModuleHandle handle); TargetPointer GetILBase(ModuleHandle handle); @@ -52,6 +53,7 @@ Data descriptors used: | `Module` | `LoaderAllocator` | LoaderAllocator of the Module | | `Module` | `ThunkHeap` | Pointer to the thunk heap | | `Module` | `Path` | Path of the Module (UTF-16, null-terminated) | +| `Module` | `FileName` | File name of the Module (UTF-16, null-terminated) | | `Module` | `FieldDefToDescMap` | Mapping table | | `Module` | `ManifestModuleReferencesMap` | Mapping table | | `Module` | `MemberRefToDescMap` | Mapping table | @@ -86,6 +88,13 @@ string GetPath(ModuleHandle handle) return new string(path); } +string GetFileName(ModuleHandle handle) +{ + TargetPointer fileNameStart = target.ReadPointer(handle.Address + /* Module::FileName offset */); + char[] fileName = // Read from target starting at fileNameStart until null terminator + return new string(fileName); +} + TargetPointer GetLoaderAllocator(ModuleHandle handle) { return target.ReadPointer(handle.Address + /* Module::LoaderAllocator offset */); diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index e00ee41c4759f..3ec4e896ca3dc 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -221,11 +221,12 @@ CDAC_TYPE_BEGIN(Module) CDAC_TYPE_INDETERMINATE(Module) CDAC_TYPE_FIELD(Module, /*pointer*/, Assembly, cdac_data::Assembly) CDAC_TYPE_FIELD(Module, /*pointer*/, Base, cdac_data::Base) -CDAC_TYPE_FIELD(Module, /*pointer*/, Flags, cdac_data::Flags) +CDAC_TYPE_FIELD(Module, /*uint32*/, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(Module, /*pointer*/, LoaderAllocator, cdac_data::LoaderAllocator) CDAC_TYPE_FIELD(Module, /*pointer*/, ThunkHeap, cdac_data::ThunkHeap) CDAC_TYPE_FIELD(Module, /*pointer*/, DynamicMetadata, cdac_data::DynamicMetadata) CDAC_TYPE_FIELD(Module, /*pointer*/, Path, cdac_data::Path) +CDAC_TYPE_FIELD(Module, /*pointer*/, FileName, cdac_data::FileName) CDAC_TYPE_FIELD(Module, /*pointer*/, FieldDefToDescMap, cdac_data::FieldDefToDescMap) CDAC_TYPE_FIELD(Module, /*pointer*/, ManifestModuleReferencesMap, cdac_data::ManifestModuleReferencesMap) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index f4d075b126e13..219d3eaa5f024 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -416,6 +416,7 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) m_loaderAllocator = GetAssembly()->GetLoaderAllocator(); m_pSimpleName = m_pPEAssembly->GetSimpleName(); m_path = m_pPEAssembly->GetPath().GetUnicode(); + m_fileName = m_pPEAssembly->GetModuleFileNameHint(); _ASSERTE(m_path != NULL); m_baseAddress = m_pPEAssembly->HasLoadedPEImage() ? m_pPEAssembly->GetLoadedLayout()->GetBase() : NULL; if (m_pPEAssembly->IsReflectionEmit()) diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 2d0d266494a17..e712365bbbdf7 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -608,8 +608,9 @@ class Module : public ModuleBase VPTR_VTABLE_CLASS(Module, ModuleBase) private: - PTR_CUTF8 m_pSimpleName; // Cached simple name for better performance and easier diagnostics - const WCHAR* m_path; // Cached path for easier diagnostics + PTR_CUTF8 m_pSimpleName; // Cached simple name for better performance and easier diagnostics + const WCHAR* m_path; // Cached path for easier diagnostics + const WCHAR* m_fileName; // Cached file name for easier diagnostics PTR_PEAssembly m_pPEAssembly; PTR_VOID m_baseAddress; // Cached base address for easier diagnostics @@ -1647,6 +1648,7 @@ struct cdac_data static constexpr size_t ThunkHeap = offsetof(Module, m_pThunkHeap); static constexpr size_t DynamicMetadata = offsetof(Module, m_pDynamicMetadata); static constexpr size_t Path = offsetof(Module, m_path); + static constexpr size_t FileName = offsetof(Module, m_fileName); // Lookup map pointers static constexpr size_t FieldDefToDescMap = offsetof(Module, m_FieldDefToDescMap); diff --git a/src/coreclr/vm/peassembly.h b/src/coreclr/vm/peassembly.h index d1f7500790d9b..6eaf5e96051e9 100644 --- a/src/coreclr/vm/peassembly.h +++ b/src/coreclr/vm/peassembly.h @@ -116,10 +116,11 @@ class PEAssembly final const SString& GetPath(); const SString& GetIdentityPath(); -#ifdef DACCESS_COMPILE - // This is the metadata module name. Used as a hint as file name. + // This is the module file name. Used as a hint as file name. + // For assemblies loaded from a path or single-file bundle, this is the file name portion of the path + // For assemblies loaded from memory, this is the module file name from metadata + // For reflection emitted assemblies, this is an empty string const SString &GetModuleFileNameHint(); -#endif // DACCESS_COMPILE LPCWSTR GetPathForErrorMessages(); diff --git a/src/coreclr/vm/peassembly.inl b/src/coreclr/vm/peassembly.inl index d71352d37d1dd..124c3bd6787b2 100644 --- a/src/coreclr/vm/peassembly.inl +++ b/src/coreclr/vm/peassembly.inl @@ -182,7 +182,6 @@ inline const SString& PEAssembly::GetIdentityPath() return m_PEImage->GetPath(); } -#ifdef DACCESS_COMPILE inline const SString &PEAssembly::GetModuleFileNameHint() { CONTRACTL @@ -201,7 +200,6 @@ inline const SString &PEAssembly::GetModuleFileNameHint() else return m_PEImage->GetModuleFileNameHintForDAC(); } -#endif // DACCESS_COMPILE #ifdef LOGGING inline LPCUTF8 PEAssembly::GetDebugName() diff --git a/src/coreclr/vm/peimage.cpp b/src/coreclr/vm/peimage.cpp index 5de04622a73e3..7592ef2a3bf3b 100644 --- a/src/coreclr/vm/peimage.cpp +++ b/src/coreclr/vm/peimage.cpp @@ -536,10 +536,11 @@ void PEImage::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) // these necessary fields enumerated no matter what. m_path.EnumMemoryRegions(flags); - // We always want this field in mini/triage/heap dumps. + // SString skips enumeration for triage dumps, but we always want this field, so we specify + // CLRDATA_ENUM_MEM_DEFAULT as the flags. This value is used in cases where we either can't + // use the full path (triage dumps) or don't have a path (in-memory assembly) m_sModuleFileNameHintUsedByDac.EnumMemoryRegions(CLRDATA_ENUM_MEM_DEFAULT); - EX_TRY { if (HasLoadedLayout() && HasNTHeaders() && HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_DEBUG)) diff --git a/src/coreclr/vm/peimage.h b/src/coreclr/vm/peimage.h index 0d4a0fb8bb549..ad885ebbabc9c 100644 --- a/src/coreclr/vm/peimage.h +++ b/src/coreclr/vm/peimage.h @@ -182,8 +182,8 @@ class PEImage final void SetModuleFileNameHintForDAC(); #ifdef DACCESS_COMPILE void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); - const SString &GetModuleFileNameHintForDAC(); #endif + const SString &GetModuleFileNameHintForDAC(); private: #ifndef DACCESS_COMPILE @@ -302,12 +302,10 @@ class PEImage final DWORD m_dwPEKind; DWORD m_dwMachine; - // This variable will have the data of module name. - // It is only used by DAC to remap fusion loaded modules back to - // disk IL. This really is a workaround. The real fix is for fusion loader - // hook (public API on hosting) to take an additional file name hint. - // We are piggy backing on the fact that module name is the same as file name!!! - SString m_sModuleFileNameHintUsedByDac; // This is only used by DAC + // This only used by DAC + // For assemblies loaded from a path or single-file bundle, this is the file name portion of the path + // For assemblies loaded from memory, this is the module file name from metadata + SString m_sModuleFileNameHintUsedByDac; enum { diff --git a/src/coreclr/vm/peimage.inl b/src/coreclr/vm/peimage.inl index f04078fe6455a..c3a07d65392b6 100644 --- a/src/coreclr/vm/peimage.inl +++ b/src/coreclr/vm/peimage.inl @@ -88,16 +88,12 @@ inline void PEImage::SetModuleFileNameHintForDAC() } } -#ifdef DACCESS_COMPILE inline const SString &PEImage::GetModuleFileNameHintForDAC() { LIMITED_METHOD_CONTRACT; return m_sModuleFileNameHintUsedByDac; } -#endif - - inline BOOL PEImage::IsFile() { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 009981ba3938c..3cdf3b20627f7 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -41,6 +41,7 @@ internal interface ILoader : IContract public virtual TargetPointer GetAssembly(ModuleHandle handle) => throw new NotImplementedException(); public virtual ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); public virtual string GetPath(ModuleHandle handle) => throw new NotImplementedException(); + public virtual string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); public virtual TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); public virtual TargetPointer GetThunkHeap(ModuleHandle handle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 6eae025454f2b..b0867feb05c6a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -37,7 +37,17 @@ ModuleFlags ILoader.GetFlags(ModuleHandle handle) string ILoader.GetPath(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - return _target.ReadUtf16String(module.Path); + return module.Path != TargetPointer.Null + ? _target.ReadUtf16String(module.Path) + : string.Empty; + } + + string ILoader.GetFileName(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + return module.FileName != TargetPointer.Null + ? _target.ReadUtf16String(module.FileName) + : string.Empty; } TargetPointer ILoader.GetLoaderAllocator(ModuleHandle handle) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index 153184c0d90e9..ec19a12f10380 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -24,6 +24,7 @@ public Module(Target target, TargetPointer address) ThunkHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(ThunkHeap)].Offset); DynamicMetadata = target.ReadPointer(address + (ulong)type.Fields[nameof(DynamicMetadata)].Offset); Path = target.ReadPointer(address + (ulong)type.Fields[nameof(Path)].Offset); + FileName = target.ReadPointer(address + (ulong)type.Fields[nameof(FileName)].Offset); FieldDefToDescMap = address + (ulong)type.Fields[nameof(FieldDefToDescMap)].Offset; ManifestModuleReferencesMap = address + (ulong)type.Fields[nameof(ManifestModuleReferencesMap)].Offset; @@ -41,6 +42,7 @@ public Module(Target target, TargetPointer address) public TargetPointer ThunkHeap { get; init; } public TargetPointer DynamicMetadata { get; init; } public TargetPointer Path { get; init; } + public TargetPointer FileName { get; init; } public TargetPointer FieldDefToDescMap { get; init; } public TargetPointer ManifestModuleReferencesMap { get; init; } diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataModule.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataModule.cs new file mode 100644 index 0000000000000..05178f474ce42 --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataModule.cs @@ -0,0 +1,188 @@ +// 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 System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +[GeneratedComClass] +internal sealed unsafe partial class ClrDataModule : ICustomQueryInterface, IXCLRDataModule, IXCLRDataModule2 +{ + private readonly TargetPointer _address; + private readonly Target _target; + private readonly nint _legacyModulePointer; + private readonly IXCLRDataModule? _legacyModule; + private readonly IXCLRDataModule2? _legacyModule2; + + public ClrDataModule(TargetPointer address, Target target, nint legacyModulePointer, object? legacyImpl) + { + _address = address; + _target = target; + _legacyModulePointer = legacyModulePointer; + _legacyModule = legacyImpl as IXCLRDataModule; + _legacyModule2 = legacyImpl as IXCLRDataModule2; + } + + private static readonly Guid IID_IMetaDataImport = Guid.Parse("7DAC8207-D3AE-4c75-9B67-92801A497D44"); + + CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) + { + ppv = default; + if (_legacyModulePointer == 0) + return CustomQueryInterfaceResult.NotHandled; + + // Legacy DAC implementation of IXCLRDataModule handles QIs for IMetaDataImport by creating and + // passing out an implementation of IMetaDataImport. Note that it does not do COM aggregation. + // It simply returns a completely separate object. See ClrDataModule::QueryInterface in task.cpp + if (iid == IID_IMetaDataImport && Marshal.QueryInterface(_legacyModulePointer, iid, out ppv) >= 0) + return CustomQueryInterfaceResult.Handled; + + return CustomQueryInterfaceResult.NotHandled; + } + + int IXCLRDataModule.StartEnumAssemblies(ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumAssemblies(handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumAssembly(ulong* handle, /*IXCLRDataAssembly*/ void** assembly) + => _legacyModule is not null ? _legacyModule.EnumAssembly(handle, assembly) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumAssemblies(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumAssemblies(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumTypeDefinitions(ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumTypeDefinitions(handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumTypeDefinition(ulong* handle, /*IXCLRDataTypeDefinition*/ void** typeDefinition) + => _legacyModule is not null ? _legacyModule.EnumTypeDefinition(handle, typeDefinition) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumTypeDefinitions(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumTypeDefinitions(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumTypeInstances(/*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumTypeInstances(appDomain, handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumTypeInstance(ulong* handle, /*IXCLRDataTypeInstance*/ void** typeInstance) + => _legacyModule is not null ? _legacyModule.EnumTypeInstance(handle, typeInstance) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumTypeInstances(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumTypeInstances(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumTypeDefinitionsByName(char* name, uint flags, ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumTypeDefinitionsByName(name, flags, handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumTypeDefinitionByName(ulong* handle, /*IXCLRDataTypeDefinition*/ void** type) + => _legacyModule is not null ? _legacyModule.EnumTypeDefinitionByName(handle, type) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumTypeDefinitionsByName(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumTypeDefinitionsByName(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumTypeInstancesByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumTypeInstancesByName(name, flags, appDomain, handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumTypeInstanceByName(ulong* handle, /*IXCLRDataTypeInstance*/ void** type) + => _legacyModule is not null ? _legacyModule.EnumTypeInstanceByName(handle, type) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumTypeInstancesByName(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumTypeInstancesByName(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.GetTypeDefinitionByToken(/*mdTypeDef*/ uint token, /*IXCLRDataTypeDefinition*/ void** typeDefinition) + => _legacyModule is not null ? _legacyModule.GetTypeDefinitionByToken(token, typeDefinition) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumMethodDefinitionsByName(char* name, uint flags, ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumMethodDefinitionsByName(name, flags, handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumMethodDefinitionByName(ulong* handle, /*IXCLRDataMethodDefinition*/ void** method) + => _legacyModule is not null ? _legacyModule.EnumMethodDefinitionByName(handle, method) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumMethodDefinitionsByName(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumMethodDefinitionsByName(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumMethodInstancesByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumMethodInstancesByName(name, flags, appDomain, handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumMethodInstanceByName(ulong* handle, /*IXCLRDataMethodInstance*/ void** method) + => _legacyModule is not null ? _legacyModule.EnumMethodInstanceByName(handle, method) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumMethodInstancesByName(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumMethodInstancesByName(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.GetMethodDefinitionByToken(/*mdMethodDef*/ uint token, /*IXCLRDataMethodDefinition*/ void** methodDefinition) + => _legacyModule is not null ? _legacyModule.GetMethodDefinitionByToken(token, methodDefinition) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumDataByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, /*IXCLRDataTask*/ void* tlsTask, ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumDataByName(name, flags, appDomain, tlsTask, handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumDataByName(ulong* handle, /*IXCLRDataValue*/ void** value) + => _legacyModule is not null ? _legacyModule.EnumDataByName(handle, value) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumDataByName(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumDataByName(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.GetName(uint bufLen, uint* nameLen, char* name) + => _legacyModule is not null ? _legacyModule.GetName(bufLen, nameLen, name) : HResults.E_NOTIMPL; + int IXCLRDataModule.GetFileName(uint bufLen, uint* nameLen, char* name) + { + try + { + Contracts.ILoader contract = _target.Contracts.Loader; + Contracts.ModuleHandle handle = contract.GetModuleHandle(_address); + string result = string.Empty; + try + { + result = contract.GetPath(handle); + } + catch (InvalidOperationException) + { + // The memory for the path may not be enumerated - for example, in triage dumps + // In this case, GetPath will throw InvalidOperationException + } + + if (string.IsNullOrEmpty(result)) + { + result = contract.GetFileName(handle); + } + + if (string.IsNullOrEmpty(result)) + return HResults.E_FAIL; + + OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, result); + } + catch (System.Exception ex) + { + return ex.HResult; + } + +#if DEBUG + if (_legacyModule is not null) + { + char[] nameLocal = new char[bufLen]; + uint nameLenLocal; + int hrLocal; + fixed (char* ptr = nameLocal) + { + hrLocal = _legacyModule.GetFileName(bufLen, &nameLenLocal, ptr); + } + Debug.Assert(hrLocal == HResults.S_OK); + Debug.Assert(nameLen == null || *nameLen == nameLenLocal); + Debug.Assert(name == null || new ReadOnlySpan(nameLocal, 0, (int)nameLenLocal - 1).SequenceEqual(new string(name))); + } +#endif + return HResults.S_OK; + } + + int IXCLRDataModule.GetFlags(uint* flags) + => _legacyModule is not null ? _legacyModule.GetFlags(flags) : HResults.E_NOTIMPL; + + int IXCLRDataModule.IsSameObject(IXCLRDataModule* mod) + => _legacyModule is not null ? _legacyModule.IsSameObject(mod) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumExtents(ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumExtents(handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumExtent(ulong* handle, /*CLRDATA_MODULE_EXTENT*/ void* extent) + => _legacyModule is not null ? _legacyModule.EnumExtent(handle, extent) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumExtents(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumExtents(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) + => _legacyModule is not null ? _legacyModule.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL; + + int IXCLRDataModule.StartEnumAppDomains(ulong* handle) + => _legacyModule is not null ? _legacyModule.StartEnumAppDomains(handle) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumAppDomain(ulong* handle, /*IXCLRDataAppDomain*/ void** appDomain) + => _legacyModule is not null ? _legacyModule.EnumAppDomain(handle, appDomain) : HResults.E_NOTIMPL; + int IXCLRDataModule.EndEnumAppDomains(ulong handle) + => _legacyModule is not null ? _legacyModule.EndEnumAppDomains(handle) : HResults.E_NOTIMPL; + + int IXCLRDataModule.GetVersionId(Guid* vid) + => _legacyModule is not null ? _legacyModule.GetVersionId(vid) : HResults.E_NOTIMPL; + + int IXCLRDataModule2.SetJITCompilerFlags(uint flags) + => _legacyModule2 is not null ? _legacyModule2.SetJITCompilerFlags(flags) : HResults.E_NOTIMPL; +} diff --git a/src/native/managed/cdacreader/src/Legacy/IXCLRData.cs b/src/native/managed/cdacreader/src/Legacy/IXCLRData.cs index fb2aff16dc7ac..677e1683d6961 100644 --- a/src/native/managed/cdacreader/src/Legacy/IXCLRData.cs +++ b/src/native/managed/cdacreader/src/Legacy/IXCLRData.cs @@ -10,6 +10,112 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; // This file contains managed declarations for the IXCLRData interfaces. // See src/coreclr/inc/xclrdata.idl +[GeneratedComInterface] +[Guid("88E32849-0A0A-4cb0-9022-7CD2E9E139E2")] +internal unsafe partial interface IXCLRDataModule +{ + [PreserveSig] + int StartEnumAssemblies(ulong* handle); + [PreserveSig] + int EnumAssembly(ulong* handle, /*IXCLRDataAssembly*/ void** assembly); + [PreserveSig] + int EndEnumAssemblies(ulong handle); + + [PreserveSig] + int StartEnumTypeDefinitions(ulong* handle); + [PreserveSig] + int EnumTypeDefinition(ulong* handle, /*IXCLRDataTypeDefinition*/ void** typeDefinition); + [PreserveSig] + int EndEnumTypeDefinitions(ulong handle); + + [PreserveSig] + int StartEnumTypeInstances(/*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); + [PreserveSig] + int EnumTypeInstance(ulong* handle, /*IXCLRDataTypeInstance*/ void** typeInstance); + [PreserveSig] + int EndEnumTypeInstances(ulong handle); + + [PreserveSig] + int StartEnumTypeDefinitionsByName(char* name, uint flags, ulong* handle); + [PreserveSig] + int EnumTypeDefinitionByName(ulong* handle, /*IXCLRDataTypeDefinition*/ void** type); + [PreserveSig] + int EndEnumTypeDefinitionsByName(ulong handle); + + [PreserveSig] + int StartEnumTypeInstancesByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); + [PreserveSig] + int EnumTypeInstanceByName(ulong* handle, /*IXCLRDataTypeInstance*/ void** type); + [PreserveSig] + int EndEnumTypeInstancesByName(ulong handle); + + [PreserveSig] + int GetTypeDefinitionByToken(/*mdTypeDef*/ uint token, /*IXCLRDataTypeDefinition*/ void** typeDefinition); + + [PreserveSig] + int StartEnumMethodDefinitionsByName(char* name, uint flags, ulong* handle); + [PreserveSig] + int EnumMethodDefinitionByName(ulong* handle, /*IXCLRDataMethodDefinition*/ void** method); + [PreserveSig] + int EndEnumMethodDefinitionsByName(ulong handle); + + [PreserveSig] + int StartEnumMethodInstancesByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); + [PreserveSig] + int EnumMethodInstanceByName(ulong* handle, /*IXCLRDataMethodInstance*/ void** method); + [PreserveSig] + int EndEnumMethodInstancesByName(ulong handle); + + [PreserveSig] + int GetMethodDefinitionByToken(/*mdMethodDef*/ uint token, /*IXCLRDataMethodDefinition*/ void** methodDefinition); + + [PreserveSig] + int StartEnumDataByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, /*IXCLRDataTask*/ void* tlsTask, ulong* handle); + [PreserveSig] + int EnumDataByName(ulong* handle, /*IXCLRDataValue*/ void** value); + [PreserveSig] + int EndEnumDataByName(ulong handle); + + [PreserveSig] + int GetName(uint bufLen, uint* nameLen, char* name); + [PreserveSig] + int GetFileName(uint bufLen, uint* nameLen, char* name); + + [PreserveSig] + int GetFlags(uint* flags); + + [PreserveSig] + int IsSameObject(IXCLRDataModule* mod); + + [PreserveSig] + int StartEnumExtents(ulong* handle); + [PreserveSig] + int EnumExtent(ulong* handle, /*CLRDATA_MODULE_EXTENT*/ void* extent); + [PreserveSig] + int EndEnumExtents(ulong handle); + + [PreserveSig] + int Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer); + + [PreserveSig] + int StartEnumAppDomains(ulong* handle); + [PreserveSig] + int EnumAppDomain(ulong* handle, /*IXCLRDataAppDomain*/ void** appDomain); + [PreserveSig] + int EndEnumAppDomains(ulong handle); + + [PreserveSig] + int GetVersionId(Guid* vid); +} + +[GeneratedComInterface] +[Guid("34625881-7EB3-4524-817B-8DB9D064C760")] +internal unsafe partial interface IXCLRDataModule2 +{ + [PreserveSig] + int SetJITCompilerFlags(uint flags); +} + [GeneratedComInterface] [Guid("5c552ab6-fc09-4cb3-8e36-22fa03c798b7")] internal unsafe partial interface IXCLRDataProcess diff --git a/src/native/managed/cdacreader/src/Legacy/OutputBufferHelpers.cs b/src/native/managed/cdacreader/src/Legacy/OutputBufferHelpers.cs new file mode 100644 index 0000000000000..1f6d00716e376 --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/OutputBufferHelpers.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +internal static class OutputBufferHelpers +{ + public static unsafe void CopyStringToBuffer(char* stringBuf, uint bufferSize, uint* neededBufferSize, string str) + { + ReadOnlySpan strSpan = str.AsSpan(); + if (neededBufferSize != null) + *neededBufferSize = checked((uint)(strSpan.Length + 1)); + + if (stringBuf != null && bufferSize > 0) + { + Span target = new Span(stringBuf, checked((int)bufferSize)); + int nullTerminatorLocation = strSpan.Length > bufferSize - 1 ? checked((int)(bufferSize - 1)) : strSpan.Length; + strSpan = strSpan.Slice(0, nullTerminatorLocation); + strSpan.CopyTo(target); + target[nullTerminatorLocation] = '\0'; + } + } +} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 745f9b2f0587e..aad3accde2fbe 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -269,7 +269,7 @@ int ISOSDacInterface.GetMethodDescName(ulong methodDesc, uint count, char* name, if (hr == HResults.S_OK) { - CopyStringToTargetBuffer(name, count, pNeeded, stringBuilder.ToString()); + OutputBufferHelpers.CopyStringToBuffer(name, count, pNeeded, stringBuilder.ToString()); } } catch (System.Exception ex) @@ -399,22 +399,6 @@ int ISOSDacInterface.GetMethodTableForEEClass(ulong eeClassReallyCanonMT, ulong* return HResults.S_OK; } - private unsafe void CopyStringToTargetBuffer(char* stringBuf, uint bufferSize, uint* neededBufferSize, string str) - { - ReadOnlySpan strSpan = str.AsSpan(); - if (neededBufferSize != null) - *neededBufferSize = checked((uint)(strSpan.Length + 1)); - - if (stringBuf != null && bufferSize > 0) - { - Span target = new Span(stringBuf, checked((int)bufferSize)); - int nullTerminatorLocation = strSpan.Length > bufferSize - 1 ? checked((int)(bufferSize - 1)) : strSpan.Length; - strSpan = strSpan.Slice(0, nullTerminatorLocation); - strSpan.CopyTo(target); - target[nullTerminatorLocation] = '\0'; - } - } - int ISOSDacInterface.GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded) { if (mt == 0) @@ -426,7 +410,7 @@ int ISOSDacInterface.GetMethodTableName(ulong mt, uint count, char* mtName, uint Contracts.TypeHandle methodTableHandle = typeSystemContract.GetTypeHandle(mt); if (typeSystemContract.IsFreeObjectMethodTable(methodTableHandle)) { - CopyStringToTargetBuffer(mtName, count, pNeeded, "Free"); + OutputBufferHelpers.CopyStringToBuffer(mtName, count, pNeeded, "Free"); return HResults.S_OK; } @@ -452,7 +436,7 @@ int ISOSDacInterface.GetMethodTableName(ulong mt, uint count, char* mtName, uint catch { } } - CopyStringToTargetBuffer(mtName, count, pNeeded, methodTableName.ToString()); + OutputBufferHelpers.CopyStringToBuffer(mtName, count, pNeeded, methodTableName.ToString()); } catch (global::System.Exception ex) { @@ -482,7 +466,37 @@ int ISOSDacInterface.GetMethodTableSlot(ulong mt, uint slot, ulong* value) int ISOSDacInterface.GetMethodTableTransparencyData(ulong mt, void* data) => _legacyImpl is not null ? _legacyImpl.GetMethodTableTransparencyData(mt, data) : HResults.E_NOTIMPL; int ISOSDacInterface.GetModule(ulong addr, /*IXCLRDataModule*/ void** mod) - => _legacyImpl is not null ? _legacyImpl.GetModule(addr, mod) : HResults.E_NOTIMPL; + { + ComWrappers cw = new StrategyBasedComWrappers(); + + int hr; + nint legacyModulePointer = 0; + object? legacyModule = null; + if (_legacyImpl is not null) + { + hr = _legacyImpl.GetModule(addr, (void**)&legacyModulePointer); + if (hr < 0) + return hr; + + legacyModule = cw.GetOrCreateObjectForComInstance(legacyModulePointer, CreateObjectFlags.None); + } + + ClrDataModule module = new(addr, _target, legacyModulePointer, legacyModule); + + // Lifetime is now managed via ClrDataModule + if (legacyModulePointer != 0) + Marshal.Release(legacyModulePointer); + + nint iunknownPtr = cw.GetOrCreateComInterfaceForObject(module, CreateComInterfaceFlags.None); + hr = Marshal.QueryInterface(iunknownPtr, typeof(IXCLRDataModule).GUID, out nint modPtr); + if (iunknownPtr != 0) + Marshal.Release(iunknownPtr); + + if (hr == HResults.S_OK) + *mod = (IXCLRDataModule*)modPtr; + + return hr; + } int ISOSDacInterface.GetModuleData(ulong moduleAddr, DacpModuleData* data) { @@ -713,7 +727,7 @@ int ISOSDacInterface.GetObjectStringData(ulong obj, uint count, char* stringData { Contracts.IObject contract = _target.Contracts.Object; string str = contract.GetStringValue(obj); - CopyStringToTargetBuffer(stringData, count, pNeeded, str); + OutputBufferHelpers.CopyStringToBuffer(stringData, count, pNeeded, str); } catch (System.Exception ex) { @@ -802,7 +816,7 @@ int ISOSDacInterface.GetPEFileName(ulong addr, uint count, char* fileName, uint* } } - CopyStringToTargetBuffer(fileName, count, pNeeded, path); + OutputBufferHelpers.CopyStringToBuffer(fileName, count, pNeeded, path); } catch (System.Exception ex) { @@ -821,7 +835,7 @@ int ISOSDacInterface.GetPEFileName(ulong addr, uint count, char* fileName, uint* } Debug.Assert(hrLocal == HResults.S_OK); Debug.Assert(pNeeded == null || *pNeeded == neededLocal); - Debug.Assert(fileName == null || new Span(fileName, (int)*pNeeded).SequenceEqual(fileNameLocal.AsSpan(0, (int)neededLocal))); + Debug.Assert(fileName == null || new ReadOnlySpan(fileNameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(fileName))); } #endif return HResults.S_OK; diff --git a/src/native/managed/cdacreader/tests/LoaderTests.cs b/src/native/managed/cdacreader/tests/LoaderTests.cs new file mode 100644 index 0000000000000..2a5913863818c --- /dev/null +++ b/src/native/managed/cdacreader/tests/LoaderTests.cs @@ -0,0 +1,85 @@ +// 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 System.IO; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +using MockLoader = MockDescriptors.Loader; + +public unsafe class LoaderTests +{ + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetPath(MockTarget.Architecture arch) + { + // Set up the target + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + builder = builder + .SetContracts([nameof(Contracts.Loader)]) + .SetTypes(MockLoader.Types(helpers)); + + string expected = $"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}TestModule.dll"; + + // Add the modules + MockLoader loader = new(builder); + TargetPointer moduleAddr = loader.AddModule(helpers, path: expected); + TargetPointer moduleAddrEmptyPath = loader.AddModule(helpers); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + // Validate the expected module data + Contracts.ILoader contract = target.Contracts.Loader; + Assert.NotNull(contract); + { + Contracts.ModuleHandle handle = contract.GetModuleHandle(moduleAddr); + string actual = contract.GetPath(handle); + Assert.Equal(expected, actual); + } + { + Contracts.ModuleHandle handle = contract.GetModuleHandle(moduleAddrEmptyPath); + string actual = contract.GetFileName(handle); + Assert.Equal(string.Empty, actual); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetFileName(MockTarget.Architecture arch) + { + // Set up the target + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + builder = builder + .SetContracts([nameof(Contracts.Loader)]) + .SetTypes(MockLoader.Types(helpers)); + + string expected = $"TestModule.dll"; + + // Add the modules + MockLoader loader = new(builder); + TargetPointer moduleAddr = loader.AddModule(helpers, fileName: expected); + TargetPointer moduleAddrEmptyName = loader.AddModule(helpers); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + // Validate the expected module data + Contracts.ILoader contract = target.Contracts.Loader; + Assert.NotNull(contract); + { + Contracts.ModuleHandle handle = contract.GetModuleHandle(moduleAddr); + string actual = contract.GetFileName(handle); + Assert.Equal(expected, actual); + } + { + Contracts.ModuleHandle handle = contract.GetModuleHandle(moduleAddrEmptyName); + string actual = contract.GetFileName(handle); + Assert.Equal(string.Empty, actual); + } + } +} diff --git a/src/native/managed/cdacreader/tests/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors.cs index 8ffcd23556afd..2aa14dea521c7 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors.cs @@ -5,11 +5,12 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; -public class MockDescriptors +internal class MockDescriptors { private static readonly Target.TypeInfo MethodTableTypeInfo = new() { @@ -88,6 +89,25 @@ public class MockDescriptors }, }; + private static readonly (string, DataType)[] ModuleFields = + [ + (nameof(Data.Module.Assembly), DataType.pointer), + (nameof(Data.Module.Flags), DataType.uint32), + (nameof(Data.Module.Base), DataType.pointer), + (nameof(Data.Module.LoaderAllocator), DataType.pointer), + (nameof(Data.Module.ThunkHeap), DataType.pointer), + (nameof(Data.Module.DynamicMetadata), DataType.pointer), + (nameof(Data.Module.Path), DataType.pointer), + (nameof(Data.Module.FileName), DataType.pointer), + (nameof(Data.Module.FieldDefToDescMap), DataType.pointer), + (nameof(Data.Module.ManifestModuleReferencesMap), DataType.pointer), + (nameof(Data.Module.MemberRefToDescMap), DataType.pointer), + (nameof(Data.Module.MethodDefToDescMap), DataType.pointer), + (nameof(Data.Module.TypeDefToMethodTableMap), DataType.pointer), + (nameof(Data.Module.TypeRefToMethodTableMap), DataType.pointer), + (nameof(Data.Module.MethodDefToILCodeVersioningStateMap), DataType.pointer), + ]; + public static class RuntimeTypeSystem { internal const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0; @@ -325,4 +345,72 @@ internal static MockMemorySpace.Builder AddArrayObject(TargetTestHelpers targetT return builder.AddHeapFragment(fragment); } } + + public class Loader + { + private const ulong DefaultAllocationRangeStart = 0x0001_0000; + private const ulong DefaultAllocationRangeEnd = 0x0002_0000; + + private readonly MockMemorySpace.Builder _builder; + private readonly MockMemorySpace.BumpAllocator _allocator; + + public Loader(MockMemorySpace.Builder builder) + : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) + { } + + public Loader(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange) + { + _builder = builder; + _allocator = _builder.CreateAllocator(allocationRange.Start, allocationRange.End); + } + + internal static Dictionary Types(TargetTestHelpers helpers) + { + TargetTestHelpers.LayoutResult layout = helpers.LayoutFields(ModuleFields); + return new() + { + [DataType.Module] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }, + }; + } + + internal TargetPointer AddModule(TargetTestHelpers helpers, string? path = null, string? fileName = null) + { + Target.TypeInfo typeInfo = Types(helpers)[DataType.Module]; + uint size = typeInfo.Size.Value; + MockMemorySpace.HeapFragment module = _allocator.Allocate(size, "Module"); + _builder.AddHeapFragment(module); + + if (path != null) + { + // Path data + Encoding encoding = helpers.Arch.IsLittleEndian ? Encoding.Unicode : Encoding.BigEndianUnicode; + ulong pathSize = (ulong)encoding.GetByteCount(path) + sizeof(char); + MockMemorySpace.HeapFragment pathFragment = _allocator.Allocate(pathSize, $"Module path = {path}"); + helpers.WriteUtf16String(pathFragment.Data, path); + _builder.AddHeapFragment(pathFragment); + + // Pointer to path + helpers.WritePointer( + module.Data.AsSpan().Slice(typeInfo.Fields[nameof(Data.Module.Path)].Offset, helpers.PointerSize), + pathFragment.Address); + } + + if (fileName != null) + { + // File name data + Encoding encoding = helpers.Arch.IsLittleEndian ? Encoding.Unicode : Encoding.BigEndianUnicode; + ulong fileNameSize = (ulong)encoding.GetByteCount(fileName) + sizeof(char); + MockMemorySpace.HeapFragment fileNameFragment = _allocator.Allocate(fileNameSize, $"Module file name = {fileName}"); + helpers.WriteUtf16String(fileNameFragment.Data, fileName); + _builder.AddHeapFragment(fileNameFragment); + + // Pointer to file name + helpers.WritePointer( + module.Data.AsSpan().Slice(typeInfo.Fields[nameof(Data.Module.FileName)].Offset, helpers.PointerSize), + fileNameFragment.Address); + } + + return module.Address; + } + } } diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs index c0339f0f6efea..0439d3cceced8 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -156,9 +156,9 @@ private string MakeContractsJson() private (HeapFragment json, HeapFragment pointerData) CreateDataDescriptor() { - string metadataTypesJson = TargetTestHelpers.MakeTypesJson(_types); - string metadataGlobalsJson = TargetTestHelpers.MakeGlobalsJson(_globals); - string interpolatedContracts = MakeContractsJson(); + string metadataTypesJson = _types is not null ? TargetTestHelpers.MakeTypesJson(_types) : string.Empty; + string metadataGlobalsJson = _globals is not null ? TargetTestHelpers.MakeGlobalsJson(_globals) : string.Empty; + string interpolatedContracts = _contracts is not null ? MakeContractsJson() : string.Empty; byte[] jsonBytes = Encoding.UTF8.GetBytes($$""" { "version": 0, diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index eba171463ef17..60feb7e7c1b62 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using System.Text; namespace Microsoft.Diagnostics.DataContractReader.UnitTests; internal unsafe class TargetTestHelpers @@ -171,9 +172,6 @@ static string FormatIndirect(uint value, string? type) #endregion Data descriptor json formatting - - - #region Mock memory initialization internal uint ObjHeaderSize => (uint)(Arch.Is64Bit ? 2 * sizeof(uint) /*alignpad + syncblock*/: sizeof(uint) /* syncblock */); @@ -224,7 +222,6 @@ internal void Write(Span dest, ulong u) } } - internal void WritePointer(Span dest, ulong value) { if (Arch.Is64Bit) @@ -265,6 +262,19 @@ internal TargetPointer ReadPointer(ReadOnlySpan src) } } + internal void WriteUtf16String(Span dest, string value) + { + Encoding encoding = Arch.IsLittleEndian ? Encoding.Unicode : Encoding.BigEndianUnicode; + byte[] valueBytes = encoding.GetBytes(value); + int len = valueBytes.Length + sizeof(char); + if (dest.Length < len) + throw new InvalidOperationException($"Destination is too short to write '{value}'. Required length: {len}, actual: {dest.Length}"); + + valueBytes.AsSpan().CopyTo(dest); + dest[^2] = 0; + dest[^1] = 0; + } + internal int SizeOfPrimitive(DataType type) { return type switch @@ -316,7 +326,7 @@ public LayoutResult LayoutFields((string Name, DataType Type)[] fields) // Implements a simple layout algorithm that aligns fields to their size // and aligns the structure to the largest field size. - public LayoutResult LayoutFields(FieldLayout style, (string Name, DataType Type)[] fields) + public LayoutResult LayoutFields(FieldLayout style, (string Name, DataType Type)[] fields) { Dictionary fieldInfos = new (); int maxAlign = 1; diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/TargetTests.cs index 50638e1f8c817..b260f42a76235 100644 --- a/src/native/managed/cdacreader/tests/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/TargetTests.cs @@ -123,6 +123,50 @@ public void ReadIndirectGlobalValue(MockTarget.Architecture arch) } } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ReadUtf8String(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + MockMemorySpace.Builder builder = new(targetTestHelpers); + + string expected = "UTF-8 string ✓"; + ulong addr = 0x1000; + + MockMemorySpace.HeapFragment fragment = new() { Address = addr, Data = new byte[Encoding.UTF8.GetByteCount(expected) + 1] }; + Encoding.UTF8.GetBytes(expected).AsSpan().CopyTo(fragment.Data); + fragment.Data[^1] = 0; + builder.AddHeapFragment(fragment); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + string actual = target.ReadUtf8String(addr); + Assert.Equal(expected, actual); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ReadUtf16String(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + MockMemorySpace.Builder builder = new(targetTestHelpers); + + string expected = "UTF-16 string ✓"; + ulong addr = 0x1000; + + Encoding encoding = arch.IsLittleEndian ? Encoding.Unicode : Encoding.BigEndianUnicode; + MockMemorySpace.HeapFragment fragment = new() { Address = addr, Data = new byte[encoding.GetByteCount(expected) + sizeof(char)] }; + targetTestHelpers.WriteUtf16String(fragment.Data, expected); + builder.AddHeapFragment(fragment); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + string actual = target.ReadUtf16String(addr); + Assert.Equal(expected, actual); + } + private static void ValidateGlobals( ContractDescriptorTarget target, (string Name, ulong Value, string? Type)[] globals, @@ -203,5 +247,4 @@ void AssertEqualsWithCallerInfo(T expected, T actual) Assert.True((expected is null && actual is null) || expected.Equals(actual), $"Expected: {expected}. Actual: {actual}. [test case: {caller} in {filePath}:{lineNumber}]"); } } - }