diff --git a/.github/skills/ci-pipeline-monitor/scripts/extract_failed_tests.py b/.github/skills/ci-pipeline-monitor/scripts/extract_failed_tests.py index b981fca7665a3f..dacac035c6a0d0 100644 --- a/.github/skills/ci-pipeline-monitor/scripts/extract_failed_tests.py +++ b/.github/skills/ci-pipeline-monitor/scripts/extract_failed_tests.py @@ -264,7 +264,7 @@ def main(): # Mark pipelines with 0 test results as inconclusive for name in zero_result_pipelines: conn.execute( - "UPDATE pipelines SET result = 'inconclusive', skip_reason = 'Build failed but no test failures detected via Test Results API' WHERE name = ?", + "UPDATE pipelines SET result = 'inconclusive', skip_reason = 'Build failed but no test failures detected via Test Results API, e.g., due to a cancelled leg' WHERE name = ?", (name,) ) if zero_result_pipelines: diff --git a/docs/design/coreclr/jit/investigate-stress.md b/docs/design/coreclr/jit/investigate-stress.md index a3b09019835530..0a70f8ae6d4410 100644 --- a/docs/design/coreclr/jit/investigate-stress.md +++ b/docs/design/coreclr/jit/investigate-stress.md @@ -22,7 +22,7 @@ Enabling GC Hole Stress causes GCs to always occur in specific locations and tha - **0x2** – GC on transitions to Preemptive GC. - **0x4** – GC on every allowable JITed instr. - **0x8** – GC on every allowable R2R instr. -- **0xF** – GC only on a unique stack trace. +- **0x10** – GC only on a unique stack trace. ### Common combinations diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index cf7163569789f9..dc906398b10867 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -57,6 +57,9 @@ struct CodeBlockHandle bool IsFunclet(CodeBlockHandle codeInfoHandle); // Returns true if the code block is specifically a filter funclet bool IsFilterFunclet(CodeBlockHandle codeInfoHandle); + + // Finds the ReadyToRun module that contains the given address. + TargetPointer FindReadyToRunModule(TargetPointer address); ``` ```csharp @@ -501,6 +504,24 @@ After obtaining the clause array bounds, the common iteration logic classifies e `IsFilterFunclet` first checks `IsFunclet`. If the code block is a funclet, it retrieves the EH clauses for the method and checks whether any filter clause's handler offset matches the funclet's relative offset. If a match is found, the funclet is a filter funclet. +### FindReadyToRunModule + +`FindReadyToRunModule` locates the ReadyToRun module whose PE image contains the given address. Unlike `GetCodeBlockHandle` (which only matches code regions), this API matches against the full PE image range - including data sections such as import tables. This is used in GCRefMap resolution as it requires finding the module that owns an import section indirection address, which is in the data section rather than the code section. + +```csharp +TargetPointer IExecutionManager.FindReadyToRunModule(TargetPointer address) +{ + // Use the RangeSectionMap to find the RangeSection containing the address. + // ReadyToRun range sections cover the entire PE image (code + data), + // so this works for import section addresses used by GCRefMap lookup. + RangeSection range = RangeSection.Find(target, topRangeSectionMap, address); + if (range.Data is null) + return TargetPointer.Null; + + return range.Data.R2RModule; +} +``` + ### EE JIT Manager and Code Heap Info ```csharp diff --git a/docs/design/datacontracts/GCInfo.md b/docs/design/datacontracts/GCInfo.md index c878957aea5001..963d7eb21514bc 100644 --- a/docs/design/datacontracts/GCInfo.md +++ b/docs/design/datacontracts/GCInfo.md @@ -2,6 +2,8 @@ This contract is for fetching information related to GCInfo associated with native code. Currently, this contract does not support x86 architecture. +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. + ## APIs of contract ```csharp @@ -19,6 +21,40 @@ IGCInfoHandle DecodeInterpreterGCInfo(TargetPointer gcInfoAddress, uint gcVersio // Fetches length of code as reported in GCInfo uint GetCodeLength(IGCInfoHandle handle); + +// Returns the stack base register number decoded from GCInfo +uint GetStackBaseRegister(IGCInfoHandle handle); + +// Returns the list of interruptible code offset ranges from the GCInfo +IReadOnlyList GetInterruptibleRanges(IGCInfoHandle handle); + +// Returns all live GC slots at the given instruction offset +IReadOnlyList EnumerateLiveSlots(IGCInfoHandle handle, uint instructionOffset, GcSlotEnumerationOptions options); +``` + +```csharp +// Describes a code region where the GC can safely interrupt execution. +public readonly record struct InterruptibleRange( + uint StartOffset, // Start of the interruptible region (byte offset from method start) + uint EndOffset); // End of the interruptible region, exclusive (byte offset from method start) + +// Describes a live GC slot at a given instruction offset. +public readonly record struct LiveSlot( + bool IsRegister, // True if the slot is a CPU register; false if stack location + uint RegisterNumber, // Register number (meaningful only when IsRegister is true) + int SpOffset, // Stack offset from the base (meaningful only when IsRegister is false) + uint SpBase, // Stack base: 0 = CALLER_SP_REL, 1 = SP_REL, 2 = FRAMEREG_REL + uint GcFlags); // GC slot flags: 0x1 = interior pointer, 0x2 = pinned + +// Options controlling which GC slots are reported by EnumerateLiveSlots. +public record struct GcSlotEnumerationOptions +{ + bool IsActiveFrame; // True if this is the active (leaf) stack frame + bool IsExecutionAborted; // True if execution was interrupted by an exception + bool IsParentOfFuncletStackFrame; // True if a funclet already reported GC references + bool SuppressUntrackedSlots; // True to suppress untracked slots (e.g., filter funclets) + bool ReportFPBasedSlotsOnly; // True to report only frame-register-relative stack slots +} ``` ## Version 1 @@ -44,10 +80,6 @@ Constants: | `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. @@ -312,7 +344,7 @@ Signed values use the same encoding as unsigned, but with sign considerations: ### Implementation -The GCInfo contract implementation follows this process: +The GCInfo decoder uses **lazy sequential decoding** — data is decoded on demand as APIs are called, and each section of the bitstream is decoded at most once. The decoder tracks a set of `DecodePoints` that represent completion of each section. When an API like `GetCodeLength()` or `GetInterruptibleRanges()` is called, the decoder advances through the bitstream until the requested data has been decoded. ```csharp IGCInfoHandle DecodePlatformSpecificGCInfo(TargetPointer gcInfoAddress, uint gcVersion) @@ -326,13 +358,231 @@ IGCInfoHandle DecodeInterpreterGCInfo(TargetPointer gcInfoAddress, uint gcVersio // Create a new decoder instance using the interpreter encoding return new GcInfoDecoder(target, gcInfoAddress, gcVersion); } +``` + +#### Header Decoding + +The first bit of the GCInfo bitstream determines whether the header is **slim** or **fat**. + +**Slim Header** (first bit = 0): + +The slim header is a compact encoding for simple methods. It reads only a few fields: + +``` +isSlimHeader = ReadBits(1) // 0 = slim +usingStackBaseRegister = ReadBits(1) +if usingStackBaseRegister: + stackBaseRegister = DenormalizeStackBaseRegister(0) +codeLength = DenormalizeCodeLength(DecodeVarLengthUnsigned(CODE_LENGTH_ENCBASE)) +numSafePoints = DecodeVarLengthUnsigned(NUM_SAFE_POINTS_ENCBASE) +numInterruptibleRanges = 0 // slim header never has interruptible ranges +``` + +All optional fields (GS cookie, PSP symbol, generics context, EnC info, reverse P/Invoke) default to their sentinel "not present" values. + +**Fat Header** (first bit = 1): + +The fat header contains a full flags bitfield and conditionally-present optional fields: + +``` +isSlimHeader = ReadBits(1) // 1 = fat +headerFlags = ReadBits(GC_INFO_FLAGS_BIT_SIZE) // 10 bits +codeLength = DenormalizeCodeLength(DecodeVarLengthUnsigned(CODE_LENGTH_ENCBASE)) + +// Prolog/epilog sizes (conditional on GS cookie or generics context) +if HAS_GS_COOKIE: + normPrologSize = DecodeVarLengthUnsigned(NORM_PROLOG_SIZE_ENCBASE) + 1 + normEpilogSize = DecodeVarLengthUnsigned(NORM_EPILOG_SIZE_ENCBASE) +elif HAS_GENERICS_INST_CONTEXT: + normPrologSize = DecodeVarLengthUnsigned(NORM_PROLOG_SIZE_ENCBASE) + 1 + +// Optional fields (each conditional on its header flag) +if HAS_GS_COOKIE: + gsCookieStackSlot = DenormalizeStackSlot(DecodeVarLengthSigned(GS_COOKIE_STACK_SLOT_ENCBASE)) +if HAS_GENERICS_INST_CONTEXT: + genericsInstContextStackSlot = DenormalizeStackSlot(DecodeVarLengthSigned(...)) +if HAS_STACK_BASE_REGISTER: + stackBaseRegister = DenormalizeStackBaseRegister(DecodeVarLengthUnsigned(...)) +if HAS_EDIT_AND_CONTINUE_INFO: + sizeOfEnCPreservedArea = DecodeVarLengthUnsigned(...) + if ARM64: sizeOfEnCFixedStackFrame = DecodeVarLengthUnsigned(...) +if REVERSE_PINVOKE_FRAME: + reversePInvokeFrameStackSlot = DenormalizeStackSlot(DecodeVarLengthSigned(...)) +if HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA: // platform-dependent + fixedStackParameterScratchArea = DenormalizeSizeOfStackArea(DecodeVarLengthUnsigned(...)) + +numSafePoints = DecodeVarLengthUnsigned(NUM_SAFE_POINTS_ENCBASE) +numInterruptibleRanges = DecodeVarLengthUnsigned(NUM_INTERRUPTIBLE_RANGES_ENCBASE) +``` + +#### Body Decoding + +Following the header, the GCInfo body contains data sections that must be decoded in strict order: + +##### 1. Safe Point Offsets + +Safe points (also called call sites) are code offsets where the GC can safely interrupt execution for partially-interruptible methods. Each offset is encoded as a fixed-width bitfield: + +``` +numBitsPerOffset = CeilOfLog2(NormalizeCodeOffset(codeLength)) +for each safe point: + offset = ReadBits(numBitsPerOffset) // normalized code offset +``` + +The offsets are stored in sorted order to enable binary search during `EnumerateLiveSlots`. + +##### 2. Interruptible Ranges + +Interruptible ranges define code regions where the method is **fully interruptible** — the GC can interrupt at any instruction within these ranges. Each range is encoded as a pair of delta-compressed, normalized offsets: + +``` +lastStopNormalized = 0 + +for each range: + startDelta = DecodeVarLengthUnsigned(INTERRUPTIBLE_RANGE_DELTA1_ENCBASE) + stopDelta = DecodeVarLengthUnsigned(INTERRUPTIBLE_RANGE_DELTA2_ENCBASE) + 1 + + startNormalized = lastStopNormalized + startDelta + stopNormalized = startNormalized + stopDelta + + startOffset = DenormalizeCodeOffset(startNormalized) + stopOffset = DenormalizeCodeOffset(stopNormalized) + + emit InterruptibleRange(startOffset, stopOffset) + lastStopNormalized = stopNormalized +``` + +##### 3. Slot Table + +The slot table describes all GC-tracked locations used by the method. It has three sections decoded in order: register slots, tracked stack slots, and untracked stack slots. +**Slot counts** are encoded with presence bits: + +``` +if ReadBits(1): // has register slots + numRegisters = DecodeVarLengthUnsigned(NUM_REGISTERS_ENCBASE) +if ReadBits(1): // has stack/untracked slots + numStackSlots = DecodeVarLengthUnsigned(NUM_STACK_SLOTS_ENCBASE) + numUntrackedSlots = DecodeVarLengthUnsigned(NUM_UNTRACKED_SLOTS_ENCBASE) +``` + +**Register slots** use delta encoding when consecutive slots share the same flags: + +``` +// First slot: absolute register number + 2-bit flags +regNum = DecodeVarLengthUnsigned(REGISTER_ENCBASE) +flags = ReadBits(2) + +// Subsequent slots: +if previousFlags != 0: + regNum = DecodeVarLengthUnsigned(REGISTER_ENCBASE) // absolute + flags = ReadBits(2) +else: + regNum += DecodeVarLengthUnsigned(REGISTER_DELTA_ENCBASE) + 1 // delta + // flags inherited from previous +``` + +**Stack slots** follow a similar delta encoding pattern: + +``` +// First slot: base (2 bits) + normalized offset + flags (2 bits) +spBase = ReadBits(2) // CALLER_SP_REL, SP_REL, or FRAMEREG_REL +normSpOffset = DecodeVarLengthSigned(STACK_SLOT_ENCBASE) +spOffset = DenormalizeStackSlot(normSpOffset) +flags = ReadBits(2) + +// Subsequent slots: +spBase = ReadBits(2) +if previousFlags != 0: + normSpOffset = DecodeVarLengthSigned(STACK_SLOT_ENCBASE) // absolute + flags = ReadBits(2) +else: + normSpOffset += DecodeVarLengthUnsigned(STACK_SLOT_DELTA_ENCBASE) // delta + // flags inherited from previous +``` + +Untracked slots use the same encoding as tracked stack slots. + +The 2-bit slot flags are: + +| Flag | Value | Meaning | +| --- | --- | --- | +| `GC_SLOT_BASE` | 0x0 | Normal object reference | +| `GC_SLOT_INTERIOR` | 0x1 | Interior pointer (points inside an object) | +| `GC_SLOT_PINNED` | 0x2 | Pinned object reference | + +##### 4. Live State Data + +Following the slot table, the remaining bitstream contains per-safe-point and per-chunk liveness information used by `EnumerateLiveSlots` to determine which slots are live at a given instruction offset. This data uses either a direct 1-bit-per-slot encoding or RLE (run-length encoding) compression for methods with many tracked slots. + +For **partially interruptible** methods (at safe points), each safe point has a bitvector indicating which tracked slots are live. An optional indirection table allows sharing identical bitvectors across safe points. + +For **fully interruptible** methods (within interruptible ranges), the interruptible region is divided into fixed-size chunks (`NUM_NORM_CODE_OFFSETS_PER_CHUNK = 64` normalized offsets). Each chunk records a "could be live" bitvector, a final state bitvector, and transition points within the chunk where slot liveness changes. + +### EnumerateLiveSlots + +`EnumerateLiveSlots` determines which GC-tracked slots (registers and stack locations) are live at a given instruction offset, then reports each live slot via a callback. The algorithm handles two distinct cases depending on whether the instruction offset falls at a **safe point** (partially-interruptible) or within an **interruptible range** (fully-interruptible). + +**Input**: instruction offset, `GcSlotEnumerationOptions`, slot report callback. + +**Step 1 — Find safe point**: Search the safe point offset table for an exact match against the normalized instruction offset. If found, the safe point index is used for the partially-interruptible path. + +**Step 2 — Partially-interruptible path** (safe point found, not `ExecutionAborted`): + +Each safe point has a bitvector with one bit per tracked slot. If the bit is set, the slot is live. An optional **indirection table** allows sharing identical bitvectors across safe points — when present, each safe point stores an offset into a deduplicated bitvector table. The bitvectors may use either direct 1-bit-per-slot encoding or **RLE** (run-length encoding) for methods with many tracked slots. + +**Step 3 — Fully-interruptible path** (no safe point match, offset is within an interruptible range): + +The total interruptible length is computed by summing all interruptible range sizes. A **pseudo-offset** maps the instruction offset into this linear space. The interruptible region is divided into fixed-size **chunks** of 64 normalized offsets each. + +For each chunk, the encoding stores: +- A **couldBeLive** bitvector identifying which slots may be live anywhere in the chunk (1-bit-per-slot or RLE). +- A **finalState** bit per couldBeLive slot indicating liveness at the end of the chunk. +- **Transition points** within the chunk where each slot's liveness toggles. + +To determine liveness at the target offset: start from the chunk's final state, then apply any transitions that occur *after* the target offset (toggling the state backwards). A slot is live if its final state (after toggle adjustment) is 1. + +**Step 4 — Report untracked slots**: Untracked slots are always live (they represent stack locations the JIT doesn't track at each safe point). They are reported unconditionally unless `ParentOfFuncletStackFrame` or `NoReportUntracked` flags are set. Untracked slots are reported with `reportScratchSlots=true` since the JIT may produce untracked scratch register slots for interior pointers. + +**Slot filtering**: Before reporting any slot, the algorithm checks: +- **Scratch registers**: Only reported for the active/leaf frame (`ActiveStackFrame` flag). +- **Scratch stack slots**: Only reported for the active/leaf frame (slots in the outgoing/scratch area). +- **FP-based-only mode** (`ReportFPBasedSlotsOnly`): Only frame-register-relative stack slots are reported; all register slots and non-frame-relative stack slots are skipped. + +#### API Implementations + +All APIs use lazy decoding — the GCInfo bitstream is decoded up to the required point on first access, and cached for subsequent calls. + +```csharp uint GetCodeLength(IGCInfoHandle handle) { - // Cast to the appropriate decoder type and return the decoded code length - GcInfoDecoder decoder = (GcInfoDecoder)handle; - return decoder.GetCodeLength(); + // Ensure header is decoded, then return the code length field. +} + +uint GetStackBaseRegister(IGCInfoHandle handle) +{ + // Ensure header is decoded through the stack base register field, + // then return the denormalized register number (e.g., RBP on x64). } -``` -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. +IReadOnlyList GetInterruptibleRanges(IGCInfoHandle handle) +{ + // Ensure header and body are decoded through interruptible ranges, + // then return the decoded range list. +} + +IReadOnlyList EnumerateLiveSlots(IGCInfoHandle handle, + uint instructionOffset, GcSlotEnumerationOptions options) +{ + // Ensure header, body, and slot table are fully decoded. + // Then execute the EnumerateLiveSlots algorithm described above: + // 1. Find safe point match for the normalized instruction offset + // 2. If found: read the per-safe-point bitvector (partially-interruptible path) + // 3. If not found: compute pseudo-offset into interruptible ranges, + // locate the chunk, read couldBeLive/finalState/transitions + // (fully-interruptible path) + // 4. Report untracked slots unconditionally (unless SuppressUntrackedSlots) + // 5. Apply slot filtering (scratch registers, FP-based-only mode) + // Collect each live slot into a list and return it. +} +``` diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index cefb2d8d5548e4..ee3ef4f1b30eea 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -80,7 +80,7 @@ IEnumerable GetInstantiatedMethods(ModuleHandle handle); bool IsProbeExtensionResultValid(ModuleHandle handle); ModuleFlags GetFlags(ModuleHandle handle); bool IsReadyToRun(ModuleHandle handle); -bool TryGetSimpleName(ModuleHandle handle, out string simpleName); +string GetSimpleName(ModuleHandle handle); string GetPath(ModuleHandle handle); string GetFileName(ModuleHandle handle); TargetPointer GetLoaderAllocator(ModuleHandle handle); @@ -622,14 +622,11 @@ ModuleFlags GetFlags(ModuleHandle handle) return GetFlags(target.Read(handle.Address + /* Module::Flags offset */)); } -bool TryGetSimpleName(ModuleHandle handle, out string simpleName) +string GetSimpleName(ModuleHandle handle) { TargetPointer simpleNameStart = target.ReadPointer(handle.Address + /* Module::SimpleName offset */); - if (simpleNameStart == TargetPointer.Null) - return false; byte[] simpleNameBytes = // Read from target starting at simpleNameStart until null terminator - simpleName = // convert to string, throw on invalid UTF-8 - return true; + return // convert to string, throw on invalid UTF-8 } string GetPath(ModuleHandle handle) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index d3f86c8bf0d74d..08adc08c19855b 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -190,6 +190,14 @@ partial interface IRuntimeTypeSystem : IContract // Return true if a MethodDesc represents an IL stub with a special MethodDesc context arg public virtual bool HasMDContextArg(MethodDescHandle); + // Return true if the method requires a hidden instantiation argument (generic context parameter). + // Corresponds to native MethodDesc::RequiresInstArg(). + public virtual bool RequiresInstArg(MethodDescHandle methodDesc); + + // Return true if the method uses the async calling convention. + // Corresponds to native MethodDesc::IsAsyncMethod(). + public virtual bool IsAsyncMethod(MethodDescHandle methodDesc); + // Return true if a MethodDesc is in a collectible module public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc); @@ -1150,6 +1158,7 @@ And the following enumeration definitions HasMethodImpl = 0x0010, HasNativeCodeSlot = 0x0020, HasAsyncMethodData = 0x0040, + Static = 0x0080, // Mask for the above flags MethodDescAdditionalPointersMask = 0x0038, #endredion Additional pointers @@ -1600,6 +1609,57 @@ Determining if a method is an async thunk method: } ``` +Determining if a method requires a hidden instantiation argument (generic context parameter): + +```csharp + public bool RequiresInstArg(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + + // RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface) + if (!IsSharedByGenericInstantiations(md)) + return false; + + if (HasMethodInstantiation(md)) + return true; + + // md.IsStatic reads from MethodDescFlags.Static (0x0080) + if (md.IsStatic) + return true; + + MethodTable mt = _methodTables[md.MethodTable]; + return mt.Flags.IsInterface || mt.Flags.IsValueType; + } + + private bool IsSharedByGenericInstantiations(MethodDesc md) + { + if (md.Classification == MethodClassification.Instantiated) + { + InstantiatedMethodDesc imd = AsInstantiatedMethodDesc(md); + if (imd.IsWrapperStubWithInstantiations) + return false; + if (/* Flags2 of InstantiatedMethodDesc has SharedMethodInstantiation set */) + return true; + } + MethodTable mt = _methodTables[md.MethodTable]; + return mt.IsCanonMT && mt.Flags.HasInstantiation; + } +``` + +Determining if a method uses the async calling convention: + +```csharp + public bool IsAsyncMethod(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + if (!md.HasAsyncMethodData) + return false; + + Data.AsyncMethodData asyncData = // Read AsyncMethodData from the async method data optional slot + return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.AsyncCall); + } +``` + Determining if a method is a wrapper stub (unboxing or instantiating): ```csharp diff --git a/docs/design/datacontracts/Signature.md b/docs/design/datacontracts/Signature.md new file mode 100644 index 00000000000000..e0eb96ab807a17 --- /dev/null +++ b/docs/design/datacontracts/Signature.md @@ -0,0 +1,71 @@ +# Contract Signature + +This contract describes the format of method, field, and local-variable signatures stored in target memory. Signatures use the ECMA-335 §II.23.2 format with CoreCLR-internal element types added by the runtime. + +## Internal element types + +The runtime extends the standard ECMA-335 element type encoding with values that may appear in signatures stored in target memory: + +| Encoding | Value | Layout following the tag | +| --- | --- | --- | +| `ELEMENT_TYPE_INTERNAL` | `0x21` | a target-sized pointer to a runtime `TypeHandle` | +| `ELEMENT_TYPE_CMOD_INTERNAL` | `0x22` | one byte (`1` = required, `0` = optional), then a target-sized pointer to a runtime `TypeHandle` | + +These tags are used in signatures generated internally by the runtime that are not persisted to a managed image. They are defined alongside the standard ECMA-335 element types in `src/coreclr/inc/corhdr.h`. Their literal values are part of this contract -- changing them is a breaking change. + +## APIs of contract + +```csharp +TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| _none_ | | | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| _none_ | | | + +Contracts used: +| Contract Name | +| --- | +| RuntimeTypeSystem | +| Loader | +| EcmaMetadata | + +Constants: +| Constant Name | Meaning | Value | +| --- | --- | --- | +| `ELEMENT_TYPE_INTERNAL` | runtime-internal element type tag for an internal `TypeHandle` | `0x21` | +| `ELEMENT_TYPE_CMOD_INTERNAL` | runtime-internal element type tag for an internal modified type | `0x22` | + +Decoding a signature follows the ECMA-335 §II.23.2 grammar. For all standard element types, decoding behaves identically to `System.Reflection.Metadata.SignatureDecoder`. When the decoder encounters one of the runtime-internal tags above, it reads the target-sized pointer (and optional `required` byte for `ELEMENT_TYPE_CMOD_INTERNAL`) from the signature blob and resolves it to a runtime `TypeHandle`. + +The decoder is implemented as `RuntimeSignatureDecoder` -- a clone of SRM's `SignatureDecoder` with added support for the runtime-internal element types. The clone takes an additional `Target` so internal-type pointers can be sized for the target architecture. Provider implementations implement `IRuntimeSignatureTypeProvider` -- a superset of `System.Reflection.Metadata.ISignatureTypeProvider` -- adding methods for the runtime-internal element types: + +```csharp +TType GetInternalType(TargetPointer typeHandlePointer); +TType GetInternalModifiedType(TargetPointer typeHandlePointer, TType unmodifiedType, bool isRequired); +``` + +The contract's provider resolves these pointers through `RuntimeTypeSystem.GetTypeHandle`. Standard ECMA-335 element types resolve through `RuntimeTypeSystem.GetPrimitiveType` and `RuntimeTypeSystem.GetConstructedType`. Generic type parameters (`VAR`) and generic method parameters (`MVAR`) resolve via `RuntimeTypeSystem.GetInstantiation` and `RuntimeTypeSystem.GetGenericMethodInstantiation` respectively, using a `TypeHandle` (for generic types) or `MethodDescHandle` (for generic methods) generic context. `GetTypeFromDefinition` and `GetTypeFromReference` resolve tokens via the module's `TypeDefToMethodTableMap` / `TypeRefToMethodTableMap`; cross-module references and `GetTypeFromSpecification` are not currently implemented. + +```csharp +TypeHandle ISignature.DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) +{ + SignatureTypeProvider provider = new(_target, moduleHandle); + MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!; + BlobReader blobReader = mdReader.GetBlobReader(blobHandle); + RuntimeSignatureDecoder decoder = new(provider, _target, mdReader, ctx); + return decoder.DecodeFieldSignature(ref blobReader); +} +``` + +### Other consumers + +`RuntimeSignatureDecoder` is shared infrastructure within the cDAC. Other contracts construct their own decoder and provider directly when they need to decode method or local signatures rather than going through this contract. For example, the [StackWalk](./StackWalk.md) contract uses `RuntimeSignatureDecoder` with a GC-specific provider to classify method parameters during signature-based GC reference scanning. diff --git a/docs/design/datacontracts/SignatureDecoder.md b/docs/design/datacontracts/SignatureDecoder.md deleted file mode 100644 index 08f41f49917dd3..00000000000000 --- a/docs/design/datacontracts/SignatureDecoder.md +++ /dev/null @@ -1,70 +0,0 @@ -# Contract SignatureDecoder - -This contract encapsulates signature decoding in the cDAC. - -## APIs of contract - -```csharp -TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx); -``` - -## Version 1 - -In version 1 of the SignatureDecoder contract we take advantage of the System.Reflection.Metadata signature decoding. We implement a SignatureTypeProvider that inherits from System.Reflection.Metadata ISignatureTypeProvider. - -Data descriptors used: -| Data Descriptor Name | Field | Meaning | -| --- | --- | --- | - - -Global variables used: -| Global Name | Type | Purpose | -| --- | --- | --- | - - -Contracts used: -| Contract Name | -| --- | -| RuntimeTypeSystem | -| Loader | -| EcmaMetadata | - -### SignatureTypeProvider -The cDAC implements the ISignatureTypeProvider with TType=TypeHandle. TGenericContext can either be a MethodDescHandle or TypeHandle; MethodDescHandle context is used to look up generic method parameters, and TypeHandle context is used to look up generic type parameters. - -A cDAC SignatureTypeProvider is instantiated over a Module which is used to lookup types. - -The following ISignatureTypeProvider APIs are trivially implemented using RuntimeTypeSystem.GetPrimitiveType and RuntimeTypeSystem.GetConstructedType: - -* GetArrayType - GetConstructedType -* GetByReferenceType - GetConstructedType -* GetFunctionPointerType - Implemented as primitive IntPtr type -* GetGenericInstantiation - GetConstructedType -* GetModifiedType - Returns unmodified type -* GetPinnedType - Returns unpinned type -* GetPointerType - GetConstructedType -* GetPrimitiveType - GetConstructedType -* GetSZArrayType - GetConstructedType - -GetGenericMethodParameter is only supported when TGenericContext=MethodDescHandle and looks up the method parameters from the context using RuntimeTypeSystem.GetGenericMethodInstantiation. - -GetGenericTypeParameter is only supported when TGenericContext=TypeHandle and looks up the type parameters from the context using RuntimeTypeSystem.GetInstantiation. - -GetTypeFromDefinition uses the SignatureTypeProvider's ModuleHandle to lookup the given Token in the Module's TypeDefToMethodTableMap. If a value is not found return null. - -GetTypeFromReference uses the SignatureTypeProvider's ModuleHandle to lookup the given Token in the Module's TypeRefToMethodTableMap. If a value is not found return null.The implementation when the type exists in a different module is incomplete. - -GetTypeFromSpecification is not currently implemented. - - -### APIs -```csharp -TypeHandle ISignatureDecoder.DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) -{ - SignatureTypeProvider provider = new(_target, moduleHandle); - MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!; - BlobReader blobReader = mdReader.GetBlobReader(blobHandle); - SignatureDecoder decoder = new(provider, mdReader, ctx); - return decoder.DecodeFieldSignature(ref blobReader); -} -``` diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 9d254591ddb0ac..e8d88ed9957bca 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -28,6 +28,10 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle); // Gets the instruction pointer from the current frame's context. TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle); + +// Walks the stack and returns all GC references found on each frame. +// This is the primary API for GC reference enumeration, used by SOSDacImpl.GetStackReferences. +IReadOnlyList WalkStackReferences(ThreadData threadData); ``` ## Version 1 @@ -60,12 +64,19 @@ This contract depends on the following descriptors: | `StubDispatchFrame` | `MethodDescPtr` | Pointer to Frame's method desc | | `StubDispatchFrame` | `RepresentativeMTPtr` | Pointer to Frame's method table pointer | | `StubDispatchFrame` | `RepresentativeSlot` | Frame's method table slot | +| `StubDispatchFrame` | `Indirection` | Import slot pointer for GCRefMap resolution via `FindReadyToRunModule` | +| `ExternalMethodFrame` | `Indirection` | Import slot pointer for GCRefMap resolution via `FindReadyToRunModule` | +| `DynamicHelperFrame` | `DynamicHelperFrameFlags` | Flags indicating which argument registers contain GC references | | `TransitionBlock` | `ReturnAddress` | Return address associated with the TransitionBlock | | `TransitionBlock` | `CalleeSavedRegisters` | Platform specific CalleeSavedRegisters struct associated with the TransitionBlock | -| `TransitionBlock` (arm) | `ArgumentRegisters` | ARM specific `ArgumentRegisters` struct | +| `TransitionBlock` | `OffsetOfArgs` | Byte offset of stack arguments (first arg after registers) = `sizeof(TransitionBlock)` | +| `TransitionBlock` | `ArgumentRegisters` | Byte offset of the argument registers area within the TransitionBlock | +| `TransitionBlock` | `FirstGCRefMapSlot` | Byte offset where GCRefMap slot enumeration begins. ARM64: RetBuffArgReg offset; others: ArgumentRegisters offset | +| `ReadyToRunInfo` | `ImportSections` | Pointer to array of `READYTORUN_IMPORT_SECTION` structs for GCRefMap resolution | +| `ReadyToRunInfo` | `NumImportSections` | Count of import sections in the array | | `FuncEvalFrame` | `DebuggerEvalPtr` | Pointer to the Frame's DebuggerEval object | | `DebuggerEval` | `TargetContext` | Context saved inside DebuggerEval | -| `DebuggerEval` | `EvalDuringException` | Flag used in processing FuncEvalFrame | +| `DebuggerEval` | `EvalUsesHijack` | Flag used in processing FuncEvalFrame | | `ResumableFrame` | `TargetContextPtr` | Pointer to the Frame's Target Context | | `FaultingExceptionFrame` | `TargetContext` | Frame's Target Context | | `HijackFrame` | `ReturnAddress` | Frame's stored instruction pointer | @@ -85,6 +96,8 @@ This contract depends on the following descriptors: | `ExceptionInfo` | `CallerOfActualHandlerFrame` | Stack frame of the caller of the catch handler | | `ExceptionInfo` | `PreviousNestedInfo` | Pointer to previous nested ExInfo | | `ExceptionInfo` | `PassNumber` | Exception handling pass (1 or 2) | +| `ExceptionInfo` | `ClauseForCatchHandlerStartPC` | Start PC offset of the catch handler clause, used for interruptible offset override | +| `ExceptionInfo` | `ClauseForCatchHandlerEndPC` | End PC offset of the catch handler clause, used for interruptible offset override | Global variables used: | Global Name | Type | Purpose | @@ -102,6 +115,7 @@ Contracts used: | `ExecutionManager` | | `Thread` | | `RuntimeTypeSystem` | +| `GCInfo` | ### Stackwalk Algorithm @@ -277,10 +291,14 @@ InlinedCallFrames store and update only the IP, SP, and FP of a given context. I * On ARM, the InlinedCallFrame stores the value of the SP after the prolog (`SPAfterProlog`) to allow unwinding for functions with stackalloc. When a function uses stackalloc, the CallSiteSP can already have been adjusted. This value should be placed in R9. +**Return Address**: `CallerReturnAddress`, but only when the frame has an active call (i.e., `CallerReturnAddress != 0`). Returns null otherwise. + #### SoftwareExceptionFrame SoftwareExceptionFrames store a copy of the context struct. The IP, SP, and all ABI specified (platform specific) callee-saved registers are copied from the stored context to the working context. +**Return Address**: Read from the `ReturnAddress` field on the frame. + #### TransitionFrame TransitionFrames hold a pointer to a `TransitionBlock`. The TransitionBlock holds a return address along with a `CalleeSavedRegisters` struct which has values for all ABI specified callee-saved registers. The SP can be found using the address of the TransitionBlock. Since the TransitionBlock will be the lowest element on the stack, the SP is the address of the TransitionBlock + sizeof(TransitionBlock). @@ -289,6 +307,8 @@ When updating the context from a TransitionFrame, the IP, SP, and all ABI specif * On ARM, the additional register values stored in `ArgumentRegisters` are copied over. The `TransitionBlock` holds a pointer to the `ArgumentRegister` struct containing these values. +**Return Address**: Read from `TransitionBlock.ReturnAddress`. This applies to all frame types that use the TransitionFrame mechanism. + The following Frame types also use this mechanism: * FramedMethodFrame * PInvokeCallIFrame @@ -302,12 +322,16 @@ The following Frame types also use this mechanism: FuncEvalFrames hold a pointer to a `DebuggerEval`. The DebuggerEval holds a full context which is completely copied over to the working context when updating. +**Return Address**: Returns null when using hijack evaluation (`EvalUsesHijack`). Otherwise, read from `TransitionBlock.ReturnAddress` like other TransitionFrame types. + #### ResumableFrame ResumableFrames hold a pointer to a context object (Note this is different from SoftwareExceptionFrames which hold the context directly). The entire context object is copied over to the working context when updating. RedirectedThreadFrames also use this mechanism. +**Return Address**: Extracted from the saved context's instruction pointer (`TargetContextPtr` -> context IP). + #### FaultingExceptionFrame FaultingExceptionFrames have two different implementations. One for Windows x86 and another for all other builds (with funclets). @@ -316,10 +340,14 @@ Given the cDAC does not yet support Windows x86, this version is not supported. The other version stores a context struct. To update the working context, the entire stored context is copied over. In addition the `ContextFlags` are updated to ensure the `CONTEXT_XSTATE` bit is not set given the debug version of the contexts can not store extended state. This bit is architecture specific. +**Return Address**: Extracted from the saved context's instruction pointer (`TargetContext` -> context IP). + #### HijackFrame HijackFrames carry a IP (ReturnAddress) and a pointer to `HijackArgs`. All platforms update the IP and use the platform specific HijackArgs to update further registers. The following details currently implemented platforms. +**Return Address**: Read from the `ReturnAddress` field directly. + * x64 - On x64, HijackArgs contains a CalleeSavedRegister struct. The saved registers values contained in the struct are copied over to the working context. * Windows - On Windows, HijackArgs also contains the SP value directly which is copied over to the working context. * Non-Windows - On OS's other than Windows, HijackArgs does not contain an SP value. Instead since the HijackArgs struct lives on the stack, the SP is `&hijackArgs + sizeof(HijackArgs)`. This value is also copied over. @@ -331,6 +359,8 @@ HijackFrames carry a IP (ReturnAddress) and a pointer to `HijackArgs`. All platf TailCallFrames only appear on x86 Windows. They hold a `CalleeSavedRegisters` struct as well as a `ReturnAddress`. While the stack pointer is not directly contained in the TailCallFrame structure, it will be on the stack immediately following the Frame (found at the address of the Frame + size of the Frame). To process these Frames, update all of the registers in `CalleeSavedRegisters`, the instruction pointer from the stored return address, and the stack pointer from the address saved on the stack. +**Return Address**: Read from the `ReturnAddress` field directly. + ### APIs The majority of the contract's complexity is the stack walking algorithm (detailed above) implemented as part of `CreateStackWalk`. @@ -399,6 +429,135 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle) TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) ``` +`WalkStackReferences` walks the entire managed stack and enumerates all live GC references at each frame. It returns a list of `StackReferenceData` describing each GC-tracked slot (its address, whether it's an interior pointer, and the register/stack location). This API is the primary consumer for `SOSDacImpl.GetStackReferences`. + +```csharp +IReadOnlyList WalkStackReferences(ThreadData threadData) +``` + +The implementation uses the same stack walk algorithm as `CreateStackWalk`, but integrates the GC-aware `Filter` directly (rather than consuming pre-generated frames) and performs GC reference enumeration at each frame. See [GC Stack Reference Scanning](#gc-stack-reference-scanning) for details. + +### GC Stack Reference Scanning + +`WalkStackReferences` scans the stack for GC references by walking through each frame and reporting live object references and interior pointers. The native equivalent is `DacStackReferenceWalker` which calls `GcStackCrawlCallBack` at each frame. + +#### Stack Walk Integration + +The GC reference walk uses the `Filter` function to drive the stack walk. `Filter` is a port of native `StackFrameIterator::Filter` (with `GC_FUNCLET_REFERENCE_REPORTING` mode) that handles funclet-to-parent frame transitions, exception tracker correlation, and determines whether each frame should report GC references. Unlike `CreateStackWalk` which yields all frames, `Filter` calls `Next()` directly and may skip frames that don't contribute GC roots. + +Key state tracked during the walk: + +- **IsInterrupted**: Set when transitioning to a managed frame from a `FaultingExceptionFrame` or `SoftwareExceptionFrame` (frames with `FRAME_ATTR_EXCEPTION`). When true, the managed frame's GC enumeration uses `ExecutionAborted` mode, which causes the GcInfoDecoder to skip live slot reporting at non-interruptible offsets. +- **LastProcessedFrameType**: Records the frame type when processing `SW_FRAME` state, so `UpdateState` can detect exception frames during the transition to `SW_FRAMELESS`. +- **IsFirst**: Preserved during skipped frame processing (native `SFITER_SKIPPED_FRAME_FUNCTION` does not modify `IsFirst`), ensuring the subsequent managed frame is still treated as the leaf/active frame. +- **GetReturnAddress gating**: In `SW_FRAME` state, `UpdateContextFromFrame` is only called when `GetReturnAddress()` returns a non-null value. This matches native behavior where the context is only updated when the frame has a valid return address. + +#### Per-Frame GC Enumeration + +At each frame yielded by `Filter`, the walk determines whether to scan for GC references: + +**Managed (frameless) frames** use `EnumGcRefsForManagedFrame`: + +1. Get the code block handle and relative offset from the `ExecutionManager` contract. +2. Decode the GCInfo for the code block via the `GCInfo` contract. +3. Determine `GcSlotEnumerationOptions`: set `IsActiveFrame` if this is the leaf frame (`IsFirst`), `IsExecutionAborted` if the frame was interrupted, `IsParentOfFuncletStackFrame` if funclet GC reporting was delegated to the parent, `SuppressUntrackedSlots` if the code block is a filter funclet. +4. **Catch handler offset override**: When `ShouldParentFrameUseUnwindTargetPCforGCReporting` is set (parent frame resuming from a catch handler), the GC liveness offset is overridden to the first interruptible point within the catch handler clause range. This uses `GetInterruptibleRanges` from the `GCInfo` contract. See [How EH affects GC info/reporting](../coreclr/botr/clr-abi.md#how-eh-affects-gc-inforeporting) for background on why this override is needed. +5. Call `GcInfoDecoder.EnumerateLiveSlots` with the computed offset and flags to report all live register and stack slots. See the [GCInfo contract — EnumerateLiveSlots](./GCInfo.md#enumerateliveslots) for details on the algorithm. + +**Capital "F" Frames** use `GcScanRoots`, which dispatches based on frame type: + +- **StubDispatchFrame / ExternalMethodFrame**: Resolve GCRefMap via `FindGCRefMap` using the frame's `Indirection` pointer, otherwise fall back to signature-based scanning. +- **DynamicHelperFrame**: Use flag-based scanning (`DynamicHelperFrameFlags`). +- **PrestubMethodFrame / CallCountingHelperFrame**: Use signature-based scanning. +- Other frame types: No GC roots to report. + +See [GCRefMap Format and Resolution](#gcrefmap-format-and-resolution) for the GCRefMap scanning path and [Signature-Based Scanning](#signature-based-scanning) for the signature decoding path. + +### Signature-Based Scanning + +When a transition frame's calling convention is not described by a precomputed GCRefMap (`PrestubMethodFrame`, `CallCountingHelperFrame`, and the fallback path for `StubDispatchFrame`/`ExternalMethodFrame`), the GC reference walk classifies caller-stack arguments by decoding the callee's method signature. This corresponds to native `TransitionFrame::PromoteCallerStack` (`src/coreclr/vm/frames.cpp`). + +#### GcSignatureTypeProvider + +`GcSignatureTypeProvider` is an `IRuntimeSignatureTypeProvider` that classifies each parameter type into one of: + +```csharp +internal enum GcTypeKind +{ + None, // Non-GC primitive that fits in a single slot + Ref, // Object reference (TYPE_GC_REF) + Interior, // Managed pointer / byref (TYPE_GC_BYREF) + Other, // Value type that may contain GC refs, or any type larger than a slot +} +``` + +The provider is scoped to the method's containing module (captured at construction) so that `TypeDef` and `TypeRef` tokens can be resolved to a loaded `MethodTable` via the module's `TypeDefToMethodTable` / `TypeRefToMethodTable` lookup tables. The decoder's generic context is a `GcSignatureContext(TypeHandle classContext, MethodDescHandle methodContext)` carrying the method's class and method instantiations. + +The provider classifies primitives directly (`String`/`Object` -> `Ref`, `TypedReference` -> `Other`, others -> `None`). For `TypeDef`/`TypeRef` it resolves the loaded `TypeHandle` and classifies via `RuntimeTypeSystem.GetSignatureCorElementType`, treating enums (`IsEnum`) as their underlying primitive (`None`). When the type cannot be resolved (e.g., not yet loaded), classification falls back to the signature's `rawTypeKind` (`ValueType` -> `Other`, otherwise `Ref`). Arrays are `Ref`, byrefs are `Interior`, raw pointers are `None`. Generic parameters (`!T`, `!!T`) are resolved against the `GcSignatureContext` (via `GetInstantiation` / `GetGenericMethodInstantiation`) and classified by their actual instantiation -- matching native `SigTypeContext`-driven `PeekElemTypeNormalized` behavior. `ELEMENT_TYPE_INTERNAL` resolves the `TypeHandle` via `RuntimeTypeSystem.GetSignatureCorElementType` and maps the `CorElementType` to a `GcTypeKind`. + +#### PromoteCallerStack Algorithm + +1. Read the `MethodDesc` pointer from the `FramedMethodFrame` and obtain a `MethodDescHandle` from `RuntimeTypeSystem`. +2. Resolve the method's `MetadataReader` via `Loader.GetModuleHandleFromModulePtr` and `EcmaMetadata.GetMetadata`. If metadata is unavailable, no caller-stack refs are reported (matches native fallback behavior). +3. Obtain the method's signature blob, matching native `MethodDesc::GetSig`: + - If `RuntimeTypeSystem.IsStoredSigMethodDesc` is true (dynamic, EEImpl, and array method descs), pin the stored signature span and pass a `BlobReader` over it to `RuntimeSignatureDecoder.DecodeMethodSignature`. + - Otherwise, look up the signature via the metadata token (`mdMethodDef`), skipping methods with a nil token (`0x06000000`). +4. Decode the signature with `RuntimeSignatureDecoder` and a `GcSignatureTypeProvider` constructed for the method's module. The `GcSignatureContext` passes the method's class and method instantiations so that `VAR`/`MVAR` placeholders resolve to their actual types. See [Signature contract](./Signature.md) for the decoder. +5. Skip varargs methods (the caller-stack layout is not described by the callee signature alone). +6. Compute the number of reserved register slots in the `TransitionBlock`: + + | Reserved Slot | Condition | + |---|---| + | `this` pointer | `MethodSignature.Header.IsInstance` | + | Return buffer | Return type is `GcTypeKind.Other` | + | Generic instantiation arg | `RuntimeTypeSystem.RequiresInstArg(methodDesc)` | + | Async continuation | `RuntimeTypeSystem.IsAsyncMethod(methodDesc)` | + | ARM64 indirect-result register (`x8`) | Target architecture is ARM64 | + +7. If `IsInstance`, report the `this` slot at position `0` (or `1` on ARM64 to skip `x8`). The slot is reported as `GC_CALL_INTERIOR` for value-type `this`, otherwise as a normal reference. +8. Walk `MethodSignature.ParameterTypes` starting at slot index = reserved slot count, advancing one slot per parameter: + - `GcTypeKind.Ref` -> report as a reference. + - `GcTypeKind.Interior` -> report with `GC_CALL_INTERIOR`. + - `GcTypeKind.Other` / `GcTypeKind.None` -> not reported (large value types are reported via the GCRefMap path when one is available; otherwise their interior refs are not visible to this scan). + +The slot address is computed using the same formula as the GCRefMap path: + +```csharp +slotAddress = transitionBlockPtr + FirstGCRefMapSlot + (position * pointerSize); +``` + +#### Limitations vs. Native + +This signature-based scan has known gaps relative to native see [dotnet/runtime#127765](https://github.com/dotnet/runtime/issues/127765) for tracking. + +### GCRefMap Format and Resolution + +A **GCRefMap** is a compact per-callsite encoding that describes which stack slots in a `TransitionBlock` contain GC references. GCRefMaps are pre-computed by the ReadyToRun compiler and stored in the PE image's import section auxiliary data. + +The GCRefMap encoding format — including token values, bit encoding, lookup table structure, and per-architecture position semantics — is documented in the [ReadyToRun format specification](../coreclr/botr/readytorun-format.md#readytorun_import_sectionsauxiliarydata). + +#### Resolution Flow + +GCRefMap resolution from a frame's `Indirection` pointer proceeds as follows: + +1. Call `FindReadyToRunModule(indirection)` (see [ExecutionManager contract](./ExecutionManager.md)) to find the ReadyToRun module containing the import slot. +2. Load the module's `ReadyToRunInfo` to access the import section array. +3. Compute the RVA of the indirection address: `rva = indirection - imageBase`. +4. Search through `READYTORUN_IMPORT_SECTION` entries to find the section containing the RVA. +5. Compute the slot index within the section: `index = (rva - sectionVA) / entrySize`. +6. Use the section's `AuxiliaryData` RVA to locate the GCRefMap lookup table. +7. Use stride-based lookup (stride = 1024) plus linear scan to find the specific GCRefMap entry. + +#### Slot Mapping + +GCRefMap positions map to `TransitionBlock` offsets using the formula: + +```csharp +slotAddress = transitionBlockPtr + FirstGCRefMapSlot + (position * pointerSize) +``` + +Where `FirstGCRefMapSlot` is the byte offset in the `TransitionBlock` where GCRefMap slot enumeration begins (platform-dependent: on ARM64 it is the return buffer argument register offset; on other platforms it is the argument registers offset). + ### x86 Specifics The x86 platform has some major differences to other platforms. In general this stems from the platform being older and not having a defined unwinding codes. Instead, to unwind managed frames, we rely on GCInfo associated with JITted code. For the unwind, we do not defer to a 'Windows like' native unwinder, instead the custom unwinder implementation was ported to managed code. diff --git a/eng/pipelines/cdac/prepare-cdac-helix-steps.yml b/eng/pipelines/cdac/prepare-cdac-helix-steps.yml index ce8e01605987ae..c3b61807915e56 100644 --- a/eng/pipelines/cdac/prepare-cdac-helix-steps.yml +++ b/eng/pipelines/cdac/prepare-cdac-helix-steps.yml @@ -6,6 +6,7 @@ parameters: buildDebuggees: true skipDebuggeeCopy: false + runtimeConfiguration: 'Checked' steps: - ${{ if parameters.buildDebuggees }}: @@ -14,6 +15,7 @@ steps: /t:BuildDebuggeesOnly /p:Configuration=$(_BuildConfig) /p:TargetArchitecture=$(archType) + /p:RuntimeConfiguration=${{ parameters.runtimeConfiguration }} -bl:$(Build.SourcesDirectory)/artifacts/log/BuildDebuggees.binlog displayName: 'Build Debuggees' diff --git a/eng/renovate.json b/eng/renovate.json index f0f90be8c4c86c..b1efc3c669c073 100644 --- a/eng/renovate.json +++ b/eng/renovate.json @@ -37,7 +37,7 @@ "eng/pipelines/libraries/helix-queues-setup.yml" ], "pinDigests": true, - "commitMessageTopic": "container image dependencies" + "commitMessageTopic": "container image digests" } ] } diff --git a/src/coreclr/gc/background.cpp b/src/coreclr/gc/background.cpp index 3fc82188afa489..a1acaeced85432 100644 --- a/src/coreclr/gc/background.cpp +++ b/src/coreclr/gc/background.cpp @@ -544,17 +544,23 @@ void gc_heap::background_promote (Object** ppObject, ScanContext* sc, uint32_t f uint8_t* o = (uint8_t*)*ppObject; - if (!is_in_find_object_range (o)) - { - return; - } - #ifdef DEBUG_DestroyedHandleValue // we can race with destroy handle during concurrent scan if (o == (uint8_t*)DEBUG_DestroyedHandleValue) return; #endif //DEBUG_DestroyedHandleValue + if (!is_in_find_object_range (o)) + { +#ifdef _DEBUG + if ((o != NULL) && !(flags & GC_CALL_INTERIOR)) + { + ((CObjectHeader*)o)->Validate(); + } +#endif //_DEBUG + return; + } + HEAP_FROM_THREAD; gc_heap* hp = gc_heap::heap_of (o); @@ -2741,6 +2747,12 @@ void gc_heap::background_promote_callback (Object** ppObject, ScanContext* sc, if (!is_in_find_object_range (o)) { +#ifdef _DEBUG + if ((o != NULL) && !(flags & GC_CALL_INTERIOR)) + { + ((CObjectHeader*)o)->Validate(); + } +#endif //_DEBUG return; } diff --git a/src/coreclr/gc/interface.cpp b/src/coreclr/gc/interface.cpp index acdae1415cb403..980b595b993476 100644 --- a/src/coreclr/gc/interface.cpp +++ b/src/coreclr/gc/interface.cpp @@ -1054,17 +1054,23 @@ void GCHeap::Promote(Object** ppObject, ScanContext* sc, uint32_t flags) uint8_t* o = (uint8_t*)*ppObject; - if (!gc_heap::is_in_find_object_range (o)) - { - return; - } - #ifdef DEBUG_DestroyedHandleValue // we can race with destroy handle during concurrent scan if (o == (uint8_t*)DEBUG_DestroyedHandleValue) return; #endif //DEBUG_DestroyedHandleValue + if (!gc_heap::is_in_find_object_range (o)) + { +#ifdef _DEBUG + if ((o != NULL) && !(flags & GC_CALL_INTERIOR)) + { + ((CObjectHeader*)o)->Validate(); + } +#endif //_DEBUG + return; + } + HEAP_FROM_THREAD; gc_heap* hp = gc_heap::heap_of (o); diff --git a/src/coreclr/inc/corhdr.h b/src/coreclr/inc/corhdr.h index 466e1e8307fddf..7c6336a6571c5b 100644 --- a/src/coreclr/inc/corhdr.h +++ b/src/coreclr/inc/corhdr.h @@ -913,7 +913,7 @@ typedef enum CorElementType ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT // This is for signatures generated internally (which will not be persisted in any way). - // [cDAC] [RuntimeTypeSystem]: Contract depends on the values of ELEMENT_TYPE_INTERNAL and ELEMENT_TYPE_CMOD_INTERNAL. + // [cDAC] [Signature][RuntimeTypeSystem]: Contract depends on the values of ELEMENT_TYPE_INTERNAL and ELEMENT_TYPE_CMOD_INTERNAL. ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL ELEMENT_TYPE_CMOD_INTERNAL = 0x22, // CMOD_INTERNAL diff --git a/src/coreclr/jit/CMakeLists.txt b/src/coreclr/jit/CMakeLists.txt index e7e69887486830..2efb2b841bb04d 100644 --- a/src/coreclr/jit/CMakeLists.txt +++ b/src/coreclr/jit/CMakeLists.txt @@ -404,10 +404,6 @@ set( JIT_HEADERS stacklevelsetter.h target.h targetcommon.h - targetx86.h - targetamd64.h - targetarm.h - targetarm64.h treelifeupdater.h typelist.h unwind.h @@ -421,15 +417,24 @@ set( JIT_HEADERS ) # Arch specific headers + set( JIT_AMD64_HEADERS emitfmtsxarch.h emitxarch.h hwintrinsiclistxarch.h - hwintrinsic.h instrsxarch.h + registeramd64.h + targetamd64.h ) -set( JIT_I386_HEADERS ${JIT_AMD64_HEADERS} ) +set( JIT_I386_HEADERS + emitfmtsxarch.h + emitxarch.h + hwintrinsiclistxarch.h + instrsxarch.h + registerx86.h + targetx86.h +) set( JIT_ARM64_HEADERS emitarm64.h @@ -440,6 +445,7 @@ set( JIT_ARM64_HEADERS instrsarm64.h instrsarm64sve.h registerarm64.h + targetarm64.h ) set( JIT_ARM_HEADERS @@ -447,6 +453,7 @@ set( JIT_ARM_HEADERS emitfmtsarm.h instrsarm.h registerarm.h + targetarm.h ) set ( JIT_ARMV6_HEADERS diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index dc6b2946c22ebe..caaec17c160446 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2302,8 +2302,9 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) else { assert(genIsValidReg(varDsc->GetRegNum())); - unsigned wasmLclIndex = WasmRegToIndex(varDsc->GetRegNum()); - GetEmitter()->emitIns_I(INS_local_get, emitTypeSize(tree), wasmLclIndex); + var_types type = varDsc->GetRegisterType(tree); + unsigned wasmLclIndex = WasmRegToIndex(varDsc->GetRegNum()); + GetEmitter()->emitIns_I(INS_local_get, emitTypeSize(type), wasmLclIndex); // In this case, the resulting tree type may be different from the local var type where the value originates, // and so we need an explicit conversion since we can't "load" // the value with a different type like we can if the value is on the shadow stack. diff --git a/src/coreclr/jit/register.h b/src/coreclr/jit/register.h index 5c9a03872e9740..4842f7264b51e3 100644 --- a/src/coreclr/jit/register.h +++ b/src/coreclr/jit/register.h @@ -1,376 +1,20 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// clang-format off - -/*****************************************************************************/ -/*****************************************************************************/ -#ifndef REGDEF -#error Must define REGDEF macro before including this file -#endif -#ifndef REGALIAS -#define REGALIAS(alias, realname) -#endif - -#if defined(TARGET_XARCH) - -#if defined(TARGET_X86) -/* -REGDEF(name, rnum, mask, sname) */ -REGDEF(EAX, 0, 0x01, "eax" ) -REGDEF(ECX, 1, 0x02, "ecx" ) -REGDEF(EDX, 2, 0x04, "edx" ) -REGDEF(EBX, 3, 0x08, "ebx" ) -REGDEF(ESP, 4, 0x10, "esp" ) -REGDEF(EBP, 5, 0x20, "ebp" ) -REGDEF(ESI, 6, 0x40, "esi" ) -REGDEF(EDI, 7, 0x80, "edi" ) -REGALIAS(RAX, EAX) -REGALIAS(RCX, ECX) -REGALIAS(RDX, EDX) -REGALIAS(RBX, EBX) -REGALIAS(RSP, ESP) -REGALIAS(RBP, EBP) -REGALIAS(RSI, ESI) -REGALIAS(RDI, EDI) - -#else // !defined(TARGET_X86) - -#define GPRMASK(x) (1ULL << (x)) -/* -REGDEF(name, rnum, mask, sname) */ -REGDEF(RAX, 0, GPRMASK(0), "rax" ) -REGDEF(RCX, 1, GPRMASK(1), "rcx" ) -REGDEF(RDX, 2, GPRMASK(2), "rdx" ) -REGDEF(RBX, 3, GPRMASK(3), "rbx" ) -REGDEF(RSP, 4, GPRMASK(4), "rsp" ) -REGDEF(RBP, 5, GPRMASK(5), "rbp" ) -REGDEF(RSI, 6, GPRMASK(6), "rsi" ) -REGDEF(RDI, 7, GPRMASK(7), "rdi" ) -REGDEF(R8, 8, GPRMASK(8), "r8" ) -REGDEF(R9, 9, GPRMASK(9), "r9" ) -REGDEF(R10, 10, GPRMASK(10), "r10" ) -REGDEF(R11, 11, GPRMASK(11), "r11" ) -REGDEF(R12, 12, GPRMASK(12), "r12" ) -REGDEF(R13, 13, GPRMASK(13), "r13" ) -REGDEF(R14, 14, GPRMASK(14), "r14" ) -REGDEF(R15, 15, GPRMASK(15), "r15" ) -REGDEF(R16, 16, GPRMASK(16), "r16" ) -REGDEF(R17, 17, GPRMASK(17), "r17" ) -REGDEF(R18, 18, GPRMASK(18), "r18" ) -REGDEF(R19, 19, GPRMASK(19), "r19" ) -REGDEF(R20, 20, GPRMASK(20), "r20" ) -REGDEF(R21, 21, GPRMASK(21), "r21" ) -REGDEF(R22, 22, GPRMASK(22), "r22" ) -REGDEF(R23, 23, GPRMASK(23), "r23" ) -REGDEF(R24, 24, GPRMASK(24), "r24" ) -REGDEF(R25, 25, GPRMASK(25), "r25" ) -REGDEF(R26, 26, GPRMASK(26), "r26" ) -REGDEF(R27, 27, GPRMASK(27), "r27" ) -REGDEF(R28, 28, GPRMASK(28), "r28" ) -REGDEF(R29, 29, GPRMASK(29), "r29" ) -REGDEF(R30, 30, GPRMASK(30), "r30" ) -REGDEF(R31, 31, GPRMASK(31), "r31" ) - -REGALIAS(EAX, RAX) -REGALIAS(ECX, RCX) -REGALIAS(EDX, RDX) -REGALIAS(EBX, RBX) -REGALIAS(ESP, RSP) -REGALIAS(EBP, RBP) -REGALIAS(ESI, RSI) -REGALIAS(EDI, RDI) - -#endif // !defined(TARGET_X86) - -#ifdef TARGET_AMD64 -#define XMMBASE 32 -#define XMMMASK(x) (1ULL << ((x)+XMMBASE)) - -#define KBASE 64 -#define KMASK(x) (1ULL << ((x))) - -#else // !TARGET_AMD64 -#define XMMBASE 8 -#define XMMMASK(x) ((int32_t)(1) << ((x)+XMMBASE)) - -#define KBASE 16 -#define KMASK(x) ((int32_t)(1) << ((x)+KBASE)) - - -#endif // !TARGET_AMD64 - -REGDEF(XMM0, 0+XMMBASE, XMMMASK(0), "mm0" ) -REGDEF(XMM1, 1+XMMBASE, XMMMASK(1), "mm1" ) -REGDEF(XMM2, 2+XMMBASE, XMMMASK(2), "mm2" ) -REGDEF(XMM3, 3+XMMBASE, XMMMASK(3), "mm3" ) -REGDEF(XMM4, 4+XMMBASE, XMMMASK(4), "mm4" ) -REGDEF(XMM5, 5+XMMBASE, XMMMASK(5), "mm5" ) -REGDEF(XMM6, 6+XMMBASE, XMMMASK(6), "mm6" ) -REGDEF(XMM7, 7+XMMBASE, XMMMASK(7), "mm7" ) - -#ifdef TARGET_AMD64 -REGDEF(XMM8, 8+XMMBASE, XMMMASK(8), "mm8" ) -REGDEF(XMM9, 9+XMMBASE, XMMMASK(9), "mm9" ) -REGDEF(XMM10, 10+XMMBASE, XMMMASK(10), "mm10" ) -REGDEF(XMM11, 11+XMMBASE, XMMMASK(11), "mm11" ) -REGDEF(XMM12, 12+XMMBASE, XMMMASK(12), "mm12" ) -REGDEF(XMM13, 13+XMMBASE, XMMMASK(13), "mm13" ) -REGDEF(XMM14, 14+XMMBASE, XMMMASK(14), "mm14" ) -REGDEF(XMM15, 15+XMMBASE, XMMMASK(15), "mm15" ) - -REGDEF(XMM16, 16+XMMBASE, XMMMASK(16), "mm16" ) -REGDEF(XMM17, 17+XMMBASE, XMMMASK(17), "mm17" ) -REGDEF(XMM18, 18+XMMBASE, XMMMASK(18), "mm18" ) -REGDEF(XMM19, 19+XMMBASE, XMMMASK(19), "mm19" ) -REGDEF(XMM20, 20+XMMBASE, XMMMASK(20), "mm20" ) -REGDEF(XMM21, 21+XMMBASE, XMMMASK(21), "mm21" ) -REGDEF(XMM22, 22+XMMBASE, XMMMASK(22), "mm22" ) -REGDEF(XMM23, 23+XMMBASE, XMMMASK(23), "mm23" ) - -REGDEF(XMM24, 24+XMMBASE, XMMMASK(24), "mm24" ) -REGDEF(XMM25, 25+XMMBASE, XMMMASK(25), "mm25" ) -REGDEF(XMM26, 26+XMMBASE, XMMMASK(26), "mm26" ) -REGDEF(XMM27, 27+XMMBASE, XMMMASK(27), "mm27" ) -REGDEF(XMM28, 28+XMMBASE, XMMMASK(28), "mm28" ) -REGDEF(XMM29, 29+XMMBASE, XMMMASK(29), "mm29" ) -REGDEF(XMM30, 30+XMMBASE, XMMMASK(30), "mm30" ) -REGDEF(XMM31, 31+XMMBASE, XMMMASK(31), "mm31" ) - -#endif // !TARGET_AMD64 - -REGDEF(K0, 0+KBASE, KMASK(0), "k0" ) -REGDEF(K1, 1+KBASE, KMASK(1), "k1" ) -REGDEF(K2, 2+KBASE, KMASK(2), "k2" ) -REGDEF(K3, 3+KBASE, KMASK(3), "k3" ) -REGDEF(K4, 4+KBASE, KMASK(4), "k4" ) -REGDEF(K5, 5+KBASE, KMASK(5), "k5" ) -REGDEF(K6, 6+KBASE, KMASK(6), "k6" ) -REGDEF(K7, 7+KBASE, KMASK(7), "k7" ) - -REGDEF(STK, 8+KBASE, 0x0000, "STK" ) - -// Ignore REG_* symbols defined in Android NDK #if defined(TARGET_X86) -#undef REG_EAX -#define REG_EAX JITREG_EAX -#undef REG_ECX -#define REG_ECX JITREG_ECX -#undef REG_EDX -#define REG_EDX JITREG_EDX -#undef REG_EBX -#define REG_EBX JITREG_EBX -#undef REG_ESP -#define REG_ESP JITREG_ESP -#undef REG_EBP -#define REG_EBP JITREG_EBP -#undef REG_ESI -#define REG_ESI JITREG_ESI -#undef REG_EDI -#define REG_EDI JITREG_EDI -#undef REG_RAX -#define REG_RAX JITREG_RAX -#undef REG_RCX -#define REG_RCX JITREG_RCX -#undef REG_RDX -#define REG_RDX JITREG_RDX -#undef REG_RBX -#define REG_RBX JITREG_RBX -#undef REG_RSP -#define REG_RSP JITREG_RSP -#undef REG_RBP -#define REG_RBP JITREG_RBP -#undef REG_RSI -#define REG_RSI JITREG_RSI -#undef REG_RDI -#define REG_RDI JITREG_RDI -#else // defined(TARGET_X86) -#undef REG_RAX -#define REG_RAX JITREG_RAX -#undef REG_RCX -#define REG_RCX JITREG_RCX -#undef REG_RDX -#define REG_RDX JITREG_RDX -#undef REG_RBX -#define REG_RBX JITREG_RBX -#undef REG_RSP -#define REG_RSP JITREG_RSP -#undef REG_RBP -#define REG_RBP JITREG_RBP -#undef REG_RSI -#define REG_RSI JITREG_RSI -#undef REG_RDI -#define REG_RDI JITREG_RDI -#undef REG_R8 -#define REG_R8 JITREG_R8 -#undef REG_R9 -#define REG_R9 JITREG_R9 -#undef REG_R10 -#define REG_R10 JITREG_R10 -#undef REG_R11 -#define REG_R11 JITREG_R11 -#undef REG_R12 -#define REG_R12 JITREG_R12 -#undef REG_R13 -#define REG_R13 JITREG_R13 -#undef REG_R14 -#define REG_R14 JITREG_R14 -#undef REG_R15 -#define REG_R15 JITREG_R15 -#undef REG_R16 -#define REG_R16 JITREG_R16 -#undef REG_R17 -#define REG_R17 JITREG_R17 -#undef REG_R18 -#define REG_R18 JITREG_R18 -#undef REG_R19 -#define REG_R19 JITREG_R19 -#undef REG_R20 -#define REG_R20 JITREG_R20 -#undef REG_R21 -#define REG_R21 JITREG_R21 -#undef REG_R22 -#define REG_R22 JITREG_R22 -#undef REG_R23 -#define REG_R23 JITREG_R23 -#undef REG_R24 -#define REG_R24 JITREG_R24 -#undef REG_R25 -#define REG_R25 JITREG_R25 -#undef REG_R26 -#define REG_R26 JITREG_R26 -#undef REG_R27 -#define REG_R27 JITREG_R27 -#undef REG_R28 -#define REG_R28 JITREG_R28 -#undef REG_R29 -#define REG_R29 JITREG_R29 -#undef REG_R30 -#define REG_R30 JITREG_R30 -#undef REG_R31 -#define REG_R31 JITREG_R31 -#undef REG_EAX -#define REG_EAX JITREG_EAX -#undef REG_ECX -#define REG_ECX JITREG_ECX -#undef REG_EDX -#define REG_EDX JITREG_EDX -#undef REG_EBX -#define REG_EBX JITREG_EBX -#undef REG_ESP -#define REG_ESP JITREG_ESP -#undef REG_EBP -#define REG_EBP JITREG_EBP -#undef REG_ESI -#define REG_ESI JITREG_ESI -#undef REG_EDI -#define REG_EDI JITREG_EDI -#endif // !defined(TARGET_X86) - -#undef REG_XMM0 -#define REG_XMM0 JITREG_XMM0 -#undef REG_XMM1 -#define REG_XMM1 JITREG_XMM1 -#undef REG_XMM2 -#define REG_XMM2 JITREG_XMM2 -#undef REG_XMM3 -#define REG_XMM3 JITREG_XMM3 -#undef REG_XMM4 -#define REG_XMM4 JITREG_XMM4 -#undef REG_XMM5 -#define REG_XMM5 JITREG_XMM5 -#undef REG_XMM6 -#define REG_XMM6 JITREG_XMM6 -#undef REG_XMM7 -#define REG_XMM7 JITREG_XMM7 - -#ifdef TARGET_AMD64 -#undef REG_XMM8 -#define REG_XMM8 JITREG_XMM8 -#undef REG_XMM9 -#define REG_XMM9 JITREG_XMM9 -#undef REG_XMM10 -#define REG_XMM10 JITREG_XMM10 -#undef REG_XMM11 -#define REG_XMM11 JITREG_XMM11 -#undef REG_XMM12 -#define REG_XMM12 JITREG_XMM12 -#undef REG_XMM13 -#define REG_XMM13 JITREG_XMM13 -#undef REG_XMM14 -#define REG_XMM14 JITREG_XMM14 -#undef REG_XMM15 -#define REG_XMM15 JITREG_XMM15 -#undef REG_XMM16 -#define REG_XMM16 JITREG_XMM16 -#undef REG_XMM17 -#define REG_XMM17 JITREG_XMM17 -#undef REG_XMM18 -#define REG_XMM18 JITREG_XMM18 -#undef REG_XMM19 -#define REG_XMM19 JITREG_XMM19 -#undef REG_XMM20 -#define REG_XMM20 JITREG_XMM20 -#undef REG_XMM21 -#define REG_XMM21 JITREG_XMM21 -#undef REG_XMM22 -#define REG_XMM22 JITREG_XMM22 -#undef REG_XMM23 -#define REG_XMM23 JITREG_XMM23 -#undef REG_XMM24 -#define REG_XMM24 JITREG_XMM24 -#undef REG_XMM25 -#define REG_XMM25 JITREG_XMM25 -#undef REG_XMM26 -#define REG_XMM26 JITREG_XMM26 -#undef REG_XMM27 -#define REG_XMM27 JITREG_XMM27 -#undef REG_XMM28 -#define REG_XMM28 JITREG_XMM28 -#undef REG_XMM29 -#define REG_XMM29 JITREG_XMM29 -#undef REG_XMM30 -#define REG_XMM30 JITREG_XMM30 -#undef REG_XMM31 -#define REG_XMM31 JITREG_XMM31 -#endif // TARGET_AMD64 - -#undef REG_K0 -#define REG_K0 JITREG_K0 -#undef REG_K1 -#define REG_K1 JITREG_K1 -#undef REG_K2 -#define REG_K2 JITREG_K2 -#undef REG_K3 -#define REG_K3 JITREG_K3 -#undef REG_K4 -#define REG_K4 JITREG_K4 -#undef REG_K5 -#define REG_K5 JITREG_K5 -#undef REG_K6 -#define REG_K6 JITREG_K6 -#undef REG_K7 -#define REG_K7 JITREG_K7 -#undef REG_STK -#define REG_STK JITREG_STK - +#include "registerx86.h" +#elif defined(TARGET_AMD64) +#include "registeramd64.h" #elif defined(TARGET_ARM) - #include "registerarm.h" +#include "registerarm.h" #elif defined(TARGET_ARM64) - #include "registerarm64.h" +#include "registerarm64.h" #elif defined(TARGET_LOONGARCH64) - #include "registerloongarch64.h" +#include "registerloongarch64.h" #elif defined(TARGET_RISCV64) - #include "registerriscv64.h" +#include "registerriscv64.h" #elif defined(TARGET_WASM) #include "registerwasm.h" #else - #error Unsupported or unset target architecture -#endif // target type -/*****************************************************************************/ -#undef REGDEF -#undef REGALIAS -#undef XMMMASK -/*****************************************************************************/ - -// clang-format on +#error Unsupported or unset target architecture +#endif diff --git a/src/coreclr/jit/registeramd64.h b/src/coreclr/jit/registeramd64.h new file mode 100644 index 00000000000000..e26fd1a6551c21 --- /dev/null +++ b/src/coreclr/jit/registeramd64.h @@ -0,0 +1,288 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// clang-format off + +/*****************************************************************************/ +/*****************************************************************************/ +#ifndef REGDEF +#error Must define REGDEF macro before including this file +#endif +#ifndef REGALIAS +#define REGALIAS(alias, realname) +#endif + +#define GPRMASK(x) (1ULL << (x)) + +/* +REGDEF(name, rnum, mask, sname) */ +REGDEF(RAX, 0, GPRMASK(0), "rax" ) +REGDEF(RCX, 1, GPRMASK(1), "rcx" ) +REGDEF(RDX, 2, GPRMASK(2), "rdx" ) +REGDEF(RBX, 3, GPRMASK(3), "rbx" ) +REGDEF(RSP, 4, GPRMASK(4), "rsp" ) +REGDEF(RBP, 5, GPRMASK(5), "rbp" ) +REGDEF(RSI, 6, GPRMASK(6), "rsi" ) +REGDEF(RDI, 7, GPRMASK(7), "rdi" ) +REGDEF(R8, 8, GPRMASK(8), "r8" ) +REGDEF(R9, 9, GPRMASK(9), "r9" ) +REGDEF(R10, 10, GPRMASK(10), "r10" ) +REGDEF(R11, 11, GPRMASK(11), "r11" ) +REGDEF(R12, 12, GPRMASK(12), "r12" ) +REGDEF(R13, 13, GPRMASK(13), "r13" ) +REGDEF(R14, 14, GPRMASK(14), "r14" ) +REGDEF(R15, 15, GPRMASK(15), "r15" ) +REGDEF(R16, 16, GPRMASK(16), "r16" ) +REGDEF(R17, 17, GPRMASK(17), "r17" ) +REGDEF(R18, 18, GPRMASK(18), "r18" ) +REGDEF(R19, 19, GPRMASK(19), "r19" ) +REGDEF(R20, 20, GPRMASK(20), "r20" ) +REGDEF(R21, 21, GPRMASK(21), "r21" ) +REGDEF(R22, 22, GPRMASK(22), "r22" ) +REGDEF(R23, 23, GPRMASK(23), "r23" ) +REGDEF(R24, 24, GPRMASK(24), "r24" ) +REGDEF(R25, 25, GPRMASK(25), "r25" ) +REGDEF(R26, 26, GPRMASK(26), "r26" ) +REGDEF(R27, 27, GPRMASK(27), "r27" ) +REGDEF(R28, 28, GPRMASK(28), "r28" ) +REGDEF(R29, 29, GPRMASK(29), "r29" ) +REGDEF(R30, 30, GPRMASK(30), "r30" ) +REGDEF(R31, 31, GPRMASK(31), "r31" ) + +REGALIAS(EAX, RAX) +REGALIAS(ECX, RCX) +REGALIAS(EDX, RDX) +REGALIAS(EBX, RBX) +REGALIAS(ESP, RSP) +REGALIAS(EBP, RBP) +REGALIAS(ESI, RSI) +REGALIAS(EDI, RDI) + +#define XMMBASE 32 +#define XMMMASK(x) (1ULL << ((x)+XMMBASE)) + +REGDEF(XMM0, 0+XMMBASE, XMMMASK(0), "mm0" ) +REGDEF(XMM1, 1+XMMBASE, XMMMASK(1), "mm1" ) +REGDEF(XMM2, 2+XMMBASE, XMMMASK(2), "mm2" ) +REGDEF(XMM3, 3+XMMBASE, XMMMASK(3), "mm3" ) +REGDEF(XMM4, 4+XMMBASE, XMMMASK(4), "mm4" ) +REGDEF(XMM5, 5+XMMBASE, XMMMASK(5), "mm5" ) +REGDEF(XMM6, 6+XMMBASE, XMMMASK(6), "mm6" ) +REGDEF(XMM7, 7+XMMBASE, XMMMASK(7), "mm7" ) +REGDEF(XMM8, 8+XMMBASE, XMMMASK(8), "mm8" ) +REGDEF(XMM9, 9+XMMBASE, XMMMASK(9), "mm9" ) +REGDEF(XMM10, 10+XMMBASE, XMMMASK(10), "mm10" ) +REGDEF(XMM11, 11+XMMBASE, XMMMASK(11), "mm11" ) +REGDEF(XMM12, 12+XMMBASE, XMMMASK(12), "mm12" ) +REGDEF(XMM13, 13+XMMBASE, XMMMASK(13), "mm13" ) +REGDEF(XMM14, 14+XMMBASE, XMMMASK(14), "mm14" ) +REGDEF(XMM15, 15+XMMBASE, XMMMASK(15), "mm15" ) +REGDEF(XMM16, 16+XMMBASE, XMMMASK(16), "mm16" ) +REGDEF(XMM17, 17+XMMBASE, XMMMASK(17), "mm17" ) +REGDEF(XMM18, 18+XMMBASE, XMMMASK(18), "mm18" ) +REGDEF(XMM19, 19+XMMBASE, XMMMASK(19), "mm19" ) +REGDEF(XMM20, 20+XMMBASE, XMMMASK(20), "mm20" ) +REGDEF(XMM21, 21+XMMBASE, XMMMASK(21), "mm21" ) +REGDEF(XMM22, 22+XMMBASE, XMMMASK(22), "mm22" ) +REGDEF(XMM23, 23+XMMBASE, XMMMASK(23), "mm23" ) +REGDEF(XMM24, 24+XMMBASE, XMMMASK(24), "mm24" ) +REGDEF(XMM25, 25+XMMBASE, XMMMASK(25), "mm25" ) +REGDEF(XMM26, 26+XMMBASE, XMMMASK(26), "mm26" ) +REGDEF(XMM27, 27+XMMBASE, XMMMASK(27), "mm27" ) +REGDEF(XMM28, 28+XMMBASE, XMMMASK(28), "mm28" ) +REGDEF(XMM29, 29+XMMBASE, XMMMASK(29), "mm29" ) +REGDEF(XMM30, 30+XMMBASE, XMMMASK(30), "mm30" ) +REGDEF(XMM31, 31+XMMBASE, XMMMASK(31), "mm31" ) + +#define KBASE 64 +#define KMASK(x) (1ULL << ((x))) + +REGDEF(K0, 0+KBASE, KMASK(0), "k0" ) +REGDEF(K1, 1+KBASE, KMASK(1), "k1" ) +REGDEF(K2, 2+KBASE, KMASK(2), "k2" ) +REGDEF(K3, 3+KBASE, KMASK(3), "k3" ) +REGDEF(K4, 4+KBASE, KMASK(4), "k4" ) +REGDEF(K5, 5+KBASE, KMASK(5), "k5" ) +REGDEF(K6, 6+KBASE, KMASK(6), "k6" ) +REGDEF(K7, 7+KBASE, KMASK(7), "k7" ) + +// This must be last! +REGDEF(STK, 8+KBASE, 0x0000, "STK" ) + +// Ignore REG_* symbols defined in Android NDK +#undef REG_RAX +#define REG_RAX JITREG_RAX +#undef REG_RCX +#define REG_RCX JITREG_RCX +#undef REG_RDX +#define REG_RDX JITREG_RDX +#undef REG_RBX +#define REG_RBX JITREG_RBX +#undef REG_RSP +#define REG_RSP JITREG_RSP +#undef REG_RBP +#define REG_RBP JITREG_RBP +#undef REG_RSI +#define REG_RSI JITREG_RSI +#undef REG_RDI +#define REG_RDI JITREG_RDI +#undef REG_R8 +#define REG_R8 JITREG_R8 +#undef REG_R9 +#define REG_R9 JITREG_R9 +#undef REG_R10 +#define REG_R10 JITREG_R10 +#undef REG_R11 +#define REG_R11 JITREG_R11 +#undef REG_R12 +#define REG_R12 JITREG_R12 +#undef REG_R13 +#define REG_R13 JITREG_R13 +#undef REG_R14 +#define REG_R14 JITREG_R14 +#undef REG_R15 +#define REG_R15 JITREG_R15 +#undef REG_R16 +#define REG_R16 JITREG_R16 +#undef REG_R17 +#define REG_R17 JITREG_R17 +#undef REG_R18 +#define REG_R18 JITREG_R18 +#undef REG_R19 +#define REG_R19 JITREG_R19 +#undef REG_R20 +#define REG_R20 JITREG_R20 +#undef REG_R21 +#define REG_R21 JITREG_R21 +#undef REG_R22 +#define REG_R22 JITREG_R22 +#undef REG_R23 +#define REG_R23 JITREG_R23 +#undef REG_R24 +#define REG_R24 JITREG_R24 +#undef REG_R25 +#define REG_R25 JITREG_R25 +#undef REG_R26 +#define REG_R26 JITREG_R26 +#undef REG_R27 +#define REG_R27 JITREG_R27 +#undef REG_R28 +#define REG_R28 JITREG_R28 +#undef REG_R29 +#define REG_R29 JITREG_R29 +#undef REG_R30 +#define REG_R30 JITREG_R30 +#undef REG_R31 +#define REG_R31 JITREG_R31 + +#undef REG_EAX +#define REG_EAX JITREG_EAX +#undef REG_ECX +#define REG_ECX JITREG_ECX +#undef REG_EDX +#define REG_EDX JITREG_EDX +#undef REG_EBX +#define REG_EBX JITREG_EBX +#undef REG_ESP +#define REG_ESP JITREG_ESP +#undef REG_EBP +#define REG_EBP JITREG_EBP +#undef REG_ESI +#define REG_ESI JITREG_ESI +#undef REG_EDI +#define REG_EDI JITREG_EDI + +#undef REG_XMM0 +#define REG_XMM0 JITREG_XMM0 +#undef REG_XMM1 +#define REG_XMM1 JITREG_XMM1 +#undef REG_XMM2 +#define REG_XMM2 JITREG_XMM2 +#undef REG_XMM3 +#define REG_XMM3 JITREG_XMM3 +#undef REG_XMM4 +#define REG_XMM4 JITREG_XMM4 +#undef REG_XMM5 +#define REG_XMM5 JITREG_XMM5 +#undef REG_XMM6 +#define REG_XMM6 JITREG_XMM6 +#undef REG_XMM7 +#define REG_XMM7 JITREG_XMM7 +#undef REG_XMM8 +#define REG_XMM8 JITREG_XMM8 +#undef REG_XMM9 +#define REG_XMM9 JITREG_XMM9 +#undef REG_XMM10 +#define REG_XMM10 JITREG_XMM10 +#undef REG_XMM11 +#define REG_XMM11 JITREG_XMM11 +#undef REG_XMM12 +#define REG_XMM12 JITREG_XMM12 +#undef REG_XMM13 +#define REG_XMM13 JITREG_XMM13 +#undef REG_XMM14 +#define REG_XMM14 JITREG_XMM14 +#undef REG_XMM15 +#define REG_XMM15 JITREG_XMM15 +#undef REG_XMM16 +#define REG_XMM16 JITREG_XMM16 +#undef REG_XMM17 +#define REG_XMM17 JITREG_XMM17 +#undef REG_XMM18 +#define REG_XMM18 JITREG_XMM18 +#undef REG_XMM19 +#define REG_XMM19 JITREG_XMM19 +#undef REG_XMM20 +#define REG_XMM20 JITREG_XMM20 +#undef REG_XMM21 +#define REG_XMM21 JITREG_XMM21 +#undef REG_XMM22 +#define REG_XMM22 JITREG_XMM22 +#undef REG_XMM23 +#define REG_XMM23 JITREG_XMM23 +#undef REG_XMM24 +#define REG_XMM24 JITREG_XMM24 +#undef REG_XMM25 +#define REG_XMM25 JITREG_XMM25 +#undef REG_XMM26 +#define REG_XMM26 JITREG_XMM26 +#undef REG_XMM27 +#define REG_XMM27 JITREG_XMM27 +#undef REG_XMM28 +#define REG_XMM28 JITREG_XMM28 +#undef REG_XMM29 +#define REG_XMM29 JITREG_XMM29 +#undef REG_XMM30 +#define REG_XMM30 JITREG_XMM30 +#undef REG_XMM31 +#define REG_XMM31 JITREG_XMM31 + +#undef REG_K0 +#define REG_K0 JITREG_K0 +#undef REG_K1 +#define REG_K1 JITREG_K1 +#undef REG_K2 +#define REG_K2 JITREG_K2 +#undef REG_K3 +#define REG_K3 JITREG_K3 +#undef REG_K4 +#define REG_K4 JITREG_K4 +#undef REG_K5 +#define REG_K5 JITREG_K5 +#undef REG_K6 +#define REG_K6 JITREG_K6 +#undef REG_K7 +#define REG_K7 JITREG_K7 + +#undef REG_STK +#define REG_STK JITREG_STK + +/*****************************************************************************/ +#undef GPRMASK +#undef XMMMASK +#undef KMASK +#undef REGDEF +#undef REGALIAS +/*****************************************************************************/ + +// clang-format on diff --git a/src/coreclr/jit/registerarm.h b/src/coreclr/jit/registerarm.h index e26319c0374afd..f1612bfe54d4dd 100644 --- a/src/coreclr/jit/registerarm.h +++ b/src/coreclr/jit/registerarm.h @@ -31,6 +31,12 @@ REGDEF(SP, 13, 0x2000, "sp" ) REGDEF(LR, 14, 0x4000, "lr" ) REGDEF(PC, 15, 0x8000, "pc" ) +// Allow us to call R11/FP, SP, LR and PC by their register number names +REGALIAS(FP, R11) +REGALIAS(R13, SP) +REGALIAS(R14, LR) +REGALIAS(R15, PC) + #define FPBASE 16 #define VFPMASK(x) (((int64_t)1) << (x+FPBASE)) @@ -67,12 +73,8 @@ REGDEF(F29, 29+FPBASE, VFPMASK(29), "f29") REGDEF(F30, 30+FPBASE, VFPMASK(30), "f30") REGDEF(F31, 31+FPBASE, VFPMASK(31), "f31") - -// Allow us to call R11/FP, SP, LR and PC by their register number names -REGALIAS(FP, R11) -REGALIAS(R13, SP) -REGALIAS(R14, LR) -REGALIAS(R15, PC) +// This must be last! +REGDEF(STK, 32+FPBASE, 0x0000, "STK") // Ignore REG_* symbols defined in Android NDK #undef REG_R0 @@ -107,6 +109,16 @@ REGALIAS(R15, PC) #define REG_LR JITREG_LR #undef REG_PC #define REG_PC JITREG_PC + +#undef REG_FP +#define REG_FP JITREG_FP +#undef REG_R13 +#define REG_R13 JITREG_R13 +#undef REG_R14 +#define REG_R14 JITREG_R14 +#undef REG_R15 +#define REG_R15 JITREG_R15 + #undef REG_F0 #define REG_F0 JITREG_F0 #undef REG_F1 @@ -171,20 +183,10 @@ REGALIAS(R15, PC) #define REG_F30 JITREG_F30 #undef REG_F31 #define REG_F31 JITREG_F31 -#undef REG_FP -#define REG_FP JITREG_FP -#undef REG_R13 -#define REG_R13 JITREG_R13 -#undef REG_R14 -#define REG_R14 JITREG_R14 -#undef REG_R15 -#define REG_R15 JITREG_R15 + #undef REG_STK #define REG_STK JITREG_STK -// This must be last! -REGDEF(STK, 32+FPBASE, 0x0000, "STK") - /*****************************************************************************/ #undef REGDEF #undef REGALIAS diff --git a/src/coreclr/jit/registerarm64.h b/src/coreclr/jit/registerarm64.h index 4f69628c8a0b34..312ef29ba4c175 100644 --- a/src/coreclr/jit/registerarm64.h +++ b/src/coreclr/jit/registerarm64.h @@ -121,6 +121,7 @@ REGDEF(P15, 15+PBASE, PMASK(15), "p15", "na") REGDEF(SP, 0+NBASE, 0x0000, "sp", "wsp?") REGDEF(FFR, 1+NBASE, 0x0000, "ffr", "na") + // This must be last! REGDEF(STK, 2+NBASE, 0x0000, "STK", "STK") @@ -189,6 +190,7 @@ REGDEF(STK, 2+NBASE, 0x0000, "STK", "STK") #define REG_LR JITREG_LR #undef REG_ZR #define REG_ZR JITREG_ZR + #undef REG_R16 #define REG_R16 JITREG_R16 #undef REG_R17 @@ -199,6 +201,7 @@ REGDEF(STK, 2+NBASE, 0x0000, "STK", "STK") #define REG_R29 JITREG_R29 #undef REG_R30 #define REG_R30 JITREG_R30 + #undef REG_V0 #define REG_V0 JITREG_V0 #undef REG_V1 @@ -263,6 +266,7 @@ REGDEF(STK, 2+NBASE, 0x0000, "STK", "STK") #define REG_V30 JITREG_V30 #undef REG_V31 #define REG_V31 JITREG_V31 + #undef REG_P0 #define REG_P0 JITREG_P0 #undef REG_P1 @@ -295,17 +299,21 @@ REGDEF(STK, 2+NBASE, 0x0000, "STK", "STK") #define REG_P14 JITREG_P14 #undef REG_P15 #define REG_P15 JITREG_P15 + #undef REG_SP #define REG_SP JITREG_SP #undef REG_FFR #define REG_FFR JITREG_FFR + #undef REG_STK #define REG_STK JITREG_STK /*****************************************************************************/ #undef RMASK -#undef VMASK #undef VBASE +#undef VMASK +#undef PBASE +#undef PMASK #undef NBASE #undef REGDEF #undef REGALIAS diff --git a/src/coreclr/jit/registerloongarch64.h b/src/coreclr/jit/registerloongarch64.h index 8f3cd157016bb2..cdb813a3fb0184 100644 --- a/src/coreclr/jit/registerloongarch64.h +++ b/src/coreclr/jit/registerloongarch64.h @@ -105,8 +105,8 @@ REGDEF(STK, 0+NBASE, 0x0000, "STK") /*****************************************************************************/ #undef RMASK -#undef FMASK #undef FBASE +#undef FMASK #undef NBASE #undef REGDEF #undef REGALIAS diff --git a/src/coreclr/jit/registerriscv64.h b/src/coreclr/jit/registerriscv64.h index be678d90148cae..8fa61870a7f3a9 100644 --- a/src/coreclr/jit/registerriscv64.h +++ b/src/coreclr/jit/registerriscv64.h @@ -97,8 +97,8 @@ REGDEF(STK, 0+NBASE, 0x0000, "STK") /*****************************************************************************/ #undef RMASK -#undef VMASK -#undef VBASE +#undef FBASE +#undef FMASK #undef NBASE #undef REGDEF #undef REGALIAS diff --git a/src/coreclr/jit/registerwasm.h b/src/coreclr/jit/registerwasm.h index 6001460c8896e1..fac983875be947 100644 --- a/src/coreclr/jit/registerwasm.h +++ b/src/coreclr/jit/registerwasm.h @@ -1,4 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// clang-format off -REGDEF(STK, 1, 0x0, "STK") +/*****************************************************************************/ +/*****************************************************************************/ +#ifndef REGDEF +#error Must define REGDEF macro before including this file +#endif +#ifndef REGALIAS +#define REGALIAS(alias, realname) +#endif + +/* +REGDEF(name, rnum, mask, sname) */ + +// This must be last! +REGDEF(STK, 1, 0x0000, "STK") + +/*****************************************************************************/ +#undef REGDEF +#undef REGALIAS +/*****************************************************************************/ + +// clang-format on diff --git a/src/coreclr/jit/registerx86.h b/src/coreclr/jit/registerx86.h new file mode 100644 index 00000000000000..0f3de489b95531 --- /dev/null +++ b/src/coreclr/jit/registerx86.h @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// clang-format off + +/*****************************************************************************/ +/*****************************************************************************/ +#ifndef REGDEF +#error Must define REGDEF macro before including this file +#endif +#ifndef REGALIAS +#define REGALIAS(alias, realname) +#endif + +/* +REGDEF(name, rnum, mask, sname) */ +REGDEF(EAX, 0, 0x01, "eax" ) +REGDEF(ECX, 1, 0x02, "ecx" ) +REGDEF(EDX, 2, 0x04, "edx" ) +REGDEF(EBX, 3, 0x08, "ebx" ) +REGDEF(ESP, 4, 0x10, "esp" ) +REGDEF(EBP, 5, 0x20, "ebp" ) +REGDEF(ESI, 6, 0x40, "esi" ) +REGDEF(EDI, 7, 0x80, "edi" ) + +REGALIAS(RAX, EAX) +REGALIAS(RCX, ECX) +REGALIAS(RDX, EDX) +REGALIAS(RBX, EBX) +REGALIAS(RSP, ESP) +REGALIAS(RBP, EBP) +REGALIAS(RSI, ESI) +REGALIAS(RDI, EDI) + +#define XMMBASE 8 +#define XMMMASK(x) ((int32_t)(1) << ((x)+XMMBASE)) + +REGDEF(XMM0, 0+XMMBASE, XMMMASK(0), "mm0" ) +REGDEF(XMM1, 1+XMMBASE, XMMMASK(1), "mm1" ) +REGDEF(XMM2, 2+XMMBASE, XMMMASK(2), "mm2" ) +REGDEF(XMM3, 3+XMMBASE, XMMMASK(3), "mm3" ) +REGDEF(XMM4, 4+XMMBASE, XMMMASK(4), "mm4" ) +REGDEF(XMM5, 5+XMMBASE, XMMMASK(5), "mm5" ) +REGDEF(XMM6, 6+XMMBASE, XMMMASK(6), "mm6" ) +REGDEF(XMM7, 7+XMMBASE, XMMMASK(7), "mm7" ) + +#define KBASE 16 +#define KMASK(x) ((int32_t)(1) << ((x)+KBASE)) + +REGDEF(K0, 0+KBASE, KMASK(0), "k0" ) +REGDEF(K1, 1+KBASE, KMASK(1), "k1" ) +REGDEF(K2, 2+KBASE, KMASK(2), "k2" ) +REGDEF(K3, 3+KBASE, KMASK(3), "k3" ) +REGDEF(K4, 4+KBASE, KMASK(4), "k4" ) +REGDEF(K5, 5+KBASE, KMASK(5), "k5" ) +REGDEF(K6, 6+KBASE, KMASK(6), "k6" ) +REGDEF(K7, 7+KBASE, KMASK(7), "k7" ) + +REGDEF(STK, 8+KBASE, 0x0000, "STK" ) + +// Ignore REG_* symbols defined in Android NDK +#undef REG_EAX +#define REG_EAX JITREG_EAX +#undef REG_ECX +#define REG_ECX JITREG_ECX +#undef REG_EDX +#define REG_EDX JITREG_EDX +#undef REG_EBX +#define REG_EBX JITREG_EBX +#undef REG_ESP +#define REG_ESP JITREG_ESP +#undef REG_EBP +#define REG_EBP JITREG_EBP +#undef REG_ESI +#define REG_ESI JITREG_ESI +#undef REG_EDI +#define REG_EDI JITREG_EDI + +#undef REG_RAX +#define REG_RAX JITREG_RAX +#undef REG_RCX +#define REG_RCX JITREG_RCX +#undef REG_RDX +#define REG_RDX JITREG_RDX +#undef REG_RBX +#define REG_RBX JITREG_RBX +#undef REG_RSP +#define REG_RSP JITREG_RSP +#undef REG_RBP +#define REG_RBP JITREG_RBP +#undef REG_RSI +#define REG_RSI JITREG_RSI +#undef REG_RDI +#define REG_RDI JITREG_RDI + +#undef REG_XMM0 +#define REG_XMM0 JITREG_XMM0 +#undef REG_XMM1 +#define REG_XMM1 JITREG_XMM1 +#undef REG_XMM2 +#define REG_XMM2 JITREG_XMM2 +#undef REG_XMM3 +#define REG_XMM3 JITREG_XMM3 +#undef REG_XMM4 +#define REG_XMM4 JITREG_XMM4 +#undef REG_XMM5 +#define REG_XMM5 JITREG_XMM5 +#undef REG_XMM6 +#define REG_XMM6 JITREG_XMM6 +#undef REG_XMM7 +#define REG_XMM7 JITREG_XMM7 + +#undef REG_K0 +#define REG_K0 JITREG_K0 +#undef REG_K1 +#define REG_K1 JITREG_K1 +#undef REG_K2 +#define REG_K2 JITREG_K2 +#undef REG_K3 +#define REG_K3 JITREG_K3 +#undef REG_K4 +#define REG_K4 JITREG_K4 +#undef REG_K5 +#define REG_K5 JITREG_K5 +#undef REG_K6 +#define REG_K6 JITREG_K6 +#undef REG_K7 +#define REG_K7 JITREG_K7 + +#undef REG_STK +#define REG_STK JITREG_STK + +/*****************************************************************************/ +#undef XMMMASK +#undef KMASK +#undef REGDEF +#undef REGALIAS +/*****************************************************************************/ + +// clang-format on diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs index f98f8990a65d98..c6b25756045c50 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs @@ -1721,10 +1721,10 @@ private void ForceSigWalk() } } - if (maxOffset == 0 && _transitionBlock.IsWasm32) + if (maxOffset == _transitionBlock.OffsetOfArgs && _transitionBlock.IsWasm32) { // Wasm puts all arguments on the stack, even the unnamed ones like the param registers, this pointer and async continuation. If we didn't see any named arguments, then we need to account for the unnamed ones here. - maxOffset = _wasmOfsStack; + maxOffset = _transitionBlock.OffsetOfArgs + _wasmOfsStack; } // Clear the iterator started flag diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 9786ffc489b473..45e0a2ff444800 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -129,30 +129,7 @@ public int Run() genericCycleDepthCutoff: Get(_command.MaxGenericCycleDepth), genericCycleBreadthCutoff: Get(_command.MaxGenericCycleBreadth)); - // - // TODO: To support our pre-compiled test tree, allow input files that aren't managed assemblies since - // some tests contain a mixture of both managed and native binaries. - // - // See: https://github.com/dotnet/corert/issues/2785 - // - // When we undo this hack, replace the foreach with - // typeSystemContext.InputFilePaths = _command.Result.GetValueForArgument(inputFilePaths); - // - Dictionary inputFilePaths = new Dictionary(); - foreach (var inputFile in _command.Result.GetValue(_command.InputFilePaths)) - { - try - { - var module = typeSystemContext.GetModuleFromPath(inputFile.Value); - inputFilePaths.Add(inputFile.Key, inputFile.Value); - } - catch (TypeSystemException.BadImageFormatException) - { - // Keep calm and carry on. - } - } - - typeSystemContext.InputFilePaths = inputFilePaths; + typeSystemContext.InputFilePaths = _command.Result.GetValue(_command.InputFilePaths); typeSystemContext.ReferenceFilePaths = Get(_command.ReferenceFiles); if (!typeSystemContext.InputFilePaths.ContainsKey(systemModuleName) && !typeSystemContext.ReferenceFilePaths.ContainsKey(systemModuleName)) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 40a9397767d289..bbeafe501f5850 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -146,6 +146,8 @@ CDAC_TYPE_FIELD(ExceptionInfo, T_UINT8, PassNumber, offsetof(ExInfo, m_passNumbe CDAC_TYPE_FIELD(ExceptionInfo, T_POINTER, CSFEHClause, offsetof(ExInfo, m_csfEHClause)) CDAC_TYPE_FIELD(ExceptionInfo, T_POINTER, CSFEnclosingClause, offsetof(ExInfo, m_csfEnclosingClause)) CDAC_TYPE_FIELD(ExceptionInfo, T_POINTER, CallerOfActualHandlerFrame, offsetof(ExInfo, m_sfCallerOfActualHandlerFrame)) +CDAC_TYPE_FIELD(ExceptionInfo, T_UINT32, ClauseForCatchHandlerStartPC, offsetof(ExInfo, m_ClauseForCatch) + offsetof(EE_ILEXCEPTION_CLAUSE, HandlerStartPC)) +CDAC_TYPE_FIELD(ExceptionInfo, T_UINT32, ClauseForCatchHandlerEndPC, offsetof(ExInfo, m_ClauseForCatch) + offsetof(EE_ILEXCEPTION_CLAUSE, HandlerEndPC)) CDAC_TYPE_END(ExceptionInfo) CDAC_TYPE_BEGIN(ObjectHandle) @@ -732,6 +734,8 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, HotColdMap, cdac_data CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, DelayLoadMethodCallThunks, cdac_data::DelayLoadMethodCallThunks) CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, DebugInfoSection, cdac_data::DebugInfoSection) CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, ExceptionInfoSection, cdac_data::ExceptionInfoSection) +CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, ImportSections, cdac_data::ImportSections) +CDAC_TYPE_FIELD(ReadyToRunInfo, T_UINT32, NumImportSections, cdac_data::NumImportSections) CDAC_TYPE_FIELD(ReadyToRunInfo, TYPE(HashMap), EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, LoadedImageBase, cdac_data::LoadedImageBase) CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, Composite, cdac_data::Composite) @@ -976,9 +980,19 @@ CDAC_TYPE_BEGIN(TransitionBlock) CDAC_TYPE_SIZE(sizeof(TransitionBlock)) CDAC_TYPE_FIELD(TransitionBlock, T_POINTER, ReturnAddress, offsetof(TransitionBlock, m_ReturnAddress)) CDAC_TYPE_FIELD(TransitionBlock, TYPE(CalleeSavedRegisters), CalleeSavedRegisters, offsetof(TransitionBlock, m_calleeSavedRegisters)) -#ifdef TARGET_ARM -CDAC_TYPE_FIELD(TransitionBlock, TYPE(ArgumentRegisters), ArgumentRegisters, offsetof(TransitionBlock, m_argumentRegisters)) -#endif // TARGET_ARM +// Offset to where stack arguments begin (just past the end of the TransitionBlock) +CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, OffsetOfArgs, sizeof(TransitionBlock)) +// Offset to argument registers and first GCRefMap slot (platform-specific) +#if (defined(TARGET_AMD64) && !defined(UNIX_AMD64_ABI)) || defined(TARGET_WASM) +CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, ArgumentRegisters, sizeof(TransitionBlock)) +CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, FirstGCRefMapSlot, sizeof(TransitionBlock)) +#elif defined(TARGET_ARM64) +CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, ArgumentRegisters, offsetof(TransitionBlock, m_argumentRegisters)) +CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, FirstGCRefMapSlot, offsetof(TransitionBlock, m_x8RetBuffReg)) +#else +CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, ArgumentRegisters, offsetof(TransitionBlock, m_argumentRegisters)) +CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, FirstGCRefMapSlot, offsetof(TransitionBlock, m_argumentRegisters)) +#endif CDAC_TYPE_END(TransitionBlock) #ifdef DEBUGGING_SUPPORTED @@ -999,8 +1013,19 @@ CDAC_TYPE_SIZE(sizeof(StubDispatchFrame)) CDAC_TYPE_FIELD(StubDispatchFrame, T_POINTER, RepresentativeMTPtr, cdac_data::RepresentativeMTPtr) CDAC_TYPE_FIELD(StubDispatchFrame, T_POINTER, MethodDescPtr, cdac_data::MethodDescPtr) CDAC_TYPE_FIELD(StubDispatchFrame, T_UINT32, RepresentativeSlot, cdac_data::RepresentativeSlot) +CDAC_TYPE_FIELD(StubDispatchFrame, T_POINTER, Indirection, cdac_data::Indirection) CDAC_TYPE_END(StubDispatchFrame) +CDAC_TYPE_BEGIN(ExternalMethodFrame) +CDAC_TYPE_SIZE(sizeof(ExternalMethodFrame)) +CDAC_TYPE_FIELD(ExternalMethodFrame, T_POINTER, Indirection, cdac_data::Indirection) +CDAC_TYPE_END(ExternalMethodFrame) + +CDAC_TYPE_BEGIN(DynamicHelperFrame) +CDAC_TYPE_SIZE(sizeof(DynamicHelperFrame)) +CDAC_TYPE_FIELD(DynamicHelperFrame, T_INT32, DynamicHelperFrameFlags, cdac_data::DynamicHelperFrameFlags) +CDAC_TYPE_END(DynamicHelperFrame) + #ifdef FEATURE_HIJACK CDAC_TYPE_BEGIN(ResumableFrame) CDAC_TYPE_SIZE(sizeof(ResumableFrame)) @@ -1385,6 +1410,7 @@ CDAC_GLOBAL_POINTER(MetadataUpdatesApplied, &::g_metadataUpdatesApplied) #undef FRAME_TYPE_NAME CDAC_GLOBAL(MethodDescTokenRemainderBitCount, T_UINT8, METHOD_TOKEN_REMAINDER_BIT_COUNT) + #if FEATURE_COMINTEROP CDAC_GLOBAL(FeatureCOMInterop, T_UINT8, 1) #else @@ -1548,7 +1574,7 @@ CDAC_GLOBAL_CONTRACT(ReJIT, c1) CDAC_GLOBAL_CONTRACT(RuntimeInfo, c1) CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, c1) CDAC_GLOBAL_CONTRACT(SHash, c1) -CDAC_GLOBAL_CONTRACT(SignatureDecoder, c1) +CDAC_GLOBAL_CONTRACT(Signature, c1) CDAC_GLOBAL_CONTRACT(StackWalk, c1) CDAC_GLOBAL_CONTRACT(StressLog, c2) CDAC_GLOBAL_CONTRACT(SyncBlock, c1) diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index f3fccab5615efa..00f4d86f2578c3 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -1490,6 +1490,7 @@ struct cdac_data { static constexpr size_t RepresentativeMTPtr = offsetof(StubDispatchFrame, m_pRepresentativeMT); static constexpr uint32_t RepresentativeSlot = offsetof(StubDispatchFrame, m_representativeSlot); + static constexpr size_t Indirection = offsetof(StubDispatchFrame, m_pIndirection); }; typedef DPTR(class StubDispatchFrame) PTR_StubDispatchFrame; @@ -1561,10 +1562,18 @@ class ExternalMethodFrame : public FramedMethodFrame #ifdef TARGET_X86 void UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats = false); #endif + + friend struct ::cdac_data; }; typedef DPTR(class ExternalMethodFrame) PTR_ExternalMethodFrame; +template <> +struct cdac_data +{ + static constexpr size_t Indirection = offsetof(ExternalMethodFrame, m_pIndirection); +}; + class DynamicHelperFrame : public FramedMethodFrame { int m_dynamicHelperFrameFlags; @@ -1583,10 +1592,18 @@ class DynamicHelperFrame : public FramedMethodFrame LIMITED_METHOD_DAC_CONTRACT; return TT_InternalCall; } + + friend struct ::cdac_data; }; typedef DPTR(class DynamicHelperFrame) PTR_DynamicHelperFrame; +template <> +struct cdac_data +{ + static constexpr size_t DynamicHelperFrameFlags = offsetof(DynamicHelperFrame, m_dynamicHelperFrameFlags); +}; + //------------------------------------------------------------------------ // This frame protects object references for the EE's convenience. // This frame type actually is created from C++. diff --git a/src/coreclr/vm/gc_unwind_x86.inl b/src/coreclr/vm/gc_unwind_x86.inl index 1142df47a0527b..54b9c54700ea95 100644 --- a/src/coreclr/vm/gc_unwind_x86.inl +++ b/src/coreclr/vm/gc_unwind_x86.inl @@ -3082,10 +3082,14 @@ bool EnumGcRefsX86(PREGDISPLAY pContext, } #endif - /* Are we in the prolog or epilog of the method? */ + /* Are we in the prolog or epilog of the method, or is this a + * non-interruptible method that will not resume execution at this offset? + * In either case, GC slots may not be initialized at the current offset + * and we can simply skip all reporting. */ if (info.prologOffs != hdrInfo::NOT_IN_PROLOG || - info.epilogOffs != hdrInfo::NOT_IN_EPILOG) + info.epilogOffs != hdrInfo::NOT_IN_EPILOG || + ((flags & ExecutionAborted) && !info.interruptible)) { #if !DUMP_PTR_REFS diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 6963a5000311e7..64c3324d9b2acc 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -406,6 +406,8 @@ struct cdac_data static constexpr size_t DelayLoadMethodCallThunks = offsetof(ReadyToRunInfo, m_pSectionDelayLoadMethodCallThunks); static constexpr size_t DebugInfoSection = offsetof(ReadyToRunInfo, m_pSectionDebugInfo); static constexpr size_t ExceptionInfoSection = offsetof(ReadyToRunInfo, m_pSectionExceptionInfo); + static constexpr size_t ImportSections = offsetof(ReadyToRunInfo, m_pImportSections); + static constexpr size_t NumImportSections = offsetof(ReadyToRunInfo, m_nImportSections); static constexpr size_t EntryPointToMethodDescMap = offsetof(ReadyToRunInfo, m_entryPointToMethodDescMap); static constexpr size_t LoadedImageBase = offsetof(ReadyToRunInfo, m_pLoadedImageBase); static constexpr size_t Composite = offsetof(ReadyToRunInfo, m_pComposite); diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs index 1383c8f7e062d6..156b6a24185940 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs @@ -31,6 +31,7 @@ public static partial class PlatformDetection public static bool IsLinuxBionic => IsBionic(); public static bool IsRedHatFamily => IsRedHatFamilyAndVersion(); public static bool IsAzureLinux => IsDistroAndVersionOrHigher("azurelinux", 3); + public static bool IsAzureLinux4OrHigher => IsDistroAndVersionOrHigher("azurelinux", 4); public static bool IsMonoLinuxArm64 => IsMonoRuntime && IsLinux && IsArm64Process; public static bool IsNotMonoLinuxArm64 => !IsMonoLinuxArm64; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/LocalAppContextSwitches.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/LocalAppContextSwitches.cs index b41f05be289cce..48042a88c5b0df 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/LocalAppContextSwitches.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/LocalAppContextSwitches.cs @@ -39,6 +39,16 @@ public static bool IgnoreKindInUtcTimeSerialization } } + private static int s_useXmlSerializerReadEndElementWorkaround; + public static bool UseXmlSerializerReadEndElementWorkaround + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return SwitchesHelpers.GetCachedSwitchValue("Switch.System.Xml.UseXmlSerializerReadEndElementWorkaround", ref s_useXmlSerializerReadEndElementWorkaround, defaultValue: true); + } + } + private static int s_allowXsdTimeToTimeOnlyWithOffsetLoss; public static bool AllowXsdTimeToTimeOnlyWithOffsetLoss { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs index 79741d5e849316..df3b1134f08887 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs @@ -1892,8 +1892,24 @@ protected void AddReadCallback(string name, string ns, Type type, XmlSerializati protected void ReadEndElement() { while (_r.NodeType == XmlNodeType.Whitespace) _r.Skip(); - if (_r.NodeType == XmlNodeType.None) _r.Skip(); - else _r.ReadEndElement(); + + if (LocalAppContextSwitches.UseXmlSerializerReadEndElementWorkaround) + { + if (_r.NodeType == XmlNodeType.None) + return; + + // Avoid forcing the reader to pull one more token after completing a top-level element. + // In fragment scenarios over streaming transports, additional data may not be immediately + // available even though deserialization of the current element is already complete. + if (_r.NodeType == XmlNodeType.EndElement && _r.Depth == 0) + return; + } + else if (_r.NodeType == XmlNodeType.None) + { + _r.Skip(); + } + + _r.ReadEndElement(); } private object ReadXmlNodes(bool elementCanBeType) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs index 51f3a7d779824f..e42e02d10f245e 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs @@ -486,6 +486,8 @@ private XmlMapping GetMapping() events.sender = this; try { + AdvancePastTopLevelEndElementIfNeeded(xmlReader); + if (_primitiveType != null) { if (encodingStyle != null && encodingStyle.Length > 0) @@ -542,6 +544,8 @@ private static bool ShouldUseReflectionBasedSerialization(XmlMapping mapping) public virtual bool CanDeserialize(XmlReader xmlReader) { + AdvancePastTopLevelEndElementIfNeeded(xmlReader); + if (_primitiveType != null) { TypeDesc typeDesc = (TypeDesc)TypeScope.PrimtiveTypes[_primitiveType]!; @@ -839,6 +843,16 @@ internal void SetTempAssembly(TempAssembly tempAssembly, XmlMapping mapping) _typedSerializer = true; } + private static void AdvancePastTopLevelEndElementIfNeeded(XmlReader xmlReader) + { + if (LocalAppContextSwitches.UseXmlSerializerReadEndElementWorkaround && + xmlReader.NodeType == XmlNodeType.EndElement && + xmlReader.Depth == 0) + { + xmlReader.Read(); + } + } + private static XmlTypeMapping? GetKnownMapping(Type type, string? ns) { if (ns != null && ns != string.Empty) diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs index fc285987efc134..1451e7407d8148 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs @@ -950,6 +950,52 @@ public static void Xml_DifferentSerializeDeserializeOverloads() } } + [Fact] + public static void Xml_DeserializeFragmentsFromXmlReader() + { + const string payload = "one1two2"; + XmlSerializer serializer = new XmlSerializer(typeof(SimpleType)); + byte[] data = Encoding.UTF8.GetBytes(payload); + + // "Switch.System.Xml.UseXmlSerializerReadEndElementWorkaround" default should be true + using var stream = new BlockingAfterBufferStream(data); + using var reader = XmlReader.Create(stream, new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }); + + Assert.True(serializer.CanDeserialize(reader)); + + SimpleType first = (SimpleType)serializer.Deserialize(reader); + Assert.Equal("one", first.P1); + Assert.Equal(1, first.P2); + + Assert.True(serializer.CanDeserialize(reader)); + + SimpleType second = (SimpleType)serializer.Deserialize(reader); + Assert.Equal("two", second.P1); + Assert.Equal(2, second.P2); + } + + [Fact] + public static void Xml_DeserializeFragmentsFromXmlReader_CanDisableWorkaround() + { + const string switchName = "Switch.System.Xml.UseXmlSerializerReadEndElementWorkaround"; + const string payload = "one1two2"; + XmlSerializer serializer = new XmlSerializer(typeof(SimpleType)); + byte[] data = Encoding.UTF8.GetBytes(payload); + + using var compatSwitch = new XmlSerializerAppContextSwitchScope(switchName, false); + Assert.False(compatSwitch.CurrentValue); + + using var stream = new BlockingAfterBufferStream(data); + using var reader = XmlReader.Create(stream, new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }); + + SimpleType first = (SimpleType)serializer.Deserialize(reader); + Assert.Equal("one", first.P1); + Assert.Equal(1, first.P2); + + InvalidOperationException exception = Assert.Throws(() => serializer.Deserialize(reader)); + Assert.IsType(exception.InnerException); + } + [Fact] public static void Xml_TypeWithTimeSpanProperty() { @@ -3115,6 +3161,45 @@ private static string FormatTimeString(DateTime time, bool ignoreUtc) => time.ToString($"HH:mm:ss.fffffff{(!ignoreUtc && time.Kind == DateTimeKind.Utc ? "Z" : "zzzzzz")}", CultureInfo.InvariantCulture); } +internal sealed class BlockingAfterBufferStream : Stream +{ + private readonly byte[] _buffer; + private int _position; + + public BlockingAfterBufferStream(byte[] buffer) + { + _buffer = buffer; + } + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => _buffer.Length; + public override long Position + { + get => _position; + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (_position < _buffer.Length) + { + int toCopy = Math.Min(count, _buffer.Length - _position); + Array.Copy(_buffer, _position, buffer, offset, toCopy); + _position += toCopy; + return toCopy; + } + + throw new TimeoutException("Simulated Exception - No additional data is available."); + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override void Flush() { } +} + internal sealed class XmlSerializerAppContextSwitchScope : IDisposable { private readonly string _name; @@ -3139,11 +3224,31 @@ public void Dispose() if (_hadValue) AppContext.SetSwitch(_name, _originalValue); else - // There's no "unset", so pick a default or false - AppContext.SetSwitch(_name, false); + UnsetSwitch(_name); + ClearCachedSwitch(_cachedName); } + private static void UnsetSwitch(string name) + { + const BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Static; + + if (typeof(AppContext).GetField("s_switches", Flags)?.GetValue(null) is IDictionary switches) + { + lock (switches) + { + switches.Remove(name); + } + } + + if (typeof(AppContext).GetField("s_dataStore", Flags)?.GetValue(null) is IDictionary dataStore) + { + lock (dataStore) + { + dataStore.Remove(name); + } + } + } private static void ClearCachedSwitch(string name) { Type t = Type.GetType("System.Xml.LocalAppContextSwitches, System.Private.Xml"); diff --git a/src/libraries/System.Security.Cryptography/tests/ChaCha20Poly1305Tests.cs b/src/libraries/System.Security.Cryptography/tests/ChaCha20Poly1305Tests.cs index 43462335b888b4..ff618943d0546f 100644 --- a/src/libraries/System.Security.Cryptography/tests/ChaCha20Poly1305Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/ChaCha20Poly1305Tests.cs @@ -480,8 +480,9 @@ public static void CheckIsSupported() } else if (PlatformDetection.IsAzureLinux) { - // Though Azure Linux uses OpenSSL, they build OpenSSL without ChaCha20-Poly1305. - expectedIsSupported = false; + // Though Azure Linux uses OpenSSL, Azure Linux 3 built OpenSSL with ChaCha20Poly1305 disabled. + // It was re-enabled in Azure Linux 4. + expectedIsSupported = PlatformDetection.IsAzureLinux4OrHigher; } else if (PlatformDetection.OpenSslPresentOnSystem && PlatformDetection.IsOpenSslSupported) { 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 9d4088ee27b15c..ae50ec7fae054b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -97,9 +97,9 @@ public abstract class ContractRegistry /// public virtual ICodeNotifications CodeNotifications => GetContract(); /// - /// Gets an instance of the SignatureDecoder contract for the target. + /// Gets an instance of the Signature contract for the target. /// - public virtual ISignatureDecoder SignatureDecoder => GetContract(); + public virtual ISignature Signature => GetContract(); /// /// Gets an instance of the SyncBlock contract for the target. /// 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 0dddd31417e5ad..1856c2c3a4ea9d 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 @@ -101,6 +101,7 @@ public interface IExecutionManager : IContract List GetExceptionClauses(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); JitManagerInfo GetEEJitManagerInfo() => throw new NotImplementedException(); IEnumerable GetCodeHeapInfos() => throw new NotImplementedException(); + TargetPointer FindReadyToRunModule(TargetPointer address) => throw new NotImplementedException(); } public readonly struct ExecutionManager : IExecutionManager 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 index d94ed45048b256..8a70ba34aae189 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs @@ -2,18 +2,57 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; namespace Microsoft.Diagnostics.DataContractReader.Contracts; public interface IGCInfoHandle { } +/// +/// Describes a code region where the GC can safely interrupt execution. +/// +/// Start of the interruptible region, as a byte offset from the method start. +/// End of the interruptible region (exclusive), as a byte offset from the method start. +public readonly record struct InterruptibleRange(uint StartOffset, uint EndOffset); + +/// +/// Describes a live GC slot at a given instruction offset. +/// +/// True if the slot is a CPU register; false if it is a stack location. +/// Register number (meaningful only when IsRegister is true). +/// Stack offset from the base (meaningful only when IsRegister is false). +/// Stack base: 0 = CALLER_SP_REL, 1 = SP_REL, 2 = FRAMEREG_REL. +/// GC slot flags: 0x1 = interior pointer, 0x2 = pinned. +public readonly record struct LiveSlot(bool IsRegister, uint RegisterNumber, int SpOffset, uint SpBase, uint GcFlags); + +/// +/// Options controlling which GC slots are reported by . +/// +public record struct GcSlotEnumerationOptions +{ + /// True if this is the active (leaf) stack frame. When false, scratch register and stack slots are excluded. + public bool IsActiveFrame { get; set; } + /// True if execution was aborted (e.g., interrupted by exception). Skips live slot reporting at non-interruptible offsets. + public bool IsExecutionAborted { get; set; } + /// True if the frame is a parent of a funclet that already reported GC references. + public bool IsParentOfFuncletStackFrame { get; set; } + /// True to suppress reporting of untracked slots (e.g., for filter funclets). + public bool SuppressUntrackedSlots { get; set; } + /// True to report only frame-register-relative stack slots (skips all register slots and non-frame-relative stack slots). + public bool ReportFPBasedSlotsOnly { get; set; } +} + public interface IGCInfo : IContract { static string IContract.Name { get; } = nameof(GCInfo); IGCInfoHandle DecodePlatformSpecificGCInfo(TargetPointer gcInfoAddress, uint gcVersion) => throw new NotImplementedException(); IGCInfoHandle DecodeInterpreterGCInfo(TargetPointer gcInfoAddress, uint gcVersion) => throw new NotImplementedException(); + uint GetCodeLength(IGCInfoHandle handle) => throw new NotImplementedException(); + uint GetStackBaseRegister(IGCInfoHandle handle) => throw new NotImplementedException(); + IReadOnlyList GetInterruptibleRanges(IGCInfoHandle handle) => throw new NotImplementedException(); + IReadOnlyList EnumerateLiveSlots(IGCInfoHandle handle, uint instructionOffset, GcSlotEnumerationOptions options) => throw new NotImplementedException(); } public readonly struct GCInfo : IGCInfo diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 96310c327c262b..76a07d7753696f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -103,7 +103,7 @@ public interface ILoader : IContract bool IsProbeExtensionResultValid(ModuleHandle handle) => throw new NotImplementedException(); ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); bool IsReadyToRun(ModuleHandle handle) => throw new NotImplementedException(); - bool TryGetSimpleName(ModuleHandle handle, out string simpleName) => throw new NotImplementedException(); + string GetSimpleName(ModuleHandle handle) => throw new NotImplementedException(); string GetPath(ModuleHandle handle) => throw new NotImplementedException(); string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 7b81723becd276..6bdde7301068bb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -185,6 +185,14 @@ public interface IRuntimeTypeSystem : IContract bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException(); ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException(); + // Return true if the method requires a hidden instantiation argument (generic context parameter). + // This corresponds to native MethodDesc::RequiresInstArg(). + bool RequiresInstArg(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + // Return true if the method uses the async calling convention (CORINFO_CALLCONV_ASYNCCALL). + // This corresponds to native MethodDesc::IsAsyncMethod(). + bool IsAsyncMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); + // Return mdtMethodDef (0x06000000) if the method doesn't have a token, otherwise return the token of the method uint GetMethodToken(MethodDescHandle methodDesc) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignature.cs similarity index 71% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignature.cs index 5977d736b74d43..f53847ea4e3b55 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignature.cs @@ -6,13 +6,13 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -public interface ISignatureDecoder : IContract +public interface ISignature : IContract { - static string IContract.Name { get; } = nameof(SignatureDecoder); + static string IContract.Name { get; } = nameof(Signature); TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) => throw new NotImplementedException(); } -public readonly struct SignatureDecoder : ISignatureDecoder +public readonly struct Signature : ISignature { // 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 edd009d638a924..9440afccfff014 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -162,6 +162,9 @@ public enum DataType HijackFrame, TailCallFrame, StubDispatchFrame, + ExternalMethodFrame, + DynamicHelperFrame, + ComCallWrapper, SimpleComCallWrapper, ComMethodTable, 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 43e89fbe2237e7..47f629ffd5700b 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 @@ -390,6 +390,19 @@ TargetNUInt IExecutionManager.GetRelativeOffset(CodeBlockHandle codeInfoHandle) return info.RelativeOffset; } + TargetPointer IExecutionManager.FindReadyToRunModule(TargetPointer address) + { + // Use the range section map to find the RangeSection containing the address. + // The R2R range section covers the entire PE image (code + data), so this + // works for import section addresses used by FindGCRefMap. + TargetCodePointer codeAddr = CodePointerUtils.CodePointerFromAddress(address, _target); + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeAddr); + if (range.Data is null) + return TargetPointer.Null; + + return range.Data.R2RModule; + } + JitManagerInfo IExecutionManager.GetEEJitManagerInfo() { TargetPointer eeJitManagerPtr = _target.ReadGlobalPointer(Constants.Globals.EEJitManagerAddress); 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 b636a2914c36ec..c082eb9ccdc969 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 @@ -34,5 +34,6 @@ internal ExecutionManager_1(Target target) public List GetExceptionClauses(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetExceptionClauses(codeInfoHandle); public JitManagerInfo GetEEJitManagerInfo() => _executionManagerCore.GetEEJitManagerInfo(); public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); + public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address); public void Flush() => _executionManagerCore.Flush(); } 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 6b84fda982ab5e..da5b1d6dc71f93 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 @@ -34,5 +34,6 @@ internal ExecutionManager_2(Target target) public List GetExceptionClauses(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetExceptionClauses(codeInfoHandle); public JitManagerInfo GetEEJitManagerInfo() => _executionManagerCore.GetEEJitManagerInfo(); public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); + public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address); public void Flush() => _executionManagerCore.Flush(); } 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 index d6a6a0da8b39f4..aa7d919b8aa8d6 100644 --- 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 @@ -68,8 +68,6 @@ internal enum GcStackSlotBase : uint GC_SPBASE_LAST = GC_FRAMEREG_REL, } - public readonly record struct InterruptibleRange(uint StartOffset, uint EndOffset); - public readonly record struct GcSlotDesc { /* Register Slot */ @@ -514,62 +512,55 @@ public uint GetCodeLength() return _codeLength; } - public IReadOnlyList GetInterruptibleRanges() + public uint GetStackBaseRegister() { - EnsureDecodedTo(DecodePoints.InterruptibleRanges); - return _interruptibleRanges; + EnsureDecodedTo(DecodePoints.ReversePInvoke); + return _stackBaseRegister; } - public uint StackBaseRegister + public IReadOnlyList GetInterruptibleRanges() { - get - { - EnsureDecodedTo(DecodePoints.ReversePInvoke); - return _stackBaseRegister; - } + EnsureDecodedTo(DecodePoints.InterruptibleRanges); + return _interruptibleRanges; } public uint NumTrackedSlots => _numSlots - _numUntrackedSlots; - bool IGCInfoDecoder.EnumerateLiveSlots( + IReadOnlyList IGCInfoDecoder.EnumerateLiveSlots( uint instructionOffset, - CodeManagerFlags flags, - LiveSlotCallback reportSlot) + GcSlotEnumerationOptions options) { - return EnumerateLiveSlots(instructionOffset, flags, + List result = []; + EnumerateLiveSlots(instructionOffset, options, (uint slotIndex, GcSlotDesc slot, uint gcFlags) => { - reportSlot(slot.IsRegister, slot.RegisterNumber, slot.SpOffset, (uint)slot.Base, gcFlags); + result.Add(new LiveSlot(slot.IsRegister, slot.RegisterNumber, slot.SpOffset, (uint)slot.Base, gcFlags)); }); + return result; } /// /// Enumerates all GC slots that are live at the given instruction offset, invoking the callback for each. /// This is the managed equivalent of the native GcInfoDecoder::EnumerateLiveSlots. /// - /// The current instruction offset (relative to method start). - /// CodeManagerFlags controlling reporting behavior. - /// Called for each live slot with (slotIndex, slotDesc, gcFlags). - /// gcFlags contains GC_SLOT_INTERIOR/GC_SLOT_PINNED from the slot descriptor. - /// True if enumeration succeeded. - public bool EnumerateLiveSlots( + private bool EnumerateLiveSlots( uint instructionOffset, - CodeManagerFlags flags, + GcSlotEnumerationOptions options, Action reportSlot) { EnsureDecodedTo(DecodePoints.SlotTable); - bool executionAborted = flags.HasFlag(CodeManagerFlags.ExecutionAborted); - bool reportScratchSlots = flags.HasFlag(CodeManagerFlags.ActiveStackFrame); - bool reportFpBasedSlotsOnly = flags.HasFlag(CodeManagerFlags.ReportFPBasedSlotsOnly); + bool executionAborted = options.IsExecutionAborted; + bool reportScratchSlots = options.IsActiveFrame; + bool reportFpBasedSlotsOnly = options.ReportFPBasedSlotsOnly; // WantsReportOnlyLeaf is always true for non-legacy formats - if (flags.HasFlag(CodeManagerFlags.ParentOfFuncletStackFrame)) + if (options.IsParentOfFuncletStackFrame) return true; uint numTracked = NumTrackedSlots; if (numTracked == 0) - goto ReportUntracked; + return ReportUntrackedAndSucceed(); uint normBreakOffset = TTraits.NormalizeCodeOffset(instructionOffset); @@ -655,7 +646,7 @@ public bool EnumerateLiveSlots( fReport = !fReport; } Debug.Assert(readSlots == numTracked); - goto ReportUntracked; + return ReportUntrackedAndSucceed(); } // Normal 1-bit-per-slot encoding follows } @@ -669,7 +660,7 @@ public bool EnumerateLiveSlots( if (_reader.ReadBits(1, ref bitOffset) != 0) ReportSlot(slotIndex, reportScratchSlots, reportFpBasedSlotsOnly, reportSlot); } - goto ReportUntracked; + return ReportUntrackedAndSucceed(); } else { @@ -682,7 +673,7 @@ public bool EnumerateLiveSlots( bitOffset += (int)(_numSafePoints * numTracked); if (_numInterruptibleRanges == 0) - goto ReportUntracked; + return ReportUntrackedAndSucceed(); } // ---- Fully-interruptible path ---- @@ -695,7 +686,7 @@ public bool EnumerateLiveSlots( uint numBitsPerPointer = (uint)_reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref bitOffset); if (numBitsPerPointer == 0) - goto ReportUntracked; + return ReportUntrackedAndSucceed(); int pointerTablePos = bitOffset; @@ -709,7 +700,7 @@ public bool EnumerateLiveSlots( if (chunkPointer != 0) break; if (chunk-- == 0) - goto ReportUntracked; + return ReportUntrackedAndSucceed(); } int chunksStartPos = (int)(((uint)pointerTablePos + numChunks * numBitsPerPointer + 7) & (~7u)); @@ -815,14 +806,22 @@ public bool EnumerateLiveSlots( } } - ReportUntracked: - if (_numUntrackedSlots > 0 && (flags & (CodeManagerFlags.ParentOfFuncletStackFrame | CodeManagerFlags.NoReportUntracked)) == 0) + return ReportUntrackedAndSucceed(); + + bool ReportUntrackedAndSucceed() { - for (uint slotIndex = numTracked; slotIndex < _numSlots; slotIndex++) - ReportSlot(slotIndex, reportScratchSlots, reportFpBasedSlotsOnly, reportSlot); + if (_numUntrackedSlots > 0 && !options.IsParentOfFuncletStackFrame && !options.SuppressUntrackedSlots) + { + // Native passes reportScratchSlots=true for untracked slots (see native + // ReportUntrackedSlots: "Report everything (although there should *never* + // be any scratch slots that are untracked)"). In practice the JIT can + // produce untracked scratch register slots for interior pointers, so they + // must be reported regardless of whether this is a leaf frame. + for (uint slotIndex = numTracked; slotIndex < _numSlots; slotIndex++) + ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot); + } + return true; } - - return true; } private void ReportSlot(uint slotIndex, bool reportScratchSlots, bool reportFpBasedSlotsOnly, Action reportSlot) 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 index f34292572a936e..db1dc4dd79d519 100644 --- 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 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -27,6 +28,24 @@ uint IGCInfo.GetCodeLength(IGCInfoHandle gcInfoHandle) return handle.GetCodeLength(); } + uint IGCInfo.GetStackBaseRegister(IGCInfoHandle gcInfoHandle) + { + IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle); + return handle.GetStackBaseRegister(); + } + + IReadOnlyList IGCInfo.GetInterruptibleRanges(IGCInfoHandle gcInfoHandle) + { + IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle); + return handle.GetInterruptibleRanges(); + } + + IReadOnlyList IGCInfo.EnumerateLiveSlots(IGCInfoHandle gcInfoHandle, uint instructionOffset, GcSlotEnumerationOptions options) + { + IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle); + return handle.EnumerateLiveSlots(instructionOffset, options); + } + private static IGCInfoDecoder AssertCorrectHandle(IGCInfoHandle gcInfoHandle) { if (gcInfoHandle is not IGCInfoDecoder handle) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs index 86f4210a7cb91d..fcf7aa46c691bb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs @@ -2,38 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; -/// -/// Flags controlling GC reference reporting behavior. -/// These match the native ICodeManager flags in eetwain.h. -/// -[Flags] -internal enum CodeManagerFlags : uint -{ - ActiveStackFrame = 0x1, - ExecutionAborted = 0x2, - ParentOfFuncletStackFrame = 0x40, - NoReportUntracked = 0x80, - ReportFPBasedSlotsOnly = 0x200, -} - internal interface IGCInfoDecoder : IGCInfoHandle { uint GetCodeLength(); - uint StackBaseRegister { get; } - - /// - /// Enumerates all live GC slots at the given instruction offset. - /// - /// Relative offset from method start. - /// CodeManagerFlags controlling reporting. - /// Callback: (isRegister, registerNumber, spOffset, spBase, gcFlags). - bool EnumerateLiveSlots( - uint instructionOffset, - CodeManagerFlags flags, - LiveSlotCallback reportSlot); + uint GetStackBaseRegister(); + IReadOnlyList GetInterruptibleRanges(); + IReadOnlyList EnumerateLiveSlots(uint instructionOffset, GcSlotEnumerationOptions options); } - -internal delegate void LiveSlotCallback(bool isRegister, uint registerNumber, int spOffset, uint spBase, uint gcFlags); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index cc517fbacb60ee..1a4ddc04445b03 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -447,17 +447,12 @@ bool ILoader.IsReadyToRun(ModuleHandle handle) return module.ReadyToRunInfo != TargetPointer.Null; } - bool ILoader.TryGetSimpleName(ModuleHandle handle, out string simpleName) + string ILoader.GetSimpleName(ModuleHandle handle) { - simpleName = string.Empty; Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - if (module.SimpleName != TargetPointer.Null) - { - simpleName = _target.ReadUtf8String(module.SimpleName, strict: true); - return true; - } - else - return false; + return module.SimpleName != TargetPointer.Null + ? _target.ReadUtf8String(module.SimpleName, strict: true) + : string.Empty; } string ILoader.GetPath(ModuleHandle handle) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index a0fe5b5eeb3a54..706c016420e3ba 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -155,6 +155,7 @@ internal enum DynamicMethodDescExtendedFlags : uint internal enum AsyncMethodFlags : uint { None = 0, + AsyncCall = 0x1, Thunk = 16, } @@ -314,6 +315,7 @@ private static uint ComputeSize(Target target, Data.MethodDesc desc) internal bool HasStableEntryPoint => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint); internal bool HasPrecode => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasPrecode); + internal bool IsStatic => HasFlags(MethodDescFlags_1.MethodDescFlags.Static); internal TargetPointer GetAddressOfNonVtableSlot() => MethodDescOptionalSlots.GetAddressOfNonVtableSlot(Address, Classification, _desc.Flags, _target); internal TargetPointer GetAddressOfNativeCodeSlot() => MethodDescOptionalSlots.GetAddressOfNativeCodeSlot(Address, Classification, _desc.Flags, _target); @@ -1326,6 +1328,68 @@ public ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle m return AsInstantiatedMethodDesc(methodDesc).Instantiation; } + /// + /// Returns true if the method requires a hidden instantiation argument (generic context parameter). + /// Matches native MethodDesc::RequiresInstArg(). + /// + public bool RequiresInstArg(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + // RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface) + if (!IsSharedByGenericInstantiations(methodDesc)) + return false; + + if (HasMethodInstantiation(methodDesc)) + return true; + + if (methodDesc.IsStatic) + return true; + + MethodTable mt = _methodTables[methodDesc.MethodTable]; + if (mt.Flags.IsInterface) + return true; + + if (mt.Flags.IsValueType) + return true; + + return false; + } + + private bool IsSharedByGenericInstantiations(MethodDesc methodDesc) + { + // Check method-level sharing: InstantiatedMethodDesc with SharedMethodInstantiation + if (methodDesc.Classification == MethodClassification.Instantiated) + { + InstantiatedMethodDesc imd = AsInstantiatedMethodDesc(methodDesc); + if (imd.IsWrapperStubWithInstantiations) + return false; + + // Check SharedMethodInstantiation flag + Data.InstantiatedMethodDesc imdData = _target.ProcessedData.GetOrAdd(methodDesc.Address); + if ((imdData.Flags2 & (ushort)InstantiatedMethodDescFlags2.KindMask) + == (ushort)InstantiatedMethodDescFlags2.SharedMethodInstantiation) + return true; + } + + // Check class-level sharing: canonical MethodTable with generic instantiation + MethodTable mt = _methodTables[methodDesc.MethodTable]; + return mt.IsCanonMT && mt.Flags.HasInstantiation; + } + + public bool IsAsyncMethod(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + if (!methodDesc.HasAsyncMethodData) + return false; + + // AsyncMethodData is the last optional slot, placed after NativeCodeSlot. + // Read the AsyncMethodFlags (first field) and check for AsyncCall. + TargetPointer asyncDataAddr = methodDesc.GetAddressOfAsyncMethodData(); + uint asyncFlags = _target.Read(asyncDataAddr); + return (asyncFlags & (uint)AsyncMethodFlags.AsyncCall) != 0; + } + public uint GetMethodToken(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/IRuntimeSignatureTypeProvider.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/IRuntimeSignatureTypeProvider.cs new file mode 100644 index 00000000000000..a8d9b15251dd40 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/IRuntimeSignatureTypeProvider.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection.Metadata; + +namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers; + +/// +/// Superset of SRM's +/// that adds support for runtime-internal type codes +/// (ELEMENT_TYPE_INTERNAL 0x21 and ELEMENT_TYPE_CMOD_INTERNAL 0x22). +/// +/// +/// Providers implementing this interface automatically satisfy SRM's +/// and can be used +/// with both SRM's SignatureDecoder and our +/// . +/// +public interface IRuntimeSignatureTypeProvider + : ISignatureTypeProvider +{ + /// + /// Classify an ELEMENT_TYPE_INTERNAL (0x21) type by resolving the + /// embedded TypeHandle pointer via the target's runtime type system. + /// + TType GetInternalType(TargetPointer typeHandlePointer); + + /// + /// Classify an ELEMENT_TYPE_CMOD_INTERNAL (0x22) custom modifier by + /// resolving the embedded TypeHandle pointer via the target's runtime type system. + /// + TType GetInternalModifiedType(TargetPointer typeHandlePointer, TType unmodifiedType, bool isRequired); +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/RuntimeSignatureDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/RuntimeSignatureDecoder.cs new file mode 100644 index 00000000000000..aa1700383b8944 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/RuntimeSignatureDecoder.cs @@ -0,0 +1,326 @@ +// 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.Immutable; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers; + +/// +/// Decodes signature blobs. Behaves identically to SRM's +/// for standard ECMA-335 type codes, +/// with added support for runtime-internal types +/// (ELEMENT_TYPE_INTERNAL 0x21 and ELEMENT_TYPE_CMOD_INTERNAL 0x22). +/// +internal readonly struct RuntimeSignatureDecoder +{ + private const int ELEMENT_TYPE_CMOD_INTERNAL = 0x22; + private const int ELEMENT_TYPE_INTERNAL = 0x21; + + private readonly IRuntimeSignatureTypeProvider _provider; + private readonly MetadataReader _metadataReader; + private readonly TGenericContext _genericContext; + private readonly int _pointerSize; + + public RuntimeSignatureDecoder( + IRuntimeSignatureTypeProvider provider, + Target target, + MetadataReader metadataReader, + TGenericContext genericContext) + { + _provider = provider; + _metadataReader = metadataReader; + _genericContext = genericContext; + _pointerSize = target.PointerSize; + } + + /// + /// Decodes a type embedded in a signature and advances the reader past the type. + /// + public TType DecodeType(ref BlobReader blobReader, bool allowTypeSpecifications = false) + { + return DecodeType(ref blobReader, allowTypeSpecifications, blobReader.ReadCompressedInteger()); + } + + private TType DecodeType(ref BlobReader blobReader, bool allowTypeSpecifications, int typeCode) + { + TType elementType; + int index; + + switch (typeCode) + { + case (int)SignatureTypeCode.Boolean: + case (int)SignatureTypeCode.Char: + case (int)SignatureTypeCode.SByte: + case (int)SignatureTypeCode.Byte: + case (int)SignatureTypeCode.Int16: + case (int)SignatureTypeCode.UInt16: + case (int)SignatureTypeCode.Int32: + case (int)SignatureTypeCode.UInt32: + case (int)SignatureTypeCode.Int64: + case (int)SignatureTypeCode.UInt64: + case (int)SignatureTypeCode.Single: + case (int)SignatureTypeCode.Double: + case (int)SignatureTypeCode.IntPtr: + case (int)SignatureTypeCode.UIntPtr: + case (int)SignatureTypeCode.Object: + case (int)SignatureTypeCode.String: + case (int)SignatureTypeCode.Void: + case (int)SignatureTypeCode.TypedReference: + return _provider.GetPrimitiveType((PrimitiveTypeCode)typeCode); + + case (int)SignatureTypeCode.Pointer: + elementType = DecodeType(ref blobReader); + return _provider.GetPointerType(elementType); + + case (int)SignatureTypeCode.ByReference: + elementType = DecodeType(ref blobReader); + return _provider.GetByReferenceType(elementType); + + case (int)SignatureTypeCode.Pinned: + elementType = DecodeType(ref blobReader); + return _provider.GetPinnedType(elementType); + + case (int)SignatureTypeCode.SZArray: + elementType = DecodeType(ref blobReader); + return _provider.GetSZArrayType(elementType); + + case (int)SignatureTypeCode.FunctionPointer: + MethodSignature methodSignature = DecodeMethodSignature(ref blobReader); + return _provider.GetFunctionPointerType(methodSignature); + + case (int)SignatureTypeCode.Array: + return DecodeArrayType(ref blobReader); + + case (int)SignatureTypeCode.RequiredModifier: + return DecodeModifiedType(ref blobReader, isRequired: true); + + case (int)SignatureTypeCode.OptionalModifier: + return DecodeModifiedType(ref blobReader, isRequired: false); + + case (int)SignatureTypeCode.GenericTypeInstance: + return DecodeGenericTypeInstance(ref blobReader); + + case (int)SignatureTypeCode.GenericTypeParameter: + index = blobReader.ReadCompressedInteger(); + return _provider.GetGenericTypeParameter(_genericContext, index); + + case (int)SignatureTypeCode.GenericMethodParameter: + index = blobReader.ReadCompressedInteger(); + return _provider.GetGenericMethodParameter(_genericContext, index); + + case (int)SignatureTypeKind.Class: + case (int)SignatureTypeKind.ValueType: + return DecodeTypeHandle(ref blobReader, (byte)typeCode, allowTypeSpecifications); + + case ELEMENT_TYPE_INTERNAL: + return DecodeInternalType(ref blobReader); + + case ELEMENT_TYPE_CMOD_INTERNAL: + return DecodeInternalModifiedType(ref blobReader); + + default: + throw new BadImageFormatException($"Unexpected signature type code: 0x{typeCode:X2}"); + } + } + + /// + /// Decodes a list of types, with at least one instance that is preceded by its count as a compressed integer. + /// + private ImmutableArray DecodeTypeSequence(ref BlobReader blobReader) + { + int count = blobReader.ReadCompressedInteger(); + if (count == 0) + { + throw new BadImageFormatException("Signature type sequence must have at least one element"); + } + + var types = ImmutableArray.CreateBuilder(count); + for (int i = 0; i < count; i++) + { + types.Add(DecodeType(ref blobReader)); + } + return types.MoveToImmutable(); + } + + /// + /// Decodes a method (definition, reference, or standalone) or property signature blob. + /// + public MethodSignature DecodeMethodSignature(ref BlobReader blobReader) + { + SignatureHeader header = blobReader.ReadSignatureHeader(); + CheckMethodOrPropertyHeader(header); + + int genericParameterCount = 0; + if (header.IsGeneric) + { + genericParameterCount = blobReader.ReadCompressedInteger(); + } + + int parameterCount = blobReader.ReadCompressedInteger(); + TType returnType = DecodeType(ref blobReader); + ImmutableArray parameterTypes; + int requiredParameterCount; + + if (parameterCount == 0) + { + requiredParameterCount = 0; + parameterTypes = ImmutableArray.Empty; + } + else + { + var parameterBuilder = ImmutableArray.CreateBuilder(parameterCount); + int parameterIndex; + + for (parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++) + { + int typeCode = blobReader.ReadCompressedInteger(); + if (typeCode == (int)SignatureTypeCode.Sentinel) + { + break; + } + parameterBuilder.Add(DecodeType(ref blobReader, allowTypeSpecifications: false, typeCode: typeCode)); + } + + requiredParameterCount = parameterIndex; + for (; parameterIndex < parameterCount; parameterIndex++) + { + parameterBuilder.Add(DecodeType(ref blobReader)); + } + parameterTypes = parameterBuilder.MoveToImmutable(); + } + + return new MethodSignature(header, returnType, requiredParameterCount, genericParameterCount, parameterTypes); + } + + /// + /// Decodes a local variable signature blob and advances the reader past the signature. + /// + public ImmutableArray DecodeLocalSignature(ref BlobReader blobReader) + { + SignatureHeader header = blobReader.ReadSignatureHeader(); + CheckHeader(header, SignatureKind.LocalVariables); + return DecodeTypeSequence(ref blobReader); + } + + /// + /// Decodes a field signature blob and advances the reader past the signature. + /// + public TType DecodeFieldSignature(ref BlobReader blobReader) + { + SignatureHeader header = blobReader.ReadSignatureHeader(); + CheckHeader(header, SignatureKind.Field); + return DecodeType(ref blobReader); + } + + private TType DecodeArrayType(ref BlobReader blobReader) + { + TType elementType = DecodeType(ref blobReader); + int rank = blobReader.ReadCompressedInteger(); + var sizes = ImmutableArray.Empty; + var lowerBounds = ImmutableArray.Empty; + + int sizesCount = blobReader.ReadCompressedInteger(); + if (sizesCount > 0) + { + var builder = ImmutableArray.CreateBuilder(sizesCount); + for (int i = 0; i < sizesCount; i++) + { + builder.Add(blobReader.ReadCompressedInteger()); + } + sizes = builder.MoveToImmutable(); + } + + int lowerBoundsCount = blobReader.ReadCompressedInteger(); + if (lowerBoundsCount > 0) + { + var builder = ImmutableArray.CreateBuilder(lowerBoundsCount); + for (int i = 0; i < lowerBoundsCount; i++) + { + builder.Add(blobReader.ReadCompressedSignedInteger()); + } + lowerBounds = builder.MoveToImmutable(); + } + + return _provider.GetArrayType(elementType, new ArrayShape(rank, sizes, lowerBounds)); + } + + private TType DecodeGenericTypeInstance(ref BlobReader blobReader) + { + TType genericType = DecodeType(ref blobReader); + ImmutableArray types = DecodeTypeSequence(ref blobReader); + return _provider.GetGenericInstantiation(genericType, types); + } + + private TType DecodeModifiedType(ref BlobReader blobReader, bool isRequired) + { + // A standard modifier may be followed by an internal modifier; allow type specifications + // for the modifier handle (matches SRM behavior). + TType modifier = DecodeTypeHandle(ref blobReader, 0, allowTypeSpecifications: true); + TType unmodifiedType = DecodeType(ref blobReader); + return _provider.GetModifiedType(modifier, unmodifiedType, isRequired); + } + + private TType DecodeInternalType(ref BlobReader blobReader) + { + ulong val = ReadPointerSized(ref blobReader); + return _provider.GetInternalType(new TargetPointer(val)); + } + + private TType DecodeInternalModifiedType(ref BlobReader blobReader) + { + bool isRequired = blobReader.ReadByte() != 0; + ulong val = ReadPointerSized(ref blobReader); + TType unmodifiedType = DecodeType(ref blobReader); + return _provider.GetInternalModifiedType(new TargetPointer(val), unmodifiedType, isRequired); + } + + private TType DecodeTypeHandle(ref BlobReader blobReader, byte rawTypeKind, bool allowTypeSpecifications) + { + EntityHandle handle = blobReader.ReadTypeHandle(); + if (!handle.IsNil) + { + switch (handle.Kind) + { + case HandleKind.TypeDefinition: + return _provider.GetTypeFromDefinition(_metadataReader, (TypeDefinitionHandle)handle, rawTypeKind); + + case HandleKind.TypeReference: + return _provider.GetTypeFromReference(_metadataReader, (TypeReferenceHandle)handle, rawTypeKind); + + case HandleKind.TypeSpecification: + if (!allowTypeSpecifications) + { + throw new BadImageFormatException("TypeSpecification handle not allowed in this context"); + } + return _provider.GetTypeFromSpecification(_metadataReader, _genericContext, (TypeSpecificationHandle)handle, rawTypeKind); + } + } + + throw new BadImageFormatException("Expected TypeDef, TypeRef, or TypeSpec handle"); + } + + private ulong ReadPointerSized(ref BlobReader blobReader) + { + return _pointerSize == 8 ? blobReader.ReadUInt64() : blobReader.ReadUInt32(); + } + + private static void CheckHeader(SignatureHeader header, SignatureKind expectedKind) + { + if (header.Kind != expectedKind) + { + throw new BadImageFormatException($"Expected signature header {expectedKind}, got {header.Kind} (raw 0x{header.RawValue:X2})"); + } + } + + private static void CheckMethodOrPropertyHeader(SignatureHeader header) + { + SignatureKind kind = header.Kind; + if (kind != SignatureKind.Method && kind != SignatureKind.Property) + { + throw new BadImageFormatException($"Expected Method or Property signature header, got {kind} (raw 0x{header.RawValue:X2})"); + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs index 82672504975f49..c6cb2bfb47fbd5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureTypeProvider.cs @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers; -public class SignatureTypeProvider : ISignatureTypeProvider +public class SignatureTypeProvider : IRuntimeSignatureTypeProvider { private readonly Target _target; private readonly Contracts.ModuleHandle _moduleHandle; @@ -89,4 +89,12 @@ public TypeHandle GetTypeFromReference(MetadataReader reader, TypeReferenceHandl public TypeHandle GetTypeFromSpecification(MetadataReader reader, T context, TypeSpecificationHandle handle, byte rawTypeKind) => throw new NotImplementedException(); + + public TypeHandle GetInternalType(TargetPointer typeHandlePointer) + => typeHandlePointer == TargetPointer.Null + ? new TypeHandle(TargetPointer.Null) + : _runtimeTypeSystem.GetTypeHandle(typeHandlePointer); + + public TypeHandle GetInternalModifiedType(TargetPointer typeHandlePointer, TypeHandle unmodifiedType, bool isRequired) + => unmodifiedType; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoder_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs similarity index 50% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoder_1.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs index adfdbeca340134..8517cf674bccdb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureDecoder_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs @@ -1,29 +1,24 @@ // 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.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; using Microsoft.Diagnostics.DataContractReader.SignatureHelpers; namespace Microsoft.Diagnostics.DataContractReader.Contracts; -/* NOTE: some elements of SignatureTypeProvider remain unimplemented or minimally implemented - * as they are not needed for the current usage of ISignatureDecoder. - * GetModifiedType and GetPinnedType ignore pinning and custom modifiers. - * GetTypeFromReference does not look up the type in another module. - * GetTypeFromSpecification is unimplemented. - * These can be completed as needed. - */ - -internal sealed class SignatureDecoder_1 : ISignatureDecoder +// NOTE: some elements of SignatureTypeProvider remain unimplemented or minimally implemented +// as they are not needed for the current usage of ISignature. +// GetModifiedType and GetPinnedType ignore pinning and custom modifiers. +// GetTypeFromReference does not look up the type in another module. +// GetTypeFromSpecification is unimplemented. +// These can be completed as needed. +internal sealed class Signature_1 : ISignature { private readonly Target _target; private readonly Dictionary> _thProviders = []; - private readonly Dictionary> _mdhProviders = []; - internal SignatureDecoder_1(Target target) + internal Signature_1(Target target) { _target = target; } @@ -31,7 +26,6 @@ internal SignatureDecoder_1(Target target) public void Flush() { _thProviders.Clear(); - _mdhProviders.Clear(); } private SignatureTypeProvider GetTypeHandleProvider(ModuleHandle moduleHandle) @@ -46,23 +40,13 @@ private SignatureTypeProvider GetTypeHandleProvider(ModuleHandle mod return newProvider; } - private SignatureTypeProvider GetMethodDescHandleProvider(ModuleHandle moduleHandle) - { - if (_mdhProviders.TryGetValue(moduleHandle, out SignatureTypeProvider? mdhProvider)) - { - return mdhProvider; - } - SignatureTypeProvider newProvider = new(_target, moduleHandle); - _mdhProviders[moduleHandle] = newProvider; - return newProvider; - } - - TypeHandle ISignatureDecoder.DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) + TypeHandle ISignature.DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) { SignatureTypeProvider provider = GetTypeHandleProvider(moduleHandle); MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!; + BlobReader blobReader = mdReader.GetBlobReader(blobHandle); - SignatureDecoder decoder = new(provider, mdReader, ctx); + RuntimeSignatureDecoder decoder = new(provider, _target, mdReader, ctx); return decoder.DecodeFieldSignature(ref blobReader); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs index 55b712edb05ad4..4c254304b1e3e5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs @@ -52,13 +52,8 @@ public override void HandleTransitionFrame(FramedMethodFrame framedMethodFrame) Data.TransitionBlock transitionBlock = _target.ProcessedData.GetOrAdd(framedMethodFrame.TransitionBlockPtr); - if (transitionBlock.ArgumentRegisters is not TargetPointer argumentRegistersPtr) - { - throw new InvalidOperationException("ARM TransitionBlock does not have ArgumentRegisters set"); - } - // On ARM, TransitionFrames update the argument registers - Data.ArgumentRegisters argumentRegisters = _target.ProcessedData.GetOrAdd(argumentRegistersPtr); + Data.ArgumentRegisters argumentRegisters = _target.ProcessedData.GetOrAdd(transitionBlock.ArgumentRegisters); UpdateFromRegisterDict(argumentRegisters.Registers); } } 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 4edd821c203dc9..915e8504082d69 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 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; @@ -132,20 +133,81 @@ public void UpdateContextFromFrame(IPlatformAgnosticContext context) } } - public bool IsInlineCallFrameWithActiveCall() + /// + /// Returns the return address for the current Frame, matching native Frame::GetReturnAddress(). + /// Returns TargetPointer.Null if the Frame has no return address (e.g., non-active ICF, + /// base Frame types, FuncEvalFrame during exception eval). + /// + public TargetPointer GetReturnAddress() { - if (GetFrameType(target, CurrentFrame.Identifier) != FrameType.InlinedCallFrame) + FrameType frameType = GetCurrentFrameType(); + switch (frameType) { - return false; - } - Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(currentFramePointer); - return InlinedCallFrameHasActiveCall(inlinedCallFrame); - } + // InlinedCallFrame: returns 0 if inactive, else m_pCallerReturnAddress + case FrameType.InlinedCallFrame: + Data.InlinedCallFrame icf = target.ProcessedData.GetOrAdd(currentFramePointer); + return InlinedCallFrameHasActiveCall(icf) ? icf.CallerReturnAddress : TargetPointer.Null; - public static bool IsInlinedCallFrame(Target target, TargetPointer framePointer) - { - Data.Frame frame = target.ProcessedData.GetOrAdd(framePointer); - return GetFrameType(target, frame.Identifier) == FrameType.InlinedCallFrame; + // TransitionFrame types: read return address from the transition block + case FrameType.FramedMethodFrame: + case FrameType.PInvokeCalliFrame: + case FrameType.PrestubMethodFrame: + case FrameType.StubDispatchFrame: + case FrameType.CallCountingHelperFrame: + case FrameType.ExternalMethodFrame: + case FrameType.DynamicHelperFrame: + Data.FramedMethodFrame fmf = target.ProcessedData.GetOrAdd(currentFramePointer); + Data.TransitionBlock tb = target.ProcessedData.GetOrAdd(fmf.TransitionBlockPtr); + return tb.ReturnAddress; + + // SoftwareExceptionFrame: stored m_ReturnAddress + case FrameType.SoftwareExceptionFrame: + Data.SoftwareExceptionFrame sef = target.ProcessedData.GetOrAdd(currentFramePointer); + return sef.ReturnAddress; + + // ResumableFrame / RedirectedThreadFrame: RIP from captured context + case FrameType.ResumableFrame: + case FrameType.RedirectedThreadFrame: + { + Data.ResumableFrame rf = target.ProcessedData.GetOrAdd(currentFramePointer); + IPlatformAgnosticContext ctx = IPlatformAgnosticContext.GetContextForPlatform(target); + ctx.ReadFromAddress(target, rf.TargetContextPtr); + return ctx.InstructionPointer; + } + + // FaultingExceptionFrame: RIP from embedded context + case FrameType.FaultingExceptionFrame: + { + Data.FaultingExceptionFrame fef = target.ProcessedData.GetOrAdd(currentFramePointer); + IPlatformAgnosticContext ctx = IPlatformAgnosticContext.GetContextForPlatform(target); + ctx.ReadFromAddress(target, fef.TargetContext); + return ctx.InstructionPointer; + } + + // HijackFrame: stored m_ReturnAddress + case FrameType.HijackFrame: + Data.HijackFrame hf = target.ProcessedData.GetOrAdd(currentFramePointer); + return hf.ReturnAddress; + + // TailCallFrame: stored m_ReturnAddress + case FrameType.TailCallFrame: + Data.TailCallFrame tcf = target.ProcessedData.GetOrAdd(currentFramePointer); + return tcf.ReturnAddress; + + // FuncEvalFrame: returns 0 during exception eval, else from transition block + case FrameType.FuncEvalFrame: + Data.FuncEvalFrame funcEval = target.ProcessedData.GetOrAdd(currentFramePointer); + Data.DebuggerEval dbgEval = target.ProcessedData.GetOrAdd(funcEval.DebuggerEvalPtr); + if (dbgEval.EvalUsesHijack) + return TargetPointer.Null; + Data.FramedMethodFrame funcEvalFmf = target.ProcessedData.GetOrAdd(currentFramePointer); + Data.TransitionBlock funcEvalTb = target.ProcessedData.GetOrAdd(funcEvalFmf.TransitionBlockPtr); + return funcEvalTb.ReturnAddress; + + // Base Frame and unknown types: return 0 (matches native Frame::GetReturnAddressPtr_Impl) + default: + return TargetPointer.Null; + } } public static string GetFrameName(Target target, TargetPointer frameIdentifier) @@ -160,7 +222,7 @@ public static string GetFrameName(Target target, TargetPointer frameIdentifier) public FrameType GetCurrentFrameType() => GetFrameType(target, CurrentFrame.Identifier); - private static FrameType GetFrameType(Target target, TargetPointer frameIdentifier) + internal static FrameType GetFrameType(Target target, TargetPointer frameIdentifier) { foreach (FrameType frameType in Enum.GetValues()) { @@ -233,21 +295,6 @@ public static TargetPointer GetMethodDescPtr(Target target, TargetPointer frameP } } - public static TargetPointer GetReturnAddress(Target target, TargetPointer framePtr) - { - Data.Frame frame = target.ProcessedData.GetOrAdd(framePtr); - FrameType frameType = GetFrameType(target, frame.Identifier); - switch (frameType) - { - case FrameType.InlinedCallFrame: - Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(frame.Address); - return InlinedCallFrameHasActiveCall(inlinedCallFrame) ? inlinedCallFrame.CallerReturnAddress : TargetPointer.Null; - default: - // NotImplemented for other frame types - return TargetPointer.Null; - } - } - private static bool InlinedCallFrameHasFunction(Data.InlinedCallFrame frame, Target target) { if (target.PointerSize == sizeof(ulong)) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.cs new file mode 100644 index 00000000000000..6815878ec65c86 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// Token values from CORCOMPILE_GCREFMAP_TOKENS (corcompile.h). +/// These indicate the type of GC reference at each transition block slot. +/// +internal enum GCRefMapToken +{ + Skip = 0, + Ref = 1, + Interior = 2, + MethodParam = 3, + TypeParam = 4, + VASigCookie = 5, +} + +/// +/// Managed port of the native GCRefMapDecoder (gcrefmap.h). +/// +/// A GCRefMap is a compact bitstream that describes which transition block slots +/// contain GC references for a given call site (e.g., in ReadyToRun stubs). +/// It is used by ExternalMethodFrame and StubDispatchFrame to report GC roots +/// without needing the full MethodDesc/signature decoding path. +/// +/// Encoding: each slot is encoded as a variable-length integer using 3 bits per +/// token (see ), with a high-bit continuation flag. +/// A "skip" token advances the slot position without reporting. The stream ends +/// when all slots have been consumed (indicated by a zero byte after the last token). +/// +/// The native implementation lives in coreclr/inc/gcrefmap.h (GCRefMapDecoder class). +/// +internal ref struct GCRefMapDecoder +{ + private readonly Target _target; + private TargetPointer _currentByte; + private int _pendingByte; + private int _pos; + + public GCRefMapDecoder(Target target, TargetPointer blob) + { + _target = target; + _currentByte = blob; + _pendingByte = 0x80; // Forces first byte read + _pos = 0; + } + + public readonly bool AtEnd => _pendingByte == 0; + + public readonly int CurrentPos => _pos; + + private int GetBit() + { + int x = _pendingByte; + if ((x & 0x80) != 0) + { + x = _target.Read(_currentByte); + _currentByte = new TargetPointer(_currentByte.Value + 1); + x |= (x & 0x80) << 7; + } + _pendingByte = x >> 1; + return x & 1; + } + + private int GetTwoBit() + { + int result = GetBit(); + result |= GetBit() << 1; + return result; + } + + private int GetInt() + { + int result = 0; + int bit = 0; + do + { + result |= GetBit() << (bit++); + result |= GetBit() << (bit++); + result |= GetBit() << (bit++); + } + while (GetBit() != 0); + return result; + } + + /// + /// x86 only: Read the stack pop count from the stream. + /// + public uint ReadStackPop() + { + int x = GetTwoBit(); + if (x == 3) + x = GetInt() + 3; + return (uint)x; + } + + /// + /// Read the next GC reference token from the stream. + /// Advances CurrentPos as appropriate. + /// + public GCRefMapToken ReadToken() + { + int val = GetTwoBit(); + if (val == 3) + { + int ext = GetInt(); + if ((ext & 1) == 0) + { + _pos += (ext >> 1) + 4; + return GCRefMapToken.Skip; + } + else + { + _pos++; + return (GCRefMapToken)((ext >> 1) + 3); + } + } + _pos++; + return (GCRefMapToken)val; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs index fa72eb606fad75..807c66ae8bacaf 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs @@ -2,10 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.SignatureHelpers; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +/// +/// Handles all GC reference scanning for stack frames. +/// Covers both managed (frameless) frames via GCInfo and +/// capital "F" Frames via GCRefMap/signature-based scanning. +/// internal class GcScanner { private readonly Target _target; @@ -19,74 +27,446 @@ internal GcScanner(Target target) _gcInfo = target.Contracts.GCInfo; } - public bool EnumGcRefs( + /// + /// Enumerates live GC slots for a managed (frameless) code frame. + /// Port of native EECodeManager::EnumGcRefs (eetwain.cpp). + /// + public void EnumGcRefsForManagedFrame( IPlatformAgnosticContext context, CodeBlockHandle cbh, - CodeManagerFlags flags, - GcScanContext scanContext) + GcSlotEnumerationOptions options, + GcScanContext scanContext, + uint? relOffsetOverride = null) { TargetNUInt relativeOffset = _eman.GetRelativeOffset(cbh); _eman.GetGCInfo(cbh, out TargetPointer gcInfoAddr, out uint gcVersion); - if (_eman.IsFilterFunclet(cbh)) - flags |= CodeManagerFlags.NoReportUntracked; - IGCInfoHandle handle = _gcInfo.DecodePlatformSpecificGCInfo(gcInfoAddr, gcVersion); - if (handle is not IGCInfoDecoder decoder) - return false; - - uint stackBaseRegister = decoder.StackBaseRegister; - // Lazily compute the caller SP for GC_CALLER_SP_REL slots. - // The native code uses GET_CALLER_SP(pRD) which comes from EnsureCallerContextIsValid. + uint stackBaseRegister = _gcInfo.GetStackBaseRegister(handle); TargetPointer? callerSP = null; + uint offsetToUse = relOffsetOverride ?? (uint)relativeOffset.Value; - return decoder.EnumerateLiveSlots( - (uint)relativeOffset.Value, - flags, - (bool isRegister, uint registerNumber, int spOffset, uint spBase, uint gcFlags) => + IReadOnlyList liveSlots = _gcInfo.EnumerateLiveSlots(handle, offsetToUse, options); + foreach (LiveSlot slot in liveSlots) + { + GcScanFlags scanFlags = GcScanFlags.None; + if ((slot.GcFlags & 0x1) != 0) + scanFlags |= GcScanFlags.GC_CALL_INTERIOR; + if ((slot.GcFlags & 0x2) != 0) + scanFlags |= GcScanFlags.GC_CALL_PINNED; + + if (slot.IsRegister) { - GcScanFlags scanFlags = GcScanFlags.None; - if ((gcFlags & 0x1) != 0) // GC_SLOT_INTERIOR - scanFlags |= GcScanFlags.GC_CALL_INTERIOR; - if ((gcFlags & 0x2) != 0) // GC_SLOT_PINNED - scanFlags |= GcScanFlags.GC_CALL_PINNED; + if (!context.TryReadRegister((int)slot.RegisterNumber, out TargetNUInt regValue)) + continue; + GcScanSlotLocation loc = new((int)slot.RegisterNumber, 0, false); + scanContext.GCEnumCallback(new TargetPointer(regValue.Value), scanFlags, loc); + } + else + { + int spReg = context.StackPointerRegister; + int reg = slot.SpBase switch + { + 1 => spReg, + 2 => (int)stackBaseRegister, + 0 => -(spReg + 1), + _ => throw new InvalidOperationException($"Unknown stack slot base: {slot.SpBase}"), + }; + TargetPointer baseAddr = slot.SpBase switch + { + 1 => context.StackPointer, + 2 => context.TryReadRegister((int)stackBaseRegister, out TargetNUInt val) + ? new TargetPointer(val.Value) + : throw new InvalidOperationException($"Failed to read register {stackBaseRegister}"), + 0 => GetCallerSP(context, ref callerSP), + _ => throw new InvalidOperationException($"Unknown stack slot base: {slot.SpBase}"), + }; + + TargetPointer addr = new(baseAddr.Value + (ulong)(long)slot.SpOffset); + GcScanSlotLocation loc = new(reg, slot.SpOffset, true); + scanContext.GCEnumCallback(addr, scanFlags, loc); + } + } + } + + /// + /// Scans GC roots for a capital "F" Frame based on its type. + /// Port of native Frame::GcScanRoots (frames.cpp). + /// + public void GcScanRoots(TargetPointer frameAddress, GcScanContext scanContext) + { + if (frameAddress == TargetPointer.Null) + return; + + Data.Frame frameData = _target.ProcessedData.GetOrAdd(frameAddress); + FrameIterator.FrameType frameType = FrameIterator.GetFrameType(_target, frameData.Identifier); + + switch (frameType) + { + case FrameIterator.FrameType.StubDispatchFrame: + { + Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd(frameAddress); + Data.StubDispatchFrame sdf = _target.ProcessedData.GetOrAdd(frameAddress); + + TargetPointer gcRefMap = sdf.Indirection != TargetPointer.Null + ? FindGCRefMap(sdf.Indirection) + : TargetPointer.Null; + + if (gcRefMap != TargetPointer.Null) + PromoteCallerStackUsingGCRefMap(fmf.TransitionBlockPtr, gcRefMap, scanContext); + else + PromoteCallerStack(frameAddress, fmf.TransitionBlockPtr, scanContext); + break; + } + + case FrameIterator.FrameType.ExternalMethodFrame: + { + Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd(frameAddress); + Data.ExternalMethodFrame emf = _target.ProcessedData.GetOrAdd(frameAddress); + + TargetPointer gcRefMap = emf.Indirection != TargetPointer.Null + ? FindGCRefMap(emf.Indirection) + : TargetPointer.Null; + + if (gcRefMap != TargetPointer.Null) + PromoteCallerStackUsingGCRefMap(fmf.TransitionBlockPtr, gcRefMap, scanContext); + else + PromoteCallerStack(frameAddress, fmf.TransitionBlockPtr, scanContext); + break; + } + + case FrameIterator.FrameType.DynamicHelperFrame: + { + Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd(frameAddress); + Data.DynamicHelperFrame dhf = _target.ProcessedData.GetOrAdd(frameAddress); + ScanDynamicHelperFrame(fmf.TransitionBlockPtr, dhf.DynamicHelperFrameFlags, scanContext); + break; + } + + case FrameIterator.FrameType.CallCountingHelperFrame: + case FrameIterator.FrameType.PrestubMethodFrame: + { + Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd(frameAddress); + PromoteCallerStack(frameAddress, fmf.TransitionBlockPtr, scanContext); + break; + } + + case FrameIterator.FrameType.HijackFrame: + // TODO(stackref): Implement HijackFrame scanning (X86 only with FEATURE_HIJACK) + break; + + case FrameIterator.FrameType.ProtectValueClassFrame: + // TODO(stackref): Implement ProtectValueClassFrame scanning + break; + + default: + break; + } + } + + /// + /// Decodes a GCRefMap bitstream and reports GC references in the transition block. + /// Port of native TransitionFrame::PromoteCallerStackUsingGCRefMap (frames.cpp). + /// + private void PromoteCallerStackUsingGCRefMap( + TargetPointer transitionBlock, + TargetPointer gcRefMapBlob, + GcScanContext scanContext) + { + Data.TransitionBlock tb = _target.ProcessedData.GetOrAdd(transitionBlock); + GCRefMapDecoder decoder = new(_target, gcRefMapBlob); + + if (_target.Contracts.RuntimeInfo.GetTargetArchitecture() is RuntimeInfoArchitecture.X86) + decoder.ReadStackPop(); + + while (!decoder.AtEnd) + { + int pos = decoder.CurrentPos; + GCRefMapToken token = decoder.ReadToken(); + TargetPointer slotAddress = AddressFromGCRefMapPos(tb, pos); + + switch (token) + { + case GCRefMapToken.Skip: + break; + case GCRefMapToken.Ref: + scanContext.GCReportCallback(slotAddress, GcScanFlags.None); + break; + case GCRefMapToken.Interior: + scanContext.GCReportCallback(slotAddress, GcScanFlags.GC_CALL_INTERIOR); + break; + case GCRefMapToken.MethodParam: + case GCRefMapToken.TypeParam: + break; + case GCRefMapToken.VASigCookie: + break; + } + } + } + + /// + /// Scans GC roots for a DynamicHelperFrame based on its flags. + /// Port of native DynamicHelperFrame::GcScanRoots_Impl (frames.cpp). + /// + private void ScanDynamicHelperFrame( + TargetPointer transitionBlock, + int dynamicHelperFrameFlags, + GcScanContext scanContext) + { + const int DynamicHelperFrameFlags_ObjectArg = 1; + const int DynamicHelperFrameFlags_ObjectArg2 = 2; + + Data.TransitionBlock tb = _target.ProcessedData.GetOrAdd(transitionBlock); + TargetPointer argRegStart = tb.ArgumentRegisters; + + if ((dynamicHelperFrameFlags & DynamicHelperFrameFlags_ObjectArg) != 0) + { + scanContext.GCReportCallback(argRegStart, GcScanFlags.None); + } + + if ((dynamicHelperFrameFlags & DynamicHelperFrameFlags_ObjectArg2) != 0) + { + TargetPointer argAddr = new(argRegStart.Value + (uint)_target.PointerSize); + scanContext.GCReportCallback(argAddr, GcScanFlags.None); + } + } + + /// + /// Resolves the GCRefMap for a Frame with m_pIndirection. + /// Port of native FindGCRefMap (frames.cpp). + /// Always resolves the module via FindReadyToRunModule. + /// + private TargetPointer FindGCRefMap(TargetPointer indirection) + { + if (indirection == TargetPointer.Null) + return TargetPointer.Null; + + TargetPointer zapModule = _eman.FindReadyToRunModule(indirection); + if (zapModule == TargetPointer.Null) + return TargetPointer.Null; + + Data.Module module = _target.ProcessedData.GetOrAdd(zapModule); + if (module.ReadyToRunInfo == TargetPointer.Null) + return TargetPointer.Null; - if (isRegister) + Data.ReadyToRunInfo r2rInfo = _target.ProcessedData.GetOrAdd(module.ReadyToRunInfo); + if (r2rInfo.ImportSections == TargetPointer.Null || r2rInfo.NumImportSections == 0) + return TargetPointer.Null; + + ulong imageBase = r2rInfo.LoadedImageBase.Value; + if (indirection.Value < imageBase) + return TargetPointer.Null; + ulong diff = indirection.Value - imageBase; + if (diff > uint.MaxValue) + return TargetPointer.Null; + uint rva = (uint)diff; + + const int ImportSectionSize = 20; + const int SectionVAOffset = 0; + const int SectionSizeOffset = 4; + const int EntrySizeOffset = 11; + const int AuxiliaryDataOffset = 16; + + TargetPointer sectionsBase = r2rInfo.ImportSections; + for (uint i = 0; i < r2rInfo.NumImportSections; i++) + { + TargetPointer sectionAddr = new(sectionsBase.Value + i * ImportSectionSize); + uint sectionVA = _target.Read(sectionAddr + SectionVAOffset); + uint sectionSize = _target.Read(sectionAddr + SectionSizeOffset); + + if (rva >= sectionVA && rva < sectionVA + sectionSize) + { + byte entrySize = _target.Read(sectionAddr + EntrySizeOffset); + if (entrySize == 0) + return TargetPointer.Null; + + uint index = (rva - sectionVA) / entrySize; + uint auxDataRva = _target.Read(sectionAddr + AuxiliaryDataOffset); + if (auxDataRva == 0) + return TargetPointer.Null; + + TargetPointer gcRefMapBase = new(imageBase + auxDataRva); + + const uint GCREFMAP_LOOKUP_STRIDE = 1024; + uint lookupIndex = index / GCREFMAP_LOOKUP_STRIDE; + uint remaining = index % GCREFMAP_LOOKUP_STRIDE; + + uint lookupOffset = _target.Read(new TargetPointer(gcRefMapBase.Value + lookupIndex * 4)); + TargetPointer p = new(gcRefMapBase.Value + lookupOffset); + + while (remaining > 0) { - TargetPointer regValue = ReadRegisterValue(context, (int)registerNumber); - GcScanSlotLocation loc = new((int)registerNumber, 0, false); - scanContext.GCEnumCallback(regValue, scanFlags, loc); + while ((_target.Read(p) & 0x80) != 0) + p = new(p.Value + 1); + p = new(p.Value + 1); + remaining--; } - else + + return p; + } + } + + return TargetPointer.Null; + } + + /// + /// Entry point for promoting caller stack GC references via method signature. + /// Matches native TransitionFrame::PromoteCallerStack (frames.cpp:1494). + /// + private void PromoteCallerStack( + TargetPointer frameAddress, + TargetPointer transitionBlock, + GcScanContext scanContext) + { + Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd(frameAddress); + TargetPointer methodDescPtr = fmf.MethodDescPtr; + if (methodDescPtr == TargetPointer.Null) + return; + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr); + + MethodSignature methodSig; + try + { + TargetPointer methodTablePtr = rts.GetMethodTable(mdh); + TypeHandle typeHandle = rts.GetTypeHandle(methodTablePtr); + TargetPointer modulePtr = rts.GetModule(typeHandle); + + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr); + MetadataReader? mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + if (mdReader is null) + return; + + GcSignatureTypeProvider provider = new(_target, moduleHandle); + GcSignatureContext genericContext = new(typeHandle, mdh); + RuntimeSignatureDecoder decoder = new( + provider, _target, mdReader, genericContext); + + // Match native MethodDesc::GetSig: prefer stored signature (dynamic, EEImpl, + // and array method descs) before falling back to a metadata token lookup. + if (rts.IsStoredSigMethodDesc(mdh, out ReadOnlySpan storedSig)) + { + unsafe { - int spReg = context.StackPointerRegister; - int reg = spBase switch + fixed (byte* pStoredSig = storedSig) { - 1 => spReg, // GC_SP_REL → SP register number - 2 => (int)stackBaseRegister, // GC_FRAMEREG_REL → frame base register - 0 => -(spReg + 1), // GC_CALLER_SP_REL → -(SP + 1) - _ => throw new InvalidOperationException($"Unknown stack slot base: {spBase}"), - }; - TargetPointer baseAddr = spBase switch - { - 1 => context.StackPointer, // GC_SP_REL - 2 => ReadRegisterValue(context, (int)stackBaseRegister), // GC_FRAMEREG_REL - 0 => GetCallerSP(context, ref callerSP), // GC_CALLER_SP_REL - _ => throw new InvalidOperationException($"Unknown stack slot base: {spBase}"), - }; - - TargetPointer addr = new(baseAddr.Value + (ulong)(long)spOffset); - GcScanSlotLocation loc = new(reg, spOffset, true); - scanContext.GCEnumCallback(addr, scanFlags, loc); + BlobReader blobReader = new BlobReader(pStoredSig, storedSig.Length); + methodSig = decoder.DecodeMethodSignature(ref blobReader); + } } - }); + } + else + { + uint methodToken = rts.GetMethodToken(mdh); + if (methodToken == (uint)EcmaMetadataUtils.TokenType.mdtMethodDef) + return; + + MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(methodToken)); + MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle); + + BlobReader blobReader = mdReader.GetBlobReader(methodDef.Signature); + methodSig = decoder.DecodeMethodSignature(ref blobReader); + } + } + catch (System.Exception) + { + return; + } + + if (methodSig.Header.CallingConvention is SignatureCallingConvention.VarArgs) + return; + + bool hasThis = methodSig.Header.IsInstance; + bool hasRetBuf = methodSig.ReturnType is GcTypeKind.Other; + bool requiresInstArg = false; + bool isAsync = false; + bool isValueTypeThis = false; + + try + { + requiresInstArg = rts.RequiresInstArg(mdh); + isAsync = rts.IsAsyncMethod(mdh); + } + catch + { + } + + PromoteCallerStackHelper(transitionBlock, methodSig, hasThis, hasRetBuf, + requiresInstArg, isAsync, isValueTypeThis, scanContext); } /// - /// Compute the caller's SP by unwinding the current context one frame. - /// Cached in to avoid repeated unwinds for the same frame. + /// Core logic for promoting caller stack GC references. + /// Matches native TransitionFrame::PromoteCallerStackHelper (frames.cpp:1560). /// + private void PromoteCallerStackHelper( + TargetPointer transitionBlock, + MethodSignature methodSig, + bool hasThis, + bool hasRetBuf, + bool requiresInstArg, + bool isAsync, + bool isValueTypeThis, + GcScanContext scanContext) + { + Data.TransitionBlock tb = _target.ProcessedData.GetOrAdd(transitionBlock); + + int numRegistersUsed = 0; + if (hasThis) + numRegistersUsed++; + if (hasRetBuf) + numRegistersUsed++; + if (requiresInstArg) + numRegistersUsed++; + if (isAsync) + numRegistersUsed++; + + bool isArm64 = IsTargetArm64(); + if (isArm64) + numRegistersUsed++; + + if (hasThis) + { + int thisPos = isArm64 ? 1 : 0; + TargetPointer thisAddr = AddressFromGCRefMapPos(tb, thisPos); + GcScanFlags thisFlags = isValueTypeThis ? GcScanFlags.GC_CALL_INTERIOR : GcScanFlags.None; + scanContext.GCReportCallback(thisAddr, thisFlags); + } + + int pos = numRegistersUsed; + foreach (GcTypeKind kind in methodSig.ParameterTypes) + { + TargetPointer slotAddress = AddressFromGCRefMapPos(tb, pos); + + switch (kind) + { + case GcTypeKind.Ref: + scanContext.GCReportCallback(slotAddress, GcScanFlags.None); + break; + case GcTypeKind.Interior: + scanContext.GCReportCallback(slotAddress, GcScanFlags.GC_CALL_INTERIOR); + break; + case GcTypeKind.Other: + break; + case GcTypeKind.None: + break; + } + pos++; + } + } + + private TargetPointer AddressFromGCRefMapPos(Data.TransitionBlock tb, int pos) + { + return new TargetPointer(tb.FirstGCRefMapSlot.Value + (ulong)(pos * _target.PointerSize)); + } + + private bool IsTargetArm64() + { + return _target.Contracts.RuntimeInfo.GetTargetArchitecture() is RuntimeInfoArchitecture.Arm64; + } + private TargetPointer GetCallerSP(IPlatformAgnosticContext context, ref TargetPointer? cached) { if (cached is null) @@ -97,13 +477,4 @@ private TargetPointer GetCallerSP(IPlatformAgnosticContext context, ref TargetPo } return cached.Value; } - - private static TargetPointer ReadRegisterValue(IPlatformAgnosticContext context, int registerNumber) - { - if (!context.TryReadRegister(registerNumber, out TargetNUInt value)) - throw new ArgumentOutOfRangeException(nameof(registerNumber), $"Register number {registerNumber} not found"); - - return new TargetPointer(value.Value); - } - } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs new file mode 100644 index 00000000000000..8852f733df6a97 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs @@ -0,0 +1,219 @@ +// 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.Immutable; +using System.Diagnostics; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.SignatureHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// Classification of a signature type for GC scanning purposes. +/// +internal enum GcTypeKind +{ + /// Not a GC reference (primitives, pointers). + None, + /// Object reference (class, string, array). + Ref, + /// Interior pointer (byref). + Interior, + /// Value type that may contain embedded GC references. + Other, +} + +/// +/// Generic context used to resolve ELEMENT_TYPE_VAR and ELEMENT_TYPE_MVAR +/// while decoding a method signature for GC scanning. is the +/// owning type's (used for VAR), and +/// is the owning method's (used for MVAR). +/// +internal readonly record struct GcSignatureContext(TypeHandle ClassContext, MethodDescHandle MethodContext); + +/// +/// Classifies signature types for GC scanning purposes. +/// Implements which +/// is a superset of SRM's , +/// adding support for ELEMENT_TYPE_INTERNAL. +/// +/// +/// The provider is scoped to a single module: GetTypeFromDefinition and +/// GetTypeFromReference resolve TypeDef/TypeRef tokens via the module's +/// lookup tables so enums (and other runtime-normalized value types) are classified +/// using the actual , matching native +/// SigPointer::PeekElemTypeNormalized. +/// +internal sealed class GcSignatureTypeProvider + : IRuntimeSignatureTypeProvider +{ + private readonly Target _target; + private readonly ModuleHandle _moduleHandle; + + public GcSignatureTypeProvider(Target target, ModuleHandle moduleHandle) + { + _target = target; + _moduleHandle = moduleHandle; + } + + public GcTypeKind GetPrimitiveType(PrimitiveTypeCode typeCode) + => typeCode switch + { + PrimitiveTypeCode.String or PrimitiveTypeCode.Object => GcTypeKind.Ref, + PrimitiveTypeCode.TypedReference => GcTypeKind.Other, + _ => GcTypeKind.None, + }; + + public GcTypeKind GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + => ClassifyTokenLookup(_target.Contracts.Loader.GetLookupTables(_moduleHandle).TypeDefToMethodTable, MetadataTokens.GetToken(handle), rawTypeKind); + + public GcTypeKind GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + => ClassifyTokenLookup(_target.Contracts.Loader.GetLookupTables(_moduleHandle).TypeRefToMethodTable, MetadataTokens.GetToken(handle), rawTypeKind); + + public GcTypeKind GetTypeFromSpecification(MetadataReader reader, GcSignatureContext genericContext, TypeSpecificationHandle handle, byte rawTypeKind) + => rawTypeKind == (byte)SignatureTypeKind.ValueType ? GcTypeKind.Other : GcTypeKind.Ref; + + public GcTypeKind GetSZArrayType(GcTypeKind elementType) => GcTypeKind.Ref; + public GcTypeKind GetArrayType(GcTypeKind elementType, ArrayShape shape) => GcTypeKind.Ref; + public GcTypeKind GetByReferenceType(GcTypeKind elementType) => GcTypeKind.Interior; + public GcTypeKind GetPointerType(GcTypeKind elementType) => GcTypeKind.None; + + public GcTypeKind GetGenericInstantiation(GcTypeKind genericType, ImmutableArray typeArguments) + => genericType; + + public GcTypeKind GetGenericMethodParameter(GcSignatureContext genericContext, int index) + { + try + { + ReadOnlySpan instantiation = _target.Contracts.RuntimeTypeSystem.GetGenericMethodInstantiation(genericContext.MethodContext); + if ((uint)index >= (uint)instantiation.Length) + return GcTypeKind.Ref; + return ClassifyTypeHandle(instantiation[index]); + } + catch + { + return GcTypeKind.Ref; + } + } + + public GcTypeKind GetGenericTypeParameter(GcSignatureContext genericContext, int index) + { + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TypeHandle classCtx = genericContext.ClassContext; + + if (rts.IsArray(classCtx, out _)) + { + // Match native SigTypeContext::InitTypeContext (typectxt.cpp): arrays use + // the element type as their class instantiation. RuntimeTypeSystem.GetInstantiation + // returns an empty span for arrays, so consult GetTypeParam directly (the + // managed equivalent of MethodTable::GetArrayInstantiation). + Debug.Assert(index == 0, "Array class context has a 1-element instantiation; index > 0 indicates a malformed signature."); + if (index != 0) + return GcTypeKind.Ref; + return ClassifyTypeHandle(rts.GetTypeParam(classCtx)); + } + + ReadOnlySpan instantiation = rts.GetInstantiation(classCtx); + if ((uint)index >= (uint)instantiation.Length) + return GcTypeKind.Ref; + return ClassifyTypeHandle(instantiation[index]); + } + catch + { + return GcTypeKind.Ref; + } + } + + public GcTypeKind GetFunctionPointerType(MethodSignature signature) => GcTypeKind.None; + public GcTypeKind GetModifiedType(GcTypeKind modifier, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType; + public GcTypeKind GetInternalModifiedType(TargetPointer typeHandlePointer, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType; + public GcTypeKind GetPinnedType(GcTypeKind elementType) => elementType; + + public GcTypeKind GetInternalType(TargetPointer typeHandlePointer) + { + if (typeHandlePointer == TargetPointer.Null) + return GcTypeKind.None; + + try + { + return ClassifyTypeHandle(_target.Contracts.RuntimeTypeSystem.GetTypeHandle(typeHandlePointer)); + } + catch + { + return GcTypeKind.Ref; + } + } + + /// + /// Resolve a TypeDef/TypeRef token via the module's lookup tables and classify the + /// resulting . Falls back to a -based + /// classification when the type has not been loaded. + /// + private GcTypeKind ClassifyTokenLookup(TargetPointer lookupTable, int token, byte rawTypeKind) + { + try + { + TargetPointer typeHandlePtr = _target.Contracts.Loader.GetModuleLookupMapElement(lookupTable, (uint)token, out _); + if (typeHandlePtr == TargetPointer.Null) + return rawTypeKind == (byte)SignatureTypeKind.ValueType ? GcTypeKind.Other : GcTypeKind.Ref; + + return ClassifyTypeHandle(_target.Contracts.RuntimeTypeSystem.GetTypeHandle(typeHandlePtr)); + } + catch + { + return rawTypeKind == (byte)SignatureTypeKind.ValueType ? GcTypeKind.Other : GcTypeKind.Ref; + } + } + + /// + /// Classify a resolved . Mirrors native + /// SigPointer::PeekElemTypeNormalized + gElementTypeInfo[etype].m_gc: + /// enums collapse to their underlying primitive () so + /// they are skipped during stack scanning, matching native behavior. + /// + private GcTypeKind ClassifyTypeHandle(TypeHandle typeHandle) + { + if (typeHandle.Address == TargetPointer.Null) + return GcTypeKind.Ref; + + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + CorElementType corType = rts.GetSignatureCorElementType(typeHandle); + + switch (corType) + { + case CorElementType.Void: + case CorElementType.Boolean: + case CorElementType.Char: + case CorElementType.I1: + case CorElementType.U1: + case CorElementType.I2: + case CorElementType.U2: + case CorElementType.I4: + case CorElementType.U4: + case CorElementType.I8: + case CorElementType.U8: + case CorElementType.R4: + case CorElementType.R8: + case CorElementType.I: + case CorElementType.U: + case CorElementType.FnPtr: + case CorElementType.Ptr: + return GcTypeKind.None; + + case CorElementType.Byref: + return GcTypeKind.Interior; + + case CorElementType.ValueType: + // Native PeekElemTypeNormalized resolves enums to their underlying primitive + // CorElementType, which classifies as TYPE_GC_NONE in gElementTypeInfo. + return rts.IsEnum(typeHandle) ? GcTypeKind.None : GcTypeKind.Other; + + default: + return GcTypeKind.Ref; + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index 3a09197254291a..c11974119d131b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -8,7 +8,6 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -using Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; using Microsoft.Diagnostics.DataContractReader.Data; using System.Linq; @@ -18,11 +17,13 @@ internal partial class StackWalk_1 : IStackWalk { private readonly Target _target; private readonly IExecutionManager _eman; + private readonly GcScanner _gcScanner; internal StackWalk_1(Target target) { _target = target; _eman = target.Contracts.ExecutionManager; + _gcScanner = new GcScanner(target); } public enum StackWalkState @@ -68,6 +69,19 @@ private class StackWalkData(IPlatformAgnosticContext context, StackWalkState sta // set back to true when encountering a ResumableFrame (FRAME_ATTR_RESUMABLE). public bool IsFirst { get; set; } = true; + // Track isInterrupted like native CrawlFrame::isInterrupted. + // Set in UpdateState when transitioning to SW_FRAMELESS after processing a Frame + // with FRAME_ATTR_EXCEPTION (e.g., FaultingExceptionFrame). When true, the managed + // frame reached via that Frame's return address was interrupted by an exception, + // and EnumGcRefs should use ExecutionAborted to skip live slot reporting at + // non-interruptible offsets. + public bool IsInterrupted { get; set; } + + // The frame type of the last SW_FRAME processed by Next(). + // Used by UpdateState to detect exception frames (FRAME_ATTR_EXCEPTION) and + // set IsInterrupted when transitioning to a managed frame. + public FrameIterator.FrameType? LastProcessedFrameType { get; set; } + public bool IsCurrentFrameResumable() { if (State is not (StackWalkState.SW_FRAME or StackWalkState.SW_SKIPPED_FRAME)) @@ -77,9 +91,10 @@ public bool IsCurrentFrameResumable() // Only frame types with FRAME_ATTR_RESUMABLE set isFirst=true. // FaultingExceptionFrame has FRAME_ATTR_FAULTED (sets hasFaulted) // but NOT FRAME_ATTR_RESUMABLE, so it must not be included here. - // TODO: HijackFrame only has FRAME_ATTR_RESUMABLE on non-x86 platforms. - // When x86 stack walking is supported, this should be conditioned on - // the target architecture. + // Note: HijackFrame only has FRAME_ATTR_RESUMABLE on non-x86 platforms + // (see frames.h). On x86 it uses GcScanRoots_Impl instead of the + // resumable frame pattern. When x86 cDAC stack walking is supported, + // HijackFrame should be conditioned on the target architecture. return ft is FrameIterator.FrameType.ResumableFrame or FrameIterator.FrameType.RedirectedThreadFrame or FrameIterator.FrameType.HijackFrame; @@ -87,9 +102,11 @@ or FrameIterator.FrameType.RedirectedThreadFrame /// /// Update the IsFirst state for the NEXT frame, matching native stackwalk.cpp: - /// - After a frameless frame: isFirst = false (line 2202) - /// - After a ResumableFrame: isFirst = true (line 2235) - /// - After other Frames: isFirst = false (implicit in line 2235 assignment) + /// - After a frameless frame: isFirst = false + /// - After a ResumableFrame: isFirst = true + /// - After other Frames: isFirst = false + /// - After a skipped frame: isFirst unchanged (native never modifies isFirst + /// in the SFITER_SKIPPED_FRAME_FUNCTION path — it keeps the value from Init) /// public void AdvanceIsFirst() { @@ -97,6 +114,14 @@ public void AdvanceIsFirst() { IsFirst = false; } + else if (State == StackWalkState.SW_SKIPPED_FRAME) + { + // Native SFITER_SKIPPED_FRAME_FUNCTION (stackwalk.cpp:2086-2128) does NOT + // modify isFirst. It stays true from Init() so the subsequent managed frame + // gets IsActiveFunc()=true. This is important because skipped frames are + // explicit Frames embedded within the active managed frame (e.g. InlinedCallFrame + // from PInvoke), and the managed frame should still be treated as the leaf. + } else { IsFirst = IsCurrentFrameResumable(); @@ -112,53 +137,13 @@ public StackDataFrameHandle ToDataFrame() } IEnumerable IStackWalk.CreateStackWalk(ThreadData threadData) - => CreateStackWalkCore(threadData, skipInitialFrames: false, flags: ContextFlags.All); - - /// - /// Core stack walk implementation. - /// - /// Thread to walk. - /// - /// When true, pre-advances the FrameIterator past explicit Frames below the initial - /// managed frame's caller SP. This matches the native DacStackReferenceWalker behavior - /// for GC reference enumeration, where these frames are within the current managed - /// frame's stack range and don't contribute additional GC roots. - /// - /// Must be false for ClrDataStackWalk, which advances the cDAC and legacy DAC in - /// lockstep and must yield the same frame sequence (including initial skipped frames). - /// - private IEnumerable CreateStackWalkCore(ThreadData threadData, bool skipInitialFrames, ContextFlags flags) { IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(_target); - uint contextFlags = flags switch - { - ContextFlags.Full => context.FullContextFlags, - ContextFlags.All => context.AllContextFlags, - _ => throw new ArgumentOutOfRangeException(nameof(flags)), - }; + uint contextFlags = context.AllContextFlags; FillContextFromThread(context, threadData, contextFlags); StackWalkState state = IsManaged(context.InstructionPointer, out _) ? StackWalkState.SW_FRAMELESS : StackWalkState.SW_FRAME; FrameIterator frameIterator = new(_target, threadData); - if (skipInitialFrames) - { - TargetPointer skipBelowSP; - if (state == StackWalkState.SW_FRAMELESS) - { - IPlatformAgnosticContext callerCtx = context.Clone(); - callerCtx.Unwind(_target); - skipBelowSP = callerCtx.StackPointer; - } - else - { - skipBelowSP = context.StackPointer; - } - while (frameIterator.IsValid() && frameIterator.CurrentFrameAddress.Value < skipBelowSP.Value) - { - frameIterator.Next(); - } - } - // if the next Frame is not valid and we are not in managed code, there is nothing to return if (state == StackWalkState.SW_FRAME && !frameIterator.IsValid()) { @@ -170,7 +155,7 @@ private IEnumerable CreateStackWalkCore(ThreadData thread // Mirror native Init() -> ProcessCurrentFrame() -> CheckForSkippedFrames(): // When the initial frame is managed (SW_FRAMELESS), check if there are explicit // Frames below the caller SP that should be reported first. The native walker - // yields skipped frames BEFORE the containing managed frame on non-x86. + // yields skipped frames BEFORE the containing managed frame. if (state == StackWalkState.SW_FRAMELESS && CheckForSkippedFrames(stackWalkData)) { stackWalkData.State = StackWalkState.SW_SKIPPED_FRAME; @@ -188,18 +173,32 @@ private IEnumerable CreateStackWalkCore(ThreadData thread IReadOnlyList IStackWalk.WalkStackReferences(ThreadData threadData) { - IEnumerable stackFrames = CreateStackWalkCore(threadData, skipInitialFrames: true, flags: ContextFlags.Full); - IEnumerable frames = stackFrames.Select(AssertCorrectHandle); - IEnumerable gcFrames = Filter(frames); + // Initialize the walk data directly + IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(_target); + FillContextFromThread(context, threadData, context.FullContextFlags); + StackWalkState state = IsManaged(context.InstructionPointer, out _) ? StackWalkState.SW_FRAMELESS : StackWalkState.SW_FRAME; + FrameIterator frameIterator = new(_target, threadData); + + if (state == StackWalkState.SW_FRAME && !frameIterator.IsValid()) + return []; + + StackWalkData walkData = new(context, state, frameIterator, threadData); + + // Mirror native Init() -> ProcessCurrentFrame() -> CheckForSkippedFrames(): + // When the initial frame is managed (SW_FRAMELESS), check if there are explicit + // Frames below the caller SP that should be reported first. The native walker + // yields skipped frames BEFORE the containing managed frame. + if (walkData.State == StackWalkState.SW_FRAMELESS && CheckForSkippedFrames(walkData)) + walkData.State = StackWalkState.SW_SKIPPED_FRAME; GcScanContext scanContext = new(_target, resolveInteriorPointers: false); - foreach (GCFrameData gcFrame in gcFrames) + // Filter drives Next() directly, matching native Filter()+NextRaw() integration. + // This prevents funclet-to-parent transitions from re-visiting already-walked frames. + foreach (GCFrameData gcFrame in Filter(walkData)) { try { - _ = ((IStackWalk)this).GetMethodDescPtr(gcFrame.Frame); - bool reportGcReferences = gcFrame.ShouldCrawlFrameReportGCReferences; TargetPointer pFrame = ((IStackWalk)this).GetFrameAddress(gcFrame.Frame); @@ -215,26 +214,48 @@ IReadOnlyList IStackWalk.WalkStackReferences(ThreadData thre if (!IsManaged(gcFrame.Frame.Context.InstructionPointer, out CodeBlockHandle? cbh)) throw new InvalidOperationException("Expected managed code"); - CodeManagerFlags codeManagerFlags = gcFrame.Frame.IsActiveFrame - ? CodeManagerFlags.ActiveStackFrame - : 0; - - if (gcFrame.ShouldParentToFuncletSkipReportingGCReferences) - codeManagerFlags |= CodeManagerFlags.ParentOfFuncletStackFrame; - - // TODO: When ShouldParentFrameUseUnwindTargetPCforGCReporting is set, - // use FindFirstInterruptiblePoint on the catch handler clause range - // to override the relOffset for GC liveness lookup. This mirrors - // native gcenv.ee.common.cpp behavior for catch-handler resumption. + GcSlotEnumerationOptions gcOptions = new() + { + IsActiveFrame = gcFrame.Frame.IsActiveFrame, + + // If the frame was interrupted by an exception (reached via a + // FaultingExceptionFrame), set ExecutionAborted so the GcInfoDecoder + // skips live slot reporting at non-interruptible offsets. This matches + // native CrawlFrame::GetCodeManagerFlags (stackwalk.h). + IsExecutionAborted = gcFrame.IsInterrupted, + IsParentOfFuncletStackFrame = gcFrame.ShouldParentToFuncletSkipReportingGCReferences, + SuppressUntrackedSlots = _eman.IsFilterFunclet(cbh.Value), + }; + + uint? relOffsetOverride = null; + if (gcFrame.ShouldParentFrameUseUnwindTargetPCforGCReporting) + { + _eman.GetGCInfo(cbh.Value, out TargetPointer gcInfoAddr, out uint gcVersion); + IGCInfoHandle gcHandle = _target.Contracts.GCInfo.DecodePlatformSpecificGCInfo(gcInfoAddr, gcVersion); + uint startPC = gcFrame.ClauseForCatchHandlerStartPC; + uint endPC = gcFrame.ClauseForCatchHandlerEndPC; + foreach (var range in _target.Contracts.GCInfo.GetInterruptibleRanges(gcHandle)) + { + if (range.EndOffset <= startPC) + continue; + if (startPC >= range.StartOffset && startPC < range.EndOffset) + { + relOffsetOverride = startPC; + break; + } + if (range.StartOffset < endPC) + { + relOffsetOverride = range.StartOffset; + break; + } + } + } - GcScanner gcScanner = new(_target); - gcScanner.EnumGcRefs(gcFrame.Frame.Context, cbh.Value, codeManagerFlags, scanContext); + _gcScanner.EnumGcRefsForManagedFrame(gcFrame.Frame.Context, cbh.Value, gcOptions, scanContext, relOffsetOverride); } else { - // TODO: Frame-based GC root scanning (ScanFrameRoots) not yet implemented. - // Frames that call PromoteCallerStack (StubDispatchFrame, ExternalMethodFrame, - // DynamicHelperFrame, etc.) will be handled in a follow-up PR. + _gcScanner.GcScanRoots(gcFrame.Frame.FrameAddress, scanContext); } } } @@ -272,11 +293,25 @@ public GCFrameData(StackDataFrameHandle frame) public bool ShouldParentToFuncletSkipReportingGCReferences { get; set; } public bool ShouldCrawlFrameReportGCReferences { get; set; } // required public bool ShouldParentFrameUseUnwindTargetPCforGCReporting { get; set; } + public uint ClauseForCatchHandlerStartPC { get; set; } + public uint ClauseForCatchHandlerEndPC { get; set; } + // Set when the frame was reached via an exception Frame (FRAME_ATTR_EXCEPTION). + // Causes ExecutionAborted to be passed to EnumGcRefs. + public bool IsInterrupted { get; set; } } - private IEnumerable Filter(IEnumerable handles) + /// + /// Port of native StackFrameIterator::Filter (GC_FUNCLET_REFERENCE_REPORTING mode). + /// Unlike the previous implementation that passively consumed pre-generated frames, + /// this version drives Next() directly — matching native Filter() which calls NextRaw() + /// internally to skip frames. This prevents funclet-to-parent transitions from + /// re-visiting already-walked frames. + /// +#pragma warning disable IDE0059 // Unnecessary assignment — false positives from goto case + do/while pattern + private IEnumerable Filter(StackWalkData walkData) { - // StackFrameIterator::Filter assuming GC_FUNCLET_REFERENCE_REPORTING is defined + // Process the initial frame, then loop calling Next() for subsequent frames. + // This matches native: Init() produces the first frame, then Filter()+NextRaw() loop. // global tracking variables bool processNonFilterFunclet = false; @@ -284,11 +319,19 @@ private IEnumerable Filter(IEnumerable handle bool didFuncletReportGCReferences = true; TargetPointer parentStackFrame = TargetPointer.Null; TargetPointer funcletParentStackFrame = TargetPointer.Null; - TargetPointer intermediaryFuncletParentStackFrame; + TargetPointer intermediaryFuncletParentStackFrame = TargetPointer.Null; - foreach (StackDataFrameHandle handle in handles) + // Process the initial frame, then advance with Next() + bool isValid = walkData.State is not (StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE); + while (isValid) { - GCFrameData gcFrame = new(handle); + StackDataFrameHandle handle = walkData.ToDataFrame(); + walkData.AdvanceIsFirst(); + + GCFrameData gcFrame = new(handle) + { + IsInterrupted = walkData.IsInterrupted, + }; // per-frame tracking variables bool stop = false; @@ -506,6 +549,9 @@ private IEnumerable Filter(IEnumerable handle didFuncletReportGCReferences = true; gcFrame.ShouldParentFrameUseUnwindTargetPCforGCReporting = true; + + gcFrame.ClauseForCatchHandlerStartPC = exInfo.ClauseForCatchHandlerStartPC; + gcFrame.ClauseForCatchHandlerEndPC = exInfo.ClauseForCatchHandlerEndPC; } else if (!IsFunclet(handle)) { @@ -580,8 +626,14 @@ private IEnumerable Filter(IEnumerable handle if (stop) yield return gcFrame; + + // Advance the iterator - matching native Filter() calling NextRaw() + // When a frame was skipped (stop=false), this advances past it. + // When a frame was yielded (stop=true), this advances to the next frame. + isValid = Next(walkData); } } +#pragma warning restore IDE0059 private bool IsUnwoundToTargetParentFrame(StackDataFrameHandle handle, TargetPointer targetParentFrame) { @@ -598,6 +650,18 @@ private bool Next(StackWalkData handle) switch (handle.State) { case StackWalkState.SW_FRAMELESS: + // Native assertion (stackwalk.cpp): current SP must be below the next Frame. + // FaultingExceptionFrame is a special case where it gets pushed after the frame is running. + Debug.Assert( + !handle.FrameIter.IsValid() || + handle.Context.StackPointer.Value < handle.FrameIter.CurrentFrameAddress.Value || + handle.FrameIter.GetCurrentFrameType() == FrameIterator.FrameType.FaultingExceptionFrame, + $"SP (0x{handle.Context.StackPointer:X}) should be below next Frame (0x{handle.FrameIter.CurrentFrameAddress:X})"); + + // Reset interrupted state after processing a managed frame. + // Native stackwalk.cpp:2203-2205: isInterrupted = false; hasFaulted = false; + handle.IsInterrupted = false; + try { handle.Context.Unwind(_target); @@ -609,13 +673,33 @@ private bool Next(StackWalkData handle) } break; case StackWalkState.SW_SKIPPED_FRAME: + // Advance past the skipped frame, then let UpdateState detect + // whether there are more skipped frames or we've reached the managed method. handle.FrameIter.Next(); break; case StackWalkState.SW_FRAME: - handle.FrameIter.UpdateContextFromFrame(handle.Context); - if (!handle.FrameIter.IsInlineCallFrameWithActiveCall()) + // Native SFITER_FRAME_FUNCTION gates ProcessIp + UpdateRegDisplay on + // GetReturnAddress() != 0, and gates GotoNextFrame on !pInlinedFrame. + // pInlinedFrame is set only for active InlinedCallFrames. { - handle.FrameIter.Next(); + var frameType = handle.FrameIter.GetCurrentFrameType(); + + TargetPointer returnAddress = handle.FrameIter.GetReturnAddress(); + bool isActiveICF = frameType == FrameIterator.FrameType.InlinedCallFrame + && returnAddress != TargetPointer.Null; + + // Record the frame type so UpdateState can detect exception frames + // and set IsInterrupted when transitioning to the managed frame. + handle.LastProcessedFrameType = frameType; + + if (returnAddress != TargetPointer.Null) + { + handle.FrameIter.UpdateContextFromFrame(handle.Context); + } + if (!isActiveICF) + { + handle.FrameIter.Next(); + } } break; case StackWalkState.SW_ERROR: @@ -641,6 +725,18 @@ private void UpdateState(StackWalkData handle) if (isManaged) { handle.State = StackWalkState.SW_FRAMELESS; + + // Detect exception frames (FRAME_ATTR_EXCEPTION) when transitioning to managed. + // Both FaultingExceptionFrame (hardware) and SoftwareExceptionFrame (managed throw) + // have FRAME_ATTR_EXCEPTION set. The resulting managed frame gets ExecutionAborted, + // causing GcInfoDecoder to skip live slot reporting at non-interruptible offsets. + if (handle.LastProcessedFrameType is FrameIterator.FrameType.FaultingExceptionFrame + or FrameIterator.FrameType.SoftwareExceptionFrame) + { + handle.IsInterrupted = true; + } + handle.LastProcessedFrameType = null; + if (CheckForSkippedFrames(handle)) { handle.State = StackWalkState.SW_SKIPPED_FRAME; @@ -719,15 +815,17 @@ TargetPointer IStackWalk.GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHa // 4) the return address method has a MDContext arg bool reportInteropMD = false; - if (FrameIterator.IsInlinedCallFrame(_target, framePtr) && + Data.Frame frameData = _target.ProcessedData.GetOrAdd(framePtr); + FrameIterator.FrameType frameType = FrameIterator.GetFrameType(_target, frameData.Identifier); + + if (frameType == FrameIterator.FrameType.InlinedCallFrame && handle.State == StackWalkState.SW_SKIPPED_FRAME) { IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - // FrameIterator.GetReturnAddress is currently only implemented for InlinedCallFrame - // This is fine as this check is only needed for that frame type - TargetPointer returnAddress = FrameIterator.GetReturnAddress(_target, framePtr); - if (_eman.GetCodeBlockHandle(returnAddress.Value) is CodeBlockHandle cbh) + Data.InlinedCallFrame icf = _target.ProcessedData.GetOrAdd(framePtr); + TargetPointer returnAddress = icf.CallerReturnAddress; + if (returnAddress != TargetPointer.Null && _eman.GetCodeBlockHandle(returnAddress.Value) is CodeBlockHandle cbh) { MethodDescHandle returnMethodDesc = rts.GetMethodDescHandle(_eman.GetMethodDesc(cbh)); reportInteropMD = rts.HasMDContextArg(returnMethodDesc); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index f84ae929637aaa..7ddc39936340d2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -25,7 +25,7 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new SHash_1(t)); registry.Register("c1", static t => new Notifications_1(t)); registry.Register("c1", static t => new CodeNotifications_1(t)); - registry.Register("c1", static t => new SignatureDecoder_1(t)); + registry.Register("c1", static t => new Signature_1(t)); registry.Register("c1", static t => new BuiltInCOM_1(t)); registry.Register("c1", static t => new ConditionalWeakTable_1(t)); registry.Register("c1", static t => new AuxiliarySymbols_1(t)); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.cs index c8d30ded52e678..d582523b5159ec 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.cs @@ -23,6 +23,8 @@ public ExceptionInfo(Target target, TargetPointer address) CSFEHClause = target.ReadPointerField(address, type, nameof(CSFEHClause)); CSFEnclosingClause = target.ReadPointerField(address, type, nameof(CSFEnclosingClause)); CallerOfActualHandlerFrame = target.ReadPointerField(address, type, nameof(CallerOfActualHandlerFrame)); + ClauseForCatchHandlerStartPC = target.ReadField(address, type, nameof(ClauseForCatchHandlerStartPC)); + ClauseForCatchHandlerEndPC = target.ReadField(address, type, nameof(ClauseForCatchHandlerEndPC)); } public TargetPointer PreviousNestedInfo { get; } @@ -35,4 +37,6 @@ public ExceptionInfo(Target target, TargetPointer address) public TargetPointer CSFEHClause { get; } public TargetPointer CSFEnclosingClause { get; } public TargetPointer CallerOfActualHandlerFrame { get; } + public uint ClauseForCatchHandlerStartPC { get; } + public uint ClauseForCatchHandlerEndPC { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.cs new file mode 100644 index 00000000000000..625c616d42616e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.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. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class DynamicHelperFrame : IData +{ + static DynamicHelperFrame IData.Create(Target target, TargetPointer address) + => new DynamicHelperFrame(target, address); + + public DynamicHelperFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.DynamicHelperFrame); + DynamicHelperFrameFlags = target.ReadField(address, type, nameof(DynamicHelperFrameFlags)); + } + + public int DynamicHelperFrameFlags { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.cs new file mode 100644 index 00000000000000..108fe0d0a5b70d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.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. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class ExternalMethodFrame : IData +{ + static ExternalMethodFrame IData.Create(Target target, TargetPointer address) + => new ExternalMethodFrame(target, address); + + public ExternalMethodFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ExternalMethodFrame); + Indirection = target.ReadPointerField(address, type, nameof(Indirection)); + } + + public TargetPointer Indirection { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.cs index c49f6919353255..a063f9f3a0dc52 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.cs @@ -14,6 +14,7 @@ public StubDispatchFrame(Target target, TargetPointer address) MethodDescPtr = target.ReadPointerField(address, type, nameof(MethodDescPtr)); RepresentativeMTPtr = target.ReadPointerField(address, type, nameof(RepresentativeMTPtr)); RepresentativeSlot = target.ReadField(address, type, nameof(RepresentativeSlot)); + Indirection = target.ReadPointerField(address, type, nameof(Indirection)); Address = address; } @@ -21,4 +22,5 @@ public StubDispatchFrame(Target target, TargetPointer address) public TargetPointer MethodDescPtr { get; } public TargetPointer RepresentativeMTPtr { get; } public uint RepresentativeSlot { get; } + public TargetPointer Indirection { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs index b2c0c71cb47ef9..470aacc61446bc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs @@ -14,17 +14,21 @@ public TransitionBlock(Target target, TargetPointer address) ReturnAddress = target.ReadPointerField(address, type, nameof(ReturnAddress)); CalleeSavedRegisters = address + (ulong)type.Fields[nameof(CalleeSavedRegisters)].Offset; - if (type.Fields.ContainsKey(nameof(ArgumentRegisters))) - { - ArgumentRegisters = address + (ulong)type.Fields[nameof(ArgumentRegisters)].Offset; - } + // These are computed positions within the TransitionBlock. + ArgumentRegisters = address + (ulong)type.Fields[nameof(ArgumentRegisters)].Offset; + FirstGCRefMapSlot = address + (ulong)type.Fields[nameof(FirstGCRefMapSlot)].Offset; } public TargetPointer ReturnAddress { get; } public TargetPointer CalleeSavedRegisters { get; } /// - /// Only available on ARM targets. + /// Address of the argument registers area within this TransitionBlock. /// - public TargetPointer? ArgumentRegisters { get; } + public TargetPointer ArgumentRegisters { get; } + + /// + /// Address of the first slot covered by the GCRefMap within this TransitionBlock. + /// + public TargetPointer FirstGCRefMapSlot { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs index 3241fa45b965a0..6557ee7aa99a1c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -33,6 +33,11 @@ public ReadyToRunInfo(Target target, TargetPointer address) DebugInfoSection = target.ReadPointerField(address, type, nameof(DebugInfoSection)); ExceptionInfoSection = target.ReadPointerField(address, type, nameof(ExceptionInfoSection)); + NumImportSections = target.Read(address + (ulong)type.Fields[nameof(NumImportSections)].Offset); + ImportSections = NumImportSections > 0 + ? target.ReadPointer(address + (ulong)type.Fields[nameof(ImportSections)].Offset) + : TargetPointer.Null; + // Map is from the composite info pointer (set to itself for non-multi-assembly composite images) EntryPointToMethodDescMap = CompositeInfo + (ulong)type.Fields[nameof(EntryPointToMethodDescMap)].Offset; LoadedImageBase = target.ReadPointerField(address, type, nameof(LoadedImageBase)); @@ -55,4 +60,6 @@ public ReadyToRunInfo(Target target, TargetPointer address) public TargetPointer EntryPointToMethodDescMap { get; } public TargetPointer LoadedImageBase { get; } public TargetPointer Composite { get; } + public uint NumImportSections { get; } + public TargetPointer ImportSections { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs index 901d9331c8938f..7fabac70993319 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs @@ -15,7 +15,8 @@ internal enum MethodDescFlags : ushort HasNonVtableSlot = 0x0008, HasMethodImpl = 0x0010, HasNativeCodeSlot = 0x0020, - HasAsyncMethodData = 0x040, + HasAsyncMethodData = 0x0040, + Static = 0x0080, #endregion Optional slots } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 552c484239d96e..5bdd38d8a1a782 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -460,8 +460,7 @@ int IXCLRDataModule.GetName(uint bufLen, uint* nameLen, char* name) *nameLen = 0; Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(_address); - if (!loader.TryGetSimpleName(handle, out string result)) - throw new ArgumentException("Module does not have a simple name"); + string result = loader.GetSimpleName(handle); uint nameLenLocal = 0; OutputBufferHelpers.CopyStringToBuffer(name, bufLen, &nameLenLocal, result); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index dc3f6b7d93cdb9..2ea47ab69e8f94 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -146,7 +146,36 @@ public int GetAppDomainFullName(ulong vmAppDomain, nint pStrName) } public int GetModuleSimpleName(ulong vmModule, nint pStrFilename) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetModuleSimpleName(vmModule, pStrFilename) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + string? cdacSimpleName = null; + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); + cdacSimpleName = loader.GetSimpleName(handle); + hr = StringHolderAssignCopy(pStrFilename, cdacSimpleName); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + using var legacyHolder = new NativeStringHolder(); + int hrLocal = _legacy.GetModuleSimpleName(vmModule, legacyHolder.Ptr); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert( + string.Equals(cdacSimpleName, legacyHolder.Value, System.StringComparison.Ordinal), + $"GetModuleSimpleName string mismatch - cDAC: '{cdacSimpleName}', DAC: '{legacyHolder.Value}'"); + } + } +#endif + return hr; + } public int GetAssemblyPath(ulong vmAssembly, nint pStrFilename, Interop.BOOL* pResult) { diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/NativeStringHolder.cs similarity index 95% rename from src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/NativeStringHolder.cs index 8bca8e70b1bb24..9cf51b96b66c26 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/NativeStringHolder.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.InteropServices; -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; +namespace Microsoft.Diagnostics.DataContractReader.Legacy; /// /// Creates a native-memory object that mimics the C++ IStringHolder vtable layout. @@ -52,7 +52,7 @@ public NativeStringHolder() private int AssignCopyImpl(IntPtr thisPtr, IntPtr psz) { Value = Marshal.PtrToStringUni(psz); - return System.HResults.S_OK; + return HResults.S_OK; } public void Dispose() diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index d67adff3ab3489..116d74eb604e61 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -1071,7 +1071,7 @@ int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescDat IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata; - ISignatureDecoder signatureDecoder = _target.Contracts.SignatureDecoder; + ISignature signatureContract = _target.Contracts.Signature; TargetPointer fieldDescTargetPtr = fieldDesc.ToTargetPointer(_target); CorElementType fieldDescType = rtsContract.GetFieldDescType(fieldDescTargetPtr); @@ -1090,7 +1090,7 @@ int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescDat try { // try to completely decode the signature - TypeHandle foundTypeHandle = signatureDecoder.DecodeFieldSignature(fieldDef.Signature, moduleHandle, ctx); + TypeHandle foundTypeHandle = signatureContract.DecodeFieldSignature(fieldDef.Signature, moduleHandle, ctx); // get the MT of the type // This is an implementation detail of the DAC that we replicate here to get method tables for non-MT types @@ -4034,13 +4034,69 @@ int ISOSEnum.GetCount(uint* pCount) int ISOSDacInterface.GetStackReferences(int osThreadID, DacComNullableByRef ppEnum) { - // Stack reference enumeration is not yet complete in the cDAC — capital-F Frame - // GC root scanning (ScanFrameRoots) is still pending. Fall through to the legacy - // DAC so that consumers (dump tests, SOS) continue to work while the implementation - // is in progress. - return _legacyImpl is not null - ? _legacyImpl.GetStackReferences(osThreadID, ppEnum) - : HResults.E_NOTIMPL; + int hr = HResults.S_OK; + try + { + IThread threadContract = _target.Contracts.Thread; + IStackWalk stackWalkContract = _target.Contracts.StackWalk; + ThreadData? matchingThread = null; + + ThreadStoreData threadStore = threadContract.GetThreadStoreData(); + TargetPointer threadAddr = threadStore.FirstThread; + while (threadAddr != TargetPointer.Null) + { + ThreadData td = threadContract.GetThreadData(threadAddr); + if (td.OSId.Value == (ulong)osThreadID) + { + matchingThread = td; + break; + } + threadAddr = td.NextThread; + } + + if (matchingThread is null) + { + throw new ArgumentException($"No thread with OS ID {osThreadID} was found."); + } + + IReadOnlyList refs = stackWalkContract.WalkStackReferences(matchingThread.Value); + + SOSStackRefData[] sosRefs = new SOSStackRefData[refs.Count]; + for (int i = 0; i < refs.Count; i++) + { + sosRefs[i] = new SOSStackRefData + { + HasRegisterInformation = refs[i].HasRegisterInformation ? 1 : 0, + Register = refs[i].Register, + Offset = refs[i].Offset, + Address = refs[i].Address.Value, + Object = refs[i].Object.Value, + Flags = refs[i].Flags, + Source = refs[i].Source.Value, + SourceType = refs[i].IsStackSourceFrame + ? SOSStackSourceType.SOS_StackSourceFrame + : SOSStackSourceType.SOS_StackSourceIP, + StackPointer = refs[i].StackPointer.Value, + }; + } + + ppEnum.Interface = new SOSStackRefEnum(sosRefs); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacyImpl is not null) + { + // Validate that the legacy DAC produces the same HResult. + // We pass isNullRef: false to request actual enumeration, but we don't + // compare individual refs — that's done by cdacstress.cpp at runtime. + int hrLocal = _legacyImpl.GetStackReferences(osThreadID, new DacComNullableByRef(isNullRef: false)); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + return hr; } int ISOSDacInterface.GetStressLogAddress(ClrDataAddress* stressLog) diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs index 8b7c92ca26066f..d82ddc0c29dc0d 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs @@ -10,11 +10,11 @@ namespace Microsoft.Diagnostics.DataContractReader.DumpTests; /// /// Dump-based integration tests for DacDbiImpl loader, assembly, and module methods. -/// Uses the MultiModule debuggee (full dump), which loads assemblies from multiple ALCs. +/// Uses the StackRefs debuggee (full dump). /// public class DacDbiLoaderDumpTests : DumpTestBase { - protected override string DebuggeeName => "MultiModule"; + protected override string DebuggeeName => "StackRefs"; private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); private IEnumerable GetAllModules() @@ -137,6 +137,30 @@ public unsafe void GetModuleData_ReturnsValidFields(TestConfiguration config) Assert.True(testedAtLeastOne, "Expected at least one module in the dump"); } + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void GetModuleSimpleName_ReturnsNonEmpty(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + ILoader loader = Target.Contracts.Loader; + + bool testedAtLeastOne = false; + foreach (ModuleHandle module in GetAllModules()) + { + TargetPointer moduleAddr = loader.GetModule(module); + + using var holder = new NativeStringHolder(); + int hr = dbi.GetModuleSimpleName(moduleAddr.Value, holder.Ptr); + Assert.Equal(System.HResults.S_OK, hr); + Assert.False(string.IsNullOrEmpty(holder.Value), "Module simple name should not be empty"); + Assert.Equal(loader.GetSimpleName(module), holder.Value); + + testedAtLeastOne = true; + } + Assert.True(testedAtLeastOne, "Expected at least one module in the dump"); + } + [ConditionalTheory] [MemberData(nameof(TestConfigurations))] public unsafe void IsModuleMapped_ReturnsValidResult(TestConfiguration config) diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.targets b/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.targets index 60cbea1938b738..8728cd5eac9853 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.targets +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.targets @@ -1,6 +1,22 @@ + + + + + $(AfterMicrosoftNETSdkTargets);$(RepositoryEngineeringDir)testing\tests.readytorun.targets + + diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets index 1485177bf16027..ae8228ffa7f7fd 100644 --- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets +++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets @@ -39,6 +39,24 @@ $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', 'Debuggees')) <_DotNetExe>$([MSBuild]::NormalizePath('$(RepoRoot)', '.dotnet', 'dotnet$(ExeSuffix)')) + + <_DebuggeeRuntimeConfig Condition="'$(_DebuggeeRuntimeConfig)' == ''">$(RuntimeConfiguration) + <_DebuggeeRuntimeConfig Condition="'$(_DebuggeeRuntimeConfig)' == ''">$(Configuration) + <_DebuggeeRuntimeConfig Condition="'$(_DebuggeeRuntimeConfig)' == ''">Debug + + + <_DebuggeePublishProps>/p:RuntimeConfiguration=$(_DebuggeeRuntimeConfig) + <_DebuggeePublishProps Condition="'$(TargetArchitecture)' != ''">$(_DebuggeePublishProps) /p:TargetArchitecture=$(TargetArchitecture) + <_DebuggeePublishProps Condition="'$(TargetOS)' != ''">$(_DebuggeePublishProps) /p:TargetOS=$(TargetOS) + <_DebuggeePublishProps Condition="'$(BuildArchitecture)' != ''">$(_DebuggeePublishProps) /p:BuildArchitecture=$(BuildArchitecture) @@ -167,7 +185,7 @@ <_PublishOutDir>$([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts', 'bin', 'DumpTests', '$(DebuggeeName)', '$(DebuggeeConfiguration)', '$(NetCoreAppCurrent)')) - + @@ -254,7 +272,7 @@ <_DebuggeeCsproj>$([MSBuild]::NormalizePath('$(_DebuggeeDir)', '$(DebuggeeName).csproj')) - diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 5150460f4ef698..cd3b2be5f8cc8e 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -112,35 +112,40 @@ public void GetFileName(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void TryGetSimpleName(MockTarget.Architecture arch) + public void GetSimpleName(MockTarget.Architecture arch) { string expected = "TestModule"; TargetPointer moduleAddr = TargetPointer.Null; - TargetPointer moduleAddrEmptyName = TargetPointer.Null; ILoader contract = CreateLoaderContract(arch, loader => { moduleAddr = loader.AddModule(simpleName: expected).Address; - moduleAddrEmptyName = loader.AddModule().Address; }); + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + string actual = contract.GetSimpleName(handle); + Assert.Equal(expected, actual); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetSimpleName_NullSimpleName(MockTarget.Architecture arch) + { + TargetPointer moduleAddr = TargetPointer.Null; + + ILoader contract = CreateLoaderContract(arch, loader => { - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); - bool result = contract.TryGetSimpleName(handle, out string actual); - Assert.True(result); - Assert.Equal(expected, actual); - } - { - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddrEmptyName); - bool result = contract.TryGetSimpleName(handle, out string actual); - Assert.False(result); - Assert.Equal(string.Empty, actual); - } + moduleAddr = loader.AddModule().Address; + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + string actual = contract.GetSimpleName(handle); + Assert.Equal(string.Empty, actual); } [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void TryGetSimpleName_InvalidUtf8(MockTarget.Architecture arch) + public void GetSimpleName_InvalidUtf8(MockTarget.Architecture arch) { // 0xFF is not valid UTF-8 byte[] invalidUtf8 = [0xFF, 0xFE]; @@ -151,7 +156,7 @@ public void TryGetSimpleName_InvalidUtf8(MockTarget.Architecture arch) }); Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); - Assert.Throws(() => contract.TryGetSimpleName(handle, out _)); + Assert.Throws(() => contract.GetSimpleName(handle)); } private static readonly Dictionary MockHeapDictionary = new() diff --git a/src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj b/src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj index 5b33a365154275..a3951ba48e1a21 100644 --- a/src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj +++ b/src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs index c72126d962f939..d054e97d09de27 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -331,6 +331,9 @@ internal sealed class MockReadyToRunInfo : TypedView private const string LoadedImageBaseFieldName = "LoadedImageBase"; private const string CompositeFieldName = "Composite"; + private const string ImportSectionsFieldName = "ImportSections"; + private const string NumImportSectionsFieldName = "NumImportSections"; + public static Layout CreateLayout(MockTarget.Architecture architecture, int hashMapStride) => new SequentialLayoutBuilder("ReadyToRunInfo", architecture) .AddPointerField(ReadyToRunHeaderFieldName) @@ -342,6 +345,8 @@ public static Layout CreateLayout(MockTarget.Architecture ar .AddPointerField(DelayLoadMethodCallThunksFieldName) .AddPointerField(DebugInfoSectionFieldName) .AddPointerField(ExceptionInfoSectionFieldName) + .AddPointerField(ImportSectionsFieldName) + .AddUInt32Field(NumImportSectionsFieldName) .AddField(EntryPointToMethodDescMapFieldName, hashMapStride) .AddPointerField(LoadedImageBaseFieldName) .AddPointerField(CompositeFieldName) diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs index cf6dd55ee67f3b..bf7603a9966c2c 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs @@ -17,6 +17,8 @@ internal sealed class MockExceptionInfo : TypedView private const string CSFEHClauseFieldName = "CSFEHClause"; private const string CSFEnclosingClauseFieldName = "CSFEnclosingClause"; private const string CallerOfActualHandlerFrameFieldName = "CallerOfActualHandlerFrame"; + private const string ClauseForCatchHandlerStartPCFieldName = "ClauseForCatchHandlerStartPC"; + private const string ClauseForCatchHandlerEndPCFieldName = "ClauseForCatchHandlerEndPC"; public static Layout CreateLayout(MockTarget.Architecture architecture) => new SequentialLayoutBuilder("ExceptionInfo", architecture) @@ -30,6 +32,8 @@ public static Layout CreateLayout(MockTarget.Architecture arc .AddPointerField(CSFEHClauseFieldName) .AddPointerField(CSFEnclosingClauseFieldName) .AddPointerField(CallerOfActualHandlerFrameFieldName) + .AddUInt32Field(ClauseForCatchHandlerStartPCFieldName) + .AddUInt32Field(ClauseForCatchHandlerEndPCFieldName) .Build(); public ulong ThrownObjectHandle diff --git a/src/tests/GC/API/GC/GetTotalAllocatedBytes.cs b/src/tests/GC/API/GC/GetTotalAllocatedBytes.cs index c6369889ca80b1..13ef35dbf46742 100644 --- a/src/tests/GC/API/GC/GetTotalAllocatedBytes.cs +++ b/src/tests/GC/API/GC/GetTotalAllocatedBytes.cs @@ -110,8 +110,11 @@ public static void TestAnotherThread() { object lck = new object(); + // 1000 quickly created threads can be too many for a 32-bit environment, so reduce on 32-bit. + int threadCount = IntPtr.Size == 4 ? 100 : 1000; + tsk = Task.Run(() => { - for (int i = 0; i < 1000; i++) + for (int i = 0; i < threadCount; i++) { Thread thd = new Thread(() => { lock (lck) @@ -177,7 +180,6 @@ public static void TestLohSohConcurrently() } [ActiveIssue("needs triage", TestRuntimes.Mono)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/121482", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsArm))] [Fact] public static void TestEntryPoint() {