From 34c50585cfb47c8235098f33df62aa4b1cea99b1 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Apr 2026 09:44:43 -0700
Subject: [PATCH 01/27] Update skip_reason to clarify cancelled leg scenario
(#127479)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Description
Improves the `skip_reason` message set on pipelines with zero test
results to hint at the most common root cause: a cancelled leg.
### Changes
- **`extract_failed_tests.py`**: Updated `skip_reason` string from
`'Build failed but no test failures detected via Test Results API'` to
`'Build failed but no test failures detected via Test Results API, e.g.,
due to a cancelled leg'`
Original prompt
> Update the `skip_reason` string on line 267 of
`.github/skills/ci-pipeline-monitor/scripts/extract_failed_tests.py` in
the `dotnet/runtime` repository.
>
> Change the existing string from:
>
> ```
> Build failed but no test failures detected via Test Results API
> ```
>
> to the exact string:
>
> ```
> Build failed but no test failures detected via Test Results API, e.g.,
due to a cancelled leg
> ```
>
> The line currently reads:
>
> ```python
> "UPDATE pipelines SET result = 'inconclusive', skip_reason = 'Build
failed but no test failures detected via Test Results API' WHERE name =
?",
> ```
>
> After the change, it should read:
>
> ```python
> "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 = ?",
> ```
>
> Only this single line needs to be modified. Do not change any other
occurrences or files unless an identical duplicate of this exact string
exists elsewhere in the same script and clearly needs to be kept
consistent.
>
> The following is the prior conversation context from the user's chat
exploration (may be truncated):
>
> User: File a PR to change this line to set skip_reason to Build failed
but no test failures detected via Test Results API, e.g., due to a
> cancelled leg.
>
> Assistant: Waiting for your response to the dialog before proceeding
with creating the pull request.
>
> User: @Copilot Dismissed Confirmation: Confirm agent session
>
> Assistant: Understood — I won't create the PR. Let me know if you'd
like me to proceed with it later.
>
> User: It should be exact string "Build failed but no test failures
detected via Test Results API, e.g., due to a cancelled leg"
>
> Assistant: I've started a coding agent session to open the PR updating
the `skip_reason` string to `"Build failed but no test failures detected
via Test Results API, e.g., due to a cancelled leg"`.
>
>
*This pull request was created from Copilot chat.*
>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JulieLeeMSFT <63486087+JulieLeeMSFT@users.noreply.github.com>
---
.../skills/ci-pipeline-monitor/scripts/extract_failed_tests.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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:
From a5ad3cf537bd9fa7d4042e0cccdc821c01703ee8 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Apr 2026 16:56:47 +0000
Subject: [PATCH 02/27] Reduce thread count on 32-bit in
GetTotalAllocatedBytes.TestAnotherThread (#127583)
`GC/API/GC/GetTotalAllocatedBytes` intermittently fails with
`OutOfMemoryException` on linux-arm Checked jitstress: rapidly creating
1000 threads exhausts the 32-bit address space.
## Changes
- **`src/tests/GC/API/GC/GetTotalAllocatedBytes.cs`**
- `TestAnotherThread`: cap the inner thread-creation loop at 100 on
32-bit (`IntPtr.Size == 4`); 64-bit keeps the original 1000.
- Remove the `[ActiveIssue(..., IsArm)]` suppression so the test runs on
32-bit ARM again.
```csharp
// 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 < threadCount; i++)
{
...
}
});
```
> [!NOTE]
> This PR description and the contained changes were generated with
assistance from GitHub Copilot.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: VSadov <8218165+VSadov@users.noreply.github.com>
---
src/tests/GC/API/GC/GetTotalAllocatedBytes.cs | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
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()
{
From 86eee16b708d4b83d0c45786b7edcf36e31cdc79 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Thu, 30 Apr 2026 18:50:19 +0100
Subject: [PATCH 03/27] Change unique stack trace GC flag from 0xF to 0x10
(#127612)
Updated the unique stack trace GC flag from 0xF to 0x10. Looks like this
was a typo, gcenv.h uses 16 / 0x10 for GCSTRESS_UNIQUE.
---
docs/design/coreclr/jit/investigate-stress.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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
From 4f111f8c99ae3fae4303fe87d231216955abeb94 Mon Sep 17 00:00:00 2001
From: Steve Molloy
Date: Thu, 30 Apr 2026 12:00:15 -0700
Subject: [PATCH 04/27] Implement XmlSerializer end element handling workaround
and tests (#126765)
This pull request improves handling of XML fragments over streaming
transports, particularly when deserializing multiple root elements. The
changes add a workaround (enabled by default) to avoid unnecessary reads
after a top-level end element, preventing timeouts or exceptions when
more data is not immediately available. An AppContext opt-out is also
included. The PR also adds comprehensive tests and supporting
infrastructure to validate and control this behavior.
**XML Serializer Compatibility and Behavior Improvements:**
* Added a new `UseXmlSerializerReadEndElementWorkaround` switch
(default: true) to `LocalAppContextSwitches` to control workaround
behavior for reading end elements in XML fragments.
* Updated `XmlSerializationReader.ReadEndElement()` to respect the new
switch, avoiding extra reads after a top-level end element when enabled,
which prevents blocking or timeouts in streaming/fragment scenarios.
* Introduced `AdvancePastTopLevelEndElementIfNeeded(XmlReader)` utility
and invoked it in `XmlSerializer` methods (`GetMapping`,
`CanDeserialize`) to ensure correct reader advancement when the
workaround is enabled.
[[1]](diffhunk://#diff-fa07acc29a1aed946fd7f4fca4b198bc71c7bb4204019cd407b9447882af374bR489-R490)
[[2]](diffhunk://#diff-fa07acc29a1aed946fd7f4fca4b198bc71c7bb4204019cd407b9447882af374bR547-R548)
[[3]](diffhunk://#diff-fa07acc29a1aed946fd7f4fca4b198bc71c7bb4204019cd407b9447882af374bR846-R855)
**Testing and Infrastructure Enhancements:**
* Added new tests to `XmlSerializerTests.cs` to verify deserialization
of XML fragments with and without the workaround, including a test that
disables the switch and expects a timeout exception.
* Implemented `BlockingAfterBufferStream`, a custom `Stream` that
simulates blocking behavior after buffer exhaustion, to facilitate
robust testing of streaming scenarios.
* Improved `XmlSerializerAppContextSwitchScope` to properly unset
AppContext switches and clear cached values, ensuring test isolation and
reliability.
Fixes: #47371
---
.../Xml/Core/LocalAppContextSwitches.cs | 10 ++
.../Serialization/XmlSerializationReader.cs | 20 +++-
.../System/Xml/Serialization/XmlSerializer.cs | 14 +++
.../tests/XmlSerializer/XmlSerializerTests.cs | 109 +++++++++++++++++-
4 files changed, 149 insertions(+), 4 deletions(-)
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");
From 0fa45b37b1c505faf0d02a6ad175f16ab10e4795 Mon Sep 17 00:00:00 2001
From: Jan Kotas
Date: Thu, 30 Apr 2026 12:05:59 -0700
Subject: [PATCH 05/27] Improve GCHeap::Promote debug validation (#127595)
- Mirrors the fix from #119403 in x86 GCInfo decoder
- Improve GCHeap::Promote debug validation to repro this class of issues
deterministically
Fixes #127581
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
---
src/coreclr/gc/background.cpp | 22 +++++++++++++++++-----
src/coreclr/gc/interface.cpp | 16 +++++++++++-----
src/coreclr/vm/gc_unwind_x86.inl | 8 ++++++--
3 files changed, 34 insertions(+), 12 deletions(-)
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/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
From 550500a978b784658a04110d49b3335dcacf33e0 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 30 Apr 2026 16:04:28 -0400
Subject: [PATCH 06/27] [cDAC] Use locally-built crossgen2 for dump test
debuggees (#127561)
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../cdac/prepare-cdac-helix-steps.yml | 2 ++
.../Debuggees/Directory.Build.targets | 16 ++++++++++++++
.../cdac/tests/DumpTests/DumpTests.targets | 22 +++++++++++++++++--
3 files changed, 38 insertions(+), 2 deletions(-)
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/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'))
-
From c77572bbcecf702b829460784fdfc1e02dd81592 Mon Sep 17 00:00:00 2001
From: David Wrighton
Date: Thu, 30 Apr 2026 13:58:18 -0700
Subject: [PATCH 07/27] Fix Wasm ArgIterator SizeOfArgStack for methods with
only hidden args (#127532)
The ForceSigWalk method had two bugs in its Wasm-specific path for
accounting for hidden arguments (this, retbuf, generic context, etc.)
when no named arguments are present:
1. The check 'maxOffset == 0' could never be true because maxOffset is
initialized to OffsetOfArgs (8 on Wasm32). Changed to compare against
OffsetOfArgs.
2. The fallback 'maxOffset = _wasmOfsStack' was incorrect because
_wasmOfsStack is relative to OffsetOfArgs, but maxOffset is an absolute
offset. Changed to 'OffsetOfArgs + _wasmOfsStack'.
These bugs caused GCRefMapBuilder to allocate a zero-length fake stack
for methods with only unnamed arguments (e.g. parameterless instance
methods), leading to IndexOutOfRangeException when writing the 'this'
pointer GC ref at ThisOffset.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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
From 3dd3c881ad4c5803876fa95555a2baf3ff3780cf Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Apr 2026 21:31:11 +0000
Subject: [PATCH 08/27] Remove corert#2785 BadImageFormatException workaround
from ILCompiler (#127591)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Removes the long-standing hack in `ILCompiler/Program.cs` that silently
swallowed `BadImageFormatException` when loading input files —
originally added to tolerate native DLLs mixed into CoreCLR test trees
(corert#2785). That scenario no longer applies.
---
src/coreclr/tools/aot/ILCompiler/Program.cs | 25 +--------------------
1 file changed, 1 insertion(+), 24 deletions(-)
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))
From e971c0ad9289befbd7e4ebf2af4186b96f3bada8 Mon Sep 17 00:00:00 2001
From: Kevin Jones
Date: Thu, 30 Apr 2026 14:42:58 -0700
Subject: [PATCH 09/27] Fix ChaCha20Poly1305 IsSupported test on Azure Linux 4
---
.../tests/TestUtilities/System/PlatformDetection.Unix.cs | 1 +
.../tests/ChaCha20Poly1305Tests.cs | 5 +++--
2 files changed, 4 insertions(+), 2 deletions(-)
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.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)
{
From 9ef2b5e33807f72fecf426e44ca1f06efd25723b Mon Sep 17 00:00:00 2001
From: Barbara Rosiak <76071368+barosiak@users.noreply.github.com>
Date: Thu, 30 Apr 2026 16:55:15 -0700
Subject: [PATCH 10/27] [cDAC] Implement GetModuleSimpleName for cDAC (#127415)
## Summary
Implement `GetModuleSimpleName` on `DacDbiImpl` using the `ILoader`
contract, replacing legacy-only delegation.
## Changes
- `DacDbiImpl.cs` - Implement `GetModuleSimpleName` via
`ILoader.GetSimpleName`
- `DacDbiLoaderDumpTests.cs` - Add dump test verifying all modules
return `S_OK` with a non-empty simple name
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rcj1
---
docs/design/datacontracts/Loader.md | 9 ++---
.../Contracts/ILoader.cs | 2 +-
.../Contracts/Loader_1.cs | 13 ++-----
.../ClrDataModule.cs | 3 +-
.../Dbi/DacDbiImpl.cs | 31 +++++++++++++++-
.../NativeStringHolder.cs | 4 +-
.../DumpTests/DacDbi/DacDbiLoaderDumpTests.cs | 28 +++++++++++++-
src/native/managed/cdac/tests/LoaderTests.cs | 37 +++++++++++--------
8 files changed, 88 insertions(+), 39 deletions(-)
rename src/native/managed/cdac/{tests/DumpTests/DacDbi => Microsoft.Diagnostics.DataContractReader.Legacy}/NativeStringHolder.cs (95%)
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/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.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.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/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/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()
From 013ca46b740c861d66c3f783ada239c669f83c34 Mon Sep 17 00:00:00 2001
From: Tanner Gooding
Date: Thu, 30 Apr 2026 17:25:41 -0700
Subject: [PATCH 11/27] Split the xarch specific data from register.h into its
own file to match other platforms (#127528)
Just improving consistency here.
---
src/coreclr/jit/CMakeLists.txt | 19 +-
src/coreclr/jit/register.h | 374 +-------------------------
src/coreclr/jit/registeramd64.h | 288 ++++++++++++++++++++
src/coreclr/jit/registerarm.h | 36 +--
src/coreclr/jit/registerarm64.h | 10 +-
src/coreclr/jit/registerloongarch64.h | 2 +-
src/coreclr/jit/registerriscv64.h | 4 +-
src/coreclr/jit/registerwasm.h | 23 +-
src/coreclr/jit/registerx86.h | 140 ++++++++++
9 files changed, 503 insertions(+), 393 deletions(-)
create mode 100644 src/coreclr/jit/registeramd64.h
create mode 100644 src/coreclr/jit/registerx86.h
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/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
From 84e5e272bf2c09d7a683025429b9d0db7a2ac579 Mon Sep 17 00:00:00 2001
From: Andy Ayers
Date: Thu, 30 Apr 2026 19:02:02 -0700
Subject: [PATCH 12/27] [Wasm RyuJit] Use local's register type when loading
it's value (#127619)
Fixes an assert loading some register-sized struct locals.
---
src/coreclr/jit/codegenwasm.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
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.
From 0e1b3d90831f6770d98ad8e1c00c15bfd39d1583 Mon Sep 17 00:00:00 2001
From: Rich Lander <2608468+richlander@users.noreply.github.com>
Date: Thu, 30 Apr 2026 20:54:56 -0700
Subject: [PATCH 13/27] Change commit message topic to 'container image
digests' (#127614)
"dependencies" is confusing or misleading. That's the default renovate
terminology. "digests" is much clearer to anyone that is in a position
to merge the generated PRs.
PR examples:
https://github.com/dotnet/runtime/pulls?q=is%3Apr+author%3Adotnet-renovate-bot+
---
eng/renovate.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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"
}
]
}
From 6e51115e318cfce2984cc4d58ba6f7953297dcba Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 1 May 2026 00:24:33 -0400
Subject: [PATCH 14/27] [cDAC] Stack walk GC reference scanning and bug fixes
(1/5) (#127395)
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](https://github.com/dotnet/runtime/pull/126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
docs/design/datacontracts/ExecutionManager.md | 21 +
docs/design/datacontracts/GCInfo.md | 270 +++++++++-
.../design/datacontracts/RuntimeTypeSystem.md | 60 +++
docs/design/datacontracts/StackWalk.md | 106 +++-
.../vm/datadescriptor/datadescriptor.inc | 32 +-
src/coreclr/vm/frames.h | 17 +
src/coreclr/vm/readytoruninfo.h | 2 +
.../Contracts/IExecutionManager.cs | 1 +
.../Contracts/IGCInfo.cs | 39 ++
.../Contracts/IRuntimeTypeSystem.cs | 8 +
.../DataType.cs | 3 +
.../ExecutionManager/ExecutionManagerCore.cs | 13 +
.../ExecutionManager/ExecutionManager_1.cs | 1 +
.../ExecutionManager/ExecutionManager_2.cs | 1 +
.../Contracts/GCInfo/GCInfoDecoder.cs | 77 ++-
.../Contracts/GCInfo/GCInfo_1.cs | 19 +
.../Contracts/GCInfo/IGCInfoDecoder.cs | 33 +-
.../Contracts/RuntimeTypeSystem_1.cs | 64 +++
.../FrameHandling/ARMFrameHandler.cs | 7 +-
.../StackWalk/FrameHandling/FrameIterator.cs | 101 +++-
.../Contracts/StackWalk/GC/GCRefMapDecoder.cs | 123 +++++
.../Contracts/StackWalk/GC/GcScanner.cs | 500 ++++++++++++++++--
.../StackWalk/GC/GcSignatureTypeProvider.cs | 64 +++
.../Contracts/StackWalk/StackWalk_1.cs | 266 +++++++---
.../Data/ExceptionInfo.cs | 4 +
.../Data/Frames/DynamicHelperFrame.cs | 18 +
.../Data/Frames/ExternalMethodFrame.cs | 18 +
.../Data/Frames/StubDispatchFrame.cs | 2 +
.../Data/Frames/TransitionBlock.cs | 16 +-
.../Data/ReadyToRunInfo.cs | 7 +
.../MethodDescFlags_1.cs | 3 +-
.../SOSDacImpl.cs | 70 ++-
...iagnostics.DataContractReader.Tests.csproj | 2 +-
.../MockDescriptors.ExecutionManager.cs | 5 +
.../MockDescriptors/MockDescriptors.Thread.cs | 4 +
35 files changed, 1705 insertions(+), 272 deletions(-)
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.cs
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.cs
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.cs
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/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/StackWalk.md b/docs/design/datacontracts/StackWalk.md
index 9d254591ddb0ac..35c8a33e00c2c7 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,78 @@ 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 details.
+
+### 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/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc
index 40a9397767d289..ff8738e9860743 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
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/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/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/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/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/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/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..d050adb8617be2 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,17 @@
// 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;
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 +26,460 @@ 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)
+ {
+ 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
{
- 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;
+ 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}"),
+ };
- if (isRegister)
+ 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;
+
+ 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;
+
+ ReadOnlySpan signature;
+ try
+ {
+ signature = GetMethodSignatureBytes(methodDescPtr);
+ }
+ catch (System.Exception)
+ {
+ return;
+ }
+
+ if (signature.IsEmpty)
+ return;
+
+ MethodSignature methodSig;
+ try
+ {
+ unsafe
+ {
+ fixed (byte* pSig = signature)
{
- int spReg = context.StackPointerRegister;
- int reg = spBase switch
- {
- 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(pSig, signature.Length);
+ SignatureDecoder decoder = new(
+ GcSignatureTypeProvider.Instance, metadataReader: null!, genericContext: null);
+ methodSig = decoder.DecodeMethodSignature(ref blobReader);
}
- });
+ }
+ }
+ catch (System.Exception)
+ {
+ return;
+ }
+
+ if (methodSig.Header.CallingConvention is SignatureCallingConvention.VarArgs)
+ return;
+
+ IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
+ MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr);
+
+ 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 ReadOnlySpan GetMethodSignatureBytes(TargetPointer methodDescPtr)
+ {
+ IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
+ MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr);
+
+ if (rts.IsStoredSigMethodDesc(mdh, out ReadOnlySpan storedSig))
+ return storedSig;
+
+ uint methodToken = rts.GetMethodToken(mdh);
+ if (methodToken == 0x06000000)
+ return default;
+
+ TargetPointer methodTablePtr = rts.GetMethodTable(mdh);
+ TypeHandle typeHandle = rts.GetTypeHandle(methodTablePtr);
+ TargetPointer modulePtr = rts.GetModule(typeHandle);
+
+ ILoader loader = _target.Contracts.Loader;
+ ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
+
+ IEcmaMetadata ecmaMetadata = _target.Contracts.EcmaMetadata;
+ MetadataReader? mdReader = ecmaMetadata.GetMetadata(moduleHandle);
+ if (mdReader is null)
+ return default;
+
+ MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)(methodToken & 0x00FFFFFF));
+ MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle);
+ BlobReader blobReader = mdReader.GetBlobReader(methodDef.Signature);
+ return blobReader.ReadBytes(blobReader.Length);
+ }
+
+ 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 +490,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..46e6c8af2de24c
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.Reflection.Metadata;
+
+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,
+}
+
+///
+/// Classifies signature types for GC scanning purposes.
+/// Implements for use
+/// with SRM's .
+///
+internal sealed class GcSignatureTypeProvider
+ : ISignatureTypeProvider
+{
+ public static readonly GcSignatureTypeProvider Instance = new();
+
+ 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)
+ => rawTypeKind == (byte)SignatureTypeKind.ValueType ? GcTypeKind.Other : GcTypeKind.Ref;
+
+ public GcTypeKind GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
+ => rawTypeKind == (byte)SignatureTypeKind.ValueType ? GcTypeKind.Other : GcTypeKind.Ref;
+
+ public GcTypeKind GetTypeFromSpecification(MetadataReader reader, object? 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(object? genericContext, int index) => GcTypeKind.Ref;
+ public GcTypeKind GetGenericTypeParameter(object? genericContext, int index) => GcTypeKind.Ref;
+ public GcTypeKind GetFunctionPointerType(MethodSignature signature) => GcTypeKind.None;
+ public GcTypeKind GetModifiedType(GcTypeKind modifier, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType;
+ public GcTypeKind GetPinnedType(GcTypeKind elementType) => elementType;
+}
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/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/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs
index d67adff3ab3489..8a909f25f5ebc7 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs
@@ -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/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
From 6083571fa32a3d3d50aedc063ded73ee33adaec7 Mon Sep 17 00:00:00 2001
From: Max Charlamb
Date: Thu, 30 Apr 2026 22:11:22 -0400
Subject: [PATCH 15/27] WIP: RuntimeSignatureDecoder, centralized
SignatureDecoder contract
- Move RuntimeSignatureDecoder to Contracts/Signature/
- Move GcSignatureTypeProvider to Contracts/Signature/ with module-scoped caching
- Add DecodeMethodSignatureForGC(BlobHandle, ModuleHandle) to ISignatureDecoder
- Add DecodeFieldSignature to RuntimeSignatureDecoder
- Add BlobHandleSignatureReader for lazy blob reading
- SignatureTypeProvider implements IRuntimeSignatureTypeProvider
- Switch DecodeFieldSignature to use RuntimeSignatureDecoder
- GcScanner uses contract API instead of direct decoder construction
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Contracts/ISignatureDecoder.cs | 23 ++
.../GcSignatureTypeProvider.cs | 70 +++-
.../Signature/RuntimeSignatureDecoder.cs | 370 ++++++++++++++++++
.../Contracts/Signature/SignatureDecoder_1.cs | 32 +-
.../Contracts/Signature/SignatureReaders.cs | 109 ++++++
.../Signature/SignatureTypeProvider.cs | 10 +-
.../Contracts/StackWalk/GC/GcScanner.cs | 74 +---
7 files changed, 609 insertions(+), 79 deletions(-)
rename src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/{StackWalk/GC => Signature}/GcSignatureTypeProvider.cs (52%)
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/RuntimeSignatureDecoder.cs
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureReaders.cs
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs
index 5977d736b74d43..492c9ce51b1b5a 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs
@@ -6,10 +6,33 @@
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
+///
+/// Classification of a signature type for GC scanning purposes.
+///
+public 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,
+}
+
public interface ISignatureDecoder : IContract
{
static string IContract.Name { get; } = nameof(SignatureDecoder);
+
TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) => throw new NotImplementedException();
+
+ ///
+ /// Decodes a method's signature for GC scanning purposes, classifying each parameter
+ /// as a GC reference, interior pointer, value type, or non-GC type.
+ /// Handles ELEMENT_TYPE_INTERNAL via the runtime type system.
+ ///
+ MethodSignature DecodeMethodSignatureForGC(BlobHandle blobHandle, ModuleHandle moduleHandle) => throw new NotImplementedException();
}
public readonly struct SignatureDecoder : ISignatureDecoder
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/Signature/GcSignatureTypeProvider.cs
similarity index 52%
rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs
rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/GcSignatureTypeProvider.cs
index 46e6c8af2de24c..7efafe8f8b1156 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/GcSignatureTypeProvider.cs
@@ -3,33 +3,27 @@
using System.Collections.Immutable;
using System.Reflection.Metadata;
+using Microsoft.Diagnostics.DataContractReader.Contracts;
-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,
-}
+namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers;
///
/// Classifies signature types for GC scanning purposes.
-/// Implements for use
-/// with SRM's .
+/// Implements which
+/// is a superset of SRM's ,
+/// adding support for ELEMENT_TYPE_INTERNAL.
///
internal sealed class GcSignatureTypeProvider
- : ISignatureTypeProvider
+ : IRuntimeSignatureTypeProvider
{
- public static readonly GcSignatureTypeProvider Instance = new();
+ 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
@@ -60,5 +54,41 @@ public GcTypeKind GetGenericInstantiation(GcTypeKind genericType, ImmutableArray
public GcTypeKind GetGenericTypeParameter(object? genericContext, int index) => GcTypeKind.Ref;
public GcTypeKind GetFunctionPointerType(MethodSignature signature) => GcTypeKind.None;
public GcTypeKind GetModifiedType(GcTypeKind modifier, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType;
+ public GcTypeKind GetInternalModifiedType(Target target, TargetPointer typeHandlePointer, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType;
public GcTypeKind GetPinnedType(GcTypeKind elementType) => elementType;
+
+ public GcTypeKind GetInternalType(Target target, TargetPointer typeHandlePointer)
+ {
+ if (typeHandlePointer == TargetPointer.Null)
+ return GcTypeKind.None;
+
+ try
+ {
+ IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;
+ TypeHandle th = rts.GetTypeHandle(typeHandlePointer);
+ CorElementType corType = rts.GetSignatureCorElementType(th);
+
+ return corType switch
+ {
+ CorElementType.Void or CorElementType.Boolean or CorElementType.Char
+ or CorElementType.I1 or CorElementType.U1
+ or CorElementType.I2 or CorElementType.U2
+ or CorElementType.I4 or CorElementType.U4
+ or CorElementType.I8 or CorElementType.U8
+ or CorElementType.R4 or CorElementType.R8
+ or CorElementType.I or CorElementType.U
+ or CorElementType.FnPtr or CorElementType.Ptr
+ => GcTypeKind.None,
+
+ CorElementType.Byref => GcTypeKind.Interior,
+ CorElementType.ValueType => GcTypeKind.Other,
+
+ _ => GcTypeKind.Ref,
+ };
+ }
+ catch
+ {
+ return GcTypeKind.Ref;
+ }
+ }
}
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..caa07f8dcd63ed
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/RuntimeSignatureDecoder.cs
@@ -0,0 +1,370 @@
+// 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;
+
+///
+/// Superset of SRM's
+/// that adds support for runtime-internal type codes (ELEMENT_TYPE_INTERNAL).
+///
+///
+/// Providers implementing this interface automatically satisfy SRM's
+/// and can be used
+/// with both SRM's SignatureDecoder and our
+/// .
+///
+internal 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(Target target, 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(Target target, TargetPointer typeHandlePointer, TType unmodifiedType, bool isRequired);
+}
+
+///
+/// Decodes method and local variable signatures, handling both standard ECMA-335
+/// types and runtime-internal types like ELEMENT_TYPE_INTERNAL (0x21).
+///
+///
+///
+/// Handles the same ECMA-335 type codes as SRM's
+/// , plus runtime-internal
+/// types (ELEMENT_TYPE_INTERNAL 0x21 and ELEMENT_TYPE_CMOD_INTERNAL 0x22).
+///
+///
+/// Internal custom modifiers (ELEMENT_TYPE_CMOD_INTERNAL) are skipped since
+/// they carry runtime TypeHandle pointers that are not meaningful for type classification.
+/// Standard custom modifiers (modreq/modopt) are decoded and dispatched
+/// to .
+///
+///
+internal ref struct RuntimeSignatureDecoder
+ where TReader : ISignatureReader, allows ref struct
+{
+ private const byte ELEMENT_TYPE_PTR = 0x0f;
+ private const byte ELEMENT_TYPE_BYREF = 0x10;
+ private const byte ELEMENT_TYPE_VALUETYPE = 0x11;
+ private const byte ELEMENT_TYPE_CLASS = 0x12;
+ private const byte ELEMENT_TYPE_VAR = 0x13;
+ private const byte ELEMENT_TYPE_ARRAY = 0x14;
+ private const byte ELEMENT_TYPE_GENERICINST = 0x15;
+ private const byte ELEMENT_TYPE_FNPTR = 0x1b;
+ private const byte ELEMENT_TYPE_SZARRAY = 0x1d;
+ private const byte ELEMENT_TYPE_MVAR = 0x1e;
+ private const byte ELEMENT_TYPE_CMOD_REQD = 0x1f;
+ private const byte ELEMENT_TYPE_CMOD_OPT = 0x20;
+ private const byte ELEMENT_TYPE_INTERNAL = 0x21;
+ private const byte ELEMENT_TYPE_CMOD_INTERNAL = 0x22;
+ private const byte ELEMENT_TYPE_SENTINEL = 0x41;
+ private const byte ELEMENT_TYPE_PINNED = 0x45;
+
+ private readonly IRuntimeSignatureTypeProvider _provider;
+ private readonly MetadataReader? _metadataReader;
+ private readonly Target _target;
+ private readonly TGenericContext _genericContext;
+ private TReader _reader;
+
+ public RuntimeSignatureDecoder(
+ IRuntimeSignatureTypeProvider provider,
+ Target target,
+ TGenericContext genericContext,
+ TReader reader,
+ MetadataReader? metadataReader = null)
+ {
+ _provider = provider;
+ _metadataReader = metadataReader;
+ _target = target;
+ _genericContext = genericContext;
+ _reader = reader;
+ }
+
+ /// Decodes a field signature (FieldSig).
+ public TType DecodeFieldSignature()
+ {
+ byte rawHeader = _reader.ReadByte();
+ SignatureHeader header = new(rawHeader);
+
+ if (header.Kind is not SignatureKind.Field)
+ throw new BadImageFormatException($"Expected field signature header, got: {header.Kind}");
+
+ return DecodeType();
+ }
+
+ /// Decodes a method signature (MethodDefSig/MethodRefSig).
+ public MethodSignature DecodeMethodSignature()
+ {
+ byte rawHeader = _reader.ReadByte();
+ SignatureHeader header = new(rawHeader);
+
+ if (header.Kind is not SignatureKind.Method and not SignatureKind.Property)
+ throw new BadImageFormatException($"Unexpected signature header kind: {header.Kind}");
+
+ int genericParameterCount = 0;
+ if (header.IsGeneric)
+ genericParameterCount = ReadCompressedUInt();
+
+ int parameterCount = ReadCompressedUInt();
+ if (parameterCount > _reader.Remaining)
+ throw new BadImageFormatException($"Parameter count {parameterCount} exceeds remaining signature bytes");
+ TType returnType = DecodeType();
+
+ var parameterTypes = ImmutableArray.CreateBuilder(parameterCount);
+ int requiredParameterCount = parameterCount;
+ bool sentinelSeen = false;
+
+ for (int i = 0; i < parameterCount; i++)
+ {
+ if (_reader.Remaining > 0 && _reader.PeekByte() == ELEMENT_TYPE_SENTINEL)
+ {
+ if (sentinelSeen)
+ throw new BadImageFormatException("Multiple sentinels in method signature");
+ sentinelSeen = true;
+ requiredParameterCount = i;
+ _reader.ReadByte();
+ }
+ parameterTypes.Add(DecodeType());
+ }
+
+ return new MethodSignature(
+ header, returnType, requiredParameterCount, genericParameterCount,
+ parameterTypes.MoveToImmutable());
+ }
+
+ /// Decodes a local variable signature (LocalVarSig).
+ public ImmutableArray DecodeLocalSignature()
+ {
+ byte header = _reader.ReadByte();
+ if (header != 0x07) // IMAGE_CEE_CS_CALLCONV_LOCAL_SIG
+ throw new BadImageFormatException($"Expected LocalVarSig header (0x07), got 0x{header:X2}");
+
+ int count = ReadCompressedUInt();
+ if (count == 0)
+ throw new BadImageFormatException("Local variable signature must have at least one entry");
+ if (count > _reader.Remaining)
+ throw new BadImageFormatException($"Local count {count} exceeds remaining signature bytes");
+ var locals = ImmutableArray.CreateBuilder(count);
+ for (int i = 0; i < count; i++)
+ locals.Add(DecodeType());
+ return locals.MoveToImmutable();
+ }
+
+ /// Decodes a single type embedded in a signature.
+ public TType DecodeType()
+ {
+ // Handle custom modifiers (standard and internal)
+ while (_reader.Remaining > 0)
+ {
+ byte peek = _reader.PeekByte();
+ if (peek is ELEMENT_TYPE_CMOD_REQD or ELEMENT_TYPE_CMOD_OPT)
+ {
+ bool isRequired = peek == ELEMENT_TYPE_CMOD_REQD;
+ _reader.ReadByte();
+ TType modifier = DecodeTypeDefOrRefOrSpec(0);
+ TType unmodifiedType = DecodeType();
+ return _provider.GetModifiedType(modifier, unmodifiedType, isRequired);
+ }
+ else if (peek == ELEMENT_TYPE_CMOD_INTERNAL)
+ {
+ _reader.ReadByte();
+ bool isRequired = _reader.ReadByte() != 0;
+ ulong val = _reader.ReadPointerSized(_target.PointerSize);
+ TType unmodifiedType = DecodeType();
+ return _provider.GetInternalModifiedType(
+ _target, new TargetPointer(val), unmodifiedType, isRequired);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ byte typeCode = _reader.ReadByte();
+
+ switch (typeCode)
+ {
+ case (byte)SignatureTypeCode.Boolean:
+ case (byte)SignatureTypeCode.Char:
+ case (byte)SignatureTypeCode.SByte:
+ case (byte)SignatureTypeCode.Byte:
+ case (byte)SignatureTypeCode.Int16:
+ case (byte)SignatureTypeCode.UInt16:
+ case (byte)SignatureTypeCode.Int32:
+ case (byte)SignatureTypeCode.UInt32:
+ case (byte)SignatureTypeCode.Int64:
+ case (byte)SignatureTypeCode.UInt64:
+ case (byte)SignatureTypeCode.Single:
+ case (byte)SignatureTypeCode.Double:
+ case (byte)SignatureTypeCode.IntPtr:
+ case (byte)SignatureTypeCode.UIntPtr:
+ case (byte)SignatureTypeCode.Object:
+ case (byte)SignatureTypeCode.String:
+ case (byte)SignatureTypeCode.Void:
+ case (byte)SignatureTypeCode.TypedReference:
+ return _provider.GetPrimitiveType((PrimitiveTypeCode)typeCode);
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ return DecodeTypeDefOrRefOrSpec(typeCode);
+
+ case ELEMENT_TYPE_PTR:
+ return _provider.GetPointerType(DecodeType());
+
+ case ELEMENT_TYPE_BYREF:
+ return _provider.GetByReferenceType(DecodeType());
+
+ case ELEMENT_TYPE_SZARRAY:
+ return _provider.GetSZArrayType(DecodeType());
+
+ case ELEMENT_TYPE_ARRAY:
+ {
+ TType elementType = DecodeType();
+ ArrayShape shape = DecodeArrayShape();
+ return _provider.GetArrayType(elementType, shape);
+ }
+
+ case ELEMENT_TYPE_GENERICINST:
+ {
+ TType baseType = DecodeType();
+ int count = ReadCompressedUInt();
+ if (count == 0)
+ throw new BadImageFormatException("Generic instantiation must have at least one type argument");
+ if (count > _reader.Remaining)
+ throw new BadImageFormatException($"Generic argument count {count} exceeds remaining signature bytes");
+ var args = ImmutableArray.CreateBuilder(count);
+ for (int i = 0; i < count; i++)
+ args.Add(DecodeType());
+ return _provider.GetGenericInstantiation(baseType, args.MoveToImmutable());
+ }
+
+ case ELEMENT_TYPE_VAR:
+ return _provider.GetGenericTypeParameter(_genericContext, ReadCompressedUInt());
+
+ case ELEMENT_TYPE_MVAR:
+ return _provider.GetGenericMethodParameter(_genericContext, ReadCompressedUInt());
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ MethodSignature fnSig = DecodeMethodSignature();
+ return _provider.GetFunctionPointerType(fnSig);
+ }
+
+ case ELEMENT_TYPE_PINNED:
+ return _provider.GetPinnedType(DecodeType());
+
+ case ELEMENT_TYPE_INTERNAL:
+ {
+ ulong val = _reader.ReadPointerSized(_target.PointerSize);
+ return _provider.GetInternalType(_target, new TargetPointer(val));
+ }
+
+ default:
+ throw new BadImageFormatException($"Unexpected signature type code: 0x{typeCode:X2}");
+ }
+ }
+
+ ///
+ /// Decodes a TypeDefOrRefOrSpecEncoded token (ECMA-335 II.23.2.8).
+ /// The compressed value encodes tag in the low 2 bits and RID in the upper bits.
+ ///
+ private TType DecodeTypeDefOrRefOrSpec(byte rawTypeKind)
+ {
+ int coded = ReadCompressedUInt();
+ int tag = coded & 0x3;
+ int rid = coded >> 2;
+
+ if (rid == 0)
+ throw new BadImageFormatException("Nil TypeDefOrRefOrSpecEncoded handle in signature");
+
+ if (rid > 0x00FFFFFF)
+ throw new BadImageFormatException($"TypeDefOrRefOrSpecEncoded RID out of range: {rid}");
+
+ return tag switch
+ {
+ 0 => _provider.GetTypeFromDefinition(_metadataReader!, MetadataTokens.TypeDefinitionHandle(rid), rawTypeKind),
+ 1 => _provider.GetTypeFromReference(_metadataReader!, MetadataTokens.TypeReferenceHandle(rid), rawTypeKind),
+ 2 => _provider.GetTypeFromSpecification(_metadataReader!, _genericContext, MetadataTokens.TypeSpecificationHandle(rid), rawTypeKind),
+ _ => _provider.GetPrimitiveType(PrimitiveTypeCode.Object), // tag=3 is BaseType in native
+ };
+ }
+
+ private ArrayShape DecodeArrayShape()
+ {
+ int rank = ReadCompressedUInt();
+ int numSizes = ReadCompressedUInt();
+ if (numSizes > _reader.Remaining)
+ throw new BadImageFormatException($"Array size count {numSizes} exceeds remaining signature bytes");
+ var sizes = ImmutableArray.CreateBuilder(numSizes);
+ for (int i = 0; i < numSizes; i++)
+ sizes.Add(ReadCompressedUInt());
+ int numLoBounds = ReadCompressedUInt();
+ if (numLoBounds > _reader.Remaining)
+ throw new BadImageFormatException($"Array lower bound count {numLoBounds} exceeds remaining signature bytes");
+ var loBounds = ImmutableArray.CreateBuilder(numLoBounds);
+ for (int i = 0; i < numLoBounds; i++)
+ loBounds.Add(ReadCompressedSignedInt());
+ return new ArrayShape(rank, sizes.MoveToImmutable(), loBounds.MoveToImmutable());
+ }
+
+ ///
+ /// Reads a compressed unsigned integer per ECMA-335 II.23.2.
+ ///
+ private int ReadCompressedUInt()
+ {
+ byte first = _reader.ReadByte();
+ if ((first & 0x80) == 0)
+ return first;
+ if ((first & 0xC0) == 0x80)
+ return ((first & 0x3F) << 8) | _reader.ReadByte();
+ if ((first & 0xE0) == 0xC0)
+ return ((first & 0x1F) << 24) | (_reader.ReadByte() << 16) | (_reader.ReadByte() << 8) | _reader.ReadByte();
+
+ throw new BadImageFormatException("Invalid compressed integer encoding");
+ }
+
+ ///
+ /// Reads a compressed signed integer per ECMA-335 II.23.2.
+ /// Uses sign extension based on encoded width, matching SRM's BlobReader.ReadCompressedSignedInteger.
+ ///
+ private int ReadCompressedSignedInt()
+ {
+ byte first = _reader.ReadByte();
+
+ if ((first & 0x80) == 0)
+ {
+ // 1-byte: 7 bits, sign bit is bit 0 of the encoded value
+ int value = first >> 1;
+ return (first & 1) != 0 ? value - 0x40 : value;
+ }
+
+ if ((first & 0xC0) == 0x80)
+ {
+ // 2-byte: 14 bits
+ int raw = ((first & 0x3F) << 8) | _reader.ReadByte();
+ int value = raw >> 1;
+ return (raw & 1) != 0 ? value - 0x2000 : value;
+ }
+
+ if ((first & 0xE0) == 0xC0)
+ {
+ // 4-byte: 29 bits
+ int raw = ((first & 0x1F) << 24) | (_reader.ReadByte() << 16) | (_reader.ReadByte() << 8) | _reader.ReadByte();
+ int value = raw >> 1;
+ return (raw & 1) != 0 ? value - 0x10000000 : value;
+ }
+
+ throw new BadImageFormatException("Invalid compressed signed integer encoding");
+ }
+}
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/SignatureDecoder_1.cs
index adfdbeca340134..641401ff69628b 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/SignatureDecoder_1.cs
@@ -22,6 +22,7 @@ internal sealed class SignatureDecoder_1 : ISignatureDecoder
private readonly Target _target;
private readonly Dictionary> _thProviders = [];
private readonly Dictionary> _mdhProviders = [];
+ private readonly Dictionary _gcProviders = [];
internal SignatureDecoder_1(Target target)
{
@@ -32,6 +33,7 @@ public void Flush()
{
_thProviders.Clear();
_mdhProviders.Clear();
+ _gcProviders.Clear();
}
private SignatureTypeProvider GetTypeHandleProvider(ModuleHandle moduleHandle)
@@ -57,12 +59,36 @@ private SignatureTypeProvider GetMethodDescHandleProvider(Modu
return newProvider;
}
+
+ private GcSignatureTypeProvider GetGcProvider(ModuleHandle moduleHandle)
+ {
+ if (_gcProviders.TryGetValue(moduleHandle, out GcSignatureTypeProvider? provider))
+ return provider;
+
+ GcSignatureTypeProvider newProvider = new(_target, moduleHandle);
+ _gcProviders[moduleHandle] = newProvider;
+ return newProvider;
+ }
+
TypeHandle ISignatureDecoder.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);
- return decoder.DecodeFieldSignature(ref blobReader);
+
+ RuntimeSignatureDecoder decoder = new(
+ provider, _target, ctx,
+ new BlobHandleSignatureReader(mdReader, blobHandle, _target.IsLittleEndian), mdReader);
+ return decoder.DecodeFieldSignature();
+ }
+
+ MethodSignature ISignatureDecoder.DecodeMethodSignatureForGC(BlobHandle blobHandle, ModuleHandle moduleHandle)
+ {
+ GcSignatureTypeProvider provider = GetGcProvider(moduleHandle);
+ MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!;
+
+ RuntimeSignatureDecoder decoder = new(
+ provider, _target, genericContext: null,
+ new BlobHandleSignatureReader(mdReader, blobHandle, _target.IsLittleEndian), mdReader);
+ return decoder.DecodeMethodSignature();
}
}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureReaders.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureReaders.cs
new file mode 100644
index 00000000000000..06aeacfbf9693d
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureReaders.cs
@@ -0,0 +1,109 @@
+// 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.Buffers.Binary;
+using System.Reflection.Metadata;
+
+namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers;
+
+///
+/// Abstraction for reading bytes from a signature blob.
+///
+///
+/// Allows the decoder to read from different sources (in-memory spans,
+/// target process memory) without allocating intermediate byte arrays.
+///
+internal interface ISignatureReader
+{
+ byte ReadByte();
+ byte PeekByte();
+ int Remaining { get; }
+
+ /// Reads a pointer-sized unsigned value (4 or 8 bytes).
+ ulong ReadPointerSized(int pointerSize);
+}
+
+///
+/// Reads signature bytes from a .
+///
+internal ref struct SpanSignatureReader : ISignatureReader
+{
+ private readonly ReadOnlySpan _blob;
+ private readonly bool _isLittleEndian;
+ private int _offset;
+
+ public SpanSignatureReader(ReadOnlySpan blob, bool isLittleEndian = true)
+ {
+ _blob = blob;
+ _isLittleEndian = isLittleEndian;
+ _offset = 0;
+ }
+
+ public int Remaining => _blob.Length - _offset;
+
+ public byte ReadByte()
+ {
+ if (_offset >= _blob.Length)
+ throw new BadImageFormatException("Unexpected end of signature blob");
+ return _blob[_offset++];
+ }
+
+ public byte PeekByte()
+ {
+ if (_offset >= _blob.Length)
+ throw new BadImageFormatException("Unexpected end of signature blob");
+ return _blob[_offset];
+ }
+
+ public ulong ReadPointerSized(int pointerSize)
+ {
+ if (_offset + pointerSize > _blob.Length)
+ throw new BadImageFormatException("Unexpected end of signature blob");
+
+ ReadOnlySpan slice = _blob.Slice(_offset, pointerSize);
+ ulong val = pointerSize == 8
+ ? (_isLittleEndian ? BinaryPrimitives.ReadUInt64LittleEndian(slice) : BinaryPrimitives.ReadUInt64BigEndian(slice))
+ : (_isLittleEndian ? BinaryPrimitives.ReadUInt32LittleEndian(slice) : BinaryPrimitives.ReadUInt32BigEndian(slice));
+ _offset += pointerSize;
+ return val;
+ }
+}
+
+///
+/// Reads signature bytes lazily from a via a .
+///
+internal ref struct BlobHandleSignatureReader : ISignatureReader
+{
+ private BlobReader _blobReader;
+ private readonly bool _isLittleEndian;
+
+ public BlobHandleSignatureReader(MetadataReader metadataReader, BlobHandle blobHandle, bool isLittleEndian = true)
+ {
+ _blobReader = metadataReader.GetBlobReader(blobHandle);
+ _isLittleEndian = isLittleEndian;
+ }
+
+ public int Remaining => _blobReader.RemainingBytes;
+
+ public byte ReadByte() => _blobReader.ReadByte();
+
+ public byte PeekByte()
+ {
+ if (_blobReader.RemainingBytes == 0)
+ throw new BadImageFormatException("Unexpected end of signature blob");
+ byte value = _blobReader.ReadByte();
+ _blobReader.Offset--;
+ return value;
+ }
+
+ public ulong ReadPointerSized(int pointerSize)
+ {
+ if (_blobReader.RemainingBytes < pointerSize)
+ throw new BadImageFormatException("Unexpected end of signature blob");
+
+ return pointerSize == 8
+ ? (_isLittleEndian ? _blobReader.ReadUInt64() : BinaryPrimitives.ReverseEndianness(_blobReader.ReadUInt64()))
+ : (_isLittleEndian ? _blobReader.ReadUInt32() : BinaryPrimitives.ReverseEndianness(_blobReader.ReadUInt32()));
+ }
+}
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..701bc0c0cba2e7 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(Target target, TargetPointer typeHandlePointer)
+ => typeHandlePointer == TargetPointer.Null
+ ? new TypeHandle(TargetPointer.Null)
+ : _runtimeTypeSystem.GetTypeHandle(typeHandlePointer);
+
+ public TypeHandle GetInternalModifiedType(Target target, TargetPointer typeHandlePointer, TypeHandle unmodifiedType, bool isRequired)
+ => unmodifiedType;
}
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 d050adb8617be2..179228dad72d54 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
@@ -323,32 +323,29 @@ private void PromoteCallerStack(
if (methodDescPtr == TargetPointer.Null)
return;
- ReadOnlySpan signature;
- try
- {
- signature = GetMethodSignatureBytes(methodDescPtr);
- }
- catch (System.Exception)
- {
- return;
- }
-
- if (signature.IsEmpty)
- return;
+ IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
+ MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr);
MethodSignature methodSig;
try
{
- unsafe
- {
- fixed (byte* pSig = signature)
- {
- BlobReader blobReader = new(pSig, signature.Length);
- SignatureDecoder decoder = new(
- GcSignatureTypeProvider.Instance, metadataReader: null!, genericContext: null);
- methodSig = decoder.DecodeMethodSignature(ref blobReader);
- }
- }
+ uint methodToken = rts.GetMethodToken(mdh);
+ if (methodToken == 0x06000000)
+ return;
+
+ 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;
+
+ MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)(methodToken & 0x00FFFFFF));
+ MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle);
+
+ methodSig = _target.Contracts.SignatureDecoder.DecodeMethodSignatureForGC(methodDef.Signature, moduleHandle);
}
catch (System.Exception)
{
@@ -358,9 +355,6 @@ private void PromoteCallerStack(
if (methodSig.Header.CallingConvention is SignatureCallingConvention.VarArgs)
return;
- IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
- MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr);
-
bool hasThis = methodSig.Header.IsInstance;
bool hasRetBuf = methodSig.ReturnType is GcTypeKind.Other;
bool requiresInstArg = false;
@@ -440,36 +434,6 @@ private void PromoteCallerStackHelper(
}
}
- private ReadOnlySpan GetMethodSignatureBytes(TargetPointer methodDescPtr)
- {
- IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
- MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr);
-
- if (rts.IsStoredSigMethodDesc(mdh, out ReadOnlySpan storedSig))
- return storedSig;
-
- uint methodToken = rts.GetMethodToken(mdh);
- if (methodToken == 0x06000000)
- return default;
-
- TargetPointer methodTablePtr = rts.GetMethodTable(mdh);
- TypeHandle typeHandle = rts.GetTypeHandle(methodTablePtr);
- TargetPointer modulePtr = rts.GetModule(typeHandle);
-
- ILoader loader = _target.Contracts.Loader;
- ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
-
- IEcmaMetadata ecmaMetadata = _target.Contracts.EcmaMetadata;
- MetadataReader? mdReader = ecmaMetadata.GetMetadata(moduleHandle);
- if (mdReader is null)
- return default;
-
- MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)(methodToken & 0x00FFFFFF));
- MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle);
- BlobReader blobReader = mdReader.GetBlobReader(methodDef.Signature);
- return blobReader.ReadBytes(blobReader.Length);
- }
-
private TargetPointer AddressFromGCRefMapPos(Data.TransitionBlock tb, int pos)
{
return new TargetPointer(tb.FirstGCRefMapSlot.Value + (ulong)(pos * _target.PointerSize));
From c468ddead4cefc0a4ad07428e6d0bcd16f654104 Mon Sep 17 00:00:00 2001
From: Max Charlamb
Date: Fri, 1 May 2026 00:50:35 -0400
Subject: [PATCH 16/27] Align RuntimeSignatureDecoder with SRM and move GC
decoding to StackWalk
Rewrites RuntimeSignatureDecoder as a readonly struct that mirrors SRM's SignatureDecoder API (ref BlobReader per method, allowTypeSpecifications flag), with only ELEMENT_TYPE_INTERNAL (0x21) and CMOD_INTERNAL (0x22) added on top via the new IRuntimeSignatureTypeProvider interface. Drops the custom ISignatureReader/BlobHandleSignatureReader/SpanSignatureReader abstraction since BlobReader already provides lazy reading.
Fixes two latent bugs vs SRM: TypeDefOrRefOrSpec tag=3 now throws (was incorrectly returning Object), and the leading element type code is read as a compressed integer rather than a raw byte.
Moves GC-specific signature decoding out of the Signature contract into the StackWalk contract (Option B). GcSignatureTypeProvider and GcTypeKind move to Contracts/StackWalk/GC/ under the StackWalkHelpers namespace. ISignatureDecoder no longer exposes DecodeMethodSignatureForGC; the _gcProviders cache is removed from SignatureDecoder_1; GcScanner constructs RuntimeSignatureDecoder directly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Contracts/ISignatureDecoder.cs | 22 -
.../Signature/RuntimeSignatureDecoder.cs | 499 +++++++++---------
.../Contracts/Signature/SignatureDecoder_1.cs | 33 +-
.../Contracts/Signature/SignatureReaders.cs | 109 ----
.../Contracts/StackWalk/GC/GcScanner.cs | 7 +-
.../GC}/GcSignatureTypeProvider.cs | 19 +-
6 files changed, 269 insertions(+), 420 deletions(-)
delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureReaders.cs
rename src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/{Signature => StackWalk/GC}/GcSignatureTypeProvider.cs (88%)
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs
index 492c9ce51b1b5a..a6db1722245069 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ISignatureDecoder.cs
@@ -6,33 +6,11 @@
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
-///
-/// Classification of a signature type for GC scanning purposes.
-///
-public 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,
-}
-
public interface ISignatureDecoder : IContract
{
static string IContract.Name { get; } = nameof(SignatureDecoder);
TypeHandle DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx) => throw new NotImplementedException();
-
- ///
- /// Decodes a method's signature for GC scanning purposes, classifying each parameter
- /// as a GC reference, interior pointer, value type, or non-GC type.
- /// Handles ELEMENT_TYPE_INTERNAL via the runtime type system.
- ///
- MethodSignature DecodeMethodSignatureForGC(BlobHandle blobHandle, ModuleHandle moduleHandle) => throw new NotImplementedException();
}
public readonly struct SignatureDecoder : ISignatureDecoder
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
index caa07f8dcd63ed..793a87cc25582d 100644
--- 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
@@ -10,13 +10,14 @@ namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers;
///
/// Superset of SRM's
-/// that adds support for runtime-internal type codes (ELEMENT_TYPE_INTERNAL).
+/// 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
-/// .
+/// .
///
internal interface IRuntimeSignatureTypeProvider
: ISignatureTypeProvider
@@ -35,336 +36,322 @@ internal interface IRuntimeSignatureTypeProvider
}
///
-/// Decodes method and local variable signatures, handling both standard ECMA-335
-/// types and runtime-internal types like ELEMENT_TYPE_INTERNAL (0x21).
+/// 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).
///
-///
-///
-/// Handles the same ECMA-335 type codes as SRM's
-/// , plus runtime-internal
-/// types (ELEMENT_TYPE_INTERNAL 0x21 and ELEMENT_TYPE_CMOD_INTERNAL 0x22).
-///
-///
-/// Internal custom modifiers (ELEMENT_TYPE_CMOD_INTERNAL) are skipped since
-/// they carry runtime TypeHandle pointers that are not meaningful for type classification.
-/// Standard custom modifiers (modreq/modopt) are decoded and dispatched
-/// to .
-///
-///
-internal ref struct RuntimeSignatureDecoder
- where TReader : ISignatureReader, allows ref struct
+internal readonly struct RuntimeSignatureDecoder
{
- private const byte ELEMENT_TYPE_PTR = 0x0f;
- private const byte ELEMENT_TYPE_BYREF = 0x10;
- private const byte ELEMENT_TYPE_VALUETYPE = 0x11;
- private const byte ELEMENT_TYPE_CLASS = 0x12;
- private const byte ELEMENT_TYPE_VAR = 0x13;
- private const byte ELEMENT_TYPE_ARRAY = 0x14;
- private const byte ELEMENT_TYPE_GENERICINST = 0x15;
- private const byte ELEMENT_TYPE_FNPTR = 0x1b;
- private const byte ELEMENT_TYPE_SZARRAY = 0x1d;
- private const byte ELEMENT_TYPE_MVAR = 0x1e;
- private const byte ELEMENT_TYPE_CMOD_REQD = 0x1f;
- private const byte ELEMENT_TYPE_CMOD_OPT = 0x20;
- private const byte ELEMENT_TYPE_INTERNAL = 0x21;
- private const byte ELEMENT_TYPE_CMOD_INTERNAL = 0x22;
- private const byte ELEMENT_TYPE_SENTINEL = 0x41;
- private const byte ELEMENT_TYPE_PINNED = 0x45;
+ 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 MetadataReader? _metadataReaderOpt;
private readonly Target _target;
private readonly TGenericContext _genericContext;
- private TReader _reader;
+ private readonly int _pointerSize;
public RuntimeSignatureDecoder(
IRuntimeSignatureTypeProvider provider,
Target target,
TGenericContext genericContext,
- TReader reader,
MetadataReader? metadataReader = null)
{
+ ArgumentNullException.ThrowIfNull(provider);
+
_provider = provider;
- _metadataReader = metadataReader;
+ _metadataReaderOpt = metadataReader;
_target = target;
_genericContext = genericContext;
- _reader = reader;
+ _pointerSize = target.PointerSize;
}
- /// Decodes a field signature (FieldSig).
- public TType DecodeFieldSignature()
+ ///
+ /// Decodes a type embedded in a signature and advances the reader past the type.
+ ///
+ public TType DecodeType(ref BlobReader blobReader, bool allowTypeSpecifications = false)
{
- byte rawHeader = _reader.ReadByte();
- SignatureHeader header = new(rawHeader);
-
- if (header.Kind is not SignatureKind.Field)
- throw new BadImageFormatException($"Expected field signature header, got: {header.Kind}");
-
- return DecodeType();
+ return DecodeType(ref blobReader, allowTypeSpecifications, blobReader.ReadCompressedInteger());
}
- /// Decodes a method signature (MethodDefSig/MethodRefSig).
- public MethodSignature DecodeMethodSignature()
+ private TType DecodeType(ref BlobReader blobReader, bool allowTypeSpecifications, int typeCode)
{
- byte rawHeader = _reader.ReadByte();
- SignatureHeader header = new(rawHeader);
+ TType elementType;
+ int index;
- if (header.Kind is not SignatureKind.Method and not SignatureKind.Property)
- throw new BadImageFormatException($"Unexpected signature header kind: {header.Kind}");
+ 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);
- int genericParameterCount = 0;
- if (header.IsGeneric)
- genericParameterCount = ReadCompressedUInt();
+ case (int)SignatureTypeCode.Pointer:
+ elementType = DecodeType(ref blobReader);
+ return _provider.GetPointerType(elementType);
- int parameterCount = ReadCompressedUInt();
- if (parameterCount > _reader.Remaining)
- throw new BadImageFormatException($"Parameter count {parameterCount} exceeds remaining signature bytes");
- TType returnType = DecodeType();
+ case (int)SignatureTypeCode.ByReference:
+ elementType = DecodeType(ref blobReader);
+ return _provider.GetByReferenceType(elementType);
- var parameterTypes = ImmutableArray.CreateBuilder(parameterCount);
- int requiredParameterCount = parameterCount;
- bool sentinelSeen = false;
+ case (int)SignatureTypeCode.Pinned:
+ elementType = DecodeType(ref blobReader);
+ return _provider.GetPinnedType(elementType);
- for (int i = 0; i < parameterCount; i++)
- {
- if (_reader.Remaining > 0 && _reader.PeekByte() == ELEMENT_TYPE_SENTINEL)
- {
- if (sentinelSeen)
- throw new BadImageFormatException("Multiple sentinels in method signature");
- sentinelSeen = true;
- requiredParameterCount = i;
- _reader.ReadByte();
- }
- parameterTypes.Add(DecodeType());
- }
+ case (int)SignatureTypeCode.SZArray:
+ elementType = DecodeType(ref blobReader);
+ return _provider.GetSZArrayType(elementType);
- return new MethodSignature(
- header, returnType, requiredParameterCount, genericParameterCount,
- parameterTypes.MoveToImmutable());
- }
+ case (int)SignatureTypeCode.FunctionPointer:
+ MethodSignature methodSignature = DecodeMethodSignature(ref blobReader);
+ return _provider.GetFunctionPointerType(methodSignature);
- /// Decodes a local variable signature (LocalVarSig).
- public ImmutableArray DecodeLocalSignature()
- {
- byte header = _reader.ReadByte();
- if (header != 0x07) // IMAGE_CEE_CS_CALLCONV_LOCAL_SIG
- throw new BadImageFormatException($"Expected LocalVarSig header (0x07), got 0x{header:X2}");
+ case (int)SignatureTypeCode.Array:
+ return DecodeArrayType(ref blobReader);
- int count = ReadCompressedUInt();
- if (count == 0)
- throw new BadImageFormatException("Local variable signature must have at least one entry");
- if (count > _reader.Remaining)
- throw new BadImageFormatException($"Local count {count} exceeds remaining signature bytes");
- var locals = ImmutableArray.CreateBuilder(count);
- for (int i = 0; i < count; i++)
- locals.Add(DecodeType());
- return locals.MoveToImmutable();
+ 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 single type embedded in a signature.
- public TType DecodeType()
+ ///
+ /// 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)
{
- // Handle custom modifiers (standard and internal)
- while (_reader.Remaining > 0)
+ int count = blobReader.ReadCompressedInteger();
+ if (count == 0)
{
- byte peek = _reader.PeekByte();
- if (peek is ELEMENT_TYPE_CMOD_REQD or ELEMENT_TYPE_CMOD_OPT)
- {
- bool isRequired = peek == ELEMENT_TYPE_CMOD_REQD;
- _reader.ReadByte();
- TType modifier = DecodeTypeDefOrRefOrSpec(0);
- TType unmodifiedType = DecodeType();
- return _provider.GetModifiedType(modifier, unmodifiedType, isRequired);
- }
- else if (peek == ELEMENT_TYPE_CMOD_INTERNAL)
- {
- _reader.ReadByte();
- bool isRequired = _reader.ReadByte() != 0;
- ulong val = _reader.ReadPointerSized(_target.PointerSize);
- TType unmodifiedType = DecodeType();
- return _provider.GetInternalModifiedType(
- _target, new TargetPointer(val), unmodifiedType, isRequired);
- }
- else
- {
- break;
- }
+ throw new BadImageFormatException("Signature type sequence must have at least one element");
}
- byte typeCode = _reader.ReadByte();
-
- switch (typeCode)
+ var types = ImmutableArray.CreateBuilder(count);
+ for (int i = 0; i < count; i++)
{
- case (byte)SignatureTypeCode.Boolean:
- case (byte)SignatureTypeCode.Char:
- case (byte)SignatureTypeCode.SByte:
- case (byte)SignatureTypeCode.Byte:
- case (byte)SignatureTypeCode.Int16:
- case (byte)SignatureTypeCode.UInt16:
- case (byte)SignatureTypeCode.Int32:
- case (byte)SignatureTypeCode.UInt32:
- case (byte)SignatureTypeCode.Int64:
- case (byte)SignatureTypeCode.UInt64:
- case (byte)SignatureTypeCode.Single:
- case (byte)SignatureTypeCode.Double:
- case (byte)SignatureTypeCode.IntPtr:
- case (byte)SignatureTypeCode.UIntPtr:
- case (byte)SignatureTypeCode.Object:
- case (byte)SignatureTypeCode.String:
- case (byte)SignatureTypeCode.Void:
- case (byte)SignatureTypeCode.TypedReference:
- return _provider.GetPrimitiveType((PrimitiveTypeCode)typeCode);
+ types.Add(DecodeType(ref blobReader));
+ }
+ return types.MoveToImmutable();
+ }
- case ELEMENT_TYPE_CLASS:
- case ELEMENT_TYPE_VALUETYPE:
- return DecodeTypeDefOrRefOrSpec(typeCode);
+ ///
+ /// Decodes a method (definition, reference, or standalone) or property signature blob.
+ ///
+ public MethodSignature DecodeMethodSignature(ref BlobReader blobReader)
+ {
+ SignatureHeader header = blobReader.ReadSignatureHeader();
+ CheckMethodOrPropertyHeader(header);
- case ELEMENT_TYPE_PTR:
- return _provider.GetPointerType(DecodeType());
+ int genericParameterCount = 0;
+ if (header.IsGeneric)
+ {
+ genericParameterCount = blobReader.ReadCompressedInteger();
+ }
- case ELEMENT_TYPE_BYREF:
- return _provider.GetByReferenceType(DecodeType());
+ int parameterCount = blobReader.ReadCompressedInteger();
+ TType returnType = DecodeType(ref blobReader);
+ ImmutableArray parameterTypes;
+ int requiredParameterCount;
- case ELEMENT_TYPE_SZARRAY:
- return _provider.GetSZArrayType(DecodeType());
+ if (parameterCount == 0)
+ {
+ requiredParameterCount = 0;
+ parameterTypes = ImmutableArray.Empty;
+ }
+ else
+ {
+ var parameterBuilder = ImmutableArray.CreateBuilder(parameterCount);
+ int parameterIndex;
- case ELEMENT_TYPE_ARRAY:
+ for (parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++)
{
- TType elementType = DecodeType();
- ArrayShape shape = DecodeArrayShape();
- return _provider.GetArrayType(elementType, shape);
+ int typeCode = blobReader.ReadCompressedInteger();
+ if (typeCode == (int)SignatureTypeCode.Sentinel)
+ {
+ break;
+ }
+ parameterBuilder.Add(DecodeType(ref blobReader, allowTypeSpecifications: false, typeCode: typeCode));
}
- case ELEMENT_TYPE_GENERICINST:
+ requiredParameterCount = parameterIndex;
+ for (; parameterIndex < parameterCount; parameterIndex++)
{
- TType baseType = DecodeType();
- int count = ReadCompressedUInt();
- if (count == 0)
- throw new BadImageFormatException("Generic instantiation must have at least one type argument");
- if (count > _reader.Remaining)
- throw new BadImageFormatException($"Generic argument count {count} exceeds remaining signature bytes");
- var args = ImmutableArray.CreateBuilder(count);
- for (int i = 0; i < count; i++)
- args.Add(DecodeType());
- return _provider.GetGenericInstantiation(baseType, args.MoveToImmutable());
+ parameterBuilder.Add(DecodeType(ref blobReader));
}
+ parameterTypes = parameterBuilder.MoveToImmutable();
+ }
- case ELEMENT_TYPE_VAR:
- return _provider.GetGenericTypeParameter(_genericContext, ReadCompressedUInt());
+ return new MethodSignature(header, returnType, requiredParameterCount, genericParameterCount, parameterTypes);
+ }
- case ELEMENT_TYPE_MVAR:
- return _provider.GetGenericMethodParameter(_genericContext, ReadCompressedUInt());
+ ///
+ /// 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);
+ }
- case ELEMENT_TYPE_FNPTR:
- {
- MethodSignature fnSig = DecodeMethodSignature();
- return _provider.GetFunctionPointerType(fnSig);
- }
+ ///
+ /// 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);
+ }
- case ELEMENT_TYPE_PINNED:
- return _provider.GetPinnedType(DecodeType());
+ private TType DecodeArrayType(ref BlobReader blobReader)
+ {
+ TType elementType = DecodeType(ref blobReader);
+ int rank = blobReader.ReadCompressedInteger();
+ var sizes = ImmutableArray.Empty;
+ var lowerBounds = ImmutableArray.Empty;
- case ELEMENT_TYPE_INTERNAL:
+ int sizesCount = blobReader.ReadCompressedInteger();
+ if (sizesCount > 0)
+ {
+ var builder = ImmutableArray.CreateBuilder(sizesCount);
+ for (int i = 0; i < sizesCount; i++)
{
- ulong val = _reader.ReadPointerSized(_target.PointerSize);
- return _provider.GetInternalType(_target, new TargetPointer(val));
+ builder.Add(blobReader.ReadCompressedInteger());
}
+ sizes = builder.MoveToImmutable();
+ }
- default:
- throw new BadImageFormatException($"Unexpected signature type code: 0x{typeCode:X2}");
+ 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));
}
- ///
- /// Decodes a TypeDefOrRefOrSpecEncoded token (ECMA-335 II.23.2.8).
- /// The compressed value encodes tag in the low 2 bits and RID in the upper bits.
- ///
- private TType DecodeTypeDefOrRefOrSpec(byte rawTypeKind)
+ private TType DecodeGenericTypeInstance(ref BlobReader blobReader)
{
- int coded = ReadCompressedUInt();
- int tag = coded & 0x3;
- int rid = coded >> 2;
-
- if (rid == 0)
- throw new BadImageFormatException("Nil TypeDefOrRefOrSpecEncoded handle in signature");
-
- if (rid > 0x00FFFFFF)
- throw new BadImageFormatException($"TypeDefOrRefOrSpecEncoded RID out of range: {rid}");
-
- return tag switch
- {
- 0 => _provider.GetTypeFromDefinition(_metadataReader!, MetadataTokens.TypeDefinitionHandle(rid), rawTypeKind),
- 1 => _provider.GetTypeFromReference(_metadataReader!, MetadataTokens.TypeReferenceHandle(rid), rawTypeKind),
- 2 => _provider.GetTypeFromSpecification(_metadataReader!, _genericContext, MetadataTokens.TypeSpecificationHandle(rid), rawTypeKind),
- _ => _provider.GetPrimitiveType(PrimitiveTypeCode.Object), // tag=3 is BaseType in native
- };
+ TType genericType = DecodeType(ref blobReader);
+ ImmutableArray types = DecodeTypeSequence(ref blobReader);
+ return _provider.GetGenericInstantiation(genericType, types);
}
- private ArrayShape DecodeArrayShape()
+ private TType DecodeModifiedType(ref BlobReader blobReader, bool isRequired)
{
- int rank = ReadCompressedUInt();
- int numSizes = ReadCompressedUInt();
- if (numSizes > _reader.Remaining)
- throw new BadImageFormatException($"Array size count {numSizes} exceeds remaining signature bytes");
- var sizes = ImmutableArray.CreateBuilder(numSizes);
- for (int i = 0; i < numSizes; i++)
- sizes.Add(ReadCompressedUInt());
- int numLoBounds = ReadCompressedUInt();
- if (numLoBounds > _reader.Remaining)
- throw new BadImageFormatException($"Array lower bound count {numLoBounds} exceeds remaining signature bytes");
- var loBounds = ImmutableArray.CreateBuilder(numLoBounds);
- for (int i = 0; i < numLoBounds; i++)
- loBounds.Add(ReadCompressedSignedInt());
- return new ArrayShape(rank, sizes.MoveToImmutable(), loBounds.MoveToImmutable());
+ // 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);
}
- ///
- /// Reads a compressed unsigned integer per ECMA-335 II.23.2.
- ///
- private int ReadCompressedUInt()
+ private TType DecodeInternalType(ref BlobReader blobReader)
{
- byte first = _reader.ReadByte();
- if ((first & 0x80) == 0)
- return first;
- if ((first & 0xC0) == 0x80)
- return ((first & 0x3F) << 8) | _reader.ReadByte();
- if ((first & 0xE0) == 0xC0)
- return ((first & 0x1F) << 24) | (_reader.ReadByte() << 16) | (_reader.ReadByte() << 8) | _reader.ReadByte();
-
- throw new BadImageFormatException("Invalid compressed integer encoding");
+ ulong val = ReadPointerSized(ref blobReader);
+ return _provider.GetInternalType(_target, new TargetPointer(val));
}
- ///
- /// Reads a compressed signed integer per ECMA-335 II.23.2.
- /// Uses sign extension based on encoded width, matching SRM's BlobReader.ReadCompressedSignedInteger.
- ///
- private int ReadCompressedSignedInt()
+ private TType DecodeInternalModifiedType(ref BlobReader blobReader)
{
- byte first = _reader.ReadByte();
+ bool isRequired = blobReader.ReadByte() != 0;
+ ulong val = ReadPointerSized(ref blobReader);
+ TType unmodifiedType = DecodeType(ref blobReader);
+ return _provider.GetInternalModifiedType(_target, new TargetPointer(val), unmodifiedType, isRequired);
+ }
- if ((first & 0x80) == 0)
+ private TType DecodeTypeHandle(ref BlobReader blobReader, byte rawTypeKind, bool allowTypeSpecifications)
+ {
+ EntityHandle handle = blobReader.ReadTypeHandle();
+ if (!handle.IsNil)
{
- // 1-byte: 7 bits, sign bit is bit 0 of the encoded value
- int value = first >> 1;
- return (first & 1) != 0 ? value - 0x40 : value;
+ switch (handle.Kind)
+ {
+ case HandleKind.TypeDefinition:
+ return _provider.GetTypeFromDefinition(_metadataReaderOpt!, (TypeDefinitionHandle)handle, rawTypeKind);
+
+ case HandleKind.TypeReference:
+ return _provider.GetTypeFromReference(_metadataReaderOpt!, (TypeReferenceHandle)handle, rawTypeKind);
+
+ case HandleKind.TypeSpecification:
+ if (!allowTypeSpecifications)
+ {
+ throw new BadImageFormatException("TypeSpecification handle not allowed in this context");
+ }
+ return _provider.GetTypeFromSpecification(_metadataReaderOpt!, _genericContext, (TypeSpecificationHandle)handle, rawTypeKind);
+ }
}
- if ((first & 0xC0) == 0x80)
+ 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)
{
- // 2-byte: 14 bits
- int raw = ((first & 0x3F) << 8) | _reader.ReadByte();
- int value = raw >> 1;
- return (raw & 1) != 0 ? value - 0x2000 : value;
+ throw new BadImageFormatException($"Expected signature header {expectedKind}, got {header.Kind} (raw 0x{header.RawValue:X2})");
}
+ }
- if ((first & 0xE0) == 0xC0)
+ private static void CheckMethodOrPropertyHeader(SignatureHeader header)
+ {
+ SignatureKind kind = header.Kind;
+ if (kind != SignatureKind.Method && kind != SignatureKind.Property)
{
- // 4-byte: 29 bits
- int raw = ((first & 0x1F) << 24) | (_reader.ReadByte() << 16) | (_reader.ReadByte() << 8) | _reader.ReadByte();
- int value = raw >> 1;
- return (raw & 1) != 0 ? value - 0x10000000 : value;
+ throw new BadImageFormatException($"Expected Method or Property signature header, got {kind} (raw 0x{header.RawValue:X2})");
}
-
- throw new BadImageFormatException("Invalid compressed signed integer encoding");
}
}
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/SignatureDecoder_1.cs
index 641401ff69628b..d057e84f15e414 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/SignatureDecoder_1.cs
@@ -1,10 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Collections.Generic;
using System.Reflection.Metadata;
-using System.Reflection.Metadata.Ecma335;
using Microsoft.Diagnostics.DataContractReader.SignatureHelpers;
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
@@ -22,7 +20,6 @@ internal sealed class SignatureDecoder_1 : ISignatureDecoder
private readonly Target _target;
private readonly Dictionary> _thProviders = [];
private readonly Dictionary> _mdhProviders = [];
- private readonly Dictionary _gcProviders = [];
internal SignatureDecoder_1(Target target)
{
@@ -33,7 +30,6 @@ public void Flush()
{
_thProviders.Clear();
_mdhProviders.Clear();
- _gcProviders.Clear();
}
private SignatureTypeProvider GetTypeHandleProvider(ModuleHandle moduleHandle)
@@ -59,36 +55,13 @@ private SignatureTypeProvider GetMethodDescHandleProvider(Modu
return newProvider;
}
-
- private GcSignatureTypeProvider GetGcProvider(ModuleHandle moduleHandle)
- {
- if (_gcProviders.TryGetValue(moduleHandle, out GcSignatureTypeProvider? provider))
- return provider;
-
- GcSignatureTypeProvider newProvider = new(_target, moduleHandle);
- _gcProviders[moduleHandle] = newProvider;
- return newProvider;
- }
-
TypeHandle ISignatureDecoder.DecodeFieldSignature(BlobHandle blobHandle, ModuleHandle moduleHandle, TypeHandle ctx)
{
SignatureTypeProvider provider = GetTypeHandleProvider(moduleHandle);
MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!;
- RuntimeSignatureDecoder decoder = new(
- provider, _target, ctx,
- new BlobHandleSignatureReader(mdReader, blobHandle, _target.IsLittleEndian), mdReader);
- return decoder.DecodeFieldSignature();
- }
-
- MethodSignature ISignatureDecoder.DecodeMethodSignatureForGC(BlobHandle blobHandle, ModuleHandle moduleHandle)
- {
- GcSignatureTypeProvider provider = GetGcProvider(moduleHandle);
- MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!;
-
- RuntimeSignatureDecoder decoder = new(
- provider, _target, genericContext: null,
- new BlobHandleSignatureReader(mdReader, blobHandle, _target.IsLittleEndian), mdReader);
- return decoder.DecodeMethodSignature();
+ BlobReader blobReader = mdReader.GetBlobReader(blobHandle);
+ RuntimeSignatureDecoder decoder = new(provider, _target, ctx, mdReader);
+ return decoder.DecodeFieldSignature(ref blobReader);
}
}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureReaders.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureReaders.cs
deleted file mode 100644
index 06aeacfbf9693d..00000000000000
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/SignatureReaders.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Buffers.Binary;
-using System.Reflection.Metadata;
-
-namespace Microsoft.Diagnostics.DataContractReader.SignatureHelpers;
-
-///
-/// Abstraction for reading bytes from a signature blob.
-///
-///
-/// Allows the decoder to read from different sources (in-memory spans,
-/// target process memory) without allocating intermediate byte arrays.
-///
-internal interface ISignatureReader
-{
- byte ReadByte();
- byte PeekByte();
- int Remaining { get; }
-
- /// Reads a pointer-sized unsigned value (4 or 8 bytes).
- ulong ReadPointerSized(int pointerSize);
-}
-
-///
-/// Reads signature bytes from a .
-///
-internal ref struct SpanSignatureReader : ISignatureReader
-{
- private readonly ReadOnlySpan _blob;
- private readonly bool _isLittleEndian;
- private int _offset;
-
- public SpanSignatureReader(ReadOnlySpan blob, bool isLittleEndian = true)
- {
- _blob = blob;
- _isLittleEndian = isLittleEndian;
- _offset = 0;
- }
-
- public int Remaining => _blob.Length - _offset;
-
- public byte ReadByte()
- {
- if (_offset >= _blob.Length)
- throw new BadImageFormatException("Unexpected end of signature blob");
- return _blob[_offset++];
- }
-
- public byte PeekByte()
- {
- if (_offset >= _blob.Length)
- throw new BadImageFormatException("Unexpected end of signature blob");
- return _blob[_offset];
- }
-
- public ulong ReadPointerSized(int pointerSize)
- {
- if (_offset + pointerSize > _blob.Length)
- throw new BadImageFormatException("Unexpected end of signature blob");
-
- ReadOnlySpan slice = _blob.Slice(_offset, pointerSize);
- ulong val = pointerSize == 8
- ? (_isLittleEndian ? BinaryPrimitives.ReadUInt64LittleEndian(slice) : BinaryPrimitives.ReadUInt64BigEndian(slice))
- : (_isLittleEndian ? BinaryPrimitives.ReadUInt32LittleEndian(slice) : BinaryPrimitives.ReadUInt32BigEndian(slice));
- _offset += pointerSize;
- return val;
- }
-}
-
-///
-/// Reads signature bytes lazily from a via a .
-///
-internal ref struct BlobHandleSignatureReader : ISignatureReader
-{
- private BlobReader _blobReader;
- private readonly bool _isLittleEndian;
-
- public BlobHandleSignatureReader(MetadataReader metadataReader, BlobHandle blobHandle, bool isLittleEndian = true)
- {
- _blobReader = metadataReader.GetBlobReader(blobHandle);
- _isLittleEndian = isLittleEndian;
- }
-
- public int Remaining => _blobReader.RemainingBytes;
-
- public byte ReadByte() => _blobReader.ReadByte();
-
- public byte PeekByte()
- {
- if (_blobReader.RemainingBytes == 0)
- throw new BadImageFormatException("Unexpected end of signature blob");
- byte value = _blobReader.ReadByte();
- _blobReader.Offset--;
- return value;
- }
-
- public ulong ReadPointerSized(int pointerSize)
- {
- if (_blobReader.RemainingBytes < pointerSize)
- throw new BadImageFormatException("Unexpected end of signature blob");
-
- return pointerSize == 8
- ? (_isLittleEndian ? _blobReader.ReadUInt64() : BinaryPrimitives.ReverseEndianness(_blobReader.ReadUInt64()))
- : (_isLittleEndian ? _blobReader.ReadUInt32() : BinaryPrimitives.ReverseEndianness(_blobReader.ReadUInt32()));
- }
-}
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 179228dad72d54..e8a9ef98b23174 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
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using Microsoft.Diagnostics.DataContractReader.SignatureHelpers;
namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
@@ -345,7 +346,11 @@ private void PromoteCallerStack(
MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)(methodToken & 0x00FFFFFF));
MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle);
- methodSig = _target.Contracts.SignatureDecoder.DecodeMethodSignatureForGC(methodDef.Signature, moduleHandle);
+ BlobReader blobReader = mdReader.GetBlobReader(methodDef.Signature);
+ GcSignatureTypeProvider gcProvider = new(_target, moduleHandle);
+ RuntimeSignatureDecoder decoder = new(
+ gcProvider, _target, genericContext: null, mdReader);
+ methodSig = decoder.DecodeMethodSignature(ref blobReader);
}
catch (System.Exception)
{
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/GcSignatureTypeProvider.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs
similarity index 88%
rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/GcSignatureTypeProvider.cs
rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs
index 7efafe8f8b1156..549cb94022b561 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/GcSignatureTypeProvider.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcSignatureTypeProvider.cs
@@ -3,9 +3,24 @@
using System.Collections.Immutable;
using System.Reflection.Metadata;
-using Microsoft.Diagnostics.DataContractReader.Contracts;
+using Microsoft.Diagnostics.DataContractReader.SignatureHelpers;
-namespace 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,
+}
///
/// Classifies signature types for GC scanning purposes.
From 112b4beedcf02c71ad8d7d398bfff337fb0c29e3 Mon Sep 17 00:00:00 2001
From: Max Charlamb