From 573d12e92245bbeb7f2fd14f909cc01ffbfd1cd7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 26 Mar 2025 18:25:45 -0400 Subject: [PATCH 01/33] add DAC like entrypoint --- .../ContractDescriptorTarget.cs | 2 +- .../managed/cdacreader/src/Entrypoints.cs | 77 +++++++++++++++++++ .../managed/cdacreader/src/Legacy/ICLRData.cs | 46 +++++++++++ src/tools/StressLogAnalyzer/src/Program.cs | 1 - 4 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 018d77120ee7c1..9e4b140c423c15 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -60,7 +60,7 @@ public static bool TryCreate( ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, GetTargetPlatformDelegate getTargetPlatform, - out ContractDescriptorTarget? target) + [NotNullWhen(true)] out ContractDescriptorTarget? target) { Reader reader = new Reader(readFromTarget, getThreadContext, getTargetPlatform); if (TryReadContractDescriptor( diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index 015247efc0a9c4..c884f87bd3a691 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Legacy; namespace Microsoft.Diagnostics.DataContractReader; @@ -83,4 +84,80 @@ private static unsafe int CreateSosInterface(IntPtr handle, IntPtr legacyImplPtr *obj = ptr; return 0; } + + [UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstance")] + private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, void** iface) + { + if (pLegacyTarget == IntPtr.Zero || iface == null) + return HResults.E_INVALIDARG; + + *iface = null; + + ComWrappers cw = new StrategyBasedComWrappers(); + object obj = cw.GetOrCreateObjectForComInstance(pLegacyTarget, CreateObjectFlags.None); + ICLRDataTarget dataTarget = obj as ICLRDataTarget ?? throw new ArgumentException($"pLegacyTarget does not implement ${nameof(ICLRDataTarget)}", nameof(pLegacyTarget)); + ICLRContractLocator contractLocator = obj as ICLRContractLocator ?? throw new ArgumentException($"pLegacyTarget does not implement ${nameof(ICLRContractLocator)}", nameof(pLegacyTarget)); + + ulong contractAddress; + if (contractLocator.GetContractDescriptor(&contractAddress) != 0) + { + throw new InvalidOperationException("Unable to retrieve contract address from ICLRContractLocator"); + } + + if (!ContractDescriptorTarget.TryCreate( + contractAddress, + (address, buffer) => + { + fixed (byte* bufferPtr = buffer) + { + uint bytesRead; + return dataTarget.ReadVirtual(address, bufferPtr, (uint)buffer.Length, &bytesRead); + } + }, + (threadId, contextFlags, contextSize, bufferToFill) => + { + fixed (byte* bufferPtr = bufferToFill) + { + return dataTarget.GetThreadContext(threadId, contextFlags, contextSize, bufferPtr); + } + }, + (out platform) => + { + platform = 0; + uint machineType; + int hr = dataTarget.GetMachineType(&machineType); + switch (machineType) + { + // ICLRDataTarget can not be used to find OS. For now labeling all platforms as Windows + // + case 0x014c: // IMAGE_FILE_MACHINE_I386 + platform = (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_X86; + break; + case 0x8664: // IMAGE_FILE_MACHINE_AMD64 + platform = (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64; + break; + case 0x01c4: // IMAGE_FILE_MACHINE_ARMNT + platform = (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM; + break; + case 0xAA64: // IMAGE_FILE_MACHINE_ARM64 + platform = (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64; + break; + } + return hr; + }, + out ContractDescriptorTarget? target)) + { + return -1; + } + + Legacy.SOSDacImpl impl = new(target, null); + nint ccw = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + Marshal.QueryInterface(ccw, *pIID, out nint ptrToIface); + *iface = (void*)ptrToIface; + + // Decrement reference count on ccw because QI increments it + Marshal.Release(ccw); + + return 0; + } } diff --git a/src/native/managed/cdacreader/src/Legacy/ICLRData.cs b/src/native/managed/cdacreader/src/Legacy/ICLRData.cs index 5b33d833beb516..56748a4f08f89d 100644 --- a/src/native/managed/cdacreader/src/Legacy/ICLRData.cs +++ b/src/native/managed/cdacreader/src/Legacy/ICLRData.cs @@ -17,3 +17,49 @@ internal unsafe partial interface ICLRDataEnumMemoryRegions [PreserveSig] int EnumMemoryRegions(/*ICLRDataEnumMemoryRegionsCallback*/ void* callback, uint miniDumpFlags, /*CLRDataEnumMemoryFlags*/ int clrFlags); } + +[GeneratedComInterface] +[Guid("3e11ccee-d08b-43e5-af01-32717a64da03")] +internal unsafe partial interface ICLRDataTarget +{ + [PreserveSig] + int GetMachineType(uint* machineType); + + [PreserveSig] + int GetPointerSize(uint* pointerSize); + + [PreserveSig] + int GetImageBase([MarshalAs(UnmanagedType.LPWStr)] string imagePath, ulong* baseAddress); + + [PreserveSig] + int ReadVirtual(ulong address, byte* buffer, uint bytesRequested, uint* bytesRead); + + [PreserveSig] + int WriteVirtual(ulong address, byte* buffer, uint bytesRequested, uint* bytesWritten); + + [PreserveSig] + int GetTLSValue(uint threadID, uint index, ulong* value); + + [PreserveSig] + int SetTLSValue(uint threadID, uint index, ulong value); + + [PreserveSig] + int GetCurrentThreadID(uint* threadID); + + [PreserveSig] + int GetThreadContext(uint threadID, uint contextFlags, uint contextSize, byte* context); + + [PreserveSig] + int SetThreadContext(uint threadID, uint contextSize, byte* context); + + [PreserveSig] + int Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer); +} + +[GeneratedComInterface] +[Guid("17d5b8c6-34a9-407f-af4f-a930201d4e02")] +internal unsafe partial interface ICLRContractLocator +{ + [PreserveSig] + int GetContractDescriptor(ulong* contractAddress); +} diff --git a/src/tools/StressLogAnalyzer/src/Program.cs b/src/tools/StressLogAnalyzer/src/Program.cs index e475d0547b2a14..d683c6f463c8f7 100644 --- a/src/tools/StressLogAnalyzer/src/Program.cs +++ b/src/tools/StressLogAnalyzer/src/Program.cs @@ -493,7 +493,6 @@ ContractDescriptorTarget CreateTarget() => ContractDescriptorTarget.Create( [TargetPointer.Null, new TargetPointer(header->memoryBase + (nuint)((byte*)&header->moduleTable - (byte*)header))], (address, buffer) => ReadFromMemoryMappedLog(address, buffer, header), (threadId, contextFlags, contextSize, bufferToFill) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetThreadContext implementation"), - (out platform) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetPlatform implementation"), true, nuint.Size); } From c60292e3d567c6d7736a5c3a925d1c3638cfe369 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 27 Mar 2025 16:12:51 -0400 Subject: [PATCH 02/33] implement RuntimeInfo contract --- src/coreclr/debug/runtimeinfo/contracts.jsonc | 1 + .../debug/runtimeinfo/datadescriptor.h | 25 +++++++++++ .../ContractRegistry.cs | 4 ++ .../Contracts/IRuntimeInfo.cs | 37 +++++++++++++++++ .../Contracts/RuntimeInfoFactory.cs | 16 ++++++++ .../Contracts/RuntimeInfo_1.cs | 41 +++++++++++++++++++ .../CachingContractRegistry.cs | 2 + 7 files changed, 126 insertions(+) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfoFactory.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index dc20297b01bed1..bcc2daae3830e3 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -19,6 +19,7 @@ "PlatformMetadata": 1, "PrecodeStubs": 2, "ReJIT": 1, + "RuntimeInfo": 1, "RuntimeTypeSystem": 1, "StackWalk": 1, "StressLog": 2, diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index d9a0556b9742b1..86dfa26747a5fd 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -757,6 +757,31 @@ CDAC_TYPE_END(CalleeSavedRegisters) CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() + +// RuntimeInfo OperatingSystem +#ifdef TARGET_UNIX +CDAC_GLOBAL(OperatingSystem, uint32, 2) // RuntimeInfoOperatingSystem.Unix +#else // !TARGET_UNIX +CDAC_GLOBAL(OperatingSystem, uint32, 1) // RuntimeInfoOperatingSystem.Windows +#endif // !TARGET_UNIX + +// RuntimeInfo Architecture +#if defined(TARGET_X86) +CDAC_GLOBAL(Architecture, uint32, 1) // RuntimeInfoArchitecture.X86 +#elif defined(TARGET_AMD64) +CDAC_GLOBAL(Architecture, uint32, 3) // RuntimeInfoArchitecture.Amd64 +#elif defined(TARGET_ARM) +CDAC_GLOBAL(Architecture, uint32, 2) // RuntimeInfoArchitecture.Arm32 +#elif defined(TARGET_ARM64) +CDAC_GLOBAL(Architecture, uint32, 4) // RuntimeInfoArchitecture.Arm64 +#elif defined(TARGET_LOONGARCH64) +CDAC_GLOBAL(Architecture, uint32, 5) // RuntimeInfoArchitecture.LoongArch64 +#elif defined(TARGET_RISCV64) +CDAC_GLOBAL(Architecture, uint32, 6) // RuntimeInfoArchitecture.RISCV +#else +#error Unknown Processor. +#endif + CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 42e0d38c545249..53faad2d53f99b 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -63,4 +63,8 @@ public abstract class ContractRegistry /// Gets an instance of the StackWalk contract for the target. /// public abstract IStackWalk StackWalk { get; } + /// + /// Gets an instance of the RuntimeInfo contract for the target. + /// + public abstract IRuntimeInfo RuntimeInfo { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs new file mode 100644 index 00000000000000..1784bcba558859 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs @@ -0,0 +1,37 @@ +// 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.Contracts; + +public enum RuntimeInfoArchitecture : uint +{ + Unknown = 0, + X86, + Arm32, + Amd64, + Arm64, + LoongArch64, + RISCV, +} + +public enum RuntimeInfoOperatingSystem : uint +{ + Unknown = 0, + Windows, + Unix, +} + +public interface IRuntimeInfo : IContract +{ + static string IContract.Name { get; } = nameof(RuntimeInfo); + public virtual RuntimeInfoArchitecture GetTargetArchitecture() => throw new NotImplementedException(); + public virtual RuntimeInfoOperatingSystem GetTargetOperatingSystem() => throw new NotImplementedException(); +} + +public readonly struct RuntimeInfo : IRuntimeInfo +{ + RuntimeInfoArchitecture IRuntimeInfo.GetTargetArchitecture() => RuntimeInfoArchitecture.Unknown; + RuntimeInfoOperatingSystem IRuntimeInfo.GetTargetOperatingSystem() => RuntimeInfoOperatingSystem.Unknown; +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfoFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfoFactory.cs new file mode 100644 index 00000000000000..de468ffc96dc75 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfoFactory.cs @@ -0,0 +1,16 @@ +// 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.DataContractReader.Contracts; + +public sealed class RuntimeInfoFactory : IContractFactory +{ + IRuntimeInfo IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new RuntimeInfo_1(target), + _ => default(RuntimeInfo), + }; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs new file mode 100644 index 00000000000000..0e4c4999c63588 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs @@ -0,0 +1,41 @@ +// 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.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal struct RuntimeInfo_1 : IRuntimeInfo +{ + private const string ArchitectureGlobalName = "Architecture"; + private const string OperatingSystemGlobalName = "OperatingSystem"; + + internal readonly Target _target; + + public RuntimeInfo_1(Target target) + { + _target = target; + } + + RuntimeInfoArchitecture IRuntimeInfo.GetTargetArchitecture() + { + if (_target.TryReadGlobal(ArchitectureGlobalName, out uint? arch)) + { + return (RuntimeInfoArchitecture)arch; + } + + return RuntimeInfoArchitecture.Unknown; + } + + RuntimeInfoOperatingSystem IRuntimeInfo.GetTargetOperatingSystem() + { + if (_target.TryReadGlobal(OperatingSystemGlobalName, out uint? os)) + { + return (RuntimeInfoOperatingSystem)os; + } + + return RuntimeInfoOperatingSystem.Unknown; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index d49ceb0eeaacd2..7b339281951cbe 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -38,6 +38,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IPrecodeStubs)] = new PrecodeStubsFactory(), [typeof(IReJIT)] = new ReJITFactory(), [typeof(IStackWalk)] = new StackWalkFactory(), + [typeof(IRuntimeInfo)] = new RuntimeInfoFactory(), }; configureFactories?.Invoke(_factories); } @@ -55,6 +56,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IPrecodeStubs PrecodeStubs => GetContract(); public override IReJIT ReJIT => GetContract(); public override IStackWalk StackWalk => GetContract(); + public override IRuntimeInfo RuntimeInfo => GetContract(); private TContract GetContract() where TContract : IContract { From 238828db49bc5440daa1f37096d676657a5a92a5 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 27 Mar 2025 16:36:27 -0400 Subject: [PATCH 03/33] convert to use IRuntimeInfo to fetch platform --- src/coreclr/debug/daccess/cdac.cpp | 12 +------ .../Target.cs | 26 --------------- .../Context/IPlatformAgnosticContext.cs | 18 ++++------ .../ContractDescriptorTarget.cs | 25 ++------------ .../managed/cdacreader/inc/cdac_reader.h | 2 -- .../managed/cdacreader/src/Entrypoints.cs | 33 +------------------ .../cdacreader/tests/TestPlaceholderTarget.cs | 2 -- 7 files changed, 12 insertions(+), 106 deletions(-) diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index 02663789ea4d4b..b66c608e2478db 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -52,16 +52,6 @@ namespace return S_OK; } - - int GetPlatform(uint32_t* platform, void* context) - { - ICorDebugDataTarget* target = reinterpret_cast(context); - HRESULT hr = target->GetPlatform((CorDebugPlatform*)platform); - if (FAILED(hr)) - return hr; - - return S_OK; - } } CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown* legacyImpl) @@ -74,7 +64,7 @@ CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown _ASSERTE(init != nullptr); intptr_t handle; - if (init(descriptorAddr, &ReadFromTargetCallback, &ReadThreadContext, &GetPlatform, target, &handle) != 0) + if (init(descriptorAddr, &ReadFromTargetCallback, &ReadThreadContext, target, &handle) != 0) { ::FreeLibrary(cdacLib); return {}; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index a9a73560c8b9b7..522e8dcbdfcd4b 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -18,27 +18,6 @@ namespace Microsoft.Diagnostics.DataContractReader; /// public abstract class Target { - /// - /// CorDebugPlatform represents the platform of the target. - /// - public enum CorDebugPlatform : int - { - CORDB_PLATFORM_WINDOWS_X86 = 0, - CORDB_PLATFORM_WINDOWS_AMD64 = 1, - CORDB_PLATFORM_WINDOWS_IA64 = 2, - CORDB_PLATFORM_MAC_PPC = 3, - CORDB_PLATFORM_MAC_X86 = 4, - CORDB_PLATFORM_WINDOWS_ARM = 5, - CORDB_PLATFORM_MAC_AMD64 = 6, - CORDB_PLATFORM_WINDOWS_ARM64 = 7, - CORDB_PLATFORM_POSIX_AMD64 = 8, - CORDB_PLATFORM_POSIX_X86 = 9, - CORDB_PLATFORM_POSIX_ARM = 10, - CORDB_PLATFORM_POSIX_ARM64 = 11, - CORDB_PLATFORM_POSIX_LOONGARCH64 = 12, - CORDB_PLATFORM_POSIX_RISCV64 = 13, - } - /// /// Pointer size of the target /// @@ -48,11 +27,6 @@ public enum CorDebugPlatform : int /// public abstract bool IsLittleEndian { get; } - /// - /// Platform of the target - /// - public abstract CorDebugPlatform Platform { get; } - /// /// Fills a buffer with the context of the given thread /// diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index 8a82caf4cb2abd..3254b33987954d 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -25,17 +25,13 @@ public interface IPlatformAgnosticContext public static IPlatformAgnosticContext GetContextForPlatform(Target target) { - switch (target.Platform) + IRuntimeInfo runtimeInfo = target.Contracts.RuntimeInfo; + return runtimeInfo.GetTargetArchitecture() switch { - case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: - case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: - case Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64: - return new ContextHolder(); - case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: - case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: - return new ContextHolder(); - default: - throw new InvalidOperationException($"Unsupported platform {target.Platform}"); - } + RuntimeInfoArchitecture.Amd64 => new ContextHolder(), + RuntimeInfoArchitecture.Arm64 => new ContextHolder(), + RuntimeInfoArchitecture.Unknown => throw new InvalidOperationException($"Processor architecture is required for creating a platform specific context and is not provided by the target"), + _ => throw new InvalidOperationException($"Unsupported architecture {runtimeInfo.GetTargetArchitecture()}"), + }; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 9e4b140c423c15..a77a024014f813 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -44,7 +44,6 @@ private readonly struct Configuration public delegate int ReadFromTargetDelegate(ulong address, Span bufferToFill); public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); - public delegate int GetTargetPlatformDelegate(out int platform); /// /// Create a new target instance from a contract descriptor embedded in the target memory. @@ -52,17 +51,15 @@ private readonly struct Configuration /// The offset of the contract descriptor in the target memory /// A callback to read memory blocks at a given address from the target /// A callback to fetch a thread's context - /// A callback to fetch the target's platform /// The target object. /// If a target instance could be created, true; otherwise, false. public static bool TryCreate( ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, - GetTargetPlatformDelegate getTargetPlatform, [NotNullWhen(true)] out ContractDescriptorTarget? target) { - Reader reader = new Reader(readFromTarget, getThreadContext, getTargetPlatform); + Reader reader = new Reader(readFromTarget, getThreadContext); if (TryReadContractDescriptor( contractDescriptor, reader, @@ -85,7 +82,6 @@ public static bool TryCreate( /// The values for any global pointers specified in the contract descriptor. /// A callback to read memory blocks at a given address from the target /// A callback to fetch a thread's context - /// A callback to fetch the target's platform /// Whether the target is little-endian /// The size of a pointer in bytes in the target process. /// The target object. @@ -94,7 +90,6 @@ public static ContractDescriptorTarget Create( TargetPointer[] globalPointerValues, ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, - GetTargetPlatformDelegate getTargetPlatform, bool isLittleEndian, int pointerSize) { @@ -102,7 +97,7 @@ public static ContractDescriptorTarget Create( new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }, contractDescriptor, globalPointerValues, - new Reader(readFromTarget, getThreadContext, getTargetPlatform)); + new Reader(readFromTarget, getThreadContext)); } private ContractDescriptorTarget(Configuration config, ContractDescriptorParser.ContractDescriptor descriptor, TargetPointer[] pointerData, Reader reader) @@ -263,14 +258,6 @@ private static DataType GetDataType(string type) public override int PointerSize => _config.PointerSize; public override bool IsLittleEndian => _config.IsLittleEndian; - public override CorDebugPlatform Platform - { - get - { - _reader.GetTargetPlatform(out int platform); - return (CorDebugPlatform)platform; - } - } public override bool TryGetThreadContext(ulong threadId, uint contextFlags, Span buffer) { @@ -625,8 +612,7 @@ public void Clear() private readonly struct Reader( ReadFromTargetDelegate readFromTarget, - GetTargetThreadContextDelegate getThreadContext, - GetTargetPlatformDelegate getTargetPlatform) + GetTargetThreadContextDelegate getThreadContext) { public int ReadFromTarget(ulong address, Span buffer) { @@ -636,11 +622,6 @@ public int ReadFromTarget(ulong address, Span buffer) public int ReadFromTarget(ulong address, byte* buffer, uint bytesToRead) => readFromTarget(address, new Span(buffer, checked((int)bytesToRead))); - public int GetTargetPlatform(out int platform) - { - return getTargetPlatform(out platform); - } - public int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span buffer) { return getThreadContext(threadId, contextFlags, contextSize, buffer); diff --git a/src/native/managed/cdacreader/inc/cdac_reader.h b/src/native/managed/cdacreader/inc/cdac_reader.h index 9bf5ddb409090e..aa011df71487d7 100644 --- a/src/native/managed/cdacreader/inc/cdac_reader.h +++ b/src/native/managed/cdacreader/inc/cdac_reader.h @@ -13,14 +13,12 @@ extern "C" // descriptor: the address of the descriptor in the target process // read_from_target: a callback that reads memory from the target process // read_thread_context: a callback that reads the context of a thread in the target process -// get_platform: a callback that reads the platform of the target process // read_context: a context pointer that will be passed to callbacks // handle: returned opaque the handle to the reader. This should be passed to other functions in this API. int cdac_reader_init( uint64_t descriptor, int(*read_from_target)(uint64_t, uint8_t*, uint32_t, void*), int(*read_thread_context)(uint32_t, uint32_t, uint32_t, uint8_t*, void*), - int(*get_platform)(uint32_t*, void*), void* read_context, /*out*/ intptr_t* handle); diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index c884f87bd3a691..42a534cb2bba5e 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -17,7 +17,6 @@ private static unsafe int Init( ulong descriptor, delegate* unmanaged readFromTarget, delegate* unmanaged readThreadContext, - delegate* unmanaged getPlatform, void* readContext, IntPtr* handle) { @@ -38,13 +37,6 @@ private static unsafe int Init( return readThreadContext(threadId, contextFlags, contextSize, bufferPtr, readContext); } }, - (out int platform) => - { - fixed (int* platformPtr = &platform) - { - return getPlatform(platformPtr, readContext); - } - }, out ContractDescriptorTarget? target)) return -1; @@ -95,6 +87,7 @@ private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTar ComWrappers cw = new StrategyBasedComWrappers(); object obj = cw.GetOrCreateObjectForComInstance(pLegacyTarget, CreateObjectFlags.None); + ICLRDataTarget dataTarget = obj as ICLRDataTarget ?? throw new ArgumentException($"pLegacyTarget does not implement ${nameof(ICLRDataTarget)}", nameof(pLegacyTarget)); ICLRContractLocator contractLocator = obj as ICLRContractLocator ?? throw new ArgumentException($"pLegacyTarget does not implement ${nameof(ICLRContractLocator)}", nameof(pLegacyTarget)); @@ -121,30 +114,6 @@ private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTar return dataTarget.GetThreadContext(threadId, contextFlags, contextSize, bufferPtr); } }, - (out platform) => - { - platform = 0; - uint machineType; - int hr = dataTarget.GetMachineType(&machineType); - switch (machineType) - { - // ICLRDataTarget can not be used to find OS. For now labeling all platforms as Windows - // - case 0x014c: // IMAGE_FILE_MACHINE_I386 - platform = (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_X86; - break; - case 0x8664: // IMAGE_FILE_MACHINE_AMD64 - platform = (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64; - break; - case 0x01c4: // IMAGE_FILE_MACHINE_ARMNT - platform = (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM; - break; - case 0xAA64: // IMAGE_FILE_MACHINE_ARM64 - platform = (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64; - break; - } - return hr; - }, out ContractDescriptorTarget? target)) { return -1; diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 6a5ca9328f39ee..f46b4f4c9c9283 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -29,7 +29,6 @@ public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegat { IsLittleEndian = arch.IsLittleEndian; PointerSize = arch.Is64Bit ? 8 : 4; - Platform = Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64; _contractRegistry = new Mock().Object; _dataCache = new DefaultDataCache(this); _typeInfoCache = types ?? []; @@ -44,7 +43,6 @@ internal void SetContracts(ContractRegistry contracts) public override int PointerSize { get; } public override bool IsLittleEndian { get; } - public override CorDebugPlatform Platform { get; } public override bool IsAlignedToPointerSize(TargetPointer pointer) { From 95433f2d4555dcf8fe1fd8360904e518179b0f46 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 27 Mar 2025 16:38:27 -0400 Subject: [PATCH 04/33] remove modifiers --- .../Contracts/IRuntimeInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs index 1784bcba558859..b984fdd7b3a207 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs @@ -26,8 +26,8 @@ public enum RuntimeInfoOperatingSystem : uint public interface IRuntimeInfo : IContract { static string IContract.Name { get; } = nameof(RuntimeInfo); - public virtual RuntimeInfoArchitecture GetTargetArchitecture() => throw new NotImplementedException(); - public virtual RuntimeInfoOperatingSystem GetTargetOperatingSystem() => throw new NotImplementedException(); + RuntimeInfoArchitecture GetTargetArchitecture() => throw new NotImplementedException(); + RuntimeInfoOperatingSystem GetTargetOperatingSystem() => throw new NotImplementedException(); } public readonly struct RuntimeInfo : IRuntimeInfo From bae1e057baa18abdd9cd400bc5869fac33e4cae4 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 28 Mar 2025 13:21:22 -0400 Subject: [PATCH 05/33] add RuntimeInfo doc --- docs/design/datacontracts/RuntimeInfo.md | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/design/datacontracts/RuntimeInfo.md diff --git a/docs/design/datacontracts/RuntimeInfo.md b/docs/design/datacontracts/RuntimeInfo.md new file mode 100644 index 00000000000000..298feeca2cf4a9 --- /dev/null +++ b/docs/design/datacontracts/RuntimeInfo.md @@ -0,0 +1,43 @@ +# Contract RuntimeInfo + +This contract encapsulates support for fetching information about the target runtime. + +## APIs of contract + +```csharp +public enum RuntimeInfoArchitecture : uint +{ + Unknown = 0, + X86, + Arm32, + Amd64, + Arm64, + LoongArch64, + RISCV, +} + +public enum RuntimeInfoOperatingSystem : uint +{ + Unknown = 0, + Windows, + Unix, +} +``` + +```csharp +// Gets the targets architecture. If this information is not available returns Unknown. +RuntimeInfoArchitecture GetTargetArchitecture(); + +// Gets the targets operating system. If this information is not available returns Unknown. +RuntimeInfoOperatingSystem GetTargetOperatingSystem(); +``` + +## Version 1 + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| Architecture | `RuntimeInfoArchitecture` enum value (`uint32`) | Target architecture | +| OperatingSystem | `RuntimeInfoOperatingSystem` enum value (`uint32`) | Target operating system | + +The contract implementation simply returns the contract descriptor global values. If these globals are not available, the contract returns Unknown. From 5e06e14b69635420bdde93c2af4cb4059e6bba8a Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 28 Mar 2025 13:38:15 -0400 Subject: [PATCH 06/33] improve error messages --- src/native/managed/cdacreader/src/Entrypoints.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index 42a534cb2bba5e..f8528ec440293e 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -88,13 +88,14 @@ private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTar ComWrappers cw = new StrategyBasedComWrappers(); object obj = cw.GetOrCreateObjectForComInstance(pLegacyTarget, CreateObjectFlags.None); - ICLRDataTarget dataTarget = obj as ICLRDataTarget ?? throw new ArgumentException($"pLegacyTarget does not implement ${nameof(ICLRDataTarget)}", nameof(pLegacyTarget)); - ICLRContractLocator contractLocator = obj as ICLRContractLocator ?? throw new ArgumentException($"pLegacyTarget does not implement ${nameof(ICLRContractLocator)}", nameof(pLegacyTarget)); + ICLRDataTarget dataTarget = obj as ICLRDataTarget ?? throw new ArgumentException($"{nameof(pLegacyTarget)} does not implement {nameof(ICLRDataTarget)}", nameof(pLegacyTarget)); + ICLRContractLocator contractLocator = obj as ICLRContractLocator ?? throw new ArgumentException($"{nameof(pLegacyTarget)} does not implement {nameof(ICLRContractLocator)}", nameof(pLegacyTarget)); ulong contractAddress; - if (contractLocator.GetContractDescriptor(&contractAddress) != 0) + int hr = contractLocator.GetContractDescriptor(&contractAddress); + if (hr != 0) { - throw new InvalidOperationException("Unable to retrieve contract address from ICLRContractLocator"); + throw new InvalidOperationException($"{nameof(ICLRContractLocator)} failed to fetch the contract descriptor with HRESULT: 0x{hr:x}."); } if (!ContractDescriptorTarget.TryCreate( @@ -124,7 +125,7 @@ private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTar Marshal.QueryInterface(ccw, *pIID, out nint ptrToIface); *iface = (void*)ptrToIface; - // Decrement reference count on ccw because QI increments it + // Decrement reference count on ccw because QI incremented it Marshal.Release(ccw); return 0; From 7fec5a468b13667a9d26032853cbecef0769bcde Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 28 Mar 2025 13:45:31 -0400 Subject: [PATCH 07/33] improve fetching runtimeinfo globals --- .../Constants.cs | 3 +++ .../Contracts/RuntimeInfo_1.cs | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 0e76ef91d7de39..ad050a57835632 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -56,6 +56,9 @@ public static class Globals public const string HashMapSlotsPerBucket = nameof(HashMapSlotsPerBucket); public const string HashMapValueMask = nameof(HashMapValueMask); + + public const string Architecture = nameof(Architecture); + public const string OperatingSystem = nameof(OperatingSystem); } public static class FieldNames { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs index 0e4c4999c63588..e414ff988dd71a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs @@ -2,16 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal struct RuntimeInfo_1 : IRuntimeInfo { - private const string ArchitectureGlobalName = "Architecture"; - private const string OperatingSystemGlobalName = "OperatingSystem"; - internal readonly Target _target; public RuntimeInfo_1(Target target) @@ -21,9 +16,12 @@ public RuntimeInfo_1(Target target) RuntimeInfoArchitecture IRuntimeInfo.GetTargetArchitecture() { - if (_target.TryReadGlobal(ArchitectureGlobalName, out uint? arch)) + if (_target.TryReadGlobal(Constants.Globals.Architecture, out uint? arch)) { - return (RuntimeInfoArchitecture)arch; + if (Enum.IsDefined(typeof(RuntimeInfoArchitecture), arch)) + { + return (RuntimeInfoArchitecture)arch; + } } return RuntimeInfoArchitecture.Unknown; @@ -31,9 +29,12 @@ RuntimeInfoArchitecture IRuntimeInfo.GetTargetArchitecture() RuntimeInfoOperatingSystem IRuntimeInfo.GetTargetOperatingSystem() { - if (_target.TryReadGlobal(OperatingSystemGlobalName, out uint? os)) + if (_target.TryReadGlobal(Constants.Globals.OperatingSystem, out uint? os)) { - return (RuntimeInfoOperatingSystem)os; + if (Enum.IsDefined(typeof(RuntimeInfoOperatingSystem), os)) + { + return (RuntimeInfoOperatingSystem)os; + } } return RuntimeInfoOperatingSystem.Unknown; From 26bea192990b00ada8759f596df07b4cca3cbdd7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 28 Mar 2025 14:23:33 -0400 Subject: [PATCH 08/33] fix ContractDescriptorBuilder --- .../ContractDescriptor/ContractDescriptorBuilder.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index ff941454038e43..4c9fdef137037f 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -168,13 +168,6 @@ public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? ta throw new InvalidOperationException("Context already created"); ulong contractDescriptorAddress = CreateDescriptorFragments(); MockMemorySpace.ReadContext context = GetReadContext(); - ContractDescriptorTarget.GetTargetPlatformDelegate getTargetPlatform = (out int platform) => - { - platform = TargetTestHelpers.Arch.Is64Bit ? - (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64 : - (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_X86; - return 0; - }; - return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, getTargetPlatform, out target); + return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, out target); } } From 20089c19374bd0d695d62d352a43067822021870 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 11:21:00 -0400 Subject: [PATCH 09/33] add RID define --- src/coreclr/debug/runtimeinfo/CMakeLists.txt | 5 +++++ src/coreclr/debug/runtimeinfo/configure.h.in | 6 ++++++ src/coreclr/debug/runtimeinfo/datadescriptor.cpp | 5 +++++ src/coreclr/runtime.proj | 1 + 4 files changed, 17 insertions(+) create mode 100644 src/coreclr/debug/runtimeinfo/configure.h.in diff --git a/src/coreclr/debug/runtimeinfo/CMakeLists.txt b/src/coreclr/debug/runtimeinfo/CMakeLists.txt index 791e0ec370479d..d59184a148e184 100644 --- a/src/coreclr/debug/runtimeinfo/CMakeLists.txt +++ b/src/coreclr/debug/runtimeinfo/CMakeLists.txt @@ -42,6 +42,11 @@ install_clr(TARGETS runtimeinfo DESTINATIONS lib COMPONENT runtime) # cDAC contract descriptor +if("${CLI_CMAKE_RID}" STREQUAL "") + message(FATAL_ERROR "CLI_CMAKE_RID is not set. Please set it to the RID of the target platform.") +endif() +configure_file(configure.h.in ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + if (NOT CDAC_BUILD_TOOL_BINARY_PATH) # if CDAC_BUILD_TOOL_BINARY_PATH is unspecified (for example for a build without a .NET SDK or msbuild), # link a stub contract descriptor into the runtime diff --git a/src/coreclr/debug/runtimeinfo/configure.h.in b/src/coreclr/debug/runtimeinfo/configure.h.in new file mode 100644 index 00000000000000..292e1579fc5009 --- /dev/null +++ b/src/coreclr/debug/runtimeinfo/configure.h.in @@ -0,0 +1,6 @@ +#ifndef RUNTIME_INFO_CONFIGURE_H_INCLUDED +#define RUNTIME_INFO_CONFIGURE_H_INCLUDED + +#define RID_STRING @CLI_CMAKE_RID@ + +#endif // RUNTIME_INFO_CONFIGURE_H_INCLUDED diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index c4d0aa3d42b645..8a924bc931d7db 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -13,6 +13,8 @@ #include "methodtable.h" #include "threads.h" +#include "configure.h" + #include "../debug/ee/debugger.h" #ifdef HAVE_GCCOVER @@ -60,6 +62,9 @@ struct GlobalPointerSpec #define MAKE_GLOBALLEN_NAME(globalname) CONCAT(cdac_string_pool_globalname__, globalname) #define MAKE_GLOBALTYPELEN_NAME(globalname) CONCAT(cdac_string_pool_globaltypename__, globalname) +// used to stringify the result of a macros expansion +#define STRINGIFY(x) #x + // define a struct where the size of each field is the length of some string. we will use offsetof to get // the offset of each struct element, which will be equal to the offset of the beginning of that string in the // string pool. diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj index 0055b2836672f8..f5152ab97ffeed 100644 --- a/src/coreclr/runtime.proj +++ b/src/coreclr/runtime.proj @@ -47,6 +47,7 @@ <_CoreClrBuildArg Condition="'$(EnableNativeSanitizers)' != ''" Include="-fsanitize $(EnableNativeSanitizers)" /> <_CoreClrBuildArg Condition="'$(HostCrossOS)' != ''" Include="-hostos $(HostCrossOS)" /> <_CoreClrBuildArg Include="-outputrid $(OutputRID)" /> + <_CoreClrBuildArg Include="-cmakeargs "-DCLI_CMAKE_RID=$(OutputRID)"" /> <_CoreClrBuildArg Condition="'$(BuildSubdirectory)' != ''" Include="-subdir $(BuildSubdirectory)" /> <_CoreClrBuildArg Include="-cmakeargs "-DCLR_DOTNET_HOST_PATH=$(DOTNET_HOST_PATH)"" /> <_CoreClrBuildArg Condition="'$(HasCdacBuildTool)' == 'true'" Include="-cmakeargs "-DCDAC_BUILD_TOOL_BINARY_PATH=$(RuntimeBinDir)cdac-build-tool\cdac-build-tool.dll"" /> From 1e6ed61fb6c35401b59de89fca8a25ab87c3c115 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 11:21:14 -0400 Subject: [PATCH 10/33] change runtimeinfo datastructures to be strings --- .../debug/runtimeinfo/datadescriptor.h | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 86dfa26747a5fd..8629c1d08e9609 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -758,30 +758,32 @@ CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() -// RuntimeInfo OperatingSystem -#ifdef TARGET_UNIX -CDAC_GLOBAL(OperatingSystem, uint32, 2) // RuntimeInfoOperatingSystem.Unix -#else // !TARGET_UNIX -CDAC_GLOBAL(OperatingSystem, uint32, 1) // RuntimeInfoOperatingSystem.Windows -#endif // !TARGET_UNIX - -// RuntimeInfo Architecture +#if defined(TARGET_UNIX) +CDAC_GLOBAL_STRING(OperatingSystem, unix) +#elif defined(TARGET_WINDOWS) +CDAC_GLOBAL_STRING(OperatingSystem, win) +#else +#error Unknown OperatingSystem. +#endif + #if defined(TARGET_X86) -CDAC_GLOBAL(Architecture, uint32, 1) // RuntimeInfoArchitecture.X86 +CDAC_GLOBAL_STRING(Architecture, x86) #elif defined(TARGET_AMD64) -CDAC_GLOBAL(Architecture, uint32, 3) // RuntimeInfoArchitecture.Amd64 +CDAC_GLOBAL_STRING(Architecture, x64) #elif defined(TARGET_ARM) -CDAC_GLOBAL(Architecture, uint32, 2) // RuntimeInfoArchitecture.Arm32 +CDAC_GLOBAL_STRING(Architecture, arm) #elif defined(TARGET_ARM64) -CDAC_GLOBAL(Architecture, uint32, 4) // RuntimeInfoArchitecture.Arm64 +CDAC_GLOBAL_STRING(Architecture, arm64) #elif defined(TARGET_LOONGARCH64) -CDAC_GLOBAL(Architecture, uint32, 5) // RuntimeInfoArchitecture.LoongArch64 +CDAC_GLOBAL_STRING(Architecture, loongarch64) #elif defined(TARGET_RISCV64) -CDAC_GLOBAL(Architecture, uint32, 6) // RuntimeInfoArchitecture.RISCV +CDAC_GLOBAL_STRING(Architecture, riscv64) #else -#error Unknown Processor. +#error Unknown Architecture. #endif +CDAC_GLOBAL_STRING(RID, RID_STRING) + CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) From 5febe9b221c8465841de04cf94b2c7ca52a3e017 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 11:21:33 -0400 Subject: [PATCH 11/33] fix managed portions of runtime info --- .../Contracts/IRuntimeInfo.cs | 7 +- .../Contracts/RuntimeInfo_1.cs | 16 +-- .../Context/IPlatformAgnosticContext.cs | 2 +- .../cdacreader/tests/RuntimeInfoTests.cs | 102 ++++++++++++++++++ 4 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 src/native/managed/cdacreader/tests/RuntimeInfoTests.cs diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs index b984fdd7b3a207..cc5550ae52d8fd 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs @@ -10,7 +10,7 @@ public enum RuntimeInfoArchitecture : uint Unknown = 0, X86, Arm32, - Amd64, + X64, Arm64, LoongArch64, RISCV, @@ -19,7 +19,7 @@ public enum RuntimeInfoArchitecture : uint public enum RuntimeInfoOperatingSystem : uint { Unknown = 0, - Windows, + Win, Unix, } @@ -32,6 +32,5 @@ public interface IRuntimeInfo : IContract public readonly struct RuntimeInfo : IRuntimeInfo { - RuntimeInfoArchitecture IRuntimeInfo.GetTargetArchitecture() => RuntimeInfoArchitecture.Unknown; - RuntimeInfoOperatingSystem IRuntimeInfo.GetTargetOperatingSystem() => RuntimeInfoOperatingSystem.Unknown; + } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs index e414ff988dd71a..5b6e3508bdc360 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs @@ -14,26 +14,26 @@ public RuntimeInfo_1(Target target) _target = target; } - RuntimeInfoArchitecture IRuntimeInfo.GetTargetArchitecture() + readonly RuntimeInfoArchitecture IRuntimeInfo.GetTargetArchitecture() { - if (_target.TryReadGlobal(Constants.Globals.Architecture, out uint? arch)) + if (_target.TryReadGlobalString(Constants.Globals.Architecture, out string? arch)) { - if (Enum.IsDefined(typeof(RuntimeInfoArchitecture), arch)) + if (Enum.TryParse(arch, ignoreCase: true, out RuntimeInfoArchitecture parsedArch)) { - return (RuntimeInfoArchitecture)arch; + return parsedArch; } } return RuntimeInfoArchitecture.Unknown; } - RuntimeInfoOperatingSystem IRuntimeInfo.GetTargetOperatingSystem() + readonly RuntimeInfoOperatingSystem IRuntimeInfo.GetTargetOperatingSystem() { - if (_target.TryReadGlobal(Constants.Globals.OperatingSystem, out uint? os)) + if (_target.TryReadGlobalString(Constants.Globals.OperatingSystem, out string? os)) { - if (Enum.IsDefined(typeof(RuntimeInfoOperatingSystem), os)) + if (Enum.TryParse(os, ignoreCase: true, out RuntimeInfoOperatingSystem parsedOS)) { - return (RuntimeInfoOperatingSystem)os; + return parsedOS; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index 3254b33987954d..cf9aac6ac54507 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -28,7 +28,7 @@ public static IPlatformAgnosticContext GetContextForPlatform(Target target) IRuntimeInfo runtimeInfo = target.Contracts.RuntimeInfo; return runtimeInfo.GetTargetArchitecture() switch { - RuntimeInfoArchitecture.Amd64 => new ContextHolder(), + RuntimeInfoArchitecture.X64 => new ContextHolder(), RuntimeInfoArchitecture.Arm64 => new ContextHolder(), RuntimeInfoArchitecture.Unknown => throw new InvalidOperationException($"Processor architecture is required for creating a platform specific context and is not provided by the target"), _ => throw new InvalidOperationException($"Unsupported architecture {runtimeInfo.GetTargetArchitecture()}"), diff --git a/src/native/managed/cdacreader/tests/RuntimeInfoTests.cs b/src/native/managed/cdacreader/tests/RuntimeInfoTests.cs new file mode 100644 index 00000000000000..01130068d7001e --- /dev/null +++ b/src/native/managed/cdacreader/tests/RuntimeInfoTests.cs @@ -0,0 +1,102 @@ +// 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.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class RuntimeInfoTests +{ + internal static Target CreateTarget( + MockTarget.Architecture arch, + (string Name, string Value)[] globalStrings) + { + MockMemorySpace.Builder builder = new MockMemorySpace.Builder(new TargetTestHelpers(arch)); + TestPlaceholderTarget target = new TestPlaceholderTarget(arch, builder.GetReadContext().ReadFromTarget, [], [], globalStrings); + + IContractFactory runtimeInfoFactory = new RuntimeInfoFactory(); + + ContractRegistry reg = Mock.Of( + c => c.RuntimeInfo == runtimeInfoFactory.CreateContract(target, 1)); + target.SetContracts(reg); + return target; + } + + public static IEnumerable StdArchAllTargetArchitectures() + { + foreach(object[] arr in new MockTarget.StdArch()) + { + MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; + + foreach(RuntimeInfoArchitecture targetArch in (RuntimeInfoArchitecture[])Enum.GetValues(typeof(RuntimeInfoArchitecture))) + { + // Skip Unknown architecture + if (targetArch == RuntimeInfoArchitecture.Unknown) + continue; + + yield return new object[] { arch, targetArch.ToString().ToLowerInvariant(), targetArch }; + } + + yield return new object[] { arch, "notATargetArch", RuntimeInfoArchitecture.Unknown }; + } + } + + [Theory] + [MemberData(nameof(StdArchAllTargetArchitectures))] + public void GetTargetArchitectureTest( + MockTarget.Architecture arch, + string architecture, + RuntimeInfoArchitecture expectedArchitecture) + { + var target = CreateTarget(arch, [(Constants.Globals.Architecture, architecture)]); + + // TEST + + var runtimeInfo = target.Contracts.RuntimeInfo; + Assert.NotNull(runtimeInfo); + + var actualArchitecture = runtimeInfo.GetTargetArchitecture(); + Assert.Equal(expectedArchitecture, actualArchitecture); + } + + public static IEnumerable StdArchAllTargetOS() + { + foreach(object[] arr in new MockTarget.StdArch()) + { + MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; + + foreach(RuntimeInfoOperatingSystem targetArch in (RuntimeInfoOperatingSystem[])Enum.GetValues(typeof(RuntimeInfoOperatingSystem))) + { + // Skip Unknown architecture + if (targetArch == RuntimeInfoOperatingSystem.Unknown) + continue; + + yield return new object[] { arch, targetArch.ToString().ToLowerInvariant(), targetArch }; + } + + yield return new object[] { arch, "notAnOperatingSystem", RuntimeInfoOperatingSystem.Unknown }; + } + } + + [Theory] + [MemberData(nameof(StdArchAllTargetOS))] + public void GetTargetOperatingSystemTest( + MockTarget.Architecture arch, + string os, + RuntimeInfoOperatingSystem expectedOS) + { + var target = CreateTarget(arch, [(Constants.Globals.OperatingSystem, os)]); + + // TEST + + var runtimeInfo = target.Contracts.RuntimeInfo; + Assert.NotNull(runtimeInfo); + + var actualArchitecture = runtimeInfo.GetTargetOperatingSystem(); + Assert.Equal(expectedOS, actualArchitecture); + } +} From 717300dd534beaeaca746cdffa459e21363d118f Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 15:35:39 -0400 Subject: [PATCH 12/33] implement runtime/cdac-built-tool portion of passing strings --- .../datacontracts/contract-descriptor.md | 3 +- docs/design/datacontracts/data_descriptor.md | 17 +- .../debug/runtimeinfo/datadescriptor.cpp | 32 ++ .../debug/runtimeinfo/datadescriptor.h | 4 + .../cdac-build-tool/DataDescriptorModel.cs | 43 ++- .../JsonConverter/GlobalValueJsonConverter.cs | 29 +- .../cdac-build-tool/ObjectFileScraper.cs | 48 +++ .../cdac-build-tool/data-descriptor-blob.md | 18 +- .../cdac-build-tool/sample/sample.blob.c | 299 ++---------------- .../cdac-build-tool/sample/sample.data.h | 59 ++++ 10 files changed, 253 insertions(+), 299 deletions(-) diff --git a/docs/design/datacontracts/contract-descriptor.md b/docs/design/datacontracts/contract-descriptor.md index fbd58eb33eb9a5..b99150c78becdd 100644 --- a/docs/design/datacontracts/contract-descriptor.md +++ b/docs/design/datacontracts/contract-descriptor.md @@ -83,7 +83,8 @@ a JSON integer constant. "globals": { "FEATURE_COMINTEROP": 0, - "s_pThreadStore": [ 0 ] // indirect from pointer data offset 0 + "s_pThreadStore": [ 0 ], // indirect from pointer data offset 0 + "RuntimeID": "windows-x64" // string value }, "contracts": {"Thread": 1, "GCHandle": 1, "ThreadStore": 1} } diff --git a/docs/design/datacontracts/data_descriptor.md b/docs/design/datacontracts/data_descriptor.md index 1338e1ae87aa60..06b3fa2bfc7f60 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -212,11 +212,11 @@ The global values will be in an array, with each value described by a dictionary * `"name": "global value name"` the name of the global value * `"type": "type name"` the type of the global value -* optional `"value": VALUE | [ int ] | "unknown"` the value of the global value, or an offset in an auxiliary array containing the value or "unknown". +* optional `"value": VALUE | [ int ] ` the value of the global value, or an offset in an auxiliary array containing the value. + +The `VALUE` may be either a number of string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. +Numeric constants must be within the range of the type of the global value. -The `VALUE` may be a JSON numeric constant integer or a string containing a signed or unsigned -decimal or hex (with prefix `0x` or `0X`) integer constant. The constant must be within the range -of the type of the global value. **Compact format**: @@ -225,7 +225,8 @@ The global values will be in a dictionary, with each key being the name of a glo * `[VALUE | [int], "type name"]` the type and value of a global * `VALUE | [int]` just the value of a global -As in the regular format, `VALUE` is a numeric constant or a string containing an integer constant. +`VALUE` may be either a number of string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. +Numeric constants must be within the range of the type of the global value. Note that a two element array is unambiguously "type and value", whereas a one-element array is unambiguously "indirect value". @@ -288,7 +289,7 @@ The baseline is given in the "regular" format. ], "globals": [ { "name": "FEATURE_EH_FUNCLETS", "type": "uint8", "value": "0" }, // baseline defaults value to 0 - { "name": "FEATURE_COMINTEROP", "type", "uint8", "value": "1"}, + { "name": "FEATURE_COMINTEROP", "type": "uint8", "value": "1"}, { "name": "s_pThreadStore", "type": "pointer" } // no baseline value ] } @@ -308,7 +309,8 @@ The following is an example of an in-memory descriptor that references the above "globals": { "FEATURE_COMINTEROP": 0, - "s_pThreadStore": [ 0 ] // indirect from aux data offset 0 + "s_pThreadStore": [ 0 ], // indirect from aux data offset 0 + "RuntimeID": "windows-x64" } } ``` @@ -332,6 +334,7 @@ And the globals will be: | FEATURE_COMINTEROP | uint8 | 0 | | FEATURE_EH_FUNCLETS | uint8 | 0 | | s_pThreadStore | pointer | 0x0100ffe0 | +| RuntimeID | string |"windows-x64"| The `FEATURE_EH_FUNCLETS` global's value comes from the baseline - not the in-memory data descriptor. By contrast, `FEATURE_COMINTEROP` comes from the in-memory data descriptor - with the diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index 8a924bc931d7db..d6e570578431f4 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -53,6 +53,12 @@ struct GlobalPointerSpec uint32_t PointerDataIndex; }; +struct GlobalStringSpec +{ + uint32_t Name; + uint32_t StringValue; +}; + #define CONCAT(token1,token2) token1 ## token2 #define CONCAT4(token1, token2, token3, token4) token1 ## token2 ## token3 ## token4 @@ -61,6 +67,7 @@ struct GlobalPointerSpec #define MAKE_FIELDTYPELEN_NAME(tyname,membername) CONCAT4(cdac_string_pool_membertypename__, tyname, __, membername) #define MAKE_GLOBALLEN_NAME(globalname) CONCAT(cdac_string_pool_globalname__, globalname) #define MAKE_GLOBALTYPELEN_NAME(globalname) CONCAT(cdac_string_pool_globaltypename__, globalname) +#define MAKE_GLOBALVALUELEN_NAME(globalname) CONCAT(cdac_string_pool_globalvalue__, globalname) // used to stringify the result of a macros expansion #define STRINGIFY(x) #x @@ -76,6 +83,8 @@ struct CDacStringPoolSizes #define CDAC_TYPE_BEGIN(name) DECL_LEN(MAKE_TYPELEN_NAME(name), sizeof(#name)) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(MAKE_FIELDLEN_NAME(tyname,membername), sizeof(#membername)) \ DECL_LEN(MAKE_FIELDTYPELEN_NAME(tyname,membername), sizeof(#membertyname)) +#define CDAC_GLOBAL_STRING(name, stringval) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ + DECL_LEN(MAKE_GLOBALVALUELEN_NAME(name), sizeof(#stringval)) #define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) #define CDAC_GLOBAL(name,tyname,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ DECL_LEN(MAKE_GLOBALTYPELEN_NAME(name), sizeof(#tyname)) @@ -89,6 +98,7 @@ struct CDacStringPoolSizes #define GET_FIELDTYPE_NAME(tyname,membername) offsetof(struct CDacStringPoolSizes, MAKE_FIELDTYPELEN_NAME(tyname,membername)) #define GET_GLOBAL_NAME(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALLEN_NAME(globalname)) #define GET_GLOBALTYPE_NAME(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALTYPELEN_NAME(globalname)) +#define GET_GLOBALSTRING_VALUE(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALVALUELEN_NAME(globalname)) // count the types enum @@ -128,6 +138,15 @@ enum #include "datadescriptor.h" }; +// count the global strings +enum +{ + CDacBlobGlobalStringsCount = +#define CDAC_GLOBALS_BEGIN() 0 +#define CDAC_GLOBAL_STRING(name,value) + 1 +#include "datadescriptor.h" +}; + #define MAKE_TYPEFIELDS_TYNAME(tyname) CONCAT(CDacFieldsPoolTypeStart__, tyname) @@ -202,6 +221,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesStart; uint32_t GlobalPointersStart; + uint32_t GlobalStringValuesStart; uint32_t NamesPoolStart; uint32_t TypeCount; @@ -209,6 +229,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesCount; uint32_t GlobalPointerValuesCount; + uint32_t GlobalStringValuesCount; uint32_t NamesPoolCount; @@ -216,6 +237,7 @@ struct BinaryBlobDataDescriptor uint8_t FieldSpecSize; uint8_t GlobalLiteralSpecSize; uint8_t GlobalPointerSpecSize; + uint8_t GlobalStringSpecSize; } Directory; uint32_t PlatformFlags; uint32_t BaselineName; @@ -223,6 +245,7 @@ struct BinaryBlobDataDescriptor struct FieldSpec FieldsPool[CDacBlobFieldsPoolCount]; struct GlobalLiteralSpec GlobalLiteralValues[CDacBlobGlobalLiteralsCount]; struct GlobalPointerSpec GlobalPointerValues[CDacBlobGlobalPointersCount]; + struct GlobalStringSpec GlobalStringValues[CDacBlobGlobalStringsCount]; uint8_t NamesPool[sizeof(struct CDacStringPoolSizes)]; uint8_t EndMagic[4]; }; @@ -247,16 +270,19 @@ struct MagicAndBlob BlobDataDescriptor = { /* .FieldsPoolStart = */ offsetof(struct BinaryBlobDataDescriptor, FieldsPool), /* .GlobalLiteralValuesStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalLiteralValues), /* .GlobalPointersStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalPointerValues), + /* .GlobalStringValuesStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalStringValues), /* .NamesPoolStart = */ offsetof(struct BinaryBlobDataDescriptor, NamesPool), /* .TypeCount = */ CDacBlobTypesCount, /* .FieldsPoolCount = */ CDacBlobFieldsPoolCount, /* .GlobalLiteralValuesCount = */ CDacBlobGlobalLiteralsCount, /* .GlobalPointerValuesCount = */ CDacBlobGlobalPointersCount, + /* .GlobalStringValuesCount = */ CDacBlobGlobalStringsCount, /* .NamesPoolCount = */ sizeof(struct CDacStringPoolSizes), /* .TypeSpecSize = */ sizeof(struct TypeSpec), /* .FieldSpecSize = */ sizeof(struct FieldSpec), /* .GlobalLiteralSpecSize = */ sizeof(struct GlobalLiteralSpec), /* .GlobalPointerSpecSize = */ sizeof(struct GlobalPointerSpec), + /* .GlobalStringSpecSize = */ sizeof(struct GlobalStringSpec) }, /* .PlatformFlags = */ (sizeof(void*) == 4 ? 0x02 : 0) | 0x01, /* .BaselineName = */ offsetof(struct CDacStringPoolSizes, cdac_string_pool_baseline_), @@ -292,10 +318,16 @@ struct MagicAndBlob BlobDataDescriptor = { #include "datadescriptor.h" }, + /* .GlobalStringValues = */ { +#define CDAC_GLOBAL_STRING(name,value) { /* .Name = */ GET_GLOBAL_NAME(name), /* .Value = */ GET_GLOBALSTRING_VALUE(name) }, +#include "datadescriptor.h" + }, + /* .NamesPool = */ ("\0" // starts with a nul #define CDAC_BASELINE(name) name "\0" #define CDAC_TYPE_BEGIN(name) #name "\0" #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #membername "\0" #membertyname "\0" +#define CDAC_GLOBAL_STRING(name,value) #name "\0" #value "\0" #define CDAC_GLOBAL_POINTER(name,value) #name "\0" #define CDAC_GLOBAL(name,tyname,value) #name "\0" #tyname "\0" #include "datadescriptor.h" diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 8629c1d08e9609..0224cecdcb2642 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -98,6 +98,9 @@ #ifndef CDAC_GLOBAL_POINTER #define CDAC_GLOBAL_POINTER(globalname,addr) #endif +#ifndef CDAC_GLOBAL_STRING +#define CDAC_GLOBAL_STRING(globalname,stringval) +#endif #ifndef CDAC_GLOBALS_END #define CDAC_GLOBALS_END() #endif @@ -858,4 +861,5 @@ CDAC_GLOBALS_END() #undef CDAC_GLOBALS_BEGIN #undef CDAC_GLOBAL #undef CDAC_GLOBAL_POINTER +#undef CDAC_GLOBAL_STRING #undef CDAC_GLOBALS_END diff --git a/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs b/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs index 55aa16283c4fa1..14ee0988ea2b8b 100644 --- a/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs +++ b/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs @@ -24,7 +24,7 @@ public class DataDescriptorModel public uint PlatformFlags { get; } // The number of indirect globals plus 1 for the placeholder at index 0 [JsonIgnore] - public int PointerDataCount => 1 + Globals.Values.Count(g => g.Value.Indirect); + public int PointerDataCount => 1 + Globals.Values.Count(g => g.Value.Kind == GlobalValue.KindEnum.Indirect); private DataDescriptorModel(string baseline, IReadOnlyDictionary types, IReadOnlyDictionary globals, IReadOnlyDictionary contracts, uint platformFlags) { @@ -36,6 +36,7 @@ private DataDescriptorModel(string baseline, IReadOnlyDictionary { - public bool Indirect { get; private init; } - public ulong Value { get; } - public static GlobalValue MakeDirect(ulong value) => new GlobalValue(value); - public static GlobalValue MakeIndirect(uint auxDataIdx) => new GlobalValue((ulong)auxDataIdx) { Indirect = true }; - private GlobalValue(ulong value) { Value = value; } + public enum KindEnum + { + Direct, + Indirect, + String + } + + public KindEnum Kind { get; private init; } + public ulong NumericValue { get; } + public string StringValue { get; } + public static GlobalValue MakeDirect(ulong value) => new GlobalValue(value) { Kind = KindEnum.Direct }; + public static GlobalValue MakeIndirect(uint auxDataIdx) => new GlobalValue((ulong)auxDataIdx) { Kind = KindEnum.Indirect }; + public static GlobalValue MakeString(string value) => new GlobalValue(value) { Kind = KindEnum.String }; + private GlobalValue(ulong value) { NumericValue = value; StringValue = string.Empty;} + private GlobalValue(string value) { StringValue = value; } - public static bool operator ==(GlobalValue left, GlobalValue right) => left.Value == right.Value && left.Indirect == right.Indirect; + public static bool operator ==(GlobalValue left, GlobalValue right) => left.Equals(right); public static bool operator !=(GlobalValue left, GlobalValue right) => !(left == right); - public bool Equals(GlobalValue other) => this == other; - public override bool Equals(object? obj) => obj is GlobalValue value && this == value; - public override int GetHashCode() => HashCode.Combine(Value, Indirect); - public override string ToString() => Indirect ? $"Indirect({Value})" : $"0x{Value:x}"; + public bool Equals(GlobalValue other) => other.Kind == Kind && other.NumericValue == NumericValue && other.StringValue == StringValue; + public override bool Equals(object? obj) => obj is GlobalValue value && Equals(value); + public override int GetHashCode() => HashCode.Combine(Kind, NumericValue, StringValue); + public override string ToString() + { + return Kind switch + { + KindEnum.Direct => $"0x{NumericValue:x}", + KindEnum.Indirect => $"Indirect({NumericValue})", + KindEnum.String => $"'{StringValue}'", + _ => throw new InvalidOperationException("Unknown GlobalValue type") + }; + } } [JsonConverter(typeof(GlobalModelJsonConverter))] diff --git a/src/coreclr/tools/cdac-build-tool/JsonConverter/GlobalValueJsonConverter.cs b/src/coreclr/tools/cdac-build-tool/JsonConverter/GlobalValueJsonConverter.cs index 429f6cc6979284..40ff3a67bab42e 100644 --- a/src/coreclr/tools/cdac-build-tool/JsonConverter/GlobalValueJsonConverter.cs +++ b/src/coreclr/tools/cdac-build-tool/JsonConverter/GlobalValueJsonConverter.cs @@ -15,18 +15,25 @@ public override DataDescriptorModel.GlobalValue Read(ref Utf8JsonReader reader, public override void Write(Utf8JsonWriter writer, DataDescriptorModel.GlobalValue value, JsonSerializerOptions options) { - if (!value.Indirect) + switch (value.Kind) { - // no type: just write value as a number. - // we always write as a string containing a hex number - writer.WriteStringValue($"0x{value.Value:x}"); - } - else - { - // pointer data index. write as a 1-element array containing a decimal number - writer.WriteStartArray(); - writer.WriteNumberValue(value.Value); - writer.WriteEndArray(); + case DataDescriptorModel.GlobalValue.KindEnum.Direct: + // no type: just write value as a number. + // we always write as a string containing a hex number + writer.WriteStringValue($"0x{value.NumericValue:x}"); + break; + case DataDescriptorModel.GlobalValue.KindEnum.Indirect: + // pointer data index. write as a 1-element array containing a decimal number + writer.WriteStartArray(); + writer.WriteNumberValue(value.NumericValue); + writer.WriteEndArray(); + break; + case DataDescriptorModel.GlobalValue.KindEnum.String: + // string data. write as a JSON string value + writer.WriteStringValue(value.StringValue); + break; + default: + throw new InvalidOperationException("Unknown GlobalValue type"); } } } diff --git a/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs b/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs index 24e7e2827d457b..1c379a7e61dd05 100644 --- a/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs +++ b/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs @@ -160,6 +160,7 @@ private struct HeaderDirectory public uint GlobalLiteralValuesStart; public uint GlobalPointersStart; + public uint GlobalStringValuesStart; public uint NamesStart; public uint TypesCount; @@ -167,6 +168,7 @@ private struct HeaderDirectory public uint GlobalLiteralValuesCount; public uint GlobalPointerValuesCount; + public uint GlobalStringValuesCount; public uint NamesPoolCount; @@ -174,6 +176,7 @@ private struct HeaderDirectory public byte FieldSpecSize; public byte GlobalLiteralSpecSize; public byte GlobalPointerSpecSize; + public byte GlobalStringSpecSize; }; private static void DumpHeaderDirectory(HeaderDirectory headerDirectory) @@ -186,12 +189,14 @@ private static void DumpHeaderDirectory(HeaderDirectory headerDirectory) Fields Pool Start = 0x{headerDirectory.FieldsPoolStart:x8} Global Literals Start = 0x{headerDirectory.GlobalLiteralValuesStart:x8} Global Pointers Start = 0x{headerDirectory.GlobalPointersStart:x8} + Global Strings Start = 0x{headerDirectory.GlobalStringValuesStart:x8} Names Pool Start = 0x{headerDirectory.NamesStart:x8} Types Count = {headerDirectory.TypesCount} Fields Pool Count = {headerDirectory.FieldsPoolCount} Global Literal Values Count = {headerDirectory.GlobalLiteralValuesCount} Global Pointer Values Count = {headerDirectory.GlobalPointerValuesCount} + Global String Values count = {headerDirectory.GlobalStringValuesCount} Names Pool Count = {headerDirectory.NamesPoolCount} """); @@ -207,6 +212,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) var globalLiteralValuesStart = state.ReadUInt32(); var globalPointersStart = state.ReadUInt32(); + var globalStringValuesStart = state.ReadUInt32(); var namesStart = state.ReadUInt32(); var typeCount = state.ReadUInt32(); @@ -214,6 +220,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) var globalLiteralValuesCount = state.ReadUInt32(); var globalPointerValuesCount = state.ReadUInt32(); + var GlobalStringValuesCount = state.ReadUInt32(); var namesPoolCount = state.ReadUInt32(); @@ -221,6 +228,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) var fieldSpecSize = state.ReadByte(); var globalLiteralSpecSize = state.ReadByte(); var globalPointerSpecSize = state.ReadByte(); + var globalStringSpecSize = state.ReadByte(); return new HeaderDirectory { FlagsAndBaselineStart = baselineStart, @@ -228,6 +236,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) FieldsPoolStart = fieldPoolStart, GlobalLiteralValuesStart = globalLiteralValuesStart, GlobalPointersStart = globalPointersStart, + GlobalStringValuesStart = globalStringValuesStart, NamesStart = namesStart, TypesCount = typeCount, @@ -235,6 +244,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) GlobalLiteralValuesCount = globalLiteralValuesCount, GlobalPointerValuesCount = globalPointerValuesCount, + GlobalStringValuesCount = GlobalStringValuesCount, NamesPoolCount = namesPoolCount, @@ -242,6 +252,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) FieldSpecSize = fieldSpecSize, GlobalLiteralSpecSize = globalLiteralSpecSize, GlobalPointerSpecSize = globalPointerSpecSize, + GlobalStringSpecSize = globalStringSpecSize, }; } @@ -280,6 +291,12 @@ private struct GlobalPointerSpec public uint AuxDataIdx; } + private struct GlobalStringSpec + { + public uint NameIdx; + public uint ValueIdx; + } + private sealed class Content { public required bool Verbose {get; init; } @@ -289,6 +306,7 @@ private sealed class Content public required IReadOnlyList FieldSpecs { get; init; } public required IReadOnlyList GlobaLiteralSpecs { get; init; } public required IReadOnlyList GlobalPointerSpecs { get; init; } + public required IReadOnlyList GlobalStringSpecs { get; init; } public required ReadOnlyMemory NamesPool { get; init; } internal string GetPoolString(uint stringIdx) @@ -360,6 +378,14 @@ public void AddToModel(DataDescriptorModel.Builder builder) builder.AddOrUpdateGlobal(globalName, DataDescriptorModel.PointerTypeName, globalValue); WriteVerbose($"Global pointer {globalName} has index {globalValue}"); } + + foreach (var globalString in GlobalStringSpecs) + { + var globalName = GetPoolString(globalString.NameIdx); + var globalValue = DataDescriptorModel.GlobalValue.MakeString(GetPoolString(globalString.ValueIdx)); + builder.AddOrUpdateGlobal(globalName, DataDescriptorModel.StringTypeName, globalValue); + WriteVerbose($"Global string {globalName} has value {globalValue}"); + } } private void WriteVerbose(string msg) @@ -381,6 +407,7 @@ private Content ReadContent(ScraperState state, HeaderDirectory header) FieldSpec[] fieldSpecs = ReadFieldSpecs(state, header); GlobalLiteralSpec[] globalLiteralSpecs = ReadGlobalLiteralSpecs(state, header); GlobalPointerSpec[] globalPointerSpecs = ReadGlobalPointerSpecs(state, header); + GlobalStringSpec[] globalStringSpecs = ReadGlobalStringSpecs(state, header); byte[] namesPool = ReadNamesPool(state, header); byte[] endMagic = new byte[4]; @@ -406,6 +433,7 @@ private Content ReadContent(ScraperState state, HeaderDirectory header) FieldSpecs = fieldSpecs, GlobaLiteralSpecs = globalLiteralSpecs, GlobalPointerSpecs = globalPointerSpecs, + GlobalStringSpecs = globalStringSpecs, NamesPool = namesPool }; } @@ -502,6 +530,26 @@ private static GlobalPointerSpec[] ReadGlobalPointerSpecs(ScraperState state, He return globalSpecs; } + private static GlobalStringSpec[] ReadGlobalStringSpecs(ScraperState state, HeaderDirectory header) + { + GlobalStringSpec[] globalSpecs = new GlobalStringSpec[header.GlobalStringValuesCount]; + state.ResetPosition(state.HeaderStart + (long)header.GlobalStringValuesStart); + for (int i = 0; i < header.GlobalStringValuesCount; i++) + { + int bytesRead = 0; + globalSpecs[i].NameIdx = state.ReadUInt32(); + bytesRead += sizeof(uint); + globalSpecs[i].ValueIdx = state.ReadUInt32(); + bytesRead += sizeof(uint); + // skip padding + if (bytesRead < header.GlobalStringSpecSize) + { + state.Skip(header.GlobalStringSpecSize - bytesRead); + } + } + return globalSpecs; + } + private static byte[] ReadNamesPool(ScraperState state, HeaderDirectory header) { byte[] namesPool = new byte[header.NamesPoolCount]; diff --git a/src/coreclr/tools/cdac-build-tool/data-descriptor-blob.md b/src/coreclr/tools/cdac-build-tool/data-descriptor-blob.md index b7321edd12c991..f04765d9f13db4 100644 --- a/src/coreclr/tools/cdac-build-tool/data-descriptor-blob.md +++ b/src/coreclr/tools/cdac-build-tool/data-descriptor-blob.md @@ -88,6 +88,14 @@ struct GlobalPointerSpec uint32_t Name; uint32_t PointerDataIndex; }; + +// A string global value. +// We record the name and the value, both as a string. +struct GlobalStringSpec +{ + uint32_t Name; + uint32_t valueIndex; +} ``` The main data we want to emit to the object file is an instance of the following structure: @@ -108,6 +116,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesStart; uint32_t GlobalPointersStart; + uint32_t GlobalStringValuesStart; uint32_t NamesStart; uint32_t TypeCount; @@ -115,6 +124,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesCount; uint32_t GlobalPointerValuesCount; + uint32_t GlobalStringValuesCount; uint32_t NamesPoolCount; @@ -122,6 +132,7 @@ struct BinaryBlobDataDescriptor uint8_t FieldSpecSize; uint8_t GlobalLiteralSpecSize; uint8_t GlobalPointerSpecSize; + uint8_t GlobalStringSpecSize; } Directory; // Platform flags (primarily pointer size) uint32_t PlatformFlags; @@ -136,6 +147,8 @@ struct BinaryBlobDataDescriptor struct GlobalLiteralSpec GlobalLiteralValues[CDacBlobGlobalLiteralsCount]; // an array of pointer globals struct GlobalPointerSpec GlobalPointerValues[CDacBlobGlobalPointersCount]; + // an array of string globals + struct GlobalStringSpec GlobalStringValues[CDacBlobGlobalStringsCount]; // all of the names that might be referenced from elsewhere in BinaryBlobDataDescriptor, // delimited by "\0" uint8_t NamesPool[sizeof(struct CDacStringPoolSizes)]; @@ -178,12 +191,15 @@ in a contiguous subsequence and are terminated by a marker `FieldSpec` with a `N For each field there is a name that gives an offset in the name pool and an offset indicating the field's offset. -The global constants are given as a sequence of `GlobalLiteralSpec` elements. Each global has a +The numeric global constants are given as a sequence of `GlobalLiteralSpec` elements. Each global has a name, type and a value. Globals that are the addresses in target memory, are in `GlobalPointerSpec` elements. Each pointer element has a name and an index in a separately compiled pointer structure that is linked into runtime . See [contract-descriptor.md](/docs/design/datacontracts/contract-descriptor.md) +Strings can be passed as `GlobalStringSpec` elements. Each string global has a name and value which +are passed as offsets into the `NamesPool`. + The `NamesPool` is a single sequence of utf-8 bytes comprising the concatenation of all the type field and global names including a terminating nul byte for each name. The same name may occur multiple times. The names could be referenced by multiple type or multiple fields. (That is, a diff --git a/src/coreclr/tools/cdac-build-tool/sample/sample.blob.c b/src/coreclr/tools/cdac-build-tool/sample/sample.blob.c index b90b7eca0e932a..09641ef3531a21 100644 --- a/src/coreclr/tools/cdac-build-tool/sample/sample.blob.c +++ b/src/coreclr/tools/cdac-build-tool/sample/sample.blob.c @@ -49,6 +49,12 @@ struct GlobalPointerSpec uint32_t AuxIndex; }; +struct GlobalStringSpec +{ + uint32_t Name; + uint32_t StringValue; +}; + #define CONCAT(token1,token2) token1 ## token2 #define CONCAT4(token1, token2, token3, token4) token1 ## token2 ## token3 ## token4 @@ -57,6 +63,7 @@ struct GlobalPointerSpec #define MAKE_FIELDTYPELEN_NAME(tyname,membername) CONCAT4(cdac_string_pool_membertypename__, tyname, __, membername) #define MAKE_GLOBALLEN_NAME(globalname) CONCAT(cdac_string_pool_globalname__, globalname) #define MAKE_GLOBALTYPELEN_NAME(globalname) CONCAT(cdac_string_pool_globaltypename__, globalname) +#define MAKE_GLOBALVALUELEN_NAME(globalname) CONCAT(cdac_string_pool_globalvalue__, globalname) // define a struct where the size of each field is the length of some string. we will use offsetof to get // the offset of each struct element, which will be equal to the offset of the beginning of that string in the @@ -66,33 +73,16 @@ struct CDacStringPoolSizes char cdac_string_pool_nil; // make the first real string start at offset 1 #define DECL_LEN(membername,len) char membername[(len)]; #define CDAC_BASELINE(name) DECL_LEN(cdac_string_pool_baseline_, (sizeof(name))) -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) DECL_LEN(MAKE_TYPELEN_NAME(name), sizeof(#name)) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(MAKE_FIELDLEN_NAME(tyname,membername), sizeof(#membername)) \ DECL_LEN(MAKE_FIELDTYPELEN_NAME(tyname,membername), sizeof(#membertyname)) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() +#define CDAC_GLOBAL_STRING(name, stringval) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ + DECL_LEN(MAKE_GLOBALVALUELEN_NAME(name), sizeof(#stringval)) #define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) #define CDAC_GLOBAL(name,tyname,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ DECL_LEN(MAKE_GLOBALTYPELEN_NAME(name), sizeof(#tyname)) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END #undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }; #define GET_TYPE_NAME(name) offsetof(struct CDacStringPoolSizes, MAKE_TYPELEN_NAME(name)) @@ -100,38 +90,15 @@ struct CDacStringPoolSizes #define GET_FIELDTYPE_NAME(tyname,membername) offsetof(struct CDacStringPoolSizes, MAKE_FIELDTYPELEN_NAME(tyname,membername)) #define GET_GLOBAL_NAME(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALLEN_NAME(globalname)) #define GET_GLOBALTYPE_NAME(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALTYPELEN_NAME(globalname)) +#define GET_GLOBALSTRING_VALUE(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALVALUELEN_NAME(globalname)) // count the types enum { CDacBlobTypesCount = #define CDAC_BASELINE(name) 0 -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) + 1 -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END - , }; // count the field pool size. @@ -140,32 +107,9 @@ enum { CDacBlobFieldsPoolCount = #define CDAC_BASELINE(name) 1 -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) + 1 #define CDAC_TYPE_END(name) + 1 -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END - , }; // count the literal globals @@ -173,32 +117,8 @@ enum { CDacBlobGlobalLiteralsCount = #define CDAC_BASELINE(name) 0 -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) #define CDAC_GLOBAL(name,tyname,value) + 1 -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END - , }; // count the aux vector globals @@ -206,32 +126,17 @@ enum { CDacBlobGlobalPointersCount = #define CDAC_BASELINE(name) 0 -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() #define CDAC_GLOBAL_POINTER(name,value) + 1 -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END - , +}; + +// count the global strings +enum +{ + CDacBlobGlobalStringsCount = +#define CDAC_GLOBALS_BEGIN() 0 +#define CDAC_GLOBAL_STRING(name,value) + 1 +#include "sample.data.h" }; @@ -257,32 +162,11 @@ struct CDacFieldsPoolSizes { #define DECL_LEN(membername) char membername; #define CDAC_BASELINE(name) DECL_LEN(cdac_fields_pool_start_placeholder__) -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) struct MAKE_TYPEFIELDS_TYNAME(name) { -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(CONCAT4(cdac_fields_pool_member__, tyname, __, membername)) #define CDAC_TYPE_END(name) DECL_LEN(CONCAT4(cdac_fields_pool_member__, tyname, _, endmarker)) \ } MAKE_TYPEFIELDS_TYNAME(name); -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END #undef DECL_LEN }; @@ -303,31 +187,9 @@ struct CDacGlobalPointerIndex { #define DECL_LEN(membername) char membername; #define CDAC_BASELINE(name) DECL_LEN(cdac_global_pointer_index_start_placeholder__) -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() #define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(CONCAT(cdac_global_pointer_index__, name)) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END #undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }; #define GET_GLOBAL_POINTER_INDEX(name) offsetof(struct CDacGlobalPointerIndex, CONCAT(cdac_global_pointer_index__, name)) @@ -343,6 +205,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesStart; uint32_t GlobalPointersStart; + uint32_t GlobalStringValuesStart; uint32_t NamesPoolStart; uint32_t TypeCount; @@ -350,6 +213,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesCount; uint32_t GlobalPointerValuesCount; + uint32_t GlobalStringValuesCount; uint32_t NamesPoolCount; @@ -357,6 +221,7 @@ struct BinaryBlobDataDescriptor uint8_t FieldSpecSize; uint8_t GlobalLiteralSpecSize; uint8_t GlobalPointerSpecSize; + uint8_t GlobalStringSpecSize; } Directory; uint32_t PlatformFlags; uint32_t BaselineName; @@ -364,6 +229,7 @@ struct BinaryBlobDataDescriptor struct FieldSpec FieldsPool[CDacBlobFieldsPoolCount]; struct GlobalLiteralSpec GlobalLiteralValues[CDacBlobGlobalLiteralsCount]; struct GlobalPointerSpec GlobalPointerValues[CDacBlobGlobalPointersCount]; + struct GlobalStringSpec GlobalStringValues[CDacBlobGlobalStringsCount]; uint8_t NamesPool[sizeof(struct CDacStringPoolSizes)]; uint8_t EndMagic[4]; }; @@ -382,16 +248,19 @@ const struct MagicAndBlob Blob = { .FieldsPoolStart = offsetof(struct BinaryBlobDataDescriptor, FieldsPool), .GlobalLiteralValuesStart = offsetof(struct BinaryBlobDataDescriptor, GlobalLiteralValues), .GlobalPointersStart = offsetof(struct BinaryBlobDataDescriptor, GlobalPointerValues), + .GlobalStringValuesStart = offsetof(struct BinaryBlobDataDescriptor, GlobalStringValues), .NamesPoolStart = offsetof(struct BinaryBlobDataDescriptor, NamesPool), .TypeCount = CDacBlobTypesCount, .FieldsPoolCount = CDacBlobFieldsPoolCount, .GlobalLiteralValuesCount = CDacBlobGlobalLiteralsCount, .GlobalPointerValuesCount = CDacBlobGlobalPointersCount, + .GlobalStringValuesCount = CDacBlobGlobalStringsCount, .NamesPoolCount = sizeof(struct CDacStringPoolSizes), .TypeSpecSize = sizeof(struct TypeSpec), .FieldSpecSize = sizeof(struct FieldSpec), .GlobalLiteralSpecSize = sizeof(struct GlobalLiteralSpec), .GlobalPointerSpecSize = sizeof(struct GlobalPointerSpec), + .GlobalStringSpecSize = sizeof(struct GlobalStringSpec), }, .EndMagic = { 0x01, 0x02, 0x03, 0x04 }, .PlatformFlags = 0x01 | (sizeof(void*) == 4 ? 0x02 : 0), @@ -399,153 +268,47 @@ const struct MagicAndBlob Blob = { .NamesPool = ("\0" // starts with a nul #define CDAC_BASELINE(name) name "\0" -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) #name "\0" -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #membername "\0" #membertyname "\0" -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() #define CDAC_GLOBAL_POINTER(name,value) #name "\0" #define CDAC_GLOBAL(name,tyname,value) #name "\0" #tyname "\0" -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END ), .FieldsPool = { #define CDAC_BASELINE(name) {0,}, -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) { \ .Name = GET_FIELD_NAME(tyname,membername), \ .TypeName = GET_FIELDTYPE_NAME(tyname,membername), \ .FieldOffset = offset, \ }, #define CDAC_TYPE_END(name) { 0, }, -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }, .Types = { -#define CDAC_BASELINE(name) -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) { \ .Name = GET_TYPE_NAME(name), \ .Fields = GET_TYPE_FIELDS(name), #define CDAC_TYPE_INDETERMINATE(name) .Size = 0, #define CDAC_TYPE_SIZE(size) .Size = size, -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #define CDAC_TYPE_END(name) }, -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }, .GlobalLiteralValues = { -#define CDAC_BASELINE(name) -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) #define CDAC_GLOBAL(name,tyname,value) { .Name = GET_GLOBAL_NAME(name), .TypeName = GET_GLOBALTYPE_NAME(name), .Value = value }, -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }, .GlobalPointerValues = { -#define CDAC_BASELINE(name) -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() #define CDAC_GLOBAL_POINTER(name,value) { .Name = GET_GLOBAL_NAME(name), .AuxIndex = GET_GLOBAL_POINTER_INDEX(name) }, -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END + }, + + .GlobalStringValues = { +#define CDAC_GLOBAL_STRING(name, value) { .Name = GET_GLOBAL_NAME(name), .StringValue = GET_GLOBALSTRING_VALUE(name) }, +#include "sample.data.h" }, } }; diff --git a/src/coreclr/tools/cdac-build-tool/sample/sample.data.h b/src/coreclr/tools/cdac-build-tool/sample/sample.data.h index e4b8bff98b5e43..58ed59d02de4a1 100644 --- a/src/coreclr/tools/cdac-build-tool/sample/sample.data.h +++ b/src/coreclr/tools/cdac-build-tool/sample/sample.data.h @@ -1,3 +1,45 @@ +#ifndef CDAC_BASELINE +#define CDAC_BASELINE(identifier) +#endif +#ifndef CDAC_TYPES_BEGIN +#define CDAC_TYPES_BEGIN() +#endif +#ifndef CDAC_TYPE_BEGIN +#define CDAC_TYPE_BEGIN(tyname) +#endif +#ifndef CDAC_TYPE_SIZE +#define CDAC_TYPE_SIZE(k) +#endif +#ifndef CDAC_TYPE_INDETERMINATE +#define CDAC_TYPE_INDETERMINATE(tyname) +#endif +#ifndef CDAC_TYPE_FIELD +#define CDAC_TYPE_FIELD(tyname,fieldtyname,fieldname,off) +#endif +#ifndef CDAC_TYPE_END +#define CDAC_TYPE_END(tyname) +#endif +#ifndef CDAC_TYPES_END +#define CDAC_TYPES_END() +#endif +#ifndef CDAC_GLOBALS_BEGIN +#define CDAC_GLOBALS_BEGIN() +#endif +#ifndef CDAC_GLOBAL +#define CDAC_GLOBAL(globalname,tyname,val) +#endif +#ifndef CDAC_GLOBAL_POINTER +#define CDAC_GLOBAL_POINTER(globalname,addr) +#endif +#ifndef CDAC_GLOBAL_STRING +#define CDAC_GLOBAL_STRING(globalname,stringval) +#endif +#ifndef CDAC_GLOBALS_END +#define CDAC_GLOBALS_END() +#endif + + + CDAC_BASELINE("empty") CDAC_TYPES_BEGIN() @@ -21,4 +63,21 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) CDAC_GLOBAL(FeatureEHFunclets, uint8, 0) #endif CDAC_GLOBAL(SomeMagicNumber, uint32, 42) +CDAC_GLOBAL_STRING(RuntimeIdentifier, "windows-x64") CDAC_GLOBALS_END() + + + +#undef CDAC_BASELINE +#undef CDAC_TYPES_BEGIN +#undef CDAC_TYPE_BEGIN +#undef CDAC_TYPE_INDETERMINATE +#undef CDAC_TYPE_SIZE +#undef CDAC_TYPE_FIELD +#undef CDAC_TYPE_END +#undef CDAC_TYPES_END +#undef CDAC_GLOBALS_BEGIN +#undef CDAC_GLOBAL +#undef CDAC_GLOBAL_POINTER +#undef CDAC_GLOBAL_STRING +#undef CDAC_GLOBALS_END From abedd902fce2eee982ae92399d18bee169529e5a Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 15:35:50 -0400 Subject: [PATCH 13/33] implement cdacreader portion of passing strings --- .../Target.cs | 15 +++ .../ContractDescriptorParser.cs | 85 ++++++++++--- .../ContractDescriptorTarget.cs | 87 ++++++++++---- .../ContractDescriptorBuilder.cs | 19 ++- .../ContractDescriptorHelpers.cs | 14 ++- .../tests/ContractDescriptor/ParserTests.cs | 113 +++++++++++++----- .../tests/ContractDescriptor/TargetTests.cs | 89 +++++++++++++- .../cdacreader/tests/TestPlaceholderTarget.cs | 31 ++++- 8 files changed, 378 insertions(+), 75 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index 522e8dcbdfcd4b..6e7507ba130267 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -93,6 +93,21 @@ public abstract class Target /// Value read from the target public abstract TargetNUInt ReadNUInt(ulong address); + /// + /// Read a well known global from the target process as a string + /// + /// The name of the global + /// The value of the global if found + /// True if a global is found, false otherwise + public abstract bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value); + + /// + /// Read a well known global from the target process as a string + /// + /// The name of the global + /// A string value + public abstract string ReadStringGlobal(string name); + /// /// Read a well known global from the target process as a number in the target endianness /// diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs index 9d351328daafe2..1f15838912670f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Unicode; namespace Microsoft.Diagnostics.DataContractReader; @@ -37,6 +39,7 @@ public partial class ContractDescriptorParser [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(TypeDescriptor))] [JsonSerializable(typeof(FieldDescriptor))] [JsonSerializable(typeof(GlobalDescriptor))] @@ -65,12 +68,14 @@ public class ContractDescriptor public Dictionary? Globals { get; set; } + public Dictionary? GlobalStrings { get; set; } + [JsonExtensionData] public Dictionary? Extras { get; set; } public override string ToString() { - return $"Version: {Version}, Baseline: {Baseline}, Contracts: {Contracts?.Count}, Types: {Types?.Count}, Globals: {Globals?.Count}"; + return $"Version: {Version}, Baseline: {Baseline}, Contracts: {Contracts?.Count}, Types: {Types?.Count}, Globals: {Globals?.Count}, GlobalStrings: {GlobalStrings?.Count}"; } } @@ -92,9 +97,13 @@ public class FieldDescriptor [JsonConverter(typeof(GlobalDescriptorConverter))] public class GlobalDescriptor { - public string? Type { get; set; } - public ulong Value { get; set; } + [MemberNotNullWhen(true, nameof(NumericValue))] public bool Indirect { get; set; } + public string? Type { get; set; } + + // When the descriptor is indirect, NumericValue must be non-null to point to the actual data + public ulong? NumericValue { get; set; } + public string? StringValue { get; set; } } internal sealed class TypeDescriptorConverter : JsonConverter @@ -191,35 +200,38 @@ internal sealed class GlobalDescriptorConverter : JsonConverter _contracts = []; - private readonly IReadOnlyDictionary _globals = new Dictionary(); + private readonly IReadOnlyDictionary _globals = new Dictionary(); private readonly Dictionary _knownTypes = []; private readonly Dictionary _types = []; @@ -147,25 +147,43 @@ private ContractDescriptorTarget(Configuration config, ContractDescriptorParser. // Read globals and map indirect values to pointer data if (descriptor.Globals is not null) { - Dictionary globals = []; + Dictionary globalValues = new(descriptor.Globals.Count); foreach ((string name, ContractDescriptorParser.GlobalDescriptor global) in descriptor.Globals) { - ulong value = global.Value; if (global.Indirect) { - if (value >= (ulong)pointerData.Length) - throw new InvalidOperationException($"Invalid pointer data index {value}."); + if (global.NumericValue.Value >= (ulong)pointerData.Length) + throw new InvalidOperationException($"Invalid pointer data index {global.NumericValue.Value}."); - value = pointerData[value].Value; + globalValues[name] = new GlobalValue + { + NumericValue = pointerData[global.NumericValue.Value].Value, + StringValue = global.StringValue, + Type = global.Type + }; + } + else // direct + { + globalValues[name] = new GlobalValue + { + NumericValue = global.NumericValue, + StringValue = global.StringValue, + Type = global.Type + }; } - - globals[name] = (value, global.Type); } - _globals = globals; + _globals = globalValues.AsReadOnly(); } } + private struct GlobalValue + { + public ulong? NumericValue; + public string? StringValue; + public string? Type; + } + // See docs/design/datacontracts/contract-descriptor.md private static bool TryReadContractDescriptor( ulong address, @@ -483,6 +501,8 @@ public bool IsAlignedToPointerSize(ulong value) public override bool IsAlignedToPointerSize(TargetPointer pointer) => IsAligned(pointer.Value, _config.PointerSize); + #region reading globals + public override bool TryReadGlobal(string name, [NotNullWhen(true)] out T? value) => TryReadGlobal(name, out value, out _); @@ -490,12 +510,13 @@ public bool TryReadGlobal(string name, [NotNullWhen(true)] out T? value, out { value = null; type = null; - if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + if (!_globals.TryGetValue(name, out GlobalValue global) || global.NumericValue is null) { + // Not found or does not contain a numeric value return false; } type = global.Type; - value = T.CreateChecked(global.Value); + value = T.CreateChecked(global.NumericValue.Value); return true; } @@ -504,11 +525,10 @@ public override T ReadGlobal(string name) public T ReadGlobal(string name, out string? type) where T : struct, INumber { - if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + if (!TryReadGlobal(name, out T? value, out type)) throw new InvalidOperationException($"Failed to read global {typeof(T)} '{name}'."); - type = global.Type; - return T.CreateChecked(global.Value); + return value.Value; } public override bool TryReadGlobalPointer(string name, [NotNullWhen(true)] out TargetPointer? value) @@ -517,12 +537,10 @@ public override bool TryReadGlobalPointer(string name, [NotNullWhen(true)] out T public bool TryReadGlobalPointer(string name, [NotNullWhen(true)] out TargetPointer? value, out string? type) { value = null; - type = null; - if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + if (!TryReadGlobal(name, out ulong? innerValue, out type)) return false; - type = global.Type; - value = new TargetPointer(global.Value); + value = new TargetPointer(innerValue.Value); return true; } @@ -531,13 +549,42 @@ public override TargetPointer ReadGlobalPointer(string name) public TargetPointer ReadGlobalPointer(string name, out string? type) { - if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + if (!TryReadGlobalPointer(name, out TargetPointer? value, out type)) throw new InvalidOperationException($"Failed to read global pointer '{name}'."); + return value.Value; + } + + public override string ReadStringGlobal(string name) + => ReadStringGlobal(name, out _); + + public string ReadStringGlobal(string name, out string? type) + { + if (!TryReadStringGlobal(name, out string? value, out type)) + throw new InvalidOperationException($"Failed to read string global '{name}'."); + + return value; + } + + public override bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value) + => TryReadStringGlobal(name, out value, out _); + + public bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value, out string? type) + { + value = null; + type = null; + if (!_globals.TryGetValue(name, out GlobalValue global) || global.StringValue is null) + { + // Not found or does not contain a string value + return false; + } type = global.Type; - return new TargetPointer(global.Value); + value = global.StringValue; + return true; } + #endregion + public override TypeInfo GetTypeInfo(DataType type) { if (!_knownTypes.TryGetValue(type, out Target.TypeInfo typeInfo)) diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index 4c9fdef137037f..4e80ecc41f0120 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -22,7 +22,7 @@ internal class ContractDescriptorBuilder : MockMemorySpace.Builder private IReadOnlyCollection _contracts; private IDictionary _types; - private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> _globals; + private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> _globals; private IReadOnlyCollection _indirectValues; public ContractDescriptorBuilder(TargetTestHelpers targetTestHelpers) @@ -51,12 +51,23 @@ public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ul throw new InvalidOperationException("Context already created"); if (_globals != null) throw new InvalidOperationException("Globals already set"); - _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.TypeName)).ToArray(); + _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, (string?)null, g.TypeName)).ToArray(); _indirectValues = null; return this; } - public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> globals, IReadOnlyCollection indirectValues) + public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, string? StringValue, string? TypeName)> globals) + { + if (_created) + throw new InvalidOperationException("Context already created"); + if (_globals != null) + throw new InvalidOperationException("Globals already set"); + _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.StringValue, g.TypeName)).ToArray(); + _indirectValues = null; + return this; + } + + public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> globals, IReadOnlyCollection indirectValues) { if (_created) throw new InvalidOperationException("Context already created"); @@ -104,7 +115,7 @@ private string MakeContractsJson() "baseline": "empty", "contracts": { {{interpolatedContracts}} }, "types": { {{metadataTypesJson}} }, - "globals": { {{metadataGlobalsJson}} } + "globals": { {{metadataGlobalsJson}} }, } """); MockMemorySpace.HeapFragment json = new() diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs index d17759a92c6aee..f9ba9241d41384 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs @@ -119,14 +119,14 @@ public static string MakeTypesJson(IDictionary types) public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals) { - return MakeGlobalsJson(globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.Type))); + return MakeGlobalsJson(globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, (string?)null, g.Type))); } - public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? Type)> globals) + public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? Type)> globals) { return string.Join(',', globals.Select(FormatGlobal)); - static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? Type) global) + static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? Type) global) { if (global.Value is ulong value) { @@ -136,6 +136,10 @@ static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, stri { return $"\"{global.Name}\": {FormatIndirect(index, global.Type)}"; } + else if (global.StringValue is string stringValue) + { + return $"\"{global.Name}\": {FormatString(stringValue, global.Type)}"; + } else { throw new InvalidOperationException("Global must have a value or indirect index"); @@ -150,6 +154,10 @@ static string FormatIndirect(uint value, string? type) { return type is null ? $"[{value}]" : $"[[{value}],\"{type}\"]"; } + static string FormatString(string value, string? type) + { + return type is null ? $"\"{value}\"" : $"[\"{value}\",\"{type}\"]"; + } } #endregion JSON formatting diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs index a74167437a240a..6a231f19dbc766 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Text.Json; using Xunit; @@ -130,6 +131,11 @@ public void ParsesGlobals() "globalTypedPtr": [[4], "uintptr"], "globalHex": "0x1234", "globalNegative": -2, + "globalTypedNegative": [-5, "int32"], + "globalString": "Hello", + "globalTypedString": ["World", "string"], + "globalIntLarge": 18446744073709551615, + "globalIntLargeNegative": -9223372036854775808, "globalStringyInt": "17", "globalStringyNegative": "-2", "globalNegativeHex": "-0xff", @@ -145,37 +151,90 @@ public void ParsesGlobals() Assert.Equal("empty", descriptor.Baseline); Assert.Empty(descriptor.Contracts); Assert.Empty(descriptor.Types); - Assert.Equal(13, descriptor.Globals.Count); - Assert.Equal((ulong)1, descriptor.Globals["globalInt"].Value); - Assert.False(descriptor.Globals["globalInt"].Indirect); - Assert.Equal((ulong)2, descriptor.Globals["globalPtr"].Value); - Assert.True(descriptor.Globals["globalPtr"].Indirect); - Assert.Equal((ulong)3, descriptor.Globals["globalTypedInt"].Value); - Assert.False(descriptor.Globals["globalTypedInt"].Indirect); + + Assert.Equal(18, descriptor.Globals.Count); + + Assert.Equal((ulong)1, descriptor.Globals["globalInt"].NumericValue); + Assert.Null(descriptor.Globals["globalInt"].StringValue); + AssertDirect(descriptor.Globals["globalInt"]); + + Assert.Equal((ulong)2, descriptor.Globals["globalPtr"].NumericValue); + Assert.Null(descriptor.Globals["globalPtr"].StringValue); + AssertIndirect(descriptor.Globals["globalPtr"]); + + Assert.Equal((ulong)3, descriptor.Globals["globalTypedInt"].NumericValue); + Assert.Null(descriptor.Globals["globalTypedInt"].StringValue); + AssertDirect(descriptor.Globals["globalTypedInt"]); Assert.Equal("uint8", descriptor.Globals["globalTypedInt"].Type); - Assert.Equal((ulong)4, descriptor.Globals["globalTypedPtr"].Value); - Assert.True(descriptor.Globals["globalTypedPtr"].Indirect); + + Assert.Equal((ulong)4, descriptor.Globals["globalTypedPtr"].NumericValue); + Assert.Null(descriptor.Globals["globalTypedPtr"].StringValue); + AssertIndirect(descriptor.Globals["globalTypedPtr"]); Assert.Equal("uintptr", descriptor.Globals["globalTypedPtr"].Type); - Assert.Equal((ulong)0x1234, descriptor.Globals["globalHex"].Value); - Assert.False(descriptor.Globals["globalHex"].Indirect); - Assert.Equal((ulong)0xfffffffffffffffe, descriptor.Globals["globalNegative"].Value); - Assert.False(descriptor.Globals["globalNegative"].Indirect); - Assert.Equal((ulong)17, descriptor.Globals["globalStringyInt"].Value); - Assert.False(descriptor.Globals["globalStringyInt"].Indirect); - Assert.Equal((ulong)0xfffffffffffffffe, descriptor.Globals["globalStringyNegative"].Value); - Assert.False(descriptor.Globals["globalStringyNegative"].Indirect); - Assert.Equal((ulong)0xffffffffffffff01, descriptor.Globals["globalNegativeHex"].Value); - Assert.False(descriptor.Globals["globalNegativeHex"].Indirect); - Assert.Equal((ulong)0x123456789abcdef, descriptor.Globals["globalBigStringyInt"].Value); - Assert.False(descriptor.Globals["globalBigStringyInt"].Indirect); - Assert.Equal((ulong)0x1234, descriptor.Globals["globalStringyPtr"].Value); - Assert.True(descriptor.Globals["globalStringyPtr"].Indirect); + + Assert.Equal((ulong)0x1234, descriptor.Globals["globalHex"].NumericValue); + Assert.Equal("0x1234", descriptor.Globals["globalHex"].StringValue); + AssertDirect(descriptor.Globals["globalHex"]); + + Assert.Equal(unchecked((ulong)-2), descriptor.Globals["globalNegative"].NumericValue); + Assert.Null(descriptor.Globals["globalNegative"].StringValue); + AssertDirect(descriptor.Globals["globalNegative"]); + + Assert.Equal(unchecked((ulong)-5), descriptor.Globals["globalTypedNegative"].NumericValue); + Assert.Null(descriptor.Globals["globalTypedNegative"].StringValue); + AssertDirect(descriptor.Globals["globalTypedNegative"]); + Assert.Equal("int32", descriptor.Globals["globalTypedNegative"].Type); + + Assert.Equal("Hello", descriptor.Globals["globalString"].StringValue); + AssertDirect(descriptor.Globals["globalString"]); + + Assert.Equal("World", descriptor.Globals["globalTypedString"].StringValue); + AssertDirect(descriptor.Globals["globalTypedString"]); + Assert.Equal("string", descriptor.Globals["globalTypedString"].Type); + + Assert.Equal(ulong.MaxValue, descriptor.Globals["globalIntLarge"].NumericValue); + Assert.Null(descriptor.Globals["globalIntLarge"].StringValue); + AssertDirect(descriptor.Globals["globalIntLarge"]); + + Assert.Equal(unchecked((ulong)long.MinValue), descriptor.Globals["globalIntLargeNegative"].NumericValue); + Assert.Null(descriptor.Globals["globalIntLargeNegative"].StringValue); + AssertDirect(descriptor.Globals["globalIntLargeNegative"]); + + Assert.Equal((ulong)17, descriptor.Globals["globalStringyInt"].NumericValue); + Assert.Equal("17", descriptor.Globals["globalStringyInt"].StringValue); + AssertDirect(descriptor.Globals["globalStringyInt"]); + + Assert.Equal(unchecked((ulong)-2), descriptor.Globals["globalStringyNegative"].NumericValue); + Assert.Equal("-2", descriptor.Globals["globalStringyNegative"].StringValue); + AssertDirect(descriptor.Globals["globalStringyNegative"]); + + Assert.Equal(unchecked((ulong)-0xff), descriptor.Globals["globalNegativeHex"].NumericValue); + Assert.Equal("-0xff", descriptor.Globals["globalNegativeHex"].StringValue); + AssertDirect(descriptor.Globals["globalNegativeHex"]); + + Assert.Equal((ulong)0x123456789abcdef, descriptor.Globals["globalBigStringyInt"].NumericValue); + Assert.Equal("0x123456789abcdef", descriptor.Globals["globalBigStringyInt"].StringValue); + AssertDirect(descriptor.Globals["globalBigStringyInt"]); + + Assert.Equal((ulong)0x1234, descriptor.Globals["globalStringyPtr"].NumericValue); + Assert.Equal("0x1234", descriptor.Globals["globalStringyPtr"].StringValue); + AssertIndirect(descriptor.Globals["globalStringyPtr"]); + + Assert.Equal((ulong)0x1234, descriptor.Globals["globalTypedStringyInt"].NumericValue); + Assert.Equal("0x1234", descriptor.Globals["globalTypedStringyInt"].StringValue); + AssertDirect(descriptor.Globals["globalTypedStringyInt"]); Assert.Equal("int", descriptor.Globals["globalTypedStringyInt"].Type); - Assert.Equal((ulong)0x1234, descriptor.Globals["globalTypedStringyInt"].Value); - Assert.False(descriptor.Globals["globalTypedStringyInt"].Indirect); + + Assert.Equal((ulong)0x1234, descriptor.Globals["globalTypedStringyPtr"].NumericValue); + Assert.Equal("0x1234", descriptor.Globals["globalTypedStringyPtr"].StringValue); + AssertIndirect(descriptor.Globals["globalTypedStringyPtr"]); Assert.Equal("int", descriptor.Globals["globalTypedStringyPtr"].Type); - Assert.Equal((ulong)0x1234, descriptor.Globals["globalTypedStringyPtr"].Value); - Assert.True(descriptor.Globals["globalTypedStringyPtr"].Indirect); + + void AssertIndirect(ContractDescriptorParser.GlobalDescriptor descriptor) + => Assert.True(descriptor.Indirect); + + void AssertDirect(ContractDescriptorParser.GlobalDescriptor descriptor) + => Assert.False(descriptor.Indirect); } [Fact] diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs index 2c7cc3e5d454a5..01b5c1c5a3309d 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs @@ -122,9 +122,42 @@ public void ReadIndirectGlobalValue(MockTarget.Architecture arch) ValidateGlobals(target, expected); - static (string Name, ulong? Value, uint? IndirectIndex, string? Type) MakeGlobalToIndirect((string Name, ulong Value, string? Type) global, int index) + static (string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? Type) MakeGlobalToIndirect((string Name, ulong Value, string? Type) global, int index) { - return (global.Name, null, (uint?)index, global.Type); + return (global.Name, null, (uint?)index, null, global.Type); + } + } + + private static readonly (string Name, string Value, ulong? NumericValue)[] GlobalStringyValues = + [ + ("value", "testString", null), + ("emptyString", "", null), + ("specialChars", "string with spaces & special chars ✓", null), + ("longString", new string('a', 1024), null), + ("hexString", "0x1234", 0x1234), + ("decimalString", "123456", 123456), + ("negativeString", "-1234", unchecked((ulong)-1234)), + ("negativeHexString", "-0x1234", unchecked((ulong)-0x1234)), + ]; + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ReadGlobalStringyValues(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + ContractDescriptorBuilder builder = new(targetTestHelpers); + builder.SetTypes(new Dictionary()) + .SetContracts([]) + .SetGlobals(GlobalStringyValues.Select(MakeGlobalsToStrings).ToArray()); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + ValidateGlobalStrings(target, GlobalStringyValues); + + static (string Name, ulong? Value, string? StringValue, string? Type) MakeGlobalsToStrings((string Name, string Value, ulong? NumericValue) global) + { + return (global.Name, null, global.Value, "string"); } } @@ -252,4 +285,56 @@ 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}]"); } } + + private static void ValidateGlobalStrings( + ContractDescriptorTarget target, + (string Name, string Value, ulong? NumericValue)[] globals, + [CallerMemberName] string caller = "", + [CallerFilePath] string filePath = "", + [CallerLineNumber] int lineNumber = 0) + { + foreach (var (name, value, numericValue) in globals) + { + string actualString; + + Assert.True(target.TryReadStringGlobal(name, out actualString)); + AssertEqualsWithCallerInfo(value, actualString); + + actualString = target.ReadStringGlobal(name); + AssertEqualsWithCallerInfo(value, actualString); + + if (numericValue != null) + { + ulong? actualNumericValue; + + Assert.True(target.TryReadGlobal(name, out actualNumericValue)); + AssertEqualsWithCallerInfo(numericValue.Value, actualNumericValue.Value); + + actualNumericValue = target.ReadGlobal(name); + AssertEqualsWithCallerInfo(numericValue.Value, actualNumericValue.Value); + + TargetPointer? actualPointer; + + Assert.True(target.TryReadGlobalPointer(name, out actualPointer)); + AssertEqualsWithCallerInfo(numericValue.Value, actualPointer.Value.Value); + + actualPointer = target.ReadGlobalPointer(name); + AssertEqualsWithCallerInfo(numericValue.Value, actualPointer.Value.Value); + } + else + { + // if there is no numeric value, assert that reading as numeric fails + Assert.False(target.TryReadGlobal(name, out ulong? _)); + Assert.ThrowsAny(() => target.ReadGlobal(name)); + Assert.False(target.TryReadGlobalPointer(name, out TargetPointer? _)); + Assert.ThrowsAny(() => target.ReadGlobalPointer(name)); + } + } + + 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}]"); + } + } + } diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index f46b4f4c9c9283..507bcbfdcaf969 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -20,12 +20,13 @@ internal class TestPlaceholderTarget : Target private readonly Target.IDataCache _dataCache; private readonly Dictionary _typeInfoCache; private readonly (string Name, ulong Value)[] _globals; + private readonly (string Name, string Value)[] _globalStrings; internal delegate int ReadFromTargetDelegate(ulong address, Span buffer); private readonly ReadFromTargetDelegate _dataReader; - public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary types = null, (string Name, ulong Value)[] globals = null) + public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary types = null, (string Name, ulong Value)[] globals = null, (string Name, string Value)[] globalStrings = null) { IsLittleEndian = arch.IsLittleEndian; PointerSize = arch.Is64Bit ? 8 : 4; @@ -34,6 +35,7 @@ public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegat _typeInfoCache = types ?? []; _dataReader = reader; _globals = globals ?? []; + _globalStrings = globalStrings ?? []; } internal void SetContracts(ContractRegistry contracts) @@ -130,6 +132,33 @@ public override T ReadGlobal(string name) throw new NotImplementedException(); } + public override string ReadStringGlobal(string name) + { + if (TryReadStringGlobal(name, out string? value)) + { + return value; + } + + throw new NotImplementedException(); + } + + public override bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value) + { + value = null; + + // first check global strings + foreach (var global in _globalStrings) + { + if (global.Name == name) + { + value = global.Value; + return true; + } + } + + return false; + } + public override T Read(ulong address) => DefaultRead(address); #region subclass reader helpers From bdf1a7376dc2972f65f39c22537668350f5ff29f Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 16:11:01 -0400 Subject: [PATCH 14/33] improve wording --- docs/design/datacontracts/data_descriptor.md | 4 ++-- .../Target.cs | 4 ++-- .../ContractDescriptorTarget.cs | 4 ++-- .../cdacreader/tests/ContractDescriptor/TargetTests.cs | 4 ++-- .../managed/cdacreader/tests/TestPlaceholderTarget.cs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/design/datacontracts/data_descriptor.md b/docs/design/datacontracts/data_descriptor.md index 06b3fa2bfc7f60..538643806a50f1 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -214,7 +214,7 @@ The global values will be in an array, with each value described by a dictionary * `"type": "type name"` the type of the global value * optional `"value": VALUE | [ int ] ` the value of the global value, or an offset in an auxiliary array containing the value. -The `VALUE` may be either a number of string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. +The `VALUE` may be either a number or a string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. Numeric constants must be within the range of the type of the global value. @@ -225,7 +225,7 @@ The global values will be in a dictionary, with each key being the name of a glo * `[VALUE | [int], "type name"]` the type and value of a global * `VALUE | [int]` just the value of a global -`VALUE` may be either a number of string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. +`VALUE` may be either a number or a string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. Numeric constants must be within the range of the type of the global value. Note that a two element array is unambiguously "type and value", whereas a one-element array is diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index 6e7507ba130267..887e5faedbe9f3 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -99,14 +99,14 @@ public abstract class Target /// The name of the global /// The value of the global if found /// True if a global is found, false otherwise - public abstract bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value); + public abstract bool TryReadGlobalString(string name, [NotNullWhen(true)] out string? value); /// /// Read a well known global from the target process as a string /// /// The name of the global /// A string value - public abstract string ReadStringGlobal(string name); + public abstract string ReadGlobalString(string name); /// /// Read a well known global from the target process as a number in the target endianness diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index efbde6adc6dd00..d84260fa382033 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -555,7 +555,7 @@ public TargetPointer ReadGlobalPointer(string name, out string? type) return value.Value; } - public override string ReadStringGlobal(string name) + public override string ReadGlobalString(string name) => ReadStringGlobal(name, out _); public string ReadStringGlobal(string name, out string? type) @@ -566,7 +566,7 @@ public string ReadStringGlobal(string name, out string? type) return value; } - public override bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value) + public override bool TryReadGlobalString(string name, [NotNullWhen(true)] out string? value) => TryReadStringGlobal(name, out value, out _); public bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value, out string? type) diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs index 01b5c1c5a3309d..6ba88e9246e6e3 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs @@ -297,10 +297,10 @@ private static void ValidateGlobalStrings( { string actualString; - Assert.True(target.TryReadStringGlobal(name, out actualString)); + Assert.True(target.TryReadGlobalString(name, out actualString)); AssertEqualsWithCallerInfo(value, actualString); - actualString = target.ReadStringGlobal(name); + actualString = target.ReadGlobalString(name); AssertEqualsWithCallerInfo(value, actualString); if (numericValue != null) diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 507bcbfdcaf969..d61e862d2ed7a4 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -132,9 +132,9 @@ public override T ReadGlobal(string name) throw new NotImplementedException(); } - public override string ReadStringGlobal(string name) + public override string ReadGlobalString(string name) { - if (TryReadStringGlobal(name, out string? value)) + if (TryReadGlobalString(name, out string? value)) { return value; } @@ -142,7 +142,7 @@ public override string ReadStringGlobal(string name) throw new NotImplementedException(); } - public override bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value) + public override bool TryReadGlobalString(string name, [NotNullWhen(true)] out string? value) { value = null; From f66fa9f58aaa974d886f57b81c38e390ea804ec1 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 16:15:40 -0400 Subject: [PATCH 15/33] update contract doc --- docs/design/datacontracts/RuntimeInfo.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/design/datacontracts/RuntimeInfo.md b/docs/design/datacontracts/RuntimeInfo.md index 298feeca2cf4a9..e3c5e0c14cbeb3 100644 --- a/docs/design/datacontracts/RuntimeInfo.md +++ b/docs/design/datacontracts/RuntimeInfo.md @@ -10,7 +10,7 @@ public enum RuntimeInfoArchitecture : uint Unknown = 0, X86, Arm32, - Amd64, + X64, Arm64, LoongArch64, RISCV, @@ -19,7 +19,7 @@ public enum RuntimeInfoArchitecture : uint public enum RuntimeInfoOperatingSystem : uint { Unknown = 0, - Windows, + Win, Unix, } ``` @@ -37,7 +37,7 @@ RuntimeInfoOperatingSystem GetTargetOperatingSystem(); Global variables used: | Global Name | Type | Purpose | | --- | --- | --- | -| Architecture | `RuntimeInfoArchitecture` enum value (`uint32`) | Target architecture | -| OperatingSystem | `RuntimeInfoOperatingSystem` enum value (`uint32`) | Target operating system | +| Architecture | string | Target architecture | +| OperatingSystem | string | Target operating system | -The contract implementation simply returns the contract descriptor global values. If these globals are not available, the contract returns Unknown. +The contract implementation simply returns the contract descriptor global values parsed as the respective enum case-insensitively. If these globals are not available, the contract returns Unknown. From c13dfdf816fc62bbbdf46fa8c8ec6344f934d5a9 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 16:35:07 -0400 Subject: [PATCH 16/33] rename cmake RID parameter --- src/coreclr/debug/runtimeinfo/CMakeLists.txt | 4 ++-- src/coreclr/debug/runtimeinfo/configure.h.in | 2 +- src/coreclr/runtime.proj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/CMakeLists.txt b/src/coreclr/debug/runtimeinfo/CMakeLists.txt index d59184a148e184..8081eb92189212 100644 --- a/src/coreclr/debug/runtimeinfo/CMakeLists.txt +++ b/src/coreclr/debug/runtimeinfo/CMakeLists.txt @@ -42,8 +42,8 @@ install_clr(TARGETS runtimeinfo DESTINATIONS lib COMPONENT runtime) # cDAC contract descriptor -if("${CLI_CMAKE_RID}" STREQUAL "") - message(FATAL_ERROR "CLI_CMAKE_RID is not set. Please set it to the RID of the target platform.") +if("${CLR_DOTNET_RID}" STREQUAL "") + message(FATAL_ERROR "CLR_DOTNET_RID is not set. Please set it to the RID of the target platform.") endif() configure_file(configure.h.in ${CMAKE_CURRENT_BINARY_DIR}/configure.h) diff --git a/src/coreclr/debug/runtimeinfo/configure.h.in b/src/coreclr/debug/runtimeinfo/configure.h.in index 292e1579fc5009..efe0ede365c644 100644 --- a/src/coreclr/debug/runtimeinfo/configure.h.in +++ b/src/coreclr/debug/runtimeinfo/configure.h.in @@ -1,6 +1,6 @@ #ifndef RUNTIME_INFO_CONFIGURE_H_INCLUDED #define RUNTIME_INFO_CONFIGURE_H_INCLUDED -#define RID_STRING @CLI_CMAKE_RID@ +#define RID_STRING @CLR_DOTNET_RID@ #endif // RUNTIME_INFO_CONFIGURE_H_INCLUDED diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj index f5152ab97ffeed..05b98031c22771 100644 --- a/src/coreclr/runtime.proj +++ b/src/coreclr/runtime.proj @@ -47,7 +47,7 @@ <_CoreClrBuildArg Condition="'$(EnableNativeSanitizers)' != ''" Include="-fsanitize $(EnableNativeSanitizers)" /> <_CoreClrBuildArg Condition="'$(HostCrossOS)' != ''" Include="-hostos $(HostCrossOS)" /> <_CoreClrBuildArg Include="-outputrid $(OutputRID)" /> - <_CoreClrBuildArg Include="-cmakeargs "-DCLI_CMAKE_RID=$(OutputRID)"" /> + <_CoreClrBuildArg Include="-cmakeargs "-DCLR_DOTNET_RID=$(OutputRID)"" /> <_CoreClrBuildArg Condition="'$(BuildSubdirectory)' != ''" Include="-subdir $(BuildSubdirectory)" /> <_CoreClrBuildArg Include="-cmakeargs "-DCLR_DOTNET_HOST_PATH=$(DOTNET_HOST_PATH)"" /> <_CoreClrBuildArg Condition="'$(HasCdacBuildTool)' == 'true'" Include="-cmakeargs "-DCDAC_BUILD_TOOL_BINARY_PATH=$(RuntimeBinDir)cdac-build-tool\cdac-build-tool.dll"" /> From b39234cbdb3e2a2fff8e36a14d5ea6c3ab14ca92 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 3 Apr 2025 16:49:58 -0400 Subject: [PATCH 17/33] add fallback entrypoint --- .../managed/cdacreader/src/Entrypoints.cs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index f8528ec440293e..169e9fb3ee72a7 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -77,25 +77,41 @@ private static unsafe int CreateSosInterface(IntPtr handle, IntPtr legacyImplPtr return 0; } + [UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstanceWithFallback")] + private static unsafe int CLRDataCreateInstanceWithFallback(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, IntPtr pLegacyImpl, void** iface) + { + return CLRDataCreateInstanceImpl(pIID, pLegacyTarget, pLegacyImpl, iface); + } + + // Same export name and signature as DAC CLRDataCreateInstance in daccess.cpp [UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstance")] private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, void** iface) + { + return CLRDataCreateInstanceImpl(pIID, pLegacyTarget, IntPtr.Zero, iface); + } + + private static unsafe int CLRDataCreateInstanceImpl(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, IntPtr pLegacyImpl, void** iface) { if (pLegacyTarget == IntPtr.Zero || iface == null) return HResults.E_INVALIDARG; - *iface = null; ComWrappers cw = new StrategyBasedComWrappers(); - object obj = cw.GetOrCreateObjectForComInstance(pLegacyTarget, CreateObjectFlags.None); + object legacyTarget = cw.GetOrCreateObjectForComInstance(pLegacyTarget, CreateObjectFlags.None); + object? legacyImpl = pLegacyImpl != IntPtr.Zero ? + cw.GetOrCreateObjectForComInstance(pLegacyImpl, CreateObjectFlags.None) : null; - ICLRDataTarget dataTarget = obj as ICLRDataTarget ?? throw new ArgumentException($"{nameof(pLegacyTarget)} does not implement {nameof(ICLRDataTarget)}", nameof(pLegacyTarget)); - ICLRContractLocator contractLocator = obj as ICLRContractLocator ?? throw new ArgumentException($"{nameof(pLegacyTarget)} does not implement {nameof(ICLRContractLocator)}", nameof(pLegacyTarget)); + ICLRDataTarget dataTarget = legacyTarget as ICLRDataTarget ?? throw new ArgumentException( + $"{nameof(pLegacyTarget)} does not implement {nameof(ICLRDataTarget)}", nameof(pLegacyTarget)); + ICLRContractLocator contractLocator = legacyTarget as ICLRContractLocator ?? throw new ArgumentException( + $"{nameof(pLegacyTarget)} does not implement {nameof(ICLRContractLocator)}", nameof(pLegacyTarget)); ulong contractAddress; int hr = contractLocator.GetContractDescriptor(&contractAddress); if (hr != 0) { - throw new InvalidOperationException($"{nameof(ICLRContractLocator)} failed to fetch the contract descriptor with HRESULT: 0x{hr:x}."); + throw new InvalidOperationException( + $"{nameof(ICLRContractLocator)} failed to fetch the contract descriptor with HRESULT: 0x{hr:x}."); } if (!ContractDescriptorTarget.TryCreate( @@ -120,7 +136,7 @@ private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTar return -1; } - Legacy.SOSDacImpl impl = new(target, null); + Legacy.SOSDacImpl impl = new(target, legacyImpl); nint ccw = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); Marshal.QueryInterface(ccw, *pIID, out nint ptrToIface); *iface = (void*)ptrToIface; From 08f557ae7961217c84056894fbc62841f6856e1f Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 11:03:27 -0400 Subject: [PATCH 18/33] make it more clear we won't overrun buffer of GetThreadContext --- .../ContractDescriptorTarget.cs | 8 ++++---- src/native/managed/cdacreader/src/Entrypoints.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index d84260fa382033..c2e2790562f763 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -43,7 +43,7 @@ private readonly struct Configuration public override DataCache ProcessedData { get; } public delegate int ReadFromTargetDelegate(ulong address, Span bufferToFill); - public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); + public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, Span bufferToFill); /// /// Create a new target instance from a contract descriptor embedded in the target memory. @@ -280,7 +280,7 @@ private static DataType GetDataType(string type) public override bool TryGetThreadContext(ulong threadId, uint contextFlags, Span buffer) { // Underlying API only supports 32-bit thread IDs, mask off top 32 bits - int hr = _reader.GetThreadContext((uint)(threadId & uint.MaxValue), contextFlags, (uint)buffer.Length, buffer); + int hr = _reader.GetThreadContext((uint)(threadId & uint.MaxValue), contextFlags, buffer); return hr == 0; } @@ -669,9 +669,9 @@ public int ReadFromTarget(ulong address, Span buffer) public int ReadFromTarget(ulong address, byte* buffer, uint bytesToRead) => readFromTarget(address, new Span(buffer, checked((int)bytesToRead))); - public int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span buffer) + public int GetThreadContext(uint threadId, uint contextFlags, Span buffer) { - return getThreadContext(threadId, contextFlags, contextSize, buffer); + return getThreadContext(threadId, contextFlags, buffer); } } } diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index 169e9fb3ee72a7..db02e3531875fd 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -30,11 +30,11 @@ private static unsafe int Init( return readFromTarget(address, bufferPtr, (uint)buffer.Length, readContext); } }, - (threadId, contextFlags, contextSize, buffer) => + (threadId, contextFlags, buffer) => { fixed (byte* bufferPtr = buffer) { - return readThreadContext(threadId, contextFlags, contextSize, bufferPtr, readContext); + return readThreadContext(threadId, contextFlags, (uint)buffer.Length, bufferPtr, readContext); } }, out ContractDescriptorTarget? target)) @@ -124,11 +124,11 @@ private static unsafe int CLRDataCreateInstanceImpl(Guid* pIID, IntPtr /*ICLRDat return dataTarget.ReadVirtual(address, bufferPtr, (uint)buffer.Length, &bytesRead); } }, - (threadId, contextFlags, contextSize, bufferToFill) => + (threadId, contextFlags, bufferToFill) => { fixed (byte* bufferPtr = bufferToFill) { - return dataTarget.GetThreadContext(threadId, contextFlags, contextSize, bufferPtr); + return dataTarget.GetThreadContext(threadId, contextFlags, (uint)bufferToFill.Length, bufferPtr); } }, out ContractDescriptorTarget? target)) From 40b78e06bc1daa6b6382ce27840fb677d1f5a1c2 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 11:04:43 -0400 Subject: [PATCH 19/33] remove trailing comma --- .../tests/ContractDescriptor/ContractDescriptorBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index 4e80ecc41f0120..3b5d4de5972e96 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -115,7 +115,7 @@ private string MakeContractsJson() "baseline": "empty", "contracts": { {{interpolatedContracts}} }, "types": { {{metadataTypesJson}} }, - "globals": { {{metadataGlobalsJson}} }, + "globals": { {{metadataGlobalsJson}} } } """); MockMemorySpace.HeapFragment json = new() From a4217984a682bb488921ef2d69a7717fd7308ab2 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 12:00:25 -0400 Subject: [PATCH 20/33] add test for global string with escaped characters --- .../cdacreader/tests/ContractDescriptor/ParserTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs index 6a231f19dbc766..bb4b36774ff3c5 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs @@ -134,6 +134,7 @@ public void ParsesGlobals() "globalTypedNegative": [-5, "int32"], "globalString": "Hello", "globalTypedString": ["World", "string"], + "globalStringSpecialChars" : "\"Hello World\"", "globalIntLarge": 18446744073709551615, "globalIntLargeNegative": -9223372036854775808, "globalStringyInt": "17", @@ -152,7 +153,7 @@ public void ParsesGlobals() Assert.Empty(descriptor.Contracts); Assert.Empty(descriptor.Types); - Assert.Equal(18, descriptor.Globals.Count); + Assert.Equal(19, descriptor.Globals.Count); Assert.Equal((ulong)1, descriptor.Globals["globalInt"].NumericValue); Assert.Null(descriptor.Globals["globalInt"].StringValue); @@ -192,6 +193,9 @@ public void ParsesGlobals() AssertDirect(descriptor.Globals["globalTypedString"]); Assert.Equal("string", descriptor.Globals["globalTypedString"].Type); + Assert.Equal("\"Hello World\"", descriptor.Globals["globalStringSpecialChars"].StringValue); + AssertDirect(descriptor.Globals["globalStringSpecialChars"]); + Assert.Equal(ulong.MaxValue, descriptor.Globals["globalIntLarge"].NumericValue); Assert.Null(descriptor.Globals["globalIntLarge"].StringValue); AssertDirect(descriptor.Globals["globalIntLarge"]); From 9568f11182b3e31c0c915ffa5160e2137ff39117 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 12:28:50 -0400 Subject: [PATCH 21/33] use portable RID value --- src/coreclr/runtime.proj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj index 05b98031c22771..70db5da95b99fc 100644 --- a/src/coreclr/runtime.proj +++ b/src/coreclr/runtime.proj @@ -47,7 +47,9 @@ <_CoreClrBuildArg Condition="'$(EnableNativeSanitizers)' != ''" Include="-fsanitize $(EnableNativeSanitizers)" /> <_CoreClrBuildArg Condition="'$(HostCrossOS)' != ''" Include="-hostos $(HostCrossOS)" /> <_CoreClrBuildArg Include="-outputrid $(OutputRID)" /> - <_CoreClrBuildArg Include="-cmakeargs "-DCLR_DOTNET_RID=$(OutputRID)"" /> + + <_CoreClrBuildArg Condition="'$(BaseOS)' == ''" Include="-cmakeargs "-DCLR_DOTNET_RID=$(OutputRID)"" /> + <_CoreClrBuildArg Condition="'$(BaseOS)' != ''" Include="-cmakeargs "-DCLR_DOTNET_RID=$(BaseOS)"" /> <_CoreClrBuildArg Condition="'$(BuildSubdirectory)' != ''" Include="-subdir $(BuildSubdirectory)" /> <_CoreClrBuildArg Include="-cmakeargs "-DCLR_DOTNET_HOST_PATH=$(DOTNET_HOST_PATH)"" /> <_CoreClrBuildArg Condition="'$(HasCdacBuildTool)' == 'true'" Include="-cmakeargs "-DCDAC_BUILD_TOOL_BINARY_PATH=$(RuntimeBinDir)cdac-build-tool\cdac-build-tool.dll"" /> From 4acb06c69bb3f4f1f1501b9a480210abef8ba158 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 13:39:12 -0400 Subject: [PATCH 22/33] update enum values --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 2 +- .../Contracts/IRuntimeInfo.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 0224cecdcb2642..9709d58409227a 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -764,7 +764,7 @@ CDAC_GLOBALS_BEGIN() #if defined(TARGET_UNIX) CDAC_GLOBAL_STRING(OperatingSystem, unix) #elif defined(TARGET_WINDOWS) -CDAC_GLOBAL_STRING(OperatingSystem, win) +CDAC_GLOBAL_STRING(OperatingSystem, windows) #else #error Unknown OperatingSystem. #endif diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs index cc5550ae52d8fd..31e80c9ddc5430 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs @@ -5,21 +5,26 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; +// Values are similar to System.Runtime.InteropServices.Architecture public enum RuntimeInfoArchitecture : uint { Unknown = 0, X86, - Arm32, X64, + Arm, Arm64, + Wasm, + S390x, LoongArch64, - RISCV, + Armv6, + Ppc64le, + RiscV64, } public enum RuntimeInfoOperatingSystem : uint { Unknown = 0, - Win, + Windows, Unix, } From d11b62df107ad47985eb05a6ae1766c87e877ce7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 13:40:26 -0400 Subject: [PATCH 23/33] update example in contract descriptor --- docs/design/datacontracts/contract-descriptor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/contract-descriptor.md b/docs/design/datacontracts/contract-descriptor.md index b99150c78becdd..b2388b1ec7d071 100644 --- a/docs/design/datacontracts/contract-descriptor.md +++ b/docs/design/datacontracts/contract-descriptor.md @@ -84,7 +84,7 @@ a JSON integer constant. { "FEATURE_COMINTEROP": 0, "s_pThreadStore": [ 0 ], // indirect from pointer data offset 0 - "RuntimeID": "windows-x64" // string value + "RuntimeID": "win-x64" // string value }, "contracts": {"Thread": 1, "GCHandle": 1, "ThreadStore": 1} } From 25e3da16d5646f0a74581f467336d52b64758abe Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 13:44:47 -0400 Subject: [PATCH 24/33] fix StressLogAnalyzer --- src/tools/StressLogAnalyzer/src/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/StressLogAnalyzer/src/Program.cs b/src/tools/StressLogAnalyzer/src/Program.cs index d683c6f463c8f7..00b4105e1d0f3b 100644 --- a/src/tools/StressLogAnalyzer/src/Program.cs +++ b/src/tools/StressLogAnalyzer/src/Program.cs @@ -492,7 +492,7 @@ ContractDescriptorTarget CreateTarget() => ContractDescriptorTarget.Create( GetDescriptor(contractVersion), [TargetPointer.Null, new TargetPointer(header->memoryBase + (nuint)((byte*)&header->moduleTable - (byte*)header))], (address, buffer) => ReadFromMemoryMappedLog(address, buffer, header), - (threadId, contextFlags, contextSize, bufferToFill) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetThreadContext implementation"), + (threadId, contextFlags, bufferToFill) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetThreadContext implementation"), true, nuint.Size); } From 1fc12895a8daf709ea7e3f640846b7d52f154742 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 14:41:46 -0400 Subject: [PATCH 25/33] improve docs using BNF --- docs/design/datacontracts/data_descriptor.md | 42 +++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/docs/design/datacontracts/data_descriptor.md b/docs/design/datacontracts/data_descriptor.md index 538643806a50f1..2470285440c13b 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -212,9 +212,9 @@ The global values will be in an array, with each value described by a dictionary * `"name": "global value name"` the name of the global value * `"type": "type name"` the type of the global value -* optional `"value": VALUE | [ int ] ` the value of the global value, or an offset in an auxiliary array containing the value. +* optional `"value": ` where `` is defined below + -The `VALUE` may be either a number or a string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. Numeric constants must be within the range of the type of the global value. @@ -222,17 +222,46 @@ Numeric constants must be within the range of the type of the global value. The global values will be in a dictionary, with each key being the name of a global and the values being one of: -* `[VALUE | [int], "type name"]` the type and value of a global -* `VALUE | [int]` just the value of a global +* `[, "type name"]` the type and value of a global +* `` just the value of a global + +Where `` is defined as below. -`VALUE` may be either a number or a string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. Numeric constants must be within the range of the type of the global value. Note that a two element array is unambiguously "type and value", whereas a one-element array is unambiguously "indirect value". + **Both formats** +#### Specification Appendix + +``` + ::= | [ ] + ::= | + ::= | | + + is identified by a "0x" or "0X" prefix +``` + +#### Parsing Rules +`` is parsed as a numeric value. +`` and `` can be parsed as either a string or numeric value. +`` (that does not form a valid hex or decimal number) is parsed as a string. + +Example using compact format: +```json +{ + "int" : 1234, // Can only be parsed as numeric constant 1234 + "stringyInt" : "1234", // Can be parsed as 1234 or "1234" + "stringyHex" : "0x1234", // Can be parsed as 4660 (0x1234 in decimal) or "0x1234" + "stringValue" : "Hello World" // Can only be parsed as "Hello World" +} +``` + +#### Typing + For pointer and nuint globals, the value may be assumed to fit in a 64-bit unsigned integer. For nint globals, the value may be assumed to fit in a 64-bit signed integer. @@ -240,6 +269,8 @@ Note that the logical descriptor does not contain "unknown" values: it is expect in-memory data descriptor will augment the baseline with a known offset for all fields in the baseline. +#### Indirect Types + If the value is given as a single-element array `[ int ]` then the value is stored in an auxiliary array that is part of the data contract descriptor. Only in-memory data descriptors may have indirect values; baseline data descriptors may not have indirect values. @@ -252,7 +283,6 @@ The indirection array is not part of the data descriptor spec. It is part of th descriptor](./contract_descriptor.md#Contract_descriptor). - ## Example This is an example of a baseline descriptor for a 64-bit architecture. Suppose it has the name `"example-64"` From b1d5fd7163ef90d51ca6997cf5faf463f39d1c10 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 14:44:33 -0400 Subject: [PATCH 26/33] change error message in datadescriptors to point user in right direction if new OS/arch --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 9709d58409227a..32fb96d0b62262 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -766,7 +766,7 @@ CDAC_GLOBAL_STRING(OperatingSystem, unix) #elif defined(TARGET_WINDOWS) CDAC_GLOBAL_STRING(OperatingSystem, windows) #else -#error Unknown OperatingSystem. +#error TARGET_{OS} define is not recognized by the cDAC. Update this define switch and enum values in IRuntimeInfo.cs #endif #if defined(TARGET_X86) @@ -782,7 +782,7 @@ CDAC_GLOBAL_STRING(Architecture, loongarch64) #elif defined(TARGET_RISCV64) CDAC_GLOBAL_STRING(Architecture, riscv64) #else -#error Unknown Architecture. +#error TARGET_{ARCH} define is not recognized by the cDAC. Update this define switch and enum values in IRuntimeInfo.cs #endif CDAC_GLOBAL_STRING(RID, RID_STRING) From 54f186ab8f6b739789060668efe65643da647397 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 14:49:40 -0400 Subject: [PATCH 27/33] text --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 32fb96d0b62262..9ba445b8fba267 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -766,7 +766,7 @@ CDAC_GLOBAL_STRING(OperatingSystem, unix) #elif defined(TARGET_WINDOWS) CDAC_GLOBAL_STRING(OperatingSystem, windows) #else -#error TARGET_{OS} define is not recognized by the cDAC. Update this define switch and enum values in IRuntimeInfo.cs +#error TARGET_{OS} define is not recognized by the cDAC. Update this switch and the enum values in IRuntimeInfo.cs #endif #if defined(TARGET_X86) @@ -782,7 +782,7 @@ CDAC_GLOBAL_STRING(Architecture, loongarch64) #elif defined(TARGET_RISCV64) CDAC_GLOBAL_STRING(Architecture, riscv64) #else -#error TARGET_{ARCH} define is not recognized by the cDAC. Update this define switch and enum values in IRuntimeInfo.cs +#error TARGET_{ARCH} define is not recognized by the cDAC. Update this switch and the enum values in IRuntimeInfo.cs #endif CDAC_GLOBAL_STRING(RID, RID_STRING) From fe80a63e00630d3aae31daaa9ed12b3c0d577db2 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 14:51:10 -0400 Subject: [PATCH 28/33] improve error message --- src/coreclr/debug/runtimeinfo/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/debug/runtimeinfo/CMakeLists.txt b/src/coreclr/debug/runtimeinfo/CMakeLists.txt index 8081eb92189212..ed82a22611ae22 100644 --- a/src/coreclr/debug/runtimeinfo/CMakeLists.txt +++ b/src/coreclr/debug/runtimeinfo/CMakeLists.txt @@ -43,7 +43,7 @@ install_clr(TARGETS runtimeinfo DESTINATIONS lib COMPONENT runtime) # cDAC contract descriptor if("${CLR_DOTNET_RID}" STREQUAL "") - message(FATAL_ERROR "CLR_DOTNET_RID is not set. Please set it to the RID of the target platform.") + message(FATAL_ERROR "CLR_DOTNET_RID is not set. Please ensure it is being set to the portable RID of the target platform by runtime.proj.") endif() configure_file(configure.h.in ${CMAKE_CURRENT_BINARY_DIR}/configure.h) From 6f833432cce1ca5bde48468b53bd2f542b7914a7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 15:00:53 -0400 Subject: [PATCH 29/33] update doc --- docs/design/datacontracts/data_descriptor.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/data_descriptor.md b/docs/design/datacontracts/data_descriptor.md index 2470285440c13b..4789a6b4fe178e 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -238,7 +238,8 @@ unambiguously "indirect value". #### Specification Appendix ``` - ::= | [ ] + ::= | + ::= [ ] ::= | ::= | | From ca053e875dcba4999f0f664a94cd72579a071da3 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 4 Apr 2025 15:03:10 -0400 Subject: [PATCH 30/33] more doc --- docs/design/datacontracts/data_descriptor.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/data_descriptor.md b/docs/design/datacontracts/data_descriptor.md index 4789a6b4fe178e..56f10a4282fcfc 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -243,7 +243,10 @@ unambiguously "indirect value". ::= | ::= | | - is identified by a "0x" or "0X" prefix + is any JSON string element + is any JSON number element + is a which can be parsed as a hexadecimal number prefixed with "0x" or "0X" + is a which can be parsed as a decimal number. ``` #### Parsing Rules From 4de13eb7c265c961890f1d4ebf341b8710c7dfd2 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 7 Apr 2025 11:24:48 -0400 Subject: [PATCH 31/33] add note about undefined behavior --- docs/design/datacontracts/data_descriptor.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/data_descriptor.md b/docs/design/datacontracts/data_descriptor.md index 56f10a4282fcfc..0189b8a2d685c3 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -215,7 +215,7 @@ The global values will be in an array, with each value described by a dictionary * optional `"value": ` where `` is defined below -Numeric constants must be within the range of the type of the global value. +Numeric constants must be within the range of the type of the global value. If a constant is out of range, behavior is undefined. **Compact format**: @@ -227,7 +227,7 @@ The global values will be in a dictionary, with each key being the name of a glo Where `` is defined as below. -Numeric constants must be within the range of the type of the global value. +Numeric constants must be within the range of the type of the global value. If a constant is out of range, behavior is undefined. Note that a two element array is unambiguously "type and value", whereas a one-element array is unambiguously "indirect value". From 4880ec048ed16db99f9dfe8b8de7b262c38c5628 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 7 Apr 2025 11:30:38 -0400 Subject: [PATCH 32/33] clean up BNF --- docs/design/datacontracts/data_descriptor.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/data_descriptor.md b/docs/design/datacontracts/data_descriptor.md index 0189b8a2d685c3..9880f225c7412e 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -238,8 +238,8 @@ unambiguously "indirect value". #### Specification Appendix ``` - ::= | - ::= [ ] + ::= | + ::= [ ] ::= | ::= | | From d0a9e0b81daf45494748b860e5c8b3fbf87a0bdf Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 9 Apr 2025 11:22:40 -0400 Subject: [PATCH 33/33] fix macro expansion stringification --- src/coreclr/debug/runtimeinfo/datadescriptor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index d6e570578431f4..f8b1ed38ed05dc 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -84,7 +84,7 @@ struct CDacStringPoolSizes #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(MAKE_FIELDLEN_NAME(tyname,membername), sizeof(#membername)) \ DECL_LEN(MAKE_FIELDTYPELEN_NAME(tyname,membername), sizeof(#membertyname)) #define CDAC_GLOBAL_STRING(name, stringval) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ - DECL_LEN(MAKE_GLOBALVALUELEN_NAME(name), sizeof(#stringval)) + DECL_LEN(MAKE_GLOBALVALUELEN_NAME(name), sizeof(STRINGIFY(stringval))) #define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) #define CDAC_GLOBAL(name,tyname,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ DECL_LEN(MAKE_GLOBALTYPELEN_NAME(name), sizeof(#tyname)) @@ -327,7 +327,7 @@ struct MagicAndBlob BlobDataDescriptor = { #define CDAC_BASELINE(name) name "\0" #define CDAC_TYPE_BEGIN(name) #name "\0" #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #membername "\0" #membertyname "\0" -#define CDAC_GLOBAL_STRING(name,value) #name "\0" #value "\0" +#define CDAC_GLOBAL_STRING(name,value) #name "\0" STRINGIFY(value) "\0" #define CDAC_GLOBAL_POINTER(name,value) #name "\0" #define CDAC_GLOBAL(name,tyname,value) #name "\0" #tyname "\0" #include "datadescriptor.h"