diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 0a488ff8399316..182e6828a12a8b 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -11,7 +11,7 @@ public enum SourceTypes : uint 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 + Async = 0x04 // Indicates suspension/resumption for an async call } ``` @@ -35,15 +35,12 @@ bool HasDebugInfo(TargetCodePointer pCode); IEnumerable GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset); ``` -## Version 1 +## Version 2 - + ### Data descriptors used -| Data Descriptor | Field | Type | Meaning | -| --- | --- | --- | --- | -| `PatchpointInfo` | *(type size)* | `uint32` | Size in bytes of the fixed patchpoint header before its variable local-offset data | -| `PatchpointInfo` | `LocalCount` | `uint32` | Number of locals in the method associated with the patchpoint. | +_None._ ### Global variables used @@ -53,19 +50,34 @@ _None._ | Contract Name | | --- | +| `CodeVersions` | | `ExecutionManager` | | `PlatformMetadata` | - +| `RuntimeInfo` | + +### Constants -Constants: | Constant Name | Meaning | Value | | --- | --- | --- | -| IL_OFFSET_BIAS | IL offsets are encoded in the DebugInfo with this bias. | `0xfffffffd` (-3) | -| DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS | Indicates bounds data contains instrumented bounds | `0xFFFFFFFF` | -| EXTRA_DEBUG_INFO_PATCHPOINT | Indicates debug info contains patchpoint information | 0x1 | -| EXTRA_DEBUG_INFO_RICH | Indicates debug info contains rich information | 0x2 | -| SOURCE_TYPE_BITS | Number of bits per bounds entry used to encode source type flags | 2 | +| `IL_OFFSET_BIAS` | Bias used to encode IL offsets | `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 to encode source type flags | `3` | +| `MAX_ILNUM` | Bias for adjusted encoding of variable numbers | `0xfffffffa` (-6) | +| `CALL_RETURN_ILNUM` | Special variable number identifying a call-return-value entry | `0xfffffffb` (-5) | +| `VLT_REG` | Variable is in a register | `0` | +| `VLT_REG_BYREF` | Address of the variable is in a register | `1` | +| `VLT_REG_FP` | Variable is in an FP register | `2` | +| `VLT_STK` | Variable is on the stack | `3` | +| `VLT_STK_BYREF` | Address of the variable is on the stack | `4` | +| `VLT_REG_REG` | Variable lives in two registers | `5` | +| `VLT_REG_STK` | Variable lives partly in a register and partly on the stack | `6` | +| `VLT_STK_REG` | Reverse of `VLT_REG_STK` | `7` | +| `VLT_STK2` | Variable lives in two stack slots | `8` | +| `VLT_FPSTK` | Variable is on the floating-point stack | `9` | +| `VLT_FIXED_VA` | Fixed argument in a varargs function | `10` | +| `VLT_COUNT` | Number of valid `VarLocType` values | `11` | +| `VLT_INVALID` | Sentinel for invalid locations | `12` | ### DebugInfo Stream Encoding @@ -95,195 +107,6 @@ Examples: Based on the encoding specification, we use a decoder defined originally for r2r dump `NibbleReader.cs` -### Bounds Data Encoding (R2R Major Version 16+) - -For R2R major version 16 and above, the bounds data uses a bit-packed encoding algorithm: - -1. The bounds entry count, bits needed for native deltas, and bits needed for IL offsets are encoded using the nibble scheme above -2. Each bounds entry is then bit-packed with: - - 2 bits for source type (SourceTypeInvalid=0, CallInstruction=1, StackEmpty=2, StackEmpty|CallInstruction=3) - - Variable bits for native offset delta (accumulated from previous offset) - - Variable bits for IL offset (with IL_OFFSET_BIAS applied) - -The bit-packed data is read byte by byte, collecting bits until enough are available for each entry. - -### Implementation - -``` csharp -bool IDebugInfo.HasDebugInfo(TargetCodePointer pCode) -{ - if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) - return false; - - return _eman.GetDebugInfo(cbh, out _) != TargetPointer.Null; -} - -IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset) -{ - // Get the method's DebugInfo - if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) - throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}."); - TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool hasFlagByte); - - TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh); - codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target)); - - // No debug info exists (e.g. ILStubs). Return empty sequence. - // Callers that need to distinguish this case should use HasDebugInfo first. - if (debugInfo == TargetPointer.Null) - return []; - - return RestoreBoundaries(debugInfo, hasFlagByte, preferUninstrumented); -} - -private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte, bool preferUninstrumented) -{ - if (hasFlagByte) - { - // Check flag byte and skip over any patchpoint info - byte flagByte = _target.Read(debugInfo++); - - if ((flagByte & EXTRA_DEBUG_INFO_PATCHPOINT) != 0) - { - uint localCount = _target.Read(debugInfo + /*PatchpointInfo::LocalCount offset*/) - debugInfo += /*size of PatchpointInfo*/ + (localCount * 4); - } - - if ((flagByte & EXTRA_DEBUG_INFO_RICH) != 0) - { - uint richDebugInfoSize = _target.Read(debugInfo); - debugInfo += 4; - debugInfo += richDebugInfoSize; - } - } - - NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 24 /*maximum size of 4 32bit ints compressed*/), _target.IsLittleEndian); - NibbleReader nibbleReader = new(nibbleNativeReader, 0); - - uint cbBounds = nibbleReader.ReadUInt(); - uint cbUninstrumentedBounds = 0; - if (cbBounds == DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS) - { - // This means we have instrumented bounds. - cbBounds = nibbleReader.ReadUInt(); - cbUninstrumentedBounds = nibbleReader.ReadUInt(); - } - uint _ /*cbVars*/ = nibbleReader.ReadUInt(); - - TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); - // TargetPointer addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; - - if (preferUninstrumented && cbUninstrumentedBounds != 0) - { - // If we have uninstrumented bounds, we will use them instead of the regular bounds. - addrBounds += cbBounds; - cbBounds = cbUninstrumentedBounds; - } - - if (cbBounds > 0) - { - NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DoBounds(boundsNativeReader); - } - - return Enumerable.Empty(); -} - -private static IEnumerable DoBounds(NativeReader nativeReader) -{ - NibbleReader reader = new(nativeReader, 0); - - uint boundsEntryCount = reader.ReadUInt(); - - uint bitsForNativeDelta = reader.ReadUInt() + 1; // Number of bits needed for native deltas - uint bitsForILOffsets = reader.ReadUInt() + 1; // Number of bits needed for IL offsets - - uint bitsPerEntry = bitsForNativeDelta + bitsForILOffsets + 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.SourceTypeInvalid, - 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++; - } - } -} -``` - -## Version 2 - -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 descriptor changes from `c1` - -| Change | Data Descriptor | Field | Type | Meaning | -| --- | --- | --- | --- | --- | -| Removed | `PatchpointInfo` | *(type size)* | `uint32` | Size in bytes of the fixed patchpoint header before its variable local-offset data | -| Removed | `PatchpointInfo` | `LocalCount` | `uint32` | Number of locals in the method associated with the patchpoint. | - -### Global variable changes from `c1` - -_No changes._ - -### Contract dependency changes from `c1` - -| Change | Contract Name | -| --- | --- | -| Added | `CodeVersions` | -| Added | `RuntimeInfo` | - - - -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` | -| SOURCE_TYPE_BITS | Number of bits per bounds entry used for source type flags | 3 | - ### Header Encoding The first nibble-decoded unsigned integer (`countBoundsOrFatMarker`): @@ -309,34 +132,33 @@ AsyncInfoStart = RichDebugInfoStart + RichDebugInfoSize DebugInfoEnd = AsyncInfoStart + AsyncInfoSize ``` -### Bounds Entry Encoding Differences from Version 1 +### Bounds Entry Encoding -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]`. +Each bounds entry uses three independent flag bits for source type: +`[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) | +| 2 | 0x4 | `Async` | `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: +Pseudo-code for 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 +if ((encoded & 0x4) != 0) sourceType |= SourceTypes.Async; ``` After masking the 3 bits, shift them out before reading native delta and IL offset fields as before. -### Variable Location APIs (Version 2+) +### Variable Location APIs -Version 2 adds support for decoding native variable location info from the Vars section of the debug info blob. +The contract decodes native variable location information from the Vars section of the debug info blob. Additional APIs: ```csharp @@ -376,25 +198,6 @@ public readonly struct DebugVarInfo IEnumerable GetMethodVarInfo(TargetCodePointer pCode, out uint codeOffset); ``` -Additional constants (Version 2): -| Constant Name | Meaning | Value | -| --- | --- | --- | -| `MAX_ILNUM` | Bias for adjusted encoding of variable numbers | `0xfffffffa` (-6) | -| `CALL_RETURN_ILNUM` | Special variable number identifying a call-return-value entry | `0xfffffffb` (-5) | -| `VLT_REG` | Variable is in a register | `0` | -| `VLT_REG_BYREF` | Address of the variable is in a register | `1` | -| `VLT_REG_FP` | Variable is in an FP register | `2` | -| `VLT_STK` | Variable is on the stack | `3` | -| `VLT_STK_BYREF` | Address of the variable is on the stack | `4` | -| `VLT_REG_REG` | Variable lives in two registers | `5` | -| `VLT_REG_STK` | Variable lives partly in a register and partly on the stack | `6` | -| `VLT_STK_REG` | Reverse of VLT_REG_STK | `7` | -| `VLT_STK2` | Variable lives in two stack slots | `8` | -| `VLT_FPSTK` | Variable is on the floating-point stack | `9` | -| `VLT_FIXED_VA` | Fixed argument in a varargs function | `10` | -| `VLT_COUNT` | Number of valid VarLocType values | `11` | -| `VLT_INVALID` | Sentinel for invalid locations | `12` | - ### Vars Data Encoding Each variable entry in the Vars section is nibble-encoded as follows: @@ -420,7 +223,7 @@ Each variable entry in the Vars section is nibble-encoded as follows: Signed integers are encoded using the same unsigned scheme, with the sign bit stored in bit 0 (`value = unsigned >> 1`, negate if `unsigned & 1`). On x86, stack offsets are DWORD-aligned and stored divided by `sizeof(DWORD)`. -### Async Suspension Point APIs (Version 2+) +### Async Suspension Point APIs We also support decoding async suspension points (and their captured continuation-object locals) from the `AsyncInfo` chunk of the debug info blob. The chunk is present only for methods that the JIT compiled with runtime-async suspension points; for all other methods, `AsyncInfoSize` is `0` in the FAT header and the API returns an empty list. diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 90a91ca24b237f..a21886b71e7a45 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -148,14 +148,14 @@ public enum CodeKind : uint } ``` -## Version 1 +## Version 2 The execution manager uses two data structures to map the entire target address space to native executable code. The [range section map](#rangesectionmap) is used to partition the address space into large chunks which point to range section fragments. Each chunk is relatively large. If there is any executable code in the chunk, the chunk will contain one or more range section fragments that cover subsets of the chunk. Conversely if a massive method is JITed a single range section fragment may span multiple adjacent chunks. Within a range section fragment, a [nibble map](#nibblemap) structure is used to map arbitrary IP addresses back to the start of the method (and to the code header which immediately preceeeds the entrypoint to the code). - + ### Data descriptors used | Data Descriptor | Field | Type | Meaning | @@ -272,7 +272,7 @@ Within a range section fragment, a [nibble map](#nibblemap) structure is used to | `PrecodeStubs` | | `RuntimeInfo` | | `RuntimeTypeSystem` | - + Contract constants used: | Name | Type | Purpose | Value | @@ -661,127 +661,66 @@ The ReadyToRun image stores data in a compressed native foramt defined in [nativ The ExecutionManager contract depends on a "nibble map" data structure that allows mapping of a code address in a contiguous subsection of -the address space to the pointer to the start of that a code sequence. -It takes advantage of the fact that the code starts are aligned and -are spaced apart to represent their addresses as a 4-bit nibble value. - -Version 1 of the contract depends on the `NibbleMapLinearLookup` implementation of the nibblemap algorithm. - -Given a contiguous region of memory in which we lay out a collection of non-overlapping code blocks that are -not too small (so that two adjacent ones aren't too close together) and where the start of each code block is aligned on some power of 2 and preceeded by a code header, -we can break up the whole memory space into buckets of a fixed size (32-bytes in the current implementation), where -each bucket either has a code block or not. -Thinking of each code block address as a hex number, we can view it as: [index, offset] -where each index gives us a bucket and the offset gives us the position of the header within the bucket. -In the current implementation code must be 4 byte aligned therefore there are 8 possible offsets in a bucket. -These are encoded as values 1-8 in the 4-bit nibble, with 0 reserved to mark the places in the map where a method doesn't start. - -To find the start of a method given an address we first convert it into a bucket index (giving the map unit) -and an offset which we can then turn into the index of the nibble that covers that address. -If the nibble is non-zero, we have the start of a method and it is near the given address. -If the nibble is zero, we have to search backward first through the current map unit, and then through previous map -units until we find a non-zero nibble. - -For example (all code addresses are relative to some unspecified base): - -Suppose there is code starting at address 304 (0x130) - -* Then the map index will be 304 / 32 = 9 and the byte offset will be 304 % 32 = 16 -* Because addresses are 4-byte aligned, the nibble value will be 1 + 16 / 4 = 5 (we reserve 0 to mean no method). -* So the map unit containing index 9 will contain the value 0x5 << 24 (the map index 9 means we want the second nibble in the second map unit, and we number the nibbles starting from the most significant) , or 0x05000000 - - -Now suppose we do a lookup for address 306 (0x132) -* The map index will be 306 / 32 = 9 and the byte offset will be 306 % 32 = 18 -* The nibble value will be 1 + 18 / 4 = 5 -* To do the lookup, we will load the map unit with index 9 (so the second 32-bit unit in the map) and get the value 0x05000000 -* We will then shift to focus on the nibble with map index 9 (which again has nibble shift 24), so - the map unit will be 0x00000005 and we will get the nibble value 5. -* Therefore we know that there is a method start at map index 9, nibble value 5. -* The map index corresponds to an offset of 288 bytes and the nibble value 5 corresponds to an offset of (5 - 1) * 4 = 16 bytes -* So the method starts at offset 288 + 16 = 304, which is the address we were looking for. - -Now suppose we do a lookup for address 302 (0x12E) - -* The map index will be 302 / 32 = 9 and the byte offset will be 302 % 32 = 14 -* The nibble value will be 1 + 14 / 4 = 4 -* To do the lookup, we will load the map unit containing map index 9 and get the value 0x05000000 -* We will then shift to focus on the nibble with map index 9 (which again has nibble shift 22), so we will get - the nibble value 5. -* Therefore we know that there is a method start at map index 9, nibble value 5. -* But the address we're looking for is map index 9, nibble value 4. -* We know that methods can't start within 32-bytes of each other, so we know that the method we're looking for is not in the current nibble. -* We will then try to shift to the previous nibble in the map unit (0x00000005 >> 4 = 0x00000000) -* Therefore we know there is no method start at any map index in the current map unit. -* We will then align the map index to the start of the current map unit (map index 8) and move back to the previous map unit (map index 7) -* At that point, we scan backwards for a non-zero map unit and a non-zero nibble within the first non-zero map unit. Since there are none, we return null. - - -## Version 2 - -Version 2 of the contract depends the new `NibbleMapConstantLookup` algorithm which has O(1) lookup time compared to the `NibbleMapLinearLookup` O(n) lookup time. - -With the exception of the nibblemap change, version 2 is identical to version 1. - - -### Data descriptor changes from `c1` - -_No changes._ - -### Global variable changes from `c1` - -_No changes._ - -### Contract dependency changes from `c1` - -_No changes._ - - -### NibbleMap - -The `NibbleMapConstantLookup` implementation is very similar to `NibbleMapLinearLookup` with the addition -of writing relative pointers into the nibblemap whenever a code block completely covers the code region -represented by a DWORD, with the current values 256 bytes. -This allows for O(1) lookup time with the cost of O(n) write time. +the address space to the start of a code block. It stores method starts as +4-bit nibble values and uses encoded relative pointers for regions fully +covered by a method, allowing lookup in constant time. -Pointers are encoded using the top 28 bits of the DWORD. The bottom 4 bits of the pointer -are reduced to 2 bits of data using the fact that code start must be 4 byte aligned. This is encoded into -the nibble in bits 28 .. 31 of the DWORD with values 9-12. This is also used to differentiate DWORDs -filled with nibble values and DWORDs with pointer values. +The covered address range is divided into 32-byte buckets. Code starts are +4-byte aligned, so a start can occupy one of eight offsets within a bucket. +Each bucket is represented by a nibble: | Nibble Value | Meaning | How to decode | |:------------:|:--------|:--------------:| | 0 | empty | | -| 1-8 | Nibble | value - 1 | -| 9-12 | Pointer | (value - 9) << 2 | +| 1-8 | Code start | `(value - 1) * 4` is the byte offset within the 32-byte bucket | +| 9-12 | Relative pointer | `(value - 9) << 2` supplies the low four bits of the pointer | | 13-15 | unused | | -To read the nibblemap, we check if the DWORD is a pointer. If so, then we know the value looked up is -part of a managed code block beginning at the map base + decoded pointer. Otherwise we can check for nibbles -as normal. If the DWORD is empty (no pointer or previous nibbles), then we check the previous DWORD for a -pointer or preceeding nibble. If that DWORD is empty, then we must not be in a managed function. If we were, -the write algorithm would have written a relative pointer in the DWORD or we would have seen the start nibble. - -Note, looking up a value that points to bytes outside of a managed function has undefined behavior. -In this implementation we may "extend" the lookup period of a function several hundred bytes -if there is not another function immediately following it. - -We will go through the same example as above with the new algorithm. Suppose there is code starting at address 304 (0x130) with length 1024 (0x400). - -* There will be a nibble at the start of the function as before. - * The map index will be 304 / 32 = 9 and the byte offset will be 304 % 32 = 16 - * Because addresses are 4-byte aligned, the nibble value will be 1 + 16 / 4 = 5 (we reserve 0 to mean no method). - * So the map unit containing index 9 will contain the value 0x5 << 24 (the map index 9 means we want the second nibble in the second map unit, and we number the nibbles starting from the most significant) , or 0x05000000 -* Since the function starts at 304 with a length of 1024, the last byte of the function is at 1327 (0x52F). Map units (DWORDs) contain 256 bytes (0x100) algined to the map base. Therefore map units represnting 0x200-0x2FF, 0x300-0x3FF and 0x400-0x4ff are completely covered by the function and will have a relative pointer. - * To get the relative pointer value we split the code start value at the bottom 4 bits. The top 28 bits are included as normal. We shift the bottom 4 bits 2 to the right and add 9, to get the bottom 4 bits encoding. This gives us a relative pointer value of 311 (0x137). - * 304 = 0b100110000 - * Top 28 bits: 304 = 0b10011xxxx - * Bottom 4 bits: 0 = 0b0000 - * Bottom 4 bits encoding: 9 = (0 >> 2) + 9 - * Relative Pointer Encoding: 311 = 304 + 9 - -Now suppose we do a lookup for address 1300 (0x514) -* The map index will be 1300 / 32 = 40 which is located in the 40 / 8 = 5th map unit (DWORD). -* We read the value of the 5th map unit and find it is empty. -* We read the value of the 4th map unit and find that the nibble in the lowest bits has the value of 9 implying that this map unit is a relative pointer. -* Since we found a relative pointer we can decode the entire map unit as a relative pointer and return that address added to the base. +Eight nibbles are packed into each 32-bit map unit, so one map unit +represents 256 bytes of code. A map unit either contains eight bucket +nibbles or contains one encoded relative pointer; values 9-12 in its low +nibble distinguish a pointer from bucket data. The pointer's upper 28 bits +are stored directly. Its low four bits contain only two bits of information +because code starts are 4-byte aligned, so they are encoded as values 9-12. +Adding the decoded relative pointer to the map base gives the method start. + +When a code block is added, its start is recorded in the nibble for the +containing bucket. Each subsequent map unit whose entire 256-byte region is +covered by that code block is filled with an encoded relative pointer to the +same start. This increases insertion work with the size of the code block, +but ensures that lookup examines at most two map units. + +To find the code block containing an address: + +1. Convert the address relative to the map base into a 32-byte bucket index + and an offset within that bucket. +2. Read the map unit containing the bucket. If it is a relative pointer, + decode and return it. +3. Otherwise, inspect the nibble for the bucket. It identifies a code start + only when its decoded offset is at or before the address being queried. + If it does not, search the preceding nibbles in the same map unit. +4. If the current map unit contains no preceding code start, inspect the + immediately preceding map unit. Decode it if it is a relative pointer; + otherwise return its last nonzero code-start nibble. If it is empty, + return null. + +Only the preceding map unit must be examined: if a code block began earlier +and extended across an intervening complete map unit, that unit would contain +its relative pointer. + +For example, suppose a code block begins at relative address 304 (`0x130`) +and has length 1024 (`0x400`): + +* Its bucket index is `304 / 32 = 9`, and its offset within the bucket is + `304 % 32 = 16`. The start is therefore encoded as nibble value + `1 + 16 / 4 = 5`. +* The code block completely covers the map units representing + `0x200-0x2ff`, `0x300-0x3ff`, and `0x400-0x4ff`, so each contains an + encoded relative pointer to `0x130`. +* Looking up address 1300 (`0x514`) first examines the map unit for + `0x500-0x5ff`, which is empty. The immediately preceding map unit contains + the relative pointer, which decodes to the method start at `0x130`. + +Lookup behavior is undefined for addresses outside a managed code block. If +no following method start limits the result, a lookup can appear to extend a +method by several hundred bytes. diff --git a/docs/design/datacontracts/PrecodeStubs.md b/docs/design/datacontracts/PrecodeStubs.md index e07ad64f88db31..c40118d88da14b 100644 --- a/docs/design/datacontracts/PrecodeStubs.md +++ b/docs/design/datacontracts/PrecodeStubs.md @@ -18,24 +18,33 @@ This contract provides support for examining [precode](../coreclr/botr/method-de TargetCodePointer GetInterpreterCodeFromInterpreterPrecodeIfPresent(TargetCodePointer entryPoint); ``` -## Version 1 +## Version 3 - + ### Data descriptors used | Data Descriptor | Field | Type | Meaning | | --- | --- | --- | --- | | `FixupPrecodeData` | `MethodDesc` | `pointer` | pointer to the MethodDesc associated with this fixup precode | -| `PrecodeMachineDescriptor` | `FixupPrecodeType` | `uint8` | Precode type byte for a fixup precode | -| `PrecodeMachineDescriptor` | `OffsetOfPrecodeType` | `uint8` | See ReadPrecodeType (Version 1 and 2 only) | +| `InterpByteCodeStart` | `Method` | `pointer` | pointer to the InterpMethod associated with the bytecode | +| `InterpMethod` | `MethodDesc` | `pointer` | pointer to the MethodDesc for the interpreted method | +| `InterpreterPrecodeData` | `ByteCodeAddr` | `pointer` | pointer to the InterpByteCodeStart for the interpreter bytecode | +| `PrecodeMachineDescriptor` | `DynamicHelperPrecodeType` | `uint8` | Precode type byte for a dynamic helper precode | +| `PrecodeMachineDescriptor` | `FixupBytes` | `uint8[]` | Assembly code of a FixupStub | +| `PrecodeMachineDescriptor` | `FixupIgnoredBytes` | `uint8[]` | Bytes to ignore when comparing FixupBytes to an actual block of memory in the target process. | +| `PrecodeMachineDescriptor` | `FixupStubPrecodeSize` | `uint8` | Byte size of FixupBytes and FixupIgnoredBytes | +| `PrecodeMachineDescriptor` | `InterpreterPrecodeType` | `uint8` | Precode type byte for an interpreter precode | | `PrecodeMachineDescriptor` | `PInvokeImportPrecodeType` | `uint8` | Precode type byte for a P/Invoke import precode | -| `PrecodeMachineDescriptor` | `ReadWidthOfPrecodeType` | `uint8` | See ReadPrecodeType (Version 1 and 2 only) | -| `PrecodeMachineDescriptor` | `ShiftOfPrecodeType` | `uint8` | See ReadPrecodeType (Version 1 and 2 only) | +| `PrecodeMachineDescriptor` | `StubBytes` | `uint8[]` | Assembly code of a StubPrecode | | `PrecodeMachineDescriptor` | `StubCodePageSize` | `uint32` | Size of a precode code page (in bytes) | +| `PrecodeMachineDescriptor` | `StubIgnoredBytes` | `uint8[]` | Bytes to ignore when comparing StubBytes to an actual block of memory in the target process. | +| `PrecodeMachineDescriptor` | `StubPrecodeSize` | `uint8` | Byte size of StubBytes and StubIgnoredBytes | | `PrecodeMachineDescriptor` | `StubPrecodeType` | `uint8` | precode sort byte for stub precodes | | `PrecodeMachineDescriptor` | `ThisPointerRetBufPrecodeType` | `uint8` | Precode type byte for a this-pointer return-buffer precode | -| `StubPrecodeData` | `MethodDesc` | `pointer` | pointer to the MethodDesc associated with this stub precode (Version 1 only) | +| `PrecodeMachineDescriptor` | `UMEntryPrecodeType` | `uint8` | Precode type byte for a UMEntry precode | +| `StubPrecodeData` | `SecretParam` | `pointer` | pointer to the MethodDesc associated with this stub precode or a second stub data pointer for other types | | `StubPrecodeData` | `Type` | `uint8` | precise sort of stub precode | +| `ThisPtrRetBufPrecodeData` | `MethodDesc` | `pointer` | pointer to the MethodDesc associated with the ThisPtrRetBufPrecode | ### Global variables used @@ -46,66 +55,13 @@ _None._ | Contract Name | | --- | | `PlatformMetadata` | - - -## Version 2 dependency changes from Version 1 - - -### Data descriptor changes from `c1` - -| Change | Data Descriptor | Field | Type | Meaning | -| --- | --- | --- | --- | --- | -| Removed | `StubPrecodeData` | `MethodDesc` | `pointer` | pointer to the MethodDesc associated with this stub precode (Version 1 only) | -| Added | `StubPrecodeData` | `SecretParam` | `pointer` | pointer to the MethodDesc associated with this stub precode or a second stub data pointer for other types (Version 2 only) | -| Added | `ThisPtrRetBufPrecodeData` | `MethodDesc` | `pointer` | pointer to the MethodDesc associated with the ThisPtrRetBufPrecode (Version 2 only) | - -### Global variable changes from `c1` - -_No changes._ - -### Contract dependency changes from `c1` - -_No changes._ - - -## Version 3 dependency changes from Version 2 - - -### Data descriptor changes from `c2` - -| Change | Data Descriptor | Field | Type | Meaning | -| --- | --- | --- | --- | --- | -| Added | `InterpByteCodeStart` | `Method` | `pointer` | pointer to the InterpMethod associated with the bytecode | -| Added | `InterpMethod` | `MethodDesc` | `pointer` | pointer to the MethodDesc for the interpreted method | -| Added | `InterpreterPrecodeData` | `ByteCodeAddr` | `pointer` | pointer to the InterpByteCodeStart for the interpreter bytecode (Version 3 only) | -| Added | `PrecodeMachineDescriptor` | `DynamicHelperPrecodeType` | `uint8` | Precode type byte for a dynamic helper precode | -| Added | `PrecodeMachineDescriptor` | `FixupBytes` | `uint8[]` | Assembly code of a FixupStub (Version 3 only) | -| Added | `PrecodeMachineDescriptor` | `FixupIgnoredBytes` | `uint8[]` | Bytes to ignore of when comparing FixupBytes to an actual block of memory in the target process. (Version 3 only) | -| Removed | `PrecodeMachineDescriptor` | `FixupPrecodeType` | `uint8` | Precode type byte for a fixup precode | -| Added | `PrecodeMachineDescriptor` | `FixupStubPrecodeSize` | `uint8` | Byte size of FixupBytes and FixupIgnoredBytes (Version 3 only) | -| Added | `PrecodeMachineDescriptor` | `InterpreterPrecodeType` | `uint8` | Precode type byte for an interpreter precode | -| Removed | `PrecodeMachineDescriptor` | `OffsetOfPrecodeType` | `uint8` | See ReadPrecodeType (Version 1 and 2 only) | -| Removed | `PrecodeMachineDescriptor` | `ReadWidthOfPrecodeType` | `uint8` | See ReadPrecodeType (Version 1 and 2 only) | -| Removed | `PrecodeMachineDescriptor` | `ShiftOfPrecodeType` | `uint8` | See ReadPrecodeType (Version 1 and 2 only) | -| Added | `PrecodeMachineDescriptor` | `StubBytes` | `uint8[]` | Assembly code of a StubPrecode (Version 3 only) | -| Added | `PrecodeMachineDescriptor` | `StubIgnoredBytes` | `uint8[]` | Bytes to ignore of when comparing StubBytes to an actual block of memory in the target process. (Version 3 only) | -| Added | `PrecodeMachineDescriptor` | `StubPrecodeSize` | `uint8` | Byte size of StubBytes and StubIgnoredBytes (Version 3 only) | -| Added | `PrecodeMachineDescriptor` | `UMEntryPrecodeType` | `uint8` | Precode type byte for a UMEntry precode | - -### Global variable changes from `c2` - -_No changes._ - -### Contract dependency changes from `c2` - -_No changes._ - + The `CodePointerToInstrPointerMask` converts IP values that may include an arm Thumb bit (for example, extracted from disassembling a call instruction or from a snapshot of the registers) into an address. On other architectures applying the mask is a no-op. -### Determining the precode type (Version 3) +### Determining the precode type ``` csharp private bool ReadBytesAndCompare(TargetPointer instrAddress, byte[] expectedBytePattern, byte[] bytesToIgnore) { @@ -168,77 +124,6 @@ registers) into an address. On other architectures applying the mask is a no-op. } ``` -### Determining the precode type (Version 1 and 2) - -An initial approximation of the precode type relies on a particular pattern at a known offset from the precode entrypoint. -The precode type is expected to be encoded as an immediate. On some platforms the value is spread over multiple instruction bytes and may need to be right-shifted. - -```csharp - private byte ReadPrecodeType(TargetPointer instrPointer) - { - if (MachineDescriptor.ReadWidthOfPrecodeType == 1) - { - byte precodeType = _target.Read(instrPointer + MachineDescriptor.OffsetOfPrecodeType); - return (byte)(precodeType >> MachineDescriptor.ShiftOfPrecodeType); - } - else if (MachineDescriptor.ReadWidthOfPrecodeType == 2) - { - ushort precodeType = _target.Read(instrPointer + MachineDescriptor.OffsetOfPrecodeType); - return (byte)(precodeType >> MachineDescriptor.ShiftOfPrecodeType); - } - else - { - throw new InvalidOperationException($"Invalid precode type width {MachineDescriptor.ReadWidthOfPrecodeType}"); - } - } -``` - -After the initial precode type is determined, for stub precodes a refined precode type is extracted from the stub precode data. - -```csharp - private KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrAddress) - { - // We get the precode type in two phases: - // 1. Read the precode type from the intruction address. - // 2. If it's "stub", look at the stub data and get the actual precode type - it could be stub, - // but it could also be a pinvoke precode - // precode.h Precode::GetType() - byte approxPrecodeType = ReadPrecodeType(instrAddress); - byte exactPrecodeType; - if (approxPrecodeType == MachineDescriptor.StubPrecodeType) - { - // get the actual type from the StubPrecodeData - Data.StubPrecodeData stubPrecodeData = GetStubPrecodeData(instrAddress); - exactPrecodeType = stubPrecodeData.Type; - } - else - { - exactPrecodeType = approxPrecodeType; - } - - if (exactPrecodeType == MachineDescriptor.StubPrecodeType) - { - return KnownPrecodeType.Stub; - } - else if (MachineDescriptor.PInvokeImportPrecodeType is byte ndType && exactPrecodeType == ndType) - { - return KnownPrecodeType.PInvokeImport; - } - else if (MachineDescriptor.FixupPrecodeType is byte fixupType && exactPrecodeType == fixupType) - { - return KnownPrecodeType.Fixup; - } - else if (MachineDescriptor.ThisPointerRetBufPrecodeType is byte thisPtrRetBufType && exactPrecodeType == thisPtrRetBufType) - { - return KnownPrecodeType.ThisPtrRetBuf; - } - else - { - return null; - } - } -``` - ### `MethodDescFromStubAddress` ```csharp @@ -310,7 +195,7 @@ After the initial precode type is determined, for stub precodes a refined precod } } - // Version 3 only: resolves MethodDesc for interpreter precodes by following + // Resolves MethodDesc for interpreter precodes by following // the InterpreterPrecodeData -> InterpByteCodeStart -> InterpMethod -> MethodDesc chain. internal sealed class InterpreterPrecode : ValidPrecode { diff --git a/docs/design/datacontracts/data-descriptor-meanings.json b/docs/design/datacontracts/data-descriptor-meanings.json index 9d867799234ddf..5ac9114612c194 100644 --- a/docs/design/datacontracts/data-descriptor-meanings.json +++ b/docs/design/datacontracts/data-descriptor-meanings.json @@ -310,7 +310,7 @@ "InterpMethodContextFrame.Stack": "Pointer to the stack base for this interpreted method, used as the frame pointer when interpreter GC info uses a stack-base register", "InterpreterFrame.IsFaulting": "Boolean indicating whether the topmost interpreted frame has thrown an exception. When set, the context for the top interpreted frame must include CONTEXT_EXCEPTION_ACTIVE so exception unwinders treat the IP as a faulting instruction rather than a return-from-call", "InterpreterFrame.TopInterpMethodContextFrame": "Pointer to the InterpreterFrame's top InterpMethodContextFrame", - "InterpreterPrecodeData.ByteCodeAddr": "pointer to the InterpByteCodeStart for the interpreter bytecode (Version 3 only)", + "InterpreterPrecodeData.ByteCodeAddr": "pointer to the InterpByteCodeStart for the interpreter bytecode", "InterpreterRealCodeHeader.DebugInfo": "Pointer to the DebugInfo for interpreter code", "InterpreterRealCodeHeader.GCInfo": "Pointer to the GCInfo encoding for interpreter code", "InterpreterRealCodeHeader.JitEHInfo": "Pointer to the `EE_ILEXCEPTION` containing exception clauses for interpreter code", @@ -449,20 +449,17 @@ "PlatformMetadata.CodePointerFlags": "fields describing the behavior of target code pointers", "PlatformMetadata.PrecodeMachineDescriptor": "precode stub-related platform specific properties", "PortableEntryPoint.MethodDesc": "Method desc of portable entrypoint (only defined if `FeaturePortableEntrypoints` is enabled)", - "PrecodeMachineDescriptor.FixupBytes": "Assembly code of a FixupStub (Version 3 only)", - "PrecodeMachineDescriptor.FixupIgnoredBytes": "Bytes to ignore of when comparing FixupBytes to an actual block of memory in the target process. (Version 3 only)", + "PrecodeMachineDescriptor.FixupBytes": "Assembly code of a FixupStub", + "PrecodeMachineDescriptor.FixupIgnoredBytes": "Bytes to ignore when comparing FixupBytes to an actual block of memory in the target process.", "PrecodeMachineDescriptor.FixupPrecodeType": "Precode type byte for a fixup precode", - "PrecodeMachineDescriptor.FixupStubPrecodeSize": "Byte size of FixupBytes and FixupIgnoredBytes (Version 3 only)", + "PrecodeMachineDescriptor.FixupStubPrecodeSize": "Byte size of FixupBytes and FixupIgnoredBytes", "PrecodeMachineDescriptor.DynamicHelperPrecodeType": "Precode type byte for a dynamic helper precode", "PrecodeMachineDescriptor.InterpreterPrecodeType": "Precode type byte for an interpreter precode", - "PrecodeMachineDescriptor.OffsetOfPrecodeType": "See ReadPrecodeType (Version 1 and 2 only)", "PrecodeMachineDescriptor.PInvokeImportPrecodeType": "Precode type byte for a P/Invoke import precode", - "PrecodeMachineDescriptor.ReadWidthOfPrecodeType": "See ReadPrecodeType (Version 1 and 2 only)", - "PrecodeMachineDescriptor.ShiftOfPrecodeType": "See ReadPrecodeType (Version 1 and 2 only)", - "PrecodeMachineDescriptor.StubBytes": "Assembly code of a StubPrecode (Version 3 only)", + "PrecodeMachineDescriptor.StubBytes": "Assembly code of a StubPrecode", "PrecodeMachineDescriptor.StubCodePageSize": "Size of a precode code page (in bytes)", - "PrecodeMachineDescriptor.StubIgnoredBytes": "Bytes to ignore of when comparing StubBytes to an actual block of memory in the target process. (Version 3 only)", - "PrecodeMachineDescriptor.StubPrecodeSize": "Byte size of StubBytes and StubIgnoredBytes (Version 3 only)", + "PrecodeMachineDescriptor.StubIgnoredBytes": "Bytes to ignore when comparing StubBytes to an actual block of memory in the target process.", + "PrecodeMachineDescriptor.StubPrecodeSize": "Byte size of StubBytes and StubIgnoredBytes", "PrecodeMachineDescriptor.StubPrecodeType": "precode sort byte for stub precodes", "PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType": "Precode type byte for a this-pointer return-buffer precode", "PrecodeMachineDescriptor.UMEntryPrecodeType": "Precode type byte for a UMEntry precode", @@ -586,8 +583,7 @@ "StubDispatchFrame.MethodDescPtr": "Pointer to Frame's method desc", "StubDispatchFrame.RepresentativeMTPtr": "Pointer to Frame's method table pointer", "StubDispatchFrame.RepresentativeSlot": "Frame's method table slot", - "StubPrecodeData.MethodDesc": "pointer to the MethodDesc associated with this stub precode (Version 1 only)", - "StubPrecodeData.SecretParam": "pointer to the MethodDesc associated with this stub precode or a second stub data pointer for other types (Version 2 only)", + "StubPrecodeData.SecretParam": "pointer to the MethodDesc associated with this stub precode or a second stub data pointer for other types", "StubPrecodeData.Type": "precise sort of stub precode", "SyncBlock.EnCInfo": "Pointer to Edit-and-Continue added-field information for the object; optional when Edit and Continue is not configured", "SyncBlock.HashCode": "Hash code stored in the sync block", @@ -629,7 +625,7 @@ "TailCallFrame.CalleeSavedRegisters": "Address of the embedded nonvolatile-register values saved in the tailcall frame", "TailCallFrame.ReturnAddress": "Return address saved in the tailcall frame", "TailCallFrame.Size": "Size in bytes of the tailcall frame, used to restore the caller's stack pointer", - "ThisPtrRetBufPrecodeData.MethodDesc": "pointer to the MethodDesc associated with the ThisPtrRetBufPrecode (Version 2 only)", + "ThisPtrRetBufPrecodeData.MethodDesc": "pointer to the MethodDesc associated with the ThisPtrRetBufPrecode", "Thread.CachedStackBase": "Pointer to the base of the stack", "Thread.CachedStackLimit": "Pointer to the limit of the stack", "Thread.CurrentCustomDebuggerNotification": "Handle to the current custom debugger notification object", diff --git a/docs/design/datacontracts/data-descriptor-overrides.json b/docs/design/datacontracts/data-descriptor-overrides.json index 52e9eae4405a14..a9e681e2089d86 100644 --- a/docs/design/datacontracts/data-descriptor-overrides.json +++ b/docs/design/datacontracts/data-descriptor-overrides.json @@ -5,35 +5,5 @@ "type.Table": "pointer", "type.TableSize": "uint32" } - }, - "_suppress": { - "PrecodeStubs@c1": [ - "PrecodeMachineDescriptor.DynamicHelperPrecodeType", - "PrecodeMachineDescriptor.FixupBytes", - "PrecodeMachineDescriptor.FixupIgnoredBytes", - "PrecodeMachineDescriptor.FixupStubPrecodeSize", - "PrecodeMachineDescriptor.InterpreterPrecodeType", - "PrecodeMachineDescriptor.StubBytes", - "PrecodeMachineDescriptor.StubIgnoredBytes", - "PrecodeMachineDescriptor.StubPrecodeSize", - "PrecodeMachineDescriptor.UMEntryPrecodeType" - ], - "PrecodeStubs@c2": [ - "PrecodeMachineDescriptor.DynamicHelperPrecodeType", - "PrecodeMachineDescriptor.FixupBytes", - "PrecodeMachineDescriptor.FixupIgnoredBytes", - "PrecodeMachineDescriptor.FixupStubPrecodeSize", - "PrecodeMachineDescriptor.InterpreterPrecodeType", - "PrecodeMachineDescriptor.StubBytes", - "PrecodeMachineDescriptor.StubIgnoredBytes", - "PrecodeMachineDescriptor.StubPrecodeSize", - "PrecodeMachineDescriptor.UMEntryPrecodeType" - ], - "PrecodeStubs@c3": [ - "PrecodeMachineDescriptor.FixupPrecodeType", - "PrecodeMachineDescriptor.OffsetOfPrecodeType", - "PrecodeMachineDescriptor.ReadWidthOfPrecodeType", - "PrecodeMachineDescriptor.ShiftOfPrecodeType" - ] } } 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 deleted file mode 100644 index 91391cfdf599bf..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs +++ /dev/null @@ -1,110 +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 sealed class DebugInfo_1(Target target) : IDebugInfo -{ - private const uint DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS = 0xFFFFFFFF; - - [Flags] - internal enum ExtraDebugInfoFlags_1 : byte - { - // Debug info contains patchpoint information - EXTRA_DEBUG_INFO_PATCHPOINT = 0x01, - // Debug info contains rich information - EXTRA_DEBUG_INFO_RICH = 0x02, - } - - private readonly Target _target = target; - private readonly IExecutionManager _eman = target.Contracts.ExecutionManager; - - bool IDebugInfo.HasDebugInfo(TargetCodePointer pCode) - { - if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) - return false; - - return _eman.GetDebugInfo(cbh, out _) != TargetPointer.Null; - } - - IEnumerable IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode, bool preferUninstrumented, out uint codeOffset) - { - // Get the method's DebugInfo - if (_eman.GetCodeBlockHandle(pCode) is not CodeBlockHandle cbh) - throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}."); - TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool hasFlagByte); - - TargetPointer nativeCodeStart = _eman.GetStartAddress(cbh); - codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - nativeCodeStart); - - if (debugInfo == TargetPointer.Null) - return []; - - return RestoreBoundaries(debugInfo, hasFlagByte, preferUninstrumented); - } - - private IEnumerable RestoreBoundaries(TargetPointer debugInfo, bool hasFlagByte, bool preferUninstrumented) - { - if (hasFlagByte) - { - // Check flag byte and skip over any patchpoint info - ExtraDebugInfoFlags_1 flagByte = (ExtraDebugInfoFlags_1)_target.Read(debugInfo++); - - if (flagByte.HasFlag(ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_PATCHPOINT)) - { - Data.PatchpointInfo patchpointInfo = _target.ProcessedData.GetOrAdd(debugInfo); - - uint patchpointSize = Data.PatchpointInfo.GetSize(_target); - debugInfo += patchpointSize + (patchpointInfo.LocalCount * sizeof(uint)); - - flagByte &= ~ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_PATCHPOINT; - } - - if (flagByte.HasFlag(ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_RICH)) - { - uint richDebugInfoSize = _target.Read(debugInfo); - debugInfo += 4; - debugInfo += richDebugInfoSize; - flagByte &= ~ExtraDebugInfoFlags_1.EXTRA_DEBUG_INFO_RICH; - } - - Debug.Assert(flagByte == 0); - } - - NativeReader nibbleNativeReader = new(new TargetStream(_target, debugInfo, 24 /*maximum size of 4 32bit ints compressed*/), _target.IsLittleEndian); - NibbleReader nibbleReader = new(nibbleNativeReader, 0); - - uint cbBounds = nibbleReader.ReadUInt(); - uint cbUninstrumentedBounds = 0; - if (cbBounds == DEBUG_INFO_BOUNDS_HAS_INSTRUMENTED_BOUNDS) - { - // This means we have instrumented bounds. - cbBounds = nibbleReader.ReadUInt(); - cbUninstrumentedBounds = nibbleReader.ReadUInt(); - } - uint _ /*cbVars*/ = nibbleReader.ReadUInt(); - - TargetPointer addrBounds = debugInfo + (uint)nibbleReader.GetNextByteOffset(); - // TargetPointer addrVars = addrBounds + cbBounds + cbUninstrumentedBounds; - - if (preferUninstrumented && cbUninstrumentedBounds != 0) - { - // If we have uninstrumented bounds, we will use them instead of the regular bounds. - addrBounds += cbBounds; - cbBounds = cbUninstrumentedBounds; - } - - if (cbBounds > 0) - { - NativeReader boundsNativeReader = new(new TargetStream(_target, addrBounds, cbBounds), _target.IsLittleEndian); - return DebugInfoHelpers.DoBounds(boundsNativeReader, 1); - } - - return []; - } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs deleted file mode 100644 index 8da20d8714bb89..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ /dev/null @@ -1,41 +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.Collections.Generic; -using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -public sealed class ExecutionManager_1 : IExecutionManager -{ - private IExecutionManager _executionManagerCore; - - internal ExecutionManager_1(Target target) - { - TargetPointer addr = target.ReadGlobalPointer(Constants.Globals.ExecutionManagerCodeRangeMapAddress); - _executionManagerCore = new ExecutionManagerCore(target, addr); - } - - public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); - public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); - public TargetPointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); - public TargetPointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle); - public void GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize) => _executionManagerCore.GetMethodRegionInfo(codeInfoHandle, out hotSize, out coldStart, out coldSize); - public TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) => _executionManagerCore.NonVirtualEntry2MethodDesc(entrypoint); - public bool IsFunclet(CodeBlockHandle codeInfoHandle) => _executionManagerCore.IsFunclet(codeInfoHandle); - public bool IsFilterFunclet(CodeBlockHandle codeInfoHandle) => _executionManagerCore.IsFilterFunclet(codeInfoHandle); - public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfo(codeInfoHandle); - public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); - public TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => _executionManagerCore.GetDebugInfo(codeInfoHandle, out hasFlagByte); - public void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => _executionManagerCore.GetGCInfo(codeInfoHandle, out gcInfo, out gcVersion); - public TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetRelativeOffset(codeInfoHandle); - public bool IsGcSafe(TargetCodePointer instructionPointer) => _executionManagerCore.IsGcSafe(instructionPointer); - public uint GetStackParameterSize(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStackParameterSize(codeInfoHandle); - public List GetExceptionClauses(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetExceptionClauses(codeInfoHandle); - public JitManagerInfo GetEEJitManagerInfo() => _executionManagerCore.GetEEJitManagerInfo(); - public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); - public IReadOnlyList GetDynamicFunctionTableEntries(TargetPointer tableAddress) => _executionManagerCore.GetDynamicFunctionTableEntries(tableAddress); - public CodeKind GetCodeKind(TargetCodePointer codeAddress) => _executionManagerCore.GetCodeKind(codeAddress); - public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address); - public void Flush(FlushScope scope) => _executionManagerCore.Flush(scope); -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NibbleMapConstantLookup.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NibbleMapConstantLookup.cs index 0f9863b4f7664a..86147c433fc608 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NibbleMapConstantLookup.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NibbleMapConstantLookup.cs @@ -11,9 +11,8 @@ namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; // CoreCLR nibblemap with O(1) lookup time. // -// Implementation very similar to NibbleMapLinearLookup, but with the addition of writing relative pointers -// into the nibblemap whenever a code block completely covers a DWORD. This allows for O(1) lookup -// with the cost of O(n) write time. +// Relative pointers are written into the nibblemap whenever a code block completely covers a DWORD. +// This allows for O(1) lookup with the cost of O(n) write time. // // Pointers are encoded using the top 28 bits of the DWORD normally, the bottom 4 bits of the pointer // are reduced to 2 bits due to 4 byte code offset and encoded in bits 28 .. 31 of the DWORD with values diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NibbleMapLinearLookup.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NibbleMapLinearLookup.cs deleted file mode 100644 index 0e323b79125cb4..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/NibbleMapLinearLookup.cs +++ /dev/null @@ -1,159 +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.Numerics; -using System.Diagnostics; -using System; - -using static Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers.NibbleMapHelpers; - -namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; - -// Given a contiguous region of memory in which we lay out a collection of non-overlapping code blocks that are -// not too small (so that two adjacent ones aren't too close together) and where the start of each code block is aligned on some power of 2 and preceeded by a code header, -// we can break up the whole memory space into buckets of a fixed size (32-bytes in the current implementation), where -// each bucket either has a code block or not. -// Thinking of each code block address as a hex number, we can view it as: [index, offset] -// where each index gives us a bucket and the offset gives us the position of the header within the bucket. -// In the current implementation code must be 4 byte aligned therefore there are 8 possible offsets in a bucket. -// These are encoded as values 1-8 in the 4-bit nibble, with 0 reserved to mark the places in the map where a method doesn't start. -// -// To find the start of a method given an address we first convert it into a bucket index (giving the map unit) -// and an offset which we can then turn into the index of the nibble that covers that address. -// If the nibble is non-zero, we have the start of a method and it is near the given address. -// If the nibble is zero, we have to search backward first through the current map unit, and then through previous map -// units until we find a non-zero nibble. -// -// For example (all code addresses are relative to some unspecified base): -// Suppose there is code starting at address 304 (0x130) -// Then the map index will be 304 / 32 = 9 and the byte offset will be 304 % 32 = 16 -// Because addresses are 4-byte aligned, the nibble value will be 1 + 16 / 4 = 5 (we reserve 0 to mean no method). -// So the map unit containing index 9 will contain the value 0x5 << 24 (the map index 9 means we want the second nibble in the second map unit, and we number the nibbles starting from the most significant) -// Or 0x05000000 -// -// Now suppose we do a lookup for address 306 (0x132) -// The map index will be 306 / 32 = 9 and the byte offset will be 306 % 32 = 18 -// The nibble value will be 1 + 18 / 4 = 5 -// To do the lookup, we will load the map unit with index 9 (so the second 32-bit unit in the map) and get the value 0x05000000 -// We will then shift to focus on the nibble with map index 9 (which again has nibble shift 24), so -// the map unit will be 0x00000005 and we will get the nibble value 5. -// Therefore we know that there is a method start at map index 9, nibble value 5. -// The map index corresponds to an offset of 288 bytes and the nibble value 5 corresponds to an offset of (5 - 1) * 4 = 16 bytes -// So the method starts at offset 288 + 16 = 304, which is the address we were looking for. -// -// Now suppose we do a lookup for address 302 (0x12E) -// The map index will be 302 / 32 = 9 and the byte offset will be 302 % 32 = 14 -// The nibble value will be 1 + 14 / 4 = 4 -// To do the lookup, we will load the map unit containing map index 9 and get the value 0x05000000 -// We will then shift to focus on the nibble with map index 9 (which again has nibble shift 24), so we will get -// the nibble value 5. -// Therefore we know that there is a method start at map index 9, nibble value 5. -// But the address we're looking for is map index 9, nibble value 4. -// We know that methods can't start within 32-bytes of each other, so we know that the method we're looking for is not in the current nibble. -// We will then try to shift to the previous nibble in the map unit (0x00000005 >> 4 = 0x00000000) -// Therefore we know there is no method start at any map index in the current map unit. -// We will then align the map index to the start of the current map unit (map index 8) and move back to the previous map unit (map index 7) -// At that point, we scan backwards for non-zero map units. Since there are none, we return null. - -internal sealed class NibbleMapLinearLookup : INibbleMap -{ - private readonly Target _target; - - private NibbleMapLinearLookup(Target target) - { - _target = target; - } - - internal TargetPointer FindMethodCode(TargetPointer mapBase, TargetPointer mapStart, TargetCodePointer currentPC) - { - TargetNUInt relativeAddress = new TargetNUInt(currentPC.Value - mapBase.Value); - DecomposeAddress(relativeAddress, out MapKey mapIdx, out Nibble bucketByteIndex); - - MapUnit t = mapIdx.ReadMapUnit(_target, mapStart); - - // shift the nibble we want to the least significant position - t = t.FocusOnIndexedNibble(mapIdx); - - // if the nibble is non-zero, we have found the start of a method, - // but we need to check that the start is before the current address, not after - if (!t.Nibble.IsEmpty && t.Nibble.Value <= bucketByteIndex.Value) - { - return GetAbsoluteAddress(mapBase, mapIdx, t.Nibble); - } - - // search backwards through the current map unit - // we processed the lsb nibble, move to the next one - t = t.ShiftNextNibble; - - // if there's any nibble set in the current unit, find it - if (!t.IsEmpty) - { - mapIdx = mapIdx.Prev; - while (t.Nibble.IsEmpty) - { - t = t.ShiftNextNibble; - mapIdx = mapIdx.Prev; - } - return GetAbsoluteAddress(mapBase, mapIdx, t.Nibble); - } - - // We finished the current map unit, we want to move to the previous one. - // But if we were in the first map unit, we can stop - if (mapIdx.InFirstMapUnit) - { - return TargetPointer.Null; - } - - // We're now done with the current map unit. - // Align the map index to the current map unit, then move back one nibble into the previous map unit - mapIdx = mapIdx.AlignDownToMapUnit(); - mapIdx = mapIdx.Prev; - - // read the map unit containing mapIdx and skip over it if it is all zeros - while (true) - { - t = mapIdx.ReadMapUnit(_target, mapStart); - if (!t.IsEmpty) - break; - if (mapIdx.InFirstMapUnit) - { - // we're at the first map unit and all the bits in the map unit are zero, - // there is no code header to find - return TargetPointer.Null; - } - mapIdx = mapIdx.PrevMapUnit; - } - - Debug.Assert(!t.IsEmpty); - - // move to the correct nibble in the map unit - while (!mapIdx.IsZero && t.Nibble.IsEmpty) - { - t = t.ShiftNextNibble; - mapIdx = mapIdx.Prev; - } - - if (mapIdx.IsZero && t.IsEmpty) - { - return TargetPointer.Null; - } - - return GetAbsoluteAddress(mapBase, mapIdx, t.Nibble); - } - - public static INibbleMap Create(Target target) - { - return new NibbleMapLinearLookup(target); - } - - public TargetPointer FindMethodCode(Data.CodeHeapListNode heapListNode, TargetCodePointer jittedCodeAddress) - { - if (jittedCodeAddress < heapListNode.StartAddress || jittedCodeAddress > heapListNode.EndAddress) - { - return TargetPointer.Null; - } - - return FindMethodCode(heapListNode.MapBase, heapListNode.HeaderMap, jittedCodeAddress); - } - -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs deleted file mode 100644 index 5a74943702c2e7..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs +++ /dev/null @@ -1,116 +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.Diagnostics; -using Microsoft.Diagnostics.DataContractReader.Data; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -internal struct PrecodeStubs_1_Impl : IPrecodeStubsContractCommonApi -{ - public static TargetPointer StubPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - TargetPointer stubPrecodeDataAddress = instrPointer + precodeMachineDescriptor.StubCodePageSize; - Data.StubPrecodeData_1 stubPrecodeData = target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); - return stubPrecodeData.MethodDesc; - } - - public static TargetPointer FixupPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - TargetPointer fixupPrecodeDataAddress = instrPointer + precodeMachineDescriptor.StubCodePageSize; - Data.FixupPrecodeData fixupPrecodeData = target.ProcessedData.GetOrAdd(fixupPrecodeDataAddress); - return fixupPrecodeData.MethodDesc; - } - - public static TargetPointer ThisPtrRetBufPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - throw new NotImplementedException(); // TODO(cdac) - } - - public static TargetPointer InterpreterPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - throw new NotImplementedException(); - } - - public static byte StubPrecodeData_GetType(Data.StubPrecodeData_1 stubPrecodeData) - { - return stubPrecodeData.Type; - } - - internal static byte ReadPrecodeType(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - if (precodeMachineDescriptor.ReadWidthOfPrecodeType!.Value == 1) - { - byte precodeType = target.Read(instrPointer + precodeMachineDescriptor.OffsetOfPrecodeType!.Value); - return (byte)(precodeType >> precodeMachineDescriptor.ShiftOfPrecodeType!.Value); - } - else if (precodeMachineDescriptor.ReadWidthOfPrecodeType!.Value == 2) - { - ushort precodeType = target.Read(instrPointer + precodeMachineDescriptor.OffsetOfPrecodeType!.Value); - return (byte)(precodeType >> precodeMachineDescriptor.ShiftOfPrecodeType!.Value); - } - else - { - throw new InvalidOperationException($"Invalid precode type width {precodeMachineDescriptor.ReadWidthOfPrecodeType}"); - } - } - - public static KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - return TryGetKnownPrecodeType_Impl(instrPointer, target, precodeMachineDescriptor); - } - - public static KnownPrecodeType? TryGetKnownPrecodeType_Impl(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) where TPrecodeStubsImplementation : IPrecodeStubsContractCommonApi where TStubPrecodeData : IData - { - // We get the precode type in two phases: - // 1. Read the precode type from the intruction address. - // 2. If it's "stub", look at the stub data and get the actual precode type - it could be stub, - // but it could also be a pinvoke precode or a ThisPtrRetBufPrecode - // precode.h Precode::GetType() - byte approxPrecodeType = ReadPrecodeType(instrPointer, target, precodeMachineDescriptor); - byte exactPrecodeType; - if (approxPrecodeType == precodeMachineDescriptor.StubPrecodeType) - { - // get the actual type from the StubPrecodeData - TStubPrecodeData stubPrecodeData = GetStubPrecodeData(instrPointer, target, precodeMachineDescriptor); - exactPrecodeType = TPrecodeStubsImplementation.StubPrecodeData_GetType(stubPrecodeData); - } - else - { - exactPrecodeType = approxPrecodeType; - } - - if (exactPrecodeType == precodeMachineDescriptor.StubPrecodeType) - { - return KnownPrecodeType.Stub; - } - else if (precodeMachineDescriptor.PInvokeImportPrecodeType is byte ndType && exactPrecodeType == ndType) - { - return KnownPrecodeType.PInvokeImport; - } - else if (precodeMachineDescriptor.FixupPrecodeType is byte fixupType && exactPrecodeType == fixupType) - { - return KnownPrecodeType.Fixup; - } - else if (precodeMachineDescriptor.ThisPointerRetBufPrecodeType is byte thisPtrRetBufType && exactPrecodeType == thisPtrRetBufType) - { - return KnownPrecodeType.ThisPtrRetBuf; - } - else - { - return null; - } - - static TStubPrecodeData GetStubPrecodeData(TargetPointer stubInstrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - TargetPointer stubPrecodeDataAddress = stubInstrPointer + precodeMachineDescriptor.StubCodePageSize; - return target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); - } - } -} - -internal sealed class PrecodeStubs_1 : PrecodeStubsCommon -{ - public PrecodeStubs_1(Target target) : base(target) { } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_2.cs deleted file mode 100644 index 5353abeea974f1..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_2.cs +++ /dev/null @@ -1,52 +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.Diagnostics; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -internal struct PrecodeStubs_2_Impl : IPrecodeStubsContractCommonApi -{ - public static TargetPointer StubPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - TargetPointer stubPrecodeDataAddress = instrPointer + precodeMachineDescriptor.StubCodePageSize; - Data.StubPrecodeData_2 stubPrecodeData = target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); - return stubPrecodeData.SecretParam; - } - - public static TargetPointer FixupPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - // Version 2 of this contract behaves just like version 1 - return PrecodeStubs_1_Impl.FixupPrecode_GetMethodDesc(instrPointer, target, precodeMachineDescriptor); - } - - public static TargetPointer ThisPtrRetBufPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - TargetPointer stubPrecodeDataAddress = instrPointer + precodeMachineDescriptor.StubCodePageSize; - Data.StubPrecodeData_2 stubPrecodeData = target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); - Data.ThisPtrRetBufPrecodeData thisPtrRetBufPrecodeData = target.ProcessedData.GetOrAdd(stubPrecodeData.SecretParam); - return thisPtrRetBufPrecodeData.MethodDesc; - } - - public static TargetPointer InterpreterPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - throw new NotImplementedException(); - } - - public static byte StubPrecodeData_GetType(Data.StubPrecodeData_2 stubPrecodeData) - { - return stubPrecodeData.Type; - } - - public static KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) - { - // Version 2 of this contract behaves just like version 1 other than the details that are abstracted away through the IPrecodeStubsContractCommonApi interface - return PrecodeStubs_1_Impl.TryGetKnownPrecodeType_Impl(instrPointer, target, precodeMachineDescriptor); - } -} - -internal sealed class PrecodeStubs_2 : PrecodeStubsCommon -{ - public PrecodeStubs_2(Target target) : base(target) { } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_3.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_3.cs index da8c550ef31a94..460e7299629926 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_3.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_3.cs @@ -6,24 +6,28 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal struct PrecodeStubs_3_Impl : IPrecodeStubsContractCommonApi +internal struct PrecodeStubs_3_Impl : IPrecodeStubsContractCommonApi { public static TargetPointer StubPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) { - // Version 3 of this contract behaves just like version 2 - return PrecodeStubs_2_Impl.StubPrecode_GetMethodDesc(instrPointer, target, precodeMachineDescriptor); + TargetPointer stubPrecodeDataAddress = instrPointer + precodeMachineDescriptor.StubCodePageSize; + Data.StubPrecodeData_2 stubPrecodeData = target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); + return stubPrecodeData.SecretParam; } public static TargetPointer FixupPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) { - // Version 3 of this contract behaves just like version 1 - return PrecodeStubs_1_Impl.FixupPrecode_GetMethodDesc(instrPointer, target, precodeMachineDescriptor); + TargetPointer fixupPrecodeDataAddress = instrPointer + precodeMachineDescriptor.StubCodePageSize; + Data.FixupPrecodeData fixupPrecodeData = target.ProcessedData.GetOrAdd(fixupPrecodeDataAddress); + return fixupPrecodeData.MethodDesc; } public static TargetPointer ThisPtrRetBufPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) { - // Version 3 of this contract behaves just like version 2 - return PrecodeStubs_2_Impl.ThisPtrRetBufPrecode_GetMethodDesc(instrPointer, target, precodeMachineDescriptor); + TargetPointer stubPrecodeDataAddress = instrPointer + precodeMachineDescriptor.StubCodePageSize; + Data.StubPrecodeData_2 stubPrecodeData = target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); + Data.ThisPtrRetBufPrecodeData thisPtrRetBufPrecodeData = target.ProcessedData.GetOrAdd(stubPrecodeData.SecretParam); + return thisPtrRetBufPrecodeData.MethodDesc; } public static TargetPointer InterpreterPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) @@ -36,12 +40,6 @@ public static TargetPointer InterpreterPrecode_GetMethodDesc(TargetPointer instr return interpMethod.MethodDesc; } - public static byte StubPrecodeData_GetType(Data.StubPrecodeData_2 stubPrecodeData) - { - // Version 3 of this contract behaves just like version 2 - return PrecodeStubs_2_Impl.StubPrecodeData_GetType(stubPrecodeData); - } - private static Data.StubPrecodeData_2 GetStubPrecodeData(TargetPointer stubInstrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) { TargetPointer stubPrecodeDataAddress = stubInstrPointer + precodeMachineDescriptor.StubCodePageSize; @@ -108,7 +106,7 @@ static bool ReadBytesAndCompare(TargetPointer instrAddress, byte[] expectedByteP } } -internal sealed class PrecodeStubs_3 : PrecodeStubsCommon +internal sealed class PrecodeStubs_3 : PrecodeStubsCommon { public PrecodeStubs_3(Target target) : base(target) { } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs index 260d0597f8d8c8..b80cf28877df7e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_Common.cs @@ -19,17 +19,16 @@ internal enum KnownPrecodeType } // Interface used to abstract behavior which may be different between multiple versions of the precode stub implementations -internal interface IPrecodeStubsContractCommonApi +internal interface IPrecodeStubsContractCommonApi { public static abstract TargetPointer StubPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor); public static abstract TargetPointer ThisPtrRetBufPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor); public static abstract TargetPointer FixupPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor); public static abstract TargetPointer InterpreterPrecode_GetMethodDesc(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor); - public static abstract byte StubPrecodeData_GetType(TStubPrecodeData stubPrecodeData); public static abstract KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrPointer, Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor); } -internal class PrecodeStubsCommon : IPrecodeStubs where TPrecodeStubsImplementation : IPrecodeStubsContractCommonApi where TStubPrecodeData : IData +internal class PrecodeStubsCommon : IPrecodeStubs where TPrecodeStubsImplementation : IPrecodeStubsContractCommonApi { private readonly Target _target; private readonly CodePointerFlags _codePointerFlags; @@ -97,12 +96,6 @@ internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachine private bool IsAlignedInstrPointer(TargetPointer instrPointer) => _target.IsAlignedToPointerSize(instrPointer); - private TStubPrecodeData GetStubPrecodeData(TargetPointer stubInstrPointer) - { - TargetPointer stubPrecodeDataAddress = stubInstrPointer + MachineDescriptor.StubCodePageSize; - return _target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); - } - private KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrAddress) { return TPrecodeStubsImplementation.TryGetKnownPrecodeType(instrAddress, _target, MachineDescriptor); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index 0b0173b118cf7f..e072753bf9c0fe 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -34,7 +34,6 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new AuxiliarySymbols_1(t)); registry.Register("c1", static t => new Debugger_1(t)); - registry.Register("c1", static t => new DebugInfo_1(t)); registry.Register("c2", static t => new DebugInfo_2(t)); registry.Register("c1", static t => new StressLog_1(t)); registry.Register("c2", static t => new StressLog_2(t)); @@ -50,8 +49,6 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new FeatureFlags_1(t)); - registry.Register("c1", static t => new PrecodeStubs_1(t)); - registry.Register("c2", static t => new PrecodeStubs_2(t)); registry.Register("c3", static t => new PrecodeStubs_3(t)); registry.Register("c1", static t => new ReJIT_1(t)); @@ -75,7 +72,6 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new SyncBlock_1(t)); - registry.Register("c1", static t => new ExecutionManager_1(t)); registry.Register("c2", static t => new ExecutionManager_2(t)); registry.Register("c1", static t => new RuntimeMutableTypeSystem_1(t)); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs index e2d2f9473d7d39..50ee774559cba7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs @@ -9,42 +9,22 @@ internal sealed partial class PrecodeMachineDescriptor : IData MaybeGetByte(target, address, nameof(OffsetOfPrecodeType)); - - [DataDescriptorDependency(nameof(ReadWidthOfPrecodeType), "uint8")] - private partial byte? InitReadWidthOfPrecodeType(Target target, TargetPointer address) - => MaybeGetByte(target, address, nameof(ReadWidthOfPrecodeType)); - - [DataDescriptorDependency(nameof(ShiftOfPrecodeType), "uint8")] - private partial byte? InitShiftOfPrecodeType(Target target, TargetPointer address) - => MaybeGetByte(target, address, nameof(ShiftOfPrecodeType)); + [CustomInit(nameof(InitInterpreterPrecodeType))] public partial byte? InterpreterPrecodeType { get; } + [CustomInit(nameof(InitUMEntryPrecodeType))] public partial byte? UMEntryPrecodeType { get; } + [CustomInit(nameof(InitDynamicHelperPrecodeType))] public partial byte? DynamicHelperPrecodeType { get; } + [CustomInit(nameof(InitFixupStubPrecodeSize))] public partial byte? FixupStubPrecodeSize { get; } + [CustomInit(nameof(InitFixupBytes))] public partial byte[]? FixupBytes { get; } + [CustomInit(nameof(InitFixupIgnoredBytes))] public partial byte[]? FixupIgnoredBytes { get; } + [CustomInit(nameof(InitStubPrecodeSize))] public partial byte? StubPrecodeSize { get; } + [CustomInit(nameof(InitStubBytes))] public partial byte[]? StubBytes { get; } + [CustomInit(nameof(InitStubIgnoredBytes))] public partial byte[]? StubIgnoredBytes { get; } [DataDescriptorDependency(nameof(PInvokeImportPrecodeType), "uint8")] private partial byte? InitPInvokeImportPrecodeType(Target target, TargetPointer address) => MaybeGetByte(target, address, nameof(PInvokeImportPrecodeType)); - [DataDescriptorDependency(nameof(FixupPrecodeType), "uint8")] - private partial byte? InitFixupPrecodeType(Target target, TargetPointer address) - => MaybeGetByte(target, address, nameof(FixupPrecodeType)); - [DataDescriptorDependency(nameof(ThisPointerRetBufPrecodeType), "uint8")] private partial byte? InitThisPointerRetBufPrecodeType(Target target, TargetPointer address) => MaybeGetByte(target, address, nameof(ThisPointerRetBufPrecodeType)); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StubPrecodeData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StubPrecodeData.cs index 54ec2cb54d6831..e0b5208cce93a0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StubPrecodeData.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StubPrecodeData.cs @@ -3,13 +3,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; -[CdacType(nameof(DataType.StubPrecodeData))] -internal sealed partial class StubPrecodeData_1 : IData -{ - [Field] public partial TargetPointer MethodDesc { get; } - [Field] public partial byte Type { get; } -} - [CdacType(nameof(DataType.StubPrecodeData))] internal sealed partial class StubPrecodeData_2 : IData { diff --git a/src/native/managed/cdac/tests/UnitTests/ContractDescriptor/TargetTests.SubDescriptors.cs b/src/native/managed/cdac/tests/UnitTests/ContractDescriptor/TargetTests.SubDescriptors.cs index f64280ef7df2f6..2369a813efc3b7 100644 --- a/src/native/managed/cdac/tests/UnitTests/ContractDescriptor/TargetTests.SubDescriptors.cs +++ b/src/native/managed/cdac/tests/UnitTests/ContractDescriptor/TargetTests.SubDescriptors.cs @@ -266,8 +266,8 @@ private static ContractDescriptorTarget CreatePendingGCSubDescriptorTarget( // IGC, each advertised at the version CoreCLRContracts registers. private static Dictionary RequiredContractsWithoutGC() => s_requiredDataAccessContracts - .Where(static c => c != "GC") - .ToDictionary(static c => c, static c => "c1"); + .Where(static pair => pair.Key != "GC") + .ToDictionary(static pair => pair.Key, static pair => pair.Value); [Theory] [ClassData(typeof(MockTarget.StdArch))] diff --git a/src/native/managed/cdac/tests/UnitTests/ContractDescriptor/TargetTests.cs b/src/native/managed/cdac/tests/UnitTests/ContractDescriptor/TargetTests.cs index e498303a476161..5bd2439b1e15ce 100644 --- a/src/native/managed/cdac/tests/UnitTests/ContractDescriptor/TargetTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ContractDescriptor/TargetTests.cs @@ -368,14 +368,40 @@ public void TryGetContract_UnrecognizedVersion_ReturnsContractUnrecognizedExcept // The contracts required by the data-access interfaces, advertised at the versions // CoreCLRContracts registers. Mirrors CoreCLRContracts.ValidateForDataAccess. - private static readonly string[] s_requiredDataAccessContracts = - [ - "AuxiliarySymbols", "BuiltInCOM", "CodeNotifications", "CodeVersions", "ComWrappers", - "ConditionalWeakTable", "DacStreams", "Debugger", "DebugInfo", "EcmaMetadata", "Exception", - "ExecutionManager", "FeatureFlags", "GC", "GCInfo", "Loader", "Notifications", "Object", - "PlatformMetadata", "PrecodeStubs", "ReJIT", "RuntimeInfo", "RuntimeMutableTypeSystem", - "RuntimeTypeSystem", "SHash", "Signature", "StackWalk", "StressLog", "SyncBlock", "Thread", - ]; + private static readonly IReadOnlyDictionary s_requiredDataAccessContracts = + new Dictionary + { + ["AuxiliarySymbols"] = "c1", + ["BuiltInCOM"] = "c1", + ["CodeNotifications"] = "c1", + ["CodeVersions"] = "c1", + ["ComWrappers"] = "c1", + ["ConditionalWeakTable"] = "c1", + ["DacStreams"] = "c1", + ["Debugger"] = "c1", + ["DebugInfo"] = "c2", + ["EcmaMetadata"] = "c1", + ["Exception"] = "c1", + ["ExecutionManager"] = "c2", + ["FeatureFlags"] = "c1", + ["GC"] = "c1", + ["GCInfo"] = "c1", + ["Loader"] = "c1", + ["Notifications"] = "c1", + ["Object"] = "c1", + ["PlatformMetadata"] = "c1", + ["PrecodeStubs"] = "c3", + ["ReJIT"] = "c1", + ["RuntimeInfo"] = "c1", + ["RuntimeMutableTypeSystem"] = "c1", + ["RuntimeTypeSystem"] = "c1", + ["SHash"] = "c1", + ["Signature"] = "c1", + ["StackWalk"] = "c1", + ["StressLog"] = "c2", + ["SyncBlock"] = "c1", + ["Thread"] = "c1", + }; // A string-valued "OperatingSystem" contract-descriptor global, used to drive the target // platform that ValidateForDataAccess reads when deciding which OS-specific contracts to require. @@ -454,7 +480,10 @@ public void ValidateForDataAccess_MissingRequiredContract_ThrowsNotAdvertised(Mo TargetTestHelpers targetTestHelpers = new(arch); ContractDescriptorBuilder builder = new(targetTestHelpers); ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); - descriptorBuilder.SetContracts(s_requiredDataAccessContracts.Where(static c => c != "RuntimeInfo").ToArray()); + descriptorBuilder.SetContracts( + s_requiredDataAccessContracts + .Where(static pair => pair.Key != "RuntimeInfo") + .ToDictionary(static pair => pair.Key, static pair => pair.Value)); Assert.True(builder.TryCreateTarget(descriptorBuilder, out ContractDescriptorTarget? target)); @@ -475,7 +504,10 @@ public void ValidateForDataAccess_MissingTransitiveContract_ThrowsNotAdvertised( TargetTestHelpers targetTestHelpers = new(arch); ContractDescriptorBuilder builder = new(targetTestHelpers); ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); - descriptorBuilder.SetContracts(s_requiredDataAccessContracts.Where(c => c != missingContract).ToArray()); + descriptorBuilder.SetContracts( + s_requiredDataAccessContracts + .Where(pair => pair.Key != missingContract) + .ToDictionary(static pair => pair.Key, static pair => pair.Value)); Assert.True(builder.TryCreateTarget(descriptorBuilder, out ContractDescriptorTarget? target)); @@ -493,7 +525,7 @@ public void ValidateForDataAccess_UnrecognizedRequiredVersion_ThrowsUnrecognized TargetTestHelpers targetTestHelpers = new(arch); ContractDescriptorBuilder builder = new(targetTestHelpers); ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); - Dictionary contracts = s_requiredDataAccessContracts.ToDictionary(static c => c, static _ => "c1"); + Dictionary contracts = new(s_requiredDataAccessContracts); contracts["RuntimeInfo"] = "version-from-the-future"; descriptorBuilder.SetContracts(contracts); @@ -513,7 +545,7 @@ public void ValidateForDataAccess_DeprecatedContractReference_ThrowsUnsupported( TargetTestHelpers targetTestHelpers = new(arch); ContractDescriptorBuilder builder = new(targetTestHelpers); ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); - Dictionary contracts = s_requiredDataAccessContracts.ToDictionary(static c => c, static _ => "c1"); + Dictionary contracts = new(s_requiredDataAccessContracts); contracts["RuntimeInfo"] = "deprecated-version"; descriptorBuilder.SetContracts(contracts); @@ -562,8 +594,12 @@ public void ValidateForDataAccess_WindowsTargetWithWindowsErrorReporting_DoesNot ContractDescriptorBuilder builder = new(targetTestHelpers); ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); + Dictionary contracts = new(s_requiredDataAccessContracts) + { + ["WindowsErrorReporting"] = "c1", + }; descriptorBuilder - .SetContracts([.. s_requiredDataAccessContracts, "WindowsErrorReporting"]) + .SetContracts(contracts) .SetGlobals(s_windowsOperatingSystemGlobal); Assert.True(builder.TryCreateTarget(descriptorBuilder, out ContractDescriptorTarget? target)); diff --git a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs index 2b61bfd9a789ed..933835d5441caa 100644 --- a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs @@ -334,7 +334,6 @@ public void GetMethodDesc_R2R_OneRuntimeFunction(string version, MockTarget.Arch // (32-bit little-endian) target, treating the code address as a virtual IP, and confirms the // R2R classification. [Theory] - [InlineData("c1")] [InlineData("c2")] public void GetMethodDesc_R2R_WasmVirtualIP(string version) { @@ -914,14 +913,10 @@ public void GetStubKind_R2R_OutsideThunkRange(string version, MockTarget.Archite public static IEnumerable StdArchAllVersions() { - const int highestVersion = 2; foreach (object[] arr in new MockTarget.StdArch()) { MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; - for (int version = 1; version <= highestVersion; version++) - { - yield return new object[] { $"c{version}", arch }; - } + yield return new object[] { "c2", arch }; } } @@ -1180,7 +1175,7 @@ public void GetDynamicFunctionTableEntries_UnsupportedPlatform_ReturnsEmpty( RuntimeInfoArchitecture architecture) { MockTarget.Architecture targetArchitecture = new() { IsLittleEndian = true, Is64Bit = true }; - MockExecutionManagerBuilder emBuilder = new("c1", targetArchitecture, MockExecutionManagerBuilder.DefaultAllocationRange); + MockExecutionManagerBuilder emBuilder = new("c2", targetArchitecture, MockExecutionManagerBuilder.DefaultAllocationRange); Target target = CreateTarget(emBuilder, operatingSystem, architecture); IReadOnlyList entries = diff --git a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/NibbleMapTestBuilder.cs b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/NibbleMapTestBuilder.cs index e1d5625783316e..1d5da64b04a510 100644 --- a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/NibbleMapTestBuilder.cs +++ b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/NibbleMapTestBuilder.cs @@ -73,48 +73,6 @@ public NibbleMapTestBuilderBase(TargetPointer mapBase, ulong mapRangeSize, MockM public abstract void AllocateCodeChunk(TargetCodePointer codeStart, uint codeSize); } -internal class NibbleMapTestBuilder_1 : NibbleMapTestBuilderBase -{ - public NibbleMapTestBuilder_1(TargetPointer mapBase, ulong mapRangeSize, TargetPointer mapStart, MockTarget.Architecture arch) - : base(mapBase, mapRangeSize, mapStart, arch) - { - } - - public NibbleMapTestBuilder_1(TargetPointer mapBase, ulong mapRangeSize, MockMemorySpace.BumpAllocator allocator, MockTarget.Architecture arch) - : base(mapBase, mapRangeSize, allocator, arch) - { - } - - public override void AllocateCodeChunk(TargetCodePointer codeStart, uint codeSize) - { - // paraphrased from EEJitManager::NibbleMapSetUnlocked - if (codeStart.Value < MapBase.Value) - { - throw new ArgumentException("Code start address is below the map base"); - } - ulong delta = codeStart.Value - MapBase.Value; - ulong pos = Addr2Pos(delta); - bool bSet = true; - uint value = bSet?Addr2Offs(delta):0; - - uint index = (uint) (pos >>> Log2NibblesPerDword); - uint mask = ~(HighestNibbleMask >>> (int)((pos & NibblesPerDwordMask) << Log2NibbleSize)); - - value = value << Pos2ShiftCount(pos); - - Span entry = NibbleMapFragment.Data.AsSpan((int)(index * sizeof(uint)), sizeof(uint)); - uint oldValue = TestPlaceholderTarget.ReadFromSpan(entry, Arch.IsLittleEndian); - - if (value != 0 && (oldValue & ~mask) != 0) - { - throw new InvalidOperationException("Overwriting existing offset"); - } - - uint newValue = (oldValue & mask) | value; - TestPlaceholderTarget.WriteToSpan(newValue, Arch.IsLittleEndian, entry); - } -} - internal class NibbleMapTestBuilder_2 : NibbleMapTestBuilderBase { public NibbleMapTestBuilder_2(TargetPointer mapBase, ulong mapRangeSize, TargetPointer mapStart, MockTarget.Architecture arch) diff --git a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/NibbleMapTests.cs b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/NibbleMapTests.cs index 4d570045ce7d5f..c067c3f7b90306 100644 --- a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/NibbleMapTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/NibbleMapTests.cs @@ -19,7 +19,7 @@ internal static Target CreateTarget(NibbleMapTestBuilderBase nibbleMapTestBuilde } } -public class NibbleMapLinearLookupTests : NibbleMapTestsBase +public class NibbleMapHelpersTests { [Fact] public void RoundTripAddressTest() @@ -59,62 +59,6 @@ public void ExhaustiveNibbbleShifts(ulong irrelevant) } } - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void NibbleMapOneItemLookupOk(MockTarget.Architecture arch) - { - // SETUP: - - // this is the beginning of the address range where code pointers might point - TargetPointer mapBase = new(0x5f5f_0000u); - // this is the beginning of the nibble map itself - TargetPointer mapStart = new(0x0456_1000u); - /// this is how big the address space is that the map covers - const uint MapRangeSize = 0x1000; - TargetPointer MapEnd = mapBase + MapRangeSize; - var builder = new NibbleMapTestBuilder_1(mapBase, MapRangeSize, mapStart, arch); - - // don't put the code too close to the start - the NibbleMap bails if the code is too close to the start of the range - TargetCodePointer inputPC = new(mapBase + 0x0200u); - uint codeSize = 0x80; // doesn't matter - builder.AllocateCodeChunk (inputPC, codeSize); - Target target = CreateTarget(builder); - - // TESTCASE: - - NibbleMapLinearLookup map = (NibbleMapLinearLookup)NibbleMapLinearLookup.Create(target); - Assert.NotNull(map); - - TargetPointer methodCode = map.FindMethodCode(mapBase, mapStart, inputPC); - Assert.Equal(inputPC.Value, methodCode.Value); - - // All addresses in the code chunk should map to the same method - for (int i = 0; i < codeSize; i++) - { - methodCode = map.FindMethodCode(mapBase, mapStart, inputPC.Value + (uint)i); - // we should always find the beginning of the method - Assert.Equal(inputPC.Value, methodCode.Value); - } - - // All addresses before the code chunk should return null - for (ulong i = mapBase; i < inputPC; i++) - { - methodCode = map.FindMethodCode(mapBase, mapStart, i); - Assert.Equal(0u, methodCode.Value); - } - - methodCode = map.FindMethodCode(mapBase, mapStart, inputPC.Value + 0x100u); - Assert.Equal(inputPC.Value, methodCode.Value); - - // interestingly, all addresses after the code chunk should also return the beginning of the method - // we don't track how long the method is, so we can't tell if we're past the end - for (TargetCodePointer ptr = inputPC + (uint)codeSize; ptr < MapEnd; ptr++) - { - methodCode = map.FindMethodCode(mapBase, mapStart, ptr); - Assert.Equal(inputPC.Value, methodCode); - } - - } } public class NibbleMapConstantLookupTests : NibbleMapTestsBase diff --git a/src/native/managed/cdac/tests/UnitTests/FunctionTableAccessTests.cs b/src/native/managed/cdac/tests/UnitTests/FunctionTableAccessTests.cs index 3de95c2c8ad9dc..2f5273cf3fe05a 100644 --- a/src/native/managed/cdac/tests/UnitTests/FunctionTableAccessTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/FunctionTableAccessTests.cs @@ -16,14 +16,10 @@ public unsafe class FunctionTableAccessTests { public static IEnumerable StdArchAllVersions() { - const int highestVersion = 2; foreach (object[] arr in new MockTarget.StdArch()) { MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; - for (int version = 1; version <= highestVersion; version++) - { - yield return new object[] { $"c{version}", arch }; - } + yield return new object[] { "c2", arch }; } } diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.ExecutionManager.cs index ceef50377468c8..1ee00dbe1725b2 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -747,7 +747,6 @@ internal NibbleMapTestBuilderBase CreateNibbleMap(ulong codeRangeStart, uint cod { NibbleMapTestBuilderBase nibBuilder = Version switch { - "c1" => new NibbleMapTestBuilder_1(codeRangeStart, codeRangeSize, _nibbleMapAllocator, Builder.TargetTestHelpers.Arch), "c2" => new NibbleMapTestBuilder_2(codeRangeStart, codeRangeSize, _nibbleMapAllocator, Builder.TargetTestHelpers.Arch), _ => throw new InvalidOperationException($"Unknown version '{Version}'"), }; diff --git a/src/native/managed/cdac/tests/UnitTests/PrecodeStubsTests.cs b/src/native/managed/cdac/tests/UnitTests/PrecodeStubsTests.cs index c24273d5bf9422..d6d6d5e8edd348 100644 --- a/src/native/managed/cdac/tests/UnitTests/PrecodeStubsTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/PrecodeStubsTests.cs @@ -19,9 +19,6 @@ public class PrecodeTestDescriptor { public string Name { get; } public required MockTarget.Architecture Arch { get; init; } public bool IsThumb { get; init; } - public required int ReadWidthOfPrecodeType { get; init; } - public required int OffsetOfPrecodeType { get; init; } - public required int ShiftOfPrecodeType { get; init; } // #if defined(TARGET_ARM64) && defined(TARGET_UNIX) // return max(16*1024u, minipal_getpagesize()); // #elif defined(TARGET_ARM) @@ -57,41 +54,16 @@ public PrecodeTestDescriptor(string name) { Name = name; } - internal void WritePrecodeType(int precodeType,TargetTestHelpers targetTestHelpers, Span dest) - { - if (ReadWidthOfPrecodeType == 1) - { - byte value = (byte)(((byte)precodeType & 0xff) << ShiftOfPrecodeType); - // TODO: fill in the other bits with something - targetTestHelpers.Write(dest.Slice(OffsetOfPrecodeType, 1), value); - } - else if (ReadWidthOfPrecodeType == 2) - { - ushort value = (ushort)(((ushort)precodeType & 0xff) << ShiftOfPrecodeType); - // TODO: fill in the other bits with something - targetTestHelpers.Write(dest.Slice(OffsetOfPrecodeType, 2), value); - } - else - { - throw new InvalidOperationException("Don't know how to write a precode type of width {ReadWidthOfPrecodeType}"); - } - } } internal static PrecodeTestDescriptor X64TestDescriptor = new PrecodeTestDescriptor("X64") { Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, - ReadWidthOfPrecodeType = 1, - ShiftOfPrecodeType = 0, - OffsetOfPrecodeType = 0, StubCodePageSize = 0x4000u, // 16KiB StubPrecode = 0x4c, StubPrecodeSize = 24, }; internal static PrecodeTestDescriptor Arm64TestDescriptor = new PrecodeTestDescriptor("Arm64") { Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, - ReadWidthOfPrecodeType = 1, - ShiftOfPrecodeType = 0, - OffsetOfPrecodeType = 0, StubCodePageSize = 0x4000u, // 16KiB StubPrecode = 0x4a, StubPrecodeSize = 24, @@ -99,9 +71,6 @@ internal void WritePrecodeType(int precodeType,TargetTestHelpers targetTestHelpe }; internal static PrecodeTestDescriptor LoongArch64TestDescriptor = new PrecodeTestDescriptor("LoongArch64") { Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, - ReadWidthOfPrecodeType = 2, - ShiftOfPrecodeType = 5, - OffsetOfPrecodeType = 0, StubCodePageSize = 0x4000u, // 16KiB StubPrecode = 0x4, StubPrecodeSize = 24, @@ -110,9 +79,6 @@ internal void WritePrecodeType(int precodeType,TargetTestHelpers targetTestHelpe internal static PrecodeTestDescriptor Arm32Thumb = new PrecodeTestDescriptor("Arm32Thumb") { Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = false }, IsThumb = true, - ReadWidthOfPrecodeType = 1, - ShiftOfPrecodeType = 0, - OffsetOfPrecodeType = 7, StubCodePageSize = 0x1000u, // 4KiB StubPrecode = 0xff, StubPrecodeSize = 12, @@ -120,9 +86,6 @@ internal void WritePrecodeType(int precodeType,TargetTestHelpers targetTestHelpe internal static PrecodeTestDescriptor RiscV64TestDescriptor = new PrecodeTestDescriptor("RiscV64") { Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, - ReadWidthOfPrecodeType = 1, - ShiftOfPrecodeType = 0, - OffsetOfPrecodeType = 0, StubCodePageSize = 0x4000u, // 16KiB StubPrecode = 0x17, StubPrecodeSize = 24, @@ -142,27 +105,18 @@ public static IEnumerable PrecodeTestDescriptorData() // FIXME: maybe make these a little more exotic yield return new object[] { new PrecodeTestDescriptor("Fake 32-bit LE") { Arch = arch32le, - ReadWidthOfPrecodeType = 1, - ShiftOfPrecodeType = 0, - OffsetOfPrecodeType = 0, StubCodePageSize = 0x4000u, // 16KiB StubPrecode = 0xa1, StubPrecodeSize = 24, }}; yield return new object[] { new PrecodeTestDescriptor("Fake 32-bit BE") { Arch = arch32be, - ReadWidthOfPrecodeType = 1, - ShiftOfPrecodeType = 0, - OffsetOfPrecodeType = 0, StubCodePageSize = 0x4000u, // 16KiB StubPrecode = 0xa1, StubPrecodeSize = 24, }}; yield return new object[] { new PrecodeTestDescriptor("Fake 64-bit BE") { Arch = arch64be, - ReadWidthOfPrecodeType = 1, - ShiftOfPrecodeType = 0, - OffsetOfPrecodeType = 0, StubCodePageSize = 0x4000u, // 16KiB StubPrecode = 0xa1, StubPrecodeSize = 24, @@ -173,19 +127,7 @@ public static IEnumerable PrecodeTestDescriptorDataWithContractVersion { foreach (var data in PrecodeTestDescriptorData()) { - yield return new object[]{data[0], "c1"}; - yield return new object[]{data[0], "c2"}; yield return new object[]{data[0], "c3"}; - - } - } - - public static IEnumerable PrecodeTestDescriptorDataUnsupportedInterpreter() - { - foreach (object[] data in PrecodeTestDescriptorData()) - { - yield return [data[0], "c1"]; - yield return [data[0], "c2"]; } } @@ -226,9 +168,8 @@ internal class PrecodeBuilder { public string PrecodesVersion { get; } - // V3-only fields - private byte[]? _v3StubBytes; - private const byte V3InterpreterPrecodeType = 0x06; + private readonly byte[] _stubBytes = new byte[1]; + private const byte InterpreterPrecodeType = 0x06; public PrecodeBuilder(MockTarget.Architecture arch, string precodesVersion) : this(DefaultAllocationRange, new MockMemorySpace.Builder(new TargetTestHelpers(arch)), precodesVersion) { } @@ -237,10 +178,6 @@ public PrecodeBuilder(AllocationRange allocationRange, MockMemorySpace.Builder b PrecodesVersion = precodesVersion; PrecodeAllocator = builder.CreateAllocator(allocationRange.PrecodeDescriptorStart, allocationRange.PrecodeDescriptorEnd); StubDataPageAllocator = builder.CreateAllocator(allocationRange.StubDataPageStart, allocationRange.StubDataPageEnd); - if (precodesVersion == "c3") - { - _v3StubBytes = new byte[1]; - } Types = typeInfoCache ?? GetTypes(Builder.TargetTestHelpers); } @@ -248,98 +185,68 @@ public PrecodeBuilder(AllocationRange allocationRange, MockMemorySpace.Builder b Dictionary types = new(); TargetTestHelpers.LayoutResult layout; - if (PrecodesVersion == "c3") - { - layout = targetTestHelpers.LayoutFields([ - new(nameof(Data.PrecodeMachineDescriptor.StubCodePageSize), DataType.uint32), - new(nameof(Data.PrecodeMachineDescriptor.InvalidPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.StubPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.StubPrecodeSize), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.StubBytes), DataType.uint8, 1u), - new(nameof(Data.PrecodeMachineDescriptor.StubIgnoredBytes), DataType.uint8, 1u), - new(nameof(Data.PrecodeMachineDescriptor.FixupStubPrecodeSize), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.FixupBytes), DataType.uint8, 1u), - new(nameof(Data.PrecodeMachineDescriptor.FixupIgnoredBytes), DataType.uint8, 1u), - new(nameof(Data.PrecodeMachineDescriptor.InterpreterPrecodeType), DataType.uint8), - ]); - } - else - { - layout = targetTestHelpers.LayoutFields([ - new(nameof(Data.PrecodeMachineDescriptor.StubCodePageSize), DataType.uint32), - new(nameof(Data.PrecodeMachineDescriptor.OffsetOfPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.ReadWidthOfPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.ShiftOfPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.InvalidPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.StubPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.PInvokeImportPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.FixupPrecodeType), DataType.uint8), - new(nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType), DataType.uint8), - ]); - } + layout = targetTestHelpers.LayoutFields([ + new(nameof(Data.PrecodeMachineDescriptor.StubCodePageSize), DataType.uint32), + new(nameof(Data.PrecodeMachineDescriptor.InvalidPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.StubPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.StubPrecodeSize), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.StubBytes), DataType.uint8, 1u), + new(nameof(Data.PrecodeMachineDescriptor.StubIgnoredBytes), DataType.uint8, 1u), + new(nameof(Data.PrecodeMachineDescriptor.FixupStubPrecodeSize), DataType.uint8), + new(nameof(Data.PrecodeMachineDescriptor.FixupBytes), DataType.uint8, 1u), + new(nameof(Data.PrecodeMachineDescriptor.FixupIgnoredBytes), DataType.uint8, 1u), + new(nameof(Data.PrecodeMachineDescriptor.InterpreterPrecodeType), DataType.uint8), + ]); types[DataType.PrecodeMachineDescriptor] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride, }; - if (PrecodesVersion == "c1") { - layout = targetTestHelpers.LayoutFields([ - new(nameof(Data.StubPrecodeData_1.Type), DataType.uint8), - new(nameof(Data.StubPrecodeData_1.MethodDesc), DataType.pointer), - ]); - } else { - layout = targetTestHelpers.LayoutFields([ - new(nameof(Data.StubPrecodeData_2.Type), DataType.uint8), - new(nameof(Data.StubPrecodeData_2.SecretParam), DataType.pointer), - ]); - } + layout = targetTestHelpers.LayoutFields([ + new(nameof(Data.StubPrecodeData_2.SecretParam), DataType.pointer), + new(nameof(Data.StubPrecodeData_2.Type), DataType.uint8), + ]); types[DataType.StubPrecodeData] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride, }; - if (PrecodesVersion is not "c1") + layout = targetTestHelpers.LayoutFields([ + new(nameof(Data.ThisPtrRetBufPrecodeData.MethodDesc), DataType.pointer), + ]); + types[DataType.ThisPtrRetBufPrecodeData] = new Target.TypeInfo() { + Fields = layout.Fields, + Size = layout.Stride, + }; + + layout = targetTestHelpers.LayoutFields([ + new(nameof(Data.InterpreterPrecodeData.ByteCodeAddr), DataType.pointer), + new(nameof(Data.InterpreterPrecodeData.Type), DataType.uint8), + ]); + types[DataType.InterpreterPrecodeData] = new Target.TypeInfo() { - layout = targetTestHelpers.LayoutFields([ - new(nameof(Data.ThisPtrRetBufPrecodeData.MethodDesc), DataType.pointer), - ]); - types[DataType.ThisPtrRetBufPrecodeData] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, - }; - } + Fields = layout.Fields, + Size = layout.Stride, + }; - if (PrecodesVersion == "c3") + layout = targetTestHelpers.LayoutFields([ + new(nameof(Data.InterpByteCodeStart.Method), DataType.pointer), + ]); + types[DataType.InterpByteCodeStart] = new Target.TypeInfo() { - layout = targetTestHelpers.LayoutFields([ - new(nameof(Data.InterpreterPrecodeData.Type), DataType.uint8), - new(nameof(Data.InterpreterPrecodeData.ByteCodeAddr), DataType.pointer), - ]); - types[DataType.InterpreterPrecodeData] = new Target.TypeInfo() - { - Fields = layout.Fields, - Size = layout.Stride, - }; - - layout = targetTestHelpers.LayoutFields([ - new(nameof(Data.InterpByteCodeStart.Method), DataType.pointer), - ]); - types[DataType.InterpByteCodeStart] = new Target.TypeInfo() - { - Fields = layout.Fields, - Size = layout.Stride, - }; - - layout = targetTestHelpers.LayoutFields([ - new(nameof(Data.InterpMethod.MethodDesc), DataType.pointer), - ]); - types[DataType.InterpMethod] = new Target.TypeInfo() - { - Fields = layout.Fields, - Size = layout.Stride, - }; - } + Fields = layout.Fields, + Size = layout.Stride, + }; + + layout = targetTestHelpers.LayoutFields([ + new(nameof(Data.InterpMethod.MethodDesc), DataType.pointer), + ]); + types[DataType.InterpMethod] = new Target.TypeInfo() + { + Fields = layout.Fields, + Size = layout.Stride, + }; return types; } @@ -359,29 +266,17 @@ public void AddPlatformMetadata(PrecodeTestDescriptor descriptor) { MachineDescriptorAddress = fragment.Address; Span desc = Builder.BorrowAddressRange(fragment.Address, (int)typeInfo.Size); - if (PrecodesVersion == "c3") - { - _v3StubBytes![0] = descriptor.StubPrecode; - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubCodePageSize)].Offset, sizeof(uint)), descriptor.StubCodePageSize); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubPrecodeType)].Offset, sizeof(byte)), descriptor.StubPrecode); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType)].Offset, sizeof(byte)), descriptor.ThisPtrRetBufPrecode); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.InterpreterPrecodeType)].Offset, sizeof(byte)), V3InterpreterPrecodeType); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubPrecodeSize)].Offset, sizeof(byte)), (byte)_v3StubBytes.Length); - _v3StubBytes.CopyTo(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubBytes)].Offset, _v3StubBytes.Length)); - desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubIgnoredBytes)].Offset, _v3StubBytes.Length).Fill(0); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.FixupStubPrecodeSize)].Offset, sizeof(byte)), (byte)1); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.FixupBytes)].Offset, sizeof(byte)), (byte)0xFE); - desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.FixupIgnoredBytes)].Offset, 1).Fill(0); - } - else - { - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.ReadWidthOfPrecodeType)].Offset, sizeof(byte)), (byte)descriptor.ReadWidthOfPrecodeType); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.OffsetOfPrecodeType)].Offset, sizeof(byte)), (byte)descriptor.OffsetOfPrecodeType); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.ShiftOfPrecodeType)].Offset, sizeof(byte)), (byte)descriptor.ShiftOfPrecodeType); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubCodePageSize)].Offset, sizeof(uint)), descriptor.StubCodePageSize); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubPrecodeType)].Offset, sizeof(byte)), descriptor.StubPrecode); - Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType)].Offset, sizeof(byte)), descriptor.ThisPtrRetBufPrecode); - } + _stubBytes[0] = descriptor.StubPrecode; + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubCodePageSize)].Offset, sizeof(uint)), descriptor.StubCodePageSize); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubPrecodeType)].Offset, sizeof(byte)), descriptor.StubPrecode); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType)].Offset, sizeof(byte)), descriptor.ThisPtrRetBufPrecode); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.InterpreterPrecodeType)].Offset, sizeof(byte)), InterpreterPrecodeType); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubPrecodeSize)].Offset, sizeof(byte)), (byte)_stubBytes.Length); + _stubBytes.CopyTo(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubBytes)].Offset, _stubBytes.Length)); + desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubIgnoredBytes)].Offset, _stubBytes.Length).Fill(0); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.FixupStubPrecodeSize)].Offset, sizeof(byte)), (byte)1); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.FixupBytes)].Offset, sizeof(byte)), (byte)0xFE); + desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.FixupIgnoredBytes)].Offset, 1).Fill(0); // FIXME: set the other fields } @@ -397,20 +292,12 @@ public TargetCodePointer AddStubPrecodeEntry(string name, PrecodeTestDescriptor Data = new byte[stubCodeSize], Name = $"Stub code for {name} on {test.Name} with data at 0x{stubDataFragment.Address:x}", }; - if (PrecodesVersion == "c3") - _v3StubBytes!.CopyTo(stubCodeFragment.Data.AsSpan()); - else - test.WritePrecodeType(test.StubPrecode, Builder.TargetTestHelpers, stubCodeFragment.Data); + _stubBytes.CopyTo(stubCodeFragment.Data.AsSpan()); Builder.AddHeapFragment(stubCodeFragment); Span stubData = Builder.BorrowAddressRange(stubDataFragment.Address, (int)stubDataTypeInfo.Size); - if (PrecodesVersion == "c1") { - Builder.TargetTestHelpers.Write(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData_1.Type)].Offset, sizeof(byte)), test.StubPrecode); - Builder.TargetTestHelpers.WritePointer(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData_1.MethodDesc)].Offset, Builder.TargetTestHelpers.PointerSize), methodDesc); - } else { - Builder.TargetTestHelpers.Write(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData_2.Type)].Offset, sizeof(byte)), test.StubPrecode); - Builder.TargetTestHelpers.WritePointer(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData_2.SecretParam)].Offset, Builder.TargetTestHelpers.PointerSize), methodDesc); - } + Builder.TargetTestHelpers.Write(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData_2.Type)].Offset, sizeof(byte)), test.StubPrecode); + Builder.TargetTestHelpers.WritePointer(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData_2.SecretParam)].Offset, Builder.TargetTestHelpers.PointerSize), methodDesc); TargetCodePointer address = stubCodeFragment.Address; if (test.IsThumb) { address = new TargetCodePointer(address.Value | 1); @@ -433,10 +320,7 @@ public TargetCodePointer AddThisPtrRetBufPrecodeEntry(string name, PrecodeTestDe Data = new byte[stubCodeSize], Name = $"Stub code for {name} on {test.Name} with data at 0x{stubDataFragment.Address:x}", }; - if (PrecodesVersion == "c3") - _v3StubBytes!.CopyTo(stubCodeFragment.Data.AsSpan()); - else - test.WritePrecodeType(test.StubPrecode, Builder.TargetTestHelpers, stubCodeFragment.Data); + _stubBytes.CopyTo(stubCodeFragment.Data.AsSpan()); Builder.AddHeapFragment(stubCodeFragment); Span thisPtrStubData = Builder.BorrowAddressRange(thisPtrRetBufStubDataFragment.Address, (int)thisPtrRetBufDataTypeInfo.Size); @@ -481,7 +365,7 @@ public TargetCodePointer AddInterpreterPrecodeEntry( Span byteCodeStartData = Builder.BorrowAddressRange(byteCodeStartFragment.Address, (int)interpByteCodeStartTypeInfo.Size); Builder.TargetTestHelpers.WritePointer(byteCodeStartData.Slice(interpByteCodeStartTypeInfo.Fields[nameof(Data.InterpByteCodeStart.Method)].Offset, Builder.TargetTestHelpers.PointerSize), interpMethodFragment.Address); - ulong stubCodeSize = (ulong)Math.Max(_v3StubBytes!.Length, (int)interpPrecodeTypeInfo.Size); + ulong stubCodeSize = (ulong)Math.Max(_stubBytes.Length, (int)interpPrecodeTypeInfo.Size); MockMemorySpace.HeapFragment stubDataFragment = StubDataPageAllocator.Allocate(Math.Max((ulong)interpPrecodeTypeInfo.Size, stubCodeSize), $"Interp precode data for {name}"); ulong stubCodeStart= stubDataFragment.Address - stubCodePageSize; @@ -491,11 +375,11 @@ public TargetCodePointer AddInterpreterPrecodeEntry( Data = new byte[stubCodeSize], Name = $"Interp stub code for {name} at data 0x{stubDataFragment.Address:x}", }; - _v3StubBytes.CopyTo(stubCodeFragment.Data.AsSpan()); + _stubBytes.CopyTo(stubCodeFragment.Data.AsSpan()); Builder.AddHeapFragment(stubCodeFragment); Span stubData = Builder.BorrowAddressRange(stubDataFragment.Address, (int)interpPrecodeTypeInfo.Size); - Builder.TargetTestHelpers.Write(stubData.Slice(interpPrecodeTypeInfo.Fields[nameof(Data.InterpreterPrecodeData.Type)].Offset, sizeof(byte)), V3InterpreterPrecodeType); + Builder.TargetTestHelpers.Write(stubData.Slice(interpPrecodeTypeInfo.Fields[nameof(Data.InterpreterPrecodeData.Type)].Offset, sizeof(byte)), InterpreterPrecodeType); TargetPointer storedByteCodeAddress = nullByteCodeAddress ? TargetPointer.Null : byteCodeStartFragment.Address; @@ -536,7 +420,7 @@ public void TestPrecodeStubPrecodeExpectedMethodDesc(PrecodeTestDescriptor test, TargetPointer expectedMethodDesc = new TargetPointer(0xeeee_eee0u); // arbitrary TargetCodePointer stub1 = builder.AddStubPrecodeEntry("Stub 1", test, expectedMethodDesc); TargetPointer expectedMethodDesc2 = new TargetPointer(0xfafa_eee0u); // arbitrary - TargetCodePointer stub2 = contractVersion is not "c1" ? builder.AddThisPtrRetBufPrecodeEntry("Stub 2", test, expectedMethodDesc2) : new TargetCodePointer(expectedMethodDesc2.Value); + TargetCodePointer stub2 = builder.AddThisPtrRetBufPrecodeEntry("Stub 2", test, expectedMethodDesc2); var target = CreateTarget(builder); Assert.NotNull(target); @@ -548,21 +432,14 @@ public void TestPrecodeStubPrecodeExpectedMethodDesc(PrecodeTestDescriptor test, var actualMethodDesc = precodeContract.GetMethodDescFromStubAddress(stub1); Assert.Equal(expectedMethodDesc, actualMethodDesc); - if (contractVersion is not "c1") - { - // Implementation of this type of precode is only handled correctly in contract version 2 and higher - var actualMethodDesc2 = precodeContract.GetMethodDescFromStubAddress(stub2); - Assert.Equal(expectedMethodDesc2, actualMethodDesc2); - } + var actualMethodDesc2 = precodeContract.GetMethodDescFromStubAddress(stub2); + Assert.Equal(expectedMethodDesc2, actualMethodDesc2); } - [ConditionalTheory] + [Theory] [MemberData(nameof(PrecodeTestDescriptorDataWithContractVersion))] public void TestInterpreterPrecodeReturnsExpectedMethodDesc(PrecodeTestDescriptor test, string contractVersion) { - if (contractVersion != "c3") - throw new SkipTestException("Interpreter precodes are only supported in contract version c3 and above."); - var builder = new PrecodeBuilder(test.Arch, contractVersion); builder.AddPlatformMetadata(test); @@ -578,24 +455,6 @@ public void TestInterpreterPrecodeReturnsExpectedMethodDesc(PrecodeTestDescripto Assert.Equal(expectedMethodDesc, actualMethodDesc); } - [Theory] - [MemberData(nameof(PrecodeTestDescriptorDataUnsupportedInterpreter))] - public void GetInterpreterCode_UnsupportedVersion_ReturnsOriginalAddress( - PrecodeTestDescriptor test, - string contractVersion) - { - var builder = new PrecodeBuilder(test.Arch, contractVersion); - builder.AddPlatformMetadata(test); - Target target = CreateTarget(builder); - TargetCodePointer entryPoint = new(0x1234_5678); - - TargetCodePointer actual = - target.Contracts.PrecodeStubs - .GetInterpreterCodeFromInterpreterPrecodeIfPresent(entryPoint); - - Assert.Equal(entryPoint, actual); - } - [Theory] [MemberData(nameof(PrecodeTestDescriptorDataVersion3))] public void GetInterpreterCode_Version3_ReturnsByteCodeAddress( diff --git a/src/native/managed/cdac/tools/CdacUsageGraph/tests/CdacUsageGraph.Tests/UsageWalkerIntegrationTests.cs b/src/native/managed/cdac/tools/CdacUsageGraph/tests/CdacUsageGraph.Tests/UsageWalkerIntegrationTests.cs index 53f539e3dfb52c..2f637e1b6107ae 100644 --- a/src/native/managed/cdac/tools/CdacUsageGraph/tests/CdacUsageGraph.Tests/UsageWalkerIntegrationTests.cs +++ b/src/native/managed/cdac/tools/CdacUsageGraph/tests/CdacUsageGraph.Tests/UsageWalkerIntegrationTests.cs @@ -102,8 +102,8 @@ public void ResolvesNativeTypesForFieldsReadThroughHelpers( } [Theory] - [InlineData("IExecutionManager", "c1", "Data.UnwindInfo", "FunctionLength")] - [InlineData("IPrecodeStubs", "c1", "Data.PrecodeMachineDescriptor", "OffsetOfPrecodeType")] + [InlineData("IExecutionManager", "c2", "Data.UnwindInfo", "FunctionLength")] + [InlineData("IPrecodeStubs", "c3", "Data.PrecodeMachineDescriptor", "StubCodePageSize")] [InlineData("IStackWalk", "c1", "Data.ReadyToRunInfo", "ImportSections")] [InlineData("IThread", "c1", "Data.Thread", "ThreadHandle")] [InlineData("IThread", "c1", "Data.Thread", "DebuggerControlledThreadState")] @@ -132,7 +132,7 @@ public void UsageWalkerTypeSizeEffectsAreIntegratedIntoUsageGraph() Assert.True(DataType( built.Value.Graph, - new ContractVersion(new ContractInterface("IExecutionManager"), "c1"), + new ContractVersion(new ContractInterface("IExecutionManager"), "c2"), "Data.R2RExceptionClause").UsesTypeSize); } @@ -251,21 +251,16 @@ public void ResolvesGenericBaseAndStaticAbstractDispatch() Assert.Contains("Data.InterpMethod", precodeTypes); } - [Theory] - [InlineData("c1", false)] - [InlineData("c2", false)] - [InlineData("c3", true)] - public void ReportsInterpreterPrecodeUsageOnlyForSupportingVersion( - string version, - bool expected) + [Fact] + public void ReportsInterpreterPrecodeUsage() { (UsageGraph Graph, string Root)? built = BuildRealGraph(); if (built is null) return; // cDAC source not found (running outside the repo) HashSet dataTypes = DataTypesUsed( built.Value.Graph, - new ContractVersion(new ContractInterface("IPrecodeStubs"), version)); - Assert.Equal(expected, dataTypes.Contains("Data.InterpreterPrecodeData")); + new ContractVersion(new ContractInterface("IPrecodeStubs"), "c3")); + Assert.Contains("Data.InterpreterPrecodeData", dataTypes); } [Fact] @@ -275,17 +270,17 @@ public void ResolvesFieldReadsReachedThroughFieldInitializerHelper() if (built is null) return; // cDAC source not found (running outside the repo) UsageGraph graph = built!.Value.Graph; - // StressLog_1's SmallStressMessageReader is constructed in a field initializer and reads + // StressLog_2's message reader is constructed in a field initializer and reads // Data.StressMsg fields; walking initializers is what surfaces these. Assert.Contains( "Header", - DataType(graph, new ContractVersion(new ContractInterface("IStressLog"), "c1"), "Data.StressMsg") + DataType(graph, new ContractVersion(new ContractInterface("IStressLog"), "c2"), "Data.StressMsg") .Fields.Select(field => field.Name)); // StressMsgHeader is used only via Data.StressMsgHeader.GetSize. Assert.True(DataType( graph, - new ContractVersion(new ContractInterface("IStressLog"), "c1"), + new ContractVersion(new ContractInterface("IStressLog"), "c2"), "Data.StressMsgHeader").UsesTypeSize); } @@ -302,7 +297,7 @@ public void ResolvesFieldReadsThroughSharedDataInterface() // referenced by a concrete-typed read. DataTypeUsage r2rUsage = DataType( graph, - new ContractVersion(new ContractInterface("IExecutionManager"), "c1"), + new ContractVersion(new ContractInterface("IExecutionManager"), "c2"), "Data.R2RExceptionClause"); string[] r2rFields = r2rUsage.Fields.Select(field => field.Name).ToArray(); Assert.Contains("Flags", r2rFields); @@ -310,7 +305,7 @@ public void ResolvesFieldReadsThroughSharedDataInterface() string[] eeFields = DataType( graph, - new ContractVersion(new ContractInterface("IExecutionManager"), "c1"), + new ContractVersion(new ContractInterface("IExecutionManager"), "c2"), "Data.EEExceptionClause").Fields.Select(field => field.Name).ToArray(); Assert.Contains("Flags", eeFields); @@ -360,7 +355,6 @@ public void ComputedConveniencePropertiesResolveToUnderlyingFields() } [Theory] - [InlineData("IExecutionManager", "c1")] [InlineData("IExecutionManager", "c2")] public void ExplicitDependenciesIncludeCompositeInfoWhereUsed(string contract, string version) {