From f4c15667664e1a4aaa4f25da51722075c9603f3e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:54:05 -0500 Subject: [PATCH 01/11] remove patchpoint datadescriptors --- src/coreclr/inc/patchpointinfo.h | 16 ---------------- src/coreclr/vm/datadescriptor/datadescriptor.inc | 5 ----- 2 files changed, 21 deletions(-) diff --git a/src/coreclr/inc/patchpointinfo.h b/src/coreclr/inc/patchpointinfo.h index f403d268d20a7c..f2c01d351c67c2 100644 --- a/src/coreclr/inc/patchpointinfo.h +++ b/src/coreclr/inc/patchpointinfo.h @@ -7,10 +7,6 @@ #include -#ifndef JIT_BUILD -#include "cdacdata.h" -#endif // JIT_BUILD - #ifndef _PATCHPOINTINFO_H_ #define _PATCHPOINTINFO_H_ @@ -205,19 +201,7 @@ 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/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index cc578f17a00b19..9a9aa614e4e82a 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -658,11 +658,6 @@ 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)) From 897a8912a9fafea2a01f7a0f92ca16b4b5ad5fb2 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:54:10 -0500 Subject: [PATCH 02/11] add docs --- docs/design/datacontracts/DebugInfo.md | 50 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 034eb3609900ed..9c99dcf31ee809 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -40,7 +40,6 @@ Data descriptors used: Contracts used: | Contract Name | | --- | -| `CodeVersions` | | `ExecutionManager` | Constants: @@ -217,3 +216,52 @@ private static IEnumerable DoBounds(NativeReader nativeReader) } } ``` + +## Version 2 + +Version 2 introduces a unified header format that replaces the flag byte of Version 1 with a "fat" or "slim" chunk table. The underlying nibble-encoded variable-length integer stream and the bounds bit-packing described in the Version 1 sections ([DebugInfo Stream Encoding](#debuginfo-stream-encoding) and [Bounds Data Encoding](#bounds-data-encoding-r2r-major-version-16)) are unchanged; only the initial header (size/chunk enumeration) format differs. + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| _(none)_ | | | + +Contracts used: +| Contract Name | +| --- | +| `ExecutionManager` | + +Constants: +| Constant Name | Meaning | Value | +| --- | --- | --- | +| IL_OFFSET_BIAS | IL offsets bias (unchanged from Version 1) | `0xfffffffd` (-3) | +| DEBUG_INFO_FAT | Marker value in first nibble-coded integer indicating a fat header follows | `0x0` | + +### Header Encoding + +The first nibble-decoded unsigned integer (`countBoundsOrFatMarker`): + +* If `countBoundsOrFatMarker` is equal to `DEBUG_INFO_FAT` (0), the header is FAT and the next 6 nibble-decoded unsigned integers are, in order: + 1. `BoundsSize` + 2. `VarsSize` + 3. `UninstrumentedBoundsSize` + 4. `PatchpointInfoSize` + 5. `RichDebugInfoSize` + 6. `AsyncInfoSize` +* Otherwise, the value is the `BoundsSize` for a SLIM header. The next nibble-decoded unsigned integer is `VarsSize`. All other sizes are implicitly 0. + +After decoding sizes, the start pointer of each chunk is computed by linear accumulation beginning at the first byte after the header stream: + +``` +BoundsStart = debugInfo + headerBytesConsumed +VarsStart = BoundsStart + BoundsSize +UninstrumentedBoundsStart = VarsStart + VarsSize +PatchpointInfoStart = UninstrumentedBoundsStart + UninstrumentedBoundsSize +RichDebugInfoStart = PatchpointInfoStart + PatchpointInfoSize +AsyncInfoStart = RichDebugInfoStart + RichDebugInfoSize +DebugInfoEnd = AsyncInfoStart + AsyncInfoSize +``` + +### Chunk Decoding (Same as Version 1) + +Once the start and size of each chunk are known, the decoding logic for the actual contents is identical to Version 1. \ No newline at end of file From e632409f5753088aa6d52bd7a906f32cb024dfdb Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:54:22 -0500 Subject: [PATCH 03/11] update DebugInfo --- .../Contracts/DebugInfo/DebugInfoFactory.cs | 1 + .../Contracts/DebugInfo/DebugInfoHelpers.cs | 70 +++++++++++ .../Contracts/DebugInfo/DebugInfo_1.cs | 61 +--------- .../Contracts/DebugInfo/DebugInfo_2.cs | 111 ++++++++++++++++++ 4 files changed, 183 insertions(+), 60 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs 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 index 40072342b05892..faf6028a641da8 100644 --- 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 @@ -10,6 +10,7 @@ IDebugInfo IContractFactory.CreateContract(Target target, int versio return version switch { 1 => new DebugInfo_1(target), + 2 => new DebugInfo_2(target), _ => default(DebugInfo), }; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs new file mode 100644 index 00000000000000..98d95e61bec148 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs @@ -0,0 +1,70 @@ +// 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 ILCompiler.Reflection.ReadyToRun; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal static class DebugInfoHelpers +{ + internal static IEnumerable DoBounds(NativeReader nativeReader, uint ilOffsetBias) + { + 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 + ilOffsetBias; + + yield return new OffsetMapping() + { + NativeOffset = nativeOffset, + ILOffset = ilOffset, + SourceType = sourceType + }; + curBoundsProcessed++; + } + } + } +} 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 a5ac95ca95847e..046534e2eaa525 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 @@ -94,68 +94,9 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DoBounds(boundsNativeReader); + return DebugInfoHelpers.DoBounds(boundsNativeReader, IL_OFFSET_BIAS); } 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/DebugInfo/DebugInfo_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs new file mode 100644 index 00000000000000..847f654774b90e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs @@ -0,0 +1,111 @@ +// 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_2(Target target) : IDebugInfo +{ + private const uint DEBUG_INFO_FAT = 0; + private const uint IL_OFFSET_BIAS = unchecked((uint)-3); + + private record struct DebugInfoChunks + { + public TargetPointer BoundsStart; + public uint BoundsSize; + public TargetPointer VarsStart; + public uint VarsSize; + public TargetPointer UninstrumentedBoundsStart; + public uint UninstrumentedBoundsSize; + public TargetPointer PatchpointInfoStart; + public uint PatchpointInfoSize; + public TargetPointer RichDebugInfoStart; + public uint RichDebugInfoSize; + public TargetPointer AsyncInfoStart; + public uint AsyncInfoSize; + public TargetPointer DebugInfoEnd; + } + + 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 _); + + TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh); + codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target)); + + return RestoreBoundaries(debugInfo, preferUninstrumented); + } + + private DebugInfoChunks DecodeChunks(TargetPointer debugInfo) + { + NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 42 /*maximum size of 7 32bit ints compressed*/), _target.IsLittleEndian); + NibbleReader nibbleReader = new(nibbleNativeReader, 0); + + uint countBoundsOrFatMarker = nibbleReader.ReadUInt(); + + DebugInfoChunks chunks = default; + + if (countBoundsOrFatMarker == DEBUG_INFO_FAT) + { + // Fat header + chunks.BoundsSize = nibbleReader.ReadUInt(); + chunks.VarsSize = nibbleReader.ReadUInt(); + chunks.UninstrumentedBoundsSize = nibbleReader.ReadUInt(); + chunks.PatchpointInfoSize = nibbleReader.ReadUInt(); + chunks.RichDebugInfoSize = nibbleReader.ReadUInt(); + chunks.AsyncInfoSize = nibbleReader.ReadUInt(); + } + else + { + chunks.BoundsSize = countBoundsOrFatMarker; + chunks.VarsSize = nibbleReader.ReadUInt(); + chunks.UninstrumentedBoundsSize = 0; + chunks.PatchpointInfoSize = 0; + chunks.RichDebugInfoSize = 0; + chunks.AsyncInfoSize = 0; + } + + chunks.BoundsStart = debugInfo + (uint)nibbleReader.GetNextByteOffset(); + chunks.VarsStart = chunks.BoundsStart + chunks.BoundsSize; + chunks.UninstrumentedBoundsStart = chunks.VarsStart + chunks.VarsSize; + chunks.PatchpointInfoStart = chunks.UninstrumentedBoundsStart + chunks.UninstrumentedBoundsSize; + chunks.RichDebugInfoStart = chunks.PatchpointInfoStart + chunks.PatchpointInfoSize; + chunks.AsyncInfoStart = chunks.RichDebugInfoStart + chunks.RichDebugInfoSize; + chunks.DebugInfoEnd = chunks.AsyncInfoStart + chunks.AsyncInfoSize; + return chunks; + } + + private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool preferUninstrumented) + { + DebugInfoChunks chunks = DecodeChunks(debugInfo); + + TargetPointer addrBounds = chunks.BoundsStart; + uint cbBounds = chunks.BoundsSize; + + if (preferUninstrumented && chunks.UninstrumentedBoundsSize != 0) + { + // If we have uninstrumented bounds, we will use them instead of the regular bounds. + addrBounds = chunks.UninstrumentedBoundsStart; + cbBounds = chunks.UninstrumentedBoundsSize; + } + + if (cbBounds > 0) + { + NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); + return DebugInfoHelpers.DoBounds(boundsNativeReader, IL_OFFSET_BIAS); + } + + return Enumerable.Empty(); + } +} From 1b060411dcac9bd86534b23109ccdf072ba5fb3d Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:58:45 -0500 Subject: [PATCH 04/11] update for sourcetype change --- docs/design/datacontracts/DebugInfo.md | 53 ++++++++++---- .../Contracts/IDebugInfo.cs | 4 ++ .../Contracts/DebugInfo/DebugInfoHelpers.cs | 70 ------------------- .../Contracts/DebugInfo/DebugInfo_1.cs | 60 +++++++++++++++- .../Contracts/DebugInfo/DebugInfo_2.cs | 61 +++++++++++++++- 5 files changed, 163 insertions(+), 85 deletions(-) delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 9c99dcf31ee809..e03e66adcc6667 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -10,7 +10,8 @@ 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. + CallInstruction = 0x02 // The actual instruction of a call + Async = 0x04 // (Version 2+) Indicates suspension/resumption for an async call } ``` @@ -219,7 +220,12 @@ private static IEnumerable DoBounds(NativeReader nativeReader) ## Version 2 -Version 2 introduces a unified header format that replaces the flag byte of Version 1 with a "fat" or "slim" chunk table. The underlying nibble-encoded variable-length integer stream and the bounds bit-packing described in the Version 1 sections ([DebugInfo Stream Encoding](#debuginfo-stream-encoding) and [Bounds Data Encoding](#bounds-data-encoding-r2r-major-version-16)) are unchanged; only the initial header (size/chunk enumeration) format differs. +Version 2 introduces two distinct changes: + +1. A unified header format ("fat" vs "slim") replacing the Version 1 flag byte and implicit layout. +2. An additional `SourceTypes.Async` flag, expanding the per-entry source type encoding from 2 bits to a 3-bit bitfield. + +The nibble-encoded variable-length integer mechanism is unchanged; only the header and bounds entry source-type packing differ. Data descriptors used: | Data Descriptor Name | Field | Meaning | @@ -241,16 +247,16 @@ Constants: The first nibble-decoded unsigned integer (`countBoundsOrFatMarker`): -* If `countBoundsOrFatMarker` is equal to `DEBUG_INFO_FAT` (0), the header is FAT and the next 6 nibble-decoded unsigned integers are, in order: - 1. `BoundsSize` - 2. `VarsSize` - 3. `UninstrumentedBoundsSize` - 4. `PatchpointInfoSize` - 5. `RichDebugInfoSize` - 6. `AsyncInfoSize` -* Otherwise, the value is the `BoundsSize` for a SLIM header. The next nibble-decoded unsigned integer is `VarsSize`. All other sizes are implicitly 0. +* If `countBoundsOrFatMarker == DEBUG_INFO_FAT` (0), the header is FAT and the next 6 nibble-decoded unsigned integers are, in order: + 1. `BoundsSize` + 2. `VarsSize` + 3. `UninstrumentedBoundsSize` + 4. `PatchpointInfoSize` + 5. `RichDebugInfoSize` + 6. `AsyncInfoSize` +* Otherwise (SLIM header), the value is `BoundsSize` and the next nibble-decoded unsigned integer is `VarsSize`; all other sizes are implicitly 0. -After decoding sizes, the start pointer of each chunk is computed by linear accumulation beginning at the first byte after the header stream: +After decoding sizes, chunk start addresses are computed by linear accumulation beginning at the first byte after the header stream: ``` BoundsStart = debugInfo + headerBytesConsumed @@ -262,6 +268,27 @@ AsyncInfoStart = RichDebugInfoStart + RichDebugInfoSize DebugInfoEnd = AsyncInfoStart + AsyncInfoSize ``` -### Chunk Decoding (Same as Version 1) +### Bounds Entry Encoding Differences from Version 1 + +Version 1 packs each bounds entry using: `[2 bits sourceType][nativeDeltaBits][ilOffsetBits]`. + +Version 2 extends this to three independent flag bits for source type and so uses: `[3 bits sourceFlags][nativeDeltaBits][ilOffsetBits]`. + +Source type bits (low → high): +| Bit | Mask | Meaning | +| --- | --- | --- | +| 0 | 0x1 | `CallInstruction` | +| 1 | 0x2 | `StackEmpty` | +| 2 | 0x4 | `Async` (new in Version 2) | + +`SourceTypeInvalid` is represented by all three bits clear (0). Combinations are produced by OR-ing masks (e.g., `StackEmpty | CallInstruction`). + +Pseudo-code for Version 2 source type extraction: +```csharp +SourceTypes sourceType = 0; +if ((encoded & 0x1) != 0) sourceType |= SourceTypes.CallInstruction; +if ((encoded & 0x2) != 0) sourceType |= SourceTypes.StackEmpty; +if ((encoded & 0x4) != 0) sourceType |= SourceTypes.Async; // New bit +``` -Once the start and size of each chunk are known, the decoding logic for the actual contents is identical to Version 1. \ No newline at end of file +After masking the 3 bits, shift them out before reading native delta and IL offset fields as before. \ No newline at end of file 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 80a34bb8aef754..c8121df19a2eda 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 @@ -21,6 +21,10 @@ public enum SourceTypes : uint /// The actual instruction of a call /// CallInstruction = 0x02, + /// + /// Indicates suspension/resumption for an async call + /// + Async = 0x04, } public readonly struct OffsetMapping diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs deleted file mode 100644 index 98d95e61bec148..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs +++ /dev/null @@ -1,70 +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; -using System.Collections.Generic; -using System.Diagnostics; -using ILCompiler.Reflection.ReadyToRun; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -internal static class DebugInfoHelpers -{ - internal static IEnumerable DoBounds(NativeReader nativeReader, uint ilOffsetBias) - { - 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 + ilOffsetBias; - - yield return new OffsetMapping() - { - NativeOffset = nativeOffset, - ILOffset = ilOffset, - SourceType = sourceType - }; - curBoundsProcessed++; - } - } - } -} 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 046534e2eaa525..ebfdf05974f745 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 @@ -94,9 +94,67 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DebugInfoHelpers.DoBounds(boundsNativeReader, IL_OFFSET_BIAS); + 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/DebugInfo/DebugInfo_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs index 847f654774b90e..dc65bd5de4aa20 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs @@ -103,9 +103,68 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DebugInfoHelpers.DoBounds(boundsNativeReader, IL_OFFSET_BIAS); + 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 + 3; // 3 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 = 0; + + if ((mappingDataEncoded & 0x1) != 0) + sourceType |= SourceTypes.CallInstruction; + + if ((mappingDataEncoded & 0x2) != 0) + sourceType |= SourceTypes.StackEmpty; + + if ((mappingDataEncoded & 0x4) != 0) + sourceType |= SourceTypes.Async; + + 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++; + } + } + } } From 97ec3ae209eebb62568d3148fd033e43904a069b Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:59:37 -0500 Subject: [PATCH 05/11] nit --- .../Contracts/DebugInfo/DebugInfo_2.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs index dc65bd5de4aa20..7b439072cd49d3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs @@ -139,15 +139,9 @@ private static IEnumerable DoBounds(NativeReader nativeReader) bitsCollected -= bitsPerEntry; SourceTypes sourceType = 0; - - if ((mappingDataEncoded & 0x1) != 0) - sourceType |= SourceTypes.CallInstruction; - - if ((mappingDataEncoded & 0x2) != 0) - sourceType |= SourceTypes.StackEmpty; - - if ((mappingDataEncoded & 0x4) != 0) - sourceType |= SourceTypes.Async; + if ((mappingDataEncoded & 0x1) != 0) sourceType |= SourceTypes.CallInstruction; + if ((mappingDataEncoded & 0x2) != 0) sourceType |= SourceTypes.StackEmpty; + if ((mappingDataEncoded & 0x4) != 0) sourceType |= SourceTypes.Async; mappingDataEncoded >>= 2; uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1)); From a85baf78f287a8d2fd07efec66e59ddc44b77532 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:16:25 -0500 Subject: [PATCH 06/11] add const for source type bits --- docs/design/datacontracts/DebugInfo.md | 4 +++- .../Contracts/DebugInfo/DebugInfo_1.cs | 3 ++- .../Contracts/DebugInfo/DebugInfo_2.cs | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index e03e66adcc6667..2dd0485d464da1 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -50,6 +50,7 @@ Constants: | 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 | +| SOURCE_TYPE_BITS | Number of bits per bounds entry used to encode source type flags | 2 | ### DebugInfo Stream Encoding @@ -169,7 +170,7 @@ private static IEnumerable DoBounds(NativeReader nativeReader) 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 + uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + SOURCE_TYPE_BITS; // 2 bits for source type ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1; int offsetOfActualBoundsData = reader.GetNextByteOffset(); @@ -242,6 +243,7 @@ Constants: | --- | --- | --- | | IL_OFFSET_BIAS | IL offsets bias (unchanged from Version 1) | `0xfffffffd` (-3) | | DEBUG_INFO_FAT | Marker value in first nibble-coded integer indicating a fat header follows | `0x0` | +| SOURCE_TYPE_BITS | Number of bits per bounds entry used for source type flags | 3 | ### Header Encoding 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 ebfdf05974f745..be48e3f489d044 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 @@ -12,6 +12,7 @@ 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 SOURCE_TYPE_BITS = 2; private const uint IL_OFFSET_BIAS = unchecked((uint)-3); [Flags] @@ -110,7 +111,7 @@ private static IEnumerable DoBounds(NativeReader nativeReader) 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 + uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + SOURCE_TYPE_BITS; // 2 bits for source type ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1; int offsetOfActualBoundsData = reader.GetNextByteOffset(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs index 7b439072cd49d3..db544f5e01a513 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs @@ -12,6 +12,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal sealed class DebugInfo_2(Target target) : IDebugInfo { private const uint DEBUG_INFO_FAT = 0; + private const uint SOURCE_TYPE_BITS = 3; private const uint IL_OFFSET_BIAS = unchecked((uint)-3); private record struct DebugInfoChunks @@ -118,7 +119,7 @@ private static IEnumerable DoBounds(NativeReader nativeReader) 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 + 3; // 3 bits for source type + uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + SOURCE_TYPE_BITS; // 3 bits for source type ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1; int offsetOfActualBoundsData = reader.GetNextByteOffset(); From 35a3c6e2e6ed1e661506761bb55402b9f39ed713 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:18:12 -0500 Subject: [PATCH 07/11] update --- docs/design/datacontracts/DebugInfo.md | 2 +- .../Contracts/DebugInfo/DebugInfo_1.cs | 2 +- .../Contracts/DebugInfo/DebugInfo_2.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 2dd0485d464da1..4058f144b9a2d9 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -199,7 +199,7 @@ private static IEnumerable DoBounds(NativeReader nativeReader) _ => throw new InvalidOperationException($"Unknown source type encoding: {mappingDataEncoded & 0x3}") }; - mappingDataEncoded >>= 2; + mappingDataEncoded >>= (int)SOURCE_TYPE_BITS; uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1)); previousNativeOffset += nativeOffsetDelta; uint nativeOffset = previousNativeOffset; 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 be48e3f489d044..9a7c62b0467856 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 @@ -140,7 +140,7 @@ private static IEnumerable DoBounds(NativeReader nativeReader) _ => throw new InvalidOperationException($"Unknown source type encoding: {mappingDataEncoded & 0x3}") }; - mappingDataEncoded >>= 2; + mappingDataEncoded >>= (int)SOURCE_TYPE_BITS; uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1)); previousNativeOffset += nativeOffsetDelta; uint nativeOffset = previousNativeOffset; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs index db544f5e01a513..636de63d00921a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs @@ -144,7 +144,7 @@ private static IEnumerable DoBounds(NativeReader nativeReader) if ((mappingDataEncoded & 0x2) != 0) sourceType |= SourceTypes.StackEmpty; if ((mappingDataEncoded & 0x4) != 0) sourceType |= SourceTypes.Async; - mappingDataEncoded >>= 2; + mappingDataEncoded >>= (int)SOURCE_TYPE_BITS; uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1)); previousNativeOffset += nativeOffsetDelta; uint nativeOffset = previousNativeOffset; From 1a610a9cffbbc04f7deeec5cffbdd428232e32c1 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:05:46 -0500 Subject: [PATCH 08/11] rename --- .../Contracts/IDebugInfo.cs | 2 +- .../Contracts/DebugInfo/DebugInfo_1.cs | 2 +- 2 files changed, 2 insertions(+), 2 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 c8121df19a2eda..f373ef10c8d248 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 @@ -12,7 +12,7 @@ public enum SourceTypes : uint /// /// Indicates that no other options apply /// - SourceTypeInvalid = 0x00, + Default = 0x00, /// /// The stack is empty here /// 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 9a7c62b0467856..1b6d6f0a7c0b8b 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 @@ -133,7 +133,7 @@ private static IEnumerable DoBounds(NativeReader nativeReader) SourceTypes sourceType = (mappingDataEncoded & 0x3) switch { - 0 => SourceTypes.SourceTypeInvalid, + 0 => SourceTypes.Default, 1 => SourceTypes.CallInstruction, 2 => SourceTypes.StackEmpty, 3 => SourceTypes.StackEmpty | SourceTypes.CallInstruction, From 9232b636db158061e3fb8a9797bb7fab1170ead0 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:16:07 -0500 Subject: [PATCH 09/11] update to share code --- .../Contracts/DebugInfo/DebugInfoHelpers.cs | 73 +++++++++++++++++++ .../Contracts/DebugInfo/DebugInfo_1.cs | 63 +--------------- .../Contracts/DebugInfo/DebugInfo_2.cs | 59 +-------------- 3 files changed, 77 insertions(+), 118 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs new file mode 100644 index 00000000000000..7f8df0e5fcdd34 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs @@ -0,0 +1,73 @@ +// 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 ILCompiler.Reflection.ReadyToRun; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +/// +/// Shared bounds decoding helpers for DebugInfo contract versions. +/// V1: 2-bit enumerated source type. +/// V2: 3-bit flags (CallInstruction, StackEmpty, Async). +/// +internal static class DebugInfoHelpers +{ + internal static IEnumerable DoBounds(NativeReader nativeReader, uint ilOffsetBias, uint sourceTypeBitCount) + { + 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 + sourceTypeBitCount; + 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; + + ulong sourceTypeBitsMask = (1UL << ((int)sourceTypeBitCount)) - 1; + ulong sourceTypeBits = mappingDataEncoded & sourceTypeBitsMask; + SourceTypes sourceType = 0; + if ((sourceTypeBits & 0x1) != 0) sourceType |= SourceTypes.CallInstruction; + if ((sourceTypeBits & 0x2) != 0) sourceType |= SourceTypes.StackEmpty; + if ((sourceTypeBits & 0x4) != 0) sourceType |= SourceTypes.Async; + + mappingDataEncoded >>= (int)sourceTypeBitCount; + uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1)); + previousNativeOffset += nativeOffsetDelta; + uint nativeOffset = previousNativeOffset; + + mappingDataEncoded >>= (int)bitsForNativeDelta; + uint ilOffset = (uint)mappingDataEncoded + ilOffsetBias; + + yield return new OffsetMapping() + { + NativeOffset = nativeOffset, + ILOffset = ilOffset, + SourceType = sourceType + }; + curBoundsProcessed++; + } + } + } +} 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 1b6d6f0a7c0b8b..8e4c56219143d4 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 @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using ILCompiler.Reflection.ReadyToRun; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -95,67 +94,9 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DoBounds(boundsNativeReader); + return DebugInfoHelpers.DoBounds(boundsNativeReader, IL_OFFSET_BIAS, SOURCE_TYPE_BITS); } - 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 + SOURCE_TYPE_BITS; // 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.Default, - 1 => SourceTypes.CallInstruction, - 2 => SourceTypes.StackEmpty, - 3 => SourceTypes.StackEmpty | SourceTypes.CallInstruction, - _ => throw new InvalidOperationException($"Unknown source type encoding: {mappingDataEncoded & 0x3}") - }; - - mappingDataEncoded >>= (int)SOURCE_TYPE_BITS; - 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 []; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs index 636de63d00921a..e892cbafeda9cb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using ILCompiler.Reflection.ReadyToRun; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -104,62 +102,9 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DoBounds(boundsNativeReader); + return DebugInfoHelpers.DoBounds(boundsNativeReader, IL_OFFSET_BIAS, SOURCE_TYPE_BITS); } - 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 + SOURCE_TYPE_BITS; // 3 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 = 0; - if ((mappingDataEncoded & 0x1) != 0) sourceType |= SourceTypes.CallInstruction; - if ((mappingDataEncoded & 0x2) != 0) sourceType |= SourceTypes.StackEmpty; - if ((mappingDataEncoded & 0x4) != 0) sourceType |= SourceTypes.Async; - - mappingDataEncoded >>= (int)SOURCE_TYPE_BITS; - 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 []; } } From 4d241fdc281e2c4ca46418e60fd9ee258e9fcc9e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:16:53 -0500 Subject: [PATCH 10/11] update docs --- docs/design/datacontracts/DebugInfo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 4058f144b9a2d9..26581a75d308d8 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -8,7 +8,7 @@ This contract is for fetching information related to DebugInfo associated with n [Flags] public enum SourceTypes : uint { - SourceTypeInvalid = 0x00, // To indicate that nothing else applies + Default = 0x00, // To indicate that nothing else applies StackEmpty = 0x01, // The stack is empty here CallInstruction = 0x02 // The actual instruction of a call Async = 0x04 // (Version 2+) Indicates suspension/resumption for an async call From 3fd049cfcd7fcbd45b69ef3b09ff8ff598c1f162 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:32:55 -0500 Subject: [PATCH 11/11] reformat slightly --- .../Contracts/DebugInfo/DebugInfoHelpers.cs | 10 ++++++++-- .../Contracts/DebugInfo/DebugInfo_1.cs | 4 +--- .../Contracts/DebugInfo/DebugInfo_2.cs | 4 +--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs index 7f8df0e5fcdd34..c5196af5f20663 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs @@ -15,7 +15,12 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; /// internal static class DebugInfoHelpers { - internal static IEnumerable DoBounds(NativeReader nativeReader, uint ilOffsetBias, uint sourceTypeBitCount) + private const uint IL_OFFSET_BIAS = unchecked((uint)-3); + private const uint SOURCE_TYPE_BITS_V1 = 2; + private const uint SOURCE_TYPE_BITS_V2 = 3; + + + internal static IEnumerable DoBounds(NativeReader nativeReader, uint version) { NibbleReader reader = new(nativeReader, 0); @@ -25,6 +30,7 @@ internal static IEnumerable DoBounds(NativeReader nativeReader, u 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 sourceTypeBitCount = (version == 1) ? SOURCE_TYPE_BITS_V1 : SOURCE_TYPE_BITS_V2; uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + sourceTypeBitCount; ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1; int offsetOfActualBoundsData = reader.GetNextByteOffset(); @@ -58,7 +64,7 @@ internal static IEnumerable DoBounds(NativeReader nativeReader, u uint nativeOffset = previousNativeOffset; mappingDataEncoded >>= (int)bitsForNativeDelta; - uint ilOffset = (uint)mappingDataEncoded + ilOffsetBias; + uint ilOffset = (uint)mappingDataEncoded + IL_OFFSET_BIAS; yield return new 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 8e4c56219143d4..cfb85043415a3f 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,8 +11,6 @@ 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 SOURCE_TYPE_BITS = 2; - private const uint IL_OFFSET_BIAS = unchecked((uint)-3); [Flags] internal enum ExtraDebugInfoFlags_1 : byte @@ -94,7 +92,7 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DebugInfoHelpers.DoBounds(boundsNativeReader, IL_OFFSET_BIAS, SOURCE_TYPE_BITS); + return DebugInfoHelpers.DoBounds(boundsNativeReader, 1); } return []; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs index e892cbafeda9cb..632d9ac2c4e923 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs @@ -10,8 +10,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal sealed class DebugInfo_2(Target target) : IDebugInfo { private const uint DEBUG_INFO_FAT = 0; - private const uint SOURCE_TYPE_BITS = 3; - private const uint IL_OFFSET_BIAS = unchecked((uint)-3); private record struct DebugInfoChunks { @@ -102,7 +100,7 @@ private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bo if (cbBounds > 0) { NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DebugInfoHelpers.DoBounds(boundsNativeReader, IL_OFFSET_BIAS, SOURCE_TYPE_BITS); + return DebugInfoHelpers.DoBounds(boundsNativeReader, 2); } return [];