Skip to content

Commit 80912f6

Browse files
authored
[cDAC] IXCLRDataMethodInstance::GetILOffsetsByAddress (#117088)
* Add DebugInfo contract * Add ability to fetch DebugInfo
1 parent 688beda commit 80912f6

37 files changed

+740
-18
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Contract DebugInfo
2+
3+
This contract is for fetching information related to DebugInfo associated with native code.
4+
5+
## APIs of contract
6+
7+
```csharp
8+
[Flags]
9+
public enum SourceTypes : uint
10+
{
11+
SourceTypeInvalid = 0x00, // To indicate that nothing else applies
12+
StackEmpty = 0x01, // The stack is empty here
13+
CallInstruction = 0x02 // The actual instruction of a call.
14+
}
15+
```
16+
17+
```csharp
18+
public readonly struct OffsetMapping
19+
{
20+
public uint NativeOffset { get; init; }
21+
public uint ILOffset { get; init; }
22+
public SourceTypes SourceType { get; init; }
23+
}
24+
```
25+
26+
```csharp
27+
// Given a code pointer, return the associated native/IL offset mapping and codeOffset.
28+
// If preferUninstrumented, will always read the uninstrumented bounds.
29+
// Otherwise will read the instrumented bounds and fallback to the uninstrumented bounds.
30+
IEnumerable<OffsetMapping> GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset);
31+
```
32+
33+
## Version 1
34+
35+
Data descriptors used:
36+
| Data Descriptor Name | Field | Meaning |
37+
| --- | --- | --- |
38+
| `PatchpointInfo` | `LocalCount` | Number of locals in the method associated with the patchpoint. |
39+
40+
Contracts used:
41+
| Contract Name |
42+
| --- |
43+
| `CodeVersions` |
44+
| `ExecutionManager` |
45+
46+
Constants:
47+
| Constant Name | Meaning | Value |
48+
| --- | --- | --- |
49+
| IL_OFFSET_BIAS | IL offsets are encoded in the DebugInfo with this bias. | `0xfffffffd` (-3) |
50+
| DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS | Indicates bounds data contains instrumented bounds | `0xFFFFFFFF` |
51+
| EXTRA_DEBUG_INFO_PATCHPOINT | Indicates debug info contains patchpoint information | 0x1 |
52+
| EXTRA_DEBUG_INFO_RICH | Indicates debug info contains rich information | 0x2 |
53+
54+
### DebugInfo Stream Encoding
55+
56+
The DebugInfo stream is encoded using variable length 32-bit values with the following scheme:
57+
58+
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.
59+
60+
In the encoded stream of bytes, the lower nibble of a byte is used before the high nibble.
61+
62+
A binary value ABCDEFGHI (where A is the highest bit) is encoded as
63+
the follow two bytes : 1DEF1ABC XXXX0GHI
64+
65+
Examples:
66+
| Decimal Value | Hex Value | Encoded Result |
67+
| --- | --- | --- |
68+
| 0 | 0x0 | X0 |
69+
| 1 | 0x1 | X1 |
70+
| 7 | 0x7 | X7 |
71+
| 8 | 0x8 | 09 |
72+
| 9 | 0x9 | 19 |
73+
| 63 | 0x3F | 7F |
74+
| 64 | 0x40 | F9 X0 |
75+
| 65 | 0x41 | F9 X1 |
76+
| 511 | 0x1FF | FF X7 |
77+
| 512 | 0x200 | 89 08 |
78+
| 513 | 0x201 | 89 18 |
79+
80+
Based on the encoding specification, we use a decoder defined originally for r2r dump `NibbleReader.cs`
81+
82+
### Bounds Data Encoding (R2R Major Version 16+)
83+
84+
For R2R major version 16 and above, the bounds data uses a bit-packed encoding algorithm:
85+
86+
1. The bounds entry count, bits needed for native deltas, and bits needed for IL offsets are encoded using the nibble scheme above
87+
2. Each bounds entry is then bit-packed with:
88+
- 2 bits for source type (SourceTypeInvalid=0, CallInstruction=1, StackEmpty=2, StackEmpty|CallInstruction=3)
89+
- Variable bits for native offset delta (accumulated from previous offset)
90+
- Variable bits for IL offset (with IL_OFFSET_BIAS applied)
91+
92+
The bit-packed data is read byte by byte, collecting bits until enough are available for each entry.
93+
94+
### Implementation
95+
96+
``` csharp
97+
IEnumerable<OffsetMapping> IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset)
98+
{
99+
// Get the method's DebugInfo
100+
if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh)
101+
throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}.");
102+
TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool hasFlagByte);
103+
104+
TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh);
105+
codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target));
106+
107+
return RestoreBoundaries(debugInfo, hasFlagByte, preferUninstrumented);
108+
}
109+
110+
private IEnumerable<OffsetMapping> RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte, bool preferUninstrumented)
111+
{
112+
if (hasFlagByte)
113+
{
114+
// Check flag byte and skip over any patchpoint info
115+
byte flagByte = _target.Read<byte>(debugInfo++);
116+
117+
if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) != 0)
118+
{
119+
uint localCount = _target.Read<uint>(debugInfo + /*PatchpointInfo::LocalCount offset*/)
120+
debugInfo += /*size of PatchpointInfo*/ + (localCount * 4);
121+
}
122+
123+
if ((flagByte & EXTRA_DEBUG_INFO_RICH) != 0)
124+
{
125+
uint richDebugInfoSize = _target.Read<uint>(debugInfo);
126+
debugInfo += 4;
127+
debugInfo += richDebugInfoSize;
128+
}
129+
}
130+
131+
NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 24 /*maximum size of 4 32bit ints compressed*/), _target.IsLittleEndian);
132+
NibbleReader nibbleReader = new(nibbleNativeReader, 0);
133+
134+
uint cbBounds = nibbleReader.ReadUInt();
135+
uint cbUninstrumentedBounds = 0;
136+
if (cbBounds == DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS)
137+
{
138+
// This means we have instrumented bounds.
139+
cbBounds = nibbleReader.ReadUInt();
140+
cbUninstrumentedBounds = nibbleReader.ReadUInt();
141+
}
142+
uint _ /*cbVars*/ = nibbleReader.ReadUInt();
143+
144+
TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset();
145+
// TargetPointer addrVars = addrBounds + cbBounds + cbUninstrumentedBounds;
146+
147+
if (preferUninstrumented && cbUninstrumentedBounds != 0)
148+
{
149+
// If we have uninstrumented bounds, we will use them instead of the regular bounds.
150+
addrBounds += cbBounds;
151+
cbBounds = cbUninstrumentedBounds;
152+
}
153+
154+
if (cbBounds > 0)
155+
{
156+
NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian);
157+
return DoBounds(boundsNativeReader);
158+
}
159+
160+
return Enumerable.Empty<OffsetMapping>();
161+
}
162+
163+
private static IEnumerable<OffsetMapping> DoBounds(NativeReader nativeReader)
164+
{
165+
NibbleReader reader = new(nativeReader, 0);
166+
167+
uint boundsEntryCount = reader.ReadUInt();
168+
169+
uint bitsForNativeDelta = reader.ReadUInt() + 1; // Number of bits needed for native deltas
170+
uint bitsForILOffsets = reader.ReadUInt() + 1; // Number of bits needed for IL offsets
171+
172+
uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + 2; // 2 bits for source type
173+
ulong bitsMeaningfulMask = (1UL << ((int)bitsPerEntry)) - 1;
174+
int offsetOfActualBoundsData = reader.GetNextByteOffset();
175+
176+
uint bitsCollected = 0;
177+
ulong bitTemp = 0;
178+
uint curBoundsProcessed = 0;
179+
180+
uint previousNativeOffset = 0;
181+
182+
while (curBoundsProcessed < boundsEntryCount)
183+
{
184+
bitTemp |= ((uint)nativeReader[offsetOfActualBoundsData++]) << (int)bitsCollected;
185+
bitsCollected += 8;
186+
while (bitsCollected >= bitsPerEntry)
187+
{
188+
ulong mappingDataEncoded = bitsMeaningfulMask & bitTemp;
189+
bitTemp >>= (int)bitsPerEntry;
190+
bitsCollected -= bitsPerEntry;
191+
192+
SourceTypes sourceType = (mappingDataEncoded & 0x3) switch
193+
{
194+
0 => SourceTypes.SourceTypeInvalid,
195+
1 => SourceTypes.CallInstruction,
196+
2 => SourceTypes.StackEmpty,
197+
3 => SourceTypes.StackEmpty | SourceTypes.CallInstruction,
198+
_ => throw new InvalidOperationException($"Unknown source type encoding: {mappingDataEncoded & 0x3}")
199+
};
200+
201+
mappingDataEncoded >>= 2;
202+
uint nativeOffsetDelta = (uint)(mappingDataEncoded & ((1UL << (int)bitsForNativeDelta) - 1));
203+
previousNativeOffset += nativeOffsetDelta;
204+
uint nativeOffset = previousNativeOffset;
205+
206+
mappingDataEncoded >>= (int)bitsForNativeDelta;
207+
uint ilOffset = (uint)mappingDataEncoded + IL_OFFSET_BIAS;
208+
209+
yield return new OffsetMapping()
210+
{
211+
NativeOffset = nativeOffset,
212+
ILOffset = ilOffset,
213+
SourceType = sourceType
214+
};
215+
curBoundsProcessed++;
216+
}
217+
}
218+
}
219+
```

docs/design/datacontracts/ExecutionManager.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ struct CodeBlockHandle
2929
TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle);
3030
// Gets the base address the UnwindInfo of codeInfoHandle is relative to
3131
TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle);
32+
// Gets the DebugInfo associated with the code block and specifies if the DebugInfo contains
33+
// the flag byte which modifies how DebugInfo is parsed.
34+
TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte);
3235
// Gets the GCInfo associated with the code block and its version
3336
// **Currently GetGCInfo only supports X86**
3437
void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion);
@@ -66,9 +69,11 @@ Data descriptors used:
6669
| `CodeHeapListNode` | `EndAddress` | End address of the used portion of the code heap |
6770
| `CodeHeapListNode` | `MapBase` | Start of the map - start address rounded down based on OS page size |
6871
| `CodeHeapListNode` | `HeaderMap` | Bit array used to find the start of methods - relative to `MapBase` |
72+
| `EEJitManager` | `StoreRichDebugInfo` | Boolean value determining if debug info associated with the JitManager contains rich info. |
6973
| `RealCodeHeader` | `MethodDesc` | Pointer to the corresponding `MethodDesc` |
7074
| `RealCodeHeader` | `NumUnwindInfos` | Number of Unwind Infos |
7175
| `RealCodeHeader` | `UnwindInfos` | Start address of Unwind Infos |
76+
| `RealCodeHeader` | `DebugInfo` | Pointer to the DebugInfo |
7277
| `RealCodeHeader` | `GCInfo` | Pointer to the GCInfo encoding |
7378
| `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module |
7479
| `ReadyToRunInfo` | `ReadyToRunHeader` | Pointer to the ReadyToRunHeader |
@@ -78,6 +83,7 @@ Data descriptors used:
7883
| `ReadyToRunInfo` | `NumHotColdMap` | Number of entries in the `HotColdMap` |
7984
| `ReadyToRunInfo` | `HotColdMap` | Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) |
8085
| `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | Pointer to an `ImageDataDirectory` for the delay load method call thunks |
86+
| `ReadyToRunInf` | `DebugInfo` | Pointer to an `ImageDataDirectory` for the debug info |
8187
| `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` of entry point addresses to `MethodDesc` pointers |
8288
| `ReadyToRunHeader` | `MajorVersion` | ReadyToRun major version |
8389
| `ReadyToRunHeader` | `MinorVersion` | ReadyToRun minor version |
@@ -100,6 +106,7 @@ Global variables used:
100106
| `HashMapValueMask` | uint64 | Bitmask used when storing values in a `HashMap` |
101107
| `FeatureEHFunclets` | uint8 | 1 if EH funclets are enabled, 0 otherwise |
102108
| `GCInfoVersion` | uint32 | JITted code GCInfo version |
109+
| `FeatureOnStackReplacement` | uint8 | 1 if FEATURE_ON_STACK_REPLACEMENT is enabled, 0 otherwise |
103110

104111
Contracts used:
105112
| Contract Name |
@@ -266,9 +273,16 @@ The `GetMethodDesc`, `GetStartAddress`, and `GetRelativeOffset` APIs extract fie
266273
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.
267274
`GetUnwindInfoBaseAddress` finds this base address for a given `CodeBlockHandle`.
268275

276+
`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.
277+
278+
* 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`.
279+
280+
* 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`.
281+
For R2R images, `hasFlagByte` is always `false`.
282+
269283
`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.
270284

271-
* 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`.
285+
* 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`.
272286

273287
* 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.
274288
* 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
301315
That is, level 5 has 256 entires pointing to level 4 maps (or nothing if there's no
302316
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.
303317

318+
### Native Format
319+
320+
The ReadyToRun image stores data in a compressed native foramt defined in [nativeformatreader.h](../../../src/coreclr/vm/nativeformatreader.h).
321+
304322
### NibbleMap
305323

306324
The ExecutionManager contract depends on a "nibble map" data structure

docs/design/datacontracts/Loader.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer);
146146
| `InstMethodHashTable` | `VolatileEntryNextEntry` | Next pointer in the hash table entry |
147147

148148

149-
150149
### Global variables used:
151150
| Global Name | Type | Purpose |
152151
| --- | --- | --- |

src/coreclr/inc/patchpointinfo.h

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

88
#include <clrtypes.h>
99

10+
#ifndef JIT_BUILD
11+
#include "cdacdata.h"
12+
#endif // JIT_BUILD
13+
1014
#ifndef _PATCHPOINTINFO_H_
1115
#define _PATCHPOINTINFO_H_
1216

@@ -201,7 +205,19 @@ struct PatchpointInfo
201205
int32_t m_securityCookieOffset;
202206
int32_t m_monitorAcquiredOffset;
203207
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);
204219
};
220+
#endif // JIT_BUILD
205221

206222
typedef DPTR(struct PatchpointInfo) PTR_PatchpointInfo;
207223

src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ private void ParseBounds(NativeReader imageReader, int offset)
158158
uint bitsCollected = 0;
159159
ulong bitTemp = 0;
160160
uint curBoundsProcessed = 0;
161-
161+
162162
uint previousNativeOffset = 0;
163163

164164
while (curBoundsProcessed < boundsEntryCount)

src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeArray.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public bool TryGetAt(uint index, ref int pOffset)
5656
if (index >= _nElements)
5757
return false;
5858

59-
uint offset = 0;
59+
uint offset;
6060
if (_entryIndexSize == 0)
6161
{
6262
int i = (int)(_baseOffset + (index / _blockSize));
@@ -82,7 +82,7 @@ public bool TryGetAt(uint index, ref int pOffset)
8282
{
8383
if ((val & 2) != 0)
8484
{
85-
offset = offset + (val >> 2);
85+
offset += val >> 2;
8686
continue;
8787
}
8888
}

src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NativeReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void ReadSpanAt(ref int start, Span<byte> buffer)
3939
throw new ArgumentOutOfRangeException(nameof(start), "Start index is out of bounds");
4040

4141
_backingStream.Seek(start, SeekOrigin.Begin);
42-
_backingStream.Read(buffer);
42+
_backingStream.ReadExactly(buffer);
4343
start += buffer.Length;
4444
}
4545

@@ -382,4 +382,4 @@ public uint DecodeUDelta(ref int start, uint lastValue)
382382
return lastValue + delta;
383383
}
384384
}
385-
}
385+
}

src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/NibbleReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace ILCompiler.Reflection.ReadyToRun
77
/// Helper to read memory by 4-bit (half-byte) nibbles as is used for encoding
88
/// method fixups. More or less ported over from CoreCLR src\inc\nibblestream.h.
99
/// </summary>
10-
class NibbleReader
10+
internal class NibbleReader
1111
{
1212
/// <summary>
1313
/// Special value in _nextNibble saying there's no next nibble and the next byte

src/coreclr/vm/codeman.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,8 +2232,17 @@ private :
22322232
HINSTANCE m_AltJITCompiler;
22332233
bool m_AltJITRequired;
22342234
#endif //ALLOW_SXS_JIT
2235+
2236+
friend struct ::cdac_data<EEJitManager>;
2237+
};
2238+
2239+
template<>
2240+
struct cdac_data<EEJitManager>
2241+
{
2242+
static constexpr size_t StoreRichDebugInfo = offsetof(EEJitManager, m_storeRichDebugInfo);
22352243
};
22362244

2245+
22372246
//*****************************************************************************
22382247
//
22392248
// This class manages IJitManagers and ICorJitCompilers. It has only static

0 commit comments

Comments
 (0)