From c6bf4e3f3cf823515c68d70d659f67b1f5b719b2 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 17 Apr 2025 15:53:17 -0400 Subject: [PATCH 01/54] wip --- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index f04d39aa8000a1..cd6e5da5152fa9 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -4,6 +4,8 @@ using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -131,8 +133,98 @@ int IXCLRDataProcess.EndEnumModules(ulong handle) int IXCLRDataProcess.GetModuleByAddress(ulong address, /*IXCLRDataModule*/ void** mod) => _legacyProcess is not null ? _legacyProcess.GetModuleByAddress(address, mod) : HResults.E_NOTIMPL; + // internal class EnumMethodInstances + // { + // private readonly Target _target; + // public EnumMethodInstances(Target target) + // { + // _target = target; + + // } + + // public int Start(TargetPointer methodDesc, IXCLRDataAppDomain* appDomain) + // { + // if (!HasClassOrMethodInstantiation(methodDesc) && !HasNativeCodeAnyVersion(methodDesc)) + // { + // return HResults.S_FALSE; + // } + + // Console.WriteLine((nint)appDomain); + + // return HResults.S_OK; + // } + + // private bool HasNativeCodeAnyVersion(TargetPointer methodDesc) + // { + // IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + // ICodeVersions cv = _target.Contracts.CodeVersions; + + // MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDesc); + // TargetCodePointer pcode = rts.GetNativeCode(mdHandle); + + // if (pcode == TargetCodePointer.Null) + // { + // NativeCodeVersionHandle nativeCodeVersion = cv.GetActiveNativeCodeVersion(methodDesc); + // if (nativeCodeVersion.Valid) + // { + // pcode = cv.GetNativeCode(nativeCodeVersion); + // } + // } + + // return pcode != TargetCodePointer.Null; + // } + + // private bool HasClassOrMethodInstantiation(TargetPointer mdAddr) + // { + // return HasClassInstantiation(mdAddr) || HasMethodInstantiation(mdAddr); + // } + + // private bool HasClassInstantiation(TargetPointer mdAddr) + // { + // IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + // MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); + // TargetPointer mtAddr = rts.GetMethodTable(md); + // TypeHandle mt = rts.GetTypeHandle(mtAddr); + // return !rts.GetInstantiation(mt).IsEmpty; + // } + + // private bool HasMethodInstantiation(TargetPointer mdAddr) + // { + // IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + // MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); + + // if (rts.IsGenericMethodDefinition(md)) return true; + // return !rts.GetGenericMethodInstantiation(md).IsEmpty; + // } + // } + int IXCLRDataProcess.StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) - => _legacyProcess is not null ? _legacyProcess.StartEnumMethodInstancesByAddress(address, appDomain, handle) : HResults.E_NOTIMPL; + { + // int hr = HResults.S_OK; + // try + // { + // *handle = 0; + // hr = HResults.S_FALSE; + + // IExecutionManager eman = _target.Contracts.ExecutionManager; + // if (eman.GetCodeBlockHandle(address) is CodeBlockHandle cbh && eman.GetMethodDesc(cbh) is TargetPointer methodDesc) + // { + // // EnumMethodInstances emi = new(methodDesc, appDomain != null ? (IXCLRDataAppDomain*)appDomain : null); + // // GCHandle gcHandle = GCHandle.Alloc(emi); + // // *handle = (ulong)GCHandle.ToIntPtr(gcHandle).ToInt64(); + + // } + // } + // catch (System.Exception ex) + // { + // hr = ex.HResult; + // } + return _legacyProcess is not null ? _legacyProcess.StartEnumMethodInstancesByAddress(address, appDomain, handle) : HResults.E_NOTIMPL; + + //return hr; + } int IXCLRDataProcess.EnumMethodInstanceByAddress(ulong* handle, /*IXCLRDataMethodInstance*/ void** method) => _legacyProcess is not null ? _legacyProcess.EnumMethodInstanceByAddress(handle, method) : HResults.E_NOTIMPL; From e71f95ed8c7d4521dff63b853a02c6a29e6e8d16 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 21 Apr 2025 12:42:51 -0400 Subject: [PATCH 02/54] wip --- .../debug/runtimeinfo/datadescriptor.h | 9 + src/coreclr/vm/dacenumerablehash.h | 6 +- src/coreclr/vm/typehash.h | 13 ++ .../Contracts/ILoader.cs | 1 + .../DataType.cs | 1 + .../Contracts/Loader_1.cs | 14 ++ .../Data/DacEnumerableHash.cs | 105 ++++++++++ .../Data/EETypeHashTable.cs | 21 ++ .../Data/Module.cs | 2 + .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 195 ++++++++++-------- 10 files changed, 280 insertions(+), 87 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index aab39b5678ad99..add2f8810ac103 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -227,6 +227,7 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, Path, cdac_data::Path) CDAC_TYPE_FIELD(Module, /*pointer*/, FileName, cdac_data::FileName) CDAC_TYPE_FIELD(Module, /*pointer*/, ReadyToRunInfo, cdac_data::ReadyToRunInfo) CDAC_TYPE_FIELD(Module, /*pointer*/, GrowableSymbolStream, cdac_data::GrowableSymbolStream) +CDAC_TYPE_FIELD(Module, /*pointer*/, AvailableTypeParams, offsetof(Module, m_pAvailableParamTypes)) CDAC_TYPE_FIELD(Module, /*pointer*/, FieldDefToDescMap, cdac_data::FieldDefToDescMap) CDAC_TYPE_FIELD(Module, /*pointer*/, ManifestModuleReferencesMap, cdac_data::ManifestModuleReferencesMap) @@ -808,6 +809,14 @@ CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Lr, offsetof(CalleeSavedRegiste #endif // Platform switch CDAC_TYPE_END(CalleeSavedRegisters) +CDAC_TYPE_BEGIN(EETypeHashTable) +CDAC_TYPE_INDETERMINATE(EETypeHashTable) +CDAC_TYPE_FIELD(EETypeHashTable, /*pointer*/, Buckets, cdac_data::Buckets) +CDAC_TYPE_FIELD(EETypeHashTable, /*uint32*/, Count, cdac_data::Count) +CDAC_TYPE_FIELD(EETypeHashTable, /*pointer*/, VolatileEntryValue, cdac_data::VolatileEntryValue) +CDAC_TYPE_FIELD(EETypeHashTable, /*pointer*/, VolatileEntryNextEntry, cdac_data::VolatileEntryNextEntry) +CDAC_TYPE_END(EETypeHashTable) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/dacenumerablehash.h b/src/coreclr/vm/dacenumerablehash.h index dda541ed951bf4..a1295cccc14b29 100644 --- a/src/coreclr/vm/dacenumerablehash.h +++ b/src/coreclr/vm/dacenumerablehash.h @@ -55,7 +55,7 @@ // // Synchronization: It is permissable to read data from the hash without taking a lock as long as: // 1) Any hash modifications are performed under a lock or otherwise serialized. -// 2) Any miss on a lookup is handled by taking a lock are retry-ing the lookup. +// 2) Any miss on a lookup is handled by taking a lock and retry-ing the lookup. // // OVERALL DESIGN // @@ -314,7 +314,7 @@ class DacEnumerableHashTable static const int SLOT_ENDSENTINEL = 2; // normal slots start at slot #3 static const int SKIP_SPECIAL_SLOTS = 3; - + static DWORD GetLength(DPTR(PTR_VolatileEntry) buckets) { return (DWORD)dac_cast(buckets[SLOT_LENGTH]); @@ -335,6 +335,8 @@ class DacEnumerableHashTable DPTR(PTR_VolatileEntry) m_pBuckets; // Pointer to a simple bucket list (array of VolatileEntry pointers) DWORD m_cEntries; // Count of elements + + friend struct ::cdac_data; }; #endif // __DAC_ENUMERABLE_HASH_INCLUDED diff --git a/src/coreclr/vm/typehash.h b/src/coreclr/vm/typehash.h index 86a97b888dd2c8..23dc5d9e364823 100644 --- a/src/coreclr/vm/typehash.h +++ b/src/coreclr/vm/typehash.h @@ -10,6 +10,7 @@ #define _TYPE_HASH_H #include "dacenumerablehash.h" +#include "cdacdata.h" //======================================================================================== // This hash table is used by class loaders to look up constructed types: @@ -136,6 +137,18 @@ class EETypeHashTable : public DacEnumerableHashTable; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Buckets = offsetof(EETypeHashTable, m_pBuckets); + static constexpr size_t Count = offsetof(EETypeHashTable, m_cEntries); + + static constexpr size_t VolatileEntryValue = offsetof(EETypeHashTable::VolatileEntry, m_sValue); + static constexpr size_t VolatileEntryNextEntry = offsetof(EETypeHashTable::VolatileEntry, m_pNextEntry); }; #endif /* _TYPE_HASH_H */ diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index cda96efcddd73e..2deed99f79d9de 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -87,6 +87,7 @@ public interface ILoader : IContract ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); string GetPath(ModuleHandle handle) => throw new NotImplementedException(); string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); + List GetAvailableTypeParams(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 5e67e0c569d552..6cdcc13c01ae92 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -99,6 +99,7 @@ public enum DataType GCCoverageInfo, ArrayListBase, ArrayListBlock, + EETypeHashTable, TransitionBlock, DebuggerEval, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 1f51583b9e69fb..14b3b057c1145a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -188,6 +188,20 @@ bool ILoader.TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, o return true; } + List ILoader.GetAvailableTypeParams(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + List typeParams = []; + + if (module.AvailableTypeParams == TargetPointer.Null) + return typeParams; + + EETypeHashTable typeHashTable = _target.ProcessedData.GetOrAdd(module.AvailableTypeParams); + typeParams.AddRange(typeHashTable.Entries); + + return typeParams; + } + bool ILoader.IsProbeExtensionResultValid(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs new file mode 100644 index 00000000000000..af7cb1fcd9a555 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +/// +/// Parses hash tables that are implemented by DacEnumerableHash defined in dacenumerablehash.h +/// Requires the following datadescriptor fields on the inherited type: +/// Buckets - Pointer to array of VolatileEntry pointers +/// Count - Count of elements +/// VolatileEntryValue - Offset of the value in the VolatileEntry struct +/// VolatileEntryNextEntry - Offset of the next entry pointer in the VolatileEntry struct +/// +internal sealed class DacEnumerableHash +{ + private const int SLOT_LENGTH = 0; + // private const int SLOT_NIEXT = 1; + private const int SLOT_ENDSENTINEL = 2; + private const int SKIP_SPECIAL_SLOTS = 3; + + private readonly Target _target; + private readonly Target.TypeInfo _type; + + public DacEnumerableHash(Target target, TargetPointer address, Target.TypeInfo type) + { + // init fields + _target = target; + _type = type; + + Buckets = _target.ReadPointer(address + (ulong)_type.Fields[nameof(Buckets)].Offset); + Count = _target.Read(address + (ulong)_type.Fields[nameof(Count)].Offset); + + // read items in the hash table + uint length = GetLength(); + + List entries = []; + for (int i = 0; i < length; i++) + { + // indexes 0, 1, 2 have special purposes. buckets start at SKIP_SPECIAL_SLOTS + int bucketOffset = i + SKIP_SPECIAL_SLOTS; + TargetPointer chainElement = _target.ReadPointer(Buckets + (ulong)(bucketOffset * _target.PointerSize)); + List elements = ReadChain(chainElement); + entries.AddRange(elements); + } + + Debug.Assert(Count == entries.Count); + + Entries = entries; + } + + public TargetPointer Buckets { get; init; } + public uint Count { get; init; } + + public IReadOnlyList Entries { get; init; } + + internal sealed class VolatileEntry + { + public VolatileEntry(Target target, TargetPointer address, Target.TypeInfo type) + { + // offsets are stored on the parent type + VolatileEntryValue = address + (ulong)type.Fields[nameof(VolatileEntryValue)].Offset; + VolatileEntryNextEntry = target.ReadPointer(address + (ulong)type.Fields[nameof(VolatileEntryNextEntry)].Offset); + } + + public TargetPointer VolatileEntryValue { get; init; } + public TargetPointer VolatileEntryNextEntry { get; init; } + } + + private uint GetLength() + { + // First pointer is a size_t length + TargetPointer length = _target.ReadPointer(Buckets + (ulong)(SLOT_LENGTH * _target.PointerSize)); + return (uint)length; + } + + private TargetPointer GetBaseSentinel() + { + // Second pointer is a size_t base sentinel + TargetPointer baseSentinel = _target.ReadPointer(Buckets + (ulong)(SLOT_ENDSENTINEL * _target.PointerSize)); + return baseSentinel; + } + + private static bool IsEndSentinel(TargetPointer value) + { + return ((ulong)value & 0x1) == 0x1; + } + + private List ReadChain(TargetPointer chainElement) + { + List elements = []; + + while (!IsEndSentinel(chainElement)) + { + VolatileEntry volatileEntry = new(_target, chainElement, _type); + elements.Add(volatileEntry.VolatileEntryValue); + + chainElement = volatileEntry.VolatileEntryNextEntry; + } + + return elements; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs new file mode 100644 index 00000000000000..892094eb18fd1a --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class EETypeHashTable : IData +{ + static EETypeHashTable IData.Create(Target target, TargetPointer address) => new EETypeHashTable(target, address); + public EETypeHashTable(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EETypeHashTable); + + DacEnumerableHash baseHashTable = new(target, address, type); + + Entries = baseHashTable.Entries; + } + + public IReadOnlyList Entries { get; init; } = []; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index c9b986a20c135d..3f7c4624b47db1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -24,6 +24,7 @@ public Module(Target target, TargetPointer address) FileName = target.ReadPointer(address + (ulong)type.Fields[nameof(FileName)].Offset); ReadyToRunInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(ReadyToRunInfo)].Offset); GrowableSymbolStream = target.ReadPointer(address + (ulong)type.Fields[nameof(GrowableSymbolStream)].Offset); + AvailableTypeParams = target.ReadPointer(address + (ulong)type.Fields[nameof(AvailableTypeParams)].Offset); FieldDefToDescMap = address + (ulong)type.Fields[nameof(FieldDefToDescMap)].Offset; ManifestModuleReferencesMap = address + (ulong)type.Fields[nameof(ManifestModuleReferencesMap)].Offset; @@ -44,6 +45,7 @@ public Module(Target target, TargetPointer address) public TargetPointer FileName { get; init; } public TargetPointer ReadyToRunInfo { get; init; } public TargetPointer GrowableSymbolStream { get; init; } + public TargetPointer AvailableTypeParams { get; init; } public TargetPointer FieldDefToDescMap { get; init; } public TargetPointer ManifestModuleReferencesMap { get; init; } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index cd6e5da5152fa9..be956a0541cc3f 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.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.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Contracts; @@ -133,94 +134,118 @@ int IXCLRDataProcess.EndEnumModules(ulong handle) int IXCLRDataProcess.GetModuleByAddress(ulong address, /*IXCLRDataModule*/ void** mod) => _legacyProcess is not null ? _legacyProcess.GetModuleByAddress(address, mod) : HResults.E_NOTIMPL; - // internal class EnumMethodInstances - // { - // private readonly Target _target; - // public EnumMethodInstances(Target target) - // { - // _target = target; - - // } - - // public int Start(TargetPointer methodDesc, IXCLRDataAppDomain* appDomain) - // { - // if (!HasClassOrMethodInstantiation(methodDesc) && !HasNativeCodeAnyVersion(methodDesc)) - // { - // return HResults.S_FALSE; - // } - - // Console.WriteLine((nint)appDomain); - - // return HResults.S_OK; - // } - - // private bool HasNativeCodeAnyVersion(TargetPointer methodDesc) - // { - // IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - // ICodeVersions cv = _target.Contracts.CodeVersions; - - // MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDesc); - // TargetCodePointer pcode = rts.GetNativeCode(mdHandle); - - // if (pcode == TargetCodePointer.Null) - // { - // NativeCodeVersionHandle nativeCodeVersion = cv.GetActiveNativeCodeVersion(methodDesc); - // if (nativeCodeVersion.Valid) - // { - // pcode = cv.GetNativeCode(nativeCodeVersion); - // } - // } - - // return pcode != TargetCodePointer.Null; - // } - - // private bool HasClassOrMethodInstantiation(TargetPointer mdAddr) - // { - // return HasClassInstantiation(mdAddr) || HasMethodInstantiation(mdAddr); - // } - - // private bool HasClassInstantiation(TargetPointer mdAddr) - // { - // IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - - // MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); - // TargetPointer mtAddr = rts.GetMethodTable(md); - // TypeHandle mt = rts.GetTypeHandle(mtAddr); - // return !rts.GetInstantiation(mt).IsEmpty; - // } - - // private bool HasMethodInstantiation(TargetPointer mdAddr) - // { - // IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - - // MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); - - // if (rts.IsGenericMethodDefinition(md)) return true; - // return !rts.GetGenericMethodInstantiation(md).IsEmpty; - // } - // } + internal class EnumMethodInstances + { + private readonly Target _target; + public EnumMethodInstances(Target target) + { + _target = target; + + } + + public int Start(TargetPointer methodDesc, TargetPointer appDomain) + { + ILoader loader = _target.Contracts.Loader; + + if (appDomain == TargetPointer.Null) + { + TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + appDomain = _target.ReadPointer(appDomainPointer); + } + + if (!HasClassOrMethodInstantiation(methodDesc) && !HasNativeCodeAnyVersion(methodDesc)) + { + return HResults.S_FALSE; + } + + List modules = loader.GetAssemblies( + appDomain, + AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); + + foreach (Contracts.ModuleHandle moduleHandle in modules) + { + List typeParams = loader.GetAvailableTypeParams(moduleHandle); + foreach (TargetPointer type in typeParams) + { + Console.WriteLine(type); + } + } + + // for each module find available type params + // for each module get inst method hash table + + return HResults.S_OK; + } + + private bool HasNativeCodeAnyVersion(TargetPointer methodDesc) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + ICodeVersions cv = _target.Contracts.CodeVersions; + + MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDesc); + TargetCodePointer pcode = rts.GetNativeCode(mdHandle); + + if (pcode == TargetCodePointer.Null) + { + NativeCodeVersionHandle nativeCodeVersion = cv.GetActiveNativeCodeVersion(methodDesc); + if (nativeCodeVersion.Valid) + { + pcode = cv.GetNativeCode(nativeCodeVersion); + } + } + + return pcode != TargetCodePointer.Null; + } + + private bool HasClassOrMethodInstantiation(TargetPointer mdAddr) + { + return HasClassInstantiation(mdAddr) || HasMethodInstantiation(mdAddr); + } + + private bool HasClassInstantiation(TargetPointer mdAddr) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle mt = rts.GetTypeHandle(mtAddr); + return !rts.GetInstantiation(mt).IsEmpty; + } + + private bool HasMethodInstantiation(TargetPointer mdAddr) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); + + if (rts.IsGenericMethodDefinition(md)) return true; + return !rts.GetGenericMethodInstantiation(md).IsEmpty; + } + } int IXCLRDataProcess.StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) { - // int hr = HResults.S_OK; - // try - // { - // *handle = 0; - // hr = HResults.S_FALSE; - - // IExecutionManager eman = _target.Contracts.ExecutionManager; - // if (eman.GetCodeBlockHandle(address) is CodeBlockHandle cbh && eman.GetMethodDesc(cbh) is TargetPointer methodDesc) - // { - // // EnumMethodInstances emi = new(methodDesc, appDomain != null ? (IXCLRDataAppDomain*)appDomain : null); - // // GCHandle gcHandle = GCHandle.Alloc(emi); - // // *handle = (ulong)GCHandle.ToIntPtr(gcHandle).ToInt64(); - - // } - // } - // catch (System.Exception ex) - // { - // hr = ex.HResult; - // } + int hr = HResults.S_OK; + try + { + *handle = 0; + hr = HResults.S_FALSE; + + IExecutionManager eman = _target.Contracts.ExecutionManager; + if (eman.GetCodeBlockHandle(address) is CodeBlockHandle cbh && eman.GetMethodDesc(cbh) is TargetPointer methodDesc) + { + + EnumMethodInstances emi = new(_target); + emi.Start(methodDesc, (ulong)appDomain); + // GCHandle gcHandle = GCHandle.Alloc(emi); + // *handle = (ulong)GCHandle.ToIntPtr(gcHandle).ToInt64(); + + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } return _legacyProcess is not null ? _legacyProcess.StartEnumMethodInstancesByAddress(address, appDomain, handle) : HResults.E_NOTIMPL; //return hr; From e21bd2ee6ae5af5ef2ae1be100434733ff69eebe Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 23 Apr 2025 17:14:35 -0400 Subject: [PATCH 03/54] wip --- .../debug/runtimeinfo/datadescriptor.h | 9 ++ src/coreclr/vm/instmethhash.h | 13 ++ .../Contracts/ILoader.cs | 1 + .../DataType.cs | 1 + .../Contracts/Loader_1.cs | 18 +++ .../Data/EETypeHashTable.cs | 11 +- .../Data/InstMethodHashTable.cs | 47 ++++++ .../Data/Module.cs | 2 + .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 139 +++++++++++++++++- 9 files changed, 234 insertions(+), 7 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index add2f8810ac103..7b007f3e578439 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -228,6 +228,7 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, FileName, cdac_data::FileName) CDAC_TYPE_FIELD(Module, /*pointer*/, ReadyToRunInfo, cdac_data::ReadyToRunInfo) CDAC_TYPE_FIELD(Module, /*pointer*/, GrowableSymbolStream, cdac_data::GrowableSymbolStream) CDAC_TYPE_FIELD(Module, /*pointer*/, AvailableTypeParams, offsetof(Module, m_pAvailableParamTypes)) +CDAC_TYPE_FIELD(Module, /*pointer*/, InstMethodHashTable, offsetof(Module, m_pInstMethodHashTable)) CDAC_TYPE_FIELD(Module, /*pointer*/, FieldDefToDescMap, cdac_data::FieldDefToDescMap) CDAC_TYPE_FIELD(Module, /*pointer*/, ManifestModuleReferencesMap, cdac_data::ManifestModuleReferencesMap) @@ -817,6 +818,14 @@ CDAC_TYPE_FIELD(EETypeHashTable, /*pointer*/, VolatileEntryValue, cdac_data::VolatileEntryNextEntry) CDAC_TYPE_END(EETypeHashTable) +CDAC_TYPE_BEGIN(InstMethodHashTable) +CDAC_TYPE_INDETERMINATE(InstMethodHashTable) +CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, Buckets, cdac_data::Buckets) +CDAC_TYPE_FIELD(InstMethodHashTable, /*uint32*/, Count, cdac_data::Count) +CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, VolatileEntryValue, cdac_data::VolatileEntryValue) +CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, VolatileEntryNextEntry, cdac_data::VolatileEntryNextEntry) +CDAC_TYPE_END(InstMethodHashTable) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/instmethhash.h b/src/coreclr/vm/instmethhash.h index dc77d892ee63fc..ef59081fc2a947 100644 --- a/src/coreclr/vm/instmethhash.h +++ b/src/coreclr/vm/instmethhash.h @@ -14,6 +14,7 @@ #define _INSTMETHHASH_H #include "dacenumerablehash.h" +#include "cdacdata.h" class AllocMemTracker; @@ -145,6 +146,18 @@ class InstMethodHashTable : public DacEnumerableHashTable; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Buckets = offsetof(InstMethodHashTable, m_pBuckets); + static constexpr size_t Count = offsetof(InstMethodHashTable, m_cEntries); + + static constexpr size_t VolatileEntryValue = offsetof(InstMethodHashTable::VolatileEntry, m_sValue); + static constexpr size_t VolatileEntryNextEntry = offsetof(InstMethodHashTable::VolatileEntry, m_pNextEntry); }; #endif /* _INSTMETHHASH_H */ diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 2deed99f79d9de..5b61ba5bfc2704 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -88,6 +88,7 @@ public interface ILoader : IContract string GetPath(ModuleHandle handle) => throw new NotImplementedException(); string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); List GetAvailableTypeParams(ModuleHandle handle) => throw new NotImplementedException(); + List GetInstantiatedMethods(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 6cdcc13c01ae92..6039bf6e8aed3b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -100,6 +100,7 @@ public enum DataType ArrayListBase, ArrayListBlock, EETypeHashTable, + InstMethodHashTable, TransitionBlock, DebuggerEval, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 14b3b057c1145a..050686dc829a07 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -202,6 +202,24 @@ List ILoader.GetAvailableTypeParams(ModuleHandle handle) return typeParams; } + List ILoader.GetInstantiatedMethods(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + List typeParams = []; + + if (module.InstMethodHashTable == TargetPointer.Null) + return typeParams; + + InstMethodHashTable methodHashTable = _target.ProcessedData.GetOrAdd(module.AvailableTypeParams); + + foreach (InstMethodHashTable.InstMethodHashTableEntry entry in methodHashTable.Entries) + { + typeParams.Add(entry.MethodDesc); + } + + return typeParams; + } + bool ILoader.IsProbeExtensionResultValid(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs index 892094eb18fd1a..4c099b54d18fab 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs @@ -1,6 +1,7 @@ // 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; namespace Microsoft.Diagnostics.DataContractReader.Data; @@ -14,8 +15,14 @@ public EETypeHashTable(Target target, TargetPointer address) DacEnumerableHash baseHashTable = new(target, address, type); - Entries = baseHashTable.Entries; + List entries = []; + foreach (TargetPointer entry in baseHashTable.Entries) + { + TargetPointer typeHandle = target.ReadPointer(entry); + entries.Add(typeHandle.Value & ~0x1ul); + } + Entries = entries; } - public IReadOnlyList Entries { get; init; } = []; + public IReadOnlyList Entries { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs new file mode 100644 index 00000000000000..81cba4eb6da11d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs @@ -0,0 +1,47 @@ +// 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 System.Reflection.Metadata; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class InstMethodHashTable : IData +{ + static InstMethodHashTable IData.Create(Target target, TargetPointer address) => new InstMethodHashTable(target, address); + public InstMethodHashTable(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.InstMethodHashTable); + + DacEnumerableHash baseHashTable = new(target, address, type); + + List entries = []; + foreach (TargetPointer entry in baseHashTable.Entries) + { + TargetPointer methodDescPtr = target.ReadPointer(entry); + InstMethodHashTableEntry instMethodHashTableEntry = new() + { + MethodDesc = methodDescPtr.Value & ~0x3ul, + Flags = (InstMethodHashTableFlags)(methodDescPtr.Value & 0x3ul) + }; + entries.Add(instMethodHashTableEntry); + } + Entries = entries; + } + + public IReadOnlyList Entries { get; init; } + + public readonly struct InstMethodHashTableEntry + { + public TargetPointer MethodDesc { get; init; } + public InstMethodHashTableFlags Flags { get; init; } + } + + [Flags] + public enum InstMethodHashTableFlags + { + UnboxingStub = 0x1, + RequiresInstArg = 0x2, + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index 3f7c4624b47db1..3fe2a7fbb0274e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -25,6 +25,7 @@ public Module(Target target, TargetPointer address) ReadyToRunInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(ReadyToRunInfo)].Offset); GrowableSymbolStream = target.ReadPointer(address + (ulong)type.Fields[nameof(GrowableSymbolStream)].Offset); AvailableTypeParams = target.ReadPointer(address + (ulong)type.Fields[nameof(AvailableTypeParams)].Offset); + InstMethodHashTable = target.ReadPointer(address + (ulong)type.Fields[nameof(InstMethodHashTable)].Offset); FieldDefToDescMap = address + (ulong)type.Fields[nameof(FieldDefToDescMap)].Offset; ManifestModuleReferencesMap = address + (ulong)type.Fields[nameof(ManifestModuleReferencesMap)].Offset; @@ -46,6 +47,7 @@ public Module(Target target, TargetPointer address) public TargetPointer ReadyToRunInfo { get; init; } public TargetPointer GrowableSymbolStream { get; init; } public TargetPointer AvailableTypeParams { get; init; } + public TargetPointer InstMethodHashTable { get; init; } public TargetPointer FieldDefToDescMap { get; init; } public TargetPointer ManifestModuleReferencesMap { get; init; } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index be956a0541cc3f..7c6c51dd8f6248 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -2,11 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; +using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; + namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -137,16 +140,30 @@ int IXCLRDataProcess.GetModuleByAddress(ulong address, /*IXCLRDataModule*/ void* internal class EnumMethodInstances { private readonly Target _target; - public EnumMethodInstances(Target target) + private readonly TargetPointer _mainMethodDesc; + private readonly TargetPointer _appDomain; + private readonly ILoader _loader; + private readonly IRuntimeTypeSystem _rts; + public EnumMethodInstances(Target target, TargetPointer methodDesc, TargetPointer appDomain) { _target = target; + _mainMethodDesc = methodDesc; + if (appDomain == TargetPointer.Null) + { + TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + _appDomain = _target.ReadPointer(appDomainPointer); + } + else + { + _appDomain = appDomain; + } + _loader = _target.Contracts.Loader; + _rts = _target.Contracts.RuntimeTypeSystem; } public int Start(TargetPointer methodDesc, TargetPointer appDomain) { - ILoader loader = _target.Contracts.Loader; - if (appDomain == TargetPointer.Null) { TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain); @@ -158,13 +175,13 @@ public int Start(TargetPointer methodDesc, TargetPointer appDomain) return HResults.S_FALSE; } - List modules = loader.GetAssemblies( + List modules = _loader.GetAssemblies( appDomain, AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); foreach (Contracts.ModuleHandle moduleHandle in modules) { - List typeParams = loader.GetAvailableTypeParams(moduleHandle); + List typeParams = _loader.GetAvailableTypeParams(moduleHandle); foreach (TargetPointer type in typeParams) { Console.WriteLine(type); @@ -177,6 +194,117 @@ public int Start(TargetPointer methodDesc, TargetPointer appDomain) return HResults.S_OK; } + public IEnumerable IterateMethodInstantiations(Contracts.ModuleHandle moduleHandle) + { + List methodInstantiations = _loader.GetInstantiatedMethods(moduleHandle); + + foreach (TargetPointer methodPtr in methodInstantiations) + { + yield return _rts.GetMethodDescHandle(methodPtr); + } + } + + public IEnumerable IterateTypeParams(Contracts.ModuleHandle moduleHandle) + { + List typeParams = _loader.GetAvailableTypeParams(moduleHandle); + + foreach (TargetPointer type in typeParams) + { + yield return _rts.GetTypeHandle(type); + } + } + + public IEnumerable IterateModules() + { + ILoader loader = _target.Contracts.Loader; + List modules = loader.GetAssemblies( + _appDomain, + AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); + + foreach (Contracts.ModuleHandle moduleHandle in modules) + { + yield return moduleHandle; + } + } + + public IEnumerable IterateMethodInstances() + { + /* + There are 4 cases for method instances: + 1. Non-generic method on non-generic type (There is 1 MethodDesc for the method (excluding unboxing stubs, and such) + 2. Generic method on non-generic type (There is a generic defining method + a instantiated method for each particular instantiation) + 3. Non-generic method on generic type (There is 1 method for each generic instance created + 1 for the method on the uninstantiated generic type) + 4. Generic method on Generic type (There are N generic defining methods where N is the number of generic instantiations of the generic type + 1 on the uninstantiated generic types + M different generic instances of the method) + */ + + MethodDescHandle mainMD = _rts.GetMethodDescHandle(_mainMethodDesc); + + if (!HasClassOrMethodInstantiation(_mainMethodDesc)) + { + // case 1 + // no method or class instantiation, then it's not generic. + yield return mainMD; + yield break; + } + + TargetPointer mtAddr = _rts.GetMethodTable(mainMD); + TypeHandle mainMT = _rts.GetTypeHandle(mtAddr); + TargetPointer mainModule = _rts.GetModule(mainMT); + uint mainMTToken = _rts.GetTypeDefToken(mainMT); + uint mainMDToken = _rts.GetMethodToken(mainMD); + ushort slotNum = _rts.GetSlotNumber(mainMD); + + if (HasMethodInstantiation(_mainMethodDesc)) + { + // case 2/4 + // 2 is trivial, 4 is covered because the defining method on a generic type is not instantiated + foreach (Contracts.ModuleHandle moduleHandle in IterateModules()) + { + foreach (MethodDescHandle methodDesc in IterateMethodInstantiations(moduleHandle)) + { + TypeHandle methodTypeHandle = _rts.GetTypeHandle(_rts.GetMethodTable(methodDesc)); + + if (mainModule != _rts.GetModule(methodTypeHandle)) continue; + if (mainMDToken != _rts.GetMethodToken(methodDesc)) continue; + + yield return methodDesc; + } + } + } + + if (HasClassInstantiation(_mainMethodDesc)) + { + // case 3 + // class instantiations are only interesting if the method is not generic + foreach (Contracts.ModuleHandle moduleHandle in IterateModules()) + { + if (HasClassInstantiation(_mainMethodDesc)) + { + foreach (Contracts.TypeHandle typeParam in IterateTypeParams(moduleHandle)) + { + uint typeParamToken = _rts.GetTypeDefToken(typeParam); + + // not a MethodTable + if (typeParamToken == 0) continue; + + // Check the class token + if (mainMTToken != typeParamToken) continue; + + // Check the module is correct + if (mainModule != _rts.GetModule(typeParam)) continue; + + TargetPointer cmt = _rts.GetCanonicalMethodTable(typeParam); + TypeHandle cmtHandle = _rts.GetTypeHandle(cmt); + + + + } + } + } + } + + } + private bool HasNativeCodeAnyVersion(TargetPointer methodDesc) { IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; @@ -187,6 +315,7 @@ private bool HasNativeCodeAnyVersion(TargetPointer methodDesc) if (pcode == TargetCodePointer.Null) { + // I think this is equivalent to get any native code version NativeCodeVersionHandle nativeCodeVersion = cv.GetActiveNativeCodeVersion(methodDesc); if (nativeCodeVersion.Valid) { From 24c55497dc81a9eaa29168607d3b341488f6f212 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 24 Apr 2025 12:20:16 -0400 Subject: [PATCH 04/54] wip --- src/coreclr/vm/methoditer.cpp | 18 +-- .../Contracts/IRuntimeTypeSystem.cs | 2 + .../Contracts/RuntimeTypeSystem_1.cs | 12 ++ .../Legacy/ClrDataMethodInstance.cs | 107 +++++++++++++ .../Legacy/ClrDataModule.cs | 7 +- .../Legacy/IXCLRData.cs | 80 +++++++++- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 144 +++++++++++------- 7 files changed, 306 insertions(+), 64 deletions(-) create mode 100644 src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs diff --git a/src/coreclr/vm/methoditer.cpp b/src/coreclr/vm/methoditer.cpp index 67eca3ec5851ce..95c760a818a046 100644 --- a/src/coreclr/vm/methoditer.cpp +++ b/src/coreclr/vm/methoditer.cpp @@ -44,18 +44,18 @@ BOOL LoadedMethodDescIterator::Next( m_fFirstTime = FALSE; // This is the 1st time we've called Next(). must Initialize iterator - if (m_mainMD == NULL) - { - m_mainMD = m_module->LookupMethodDef(m_md); - } + // if (m_mainMD == NULL) + // { + // m_mainMD = m_module->LookupMethodDef(m_md); + // } // note m_mainMD should be sufficiently restored to allow us to get // at the method table, flags and token etc. - if (m_mainMD == NULL) - { - *pAssemblyHolder = NULL; - return FALSE; - } + // if (m_mainMD == NULL) + // { + // *pAssemblyHolder = NULL; + // return FALSE; + // } // Needs to work w/ non-generic methods too. if (!m_mainMD->HasClassOrMethodInstantiation()) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 61c4274fc763e5..0d5fac5d2c7522 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -88,6 +88,8 @@ public interface IRuntimeTypeSystem : IContract TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); TargetPointer GetParentMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); + MethodDescHandle GetMethodDescForSlot(TypeHandle methodTable, ushort slot) => throw new NotImplementedException(); + uint GetBaseSize(TypeHandle typeHandle) => throw new NotImplementedException(); // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) uint GetComponentSize(TypeHandle typeHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 1225c281df73ee..525a725b2d3b11 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -966,6 +966,18 @@ bool IRuntimeTypeSystem.HasNativeCodeSlot(MethodDescHandle methodDesc) return md.HasNativeCodeSlot; } + MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, ushort slot) + { + if (!typeHandle.IsMethodTable()) + throw new ArgumentException($"{nameof(typeHandle)} is not a MethodTable"); + + TargetPointer slotPtr = GetAddressOfMethodTableSlot(GetCanonicalMethodTable(typeHandle), slot); + TargetCodePointer pCode = _target.ReadCodePointer(slotPtr); + + // NOT IMPLEMENTED + return new MethodDescHandle(pCode.AsTargetPointer); + } + TargetPointer IRuntimeTypeSystem.GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) { MethodDesc md = _methodDescs[methodDesc.Address]; diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs new file mode 100644 index 00000000000000..c8ef60a0b509f4 --- /dev/null +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +[GeneratedComClass] +internal sealed unsafe partial class ClrDataMethodInstance : IXCLRDataMethodInstance +{ + private readonly Target _target; + private readonly MethodDescHandle _methodDesc; + private readonly TargetPointer _appDomain; + private readonly IXCLRDataMethodInstance? _legacyImpl; + public ClrDataMethodInstance( + Target target, + MethodDescHandle methodDesc, + TargetPointer appDomain, + IXCLRDataMethodInstance? legacyImpl) + { + _target = target; + _methodDesc = methodDesc; + _appDomain = appDomain; + _legacyImpl = legacyImpl; + } + + int IXCLRDataMethodInstance.GetTypeInstance(void** typeInstance) + => _legacyImpl is not null ? _legacyImpl.GetTypeInstance(typeInstance) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetDefinition(void** methodDefinition) + => _legacyImpl is not null ? _legacyImpl.GetDefinition(methodDefinition) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, out IXCLRDataModule? mod) + { + //_legacyImpl is not null ? _legacyImpl.GetTokenAndScope(token, mod) : HResults.E_NOTIMPL; + int hr = HResults.S_OK; + + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + if (token is not null) + { + *token = rts.GetMethodToken(_methodDesc); + } + if (mod is not null) + { + TargetPointer mtAddr = rts.GetMethodTable(_methodDesc); + TypeHandle mainMT = rts.GetTypeHandle(mtAddr); + TargetPointer module = rts.GetModule(mainMT); + *mod = module.Value; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + return hr; + } + + int IXCLRDataMethodInstance.GetName(uint flags, uint bufLen, uint* nameLen, char* nameBuf) + => _legacyImpl is not null ? _legacyImpl.GetName(flags, bufLen, nameLen, nameBuf) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetFlags(uint* flags) + => _legacyImpl is not null ? _legacyImpl.GetFlags(flags) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.IsSameObject(IXCLRDataMethodInstance* method) + => _legacyImpl is not null ? _legacyImpl.IsSameObject(method) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetEnCVersion(uint* version) + => _legacyImpl is not null ? _legacyImpl.GetEnCVersion(version) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetNumTypeArguments(uint* numTypeArgs) + => _legacyImpl is not null ? _legacyImpl.GetNumTypeArguments(numTypeArgs) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetTypeArgumentByIndex(uint index, void** typeArg) + => _legacyImpl is not null ? _legacyImpl.GetTypeArgumentByIndex(index, typeArg) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetILOffsetsByAddress(ulong address, uint offsetsLen, uint* offsetsNeeded, uint* ilOffsets) + => _legacyImpl is not null ? _legacyImpl.GetILOffsetsByAddress(address, offsetsLen, offsetsNeeded, ilOffsets) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetAddressRangesByILOffset(uint ilOffset, uint rangesLen, uint* rangesNeeded, void* addressRanges) + => _legacyImpl is not null ? _legacyImpl.GetAddressRangesByILOffset(ilOffset, rangesLen, rangesNeeded, addressRanges) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetILAddressMap(uint mapLen, uint* mapNeeded, void* maps) + => _legacyImpl is not null ? _legacyImpl.GetILAddressMap(mapLen, mapNeeded, maps) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.StartEnumExtents(ulong* handle) + => _legacyImpl is not null ? _legacyImpl.StartEnumExtents(handle) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.EnumExtent(ulong* handle, void* extent) + => _legacyImpl is not null ? _legacyImpl.EnumExtent(handle, extent) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.EndEnumExtents(ulong handle) + => _legacyImpl is not null ? _legacyImpl.EndEnumExtents(handle) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) + => _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetRepresentativeEntryAddress(ulong* addr) + => _legacyImpl is not null ? _legacyImpl.GetRepresentativeEntryAddress(addr) : HResults.E_NOTIMPL; +} diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataModule.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataModule.cs index 06a28ad8fa39a5..c5c665848e8f35 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataModule.cs @@ -107,8 +107,11 @@ int IXCLRDataModule.EndEnumMethodDefinitionsByName(ulong handle) int IXCLRDataModule.StartEnumMethodInstancesByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) => _legacyModule is not null ? _legacyModule.StartEnumMethodInstancesByName(name, flags, appDomain, handle) : HResults.E_NOTIMPL; - int IXCLRDataModule.EnumMethodInstanceByName(ulong* handle, /*IXCLRDataMethodInstance*/ void** method) - => _legacyModule is not null ? _legacyModule.EnumMethodInstanceByName(handle, method) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumMethodInstanceByName(ulong* handle, out IXCLRDataMethodInstance? method) + { + method = default; + return _legacyModule is not null ? _legacyModule.EnumMethodInstanceByName(handle, out method) : HResults.E_NOTIMPL; + } int IXCLRDataModule.EndEnumMethodInstancesByName(ulong handle) => _legacyModule is not null ? _legacyModule.EndEnumMethodInstancesByName(handle) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs index 2212af540bb44e..fcfefacf8dacff 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs @@ -81,7 +81,7 @@ internal unsafe partial interface IXCLRDataModule [PreserveSig] int StartEnumMethodInstancesByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); [PreserveSig] - int EnumMethodInstanceByName(ulong* handle, /*IXCLRDataMethodInstance*/ void** method); + int EnumMethodInstanceByName(ulong* handle, out IXCLRDataMethodInstance? method); [PreserveSig] int EndEnumMethodInstancesByName(ulong handle); @@ -208,7 +208,7 @@ int GetRuntimeNameByAddress( [PreserveSig] int StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); [PreserveSig] - int EnumMethodInstanceByAddress(ulong* handle, /*IXCLRDataMethodInstance*/ void** method); + int EnumMethodInstanceByAddress(ulong* handle, out IXCLRDataMethodInstance? method); [PreserveSig] int EndEnumMethodInstancesByAddress(ulong handle); @@ -412,3 +412,79 @@ internal unsafe partial interface IXCLRDataTask [PreserveSig] int GetLastExceptionState(/*IXCLRDataExceptionState*/ void** exception); } + +[GeneratedComInterface] +[Guid("ECD73800-22CA-4b0d-AB55-E9BA7E6318A5")] +internal unsafe partial interface IXCLRDataMethodInstance +{ + [PreserveSig] + int GetTypeInstance(/*IXCLRDataTypeInstance*/ void** typeInstance); + + [PreserveSig] + int GetDefinition(/*IXCLRDataMethodDefinition*/ void** methodDefinition); + + [PreserveSig] + int GetTokenAndScope(uint* token, out IXCLRDataModule? mod); + + [PreserveSig] + int GetName( + uint flags, + uint bufLen, + uint* nameLen, + char* nameBuf); + + [PreserveSig] + int GetFlags(uint* flags); + + [PreserveSig] + int IsSameObject(IXCLRDataMethodInstance* method); + + [PreserveSig] + int GetEnCVersion(uint* version); + + [PreserveSig] + int GetNumTypeArguments(uint* numTypeArgs); + + [PreserveSig] + int GetTypeArgumentByIndex(uint index, /*IXCLRDataTypeInstance*/ void** typeArg); + + [PreserveSig] + int GetILOffsetsByAddress( + ulong address, + uint offsetsLen, + uint* offsetsNeeded, + uint* ilOffsets); + + [PreserveSig] + int GetAddressRangesByILOffset( + uint ilOffset, + uint rangesLen, + uint* rangesNeeded, + /*CLRDATA_ADDRESS_RANGE* */ void* addressRanges); + + [PreserveSig] + int GetILAddressMap( + uint mapLen, + uint* mapNeeded, + /*CLRDATA_IL_ADDRESS_MAP* */ void* maps); + + [PreserveSig] + int StartEnumExtents(ulong* handle); + + [PreserveSig] + int EnumExtent(ulong* handle, /*CLRDATA_ADDRESS_RANGE*/ void* extent); + + [PreserveSig] + int EndEnumExtents(ulong handle); + + [PreserveSig] + int Request( + uint reqCode, + uint inBufferSize, + byte* inBuffer, + uint outBufferSize, + byte* outBuffer); + + [PreserveSig] + int GetRepresentativeEntryAddress(ulong* addr); +} diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 7c6c51dd8f6248..8b2a478d71f6a0 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -4,6 +4,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Contracts; @@ -141,9 +143,12 @@ internal class EnumMethodInstances { private readonly Target _target; private readonly TargetPointer _mainMethodDesc; - private readonly TargetPointer _appDomain; + public readonly TargetPointer _appDomain; private readonly ILoader _loader; private readonly IRuntimeTypeSystem _rts; + public IEnumerator methodEnumerator = Enumerable.Empty().GetEnumerator(); + public TargetPointer LegacyHandle { get; set; } = TargetPointer.Null; + public EnumMethodInstances(Target target, TargetPointer methodDesc, TargetPointer appDomain) { _target = target; @@ -162,39 +167,19 @@ public EnumMethodInstances(Target target, TargetPointer methodDesc, TargetPointe _rts = _target.Contracts.RuntimeTypeSystem; } - public int Start(TargetPointer methodDesc, TargetPointer appDomain) + public int Start() { - if (appDomain == TargetPointer.Null) - { - TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain); - appDomain = _target.ReadPointer(appDomainPointer); - } - - if (!HasClassOrMethodInstantiation(methodDesc) && !HasNativeCodeAnyVersion(methodDesc)) + if (!HasClassOrMethodInstantiation(_mainMethodDesc) && !HasNativeCodeAnyVersion(_mainMethodDesc)) { return HResults.S_FALSE; } - List modules = _loader.GetAssemblies( - appDomain, - AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); - - foreach (Contracts.ModuleHandle moduleHandle in modules) - { - List typeParams = _loader.GetAvailableTypeParams(moduleHandle); - foreach (TargetPointer type in typeParams) - { - Console.WriteLine(type); - } - } - - // for each module find available type params - // for each module get inst method hash table + methodEnumerator = IterateMethodInstances().GetEnumerator(); return HResults.S_OK; } - public IEnumerable IterateMethodInstantiations(Contracts.ModuleHandle moduleHandle) + private IEnumerable IterateMethodInstantiations(Contracts.ModuleHandle moduleHandle) { List methodInstantiations = _loader.GetInstantiatedMethods(moduleHandle); @@ -204,7 +189,7 @@ public IEnumerable IterateMethodInstantiations(Contracts.Modul } } - public IEnumerable IterateTypeParams(Contracts.ModuleHandle moduleHandle) + private IEnumerable IterateTypeParams(Contracts.ModuleHandle moduleHandle) { List typeParams = _loader.GetAvailableTypeParams(moduleHandle); @@ -214,7 +199,7 @@ public IEnumerable IterateMethodInstantiations(Contracts.Modul } } - public IEnumerable IterateModules() + private IEnumerable IterateModules() { ILoader loader = _target.Contracts.Loader; List modules = loader.GetAssemblies( @@ -227,7 +212,7 @@ public IEnumerable IterateMethodInstantiations(Contracts.Modul } } - public IEnumerable IterateMethodInstances() + private IEnumerable IterateMethodInstances() { /* There are 4 cases for method instances: @@ -252,7 +237,7 @@ 4. Generic method on Generic type (There are N generic defining methods where N TargetPointer mainModule = _rts.GetModule(mainMT); uint mainMTToken = _rts.GetTypeDefToken(mainMT); uint mainMDToken = _rts.GetMethodToken(mainMD); - ushort slotNum = _rts.GetSlotNumber(mainMD); + // ushort slotNum = _rts.GetSlotNumber(mainMD); if (HasMethodInstantiation(_mainMethodDesc)) { @@ -295,9 +280,8 @@ 4. Generic method on Generic type (There are N generic defining methods where N TargetPointer cmt = _rts.GetCanonicalMethodTable(typeParam); TypeHandle cmtHandle = _rts.GetTypeHandle(cmt); - - - + Console.WriteLine(cmtHandle); + Debug.Assert(false, "Iterating class instantiations is not implemented yet"); } } } @@ -355,36 +339,94 @@ private bool HasMethodInstantiation(TargetPointer mdAddr) int IXCLRDataProcess.StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) { int hr = HResults.S_OK; - try + + *handle = 0; + hr = HResults.S_FALSE; + + int hrLocal = default; + ulong handleLocal = default; + if (_legacyProcess is not null) { - *handle = 0; - hr = HResults.S_FALSE; + hrLocal = _legacyProcess.StartEnumMethodInstancesByAddress(address, appDomain, &handleLocal); + if (hrLocal < 0) + return hrLocal; + } - IExecutionManager eman = _target.Contracts.ExecutionManager; - if (eman.GetCodeBlockHandle(address) is CodeBlockHandle cbh && eman.GetMethodDesc(cbh) is TargetPointer methodDesc) - { + IExecutionManager eman = _target.Contracts.ExecutionManager; + if (eman.GetCodeBlockHandle(address) is CodeBlockHandle cbh && eman.GetMethodDesc(cbh) is TargetPointer methodDesc) + { + EnumMethodInstances emi = new(_target, methodDesc, TargetPointer.Null); - EnumMethodInstances emi = new(_target); - emi.Start(methodDesc, (ulong)appDomain); - // GCHandle gcHandle = GCHandle.Alloc(emi); - // *handle = (ulong)GCHandle.ToIntPtr(gcHandle).ToInt64(); + emi.LegacyHandle = handleLocal; - } + GCHandle gcHandle = GCHandle.Alloc(emi); + *handle = (ulong)GCHandle.ToIntPtr(gcHandle).ToInt64(); + hr = emi.Start(); + } + +#if DEBUG + if (_legacyProcess is not null) + { + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); } - catch (System.Exception ex) +#endif + return hr; + } + + int IXCLRDataProcess.EnumMethodInstanceByAddress(ulong* handle, out IXCLRDataMethodInstance? method) + { + method = default; + int hr = HResults.S_OK; + + GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)(*handle)); + if (gcHandle.Target is not EnumMethodInstances emi) return HResults.E_INVALIDARG; + + IXCLRDataMethodInstance? legacyMethod = null; + int hrLocal = default; + if (_legacyProcess is not null) { - hr = ex.HResult; + ulong legacyHandle = emi.LegacyHandle; + hrLocal = _legacyProcess.EnumMethodInstanceByAddress(&legacyHandle, out legacyMethod); + emi.LegacyHandle = legacyHandle; + if (hrLocal < 0) + return hrLocal; } - return _legacyProcess is not null ? _legacyProcess.StartEnumMethodInstancesByAddress(address, appDomain, handle) : HResults.E_NOTIMPL; - //return hr; - } + if (emi.methodEnumerator.MoveNext()) + { + MethodDescHandle methodDesc = emi.methodEnumerator.Current; + method = new ClrDataMethodInstance(_target, methodDesc, emi._appDomain, legacyMethod); + } + else + { + hr = HResults.S_FALSE; + } - int IXCLRDataProcess.EnumMethodInstanceByAddress(ulong* handle, /*IXCLRDataMethodInstance*/ void** method) - => _legacyProcess is not null ? _legacyProcess.EnumMethodInstanceByAddress(handle, method) : HResults.E_NOTIMPL; + if (legacyMethod is not null) + { + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + } + + return hr; + } int IXCLRDataProcess.EndEnumMethodInstancesByAddress(ulong handle) - => _legacyProcess is not null ? _legacyProcess.EndEnumMethodInstancesByAddress(handle) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)handle); + if (gcHandle.Target is not EnumMethodInstances emi) return HResults.E_INVALIDARG; + gcHandle.Free(); + + if (_legacyProcess != null && emi.LegacyHandle != TargetPointer.Null) + { + int hrLocal = _legacyProcess.EndEnumMethodInstancesByAddress(emi.LegacyHandle); + if (hrLocal < 0) + return hrLocal; + } + + return hr; + } int IXCLRDataProcess.GetDataByAddress( ulong address, From 7aeaa51c61053701b69a0ecd8302f9bdfcdde80d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 24 Apr 2025 15:15:24 -0400 Subject: [PATCH 05/54] implement IXCLRDataMethodInstance.GetRepresentativeEntryAddress and IXCLRDataMethodInstance.GetTokenAndScope --- .../Legacy/ClrDataMethodInstance.cs | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index c8ef60a0b509f4..0f46461334c1ca 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -37,6 +37,7 @@ int IXCLRDataMethodInstance.GetDefinition(void** methodDefinition) int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, out IXCLRDataModule? mod) { + mod = default; //_legacyImpl is not null ? _legacyImpl.GetTokenAndScope(token, mod) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -47,12 +48,19 @@ int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, out IXCLRDataModule? m { *token = rts.GetMethodToken(_methodDesc); } - if (mod is not null) + //if (mod is not null) { + IXCLRDataModule? legacyMod = null; + if (_legacyImpl is not null) + { + int hrLegacy = _legacyImpl.GetTokenAndScope(token, out legacyMod); + if (hrLegacy < 0) + return hrLegacy; + } TargetPointer mtAddr = rts.GetMethodTable(_methodDesc); TypeHandle mainMT = rts.GetTypeHandle(mtAddr); TargetPointer module = rts.GetModule(mainMT); - *mod = module.Value; + mod = new ClrDataModule(module, _target, legacyMod); } } catch (System.Exception ex) @@ -60,6 +68,17 @@ int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, out IXCLRDataModule? m hr = ex.HResult; } +#if DEBUG + if (_legacyImpl is not null) + { + uint tokenLocal; + int hrLocal = _legacyImpl.GetTokenAndScope(&tokenLocal, out IXCLRDataModule? legacyMod); + + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + Debug.Assert(tokenLocal == *token, $"cDAC: {*token:x}, DAC: {tokenLocal:x}"); + } +#endif + return hr; } @@ -103,5 +122,40 @@ int IXCLRDataMethodInstance.Request(uint reqCode, uint inBufferSize, byte* inBuf => _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL; int IXCLRDataMethodInstance.GetRepresentativeEntryAddress(ulong* addr) - => _legacyImpl is not null ? _legacyImpl.GetRepresentativeEntryAddress(addr) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + TargetCodePointer addrCode = rts.GetNativeCode(_methodDesc); + + if (addrCode.Value != 0) + { + *addr = addrCode.Value; + } + else + { + hr = unchecked((int)0x8000FFFF); // E_UNEXPECTED + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + ulong addrLocal; + int hrLocal = _legacyImpl.GetRepresentativeEntryAddress(&addrLocal); + + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + Debug.Assert(addrLocal == *addr, $"cDAC: {*addr:x}, DAC: {addrLocal:x}"); + } +#endif + + return hr; + } } From c778639553f9afc9d18687d82c261e6b797c77d7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 24 Apr 2025 16:34:25 -0400 Subject: [PATCH 06/54] wip --- .../debug/runtimeinfo/datadescriptor.h | 1 - .../Contracts/RuntimeTypeSystem_1.cs | 21 +++++++- .../Legacy/ClrDataMethodInstance.cs | 1 + .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 51 +++++++++++-------- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 7b007f3e578439..71a8657772d5f4 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -228,7 +228,6 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, FileName, cdac_data::FileName) CDAC_TYPE_FIELD(Module, /*pointer*/, ReadyToRunInfo, cdac_data::ReadyToRunInfo) CDAC_TYPE_FIELD(Module, /*pointer*/, GrowableSymbolStream, cdac_data::GrowableSymbolStream) CDAC_TYPE_FIELD(Module, /*pointer*/, AvailableTypeParams, offsetof(Module, m_pAvailableParamTypes)) -CDAC_TYPE_FIELD(Module, /*pointer*/, InstMethodHashTable, offsetof(Module, m_pInstMethodHashTable)) CDAC_TYPE_FIELD(Module, /*pointer*/, FieldDefToDescMap, cdac_data::FieldDefToDescMap) CDAC_TYPE_FIELD(Module, /*pointer*/, ManifestModuleReferencesMap, cdac_data::ManifestModuleReferencesMap) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 525a725b2d3b11..ffbab20b6c6e9d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -974,8 +974,25 @@ MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, TargetPointer slotPtr = GetAddressOfMethodTableSlot(GetCanonicalMethodTable(typeHandle), slot); TargetCodePointer pCode = _target.ReadCodePointer(slotPtr); - // NOT IMPLEMENTED - return new MethodDescHandle(pCode.AsTargetPointer); + if (pCode == TargetCodePointer.Null) + { + // TODO(cdac): Implement this path + // if pCode is null, we iterate through the method descs in the MT. + } + + // standard path, ask ExecutionManager for the MethodDesc + IExecutionManager executionManager = _target.Contracts.ExecutionManager; + if (executionManager.GetCodeBlockHandle(pCode) is CodeBlockHandle cbh) + { + TargetPointer methodDescPtr = executionManager.GetMethodDesc(cbh); + return GetMethodDescHandle(methodDescPtr); + } + + // FCall path, look up address in the FCall table + + // stub path, read address as a Precode and read MethodDesc from it + + throw new NotImplementedException("MethodDesc for slot is not implemented yet"); } TargetPointer IRuntimeTypeSystem.GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index 0f46461334c1ca..0f6f014d5e8e22 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -37,6 +37,7 @@ int IXCLRDataMethodInstance.GetDefinition(void** methodDefinition) int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, out IXCLRDataModule? mod) { + // TODO(cdac): change to explicitly marshall mod as a void**. This approach does not support mod being null mod = default; //_legacyImpl is not null ? _legacyImpl.GetTokenAndScope(token, mod) : HResults.E_NOTIMPL; int hr = HResults.S_OK; diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 8b2a478d71f6a0..e22b3827488bf3 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -169,7 +169,8 @@ public EnumMethodInstances(Target target, TargetPointer methodDesc, TargetPointe public int Start() { - if (!HasClassOrMethodInstantiation(_mainMethodDesc) && !HasNativeCodeAnyVersion(_mainMethodDesc)) + MethodDescHandle mainMD = _rts.GetMethodDescHandle(_mainMethodDesc); + if (!HasClassOrMethodInstantiation(mainMD) && !HasNativeCodeAnyVersion(mainMD)) { return HResults.S_FALSE; } @@ -224,11 +225,14 @@ 4. Generic method on Generic type (There are N generic defining methods where N MethodDescHandle mainMD = _rts.GetMethodDescHandle(_mainMethodDesc); - if (!HasClassOrMethodInstantiation(_mainMethodDesc)) + if (!HasClassOrMethodInstantiation(mainMD)) { // case 1 // no method or class instantiation, then it's not generic. - yield return mainMD; + if (HasNativeCodeAnyVersion(mainMD)) + { + yield return mainMD; + } yield break; } @@ -237,9 +241,9 @@ 4. Generic method on Generic type (There are N generic defining methods where N TargetPointer mainModule = _rts.GetModule(mainMT); uint mainMTToken = _rts.GetTypeDefToken(mainMT); uint mainMDToken = _rts.GetMethodToken(mainMD); - // ushort slotNum = _rts.GetSlotNumber(mainMD); + ushort slotNum = _rts.GetSlotNumber(mainMD); - if (HasMethodInstantiation(_mainMethodDesc)) + if (HasMethodInstantiation(mainMD)) { // case 2/4 // 2 is trivial, 4 is covered because the defining method on a generic type is not instantiated @@ -252,18 +256,23 @@ 4. Generic method on Generic type (There are N generic defining methods where N if (mainModule != _rts.GetModule(methodTypeHandle)) continue; if (mainMDToken != _rts.GetMethodToken(methodDesc)) continue; - yield return methodDesc; + if (HasNativeCodeAnyVersion(methodDesc)) + { + yield return methodDesc; + } } } + + yield break; } - if (HasClassInstantiation(_mainMethodDesc)) + if (HasClassInstantiation(mainMD)) { // case 3 // class instantiations are only interesting if the method is not generic foreach (Contracts.ModuleHandle moduleHandle in IterateModules()) { - if (HasClassInstantiation(_mainMethodDesc)) + if (HasClassInstantiation(mainMD)) { foreach (Contracts.TypeHandle typeParam in IterateTypeParams(moduleHandle)) { @@ -280,27 +289,32 @@ 4. Generic method on Generic type (There are N generic defining methods where N TargetPointer cmt = _rts.GetCanonicalMethodTable(typeParam); TypeHandle cmtHandle = _rts.GetTypeHandle(cmt); - Console.WriteLine(cmtHandle); - Debug.Assert(false, "Iterating class instantiations is not implemented yet"); + + MethodDescHandle methodDesc = _rts.GetMethodDescForSlot(cmtHandle, slotNum); + if (HasNativeCodeAnyVersion(methodDesc)) + { + yield return methodDesc; + } } } } + + yield break; } } - private bool HasNativeCodeAnyVersion(TargetPointer methodDesc) + private bool HasNativeCodeAnyVersion(MethodDescHandle mdHandle) { IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; ICodeVersions cv = _target.Contracts.CodeVersions; - MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDesc); TargetCodePointer pcode = rts.GetNativeCode(mdHandle); if (pcode == TargetCodePointer.Null) { // I think this is equivalent to get any native code version - NativeCodeVersionHandle nativeCodeVersion = cv.GetActiveNativeCodeVersion(methodDesc); + NativeCodeVersionHandle nativeCodeVersion = cv.GetActiveNativeCodeVersion(mdHandle.Address); if (nativeCodeVersion.Valid) { pcode = cv.GetNativeCode(nativeCodeVersion); @@ -310,27 +324,24 @@ private bool HasNativeCodeAnyVersion(TargetPointer methodDesc) return pcode != TargetCodePointer.Null; } - private bool HasClassOrMethodInstantiation(TargetPointer mdAddr) + private bool HasClassOrMethodInstantiation(MethodDescHandle md) { - return HasClassInstantiation(mdAddr) || HasMethodInstantiation(mdAddr); + return HasClassInstantiation(md) || HasMethodInstantiation(md); } - private bool HasClassInstantiation(TargetPointer mdAddr) + private bool HasClassInstantiation(MethodDescHandle md) { IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); TargetPointer mtAddr = rts.GetMethodTable(md); TypeHandle mt = rts.GetTypeHandle(mtAddr); return !rts.GetInstantiation(mt).IsEmpty; } - private bool HasMethodInstantiation(TargetPointer mdAddr) + private bool HasMethodInstantiation(MethodDescHandle md) { IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); - if (rts.IsGenericMethodDefinition(md)) return true; return !rts.GetGenericMethodInstantiation(md).IsEmpty; } From e73b92e8e02ea8f692564f1bb3472bf3e9537614 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 25 Apr 2025 14:33:54 -0400 Subject: [PATCH 07/54] add missing data descriptor --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 71a8657772d5f4..7b007f3e578439 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -228,6 +228,7 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, FileName, cdac_data::FileName) CDAC_TYPE_FIELD(Module, /*pointer*/, ReadyToRunInfo, cdac_data::ReadyToRunInfo) CDAC_TYPE_FIELD(Module, /*pointer*/, GrowableSymbolStream, cdac_data::GrowableSymbolStream) CDAC_TYPE_FIELD(Module, /*pointer*/, AvailableTypeParams, offsetof(Module, m_pAvailableParamTypes)) +CDAC_TYPE_FIELD(Module, /*pointer*/, InstMethodHashTable, offsetof(Module, m_pInstMethodHashTable)) CDAC_TYPE_FIELD(Module, /*pointer*/, FieldDefToDescMap, cdac_data::FieldDefToDescMap) CDAC_TYPE_FIELD(Module, /*pointer*/, ManifestModuleReferencesMap, cdac_data::ManifestModuleReferencesMap) From af6fd4d4694e678601ad5613f3d456f4e502f4d3 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 25 Apr 2025 16:32:05 -0400 Subject: [PATCH 08/54] implement IntroducedMethodIterator --- .../debug/runtimeinfo/datadescriptor.h | 2 + src/coreclr/vm/class.h | 1 + .../Constants.cs | 2 + .../Contracts/RuntimeTypeSystem_1.cs | 44 ++++++++++++++++++- .../Data/EEClass.cs | 2 + .../Data/MethodDescChunk.cs | 5 +++ 6 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 7b007f3e578439..4faa1d7806eb3a 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -334,6 +334,7 @@ CDAC_TYPE_END(MethodTableAuxiliaryData) CDAC_TYPE_BEGIN(EEClass) CDAC_TYPE_INDETERMINATE(EEClass) CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_data::MethodTable) +CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodDescChunk, cdac_data::MethodDescChunk) CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_data::NumMethods) CDAC_TYPE_FIELD(EEClass, /*uint32*/, CorTypeAttr, cdac_data::CorTypeAttr) CDAC_TYPE_FIELD(EEClass, /*uint8*/, InternalCorElementType, cdac_data::InternalCorElementType) @@ -918,6 +919,7 @@ CDAC_GLOBAL(StressLogEnabled, uint8, 0) CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data::CodeRangeMapAddress) CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata) CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) +CDAC_GLOBAL_POINTER(MethodDescSizeTable, &::s_ClassificationSizeTable) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index d95129ab096a00..e16a12b952644b 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1760,6 +1760,7 @@ template<> struct cdac_data { static constexpr size_t InternalCorElementType = offsetof(EEClass, m_NormType); static constexpr size_t MethodTable = offsetof(EEClass, m_pMethodTable); + static constexpr size_t MethodDescChunk = offsetof(EEClass, m_pChunks); static constexpr size_t NumMethods = offsetof(EEClass, m_NumMethods); static constexpr size_t CorTypeAttr = offsetof(EEClass, m_dwAttrClass); static constexpr size_t NumNonVirtualSlots = offsetof(EEClass, m_NumNonVirtualSlots); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index a110102616bd8d..c90e55cf517d0c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -55,6 +55,8 @@ public static class Globals public const string PlatformMetadata = nameof(PlatformMetadata); public const string ProfilerControlBlock = nameof(ProfilerControlBlock); + public const string MethodDescSizeTable = nameof(MethodDescSizeTable); + public const string HashMapSlotsPerBucket = nameof(HashMapSlotsPerBucket); public const string HashMapValueMask = nameof(HashMapValueMask); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index ffbab20b6c6e9d..0ae46b6d923049 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -109,11 +109,13 @@ internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodD Address = methodDescPointer; Token = ComputeToken(target, desc, chunk); + Size = ComputeSize(target, desc); } public TargetPointer MethodTable => _chunk.MethodTable; public ushort Slot => _desc.Slot; public uint Token { get; } + public uint Size { get; } private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk) { @@ -128,6 +130,21 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho return EcmaMetadataUtils.CreateMethodDef(tokenRange | tokenRemainder); } + private static uint ComputeSize(Target target, Data.MethodDesc desc) + { + // Size of the MethodDesc is variable, read it from the targets lookup table + // See MethodDesc::SizeOf in method.cpp for details + // TODO(cdac): make sure this value is stored in minidumps + TargetPointer methodDescSizeTable = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable)); + + ushort arrayOffset = (ushort)(desc.Flags & (ushort)( + MethodDescFlags_1.MethodDescFlags.ClassificationMask | + MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | + MethodDescFlags_1.MethodDescFlags.HasMethodImpl | + MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot)); + return (uint)target.ReadNUInt(methodDescSizeTable + arrayOffset * (ulong)target.PointerSize).Value; + } + public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask); private bool HasFlags(MethodDescFlags_1.MethodDescFlags flags) => (_desc.Flags & (ushort)flags) != 0; @@ -327,7 +344,7 @@ private TargetPointer GetClassPointer(TypeHandle typeHandle) case MethodTableFlags_1.EEClassOrCanonMTBits.EEClass: return methodTable.EEClassOrCanonMT; case MethodTableFlags_1.EEClassOrCanonMTBits.CanonMT: - TargetPointer canonMTPtr =MethodTableFlags_1.UntagEEClassOrCanonMT(methodTable.EEClassOrCanonMT); + TargetPointer canonMTPtr = MethodTableFlags_1.UntagEEClassOrCanonMT(methodTable.EEClassOrCanonMT); TypeHandle canonMTHandle = GetTypeHandle(canonMTPtr); MethodTable canonMT = _methodTables[canonMTHandle.Address]; return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass @@ -966,6 +983,31 @@ bool IRuntimeTypeSystem.HasNativeCodeSlot(MethodDescHandle methodDesc) return md.HasNativeCodeSlot; } + // Based on MethodTable::IntroducedMethodIterator + private IEnumerable GetIntroducedMethods(TypeHandle typeHandle) + { + Debug.Assert(typeHandle.IsMethodTable()); + + EEClass eeClass = GetClassData(typeHandle); + + TargetPointer chunkAddr = eeClass.MethodDescChunk; + while (chunkAddr != TargetPointer.Null) + { + MethodDescChunk chunk = _target.ProcessedData.GetOrAdd(chunkAddr); + TargetPointer methodDescPtr = chunk.FirstMethodDesc; + // chunk.Count is the number of MethodDescs in the chunk - 1 + for (int i = 0; i < chunk.Count + 1; i++) + { + MethodDescHandle methodDescHandle = GetMethodDescHandle(methodDescPtr); + MethodDesc md = _methodDescs[methodDescHandle.Address]; + methodDescPtr += md.Size; + yield return methodDescHandle; + } + + chunkAddr = chunk.Next; + } + } + MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, ushort slot) { if (!typeHandle.IsMethodTable()) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs index c9e62eb39e85ef..2669984b62ce0a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs @@ -11,6 +11,7 @@ public EEClass(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.EEClass); MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); + MethodDescChunk = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDescChunk)].Offset); NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); CorTypeAttr = target.Read(address + (ulong)type.Fields[nameof(CorTypeAttr)].Offset); InternalCorElementType = target.Read(address + (ulong)type.Fields[nameof(InternalCorElementType)].Offset); @@ -18,6 +19,7 @@ public EEClass(Target target, TargetPointer address) } public TargetPointer MethodTable { get; init; } + public TargetPointer MethodDescChunk { get; init; } public ushort NumMethods { get; init; } public uint CorTypeAttr { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs index 028050c40d9d42..a157f76e2c31fc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs @@ -17,6 +17,9 @@ public MethodDescChunk(Target target, TargetPointer address) Size = target.Read(address + (ulong)type.Fields[nameof(Size)].Offset); Count = target.Read(address + (ulong)type.Fields[nameof(Count)].Offset); FlagsAndTokenRange = target.Read(address + (ulong)type.Fields[nameof(FlagsAndTokenRange)].Offset); + + // The first MethodDesc is at the end of the MethodDescChunk + FirstMethodDesc = address + type.Size!.Value; } public TargetPointer MethodTable { get; init; } @@ -24,4 +27,6 @@ public MethodDescChunk(Target target, TargetPointer address) public byte Size { get; init; } public byte Count { get; init; } public ushort FlagsAndTokenRange { get; init; } + + public TargetPointer FirstMethodDesc { get; init; } } From 1f4ad2fdc41cec768b475ec84d22ddc20c5c5718 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 28 Apr 2025 13:44:21 -0400 Subject: [PATCH 09/54] fix bugs in Getting methoddesc from slot --- .../debug/runtimeinfo/datadescriptor.h | 2 +- .../Contracts/RuntimeTypeSystem_1.cs | 20 ++++++++++++++++--- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 18 ++++++++++++----- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 4faa1d7806eb3a..c931a2ffa2570f 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -919,7 +919,7 @@ CDAC_GLOBAL(StressLogEnabled, uint8, 0) CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data::CodeRangeMapAddress) CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata) CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) -CDAC_GLOBAL_POINTER(MethodDescSizeTable, &::s_ClassificationSizeTable) +CDAC_GLOBAL_POINTER(MethodDescSizeTable, &MethodDesc::s_ClassificationSizeTable) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 0ae46b6d923049..21d2834c054ec0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -135,14 +135,14 @@ private static uint ComputeSize(Target target, Data.MethodDesc desc) // Size of the MethodDesc is variable, read it from the targets lookup table // See MethodDesc::SizeOf in method.cpp for details // TODO(cdac): make sure this value is stored in minidumps - TargetPointer methodDescSizeTable = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable)); + TargetPointer methodDescSizeTable = target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable); ushort arrayOffset = (ushort)(desc.Flags & (ushort)( MethodDescFlags_1.MethodDescFlags.ClassificationMask | MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasMethodImpl | MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot)); - return (uint)target.ReadNUInt(methodDescSizeTable + arrayOffset * (ulong)target.PointerSize).Value; + return target.Read(methodDescSizeTable + arrayOffset); } public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask); @@ -1018,8 +1018,15 @@ MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, if (pCode == TargetCodePointer.Null) { - // TODO(cdac): Implement this path // if pCode is null, we iterate through the method descs in the MT. + foreach (MethodDescHandle mdh in GetIntroducedMethods(typeHandle)) + { + MethodDesc md = _methodDescs[mdh.Address]; + if (md.Slot == slot) + { + return mdh; + } + } } // standard path, ask ExecutionManager for the MethodDesc @@ -1031,8 +1038,15 @@ MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, } // FCall path, look up address in the FCall table + { + + } // stub path, read address as a Precode and read MethodDesc from it + { + TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromStubAddress(pCode); + return GetMethodDescHandle(methodDescPtr); + } throw new NotImplementedException("MethodDesc for slot is not implemented yet"); } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index e22b3827488bf3..90d5b623ed7342 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -403,16 +403,24 @@ int IXCLRDataProcess.EnumMethodInstanceByAddress(ulong* handle, out IXCLRDataMet return hrLocal; } - if (emi.methodEnumerator.MoveNext()) + try { - MethodDescHandle methodDesc = emi.methodEnumerator.Current; - method = new ClrDataMethodInstance(_target, methodDesc, emi._appDomain, legacyMethod); + if (emi.methodEnumerator.MoveNext()) + { + MethodDescHandle methodDesc = emi.methodEnumerator.Current; + method = new ClrDataMethodInstance(_target, methodDesc, emi._appDomain, legacyMethod); + } + else + { + hr = HResults.S_FALSE; + } } - else + catch (System.Exception ex) { - hr = HResults.S_FALSE; + hr = ex.HResult; } + if (legacyMethod is not null) { Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); From 77a5ed0d6e4a7fffad8d1e1b46e7b15bed667d46 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 28 Apr 2025 13:44:29 -0400 Subject: [PATCH 10/54] add todo --- .../Contracts/RuntimeTypeSystem_1.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 21d2834c054ec0..35d30ddc380a1c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -1038,9 +1038,7 @@ MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, } // FCall path, look up address in the FCall table - { - - } + // TODO(cdac): FCall path if relevant // stub path, read address as a Precode and read MethodDesc from it { From 42eb4f946f15f004db3e2d2ee28a91e63f064eeb Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 28 Apr 2025 14:57:09 -0400 Subject: [PATCH 11/54] fix bug in GetInstantiatedMethods --- .../Contracts/Loader_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 050686dc829a07..4384560fdfaed5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -210,7 +210,7 @@ List ILoader.GetInstantiatedMethods(ModuleHandle handle) if (module.InstMethodHashTable == TargetPointer.Null) return typeParams; - InstMethodHashTable methodHashTable = _target.ProcessedData.GetOrAdd(module.AvailableTypeParams); + InstMethodHashTable methodHashTable = _target.ProcessedData.GetOrAdd(module.InstMethodHashTable); foreach (InstMethodHashTable.InstMethodHashTableEntry entry in methodHashTable.Entries) { From 1dadeb3bfd37885fe663a9c53b0ea11e8e3ec922 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 28 Apr 2025 17:44:08 -0400 Subject: [PATCH 12/54] uncomment changes --- src/coreclr/vm/methoditer.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/coreclr/vm/methoditer.cpp b/src/coreclr/vm/methoditer.cpp index 95c760a818a046..67eca3ec5851ce 100644 --- a/src/coreclr/vm/methoditer.cpp +++ b/src/coreclr/vm/methoditer.cpp @@ -44,18 +44,18 @@ BOOL LoadedMethodDescIterator::Next( m_fFirstTime = FALSE; // This is the 1st time we've called Next(). must Initialize iterator - // if (m_mainMD == NULL) - // { - // m_mainMD = m_module->LookupMethodDef(m_md); - // } + if (m_mainMD == NULL) + { + m_mainMD = m_module->LookupMethodDef(m_md); + } // note m_mainMD should be sufficiently restored to allow us to get // at the method table, flags and token etc. - // if (m_mainMD == NULL) - // { - // *pAssemblyHolder = NULL; - // return FALSE; - // } + if (m_mainMD == NULL) + { + *pAssemblyHolder = NULL; + return FALSE; + } // Needs to work w/ non-generic methods too. if (!m_mainMD->HasClassOrMethodInstantiation()) From 55fbb58bf670a6366954758001f6e03985f24bf4 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 30 Apr 2025 14:36:46 -0400 Subject: [PATCH 13/54] try manually marshalling IXCLRDataMoldue --- .../Legacy/ClrDataMethodInstance.cs | 34 ++++++++++++++----- .../Legacy/IXCLRData.cs | 2 +- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index 0f6f014d5e8e22..b99ea91076d7b2 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -35,12 +35,10 @@ int IXCLRDataMethodInstance.GetTypeInstance(void** typeInstance) int IXCLRDataMethodInstance.GetDefinition(void** methodDefinition) => _legacyImpl is not null ? _legacyImpl.GetDefinition(methodDefinition) : HResults.E_NOTIMPL; - int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, out IXCLRDataModule? mod) + int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, void** /*IXCLRDataModule*/ mod) { - // TODO(cdac): change to explicitly marshall mod as a void**. This approach does not support mod being null - mod = default; - //_legacyImpl is not null ? _legacyImpl.GetTokenAndScope(token, mod) : HResults.E_NOTIMPL; int hr = HResults.S_OK; + StrategyBasedComWrappers cw = new(); try { @@ -49,19 +47,30 @@ int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, out IXCLRDataModule? m { *token = rts.GetMethodToken(_methodDesc); } - //if (mod is not null) + if (mod is not null) { - IXCLRDataModule? legacyMod = null; + void* legacyModPtr = null; if (_legacyImpl is not null) { - int hrLegacy = _legacyImpl.GetTokenAndScope(token, out legacyMod); + int hrLegacy = _legacyImpl.GetTokenAndScope(token, &legacyModPtr); if (hrLegacy < 0) return hrLegacy; } + + object obj = cw.GetOrCreateObjectForComInstance((nint)legacyModPtr, CreateObjectFlags.None); + if (obj is not IXCLRDataModule legacyMod) + { + throw new ArgumentException("Invalid module object", nameof(mod)); + } + TargetPointer mtAddr = rts.GetMethodTable(_methodDesc); TypeHandle mainMT = rts.GetTypeHandle(mtAddr); TargetPointer module = rts.GetModule(mainMT); - mod = new ClrDataModule(module, _target, legacyMod); + IXCLRDataModule modImpl = new ClrDataModule(module, _target, legacyMod); + nint modImplPtr = cw.GetOrCreateComInterfaceForObject(modImpl, CreateComInterfaceFlags.None); + Marshal.QueryInterface(modImplPtr, typeof(IXCLRDataModule).GUID, out nint ptrToMod); + Marshal.Release(modImplPtr); + *mod = (void*)ptrToMod; } } catch (System.Exception ex) @@ -73,10 +82,17 @@ int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, out IXCLRDataModule? m if (_legacyImpl is not null) { uint tokenLocal; - int hrLocal = _legacyImpl.GetTokenAndScope(&tokenLocal, out IXCLRDataModule? legacyMod); + void* legacyModPtr = null; + int hrLocal = _legacyImpl.GetTokenAndScope(&tokenLocal, &legacyModPtr); Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); Debug.Assert(tokenLocal == *token, $"cDAC: {*token:x}, DAC: {tokenLocal:x}"); + + if (hr == HResults.S_OK) + { + // ensure the COM object is freed + cw.GetOrCreateObjectForComInstance((nint)legacyModPtr, CreateObjectFlags.None); + } } #endif diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs index fcfefacf8dacff..3f728bdcce2788 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs @@ -424,7 +424,7 @@ internal unsafe partial interface IXCLRDataMethodInstance int GetDefinition(/*IXCLRDataMethodDefinition*/ void** methodDefinition); [PreserveSig] - int GetTokenAndScope(uint* token, out IXCLRDataModule? mod); + int GetTokenAndScope(uint* token, void** /*IXCLRDataModule*/ mod); [PreserveSig] int GetName( From c8e1f00fa8c67d00c78a5ee097f8742f2c6168bb Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 30 Apr 2025 15:19:19 -0400 Subject: [PATCH 14/54] fix manual com marshalling --- .../mscordaccore_universal/Legacy/ClrDataMethodInstance.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index b99ea91076d7b2..931400ae4290b8 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -90,8 +90,7 @@ int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, void** /*IXCLRDataModu if (hr == HResults.S_OK) { - // ensure the COM object is freed - cw.GetOrCreateObjectForComInstance((nint)legacyModPtr, CreateObjectFlags.None); + Marshal.Release((nint)legacyModPtr); // release the legacy module pointer to } } #endif From d37b68e9e22bcef20a8d5e964212b2262736e011 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 30 Apr 2025 15:35:18 -0400 Subject: [PATCH 15/54] fix --- .../cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index 931400ae4290b8..ed87cf26571a33 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -90,7 +90,7 @@ int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, void** /*IXCLRDataModu if (hr == HResults.S_OK) { - Marshal.Release((nint)legacyModPtr); // release the legacy module pointer to + Marshal.Release((nint)legacyModPtr); // release the legacy module } } #endif From 6e7368b27c2da9f896adf898cba37fa102fb0660 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 1 May 2025 17:34:52 -0400 Subject: [PATCH 16/54] implement FCall lookup --- .../debug/runtimeinfo/contractpointerdata.cpp | 1 + src/coreclr/debug/runtimeinfo/contracts.jsonc | 1 + .../debug/runtimeinfo/datadescriptor.cpp | 1 + .../debug/runtimeinfo/datadescriptor.h | 11 +++++ src/coreclr/vm/ecall.h | 3 +- .../ContractRegistry.cs | 4 ++ .../Contracts/IECall.cs | 18 +++++++ .../DataType.cs | 1 + .../Constants.cs | 3 ++ .../Contracts/ECallFactory.cs | 16 +++++++ .../Contracts/ECall_1.cs | 47 +++++++++++++++++++ .../Contracts/RuntimeTypeSystem_1.cs | 6 ++- .../Data/ECHash.cs | 21 +++++++++ .../CachingContractRegistry.cs | 5 +- 14 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IECall.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECallFactory.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ECHash.cs diff --git a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp index 1dba7454b1bb26..c7d5bf15cb1b01 100644 --- a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp +++ b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp @@ -9,6 +9,7 @@ #include "cdacplatformmetadata.hpp" #include "threads.h" #include "vars.hpp" +#include "ecall.h" extern "C" { diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index bcc2daae3830e3..56eda8d256e469 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -11,6 +11,7 @@ { "CodeVersions": 1, "DacStreams": 1, + "ECall": 1, "EcmaMetadata" : 1, "Exception": 1, "ExecutionManager": 2, diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index a854a9a5b92ec1..61e9732d5e133a 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -17,6 +17,7 @@ #include "configure.h" #include "../debug/ee/debugger.h" +#include "ecall.h" #ifdef HAVE_GCCOVER #include "gccover.h" diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index c931a2ffa2570f..bee87d3836ca7d 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -827,6 +827,13 @@ CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, VolatileEntryValue, cdac_data< CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, VolatileEntryNextEntry, cdac_data::VolatileEntryNextEntry) CDAC_TYPE_END(InstMethodHashTable) +CDAC_TYPE_BEGIN(ECHash) +CDAC_TYPE_INDETERMINATE(ECHash) +CDAC_TYPE_FIELD(ECHash, /*pointer*/, Next, offsetof(ECHash, m_pNext)) +CDAC_TYPE_FIELD(ECHash, /*pointer*/, Implementation, offsetof(ECHash, m_pImplementation)) +CDAC_TYPE_FIELD(ECHash, /*pointer*/, MethodDesc, offsetof(ECHash, m_pMD)) +CDAC_TYPE_END(ECHash) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -920,6 +927,10 @@ CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data public abstract IRuntimeInfo RuntimeInfo { get; } + /// + /// Gets an instance of the ECall contract for the target. + /// + public abstract IECall ECall { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IECall.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IECall.cs new file mode 100644 index 00000000000000..34e0965ed8145b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IECall.cs @@ -0,0 +1,18 @@ +// 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 interface IECall : IContract +{ + static string IContract.Name { get; } = nameof(ECall); + + TargetPointer MapTargetBackToMethodDesc(TargetCodePointer codePointer) => throw new NotImplementedException(); +} + +public readonly struct ECall : IECall +{ + // throws NotImplementedException for all methods +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 6039bf6e8aed3b..9dedef1d4918fe 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -101,6 +101,7 @@ public enum DataType ArrayListBlock, EETypeHashTable, InstMethodHashTable, + ECHash, TransitionBlock, DebuggerEval, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index c90e55cf517d0c..c8a53ea5876266 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -62,6 +62,9 @@ public static class Globals public const string Architecture = nameof(Architecture); public const string OperatingSystem = nameof(OperatingSystem); + + public const string FCallHashSize = nameof(FCallHashSize); + public const string FCallMethods = nameof(FCallMethods); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECallFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECallFactory.cs new file mode 100644 index 00000000000000..10e160aa764fe6 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECallFactory.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 ECallFactory : IContractFactory +{ + IECall IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new ECall_1(target), + _ => default(ECall), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs new file mode 100644 index 00000000000000..57aa68ea3b7c50 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs @@ -0,0 +1,47 @@ +// 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; + +internal readonly struct ECall_1 : IECall +{ + private readonly Target _target; + + internal ECall_1(Target target) + { + _target = target; + } + + TargetPointer IECall.MapTargetBackToMethodDesc(TargetCodePointer codePointer) + { + if (codePointer == TargetCodePointer.Null) + return TargetPointer.Null; + + + TargetPointer pECHashTable = _target.ReadGlobalPointer(Constants.Globals.FCallMethods); + TargetPointer ecHashTable = _target.ReadPointer(pECHashTable); + + TargetPointer pECHash = ecHashTable + (ulong)(FCallHash(codePointer) * _target.PointerSize); + + while (pECHash != TargetPointer.Null) + { + Data.ECHash eCHash = _target.ProcessedData.GetOrAdd(pECHash); + if (eCHash.Implementation == codePointer) + { + return eCHash.MethodDesc; + } + pECHash = eCHash.Next; + } + + return TargetPointer.Null; // not found + } + + private uint FCallHash(TargetCodePointer codePointer) + { + // see FCallHash in ecall.cpp + uint hashBucketCount = _target.ReadGlobal(Constants.Globals.FCallHashSize); + return (uint)(codePointer.Value % hashBucketCount); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 35d30ddc380a1c..80d5d499641e2c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -1038,7 +1038,11 @@ MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, } // FCall path, look up address in the FCall table - // TODO(cdac): FCall path if relevant + TargetPointer fCallMethodDesc = _target.Contracts.ECall.MapTargetBackToMethodDesc(pCode); + if (fCallMethodDesc != TargetPointer.Null) + { + return GetMethodDescHandle(fCallMethodDesc); + } // stub path, read address as a Precode and read MethodDesc from it { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ECHash.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ECHash.cs new file mode 100644 index 00000000000000..d64aaf65ea24eb --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ECHash.cs @@ -0,0 +1,21 @@ +// 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.Data; + +internal sealed class ECHash : IData +{ + static ECHash IData.Create(Target target, TargetPointer address) => new ECHash(target, address); + public ECHash(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ECHash); + + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + Implementation = target.ReadCodePointer(address + (ulong)type.Fields[nameof(Implementation)].Offset); + MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + } + + public TargetPointer Next { get; init; } + public TargetCodePointer Implementation { get; init; } + public TargetPointer MethodDesc { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 7b339281951cbe..a3da176f1548c9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -24,7 +24,8 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG { _target = target; _tryGetContractVersion = tryGetContractVersion; - _factories = new() { + _factories = new() + { [typeof(IException)] = new ExceptionFactory(), [typeof(ILoader)] = new LoaderFactory(), [typeof(IEcmaMetadata)] = new EcmaMetadataFactory(), @@ -39,6 +40,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IReJIT)] = new ReJITFactory(), [typeof(IStackWalk)] = new StackWalkFactory(), [typeof(IRuntimeInfo)] = new RuntimeInfoFactory(), + [typeof(IECall)] = new ECallFactory(), }; configureFactories?.Invoke(_factories); } @@ -57,6 +59,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IReJIT ReJIT => GetContract(); public override IStackWalk StackWalk => GetContract(); public override IRuntimeInfo RuntimeInfo => GetContract(); + public override IECall ECall => GetContract(); private TContract GetContract() where TContract : IContract { From cccf552c7ad441972c9c0cb37444c2d63456f098 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 2 May 2025 12:07:41 -0400 Subject: [PATCH 17/54] test and fix FCall lookup --- .../Contracts/IRuntimeTypeSystem.cs | 2 +- .../Contracts/ECall_1.cs | 12 ++++------ .../Contracts/RuntimeTypeSystem_1.cs | 23 +++++++++++-------- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 5 +++- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 0d5fac5d2c7522..51e7670e1789d4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -88,7 +88,7 @@ public interface IRuntimeTypeSystem : IContract TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); TargetPointer GetParentMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); - MethodDescHandle GetMethodDescForSlot(TypeHandle methodTable, ushort slot) => throw new NotImplementedException(); + TargetPointer GetMethodDescForSlot(TypeHandle methodTable, ushort slot) => throw new NotImplementedException(); uint GetBaseSize(TypeHandle typeHandle) => throw new NotImplementedException(); // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs index 57aa68ea3b7c50..7c2cbe1414907e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs @@ -1,8 +1,6 @@ // 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; internal readonly struct ECall_1 : IECall @@ -21,18 +19,18 @@ TargetPointer IECall.MapTargetBackToMethodDesc(TargetCodePointer codePointer) TargetPointer pECHashTable = _target.ReadGlobalPointer(Constants.Globals.FCallMethods); - TargetPointer ecHashTable = _target.ReadPointer(pECHashTable); - TargetPointer pECHash = ecHashTable + (ulong)(FCallHash(codePointer) * _target.PointerSize); + TargetPointer pECHash = pECHashTable + (ulong)(FCallHash(codePointer) * _target.PointerSize); + TargetPointer ecHashAddr = _target.ReadPointer(pECHash); - while (pECHash != TargetPointer.Null) + while (ecHashAddr != TargetPointer.Null) { - Data.ECHash eCHash = _target.ProcessedData.GetOrAdd(pECHash); + Data.ECHash eCHash = _target.ProcessedData.GetOrAdd(ecHashAddr); if (eCHash.Implementation == codePointer) { return eCHash.MethodDesc; } - pECHash = eCHash.Next; + ecHashAddr = eCHash.Next; } return TargetPointer.Null; // not found diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 80d5d499641e2c..e77204fe6705a6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -1008,7 +1008,7 @@ private IEnumerable GetIntroducedMethods(TypeHandle typeHandle } } - MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, ushort slot) + TargetPointer IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, ushort slot) { if (!typeHandle.IsMethodTable()) throw new ArgumentException($"{nameof(typeHandle)} is not a MethodTable"); @@ -1024,33 +1024,38 @@ MethodDescHandle IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, MethodDesc md = _methodDescs[mdh.Address]; if (md.Slot == slot) { - return mdh; + return mdh.Address; } } } + return GetMethodDescForEntrypoint(pCode); + } + + private readonly TargetPointer GetMethodDescForEntrypoint(TargetCodePointer pCode) + { // standard path, ask ExecutionManager for the MethodDesc IExecutionManager executionManager = _target.Contracts.ExecutionManager; if (executionManager.GetCodeBlockHandle(pCode) is CodeBlockHandle cbh) { TargetPointer methodDescPtr = executionManager.GetMethodDesc(cbh); - return GetMethodDescHandle(methodDescPtr); + return methodDescPtr; } // FCall path, look up address in the FCall table - TargetPointer fCallMethodDesc = _target.Contracts.ECall.MapTargetBackToMethodDesc(pCode); - if (fCallMethodDesc != TargetPointer.Null) { - return GetMethodDescHandle(fCallMethodDesc); + TargetPointer methodDescPtr = _target.Contracts.ECall.MapTargetBackToMethodDesc(pCode); + if (methodDescPtr != TargetPointer.Null) + { + return methodDescPtr; + } } // stub path, read address as a Precode and read MethodDesc from it { TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromStubAddress(pCode); - return GetMethodDescHandle(methodDescPtr); + return methodDescPtr; } - - throw new NotImplementedException("MethodDesc for slot is not implemented yet"); } TargetPointer IRuntimeTypeSystem.GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 90d5b623ed7342..658673c8cd9ee2 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -290,7 +290,10 @@ 4. Generic method on Generic type (There are N generic defining methods where N TargetPointer cmt = _rts.GetCanonicalMethodTable(typeParam); TypeHandle cmtHandle = _rts.GetTypeHandle(cmt); - MethodDescHandle methodDesc = _rts.GetMethodDescForSlot(cmtHandle, slotNum); + TargetPointer methodDescAddr = _rts.GetMethodDescForSlot(cmtHandle, slotNum); + if (methodDescAddr == TargetPointer.Null) continue; + MethodDescHandle methodDesc = _rts.GetMethodDescHandle(methodDescAddr); + if (HasNativeCodeAnyVersion(methodDesc)) { yield return methodDesc; From 555ad9ca2ef5dc65f2e4117cca7d4020dc59f4f4 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 5 May 2025 11:17:15 -0400 Subject: [PATCH 18/54] add docs for and improve DacEnumerableHash table --- docs/design/datacontracts/Loader.md | 107 ++++++++++++++++++ .../Contracts/ILoader.cs | 6 +- .../Contracts/Loader_1.cs | 22 ++-- .../Data/DacEnumerableHash.cs | 10 +- .../Data/EETypeHashTable.cs | 14 ++- .../Data/InstMethodHashTable.cs | 27 ++--- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 4 +- 7 files changed, 139 insertions(+), 51 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 916b4ff03bf77b..107e7d5617dfc2 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -59,6 +59,9 @@ TargetPointer GetAssembly(ModuleHandle handle); TargetPointer GetPEAssembly(ModuleHandle handle); bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size); +IEnumerable GetAvailableTypeParams(ModuleHandle handle); +IEnumerable GetInstantiatedMethods(ModuleHandle handle); + bool IsProbeExtensionResultValid(ModuleHandle handle); ModuleFlags GetFlags(ModuleHandle handle); string GetPath(ModuleHandle handle); @@ -86,6 +89,8 @@ bool IsAssemblyLoaded(ModuleHandle handle); | `Module` | `Path` | Path of the Module (UTF-16, null-terminated) | | `Module` | `FileName` | File name of the Module (UTF-16, null-terminated) | | `Module` | `GrowableSymbolStream` | Pointer to the in memory symbol stream | +| `Module` | `AvailableTypeParams` | Pointer to an EETypeHashTable | +| `Module` | `InstMethodHashTable` | Pointer to an InstMethodHashTable | | `Module` | `FieldDefToDescMap` | Mapping table | | `Module` | `ManifestModuleReferencesMap` | Mapping table | | `Module` | `MemberRefToDescMap` | Mapping table | @@ -118,6 +123,14 @@ bool IsAssemblyLoaded(ModuleHandle handle); | `ArrayListBlock` | `Next` | Next ArrayListBlock in chain | | `ArrayListBlock` | `Size` | Size of data section in block | | `ArrayListBlock` | `ArrayStart` | Start of data section in block | +| `EETypeHashTable` | `Buckets` | Pointer to hash table buckets | +| `EETypeHashTable` | `Count` | Count of elements in the hash table | +| `EETypeHashTable` | `VolatileEntryValue` | The data stored in the hash table entry | +| `EETypeHashTable` | `VolatileEntryNextEntry` | Next pointer in the hash table entry | +| `InstMethodHashTable` | `Buckets` | Pointer to hash table buckets | +| `InstMethodHashTable` | `Count` | Count of elements in the hash table | +| `InstMethodHashTable` | `VolatileEntryValue` | The data stored in the hash table entry | +| `InstMethodHashTable` | `VolatileEntryNextEntry` | Next pointer in the hash table entry | ### Global variables used: @@ -298,6 +311,30 @@ bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint return true; } +IEnumerable GetAvailableTypeParams(ModuleHandle handle) +{ + TargetPointer availableTypeParams = target.ReadPointer(handle.Address + /* Module::AvailableTypeParams offset */); + + if (availableTypeParams == TargetPointer.Null) return []; + + // EETypeHashTable is read as a DacEnumerableHash table. + // For more information on how this is read, see section below. + EETypeHashTable typeHashTable = // read EETypeHashTable at availableTypeParams + return typeHashTable.Entries.Select(entry => entry.TypeHandle); +} + +IEnumerable GetInstantiatedMethods(ModuleHandle handle) +{ + TargetPointer instMethodHashTable = target.ReadPointer(handle.Address + /* Module::InstMethodHashTable offset */); + + if (instMethodHashTable == TargetPointer.Null) return []; + + // InstMethodHashTable is read as a DacEnumerableHash table. + // For more information on how this is read, see section below. + InstMethodHashTable methodHashTable = // read InstMethodHashTable at instMethodHashTable + return methodHashTable.Entries.Select(entry => entry.MethodDesc); +} + bool IsProbeExtensionResultValid(ModuleHandle handle) { TargetPointer peAssembly = target.ReadPointer(handle.Address + /* Module::PEAssembly offset */); @@ -415,3 +452,73 @@ bool ILoader.IsAssemblyLoaded(ModuleHandle handle) return assembly.Level >= ASSEMBLY_LEVEL_LOADED; } ``` + +### DacEnumerableHash (EETypeHashTable and InstMethodHashTable) + +Both `EETypeHashTable` and `InstMethodHashTable` are based on the templated `DacEnumerableHash`. Because the base class is templated on the derived type, offsets may be different in derived types. + +The base implementation of `DacEnumerableHash` uses four datadescriptors: +| Datadescriptor | Purpose | +| --- | --- | +| `Buckets` | Pointer to the bucket array | +| `Count` | Number of elements in the hash table | +| `VolatileEntryValue` | The data held by an entry, defined by the derived class | +| `VolatileEntryNextEntry` | The next pointer on an hash table entry | + +The hash table is laid out as an array of `VolatileEntry` pointers's (buckets), each possibly forming a chain for values that hash into that bucket. The first three buckets are special and reserved for metadata. Instead of containing a `VolatileEntry`, these pointers are read as values with the following meanings. + +| Reserved Bucket offset | Purpose | +| --- | --- | +| `0` | Length of the Bucket array, this value does not include the first 3 slots which are special | +| `1` | Pointer to the next bucket array, not currently used in the cDAC | +| `2` | End sentinel for the current bucket array, not currently used in the cDAC | + +The current cDAC implementation does not use the 'hash' part of the table at all. Instead it iterates all elements in the table. Following the existing iteration logic in the runtime (and DAC), resizing the table while iterating is not supported. Given this constraint, the pointer to the next bucket array (resized data table) and the current end sentinel are not required to iterate all entries. + +To read all entries in the hash table: +1. Read the length bucket to find the number of chains `n`. +2. Initialize a list of elements `entries = []`. +3. For each chain, (buckets with offsets `3..n + 3`): + 1. Read the pointer in the bucket as `volatileEntryPtr`. + 2. If `volatileEntryPtr & 0x1 == 0x1`, this is an end sentinel and we stop reading this chain. + 3. Otherwise, add `volatileEntryPtr + /* VolatileEntryValue offset */` to entries. This points to the derived class defined data type. + 4. Set `volatileEntryPtr` to the value of the pointer located at `volatileEntryPtr + /* VolatileEntryNextEntry offset */` and go to step 3.2. +4. Return `entries` to be further parsed by derived classes. + +While both EETypeHashTable and InstMethodHashTable store pointer sized data types, they both use the LSBs as special flags. + +#### EETypeHashTable +EETypeHashTable uses the LSB to indicate if the TypeHandle is a hot entry. The cDAC implementation separates each value `value` in the table into two parts. The actual TypeHandle pointer and the associated flags. + +```csharp +class EETypeHashTable +{ + private const ulong FLAG_MASK = 0x1ul; + + public IReadOnlyList Entires { get; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer TypeHandle { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } +} +``` + +#### InstMethodHashTable +InstMethodHashTable uses the 2 LSBs as flags for the MethodDesc. The cDAC implementation separates each value `value` in the table into two parts. The actual MethodDesc pointer and the associated flags. + +```csharp +class InstMethodHashTable +{ + private const ulong FLAG_MASK = 0x3ul; + + public IReadOnlyList Entires { get; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer MethodDesc { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } +} +``` diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 5b61ba5bfc2704..38be405a5138a1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -83,13 +83,13 @@ public interface ILoader : IContract TargetPointer GetPEAssembly(ModuleHandle handle) => throw new NotImplementedException(); bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags) => throw new NotImplementedException(); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size) => throw new NotImplementedException(); + IEnumerable GetAvailableTypeParams(ModuleHandle handle) => throw new NotImplementedException(); + IEnumerable GetInstantiatedMethods(ModuleHandle handle) => throw new NotImplementedException(); + bool IsProbeExtensionResultValid(ModuleHandle handle) => throw new NotImplementedException(); ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); string GetPath(ModuleHandle handle) => throw new NotImplementedException(); string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); - List GetAvailableTypeParams(ModuleHandle handle) => throw new NotImplementedException(); - List GetInstantiatedMethods(ModuleHandle handle) => throw new NotImplementedException(); - TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 4384560fdfaed5..9df2e7f91dfdb3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -188,36 +189,27 @@ bool ILoader.TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, o return true; } - List ILoader.GetAvailableTypeParams(ModuleHandle handle) + IEnumerable ILoader.GetAvailableTypeParams(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - List typeParams = []; if (module.AvailableTypeParams == TargetPointer.Null) - return typeParams; + return []; EETypeHashTable typeHashTable = _target.ProcessedData.GetOrAdd(module.AvailableTypeParams); - typeParams.AddRange(typeHashTable.Entries); - - return typeParams; + return typeHashTable.Entries.Select(entry => entry.TypeHandle); } - List ILoader.GetInstantiatedMethods(ModuleHandle handle) + IEnumerable ILoader.GetInstantiatedMethods(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - List typeParams = []; if (module.InstMethodHashTable == TargetPointer.Null) - return typeParams; + return []; InstMethodHashTable methodHashTable = _target.ProcessedData.GetOrAdd(module.InstMethodHashTable); - foreach (InstMethodHashTable.InstMethodHashTableEntry entry in methodHashTable.Entries) - { - typeParams.Add(entry.MethodDesc); - } - - return typeParams; + return methodHashTable.Entries.Select(entry => entry.MethodDesc); } bool ILoader.IsProbeExtensionResultValid(ModuleHandle handle) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs index af7cb1fcd9a555..98270f7019f7be 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs @@ -8,7 +8,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; /// /// Parses hash tables that are implemented by DacEnumerableHash defined in dacenumerablehash.h -/// Requires the following datadescriptor fields on the inherited type: +/// Requires the following datadescriptor fields on the passed type: /// Buckets - Pointer to array of VolatileEntry pointers /// Count - Count of elements /// VolatileEntryValue - Offset of the value in the VolatileEntry struct @@ -17,8 +17,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class DacEnumerableHash { private const int SLOT_LENGTH = 0; - // private const int SLOT_NIEXT = 1; - private const int SLOT_ENDSENTINEL = 2; private const int SKIP_SPECIAL_SLOTS = 3; private readonly Target _target; @@ -76,12 +74,6 @@ private uint GetLength() return (uint)length; } - private TargetPointer GetBaseSentinel() - { - // Second pointer is a size_t base sentinel - TargetPointer baseSentinel = _target.ReadPointer(Buckets + (ulong)(SLOT_ENDSENTINEL * _target.PointerSize)); - return baseSentinel; - } private static bool IsEndSentinel(TargetPointer value) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs index 4c099b54d18fab..f96645d2d4409a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs @@ -8,6 +8,8 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class EETypeHashTable : IData { + private const ulong FLAG_MASK = 0x1ul; + static EETypeHashTable IData.Create(Target target, TargetPointer address) => new EETypeHashTable(target, address); public EETypeHashTable(Target target, TargetPointer address) { @@ -15,14 +17,20 @@ public EETypeHashTable(Target target, TargetPointer address) DacEnumerableHash baseHashTable = new(target, address, type); - List entries = []; + List entries = []; foreach (TargetPointer entry in baseHashTable.Entries) { TargetPointer typeHandle = target.ReadPointer(entry); - entries.Add(typeHandle.Value & ~0x1ul); + entries.Add(new(typeHandle)); } Entries = entries; } - public IReadOnlyList Entries { get; init; } + public IReadOnlyList Entries { get; init; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer TypeHandle { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs index 81cba4eb6da11d..a1b36a5617cde4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs @@ -3,12 +3,13 @@ using System; using System.Collections.Generic; -using System.Reflection.Metadata; namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class InstMethodHashTable : IData { + private const ulong FLAG_MASK = 0x3ul; + static InstMethodHashTable IData.Create(Target target, TargetPointer address) => new InstMethodHashTable(target, address); public InstMethodHashTable(Target target, TargetPointer address) { @@ -16,32 +17,20 @@ public InstMethodHashTable(Target target, TargetPointer address) DacEnumerableHash baseHashTable = new(target, address, type); - List entries = []; + List entries = []; foreach (TargetPointer entry in baseHashTable.Entries) { TargetPointer methodDescPtr = target.ReadPointer(entry); - InstMethodHashTableEntry instMethodHashTableEntry = new() - { - MethodDesc = methodDescPtr.Value & ~0x3ul, - Flags = (InstMethodHashTableFlags)(methodDescPtr.Value & 0x3ul) - }; - entries.Add(instMethodHashTableEntry); + entries.Add(new(methodDescPtr)); } Entries = entries; } - public IReadOnlyList Entries { get; init; } - - public readonly struct InstMethodHashTableEntry - { - public TargetPointer MethodDesc { get; init; } - public InstMethodHashTableFlags Flags { get; init; } - } + public IReadOnlyList Entries { get; init; } - [Flags] - public enum InstMethodHashTableFlags + public readonly struct Entry(TargetPointer value) { - UnboxingStub = 0x1, - RequiresInstArg = 0x2, + public TargetPointer MethodDesc { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); } } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 658673c8cd9ee2..8819b5b4575ec8 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -182,7 +182,7 @@ public int Start() private IEnumerable IterateMethodInstantiations(Contracts.ModuleHandle moduleHandle) { - List methodInstantiations = _loader.GetInstantiatedMethods(moduleHandle); + IEnumerable methodInstantiations = _loader.GetInstantiatedMethods(moduleHandle); foreach (TargetPointer methodPtr in methodInstantiations) { @@ -192,7 +192,7 @@ private IEnumerable IterateMethodInstantiations(Contracts.Modu private IEnumerable IterateTypeParams(Contracts.ModuleHandle moduleHandle) { - List typeParams = _loader.GetAvailableTypeParams(moduleHandle); + IEnumerable typeParams = _loader.GetAvailableTypeParams(moduleHandle); foreach (TargetPointer type in typeParams) { From e996b25b90a68b93a8e005a246d9da1a4d65723f Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 5 May 2025 11:27:02 -0400 Subject: [PATCH 19/54] add docs related to ECall --- docs/design/datacontracts/ECall.md | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 docs/design/datacontracts/ECall.md diff --git a/docs/design/datacontracts/ECall.md b/docs/design/datacontracts/ECall.md new file mode 100644 index 00000000000000..f1ea6c4f8cc07d --- /dev/null +++ b/docs/design/datacontracts/ECall.md @@ -0,0 +1,39 @@ +# Contract ECall + +This contract is for fetching information related to native calls into the runtime. + +## APIs of contract + +``` csharp +// Given an FCall entrypoint returns the corresponding MethodDesc. +// If the address does not correspond to an FCall, returns TargetPointer.Null. +TargetPointer MapTargetBackToMethodDesc(TargetCodePointer address); +``` + +## Version 1 + +Global variables used +| Global Name | Type | Purpose | +| --- | --- | --- | +| FCallMethods | ECHash[] | Hash table containing ECHash structures | +| FCallHashSize | uint | Number of buckets in the hash table | + + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `ECHash` | `Next` | Pointer to the next ECHash in the chain | +| `ECHash` | `Implementation` | FCall's Entrypoint address | +| `ECHash` | `MethodDesc` | Pointer to the FCall's method desc | + + +``` csharp +TargetPointer IECall.MapTargetBackToMethodDesc(TargetCodePointer codePointer) +``` + +To map an FCall entrypoint back to a MethodDesc, we read the global `FCallMethods` hash table. This is a array of pointers to `ECHash` objects. The length of this array is defined by the global `FCallHashSize` where each element is an `ECHash` which can form a chain. It uses a simple hash function: ` = codePointer % FCallHashSize` to map code entry points to buckets. To map a `codePointer` back to a MethodDesc pointer: + +1. Calculate the `` corresponding to the given `codePointer`. +2. Take the `` offset into the `FCallMethods` array. +3. Now that we have the correct `ECHash` chain, iterate the chain using the `ECHash.Next` pointer until we find an `ECHash` where the `Implementation` field matches the `codePointer`. If found, return the `MethodDesc` field. +4. If no `ECHash` matches return `TargetPointer.Null` to indicate a MethodDesc was not found. From 6a1760c1be36fc716b323be8a01c917209140c26 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 5 May 2025 11:28:12 -0400 Subject: [PATCH 20/54] fix loader.GetModules call --- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 8819b5b4575ec8..b5e5b616794395 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -203,7 +203,7 @@ private IEnumerable IterateMethodInstantiations(Contracts.Modu private IEnumerable IterateModules() { ILoader loader = _target.Contracts.Loader; - List modules = loader.GetAssemblies( + IEnumerable modules = loader.GetModules( _appDomain, AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); From 0385adeed849cc42a316ae0dc72989b06384d4ec Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 5 May 2025 12:51:46 -0400 Subject: [PATCH 21/54] add docs for RuntimeTypeSystem changes --- .../design/datacontracts/RuntimeTypeSystem.md | 111 ++++++++++++++++++ .../Contracts/RuntimeTypeSystem_1.cs | 4 +- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index b1d65b7c825273..346fa5a2547433 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -41,6 +41,8 @@ partial interface IRuntimeTypeSystem : IContract public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle); public virtual TargetPointer GetParentMethodTable(TypeHandle typeHandle); + public virtual TargetPointer GetMethodDescForSlot(TypeHandle typeHandle, ushort slot); + public virtual uint GetBaseSize(TypeHandle typeHandle); // The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes) public virtual uint GetComponentSize(TypeHandle typeHandle); @@ -345,6 +347,7 @@ The contract additionally depends on these data descriptors | `MethodTable` | `PerInstInfo` | Either the array element type, or pointer to generic information for `MethodTable` | | `EEClass` | `InternalCorElementType` | An InternalCorElementType uses the enum values of a CorElementType to indicate some of the information about the type of the type which uses the EEClass In particular, all reference types are CorElementType.Class, Enums are the element type of their underlying type and ValueTypes which can exactly be represented as an element type are represented as such, all other values types are represented as CorElementType.ValueType. | | `EEClass` | `MethodTable` | Pointer to the canonical MethodTable of this type | +| `EEClass` | `MethodDescChunk` | Pointer to the first MethodDescChunk of the EEClass | | `EEClass` | `NumMethods` | Count of methods attached to the EEClass | | `EEClass` | `NumNonVirtualSlots` | Count of non-virtual slots for the EEClass | | `EEClass` | `CorTypeAttr` | Various flags | @@ -629,6 +632,8 @@ The version 1 `MethodDesc` APIs depend on the following globals: | --- | --- | | `MethodDescAlignment` | `MethodDescChunk` trailing data is allocated in multiples of this constant. The size (in bytes) of each `MethodDesc` (or subclass) instance is a multiple of this constant. | | `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` | +| `MethodDescSizeTable` | A pointer to the MethodDesc size table. The MethodDesc flags are used as an offset into this table to lookup the MethodDesc size. | + In the runtime a `MethodDesc` implicitly belongs to a single `MethodDescChunk` and some common data is shared between method descriptors that belong to the same chunk. A single method table will typically have multiple chunks. There are subkinds of MethodDescs at runtime of varying sizes (but the sizes must be mutliples of `MethodDescAlignment`) and each chunk contains method descriptors of the same size. @@ -668,6 +673,9 @@ The contract depends on the following other contracts | Loader | | PlatformMetadata | | ReJIT | +| ExecutionManager | +| PrecodeStubs | +| ECall | And the following enumeration definitions @@ -871,6 +879,22 @@ And the various apis are implemented with the following algorithms return 0x06000000 | tokenRange | tokenRemainder; } + public uint GetMethodDescSize(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + // the runtime generates a table to lookup the size of a MethodDesc based on the flags + // read the location of the table and index into it using certain bits of MethodDesc.Flags + TargetPointer methodDescSizeTable = target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable); + + ushort arrayOffset = (ushort)(methodDesc.Flags & (ushort)( + MethodDescFlags.ClassificationMask | + MethodDescFlags.HasNonVtableSlot | + MethodDescFlags.HasMethodImpl | + MethodDescFlags.HasNativeCodeSlot)); + return target.Read(methodDescSizeTable + arrayOffset); + } + public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; @@ -1161,3 +1185,90 @@ Getting the native code pointer for methods with a NativeCodeSlot or a stable en return GetStableEntryPoint(methodDescHandle.Address, md); } ``` + +Getting a MethodDesc for a certain slot in a MethodTable +```csharp + // Based on MethodTable::IntroducedMethodIterator + private IEnumerable GetIntroducedMethods(TypeHandle typeHandle) + { + // typeHandle must represent a MethodTable + + EEClass eeClass = GetClassData(typeHandle); + + // pointer to the first MethodDescChunk + TargetPointer chunkAddr = eeClass.MethodDescChunk; + while (chunkAddr != TargetPointer.Null) + { + MethodDescChunk chunk = // read Data.MethodDescChunk data from chunkAddr + TargetPointer methodDescPtr = chunk.FirstMethodDesc; + + // chunk.Count is the number of MethodDescs in the chunk - 1 + // add 1 to get the actual number of MethodDescs within the chunk + for (int i = 0; i < chunk.Count + 1; i++) + { + MethodDescHandle methodDescHandle = GetMethodDescHandle(methodDescPtr); + + // increment pointer to the beginning of the next MethodDesc + methodDescPtr += GetMethodDescSize(methodDescHandle); + yield return methodDescHandle; + } + + // go to the next chunk + chunkAddr = chunk.Next; + } + } + + private readonly TargetPointer GetMethodDescForEntrypoint(TargetCodePointer pCode) + { + // Standard path, ask ExecutionManager for the MethodDesc + IExecutionManager executionManager = _target.Contracts.ExecutionManager; + if (executionManager.GetCodeBlockHandle(pCode) is CodeBlockHandle cbh) + { + TargetPointer methodDescPtr = executionManager.GetMethodDesc(cbh); + return methodDescPtr; + } + + // FCall path, look up address in the FCall table using the ECall contract + { + TargetPointer methodDescPtr = _target.Contracts.ECall.MapTargetBackToMethodDesc(pCode); + if (methodDescPtr != TargetPointer.Null) + { + return methodDescPtr; + } + } + + // Stub path, read address as a Precode and get the MethodDesc from it + { + TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromStubAddress(pCode); + return methodDescPtr; + } + } + + public TargetPointer GetMethodDescForSlot(TypeHandle methodTable, ushort slot) + { + if (!typeHandle.IsMethodTable()) + throw new ArgumentException($"{nameof(typeHandle)} is not a MethodTable"); + + TargetPointer cannonMTPTr = GetCanonicalMethodTable(typeHandle); + TypeHandle canonMT = GetTypeHandle(cannonMTPTr); + TargetPointer slotPtr = GetAddressOfSlot(canonMT, slot); + TargetCodePointer pCode = _target.ReadCodePointer(slotPtr); + + if (pCode == TargetCodePointer.Null) + { + // if pCode is null, we iterate through the method descs in the MT. + foreach (MethodDescHandle mdh in GetIntroducedMethods(typeHandle)) + { + MethodDesc md = _methodDescs[mdh.Address]; + + // if a MethodDesc matches the slot, return that MethodDesc + if (md.Slot == slot) + { + return mdh.Address; + } + } + } + + return GetMethodDescForEntrypoint(pCode); + } +``` diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index e77204fe6705a6..f5899e50f949c6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -1013,7 +1013,9 @@ TargetPointer IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, ush if (!typeHandle.IsMethodTable()) throw new ArgumentException($"{nameof(typeHandle)} is not a MethodTable"); - TargetPointer slotPtr = GetAddressOfMethodTableSlot(GetCanonicalMethodTable(typeHandle), slot); + TargetPointer cannonMTPTr = GetCanonicalMethodTable(typeHandle); + TypeHandle canonMT = GetTypeHandle(cannonMTPTr); + TargetPointer slotPtr = GetAddressOfSlot(canonMT, slot); TargetCodePointer pCode = _target.ReadCodePointer(slotPtr); if (pCode == TargetCodePointer.Null) From e9c3195e5cf9300eb8f575f36cdc3b066581f7e0 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 6 May 2025 15:02:25 -0400 Subject: [PATCH 22/54] MethodDesc::s_ClassificationSizeTable to heapdumps --- src/coreclr/debug/daccess/enummem.cpp | 3 +++ src/coreclr/vm/method.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 95d401caa0daa9..449ea17a5e19eb 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -208,6 +208,9 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) EX_TRY { + // ClassificationSizeTable is a static array of bytes used by the cDAC to lookup MethodDesc sizes + ReportMem((TADDR)&MethodDesc::s_ClassificationSizeTable, MethodDesc::s_ClassificationSizeTableSize); + // These two static pointers are pointed to static data of byte[] // then run constructor in place // diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 6384d8aa2e9dc2..7437a347e7cbf7 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -101,6 +101,8 @@ const BYTE MethodDesc::s_ClassificationSizeTable[] = { METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), }; +const SIZE_T MethodDesc::s_ClassificationSizeTableSize = sizeof(MethodDesc::s_ClassificationSizeTable); + #ifndef FEATURE_COMINTEROP #undef CLRToCOMCallMethodDesc #endif From ba1cadd2dfdfbad6fc1173c4a1ce2bc0537328ee Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 6 May 2025 15:17:37 -0400 Subject: [PATCH 23/54] Revert "MethodDesc::s_ClassificationSizeTable to heapdumps" This reverts commit 16becb8b5ee13708856d50bb4e44ab1a948e2557. --- src/coreclr/debug/daccess/enummem.cpp | 3 --- src/coreclr/vm/method.cpp | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 449ea17a5e19eb..95d401caa0daa9 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -208,9 +208,6 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) EX_TRY { - // ClassificationSizeTable is a static array of bytes used by the cDAC to lookup MethodDesc sizes - ReportMem((TADDR)&MethodDesc::s_ClassificationSizeTable, MethodDesc::s_ClassificationSizeTableSize); - // These two static pointers are pointed to static data of byte[] // then run constructor in place // diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 7437a347e7cbf7..6384d8aa2e9dc2 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -101,8 +101,6 @@ const BYTE MethodDesc::s_ClassificationSizeTable[] = { METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), }; -const SIZE_T MethodDesc::s_ClassificationSizeTableSize = sizeof(MethodDesc::s_ClassificationSizeTable); - #ifndef FEATURE_COMINTEROP #undef CLRToCOMCallMethodDesc #endif From ae58823d70acdaa72ff2797d2c41cfc3b1888a96 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 6 May 2025 17:35:38 -0400 Subject: [PATCH 24/54] fix tests --- .../MockDescriptors/MockDescriptors.MethodDescriptors.cs | 6 ++++++ .../managed/cdac/tests/MockDescriptors/MockDescriptors.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index 4a0787ac65c27f..ea4e7ebd2e96b9 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -100,9 +100,15 @@ internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder, ( LoaderBuilder = loaderBuilder; _allocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End); Types = GetTypes(); + + // Add dummy MethodDescSizeTable. Sizes will be incorrect, but we don't use it in tests. + MockMemorySpace.HeapFragment methodDescSizeTable = _allocator.Allocate(0x100, "MethodDescSizeTable"); + Builder.AddHeapFragment(methodDescSizeTable); + Globals = rtsBuilder.Globals.Concat( [ new(nameof(Constants.Globals.MethodDescTokenRemainderBitCount), TokenRemainderBitCount), + new(nameof(Constants.Globals.MethodDescSizeTable), methodDescSizeTable.Address), ]).ToArray(); } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index 10d7d1418daffa..e6fdd8eb4e2ec2 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs @@ -38,6 +38,7 @@ internal record TypeFields Fields = [ new(nameof(Data.EEClass.MethodTable), DataType.pointer), + new(nameof(Data.EEClass.MethodDescChunk), DataType.pointer), new(nameof(Data.EEClass.CorTypeAttr), DataType.uint32), new(nameof(Data.EEClass.NumMethods), DataType.uint16), new(nameof(Data.EEClass.InternalCorElementType), DataType.uint8), @@ -138,6 +139,8 @@ internal record TypeFields new(nameof(Data.Module.FileName), DataType.pointer), new(nameof(Data.Module.ReadyToRunInfo), DataType.pointer), new(nameof(Data.Module.GrowableSymbolStream), DataType.pointer), + new(nameof(Data.Module.AvailableTypeParams), DataType.pointer), + new(nameof(Data.Module.InstMethodHashTable), DataType.pointer), new(nameof(Data.Module.FieldDefToDescMap), DataType.pointer), new(nameof(Data.Module.ManifestModuleReferencesMap), DataType.pointer), new(nameof(Data.Module.MemberRefToDescMap), DataType.pointer), From 33631e78eb3efdcf5a96f5ffcbb70b93b338ac94 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 15 May 2025 19:38:24 -0400 Subject: [PATCH 25/54] fix async v2 flags --- .../Contracts/RuntimeTypeSystem_1.cs | 4 ++-- .../RuntimeTypeSystemHelpers/MethodDescFlags_1.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index f5899e50f949c6..06772fbe3a71d3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -134,14 +134,14 @@ private static uint ComputeSize(Target target, Data.MethodDesc desc) { // Size of the MethodDesc is variable, read it from the targets lookup table // See MethodDesc::SizeOf in method.cpp for details - // TODO(cdac): make sure this value is stored in minidumps TargetPointer methodDescSizeTable = target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable); ushort arrayOffset = (ushort)(desc.Flags & (ushort)( MethodDescFlags_1.MethodDescFlags.ClassificationMask | MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasMethodImpl | - MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot)); + MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot | + MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData)); return target.Read(methodDescSizeTable + arrayOffset); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs index 89988f355a39ad..901d9331c8938f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs @@ -15,6 +15,7 @@ internal enum MethodDescFlags : ushort HasNonVtableSlot = 0x0008, HasMethodImpl = 0x0010, HasNativeCodeSlot = 0x0020, + HasAsyncMethodData = 0x040, #endregion Optional slots } From c039dd64025af612146107bdbb3e1d9589657cd8 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 23 Jun 2025 14:20:53 -0400 Subject: [PATCH 26/54] add docs for MethodDescFlags.HasAsyncMethodData --- docs/design/datacontracts/RuntimeTypeSystem.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 346fa5a2547433..f0bfe7b66eb571 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -702,6 +702,7 @@ And the following enumeration definitions HasNonVtableSlot = 0x0008, HasMethodImpl = 0x0010, HasNativeCodeSlot = 0x0020, + HasAsyncMethodData = 0x0040, // Mask for the above flags MethodDescAdditionalPointersMask = 0x0038, #endredion Additional pointers @@ -891,7 +892,8 @@ And the various apis are implemented with the following algorithms MethodDescFlags.ClassificationMask | MethodDescFlags.HasNonVtableSlot | MethodDescFlags.HasMethodImpl | - MethodDescFlags.HasNativeCodeSlot)); + MethodDescFlags.HasNativeCodeSlot | + MethodDescFlags.HasAsyncMethodData)); return target.Read(methodDescSizeTable + arrayOffset); } From 9f677af325dbdb8859c9b53e6292639a050fff0d Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 23 Jun 2025 14:40:25 -0400 Subject: [PATCH 27/54] update to use ClrDataAddress --- .../Legacy/ClrDataMethodInstance.cs | 36 +++++++++++-------- .../Legacy/IXCLRData.cs | 4 +-- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index ed87cf26571a33..27cabfe18834b9 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -49,18 +49,19 @@ int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, void** /*IXCLRDataModu } if (mod is not null) { - void* legacyModPtr = null; + IXCLRDataModule? legacyMod = null; if (_legacyImpl is not null) { + void* legacyModPtr = null; int hrLegacy = _legacyImpl.GetTokenAndScope(token, &legacyModPtr); if (hrLegacy < 0) return hrLegacy; - } - - object obj = cw.GetOrCreateObjectForComInstance((nint)legacyModPtr, CreateObjectFlags.None); - if (obj is not IXCLRDataModule legacyMod) - { - throw new ArgumentException("Invalid module object", nameof(mod)); + object obj = cw.GetOrCreateObjectForComInstance((nint)legacyModPtr, CreateObjectFlags.None); + if (obj is not IXCLRDataModule) + { + throw new ArgumentException("Invalid module object", nameof(mod)); + } + legacyMod = obj as IXCLRDataModule; } TargetPointer mtAddr = rts.GetMethodTable(_methodDesc); @@ -81,14 +82,21 @@ int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, void** /*IXCLRDataModu #if DEBUG if (_legacyImpl is not null) { - uint tokenLocal; + bool validateToken = token is not null; + bool validateMod = mod is not null; + + uint tokenLocal = 0; void* legacyModPtr = null; - int hrLocal = _legacyImpl.GetTokenAndScope(&tokenLocal, &legacyModPtr); + int hrLocal = _legacyImpl.GetTokenAndScope(validateToken ? &tokenLocal : null, validateMod ? &legacyModPtr : null); Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); - Debug.Assert(tokenLocal == *token, $"cDAC: {*token:x}, DAC: {tokenLocal:x}"); - if (hr == HResults.S_OK) + if (validateToken) + { + Debug.Assert(tokenLocal == *token, $"cDAC: {*token:x}, DAC: {tokenLocal:x}"); + } + + if (validateMod && hr == HResults.S_OK) { Marshal.Release((nint)legacyModPtr); // release the legacy module } @@ -116,7 +124,7 @@ int IXCLRDataMethodInstance.GetNumTypeArguments(uint* numTypeArgs) int IXCLRDataMethodInstance.GetTypeArgumentByIndex(uint index, void** typeArg) => _legacyImpl is not null ? _legacyImpl.GetTypeArgumentByIndex(index, typeArg) : HResults.E_NOTIMPL; - int IXCLRDataMethodInstance.GetILOffsetsByAddress(ulong address, uint offsetsLen, uint* offsetsNeeded, uint* ilOffsets) + int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint offsetsLen, uint* offsetsNeeded, uint* ilOffsets) => _legacyImpl is not null ? _legacyImpl.GetILOffsetsByAddress(address, offsetsLen, offsetsNeeded, ilOffsets) : HResults.E_NOTIMPL; int IXCLRDataMethodInstance.GetAddressRangesByILOffset(uint ilOffset, uint rangesLen, uint* rangesNeeded, void* addressRanges) @@ -137,7 +145,7 @@ int IXCLRDataMethodInstance.EndEnumExtents(ulong handle) int IXCLRDataMethodInstance.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) => _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL; - int IXCLRDataMethodInstance.GetRepresentativeEntryAddress(ulong* addr) + int IXCLRDataMethodInstance.GetRepresentativeEntryAddress(ClrDataAddress* addr) { int hr = HResults.S_OK; @@ -164,7 +172,7 @@ int IXCLRDataMethodInstance.GetRepresentativeEntryAddress(ulong* addr) #if DEBUG if (_legacyImpl is not null) { - ulong addrLocal; + ClrDataAddress addrLocal; int hrLocal = _legacyImpl.GetRepresentativeEntryAddress(&addrLocal); Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs index 9d1bfe491b9c1e..ae043ab996a5a4 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs @@ -450,7 +450,7 @@ int GetName( [PreserveSig] int GetILOffsetsByAddress( - ulong address, + ClrDataAddress address, uint offsetsLen, uint* offsetsNeeded, uint* ilOffsets); @@ -486,5 +486,5 @@ int Request( byte* outBuffer); [PreserveSig] - int GetRepresentativeEntryAddress(ulong* addr); + int GetRepresentativeEntryAddress(ClrDataAddress* addr); } From 5048231c349bc84583af731ab820bbe2f17b61b4 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 23 Jun 2025 15:18:09 -0400 Subject: [PATCH 28/54] change todo message --- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index d7df1956b0dea7..a51a20832f56ca 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -316,7 +316,7 @@ private bool HasNativeCodeAnyVersion(MethodDescHandle mdHandle) if (pcode == TargetCodePointer.Null) { - // I think this is equivalent to get any native code version + // TODO(cdac): Fix this to check for any native code version NativeCodeVersionHandle nativeCodeVersion = cv.GetActiveNativeCodeVersion(mdHandle.Address); if (nativeCodeVersion.Valid) { From 4c56298cbc443d6c1fb5bea284250c86e0e792e2 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Thu, 26 Jun 2025 15:11:54 -0400 Subject: [PATCH 29/54] support parsing DebugInfo for jitted code --- src/coreclr/debug/runtimeinfo/contracts.jsonc | 5 +- .../debug/runtimeinfo/datadescriptor.cpp | 1 + .../debug/runtimeinfo/datadescriptor.h | 16 ++ src/coreclr/inc/patchpointinfo.h | 16 ++ src/coreclr/vm/codeman.h | 9 + .../ContractRegistry.cs | 6 +- .../Contracts/IDebugInfo.cs | 35 ++++ .../Contracts/IExecutionManager.cs | 1 + .../DataType.cs | 2 + .../Constants.cs | 1 + .../Contracts/DebugInfo/DebugInfoFactory.cs | 16 ++ .../Contracts/DebugInfo/DebugInfo_1.cs | 162 ++++++++++++++++++ .../Contracts/DebugInfo/DebugStreamReader.cs | 79 +++++++++ .../ExecutionManagerCore.EEJitManager.cs | 24 +++ ...ecutionManagerCore.ReadyToRunJitManager.cs | 6 + .../ExecutionManager/ExecutionManagerCore.cs | 15 ++ .../ExecutionManager/ExecutionManager_1.cs | 1 + .../ExecutionManager/ExecutionManager_2.cs | 1 + .../Data/EEJitManager.cs | 17 ++ .../Data/PatchpointInfo.cs | 21 +++ .../Data/RealCodeHeader.cs | 4 + .../CachingContractRegistry.cs | 2 + .../Legacy/ClrDataMethodInstance.cs | 77 ++++++++- 23 files changed, 513 insertions(+), 4 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEJitManager.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PatchpointInfo.cs diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 56eda8d256e469..3737d69ddf2fcd 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -11,8 +11,9 @@ { "CodeVersions": 1, "DacStreams": 1, + "DebugInfo": 1, "ECall": 1, - "EcmaMetadata" : 1, + "EcmaMetadata": 1, "Exception": 1, "ExecutionManager": 2, "Loader": 1, @@ -25,4 +26,4 @@ "StackWalk": 1, "StressLog": 2, "Thread": 1 -} +} \ No newline at end of file diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index 61e9732d5e133a..cd0b3bc9587c97 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -18,6 +18,7 @@ #include "../debug/ee/debugger.h" #include "ecall.h" +#include "patchpointinfo.h" #ifdef HAVE_GCCOVER #include "gccover.h" diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 94157d0e8f955c..3d1ab1339308e2 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -642,9 +642,15 @@ CDAC_TYPE_FIELD(RangeSection, /*pointer*/, HeapList, cdac_data::He CDAC_TYPE_FIELD(RangeSection, /*pointer*/, R2RModule, cdac_data::R2RModule) CDAC_TYPE_END(RangeSection) +CDAC_TYPE_BEGIN(EEJitManager) +CDAC_TYPE_INDETERMINATE(EEJitManager) +CDAC_TYPE_FIELD(EEJitManager, /*bool*/, StoreRichDebugInfo, cdac_data::StoreRichDebugInfo) +CDAC_TYPE_END(EEJitManager) + CDAC_TYPE_BEGIN(RealCodeHeader) CDAC_TYPE_INDETERMINATE(RealCodeHeader) CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, MethodDesc, offsetof(RealCodeHeader, phdrMDesc)) +CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, DebugInfo, offsetof(RealCodeHeader, phdrDebugInfo)) CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, GCInfo, offsetof(RealCodeHeader, phdrJitGCInfo)) #ifdef FEATURE_EH_FUNCLETS CDAC_TYPE_FIELD(RealCodeHeader, /*uint32*/, NumUnwindInfos, offsetof(RealCodeHeader, nUnwindInfos)) @@ -652,6 +658,11 @@ CDAC_TYPE_FIELD(RealCodeHeader, /* T_RUNTIME_FUNCTION */, UnwindInfos, offsetof( #endif // FEATURE_EH_FUNCLETS CDAC_TYPE_END(RealCodeHeader) +CDAC_TYPE_BEGIN(PatchpointInfo) +CDAC_TYPE_SIZE(sizeof(PatchpointInfo)) +CDAC_TYPE_FIELD(PatchpointInfo, /*uint32*/, LocalCount, cdac_data::LocalCount) +CDAC_TYPE_END(PatchpointInfo) + CDAC_TYPE_BEGIN(CodeHeapListNode) CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, Next, offsetof(HeapList, hpNext)) CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, StartAddress, offsetof(HeapList, startAddress)) @@ -956,6 +967,11 @@ CDAC_GLOBAL(FeatureCOMInterop, uint8, 1) #else CDAC_GLOBAL(FeatureCOMInterop, uint8, 0) #endif +#ifdef FEATURE_ON_STACK_REPLACEMENT +CDAC_GLOBAL(FeatureOnStackReplacement, uint8, 1) +#else +CDAC_GLOBAL(FeatureOnStackReplacement, uint8, 0) +#endif // FEATURE_ON_STACK_REPLACEMENT // See Object::GetGCSafeMethodTable #ifdef TARGET_64BIT CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2) diff --git a/src/coreclr/inc/patchpointinfo.h b/src/coreclr/inc/patchpointinfo.h index f2c01d351c67c2..f403d268d20a7c 100644 --- a/src/coreclr/inc/patchpointinfo.h +++ b/src/coreclr/inc/patchpointinfo.h @@ -7,6 +7,10 @@ #include +#ifndef JIT_BUILD +#include "cdacdata.h" +#endif // JIT_BUILD + #ifndef _PATCHPOINTINFO_H_ #define _PATCHPOINTINFO_H_ @@ -201,7 +205,19 @@ struct PatchpointInfo int32_t m_securityCookieOffset; int32_t m_monitorAcquiredOffset; int32_t m_offsetAndExposureData[]; + +#ifndef JIT_BUILD + friend struct ::cdac_data; +#endif // JIT_BUILD +}; + +#ifndef JIT_BUILD +template<> +struct cdac_data +{ + static constexpr size_t LocalCount = offsetof(PatchpointInfo, m_numberOfLocals); }; +#endif // JIT_BUILD typedef DPTR(struct PatchpointInfo) PTR_PatchpointInfo; diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index 4e4401c7589919..dd1a2b7e4843cb 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -2204,8 +2204,17 @@ private : HINSTANCE m_AltJITCompiler; bool m_AltJITRequired; #endif //ALLOW_SXS_JIT + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t StoreRichDebugInfo = offsetof(EEJitManager, m_storeRichDebugInfo); }; + //***************************************************************************** // // This class manages IJitManagers and ICorJitCompilers. It has only static diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index ebd7aec68f46b9..7caf8553797fae 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -14,7 +14,7 @@ public abstract class ContractRegistry /// /// Gets an instance of the Exception contract for the target. /// - public abstract IException Exception { get;} + public abstract IException Exception { get; } /// /// Gets an instance of the Loader contract for the target. /// @@ -71,4 +71,8 @@ public abstract class ContractRegistry /// Gets an instance of the ECall contract for the target. /// public abstract IECall ECall { get; } + /// + /// Gets an instance of the DebugInfo contract for the target. + /// + public abstract IDebugInfo DebugInfo { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs new file mode 100644 index 00000000000000..f9089bdd2e697c --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public enum SourceTypes : uint +{ + SourceTypeInvalid = 0x00, // To indicate that nothing else applies + SequencePoint = 0x01, // The debugger asked for it. + StackEmpty = 0x02, // The stack is empty here + CallSite = 0x04, // This is a call site. + NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint + CallInstruction = 0x10 // The actual instruction of a call. +} + +public interface IOffsetMapping +{ + public uint NativeOffset { get; } + public uint ILOffset { get; } + public SourceTypes SourceType { get; } +} + +public interface IDebugInfo : IContract +{ + static string IContract.Name { get; } = nameof(DebugInfo); + IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) => throw new NotImplementedException(); +} + +public readonly struct DebugInfo : IDebugInfo +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs index 408d8e8f91846f..8a9653e17e7618 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs @@ -21,6 +21,7 @@ public interface IExecutionManager : IContract TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => throw new NotImplementedException(); // **Currently GetGCInfo only supports X86** void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => throw new NotImplementedException(); TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 9ab4b9ac7b0019..b02e9400d0d1ce 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -103,6 +103,8 @@ public enum DataType EETypeHashTable, InstMethodHashTable, ECHash, + EEJitManager, + PatchpointInfo, TransitionBlock, DebuggerEval, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 20b90d29a8f19d..b90e3fb24ab480 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -15,6 +15,7 @@ public static class Globals public const string GCThread = nameof(GCThread); public const string FeatureCOMInterop = nameof(FeatureCOMInterop); + public const string FeatureOnStackReplacement = nameof(FeatureOnStackReplacement); public const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask); public const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.cs new file mode 100644 index 00000000000000..40072342b05892 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.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 DebugInfoFactory : IContractFactory +{ + IDebugInfo IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new DebugInfo_1(target), + _ => default(DebugInfo), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs new file mode 100644 index 00000000000000..12e1ed3b431672 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -0,0 +1,162 @@ +// 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 System.Diagnostics; +using System.Linq; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal sealed class DebugInfo_1(Target target) : IDebugInfo +{ + private const uint IL_OFFSET_BIAS = unchecked((uint)-3); + + internal enum SourceTypes_1 : uint + { + SourceTypeInvalid = 0x00, // To indicate that nothing else applies + SequencePoint = 0x01, // The debugger asked for it. + StackEmpty = 0x02, // The stack is empty here + CallSite = 0x04, // This is a call site. + NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint + CallInstruction = 0x10 // The actual instruction of a call. + } + + internal readonly struct OffsetMapping_1 : IOffsetMapping + { + public uint NativeOffset { get; init; } + public uint ILOffset { get; init; } + internal SourceTypes_1 InternalSourceType { get; init; } + public readonly SourceTypes SourceType + { + get + { + switch (InternalSourceType) + { + case SourceTypes_1.SourceTypeInvalid: + return SourceTypes.SourceTypeInvalid; + case SourceTypes_1.SequencePoint: + return SourceTypes.SequencePoint; + case SourceTypes_1.StackEmpty: + return SourceTypes.StackEmpty; + case SourceTypes_1.CallSite: + return SourceTypes.CallSite; + case SourceTypes_1.NativeEndOffsetUnknown: + return SourceTypes.NativeEndOffsetUnknown; + case SourceTypes_1.CallInstruction: + return SourceTypes.CallInstruction; + default: + Debug.Fail($"Unknown source type: {InternalSourceType}"); + return SourceTypes.SourceTypeInvalid; + } + } + } + } + + [Flags] + internal enum ExtraDebugInfoFlags_1 : byte + { + // Debug info contains patchpoint information + EXTRA_DEBUG_INFO_PATCHPOINT = 0x01, + // Debug info contains rich information + EXTRA_DEBUG_INFO_RICH = 0x02, + } + + private readonly Target _target = target; + private readonly ICodeVersions _cv = target.Contracts.CodeVersions; + private readonly IExecutionManager _eman = target.Contracts.ExecutionManager; + + IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) + { + NativeCodeVersionHandle requestedNativeCodeVersion = _cv.GetNativeCodeVersionForIP(pCode); + if (!requestedNativeCodeVersion.Valid || _cv.GetNativeCode(requestedNativeCodeVersion) == TargetCodePointer.Null) + { + // E_INVALIDARG + throw new InvalidOperationException( + $"The provided pCode {pCode} does not correspond to a valid native code version."); + } + TargetCodePointer nativeCodeStart = _cv.GetNativeCode(requestedNativeCodeVersion); + + // Get the method's DebugInfo + if (_eman.GetCodeBlockHandle(nativeCodeStart) is not CodeBlockHandle cbh) + throw new InvalidOperationException($"No CodeBlockHandle found for native code start {nativeCodeStart}."); + TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool hasFlagByte); + + codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target)); + + return RestoreBoundaries(debugInfo, hasFlagByte); + } + + private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte) + { + if (hasFlagByte) + { + // Check flag byte and skip over any patchpoint info + ExtraDebugInfoFlags_1 flagByte = (ExtraDebugInfoFlags_1)_target.Read(debugInfo++); + + if (flagByte.HasFlag(ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_PATCHPOINT)) + { + Data.PatchpointInfo patchpointInfo = _target.ProcessedData.GetOrAdd(debugInfo); + + if (_target.GetTypeInfo(DataType.PatchpointInfo).Size is not uint patchpointSize) + throw new InvalidOperationException("PatchpointInfo type size is not defined."); + debugInfo += patchpointSize + (patchpointInfo.LocalCount * sizeof(uint)); + + flagByte &= ~ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_PATCHPOINT; + } + + if (flagByte.HasFlag(ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_RICH)) + { + uint richDebugInfoSize = _target.Read(debugInfo); + debugInfo += 4; + debugInfo += richDebugInfoSize; + flagByte &= ~ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_RICH; + } + + Debug.Assert(flagByte == 0); + } + + DebugStreamReader reader = new(_target, debugInfo, 12 /*maximum size of 2 32bit ints compressed*/); + + uint cbBounds = reader.ReadEncodedU32(); + uint _ /*cbVars*/ = reader.ReadEncodedU32(); + + TargetPointer addrBounds = debugInfo + reader.NextByteIndex; + + if (cbBounds > 0) + { + DebugStreamReader boundsReader = new(_target, addrBounds, cbBounds); + + uint countEntries = boundsReader.ReadEncodedU32(); + Debug.Assert(countEntries > 0, "Expected at least one entry in bounds."); + + return DoBounds(boundsReader, countEntries); + } + + return Enumerable.Empty(); + } + + private static IEnumerable DoBounds(DebugStreamReader reader, uint count) + { + uint nativeOffset = 0; + for (uint i = 0; i < count; i++) + { + // native offsets are encoded as a delta from the previous offset + nativeOffset += reader.ReadEncodedU32(); + + // il offsets are encoded with a bias of ICorDebugInfo::MAX_MAPPING_VALUE + uint ilOffset = unchecked(reader.ReadEncodedU32() + IL_OFFSET_BIAS); + + SourceTypes_1 sourceType = (SourceTypes_1)reader.ReadEncodedU32(); + + // TODO(cdac debugInfo): Handle cookie + + yield return new OffsetMapping_1 + { + NativeOffset = nativeOffset, + ILOffset = ilOffset, + InternalSourceType = sourceType + }; + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs new file mode 100644 index 00000000000000..96eea4361ef1a0 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs @@ -0,0 +1,79 @@ +// 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; + +internal sealed class DebugStreamReader +{ + private readonly uint _size; + + private byte[] _buffer; + private int _currentNibble; + + public uint NextByteIndex => (uint)((_currentNibble + 1) / 2); + + public DebugStreamReader(Target target, TargetPointer address, uint size) + { + _size = size; + + _buffer = new byte[size]; + target.ReadBuffer(address, _buffer); + } + + public uint ReadEncodedU32() + { + uint result = 0; + uint nibsRead = 0; + Nibble nib; + do + { + if (nibsRead > 11) + { + throw new InvalidOperationException("Corrupt nibble stream, too many nibbles read for encoded uint32."); + } + + nib = ReadNibble(); + result = (result << 3) + nib.Data; + + nibsRead++; + } while (!nib.IsEndOfWord); + return result; + } + + public int ReadEncodedInt32() + { + uint result = ReadEncodedU32(); + int x = (int)(result >> 1); + return (result & 1) != 0 ? -x : x; + } + + private Nibble ReadNibble() + { + if (_currentNibble / 2 >= _size) throw new InvalidOperationException("No more nibbles to read."); + + byte b = _buffer[_currentNibble / 2]; + Nibble nib; + if (_currentNibble % 2 == 0) + { + // Read the low nibble first + nib = new Nibble((byte)(b & 0xF)); + } + else + { + // Read the high nibble after the low nibble has been read + nib = new Nibble((byte)((b >> 4) & 0xF)); + } + _currentNibble++; + return nib; + } + + private readonly struct Nibble(byte data) + { + private readonly byte _data = data; + + public readonly byte Data => (byte)(_data & 0x7); + public readonly bool IsEndOfWord => (_data & 0x8) == 0; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs index 6a23be3be862d4..c62499cad64ef7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs @@ -75,6 +75,30 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod return _runtimeFunctions.GetRuntimeFunctionAddress(realCodeHeader.UnwindInfos, index); } + public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte) + { + hasFlagByte = false; + if (rangeSection.IsRangeList) + return TargetPointer.Null; + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress); + if (codeStart == TargetPointer.Null) + return TargetPointer.Null; + Debug.Assert(codeStart.Value <= jittedCodeAddress.Value); + + if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) + return TargetPointer.Null; + + bool featureOnStackReplacement = Target.ReadGlobal(Constants.Globals.FeatureOnStackReplacement) != 0; + Data.EEJitManager eeJitManager = Target.ProcessedData.GetOrAdd(rangeSection.Data.JitManager); + if (featureOnStackReplacement || eeJitManager.StoreRichDebugInfo) + hasFlagByte = true; + + return realCodeHeader.DebugInfo; + } + public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) { gcInfo = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index bdbe99980c4107..5900999db5ff29 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -70,6 +70,12 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod return _runtimeFunctions.GetRuntimeFunctionAddress(r2rInfo.RuntimeFunctions, index); } + public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte) + { + hasFlagByte = false; + throw new NotImplementedException(); + } + public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) { gcInfo = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index 6d9b993dcfff6c..7161eae75e3840 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -66,6 +66,7 @@ protected JitManager(Target target) public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info); public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress); + public abstract TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte); public abstract void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion); } @@ -230,6 +231,20 @@ TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInf return range.Data.RangeBegin; } + TargetPointer IExecutionManager.GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) + { + hasFlagByte = false; + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + if (range.Data == null) + return TargetPointer.Null; + + JitManager jitManager = GetJitManager(range.Data); + return jitManager.GetDebugInfo(range, codeInfoHandle.Address.Value, out hasFlagByte); + } + void IExecutionManager.GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) { gcInfo = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index 2feda4180c2feb..551c664237096c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -20,6 +20,7 @@ internal ExecutionManager_1(Target target, Data.RangeSectionMap topRangeSectionM public TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle); public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfo(codeInfoHandle); public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); + public TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => _executionManagerCore.GetDebugInfo(codeInfoHandle, out hasFlagByte); public void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => _executionManagerCore.GetGCInfo(codeInfoHandle, out gcInfo, out gcVersion); public TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetRelativeOffset(codeInfoHandle); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index 356081e289b492..ab1fda5e329640 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -20,6 +20,7 @@ internal ExecutionManager_2(Target target, Data.RangeSectionMap topRangeSectionM public TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle); public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfo(codeInfoHandle); public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); + public TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => _executionManagerCore.GetDebugInfo(codeInfoHandle, out hasFlagByte); public void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => _executionManagerCore.GetGCInfo(codeInfoHandle, out gcInfo, out gcVersion); public TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetRelativeOffset(codeInfoHandle); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEJitManager.cs new file mode 100644 index 00000000000000..58eae2a2cf67d1 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEJitManager.cs @@ -0,0 +1,17 @@ +// 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.Data; + +internal sealed class EEJitManager : IData +{ + static EEJitManager IData.Create(Target target, TargetPointer address) => new EEJitManager(target, address); + public EEJitManager(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EEJitManager); + + StoreRichDebugInfo = target.Read(address + (ulong)type.Fields[nameof(StoreRichDebugInfo)].Offset) != 0; + } + + public bool StoreRichDebugInfo { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PatchpointInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PatchpointInfo.cs new file mode 100644 index 00000000000000..482174b7465406 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PatchpointInfo.cs @@ -0,0 +1,21 @@ +// 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.Data; + +internal sealed class PatchpointInfo : IData +{ + static PatchpointInfo IData.Create(Target target, TargetPointer address) + => new PatchpointInfo(target, address); + + public PatchpointInfo(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.PatchpointInfo); + + LocalCount = target.Read(address + (ulong)type.Fields[nameof(LocalCount)].Offset); + } + + public uint LocalCount { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs index f40998be18b114..97af139996a5ea 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Diagnostics.DataContractReader.Contracts; + namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class RealCodeHeader : IData @@ -12,12 +14,14 @@ public RealCodeHeader(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.RealCodeHeader); MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + DebugInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(DebugInfo)].Offset); GCInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCInfo)].Offset); NumUnwindInfos = target.Read(address + (ulong)type.Fields[nameof(NumUnwindInfos)].Offset); UnwindInfos = address + (ulong)type.Fields[nameof(UnwindInfos)].Offset; } public TargetPointer MethodDesc { get; init; } + public TargetPointer DebugInfo { get; init; } public TargetPointer GCInfo { get; init; } public uint NumUnwindInfos { get; init; } public TargetPointer UnwindInfos { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index a3da176f1548c9..e31964cfaf9f22 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -41,6 +41,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IStackWalk)] = new StackWalkFactory(), [typeof(IRuntimeInfo)] = new RuntimeInfoFactory(), [typeof(IECall)] = new ECallFactory(), + [typeof(IDebugInfo)] = new DebugInfoFactory(), }; configureFactories?.Invoke(_factories); } @@ -60,6 +61,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IStackWalk StackWalk => GetContract(); public override IRuntimeInfo RuntimeInfo => GetContract(); public override IECall ECall => GetContract(); + public override IDebugInfo DebugInfo => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index 27cabfe18834b9..b574727405edf0 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -2,7 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -125,7 +128,79 @@ int IXCLRDataMethodInstance.GetTypeArgumentByIndex(uint index, void** typeArg) => _legacyImpl is not null ? _legacyImpl.GetTypeArgumentByIndex(index, typeArg) : HResults.E_NOTIMPL; int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint offsetsLen, uint* offsetsNeeded, uint* ilOffsets) - => _legacyImpl is not null ? _legacyImpl.GetILOffsetsByAddress(address, offsetsLen, offsetsNeeded, ilOffsets) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + try + { + TargetCodePointer pCode = address.ToTargetCodePointer(_target); + List map = _target.Contracts.DebugInfo.GetMethodNativeMap(pCode, out uint codeOffset).ToList(); + + uint hits = 0; + for (int i = 0; i < map.Count; i++) + { + bool isEpilog = map[i].ILOffset == unchecked((uint)-3); // -3 is used to indicate an epilog + bool lastValue = i == map.Count - 1; + uint nativeEndOffset = lastValue ? 0 : map[i + 1].NativeOffset; + if (codeOffset >= map[i].NativeOffset && (((isEpilog || lastValue) && nativeEndOffset == 0) || codeOffset < nativeEndOffset)) + { + if (hits < offsetsLen && ilOffsets is not null) + { + ilOffsets[hits] = map[i].ILOffset; + } + + hits++; + } + } + + if (offsetsNeeded is not null) + { + *offsetsNeeded = hits; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal; + + bool validateOffsetsNeeded = offsetsNeeded is not null; + uint localOffsetsNeeded = 0; + + bool validateIlOffsets = ilOffsets is not null; + uint[] localIlOffsets = new uint[offsetsLen]; + + fixed (uint* localIlOffsetsPtr = localIlOffsets) + { + hrLocal = _legacyImpl.GetILOffsetsByAddress(address, offsetsLen, validateOffsetsNeeded ? &localOffsetsNeeded : null, validateIlOffsets ? localIlOffsetsPtr : null); + } + + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + + if (hr == HResults.S_OK) + { + if (validateOffsetsNeeded) + { + Debug.Assert(localOffsetsNeeded == *offsetsNeeded, $"cDAC: {*offsetsNeeded:x}, DAC: {localOffsetsNeeded:x}"); + } + + if (validateIlOffsets) + { + for (int i = 0; i < localIlOffsets.Length; i++) + { + Debug.Assert(localIlOffsets[i] == ilOffsets[i], $"cDAC: {localIlOffsets[i]:x}, DAC: {ilOffsets[i]:x}"); + } + } + } + } +#endif + + return hr; + } int IXCLRDataMethodInstance.GetAddressRangesByILOffset(uint ilOffset, uint rangesLen, uint* rangesNeeded, void* addressRanges) => _legacyImpl is not null ? _legacyImpl.GetAddressRangesByILOffset(ilOffset, rangesLen, rangesNeeded, addressRanges) : HResults.E_NOTIMPL; From e4ce021cb799556a86d53fc387f797ae70eaa40c Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 7 Jul 2025 17:12:43 -0400 Subject: [PATCH 30/54] wip --- .../debug/runtimeinfo/datadescriptor.h | 1 + src/coreclr/vm/readytoruninfo.cpp | 1 + src/coreclr/vm/readytoruninfo.h | 2 + ...ecutionManagerCore.ReadyToRunJitManager.cs | 25 +++++- .../ExecutionManager/Helpers/NativeArray.cs | 77 +++++++++++++++++++ .../ExecutionManager/Helpers/NativeReader.cs | 63 +++++++++++++++ .../Data/ImageDataDirectory.cs | 2 +- .../Data/ReadyToRunInfo.cs | 2 + 8 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 3d1ab1339308e2..1f9e2c31138c6e 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -576,6 +576,7 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data::NumHotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, HotColdMap, cdac_data::HotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_data::DelayLoadMethodCallThunks) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DebugInfo, cdac_data::DebugInfo) CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) CDAC_TYPE_END(ReadyToRunInfo) diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 2fa9632fea82b8..147294791b161f 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -884,6 +884,7 @@ ReadyToRunInfo::ReadyToRunInfo(Module * pModule, LoaderAllocator* pLoaderAllocat } m_pSectionDelayLoadMethodCallThunks = m_pComposite->FindSection(ReadyToRunSectionType::DelayLoadMethodCallThunks); + m_pSectionDebugInfo = m_pComposite->FindSection(ReadyToRunSectionType::DebugInfo); IMAGE_DATA_DIRECTORY * pinstMethodsDir = m_pComposite->FindSection(ReadyToRunSectionType::InstanceMethodEntryPoints); if (pinstMethodsDir != NULL) diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index a67a539f743a17..2b5e1791e15a2c 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -119,6 +119,7 @@ class ReadyToRunInfo DWORD m_nHotColdMap; PTR_IMAGE_DATA_DIRECTORY m_pSectionDelayLoadMethodCallThunks; + PTR_IMAGE_DATA_DIRECTORY m_pSectionDebugInfo; PTR_READYTORUN_IMPORT_SECTION m_pImportSections; DWORD m_nImportSections; @@ -351,6 +352,7 @@ struct cdac_data static constexpr size_t NumHotColdMap = offsetof(ReadyToRunInfo, m_nHotColdMap); static constexpr size_t HotColdMap = offsetof(ReadyToRunInfo, m_pHotColdMap); static constexpr size_t DelayLoadMethodCallThunks = offsetof(ReadyToRunInfo, m_pSectionDelayLoadMethodCallThunks); + static constexpr size_t DebugInfo = offsetof(ReadyToRunInfo, m_pSectionDebugInfo); static constexpr size_t EntryPointToMethodDescMap = offsetof(ReadyToRunInfo, m_entryPointToMethodDescMap); }; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index 5900999db5ff29..9ef36040029694 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -72,8 +72,31 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte) { + // ReadyToRun does not contain PatchpointInfo hasFlagByte = false; - throw new NotImplementedException(); + + // ReadyToRunJitManager::GetDebugInfo + Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) + return TargetPointer.Null; + + index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index); + index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _); + + Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd(r2rInfo.DebugInfo); + + NativeReader reader = new NativeReader(Target, imageBase); + NativeArray debugInfoArray = new NativeArray(reader, debugInfoData.VirtualAddress); + + if (!debugInfoArray.TryGetAt(index, out uint offset)) + // If the index is not found in the debug info array, return null + return TargetPointer.Null; + + uint debugInfoOffset = reader.DecodeUnsigned(offset, out uint lookBack); + if (lookBack != 0) + debugInfoOffset = offset - lookBack; + + return imageBase + debugInfoOffset; } public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs new file mode 100644 index 00000000000000..c1b748ec80562d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs @@ -0,0 +1,77 @@ +// 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.ExecutionManagerHelpers; + +internal sealed class NativeArray +{ + private const uint BLOCK_SIZE = 16; + + private readonly NativeReader _reader; + private readonly uint _count; + private readonly uint _entryIndexSize; + private readonly uint _baseOffset; + + public NativeArray(NativeReader reader, uint offset) + { + _reader = reader; + + _baseOffset = _reader.DecodeUnsigned(offset, out uint val); + _count = val >> 2; + _entryIndexSize = val & 3; + } + + public bool TryGetAt(uint index, out uint value) + { + value = 0; + if (index >= _count) + return false; + + uint offset = _entryIndexSize switch + { + 0 => _reader.ReadUInt8(_baseOffset + (index / BLOCK_SIZE)), + 1 => _reader.ReadUInt16(_baseOffset + 2 * (index / BLOCK_SIZE)), + _ => _reader.ReadUInt32(_baseOffset + 4 * (index / BLOCK_SIZE)), + }; + offset += _baseOffset; + + for (uint bit = BLOCK_SIZE >> 1; bit > 0; bit >>= 1) + { + uint offset2 = _reader.DecodeUnsigned(offset, out uint val); + + if ((index & bit) != 0) + { + if ((val & 2) != 0) + { + offset += val >> 2; + continue; + } + } + else + { + if ((val & 1) != 0) + { + offset = offset2; + continue; + } + } + + // Not found + if ((val & 3) == 0) + { + // Matching special leaf node? + if ((val >> 2) == (index & (BLOCK_SIZE - 1))) + { + offset = offset2; + break; + } + } + return false; + } + + value = offset; + return true; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs new file mode 100644 index 00000000000000..9d598f3f5062a3 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs @@ -0,0 +1,63 @@ +// 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.ExecutionManagerHelpers; + +internal sealed class NativeReader(Target target, TargetPointer baseAddress) +{ + private readonly Target _target = target; + private readonly TargetPointer _baseAddress = baseAddress; + + public byte ReadUInt8(uint offset) => _target.Read(_baseAddress + offset); + public ushort ReadUInt16(uint offset) => _target.Read(_baseAddress + offset); + public uint ReadUInt32(uint offset) => _target.Read(_baseAddress + offset); + + public uint DecodeUnsigned(uint offset, out uint value) + { + value = 0; + uint val = ReadUInt8(offset); + if ((val & 1) == 0) + { + value = val >> 1; + offset += 1; + } + else + if ((val & 2) == 0) + { + value = val >> 2; + value |= ((uint)ReadUInt8(offset + 1)) << 6; + offset += 2; + } + else + if ((val & 4) == 0) + { + value = val >> 3; + value |= ((uint)ReadUInt8(offset + 1)) << 5; + value |= ((uint)ReadUInt8(offset + 2)) << 13; + offset += 3; + } + else + if ((val & 8) == 0) + { + value = val >> 4; + value |= ((uint)ReadUInt8(offset + 1)) << 4; + value |= ((uint)ReadUInt8(offset + 2)) << 12; + value |= ((uint)ReadUInt8(offset + 3)) << 20; + offset += 4; + } + else + if ((val & 16) == 0) + { + value = ReadUInt32(offset + 1); + offset += 5; + } + else + { + throw new InvalidOperationException("Invalid encoding in DecodeUnsigned"); + } + + return offset; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs index d3717f5eea481b..241cf6daabd540 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs @@ -14,7 +14,7 @@ public ImageDataDirectory(Target target, TargetPointer address) VirtualAddress = target.Read(address + (ulong)type.Fields[nameof(VirtualAddress)].Offset); Size = target.Read(address + (ulong)type.Fields[nameof(Size)].Offset); - } + } public uint VirtualAddress { get; } public uint Size { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs index ed932946d195de..95bb81bd9d76fe 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -30,6 +30,7 @@ public ReadyToRunInfo(Target target, TargetPointer address) : TargetPointer.Null; DelayLoadMethodCallThunks = target.ReadPointer(address + (ulong)type.Fields[nameof(DelayLoadMethodCallThunks)].Offset); + DebugInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(DebugInfo)].Offset); // Map is from the composite info pointer (set to itself for non-multi-assembly composite images) EntryPointToMethodDescMap = CompositeInfo + (ulong)type.Fields[nameof(EntryPointToMethodDescMap)].Offset; @@ -46,5 +47,6 @@ public ReadyToRunInfo(Target target, TargetPointer address) public TargetPointer HotColdMap { get; } public TargetPointer DelayLoadMethodCallThunks { get; } + public TargetPointer DebugInfo { get; } public TargetPointer EntryPointToMethodDescMap { get; } } From 4da26b65abea9d1d5ddc274fe26f49dfb5fd342c Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 8 Jul 2025 12:38:36 -0400 Subject: [PATCH 31/54] add docs for DebugInfo --- docs/design/datacontracts/DebugInfo.md | 167 ++++++++++++++++++ .../Contracts/DebugInfo/DebugInfo_1.cs | 15 +- 2 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 docs/design/datacontracts/DebugInfo.md diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md new file mode 100644 index 00000000000000..6ad3aeee290868 --- /dev/null +++ b/docs/design/datacontracts/DebugInfo.md @@ -0,0 +1,167 @@ +# Contract DebugInfo + +This contract is for fetching information related to DebugInfo associated with native code. + +## APIs of contract + +```csharp +public enum SourceTypes : uint +{ + SourceTypeInvalid = 0x00, // To indicate that nothing else applies + SequencePoint = 0x01, // The debugger asked for it. + StackEmpty = 0x02, // The stack is empty here + CallSite = 0x04, // This is a call site. + NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint + CallInstruction = 0x10 // The actual instruction of a call. +} +``` + +```csharp +public interface IOffsetMapping +{ + public uint NativeOffset { get; } + public uint ILOffset { get; } + public SourceTypes SourceType { get; } +} +``` + +```csharp +// Given a code pointer, return the associated native/IL offset mapping +IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `PatchpointInfo` | `LocalCount` | Number of locals in the method associated with the patchpoint. | + +Contracts used: +| Contract Name | +| --- | +| `CodeVersions` | +| `ExecutionManager` | + +Constants: +| Constant Name | Meaning | Value | +| --- | --- | --- | +| IL_OFFSET_BIAS | IL offsets are encoded in the DebugInfo with this bias. | `0xfffffffd` (-3) | +| EXTRA_DEBUG_INFO_PATCHPOINT | Indicates debug info contains patchpoint information | 0x1 | +| EXTRA_DEBUG_INFO_RICH | Indicates debug info contains rich information | 0x2 | +| SourceTypeInvalid | To indicate that nothing else applies | 0x00 | +| SourceTypeSequencePoint | Source type indicating the debugger asked for it | 0x01 | +| SourceTypeStackEmpty | Source type indicating the stack is empty here | 0x02 | +| SourceTypeCallSite | Source type indicating This is a call site | 0x04 | +| SourceTypeNativeEndOffsetUnknown | Source type indicating a epilog endpoint | 0x08 | +| SourceTypeCallInstruction | Source type indicating the actual instruction of a call | 0x10 | + +### DebugInfo Stream Encoding + +The DebugInfo stream is encoded using varible length 32-bit values with the following scheme: + +A value can be stored using one or more nibbles (a nibble is a 4-bit value). 3 bits of a nibble are used to store 3 bits of the value, and the top bit indicates if the following nibble contains rest of the value. If the top bit is not set, then this nibble is the last part of the value. The higher bits of the value are written out first, and the lowest 3 bits are written out last. + +In the encoded stream of bytes, the lower nibble of a byte is used before the high nibble. + +A binary value ABCDEFGHI (where A is the highest bit) is encoded as +the follow two bytes : 1DEF1ABC XXXX0GHI + +Examples: +| Decimal Value | Hex Value | Encoded Result | +| --- | --- | --- | +| 0 | 0x0 | X0 | +| 1 | 0x1 | X1 | +| 7 | 0x7 | X7 | +| 8 | 0x8 | 09 | +| 9 | 0x9 | 19 | +| 63 | 0x3F | 7F | +| 64 | 0x40 | F9 X0 | +| 65 | 0x41 | F9 X1 | +| 511 | 0x1FF | FF X7 | +| 512 | 0x200 | 89 08 | +| 513 | 0x201 | 89 18 | + +Based on the encoding specification, we use an decoder with the following interface: + +```csharp +class DebugStreamReader(TargetPointer startOfBuffer, uint sizeOfBuffer) +{ + // Returns the offset of the next byte relative to the startOfBuffer + public uint GetNextByteIndex(); + + // Reads the next encoded value and moves to the following nibble + public uint ReadEncodedU32(); +} +``` + +### Implementation + +``` csharp +IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) +{ + // Get the method's DebugInfo + if (/*ExecutionManager*/.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) + throw NotValid // pCode must point to a valid code block + TargetPointer debugInfo = /*ExecutionManager*/.GetDebugInfo(cbh, out bool hasFlagByte); + + TargetCodePointer nativeCodeStart = /*ExecutionManager*/.GetStartAddress(cbh); + + TargetPointer startAddress = /*convert nativeCodeStart to a TargetPointer*/ + TargetPointer currAddress = /*convert pCode to a TargetPointer*/ + codeOffset = currAddress - startAddress; + + if (hasFlagByte) + { + // Check flag byte and skip over any patchpoint info + byte flagByte = _target.Read(debugInfo++); + + if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) != 0) + { + uint localCount = _target.Read(debugInfo + /*PatchpointInfo::LocalCount offset*/) + debugInfo += /*size of PatchpointInfo*/ + (localCount * 4); + } + + if ((flagByte & EXTRA_DEBUG_INFO_RICH) != 0) + { + uint richDebugInfoSize = _target.Read(debugInfo); + debugInfo += 4; + debugInfo += richDebugInfoSize; + } + } + + DebugStreamReader reader = new(debugInfo, 12 /*maximum size of 2 32bit ints compressed*/); + + uint cbBounds = reader.ReadEncodedU32(); + uint _ /*cbVars*/ = reader.ReadEncodedU32(); + + TargetPointer addrBounds = debugInfo + reader.NextByteIndex; + + if (cbBounds == 0) + // No bounds data was found, return an empty enumerable + return Enumerable.Empty(); + + // Create a DebugInfo stream decoder with the start address and size of bounds data + DebugStreamReader boundsReader = new(addrBounds, cbBounds); + + uint countEntries = boundsReader.ReadEncodedU32(); + uint nativeOffset = 0; + for (uint i = 0; i < count; i++) + { + // native offsets are encoded as a delta from the previous offset + nativeOffset += boundsReader.ReadEncodedU32(); + + // il offsets are encoded with a bias of IL_OFFSET_BIAS + uint ilOffset = unchecked(boundsReader.ReadEncodedU32() + IL_OFFSET_BIAS); + + uint sourceType = boundsReader.ReadEncodedU32(); + + yield return new OffsetMapping_1 + { + NativeOffset = nativeOffset, + ILOffset = ilOffset, + InternalSourceType = sourceType + }; + } +} +``` \ No newline at end of file diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs index 12e1ed3b431672..23b71e3e4eba0b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -63,25 +63,16 @@ internal enum ExtraDebugInfoFlags_1 : byte } private readonly Target _target = target; - private readonly ICodeVersions _cv = target.Contracts.CodeVersions; private readonly IExecutionManager _eman = target.Contracts.ExecutionManager; IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) { - NativeCodeVersionHandle requestedNativeCodeVersion = _cv.GetNativeCodeVersionForIP(pCode); - if (!requestedNativeCodeVersion.Valid || _cv.GetNativeCode(requestedNativeCodeVersion) == TargetCodePointer.Null) - { - // E_INVALIDARG - throw new InvalidOperationException( - $"The provided pCode {pCode} does not correspond to a valid native code version."); - } - TargetCodePointer nativeCodeStart = _cv.GetNativeCode(requestedNativeCodeVersion); - // Get the method's DebugInfo - if (_eman.GetCodeBlockHandle(nativeCodeStart) is not CodeBlockHandle cbh) - throw new InvalidOperationException($"No CodeBlockHandle found for native code start {nativeCodeStart}."); + if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) + throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}."); TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool hasFlagByte); + TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh); codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target)); return RestoreBoundaries(debugInfo, hasFlagByte); From 12137f92d7d10241b1e24570fa50263e74aaec47 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 8 Jul 2025 18:52:05 -0400 Subject: [PATCH 32/54] some GetDebugInfo docs --- docs/design/datacontracts/ExecutionManager.md | 22 ++++++++++++++++++- .../Contracts/DebugInfo/DebugStreamReader.cs | 7 ------ .../Contracts/Loader_1.cs | 1 + 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 1b5e360f6ec308..0b7209cc3599cd 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -29,6 +29,9 @@ struct CodeBlockHandle TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle); // Gets the base address the UnwindInfo of codeInfoHandle is relative to TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle); + // Gets the DebugInfo associated with the code block and specifies if the DebugInfo contains + // the flag byte which modifies how DebugInfo is parsed. + TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte); // Gets the GCInfo associated with the code block and its version // **Currently GetGCInfo only supports X86** void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion); @@ -66,9 +69,11 @@ Data descriptors used: | `CodeHeapListNode` | `EndAddress` | End address of the used portion of the code heap | | `CodeHeapListNode` | `MapBase` | Start of the map - start address rounded down based on OS page size | | `CodeHeapListNode` | `HeaderMap` | Bit array used to find the start of methods - relative to `MapBase` | +| `EEJitManager` | `StoreRichDebugInfo` | Boolean value determining if debug info associated with the JitManager contains rich info. | | `RealCodeHeader` | `MethodDesc` | Pointer to the corresponding `MethodDesc` | | `RealCodeHeader` | `NumUnwindInfos` | Number of Unwind Infos | | `RealCodeHeader` | `UnwindInfos` | Start address of Unwind Infos | +| `RealCodeHeader` | `DebugInfo` | Pointer to the DebugInfo | | `RealCodeHeader` | `GCInfo` | Pointer to the GCInfo encoding | | `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module | | `ReadyToRunInfo` | `ReadyToRunHeader` | Pointer to the ReadyToRunHeader | @@ -78,6 +83,7 @@ Data descriptors used: | `ReadyToRunInfo` | `NumHotColdMap` | Number of entries in the `HotColdMap` | | `ReadyToRunInfo` | `HotColdMap` | Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) | | `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | Pointer to an `ImageDataDirectory` for the delay load method call thunks | +| `ReadyToRunInf` | `DebugInfo` | Pointer to an `ImageDataDirectory` for the debug info | | `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` of entry point addresses to `MethodDesc` pointers | | `ReadyToRunHeader` | `MajorVersion` | ReadyToRun major version | | `ReadyToRunHeader` | `MinorVersion` | ReadyToRun minor version | @@ -100,6 +106,7 @@ Global variables used: | `HashMapValueMask` | uint64 | Bitmask used when storing values in a `HashMap` | | `FeatureEHFunclets` | uint8 | 1 if EH funclets are enabled, 0 otherwise | | `GCInfoVersion` | uint32 | JITted code GCInfo version | +| `FeatureOnStackReplacement` | uint8 | 1 if FEATURE_ON_STACK_REPLACEMENT is enabled, 0 otherwise | Contracts used: | Contract Name | @@ -266,9 +273,16 @@ The `GetMethodDesc`, `GetStartAddress`, and `GetRelativeOffset` APIs extract fie Unwind info (`RUNTIME_FUNCTION`) use relative addressing. For managed code, these values are relative to the start of the code's containing range in the RangeSectionMap (described below). This could be the beginning of a `CodeHeap` for jitted code or the base address of the loaded image for ReadyToRun code. `GetUnwindInfoBaseAddress` finds this base address for a given `CodeBlockHandle`. +`IExecutionManager.GetDebugInfo` gets a pointer to the relevant DebugInfo for a `CodeBlockHandle`. The ExecutionManager delegates to the JitManager implementations as the DebugInfo is stored in different ways on jitted and R2R code. + +* For Jitted code (`EEJitManager`) a pointer to the `DebugInfo` is stored on the `RealCodeHeader` which is accessed in the same way as `GetMethodInfo` described above. `hasFlagByte` is `true` if either the global `FeatureOnStackReplacement` is `true` or `StoreRichDebugInfo` is `true` on the `EEJitManager`. + +* For R2R code (`ReadyToRunJitManager`) the `DebugInfo` is stored as part of the R2R image. The relevant `ReadyToRunInfo` stores a pointer to the an `ImageDataDirectory` representing the `DebugInfo` directory. Read the `VirtualAddress` of this data directory as a `NativeArray` containing the `DebugInfos`. To find the specific `DebugInfo`, index into the array using the `index` of the beginning of the R2R function as found like in `GetMethodInfo` above. This yields an offset `offset` value relative to the image base. Read the first variable length uint at `imageBase + offset`, `lookBack`. If `lookBack != 0`, return `imageBase + offset - lookback`. Otherwise return `offset + size of reading lookback`. +For R2R images, `hasFlagByte` is always `false`. + `IExecutionManager.GetGCInfo` gets a pointer to the relevant GCInfo for a `CodeBlockHandle`. The ExecutionManager delegates to the JitManager implementations as the GCInfo is stored differently on jitted and R2R code. -* For jitted code (`EEJitManager`) a pointer to the `GCInfo` is stored on the `RealCodeHeader` which is accessed in the same was as `GetMethodInfo` described above. This can simply be returned as is. The `GCInfoVersion` is defined by the runtime global `GCInfoVersion`. +* For jitted code (`EEJitManager`) a pointer to the `GCInfo` is stored on the `RealCodeHeader` which is accessed in the same way as `GetMethodInfo` described above. This can simply be returned as is. The `GCInfoVersion` is defined by the runtime global `GCInfoVersion`. * For R2R code (`ReadyToRunJitManager`), the `GCInfo` is stored directly after the `UnwindData`. This in turn is found by looking up the `UnwindInfo` (`RUNTIME_FUNCTION`) and reading the `UnwindData` offset. We find the `UnwindInfo` as described above in `IExecutionManager.GetUnwindInfo`. Once we have the relevant unwind data, we calculate the size of the unwind data and return a pointer to the following byte (first byte of the GCInfo). The size of the unwind data is a platform specific. Currently only X86 is supported with a constant unwind data size of 32-bits. * The `GCInfoVersion` of R2R code is mapped from the R2R MajorVersion and MinorVersion which is read from the ReadyToRunHeader which itself is read from the ReadyToRunInfo (can be found as in GetMethodInfo). The current GCInfoVersion mapping is: @@ -301,6 +315,12 @@ On 64-bit targets, we take advantage of the fact that most architectures don't s That is, level 5 has 256 entires pointing to level 4 maps (or nothing if there's no code allocated in that address range), level 4 entires point to level 3 maps and so on. Each level 1 map has 256 entries covering a 128 KiB chunk and pointing to a linked list of range section fragments that fall within that 128 KiB chunk. +### Native Format + +The ReadyToRun image stores data in a compressed native foramt defined in [nativeformatreader.h](../../../src/coreclr/vm/nativeformatreader.h). + +The ReadyToRunJitManager interacts with this format through t + ### NibbleMap The ExecutionManager contract depends on a "nibble map" data structure diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs index 96eea4361ef1a0..83f85a51399f7c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs @@ -42,13 +42,6 @@ public uint ReadEncodedU32() return result; } - public int ReadEncodedInt32() - { - uint result = ReadEncodedU32(); - int x = (int)(result >> 1); - return (result & 1) != 0 ? -x : x; - } - private Nibble ReadNibble() { if (_currentNibble / 2 >= _size) throw new InvalidOperationException("No more nibbles to read."); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 9df2e7f91dfdb3..1b9e0753fff21f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -52,6 +52,7 @@ ModuleHandle ILoader.GetModuleHandle(TargetPointer modulePointer) return new ModuleHandle(modulePointer); } + IEnumerable ILoader.GetModules(TargetPointer appDomain, AssemblyIterationFlags iterationFlags) { if (appDomain == TargetPointer.Null) From e8b2b490e4be5ec132a96161deb6591e808fb812 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 8 Jul 2025 19:00:53 -0400 Subject: [PATCH 33/54] use nativereader instead of byte array --- ...Reflection.ReadyToRun.Experimental.pkgproj | 4 +- .../Amd64/GcInfo.cs | 126 ++++++------ .../Amd64/GcSlotTable.cs | 46 ++--- .../Amd64/UnwindInfo.cs | 88 ++++----- .../Arm/UnwindInfo.cs | 8 +- .../Arm64/UnwindInfo.cs | 8 +- .../DebugInfo.cs | 18 +- .../ILCompiler.Reflection.ReadyToRun.csproj | 8 +- .../InliningInfoSection.cs | 8 +- .../InliningInfoSection2.cs | 4 +- .../LoongArch64/UnwindInfo.cs | 8 +- .../NativeArray.cs | 24 +-- .../NativeHashtable.cs | 52 ++--- .../NativeReader.cs | 180 +++++++++--------- .../NibbleReader.cs | 10 +- .../ReadyToRunHeader.cs | 48 +++-- .../ReadyToRunMethod.cs | 64 +++---- .../ReadyToRunReader.cs | 72 ++++--- .../RiscV64/UnwindInfo.cs | 8 +- .../x86/GcInfo.cs | 108 +++++------ .../x86/GcSlotTable.cs | 18 +- .../x86/InfoHdr.cs | 29 +-- .../x86/NoGcRegionTable.cs | 8 +- .../x86/UnwindInfo.cs | 4 +- 24 files changed, 480 insertions(+), 471 deletions(-) diff --git a/src/coreclr/.nuget/ILCompiler.Reflection.ReadyToRun.Experimental/ILCompiler.Reflection.ReadyToRun.Experimental.pkgproj b/src/coreclr/.nuget/ILCompiler.Reflection.ReadyToRun.Experimental/ILCompiler.Reflection.ReadyToRun.Experimental.pkgproj index 0480327017db55..7d202885cc77c5 100644 --- a/src/coreclr/.nuget/ILCompiler.Reflection.ReadyToRun.Experimental/ILCompiler.Reflection.ReadyToRun.Experimental.pkgproj +++ b/src/coreclr/.nuget/ILCompiler.Reflection.ReadyToRun.Experimental/ILCompiler.Reflection.ReadyToRun.Experimental.pkgproj @@ -12,11 +12,11 @@ - \lib\netstandard2.0\ + \lib\$(NetCoreAppMinimum)\ $(SystemReflectionMetadataVersion) - netstandard2.0 + $(NetCoreAppMinimum) Build,Analyzers diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs index 3170dadd9dcafd..e162ae0569e02f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs @@ -87,7 +87,7 @@ public GcInfo() { } /// /// based on GcInfoDecoder::GcInfoDecoder /// - public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion, ushort minorVersion) + public GcInfo(NativeReader imageReader, int offset, Machine machine, ushort majorVersion, ushort minorVersion) { Offset = offset; Version = ReadyToRunVersionToGcInfoVersion(majorVersion, minorVersion); @@ -106,98 +106,98 @@ public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion, us int bitOffset = offset * 8; - ParseHeaderFlags(image, ref bitOffset); + ParseHeaderFlags(imageReader, ref bitOffset); if (Version >= MIN_GCINFO_VERSION_WITH_RETURN_KIND && Version <= MAX_GCINFO_VERSION_WITH_RETURN_KIND) // IsReturnKindAvailable { int returnKindBits = (_slimHeader) ? _gcInfoTypes.SIZE_OF_RETURN_KIND_SLIM : _gcInfoTypes.SIZE_OF_RETURN_KIND_FAT; - ReturnKind = (ReturnKinds)NativeReader.ReadBits(image, returnKindBits, ref bitOffset); + ReturnKind = (ReturnKinds)imageReader.ReadBits(returnKindBits, ref bitOffset); } - CodeLength = _gcInfoTypes.DenormalizeCodeLength((int)NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.CODE_LENGTH_ENCBASE, ref bitOffset)); + CodeLength = _gcInfoTypes.DenormalizeCodeLength((int)imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.CODE_LENGTH_ENCBASE, ref bitOffset)); if (_hasGSCookie) { - uint normPrologSize = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NORM_PROLOG_SIZE_ENCBASE, ref bitOffset) + 1; - uint normEpilogSize = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NORM_EPILOG_SIZE_ENCBASE, ref bitOffset); + uint normPrologSize = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.NORM_PROLOG_SIZE_ENCBASE, ref bitOffset) + 1; + uint normEpilogSize = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.NORM_EPILOG_SIZE_ENCBASE, ref bitOffset); ValidRangeStart = _gcInfoTypes.DenormalizeCodeOffset(normPrologSize); ValidRangeEnd = (uint)CodeLength - _gcInfoTypes.DenormalizeCodeOffset(normEpilogSize); } else if (_hasSecurityObject || _hasGenericsInstContext) { - uint normValidRangeStart = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NORM_PROLOG_SIZE_ENCBASE, ref bitOffset) + 1; + uint normValidRangeStart = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.NORM_PROLOG_SIZE_ENCBASE, ref bitOffset) + 1; ValidRangeStart = _gcInfoTypes.DenormalizeCodeOffset(normValidRangeStart); ValidRangeEnd = ValidRangeStart + 1; } if (_hasSecurityObject) { - SecurityObjectStackSlot = _gcInfoTypes.DenormalizeStackSlot(NativeReader.DecodeVarLengthSigned(image, _gcInfoTypes.SECURITY_OBJECT_STACK_SLOT_ENCBASE, ref bitOffset)); + SecurityObjectStackSlot = _gcInfoTypes.DenormalizeStackSlot(imageReader.DecodeVarLengthSigned(_gcInfoTypes.SECURITY_OBJECT_STACK_SLOT_ENCBASE, ref bitOffset)); } if (_hasGSCookie) { - GSCookieStackSlot = _gcInfoTypes.DenormalizeStackSlot(NativeReader.DecodeVarLengthSigned(image, _gcInfoTypes.GS_COOKIE_STACK_SLOT_ENCBASE, ref bitOffset)); + GSCookieStackSlot = _gcInfoTypes.DenormalizeStackSlot(imageReader.DecodeVarLengthSigned(_gcInfoTypes.GS_COOKIE_STACK_SLOT_ENCBASE, ref bitOffset)); } if (_hasPSPSym) { - PSPSymStackSlot = _gcInfoTypes.DenormalizeStackSlot(NativeReader.DecodeVarLengthSigned(image, _gcInfoTypes.PSP_SYM_STACK_SLOT_ENCBASE, ref bitOffset)); + PSPSymStackSlot = _gcInfoTypes.DenormalizeStackSlot(imageReader.DecodeVarLengthSigned(_gcInfoTypes.PSP_SYM_STACK_SLOT_ENCBASE, ref bitOffset)); } if (_hasGenericsInstContext) { - GenericsInstContextStackSlot = _gcInfoTypes.DenormalizeStackSlot(NativeReader.DecodeVarLengthSigned(image, _gcInfoTypes.GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE, ref bitOffset)); + GenericsInstContextStackSlot = _gcInfoTypes.DenormalizeStackSlot(imageReader.DecodeVarLengthSigned(_gcInfoTypes.GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE, ref bitOffset)); } if (_hasStackBaseRegister && !_slimHeader) { - StackBaseRegister = _gcInfoTypes.DenormalizeStackBaseRegister(NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.STACK_BASE_REGISTER_ENCBASE, ref bitOffset)); + StackBaseRegister = _gcInfoTypes.DenormalizeStackBaseRegister(imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.STACK_BASE_REGISTER_ENCBASE, ref bitOffset)); } if (_hasSizeOfEditAndContinuePreservedArea) { - SizeOfEditAndContinuePreservedArea = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE, ref bitOffset); + SizeOfEditAndContinuePreservedArea = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE, ref bitOffset); } if (_hasReversePInvokeFrame) { - ReversePInvokeFrameStackSlot = NativeReader.DecodeVarLengthSigned(image, _gcInfoTypes.REVERSE_PINVOKE_FRAME_ENCBASE, ref bitOffset); + ReversePInvokeFrameStackSlot = imageReader.DecodeVarLengthSigned(_gcInfoTypes.REVERSE_PINVOKE_FRAME_ENCBASE, ref bitOffset); } // FIXED_STACK_PARAMETER_SCRATCH_AREA (this macro is always defined in _gcInfoTypes.h) if (!_slimHeader) { - SizeOfStackOutgoingAndScratchArea = _gcInfoTypes.DenormalizeSizeOfStackArea(NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.SIZE_OF_STACK_AREA_ENCBASE, ref bitOffset)); + SizeOfStackOutgoingAndScratchArea = _gcInfoTypes.DenormalizeSizeOfStackArea(imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.SIZE_OF_STACK_AREA_ENCBASE, ref bitOffset)); } // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED (this macro is always defined in _gcInfoTypes.h) - NumSafePoints = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NUM_SAFE_POINTS_ENCBASE, ref bitOffset); + NumSafePoints = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.NUM_SAFE_POINTS_ENCBASE, ref bitOffset); if (!_slimHeader) { - NumInterruptibleRanges = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NUM_INTERRUPTIBLE_RANGES_ENCBASE, ref bitOffset); + NumInterruptibleRanges = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.NUM_INTERRUPTIBLE_RANGES_ENCBASE, ref bitOffset); } // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED (this macro is always defined in _gcInfoTypes.h) - SafePointOffsets = EnumerateSafePoints(image, ref bitOffset); + SafePointOffsets = EnumerateSafePoints(imageReader, ref bitOffset); - InterruptibleRanges = EnumerateInterruptibleRanges(image, _gcInfoTypes.INTERRUPTIBLE_RANGE_DELTA1_ENCBASE, _gcInfoTypes.INTERRUPTIBLE_RANGE_DELTA2_ENCBASE, ref bitOffset); + InterruptibleRanges = EnumerateInterruptibleRanges(imageReader, _gcInfoTypes.INTERRUPTIBLE_RANGE_DELTA1_ENCBASE, _gcInfoTypes.INTERRUPTIBLE_RANGE_DELTA2_ENCBASE, ref bitOffset); - SlotTable = new GcSlotTable(image, machine, _gcInfoTypes, ref bitOffset); + SlotTable = new GcSlotTable(imageReader, machine, _gcInfoTypes, ref bitOffset); if (SlotTable.NumSlots > 0) { if (NumSafePoints > 0) { // Partially interruptible code - LiveSlotsAtSafepoints = GetLiveSlotsAtSafepoints(image, ref bitOffset); + LiveSlotsAtSafepoints = GetLiveSlotsAtSafepoints(imageReader, ref bitOffset); } else { // Fully interruptible code - Transitions = GetTransitions(image, ref bitOffset); + Transitions = GetTransitions(imageReader, ref bitOffset); } } @@ -326,18 +326,18 @@ public override string ToString() /// /// based on GcInfoDecoder::GcInfoDecoder /// - private void ParseHeaderFlags(byte[] image, ref int bitOffset) + private void ParseHeaderFlags(NativeReader imageReader, ref int bitOffset) { GcInfoHeaderFlags headerFlags; - _slimHeader = (NativeReader.ReadBits(image, 1, ref bitOffset) == 0); + _slimHeader = (imageReader.ReadBits(1, ref bitOffset) == 0); if (_slimHeader) { - headerFlags = NativeReader.ReadBits(image, 1, ref bitOffset) == 1 ? GcInfoHeaderFlags.GC_INFO_HAS_STACK_BASE_REGISTER : 0; + headerFlags = imageReader.ReadBits(1, ref bitOffset) == 1 ? GcInfoHeaderFlags.GC_INFO_HAS_STACK_BASE_REGISTER : 0; } else { int numFlagBits = (int)((Version == 1) ? GcInfoHeaderFlags.GC_INFO_FLAGS_BIT_SIZE_VERSION_1 : GcInfoHeaderFlags.GC_INFO_FLAGS_BIT_SIZE); - headerFlags = (GcInfoHeaderFlags)NativeReader.ReadBits(image, numFlagBits, ref bitOffset); + headerFlags = (GcInfoHeaderFlags)imageReader.ReadBits(numFlagBits, ref bitOffset); } _hasSecurityObject = (headerFlags & GcInfoHeaderFlags.GC_INFO_HAS_SECURITY_OBJECT) != 0; @@ -353,13 +353,13 @@ private void ParseHeaderFlags(byte[] image, ref int bitOffset) _wantsReportOnlyLeaf = ((headerFlags & GcInfoHeaderFlags.GC_INFO_WANTS_REPORT_ONLY_LEAF) != 0); } - private List EnumerateSafePoints(byte[] image, ref int bitOffset) + private List EnumerateSafePoints(NativeReader imageReader, ref int bitOffset) { List safePoints = new List(); uint numBitsPerOffset = GcInfoTypes.CeilOfLog2((int)_gcInfoTypes.NormalizeCodeOffset((uint)CodeLength)); for (int i = 0; i < NumSafePoints; i++) { - uint normOffset = (uint)NativeReader.ReadBits(image, (int)numBitsPerOffset, ref bitOffset); + uint normOffset = (uint)imageReader.ReadBits((int)numBitsPerOffset, ref bitOffset); safePoints.Add(new SafePointOffset(i, _gcInfoTypes.DenormalizeCodeOffset(normOffset))); } return safePoints; @@ -368,15 +368,15 @@ private List EnumerateSafePoints(byte[] image, ref int bitOffse /// /// based on beginning of GcInfoDecoder::EnumerateLiveSlots /// - private List EnumerateInterruptibleRanges(byte[] image, int interruptibleRangeDelta1EncBase, int interruptibleRangeDelta2EncBase, ref int bitOffset) + private List EnumerateInterruptibleRanges(NativeReader imageReader, int interruptibleRangeDelta1EncBase, int interruptibleRangeDelta2EncBase, ref int bitOffset) { List ranges = new List(); uint normLastinterruptibleRangeStopOffset = 0; for (uint i = 0; i < NumInterruptibleRanges; i++) { - uint normStartDelta = NativeReader.DecodeVarLengthUnsigned(image, interruptibleRangeDelta1EncBase, ref bitOffset); - uint normStopDelta = NativeReader.DecodeVarLengthUnsigned(image, interruptibleRangeDelta2EncBase, ref bitOffset) + 1; + uint normStartDelta = imageReader.DecodeVarLengthUnsigned(interruptibleRangeDelta1EncBase, ref bitOffset); + uint normStopDelta = imageReader.DecodeVarLengthUnsigned(interruptibleRangeDelta2EncBase, ref bitOffset) + 1; uint normRangeStartOffset = normLastinterruptibleRangeStopOffset + normStartDelta; uint normRangeStopOffset = normRangeStartOffset + normStopDelta; @@ -411,7 +411,7 @@ private int ReadyToRunVersionToGcInfoVersion(int readyToRunMajorVersion, int rea return 4; } - private List> GetLiveSlotsAtSafepoints(byte[] image, ref int bitOffset) + private List> GetLiveSlotsAtSafepoints(NativeReader imageReader, ref int bitOffset) { // For each safe point, enumerates a list of GC slots that are alive at that point var result = new List>(); @@ -423,9 +423,9 @@ private List> GetLiveSlotsAtSafepoints(byte[] image, ref int bi uint numBitsPerOffset = 0; // Duplicate the encoder's heuristic to determine if we have indirect live // slot table (similar to the chunk pointers) - if (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) + if (imageReader.ReadBits(1, ref bitOffset) != 0) { - numBitsPerOffset = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.POINTER_SIZE_ENCBASE, ref bitOffset) + 1; + numBitsPerOffset = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.POINTER_SIZE_ENCBASE, ref bitOffset) + 1; Debug.Assert(numBitsPerOffset != 0); } @@ -441,20 +441,20 @@ private List> GetLiveSlotsAtSafepoints(byte[] image, ref int bi { bitOffset += (int)(numBitsPerOffset * safePointIndex); - uint liveStatesOffset = (uint)NativeReader.ReadBits(image, (int)numBitsPerOffset, ref bitOffset); + uint liveStatesOffset = (uint)imageReader.ReadBits((int)numBitsPerOffset, ref bitOffset); uint liveStatesStart = (uint)((offsetTablePos + NumSafePoints * numBitsPerOffset + 7) & (~7)); bitOffset = (int)(liveStatesStart + liveStatesOffset); - if (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) + if (imageReader.ReadBits(1, ref bitOffset) != 0) { // RLE encoded - bool skip = NativeReader.ReadBits(image, 1, ref bitOffset) == 0; + bool skip = imageReader.ReadBits(1, ref bitOffset) == 0; bool report = true; - uint readSlots = NativeReader.DecodeVarLengthUnsigned(image, + uint readSlots = imageReader.DecodeVarLengthUnsigned( skip ? _gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE : _gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset); skip = !skip; while (readSlots < numSlots) { - uint cnt = NativeReader.DecodeVarLengthUnsigned(image, + uint cnt = imageReader.DecodeVarLengthUnsigned( skip ? _gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE : _gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset) + 1; if (report) { @@ -492,7 +492,7 @@ private List> GetLiveSlotsAtSafepoints(byte[] image, ref int bi for (uint slotIndex = 0; slotIndex < numSlots; slotIndex++) { - bool isLive = NativeReader.ReadBits(image, 1, ref bitOffset) != 0; + bool isLive = imageReader.ReadBits(1, ref bitOffset) != 0; if (isLive) { int trackedSlotIndex = 0; @@ -520,7 +520,7 @@ private List> GetLiveSlotsAtSafepoints(byte[] image, ref int bi /// /// based on end of GcInfoDecoder::EnumerateLiveSlots and GcInfoEncoder::Build /// - private Dictionary> GetTransitions(byte[] image, ref int bitOffset) + private Dictionary> GetTransitions(NativeReader imageReader, ref int bitOffset) { int totalInterruptibleLength = 0; if (NumInterruptibleRanges == 0) @@ -532,7 +532,7 @@ private Dictionary> GetTransitions(byte[] image, ref foreach (InterruptibleRange range in InterruptibleRanges) { uint normStart = _gcInfoTypes.NormalizeCodeOffset(range.StartOffset); - uint normStop = _gcInfoTypes.NormalizeCodeOffset(range.StopOffset); + uint normStop = _gcInfoTypes.NormalizeCodeOffset(range.StopOffset); totalInterruptibleLength += (int)(normStop - normStart); } } @@ -541,7 +541,7 @@ private Dictionary> GetTransitions(byte[] image, ref return new Dictionary>(); int numChunks = (totalInterruptibleLength + _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK - 1) / _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK; - int numBitsPerPointer = (int)NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.POINTER_SIZE_ENCBASE, ref bitOffset); + int numBitsPerPointer = (int)imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.POINTER_SIZE_ENCBASE, ref bitOffset); if (numBitsPerPointer == 0) { return new Dictionary>(); @@ -551,7 +551,7 @@ private Dictionary> GetTransitions(byte[] image, ref int[] chunkPointers = new int[numChunks]; for (int i = 0; i < numChunks; i++) { - chunkPointers[i] = NativeReader.ReadBits(image, numBitsPerPointer, ref bitOffset); + chunkPointers[i] = imageReader.ReadBits(numBitsPerPointer, ref bitOffset); } // Offset to m_Info2 containing all the info on register liveness, which starts at the next byte @@ -572,16 +572,16 @@ private Dictionary> GetTransitions(byte[] image, ref int couldBeLiveOffset = bitOffset; // points to the couldBeLive bit array (array of bits indicating the slot changed state in the chunk) int slotId = 0; - bool fSimple = (NativeReader.ReadBits(image, 1, ref couldBeLiveOffset) == 0); + bool fSimple = (imageReader.ReadBits(1, ref couldBeLiveOffset) == 0); bool fSkipFirst = false; int couldBeLiveCnt = 0; if (!fSimple) { - fSkipFirst = (NativeReader.ReadBits(image, 1, ref couldBeLiveOffset) == 0); + fSkipFirst = (imageReader.ReadBits(1, ref couldBeLiveOffset) == 0); slotId = -1; } - uint numCouldBeLiveSlots = GetNumCouldBeLiveSlots(image, ref bitOffset); // count the number of set bits in the couldBeLive array + uint numCouldBeLiveSlots = GetNumCouldBeLiveSlots(imageReader, ref bitOffset); // count the number of set bits in the couldBeLive array int finalStateOffset = bitOffset; // points to the finalState bit array (array of bits indicating if the slot is live at the end of the chunk) bitOffset += (int)numCouldBeLiveSlots; // points to the array of code offsets @@ -590,16 +590,16 @@ private Dictionary> GetTransitions(byte[] image, ref for (int i = 0; i < numCouldBeLiveSlots; i++) { // get the index of the next couldBeLive slot - slotId = GetNextSlotId(image, fSimple, fSkipFirst, slotId, ref couldBeLiveCnt, ref couldBeLiveOffset); + slotId = GetNextSlotId(imageReader, fSimple, fSkipFirst, slotId, ref couldBeLiveCnt, ref couldBeLiveOffset); // set the liveAtEnd for the slot at slotId bool isLive = !liveAtEnd[slotId]; - liveAtEnd[slotId] = (NativeReader.ReadBits(image, 1, ref finalStateOffset) != 0); + liveAtEnd[slotId] = (imageReader.ReadBits(1, ref finalStateOffset) != 0); // Read all the code offsets where the slot at slotId changed state - while (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) + while (imageReader.ReadBits(1, ref bitOffset) != 0) { - int transitionOffset = NativeReader.ReadBits(image, _gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2, ref bitOffset) + normChunkBaseCodeOffset; + int transitionOffset = imageReader.ReadBits(_gcInfoTypes.NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2, ref bitOffset) + normChunkBaseCodeOffset; transitions.Add(new GcTransition(transitionOffset, slotId, isLive, currentChunk, SlotTable, _machine)); isLive = !isLive; } @@ -612,20 +612,20 @@ private Dictionary> GetTransitions(byte[] image, ref return UpdateTransitionCodeOffset(transitions); } - private uint GetNumCouldBeLiveSlots(byte[] image, ref int bitOffset) + private uint GetNumCouldBeLiveSlots(NativeReader imageReader, ref int bitOffset) { uint numCouldBeLiveSlots = 0; uint numTracked = SlotTable.NumTracked; - if (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) + if (imageReader.ReadBits(1, ref bitOffset) != 0) { // RLE encoded - bool fSkip = (NativeReader.ReadBits(image, 1, ref bitOffset) == 0); + bool fSkip = (imageReader.ReadBits(1, ref bitOffset) == 0); bool fReport = true; - uint readSlots = NativeReader.DecodeVarLengthUnsigned(image, fSkip ? _gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE : _gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset); + uint readSlots = imageReader.DecodeVarLengthUnsigned(fSkip ? _gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE : _gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset); fSkip = !fSkip; while (readSlots < numTracked) { - uint cnt = NativeReader.DecodeVarLengthUnsigned(image, fSkip ? _gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE : _gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset) + 1; + uint cnt = imageReader.DecodeVarLengthUnsigned(fSkip ? _gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE : _gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset) + 1; if (fReport) { numCouldBeLiveSlots += cnt; @@ -644,7 +644,7 @@ private uint GetNumCouldBeLiveSlots(byte[] image, ref int bitOffset) if ((slot.Flags & GcSlotFlags.GC_SLOT_UNTRACKED) != 0) break; - if (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) + if (imageReader.ReadBits(1, ref bitOffset) != 0) numCouldBeLiveSlots++; } } @@ -652,12 +652,12 @@ private uint GetNumCouldBeLiveSlots(byte[] image, ref int bitOffset) return numCouldBeLiveSlots; } - private int GetNextSlotId(byte[] image, bool fSimple, bool fSkipFirst, int slotId, ref int couldBeLiveCnt, ref int couldBeLiveOffset) + private int GetNextSlotId(NativeReader imageReader, bool fSimple, bool fSkipFirst, int slotId, ref int couldBeLiveCnt, ref int couldBeLiveOffset) { if (fSimple) { // Get the slotId by iterating through the couldBeLive bit array. The slotId is the index of the next set bit - while (NativeReader.ReadBits(image, 1, ref couldBeLiveOffset) == 0) + while (imageReader.ReadBits(1, ref couldBeLiveOffset) == 0) slotId++; } else if (couldBeLiveCnt > 0) @@ -668,15 +668,15 @@ private int GetNextSlotId(byte[] image, bool fSimple, bool fSkipFirst, int slotI // We need to find a new run else if (fSkipFirst) { - int tmp = (int)NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE, ref couldBeLiveOffset) + 1; + int tmp = (int)imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE, ref couldBeLiveOffset) + 1; slotId += tmp; - couldBeLiveCnt = (int)NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref couldBeLiveOffset); + couldBeLiveCnt = (int)imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref couldBeLiveOffset); } else { - int tmp = (int)NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref couldBeLiveOffset) + 1; + int tmp = (int)imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.LIVESTATE_RLE_RUN_ENCBASE, ref couldBeLiveOffset) + 1; slotId += tmp; - couldBeLiveCnt = (int)NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE, ref couldBeLiveOffset); + couldBeLiveCnt = (int)imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.LIVESTATE_RLE_SKIP_ENCBASE, ref couldBeLiveOffset); } return slotId; } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs index d5ac7048589c29..bd633fbbc9e406 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs @@ -104,33 +104,33 @@ public GcSlotTable() { } /// /// based on GcSlotDecoder::DecodeSlotTable /// - public GcSlotTable(byte[] image, Machine machine, GcInfoTypes gcInfoTypes, ref int bitOffset) + public GcSlotTable(NativeReader imageReader, Machine machine, GcInfoTypes gcInfoTypes, ref int bitOffset) { _machine = machine; - if (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) + if (imageReader.ReadBits(1, ref bitOffset) != 0) { - NumRegisters = NativeReader.DecodeVarLengthUnsigned(image, gcInfoTypes.NUM_REGISTERS_ENCBASE, ref bitOffset); + NumRegisters = imageReader.DecodeVarLengthUnsigned(gcInfoTypes.NUM_REGISTERS_ENCBASE, ref bitOffset); } - if (NativeReader.ReadBits(image, 1, ref bitOffset) != 0) + if (imageReader.ReadBits(1, ref bitOffset) != 0) { - NumStackSlots = NativeReader.DecodeVarLengthUnsigned(image, gcInfoTypes.NUM_STACK_SLOTS_ENCBASE, ref bitOffset); - NumUntracked = NativeReader.DecodeVarLengthUnsigned(image, gcInfoTypes.NUM_UNTRACKED_SLOTS_ENCBASE, ref bitOffset); + NumStackSlots = imageReader.DecodeVarLengthUnsigned(gcInfoTypes.NUM_STACK_SLOTS_ENCBASE, ref bitOffset); + NumUntracked = imageReader.DecodeVarLengthUnsigned(gcInfoTypes.NUM_UNTRACKED_SLOTS_ENCBASE, ref bitOffset); } NumSlots = NumRegisters + NumStackSlots + NumUntracked; GcSlots = new List(); if (NumRegisters > 0) { - DecodeRegisters(image, gcInfoTypes, ref bitOffset); + DecodeRegisters(imageReader, gcInfoTypes, ref bitOffset); } if (NumStackSlots > 0) { - DecodeStackSlots(image, machine, gcInfoTypes, NumStackSlots, false, ref bitOffset); + DecodeStackSlots(imageReader, machine, gcInfoTypes, NumStackSlots, false, ref bitOffset); } if (NumUntracked > 0) { - DecodeStackSlots(image, machine, gcInfoTypes, NumUntracked, true, ref bitOffset); + DecodeStackSlots(imageReader, machine, gcInfoTypes, NumUntracked, true, ref bitOffset); } } @@ -150,50 +150,50 @@ public override string ToString() return sb.ToString(); } - private void DecodeRegisters(byte[] image, GcInfoTypes gcInfoTypes, ref int bitOffset) + private void DecodeRegisters(NativeReader imageReader, GcInfoTypes gcInfoTypes, ref int bitOffset) { // We certainly predecode the first register - uint regNum = NativeReader.DecodeVarLengthUnsigned(image, gcInfoTypes.REGISTER_ENCBASE, ref bitOffset); - GcSlotFlags flags = (GcSlotFlags)NativeReader.ReadBits(image, 2, ref bitOffset); + uint regNum = imageReader.DecodeVarLengthUnsigned(gcInfoTypes.REGISTER_ENCBASE, ref bitOffset); + GcSlotFlags flags = (GcSlotFlags)imageReader.ReadBits(2, ref bitOffset); GcSlots.Add(new GcSlot(GcSlots.Count, (int)regNum, null, flags)); for (int i = 1; i < NumRegisters; i++) { if ((uint)flags != 0) { - regNum = NativeReader.DecodeVarLengthUnsigned(image, gcInfoTypes.REGISTER_ENCBASE, ref bitOffset); - flags = (GcSlotFlags)NativeReader.ReadBits(image, 2, ref bitOffset); + regNum = imageReader.DecodeVarLengthUnsigned(gcInfoTypes.REGISTER_ENCBASE, ref bitOffset); + flags = (GcSlotFlags)imageReader.ReadBits(2, ref bitOffset); } else { - uint regDelta = NativeReader.DecodeVarLengthUnsigned(image, gcInfoTypes.REGISTER_DELTA_ENCBASE, ref bitOffset) + 1; + uint regDelta = imageReader.DecodeVarLengthUnsigned(gcInfoTypes.REGISTER_DELTA_ENCBASE, ref bitOffset) + 1; regNum += regDelta; } GcSlots.Add(new GcSlot(GcSlots.Count, (int)regNum, null, flags)); } } - private void DecodeStackSlots(byte[] image, Machine machine, GcInfoTypes gcInfoTypes, uint nSlots, bool isUntracked, ref int bitOffset) + private void DecodeStackSlots(NativeReader imageReader, Machine machine, GcInfoTypes gcInfoTypes, uint nSlots, bool isUntracked, ref int bitOffset) { // We have stack slots left and more room to predecode - GcStackSlotBase spBase = (GcStackSlotBase)NativeReader.ReadBits(image, 2, ref bitOffset); - int normSpOffset = NativeReader.DecodeVarLengthSigned(image, gcInfoTypes.STACK_SLOT_ENCBASE, ref bitOffset); + GcStackSlotBase spBase = (GcStackSlotBase)imageReader.ReadBits(2, ref bitOffset); + int normSpOffset = imageReader.DecodeVarLengthSigned(gcInfoTypes.STACK_SLOT_ENCBASE, ref bitOffset); int spOffset = gcInfoTypes.DenormalizeStackSlot(normSpOffset); - GcSlotFlags flags = (GcSlotFlags)NativeReader.ReadBits(image, 2, ref bitOffset); + GcSlotFlags flags = (GcSlotFlags)imageReader.ReadBits(2, ref bitOffset); GcSlots.Add(new GcSlot(GcSlots.Count, -1, new GcStackSlot(spOffset, spBase), flags, isUntracked)); for (int i = 1; i < nSlots; i++) { - spBase = (GcStackSlotBase)NativeReader.ReadBits(image, 2, ref bitOffset); + spBase = (GcStackSlotBase)imageReader.ReadBits(2, ref bitOffset); if ((uint)flags != 0) { - normSpOffset = NativeReader.DecodeVarLengthSigned(image, gcInfoTypes.STACK_SLOT_ENCBASE, ref bitOffset); + normSpOffset = imageReader.DecodeVarLengthSigned(gcInfoTypes.STACK_SLOT_ENCBASE, ref bitOffset); spOffset = gcInfoTypes.DenormalizeStackSlot(normSpOffset); - flags = (GcSlotFlags)NativeReader.ReadBits(image, 2, ref bitOffset); + flags = (GcSlotFlags)imageReader.ReadBits(2, ref bitOffset); } else { - int normSpOffsetDelta = NativeReader.DecodeVarLengthSigned(image, gcInfoTypes.STACK_SLOT_DELTA_ENCBASE, ref bitOffset); + int normSpOffsetDelta = imageReader.DecodeVarLengthSigned(gcInfoTypes.STACK_SLOT_DELTA_ENCBASE, ref bitOffset); normSpOffset += normSpOffsetDelta; spOffset = gcInfoTypes.DenormalizeStackSlot(normSpOffset); } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/UnwindInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/UnwindInfo.cs index 8cc66d025abbdf..9b89b3b5755d55 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/UnwindInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/UnwindInfo.cs @@ -61,10 +61,10 @@ public UnwindCode() { } /// /// Unwind code parsing is based on src\jit\unwindamd64.cpp DumpUnwindInfo /// - public UnwindCode(byte[] image, ref int frameOffset, ref int offset) + public UnwindCode(NativeReader imageReader, ref int frameOffset, ref int offset) { - CodeOffset = NativeReader.ReadByte(image, ref offset); - byte op = NativeReader.ReadByte(image, ref offset); + CodeOffset = imageReader.ReadByte(ref offset); + byte op = imageReader.ReadByte(ref offset); UnwindOp = (UnwindOpCodes)(op & 15); OpInfo = (byte)(op >> 4); @@ -83,13 +83,13 @@ public UnwindCode(byte[] image, ref int frameOffset, ref int offset) if (OpInfo == 0) { OpInfoStr += "Scaled small"; - NextFrameOffset = 8 * NativeReader.ReadUInt16(image, ref offset); + NextFrameOffset = 8 * imageReader.ReadUInt16(ref offset); } else if (OpInfo == 1) { OpInfoStr += "Unscaled large"; - uint nextOffset = NativeReader.ReadUInt16(image, ref offset); - NextFrameOffset = (int)((uint)(NativeReader.ReadUInt16(image, ref offset) << 16) | nextOffset); + uint nextOffset = imageReader.ReadUInt16(ref offset); + NextFrameOffset = (int)((uint)(imageReader.ReadUInt16(ref offset) << 16) | nextOffset); } else { @@ -104,43 +104,43 @@ public UnwindCode(byte[] image, ref int frameOffset, ref int offset) OpInfoStr = $"Unused({OpInfo})"; break; case UnwindOpCodes.UWOP_SET_FPREG_LARGE: + { + OpInfoStr = $"Unused({OpInfo})"; + uint nextOffset = imageReader.ReadUInt16(ref offset); + nextOffset = ((uint)(imageReader.ReadUInt16(ref offset) << 16) | nextOffset); + NextFrameOffset = (int)nextOffset * 16; + if ((NextFrameOffset & 0xF0000000) != 0) { - OpInfoStr = $"Unused({OpInfo})"; - uint nextOffset = NativeReader.ReadUInt16(image, ref offset); - nextOffset = ((uint)(NativeReader.ReadUInt16(image, ref offset) << 16) | nextOffset); - NextFrameOffset = (int)nextOffset * 16; - if ((NextFrameOffset & 0xF0000000) != 0) - { - throw new BadImageFormatException("Warning: Illegal unwindInfo unscaled offset: too large"); - } + throw new BadImageFormatException("Warning: Illegal unwindInfo unscaled offset: too large"); } - break; + } + break; case UnwindOpCodes.UWOP_SAVE_NONVOL: - { - OpInfoStr = $"{(Registers)OpInfo}({OpInfo})"; - NextFrameOffset = NativeReader.ReadUInt16(image, ref offset) * 8; - } - break; + { + OpInfoStr = $"{(Registers)OpInfo}({OpInfo})"; + NextFrameOffset = imageReader.ReadUInt16(ref offset) * 8; + } + break; case UnwindOpCodes.UWOP_SAVE_NONVOL_FAR: - { - OpInfoStr = $"{(Registers)OpInfo}({OpInfo})"; - uint nextOffset = NativeReader.ReadUInt16(image, ref offset); - NextFrameOffset = (int)((uint)(NativeReader.ReadUInt16(image, ref offset) << 16) | nextOffset); - } - break; + { + OpInfoStr = $"{(Registers)OpInfo}({OpInfo})"; + uint nextOffset = imageReader.ReadUInt16(ref offset); + NextFrameOffset = (int)((uint)(imageReader.ReadUInt16(ref offset) << 16) | nextOffset); + } + break; case UnwindOpCodes.UWOP_SAVE_XMM128: - { - OpInfoStr = $"XMM{OpInfo}({OpInfo})"; - NextFrameOffset = (int)NativeReader.ReadUInt16(image, ref offset) * 16; - } - break; + { + OpInfoStr = $"XMM{OpInfo}({OpInfo})"; + NextFrameOffset = (int)imageReader.ReadUInt16(ref offset) * 16; + } + break; case UnwindOpCodes.UWOP_SAVE_XMM128_FAR: - { - OpInfoStr = $"XMM{OpInfo}({OpInfo})"; - uint nextOffset = NativeReader.ReadUInt16(image, ref offset); - NextFrameOffset = (int)((uint)(NativeReader.ReadUInt16(image, ref offset) << 16) | nextOffset); - } - break; + { + OpInfoStr = $"XMM{OpInfo}({OpInfo})"; + uint nextOffset = imageReader.ReadUInt16(ref offset); + NextFrameOffset = (int)((uint)(imageReader.ReadUInt16(ref offset) << 16) | nextOffset); + } + break; default: throw new NotImplementedException(UnwindOp.ToString()); } @@ -172,14 +172,14 @@ public UnwindInfo() { } /// /// based on ZapUnwindData::Save /// - public UnwindInfo(byte[] image, int offset) + public UnwindInfo(NativeReader imageReader, int offset) { - byte versionAndFlags = NativeReader.ReadByte(image, ref offset); + byte versionAndFlags = imageReader.ReadByte(ref offset); Version = (byte)(versionAndFlags & 7); Flags = (byte)(versionAndFlags >> 3); - SizeOfProlog = NativeReader.ReadByte(image, ref offset); - CountOfUnwindCodes = NativeReader.ReadByte(image, ref offset); - byte frameRegisterAndOffset = NativeReader.ReadByte(image, ref offset); + SizeOfProlog = imageReader.ReadByte(ref offset); + CountOfUnwindCodes = imageReader.ReadByte(ref offset); + byte frameRegisterAndOffset = imageReader.ReadByte(ref offset); FrameRegister = (Registers)(frameRegisterAndOffset & 15); FrameOffset = (byte)(frameRegisterAndOffset >> 4); @@ -190,7 +190,7 @@ public UnwindInfo(byte[] image, int offset) int endOffset = offset + sizeOfUnwindCodes; while (offset < endOffset) { - UnwindCode unwindCode = new UnwindCode(image, ref frameOffset, ref offset); + UnwindCode unwindCode = new UnwindCode(imageReader, ref frameOffset, ref offset); CodeOffsetToUnwindCodeIndex.Add(unwindCode.CodeOffset, UnwindCodes.Count); UnwindCodes.Add(unwindCode); } @@ -201,7 +201,7 @@ public UnwindInfo(byte[] image, int offset) // Personality routine RVA must be at 4-aligned address offset += alignmentPad; - PersonalityRoutineRVA = NativeReader.ReadUInt32(image, ref offset); + PersonalityRoutineRVA = imageReader.ReadUInt32(ref offset); } public override string ToString() diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm/UnwindInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm/UnwindInfo.cs index 5a7d919910fe7b..4e6d81167f206b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm/UnwindInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm/UnwindInfo.cs @@ -75,11 +75,11 @@ public class UnwindInfo : BaseUnwindInfo public UnwindInfo() { } - public UnwindInfo(byte[] image, int offset) + public UnwindInfo(NativeReader imageReader, int offset) { uint startOffset = (uint)offset; - int dw = NativeReader.ReadInt32(image, ref offset); + int dw = imageReader.ReadInt32(ref offset); CodeWords = ExtractBits(dw, 28, 4); EpilogCount = ExtractBits(dw, 23, 5); FBit = ExtractBits(dw, 22, 1); @@ -92,7 +92,7 @@ public UnwindInfo(byte[] image, int offset) { // We have an extension word specifying a larger number of Code Words or Epilog Counts // than can be specified in the header word. - dw = NativeReader.ReadInt32(image, ref offset); + dw = imageReader.ReadInt32(ref offset); ExtendedCodeWords = ExtractBits(dw, 16, 8); ExtendedEpilogCount = ExtractBits(dw, 0, 16); } @@ -106,7 +106,7 @@ public UnwindInfo(byte[] image, int offset) { for (int scope = 0; scope < EpilogCount; scope++) { - dw = NativeReader.ReadInt32(image, ref offset); + dw = imageReader.ReadInt32(ref offset); Epilogs[scope] = new Epilog(scope, dw, startOffset); epilogStartAt[Epilogs[scope].EpilogStartIndex] = true; // an epilog starts at this offset in the unwind codes } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm64/UnwindInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm64/UnwindInfo.cs index c44e7c80833531..5809959d13aca8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm64/UnwindInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm64/UnwindInfo.cs @@ -75,11 +75,11 @@ public class UnwindInfo : BaseUnwindInfo public UnwindInfo() { } - public UnwindInfo(byte[] image, int offset) + public UnwindInfo(NativeReader imageReader, int offset) { uint startOffset = (uint)offset; - int dw = NativeReader.ReadInt32(image, ref offset); + int dw = imageReader.ReadInt32(ref offset); CodeWords = ExtractBits(dw, 27, 5); EpilogCount = ExtractBits(dw, 22, 5); EBit = ExtractBits(dw, 21, 1); @@ -91,7 +91,7 @@ public UnwindInfo(byte[] image, int offset) { // We have an extension word specifying a larger number of Code Words or Epilog Counts // than can be specified in the header word. - dw = NativeReader.ReadInt32(image, ref offset); + dw = imageReader.ReadInt32(ref offset); ExtendedCodeWords = ExtractBits(dw, 16, 8); ExtendedEpilogCount = ExtractBits(dw, 0, 16); } @@ -105,7 +105,7 @@ public UnwindInfo(byte[] image, int offset) { for (int scope = 0; scope < EpilogCount; scope++) { - dw = NativeReader.ReadInt32(image, ref offset); + dw = imageReader.ReadInt32(ref offset); Epilogs[scope] = new Epilog(scope, dw, startOffset); epilogStartAt[Epilogs[scope].EpilogStartIndex] = true; // an epilog starts at this offset in the unwind codes } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs index f570dffdca505b..7dc7efc344570d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs @@ -92,12 +92,12 @@ private void EnsureInitialized() _boundsList = new List(); _variablesList = new List(); Machine machine = _readyToRunReader.Machine; - byte[] image = _readyToRunReader.Image; + NativeReader imageReader = _readyToRunReader.ImageReader; _machine = machine; // Get the id of the runtime function from the NativeArray uint lookback = 0; - uint debugInfoOffset = NativeReader.DecodeUnsigned(image, (uint)offset, ref lookback); + uint debugInfoOffset = imageReader.DecodeUnsigned((uint)offset, ref lookback); if (lookback != 0) { @@ -105,7 +105,7 @@ private void EnsureInitialized() debugInfoOffset = (uint)offset - lookback; } - NibbleReader reader = new NibbleReader(image, (int)debugInfoOffset); + NibbleReader reader = new NibbleReader(imageReader, (int)debugInfoOffset); uint boundsByteCount = reader.ReadUInt(); uint variablesByteCount = reader.ReadUInt(); int boundsOffset = reader.GetNextByteOffset(); @@ -113,23 +113,23 @@ private void EnsureInitialized() if (boundsByteCount > 0) { - ParseBounds(image, boundsOffset); + ParseBounds(imageReader, boundsOffset); } if (variablesByteCount > 0) { - ParseNativeVarInfo(image, variablesOffset); + ParseNativeVarInfo(imageReader, variablesOffset); } } - private void ParseBounds(byte[] image, int offset) + private void ParseBounds(NativeReader imageReader, int offset) { // Bounds info contains (Native Offset, IL Offset, flags) // - Sorted by native offset (so use a delta encoding for that). // - IL offsets aren't sorted, but they should be close to each other (so a signed delta encoding) // They may also include a sentinel value from MappingTypes. // - flags is 3 independent bits. - NibbleReader reader = new NibbleReader(image, offset); + NibbleReader reader = new NibbleReader(imageReader, offset); uint boundsEntryCount = reader.ReadUInt(); Debug.Assert(boundsEntryCount > 0); @@ -145,14 +145,14 @@ private void ParseBounds(byte[] image, int offset) } } - private void ParseNativeVarInfo(byte[] image, int offset) + private void ParseNativeVarInfo(NativeReader imageReader, int offset) { // Each Varinfo has a: // - native start +End offset. We can use a delta for the end offset. // - Il variable number. These are usually small. // - VarLoc information. This is a tagged variant. // The entries aren't sorted in any particular order. - NibbleReader reader = new NibbleReader(image, offset); + NibbleReader reader = new NibbleReader(imageReader, offset); uint nativeVarCount = reader.ReadUInt(); for (int i = 0; i < nativeVarCount; ++i) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj index f60794a8abf0be..71981555136054 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj @@ -7,8 +7,7 @@ AnyCPU Open true - - netstandard2.0 + $(NetCoreAppMinimum) false $(NoWarn);8002;NU1701 win-x64;win-x86 @@ -16,11 +15,6 @@ AnyCPU;x64 AnyCPU false - - false diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/InliningInfoSection.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/InliningInfoSection.cs index c67582623af7fc..4d3a6ea4fc8ad6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/InliningInfoSection.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/InliningInfoSection.cs @@ -24,14 +24,14 @@ public override string ToString() StringBuilder sb = new StringBuilder(); int iiOffset = _startOffset; - int sizeOfInlineIndex = NativeReader.ReadInt32(_r2r.Image, ref iiOffset); + int sizeOfInlineIndex = _r2r.ImageReader.ReadInt32(ref iiOffset); int inlineIndexEndOffset = iiOffset + sizeOfInlineIndex; while (iiOffset < inlineIndexEndOffset) { - int inlineeRid = NativeReader.ReadInt32(_r2r.Image, ref iiOffset); - int inlinersOffset = NativeReader.ReadInt32(_r2r.Image, ref iiOffset); + int inlineeRid = _r2r.ImageReader.ReadInt32(ref iiOffset); + int inlinersOffset = _r2r.ImageReader.ReadInt32(ref iiOffset); sb.AppendLine($"Inliners for inlinee {RidToMethodDef(inlineeRid):X8}:"); - var inlinersReader = new NibbleReader(_r2r.Image, inlineIndexEndOffset + inlinersOffset); + var inlinersReader = new NibbleReader(_r2r.ImageReader, inlineIndexEndOffset + inlinersOffset); uint sameModuleCount = inlinersReader.ReadUInt(); int baseRid = 0; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/InliningInfoSection2.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/InliningInfoSection2.cs index 46399743450124..036c771e1bb215 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/InliningInfoSection2.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/InliningInfoSection2.cs @@ -23,8 +23,8 @@ public override string ToString() { StringBuilder sb = new StringBuilder(); - NativeParser parser = new NativeParser(_r2r.Image, (uint)_startOffset); - NativeHashtable hashtable = new NativeHashtable(_r2r.Image, parser, (uint)_endOffset); + NativeParser parser = new NativeParser(_r2r.ImageReader, (uint)_startOffset); + NativeHashtable hashtable = new NativeHashtable(_r2r.ImageReader, parser, (uint)_endOffset); var enumerator = hashtable.EnumerateAllEntries(); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/LoongArch64/UnwindInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/LoongArch64/UnwindInfo.cs index 98fe6cbfdde3e1..803c565092d12e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/LoongArch64/UnwindInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/LoongArch64/UnwindInfo.cs @@ -75,11 +75,11 @@ public class UnwindInfo : BaseUnwindInfo public UnwindInfo() { } - public UnwindInfo(byte[] image, int offset) + public UnwindInfo(NativeReader imageReader, int offset) { uint startOffset = (uint)offset; - int dw = NativeReader.ReadInt32(image, ref offset); + int dw = imageReader.ReadInt32(ref offset); CodeWords = ExtractBits(dw, 27, 5); EpilogCount = ExtractBits(dw, 22, 5); EBit = ExtractBits(dw, 21, 1); @@ -91,7 +91,7 @@ public UnwindInfo(byte[] image, int offset) { // We have an extension word specifying a larger number of Code Words or Epilog Counts // than can be specified in the header word. - dw = NativeReader.ReadInt32(image, ref offset); + dw = imageReader.ReadInt32(ref offset); ExtendedCodeWords = ExtractBits(dw, 16, 8); ExtendedEpilogCount = ExtractBits(dw, 0, 16); } @@ -105,7 +105,7 @@ public UnwindInfo(byte[] image, int offset) { for (int scope = 0; scope < EpilogCount; scope++) { - dw = NativeReader.ReadInt32(image, ref offset); + dw = imageReader.ReadInt32(ref offset); Epilogs[scope] = new Epilog(scope, dw, startOffset); epilogStartAt[Epilogs[scope].EpilogStartIndex] = true; // an epilog starts at this offset in the unwind codes } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs index 7a9008100a23b5..d241e5c76d2296 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using System.Text; namespace ILCompiler.Reflection.ReadyToRun @@ -10,20 +11,21 @@ namespace ILCompiler.Reflection.ReadyToRun /// public class NativeArray { - // TODO (refactoring) - all these Native* class should be private private const int _blockSize = 16; + + private NativeReader _reader; private uint _baseOffset; private uint _nElements; private byte _entryIndexSize; - private byte[] _image; - public NativeArray(byte[] image, uint offset) + public NativeArray(NativeReader reader, uint offset) { + _reader = reader; + uint val = 0; - _baseOffset = NativeReader.DecodeUnsigned(image, offset, ref val); + _baseOffset = _reader.DecodeUnsigned(offset, ref val); _nElements = (val >> 2); _entryIndexSize = (byte)(val & 3); - _image = image; } public uint GetCount() @@ -40,7 +42,7 @@ public override string ToString() for (uint i = 0; i < _nElements; i++) { int val = 0; - if (TryGetAt(_image, i, ref val)) + if (TryGetAt(i, ref val)) { sb.AppendLine($"{i}: {val}"); } @@ -49,7 +51,7 @@ public override string ToString() return sb.ToString(); } - public bool TryGetAt(byte[] image, uint index, ref int pOffset) + public bool TryGetAt(uint index, ref int pOffset) { if (index >= _nElements) return false; @@ -58,24 +60,24 @@ public bool TryGetAt(byte[] image, uint index, ref int pOffset) if (_entryIndexSize == 0) { int i = (int)(_baseOffset + (index / _blockSize)); - offset = NativeReader.ReadByte(image, ref i); + offset = _reader.ReadByte(ref i); } else if (_entryIndexSize == 1) { int i = (int)(_baseOffset + 2 * (index / _blockSize)); - offset = NativeReader.ReadUInt16(image, ref i); + offset = _reader.ReadUInt16(ref i); } else { int i = (int)(_baseOffset + 4 * (index / _blockSize)); - offset = NativeReader.ReadUInt32(image, ref i); + offset = _reader.ReadUInt32(ref i); } offset += _baseOffset; for (uint bit = _blockSize >> 1; bit > 0; bit >>= 1) { uint val = 0; - uint offset2 = NativeReader.DecodeUnsigned(image, offset, ref val); + uint offset2 = _reader.DecodeUnsigned(offset, ref val); if ((index & bit) != 0) { if ((val & 2) != 0) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeHashtable.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeHashtable.cs index efeeaf18584087..846a60e22d965c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeHashtable.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeHashtable.cs @@ -18,18 +18,18 @@ public struct NativeParser public uint Offset { get; set; } public byte LowHashcode { get; } - byte[] _image; + NativeReader _imageReader; - public NativeParser(byte[] image, uint offset, byte lowHashcode = 0) + public NativeParser(NativeReader imageReader, uint offset, byte lowHashcode = 0) { Offset = offset; LowHashcode = lowHashcode; - _image = image; + _imageReader = imageReader; } public bool IsNull() { - return _image == null; + return _imageReader == null; } public uint GetRelativeOffset() @@ -37,7 +37,7 @@ public uint GetRelativeOffset() uint pos = Offset; int delta = 0; - Offset = NativeReader.DecodeSigned(_image, Offset, ref delta); + Offset = _imageReader.DecodeSigned(Offset, ref delta); return pos + (uint)delta; } @@ -45,13 +45,13 @@ public uint GetRelativeOffset() public NativeParser GetParserFromRelativeOffset() { byte lowHashcode = GetByte(); - return new NativeParser(_image, GetRelativeOffset(), lowHashcode); + return new NativeParser(_imageReader, GetRelativeOffset(), lowHashcode); } public byte GetByte() { int off = (int)Offset; - byte val = NativeReader.ReadByte(_image, ref off); + byte val = _imageReader.ReadByte(ref off); Offset += 1; return val; } @@ -59,7 +59,7 @@ public byte GetByte() public uint GetCompressedData() { int off = (int)Offset; - uint val = NativeReader.ReadCompressedData(_image, ref off); + uint val = _imageReader.ReadCompressedData(ref off); Offset = (uint)off; return val; } @@ -67,14 +67,14 @@ public uint GetCompressedData() public uint GetUnsigned() { uint value = 0; - Offset = NativeReader.DecodeUnsigned(_image, Offset, ref value); + Offset = _imageReader.DecodeUnsigned(Offset, ref value); return value; } public int GetSigned() { int value = 0; - Offset = NativeReader.DecodeSigned(_image, Offset, ref value); + Offset = _imageReader.DecodeSigned(Offset, ref value); return value; } } @@ -85,17 +85,17 @@ public int GetSigned() public struct NativeHashtable { // TODO (refactoring) - all these Native* class should be private - private byte[] _image; + private NativeReader _imageReader; private uint _baseOffset; private uint _bucketMask; private byte _entryIndexSize; private uint _endOffset; - public NativeHashtable(byte[] image, NativeParser parser, uint endOffset) + public NativeHashtable(NativeReader imageReader, NativeParser parser, uint endOffset) { uint header = parser.GetByte(); _baseOffset = parser.Offset; - _image = image; + _imageReader = imageReader; int numberOfBucketsShift = (int)(header >> 2); if (numberOfBucketsShift > 31) @@ -134,7 +134,7 @@ public override string ToString() { for (int i = curOffset; i < nextOffset; i++) { - sb.Append($"{_image[i]:X2} "); + sb.Append($"{_imageReader[i]:X2} "); } sb.AppendLine(); } @@ -205,24 +205,24 @@ private NativeParser GetParserForBucket(uint bucket, out uint endOffset) if (_entryIndexSize == 0) { int bucketOffset = (int)(_baseOffset + bucket); - start = NativeReader.ReadByte(_image, ref bucketOffset); - end = NativeReader.ReadByte(_image, ref bucketOffset); + start = _imageReader.ReadByte(ref bucketOffset); + end = _imageReader.ReadByte(ref bucketOffset); } else if (_entryIndexSize == 1) { int bucketOffset = (int)(_baseOffset + 2 * bucket); - start = NativeReader.ReadUInt16(_image, ref bucketOffset); - end = NativeReader.ReadUInt16(_image, ref bucketOffset); + start = _imageReader.ReadUInt16(ref bucketOffset); + end = _imageReader.ReadUInt16(ref bucketOffset); } else { int bucketOffset = (int)(_baseOffset + 4 * bucket); - start = NativeReader.ReadUInt32(_image, ref bucketOffset); - end = NativeReader.ReadUInt32(_image, ref bucketOffset); + start = _imageReader.ReadUInt32(ref bucketOffset); + end = _imageReader.ReadUInt32(ref bucketOffset); } endOffset = end + _baseOffset; - return new NativeParser(_image, _baseOffset + start); + return new NativeParser(_imageReader, _baseOffset + start); } public Enumerator Lookup(int hashcode) @@ -246,13 +246,13 @@ public AllEntriesEnumerator EnumerateAllEntries() public struct NativeCuckooFilter { // TODO (refactoring) - all these Native* class should be private - private byte[] _image; + private NativeReader _imageReader; private int _filterStartOffset; private int _filterEndOffset; - public NativeCuckooFilter(byte[] image, int filterStartOffset, int filterEndOffset) + public NativeCuckooFilter(NativeReader imageReader, int filterStartOffset, int filterEndOffset) { - _image = image; + _imageReader = imageReader; _filterStartOffset = filterStartOffset; _filterEndOffset = filterEndOffset; @@ -271,7 +271,7 @@ private IEnumerable GetBuckets() ushort[] bucket = new ushort[8]; for (int i = 0; i < bucket.Length; i++) { - bucket[i] = NativeReader.ReadUInt16(_image, ref offset); + bucket[i] = _imageReader.ReadUInt16(ref offset); } yield return bucket; } @@ -283,7 +283,7 @@ public override string ToString() sb.AppendLine($"NativeCuckooFilter Size: {(_filterEndOffset - _filterStartOffset) / 16}"); int bucket = 0; - foreach (ushort [] bucketContents in GetBuckets()) + foreach (ushort[] bucketContents in GetBuckets()) { sb.Append($"Bucket: {bucket} ["); for (int i = 0; i < 8; i++) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs index 2805a24d139ab0..6641bb0c2511f4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs @@ -2,122 +2,127 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers.Binary; using System.Collections.Generic; +using System.IO; using System.Text; namespace ILCompiler.Reflection.ReadyToRun { - public class NativeReader + + public class NativeReader(Stream backingStream, bool littleEndian = true) { - // TODO (refactoring) - all these Native* class should be private private const int BITS_PER_BYTE = 8; private const int BITS_PER_SIZE_T = 32; + private readonly Stream _backingStream = backingStream; + private readonly bool _littleEndian = littleEndian; + + public int this[int index] + { + get => ReadByte(ref index); + } + + public void ReadSpanAt(ref int start, Span buffer) + { + if (start < 0 || start + buffer.Length > _backingStream.Length) + throw new ArgumentOutOfRangeException(nameof(start), "Start index is out of bounds"); + + _backingStream.Seek(start, SeekOrigin.Begin); + _backingStream.Read(buffer); + start += buffer.Length; + } + /// /// Extracts a 64bit value from the image byte array /// - /// PE image /// Starting index of the value /// /// The gets incremented by the size of the value /// - public static long ReadInt64(byte[] image, ref int start) + public long ReadInt64(ref int start) { - int size = sizeof(long); - byte[] bytes = new byte[size]; - Array.Copy(image, start, bytes, 0, size); - start += size; - return BitConverter.ToInt64(bytes, 0); + Span bytes = stackalloc byte[sizeof(long)]; + ReadSpanAt(ref start, bytes); + return _littleEndian ? BinaryPrimitives.ReadInt64LittleEndian(bytes) : BinaryPrimitives.ReadInt64BigEndian(bytes); } // /// Extracts a 32bit value from the image byte array /// - /// PE image /// Starting index of the value /// /// The gets incremented by the size of the value /// - public static int ReadInt32(byte[] image, ref int start) + public int ReadInt32(ref int start) { - int size = sizeof(int); - byte[] bytes = new byte[size]; - Array.Copy(image, start, bytes, 0, size); - start += size; - return BitConverter.ToInt32(bytes, 0); + Span bytes = stackalloc byte[sizeof(int)]; + ReadSpanAt(ref start, bytes); + return _littleEndian ? BinaryPrimitives.ReadInt32LittleEndian(bytes) : BinaryPrimitives.ReadInt32BigEndian(bytes); } // /// Extracts an unsigned 32bit value from the image byte array /// - /// PE image /// Starting index of the value /// /// The gets incremented by the size of the value /// - public static uint ReadUInt32(byte[] image, ref int start) + public uint ReadUInt32(ref int start) { - int size = sizeof(int); - byte[] bytes = new byte[size]; - Array.Copy(image, start, bytes, 0, size); - start += size; - return (uint)BitConverter.ToInt32(bytes, 0); + Span bytes = stackalloc byte[sizeof(uint)]; + ReadSpanAt(ref start, bytes); + return _littleEndian ? BinaryPrimitives.ReadUInt32LittleEndian(bytes) : BinaryPrimitives.ReadUInt32BigEndian(bytes); } // /// Extracts an unsigned 16bit value from the image byte array /// - /// PE image /// Starting index of the value /// /// The gets incremented by the size of the value /// - public static ushort ReadUInt16(byte[] image, ref int start) + public ushort ReadUInt16(ref int start) { - int size = sizeof(short); - byte[] bytes = new byte[size]; - Array.Copy(image, start, bytes, 0, size); - start += size; - return (ushort)BitConverter.ToInt16(bytes, 0); + Span bytes = stackalloc byte[sizeof(ushort)]; + ReadSpanAt(ref start, bytes); + return _littleEndian ? BinaryPrimitives.ReadUInt16LittleEndian(bytes) : BinaryPrimitives.ReadUInt16BigEndian(bytes); } // /// Extracts byte from the image byte array /// - /// PE image /// Start index of the value /// /// The gets incremented by the size of the value /// - public static byte ReadByte(byte[] image, ref int start) + public byte ReadByte(ref int start) { - byte val = image[start]; - start += sizeof(byte); - return val; + Span bytes = stackalloc byte[sizeof(byte)]; + ReadSpanAt(ref start, bytes); + return bytes[0]; } // /// Extracts bits from the image byte array /// - /// PE image /// Number of bits to read /// Start bit of the value /// /// The gets incremented by /// - public static int ReadBits(byte[] image, int numBits, ref int bitOffset) + public int ReadBits(int numBits, ref int bitOffset) { int start = bitOffset / BITS_PER_BYTE; int bits = bitOffset % BITS_PER_BYTE; - int val = image[start] >> bits; + int val = ReadByte(ref start) >> bits; bits += numBits; while (bits > BITS_PER_BYTE) { - start++; bits -= BITS_PER_BYTE; if (bits > 0) { - int extraBits = image[start] << (numBits - bits); + int extraBits = ReadByte(ref start) << (numBits - bits); val ^= extraBits; } } @@ -130,19 +135,18 @@ public static int ReadBits(byte[] image, int numBits, ref int bitOffset) /// Decode variable length numbers /// based on src\inc\gcinfodecoder.h DecodeVarLengthUnsigned /// - /// PE image /// Number of bits to read /// Start bit of the value /// /// The gets incremented by the size of the value /// - public static uint DecodeVarLengthUnsigned(byte[] image, int len, ref int bitOffset) + public uint DecodeVarLengthUnsigned(int len, ref int bitOffset) { uint numEncodings = (uint)(1 << len); uint result = 0; for (int shift = 0; ; shift += len) { - uint currentChunk = (uint)ReadBits(image, len + 1, ref bitOffset); + uint currentChunk = (uint)ReadBits(len + 1, ref bitOffset); result |= (currentChunk & (numEncodings - 1)) << shift; if ((currentChunk & numEncodings) == 0) { @@ -155,13 +159,13 @@ public static uint DecodeVarLengthUnsigned(byte[] image, int len, ref int bitOff // /// based on src\inc\gcinfodecoder.h DecodeVarLengthSigned /// - public static int DecodeVarLengthSigned(byte[] image, int len, ref int bitOffset) + public int DecodeVarLengthSigned(int len, ref int bitOffset) { int numEncodings = (1 << len); int result = 0; for (int shift = 0; ; shift += len) { - int currentChunk = ReadBits(image, len + 1, ref bitOffset); + int currentChunk = ReadBits(len + 1, ref bitOffset); result |= (currentChunk & (numEncodings - 1)) << shift; if ((currentChunk & numEncodings) == 0) { @@ -177,13 +181,13 @@ public static int DecodeVarLengthSigned(byte[] image, int len, ref int bitOffset // /// based on src\vm\nativeformatreader.h DecodeUnsigned /// - public static uint DecodeUnsigned(byte[] image, uint offset, ref uint pValue) + public uint DecodeUnsigned(uint offset, ref uint pValue) { - if (offset >= image.Length) + if (offset >= _backingStream.Length) throw new System.BadImageFormatException("offset out of bounds"); int off = (int)offset; - uint val = ReadByte(image, ref off); + uint val = ReadByte(ref off); if ((val & 1) == 0) { @@ -192,37 +196,37 @@ public static uint DecodeUnsigned(byte[] image, uint offset, ref uint pValue) } else if ((val & 2) == 0) { - if (offset + 1 >= image.Length) + if (offset + 1 >= _backingStream.Length) throw new System.BadImageFormatException("offset out of bounds"); pValue = (val >> 2) | - ((uint)ReadByte(image, ref off) << 6); + ((uint)ReadByte(ref off) << 6); offset += 2; } else if ((val & 4) == 0) { - if (offset + 2 >= image.Length) + if (offset + 2 >= _backingStream.Length) throw new System.BadImageFormatException("offset out of bounds"); pValue = (val >> 3) | - ((uint)ReadByte(image, ref off) << 5) | - ((uint)ReadByte(image, ref off) << 13); + ((uint)ReadByte(ref off) << 5) | + ((uint)ReadByte(ref off) << 13); offset += 3; } else if ((val & 8) == 0) { - if (offset + 3 >= image.Length) + if (offset + 3 >= _backingStream.Length) throw new System.BadImageFormatException("offset out of bounds"); pValue = (val >> 4) | - ((uint)ReadByte(image, ref off) << 4) | - ((uint)ReadByte(image, ref off) << 12) | - ((uint)ReadByte(image, ref off) << 20); + ((uint)ReadByte(ref off) << 4) | + ((uint)ReadByte(ref off) << 12) | + ((uint)ReadByte(ref off) << 20); offset += 4; } else if ((val & 16) == 0) { - pValue = ReadUInt32(image, ref off); + pValue = ReadUInt32(ref off); offset += 5; } else @@ -236,13 +240,13 @@ public static uint DecodeUnsigned(byte[] image, uint offset, ref uint pValue) // /// based on src\vm\nativeformatreader.h DecodeSigned /// - public static uint DecodeSigned(byte[] image, uint offset, ref int pValue) + public uint DecodeSigned(uint offset, ref int pValue) { - if (offset >= image.Length) + if (offset >= _backingStream.Length) throw new System.BadImageFormatException("offset out of bounds"); int off = (int)offset; - int val = ReadByte(image, ref off); + int val = ReadByte(ref off); if ((val & 1) == 0) { @@ -251,37 +255,37 @@ public static uint DecodeSigned(byte[] image, uint offset, ref int pValue) } else if ((val & 2) == 0) { - if (offset + 1 >= image.Length) + if (offset + 1 >= _backingStream.Length) throw new System.BadImageFormatException("offset out of bounds"); pValue = (val >> 2) | - (ReadByte(image, ref off) << 6); + (ReadByte(ref off) << 6); offset += 2; } else if ((val & 4) == 0) { - if (offset + 2 >= image.Length) + if (offset + 2 >= _backingStream.Length) throw new System.BadImageFormatException("offset out of bounds"); pValue = (val >> 3) | - (ReadByte(image, ref off) << 5) | - (ReadByte(image, ref off) << 13); + (ReadByte(ref off) << 5) | + (ReadByte(ref off) << 13); offset += 3; } else if ((val & 8) == 0) { - if (offset + 3 >= image.Length) + if (offset + 3 >= _backingStream.Length) throw new System.BadImageFormatException("offset out of bounds"); pValue = (val >> 4) | - (ReadByte(image, ref off) << 4) | - (ReadByte(image, ref off) << 12) | - (ReadByte(image, ref off) << 20); + (ReadByte(ref off) << 4) | + (ReadByte(ref off) << 12) | + (ReadByte(ref off) << 20); offset += 4; } else if ((val & 16) == 0) { - pValue = ReadInt32(image, ref off); + pValue = ReadInt32(ref off); offset += 5; } else @@ -295,10 +299,10 @@ public static uint DecodeSigned(byte[] image, uint offset, ref int pValue) // /// based on src\debug\daccess\nidump.cpp DacSigUncompressData and DacSigUncompressBigData /// - public static uint ReadCompressedData(byte[] image, ref int start) + public uint ReadCompressedData(ref int start) { int off = start; - uint data = ReadUInt32(image, ref off); + uint data = ReadUInt32(ref off); if ((data & 0x80) == 0x00) { start++; @@ -306,15 +310,15 @@ public static uint ReadCompressedData(byte[] image, ref int start) } if ((data & 0xC0) == 0x80) // 10?? ???? { - data = (uint)((ReadByte(image, ref start) & 0x3f) << 8); - data |= ReadByte(image, ref start); + data = (uint)((ReadByte(ref start) & 0x3f) << 8); + data |= ReadByte(ref start); } else // 110? ???? { - data = (uint)(ReadByte(image, ref start) & 0x1f) << 24; - data |= (uint)ReadByte(image, ref start) << 16; - data |= (uint)ReadByte(image, ref start) << 8; - data |= ReadByte(image, ref start); + data = (uint)(ReadByte(ref start) & 0x1f) << 24; + data |= (uint)ReadByte(ref start) << 16; + data |= (uint)ReadByte(ref start) << 8; + data |= ReadByte(ref start); } return data; } @@ -322,15 +326,15 @@ public static uint ReadCompressedData(byte[] image, ref int start) /// /// based on src\inc\gcdecoder.cpp decodeUnsigned /// - public static uint DecodeUnsignedGc(byte[] image, ref int start) + public uint DecodeUnsignedGc(ref int start) { int size = 1; - byte data = image[start++]; + byte data = ReadByte(ref start); uint value = (uint)data & 0x7f; while ((data & 0x80) != 0) { size++; - data = image[start++]; + data = ReadByte(ref start); value <<= 7; value += (uint)data & 0x7f; } @@ -340,16 +344,16 @@ public static uint DecodeUnsignedGc(byte[] image, ref int start) /// /// based on src\inc\gcdecoder.cpp decodeSigned /// - public static int DecodeSignedGc(byte[] image, ref int start) + public int DecodeSignedGc(ref int start) { int size = 1; - byte data = image[start++]; + byte data = ReadByte(ref start); byte first = data; int value = data & 0x3f; while ((data & 0x80) != 0) { size++; - data = image[start++]; + data = ReadByte(ref start); value <<= 7; value += data & 0x7f; } @@ -362,10 +366,10 @@ public static int DecodeSignedGc(byte[] image, ref int start) /// /// based on src\inc\gcdecoder.cpp decodeUDelta /// - public static uint DecodeUDelta(byte[] image, ref int start, uint lastValue) + public uint DecodeUDelta(ref int start, uint lastValue) { - uint delta = DecodeUnsignedGc(image, ref start); + uint delta = DecodeUnsignedGc(ref start); return lastValue + delta; } } -} +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs index 7a024ed33445b5..26d4599b6a44b2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs @@ -16,9 +16,9 @@ class NibbleReader private const byte NoNextNibble = 0xFF; /// - /// Byte array representing the PE file. + /// NativeReader representing the PE file. /// - private byte[] _image; + private NativeReader _imageReader; /// /// Offset within the image. @@ -30,9 +30,9 @@ class NibbleReader /// private byte _nextNibble; - public NibbleReader(byte[] image, int offset) + public NibbleReader(NativeReader imageReader, int offset) { - _image = image; + _imageReader = imageReader; _offset = offset; _nextNibble = NoNextNibble; } @@ -47,7 +47,7 @@ public byte ReadNibble() } else { - _nextNibble = _image[_offset++]; + _nextNibble = _imageReader.ReadByte(ref _offset); result = (byte)(_nextNibble & 0x0F); _nextNibble >>= 4; } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunHeader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunHeader.cs index 173ece1227ef29..2579be145ef57c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunHeader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunHeader.cs @@ -22,16 +22,12 @@ public class ComponentAssembly public readonly int AssemblyHeaderRVA; public readonly int AssemblyHeaderSize; - public ComponentAssembly(byte[] image, ref int curOffset) + public ComponentAssembly(NativeReader imageReader, ref int curOffset) { - CorHeaderRVA = BitConverter.ToInt32(image, curOffset); - curOffset += sizeof(int); - CorHeaderSize = BitConverter.ToInt32(image, curOffset); - curOffset += sizeof(int); - AssemblyHeaderRVA = BitConverter.ToInt32(image, curOffset); - curOffset += sizeof(int); - AssemblyHeaderSize = BitConverter.ToInt32(image, curOffset); - curOffset += sizeof(int); + CorHeaderRVA = imageReader.ReadInt32(ref curOffset); + CorHeaderSize = imageReader.ReadInt32(ref curOffset); + AssemblyHeaderRVA = imageReader.ReadInt32(ref curOffset); + AssemblyHeaderSize = imageReader.ReadInt32(ref curOffset); } } @@ -55,32 +51,32 @@ public ReadyToRunCoreHeader() { } - public ReadyToRunCoreHeader(byte[] image, ref int curOffset) + public ReadyToRunCoreHeader(NativeReader imageReader, ref int curOffset) { - ParseCoreHeader(image, ref curOffset); + ParseCoreHeader(imageReader, ref curOffset); } /// /// Parse core header fields common to global R2R file header and per assembly headers in composite R2R images. /// - /// PE image + /// PE Image reader /// Index in the image byte array to the start of the ReadyToRun core header - public void ParseCoreHeader(byte[] image, ref int curOffset) + public void ParseCoreHeader(NativeReader imageReader, ref int curOffset) { - Flags = NativeReader.ReadUInt32(image, ref curOffset); - int nSections = NativeReader.ReadInt32(image, ref curOffset); + Flags = imageReader.ReadUInt32(ref curOffset); + int nSections = imageReader.ReadInt32(ref curOffset); Sections = new Dictionary(); for (int i = 0; i < nSections; i++) { - int type = NativeReader.ReadInt32(image, ref curOffset); + int type = imageReader.ReadInt32(ref curOffset); var sectionType = (ReadyToRunSectionType)type; if (!Enum.IsDefined(typeof(ReadyToRunSectionType), type)) { throw new BadImageFormatException("Warning: Invalid ReadyToRun section type"); } - int sectionStartRva = NativeReader.ReadInt32(image, ref curOffset); - int sectionLength = NativeReader.ReadInt32(image, ref curOffset); + int sectionStartRva = imageReader.ReadInt32(ref curOffset); + int sectionLength = imageReader.ReadInt32(ref curOffset); Sections[sectionType] = new ReadyToRunSection(sectionType, sectionStartRva, sectionLength); } } @@ -124,28 +120,30 @@ public ReadyToRunHeader() { } /// /// Initializes the fields of the R2RHeader /// - /// PE image + /// PE Image reader /// Relative virtual address of the ReadyToRun header /// Index in the image byte array to the start of the ReadyToRun header /// The signature must be 0x00525452 - public ReadyToRunHeader(byte[] image, int rva, int curOffset) + public ReadyToRunHeader(NativeReader imageReader, int rva, int curOffset) { RelativeVirtualAddress = rva; int startOffset = curOffset; byte[] signature = new byte[sizeof(uint) - 1]; // -1 removes the null character at the end of the cstring - Array.Copy(image, curOffset, signature, 0, sizeof(uint) - 1); + imageReader.ReadSpanAt(ref curOffset, signature); + curOffset = startOffset; + Array.Copy(Array.Empty(), curOffset, signature, 0, sizeof(uint) - 1); SignatureString = Encoding.UTF8.GetString(signature); - Signature = NativeReader.ReadUInt32(image, ref curOffset); + Signature = imageReader.ReadUInt32(ref curOffset); if (Signature != READYTORUN_SIGNATURE) { throw new System.BadImageFormatException("Incorrect R2R header signature: " + SignatureString); } - MajorVersion = NativeReader.ReadUInt16(image, ref curOffset); - MinorVersion = NativeReader.ReadUInt16(image, ref curOffset); + MajorVersion = imageReader.ReadUInt16(ref curOffset); + MinorVersion = imageReader.ReadUInt16(ref curOffset); - ParseCoreHeader(image, ref curOffset); + ParseCoreHeader(imageReader, ref curOffset); Size = curOffset - startOffset; } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs index 67f581e8d8dfe6..10ab13a6e9eaad 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs @@ -399,32 +399,32 @@ public ReadyToRunMethod( switch (MethodHandle.Kind) { case HandleKind.MethodDefinition: + { + MethodDefinition methodDef = ComponentReader.MetadataReader.GetMethodDefinition((MethodDefinitionHandle)MethodHandle); + if (methodDef.RelativeVirtualAddress != 0) { - MethodDefinition methodDef = ComponentReader.MetadataReader.GetMethodDefinition((MethodDefinitionHandle)MethodHandle); - if (methodDef.RelativeVirtualAddress != 0) + MethodBodyBlock mbb = ComponentReader.ImageReader.GetMethodBody(methodDef.RelativeVirtualAddress); + if (!mbb.LocalSignature.IsNil) { - MethodBodyBlock mbb = ComponentReader.ImageReader.GetMethodBody(methodDef.RelativeVirtualAddress); - if (!mbb.LocalSignature.IsNil) - { - StandaloneSignature ss = ComponentReader.MetadataReader.GetStandaloneSignature(mbb.LocalSignature); - LocalSignature = ss.DecodeLocalSignature(typeProvider, genericContext); - } + StandaloneSignature ss = ComponentReader.MetadataReader.GetStandaloneSignature(mbb.LocalSignature); + LocalSignature = ss.DecodeLocalSignature(typeProvider, genericContext); } - Name = ComponentReader.MetadataReader.GetString(methodDef.Name); - Signature = methodDef.DecodeSignature(typeProvider, genericContext); - owningTypeHandle = methodDef.GetDeclaringType(); - genericParams = methodDef.GetGenericParameters(); } - break; + Name = ComponentReader.MetadataReader.GetString(methodDef.Name); + Signature = methodDef.DecodeSignature(typeProvider, genericContext); + owningTypeHandle = methodDef.GetDeclaringType(); + genericParams = methodDef.GetGenericParameters(); + } + break; case HandleKind.MemberReference: - { - MemberReference memberRef = ComponentReader.MetadataReader.GetMemberReference((MemberReferenceHandle)MethodHandle); - Name = ComponentReader.MetadataReader.GetString(memberRef.Name); - Signature = memberRef.DecodeMethodSignature(typeProvider, genericContext); - owningTypeHandle = memberRef.Parent; - } - break; + { + MemberReference memberRef = ComponentReader.MetadataReader.GetMemberReference((MemberReferenceHandle)MethodHandle); + Name = ComponentReader.MetadataReader.GetString(memberRef.Name); + Signature = memberRef.DecodeMethodSignature(typeProvider, genericContext); + owningTypeHandle = memberRef.Parent; + } + break; default: throw new NotImplementedException(); @@ -492,13 +492,13 @@ private void EnsureInitialized() int gcInfoOffset = _readyToRunReader.CompositeReader.GetOffset(GcInfoRva); if (_readyToRunReader.Machine == Machine.I386) { - _gcInfo = new x86.GcInfo(_readyToRunReader.Image, gcInfoOffset); + _gcInfo = new x86.GcInfo(_readyToRunReader.ImageReader, gcInfoOffset); } else { // Arm, Arm64, LoongArch64 and RISCV64 use the same GcInfo format as Amd64 _gcInfo = new Amd64.GcInfo( - _readyToRunReader.Image, + _readyToRunReader.ImageReader, gcInfoOffset, _readyToRunReader.Machine, _readyToRunReader.ReadyToRunHeader.MajorVersion, @@ -527,7 +527,7 @@ private void EnsureFixupCells() return; } _fixupCells = new List(); - NibbleReader reader = new NibbleReader(_readyToRunReader.Image, _fixupOffset.Value); + NibbleReader reader = new NibbleReader(_readyToRunReader.ImageReader, _fixupOffset.Value); // The following algorithm has been loosely ported from CoreCLR, // src\vm\ceeload.inl, BOOL Module::FixupDelayListAux @@ -585,7 +585,7 @@ private void ParseRuntimeFunctions(bool partial) curOffset = coldOffset; runtimeFunctionId = coldRuntimeFunctionId; } - int startRva = NativeReader.ReadInt32(_readyToRunReader.Image, ref curOffset); + int startRva = _readyToRunReader.ImageReader.ReadInt32(ref curOffset); if (_readyToRunReader.Machine == Machine.ArmThumb2) { // The low bit of this address is set since the function contains thumb code. @@ -595,35 +595,35 @@ private void ParseRuntimeFunctions(bool partial) int endRva = -1; if (_readyToRunReader.Machine == Machine.Amd64) { - endRva = NativeReader.ReadInt32(_readyToRunReader.Image, ref curOffset); + endRva = _readyToRunReader.ImageReader.ReadInt32(ref curOffset); } - int unwindRva = NativeReader.ReadInt32(_readyToRunReader.Image, ref curOffset); + int unwindRva = _readyToRunReader.ImageReader.ReadInt32(ref curOffset); int unwindOffset = _readyToRunReader.CompositeReader.GetOffset(unwindRva); BaseUnwindInfo unwindInfo = null; if (_readyToRunReader.Machine == Machine.I386) { - unwindInfo = new x86.UnwindInfo(_readyToRunReader.Image, unwindOffset); + unwindInfo = new x86.UnwindInfo(_readyToRunReader.ImageReader, unwindOffset); } else if (_readyToRunReader.Machine == Machine.Amd64) { - unwindInfo = new Amd64.UnwindInfo(_readyToRunReader.Image, unwindOffset); + unwindInfo = new Amd64.UnwindInfo(_readyToRunReader.ImageReader, unwindOffset); } else if (_readyToRunReader.Machine == Machine.ArmThumb2) { - unwindInfo = new Arm.UnwindInfo(_readyToRunReader.Image, unwindOffset); + unwindInfo = new Arm.UnwindInfo(_readyToRunReader.ImageReader, unwindOffset); } else if (_readyToRunReader.Machine == Machine.Arm64) { - unwindInfo = new Arm64.UnwindInfo(_readyToRunReader.Image, unwindOffset); + unwindInfo = new Arm64.UnwindInfo(_readyToRunReader.ImageReader, unwindOffset); } else if (_readyToRunReader.Machine == Machine.LoongArch64) { - unwindInfo = new LoongArch64.UnwindInfo(_readyToRunReader.Image, unwindOffset); + unwindInfo = new LoongArch64.UnwindInfo(_readyToRunReader.ImageReader, unwindOffset); } else if (_readyToRunReader.Machine == Machine.RiscV64) { - unwindInfo = new RiscV64.UnwindInfo(_readyToRunReader.Image, unwindOffset); + unwindInfo = new RiscV64.UnwindInfo(_readyToRunReader.ImageReader, unwindOffset); } if (i == 0 && unwindInfo != null) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs index 3dc6cbb9aae659..1838593a20fe25 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs @@ -141,8 +141,14 @@ public sealed class ReadyToRunReader /// Byte array containing the ReadyToRun image /// public byte[] Image { get; private set; } + private PinningReference ImagePin; + /// + /// NativeReader to read the image contents + /// + public NativeReader ImageReader { get; private set; } + /// /// Name of the image file /// @@ -407,6 +413,7 @@ public ReadyToRunReader(IAssemblyResolver assemblyResolver, IAssemblyMetadata me CompositeReader = peReader; Filename = filename; Image = ConvertToArray(content); + ImageReader = new NativeReader(new MemoryStream(Image)); Initialize(metadata); } @@ -433,6 +440,7 @@ public unsafe ReadyToRunReader(IAssemblyResolver assemblyResolver, string filena _assemblyResolver = assemblyResolver; Filename = filename; Image = ConvertToArray(content); + ImageReader = new NativeReader(new MemoryStream(Image)); Initialize(metadata: null); } @@ -484,6 +492,7 @@ private unsafe void Initialize(IAssemblyMetadata metadata) if (CompositeReader == null) { Image ??= File.ReadAllBytes(Filename); + ImageReader = new NativeReader(new MemoryStream(Image)); byte[] image = Image; ImagePin = new PinningReference(image); CompositeReader = new PEReader(Unsafe.As>(ref image)); @@ -492,6 +501,7 @@ private unsafe void Initialize(IAssemblyMetadata metadata) { ImmutableArray content = CompositeReader.GetEntireImage().GetContent(); Image = Unsafe.As, byte[]>(ref content); + ImageReader = new NativeReader(new MemoryStream(Image)); ImagePin = new PinningReference(Image); } @@ -560,7 +570,7 @@ internal void EnsureMethods() for (int i = 0; i < count; i++) { - mHotColdMap.Add(new List { NativeReader.ReadInt32(Image, ref hotColdMapOffset), NativeReader.ReadInt32(Image, ref hotColdMapOffset) }); + mHotColdMap.Add(new List { ImageReader.ReadInt32(ref hotColdMapOffset), ImageReader.ReadInt32(ref hotColdMapOffset) }); } for (int i = 0; i < count - 1; i++) @@ -687,7 +697,7 @@ private unsafe void EnsureHeader() // Initialize R2RHeader Debug.Assert(_readyToRunHeaderRVA != 0); int r2rHeaderOffset = GetOffset(_readyToRunHeaderRVA); - _readyToRunHeader = new ReadyToRunHeader(Image, _readyToRunHeaderRVA, r2rHeaderOffset); + _readyToRunHeader = new ReadyToRunHeader(ImageReader, _readyToRunHeaderRVA, r2rHeaderOffset); FindOwnerCompositeExecutable(); @@ -716,11 +726,11 @@ private void EnsureDebugInfo() int debugInfoSectionOffset = GetOffset(debugInfoSection.RelativeVirtualAddress); - NativeArray debugInfoArray = new NativeArray(Image, (uint)debugInfoSectionOffset); + NativeArray debugInfoArray = new NativeArray(ImageReader, (uint)debugInfoSectionOffset); for (uint i = 0; i < debugInfoArray.GetCount(); ++i) { int offset = 0; - if (!debugInfoArray.TryGetAt(Image, i, ref offset)) + if (!debugInfoArray.TryGetAt(i, ref offset)) { continue; } @@ -837,13 +847,13 @@ private void ParseMethodDefEntrypointsSection(ReadyToRunSection section, IAssemb { int assemblyIndex = GetAssemblyIndex(section); int methodDefEntryPointsOffset = GetOffset(section.RelativeVirtualAddress); - NativeArray methodEntryPoints = new NativeArray(Image, (uint)methodDefEntryPointsOffset); + NativeArray methodEntryPoints = new NativeArray(ImageReader, (uint)methodDefEntryPointsOffset); uint nMethodEntryPoints = methodEntryPoints.GetCount(); for (uint rid = 1; rid <= nMethodEntryPoints; rid++) { int offset = 0; - if (methodEntryPoints.TryGetAt(Image, rid - 1, ref offset)) + if (methodEntryPoints.TryGetAt(rid - 1, ref offset)) { EntityHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)rid); int runtimeFunctionId; @@ -870,13 +880,13 @@ private void ParseMethodDefEntrypointsSection(ReadyToRunSection section, IAssemb private void ParseMethodDefEntrypointsSectionCustom(IR2RSignatureTypeProvider provider, Dictionary foundMethods, ReadyToRunSection section, IAssemblyMetadata metadataReader) { int methodDefEntryPointsOffset = GetOffset(section.RelativeVirtualAddress); - NativeArray methodEntryPoints = new NativeArray(Image, (uint)methodDefEntryPointsOffset); + NativeArray methodEntryPoints = new NativeArray(ImageReader, (uint)methodDefEntryPointsOffset); uint nMethodEntryPoints = methodEntryPoints.GetCount(); for (uint rid = 1; rid <= nMethodEntryPoints; rid++) { int offset = 0; - if (methodEntryPoints.TryGetAt(Image, rid - 1, ref offset)) + if (methodEntryPoints.TryGetAt(rid - 1, ref offset)) { EntityHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)rid); int runtimeFunctionId; @@ -901,8 +911,8 @@ private void ParseInstanceMethodEntrypointsCustom(); int availableTypesOffset = GetOffset(availableTypesSection.RelativeVirtualAddress); - NativeParser parser = new NativeParser(Image, (uint)availableTypesOffset); - NativeHashtable availableTypes = new NativeHashtable(Image, parser, (uint)(availableTypesOffset + availableTypesSection.Size)); + NativeParser parser = new NativeParser(ImageReader, (uint)availableTypesOffset); + NativeHashtable availableTypes = new NativeHashtable(ImageReader, parser, (uint)(availableTypesOffset + availableTypesSection.Size)); NativeHashtable.AllEntriesEnumerator allEntriesEnum = availableTypes.EnumerateAllEntries(); NativeParser curParser = allEntriesEnum.GetNext(); while (!curParser.IsNull()) @@ -1396,10 +1406,10 @@ private void ParseComponentAssemblies() for (int assemblyIndex = 0; assemblyIndex < numberOfAssemblyHeaderRVAs; assemblyIndex++) { - ComponentAssembly assembly = new ComponentAssembly(Image, ref offset); + ComponentAssembly assembly = new ComponentAssembly(ImageReader, ref offset); int headerOffset = GetOffset(assembly.AssemblyHeaderRVA); - ReadyToRunCoreHeader assemblyHeader = new ReadyToRunCoreHeader(Image, ref headerOffset); + ReadyToRunCoreHeader assemblyHeader = new ReadyToRunCoreHeader(ImageReader, ref headerOffset); _readyToRunAssemblyHeaders.Add(assemblyHeader); _readyToRunAssemblies.Add(new ReadyToRunAssembly(this)); } @@ -1439,13 +1449,13 @@ private void EnsureImportSections() int endOffset = offset + importSectionsSection.Size; while (offset < endOffset) { - int rva = NativeReader.ReadInt32(Image, ref offset); + int rva = ImageReader.ReadInt32(ref offset); int sectionOffset = GetOffset(rva); int startOffset = sectionOffset; - int size = NativeReader.ReadInt32(Image, ref offset); - ReadyToRunImportSectionFlags flags = (ReadyToRunImportSectionFlags)NativeReader.ReadUInt16(Image, ref offset); - ReadyToRunImportSectionType type = (ReadyToRunImportSectionType)NativeReader.ReadByte(Image, ref offset); - byte entrySize = NativeReader.ReadByte(Image, ref offset); + int size = ImageReader.ReadInt32(ref offset); + ReadyToRunImportSectionFlags flags = (ReadyToRunImportSectionFlags)ImageReader.ReadUInt16(ref offset); + ReadyToRunImportSectionType type = (ReadyToRunImportSectionType)ImageReader.ReadByte(ref offset); + byte entrySize = ImageReader.ReadByte(ref offset); if (entrySize == 0) { switch (Machine) @@ -1471,7 +1481,7 @@ private void EnsureImportSections() { entryCount = size / entrySize; } - int signatureRVA = NativeReader.ReadInt32(Image, ref offset); + int signatureRVA = ImageReader.ReadInt32(ref offset); int signatureOffset = 0; if (signatureRVA != 0) @@ -1482,15 +1492,15 @@ private void EnsureImportSections() for (int i = 0; i < entryCount; i++) { int entryOffset = sectionOffset - startOffset; - long section = NativeReader.ReadInt64(Image, ref sectionOffset); - uint sigRva = NativeReader.ReadUInt32(Image, ref signatureOffset); + long section = ImageReader.ReadInt64(ref sectionOffset); + uint sigRva = ImageReader.ReadUInt32(ref signatureOffset); int sigOffset = GetOffset((int)sigRva); ReadyToRunSignature signature = MetadataNameFormatter.FormatSignature(_assemblyResolver, this, sigOffset); entries.Add(new ReadyToRunImportSection.ImportSectionEntry(entries.Count, entryOffset, entryOffset + rva, section, sigRva, signature)); _importSignatures.Add(rva + entrySize * i, signature); } - int auxDataRVA = NativeReader.ReadInt32(Image, ref offset); + int auxDataRVA = ImageReader.ReadInt32(ref offset); int auxDataOffset = 0; if (auxDataRVA != 0) { @@ -1539,13 +1549,13 @@ private void GetRuntimeFunctionIndexFromOffset(int offset, out int runtimeFuncti // get the id of the entry point runtime function from the MethodEntryPoints NativeArray uint id = 0; // the RUNTIME_FUNCTIONS index - offset = (int)NativeReader.DecodeUnsigned(Image, (uint)offset, ref id); + offset = (int)ImageReader.DecodeUnsigned((uint)offset, ref id); if ((id & 1) != 0) { if ((id & 2) != 0) { uint val = 0; - NativeReader.DecodeUnsigned(Image, (uint)offset, ref val); + ImageReader.DecodeUnsigned((uint)offset, ref val); offset -= (int)val; } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/RiscV64/UnwindInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/RiscV64/UnwindInfo.cs index 34411efe8e2ca7..34d14ca6ae04a1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/RiscV64/UnwindInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/RiscV64/UnwindInfo.cs @@ -75,11 +75,11 @@ public class UnwindInfo : BaseUnwindInfo public UnwindInfo() { } - public UnwindInfo(byte[] image, int offset) + public UnwindInfo(NativeReader imageReader, int offset) { uint startOffset = (uint)offset; - int dw = NativeReader.ReadInt32(image, ref offset); + int dw = imageReader.ReadInt32(ref offset); CodeWords = ExtractBits(dw, 27, 5); EpilogCount = ExtractBits(dw, 22, 5); EBit = ExtractBits(dw, 21, 1); @@ -91,7 +91,7 @@ public UnwindInfo(byte[] image, int offset) { // We have an extension word specifying a larger number of Code Words or Epilog Counts // than can be specified in the header word. - dw = NativeReader.ReadInt32(image, ref offset); + dw = imageReader.ReadInt32(ref offset); ExtendedCodeWords = ExtractBits(dw, 16, 8); ExtendedEpilogCount = ExtractBits(dw, 0, 16); } @@ -105,7 +105,7 @@ public UnwindInfo(byte[] image, int offset) { for (int scope = 0; scope < EpilogCount; scope++) { - dw = NativeReader.ReadInt32(image, ref offset); + dw = imageReader.ReadInt32(ref offset); Epilogs[scope] = new Epilog(scope, dw, startOffset); epilogStartAt[Epilogs[scope].EpilogStartIndex] = true; // an epilog starts at this offset in the unwind codes } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs index 7b6f466c2f1a3b..d7e6f5a9cd7bd1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs @@ -21,30 +21,30 @@ public GcInfo() { } /// /// based on GCDump::DumpGCTable /// - public GcInfo(byte[] image, int offset) + public GcInfo(NativeReader imageReader, int offset) { Offset = offset; - CodeLength = (int)NativeReader.DecodeUnsignedGc(image, ref offset); + CodeLength = (int)imageReader.DecodeUnsignedGc(ref offset); - Header = InfoHdrDecoder.DecodeHeader(image, ref offset, CodeLength); + Header = InfoHdrDecoder.DecodeHeader(imageReader, ref offset, CodeLength); - NoGCRegions = new NoGcRegionTable(image, Header, ref offset); + NoGCRegions = new NoGcRegionTable(imageReader, Header, ref offset); - SlotTable = new GcSlotTable(image, Header, ref offset); + SlotTable = new GcSlotTable(imageReader, Header, ref offset); Transitions = new Dictionary>(); if (Header.Interruptible) { - GetTransitionsFullyInterruptible(image, ref offset); + GetTransitionsFullyInterruptible(imageReader, ref offset); } else if (Header.EbpFrame) { - GetTransitionsEbpFrame(image, ref offset); + GetTransitionsEbpFrame(imageReader, ref offset); } else { - GetTransitionsNoEbp(image, ref offset); + GetTransitionsNoEbp(imageReader, ref offset); } Size = offset - Offset; @@ -79,7 +79,7 @@ private void AddNewTransition(BaseGcTransition transition) Transitions[transition.CodeOffset].Add(transition); } - private void ArgEncoding(byte[] image, ref uint isPop, ref uint argOffs, ref uint argCnt, ref uint curOffs, ref bool isThis, ref bool iptr) + private void ArgEncoding(NativeReader imageReader, ref uint isPop, ref uint argOffs, ref uint argCnt, ref uint curOffs, ref bool isThis, ref bool iptr) { if (isPop != 0) { @@ -101,7 +101,7 @@ private void ArgEncoding(byte[] image, ref uint isPop, ref uint argOffs, ref uin /// /// based on GCDump::DumpGCTable /// - private void GetTransitionsFullyInterruptible(byte[] image, ref int offset) + private void GetTransitionsFullyInterruptible(NativeReader imageReader, ref int offset) { uint argCnt = 0; bool isThis = false; @@ -112,7 +112,7 @@ private void GetTransitionsFullyInterruptible(byte[] image, ref int offset) { uint isPop; uint argOffs; - uint val = image[offset++]; + uint val = imageReader.ReadByte(ref offset); if ((val & 0x80) == 0) { @@ -143,7 +143,7 @@ private void GetTransitionsFullyInterruptible(byte[] image, ref int offset) curOffs += (val & 0x07); isPop = (val & 0x40); - ArgEncoding(image, ref isPop, ref argOffs, ref argCnt, ref curOffs, ref isThis, ref iptr); + ArgEncoding(imageReader, ref isPop, ref argOffs, ref argCnt, ref curOffs, ref isThis, ref iptr); continue; } @@ -177,21 +177,21 @@ private void GetTransitionsFullyInterruptible(byte[] image, ref int offset) iptr = true; break; case 0xB8: - val = NativeReader.DecodeUnsignedGc(image, ref offset); + val = imageReader.DecodeUnsignedGc(ref offset); curOffs += val; break; case 0xF8: case 0xFC: isPop = val & 0x04; - argOffs = NativeReader.DecodeUnsignedGc(image, ref offset); - ArgEncoding(image, ref isPop, ref argOffs, ref argCnt, ref curOffs, ref isThis, ref iptr); + argOffs = imageReader.DecodeUnsignedGc(ref offset); + ArgEncoding(imageReader, ref isPop, ref argOffs, ref argCnt, ref curOffs, ref isThis, ref iptr); break; case 0xFD: - argOffs = NativeReader.DecodeUnsignedGc(image, ref offset); + argOffs = imageReader.DecodeUnsignedGc(ref offset); AddNewTransition(new GcTransitionPointer((int)curOffs, argOffs, argCnt, Action.KILL, Header.EbpFrame)); break; case 0xF9: - argOffs = NativeReader.DecodeUnsignedGc(image, ref offset); + argOffs = imageReader.DecodeUnsignedGc(ref offset); argCnt += argOffs; break; default: @@ -203,7 +203,7 @@ private void GetTransitionsFullyInterruptible(byte[] image, ref int offset) /// /// based on GCDump::DumpGCTable /// - private void GetTransitionsEbpFrame(byte[] image, ref int offset) + private void GetTransitionsEbpFrame(NativeReader imageReader, ref int offset) { while (true) { @@ -218,7 +218,7 @@ private void GetTransitionsEbpFrame(byte[] image, ref int offset) uint curOffs = 0; // Get the next byte and check for a 'special' entry - uint encType = image[offset++]; + uint encType = imageReader.ReadByte(ref offset); GcTransitionCall transition = null; switch (encType) @@ -259,50 +259,50 @@ private void GetTransitionsEbpFrame(byte[] image, ref int offset) { // A small call entry curOffs += (val & 0x7F); - val = image[offset++]; + val = imageReader.ReadByte(ref offset); regMask = val >> 5; argMask = val & 0x1F; } break; case 0xFD: // medium encoding - argMask = image[offset++]; - val = image[offset++]; + argMask = imageReader.ReadByte(ref offset); + val = imageReader.ReadByte(ref offset); argMask |= (val & 0xF0) << 4; - nxt = image[offset++]; + nxt = imageReader.ReadByte(ref offset); curOffs += (val & 0x0F) + ((nxt & 0x1F) << 4); regMask = nxt >> 5; // EBX,ESI,EDI break; case 0xF9: // medium encoding with byrefs - curOffs += image[offset++]; - val = image[offset++]; + curOffs += imageReader.ReadByte(ref offset); + val = imageReader.ReadByte(ref offset); argMask = val & 0x1F; regMask = val >> 5; - val = image[offset++]; + val = imageReader.ReadByte(ref offset); byrefArgMask = val & 0x1F; byrefRegMask = val >> 5; break; case 0xFE: // large encoding case 0xFA: // large encoding with byrefs - val = image[offset++]; + val = imageReader.ReadByte(ref offset); regMask = val & 0x7; byrefRegMask = val >> 4; - curOffs += NativeReader.ReadUInt32(image, ref offset); - argMask = NativeReader.ReadUInt32(image, ref offset); + curOffs += imageReader.ReadUInt32(ref offset); + argMask = imageReader.ReadUInt32(ref offset); if (encType == 0xFA) // read byrefArgMask { - byrefArgMask = NativeReader.ReadUInt32(image, ref offset); + byrefArgMask = imageReader.ReadUInt32(ref offset); } break; case 0xFB: // huge encoding - val = image[offset++]; + val = imageReader.ReadByte(ref offset); regMask = val & 0x7; byrefRegMask = val >> 4; - curOffs = NativeReader.ReadUInt32(image, ref offset); - argCnt = NativeReader.ReadUInt32(image, ref offset); - argTabSize = NativeReader.ReadUInt32(image, ref offset); + curOffs = imageReader.ReadUInt32(ref offset); + argCnt = imageReader.ReadUInt32(ref offset); + argTabSize = imageReader.ReadUInt32(ref offset); argOffset = offset; offset += (int)argTabSize; break; @@ -326,7 +326,7 @@ argMask ... bitmask of pushed pointer arguments { do { - val = NativeReader.DecodeUnsignedGc(image, ref argOffset); + val = imageReader.DecodeUnsignedGc(ref argOffset); uint stkOffs = val & ~byref_OFFSET_FLAG; uint lowBit = val & byref_OFFSET_FLAG; @@ -346,7 +346,7 @@ argMask ... bitmask of pushed pointer arguments /// /// based on GCDump::DumpGCTable /// - private void SaveCallTransition(byte[] image, ref int offset, uint val, uint curOffs, uint callRegMask, bool callPndTab, uint callPndTabCnt, uint callPndMask, uint lastSkip, ref uint imask) + private void SaveCallTransition(NativeReader imageReader, ref int offset, uint val, uint curOffs, uint callRegMask, bool callPndTab, uint callPndTabCnt, uint callPndMask, uint lastSkip, ref uint imask) { uint iregMask, iargMask; iregMask = imask & 0xF; @@ -359,7 +359,7 @@ private void SaveCallTransition(byte[] image, ref int offset, uint val, uint cur { for (int i = 0; i < callPndTabCnt; i++) { - uint pndOffs = NativeReader.DecodeUnsignedGc(image, ref offset); + uint pndOffs = imageReader.DecodeUnsignedGc(ref offset); uint stkOffs = val & ~byref_OFFSET_FLAG; uint lowBit = val & byref_OFFSET_FLAG; @@ -377,7 +377,7 @@ private void SaveCallTransition(byte[] image, ref int offset, uint val, uint cur imask = lastSkip = 0; } - private void GetTransitionsNoEbp(byte[] image, ref int offset) + private void GetTransitionsNoEbp(NativeReader imageReader, ref int offset) { uint curOffs = 0; uint lastSkip = 0; @@ -385,7 +385,7 @@ private void GetTransitionsNoEbp(byte[] image, ref int offset) for (; ; ) { - uint val = image[offset++]; + uint val = imageReader.ReadByte(ref offset); if ((val & 0x80) == 0) { @@ -400,7 +400,7 @@ private void GetTransitionsNoEbp(byte[] image, ref int offset) else { // push 00100000 [pushCount] ESP push multiple items - uint pushCount = NativeReader.DecodeUnsignedGc(image, ref offset); + uint pushCount = imageReader.DecodeUnsignedGc(ref offset); AddNewTransition(new GcTransitionRegister((int)curOffs, Registers.ESP, Action.PUSH, false, false, (int)pushCount)); } } @@ -414,7 +414,7 @@ private void GetTransitionsNoEbp(byte[] image, ref int offset) // // skip 01000000 [Delta] Skip arbitrary sized delta // - skip = NativeReader.DecodeUnsignedGc(image, ref offset); + skip = imageReader.DecodeUnsignedGc(ref offset); curOffs += skip; lastSkip = skip; } @@ -450,7 +450,7 @@ private void GetTransitionsNoEbp(byte[] image, ref int offset) // CallPattern.DecodeCallPattern((val & 0x7f), out callArgCnt, out callRegMask, out callPndMask, out lastSkip); curOffs += lastSkip; - SaveCallTransition(image, ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); + SaveCallTransition(imageReader, ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); break; case 5: @@ -459,12 +459,12 @@ private void GetTransitionsNoEbp(byte[] image, ref int offset) // ArgMask=MMM Delta=commonDelta[DD] // callRegMask = val & 0xf; // EBP,EBX,ESI,EDI - val = image[offset++]; + val = imageReader.ReadByte(ref offset); callPndMask = (val & 0x7); callArgCnt = (val >> 3) & 0x7; lastSkip = CallPattern.callCommonDelta[val >> 6]; curOffs += lastSkip; - SaveCallTransition(image, ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); + SaveCallTransition(imageReader, ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); break; case 6: // @@ -472,16 +472,16 @@ private void GetTransitionsNoEbp(byte[] image, ref int offset) // Call ArgCnt,RegMask=RRR,ArgMask // callRegMask = val & 0xf; // EBP,EBX,ESI,EDI - callArgCnt = NativeReader.DecodeUnsignedGc(image, ref offset); - callPndMask = NativeReader.DecodeUnsignedGc(image, ref offset); - SaveCallTransition(image, ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); + callArgCnt = imageReader.DecodeUnsignedGc(ref offset); + callPndMask = imageReader.DecodeUnsignedGc(ref offset); + SaveCallTransition(imageReader, ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); break; case 7: switch (val & 0x0C) { case 0x00: // iptr 11110000 [IPtrMask] Arbitrary Interior Pointer Mask - imask = NativeReader.DecodeUnsignedGc(image, ref offset); + imask = imageReader.DecodeUnsignedGc(ref offset); AddNewTransition(new IPtrMask((int)curOffs, imask)); break; @@ -490,16 +490,16 @@ private void GetTransitionsNoEbp(byte[] image, ref int offset) break; case 0x08: - val = image[offset++]; + val = imageReader.ReadByte(ref offset); callRegMask = val & 0xF; imask = val >> 4; - lastSkip = NativeReader.ReadUInt32(image, ref offset); + lastSkip = imageReader.ReadUInt32(ref offset); curOffs += lastSkip; - callArgCnt = NativeReader.ReadUInt32(image, ref offset); - callPndTabCnt = NativeReader.ReadUInt32(image, ref offset); - callPndTabSize = NativeReader.ReadUInt32(image, ref offset); + callArgCnt = imageReader.ReadUInt32(ref offset); + callPndTabCnt = imageReader.ReadUInt32(ref offset); + callPndTabSize = imageReader.ReadUInt32(ref offset); callPndTab = true; - SaveCallTransition(image, ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); + SaveCallTransition(imageReader, ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); break; case 0x0C: return; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcSlotTable.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcSlotTable.cs index 534780b671834f..b4cf2795031d57 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcSlotTable.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcSlotTable.cs @@ -98,12 +98,12 @@ public override string ToString() public GcSlotTable() { } - public GcSlotTable(byte[] image, InfoHdrSmall header, ref int offset) + public GcSlotTable(NativeReader imageReader, InfoHdrSmall header, ref int offset) { GcSlots = new List(); - DecodeUntracked(image, header, ref offset); - DecodeFrameVariableLifetimeTable(image, header, ref offset); + DecodeUntracked(imageReader, header, ref offset); + DecodeFrameVariableLifetimeTable(imageReader, header, ref offset); } public override string ToString() @@ -124,7 +124,7 @@ public override string ToString() /// /// based on GCDump::DumpGCTable /// - private void DecodeUntracked(byte[] image, InfoHdrSmall header, ref int offset) + private void DecodeUntracked(NativeReader imageReader, InfoHdrSmall header, ref int offset) { uint calleeSavedRegs = 0; if (header.DoubleAlign) @@ -144,7 +144,7 @@ private void DecodeUntracked(byte[] image, InfoHdrSmall header, ref int offset) char reg = header.EbpFrame ? 'B' : 'S'; - stkOffsDelta = NativeReader.DecodeSignedGc(image, ref offset); + stkOffsDelta = imageReader.DecodeSignedGc(ref offset); int stkOffs = lastStkOffs - stkOffsDelta; lastStkOffs = stkOffs; @@ -165,15 +165,15 @@ private void DecodeUntracked(byte[] image, InfoHdrSmall header, ref int offset) /// /// based on GCDump::DumpGCTable /// - private void DecodeFrameVariableLifetimeTable(byte[] image, InfoHdrSmall header, ref int offset) + private void DecodeFrameVariableLifetimeTable(NativeReader imageReader, InfoHdrSmall header, ref int offset) { uint count = header.VarPtrTableSize; uint curOffs = 0; while (count-- > 0) { - uint varOffs = NativeReader.DecodeUnsignedGc(image, ref offset); - uint begOffs = NativeReader.DecodeUDelta(image, ref offset, curOffs); - uint endOffs = NativeReader.DecodeUDelta(image, ref offset, begOffs); + uint varOffs = imageReader.DecodeUnsignedGc(ref offset); + uint begOffs = imageReader.DecodeUDelta(ref offset, curOffs); + uint endOffs = imageReader.DecodeUDelta(ref offset, begOffs); uint lowBits = varOffs & 0x3; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/InfoHdr.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/InfoHdr.cs index b18706b7661dc2..7f2a22465f4a0d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/InfoHdr.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/InfoHdr.cs @@ -158,7 +158,8 @@ public override string ToString() } }; - public class InfoHdrDecoder { + public class InfoHdrDecoder + { private const uint HAS_VARPTR = 0xFFFFFFFF; private const uint HAS_UNTRACKED = 0xFFFFFFFF; @@ -181,14 +182,14 @@ public static InfoHdrSmall GetInfoHdr(byte encoding) /// Initialize the GcInfo header /// based on src\inc\gcdecoder.cpp DecodeHeader and GCDump::DumpInfoHdr /// - public static InfoHdrSmall DecodeHeader(byte[] image, ref int offset, int codeLength) + public static InfoHdrSmall DecodeHeader(NativeReader imageReader, ref int offset, int codeLength) { - byte nextByte = image[offset++]; + byte nextByte = imageReader.ReadByte(ref offset); byte encoding = (byte)(nextByte & 0x7f); InfoHdrSmall header = GetInfoHdr(encoding); while ((nextByte & (uint)InfoHdrAdjustConstants.MORE_BYTES_TO_FOLLOW) != 0) { - nextByte = image[offset++]; + nextByte = imageReader.ReadByte(ref offset); encoding = (byte)(nextByte & (uint)InfoHdrAdjustConstants.ADJ_ENCODING_MAX); if (encoding < (uint)InfoHdrAdjust.NEXT_FOUR_START) @@ -284,7 +285,7 @@ public static InfoHdrSmall DecodeHeader(byte[] image, ref int offset, int codeLe break; case (byte)InfoHdrAdjust.NEXT_OPCODE: - nextByte = image[offset++]; + nextByte = imageReader.ReadByte(ref offset); encoding = (byte)(nextByte & (int)InfoHdrAdjustConstants.ADJ_ENCODING_MAX); // encoding here always corresponds to codes in InfoHdrAdjust2 set if (encoding <= (int)InfoHdrAdjustConstants.SET_RET_KIND_MAX) @@ -347,29 +348,29 @@ public static InfoHdrSmall DecodeHeader(byte[] image, ref int offset, int codeLe if (header.UntrackedCnt == HAS_UNTRACKED) { header.HasArgTabOffset = true; - header.UntrackedCnt = NativeReader.DecodeUnsignedGc(image, ref offset); + header.UntrackedCnt = imageReader.DecodeUnsignedGc(ref offset); } if (header.VarPtrTableSize == HAS_VARPTR) { header.HasArgTabOffset = true; - header.VarPtrTableSize = NativeReader.DecodeUnsignedGc(image, ref offset); + header.VarPtrTableSize = imageReader.DecodeUnsignedGc(ref offset); } if (header.GsCookieOffset == HAS_GS_COOKIE_OFFSET) { - header.GsCookieOffset = NativeReader.DecodeUnsignedGc(image, ref offset); + header.GsCookieOffset = imageReader.DecodeUnsignedGc(ref offset); } if (header.SyncStartOffset == HAS_SYNC_OFFSET) { - header.SyncStartOffset = NativeReader.DecodeUnsignedGc(image, ref offset); - header.SyncEndOffset = NativeReader.DecodeUnsignedGc(image, ref offset); + header.SyncStartOffset = imageReader.DecodeUnsignedGc(ref offset); + header.SyncEndOffset = imageReader.DecodeUnsignedGc(ref offset); } if (header.RevPInvokeOffset == HAS_REV_PINVOKE_FRAME_OFFSET) { - header.RevPInvokeOffset = NativeReader.DecodeUnsignedGc(image, ref offset); + header.RevPInvokeOffset = imageReader.DecodeUnsignedGc(ref offset); } if (header.NoGCRegionCnt == HAS_NOGCREGIONS) { - header.NoGCRegionCnt = NativeReader.DecodeUnsignedGc(image, ref offset); + header.NoGCRegionCnt = imageReader.DecodeUnsignedGc(ref offset); } header.Epilogs = new List(); @@ -379,7 +380,7 @@ public static InfoHdrSmall DecodeHeader(byte[] image, ref int offset, int codeLe for (int i = 0; i < header.EpilogCount; i++) { - offs = NativeReader.DecodeUDelta(image, ref offset, offs); + offs = imageReader.DecodeUDelta(ref offset, offs); header.Epilogs.Add((int)offs); } } @@ -391,7 +392,7 @@ public static InfoHdrSmall DecodeHeader(byte[] image, ref int offset, int codeLe if (header.HasArgTabOffset) { - header.ArgTabOffset = NativeReader.DecodeUnsignedGc(image, ref offset); + header.ArgTabOffset = imageReader.DecodeUnsignedGc(ref offset); } return header; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/NoGcRegionTable.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/NoGcRegionTable.cs index 4dcc332cb7ce73..96123c94a69d39 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/NoGcRegionTable.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/NoGcRegionTable.cs @@ -23,7 +23,7 @@ public NoGcRegion(uint offset, uint size) public override string ToString() { - return $" [{Offset:04X}-{Offset+Size:04X})\n"; + return $" [{Offset:04X}-{Offset + Size:04X})\n"; } } @@ -31,15 +31,15 @@ public override string ToString() public NoGcRegionTable() { } - public NoGcRegionTable(byte[] image, InfoHdrSmall header, ref int offset) + public NoGcRegionTable(NativeReader imageReader, InfoHdrSmall header, ref int offset) { Regions = new List((int)header.NoGCRegionCnt); uint count = header.NoGCRegionCnt; while (count-- > 0) { - uint regionOffset = NativeReader.DecodeUnsignedGc(image, ref offset); - uint regionSize = NativeReader.DecodeUnsignedGc(image, ref offset); + uint regionOffset = imageReader.DecodeUnsignedGc(ref offset); + uint regionSize = imageReader.DecodeUnsignedGc(ref offset); Regions.Add(new NoGcRegion(regionOffset, regionSize)); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/UnwindInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/UnwindInfo.cs index 6c699eec54bc14..8f5d1245026796 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/UnwindInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/UnwindInfo.cs @@ -14,10 +14,10 @@ public class UnwindInfo : BaseUnwindInfo public UnwindInfo() { } - public UnwindInfo(byte[] image, int offset) + public UnwindInfo(NativeReader imageReader, int offset) { int startOffset = offset; - FunctionLength = NativeReader.DecodeUnsignedGc(image, ref offset); + FunctionLength = imageReader.DecodeUnsignedGc(ref offset); Size = offset - startOffset; } From 9c2093ab88632c5580dfcba43db474f6e5f85543 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 8 Jul 2025 19:43:58 -0400 Subject: [PATCH 34/54] try using existing NativeReader --- ...ExecutionManagerCore.ReadyToRunJitManager.cs | 17 +++++++++++------ ...gnostics.DataContractReader.Contracts.csproj | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index 9ef36040029694..78c14957c9fde5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -85,16 +85,21 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd(r2rInfo.DebugInfo); - NativeReader reader = new NativeReader(Target, imageBase); - NativeArray debugInfoArray = new NativeArray(reader, debugInfoData.VirtualAddress); - - if (!debugInfoArray.TryGetAt(index, out uint offset)) + ILCompiler.Reflection.ReadyToRun.NativeReader imageReader = new( + new TargetStream(Target, imageBase, debugInfoData.Size), + Target.IsLittleEndian + ); + ILCompiler.Reflection.ReadyToRun.NativeArray debugInfoArray = new(imageReader, debugInfoData.VirtualAddress); + + int offset = 0; + if (!debugInfoArray.TryGetAt(index, ref offset)) // If the index is not found in the debug info array, return null return TargetPointer.Null; - uint debugInfoOffset = reader.DecodeUnsigned(offset, out uint lookBack); + uint lookBack = 0; + uint debugInfoOffset = imageReader.DecodeUnsigned((uint)offset, ref lookBack); if (lookBack != 0) - debugInfoOffset = offset - lookBack; + debugInfoOffset = (uint)offset - lookBack; return imageBase + debugInfoOffset; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj index de4256093c7601..ef8088dc5f4417 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj @@ -17,5 +17,6 @@ + From dc896128254d7b6c15946a85e055f85fb3082d24 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Thu, 10 Jul 2025 17:57:55 -0400 Subject: [PATCH 35/54] try using existing NativeReader --- .../NibbleReader.cs | 2 +- .../Contracts/DebugInfo/DebugInfo_1.cs | 23 +++--- .../Contracts/DebugInfo/DebugStreamReader.cs | 72 ------------------- 3 files changed, 14 insertions(+), 83 deletions(-) delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs index 26d4599b6a44b2..a000c383e101a9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs @@ -7,7 +7,7 @@ namespace ILCompiler.Reflection.ReadyToRun /// Helper to read memory by 4-bit (half-byte) nibbles as is used for encoding /// method fixups. More or less ported over from CoreCLR src\inc\nibblestream.h. /// - class NibbleReader + public class NibbleReader { /// /// Special value in _nextNibble saying there's no next nibble and the next byte diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs index 23b71e3e4eba0b..8d3d3820077720 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using ILCompiler.Reflection.ReadyToRun; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -107,18 +108,20 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, b Debug.Assert(flagByte == 0); } - DebugStreamReader reader = new(_target, debugInfo, 12 /*maximum size of 2 32bit ints compressed*/); + NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 12 /*maximum size of 2 32bit ints compressed*/), _target.IsLittleEndian); + NibbleReader nibbleReader = new(nibbleNativeReader, 0); - uint cbBounds = reader.ReadEncodedU32(); - uint _ /*cbVars*/ = reader.ReadEncodedU32(); + uint cbBounds = nibbleReader.ReadUInt(); + uint _ /*cbVars*/ = nibbleReader.ReadUInt(); - TargetPointer addrBounds = debugInfo + reader.NextByteIndex; + TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); if (cbBounds > 0) { - DebugStreamReader boundsReader = new(_target, addrBounds, cbBounds); + NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); + NibbleReader boundsReader = new(boundsNativeReader, 0); - uint countEntries = boundsReader.ReadEncodedU32(); + uint countEntries = boundsReader.ReadUInt(); Debug.Assert(countEntries > 0, "Expected at least one entry in bounds."); return DoBounds(boundsReader, countEntries); @@ -127,18 +130,18 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, b return Enumerable.Empty(); } - private static IEnumerable DoBounds(DebugStreamReader reader, uint count) + private static IEnumerable DoBounds(NibbleReader reader, uint count) { uint nativeOffset = 0; for (uint i = 0; i < count; i++) { // native offsets are encoded as a delta from the previous offset - nativeOffset += reader.ReadEncodedU32(); + nativeOffset += reader.ReadUInt(); // il offsets are encoded with a bias of ICorDebugInfo::MAX_MAPPING_VALUE - uint ilOffset = unchecked(reader.ReadEncodedU32() + IL_OFFSET_BIAS); + uint ilOffset = unchecked(reader.ReadUInt() + IL_OFFSET_BIAS); - SourceTypes_1 sourceType = (SourceTypes_1)reader.ReadEncodedU32(); + SourceTypes_1 sourceType = (SourceTypes_1)reader.ReadUInt(); // TODO(cdac debugInfo): Handle cookie diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs deleted file mode 100644 index 83f85a51399f7c..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugStreamReader.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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; - -internal sealed class DebugStreamReader -{ - private readonly uint _size; - - private byte[] _buffer; - private int _currentNibble; - - public uint NextByteIndex => (uint)((_currentNibble + 1) / 2); - - public DebugStreamReader(Target target, TargetPointer address, uint size) - { - _size = size; - - _buffer = new byte[size]; - target.ReadBuffer(address, _buffer); - } - - public uint ReadEncodedU32() - { - uint result = 0; - uint nibsRead = 0; - Nibble nib; - do - { - if (nibsRead > 11) - { - throw new InvalidOperationException("Corrupt nibble stream, too many nibbles read for encoded uint32."); - } - - nib = ReadNibble(); - result = (result << 3) + nib.Data; - - nibsRead++; - } while (!nib.IsEndOfWord); - return result; - } - - private Nibble ReadNibble() - { - if (_currentNibble / 2 >= _size) throw new InvalidOperationException("No more nibbles to read."); - - byte b = _buffer[_currentNibble / 2]; - Nibble nib; - if (_currentNibble % 2 == 0) - { - // Read the low nibble first - nib = new Nibble((byte)(b & 0xF)); - } - else - { - // Read the high nibble after the low nibble has been read - nib = new Nibble((byte)((b >> 4) & 0xF)); - } - _currentNibble++; - return nib; - } - - private readonly struct Nibble(byte data) - { - private readonly byte _data = data; - - public readonly byte Data => (byte)(_data & 0x7); - public readonly bool IsEndOfWord => (_data & 0x8) == 0; - } -} From 2b0507d7d5d32e8265d16996205f5826462efbf5 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Thu, 10 Jul 2025 19:33:55 -0400 Subject: [PATCH 36/54] share logic using code instead of project import --- .../aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs | 4 ++-- .../aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs | 4 ++-- .../Contracts/DebugInfo/DebugInfo_1.cs | 2 -- ...crosoft.Diagnostics.DataContractReader.Contracts.csproj | 7 ++++++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs index d241e5c76d2296..ba05b0b6e3cf42 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs @@ -56,7 +56,7 @@ public bool TryGetAt(uint index, ref int pOffset) if (index >= _nElements) return false; - uint offset = 0; + uint offset; if (_entryIndexSize == 0) { int i = (int)(_baseOffset + (index / _blockSize)); @@ -82,7 +82,7 @@ public bool TryGetAt(uint index, ref int pOffset) { if ((val & 2) != 0) { - offset = offset + (val >> 2); + offset += (val >> 2); continue; } } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs index 6641bb0c2511f4..98441b366d14c9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs @@ -29,7 +29,7 @@ public void ReadSpanAt(ref int start, Span buffer) throw new ArgumentOutOfRangeException(nameof(start), "Start index is out of bounds"); _backingStream.Seek(start, SeekOrigin.Begin); - _backingStream.Read(buffer); + _backingStream.ReadExactly(buffer); start += buffer.Length; } @@ -372,4 +372,4 @@ public uint DecodeUDelta(ref int start, uint lastValue) return lastValue + delta; } } -} \ No newline at end of file +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs index 8d3d3820077720..675ac2eb53dddb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -143,8 +143,6 @@ private static IEnumerable DoBounds(NibbleReader reader, uint co SourceTypes_1 sourceType = (SourceTypes_1)reader.ReadUInt(); - // TODO(cdac debugInfo): Handle cookie - yield return new OffsetMapping_1 { NativeOffset = nativeOffset, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj index ef8088dc5f4417..b409a9d247839c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj @@ -17,6 +17,11 @@ - + + + + + + From 0c8f8b8790e8e7e97f865abfcb2b6fb704478c49 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 11 Aug 2025 14:38:33 -0400 Subject: [PATCH 37/54] start fixing merge issues --- .../NativeReader.cs | 2 +- .../vm/datadescriptor/datadescriptor.h | 1 + .../ContractRegistry.cs | 4 -- .../Contracts/IECall.cs | 18 -------- .../Contracts/ECallFactory.cs | 16 ------- .../Contracts/ECall_1.cs | 45 ------------------- .../Data/ECHash.cs | 21 --------- .../CachingContractRegistry.cs | 2 - 8 files changed, 2 insertions(+), 107 deletions(-) delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IECall.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECallFactory.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ECHash.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs index 44d6c238005318..58964e235984f4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs @@ -382,4 +382,4 @@ public uint DecodeUDelta(ref int start, uint lastValue) return lastValue + delta; } } -} \ No newline at end of file +} diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.h b/src/coreclr/vm/datadescriptor/datadescriptor.h index 84adff2a49b4d3..2123bd81f8ebce 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.h +++ b/src/coreclr/vm/datadescriptor/datadescriptor.h @@ -18,6 +18,7 @@ #include "configure.h" #include "../debug/ee/debugger.h" +#include "patchpointinfo.h" #ifdef HAVE_GCCOVER #include "gccover.h" diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 7caf8553797fae..61ca9fc81f842a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -68,10 +68,6 @@ public abstract class ContractRegistry /// public abstract IRuntimeInfo RuntimeInfo { get; } /// - /// Gets an instance of the ECall contract for the target. - /// - public abstract IECall ECall { get; } - /// /// Gets an instance of the DebugInfo contract for the target. /// public abstract IDebugInfo DebugInfo { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IECall.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IECall.cs deleted file mode 100644 index 34e0965ed8145b..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IECall.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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 interface IECall : IContract -{ - static string IContract.Name { get; } = nameof(ECall); - - TargetPointer MapTargetBackToMethodDesc(TargetCodePointer codePointer) => throw new NotImplementedException(); -} - -public readonly struct ECall : IECall -{ - // throws NotImplementedException for all methods -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECallFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECallFactory.cs deleted file mode 100644 index 10e160aa764fe6..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECallFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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 ECallFactory : IContractFactory -{ - IECall IContractFactory.CreateContract(Target target, int version) - { - return version switch - { - 1 => new ECall_1(target), - _ => default(ECall), - }; - } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs deleted file mode 100644 index 7c2cbe1414907e..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ECall_1.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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; - -internal readonly struct ECall_1 : IECall -{ - private readonly Target _target; - - internal ECall_1(Target target) - { - _target = target; - } - - TargetPointer IECall.MapTargetBackToMethodDesc(TargetCodePointer codePointer) - { - if (codePointer == TargetCodePointer.Null) - return TargetPointer.Null; - - - TargetPointer pECHashTable = _target.ReadGlobalPointer(Constants.Globals.FCallMethods); - - TargetPointer pECHash = pECHashTable + (ulong)(FCallHash(codePointer) * _target.PointerSize); - TargetPointer ecHashAddr = _target.ReadPointer(pECHash); - - while (ecHashAddr != TargetPointer.Null) - { - Data.ECHash eCHash = _target.ProcessedData.GetOrAdd(ecHashAddr); - if (eCHash.Implementation == codePointer) - { - return eCHash.MethodDesc; - } - ecHashAddr = eCHash.Next; - } - - return TargetPointer.Null; // not found - } - - private uint FCallHash(TargetCodePointer codePointer) - { - // see FCallHash in ecall.cpp - uint hashBucketCount = _target.ReadGlobal(Constants.Globals.FCallHashSize); - return (uint)(codePointer.Value % hashBucketCount); - } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ECHash.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ECHash.cs deleted file mode 100644 index d64aaf65ea24eb..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ECHash.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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.Data; - -internal sealed class ECHash : IData -{ - static ECHash IData.Create(Target target, TargetPointer address) => new ECHash(target, address); - public ECHash(Target target, TargetPointer address) - { - Target.TypeInfo type = target.GetTypeInfo(DataType.ECHash); - - Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); - Implementation = target.ReadCodePointer(address + (ulong)type.Fields[nameof(Implementation)].Offset); - MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); - } - - public TargetPointer Next { get; init; } - public TargetCodePointer Implementation { get; init; } - public TargetPointer MethodDesc { get; init; } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index e31964cfaf9f22..d979a8f7705727 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -40,7 +40,6 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IReJIT)] = new ReJITFactory(), [typeof(IStackWalk)] = new StackWalkFactory(), [typeof(IRuntimeInfo)] = new RuntimeInfoFactory(), - [typeof(IECall)] = new ECallFactory(), [typeof(IDebugInfo)] = new DebugInfoFactory(), }; configureFactories?.Invoke(_factories); @@ -60,7 +59,6 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IReJIT ReJIT => GetContract(); public override IStackWalk StackWalk => GetContract(); public override IRuntimeInfo RuntimeInfo => GetContract(); - public override IECall ECall => GetContract(); public override IDebugInfo DebugInfo => GetContract(); private TContract GetContract() where TContract : IContract From cb06f9bc7bee8886e78caa88553e85c9e30dcbc8 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 11 Aug 2025 14:47:11 -0400 Subject: [PATCH 38/54] merge fixes --- docs/design/datacontracts/DebugInfo.md | 167 ++++++++++++++++++ docs/design/datacontracts/ExecutionManager.md | 22 ++- docs/design/datacontracts/Loader.md | 71 +++++++- src/coreclr/inc/patchpointinfo.h | 16 ++ .../NativeReader.cs | 2 +- .../NibbleReader.cs | 2 +- src/coreclr/vm/codeman.h | 9 + src/coreclr/vm/datadescriptor/contracts.jsonc | 1 + .../vm/datadescriptor/datadescriptor.h | 1 + .../vm/datadescriptor/datadescriptor.inc | 17 ++ src/coreclr/vm/readytoruninfo.cpp | 1 + src/coreclr/vm/readytoruninfo.h | 2 + .../ContractRegistry.cs | 4 + .../Contracts/IDebugInfo.cs | 35 ++++ .../Contracts/IExecutionManager.cs | 1 + .../DataType.cs | 2 + .../Constants.cs | 4 + .../Contracts/DebugInfo/DebugInfoFactory.cs | 16 ++ .../Contracts/DebugInfo/DebugInfo_1.cs | 154 ++++++++++++++++ .../ExecutionManagerCore.EEJitManager.cs | 24 +++ ...ecutionManagerCore.ReadyToRunJitManager.cs | 34 ++++ .../ExecutionManager/ExecutionManagerCore.cs | 15 ++ .../ExecutionManager/ExecutionManager_1.cs | 1 + .../ExecutionManager/ExecutionManager_2.cs | 1 + .../ExecutionManager/Helpers/NativeArray.cs | 77 ++++++++ .../ExecutionManager/Helpers/NativeReader.cs | 63 +++++++ .../Data/EEJitManager.cs | 17 ++ .../Data/ImageDataDirectory.cs | 2 +- .../Data/PatchpointInfo.cs | 21 +++ .../Data/ReadyToRunInfo.cs | 2 + .../Data/RealCodeHeader.cs | 4 + ...ostics.DataContractReader.Contracts.csproj | 6 + .../CachingContractRegistry.cs | 2 + .../Legacy/ClrDataMethodInstance.cs | 77 +++++++- 34 files changed, 867 insertions(+), 6 deletions(-) create mode 100644 docs/design/datacontracts/DebugInfo.md create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEJitManager.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PatchpointInfo.cs diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md new file mode 100644 index 00000000000000..6ad3aeee290868 --- /dev/null +++ b/docs/design/datacontracts/DebugInfo.md @@ -0,0 +1,167 @@ +# Contract DebugInfo + +This contract is for fetching information related to DebugInfo associated with native code. + +## APIs of contract + +```csharp +public enum SourceTypes : uint +{ + SourceTypeInvalid = 0x00, // To indicate that nothing else applies + SequencePoint = 0x01, // The debugger asked for it. + StackEmpty = 0x02, // The stack is empty here + CallSite = 0x04, // This is a call site. + NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint + CallInstruction = 0x10 // The actual instruction of a call. +} +``` + +```csharp +public interface IOffsetMapping +{ + public uint NativeOffset { get; } + public uint ILOffset { get; } + public SourceTypes SourceType { get; } +} +``` + +```csharp +// Given a code pointer, return the associated native/IL offset mapping +IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `PatchpointInfo` | `LocalCount` | Number of locals in the method associated with the patchpoint. | + +Contracts used: +| Contract Name | +| --- | +| `CodeVersions` | +| `ExecutionManager` | + +Constants: +| Constant Name | Meaning | Value | +| --- | --- | --- | +| IL_OFFSET_BIAS | IL offsets are encoded in the DebugInfo with this bias. | `0xfffffffd` (-3) | +| EXTRA_DEBUG_INFO_PATCHPOINT | Indicates debug info contains patchpoint information | 0x1 | +| EXTRA_DEBUG_INFO_RICH | Indicates debug info contains rich information | 0x2 | +| SourceTypeInvalid | To indicate that nothing else applies | 0x00 | +| SourceTypeSequencePoint | Source type indicating the debugger asked for it | 0x01 | +| SourceTypeStackEmpty | Source type indicating the stack is empty here | 0x02 | +| SourceTypeCallSite | Source type indicating This is a call site | 0x04 | +| SourceTypeNativeEndOffsetUnknown | Source type indicating a epilog endpoint | 0x08 | +| SourceTypeCallInstruction | Source type indicating the actual instruction of a call | 0x10 | + +### DebugInfo Stream Encoding + +The DebugInfo stream is encoded using varible length 32-bit values with the following scheme: + +A value can be stored using one or more nibbles (a nibble is a 4-bit value). 3 bits of a nibble are used to store 3 bits of the value, and the top bit indicates if the following nibble contains rest of the value. If the top bit is not set, then this nibble is the last part of the value. The higher bits of the value are written out first, and the lowest 3 bits are written out last. + +In the encoded stream of bytes, the lower nibble of a byte is used before the high nibble. + +A binary value ABCDEFGHI (where A is the highest bit) is encoded as +the follow two bytes : 1DEF1ABC XXXX0GHI + +Examples: +| Decimal Value | Hex Value | Encoded Result | +| --- | --- | --- | +| 0 | 0x0 | X0 | +| 1 | 0x1 | X1 | +| 7 | 0x7 | X7 | +| 8 | 0x8 | 09 | +| 9 | 0x9 | 19 | +| 63 | 0x3F | 7F | +| 64 | 0x40 | F9 X0 | +| 65 | 0x41 | F9 X1 | +| 511 | 0x1FF | FF X7 | +| 512 | 0x200 | 89 08 | +| 513 | 0x201 | 89 18 | + +Based on the encoding specification, we use an decoder with the following interface: + +```csharp +class DebugStreamReader(TargetPointer startOfBuffer, uint sizeOfBuffer) +{ + // Returns the offset of the next byte relative to the startOfBuffer + public uint GetNextByteIndex(); + + // Reads the next encoded value and moves to the following nibble + public uint ReadEncodedU32(); +} +``` + +### Implementation + +``` csharp +IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) +{ + // Get the method's DebugInfo + if (/*ExecutionManager*/.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) + throw NotValid // pCode must point to a valid code block + TargetPointer debugInfo = /*ExecutionManager*/.GetDebugInfo(cbh, out bool hasFlagByte); + + TargetCodePointer nativeCodeStart = /*ExecutionManager*/.GetStartAddress(cbh); + + TargetPointer startAddress = /*convert nativeCodeStart to a TargetPointer*/ + TargetPointer currAddress = /*convert pCode to a TargetPointer*/ + codeOffset = currAddress - startAddress; + + if (hasFlagByte) + { + // Check flag byte and skip over any patchpoint info + byte flagByte = _target.Read(debugInfo++); + + if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) != 0) + { + uint localCount = _target.Read(debugInfo + /*PatchpointInfo::LocalCount offset*/) + debugInfo += /*size of PatchpointInfo*/ + (localCount * 4); + } + + if ((flagByte & EXTRA_DEBUG_INFO_RICH) != 0) + { + uint richDebugInfoSize = _target.Read(debugInfo); + debugInfo += 4; + debugInfo += richDebugInfoSize; + } + } + + DebugStreamReader reader = new(debugInfo, 12 /*maximum size of 2 32bit ints compressed*/); + + uint cbBounds = reader.ReadEncodedU32(); + uint _ /*cbVars*/ = reader.ReadEncodedU32(); + + TargetPointer addrBounds = debugInfo + reader.NextByteIndex; + + if (cbBounds == 0) + // No bounds data was found, return an empty enumerable + return Enumerable.Empty(); + + // Create a DebugInfo stream decoder with the start address and size of bounds data + DebugStreamReader boundsReader = new(addrBounds, cbBounds); + + uint countEntries = boundsReader.ReadEncodedU32(); + uint nativeOffset = 0; + for (uint i = 0; i < count; i++) + { + // native offsets are encoded as a delta from the previous offset + nativeOffset += boundsReader.ReadEncodedU32(); + + // il offsets are encoded with a bias of IL_OFFSET_BIAS + uint ilOffset = unchecked(boundsReader.ReadEncodedU32() + IL_OFFSET_BIAS); + + uint sourceType = boundsReader.ReadEncodedU32(); + + yield return new OffsetMapping_1 + { + NativeOffset = nativeOffset, + ILOffset = ilOffset, + InternalSourceType = sourceType + }; + } +} +``` \ No newline at end of file diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 1b5e360f6ec308..0b7209cc3599cd 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -29,6 +29,9 @@ struct CodeBlockHandle TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle); // Gets the base address the UnwindInfo of codeInfoHandle is relative to TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle); + // Gets the DebugInfo associated with the code block and specifies if the DebugInfo contains + // the flag byte which modifies how DebugInfo is parsed. + TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte); // Gets the GCInfo associated with the code block and its version // **Currently GetGCInfo only supports X86** void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion); @@ -66,9 +69,11 @@ Data descriptors used: | `CodeHeapListNode` | `EndAddress` | End address of the used portion of the code heap | | `CodeHeapListNode` | `MapBase` | Start of the map - start address rounded down based on OS page size | | `CodeHeapListNode` | `HeaderMap` | Bit array used to find the start of methods - relative to `MapBase` | +| `EEJitManager` | `StoreRichDebugInfo` | Boolean value determining if debug info associated with the JitManager contains rich info. | | `RealCodeHeader` | `MethodDesc` | Pointer to the corresponding `MethodDesc` | | `RealCodeHeader` | `NumUnwindInfos` | Number of Unwind Infos | | `RealCodeHeader` | `UnwindInfos` | Start address of Unwind Infos | +| `RealCodeHeader` | `DebugInfo` | Pointer to the DebugInfo | | `RealCodeHeader` | `GCInfo` | Pointer to the GCInfo encoding | | `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module | | `ReadyToRunInfo` | `ReadyToRunHeader` | Pointer to the ReadyToRunHeader | @@ -78,6 +83,7 @@ Data descriptors used: | `ReadyToRunInfo` | `NumHotColdMap` | Number of entries in the `HotColdMap` | | `ReadyToRunInfo` | `HotColdMap` | Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) | | `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | Pointer to an `ImageDataDirectory` for the delay load method call thunks | +| `ReadyToRunInf` | `DebugInfo` | Pointer to an `ImageDataDirectory` for the debug info | | `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` of entry point addresses to `MethodDesc` pointers | | `ReadyToRunHeader` | `MajorVersion` | ReadyToRun major version | | `ReadyToRunHeader` | `MinorVersion` | ReadyToRun minor version | @@ -100,6 +106,7 @@ Global variables used: | `HashMapValueMask` | uint64 | Bitmask used when storing values in a `HashMap` | | `FeatureEHFunclets` | uint8 | 1 if EH funclets are enabled, 0 otherwise | | `GCInfoVersion` | uint32 | JITted code GCInfo version | +| `FeatureOnStackReplacement` | uint8 | 1 if FEATURE_ON_STACK_REPLACEMENT is enabled, 0 otherwise | Contracts used: | Contract Name | @@ -266,9 +273,16 @@ The `GetMethodDesc`, `GetStartAddress`, and `GetRelativeOffset` APIs extract fie Unwind info (`RUNTIME_FUNCTION`) use relative addressing. For managed code, these values are relative to the start of the code's containing range in the RangeSectionMap (described below). This could be the beginning of a `CodeHeap` for jitted code or the base address of the loaded image for ReadyToRun code. `GetUnwindInfoBaseAddress` finds this base address for a given `CodeBlockHandle`. +`IExecutionManager.GetDebugInfo` gets a pointer to the relevant DebugInfo for a `CodeBlockHandle`. The ExecutionManager delegates to the JitManager implementations as the DebugInfo is stored in different ways on jitted and R2R code. + +* For Jitted code (`EEJitManager`) a pointer to the `DebugInfo` is stored on the `RealCodeHeader` which is accessed in the same way as `GetMethodInfo` described above. `hasFlagByte` is `true` if either the global `FeatureOnStackReplacement` is `true` or `StoreRichDebugInfo` is `true` on the `EEJitManager`. + +* For R2R code (`ReadyToRunJitManager`) the `DebugInfo` is stored as part of the R2R image. The relevant `ReadyToRunInfo` stores a pointer to the an `ImageDataDirectory` representing the `DebugInfo` directory. Read the `VirtualAddress` of this data directory as a `NativeArray` containing the `DebugInfos`. To find the specific `DebugInfo`, index into the array using the `index` of the beginning of the R2R function as found like in `GetMethodInfo` above. This yields an offset `offset` value relative to the image base. Read the first variable length uint at `imageBase + offset`, `lookBack`. If `lookBack != 0`, return `imageBase + offset - lookback`. Otherwise return `offset + size of reading lookback`. +For R2R images, `hasFlagByte` is always `false`. + `IExecutionManager.GetGCInfo` gets a pointer to the relevant GCInfo for a `CodeBlockHandle`. The ExecutionManager delegates to the JitManager implementations as the GCInfo is stored differently on jitted and R2R code. -* For jitted code (`EEJitManager`) a pointer to the `GCInfo` is stored on the `RealCodeHeader` which is accessed in the same was as `GetMethodInfo` described above. This can simply be returned as is. The `GCInfoVersion` is defined by the runtime global `GCInfoVersion`. +* For jitted code (`EEJitManager`) a pointer to the `GCInfo` is stored on the `RealCodeHeader` which is accessed in the same way as `GetMethodInfo` described above. This can simply be returned as is. The `GCInfoVersion` is defined by the runtime global `GCInfoVersion`. * For R2R code (`ReadyToRunJitManager`), the `GCInfo` is stored directly after the `UnwindData`. This in turn is found by looking up the `UnwindInfo` (`RUNTIME_FUNCTION`) and reading the `UnwindData` offset. We find the `UnwindInfo` as described above in `IExecutionManager.GetUnwindInfo`. Once we have the relevant unwind data, we calculate the size of the unwind data and return a pointer to the following byte (first byte of the GCInfo). The size of the unwind data is a platform specific. Currently only X86 is supported with a constant unwind data size of 32-bits. * The `GCInfoVersion` of R2R code is mapped from the R2R MajorVersion and MinorVersion which is read from the ReadyToRunHeader which itself is read from the ReadyToRunInfo (can be found as in GetMethodInfo). The current GCInfoVersion mapping is: @@ -301,6 +315,12 @@ On 64-bit targets, we take advantage of the fact that most architectures don't s That is, level 5 has 256 entires pointing to level 4 maps (or nothing if there's no code allocated in that address range), level 4 entires point to level 3 maps and so on. Each level 1 map has 256 entries covering a 128 KiB chunk and pointing to a linked list of range section fragments that fall within that 128 KiB chunk. +### Native Format + +The ReadyToRun image stores data in a compressed native foramt defined in [nativeformatreader.h](../../../src/coreclr/vm/nativeformatreader.h). + +The ReadyToRunJitManager interacts with this format through t + ### NibbleMap The ExecutionManager contract depends on a "nibble map" data structure diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index be25e4cba9787d..73ad017d5cdef4 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -146,7 +146,6 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); | `InstMethodHashTable` | `VolatileEntryNextEntry` | Next pointer in the hash table entry | - ### Global variables used: | Global Name | Type | Purpose | | --- | --- | --- | @@ -581,3 +580,73 @@ class InstMethodHashTable } } ``` + +### DacEnumerableHash (EETypeHashTable and InstMethodHashTable) + +Both `EETypeHashTable` and `InstMethodHashTable` are based on the templated `DacEnumerableHash`. Because the base class is templated on the derived type, offsets may be different in derived types. + +The base implementation of `DacEnumerableHash` uses four datadescriptors: +| Datadescriptor | Purpose | +| --- | --- | +| `Buckets` | Pointer to the bucket array | +| `Count` | Number of elements in the hash table | +| `VolatileEntryValue` | The data held by an entry, defined by the derived class | +| `VolatileEntryNextEntry` | The next pointer on an hash table entry | + +The hash table is laid out as an array of `VolatileEntry` pointers's (buckets), each possibly forming a chain for values that hash into that bucket. The first three buckets are special and reserved for metadata. Instead of containing a `VolatileEntry`, these pointers are read as values with the following meanings. + +| Reserved Bucket offset | Purpose | +| --- | --- | +| `0` | Length of the Bucket array, this value does not include the first 3 slots which are special | +| `1` | Pointer to the next bucket array, not currently used in the cDAC | +| `2` | End sentinel for the current bucket array, not currently used in the cDAC | + +The current cDAC implementation does not use the 'hash' part of the table at all. Instead it iterates all elements in the table. Following the existing iteration logic in the runtime (and DAC), resizing the table while iterating is not supported. Given this constraint, the pointer to the next bucket array (resized data table) and the current end sentinel are not required to iterate all entries. + +To read all entries in the hash table: +1. Read the length bucket to find the number of chains `n`. +2. Initialize a list of elements `entries = []`. +3. For each chain, (buckets with offsets `3..n + 3`): + 1. Read the pointer in the bucket as `volatileEntryPtr`. + 2. If `volatileEntryPtr & 0x1 == 0x1`, this is an end sentinel and we stop reading this chain. + 3. Otherwise, add `volatileEntryPtr + /* VolatileEntryValue offset */` to entries. This points to the derived class defined data type. + 4. Set `volatileEntryPtr` to the value of the pointer located at `volatileEntryPtr + /* VolatileEntryNextEntry offset */` and go to step 3.2. +4. Return `entries` to be further parsed by derived classes. + +While both EETypeHashTable and InstMethodHashTable store pointer sized data types, they both use the LSBs as special flags. + +#### EETypeHashTable +EETypeHashTable uses the LSB to indicate if the TypeHandle is a hot entry. The cDAC implementation separates each value `value` in the table into two parts. The actual TypeHandle pointer and the associated flags. + +```csharp +class EETypeHashTable +{ + private const ulong FLAG_MASK = 0x1ul; + + public IReadOnlyList Entires { get; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer TypeHandle { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } +} +``` + +#### InstMethodHashTable +InstMethodHashTable uses the 2 LSBs as flags for the MethodDesc. The cDAC implementation separates each value `value` in the table into two parts. The actual MethodDesc pointer and the associated flags. + +```csharp +class InstMethodHashTable +{ + private const ulong FLAG_MASK = 0x3ul; + + public IReadOnlyList Entires { get; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer MethodDesc { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } +} +``` diff --git a/src/coreclr/inc/patchpointinfo.h b/src/coreclr/inc/patchpointinfo.h index f2c01d351c67c2..f403d268d20a7c 100644 --- a/src/coreclr/inc/patchpointinfo.h +++ b/src/coreclr/inc/patchpointinfo.h @@ -7,6 +7,10 @@ #include +#ifndef JIT_BUILD +#include "cdacdata.h" +#endif // JIT_BUILD + #ifndef _PATCHPOINTINFO_H_ #define _PATCHPOINTINFO_H_ @@ -201,7 +205,19 @@ struct PatchpointInfo int32_t m_securityCookieOffset; int32_t m_monitorAcquiredOffset; int32_t m_offsetAndExposureData[]; + +#ifndef JIT_BUILD + friend struct ::cdac_data; +#endif // JIT_BUILD +}; + +#ifndef JIT_BUILD +template<> +struct cdac_data +{ + static constexpr size_t LocalCount = offsetof(PatchpointInfo, m_numberOfLocals); }; +#endif // JIT_BUILD typedef DPTR(struct PatchpointInfo) PTR_PatchpointInfo; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs index 44d6c238005318..58964e235984f4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs @@ -382,4 +382,4 @@ public uint DecodeUDelta(ref int start, uint lastValue) return lastValue + delta; } } -} \ No newline at end of file +} diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs index 26d4599b6a44b2..90ad9b5f58dfa5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs @@ -7,7 +7,7 @@ namespace ILCompiler.Reflection.ReadyToRun /// Helper to read memory by 4-bit (half-byte) nibbles as is used for encoding /// method fixups. More or less ported over from CoreCLR src\inc\nibblestream.h. /// - class NibbleReader + internal class NibbleReader { /// /// Special value in _nextNibble saying there's no next nibble and the next byte diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index 1be9d9ce96d81b..47304d833fc0ea 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -2232,8 +2232,17 @@ private : HINSTANCE m_AltJITCompiler; bool m_AltJITRequired; #endif //ALLOW_SXS_JIT + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t StoreRichDebugInfo = offsetof(EEJitManager, m_storeRichDebugInfo); }; + //***************************************************************************** // // This class manages IJitManagers and ICorJitCompilers. It has only static diff --git a/src/coreclr/vm/datadescriptor/contracts.jsonc b/src/coreclr/vm/datadescriptor/contracts.jsonc index de820476c734dd..63d61aeda40baf 100644 --- a/src/coreclr/vm/datadescriptor/contracts.jsonc +++ b/src/coreclr/vm/datadescriptor/contracts.jsonc @@ -11,6 +11,7 @@ { "CodeVersions": 1, "DacStreams": 1, + "DebugInfo": 1, "EcmaMetadata": 1, "Exception": 1, "ExecutionManager": 2, diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.h b/src/coreclr/vm/datadescriptor/datadescriptor.h index 84adff2a49b4d3..2123bd81f8ebce 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.h +++ b/src/coreclr/vm/datadescriptor/datadescriptor.h @@ -18,6 +18,7 @@ #include "configure.h" #include "../debug/ee/debugger.h" +#include "patchpointinfo.h" #ifdef HAVE_GCCOVER #include "gccover.h" diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 934bee1662b4d6..c3e4b9228a6c11 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -519,6 +519,7 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data::NumHotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, HotColdMap, cdac_data::HotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_data::DelayLoadMethodCallThunks) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DebugInfo, cdac_data::DebugInfo) CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) CDAC_TYPE_END(ReadyToRunInfo) @@ -585,9 +586,15 @@ CDAC_TYPE_FIELD(RangeSection, /*pointer*/, HeapList, cdac_data::He CDAC_TYPE_FIELD(RangeSection, /*pointer*/, R2RModule, cdac_data::R2RModule) CDAC_TYPE_END(RangeSection) +CDAC_TYPE_BEGIN(EEJitManager) +CDAC_TYPE_INDETERMINATE(EEJitManager) +CDAC_TYPE_FIELD(EEJitManager, /*bool*/, StoreRichDebugInfo, cdac_data::StoreRichDebugInfo) +CDAC_TYPE_END(EEJitManager) + CDAC_TYPE_BEGIN(RealCodeHeader) CDAC_TYPE_INDETERMINATE(RealCodeHeader) CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, MethodDesc, offsetof(RealCodeHeader, phdrMDesc)) +CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, DebugInfo, offsetof(RealCodeHeader, phdrDebugInfo)) CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, GCInfo, offsetof(RealCodeHeader, phdrJitGCInfo)) #ifdef FEATURE_EH_FUNCLETS CDAC_TYPE_FIELD(RealCodeHeader, /*uint32*/, NumUnwindInfos, offsetof(RealCodeHeader, nUnwindInfos)) @@ -595,6 +602,11 @@ CDAC_TYPE_FIELD(RealCodeHeader, /* T_RUNTIME_FUNCTION */, UnwindInfos, offsetof( #endif // FEATURE_EH_FUNCLETS CDAC_TYPE_END(RealCodeHeader) +CDAC_TYPE_BEGIN(PatchpointInfo) +CDAC_TYPE_SIZE(sizeof(PatchpointInfo)) +CDAC_TYPE_FIELD(PatchpointInfo, /*uint32*/, LocalCount, cdac_data::LocalCount) +CDAC_TYPE_END(PatchpointInfo) + CDAC_TYPE_BEGIN(CodeHeapListNode) CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, Next, offsetof(HeapList, hpNext)) CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, StartAddress, offsetof(HeapList, startAddress)) @@ -892,6 +904,11 @@ CDAC_GLOBAL(FeatureCOMInterop, uint8, 1) #else CDAC_GLOBAL(FeatureCOMInterop, uint8, 0) #endif +#ifdef FEATURE_ON_STACK_REPLACEMENT +CDAC_GLOBAL(FeatureOnStackReplacement, uint8, 1) +#else +CDAC_GLOBAL(FeatureOnStackReplacement, uint8, 0) +#endif // FEATURE_ON_STACK_REPLACEMENT // See Object::GetGCSafeMethodTable #ifdef TARGET_64BIT CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2) diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index dee2504a637901..b99db783665385 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -884,6 +884,7 @@ ReadyToRunInfo::ReadyToRunInfo(Module * pModule, LoaderAllocator* pLoaderAllocat } m_pSectionDelayLoadMethodCallThunks = m_pComposite->FindSection(ReadyToRunSectionType::DelayLoadMethodCallThunks); + m_pSectionDebugInfo = m_pComposite->FindSection(ReadyToRunSectionType::DebugInfo); IMAGE_DATA_DIRECTORY * pinstMethodsDir = m_pComposite->FindSection(ReadyToRunSectionType::InstanceMethodEntryPoints); if (pinstMethodsDir != NULL) diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index a67a539f743a17..2b5e1791e15a2c 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -119,6 +119,7 @@ class ReadyToRunInfo DWORD m_nHotColdMap; PTR_IMAGE_DATA_DIRECTORY m_pSectionDelayLoadMethodCallThunks; + PTR_IMAGE_DATA_DIRECTORY m_pSectionDebugInfo; PTR_READYTORUN_IMPORT_SECTION m_pImportSections; DWORD m_nImportSections; @@ -351,6 +352,7 @@ struct cdac_data static constexpr size_t NumHotColdMap = offsetof(ReadyToRunInfo, m_nHotColdMap); static constexpr size_t HotColdMap = offsetof(ReadyToRunInfo, m_pHotColdMap); static constexpr size_t DelayLoadMethodCallThunks = offsetof(ReadyToRunInfo, m_pSectionDelayLoadMethodCallThunks); + static constexpr size_t DebugInfo = offsetof(ReadyToRunInfo, m_pSectionDebugInfo); static constexpr size_t EntryPointToMethodDescMap = offsetof(ReadyToRunInfo, m_entryPointToMethodDescMap); }; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 9056a9002a90e5..61ca9fc81f842a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -67,4 +67,8 @@ public abstract class ContractRegistry /// Gets an instance of the RuntimeInfo contract for the target. /// public abstract IRuntimeInfo RuntimeInfo { get; } + /// + /// Gets an instance of the DebugInfo contract for the target. + /// + public abstract IDebugInfo DebugInfo { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs new file mode 100644 index 00000000000000..f9089bdd2e697c --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public enum SourceTypes : uint +{ + SourceTypeInvalid = 0x00, // To indicate that nothing else applies + SequencePoint = 0x01, // The debugger asked for it. + StackEmpty = 0x02, // The stack is empty here + CallSite = 0x04, // This is a call site. + NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint + CallInstruction = 0x10 // The actual instruction of a call. +} + +public interface IOffsetMapping +{ + public uint NativeOffset { get; } + public uint ILOffset { get; } + public SourceTypes SourceType { get; } +} + +public interface IDebugInfo : IContract +{ + static string IContract.Name { get; } = nameof(DebugInfo); + IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) => throw new NotImplementedException(); +} + +public readonly struct DebugInfo : IDebugInfo +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs index 408d8e8f91846f..8a9653e17e7618 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs @@ -21,6 +21,7 @@ public interface IExecutionManager : IContract TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => throw new NotImplementedException(); // **Currently GetGCInfo only supports X86** void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => throw new NotImplementedException(); TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 0a8ddc1b5cf24a..54d9e932198842 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -105,6 +105,8 @@ public enum DataType ArrayListBlock, EETypeHashTable, InstMethodHashTable, + EEJitManager, + PatchpointInfo, TransitionBlock, DebuggerEval, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 3ea5c5fa948371..4f2e0d00c0bda4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -15,6 +15,7 @@ public static class Globals public const string GCThread = nameof(GCThread); public const string FeatureCOMInterop = nameof(FeatureCOMInterop); + public const string FeatureOnStackReplacement = nameof(FeatureOnStackReplacement); public const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask); public const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); @@ -70,6 +71,9 @@ public static class Globals public const string OperatingSystem = nameof(OperatingSystem); public const string GCInfoVersion = nameof(GCInfoVersion); + + public const string FCallHashSize = nameof(FCallHashSize); + public const string FCallMethods = nameof(FCallMethods); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.cs new file mode 100644 index 00000000000000..40072342b05892 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.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 DebugInfoFactory : IContractFactory +{ + IDebugInfo IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new DebugInfo_1(target), + _ => default(DebugInfo), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs new file mode 100644 index 00000000000000..675ac2eb53dddb --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -0,0 +1,154 @@ +// 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 System.Diagnostics; +using System.Linq; +using ILCompiler.Reflection.ReadyToRun; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal sealed class DebugInfo_1(Target target) : IDebugInfo +{ + private const uint IL_OFFSET_BIAS = unchecked((uint)-3); + + internal enum SourceTypes_1 : uint + { + SourceTypeInvalid = 0x00, // To indicate that nothing else applies + SequencePoint = 0x01, // The debugger asked for it. + StackEmpty = 0x02, // The stack is empty here + CallSite = 0x04, // This is a call site. + NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint + CallInstruction = 0x10 // The actual instruction of a call. + } + + internal readonly struct OffsetMapping_1 : IOffsetMapping + { + public uint NativeOffset { get; init; } + public uint ILOffset { get; init; } + internal SourceTypes_1 InternalSourceType { get; init; } + public readonly SourceTypes SourceType + { + get + { + switch (InternalSourceType) + { + case SourceTypes_1.SourceTypeInvalid: + return SourceTypes.SourceTypeInvalid; + case SourceTypes_1.SequencePoint: + return SourceTypes.SequencePoint; + case SourceTypes_1.StackEmpty: + return SourceTypes.StackEmpty; + case SourceTypes_1.CallSite: + return SourceTypes.CallSite; + case SourceTypes_1.NativeEndOffsetUnknown: + return SourceTypes.NativeEndOffsetUnknown; + case SourceTypes_1.CallInstruction: + return SourceTypes.CallInstruction; + default: + Debug.Fail($"Unknown source type: {InternalSourceType}"); + return SourceTypes.SourceTypeInvalid; + } + } + } + } + + [Flags] + internal enum ExtraDebugInfoFlags_1 : byte + { + // Debug info contains patchpoint information + EXTRA_DEBUG_INFO_PATCHPOINT = 0x01, + // Debug info contains rich information + EXTRA_DEBUG_INFO_RICH = 0x02, + } + + private readonly Target _target = target; + private readonly IExecutionManager _eman = target.Contracts.ExecutionManager; + + IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) + { + // Get the method's DebugInfo + if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) + throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}."); + TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool hasFlagByte); + + TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh); + codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target)); + + return RestoreBoundaries(debugInfo, hasFlagByte); + } + + private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte) + { + if (hasFlagByte) + { + // Check flag byte and skip over any patchpoint info + ExtraDebugInfoFlags_1 flagByte = (ExtraDebugInfoFlags_1)_target.Read(debugInfo++); + + if (flagByte.HasFlag(ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_PATCHPOINT)) + { + Data.PatchpointInfo patchpointInfo = _target.ProcessedData.GetOrAdd(debugInfo); + + if (_target.GetTypeInfo(DataType.PatchpointInfo).Size is not uint patchpointSize) + throw new InvalidOperationException("PatchpointInfo type size is not defined."); + debugInfo += patchpointSize + (patchpointInfo.LocalCount * sizeof(uint)); + + flagByte &= ~ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_PATCHPOINT; + } + + if (flagByte.HasFlag(ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_RICH)) + { + uint richDebugInfoSize = _target.Read(debugInfo); + debugInfo += 4; + debugInfo += richDebugInfoSize; + flagByte &= ~ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_RICH; + } + + Debug.Assert(flagByte == 0); + } + + NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 12 /*maximum size of 2 32bit ints compressed*/), _target.IsLittleEndian); + NibbleReader nibbleReader = new(nibbleNativeReader, 0); + + uint cbBounds = nibbleReader.ReadUInt(); + uint _ /*cbVars*/ = nibbleReader.ReadUInt(); + + TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); + + if (cbBounds > 0) + { + NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); + NibbleReader boundsReader = new(boundsNativeReader, 0); + + uint countEntries = boundsReader.ReadUInt(); + Debug.Assert(countEntries > 0, "Expected at least one entry in bounds."); + + return DoBounds(boundsReader, countEntries); + } + + return Enumerable.Empty(); + } + + private static IEnumerable DoBounds(NibbleReader reader, uint count) + { + uint nativeOffset = 0; + for (uint i = 0; i < count; i++) + { + // native offsets are encoded as a delta from the previous offset + nativeOffset += reader.ReadUInt(); + + // il offsets are encoded with a bias of ICorDebugInfo::MAX_MAPPING_VALUE + uint ilOffset = unchecked(reader.ReadUInt() + IL_OFFSET_BIAS); + + SourceTypes_1 sourceType = (SourceTypes_1)reader.ReadUInt(); + + yield return new OffsetMapping_1 + { + NativeOffset = nativeOffset, + ILOffset = ilOffset, + InternalSourceType = sourceType + }; + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs index 6a23be3be862d4..c62499cad64ef7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs @@ -75,6 +75,30 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod return _runtimeFunctions.GetRuntimeFunctionAddress(realCodeHeader.UnwindInfos, index); } + public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte) + { + hasFlagByte = false; + if (rangeSection.IsRangeList) + return TargetPointer.Null; + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress); + if (codeStart == TargetPointer.Null) + return TargetPointer.Null; + Debug.Assert(codeStart.Value <= jittedCodeAddress.Value); + + if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) + return TargetPointer.Null; + + bool featureOnStackReplacement = Target.ReadGlobal(Constants.Globals.FeatureOnStackReplacement) != 0; + Data.EEJitManager eeJitManager = Target.ProcessedData.GetOrAdd(rangeSection.Data.JitManager); + if (featureOnStackReplacement || eeJitManager.StoreRichDebugInfo) + hasFlagByte = true; + + return realCodeHeader.DebugInfo; + } + public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) { gcInfo = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index bdbe99980c4107..78c14957c9fde5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -70,6 +70,40 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod return _runtimeFunctions.GetRuntimeFunctionAddress(r2rInfo.RuntimeFunctions, index); } + public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte) + { + // ReadyToRun does not contain PatchpointInfo + hasFlagByte = false; + + // ReadyToRunJitManager::GetDebugInfo + Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) + return TargetPointer.Null; + + index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index); + index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _); + + Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd(r2rInfo.DebugInfo); + + ILCompiler.Reflection.ReadyToRun.NativeReader imageReader = new( + new TargetStream(Target, imageBase, debugInfoData.Size), + Target.IsLittleEndian + ); + ILCompiler.Reflection.ReadyToRun.NativeArray debugInfoArray = new(imageReader, debugInfoData.VirtualAddress); + + int offset = 0; + if (!debugInfoArray.TryGetAt(index, ref offset)) + // If the index is not found in the debug info array, return null + return TargetPointer.Null; + + uint lookBack = 0; + uint debugInfoOffset = imageReader.DecodeUnsigned((uint)offset, ref lookBack); + if (lookBack != 0) + debugInfoOffset = (uint)offset - lookBack; + + return imageBase + debugInfoOffset; + } + public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) { gcInfo = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index 6d9b993dcfff6c..7161eae75e3840 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -66,6 +66,7 @@ protected JitManager(Target target) public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info); public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress); + public abstract TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte); public abstract void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion); } @@ -230,6 +231,20 @@ TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInf return range.Data.RangeBegin; } + TargetPointer IExecutionManager.GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) + { + hasFlagByte = false; + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + if (range.Data == null) + return TargetPointer.Null; + + JitManager jitManager = GetJitManager(range.Data); + return jitManager.GetDebugInfo(range, codeInfoHandle.Address.Value, out hasFlagByte); + } + void IExecutionManager.GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) { gcInfo = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index 2feda4180c2feb..551c664237096c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -20,6 +20,7 @@ internal ExecutionManager_1(Target target, Data.RangeSectionMap topRangeSectionM public TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle); public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfo(codeInfoHandle); public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); + public TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => _executionManagerCore.GetDebugInfo(codeInfoHandle, out hasFlagByte); public void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => _executionManagerCore.GetGCInfo(codeInfoHandle, out gcInfo, out gcVersion); public TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetRelativeOffset(codeInfoHandle); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index 356081e289b492..ab1fda5e329640 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -20,6 +20,7 @@ internal ExecutionManager_2(Target target, Data.RangeSectionMap topRangeSectionM public TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle); public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfo(codeInfoHandle); public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); + public TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => _executionManagerCore.GetDebugInfo(codeInfoHandle, out hasFlagByte); public void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => _executionManagerCore.GetGCInfo(codeInfoHandle, out gcInfo, out gcVersion); public TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetRelativeOffset(codeInfoHandle); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs new file mode 100644 index 00000000000000..c1b748ec80562d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs @@ -0,0 +1,77 @@ +// 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.ExecutionManagerHelpers; + +internal sealed class NativeArray +{ + private const uint BLOCK_SIZE = 16; + + private readonly NativeReader _reader; + private readonly uint _count; + private readonly uint _entryIndexSize; + private readonly uint _baseOffset; + + public NativeArray(NativeReader reader, uint offset) + { + _reader = reader; + + _baseOffset = _reader.DecodeUnsigned(offset, out uint val); + _count = val >> 2; + _entryIndexSize = val & 3; + } + + public bool TryGetAt(uint index, out uint value) + { + value = 0; + if (index >= _count) + return false; + + uint offset = _entryIndexSize switch + { + 0 => _reader.ReadUInt8(_baseOffset + (index / BLOCK_SIZE)), + 1 => _reader.ReadUInt16(_baseOffset + 2 * (index / BLOCK_SIZE)), + _ => _reader.ReadUInt32(_baseOffset + 4 * (index / BLOCK_SIZE)), + }; + offset += _baseOffset; + + for (uint bit = BLOCK_SIZE >> 1; bit > 0; bit >>= 1) + { + uint offset2 = _reader.DecodeUnsigned(offset, out uint val); + + if ((index & bit) != 0) + { + if ((val & 2) != 0) + { + offset += val >> 2; + continue; + } + } + else + { + if ((val & 1) != 0) + { + offset = offset2; + continue; + } + } + + // Not found + if ((val & 3) == 0) + { + // Matching special leaf node? + if ((val >> 2) == (index & (BLOCK_SIZE - 1))) + { + offset = offset2; + break; + } + } + return false; + } + + value = offset; + return true; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs new file mode 100644 index 00000000000000..9d598f3f5062a3 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs @@ -0,0 +1,63 @@ +// 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.ExecutionManagerHelpers; + +internal sealed class NativeReader(Target target, TargetPointer baseAddress) +{ + private readonly Target _target = target; + private readonly TargetPointer _baseAddress = baseAddress; + + public byte ReadUInt8(uint offset) => _target.Read(_baseAddress + offset); + public ushort ReadUInt16(uint offset) => _target.Read(_baseAddress + offset); + public uint ReadUInt32(uint offset) => _target.Read(_baseAddress + offset); + + public uint DecodeUnsigned(uint offset, out uint value) + { + value = 0; + uint val = ReadUInt8(offset); + if ((val & 1) == 0) + { + value = val >> 1; + offset += 1; + } + else + if ((val & 2) == 0) + { + value = val >> 2; + value |= ((uint)ReadUInt8(offset + 1)) << 6; + offset += 2; + } + else + if ((val & 4) == 0) + { + value = val >> 3; + value |= ((uint)ReadUInt8(offset + 1)) << 5; + value |= ((uint)ReadUInt8(offset + 2)) << 13; + offset += 3; + } + else + if ((val & 8) == 0) + { + value = val >> 4; + value |= ((uint)ReadUInt8(offset + 1)) << 4; + value |= ((uint)ReadUInt8(offset + 2)) << 12; + value |= ((uint)ReadUInt8(offset + 3)) << 20; + offset += 4; + } + else + if ((val & 16) == 0) + { + value = ReadUInt32(offset + 1); + offset += 5; + } + else + { + throw new InvalidOperationException("Invalid encoding in DecodeUnsigned"); + } + + return offset; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEJitManager.cs new file mode 100644 index 00000000000000..58eae2a2cf67d1 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEJitManager.cs @@ -0,0 +1,17 @@ +// 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.Data; + +internal sealed class EEJitManager : IData +{ + static EEJitManager IData.Create(Target target, TargetPointer address) => new EEJitManager(target, address); + public EEJitManager(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EEJitManager); + + StoreRichDebugInfo = target.Read(address + (ulong)type.Fields[nameof(StoreRichDebugInfo)].Offset) != 0; + } + + public bool StoreRichDebugInfo { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs index d3717f5eea481b..241cf6daabd540 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDataDirectory.cs @@ -14,7 +14,7 @@ public ImageDataDirectory(Target target, TargetPointer address) VirtualAddress = target.Read(address + (ulong)type.Fields[nameof(VirtualAddress)].Offset); Size = target.Read(address + (ulong)type.Fields[nameof(Size)].Offset); - } + } public uint VirtualAddress { get; } public uint Size { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PatchpointInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PatchpointInfo.cs new file mode 100644 index 00000000000000..482174b7465406 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PatchpointInfo.cs @@ -0,0 +1,21 @@ +// 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.Data; + +internal sealed class PatchpointInfo : IData +{ + static PatchpointInfo IData.Create(Target target, TargetPointer address) + => new PatchpointInfo(target, address); + + public PatchpointInfo(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.PatchpointInfo); + + LocalCount = target.Read(address + (ulong)type.Fields[nameof(LocalCount)].Offset); + } + + public uint LocalCount { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs index ed932946d195de..95bb81bd9d76fe 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -30,6 +30,7 @@ public ReadyToRunInfo(Target target, TargetPointer address) : TargetPointer.Null; DelayLoadMethodCallThunks = target.ReadPointer(address + (ulong)type.Fields[nameof(DelayLoadMethodCallThunks)].Offset); + DebugInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(DebugInfo)].Offset); // Map is from the composite info pointer (set to itself for non-multi-assembly composite images) EntryPointToMethodDescMap = CompositeInfo + (ulong)type.Fields[nameof(EntryPointToMethodDescMap)].Offset; @@ -46,5 +47,6 @@ public ReadyToRunInfo(Target target, TargetPointer address) public TargetPointer HotColdMap { get; } public TargetPointer DelayLoadMethodCallThunks { get; } + public TargetPointer DebugInfo { get; } public TargetPointer EntryPointToMethodDescMap { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs index f40998be18b114..97af139996a5ea 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Diagnostics.DataContractReader.Contracts; + namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class RealCodeHeader : IData @@ -12,12 +14,14 @@ public RealCodeHeader(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.RealCodeHeader); MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + DebugInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(DebugInfo)].Offset); GCInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCInfo)].Offset); NumUnwindInfos = target.Read(address + (ulong)type.Fields[nameof(NumUnwindInfos)].Offset); UnwindInfos = address + (ulong)type.Fields[nameof(UnwindInfos)].Offset; } public TargetPointer MethodDesc { get; init; } + public TargetPointer DebugInfo { get; init; } public TargetPointer GCInfo { get; init; } public uint NumUnwindInfos { get; init; } public TargetPointer UnwindInfos { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj index de4256093c7601..b409a9d247839c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj @@ -18,4 +18,10 @@ + + + + + + diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 91448e65c86d61..d979a8f7705727 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -40,6 +40,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IReJIT)] = new ReJITFactory(), [typeof(IStackWalk)] = new StackWalkFactory(), [typeof(IRuntimeInfo)] = new RuntimeInfoFactory(), + [typeof(IDebugInfo)] = new DebugInfoFactory(), }; configureFactories?.Invoke(_factories); } @@ -58,6 +59,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IReJIT ReJIT => GetContract(); public override IStackWalk StackWalk => GetContract(); public override IRuntimeInfo RuntimeInfo => GetContract(); + public override IDebugInfo DebugInfo => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index 27cabfe18834b9..b574727405edf0 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -2,7 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -125,7 +128,79 @@ int IXCLRDataMethodInstance.GetTypeArgumentByIndex(uint index, void** typeArg) => _legacyImpl is not null ? _legacyImpl.GetTypeArgumentByIndex(index, typeArg) : HResults.E_NOTIMPL; int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint offsetsLen, uint* offsetsNeeded, uint* ilOffsets) - => _legacyImpl is not null ? _legacyImpl.GetILOffsetsByAddress(address, offsetsLen, offsetsNeeded, ilOffsets) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + try + { + TargetCodePointer pCode = address.ToTargetCodePointer(_target); + List map = _target.Contracts.DebugInfo.GetMethodNativeMap(pCode, out uint codeOffset).ToList(); + + uint hits = 0; + for (int i = 0; i < map.Count; i++) + { + bool isEpilog = map[i].ILOffset == unchecked((uint)-3); // -3 is used to indicate an epilog + bool lastValue = i == map.Count - 1; + uint nativeEndOffset = lastValue ? 0 : map[i + 1].NativeOffset; + if (codeOffset >= map[i].NativeOffset && (((isEpilog || lastValue) && nativeEndOffset == 0) || codeOffset < nativeEndOffset)) + { + if (hits < offsetsLen && ilOffsets is not null) + { + ilOffsets[hits] = map[i].ILOffset; + } + + hits++; + } + } + + if (offsetsNeeded is not null) + { + *offsetsNeeded = hits; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal; + + bool validateOffsetsNeeded = offsetsNeeded is not null; + uint localOffsetsNeeded = 0; + + bool validateIlOffsets = ilOffsets is not null; + uint[] localIlOffsets = new uint[offsetsLen]; + + fixed (uint* localIlOffsetsPtr = localIlOffsets) + { + hrLocal = _legacyImpl.GetILOffsetsByAddress(address, offsetsLen, validateOffsetsNeeded ? &localOffsetsNeeded : null, validateIlOffsets ? localIlOffsetsPtr : null); + } + + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + + if (hr == HResults.S_OK) + { + if (validateOffsetsNeeded) + { + Debug.Assert(localOffsetsNeeded == *offsetsNeeded, $"cDAC: {*offsetsNeeded:x}, DAC: {localOffsetsNeeded:x}"); + } + + if (validateIlOffsets) + { + for (int i = 0; i < localIlOffsets.Length; i++) + { + Debug.Assert(localIlOffsets[i] == ilOffsets[i], $"cDAC: {localIlOffsets[i]:x}, DAC: {ilOffsets[i]:x}"); + } + } + } + } +#endif + + return hr; + } int IXCLRDataMethodInstance.GetAddressRangesByILOffset(uint ilOffset, uint rangesLen, uint* rangesNeeded, void* addressRanges) => _legacyImpl is not null ? _legacyImpl.GetAddressRangesByILOffset(ilOffset, rangesLen, rangesNeeded, addressRanges) : HResults.E_NOTIMPL; From 013b60591f0f90391cbfef5002f29c903a712cfe Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 11 Aug 2025 14:47:19 -0400 Subject: [PATCH 39/54] merge fixes --- .../tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs | 4 ++-- .../aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs index d241e5c76d2296..c8810a66476500 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs @@ -56,7 +56,7 @@ public bool TryGetAt(uint index, ref int pOffset) if (index >= _nElements) return false; - uint offset = 0; + uint offset; if (_entryIndexSize == 0) { int i = (int)(_baseOffset + (index / _blockSize)); @@ -82,7 +82,7 @@ public bool TryGetAt(uint index, ref int pOffset) { if ((val & 2) != 0) { - offset = offset + (val >> 2); + offset += val >> 2; continue; } } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs index 58964e235984f4..b26a75790eeb89 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs @@ -39,7 +39,7 @@ public void ReadSpanAt(ref int start, Span buffer) throw new ArgumentOutOfRangeException(nameof(start), "Start index is out of bounds"); _backingStream.Seek(start, SeekOrigin.Begin); - _backingStream.Read(buffer); + _backingStream.ReadExactly(buffer); start += buffer.Length; } From 4bb2820512edf88dcf06fb4284120c42e0b61b3b Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 11 Aug 2025 14:49:35 -0400 Subject: [PATCH 40/54] fix ClrDataAddress issue --- .../cdac/mscordaccore_universal/Legacy/IXCLRData.cs | 2 +- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs index ae043ab996a5a4..e80aabc43f62bc 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs @@ -206,7 +206,7 @@ int GetRuntimeNameByAddress( int GetModuleByAddress(ClrDataAddress address, /*IXCLRDataModule*/ void** mod); [PreserveSig] - int StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); + int StartEnumMethodInstancesByAddress(ClrDataAddress address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); [PreserveSig] int EnumMethodInstanceByAddress(ulong* handle, out IXCLRDataMethodInstance? method); [PreserveSig] diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 4ff34b79501d9d..3f7a880196944e 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -329,7 +329,7 @@ private bool HasMethodInstantiation(MethodDescHandle md) } } - int IXCLRDataProcess.StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) + int IXCLRDataProcess.StartEnumMethodInstancesByAddress(ClrDataAddress address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) { int hr = HResults.S_OK; @@ -347,14 +347,16 @@ int IXCLRDataProcess.StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDat try { + TargetCodePointer methodAddr = address.ToTargetCodePointer(_target); + // ClrDataAccess::IsPossibleCodeAddress // Does a trivial check on the readability of the address - bool isTriviallyReadable = _target.TryRead(address, out byte _); + bool isTriviallyReadable = _target.TryRead(methodAddr, out byte _); if (!isTriviallyReadable) throw new ArgumentException(); IExecutionManager eman = _target.Contracts.ExecutionManager; - if (eman.GetCodeBlockHandle(address) is CodeBlockHandle cbh && + if (eman.GetCodeBlockHandle(methodAddr) is CodeBlockHandle cbh && eman.GetMethodDesc(cbh) is TargetPointer methodDesc) { EnumMethodInstances emi = new(_target, methodDesc, TargetPointer.Null); From 4682da4f20e7021b856bc5f940ab4b6194f0432e Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 11 Aug 2025 16:22:42 -0400 Subject: [PATCH 41/54] fix md lint --- docs/design/datacontracts/ExecutionManager.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 0b7209cc3599cd..103aac455a2a48 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -317,9 +317,7 @@ code allocated in that address range), level 4 entires point to level 3 maps and ### Native Format -The ReadyToRun image stores data in a compressed native foramt defined in [nativeformatreader.h](../../../src/coreclr/vm/nativeformatreader.h). - -The ReadyToRunJitManager interacts with this format through t +The ReadyToRun image stores data in a compressed native foramt defined in [nativeformatreader.h](../../../src/coreclr/vm/nativeformatreader.h). ### NibbleMap From 48c9d54deac80c98e8936bc7d9d47b181dfda6f8 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 11 Aug 2025 16:32:06 -0400 Subject: [PATCH 42/54] clean up --- docs/design/datacontracts/ECall.md | 39 ---------- .../Constants.cs | 3 - .../ExecutionManager/Helpers/NativeArray.cs | 77 ------------------- .../ExecutionManager/Helpers/NativeReader.cs | 63 --------------- .../Legacy/ClrDataMethodInstance.cs | 6 +- 5 files changed, 5 insertions(+), 183 deletions(-) delete mode 100644 docs/design/datacontracts/ECall.md delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs diff --git a/docs/design/datacontracts/ECall.md b/docs/design/datacontracts/ECall.md deleted file mode 100644 index f1ea6c4f8cc07d..00000000000000 --- a/docs/design/datacontracts/ECall.md +++ /dev/null @@ -1,39 +0,0 @@ -# Contract ECall - -This contract is for fetching information related to native calls into the runtime. - -## APIs of contract - -``` csharp -// Given an FCall entrypoint returns the corresponding MethodDesc. -// If the address does not correspond to an FCall, returns TargetPointer.Null. -TargetPointer MapTargetBackToMethodDesc(TargetCodePointer address); -``` - -## Version 1 - -Global variables used -| Global Name | Type | Purpose | -| --- | --- | --- | -| FCallMethods | ECHash[] | Hash table containing ECHash structures | -| FCallHashSize | uint | Number of buckets in the hash table | - - -Data descriptors used: -| Data Descriptor Name | Field | Meaning | -| --- | --- | --- | -| `ECHash` | `Next` | Pointer to the next ECHash in the chain | -| `ECHash` | `Implementation` | FCall's Entrypoint address | -| `ECHash` | `MethodDesc` | Pointer to the FCall's method desc | - - -``` csharp -TargetPointer IECall.MapTargetBackToMethodDesc(TargetCodePointer codePointer) -``` - -To map an FCall entrypoint back to a MethodDesc, we read the global `FCallMethods` hash table. This is a array of pointers to `ECHash` objects. The length of this array is defined by the global `FCallHashSize` where each element is an `ECHash` which can form a chain. It uses a simple hash function: ` = codePointer % FCallHashSize` to map code entry points to buckets. To map a `codePointer` back to a MethodDesc pointer: - -1. Calculate the `` corresponding to the given `codePointer`. -2. Take the `` offset into the `FCallMethods` array. -3. Now that we have the correct `ECHash` chain, iterate the chain using the `ECHash.Next` pointer until we find an `ECHash` where the `Implementation` field matches the `codePointer`. If found, return the `MethodDesc` field. -4. If no `ECHash` matches return `TargetPointer.Null` to indicate a MethodDesc was not found. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 4f2e0d00c0bda4..96df8ba03be869 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -71,9 +71,6 @@ public static class Globals public const string OperatingSystem = nameof(OperatingSystem); public const string GCInfoVersion = nameof(GCInfoVersion); - - public const string FCallHashSize = nameof(FCallHashSize); - public const string FCallMethods = nameof(FCallMethods); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs deleted file mode 100644 index c1b748ec80562d..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeArray.cs +++ /dev/null @@ -1,77 +0,0 @@ -// 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.ExecutionManagerHelpers; - -internal sealed class NativeArray -{ - private const uint BLOCK_SIZE = 16; - - private readonly NativeReader _reader; - private readonly uint _count; - private readonly uint _entryIndexSize; - private readonly uint _baseOffset; - - public NativeArray(NativeReader reader, uint offset) - { - _reader = reader; - - _baseOffset = _reader.DecodeUnsigned(offset, out uint val); - _count = val >> 2; - _entryIndexSize = val & 3; - } - - public bool TryGetAt(uint index, out uint value) - { - value = 0; - if (index >= _count) - return false; - - uint offset = _entryIndexSize switch - { - 0 => _reader.ReadUInt8(_baseOffset + (index / BLOCK_SIZE)), - 1 => _reader.ReadUInt16(_baseOffset + 2 * (index / BLOCK_SIZE)), - _ => _reader.ReadUInt32(_baseOffset + 4 * (index / BLOCK_SIZE)), - }; - offset += _baseOffset; - - for (uint bit = BLOCK_SIZE >> 1; bit > 0; bit >>= 1) - { - uint offset2 = _reader.DecodeUnsigned(offset, out uint val); - - if ((index & bit) != 0) - { - if ((val & 2) != 0) - { - offset += val >> 2; - continue; - } - } - else - { - if ((val & 1) != 0) - { - offset = offset2; - continue; - } - } - - // Not found - if ((val & 3) == 0) - { - // Matching special leaf node? - if ((val >> 2) == (index & (BLOCK_SIZE - 1))) - { - offset = offset2; - break; - } - } - return false; - } - - value = offset; - return true; - } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs deleted file mode 100644 index 9d598f3f5062a3..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NativeReader.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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.ExecutionManagerHelpers; - -internal sealed class NativeReader(Target target, TargetPointer baseAddress) -{ - private readonly Target _target = target; - private readonly TargetPointer _baseAddress = baseAddress; - - public byte ReadUInt8(uint offset) => _target.Read(_baseAddress + offset); - public ushort ReadUInt16(uint offset) => _target.Read(_baseAddress + offset); - public uint ReadUInt32(uint offset) => _target.Read(_baseAddress + offset); - - public uint DecodeUnsigned(uint offset, out uint value) - { - value = 0; - uint val = ReadUInt8(offset); - if ((val & 1) == 0) - { - value = val >> 1; - offset += 1; - } - else - if ((val & 2) == 0) - { - value = val >> 2; - value |= ((uint)ReadUInt8(offset + 1)) << 6; - offset += 2; - } - else - if ((val & 4) == 0) - { - value = val >> 3; - value |= ((uint)ReadUInt8(offset + 1)) << 5; - value |= ((uint)ReadUInt8(offset + 2)) << 13; - offset += 3; - } - else - if ((val & 8) == 0) - { - value = val >> 4; - value |= ((uint)ReadUInt8(offset + 1)) << 4; - value |= ((uint)ReadUInt8(offset + 2)) << 12; - value |= ((uint)ReadUInt8(offset + 3)) << 20; - offset += 4; - } - else - if ((val & 16) == 0) - { - value = ReadUInt32(offset + 1); - offset += 5; - } - else - { - throw new InvalidOperationException("Invalid encoding in DecodeUnsigned"); - } - - return offset; - } -} diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index b574727405edf0..56c0adbd356181 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -176,7 +176,11 @@ int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint o fixed (uint* localIlOffsetsPtr = localIlOffsets) { - hrLocal = _legacyImpl.GetILOffsetsByAddress(address, offsetsLen, validateOffsetsNeeded ? &localOffsetsNeeded : null, validateIlOffsets ? localIlOffsetsPtr : null); + hrLocal = _legacyImpl.GetILOffsetsByAddress( + address, + offsetsLen, + validateOffsetsNeeded ? &localOffsetsNeeded : null, + validateIlOffsets ? localIlOffsetsPtr : null); } Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); From fc3236c69a10e6355784cdaf9ba27179392407c0 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 11 Aug 2025 16:33:50 -0400 Subject: [PATCH 43/54] remove unused import --- .../Data/RealCodeHeader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs index 97af139996a5ea..70fbccc0b52a7b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Diagnostics.DataContractReader.Contracts; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class RealCodeHeader : IData From d137948098b50365311dd3a5aa7ba9810bba542e Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Mon, 11 Aug 2025 16:38:29 -0400 Subject: [PATCH 44/54] use m_pSectionDebugInfo in runtime --- src/coreclr/vm/readytoruninfo.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index b99db783665385..e9e68f9c1fb45f 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -333,14 +333,13 @@ PTR_BYTE ReadyToRunInfo::GetDebugInfo(PTR_RUNTIME_FUNCTION pRuntimeFunction) } CONTRACTL_END; - IMAGE_DATA_DIRECTORY * pDebugInfoDir = m_pComposite->FindSection(ReadyToRunSectionType::DebugInfo); - if (pDebugInfoDir == NULL) + if (m_pSectionDebugInfo == NULL) return NULL; SIZE_T methodIndex = pRuntimeFunction - m_pRuntimeFunctions; _ASSERTE(methodIndex < m_nRuntimeFunctions); - NativeArray debugInfoIndex(dac_cast(PTR_HOST_INT_TO_TADDR(&m_nativeReader)), pDebugInfoDir->VirtualAddress); + NativeArray debugInfoIndex(dac_cast(PTR_HOST_INT_TO_TADDR(&m_nativeReader)), m_pSectionDebugInfo->VirtualAddress); uint offset; if (!debugInfoIndex.TryGetAt((DWORD)methodIndex, &offset)) From 523bad0f393fb2d5e925f688981473bfa4e591f2 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 12 Aug 2025 13:09:58 -0400 Subject: [PATCH 45/54] fix implementation --- .../Contracts/IDebugInfo.cs | 41 +++-- .../Contracts/DebugInfo/DebugInfo_1.cs | 145 +++++++++++------- ...ecutionManagerCore.ReadyToRunJitManager.cs | 2 +- .../Legacy/ClrDataMethodInstance.cs | 3 +- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 5 + 5 files changed, 129 insertions(+), 67 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs index f9089bdd2e697c..f52dbf59c26f26 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs @@ -6,27 +6,46 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; +[Flags] public enum SourceTypes : uint { - SourceTypeInvalid = 0x00, // To indicate that nothing else applies - SequencePoint = 0x01, // The debugger asked for it. - StackEmpty = 0x02, // The stack is empty here - CallSite = 0x04, // This is a call site. - NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint - CallInstruction = 0x10 // The actual instruction of a call. + /// + /// Indicates that no other options apply + /// + SourceTypeInvalid = 0x00, + /// + /// The debugger asked for it + /// + SequencePoint = 0x01, + /// + /// The stack is empty here + /// + StackEmpty = 0x02, + /// + /// This is a call site + /// + CallSite = 0x04, + /// + /// Indicate an epilog endpoint + /// + NativeEndOffsetUnknown = 0x08, + /// + /// The actual instruction of a call + /// + CallInstruction = 0x10 } -public interface IOffsetMapping +public readonly struct OffsetMapping { - public uint NativeOffset { get; } - public uint ILOffset { get; } - public SourceTypes SourceType { get; } + public uint NativeOffset { get; init; } + public uint ILOffset { get; init; } + public SourceTypes SourceType { get; init; } } public interface IDebugInfo : IContract { static string IContract.Name { get; } = nameof(DebugInfo); - IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) => throw new NotImplementedException(); + IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) => throw new NotImplementedException(); } public readonly struct DebugInfo : IDebugInfo diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs index 675ac2eb53dddb..80f8d5ceb72be8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -11,49 +11,9 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal sealed class DebugInfo_1(Target target) : IDebugInfo { + private const uint DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS = 0xFFFFFFFF; private const uint IL_OFFSET_BIAS = unchecked((uint)-3); - internal enum SourceTypes_1 : uint - { - SourceTypeInvalid = 0x00, // To indicate that nothing else applies - SequencePoint = 0x01, // The debugger asked for it. - StackEmpty = 0x02, // The stack is empty here - CallSite = 0x04, // This is a call site. - NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint - CallInstruction = 0x10 // The actual instruction of a call. - } - - internal readonly struct OffsetMapping_1 : IOffsetMapping - { - public uint NativeOffset { get; init; } - public uint ILOffset { get; init; } - internal SourceTypes_1 InternalSourceType { get; init; } - public readonly SourceTypes SourceType - { - get - { - switch (InternalSourceType) - { - case SourceTypes_1.SourceTypeInvalid: - return SourceTypes.SourceTypeInvalid; - case SourceTypes_1.SequencePoint: - return SourceTypes.SequencePoint; - case SourceTypes_1.StackEmpty: - return SourceTypes.StackEmpty; - case SourceTypes_1.CallSite: - return SourceTypes.CallSite; - case SourceTypes_1.NativeEndOffsetUnknown: - return SourceTypes.NativeEndOffsetUnknown; - case SourceTypes_1.CallInstruction: - return SourceTypes.CallInstruction; - default: - Debug.Fail($"Unknown source type: {InternalSourceType}"); - return SourceTypes.SourceTypeInvalid; - } - } - } - } - [Flags] internal enum ExtraDebugInfoFlags_1 : byte { @@ -66,7 +26,7 @@ internal enum ExtraDebugInfoFlags_1 : byte private readonly Target _target = target; private readonly IExecutionManager _eman = target.Contracts.ExecutionManager; - IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) + IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) { // Get the method's DebugInfo if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) @@ -79,7 +39,7 @@ IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCod return RestoreBoundaries(debugInfo, hasFlagByte); } - private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte) + private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte) { if (hasFlagByte) { @@ -108,30 +68,99 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, b Debug.Assert(flagByte == 0); } - NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 12 /*maximum size of 2 32bit ints compressed*/), _target.IsLittleEndian); + NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 24 /*maximum size of 4 32bit ints compressed*/), _target.IsLittleEndian); NibbleReader nibbleReader = new(nibbleNativeReader, 0); uint cbBounds = nibbleReader.ReadUInt(); + // uint cbUninstrumentedBounds = 0; + if (cbBounds == DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS) + { + // This means we have instrumented bounds. + cbBounds = nibbleReader.ReadUInt(); + // cbUninstrumentedBounds = nibbleReader.ReadUInt(); + } uint _ /*cbVars*/ = nibbleReader.ReadUInt(); TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); + // TargetPointer addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - NibbleReader boundsReader = new(boundsNativeReader, 0); + return DoBounds_2(boundsNativeReader); + } + + return Enumerable.Empty(); + } - uint countEntries = boundsReader.ReadUInt(); - Debug.Assert(countEntries > 0, "Expected at least one entry in bounds."); + // Algorithm for R2R major version 16 and above + private static IEnumerable DoBounds_2(NativeReader nativeReader) + { + NibbleReader reader = new(nativeReader, 0); + + uint boundsEntryCount = reader.ReadUInt(); + Debug.Assert(boundsEntryCount > 0, "Expected at least one entry in bounds."); + + uint bitsForNativeDelta = reader.ReadUInt() + 1; // Number of bits needed for native deltas + uint bitsForILOffsets = reader.ReadUInt() + 1; // Number of bits needed for IL offsets + + uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + 2; // 2 bits for source type + ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1; + int offsetOfActualBoundsData = reader.GetNextByteOffset(); + + uint bitsCollected = 0; + ulong bitTemp = 0; + uint curBoundsProcessed = 0; + + uint previousNativeOffset = 0; + + while (curBoundsProcessed < boundsEntryCount) + { + bitTemp |= ((uint)nativeReader[offsetOfActualBoundsData++]) << (int)bitsCollected; + bitsCollected += 8; + while (bitsCollected >= bitsPerEntry) + { + ulong mappingDataEncoded = bitsMeaningfulMask & bitTemp; + bitTemp >>= (int)bitsPerEntry; + bitsCollected -= bitsPerEntry; - return DoBounds(boundsReader, countEntries); + SourceTypes sourceType = (mappingDataEncoded & 0x3) switch + { + 0 => SourceTypes.SourceTypeInvalid, + 1 => SourceTypes.CallInstruction, + 2 => SourceTypes.StackEmpty, + 3 => SourceTypes.StackEmpty | SourceTypes.CallInstruction, + _ => throw new InvalidOperationException($"Unknown source type encoding: {mappingDataEncoded & 0x3}") + }; + + mappingDataEncoded >>= 2; + uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1)); + previousNativeOffset += nativeOffsetDelta; + uint nativeOffset = previousNativeOffset; + + mappingDataEncoded >>= (int)bitsForNativeDelta; + uint ilOffset = (uint)mappingDataEncoded + IL_OFFSET_BIAS; + + yield return new OffsetMapping() + { + NativeOffset = nativeOffset, + ILOffset = ilOffset, + SourceType = sourceType + }; + curBoundsProcessed++; + } } - return Enumerable.Empty(); } - private static IEnumerable DoBounds(NibbleReader reader, uint count) + // Algorithm for R2R major version 15 and below + private static IEnumerable DoBounds_1(NativeReader nativeReader) { + NibbleReader reader = new(nativeReader, 0); + + uint count = reader.ReadUInt(); + Debug.Assert(count > 0, "Expected at least one entry in bounds."); + uint nativeOffset = 0; for (uint i = 0; i < count; i++) { @@ -140,14 +169,22 @@ private static IEnumerable DoBounds(NibbleReader reader, uint co // il offsets are encoded with a bias of ICorDebugInfo::MAX_MAPPING_VALUE uint ilOffset = unchecked(reader.ReadUInt() + IL_OFFSET_BIAS); + uint sourceType = reader.ReadUInt(); - SourceTypes_1 sourceType = (SourceTypes_1)reader.ReadUInt(); - - yield return new OffsetMapping_1 + yield return new OffsetMapping() { NativeOffset = nativeOffset, ILOffset = ilOffset, - InternalSourceType = sourceType + SourceType = sourceType switch + { + 0x00 => SourceTypes.SourceTypeInvalid, + 0x01 => SourceTypes.SequencePoint, + 0x02 => SourceTypes.StackEmpty, + 0x04 => SourceTypes.CallSite, + 0x08 => SourceTypes.NativeEndOffsetUnknown, + 0x10 => SourceTypes.CallInstruction, + _ => throw new InvalidOperationException($"Unknown source type: {sourceType}") + } }; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index 78c14957c9fde5..e11993f3baa08c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -86,7 +86,7 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd(r2rInfo.DebugInfo); ILCompiler.Reflection.ReadyToRun.NativeReader imageReader = new( - new TargetStream(Target, imageBase, debugInfoData.Size), + new TargetStream(Target, imageBase, debugInfoData.VirtualAddress + debugInfoData.Size), Target.IsLittleEndian ); ILCompiler.Reflection.ReadyToRun.NativeArray debugInfoArray = new(imageReader, debugInfoData.VirtualAddress); diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index 56c0adbd356181..78d0a8c4448cbc 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -134,7 +134,7 @@ int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint o try { TargetCodePointer pCode = address.ToTargetCodePointer(_target); - List map = _target.Contracts.DebugInfo.GetMethodNativeMap(pCode, out uint codeOffset).ToList(); + List map = _target.Contracts.DebugInfo.GetMethodNativeMap(pCode, out uint codeOffset).ToList(); uint hits = 0; for (int i = 0; i < map.Count; i++) @@ -161,6 +161,7 @@ int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint o catch (System.Exception ex) { hr = ex.HResult; + Debug.WriteLine(ex.StackTrace); } #if DEBUG diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 3f7a880196944e..186869ceb3fbc7 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -413,6 +413,11 @@ int IXCLRDataProcess.EnumMethodInstanceByAddress(ulong* handle, out IXCLRDataMet hr = HResults.S_FALSE; } } + catch (InvalidOperationException) + { + // If the target read fails, expect HResult to be CORDBG_E_READVIRTUAL_FAILURE + hr = CorDbgHResults.CORDBG_E_READVIRTUAL_FAILURE; + } catch (System.Exception ex) { hr = ex.HResult; From 0d3a6c9c13d8fd4f2defdd72364b921b5394a529 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 12 Aug 2025 14:10:52 -0400 Subject: [PATCH 46/54] fix tests --- src/coreclr/vm/datadescriptor/datadescriptor.inc | 2 +- src/coreclr/vm/readytoruninfo.h | 2 +- .../ExecutionManagerCore.ReadyToRunJitManager.cs | 2 +- .../Data/ReadyToRunInfo.cs | 4 ++-- .../MockDescriptors/MockDescriptors.ExecutionManager.cs | 5 ++++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index c3e4b9228a6c11..0947a56cf7e53a 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -519,7 +519,7 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data::NumHotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, HotColdMap, cdac_data::HotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_data::DelayLoadMethodCallThunks) -CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DebugInfo, cdac_data::DebugInfo) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DebugInfo, cdac_data::DebugInfoSection) CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) CDAC_TYPE_END(ReadyToRunInfo) diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 2b5e1791e15a2c..5b7d071c171eb9 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -352,7 +352,7 @@ struct cdac_data static constexpr size_t NumHotColdMap = offsetof(ReadyToRunInfo, m_nHotColdMap); static constexpr size_t HotColdMap = offsetof(ReadyToRunInfo, m_pHotColdMap); static constexpr size_t DelayLoadMethodCallThunks = offsetof(ReadyToRunInfo, m_pSectionDelayLoadMethodCallThunks); - static constexpr size_t DebugInfo = offsetof(ReadyToRunInfo, m_pSectionDebugInfo); + static constexpr size_t DebugInfoSection = offsetof(ReadyToRunInfo, m_pSectionDebugInfo); static constexpr size_t EntryPointToMethodDescMap = offsetof(ReadyToRunInfo, m_entryPointToMethodDescMap); }; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index e11993f3baa08c..166e85ca8945b3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -83,7 +83,7 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index); index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _); - Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd(r2rInfo.DebugInfo); + Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd(r2rInfo.DebugInfoSection); ILCompiler.Reflection.ReadyToRun.NativeReader imageReader = new( new TargetStream(Target, imageBase, debugInfoData.VirtualAddress + debugInfoData.Size), diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs index 95bb81bd9d76fe..bce79429a10f19 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -30,7 +30,7 @@ public ReadyToRunInfo(Target target, TargetPointer address) : TargetPointer.Null; DelayLoadMethodCallThunks = target.ReadPointer(address + (ulong)type.Fields[nameof(DelayLoadMethodCallThunks)].Offset); - DebugInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(DebugInfo)].Offset); + DebugInfoSection = target.ReadPointer(address + (ulong)type.Fields[nameof(DebugInfoSection)].Offset); // Map is from the composite info pointer (set to itself for non-multi-assembly composite images) EntryPointToMethodDescMap = CompositeInfo + (ulong)type.Fields[nameof(EntryPointToMethodDescMap)].Offset; @@ -47,6 +47,6 @@ public ReadyToRunInfo(Target target, TargetPointer address) public TargetPointer HotColdMap { get; } public TargetPointer DelayLoadMethodCallThunks { get; } - public TargetPointer DebugInfo { get; } + public TargetPointer DebugInfoSection { get; } public TargetPointer EntryPointToMethodDescMap { get; } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs index c193f426da87cf..85efb7fd191203 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -17,7 +17,7 @@ internal class ExecutionManager { public const ulong ExecutionManagerCodeRangeMapAddress = 0x000a_fff0; - const int RealCodeHeaderSize = 0x20; // must be big enough for the offsets of RealCodeHeader size in ExecutionManagerTestTarget, below + const int RealCodeHeaderSize = 0x28; // must be big enough for the offsets of RealCodeHeader size in ExecutionManagerTestTarget, below public struct AllocationRange { @@ -232,6 +232,7 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect Fields = [ new(nameof(Data.RealCodeHeader.MethodDesc), DataType.pointer), + new(nameof(Data.RealCodeHeader.DebugInfo), DataType.pointer), new(nameof(Data.RealCodeHeader.GCInfo), DataType.pointer), new(nameof(Data.RealCodeHeader.NumUnwindInfos), DataType.uint32), new(nameof(Data.RealCodeHeader.UnwindInfos), DataType.pointer), @@ -250,6 +251,7 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect new(nameof(Data.ReadyToRunInfo.NumHotColdMap), DataType.uint32), new(nameof(Data.ReadyToRunInfo.HotColdMap), DataType.pointer), new(nameof(Data.ReadyToRunInfo.DelayLoadMethodCallThunks), DataType.pointer), + new(nameof(Data.ReadyToRunInfo.DebugInfoSection), DataType.pointer), new(nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap), DataType.Unknown, helpers.LayoutFields(MockDescriptors.HashMap.HashMapFields.Fields).Stride), ] }; @@ -430,6 +432,7 @@ public TargetCodePointer AddJittedMethod(JittedCodeRange jittedCodeRange, uint c Builder.TargetTestHelpers.WritePointer(chf.Slice(tyInfo.Fields[nameof(Data.RealCodeHeader.MethodDesc)].Offset, Builder.TargetTestHelpers.PointerSize), methodDescAddress); // fields are not used in the test, but we still need to write them + Builder.TargetTestHelpers.WritePointer(chf.Slice(tyInfo.Fields[nameof(Data.RealCodeHeader.DebugInfo)].Offset, Builder.TargetTestHelpers.PointerSize), TargetPointer.Null); Builder.TargetTestHelpers.WritePointer(chf.Slice(tyInfo.Fields[nameof(Data.RealCodeHeader.GCInfo)].Offset, Builder.TargetTestHelpers.PointerSize), TargetPointer.Null); Builder.TargetTestHelpers.Write(chf.Slice(tyInfo.Fields[nameof(Data.RealCodeHeader.NumUnwindInfos)].Offset, sizeof(uint)), 0u); Builder.TargetTestHelpers.WritePointer(chf.Slice(tyInfo.Fields[nameof(Data.RealCodeHeader.UnwindInfos)].Offset, Builder.TargetTestHelpers.PointerSize), TargetPointer.Null); From 894fad5e7166f6d016f6a8fbf7b4a2aa9bce1061 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 12 Aug 2025 15:33:21 -0400 Subject: [PATCH 47/54] fix naming issue --- src/coreclr/vm/datadescriptor/datadescriptor.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 0947a56cf7e53a..656a8675641d11 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -519,7 +519,7 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data::NumHotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, HotColdMap, cdac_data::HotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_data::DelayLoadMethodCallThunks) -CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DebugInfo, cdac_data::DebugInfoSection) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DebugInfoSection, cdac_data::DebugInfoSection) CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) CDAC_TYPE_END(ReadyToRunInfo) From 41de372071520e3dcbb361672eeaee9399b0b522 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 12 Aug 2025 17:24:32 -0400 Subject: [PATCH 48/54] loosen assertion --- .../mscordaccore_universal/Legacy/ClrDataMethodInstance.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index 78d0a8c4448cbc..3cce79b2b973a6 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -161,7 +161,6 @@ int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint o catch (System.Exception ex) { hr = ex.HResult; - Debug.WriteLine(ex.StackTrace); } #if DEBUG @@ -184,7 +183,8 @@ int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint o validateIlOffsets ? localIlOffsetsPtr : null); } - Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + // DAC function returns odd failure codes it doesn't make sense to match directly + Debug.Assert(hrLocal == hr || (hrLocal < 0 && hr < 0), $"cDAC: {hr:x}, DAC: {hrLocal:x}"); if (hr == HResults.S_OK) { From a2819b385183e7cf2542bbe3661eb236f469bccb Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 13 Aug 2025 14:54:48 -0400 Subject: [PATCH 49/54] small improvements --- docs/design/datacontracts/DebugInfo.md | 29 ++++++++------------------ 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 6ad3aeee290868..23b614f9369564 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -17,17 +17,17 @@ public enum SourceTypes : uint ``` ```csharp -public interface IOffsetMapping +public readonly struct OffsetMapping { - public uint NativeOffset { get; } - public uint ILOffset { get; } - public SourceTypes SourceType { get; } + public uint NativeOffset { get; init; } + public uint ILOffset { get; init; } + public SourceTypes SourceType { get; init; } } ``` ```csharp // Given a code pointer, return the associated native/IL offset mapping -IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset); +IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset); ``` ## Version 1 @@ -58,7 +58,7 @@ Constants: ### DebugInfo Stream Encoding -The DebugInfo stream is encoded using varible length 32-bit values with the following scheme: +The DebugInfo stream is encoded using variable length 32-bit values with the following scheme: A value can be stored using one or more nibbles (a nibble is a 4-bit value). 3 bits of a nibble are used to store 3 bits of the value, and the top bit indicates if the following nibble contains rest of the value. If the top bit is not set, then this nibble is the last part of the value. The higher bits of the value are written out first, and the lowest 3 bits are written out last. @@ -82,23 +82,12 @@ Examples: | 512 | 0x200 | 89 08 | | 513 | 0x201 | 89 18 | -Based on the encoding specification, we use an decoder with the following interface: - -```csharp -class DebugStreamReader(TargetPointer startOfBuffer, uint sizeOfBuffer) -{ - // Returns the offset of the next byte relative to the startOfBuffer - public uint GetNextByteIndex(); - - // Reads the next encoded value and moves to the following nibble - public uint ReadEncodedU32(); -} -``` +Based on the encoding specification, we use a decoder defined original for r2r dump `NibbleReader.cs` ### Implementation ``` csharp -IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) +IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) { // Get the method's DebugInfo if (/*ExecutionManager*/.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) @@ -164,4 +153,4 @@ IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCod }; } } -``` \ No newline at end of file +``` From 32fd73dc85567f90cf7a9b7d2225f6a833c60791 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:15:53 -0400 Subject: [PATCH 50/54] remove old debuginfo algo --- .../Contracts/DebugInfo/DebugInfo_1.cs | 40 +------------------ 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs index 80f8d5ceb72be8..de840b3284e9f2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -87,14 +87,14 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DoBounds_2(boundsNativeReader); + return DoBounds(boundsNativeReader); } return Enumerable.Empty(); } // Algorithm for R2R major version 16 and above - private static IEnumerable DoBounds_2(NativeReader nativeReader) + private static IEnumerable DoBounds(NativeReader nativeReader) { NibbleReader reader = new(nativeReader, 0); @@ -152,40 +152,4 @@ private static IEnumerable DoBounds_2(NativeReader nativeReader) } } - - // Algorithm for R2R major version 15 and below - private static IEnumerable DoBounds_1(NativeReader nativeReader) - { - NibbleReader reader = new(nativeReader, 0); - - uint count = reader.ReadUInt(); - Debug.Assert(count > 0, "Expected at least one entry in bounds."); - - uint nativeOffset = 0; - for (uint i = 0; i < count; i++) - { - // native offsets are encoded as a delta from the previous offset - nativeOffset += reader.ReadUInt(); - - // il offsets are encoded with a bias of ICorDebugInfo::MAX_MAPPING_VALUE - uint ilOffset = unchecked(reader.ReadUInt() + IL_OFFSET_BIAS); - uint sourceType = reader.ReadUInt(); - - yield return new OffsetMapping() - { - NativeOffset = nativeOffset, - ILOffset = ilOffset, - SourceType = sourceType switch - { - 0x00 => SourceTypes.SourceTypeInvalid, - 0x01 => SourceTypes.SequencePoint, - 0x02 => SourceTypes.StackEmpty, - 0x04 => SourceTypes.CallSite, - 0x08 => SourceTypes.NativeEndOffsetUnknown, - 0x10 => SourceTypes.CallInstruction, - _ => throw new InvalidOperationException($"Unknown source type: {sourceType}") - } - }; - } - } } From a263a5ffcb0e6f025eabc953999282f699881f2e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:42:05 -0400 Subject: [PATCH 51/54] update docs and debuginfo --- docs/design/datacontracts/DebugInfo.md | 140 ++++++++++++------ .../Contracts/IDebugInfo.cs | 16 +- .../Contracts/DebugInfo/DebugInfo_1.cs | 5 +- 3 files changed, 102 insertions(+), 59 deletions(-) diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 23b614f9369564..182675565d10b2 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -5,14 +5,12 @@ This contract is for fetching information related to DebugInfo associated with n ## APIs of contract ```csharp +[Flags] public enum SourceTypes : uint { SourceTypeInvalid = 0x00, // To indicate that nothing else applies - SequencePoint = 0x01, // The debugger asked for it. - StackEmpty = 0x02, // The stack is empty here - CallSite = 0x04, // This is a call site. - NativeEndOffsetUnknown = 0x08, // Indicates a epilog endpoint - CallInstruction = 0x10 // The actual instruction of a call. + StackEmpty = 0x01, // The stack is empty here + CallInstruction = 0x02 // The actual instruction of a call. } ``` @@ -47,14 +45,9 @@ Constants: | Constant Name | Meaning | Value | | --- | --- | --- | | IL_OFFSET_BIAS | IL offsets are encoded in the DebugInfo with this bias. | `0xfffffffd` (-3) | +| DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS | Indicates bounds data contains instrumented bounds | `0xFFFFFFFF` | | EXTRA_DEBUG_INFO_PATCHPOINT | Indicates debug info contains patchpoint information | 0x1 | | EXTRA_DEBUG_INFO_RICH | Indicates debug info contains rich information | 0x2 | -| SourceTypeInvalid | To indicate that nothing else applies | 0x00 | -| SourceTypeSequencePoint | Source type indicating the debugger asked for it | 0x01 | -| SourceTypeStackEmpty | Source type indicating the stack is empty here | 0x02 | -| SourceTypeCallSite | Source type indicating This is a call site | 0x04 | -| SourceTypeNativeEndOffsetUnknown | Source type indicating a epilog endpoint | 0x08 | -| SourceTypeCallInstruction | Source type indicating the actual instruction of a call | 0x10 | ### DebugInfo Stream Encoding @@ -82,7 +75,19 @@ Examples: | 512 | 0x200 | 89 08 | | 513 | 0x201 | 89 18 | -Based on the encoding specification, we use a decoder defined original for r2r dump `NibbleReader.cs` +Based on the encoding specification, we use a decoder defined originally for r2r dump `NibbleReader.cs` + +### Bounds Data Encoding (R2R Major Version 16+) + +For R2R major version 16 and above, the bounds data uses a bit-packed encoding algorithm: + +1. The bounds entry count, bits needed for native deltas, and bits needed for IL offsets are encoded using the nibble scheme above +2. Each bounds entry is then bit-packed with: + - 2 bits for source type (SourceTypeInvalid=0, CallInstruction=1, StackEmpty=2, StackEmpty|CallInstruction=3) + - Variable bits for native offset delta (accumulated from previous offset) + - Variable bits for IL offset (with IL_OFFSET_BIAS applied) + +The bit-packed data is read byte by byte, collecting bits until enough are available for each entry. ### Implementation @@ -90,16 +95,18 @@ Based on the encoding specification, we use a decoder defined original for r2r d IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) { // Get the method's DebugInfo - if (/*ExecutionManager*/.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) - throw NotValid // pCode must point to a valid code block - TargetPointer debugInfo = /*ExecutionManager*/.GetDebugInfo(cbh, out bool hasFlagByte); + if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) + throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}."); + TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool hasFlagByte); - TargetCodePointer nativeCodeStart = /*ExecutionManager*/.GetStartAddress(cbh); + TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh); + codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target)); - TargetPointer startAddress = /*convert nativeCodeStart to a TargetPointer*/ - TargetPointer currAddress = /*convert pCode to a TargetPointer*/ - codeOffset = currAddress - startAddress; + return RestoreBoundaries(debugInfo, hasFlagByte); +} +private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte) +{ if (hasFlagByte) { // Check flag byte and skip over any patchpoint info @@ -119,38 +126,87 @@ IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode } } - DebugStreamReader reader = new(debugInfo, 12 /*maximum size of 2 32bit ints compressed*/); - - uint cbBounds = reader.ReadEncodedU32(); - uint _ /*cbVars*/ = reader.ReadEncodedU32(); + } - TargetPointer addrBounds = debugInfo + reader.NextByteIndex; + NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 24 /*maximum size of 4 32bit ints compressed*/), _target.IsLittleEndian); + NibbleReader nibbleReader = new(nibbleNativeReader, 0); - if (cbBounds == 0) - // No bounds data was found, return an empty enumerable - return Enumerable.Empty(); + uint cbBounds = nibbleReader.ReadUInt(); + uint cbUninstrumentedBounds = 0; + if (cbBounds == DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS) + { + // This means we have instrumented bounds. + cbBounds = nibbleReader.ReadUInt(); + cbUninstrumentedBounds = nibbleReader.ReadUInt(); + } + uint _ /*cbVars*/ = nibbleReader.ReadUInt(); - // Create a DebugInfo stream decoder with the start address and size of bounds data - DebugStreamReader boundsReader = new(addrBounds, cbBounds); + TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); + // TargetPointer addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; - uint countEntries = boundsReader.ReadEncodedU32(); - uint nativeOffset = 0; - for (uint i = 0; i < count; i++) + if (cbBounds > 0) { - // native offsets are encoded as a delta from the previous offset - nativeOffset += boundsReader.ReadEncodedU32(); + NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); + return DoBounds(boundsNativeReader); + } + + return Enumerable.Empty(); +} + +private static IEnumerable DoBounds(NativeReader nativeReader) +{ + NibbleReader reader = new(nativeReader, 0); - // il offsets are encoded with a bias of IL_OFFSET_BIAS - uint ilOffset = unchecked(boundsReader.ReadEncodedU32() + IL_OFFSET_BIAS); + uint boundsEntryCount = reader.ReadUInt(); - uint sourceType = boundsReader.ReadEncodedU32(); + uint bitsForNativeDelta = reader.ReadUInt() + 1; // Number of bits needed for native deltas + uint bitsForILOffsets = reader.ReadUInt() + 1; // Number of bits needed for IL offsets - yield return new OffsetMapping_1 + uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + 2; // 2 bits for source type + ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1; + int offsetOfActualBoundsData = reader.GetNextByteOffset(); + + uint bitsCollected = 0; + ulong bitTemp = 0; + uint curBoundsProcessed = 0; + + uint previousNativeOffset = 0; + + while (curBoundsProcessed < boundsEntryCount) + { + bitTemp |= ((uint)nativeReader[offsetOfActualBoundsData++]) << (int)bitsCollected; + bitsCollected += 8; + while (bitsCollected >= bitsPerEntry) { - NativeOffset = nativeOffset, - ILOffset = ilOffset, - InternalSourceType = sourceType - }; + ulong mappingDataEncoded = bitsMeaningfulMask & bitTemp; + bitTemp >>= (int)bitsPerEntry; + bitsCollected -= bitsPerEntry; + + SourceTypes sourceType = (mappingDataEncoded & 0x3) switch + { + 0 => SourceTypes.SourceTypeInvalid, + 1 => SourceTypes.CallInstruction, + 2 => SourceTypes.StackEmpty, + 3 => SourceTypes.StackEmpty | SourceTypes.CallInstruction, + _ => throw new InvalidOperationException($"Unknown source type encoding: {mappingDataEncoded & 0x3}") + }; + + mappingDataEncoded >>= 2; + uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1)); + previousNativeOffset += nativeOffsetDelta; + uint nativeOffset = previousNativeOffset; + + mappingDataEncoded >>= (int)bitsForNativeDelta; + uint ilOffset = (uint)mappingDataEncoded + IL_OFFSET_BIAS; + + yield return new OffsetMapping() + { + NativeOffset = nativeOffset, + ILOffset = ilOffset, + SourceType = sourceType + }; + curBoundsProcessed++; + } } } ``` diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs index f52dbf59c26f26..f793fc88cc4be2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs @@ -14,25 +14,13 @@ public enum SourceTypes : uint /// SourceTypeInvalid = 0x00, /// - /// The debugger asked for it - /// - SequencePoint = 0x01, - /// /// The stack is empty here /// - StackEmpty = 0x02, - /// - /// This is a call site - /// - CallSite = 0x04, - /// - /// Indicate an epilog endpoint - /// - NativeEndOffsetUnknown = 0x08, + StackEmpty = 0x01, /// /// The actual instruction of a call /// - CallInstruction = 0x10 + CallInstruction = 0x02, } public readonly struct OffsetMapping diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs index de840b3284e9f2..9f4656e45407e2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -77,9 +77,9 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo { // This means we have instrumented bounds. cbBounds = nibbleReader.ReadUInt(); - // cbUninstrumentedBounds = nibbleReader.ReadUInt(); + uint _1 /*cbUninstrumentedBounds*/ = nibbleReader.ReadUInt(); } - uint _ /*cbVars*/ = nibbleReader.ReadUInt(); + uint _2 /*cbVars*/ = nibbleReader.ReadUInt(); TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); // TargetPointer addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; @@ -93,7 +93,6 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo return Enumerable.Empty(); } - // Algorithm for R2R major version 16 and above private static IEnumerable DoBounds(NativeReader nativeReader) { NibbleReader reader = new(nativeReader, 0); From b960ed642ee0517defc9b67cca4f7e3c7aca06aa Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Tue, 19 Aug 2025 15:13:30 -0400 Subject: [PATCH 52/54] update to support reading uninstrumented bounds --- docs/design/datacontracts/DebugInfo.md | 21 ++++++++++++------- .../Contracts/IDebugInfo.cs | 2 +- .../Contracts/DebugInfo/DebugInfo_1.cs | 19 +++++++++++------ .../Legacy/ClrDataMethodInstance.cs | 5 ++++- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 182675565d10b2..034eb3609900ed 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -24,8 +24,10 @@ public readonly struct OffsetMapping ``` ```csharp -// Given a code pointer, return the associated native/IL offset mapping -IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset); +// Given a code pointer, return the associated native/IL offset mapping and codeOffset. +// If preferUninstrumented, will always read the uninstrumented bounds. +// Otherwise will read the instrumented bounds and fallback to the uninstrumented bounds. +IEnumerable GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset); ``` ## Version 1 @@ -92,7 +94,7 @@ The bit-packed data is read byte by byte, collecting bits until enough are avail ### Implementation ``` csharp -IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) +IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset) { // Get the method's DebugInfo if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) @@ -102,10 +104,10 @@ IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh); codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target)); - return RestoreBoundaries(debugInfo, hasFlagByte); + return RestoreBoundaries(debugInfo, hasFlagByte, preferUninstrumented); } -private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte) +private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte, bool preferUninstrumented) { if (hasFlagByte) { @@ -126,8 +128,6 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo } } - } - NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 24 /*maximum size of 4 32bit ints compressed*/), _target.IsLittleEndian); NibbleReader nibbleReader = new(nibbleNativeReader, 0); @@ -144,6 +144,13 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); // TargetPointer addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; + if (preferUninstrumented && cbUninstrumentedBounds != 0) + { + // If we have uninstrumented bounds, we will use them instead of the regular bounds. + addrBounds += cbBounds; + cbBounds = cbUninstrumentedBounds; + } + if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs index f793fc88cc4be2..80a34bb8aef754 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs @@ -33,7 +33,7 @@ public readonly struct OffsetMapping public interface IDebugInfo : IContract { static string IContract.Name { get; } = nameof(DebugInfo); - IEnumerable GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) => throw new NotImplementedException(); + IEnumerable GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset) => throw new NotImplementedException(); } public readonly struct DebugInfo : IDebugInfo diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs index 9f4656e45407e2..a5ac95ca95847e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -26,7 +26,7 @@ internal enum ExtraDebugInfoFlags_1 : byte private readonly Target _target = target; private readonly IExecutionManager _eman = target.Contracts.ExecutionManager; - IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, out uint codeOffset) + IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset) { // Get the method's DebugInfo if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) @@ -36,10 +36,10 @@ IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh); codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target)); - return RestoreBoundaries(debugInfo, hasFlagByte); + return RestoreBoundaries(debugInfo, hasFlagByte, preferUninstrumented); } - private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte) + private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte, bool preferUninstrumented) { if (hasFlagByte) { @@ -72,18 +72,25 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo NibbleReader nibbleReader = new(nibbleNativeReader, 0); uint cbBounds = nibbleReader.ReadUInt(); - // uint cbUninstrumentedBounds = 0; + uint cbUninstrumentedBounds = 0; if (cbBounds == DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS) { // This means we have instrumented bounds. cbBounds = nibbleReader.ReadUInt(); - uint _1 /*cbUninstrumentedBounds*/ = nibbleReader.ReadUInt(); + cbUninstrumentedBounds = nibbleReader.ReadUInt(); } - uint _2 /*cbVars*/ = nibbleReader.ReadUInt(); + uint _ /*cbVars*/ = nibbleReader.ReadUInt(); TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); // TargetPointer addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; + if (preferUninstrumented && cbUninstrumentedBounds != 0) + { + // If we have uninstrumented bounds, we will use them instead of the regular bounds. + addrBounds += cbBounds; + cbBounds = cbUninstrumentedBounds; + } + if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs index 3cce79b2b973a6..ae823df64d721a 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -134,7 +134,10 @@ int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint o try { TargetCodePointer pCode = address.ToTargetCodePointer(_target); - List map = _target.Contracts.DebugInfo.GetMethodNativeMap(pCode, out uint codeOffset).ToList(); + List map = _target.Contracts.DebugInfo.GetMethodNativeMap( + pCode, + preferUninstrumented: false, + out uint codeOffset).ToList(); uint hits = 0; for (int i = 0; i < map.Count; i++) From 6d2c0a66c42c8aa5398e2877375f08b0006a60fb Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:37:58 -0400 Subject: [PATCH 53/54] remove no longer needed specific catch --- .../Legacy/SOSDacImpl.IXCLRDataProcess.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 186869ceb3fbc7..3f7a880196944e 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -413,11 +413,6 @@ int IXCLRDataProcess.EnumMethodInstanceByAddress(ulong* handle, out IXCLRDataMet hr = HResults.S_FALSE; } } - catch (InvalidOperationException) - { - // If the target read fails, expect HResult to be CORDBG_E_READVIRTUAL_FAILURE - hr = CorDbgHResults.CORDBG_E_READVIRTUAL_FAILURE; - } catch (System.Exception ex) { hr = ex.HResult; From 3ed766de2d013d1382d5eac8c49669b125d953e0 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 19 Aug 2025 22:21:11 -0400 Subject: [PATCH 54/54] remove duplicate --- docs/design/datacontracts/Loader.md | 70 ----------------------------- 1 file changed, 70 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 39959e322954a7..30372f60e6eb71 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -580,73 +580,3 @@ class InstMethodHashTable } } ``` - -### DacEnumerableHash (EETypeHashTable and InstMethodHashTable) - -Both `EETypeHashTable` and `InstMethodHashTable` are based on the templated `DacEnumerableHash`. Because the base class is templated on the derived type, offsets may be different in derived types. - -The base implementation of `DacEnumerableHash` uses four datadescriptors: -| Datadescriptor | Purpose | -| --- | --- | -| `Buckets` | Pointer to the bucket array | -| `Count` | Number of elements in the hash table | -| `VolatileEntryValue` | The data held by an entry, defined by the derived class | -| `VolatileEntryNextEntry` | The next pointer on an hash table entry | - -The hash table is laid out as an array of `VolatileEntry` pointers's (buckets), each possibly forming a chain for values that hash into that bucket. The first three buckets are special and reserved for metadata. Instead of containing a `VolatileEntry`, these pointers are read as values with the following meanings. - -| Reserved Bucket offset | Purpose | -| --- | --- | -| `0` | Length of the Bucket array, this value does not include the first 3 slots which are special | -| `1` | Pointer to the next bucket array, not currently used in the cDAC | -| `2` | End sentinel for the current bucket array, not currently used in the cDAC | - -The current cDAC implementation does not use the 'hash' part of the table at all. Instead it iterates all elements in the table. Following the existing iteration logic in the runtime (and DAC), resizing the table while iterating is not supported. Given this constraint, the pointer to the next bucket array (resized data table) and the current end sentinel are not required to iterate all entries. - -To read all entries in the hash table: -1. Read the length bucket to find the number of chains `n`. -2. Initialize a list of elements `entries = []`. -3. For each chain, (buckets with offsets `3..n + 3`): - 1. Read the pointer in the bucket as `volatileEntryPtr`. - 2. If `volatileEntryPtr & 0x1 == 0x1`, this is an end sentinel and we stop reading this chain. - 3. Otherwise, add `volatileEntryPtr + /* VolatileEntryValue offset */` to entries. This points to the derived class defined data type. - 4. Set `volatileEntryPtr` to the value of the pointer located at `volatileEntryPtr + /* VolatileEntryNextEntry offset */` and go to step 3.2. -4. Return `entries` to be further parsed by derived classes. - -While both EETypeHashTable and InstMethodHashTable store pointer sized data types, they both use the LSBs as special flags. - -#### EETypeHashTable -EETypeHashTable uses the LSB to indicate if the TypeHandle is a hot entry. The cDAC implementation separates each value `value` in the table into two parts. The actual TypeHandle pointer and the associated flags. - -```csharp -class EETypeHashTable -{ - private const ulong FLAG_MASK = 0x1ul; - - public IReadOnlyList Entires { get; } - - public readonly struct Entry(TargetPointer value) - { - public TargetPointer TypeHandle { get; } = value & ~FLAG_MASK; - public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); - } -} -``` - -#### InstMethodHashTable -InstMethodHashTable uses the 2 LSBs as flags for the MethodDesc. The cDAC implementation separates each value `value` in the table into two parts. The actual MethodDesc pointer and the associated flags. - -```csharp -class InstMethodHashTable -{ - private const ulong FLAG_MASK = 0x3ul; - - public IReadOnlyList Entires { get; } - - public readonly struct Entry(TargetPointer value) - { - public TargetPointer MethodDesc { get; } = value & ~FLAG_MASK; - public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); - } -} -```