Skip to content

[cDAC] Fix bugs in x86 unwinder - #132169

Merged
rcj1 merged 3 commits into
dotnet:mainfrom
rcj1:x86-unwinder-native-parity
Aug 12, 2026
Merged

rcj1 merged 3 commits into
dotnet:mainfrom
rcj1:x86-unwinder-native-parity

Conversation

@rcj1

@rcj1 rcj1 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Fix a few discrepancies in the x86 unwinder implementation.

@rcj1 rcj1 self-assigned this Aug 11, 2026
Copilot AI lite review requested due to automatic review settings August 11, 2026 21:01
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@rcj1
rcj1 enabled auto-merge (squash) August 11, 2026 21:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the cDAC x86 stack unwinder logic to better match expected x86 prolog/epilog semantics and instruction decoding, correcting several discrepancies that can lead to incorrect register/stack reconstruction during unwinding.

Changes:

  • Fix callee-saved register push tracking in ESP-frame prolog unwinding by iterating in the correct push order.
  • Correct EBP/double-align saved-register pointer math and fix EBP-frame prolog ESP restoration to point at the return address slot.
  • Fix instruction decoding helpers (ModRM byte check) and make immediate “compression” detection correctly handle signed imm8 encodings.
Show a summary per file
File Description
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs Corrects x86 unwind mechanics and instruction decoding to produce accurate context reconstruction.

Review details

Suppressed comments (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs:597

  • These are subtle behavioral fixes in the x86 unwinder (e.g., EBP-frame prolog ESP computation, signed-imm8 compression, and instruction decoding asserts), but there doesn’t appear to be any unit test coverage for x86 unwinding. Wasm has targeted unwinder unit tests (WasmUnwinderTests) using TestPlaceholderTarget; adding a small x86 counterpart that exercises at least one EBP-frame prolog and one SKIP_ARITH_REG(-8) path would help prevent regressions.
        /* The caller's saved EBP is pointed to by our EBP */
        context.Ebp = (uint)_target.ReadPointer(curEBP);
        context.Esp = curEBP + _pointerSize;

        /* Stack pointer points to return address */
        context.Eip = (uint)_target.ReadPointer(context.Esp);
    }
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

@noahfalk noahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If one doesn't already exist, could you have copilot create a cDAC unit test that validates unwinding using some mocked register and stack data?

Co-authored-by: Copilot <[email protected]>

Copilot-Session: 11b73ad8-c726-42c3-bc38-a8360c68b4de
Copilot AI review requested due to automatic review settings August 11, 2026 23:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (1)

src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs:22

  • The PR changes multiple x86 unwinder code paths (ESP-frame prolog reg tracking, double-align saved-reg pointer adjustment, imm8/imm32 decoding via CAN_COMPRESS, and mov-reg-reg decoding), but the only new unit test exercises the EBP-frame prolog case. I couldn’t find any other tests referencing X86Unwinder, so regressions in the other fixed paths would go untested. Consider adding targeted regression tests for (1) an ESP-frame prolog with partial callee-saved pushes, and (2) a double-aligned EBP frame where (curEBP & 0x04) != 0, plus at least one prolog containing a stack adjustment that requires the imm32 form (so CAN_COMPRESS returns false).
    [Fact]
    public void X86Unwind_EbpProlog_RestoresCallerContext()
    {
        MockTarget.Architecture arch = new() { IsLittleEndian = true, Is64Bit = false };
        TestPlaceholderTarget.Builder targetBuilder = new(arch);
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings August 12, 2026 04:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs:374

  • This change fixes the order used to walk already-executed push instructions in an ESP-frame prolog, which is subtle and easy to regress. There isn’t currently a unit test covering the ESP-frame + in-prolog unwind path; adding one would help ensure this stays correct (similar to the new EBP-prolog test, but with an ESP-frame header and no push ebp; mov ebp, esp).
        TargetPointer savedRegPtr = esp;

        // Find out how many callee-saved regs have already been pushed
        foreach (RegMask regMask in registerOrder.Reverse())
        {
            if (!gcInfo.SavedRegsMask.HasFlag(regMask))

src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs:50

  • The test writes a value into the would-be saved-EBX stack slot even though, at RelativeOffset=5 (EIP at the push ebx), that push has not executed yet. Using the same value as CurrentEbx can mask bugs where the unwinder incorrectly restores EBX from the stack at this point. Consider writing a distinct sentinel value so the test fails if EBX is restored prematurely.
        byte[] stack = new byte[0x20];
        helpers.Write(stack.AsSpan(0x04, sizeof(uint)), CurrentEbx);
        helpers.Write(stack.AsSpan(0x08, sizeof(uint)), SavedEsi);
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@rcj1
rcj1 merged commit 656d1d6 into dotnet:main Aug 12, 2026
71 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Aug 13, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants