diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 103aac455a2a48..8296adab68f676 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -25,6 +25,13 @@ struct CodeBlockHandle TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle); // Get the instruction pointer address of the start of the funclet containing the code block TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle); + // Get the method region info (hot and cold code size, and cold code start address) + void GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize); + // Get the JIT type + uint GetJITType(CodeBlockHandle codeInfoHandle); + // Attempt to get the method desc of an entrypoint + TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint); + // Gets the unwind info of the code block at the specified code pointer TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle); // Gets the base address the UnwindInfo of codeInfoHandle is relative to @@ -96,6 +103,9 @@ Data descriptors used: | `Bucket` | `Keys` | Array of keys of `HashMapSlotsPerBucket` length | | `Bucket` | `Values` | Array of values of `HashMapSlotsPerBucket` length | | `UnwindInfo` | `FunctionLength` | Length of the associated function in bytes. Only exists on some platforms | +| `UnwindInfo` | `CountOfUnwindCodes` | Number of unwind codes in the unwind info. Only exists on some platforms | +| `UnwindInfo` | `UnwindCodeOffset` | Offset of UnwindCodeOffset in the UnwindInfo data struct. Only exists on some platforms | +| `PortableEntryPoint` | `MethodDesc` | Method desc of portable entrypoint (only defined if `FeaturePortableEntrypoints` is enabled) | Global variables used: | Global Name | Type | Purpose | @@ -107,6 +117,7 @@ Global variables used: | `FeatureEHFunclets` | uint8 | 1 if EH funclets are enabled, 0 otherwise | | `GCInfoVersion` | uint32 | JITted code GCInfo version | | `FeatureOnStackReplacement` | uint8 | 1 if FEATURE_ON_STACK_REPLACEMENT is enabled, 0 otherwise | +| `FeaturePortableEntrypoints` | uint8 | 1 if FEATURE_PORTABLE_ENTRYPOINTS is enabled, 0 otherwise | Contracts used: | Contract Name | @@ -220,6 +231,142 @@ bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddre } ``` +The EE JitManager `GetMethodRegionInfo` sums up the lengths of each runtime function in a given method, attempting to find the runtime function length in several ways depending on the data availale on the platform. + +```csharp +public override void GetMethodRegionInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddress, out uint hotSize, out TargetPointer coldStart, out uint coldSize) +{ + hotSize = 0; + coldStart = TargetPointer.Null; + coldSize = 0; + + TargetPointer start = // look up jittedCodeAddress in nibble map for rangeSection - see NibbleMap below + if (start == TargetPointer.Null) + return false; + + TargetNUInt relativeOffset = jittedCodeAddress - start; + int codeHeaderOffset = Target.PointerSize; + TargetPointer codeHeaderIndirect = start - codeHeaderOffset; + + // Check if address is in a stub code block + if (codeHeaderIndirect < Target.ReadGlobal("StubCodeBlockLast")) + return false; + + TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); + uint numUnwindInfos = Target.ReadPointer(codeHeaderAddress + /* RealCodeHeader::NumUnwindInfos offset */); + + if (numUnwindInfos == 0) + { + return; + } + TypeInfo type = target.GetTypeInfo(DataType.RuntimeFunction); + TypeInfo unwindType = target.GetTypeInfo(DataType.RuntimeFunction); + + // Sum up the lengths of all the runtime functions to get the hot size + for (uint i = 0; i < numUnwindInfos; i++) + { + TargetPointer addr = runtimeFunctions + (index * type.Size!.Value); + if (/* function has end address */) + hotSize += target.Read(addr + /* RuntimeFunction::EndAddress offset */) - target.Read(addr + /* RuntimeFunction::BeginAddress offset */) + + Data.UnwindInfo unwindInfo = _target.ProcessedData.GetOrAdd(function.UnwindData); + TargetPointer unwindAddr = target.Read(addr + /* RuntimeFunction::UnwindData offset */); + + if (/* unwind datatype has function length */) + hotSize += target.Read(unwindAddr + /* UnwindInfo::FunctionLength offset */) + + // First 18 bits are function length / (pointer size / 2). + // See UnwindFragmentInfo::Finalize + uint funcLengthInHeader = target.Read(unwindAddr + /* UnwindInfo::Header offset */) & ((1 << 18) - 1); + hotSize += (uint)(funcLengthInHeader * (_target.PointerSize / 2)); + } + return hotSize; +} +``` + +The R2R JitManager `GetMethodRegionInfo` has different behavior depending on whether the method was found in the hot cold map. If it was found, we find the bounds of the hot and cold regions by finding the start indices of the next hot and cold regions. If it is not in the map, we simply add the lengths of the runtime functions as shown above. +```csharp +public override void GetMethodRegionInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddress, out uint hotSize, out TargetPointer coldStart, out uint coldSize) +{ + hotSize = 0; + coldSize = 0; + coldStart = TargetPointer.Null; + + info = default; + + TargetPointer r2rModule = Target.ReadPointer(/* range section address + RangeSection::R2RModule offset */); + TargetPointer r2rInfo = Target.ReadPointer(r2rModule + /* Module::ReadyToRunInfo offset */); + + // Check if address is in a thunk + if (/* jittedCodeAddress is in ReadyToRunInfo::DelayLoadMethodCallThunks */) + return false; + + // Find the relative address that we are looking for + TargetCodePointer addr = /* code pointer from jittedCodeAddress using PlatformMetadata.GetCodePointerFlags */ + TargetPointer imageBase = Target.ReadPointer(/* range section address + RangeSection::RangeBegin offset */); + TargetPointer relativeAddr = addr - imageBase; + + TargetPointer runtimeFunctions = Target.ReadPointer(r2rInfo + /* ReadyToRunInfo::RuntimeFunctions offset */); + int index = // Iterate through runtimeFunctions and find index of function with relativeAddress + + // look up hot and cold start and end indices in hot/cold map (hotIdx, coldStartIdx, coldEndIdx) + if (/* found in hot/cold map */) + { + hotSize = /* runtime function length at index hotIdx */ ; + // Sum up the lengths of all the runtime functions to get the cold size + for (uint i = coldStartIdx; i <= coldEndIdx; i++) + { + coldSize += // add up lengths of runtime functions at index i as shown above in the EEJitManager + } + coldStart = imageBase + /* start address of runtime function at index coldStartIdx */; + } + else + { + // No hot/cold splitting for this method, sum up the hot size only + uint hotStartIdx = // iterate through until you reach a valid MethodDesc signifying beginning of hot area + uint hotEndIdx = // iterate through until you reach a valid MethodDesc signifying beginning of next hot area + for (uint i = hotStartIdx; i <= hotEndIdx; i++) + { + hotSize += // add up lengths of runtime functions at index i as shown above in the EEJitManager + } + } +} + +``` + +`GetJitType` returns the JIT type by finding the JIT manager for the data range containing the relevant code block. We return TYPE_JIT for the EEJitManager, TYPE_R2R for the R2RJitManager, and TYPE_UNKNOWN for any other value. +```csharp +private enum JITTypes +{ + TYPE_UNKNOWN = 0, + TYPE_JIT = 1, + TYPE_R2R = 2, + TYPE_INTERPRETER = 3 +}; +``` +`NonVirtualEntry2MethodDesc` attempts to find a method desc from an entrypoint. If portable entrypoints are enabled, we attempt to read the entrypoint data structure to find the method table. We also attempt to find the method desc from a precode stub. Finally, we attempt to find the method desc using `GetMethodInfo` as described above. +```csharp +TargetPointer IExecutionManager.NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) +{ + TargetPointer rangeSection = // find range section corresponding to jittedCodeAddress - see RangeSectionMap + if (/* no corresponding range section */) + return null; + + if (/* range flags indicate RangeList */) + { + IPrecodeStubs precodeStubs = _target.Contracts.PrecodeStubs; + return precodeStubs.GetMethodDescFromStubAddress(entrypoint); + } + else + { + // get the jit manager + // attempt to get the method info from a code block + } + return TargetPointer.Null; +} +``` + + The `CodeBlock` encapsulates the `MethodDesc` data from the target runtime together with the start of the jitted method ```csharp @@ -284,7 +431,7 @@ For R2R images, `hasFlagByte` is always `false`. * For jitted code (`EEJitManager`) a pointer to the `GCInfo` is stored on the `RealCodeHeader` which is accessed in the same way as `GetMethodInfo` described above. This can simply be returned as is. The `GCInfoVersion` is defined by the runtime global `GCInfoVersion`. -* For R2R code (`ReadyToRunJitManager`), the `GCInfo` is stored directly after the `UnwindData`. This in turn is found by looking up the `UnwindInfo` (`RUNTIME_FUNCTION`) and reading the `UnwindData` offset. We find the `UnwindInfo` as described above in `IExecutionManager.GetUnwindInfo`. Once we have the relevant unwind data, we calculate the size of the unwind data and return a pointer to the following byte (first byte of the GCInfo). The size of the unwind data is a platform specific. Currently only X86 is supported with a constant unwind data size of 32-bits. +* For R2R code (`ReadyToRunJitManager`), the `GCInfo` is stored directly after the `UnwindData`. This in turn is found by looking up the `UnwindInfo` (`RUNTIME_FUNCTION`) and reading the `UnwindData` offset. We find the `UnwindInfo` as described above in `IExecutionManager.GetUnwindInfo`. Once we have the relevant unwind data, we calculate the size of the unwind data and return a pointer to the following byte (first byte of the GCInfo). The size of the unwind data is a platform specific. See src/coreclr/vm/codeman.cpp GetUnwindDataBlob for more details. * The `GCInfoVersion` of R2R code is mapped from the R2R MajorVersion and MinorVersion which is read from the ReadyToRunHeader which itself is read from the ReadyToRunInfo (can be found as in GetMethodInfo). The current GCInfoVersion mapping is: * MajorVersion >= 11 and MajorVersion < 15 => 4 diff --git a/docs/design/datacontracts/GCInfo.md b/docs/design/datacontracts/GCInfo.md new file mode 100644 index 00000000000000..bf06175be75718 --- /dev/null +++ b/docs/design/datacontracts/GCInfo.md @@ -0,0 +1,286 @@ +# Contract GCInfo + +This contract is for fetching information related to GCInfo associated with native code. Currently, this contract does not support x86 architecture. + +## APIs of contract + +```csharp +public interface IGCInfoHandle { } +``` + +```csharp +// Decodes GCInfo with a given address and version +IGCInfoHandle DecodeGCInfo(TargetPointer gcInfoAddress, uint gcVersion); + +/* Methods to query information from the GCInfo */ + +// Fetches length of code as reported in GCInfo +uint GetCodeLength(IGCInfoHandle handle); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| _none_ | | | + +Contracts used: +| Contract Name | +| --- | +| _none_ | + +Constants: +| Constant Name | Meaning | Value | +| --- | --- | --- | +| `NO_GS_COOKIE` | Indicates no GS cookie is present | -1 | +| `NO_STACK_BASE_REGISTER` | Indicates no stack base register is used | 0xFFFFFFFF | +| `NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA` | Indicates no Edit and Continue preserved area | 0xFFFFFFFF | +| `NO_GENERICS_INST_CONTEXT` | Indicates no generics instantiation context | -1 | +| `NO_REVERSE_PINVOKE_FRAME` | Indicates no reverse P/Invoke frame | -1 | +| `NO_PSP_SYM` | Indicates no PSP symbol | -1 | + + +## Implementation + +The GCInfo contract has platform specific implementations as GCInfo differs per architecture. With the exception of x86, all platforms have a common encoding scheme with different encoding lengths and normalization functions for data. x86 uses an entirely different scheme which is not currently supported by this contract. + +### GCInfo Format + +The GCInfo format consists of a header structure and following data types. The header is either 'slim' for simple methods that can use the compact encoding scheme or a 'fat' header containing more details. + +#### GCInfo Header + +##### Slim Header +| Name | Bits | Meaning | Condition | +| --- | --- | --- | --- | +| IsSlimHeader | 1 | If 0, this GCInfo uses the slim header encoding | | +| UsingStackBaseRegister | 1 | If true, has stack base register of normalized value `0`. Otherwise this GCInfo has no stack base register. Controls `GC_INFO_HAS_STACK_BASE_REGISTER` of HeaderFlags | | +| CodeLength | `CODE_LENGTH_ENCBASE` | Normalized method length | | +| NumSafePoints | `NUM_SAFE_POINTS_ENCBASE` | Number of safe points/callsites | #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED | + +All other values are assumed to be `0`. + +#### Fat Header + +The fat header is used for methods that cannot be encoded using the compact slim header format. It contains additional flags and optional fields based on method characteristics. + +| Name | Bits | Meaning | Condition | +| --- | --- | --- | --- | +| IsSlimHeader | 1 | If 1, this GCInfo uses the fat header encoding | | +| HeaderFlags | `GC_INFO_FLAGS_BIT_SIZE` (10) | Bitfield containing various method flags | | +| CodeLength | `CODE_LENGTH_ENCBASE` | Normalized method length | | +| PrologSize | `NORM_PROLOG_SIZE_ENCBASE` | Normalized prolog size - 1 | If HeaderFlags has `GC_INFO_HAS_GS_COOKIE` or `HeaderFlags & GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK != 0` | +| EpilogSize | `NORM_EPILOG_SIZE_ENCBASE` | Normalized epilog size | If HeaderFlags has `GC_INFO_HAS_GS_COOKIE` | +| GSCookieStackSlot | `GS_COOKIE_STACK_SLOT_ENCBASE` | Normalized stack slot for GS cookie | If HeaderFlags has `GC_INFO_HAS_GS_COOKIE` | +| GenericsInstContextStackSlot | `GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE` | Normalized stack slot for generics context | `HeaderFlags & GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK != 0` | +| StackBaseRegister | `STACK_BASE_REGISTER_ENCBASE` | Normalized stack base register number | If HeaderFlags has GC_INFO_HAS_STACK_BASE_REGISTER | +| SizeOfEditAndContinuePreservedArea | `SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE` | Size of EnC preserved area | If HeaderFlags has `GC_INFO_HAS_EDIT_AND_CONTINUE_INFO` | +| ReversePInvokeFrameSlot | `REVERSE_PINVOKE_FRAME_ENCBASE` | Normalized reverse P/Invoke frame slot | If GC_INFO_REVERSE_PINVOKE_FRAME | +| SizeOfStackOutgoingAndScratchArea | `SIZE_OF_STACK_AREA_ENCBASE` | Size of stack parameter area | Platform dependent | +| NumSafePoints | `NUM_SAFE_POINTS_ENCBASE` | Number of safe points/callsites | #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED | +| NumInterruptibleRanges | `NUM_INTERRUPTIBLE_RANGES_ENCBASE` | Number of interruptible ranges | | + +##### Header Flags + +The HeaderFlags field contains the following bit flags: + +| Flag | Bit Position | Meaning | +| --- | --- | --- | +| GC_INFO_IS_VARARG | 0x1 | Method uses variable arguments | +| GC_INFO_HAS_SECURITY_OBJECT | 0x2 | Method has security object (deprecated) | +| GC_INFO_HAS_GS_COOKIE | 0x4 | Method has GS cookie for stack protection | +| GC_INFO_HAS_PSP_SYM | 0x8 | Method has PSP symbol (deprecated) | +| GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK | 0x30 | Mask for generics instantiation context type | +| GC_INFO_HAS_STACK_BASE_REGISTER | 0x40 | Method uses a stack base register | +| GC_INFO_WANTS_REPORT_ONLY_LEAF | 0x80 | AMD64: Report only leaf frames; ARM/ARM64: Has tail calls | +| GC_INFO_HAS_EDIT_AND_CONTINUE_INFO | 0x100 | Method has Edit and Continue information | +| GC_INFO_REVERSE_PINVOKE_FRAME | 0x200 | Method has reverse P/Invoke frame | + +### GCInfo Body + +Following the header, the GCInfo body contains several data sections in the following order: + +1. **Call Sites Offsets** - Encoded offsets of call sites/safe points (if PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED) +2. **Interruptible Ranges** - Ranges where the method can be interrupted for GC +3. **Slot Table** - Information about GC-tracked slots (registers and stack locations) + +The rest of the GCInfo body is not yet decoded in the cDAC. + +#### Interruptible Ranges + +Interruptible ranges define code regions where garbage collection can safely occur. Each range is encoded as: + +- **StartOffset** - Normalized code offset where the range begins +- **Length** - Normalized length of the interruptible region + +The ranges are encoded using delta compression, where each range's start offset is relative to the previous range's end offset. + +#### Slot Table + +The slot table describes all GC-tracked locations (registers and stack slots) used by the method. It consists of three sections: + +1. **Register Slots** - GC-tracked CPU registers +2. **Stack Slots** - GC-tracked stack locations +3. **Untracked Slots** - Stack locations that are not GC-tracked + +Each slot entry contains: +- **Location** - Register number or stack offset +- **Flags** - GC slot type (base pointer, interior pointer, pinned, untracked) +- **Base** - For stack slots: caller SP relative, SP relative, or frame register relative + +Slots use delta encoding where consecutive entries encode only the difference from the previous entry when flags match. + +### Platform specific constants + +#### AMD64 + +| Encoding Base | Value | Purpose | +| --- | --- | --- | +| `GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE` | 6 | Base bits for generics instantiation context stack slot | +| `GS_COOKIE_STACK_SLOT_ENCBASE` | 6 | Base bits for GS cookie stack slot | +| `CODE_LENGTH_ENCBASE` | 8 | Base bits for encoding method code length | +| `STACK_BASE_REGISTER_ENCBASE` | 3 | Base bits for stack base register number | +| `SIZE_OF_STACK_AREA_ENCBASE` | 3 | Base bits for stack parameter area size | +| `SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE` | 4 | Base bits for Edit and Continue preserved area size | +| `REVERSE_PINVOKE_FRAME_ENCBASE` | 6 | Base bits for reverse P/Invoke frame slot | +| `NUM_REGISTERS_ENCBASE` | 2 | Base bits for number of register slots | +| `NUM_STACK_SLOTS_ENCBASE` | 2 | Base bits for number of stack slots | +| `NUM_UNTRACKED_SLOTS_ENCBASE` | 1 | Base bits for number of untracked slots | +| `NORM_PROLOG_SIZE_ENCBASE` | 5 | Base bits for normalized prolog size | +| `NORM_EPILOG_SIZE_ENCBASE` | 3 | Base bits for normalized epilog size | +| `INTERRUPTIBLE_RANGE_DELTA1_ENCBASE` | 6 | Base bits for first interruptible range delta | +| `INTERRUPTIBLE_RANGE_DELTA2_ENCBASE` | 6 | Base bits for second interruptible range delta | +| `REGISTER_ENCBASE` | 3 | Base bits for register slot encoding | +| `REGISTER_DELTA_ENCBASE` | 2 | Base bits for register slot delta encoding | +| `STACK_SLOT_ENCBASE` | 6 | Base bits for stack slot encoding | +| `STACK_SLOT_DELTA_ENCBASE` | 4 | Base bits for stack slot delta encoding | +| `NUM_SAFE_POINTS_ENCBASE` | 2 | Base bits for number of safe points | +| `NUM_INTERRUPTIBLE_RANGES_ENCBASE` | 1 | Base bits for number of interruptible ranges | + +##### AMD64 Normalization/Denormalization Rules + +| Operation | Normalization (Encode) | Denormalization (Decode) | +| --- | --- | --- | +| **Stack Base Register** | `reg ^ 0x5` | `reg ^ 0x5` | +| **Code Length** | No change | No change | +| **Code Offset** | No change | No change | +| **Stack Slot** | `offset >> 3` | `offset << 3` | +| **Stack Area Size** | `size >> 3` | `size << 3` | + +#### ARM64 + +| Encoding Base | Value | Purpose | +| --- | --- | --- | +| `GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE` | 6 | Base bits for generics instantiation context stack slot | +| `GS_COOKIE_STACK_SLOT_ENCBASE` | 6 | Base bits for GS cookie stack slot | +| `CODE_LENGTH_ENCBASE` | 8 | Base bits for encoding method code length | +| `STACK_BASE_REGISTER_ENCBASE` | 2 | Base bits for stack base register number | +| `SIZE_OF_STACK_AREA_ENCBASE` | 3 | Base bits for stack parameter area size | +| `SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE` | 4 | Base bits for Edit and Continue preserved area size | +| `REVERSE_PINVOKE_FRAME_ENCBASE` | 6 | Base bits for reverse P/Invoke frame slot | +| `NUM_REGISTERS_ENCBASE` | 3 | Base bits for number of register slots | +| `NUM_STACK_SLOTS_ENCBASE` | 2 | Base bits for number of stack slots | +| `NUM_UNTRACKED_SLOTS_ENCBASE` | 1 | Base bits for number of untracked slots | +| `NORM_PROLOG_SIZE_ENCBASE` | 5 | Base bits for normalized prolog size | +| `NORM_EPILOG_SIZE_ENCBASE` | 3 | Base bits for normalized epilog size | +| `INTERRUPTIBLE_RANGE_DELTA1_ENCBASE` | 6 | Base bits for first interruptible range delta | +| `INTERRUPTIBLE_RANGE_DELTA2_ENCBASE` | 6 | Base bits for second interruptible range delta | +| `REGISTER_ENCBASE` | 3 | Base bits for register slot encoding | +| `REGISTER_DELTA_ENCBASE` | 2 | Base bits for register slot delta encoding | +| `STACK_SLOT_ENCBASE` | 6 | Base bits for stack slot encoding | +| `STACK_SLOT_DELTA_ENCBASE` | 4 | Base bits for stack slot delta encoding | +| `NUM_SAFE_POINTS_ENCBASE` | 3 | Base bits for number of safe points | +| `NUM_INTERRUPTIBLE_RANGES_ENCBASE` | 1 | Base bits for number of interruptible ranges | + +##### ARM64 Normalization/Denormalization Rules + +| Operation | Normalization (Encode) | Denormalization (Decode) | +| --- | --- | --- | +| **Stack Base Register** | `reg ^ 0x29` | `reg ^ 0x29` | +| **Code Length** | `length << 2` | `length >> 2` | +| **Code Offset** | `offset << 2` | `offset >> 2` | +| **Stack Slot** | `offset >> 3` | `offset << 3` | +| **Stack Area Size** | `size >> 3` | `size << 3` | + +#### ARM (32-bit) + +| Encoding Base | Value | Purpose | +| --- | --- | --- | +| `GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE` | 5 | Base bits for generics instantiation context stack slot | +| `GS_COOKIE_STACK_SLOT_ENCBASE` | 5 | Base bits for GS cookie stack slot | +| `CODE_LENGTH_ENCBASE` | 7 | Base bits for encoding method code length | +| `STACK_BASE_REGISTER_ENCBASE` | 1 | Base bits for stack base register number | +| `SIZE_OF_STACK_AREA_ENCBASE` | 3 | Base bits for stack parameter area size | +| `SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE` | 3 | Base bits for Edit and Continue preserved area size | +| `REVERSE_PINVOKE_FRAME_ENCBASE` | 5 | Base bits for reverse P/Invoke frame slot | +| `NUM_REGISTERS_ENCBASE` | 2 | Base bits for number of register slots | +| `NUM_STACK_SLOTS_ENCBASE` | 3 | Base bits for number of stack slots | +| `NUM_UNTRACKED_SLOTS_ENCBASE` | 3 | Base bits for number of untracked slots | +| `NORM_PROLOG_SIZE_ENCBASE` | 5 | Base bits for normalized prolog size | +| `NORM_EPILOG_SIZE_ENCBASE` | 3 | Base bits for normalized epilog size | +| `INTERRUPTIBLE_RANGE_DELTA1_ENCBASE` | 4 | Base bits for first interruptible range delta | +| `INTERRUPTIBLE_RANGE_DELTA2_ENCBASE` | 6 | Base bits for second interruptible range delta | +| `REGISTER_ENCBASE` | 2 | Base bits for register slot encoding | +| `REGISTER_DELTA_ENCBASE` | 1 | Base bits for register slot delta encoding | +| `STACK_SLOT_ENCBASE` | 6 | Base bits for stack slot encoding | +| `STACK_SLOT_DELTA_ENCBASE` | 4 | Base bits for stack slot delta encoding | +| `NUM_SAFE_POINTS_ENCBASE` | 3 | Base bits for number of safe points | +| `NUM_INTERRUPTIBLE_RANGES_ENCBASE` | 2 | Base bits for number of interruptible ranges | + +##### ARM (32-bit) Normalization/Denormalization Rules + +| Operation | Normalization (Encode) | Denormalization (Decode) | +| --- | --- | --- | +| **Stack Base Register** | `((reg - 4) & 7) ^ 7` | `(reg ^ 7) + 4` | +| **Code Length** | `length >> 1` | `length << 1` | +| **Code Offset** | `offset >> 1` | `offset << 1` | +| **Stack Slot** | `offset >> 2` | `offset << 2` | +| **Stack Area Size** | `size >> 2` | `size << 2` | + +### Encoding Scheme + +GCInfo uses a variable-length encoding scheme to efficiently store numeric values. The encoding is designed to use fewer bits for smaller, more common values. + +#### Variable Length Unsigned Encoding + +Numbers are encoded using a base number of bits plus extension bits when needed: + +1. **Base Encoding**: Use `base` bits to store the value +2. **Extension Bit**: If the value doesn't fit in `base` bits, set bit `base` to 1 and use `base+1` additional bits +3. **Continuation**: This process continues until the value fits + +The minimum encoding uses `base+1` bits, where bit `base` indicates if more bits follow. + +**Example with base=3:** +- Value 0-6: Encoded in 4 bits as `0XXX` (where XXX is the 3-bit value) +- Value 7-14: Encoded in 8 bits as `1XXX YYYY` (where XXX are the low 3 bits, YYYY are the next 4 bits) +- Value 15+: Uses additional extension groups + +#### Variable Length Signed Encoding + +Signed values use the same encoding as unsigned, but with sign considerations: + +- A number fits in `base` bits if the topmost bit of the base-bit chunk matches the sign of the entire number +- This ensures proper sign extension when decoding + +### Implementation + +The GCInfo contract implementation follows this process: + +```csharp +IGCInfoHandle DecodeGCInfo(TargetPointer gcInfoAddress, uint gcVersion) +{ + // Create a new decoder instance for the specified platform traits + return new GcInfoDecoder(target, gcInfoAddress, gcVersion); +} + +uint GetCodeLength(IGCInfoHandle handle) +{ + // Cast to the appropriate decoder type and return the decoded code length + GcInfoDecoder decoder = (GcInfoDecoder)handle; + return decoder.GetCodeLength(); +} +``` + +The decoder reads and parses the GCInfo data structure sequentially, using the platform-specific encoding bases and normalization rules to reconstruct the original method metadata. diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index cc578f17a00b19..a5db5c45e3f924 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -496,6 +496,13 @@ CDAC_TYPE_BEGIN(CodePointer) CDAC_TYPE_SIZE(sizeof(PCODE)) CDAC_TYPE_END(CodePointer) +#ifdef FEATURE_PORTABLE_ENTRYPOINTS +CDAC_TYPE_BEGIN(PortableEntryPoint) +CDAC_TYPE_INDETERMINATE(PortableEntryPoint) +CDAC_TYPE_FIELD(PortableEntryPoint, /*pointer*/, MethodDesc, cdac_data::MethodDesc) +CDAC_TYPE_END(PortableEntryPoint) +#endif + CDAC_TYPE_BEGIN(MethodDescCodeData) CDAC_TYPE_INDETERMINATE(MethodDescCodeData) CDAC_TYPE_FIELD(MethodDescCodeData, /*CodePointer*/, TemporaryEntryPoint, offsetof(MethodDescCodeData,TemporaryEntryPoint)) @@ -995,6 +1002,11 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2) #else CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1) #endif //TARGET_64BIT +#ifdef FEATURE_PORTABLE_ENTRYPOINTS +CDAC_GLOBAL(FeaturePortableEntrypoints, uint8, 1) +#else +CDAC_GLOBAL(FeaturePortableEntrypoints, uint8, 0) +#endif // FEATURE_PORTABLE_ENTRYPOINTS CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) CDAC_GLOBAL(DirectorySeparator, uint8, (uint8_t)DIRECTORY_SEPARATOR_CHAR_A) CDAC_GLOBAL(HashMapSlotsPerBucket, uint32, SLOTS_PER_BUCKET) @@ -1060,6 +1072,7 @@ CDAC_GLOBAL_CONTRACT(DebugInfo, 1) CDAC_GLOBAL_CONTRACT(EcmaMetadata, 1) CDAC_GLOBAL_CONTRACT(Exception, 1) CDAC_GLOBAL_CONTRACT(ExecutionManager, 2) +CDAC_GLOBAL_CONTRACT(GCInfo, 1) CDAC_GLOBAL_CONTRACT(Loader, 1) CDAC_GLOBAL_CONTRACT(Notifications, 1) CDAC_GLOBAL_CONTRACT(Object, 1) diff --git a/src/coreclr/vm/precode_portable.hpp b/src/coreclr/vm/precode_portable.hpp index 57cf812e7186ae..0da166ace211b2 100644 --- a/src/coreclr/vm/precode_portable.hpp +++ b/src/coreclr/vm/precode_portable.hpp @@ -8,6 +8,7 @@ #ifndef FEATURE_PORTABLE_ENTRYPOINTS #error Requires FEATURE_PORTABLE_ENTRYPOINTS to be set #endif // !FEATURE_PORTABLE_ENTRYPOINTS +#include "cdacdata.h" class PortableEntryPoint final { @@ -62,7 +63,14 @@ class PortableEntryPoint final // pActualCode is a managed calling convention -> interpreter executor call stub in this case. return _pInterpreterData != nullptr && _pActualCode != nullptr; } + friend struct ::cdac_data; }; +template<> +struct cdac_data +{ + static constexpr size_t MethodDesc = offsetof(PortableEntryPoint, _pMD); +}; + extern InterleavedLoaderHeapConfig s_stubPrecodeHeapConfig; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 85f4f954404536..3f93076214ba25 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -83,6 +83,9 @@ public abstract class ContractRegistry /// public abstract IGC GC { get; } /// + /// Gets an instance of the GCInfo contract for the target. + /// + public abstract IGCInfo GCInfo { get; } /// Gets an instance of the Notifications contract for the target. /// public abstract INotifications Notifications { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs index 8a9653e17e7618..52046befb5b0eb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs @@ -19,10 +19,12 @@ public interface IExecutionManager : IContract TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + void GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize) => throw new NotImplementedException(); + uint GetJITType(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) => throw new NotImplementedException(); TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => throw new NotImplementedException(); - // **Currently GetGCInfo only supports X86** void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => throw new NotImplementedException(); TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs new file mode 100644 index 00000000000000..0df65385ab1a46 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public interface IGCInfoHandle { } + +public interface IGCInfo : IContract +{ + static string IContract.Name { get; } = nameof(GCInfo); + + IGCInfoHandle DecodeGCInfo(TargetPointer gcInfoAddress, uint gcVersion) => throw new NotImplementedException(); + uint GetCodeLength(IGCInfoHandle handle) => throw new NotImplementedException(); +} + +public readonly struct GCInfo : IGCInfo +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 35f243961ee539..ceefea600ebd91 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -107,6 +107,7 @@ public enum DataType HashMap, Bucket, UnwindInfo, + UnwindCode, NonVtableSlot, MethodImpl, NativeCodeSlot, @@ -118,6 +119,7 @@ public enum DataType DynamicILBlobTable, EEJitManager, PatchpointInfo, + PortableEntryPoint, TransitionBlock, DebuggerEval, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 1903486453ce0d..d29fbd7dc5fc7f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -16,6 +16,7 @@ public static class Globals public const string FeatureCOMInterop = nameof(FeatureCOMInterop); public const string FeatureOnStackReplacement = nameof(FeatureOnStackReplacement); + public const string FeaturePortableEntrypoints = nameof(FeaturePortableEntrypoints); public const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask); public const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs index c62499cad64ef7..e05337f1e92065 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs @@ -44,6 +44,24 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer return true; } + public override void GetMethodRegionInfo( + RangeSection rangeSection, + TargetCodePointer jittedCodeAddress, + out uint hotSize, + out TargetPointer coldStart, + out uint coldSize) + { + // cold regions are not supported for JITted code + coldStart = TargetPointer.Null; + coldSize = 0; + + IGCInfo gcInfo = Target.Contracts.GCInfo; + GetGCInfo(rangeSection, jittedCodeAddress, out TargetPointer pGcInfo, out uint gcVersion); + IGCInfoHandle gcInfoHandle = gcInfo.DecodeGCInfo(pGcInfo, gcVersion); + hotSize = gcInfo.GetCodeLength(gcInfoHandle); + Debug.Assert(hotSize > 0); + } + public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) { if (rangeSection.IsRangeList) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index c8d86f1510cf92..e6b05d170ef690 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -60,6 +60,38 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer return true; } + public override void GetMethodRegionInfo( + RangeSection rangeSection, + TargetCodePointer jittedCodeAddress, + out uint hotSize, + out TargetPointer coldStart, + out uint coldSize) + { + coldSize = 0; + coldStart = TargetPointer.Null; + + IGCInfo gcInfo = Target.Contracts.GCInfo; + GetGCInfo(rangeSection, jittedCodeAddress, out TargetPointer pGcInfo, out uint gcVersion); + IGCInfoHandle gcInfoHandle = gcInfo.DecodeGCInfo(pGcInfo, gcVersion); + hotSize = gcInfo.GetCodeLength(gcInfoHandle); + + Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) + return; + + if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, r2rInfo.NumRuntimeFunctions, out uint coldStartIdx, out uint coldEndIdx)) + { + Data.RuntimeFunction coldStartFunc = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldStartIdx); + Data.RuntimeFunction coldEndFunc = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldEndIdx); + uint coldBeginOffset = coldStartFunc.BeginAddress; + uint coldEndOffset = coldEndFunc.BeginAddress + _runtimeFunctions.GetFunctionLength(imageBase, coldEndFunc); + coldSize = coldEndOffset - coldBeginOffset; + coldStart = imageBase + coldBeginOffset; + + hotSize -= coldSize; + } + } + public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) { // ReadyToRunJitManager::JitCodeToMethodInfo @@ -120,7 +152,7 @@ public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jitt Data.RuntimeFunction runtimeFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index); TargetPointer unwindInfo = runtimeFunction.UnwindData + imageBase; - uint unwindDataSize = GetUnwindDataSize(); + uint unwindDataSize = UnwindDataSize.GetUnwindDataSize(Target, unwindInfo, Target.Contracts.RuntimeInfo.GetTargetArchitecture()); gcInfo = unwindInfo + unwindDataSize; gcVersion = GetR2RGCInfoVersion(r2rInfo); } @@ -141,16 +173,6 @@ private uint GetR2RGCInfoVersion(Data.ReadyToRunInfo r2rInfo) }; } - private uint GetUnwindDataSize() - { - RuntimeInfoArchitecture arch = Target.Contracts.RuntimeInfo.GetTargetArchitecture(); - return arch switch - { - RuntimeInfoArchitecture.X86 => sizeof(uint), - _ => throw new NotSupportedException($"GetUnwindDataSize not supported for architecture: {arch}") - }; - } - #region RuntimeFunction Helpers private Data.ReadyToRunInfo GetReadyToRunInfo(RangeSection rangeSection) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index 7161eae75e3840..dbbe86f7cdcc45 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; @@ -55,6 +56,14 @@ private enum RangeSectionFlags : int RangeList = 0x04, } + private enum JitTypes + { + TYPE_UNKNOWN = 0, + TYPE_JIT = 1, + TYPE_R2R = 2, + TYPE_INTERPRETER = 3 + }; + private abstract class JitManager { public Target Target { get; } @@ -65,6 +74,12 @@ protected JitManager(Target target) } public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info); + public abstract void GetMethodRegionInfo( + RangeSection rangeSection, + TargetCodePointer jittedCodeAddress, + out uint hotSize, + out TargetPointer coldStart, + out uint coldSize); public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress); public abstract TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte); public abstract void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion); @@ -184,10 +199,7 @@ TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHand TargetCodePointer IExecutionManager.GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) { - if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) - throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); - - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle); if (range.Data == null) throw new InvalidOperationException("Unable to get runtime function address"); @@ -205,12 +217,71 @@ TargetCodePointer IExecutionManager.GetFuncletStartAddress(CodeBlockHandle codeI return range.Data.RangeBegin + runtimeFunction.BeginAddress; } - TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle) + void IExecutionManager.GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize) { - if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) - throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + hotSize = 0; + coldStart = TargetPointer.Null; + coldSize = 0; - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle); + if (range.Data == null) + throw new InvalidOperationException("Unable to get runtime function address"); + + JitManager jitManager = GetJitManager(range.Data); + + jitManager.GetMethodRegionInfo(range, codeInfoHandle.Address.Value, out hotSize, out coldStart, out coldSize); + } + + uint IExecutionManager.GetJITType(CodeBlockHandle codeInfoHandle) + { + RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle); + if (range.Data == null) + return 0; + + JitManager jitManager = GetJitManager(range.Data); + + if (jitManager == _eeJitManager) + { + return (uint)JitTypes.TYPE_JIT; + } + else if (jitManager == _r2rJitManager) + { + return (uint)JitTypes.TYPE_R2R; + } + else + { + return (uint)JitTypes.TYPE_UNKNOWN; + } + } + + TargetPointer IExecutionManager.NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) + { + if (_target.ReadGlobal(Constants.Globals.FeaturePortableEntrypoints) != 0) + { + Data.PortableEntryPoint portableEntryPoint = _target.ProcessedData.GetOrAdd(entrypoint.AsTargetPointer); + return portableEntryPoint.MethodDesc; + } + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, entrypoint); + if (range.Data == null) + return TargetPointer.Null; + if (range.IsRangeList) + { + IPrecodeStubs precodeStubs = _target.Contracts.PrecodeStubs; + return precodeStubs.GetMethodDescFromStubAddress(entrypoint); + } + else + { + JitManager jitManager = GetJitManager(range.Data); + if (jitManager.GetMethodInfo(range, entrypoint, out CodeBlock? info) && info != null) + { + return info.MethodDescAddress; + } + } + return TargetPointer.Null; + } + TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle) + { + RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle); if (range.Data == null) return TargetPointer.Null; @@ -221,10 +292,7 @@ TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle) TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) { - if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) - throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); - - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, new TargetCodePointer(codeInfoHandle.Address)); + RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle); if (range.Data == null) throw new InvalidOperationException($"{nameof(RangeSection)} not found for {codeInfoHandle.Address}"); @@ -234,10 +302,7 @@ TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInf TargetPointer IExecutionManager.GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) { hasFlagByte = false; - if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) - throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); - - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle); if (range.Data == null) return TargetPointer.Null; @@ -250,10 +315,7 @@ void IExecutionManager.GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPoint gcInfo = TargetPointer.Null; gcVersion = 0; - if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) - throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); - - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle); if (range.Data == null) return; @@ -269,4 +331,13 @@ TargetNUInt IExecutionManager.GetRelativeOffset(CodeBlockHandle codeInfoHandle) return info.RelativeOffset; } + + private RangeSection RangeSectionFromCodeBlockHandle(CodeBlockHandle codeInfoHandle) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + return range; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index 551c664237096c..5cc7d8cfae6539 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -18,6 +18,9 @@ internal ExecutionManager_1(Target target, Data.RangeSectionMap topRangeSectionM public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); public TargetCodePointer 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 uint GetJITType(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetJITType(codeInfoHandle); + public TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) => _executionManagerCore.NonVirtualEntry2MethodDesc(entrypoint); 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); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index ab1fda5e329640..8b20e02d27a390 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -18,6 +18,9 @@ internal ExecutionManager_2(Target target, Data.RangeSectionMap topRangeSectionM public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); public TargetCodePointer 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 uint GetJITType(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetJITType(codeInfoHandle); + public TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) => _executionManagerCore.NonVirtualEntry2MethodDesc(entrypoint); 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); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs index d2bea5f7eb3c2d..6bf097a0419bc5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/HotColdLookup.cs @@ -17,6 +17,29 @@ private HotColdLookup(Target target) _target = target; } + public bool TryGetColdFunctionIndex( + uint numHotColdMap, + TargetPointer hotColdMap, + uint runtimeFunctionIndex, + uint numRuntimeFunctions, + out uint coldStart, + out uint coldEnd) + { + coldStart = 0; + coldEnd = 0; + + // The first entry in the HotColdMap is the runtime function index of the first cold part. + if (TryLookupHotColdMappingForMethod(numHotColdMap, hotColdMap, runtimeFunctionIndex, out uint _, out uint coldIndex)) + { + coldStart = _target.Read(hotColdMap + (ulong)coldIndex * sizeof(uint)); + coldEnd = (coldIndex + 2) < numHotColdMap + ? _target.Read(hotColdMap + (ulong)(coldIndex + 2) * sizeof(uint)) - 1 + : numRuntimeFunctions - 1; + return true; + } + return false; + } + public uint GetHotFunctionIndex(uint numHotColdMap, TargetPointer hotColdMap, uint runtimeFunctionIndex) { if (!IsColdCode(numHotColdMap, hotColdMap, runtimeFunctionIndex)) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/UnwindDataSize.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/UnwindDataSize.cs new file mode 100644 index 00000000000000..12b274c801fe17 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/UnwindDataSize.cs @@ -0,0 +1,98 @@ +// 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.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; + +internal static class UnwindDataSize +{ + public static uint GetUnwindDataSize(Target target, TargetPointer unwindInfo, RuntimeInfoArchitecture arch) + { + switch (arch) + { + case RuntimeInfoArchitecture.X86: + return sizeof(uint); + case RuntimeInfoArchitecture.X64: + // see https://learn.microsoft.com/cpp/build/exception-handling-x64 + int sizeOfUnwindCode = 2; // from spec + int unwindCodeOffset = 4; // from spec + int countOfUnwindCodes = target.Read(unwindInfo + 2); // from spec + return AlignUp( + unwindCodeOffset + + (countOfUnwindCodes * sizeOfUnwindCode) + + sizeof(uint) /* personality routine is always present */, + sizeof(uint)); + case RuntimeInfoArchitecture.Arm: + case RuntimeInfoArchitecture.Arm64: + TargetPointer xdata = unwindInfo; + uint xdata0 = target.Read(xdata); + uint size = 4; // initial header + uint unwindWords; + uint epilogScopes; + if (arch == RuntimeInfoArchitecture.Arm) + { + // See https://learn.microsoft.com/cpp/build/arm-exception-handling + unwindWords = xdata0 >> 28; + epilogScopes = (xdata0 >> 23) & 0x1F; + } + else + { + // See https://learn.microsoft.com/cpp/build/arm64-exception-handling + unwindWords = xdata0 >> 27; + epilogScopes = (xdata0 >> 22) & 0x1F; + } + if (unwindWords == 0 && epilogScopes == 0) + { + size += 4; + uint xdata1 = target.Read(xdata + 4); + unwindWords = (xdata1 >> 16) & 0xff; + epilogScopes = xdata1 & 0xffff; + } + + if ((xdata0 & (1 << 21)) != 0) + size += 4 * epilogScopes; + + size += 4 * unwindWords; + size += 4; + return size; + + case RuntimeInfoArchitecture.LoongArch64: + case RuntimeInfoArchitecture.RiscV64: + xdata = unwindInfo; + xdata0 = target.Read(xdata); + + // If both Epilog Count and Code Word is not zero + // Info of Epilog and Unwind scopes are given by 1 word header + // Otherwise this info is given by a 2 word header + if ((xdata0 >> 27) != 0) + { + size = 4; + epilogScopes = (xdata0 >> 22) & 0x1f; + unwindWords = (xdata0 >> 27) & 0x1f; + } + else + { + size = 8; + uint xdata1 = target.Read(xdata + 4); + epilogScopes = xdata1 & 0xffff; + unwindWords = (xdata1 >> 16) & 0xff; + } + + if ((xdata0 & (1 << 21)) != 0) + size += 4 * epilogScopes; + + size += 4 * unwindWords; + + size += 4; // exception handler RVA + return size; + default: + throw new NotSupportedException($"GetUnwindDataSize not supported for architecture: {arch}"); + } + } + + private static uint AlignUp(int offset, int align) + => (uint)((offset + align - 1) & ~(align - 1)); +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs new file mode 100644 index 00000000000000..d889153541b9d9 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs @@ -0,0 +1,493 @@ +// 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.GCInfoHelpers; + +internal class GcInfoDecoder : IGCInfoHandle where TTraits : IGCInfoTraits +{ + private enum DecodePoints + { + CodeLength, + ReturnKind, + VarArg, + PrologLength, + GSCookie, + PSPSym, + GenericInstContext, + EditAndContinue, + ReversePInvoke, + Complete, + } + + [Flags] + internal enum GcInfoHeaderFlags : uint + { + GC_INFO_IS_VARARG = 0x1, + GC_INFO_HAS_SECURITY_OBJECT = 0x2, + GC_INFO_HAS_GS_COOKIE = 0x4, + GC_INFO_HAS_PSP_SYM = 0x8, + GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK = 0x30, + GC_INFO_HAS_GENERICS_INST_CONTEXT_NONE = 0x00, + GC_INFO_HAS_GENERICS_INST_CONTEXT_MT = 0x10, + GC_INFO_HAS_GENERICS_INST_CONTEXT_MD = 0x20, + GC_INFO_HAS_GENERICS_INST_CONTEXT_THIS = 0x30, + GC_INFO_HAS_STACK_BASE_REGISTER = 0x40, + GC_INFO_WANTS_REPORT_ONLY_LEAF = 0x80, // GC_INFO_HAS_TAILCALLS = 0x80, for ARM and ARM64 + GC_INFO_HAS_EDIT_AND_CONTINUE_INFO = 0x100, + GC_INFO_REVERSE_PINVOKE_FRAME = 0x200, + + GC_INFO_FLAGS_BIT_SIZE_VERSION_1 = 9, + GC_INFO_FLAGS_BIT_SIZE = 10, + }; + + [Flags] + internal enum GcSlotFlags : uint + { + GC_SLOT_BASE = 0x0, + GC_SLOT_INTERIOR = 0x1, + GC_SLOT_PINNED = 0x2, + GC_SLOT_UNTRACKED = 0x4, + } + + [Flags] + internal enum GcStackSlotBase : uint + { + GC_CALLER_SP_REL = 0x0, + GC_SP_REL = 0x1, + GC_FRAMEREG_REL = 0x2, + + GC_SPBASE_FIRST = GC_CALLER_SP_REL, + GC_SPBASE_LAST = GC_FRAMEREG_REL, + } + + public readonly record struct InterruptibleRange(uint StartOffset, uint EndOffset); + + public readonly record struct GcSlotDesc + { + /* Register Slot */ + public readonly uint RegisterNumber; + + /* Stack Slot */ + public readonly int SpOffset; + public readonly GcStackSlotBase Base; + + /* Shared fields */ + public readonly GcSlotFlags Flags; + public readonly bool IsRegister; + + private GcSlotDesc(uint registerNumber, int spOffset, GcStackSlotBase slotBase, GcSlotFlags flags, bool isRegister = false) + { + RegisterNumber = registerNumber; + SpOffset = spOffset; + Base = slotBase; + Flags = flags; + IsRegister = isRegister; + } + + public static GcSlotDesc CreateRegisterSlot(uint registerNumber, GcSlotFlags flags) + => new GcSlotDesc(registerNumber, 0, 0, flags, isRegister: true); + + public static GcSlotDesc CreateStackSlot(int spOffset, GcStackSlotBase slotBase, GcSlotFlags flags) + => new GcSlotDesc(0, spOffset, slotBase, flags, isRegister: false); + } + + private readonly Target _target; + private readonly TargetPointer _pGcInfo; + private readonly uint _gcVersion; + private readonly NativeReader _reader; + private readonly RuntimeInfoArchitecture _arch; + private readonly bool PartiallyInterruptibleGCSupported = true; + + /* Decode State */ + private int _bitOffset; + private IEnumerator _decodePoints; + private List _completedDecodePoints = []; + + /* Header Fields */ + private bool _slimHeader; + private GcInfoHeaderFlags _headerFlags; + private uint _stackBaseRegister; + private uint _codeLength; + private uint _validRangeStart; + private uint _validRangeEnd; + private int _gsCookieStackSlot; + private int _pspSymStackSlot; + private int _genericsInstContextStackSlot; + private uint _sizeOfEnCPreservedArea; + private int _reversePInvokeFrameStackSlot; + private uint _fixedStackParameterScratchArea; + + /* Fields */ + + private uint _numSafePoints; + private uint _numInterruptibleRanges; + private List _interruptibleRanges = []; + + /* Slot Table Fields */ + private uint _numRegisters; + private uint _numUntrackedSlots; + private uint _numSlots; + private List _slots = []; + + public GcInfoDecoder(Target target, TargetPointer gcInfoAddress, uint gcVersion) + { + _target = target; + _pGcInfo = gcInfoAddress; + _gcVersion = gcVersion; + + TargetStream targetStream = new TargetStream(_target, _pGcInfo, /*arbitrary*/ 10000); + _reader = new NativeReader(targetStream, _target.IsLittleEndian); + + _arch = target.Contracts.RuntimeInfo.GetTargetArchitecture(); + + _decodePoints = Decode().GetEnumerator(); + } + + #region Decoding Methods + + private IEnumerable Decode() + { + IEnumerable headerDecodePoints = DecodeHeader(); + foreach (DecodePoints dp in headerDecodePoints) + yield return dp; + + IEnumerable bodyDecodePoints = DecodeBody(); + foreach (DecodePoints dp in bodyDecodePoints) + yield return dp; + + yield return DecodePoints.Complete; + } + + private IEnumerable DecodeBody() + { + IEnumerable safePoints = DecodeSafePoints(); + foreach (DecodePoints dp in safePoints) + yield return dp; + + IEnumerable interruptibleRanges = DecodeInterruptibleRanges(); + foreach (DecodePoints dp in interruptibleRanges) + yield return dp; + + IEnumerable slotTable = DecodeSlotTable(); + foreach (DecodePoints dp in slotTable) + yield return dp; + } + + private IEnumerable DecodeSlotTable() + { + if (_reader.ReadBits(1, ref _bitOffset) != 0) + { + _numRegisters = _reader.DecodeVarLengthUnsigned(TTraits.NUM_REGISTERS_ENCBASE, ref _bitOffset); + } + + uint numStackSlots = 0; + if (_reader.ReadBits(1, ref _bitOffset) != 0) + { + numStackSlots = _reader.DecodeVarLengthUnsigned(TTraits.NUM_STACK_SLOTS_ENCBASE, ref _bitOffset); + _numUntrackedSlots = _reader.DecodeVarLengthUnsigned(TTraits.NUM_UNTRACKED_SLOTS_ENCBASE, ref _bitOffset); + } + + _numSlots = _numRegisters + numStackSlots + _numUntrackedSlots; + _slots = new List((int)_numSlots); + + // Decode register slots + if (_numRegisters > 0) + { + uint regNum = _reader.DecodeVarLengthUnsigned(TTraits.REGISTER_ENCBASE, ref _bitOffset); + GcSlotFlags flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset); + + _slots.Add(GcSlotDesc.CreateRegisterSlot(regNum, flags)); + + for (int i = 1; i < _numRegisters; i++) + { + if (flags != 0) + { + regNum = _reader.DecodeVarLengthUnsigned(TTraits.REGISTER_ENCBASE, ref _bitOffset); + flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset); + } + else + { + regNum += _reader.DecodeVarLengthUnsigned(TTraits.REGISTER_DELTA_ENCBASE, ref _bitOffset) + 1; + } + + _slots.Add(GcSlotDesc.CreateRegisterSlot(regNum, flags)); + } + } + + // Decode stack slots + if (numStackSlots > 0) + { + GcStackSlotBase spBase = (GcStackSlotBase)_reader.ReadBits(2, ref _bitOffset); + int normSpOffset = _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_ENCBASE, ref _bitOffset); + int spOffset = TTraits.DenormalizeStackSlot(normSpOffset); + GcSlotFlags flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset); + + _slots.Add(GcSlotDesc.CreateStackSlot(spOffset, spBase, flags)); + + for (int i = 1; i < numStackSlots; i++) + { + spBase = (GcStackSlotBase)_reader.ReadBits(2, ref _bitOffset); + + if (flags != 0) + { + normSpOffset = _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_DELTA_ENCBASE, ref _bitOffset); + spOffset = TTraits.DenormalizeStackSlot(normSpOffset); + flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset); + } + else + { + normSpOffset += _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_DELTA_ENCBASE, ref _bitOffset) + 1; + spOffset = TTraits.DenormalizeStackSlot(normSpOffset); + } + + _slots.Add(GcSlotDesc.CreateStackSlot(spOffset, spBase, flags)); + } + } + + // Decode untracked slots + if (_numUntrackedSlots > 0) + { + GcStackSlotBase spBase = (GcStackSlotBase)_reader.ReadBits(2, ref _bitOffset); + int normSpOffset = _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_ENCBASE, ref _bitOffset); + int spOffset = TTraits.DenormalizeStackSlot(normSpOffset); + GcSlotFlags flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset); + + _slots.Add(GcSlotDesc.CreateStackSlot(spOffset, spBase, flags)); + + for (int i = 1; i < _numUntrackedSlots; i++) + { + spBase = (GcStackSlotBase)_reader.ReadBits(2, ref _bitOffset); + + if (flags != 0) + { + normSpOffset = _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_DELTA_ENCBASE, ref _bitOffset); + spOffset = TTraits.DenormalizeStackSlot(normSpOffset); + flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset); + } + else + { + normSpOffset += _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_DELTA_ENCBASE, ref _bitOffset) + 1; + spOffset = TTraits.DenormalizeStackSlot(normSpOffset); + } + + _slots.Add(GcSlotDesc.CreateStackSlot(spOffset, spBase, flags)); + } + } + + yield break; + } + + private IEnumerable DecodeInterruptibleRanges() + { + if (_numInterruptibleRanges == 0) + yield break; + + uint prevEndOffset = 0; + + uint lastInterruptibleRangeStopOffsetNormalized = 0; + + _interruptibleRanges = new List((int)_numInterruptibleRanges); + for (uint i = 0; i < _numInterruptibleRanges; i++) + { + uint normStartDelta = _reader.DecodeVarLengthUnsigned(TTraits.INTERRUPTIBLE_RANGE_DELTA1_ENCBASE, ref _bitOffset); + uint normStopDelta = _reader.DecodeVarLengthUnsigned(TTraits.INTERRUPTIBLE_RANGE_DELTA2_ENCBASE, ref _bitOffset) + 1; + + uint rangeStartOffsetNormalized = lastInterruptibleRangeStopOffsetNormalized + normStartDelta; + uint rangeStopOffsetNormalized = rangeStartOffsetNormalized + normStopDelta; + + uint rangeStartOffset = TTraits.DenormalizeCodeOffset(rangeStartOffsetNormalized); + uint rangeStopOffset = TTraits.DenormalizeCodeOffset(rangeStopOffsetNormalized); + + Debug.Assert(rangeStartOffset < rangeStopOffset); + Debug.Assert(rangeStartOffset >= prevEndOffset); + + lastInterruptibleRangeStopOffsetNormalized = rangeStopOffsetNormalized; + + _interruptibleRanges.Add(new(rangeStartOffset, rangeStopOffset)); + } + + yield break; + } + + private IEnumerable DecodeSafePoints() + { + // skip over safe point data + uint numBitsPerOffset = CeilOfLog2(TTraits.NormalizeCodeOffset(_codeLength)); + _bitOffset += (int)(numBitsPerOffset * _numSafePoints); + yield break; + } + + private IEnumerable DecodeHeader() + { + _slimHeader = _reader.ReadBits(1, ref _bitOffset) == 0; + + if (!_slimHeader) + { + return DecodeFatHeader(); + } + else + { + return DecodeSlimHeader(); + } + } + + private IEnumerable DecodeSlimHeader() + { + if (_reader.ReadBits(1, ref _bitOffset) != 0) + { + _headerFlags = GcInfoHeaderFlags.GC_INFO_HAS_STACK_BASE_REGISTER; + _stackBaseRegister = TTraits.DenormalizeStackBaseRegister(0); + } + else + { + _headerFlags = default; + _stackBaseRegister = TTraits.NO_STACK_BASE_REGISTER; + } + yield return DecodePoints.ReturnKind; + yield return DecodePoints.VarArg; + + _codeLength = TTraits.DenormalizeCodeLength(_reader.DecodeVarLengthUnsigned(TTraits.CODE_LENGTH_ENCBASE, ref _bitOffset)); + + // predecoding the rest of slim header does not require any reading. + _validRangeStart = 0; + _validRangeEnd = 0; + _gsCookieStackSlot = TTraits.NO_GS_COOKIE; + _pspSymStackSlot = TTraits.NO_PSP_SYM; + _genericsInstContextStackSlot = TTraits.NO_GENERICS_INST_CONTEXT; + _sizeOfEnCPreservedArea = TTraits.NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA; + _reversePInvokeFrameStackSlot = TTraits.NO_REVERSE_PINVOKE_FRAME; + + if (TTraits.HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA) + _fixedStackParameterScratchArea = 0; + + yield return DecodePoints.CodeLength; + yield return DecodePoints.PrologLength; + yield return DecodePoints.GSCookie; + yield return DecodePoints.PSPSym; + yield return DecodePoints.GenericInstContext; + yield return DecodePoints.EditAndContinue; + yield return DecodePoints.ReversePInvoke; + + if (PartiallyInterruptibleGCSupported) + { + _numSafePoints = _reader.DecodeVarLengthUnsigned(TTraits.NUM_SAFE_POINTS_ENCBASE, ref _bitOffset); + } + + _numInterruptibleRanges = 0; + } + + private IEnumerable DecodeFatHeader() + { + _headerFlags = (GcInfoHeaderFlags)_reader.ReadBits((int)GcInfoHeaderFlags.GC_INFO_FLAGS_BIT_SIZE, ref _bitOffset); + yield return DecodePoints.ReturnKind; + yield return DecodePoints.VarArg; + + _codeLength = TTraits.DenormalizeCodeLength(_reader.DecodeVarLengthUnsigned(TTraits.CODE_LENGTH_ENCBASE, ref _bitOffset)); + yield return DecodePoints.CodeLength; + + if (_headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_HAS_GS_COOKIE)) + { + // Note that normalization as a code offset can be different than + // normalization as code length + uint normCodeLength = TTraits.NormalizeCodeLength(_codeLength); + + // Decode prolog/epilog information + uint normPrologSize = _reader.DecodeVarLengthUnsigned(TTraits.NORM_PROLOG_SIZE_ENCBASE, ref _bitOffset) + 1; + uint normEpilogSize = _reader.DecodeVarLengthUnsigned(TTraits.NORM_EPILOG_SIZE_ENCBASE, ref _bitOffset); + + _validRangeStart = TTraits.DenormalizeCodeOffset(normPrologSize); + _validRangeEnd = TTraits.DenormalizeCodeOffset(normCodeLength - normEpilogSize); + Debug.Assert(_validRangeStart < _validRangeEnd); + } + else if ((_headerFlags & GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK) != GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_NONE) + { + // Decode prolog information + uint normPrologSize = _reader.DecodeVarLengthUnsigned(TTraits.NORM_PROLOG_SIZE_ENCBASE, ref _bitOffset) + 1; + _validRangeStart = TTraits.DenormalizeCodeOffset(normPrologSize); + // satisfy asserts that assume m_GSCookieValidRangeStart != 0 ==> m_GSCookieValidRangeStart < m_GSCookieValidRangeEnd + _validRangeEnd = _validRangeStart + 1; + } + else + { + _validRangeStart = 0; + _validRangeEnd = 0; + } + yield return DecodePoints.PrologLength; + + _gsCookieStackSlot = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_HAS_GS_COOKIE) ? + TTraits.DenormalizeStackSlot(_reader.DecodeVarLengthSigned(TTraits.GS_COOKIE_STACK_SLOT_ENCBASE, ref _bitOffset)) : + TTraits.NO_GS_COOKIE; + yield return DecodePoints.GSCookie; + + _pspSymStackSlot = TTraits.NO_PSP_SYM; + yield return DecodePoints.PSPSym; + + _genericsInstContextStackSlot = (_headerFlags & GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK) != GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_NONE ? + TTraits.DenormalizeStackSlot(_reader.DecodeVarLengthSigned(TTraits.GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE, ref _bitOffset)) : + TTraits.NO_GENERICS_INST_CONTEXT; + yield return DecodePoints.GenericInstContext; + + _stackBaseRegister = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_HAS_STACK_BASE_REGISTER) ? + TTraits.DenormalizeStackBaseRegister(_reader.DecodeVarLengthUnsigned(TTraits.STACK_BASE_REGISTER_ENCBASE, ref _bitOffset)) : + TTraits.NO_STACK_BASE_REGISTER; + + _sizeOfEnCPreservedArea = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_HAS_EDIT_AND_CONTINUE_INFO) ? + _reader.DecodeVarLengthUnsigned(TTraits.SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE, ref _bitOffset) : + TTraits.NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA; + yield return DecodePoints.EditAndContinue; + + _reversePInvokeFrameStackSlot = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_REVERSE_PINVOKE_FRAME) ? + TTraits.DenormalizeStackSlot(_reader.DecodeVarLengthSigned(TTraits.REVERSE_PINVOKE_FRAME_ENCBASE, ref _bitOffset)) : + TTraits.NO_REVERSE_PINVOKE_FRAME; + yield return DecodePoints.ReversePInvoke; + + if (TTraits.HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA) + { + _fixedStackParameterScratchArea = + TTraits.DenormalizeSizeOfStackArea(_reader.DecodeVarLengthUnsigned(TTraits.SIZE_OF_STACK_AREA_ENCBASE, ref _bitOffset)); + } + + if (PartiallyInterruptibleGCSupported) + { + _numSafePoints = _reader.DecodeVarLengthUnsigned(TTraits.NUM_SAFE_POINTS_ENCBASE, ref _bitOffset); + } + + _numInterruptibleRanges = _reader.DecodeVarLengthUnsigned(TTraits.NUM_INTERRUPTIBLE_RANGES_ENCBASE, ref _bitOffset); + } + + private void EnsureDecodedTo(DecodePoints point) + { + while (!_completedDecodePoints.Contains(point)) + { + if (!_decodePoints.MoveNext()) + return; // nothing more to decode + + _completedDecodePoints.Add(_decodePoints.Current); + } + } + + #endregion + #region Access Methods + + public uint GetCodeLength() + { + EnsureDecodedTo(DecodePoints.CodeLength); + return _codeLength; + } + + #endregion + #region Helper Methods + + private static uint CeilOfLog2(ulong value) + { + uint result = (uint)Math.Ceiling(Math.Log2(value)); + return result; + } + + #endregion +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoFactory.cs new file mode 100644 index 00000000000000..47135e41ab4861 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoFactory.cs @@ -0,0 +1,22 @@ +// 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 Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public sealed class GCInfoFactory : IContractFactory +{ + IGCInfo IContractFactory.CreateContract(Target target, int version) + { + RuntimeInfoArchitecture arch = target.Contracts.RuntimeInfo.GetTargetArchitecture(); + return (version, arch) switch + { + (1, RuntimeInfoArchitecture.X64) => new GCInfo_1(target), + (1, RuntimeInfoArchitecture.Arm64) => new GCInfo_1(target), + (1, RuntimeInfoArchitecture.Arm) => new GCInfo_1(target), + _ => default(GCInfo), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs new file mode 100644 index 00000000000000..66f80f4f1e2c91 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs @@ -0,0 +1,36 @@ +// 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 Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal class GCInfo_1 : IGCInfo where TTraits : IGCInfoTraits +{ + private readonly Target _target; + + internal GCInfo_1(Target target) + { + _target = target; + } + + IGCInfoHandle IGCInfo.DecodeGCInfo(TargetPointer gcInfoAddress, uint gcVersion) + => new GcInfoDecoder(_target, gcInfoAddress, gcVersion); + + uint IGCInfo.GetCodeLength(IGCInfoHandle gcInfoHandle) + { + GcInfoDecoder decoder = AssertCorrectHandle(gcInfoHandle); + return decoder.GetCodeLength(); + } + + private static GcInfoDecoder AssertCorrectHandle(IGCInfoHandle gcInfoHandle) + { + if (gcInfoHandle is not GcInfoDecoder handle) + { + throw new ArgumentException("Invalid GC info handle", nameof(gcInfoHandle)); + } + + return handle; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/AMD64GCInfoTraits.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/AMD64GCInfoTraits.cs new file mode 100644 index 00000000000000..1914b13d752682 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/AMD64GCInfoTraits.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +internal class AMD64GCInfoTraits : IGCInfoTraits +{ + public static uint DenormalizeStackBaseRegister(uint reg) => reg ^ 0x5u; + public static uint DenormalizeCodeLength(uint len) => len; + public static uint NormalizeCodeLength(uint len) => len; + public static uint DenormalizeCodeOffset(uint offset) => offset; + public static uint NormalizeCodeOffset(uint offset) => offset; + public static int DenormalizeStackSlot(int x) => x << 3; + public static uint DenormalizeSizeOfStackArea(uint size) => size << 3; + + public static int GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE => 6; + + public static int GS_COOKIE_STACK_SLOT_ENCBASE => 6; + public static int CODE_LENGTH_ENCBASE => 8; + + public static int STACK_BASE_REGISTER_ENCBASE => 3; + public static int SIZE_OF_STACK_AREA_ENCBASE => 3; + public static int SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE => 4; + public static int REVERSE_PINVOKE_FRAME_ENCBASE => 6; + public static int NUM_REGISTERS_ENCBASE => 2; + public static int NUM_STACK_SLOTS_ENCBASE => 2; + public static int NUM_UNTRACKED_SLOTS_ENCBASE => 1; + public static int NORM_PROLOG_SIZE_ENCBASE => 5; + public static int NORM_EPILOG_SIZE_ENCBASE => 3; + public static int INTERRUPTIBLE_RANGE_DELTA1_ENCBASE => 6; + public static int INTERRUPTIBLE_RANGE_DELTA2_ENCBASE => 6; + public static int REGISTER_ENCBASE => 3; + public static int REGISTER_DELTA_ENCBASE => 2; + public static int STACK_SLOT_ENCBASE => 6; + public static int STACK_SLOT_DELTA_ENCBASE => 4; + public static int NUM_SAFE_POINTS_ENCBASE => 2; + public static int NUM_INTERRUPTIBLE_RANGES_ENCBASE => 1; + + public static bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA => true; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/ARM64GCInfoTraits.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/ARM64GCInfoTraits.cs new file mode 100644 index 00000000000000..9bdb9b6fab052b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/ARM64GCInfoTraits.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +internal class ARM64GCInfoTraits : IGCInfoTraits +{ + public static uint DenormalizeStackBaseRegister(uint reg) => reg ^ 0x29u; + public static uint DenormalizeCodeLength(uint len) => len >> 2; + public static uint NormalizeCodeLength(uint len) => len << 2; + public static uint DenormalizeCodeOffset(uint offset) => offset >> 2; + public static uint NormalizeCodeOffset(uint offset) => offset << 2; + public static int DenormalizeStackSlot(int x) => x << 3; + public static uint DenormalizeSizeOfStackArea(uint size) => size << 3; + + public static int GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE => 6; + + public static int GS_COOKIE_STACK_SLOT_ENCBASE => 6; + public static int CODE_LENGTH_ENCBASE => 8; + + public static int STACK_BASE_REGISTER_ENCBASE => 2; + public static int SIZE_OF_STACK_AREA_ENCBASE => 3; + public static int SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE => 4; + public static int REVERSE_PINVOKE_FRAME_ENCBASE => 6; + public static int NUM_REGISTERS_ENCBASE => 3; + public static int NUM_STACK_SLOTS_ENCBASE => 2; + public static int NUM_UNTRACKED_SLOTS_ENCBASE => 1; + public static int NORM_PROLOG_SIZE_ENCBASE => 5; + public static int NORM_EPILOG_SIZE_ENCBASE => 3; + public static int INTERRUPTIBLE_RANGE_DELTA1_ENCBASE => 6; + public static int INTERRUPTIBLE_RANGE_DELTA2_ENCBASE => 6; + public static int REGISTER_ENCBASE => 3; + public static int REGISTER_DELTA_ENCBASE => 2; + public static int STACK_SLOT_ENCBASE => 6; + public static int STACK_SLOT_DELTA_ENCBASE => 4; + public static int NUM_SAFE_POINTS_ENCBASE => 3; + public static int NUM_INTERRUPTIBLE_RANGES_ENCBASE => 1; + + public static bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA => true; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/ARMGCInfoTraits.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/ARMGCInfoTraits.cs new file mode 100644 index 00000000000000..500e6b1867052d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/ARMGCInfoTraits.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +internal class ARMGCInfoTraits : IGCInfoTraits +{ + public static uint DenormalizeStackBaseRegister(uint reg) => (reg ^ 7u) + 4; + public static uint DenormalizeCodeLength(uint len) => len << 1; + public static uint NormalizeCodeLength(uint len) => len >> 1; + public static uint DenormalizeCodeOffset(uint offset) => offset << 1; + public static uint NormalizeCodeOffset(uint offset) => offset >> 1; + public static int DenormalizeStackSlot(int x) => x << 2; + public static uint DenormalizeSizeOfStackArea(uint size) => size << 2; + + public static int GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE => 5; + + public static int GS_COOKIE_STACK_SLOT_ENCBASE => 5; + public static int CODE_LENGTH_ENCBASE => 7; + + public static int STACK_BASE_REGISTER_ENCBASE => 1; + public static int SIZE_OF_STACK_AREA_ENCBASE => 3; + public static int SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE => 3; + public static int REVERSE_PINVOKE_FRAME_ENCBASE => 5; + public static int NUM_REGISTERS_ENCBASE => 2; + public static int NUM_STACK_SLOTS_ENCBASE => 3; + public static int NUM_UNTRACKED_SLOTS_ENCBASE => 3; + public static int NORM_PROLOG_SIZE_ENCBASE => 5; + public static int NORM_EPILOG_SIZE_ENCBASE => 3; + public static int INTERRUPTIBLE_RANGE_DELTA1_ENCBASE => 4; + public static int INTERRUPTIBLE_RANGE_DELTA2_ENCBASE => 6; + public static int REGISTER_ENCBASE => 2; + public static int REGISTER_DELTA_ENCBASE => 1; + public static int STACK_SLOT_ENCBASE => 6; + public static int STACK_SLOT_DELTA_ENCBASE => 4; + public static int NUM_SAFE_POINTS_ENCBASE => 3; + public static int NUM_INTERRUPTIBLE_RANGES_ENCBASE => 2; + + public static bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA => true; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/IGCInfoTraits.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/IGCInfoTraits.cs new file mode 100644 index 00000000000000..e9b43298005ca0 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/IGCInfoTraits.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +internal interface IGCInfoTraits +{ + static virtual int NO_GS_COOKIE { get; } = -1; + static virtual uint NO_STACK_BASE_REGISTER { get; } = 0xFFFFFFFF; + static virtual uint NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA { get; } = 0xFFFFFFFF; + static virtual int NO_GENERICS_INST_CONTEXT { get; } = -1; + static virtual int NO_REVERSE_PINVOKE_FRAME { get; } = -1; + static virtual int NO_PSP_SYM { get; } = -1; + + static abstract uint DenormalizeStackBaseRegister(uint reg); + static abstract uint DenormalizeCodeOffset(uint offset); + static abstract uint NormalizeCodeOffset(uint offset); + static abstract uint DenormalizeCodeLength(uint len); + static abstract uint NormalizeCodeLength(uint len); + static abstract int DenormalizeStackSlot(int x); + static abstract uint DenormalizeSizeOfStackArea(uint size); + + static abstract int GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE { get; } + + static abstract int GS_COOKIE_STACK_SLOT_ENCBASE { get; } + static abstract int CODE_LENGTH_ENCBASE { get; } + + static abstract int STACK_BASE_REGISTER_ENCBASE { get; } + static abstract int SIZE_OF_STACK_AREA_ENCBASE { get; } + static abstract int SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE { get; } + static abstract int REVERSE_PINVOKE_FRAME_ENCBASE { get; } + static abstract int NUM_REGISTERS_ENCBASE { get; } + static abstract int NUM_STACK_SLOTS_ENCBASE { get; } + static abstract int NUM_UNTRACKED_SLOTS_ENCBASE { get; } + static abstract int NORM_PROLOG_SIZE_ENCBASE { get; } + static abstract int NORM_EPILOG_SIZE_ENCBASE { get; } + static abstract int INTERRUPTIBLE_RANGE_DELTA1_ENCBASE { get; } + static abstract int INTERRUPTIBLE_RANGE_DELTA2_ENCBASE { get; } + static abstract int REGISTER_ENCBASE { get; } + static abstract int REGISTER_DELTA_ENCBASE { get; } + static abstract int STACK_SLOT_ENCBASE { get; } + static abstract int STACK_SLOT_DELTA_ENCBASE { get; } + static abstract int NUM_SAFE_POINTS_ENCBASE { get; } + static abstract int NUM_INTERRUPTIBLE_RANGES_ENCBASE { get; } + + static abstract bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs index f8c87879aafe50..93139200d8dac6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs @@ -254,9 +254,9 @@ public static TargetPointer GetReturnAddress(Target target, TargetPointer frameP private static bool InlinedCallFrameHasFunction(Data.InlinedCallFrame frame, Target target) { - if (target.PointerSize == 4) + if (target.PointerSize == sizeof(ulong)) { - return frame.Datum != TargetPointer.Null && (frame.Datum.Value & 0x1) != 0; + return frame.Datum != TargetPointer.Null && (frame.Datum.Value & 0x1) == 0; } else { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PortableEntryPoint.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PortableEntryPoint.cs new file mode 100644 index 00000000000000..591d6e83420f28 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PortableEntryPoint.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class PortableEntryPoint : IData +{ + static PortableEntryPoint IData.Create(Target target, TargetPointer address) => new PortableEntryPoint(target, address); + public PortableEntryPoint(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.PortableEntryPoint); + + MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + } + public TargetPointer MethodDesc { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs index add5603fdfd517..6af069e342b7ca 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/UnwindInfo.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Diagnostics.DataContractReader.Contracts; + namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class UnwindInfo : IData @@ -22,7 +24,7 @@ public UnwindInfo(Target target, TargetPointer address) // Otherwise, it starts with a bitfield header Header = target.Read(address); } - } + } public uint? FunctionLength { get; } public uint? Header { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 0afe774efde541..1935961f79e32d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -44,6 +44,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IDebugInfo)] = new DebugInfoFactory(), [typeof(ISHash)] = new SHashFactory(), [typeof(IGC)] = new GCFactory(), + [typeof(IGCInfo)] = new GCInfoFactory(), [typeof(INotifications)] = new NotificationsFactory(), [typeof(ISignatureDecoder)] = new SignatureDecoderFactory(), }; @@ -68,6 +69,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IDebugInfo DebugInfo => GetContract(); public override ISHash SHash => GetContract(); public override IGC GC => GetContract(); + public override IGCInfo GCInfo => GetContract(); public override INotifications Notifications => GetContract(); public override ISignatureDecoder SignatureDecoder => GetContract(); diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs index 0426e0cce705fd..6f5c1785ea150c 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs @@ -390,6 +390,26 @@ internal unsafe partial interface ISOSEnum int GetCount(uint* pCount); } +internal enum JitTypes +{ + TYPE_UNKNOWN = 0, + TYPE_JIT, + TYPE_PJIT, + TYPE_INTERPRETER +} + +internal struct DacpCodeHeaderData +{ + public ClrDataAddress GCInfo; + public JitTypes JITType; + public ClrDataAddress MethodDescPtr; + public ClrDataAddress MethodStart; + public uint MethodSize; + public ClrDataAddress ColdRegionStart; + public uint ColdRegionSize; + public uint HotRegionSize; +} + internal struct DacpFieldDescData { public CorElementType Type; @@ -473,7 +493,7 @@ internal unsafe partial interface ISOSDacInterface // JIT Data [PreserveSig] - int GetCodeHeaderData(ClrDataAddress ip, /*struct DacpCodeHeaderData*/ void* data); + int GetCodeHeaderData(ClrDataAddress ip, DacpCodeHeaderData* data); [PreserveSig] int GetJitManagerList(uint count, /*struct DacpJitManagerInfo*/ void* managers, uint* pNeeded); [PreserveSig] diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index cc8581fef57fd7..4f02db860dacd7 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -62,7 +62,7 @@ internal sealed unsafe partial class SOSDacImpl private readonly IXCLRDataProcess2? _legacyProcess2; private readonly ICLRDataEnumMemoryRegions? _legacyEnumMemory; - private enum CorTokenType: uint + private enum CorTokenType : uint { mdtTypeRef = 0x01000000, mdtTypeDef = 0x02000000, @@ -609,7 +609,6 @@ int ISOSDacInterface.GetClrWatsonBuckets(ClrDataAddress thread, void* pGenericMo { hr = ex.HResult; } - #if DEBUG if (_legacyImpl is not null) { @@ -630,8 +629,75 @@ int ISOSDacInterface.GetClrWatsonBuckets(ClrDataAddress thread, void* pGenericMo #endif return hr; } - int ISOSDacInterface.GetCodeHeaderData(ClrDataAddress ip, void* data) - => _legacyImpl is not null ? _legacyImpl.GetCodeHeaderData(ip, data) : HResults.E_NOTIMPL; + int ISOSDacInterface.GetCodeHeaderData(ClrDataAddress ip, DacpCodeHeaderData* data) + { + int hr = HResults.S_OK; + try + { + if (ip == 0 || data == null) + throw new ArgumentException(); + + IExecutionManager eman = _target.Contracts.ExecutionManager; + IGCInfo gcInfo = _target.Contracts.GCInfo; + + TargetCodePointer targetCodePointer = ip.ToTargetCodePointer(_target); + if (eman.GetCodeBlockHandle(targetCodePointer) is not CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.NonVirtualEntry2MethodDesc(targetCodePointer); + if (methodDesc == TargetPointer.Null) + throw new ArgumentException(); + data->MethodDescPtr = methodDesc.ToClrDataAddress(_target); + data->JITType = JitTypes.TYPE_UNKNOWN; + data->GCInfo = 0; + data->MethodStart = 0; + data->MethodSize = 0; + data->ColdRegionStart = 0; + } + else + { + data->MethodDescPtr = eman.GetMethodDesc(cbh).ToClrDataAddress(_target); + + data->JITType = (JitTypes)eman.GetJITType(cbh); + + eman.GetGCInfo(cbh, out TargetPointer pGcInfo, out uint gcVersion); + data->GCInfo = pGcInfo.ToClrDataAddress(_target); + + data->MethodStart = eman.GetStartAddress(cbh).Value; + + IGCInfoHandle gcInfoHandle = gcInfo.DecodeGCInfo(pGcInfo, gcVersion); + data->MethodSize = gcInfo.GetCodeLength(gcInfoHandle); + + eman.GetMethodRegionInfo(cbh, out uint hotRegionSize, out TargetPointer coldRegionStart, out uint coldRegionSize); + data->HotRegionSize = hotRegionSize; + data->ColdRegionSize = coldRegionSize; + data->ColdRegionStart = coldRegionStart.ToClrDataAddress(_target); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacyImpl is not null) + { + DacpCodeHeaderData dataLocal = default; + int hrLocal = _legacyImpl.GetCodeHeaderData(ip, &dataLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(data->MethodDescPtr == dataLocal.MethodDescPtr, $"cDAC: {data->MethodDescPtr:x}, DAC: {dataLocal.MethodDescPtr:x}"); + Debug.Assert(data->JITType == dataLocal.JITType, $"cDAC: {data->JITType}, DAC: {dataLocal.JITType}"); + Debug.Assert(data->GCInfo == dataLocal.GCInfo, $"cDAC: {data->GCInfo:x}, DAC: {dataLocal.GCInfo:x}"); + Debug.Assert(data->MethodStart == dataLocal.MethodStart, $"cDAC: {data->MethodStart:x}, DAC: {dataLocal.MethodStart:x}"); + Debug.Assert(data->MethodSize == dataLocal.MethodSize, $"cDAC: {data->MethodSize}, DAC: {dataLocal.MethodSize}"); + Debug.Assert(data->HotRegionSize == dataLocal.HotRegionSize, $"cDAC: {data->HotRegionSize}, DAC: {dataLocal.HotRegionSize}"); + Debug.Assert(data->ColdRegionStart == dataLocal.ColdRegionStart, $"cDAC: {data->ColdRegionStart:x}, DAC: {dataLocal.ColdRegionStart:x}"); + Debug.Assert(data->ColdRegionSize == dataLocal.ColdRegionSize, $"cDAC: {data->ColdRegionSize}, DAC: {dataLocal.ColdRegionSize}"); + } + } +#endif + return hr; + } int ISOSDacInterface.GetCodeHeapList(ClrDataAddress jitManager, uint count, void* codeHeaps, uint* pNeeded) => _legacyImpl is not null ? _legacyImpl.GetCodeHeapList(jitManager, count, codeHeaps, pNeeded) : HResults.E_NOTIMPL; int ISOSDacInterface.GetDacModuleHandle(void* phModule)