[cDAC] Draft CodeVersionsTest refactor - #2

Closed
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests
Closed

[cDAC] Draft CodeVersionsTest refactor#2
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests

Conversation

@max-charlamb

Copy link
Copy Markdown
Owner

No description provided.

private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);
private Mock<IExecutionManager> mockExecutionManager = new(MockBehavior.Strict);
private Mock<ILoader> mockLoader = new(MockBehavior.Strict);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockBehavior.Strict isn't required, but I wanted to make sure I was capturing all the calls.

ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1);

// this is not quite accurate on 32 bit architectures, but it's good enough for testing
ulong addressLowBits = (ulong)address & 7;

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (ulong)address & ((ulong)_target.PointerSize - 1);, however when we set this up the target isn't created. I guess we could create the target first and pass it in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to get the pointer size off of the TargetTestHelpers for MockDescriptor.CodeVersions, which we create before doing this.

[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
{
mockExecutionManager.Setup(e => e.GetCodeBlockHandle(TargetCodePointer.Null)).Returns(() => null);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only required with MockBehavior.Strict. Otherwise this test relies on the mock returning null when there is no setup. I find this a bit confusing.


TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null;
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null;
private void AddCodeBlock(MockCodeBlockStart block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think MockCodeBlockStart and friends are still helpful? Versus having these method just take in the address, length, method desc, etc. as separate parameters.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are still helpful to encapsulate state related to a mock. I think if we removed them, it would make tests with multiple MethodDescs confusing due to keeping track of multiple similar fields.

internal class MockExecutionManager : IExecutionManager
{
private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to find it easier to reason about tests when everything is contained in the test method (explicitly sets up what it needs / no state outside the method, don't have to know that each test method actually gets a new test class instance).

Maybe try something like having the test cases explicitly create the mock(s) they need and turn the Add* methods into extension methods for Mock<...>?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make this change. I settled on having a MockExtensions class which adds the helpers as extensions on the correct generic implementation of Mock<...>.

@max-charlamb
max-charlamb changed the base branch from md-gccover to mainDecember 16, 2024 17:48
@max-charlamb
max-charlamb changed the base branch from main to md-dynamicDecember 16, 2024 17:49
@max-charlamb
max-charlamb changed the base branch from md-dynamic to mainDecember 16, 2024 17:49
@max-charlamb
max-charlamb deleted the cdac-update-cv-tests branch June 10, 2025 16:37
max-charlamb pushed a commit that referenced this pull request Mar 24, 2026
…otnet#124642)
## Summary
Fixesdotnet#123621
When a constant-folded operand appears **after** a non-constant operand
in a short-circuit `&&` expression (e.g., `v == 2 && Environment.NewLine
!= "\r\n"`), callee inlining can leave dead local stores in the return
block. The `isReturnBool` lambda in `fgFoldCondToReturnBlock` required
`hasSingleStmt()`, which caused the optimization to bail out when these
dead stores were present, resulting in suboptimal branching codegen.
### Changes
- **`src/coreclr/jit/optimizebools.cpp`**: Relax the `hasSingleStmt()`
constraint in `isReturnBool` to allow preceding statements as long as
they have no globally visible side effects
(`GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS`). This enables
`fgFoldCondToReturnBlock` to fold the conditional into a branchless
return even when dead local stores from inlining remain in the block.
### Before (ARM64, `Inline_After`)
```asm
cmp w0, #2
bne G_M4495_IG04
mov w0, #1
ret
G_M4495_IG04:
mov w0, #0
ret
```
### After (ARM64, `Inline_After`)
```asm
cmp w0, #2
cset x0, eq
ret
```
## Test plan
- [x] Added regression test `Runtime_123621` covering the original issue
pattern
- [x] Verified `Hoisted`, `Inline_Before`, and `Inline_After` all
produce identical branchless codegen (`cset` on ARM64)
- [x] Verified existing `DevDiv_168744` regression test still passes
- [x] Verified side-effect-ful blocks are correctly excluded from the
optimization
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 24, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request May 14, 2026
…128163)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
Fixesdotnet#128044.
## Problem
createdump SIGSEGVs on Linux when generating a Heap-type minidump for a
process running interpreted code. The crash reproduces locally with the
`InterpreterStack` DumpTests debuggee and matches the CI failure that
prompted `<DumpTypes>Full</DumpTypes>` to be added as a temporary
workaround.
The faulting backtrace is:
```
#0 Thread::IsAddressInStack threads.cpp:6741
#1 Thread::EnumMemoryRegionsWorker threads.cpp:6909 (calls IsAddressInStack(currentSP))
#2 Thread::EnumMemoryRegions threads.cpp
#3 ThreadStore::EnumMemoryRegions
#4 ClrDataAccess::EnumMemDumpAllThreadsStack
#5 ClrDataAccess::EnumMemoryRegionsWorkerHeap (HEAP2-only path)
```
## Root cause
`Thread::m_pInterpThreadContext` was declared as a raw
`InterpThreadContext *`. In non-DAC code that's a normal host pointer,
but in
DAC mode the field's value is a target-process address. When
`IsAddressInStack` (a DAC-callable helper) dereferenced
`m_pInterpThreadContext->pStackStart` it read from a target-process
address
as if it were a host address, which faults inside createdump.
## Fix
Change the field type to `PTR_InterpThreadContext` (DPTR), matching the
treatment of other Thread fields like `m_pFrame`. In non-DAC builds
`DPTR(T)` is just `T*`, so there is no overhead or behavior change. In
DAC
builds the read goes through `__DPtr<T>` and marshals correctly from the
target.
Also remove the `<DumpTypes>Full</DumpTypes>` workaround on the
`InterpreterStack` DumpTests debuggee so the Heap path that originally
failed is exercised again.
## Validation
Locally reproduced the original SIGSEGV on Linux x64 with the auto-dump
mechanism (`DOTNET_DbgMiniDumpType=2` + `DOTNET_Interpreter=MethodA`)
running the `InterpreterStack` debuggee. With this fix applied,
createdump
produces a complete Heap dump (~74 MB) instead of crashing.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Jun 3, 2026
An Android production app reported a native abort while building an
X.509 chain on arm64. The available tombstone snippet showed the process
aborting in `AndroidCryptoNative_X509ChainBuild` from `pal_x509chain.c`,
with the native guard reporting that parameter `ctx` was not a valid
pointer. The report did not include a repro or full tombstone, but the
observed failure mode means managed code reached the native build entry
point with a null `X509ChainContext*`.
```
Thread
/__w/1/s/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c:113 (AndroidCryptoNative_X509ChainBuild): Parameter 'ctx' must be a valid pointer
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 31609 >>> com.app.name <<<
backtrace:
#00 pc 0x000000000002232c /system/lib64/libc.so (abort+116)
#1 pc 0x0000000000021fe8 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so
#2 pc 0x00000000000220b0 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so (AndroidCryptoNative_X509ChainBuild+88)
#3 pc 0x000000000000cfcc
```
`X509ChainContext` is created by
`AndroidCryptoNative_X509ChainCreateContext`. That initialization can
fail if Android certificate store setup or PKIX parameter construction
throws, or if required JNI global references cannot be created.
Previously, the managed Android chain path stored the returned
`SafeHandle` without checking whether context creation failed, so a
later build could pass a null native context to
`AndroidCryptoNative_X509ChainBuild` and terminate the app process.
This change makes context creation fail gracefully:
- The native create path checks Java exceptions around object creation
and method calls more consistently.
- Partial native contexts are destroyed if global-reference creation
fails.
- The Android interop wrapper checks the returned chain context
immediately, including a null safe-handle return, and throws
`CryptographicException` if initialization failed.
No regression test is included because the reliable failure modes depend
on Android platform/provider state or artificial fault injection, and a
test hook would be fragile and not representative.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Jones <kevin@vcsjones.com>
max-charlamb pushed a commit that referenced this pull request Jun 20, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 25, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 27, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@max-charlamb@elinor-fung
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[cDAC] Draft CodeVersionsTest refactor - #2

Closed
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests
Closed

[cDAC] Draft CodeVersionsTest refactor#2
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests

Conversation

@max-charlamb

Copy link
Copy Markdown
Owner

No description provided.

private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);
private Mock<IExecutionManager> mockExecutionManager = new(MockBehavior.Strict);
private Mock<ILoader> mockLoader = new(MockBehavior.Strict);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockBehavior.Strict isn't required, but I wanted to make sure I was capturing all the calls.

ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1);

// this is not quite accurate on 32 bit architectures, but it's good enough for testing
ulong addressLowBits = (ulong)address & 7;

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (ulong)address & ((ulong)_target.PointerSize - 1);, however when we set this up the target isn't created. I guess we could create the target first and pass it in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to get the pointer size off of the TargetTestHelpers for MockDescriptor.CodeVersions, which we create before doing this.

[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
{
mockExecutionManager.Setup(e => e.GetCodeBlockHandle(TargetCodePointer.Null)).Returns(() => null);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only required with MockBehavior.Strict. Otherwise this test relies on the mock returning null when there is no setup. I find this a bit confusing.


TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null;
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null;
private void AddCodeBlock(MockCodeBlockStart block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think MockCodeBlockStart and friends are still helpful? Versus having these method just take in the address, length, method desc, etc. as separate parameters.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are still helpful to encapsulate state related to a mock. I think if we removed them, it would make tests with multiple MethodDescs confusing due to keeping track of multiple similar fields.

internal class MockExecutionManager : IExecutionManager
{
private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to find it easier to reason about tests when everything is contained in the test method (explicitly sets up what it needs / no state outside the method, don't have to know that each test method actually gets a new test class instance).

Maybe try something like having the test cases explicitly create the mock(s) they need and turn the Add* methods into extension methods for Mock<...>?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make this change. I settled on having a MockExtensions class which adds the helpers as extensions on the correct generic implementation of Mock<...>.

@max-charlamb
max-charlamb changed the base branch from md-gccover to mainDecember 16, 2024 17:48
@max-charlamb
max-charlamb changed the base branch from main to md-dynamicDecember 16, 2024 17:49
@max-charlamb
max-charlamb changed the base branch from md-dynamic to mainDecember 16, 2024 17:49
@max-charlamb
max-charlamb deleted the cdac-update-cv-tests branch June 10, 2025 16:37
max-charlamb pushed a commit that referenced this pull request Mar 24, 2026
…otnet#124642)
## Summary
Fixesdotnet#123621
When a constant-folded operand appears **after** a non-constant operand
in a short-circuit `&&` expression (e.g., `v == 2 && Environment.NewLine
!= "\r\n"`), callee inlining can leave dead local stores in the return
block. The `isReturnBool` lambda in `fgFoldCondToReturnBlock` required
`hasSingleStmt()`, which caused the optimization to bail out when these
dead stores were present, resulting in suboptimal branching codegen.
### Changes
- **`src/coreclr/jit/optimizebools.cpp`**: Relax the `hasSingleStmt()`
constraint in `isReturnBool` to allow preceding statements as long as
they have no globally visible side effects
(`GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS`). This enables
`fgFoldCondToReturnBlock` to fold the conditional into a branchless
return even when dead local stores from inlining remain in the block.
### Before (ARM64, `Inline_After`)
```asm
cmp w0, #2
bne G_M4495_IG04
mov w0, #1
ret
G_M4495_IG04:
mov w0, #0
ret
```
### After (ARM64, `Inline_After`)
```asm
cmp w0, #2
cset x0, eq
ret
```
## Test plan
- [x] Added regression test `Runtime_123621` covering the original issue
pattern
- [x] Verified `Hoisted`, `Inline_Before`, and `Inline_After` all
produce identical branchless codegen (`cset` on ARM64)
- [x] Verified existing `DevDiv_168744` regression test still passes
- [x] Verified side-effect-ful blocks are correctly excluded from the
optimization
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 24, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request May 14, 2026
…128163)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
Fixesdotnet#128044.
## Problem
createdump SIGSEGVs on Linux when generating a Heap-type minidump for a
process running interpreted code. The crash reproduces locally with the
`InterpreterStack` DumpTests debuggee and matches the CI failure that
prompted `<DumpTypes>Full</DumpTypes>` to be added as a temporary
workaround.
The faulting backtrace is:
```
#0 Thread::IsAddressInStack threads.cpp:6741
#1 Thread::EnumMemoryRegionsWorker threads.cpp:6909 (calls IsAddressInStack(currentSP))
#2 Thread::EnumMemoryRegions threads.cpp
#3 ThreadStore::EnumMemoryRegions
#4 ClrDataAccess::EnumMemDumpAllThreadsStack
#5 ClrDataAccess::EnumMemoryRegionsWorkerHeap (HEAP2-only path)
```
## Root cause
`Thread::m_pInterpThreadContext` was declared as a raw
`InterpThreadContext *`. In non-DAC code that's a normal host pointer,
but in
DAC mode the field's value is a target-process address. When
`IsAddressInStack` (a DAC-callable helper) dereferenced
`m_pInterpThreadContext->pStackStart` it read from a target-process
address
as if it were a host address, which faults inside createdump.
## Fix
Change the field type to `PTR_InterpThreadContext` (DPTR), matching the
treatment of other Thread fields like `m_pFrame`. In non-DAC builds
`DPTR(T)` is just `T*`, so there is no overhead or behavior change. In
DAC
builds the read goes through `__DPtr<T>` and marshals correctly from the
target.
Also remove the `<DumpTypes>Full</DumpTypes>` workaround on the
`InterpreterStack` DumpTests debuggee so the Heap path that originally
failed is exercised again.
## Validation
Locally reproduced the original SIGSEGV on Linux x64 with the auto-dump
mechanism (`DOTNET_DbgMiniDumpType=2` + `DOTNET_Interpreter=MethodA`)
running the `InterpreterStack` debuggee. With this fix applied,
createdump
produces a complete Heap dump (~74 MB) instead of crashing.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Jun 3, 2026
An Android production app reported a native abort while building an
X.509 chain on arm64. The available tombstone snippet showed the process
aborting in `AndroidCryptoNative_X509ChainBuild` from `pal_x509chain.c`,
with the native guard reporting that parameter `ctx` was not a valid
pointer. The report did not include a repro or full tombstone, but the
observed failure mode means managed code reached the native build entry
point with a null `X509ChainContext*`.
```
Thread
/__w/1/s/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c:113 (AndroidCryptoNative_X509ChainBuild): Parameter 'ctx' must be a valid pointer
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 31609 >>> com.app.name <<<
backtrace:
#00 pc 0x000000000002232c /system/lib64/libc.so (abort+116)
#1 pc 0x0000000000021fe8 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so
#2 pc 0x00000000000220b0 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so (AndroidCryptoNative_X509ChainBuild+88)
#3 pc 0x000000000000cfcc
```
`X509ChainContext` is created by
`AndroidCryptoNative_X509ChainCreateContext`. That initialization can
fail if Android certificate store setup or PKIX parameter construction
throws, or if required JNI global references cannot be created.
Previously, the managed Android chain path stored the returned
`SafeHandle` without checking whether context creation failed, so a
later build could pass a null native context to
`AndroidCryptoNative_X509ChainBuild` and terminate the app process.
This change makes context creation fail gracefully:
- The native create path checks Java exceptions around object creation
and method calls more consistently.
- Partial native contexts are destroyed if global-reference creation
fails.
- The Android interop wrapper checks the returned chain context
immediately, including a null safe-handle return, and throws
`CryptographicException` if initialization failed.
No regression test is included because the reliable failure modes depend
on Android platform/provider state or artificial fault injection, and a
test hook would be fragile and not representative.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Jones <kevin@vcsjones.com>
max-charlamb pushed a commit that referenced this pull request Jun 20, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 25, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 27, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@max-charlamb@elinor-fung
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[cDAC] Draft CodeVersionsTest refactor - #2

Closed
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests
Closed

[cDAC] Draft CodeVersionsTest refactor#2
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests

Conversation

@max-charlamb

Copy link
Copy Markdown
Owner

No description provided.

private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);
private Mock<IExecutionManager> mockExecutionManager = new(MockBehavior.Strict);
private Mock<ILoader> mockLoader = new(MockBehavior.Strict);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockBehavior.Strict isn't required, but I wanted to make sure I was capturing all the calls.

ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1);

// this is not quite accurate on 32 bit architectures, but it's good enough for testing
ulong addressLowBits = (ulong)address & 7;

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (ulong)address & ((ulong)_target.PointerSize - 1);, however when we set this up the target isn't created. I guess we could create the target first and pass it in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to get the pointer size off of the TargetTestHelpers for MockDescriptor.CodeVersions, which we create before doing this.

[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
{
mockExecutionManager.Setup(e => e.GetCodeBlockHandle(TargetCodePointer.Null)).Returns(() => null);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only required with MockBehavior.Strict. Otherwise this test relies on the mock returning null when there is no setup. I find this a bit confusing.


TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null;
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null;
private void AddCodeBlock(MockCodeBlockStart block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think MockCodeBlockStart and friends are still helpful? Versus having these method just take in the address, length, method desc, etc. as separate parameters.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are still helpful to encapsulate state related to a mock. I think if we removed them, it would make tests with multiple MethodDescs confusing due to keeping track of multiple similar fields.

internal class MockExecutionManager : IExecutionManager
{
private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to find it easier to reason about tests when everything is contained in the test method (explicitly sets up what it needs / no state outside the method, don't have to know that each test method actually gets a new test class instance).

Maybe try something like having the test cases explicitly create the mock(s) they need and turn the Add* methods into extension methods for Mock<...>?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make this change. I settled on having a MockExtensions class which adds the helpers as extensions on the correct generic implementation of Mock<...>.

@max-charlamb
max-charlamb changed the base branch from md-gccover to mainDecember 16, 2024 17:48
@max-charlamb
max-charlamb changed the base branch from main to md-dynamicDecember 16, 2024 17:49
@max-charlamb
max-charlamb changed the base branch from md-dynamic to mainDecember 16, 2024 17:49
@max-charlamb
max-charlamb deleted the cdac-update-cv-tests branch June 10, 2025 16:37
max-charlamb pushed a commit that referenced this pull request Mar 24, 2026
…otnet#124642)
## Summary
Fixesdotnet#123621
When a constant-folded operand appears **after** a non-constant operand
in a short-circuit `&&` expression (e.g., `v == 2 && Environment.NewLine
!= "\r\n"`), callee inlining can leave dead local stores in the return
block. The `isReturnBool` lambda in `fgFoldCondToReturnBlock` required
`hasSingleStmt()`, which caused the optimization to bail out when these
dead stores were present, resulting in suboptimal branching codegen.
### Changes
- **`src/coreclr/jit/optimizebools.cpp`**: Relax the `hasSingleStmt()`
constraint in `isReturnBool` to allow preceding statements as long as
they have no globally visible side effects
(`GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS`). This enables
`fgFoldCondToReturnBlock` to fold the conditional into a branchless
return even when dead local stores from inlining remain in the block.
### Before (ARM64, `Inline_After`)
```asm
cmp w0, #2
bne G_M4495_IG04
mov w0, #1
ret
G_M4495_IG04:
mov w0, #0
ret
```
### After (ARM64, `Inline_After`)
```asm
cmp w0, #2
cset x0, eq
ret
```
## Test plan
- [x] Added regression test `Runtime_123621` covering the original issue
pattern
- [x] Verified `Hoisted`, `Inline_Before`, and `Inline_After` all
produce identical branchless codegen (`cset` on ARM64)
- [x] Verified existing `DevDiv_168744` regression test still passes
- [x] Verified side-effect-ful blocks are correctly excluded from the
optimization
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 24, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request May 14, 2026
…128163)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
Fixesdotnet#128044.
## Problem
createdump SIGSEGVs on Linux when generating a Heap-type minidump for a
process running interpreted code. The crash reproduces locally with the
`InterpreterStack` DumpTests debuggee and matches the CI failure that
prompted `<DumpTypes>Full</DumpTypes>` to be added as a temporary
workaround.
The faulting backtrace is:
```
#0 Thread::IsAddressInStack threads.cpp:6741
#1 Thread::EnumMemoryRegionsWorker threads.cpp:6909 (calls IsAddressInStack(currentSP))
#2 Thread::EnumMemoryRegions threads.cpp
#3 ThreadStore::EnumMemoryRegions
#4 ClrDataAccess::EnumMemDumpAllThreadsStack
#5 ClrDataAccess::EnumMemoryRegionsWorkerHeap (HEAP2-only path)
```
## Root cause
`Thread::m_pInterpThreadContext` was declared as a raw
`InterpThreadContext *`. In non-DAC code that's a normal host pointer,
but in
DAC mode the field's value is a target-process address. When
`IsAddressInStack` (a DAC-callable helper) dereferenced
`m_pInterpThreadContext->pStackStart` it read from a target-process
address
as if it were a host address, which faults inside createdump.
## Fix
Change the field type to `PTR_InterpThreadContext` (DPTR), matching the
treatment of other Thread fields like `m_pFrame`. In non-DAC builds
`DPTR(T)` is just `T*`, so there is no overhead or behavior change. In
DAC
builds the read goes through `__DPtr<T>` and marshals correctly from the
target.
Also remove the `<DumpTypes>Full</DumpTypes>` workaround on the
`InterpreterStack` DumpTests debuggee so the Heap path that originally
failed is exercised again.
## Validation
Locally reproduced the original SIGSEGV on Linux x64 with the auto-dump
mechanism (`DOTNET_DbgMiniDumpType=2` + `DOTNET_Interpreter=MethodA`)
running the `InterpreterStack` debuggee. With this fix applied,
createdump
produces a complete Heap dump (~74 MB) instead of crashing.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Jun 3, 2026
An Android production app reported a native abort while building an
X.509 chain on arm64. The available tombstone snippet showed the process
aborting in `AndroidCryptoNative_X509ChainBuild` from `pal_x509chain.c`,
with the native guard reporting that parameter `ctx` was not a valid
pointer. The report did not include a repro or full tombstone, but the
observed failure mode means managed code reached the native build entry
point with a null `X509ChainContext*`.
```
Thread
/__w/1/s/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c:113 (AndroidCryptoNative_X509ChainBuild): Parameter 'ctx' must be a valid pointer
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 31609 >>> com.app.name <<<
backtrace:
#00 pc 0x000000000002232c /system/lib64/libc.so (abort+116)
#1 pc 0x0000000000021fe8 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so
#2 pc 0x00000000000220b0 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so (AndroidCryptoNative_X509ChainBuild+88)
#3 pc 0x000000000000cfcc
```
`X509ChainContext` is created by
`AndroidCryptoNative_X509ChainCreateContext`. That initialization can
fail if Android certificate store setup or PKIX parameter construction
throws, or if required JNI global references cannot be created.
Previously, the managed Android chain path stored the returned
`SafeHandle` without checking whether context creation failed, so a
later build could pass a null native context to
`AndroidCryptoNative_X509ChainBuild` and terminate the app process.
This change makes context creation fail gracefully:
- The native create path checks Java exceptions around object creation
and method calls more consistently.
- Partial native contexts are destroyed if global-reference creation
fails.
- The Android interop wrapper checks the returned chain context
immediately, including a null safe-handle return, and throws
`CryptographicException` if initialization failed.
No regression test is included because the reliable failure modes depend
on Android platform/provider state or artificial fault injection, and a
test hook would be fragile and not representative.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Jones <kevin@vcsjones.com>
max-charlamb pushed a commit that referenced this pull request Jun 20, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 25, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 27, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@max-charlamb@elinor-fung
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[cDAC] Draft CodeVersionsTest refactor - #2

Closed
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests
Closed

[cDAC] Draft CodeVersionsTest refactor#2
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests

Conversation

@max-charlamb

Copy link
Copy Markdown
Owner

No description provided.

private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);
private Mock<IExecutionManager> mockExecutionManager = new(MockBehavior.Strict);
private Mock<ILoader> mockLoader = new(MockBehavior.Strict);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockBehavior.Strict isn't required, but I wanted to make sure I was capturing all the calls.

ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1);

// this is not quite accurate on 32 bit architectures, but it's good enough for testing
ulong addressLowBits = (ulong)address & 7;

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (ulong)address & ((ulong)_target.PointerSize - 1);, however when we set this up the target isn't created. I guess we could create the target first and pass it in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to get the pointer size off of the TargetTestHelpers for MockDescriptor.CodeVersions, which we create before doing this.

[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
{
mockExecutionManager.Setup(e => e.GetCodeBlockHandle(TargetCodePointer.Null)).Returns(() => null);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only required with MockBehavior.Strict. Otherwise this test relies on the mock returning null when there is no setup. I find this a bit confusing.


TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null;
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null;
private void AddCodeBlock(MockCodeBlockStart block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think MockCodeBlockStart and friends are still helpful? Versus having these method just take in the address, length, method desc, etc. as separate parameters.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are still helpful to encapsulate state related to a mock. I think if we removed them, it would make tests with multiple MethodDescs confusing due to keeping track of multiple similar fields.

internal class MockExecutionManager : IExecutionManager
{
private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to find it easier to reason about tests when everything is contained in the test method (explicitly sets up what it needs / no state outside the method, don't have to know that each test method actually gets a new test class instance).

Maybe try something like having the test cases explicitly create the mock(s) they need and turn the Add* methods into extension methods for Mock<...>?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make this change. I settled on having a MockExtensions class which adds the helpers as extensions on the correct generic implementation of Mock<...>.

@max-charlamb
max-charlamb changed the base branch from md-gccover to mainDecember 16, 2024 17:48
@max-charlamb
max-charlamb changed the base branch from main to md-dynamicDecember 16, 2024 17:49
@max-charlamb
max-charlamb changed the base branch from md-dynamic to mainDecember 16, 2024 17:49
@max-charlamb
max-charlamb deleted the cdac-update-cv-tests branch June 10, 2025 16:37
max-charlamb pushed a commit that referenced this pull request Mar 24, 2026
…otnet#124642)
## Summary
Fixesdotnet#123621
When a constant-folded operand appears **after** a non-constant operand
in a short-circuit `&&` expression (e.g., `v == 2 && Environment.NewLine
!= "\r\n"`), callee inlining can leave dead local stores in the return
block. The `isReturnBool` lambda in `fgFoldCondToReturnBlock` required
`hasSingleStmt()`, which caused the optimization to bail out when these
dead stores were present, resulting in suboptimal branching codegen.
### Changes
- **`src/coreclr/jit/optimizebools.cpp`**: Relax the `hasSingleStmt()`
constraint in `isReturnBool` to allow preceding statements as long as
they have no globally visible side effects
(`GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS`). This enables
`fgFoldCondToReturnBlock` to fold the conditional into a branchless
return even when dead local stores from inlining remain in the block.
### Before (ARM64, `Inline_After`)
```asm
cmp w0, #2
bne G_M4495_IG04
mov w0, #1
ret
G_M4495_IG04:
mov w0, #0
ret
```
### After (ARM64, `Inline_After`)
```asm
cmp w0, #2
cset x0, eq
ret
```
## Test plan
- [x] Added regression test `Runtime_123621` covering the original issue
pattern
- [x] Verified `Hoisted`, `Inline_Before`, and `Inline_After` all
produce identical branchless codegen (`cset` on ARM64)
- [x] Verified existing `DevDiv_168744` regression test still passes
- [x] Verified side-effect-ful blocks are correctly excluded from the
optimization
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 24, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request May 14, 2026
…128163)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
Fixesdotnet#128044.
## Problem
createdump SIGSEGVs on Linux when generating a Heap-type minidump for a
process running interpreted code. The crash reproduces locally with the
`InterpreterStack` DumpTests debuggee and matches the CI failure that
prompted `<DumpTypes>Full</DumpTypes>` to be added as a temporary
workaround.
The faulting backtrace is:
```
#0 Thread::IsAddressInStack threads.cpp:6741
#1 Thread::EnumMemoryRegionsWorker threads.cpp:6909 (calls IsAddressInStack(currentSP))
#2 Thread::EnumMemoryRegions threads.cpp
#3 ThreadStore::EnumMemoryRegions
#4 ClrDataAccess::EnumMemDumpAllThreadsStack
#5 ClrDataAccess::EnumMemoryRegionsWorkerHeap (HEAP2-only path)
```
## Root cause
`Thread::m_pInterpThreadContext` was declared as a raw
`InterpThreadContext *`. In non-DAC code that's a normal host pointer,
but in
DAC mode the field's value is a target-process address. When
`IsAddressInStack` (a DAC-callable helper) dereferenced
`m_pInterpThreadContext->pStackStart` it read from a target-process
address
as if it were a host address, which faults inside createdump.
## Fix
Change the field type to `PTR_InterpThreadContext` (DPTR), matching the
treatment of other Thread fields like `m_pFrame`. In non-DAC builds
`DPTR(T)` is just `T*`, so there is no overhead or behavior change. In
DAC
builds the read goes through `__DPtr<T>` and marshals correctly from the
target.
Also remove the `<DumpTypes>Full</DumpTypes>` workaround on the
`InterpreterStack` DumpTests debuggee so the Heap path that originally
failed is exercised again.
## Validation
Locally reproduced the original SIGSEGV on Linux x64 with the auto-dump
mechanism (`DOTNET_DbgMiniDumpType=2` + `DOTNET_Interpreter=MethodA`)
running the `InterpreterStack` debuggee. With this fix applied,
createdump
produces a complete Heap dump (~74 MB) instead of crashing.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Jun 3, 2026
An Android production app reported a native abort while building an
X.509 chain on arm64. The available tombstone snippet showed the process
aborting in `AndroidCryptoNative_X509ChainBuild` from `pal_x509chain.c`,
with the native guard reporting that parameter `ctx` was not a valid
pointer. The report did not include a repro or full tombstone, but the
observed failure mode means managed code reached the native build entry
point with a null `X509ChainContext*`.
```
Thread
/__w/1/s/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c:113 (AndroidCryptoNative_X509ChainBuild): Parameter 'ctx' must be a valid pointer
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 31609 >>> com.app.name <<<
backtrace:
#00 pc 0x000000000002232c /system/lib64/libc.so (abort+116)
#1 pc 0x0000000000021fe8 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so
#2 pc 0x00000000000220b0 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so (AndroidCryptoNative_X509ChainBuild+88)
#3 pc 0x000000000000cfcc
```
`X509ChainContext` is created by
`AndroidCryptoNative_X509ChainCreateContext`. That initialization can
fail if Android certificate store setup or PKIX parameter construction
throws, or if required JNI global references cannot be created.
Previously, the managed Android chain path stored the returned
`SafeHandle` without checking whether context creation failed, so a
later build could pass a null native context to
`AndroidCryptoNative_X509ChainBuild` and terminate the app process.
This change makes context creation fail gracefully:
- The native create path checks Java exceptions around object creation
and method calls more consistently.
- Partial native contexts are destroyed if global-reference creation
fails.
- The Android interop wrapper checks the returned chain context
immediately, including a null safe-handle return, and throws
`CryptographicException` if initialization failed.
No regression test is included because the reliable failure modes depend
on Android platform/provider state or artificial fault injection, and a
test hook would be fragile and not representative.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Jones <kevin@vcsjones.com>
max-charlamb pushed a commit that referenced this pull request Jun 20, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 25, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 27, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@max-charlamb@elinor-fung
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[cDAC] Draft CodeVersionsTest refactor - #2

Closed
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests
Closed

[cDAC] Draft CodeVersionsTest refactor#2
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests

Conversation

@max-charlamb

Copy link
Copy Markdown
Owner

No description provided.

private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);
private Mock<IExecutionManager> mockExecutionManager = new(MockBehavior.Strict);
private Mock<ILoader> mockLoader = new(MockBehavior.Strict);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockBehavior.Strict isn't required, but I wanted to make sure I was capturing all the calls.

ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1);

// this is not quite accurate on 32 bit architectures, but it's good enough for testing
ulong addressLowBits = (ulong)address & 7;

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (ulong)address & ((ulong)_target.PointerSize - 1);, however when we set this up the target isn't created. I guess we could create the target first and pass it in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to get the pointer size off of the TargetTestHelpers for MockDescriptor.CodeVersions, which we create before doing this.

[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
{
mockExecutionManager.Setup(e => e.GetCodeBlockHandle(TargetCodePointer.Null)).Returns(() => null);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only required with MockBehavior.Strict. Otherwise this test relies on the mock returning null when there is no setup. I find this a bit confusing.


TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null;
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null;
private void AddCodeBlock(MockCodeBlockStart block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think MockCodeBlockStart and friends are still helpful? Versus having these method just take in the address, length, method desc, etc. as separate parameters.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are still helpful to encapsulate state related to a mock. I think if we removed them, it would make tests with multiple MethodDescs confusing due to keeping track of multiple similar fields.

internal class MockExecutionManager : IExecutionManager
{
private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to find it easier to reason about tests when everything is contained in the test method (explicitly sets up what it needs / no state outside the method, don't have to know that each test method actually gets a new test class instance).

Maybe try something like having the test cases explicitly create the mock(s) they need and turn the Add* methods into extension methods for Mock<...>?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make this change. I settled on having a MockExtensions class which adds the helpers as extensions on the correct generic implementation of Mock<...>.

@max-charlamb
max-charlamb changed the base branch from md-gccover to mainDecember 16, 2024 17:48
@max-charlamb
max-charlamb changed the base branch from main to md-dynamicDecember 16, 2024 17:49
@max-charlamb
max-charlamb changed the base branch from md-dynamic to mainDecember 16, 2024 17:49
@max-charlamb
max-charlamb deleted the cdac-update-cv-tests branch June 10, 2025 16:37
max-charlamb pushed a commit that referenced this pull request Mar 24, 2026
…otnet#124642)
## Summary
Fixesdotnet#123621
When a constant-folded operand appears **after** a non-constant operand
in a short-circuit `&&` expression (e.g., `v == 2 && Environment.NewLine
!= "\r\n"`), callee inlining can leave dead local stores in the return
block. The `isReturnBool` lambda in `fgFoldCondToReturnBlock` required
`hasSingleStmt()`, which caused the optimization to bail out when these
dead stores were present, resulting in suboptimal branching codegen.
### Changes
- **`src/coreclr/jit/optimizebools.cpp`**: Relax the `hasSingleStmt()`
constraint in `isReturnBool` to allow preceding statements as long as
they have no globally visible side effects
(`GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS`). This enables
`fgFoldCondToReturnBlock` to fold the conditional into a branchless
return even when dead local stores from inlining remain in the block.
### Before (ARM64, `Inline_After`)
```asm
cmp w0, #2
bne G_M4495_IG04
mov w0, #1
ret
G_M4495_IG04:
mov w0, #0
ret
```
### After (ARM64, `Inline_After`)
```asm
cmp w0, #2
cset x0, eq
ret
```
## Test plan
- [x] Added regression test `Runtime_123621` covering the original issue
pattern
- [x] Verified `Hoisted`, `Inline_Before`, and `Inline_After` all
produce identical branchless codegen (`cset` on ARM64)
- [x] Verified existing `DevDiv_168744` regression test still passes
- [x] Verified side-effect-ful blocks are correctly excluded from the
optimization
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 24, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request May 14, 2026
…128163)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
Fixesdotnet#128044.
## Problem
createdump SIGSEGVs on Linux when generating a Heap-type minidump for a
process running interpreted code. The crash reproduces locally with the
`InterpreterStack` DumpTests debuggee and matches the CI failure that
prompted `<DumpTypes>Full</DumpTypes>` to be added as a temporary
workaround.
The faulting backtrace is:
```
#0 Thread::IsAddressInStack threads.cpp:6741
#1 Thread::EnumMemoryRegionsWorker threads.cpp:6909 (calls IsAddressInStack(currentSP))
#2 Thread::EnumMemoryRegions threads.cpp
#3 ThreadStore::EnumMemoryRegions
#4 ClrDataAccess::EnumMemDumpAllThreadsStack
#5 ClrDataAccess::EnumMemoryRegionsWorkerHeap (HEAP2-only path)
```
## Root cause
`Thread::m_pInterpThreadContext` was declared as a raw
`InterpThreadContext *`. In non-DAC code that's a normal host pointer,
but in
DAC mode the field's value is a target-process address. When
`IsAddressInStack` (a DAC-callable helper) dereferenced
`m_pInterpThreadContext->pStackStart` it read from a target-process
address
as if it were a host address, which faults inside createdump.
## Fix
Change the field type to `PTR_InterpThreadContext` (DPTR), matching the
treatment of other Thread fields like `m_pFrame`. In non-DAC builds
`DPTR(T)` is just `T*`, so there is no overhead or behavior change. In
DAC
builds the read goes through `__DPtr<T>` and marshals correctly from the
target.
Also remove the `<DumpTypes>Full</DumpTypes>` workaround on the
`InterpreterStack` DumpTests debuggee so the Heap path that originally
failed is exercised again.
## Validation
Locally reproduced the original SIGSEGV on Linux x64 with the auto-dump
mechanism (`DOTNET_DbgMiniDumpType=2` + `DOTNET_Interpreter=MethodA`)
running the `InterpreterStack` debuggee. With this fix applied,
createdump
produces a complete Heap dump (~74 MB) instead of crashing.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Jun 3, 2026
An Android production app reported a native abort while building an
X.509 chain on arm64. The available tombstone snippet showed the process
aborting in `AndroidCryptoNative_X509ChainBuild` from `pal_x509chain.c`,
with the native guard reporting that parameter `ctx` was not a valid
pointer. The report did not include a repro or full tombstone, but the
observed failure mode means managed code reached the native build entry
point with a null `X509ChainContext*`.
```
Thread
/__w/1/s/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c:113 (AndroidCryptoNative_X509ChainBuild): Parameter 'ctx' must be a valid pointer
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 31609 >>> com.app.name <<<
backtrace:
#00 pc 0x000000000002232c /system/lib64/libc.so (abort+116)
#1 pc 0x0000000000021fe8 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so
#2 pc 0x00000000000220b0 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so (AndroidCryptoNative_X509ChainBuild+88)
#3 pc 0x000000000000cfcc
```
`X509ChainContext` is created by
`AndroidCryptoNative_X509ChainCreateContext`. That initialization can
fail if Android certificate store setup or PKIX parameter construction
throws, or if required JNI global references cannot be created.
Previously, the managed Android chain path stored the returned
`SafeHandle` without checking whether context creation failed, so a
later build could pass a null native context to
`AndroidCryptoNative_X509ChainBuild` and terminate the app process.
This change makes context creation fail gracefully:
- The native create path checks Java exceptions around object creation
and method calls more consistently.
- Partial native contexts are destroyed if global-reference creation
fails.
- The Android interop wrapper checks the returned chain context
immediately, including a null safe-handle return, and throws
`CryptographicException` if initialization failed.
No regression test is included because the reliable failure modes depend
on Android platform/provider state or artificial fault injection, and a
test hook would be fragile and not representative.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Jones <kevin@vcsjones.com>
max-charlamb pushed a commit that referenced this pull request Jun 20, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 25, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 27, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@max-charlamb@elinor-fung
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[cDAC] Draft CodeVersionsTest refactor - #2

Closed
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests
Closed

[cDAC] Draft CodeVersionsTest refactor#2
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests

Conversation

@max-charlamb

Copy link
Copy Markdown
Owner

No description provided.

private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);
private Mock<IExecutionManager> mockExecutionManager = new(MockBehavior.Strict);
private Mock<ILoader> mockLoader = new(MockBehavior.Strict);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockBehavior.Strict isn't required, but I wanted to make sure I was capturing all the calls.

ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1);

// this is not quite accurate on 32 bit architectures, but it's good enough for testing
ulong addressLowBits = (ulong)address & 7;

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (ulong)address & ((ulong)_target.PointerSize - 1);, however when we set this up the target isn't created. I guess we could create the target first and pass it in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to get the pointer size off of the TargetTestHelpers for MockDescriptor.CodeVersions, which we create before doing this.

[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
{
mockExecutionManager.Setup(e => e.GetCodeBlockHandle(TargetCodePointer.Null)).Returns(() => null);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only required with MockBehavior.Strict. Otherwise this test relies on the mock returning null when there is no setup. I find this a bit confusing.


TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null;
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null;
private void AddCodeBlock(MockCodeBlockStart block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think MockCodeBlockStart and friends are still helpful? Versus having these method just take in the address, length, method desc, etc. as separate parameters.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are still helpful to encapsulate state related to a mock. I think if we removed them, it would make tests with multiple MethodDescs confusing due to keeping track of multiple similar fields.

internal class MockExecutionManager : IExecutionManager
{
private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to find it easier to reason about tests when everything is contained in the test method (explicitly sets up what it needs / no state outside the method, don't have to know that each test method actually gets a new test class instance).

Maybe try something like having the test cases explicitly create the mock(s) they need and turn the Add* methods into extension methods for Mock<...>?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make this change. I settled on having a MockExtensions class which adds the helpers as extensions on the correct generic implementation of Mock<...>.

@max-charlamb
max-charlamb changed the base branch from md-gccover to mainDecember 16, 2024 17:48
@max-charlamb
max-charlamb changed the base branch from main to md-dynamicDecember 16, 2024 17:49
@max-charlamb
max-charlamb changed the base branch from md-dynamic to mainDecember 16, 2024 17:49
@max-charlamb
max-charlamb deleted the cdac-update-cv-tests branch June 10, 2025 16:37
max-charlamb pushed a commit that referenced this pull request Mar 24, 2026
…otnet#124642)
## Summary
Fixesdotnet#123621
When a constant-folded operand appears **after** a non-constant operand
in a short-circuit `&&` expression (e.g., `v == 2 && Environment.NewLine
!= "\r\n"`), callee inlining can leave dead local stores in the return
block. The `isReturnBool` lambda in `fgFoldCondToReturnBlock` required
`hasSingleStmt()`, which caused the optimization to bail out when these
dead stores were present, resulting in suboptimal branching codegen.
### Changes
- **`src/coreclr/jit/optimizebools.cpp`**: Relax the `hasSingleStmt()`
constraint in `isReturnBool` to allow preceding statements as long as
they have no globally visible side effects
(`GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS`). This enables
`fgFoldCondToReturnBlock` to fold the conditional into a branchless
return even when dead local stores from inlining remain in the block.
### Before (ARM64, `Inline_After`)
```asm
cmp w0, #2
bne G_M4495_IG04
mov w0, #1
ret
G_M4495_IG04:
mov w0, #0
ret
```
### After (ARM64, `Inline_After`)
```asm
cmp w0, #2
cset x0, eq
ret
```
## Test plan
- [x] Added regression test `Runtime_123621` covering the original issue
pattern
- [x] Verified `Hoisted`, `Inline_Before`, and `Inline_After` all
produce identical branchless codegen (`cset` on ARM64)
- [x] Verified existing `DevDiv_168744` regression test still passes
- [x] Verified side-effect-ful blocks are correctly excluded from the
optimization
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 24, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request May 14, 2026
…128163)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
Fixesdotnet#128044.
## Problem
createdump SIGSEGVs on Linux when generating a Heap-type minidump for a
process running interpreted code. The crash reproduces locally with the
`InterpreterStack` DumpTests debuggee and matches the CI failure that
prompted `<DumpTypes>Full</DumpTypes>` to be added as a temporary
workaround.
The faulting backtrace is:
```
#0 Thread::IsAddressInStack threads.cpp:6741
#1 Thread::EnumMemoryRegionsWorker threads.cpp:6909 (calls IsAddressInStack(currentSP))
#2 Thread::EnumMemoryRegions threads.cpp
#3 ThreadStore::EnumMemoryRegions
#4 ClrDataAccess::EnumMemDumpAllThreadsStack
#5 ClrDataAccess::EnumMemoryRegionsWorkerHeap (HEAP2-only path)
```
## Root cause
`Thread::m_pInterpThreadContext` was declared as a raw
`InterpThreadContext *`. In non-DAC code that's a normal host pointer,
but in
DAC mode the field's value is a target-process address. When
`IsAddressInStack` (a DAC-callable helper) dereferenced
`m_pInterpThreadContext->pStackStart` it read from a target-process
address
as if it were a host address, which faults inside createdump.
## Fix
Change the field type to `PTR_InterpThreadContext` (DPTR), matching the
treatment of other Thread fields like `m_pFrame`. In non-DAC builds
`DPTR(T)` is just `T*`, so there is no overhead or behavior change. In
DAC
builds the read goes through `__DPtr<T>` and marshals correctly from the
target.
Also remove the `<DumpTypes>Full</DumpTypes>` workaround on the
`InterpreterStack` DumpTests debuggee so the Heap path that originally
failed is exercised again.
## Validation
Locally reproduced the original SIGSEGV on Linux x64 with the auto-dump
mechanism (`DOTNET_DbgMiniDumpType=2` + `DOTNET_Interpreter=MethodA`)
running the `InterpreterStack` debuggee. With this fix applied,
createdump
produces a complete Heap dump (~74 MB) instead of crashing.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Jun 3, 2026
An Android production app reported a native abort while building an
X.509 chain on arm64. The available tombstone snippet showed the process
aborting in `AndroidCryptoNative_X509ChainBuild` from `pal_x509chain.c`,
with the native guard reporting that parameter `ctx` was not a valid
pointer. The report did not include a repro or full tombstone, but the
observed failure mode means managed code reached the native build entry
point with a null `X509ChainContext*`.
```
Thread
/__w/1/s/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c:113 (AndroidCryptoNative_X509ChainBuild): Parameter 'ctx' must be a valid pointer
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 31609 >>> com.app.name <<<
backtrace:
#00 pc 0x000000000002232c /system/lib64/libc.so (abort+116)
#1 pc 0x0000000000021fe8 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so
#2 pc 0x00000000000220b0 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so (AndroidCryptoNative_X509ChainBuild+88)
#3 pc 0x000000000000cfcc
```
`X509ChainContext` is created by
`AndroidCryptoNative_X509ChainCreateContext`. That initialization can
fail if Android certificate store setup or PKIX parameter construction
throws, or if required JNI global references cannot be created.
Previously, the managed Android chain path stored the returned
`SafeHandle` without checking whether context creation failed, so a
later build could pass a null native context to
`AndroidCryptoNative_X509ChainBuild` and terminate the app process.
This change makes context creation fail gracefully:
- The native create path checks Java exceptions around object creation
and method calls more consistently.
- Partial native contexts are destroyed if global-reference creation
fails.
- The Android interop wrapper checks the returned chain context
immediately, including a null safe-handle return, and throws
`CryptographicException` if initialization failed.
No regression test is included because the reliable failure modes depend
on Android platform/provider state or artificial fault injection, and a
test hook would be fragile and not representative.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Jones <kevin@vcsjones.com>
max-charlamb pushed a commit that referenced this pull request Jun 20, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 25, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 27, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@max-charlamb@elinor-fung
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[cDAC] Draft CodeVersionsTest refactor - #2

Closed
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests
Closed

[cDAC] Draft CodeVersionsTest refactor#2
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests

Conversation

@max-charlamb

Copy link
Copy Markdown
Owner

No description provided.

private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);
private Mock<IExecutionManager> mockExecutionManager = new(MockBehavior.Strict);
private Mock<ILoader> mockLoader = new(MockBehavior.Strict);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockBehavior.Strict isn't required, but I wanted to make sure I was capturing all the calls.

ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1);

// this is not quite accurate on 32 bit architectures, but it's good enough for testing
ulong addressLowBits = (ulong)address & 7;

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (ulong)address & ((ulong)_target.PointerSize - 1);, however when we set this up the target isn't created. I guess we could create the target first and pass it in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to get the pointer size off of the TargetTestHelpers for MockDescriptor.CodeVersions, which we create before doing this.

[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
{
mockExecutionManager.Setup(e => e.GetCodeBlockHandle(TargetCodePointer.Null)).Returns(() => null);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only required with MockBehavior.Strict. Otherwise this test relies on the mock returning null when there is no setup. I find this a bit confusing.


TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null;
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null;
private void AddCodeBlock(MockCodeBlockStart block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think MockCodeBlockStart and friends are still helpful? Versus having these method just take in the address, length, method desc, etc. as separate parameters.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are still helpful to encapsulate state related to a mock. I think if we removed them, it would make tests with multiple MethodDescs confusing due to keeping track of multiple similar fields.

internal class MockExecutionManager : IExecutionManager
{
private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to find it easier to reason about tests when everything is contained in the test method (explicitly sets up what it needs / no state outside the method, don't have to know that each test method actually gets a new test class instance).

Maybe try something like having the test cases explicitly create the mock(s) they need and turn the Add* methods into extension methods for Mock<...>?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make this change. I settled on having a MockExtensions class which adds the helpers as extensions on the correct generic implementation of Mock<...>.

@max-charlamb
max-charlamb changed the base branch from md-gccover to mainDecember 16, 2024 17:48
@max-charlamb
max-charlamb changed the base branch from main to md-dynamicDecember 16, 2024 17:49
@max-charlamb
max-charlamb changed the base branch from md-dynamic to mainDecember 16, 2024 17:49
@max-charlamb
max-charlamb deleted the cdac-update-cv-tests branch June 10, 2025 16:37
max-charlamb pushed a commit that referenced this pull request Mar 24, 2026
…otnet#124642)
## Summary
Fixesdotnet#123621
When a constant-folded operand appears **after** a non-constant operand
in a short-circuit `&&` expression (e.g., `v == 2 && Environment.NewLine
!= "\r\n"`), callee inlining can leave dead local stores in the return
block. The `isReturnBool` lambda in `fgFoldCondToReturnBlock` required
`hasSingleStmt()`, which caused the optimization to bail out when these
dead stores were present, resulting in suboptimal branching codegen.
### Changes
- **`src/coreclr/jit/optimizebools.cpp`**: Relax the `hasSingleStmt()`
constraint in `isReturnBool` to allow preceding statements as long as
they have no globally visible side effects
(`GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS`). This enables
`fgFoldCondToReturnBlock` to fold the conditional into a branchless
return even when dead local stores from inlining remain in the block.
### Before (ARM64, `Inline_After`)
```asm
cmp w0, #2
bne G_M4495_IG04
mov w0, #1
ret
G_M4495_IG04:
mov w0, #0
ret
```
### After (ARM64, `Inline_After`)
```asm
cmp w0, #2
cset x0, eq
ret
```
## Test plan
- [x] Added regression test `Runtime_123621` covering the original issue
pattern
- [x] Verified `Hoisted`, `Inline_Before`, and `Inline_After` all
produce identical branchless codegen (`cset` on ARM64)
- [x] Verified existing `DevDiv_168744` regression test still passes
- [x] Verified side-effect-ful blocks are correctly excluded from the
optimization
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 24, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request May 14, 2026
…128163)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
Fixesdotnet#128044.
## Problem
createdump SIGSEGVs on Linux when generating a Heap-type minidump for a
process running interpreted code. The crash reproduces locally with the
`InterpreterStack` DumpTests debuggee and matches the CI failure that
prompted `<DumpTypes>Full</DumpTypes>` to be added as a temporary
workaround.
The faulting backtrace is:
```
#0 Thread::IsAddressInStack threads.cpp:6741
#1 Thread::EnumMemoryRegionsWorker threads.cpp:6909 (calls IsAddressInStack(currentSP))
#2 Thread::EnumMemoryRegions threads.cpp
#3 ThreadStore::EnumMemoryRegions
#4 ClrDataAccess::EnumMemDumpAllThreadsStack
#5 ClrDataAccess::EnumMemoryRegionsWorkerHeap (HEAP2-only path)
```
## Root cause
`Thread::m_pInterpThreadContext` was declared as a raw
`InterpThreadContext *`. In non-DAC code that's a normal host pointer,
but in
DAC mode the field's value is a target-process address. When
`IsAddressInStack` (a DAC-callable helper) dereferenced
`m_pInterpThreadContext->pStackStart` it read from a target-process
address
as if it were a host address, which faults inside createdump.
## Fix
Change the field type to `PTR_InterpThreadContext` (DPTR), matching the
treatment of other Thread fields like `m_pFrame`. In non-DAC builds
`DPTR(T)` is just `T*`, so there is no overhead or behavior change. In
DAC
builds the read goes through `__DPtr<T>` and marshals correctly from the
target.
Also remove the `<DumpTypes>Full</DumpTypes>` workaround on the
`InterpreterStack` DumpTests debuggee so the Heap path that originally
failed is exercised again.
## Validation
Locally reproduced the original SIGSEGV on Linux x64 with the auto-dump
mechanism (`DOTNET_DbgMiniDumpType=2` + `DOTNET_Interpreter=MethodA`)
running the `InterpreterStack` debuggee. With this fix applied,
createdump
produces a complete Heap dump (~74 MB) instead of crashing.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Jun 3, 2026
An Android production app reported a native abort while building an
X.509 chain on arm64. The available tombstone snippet showed the process
aborting in `AndroidCryptoNative_X509ChainBuild` from `pal_x509chain.c`,
with the native guard reporting that parameter `ctx` was not a valid
pointer. The report did not include a repro or full tombstone, but the
observed failure mode means managed code reached the native build entry
point with a null `X509ChainContext*`.
```
Thread
/__w/1/s/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c:113 (AndroidCryptoNative_X509ChainBuild): Parameter 'ctx' must be a valid pointer
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 31609 >>> com.app.name <<<
backtrace:
#00 pc 0x000000000002232c /system/lib64/libc.so (abort+116)
#1 pc 0x0000000000021fe8 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so
#2 pc 0x00000000000220b0 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so (AndroidCryptoNative_X509ChainBuild+88)
#3 pc 0x000000000000cfcc
```
`X509ChainContext` is created by
`AndroidCryptoNative_X509ChainCreateContext`. That initialization can
fail if Android certificate store setup or PKIX parameter construction
throws, or if required JNI global references cannot be created.
Previously, the managed Android chain path stored the returned
`SafeHandle` without checking whether context creation failed, so a
later build could pass a null native context to
`AndroidCryptoNative_X509ChainBuild` and terminate the app process.
This change makes context creation fail gracefully:
- The native create path checks Java exceptions around object creation
and method calls more consistently.
- Partial native contexts are destroyed if global-reference creation
fails.
- The Android interop wrapper checks the returned chain context
immediately, including a null safe-handle return, and throws
`CryptographicException` if initialization failed.
No regression test is included because the reliable failure modes depend
on Android platform/provider state or artificial fault injection, and a
test hook would be fragile and not representative.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Jones <kevin@vcsjones.com>
max-charlamb pushed a commit that referenced this pull request Jun 20, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 25, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 27, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@max-charlamb@elinor-fung
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[cDAC] Draft CodeVersionsTest refactor - #2

Closed
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests
Closed

[cDAC] Draft CodeVersionsTest refactor#2
max-charlamb wants to merge 1 commit into
mainfrom
cdac-update-cv-tests

Conversation

@max-charlamb

Copy link
Copy Markdown
Owner

No description provided.

private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);
private Mock<IExecutionManager> mockExecutionManager = new(MockBehavior.Strict);
private Mock<ILoader> mockLoader = new(MockBehavior.Strict);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockBehavior.Strict isn't required, but I wanted to make sure I was capturing all the calls.

ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1);

// this is not quite accurate on 32 bit architectures, but it's good enough for testing
ulong addressLowBits = (ulong)address & 7;

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (ulong)address & ((ulong)_target.PointerSize - 1);, however when we set this up the target isn't created. I guess we could create the target first and pass it in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to get the pointer size off of the TargetTestHelpers for MockDescriptor.CodeVersions, which we create before doing this.

[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
{
mockExecutionManager.Setup(e => e.GetCodeBlockHandle(TargetCodePointer.Null)).Returns(() => null);

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only required with MockBehavior.Strict. Otherwise this test relies on the mock returning null when there is no setup. I find this a bit confusing.


TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null;
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null;
private void AddCodeBlock(MockCodeBlockStart block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think MockCodeBlockStart and friends are still helpful? Versus having these method just take in the address, length, method desc, etc. as separate parameters.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are still helpful to encapsulate state related to a mock. I think if we removed them, it would make tests with multiple MethodDescs confusing due to keeping track of multiple similar fields.

internal class MockExecutionManager : IExecutionManager
{
private IReadOnlyCollection<MockCodeBlockStart> _codeBlocks;
private Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem = new(MockBehavior.Strict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to find it easier to reason about tests when everything is contained in the test method (explicitly sets up what it needs / no state outside the method, don't have to know that each test method actually gets a new test class instance).

Maybe try something like having the test cases explicitly create the mock(s) they need and turn the Add* methods into extension methods for Mock<...>?

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make this change. I settled on having a MockExtensions class which adds the helpers as extensions on the correct generic implementation of Mock<...>.

@max-charlamb
max-charlamb changed the base branch from md-gccover to mainDecember 16, 2024 17:48
@max-charlamb
max-charlamb changed the base branch from main to md-dynamicDecember 16, 2024 17:49
@max-charlamb
max-charlamb changed the base branch from md-dynamic to mainDecember 16, 2024 17:49
@max-charlamb
max-charlamb deleted the cdac-update-cv-tests branch June 10, 2025 16:37
max-charlamb pushed a commit that referenced this pull request Mar 24, 2026
…otnet#124642)
## Summary
Fixesdotnet#123621
When a constant-folded operand appears **after** a non-constant operand
in a short-circuit `&&` expression (e.g., `v == 2 && Environment.NewLine
!= "\r\n"`), callee inlining can leave dead local stores in the return
block. The `isReturnBool` lambda in `fgFoldCondToReturnBlock` required
`hasSingleStmt()`, which caused the optimization to bail out when these
dead stores were present, resulting in suboptimal branching codegen.
### Changes
- **`src/coreclr/jit/optimizebools.cpp`**: Relax the `hasSingleStmt()`
constraint in `isReturnBool` to allow preceding statements as long as
they have no globally visible side effects
(`GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS`). This enables
`fgFoldCondToReturnBlock` to fold the conditional into a branchless
return even when dead local stores from inlining remain in the block.
### Before (ARM64, `Inline_After`)
```asm
cmp w0, #2
bne G_M4495_IG04
mov w0, #1
ret
G_M4495_IG04:
mov w0, #0
ret
```
### After (ARM64, `Inline_After`)
```asm
cmp w0, #2
cset x0, eq
ret
```
## Test plan
- [x] Added regression test `Runtime_123621` covering the original issue
pattern
- [x] Verified `Hoisted`, `Inline_Before`, and `Inline_After` all
produce identical branchless codegen (`cset` on ARM64)
- [x] Verified existing `DevDiv_168744` regression test still passes
- [x] Verified side-effect-ful blocks are correctly excluded from the
optimization
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 22, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 23, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Apr 24, 2026
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request May 14, 2026
…128163)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
Fixesdotnet#128044.
## Problem
createdump SIGSEGVs on Linux when generating a Heap-type minidump for a
process running interpreted code. The crash reproduces locally with the
`InterpreterStack` DumpTests debuggee and matches the CI failure that
prompted `<DumpTypes>Full</DumpTypes>` to be added as a temporary
workaround.
The faulting backtrace is:
```
#0 Thread::IsAddressInStack threads.cpp:6741
#1 Thread::EnumMemoryRegionsWorker threads.cpp:6909 (calls IsAddressInStack(currentSP))
#2 Thread::EnumMemoryRegions threads.cpp
#3 ThreadStore::EnumMemoryRegions
#4 ClrDataAccess::EnumMemDumpAllThreadsStack
#5 ClrDataAccess::EnumMemoryRegionsWorkerHeap (HEAP2-only path)
```
## Root cause
`Thread::m_pInterpThreadContext` was declared as a raw
`InterpThreadContext *`. In non-DAC code that's a normal host pointer,
but in
DAC mode the field's value is a target-process address. When
`IsAddressInStack` (a DAC-callable helper) dereferenced
`m_pInterpThreadContext->pStackStart` it read from a target-process
address
as if it were a host address, which faults inside createdump.
## Fix
Change the field type to `PTR_InterpThreadContext` (DPTR), matching the
treatment of other Thread fields like `m_pFrame`. In non-DAC builds
`DPTR(T)` is just `T*`, so there is no overhead or behavior change. In
DAC
builds the read goes through `__DPtr<T>` and marshals correctly from the
target.
Also remove the `<DumpTypes>Full</DumpTypes>` workaround on the
`InterpreterStack` DumpTests debuggee so the Heap path that originally
failed is exercised again.
## Validation
Locally reproduced the original SIGSEGV on Linux x64 with the auto-dump
mechanism (`DOTNET_DbgMiniDumpType=2` + `DOTNET_Interpreter=MethodA`)
running the `InterpreterStack` debuggee. With this fix applied,
createdump
produces a complete Heap dump (~74 MB) instead of crashing.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit that referenced this pull request Jun 3, 2026
An Android production app reported a native abort while building an
X.509 chain on arm64. The available tombstone snippet showed the process
aborting in `AndroidCryptoNative_X509ChainBuild` from `pal_x509chain.c`,
with the native guard reporting that parameter `ctx` was not a valid
pointer. The report did not include a repro or full tombstone, but the
observed failure mode means managed code reached the native build entry
point with a null `X509ChainContext*`.
```
Thread
/__w/1/s/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c:113 (AndroidCryptoNative_X509ChainBuild): Parameter 'ctx' must be a valid pointer
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 31609 >>> com.app.name <<<
backtrace:
#00 pc 0x000000000002232c /system/lib64/libc.so (abort+116)
#1 pc 0x0000000000021fe8 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so
#2 pc 0x00000000000220b0 [removed]-KwPZdoEumri00C7kBm3pQw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so (AndroidCryptoNative_X509ChainBuild+88)
#3 pc 0x000000000000cfcc
```
`X509ChainContext` is created by
`AndroidCryptoNative_X509ChainCreateContext`. That initialization can
fail if Android certificate store setup or PKIX parameter construction
throws, or if required JNI global references cannot be created.
Previously, the managed Android chain path stored the returned
`SafeHandle` without checking whether context creation failed, so a
later build could pass a null native context to
`AndroidCryptoNative_X509ChainBuild` and terminate the app process.
This change makes context creation fail gracefully:
- The native create path checks Java exceptions around object creation
and method calls more consistently.
- Partial native contexts are destroyed if global-reference creation
fails.
- The Android interop wrapper checks the returned chain context
immediately, including a null safe-handle return, and throws
`CryptographicException` if initialization failed.
No regression test is included because the reliable failure modes depend
on Android platform/provider state or artificial fault injection, and a
test hook would be fragile and not representative.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Jones <kevin@vcsjones.com>
max-charlamb pushed a commit that referenced this pull request Jun 20, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 25, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
max-charlamb pushed a commit that referenced this pull request Jun 27, 2026
Three issues prevented x86 stress from passing in CI (build 1473187):
1. Leftover Console.WriteLine debug output in GCArgTable.cs flooded the
Helix log with thousands of "CallArgCount/CallPndTabCnt/lastSkip/stkOffs"
lines and made test triage impossible. Removed all four diagnostic
writes plus their now-dead locals (stkOffs/lowBit, lastSkip+param,
callPndTabSize, callPndTab read-without-use).
2. CalculatePushedArgSizeAt threw "Unsupported gc transition type"
(InvalidOperationException, HRESULT 0x80131509) whenever an ESP-frame
walk hit a CalleeSavedRegister transition (emitted by the partial-
interrupt no-EBP decoder, case 7 / 0x04). Native scanArgRegTable
treats this tag as informational (no effect on outgoing-arg depth);
the cDAC switch was missing the case so every ESP-frame stack with
a callee-saved-reg tag aborted GetStackReferences with hr=0x80131509.
This was the source of 18-70 [FAIL] entries per debuggee in CI.
3. AddressFromGCRefMapPos used the same forward-from-FirstGCRefMapSlot
formula on every architecture, but x86's TransitionBlock lays out
its two argument registers (EDX/ECX) backward via
ENUM_ARGUMENT_REGISTERS_BACKWARD. Native OffsetFromGCRefMapPos
(frames.cpp) reverses pos 0/1 within the arg-regs area on x86.
Without this fix, ExternalMethodFrame GCRefMap-driven scans reported
the wrong arg-reg slot for the first two positions. Stack-arg
positions (pos >= 2) remain forward, matching native.
Local PInvoke debuggee under DOTNET_CdacStress=0x001:
Before fix#1+#2: 70 fail / 64926 matched / 0 mismatched (every
[FAIL] was an InvalidOperationException, not a real mismatch).
After #1+#2: 2 fail / 66250 matched / 2 mismatched.
After all three: 2 fail (remaining is a separate x86 GCRefMap
decode bug in pos >= 2 stack args, tracked as follow-up).
5 of 9 debuggees now report 0 failures and 0 mismatches.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@max-charlamb@elinor-fung