Skip to content

Commit f84ba39

Browse files
authored
[cDAC] Update DebugInfo contract for new format (#121418)
* Add support for DebugInfo 2 contract * Refactor DebugInfo contract
1 parent d0bc904 commit f84ba39

File tree

8 files changed

+277
-90
lines changed

8 files changed

+277
-90
lines changed

docs/design/datacontracts/DebugInfo.md

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ This contract is for fetching information related to DebugInfo associated with n
88
[Flags]
99
public enum SourceTypes : uint
1010
{
11-
SourceTypeInvalid = 0x00, // To indicate that nothing else applies
11+
Default = 0x00, // To indicate that nothing else applies
1212
StackEmpty = 0x01, // The stack is empty here
13-
CallInstruction = 0x02 // The actual instruction of a call.
13+
CallInstruction = 0x02 // The actual instruction of a call
14+
Async = 0x04 // (Version 2+) Indicates suspension/resumption for an async call
1415
}
1516
```
1617

@@ -40,7 +41,6 @@ Data descriptors used:
4041
Contracts used:
4142
| Contract Name |
4243
| --- |
43-
| `CodeVersions` |
4444
| `ExecutionManager` |
4545

4646
Constants:
@@ -50,6 +50,7 @@ Constants:
5050
| DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS | Indicates bounds data contains instrumented bounds | `0xFFFFFFFF` |
5151
| EXTRA_DEBUG_INFO_PATCHPOINT | Indicates debug info contains patchpoint information | 0x1 |
5252
| EXTRA_DEBUG_INFO_RICH | Indicates debug info contains rich information | 0x2 |
53+
| SOURCE_TYPE_BITS | Number of bits per bounds entry used to encode source type flags | 2 |
5354

5455
### DebugInfo Stream Encoding
5556

@@ -169,7 +170,7 @@ private static IEnumerable<OffsetMapping> DoBounds(NativeReader nativeReader)
169170
uint bitsForNativeDelta = reader.ReadUInt() + 1; // Number of bits needed for native deltas
170171
uint bitsForILOffsets = reader.ReadUInt() + 1; // Number of bits needed for IL offsets
171172
172-
uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + 2; // 2 bits for source type
173+
uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + SOURCE_TYPE_BITS; // 2 bits for source type
173174
ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1;
174175
int offsetOfActualBoundsData = reader.GetNextByteOffset();
175176

@@ -198,7 +199,7 @@ private static IEnumerable<OffsetMapping> DoBounds(NativeReader nativeReader)
198199
_ => throw new InvalidOperationException($"Unknown source type encoding: {mappingDataEncoded & 0x3}")
199200
};
200201

201-
mappingDataEncoded >>= 2;
202+
mappingDataEncoded >>= (int)SOURCE_TYPE_BITS;
202203
uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1));
203204
previousNativeOffset += nativeOffsetDelta;
204205
uint nativeOffset = previousNativeOffset;
@@ -217,3 +218,79 @@ private static IEnumerable<OffsetMapping> DoBounds(NativeReader nativeReader)
217218
}
218219
}
219220
```
221+
222+
## Version 2
223+
224+
Version 2 introduces two distinct changes:
225+
226+
1. A unified header format ("fat" vs "slim") replacing the Version 1 flag byte and implicit layout.
227+
2. An additional `SourceTypes.Async` flag, expanding the per-entry source type encoding from 2 bits to a 3-bit bitfield.
228+
229+
The nibble-encoded variable-length integer mechanism is unchanged; only the header and bounds entry source-type packing differ.
230+
231+
Data descriptors used:
232+
| Data Descriptor Name | Field | Meaning |
233+
| --- | --- | --- |
234+
| _(none)_ | | |
235+
236+
Contracts used:
237+
| Contract Name |
238+
| --- |
239+
| `ExecutionManager` |
240+
241+
Constants:
242+
| Constant Name | Meaning | Value |
243+
| --- | --- | --- |
244+
| IL_OFFSET_BIAS | IL offsets bias (unchanged from Version 1) | `0xfffffffd` (-3) |
245+
| DEBUG_INFO_FAT | Marker value in first nibble-coded integer indicating a fat header follows | `0x0` |
246+
| SOURCE_TYPE_BITS | Number of bits per bounds entry used for source type flags | 3 |
247+
248+
### Header Encoding
249+
250+
The first nibble-decoded unsigned integer (`countBoundsOrFatMarker`):
251+
252+
* If `countBoundsOrFatMarker == DEBUG_INFO_FAT` (0), the header is FAT and the next 6 nibble-decoded unsigned integers are, in order:
253+
1. `BoundsSize`
254+
2. `VarsSize`
255+
3. `UninstrumentedBoundsSize`
256+
4. `PatchpointInfoSize`
257+
5. `RichDebugInfoSize`
258+
6. `AsyncInfoSize`
259+
* Otherwise (SLIM header), the value is `BoundsSize` and the next nibble-decoded unsigned integer is `VarsSize`; all other sizes are implicitly 0.
260+
261+
After decoding sizes, chunk start addresses are computed by linear accumulation beginning at the first byte after the header stream:
262+
263+
```
264+
BoundsStart = debugInfo + headerBytesConsumed
265+
VarsStart = BoundsStart + BoundsSize
266+
UninstrumentedBoundsStart = VarsStart + VarsSize
267+
PatchpointInfoStart = UninstrumentedBoundsStart + UninstrumentedBoundsSize
268+
RichDebugInfoStart = PatchpointInfoStart + PatchpointInfoSize
269+
AsyncInfoStart = RichDebugInfoStart + RichDebugInfoSize
270+
DebugInfoEnd = AsyncInfoStart + AsyncInfoSize
271+
```
272+
273+
### Bounds Entry Encoding Differences from Version 1
274+
275+
Version 1 packs each bounds entry using: `[2 bits sourceType][nativeDeltaBits][ilOffsetBits]`.
276+
277+
Version 2 extends this to three independent flag bits for source type and so uses: `[3 bits sourceFlags][nativeDeltaBits][ilOffsetBits]`.
278+
279+
Source type bits (low → high):
280+
| Bit | Mask | Meaning |
281+
| --- | --- | --- |
282+
| 0 | 0x1 | `CallInstruction` |
283+
| 1 | 0x2 | `StackEmpty` |
284+
| 2 | 0x4 | `Async` (new in Version 2) |
285+
286+
`SourceTypeInvalid` is represented by all three bits clear (0). Combinations are produced by OR-ing masks (e.g., `StackEmpty | CallInstruction`).
287+
288+
Pseudo-code for Version 2 source type extraction:
289+
```csharp
290+
SourceTypes sourceType = 0;
291+
if ((encoded & 0x1) != 0) sourceType |= SourceTypes.CallInstruction;
292+
if ((encoded & 0x2) != 0) sourceType |= SourceTypes.StackEmpty;
293+
if ((encoded & 0x4) != 0) sourceType |= SourceTypes.Async; // New bit
294+
```
295+
296+
After masking the 3 bits, shift them out before reading native delta and IL offset fields as before.

src/coreclr/inc/patchpointinfo.h

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77

88
#include <clrtypes.h>
99

10-
#ifndef JIT_BUILD
11-
#include "cdacdata.h"
12-
#endif // JIT_BUILD
13-
1410
#ifndef _PATCHPOINTINFO_H_
1511
#define _PATCHPOINTINFO_H_
1612

@@ -205,19 +201,7 @@ struct PatchpointInfo
205201
int32_t m_securityCookieOffset;
206202
int32_t m_monitorAcquiredOffset;
207203
int32_t m_offsetAndExposureData[];
208-
209-
#ifndef JIT_BUILD
210-
friend struct ::cdac_data<PatchpointInfo>;
211-
#endif // JIT_BUILD
212-
};
213-
214-
#ifndef JIT_BUILD
215-
template<>
216-
struct cdac_data<PatchpointInfo>
217-
{
218-
static constexpr size_t LocalCount = offsetof(PatchpointInfo, m_numberOfLocals);
219204
};
220-
#endif // JIT_BUILD
221205

222206
typedef DPTR(struct PatchpointInfo) PTR_PatchpointInfo;
223207

src/coreclr/vm/datadescriptor/datadescriptor.inc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -670,11 +670,6 @@ CDAC_TYPE_FIELD(RealCodeHeader, /* T_RUNTIME_FUNCTION */, UnwindInfos, offsetof(
670670
#endif // FEATURE_EH_FUNCLETS
671671
CDAC_TYPE_END(RealCodeHeader)
672672

673-
CDAC_TYPE_BEGIN(PatchpointInfo)
674-
CDAC_TYPE_SIZE(sizeof(PatchpointInfo))
675-
CDAC_TYPE_FIELD(PatchpointInfo, /*uint32*/, LocalCount, cdac_data<PatchpointInfo>::LocalCount)
676-
CDAC_TYPE_END(PatchpointInfo)
677-
678673
CDAC_TYPE_BEGIN(CodeHeapListNode)
679674
CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, Next, offsetof(HeapList, hpNext))
680675
CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, StartAddress, offsetof(HeapList, startAddress))

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public enum SourceTypes : uint
1212
/// <summary>
1313
/// Indicates that no other options apply
1414
/// </summary>
15-
SourceTypeInvalid = 0x00,
15+
Default = 0x00,
1616
/// <summary>
1717
/// The stack is empty here
1818
/// </summary>
@@ -21,6 +21,10 @@ public enum SourceTypes : uint
2121
/// The actual instruction of a call
2222
/// </summary>
2323
CallInstruction = 0x02,
24+
/// <summary>
25+
/// Indicates suspension/resumption for an async call
26+
/// </summary>
27+
Async = 0x04,
2428
}
2529

2630
public readonly struct OffsetMapping

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ IDebugInfo IContractFactory<IDebugInfo>.CreateContract(Target target, int versio
1010
return version switch
1111
{
1212
1 => new DebugInfo_1(target),
13+
2 => new DebugInfo_2(target),
1314
_ => default(DebugInfo),
1415
};
1516
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using ILCompiler.Reflection.ReadyToRun;
8+
9+
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
10+
11+
/// <summary>
12+
/// Shared bounds decoding helpers for DebugInfo contract versions.
13+
/// V1: 2-bit enumerated source type.
14+
/// V2: 3-bit flags (CallInstruction, StackEmpty, Async).
15+
/// </summary>
16+
internal static class DebugInfoHelpers
17+
{
18+
private const uint IL_OFFSET_BIAS = unchecked((uint)-3);
19+
private const uint SOURCE_TYPE_BITS_V1 = 2;
20+
private const uint SOURCE_TYPE_BITS_V2 = 3;
21+
22+
23+
internal static IEnumerable<OffsetMapping> DoBounds(NativeReader nativeReader, uint version)
24+
{
25+
NibbleReader reader = new(nativeReader, 0);
26+
27+
uint boundsEntryCount = reader.ReadUInt();
28+
Debug.Assert(boundsEntryCount > 0, "Expected at least one entry in bounds.");
29+
30+
uint bitsForNativeDelta = reader.ReadUInt() + 1; // Number of bits needed for native deltas
31+
uint bitsForILOffsets = reader.ReadUInt() + 1; // Number of bits needed for IL offsets
32+
33+
uint sourceTypeBitCount = (version == 1) ? SOURCE_TYPE_BITS_V1 : SOURCE_TYPE_BITS_V2;
34+
uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + sourceTypeBitCount;
35+
ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1;
36+
int offsetOfActualBoundsData = reader.GetNextByteOffset();
37+
38+
uint bitsCollected = 0;
39+
ulong bitTemp = 0;
40+
uint curBoundsProcessed = 0;
41+
42+
uint previousNativeOffset = 0;
43+
44+
while (curBoundsProcessed < boundsEntryCount)
45+
{
46+
bitTemp |= ((uint)nativeReader[offsetOfActualBoundsData++]) << (int)bitsCollected;
47+
bitsCollected += 8;
48+
while (bitsCollected >= bitsPerEntry)
49+
{
50+
ulong mappingDataEncoded = bitsMeaningfulMask & bitTemp;
51+
bitTemp >>= (int)bitsPerEntry;
52+
bitsCollected -= bitsPerEntry;
53+
54+
ulong sourceTypeBitsMask = (1UL << ((int)sourceTypeBitCount)) - 1;
55+
ulong sourceTypeBits = mappingDataEncoded & sourceTypeBitsMask;
56+
SourceTypes sourceType = 0;
57+
if ((sourceTypeBits & 0x1) != 0) sourceType |= SourceTypes.CallInstruction;
58+
if ((sourceTypeBits & 0x2) != 0) sourceType |= SourceTypes.StackEmpty;
59+
if ((sourceTypeBits & 0x4) != 0) sourceType |= SourceTypes.Async;
60+
61+
mappingDataEncoded >>= (int)sourceTypeBitCount;
62+
uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1));
63+
previousNativeOffset += nativeOffsetDelta;
64+
uint nativeOffset = previousNativeOffset;
65+
66+
mappingDataEncoded >>= (int)bitsForNativeDelta;
67+
uint ilOffset = (uint)mappingDataEncoded + IL_OFFSET_BIAS;
68+
69+
yield return new OffsetMapping()
70+
{
71+
NativeOffset = nativeOffset,
72+
ILOffset = ilOffset,
73+
SourceType = sourceType
74+
};
75+
curBoundsProcessed++;
76+
}
77+
}
78+
}
79+
}

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7-
using System.Linq;
87
using ILCompiler.Reflection.ReadyToRun;
98

109
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
1110

1211
internal sealed class DebugInfo_1(Target target) : IDebugInfo
1312
{
1413
private const uint DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS = 0xFFFFFFFF;
15-
private const uint IL_OFFSET_BIAS = unchecked((uint)-3);
1614

1715
[Flags]
1816
internal enum ExtraDebugInfoFlags_1 : byte
@@ -94,68 +92,9 @@ private IEnumerable<OffsetMapping> RestoreBoundaries(TargetPointer debugInfo, bo
9492
if (cbBounds > 0)
9593
{
9694
NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian);
97-
return DoBounds(boundsNativeReader);
98-
}
99-
100-
return Enumerable.Empty<OffsetMapping>();
101-
}
102-
103-
private static IEnumerable<OffsetMapping> DoBounds(NativeReader nativeReader)
104-
{
105-
NibbleReader reader = new(nativeReader, 0);
106-
107-
uint boundsEntryCount = reader.ReadUInt();
108-
Debug.Assert(boundsEntryCount > 0, "Expected at least one entry in bounds.");
109-
110-
uint bitsForNativeDelta = reader.ReadUInt() + 1; // Number of bits needed for native deltas
111-
uint bitsForILOffsets = reader.ReadUInt() + 1; // Number of bits needed for IL offsets
112-
113-
uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + 2; // 2 bits for source type
114-
ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1;
115-
int offsetOfActualBoundsData = reader.GetNextByteOffset();
116-
117-
uint bitsCollected = 0;
118-
ulong bitTemp = 0;
119-
uint curBoundsProcessed = 0;
120-
121-
uint previousNativeOffset = 0;
122-
123-
while (curBoundsProcessed < boundsEntryCount)
124-
{
125-
bitTemp |= ((uint)nativeReader[offsetOfActualBoundsData++]) << (int)bitsCollected;
126-
bitsCollected += 8;
127-
while (bitsCollected >= bitsPerEntry)
128-
{
129-
ulong mappingDataEncoded = bitsMeaningfulMask & bitTemp;
130-
bitTemp >>= (int)bitsPerEntry;
131-
bitsCollected -= bitsPerEntry;
132-
133-
SourceTypes sourceType = (mappingDataEncoded & 0x3) switch
134-
{
135-
0 => SourceTypes.SourceTypeInvalid,
136-
1 => SourceTypes.CallInstruction,
137-
2 => SourceTypes.StackEmpty,
138-
3 => SourceTypes.StackEmpty | SourceTypes.CallInstruction,
139-
_ => throw new InvalidOperationException($"Unknown source type encoding: {mappingDataEncoded & 0x3}")
140-
};
141-
142-
mappingDataEncoded >>= 2;
143-
uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1));
144-
previousNativeOffset += nativeOffsetDelta;
145-
uint nativeOffset = previousNativeOffset;
146-
147-
mappingDataEncoded >>= (int)bitsForNativeDelta;
148-
uint ilOffset = (uint)mappingDataEncoded + IL_OFFSET_BIAS;
149-
150-
yield return new OffsetMapping()
151-
{
152-
NativeOffset = nativeOffset,
153-
ILOffset = ilOffset,
154-
SourceType = sourceType
155-
};
156-
curBoundsProcessed++;
157-
}
95+
return DebugInfoHelpers.DoBounds(boundsNativeReader, 1);
15896
}
15997

98+
return [];
16099
}
161100
}

0 commit comments

Comments
 (0)