diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md new file mode 100644 index 00000000000000..034eb3609900ed --- /dev/null +++ b/docs/design/datacontracts/DebugInfo.md @@ -0,0 +1,219 @@ +# Contract DebugInfo + +This contract is for fetching information related to DebugInfo associated with native code. + +## APIs of contract + +```csharp +[Flags] +public enum SourceTypes : uint +{ + SourceTypeInvalid = 0x00, // To indicate that nothing else applies + StackEmpty = 0x01, // The stack is empty here + CallInstruction = 0x02 // The actual instruction of a call. +} +``` + +```csharp +public readonly struct OffsetMapping +{ + 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 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 + +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) | +| 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 | + +### DebugInfo Stream Encoding + +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. + +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 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 + +``` csharp +IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, 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, preferUninstrumented); +} + +private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte, bool preferUninstrumented) +{ + 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; + } + } + + 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 (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); + return DoBounds(boundsNativeReader); + } + + return Enumerable.Empty(); +} + +private static IEnumerable DoBounds(NativeReader nativeReader) +{ + NibbleReader reader = new(nativeReader, 0); + + uint boundsEntryCount = reader.ReadUInt(); + + 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; + + 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/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 1b5e360f6ec308..103aac455a2a48 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,10 @@ 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). + ### 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 46fa6b716fb651..30372f60e6eb71 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 | | --- | --- | --- | 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/DebugInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs index 7796c9129fbc89..019b74c714b3a9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs @@ -158,7 +158,7 @@ private void ParseBounds(NativeReader imageReader, int offset) uint bitsCollected = 0; ulong bitTemp = 0; uint curBoundsProcessed = 0; - + uint previousNativeOffset = 0; while (curBoundsProcessed < boundsEntryCount) 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 44d6c238005318..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; } @@ -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 5a5755306a4ebd..ffc5f2ec3a0c9d 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -554,6 +554,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*/, DebugInfoSection, cdac_data::DebugInfoSection) CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) CDAC_TYPE_END(ReadyToRunInfo) @@ -620,9 +621,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)) @@ -630,6 +637,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)) @@ -927,6 +939,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..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)) @@ -884,6 +883,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..5b7d071c171eb9 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 DebugInfoSection = 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..80a34bb8aef754 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs @@ -0,0 +1,42 @@ +// 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; + +[Flags] +public enum SourceTypes : uint +{ + /// + /// Indicates that no other options apply + /// + SourceTypeInvalid = 0x00, + /// + /// The stack is empty here + /// + StackEmpty = 0x01, + /// + /// The actual instruction of a call + /// + CallInstruction = 0x02, +} + +public readonly struct OffsetMapping +{ + 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, bool preferUninstrumented, 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 c2619f97b7b679..86fb2923186657 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -110,6 +110,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 3418712a4422e1..e8a7e41e6a4afe 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..a5ac95ca95847e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs @@ -0,0 +1,161 @@ +// 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 DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS = 0xFFFFFFFF; + private const uint IL_OFFSET_BIAS = unchecked((uint)-3); + + [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, bool preferUninstrumented, 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, preferUninstrumented); + } + + private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte, bool preferUninstrumented) + { + 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, 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 (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); + return DoBounds(boundsNativeReader); + } + + return Enumerable.Empty(); + } + + private static IEnumerable DoBounds(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; + + 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.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..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 @@ -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.DebugInfoSection); + + ILCompiler.Reflection.ReadyToRun.NativeReader imageReader = new( + new TargetStream(Target, imageBase, debugInfoData.VirtualAddress + 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/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..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,6 +30,7 @@ public ReadyToRunInfo(Target target, TargetPointer address) : TargetPointer.Null; DelayLoadMethodCallThunks = target.ReadPointer(address + (ulong)type.Fields[nameof(DelayLoadMethodCallThunks)].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; @@ -46,5 +47,6 @@ public ReadyToRunInfo(Target target, TargetPointer address) public TargetPointer HotColdMap { get; } public TargetPointer DelayLoadMethodCallThunks { get; } + public TargetPointer DebugInfoSection { 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..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 @@ -12,12 +12,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..ae823df64d721a 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,87 @@ 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, + preferUninstrumented: false, + 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); + } + + // 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) + { + 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; 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); 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);