Skip to content

[cDAC] Support x86 stackwalking #116072

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

max-charlamb
Copy link
Contributor

@max-charlamb max-charlamb commented May 28, 2025

Changes

IExecutionManager

GetFuncletStartAddress

  • Relatively simple, uses the existing GetUnwindInfo implementation.
  • Added docs

GetGCInfo

  • Uses new datadescriptor RealCodeHeader::GCInfo
  • Added doc explaining how this is found for R2R and jitted code.
  • R2R section still needs work to handle platforms != x86 due to complexities calculating unwind code size.

GetRelativeOffset

  • Added docs, simple API to extract field off of CodeBlock, similar to GetMethodDesc and GetStartAddress

Extension IsFunclet

  • Implemented simply in terms of IExecutionManager.StartAddress(cbh) != IExecutionManager.GetFuncletStartAddress(cbh)
  • Added docs

IStackWalk

Added support for X86 Platform

Managed Implementation of X86 GCInfo decoding

  • Based on src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/

Managed Implementation of X86 Unwinder

  • Based on implementation in gc_unwind_x86.inl
  • GCInfo uses
    • InfoHdr to decode the header. This involves reading an initial header from a lookup table then modifying with a series of instructions.
    • GCSlotTable
    • NoGcRegionTable
    • GCTransition
    • CallPattern to decode common call pattern encodings. Uses look up table similar to InfoHdr.

Added X86FrameHandler

  • The following Frames were verified:
    • SoftwareExceptionFrame
    • InlinedCallFrame
    • TransitionFrames
    • FaultingExceptionFrame
  • Frames not currently verified/implemented:
    • ResumableFrame
    • HijackFrame
    • TailCallFrame

Questions

GCInfo Version

How do we deal with this? Currently the runtime looks at the major version to determine what version for R2R code. I think we should parse this, but not support old versions
Need to add constant in doc file

Copy link
Contributor

Tagging subscribers to this area: @tommcdon
See info in area-owners.md if you want to be subscribed.

@filipnavara
Copy link
Member

Currently the runtime looks at the major version to determine what version for R2R code. I think we should parse this, but not support old versions

We recently switched x86 to use funclets which is a major R2R breaking change. I feel that there's no point in supporting anything older, both cDAC and x86/funclets are on the same shipping schedule.

@@ -163,7 +163,7 @@ public class InfoHdrDecoder {
private const uint HAS_UNTRACKED = 0xFFFFFFFF;
private const uint HAS_GS_COOKIE_OFFSET = 0xFFFFFFFF;
private const uint HAS_SYNC_OFFSET = 0xFFFFFFFF;
private const uint HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFF;
private const uint HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFF; // is this wrong??
Copy link
Member

Choose a reason for hiding this comment

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

No, it's not wrong. These are just placeholders during header parsing. The x86 GC info has a bunch of optional flags (sometimes encoded as diff but let's not go there). One of the flags - FLIP_REV_PINVOKE_FRAME - flips whether the REV_PINVOKE_FRAME_OFFSET exists in particular header. If it exists then the sentinel value HAS_REV_PINVOKE_FRAME_OFFSET is used for a moment. Once the first part of header is read it is followed by all the optional fields and then HAS_REV_PINVOKE_FRAME_OFFSET gets replaced by reading the real value.

This constant (and the others around it) are not used after the whole header is read.

Copy link
Member

@filipnavara filipnavara May 28, 2025

Choose a reason for hiding this comment

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

Oh, you probably meant that it's wrong because the value in the C++ code is different... yeah, that's a fair point. The managed decoder completely lacks the INVALID_REV_PINVOKE_OFFSET constant and the zero value is valid (and thus it cannot currently be distinguished from the invalid one).

Copy link
Contributor Author

@max-charlamb max-charlamb May 29, 2025

Choose a reason for hiding this comment

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

Yes.

I think this R2R GCDecoder handles the case where there isn't a Reverse Pinvoke incorrectly. The InfoHdr should have a value of INVALID_REV_PINVOKE_OFFSET = 0xFFFFFFFF, but it ends up with a value of 0x0. I don't think this is a huge deal, but something that impacts the unwinder logic.

edit: fixed wrong value

Copy link
Member

Choose a reason for hiding this comment

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

You're right, this should be fixed. I think the native code uses HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFE and INVALID_REV_PINVOKE_OFFSET = 0xFFFFFFFF. The actual values are not part of the format and they are both invalid so it doesn't really matter except for consistency.

@max-charlamb
Copy link
Contributor Author

/azp run runtime-diagnostics

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@@ -163,7 +163,7 @@ public class InfoHdrDecoder {
private const uint HAS_UNTRACKED = 0xFFFFFFFF;
private const uint HAS_GS_COOKIE_OFFSET = 0xFFFFFFFF;
private const uint HAS_SYNC_OFFSET = 0xFFFFFFFF;
private const uint HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFF;
private const uint HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFF; // is this wrong??
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private const uint HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFF; // is this wrong??
private const uint HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFE;


private void CalculateDepth()
{
using StreamWriter outputFile = new StreamWriter("C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt", true);
Copy link
Member

Choose a reason for hiding this comment

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

Just a reminder to remove the debug prints before it goes for review. ;-)

@max-charlamb
Copy link
Contributor Author

/azp run runtime-diagnostics

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Comment on lines +158 to +160
uint unwindDataSize = sizeof(uint); // TODO(cdac): This is platform specific and maybe needs its own contract. Current value is for x86
gcInfo = unwindInfo + unwindDataSize;
gcVersion = GCINFO_VERSION; // TODO(cdac): This depends on the major version of the runtime.
Copy link
Member

Choose a reason for hiding this comment

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

I think the best way to handle this versioning would be to split it out into its own contract. That way we can use the contract versioning mechanism to handle versioning the format.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current contract versioning mechanism wouldn't exactly fit, but could be adapted. Multiple GCInfo formats could potentially exist within a project (old R2R code). I don't know if this is a supported scenario.

Copy link
Member

Choose a reason for hiding this comment

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

I don't know if this is a supported scenario.

Generally speaking it is supported by the runtime. There's only enforcement of minimum R2R format version, and IIRC version 2 and 3 of GC info could co-exist in the same process for a while.

Copy link
Member

Choose a reason for hiding this comment

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

Im pretty sure that versioning the GC Info format is a backwards-incompatible change for R2R (and forces a minimum major version bump). There's only one decoder in the runtime and SOS only has a copy of the most recent GCInfo layout.

Copy link
Member

Choose a reason for hiding this comment

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

GC info version 2 and 3 had the same format. IIRC they differed in some offset adjustment and I'm somewhat convinced that they did co-exist for a moment without a minimum R2R version bump. I think there are some GC info version changes that are backwards compatible even if it was rare. That said, I am happy to defer to the subject expert on this one - @VSadov - who probably remembers the exact details of the version changes he has made.

Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to share the GC Info decoding source between ILCompiler.Reflection.ReadyToRun and the cdac? Like providing an abstraction for reading memory and including the source in both projects (like how the various ILCompiler projects include various source files instead of everything being factored into specific libraries).

Copy link
Member

Choose a reason for hiding this comment

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

I think it's a path worth exploring. We don't often make changes to the GC info format but it happens, and in the last month it happened both for the x86 GC info (added no-GC regions) and the regular GC info (deprecated PSPSym). We already have three places that need to be synced up - runtime itself, ILCompiler's managed version of the code, and the dotnet/diagnostics copy of the runtime files. Adding a fourth should be done only after careful consideration of other options.

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
using System.IO;

/// <summary>
/// X86-specific windows thread context.
/// </summary>
[StructLayout(LayoutKind.Explicit, Pack = 1)]
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't need both Pack=1 and explicit layout. Can we switch this to sequential layout, or can the layout not be specified as a C structure?

Comment on lines +457 to +511
// Prolog size
// |
// | Epilog size
// | |
// | | Epilog count
// | | |
// | | | Epilog at end
// | | | |
// | | | | EDI saved
// | | | | |
// | | | | | ESI saved
// | | | | | |
// | | | | | | EBX saved
// | | | | | | |
// | | | | | | | EBP saved
// | | | | | | | |
// | | | | | | | | EBP-frame
// | | | | | | | | |
// | | | | | | | | | Interruptible method
// | | | | | | | | | |
// | | | | | | | | | | doubleAlign
// | | | | | | | | | | |
// | | | | | | | | | | | security flag
// | | | | | | | | | | | |
// | | | | | | | | | | | | handlers
// | | | | | | | | | | | | |
// | | | | | | | | | | | | | localloc
// | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | edit and continue
// | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | varargs
// | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | ProfCallbacks
// | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | genericsContext
// | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | genericsContextIsMethodDesc
// | | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | | returnKind
// | | | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | | | Arg count
// | | | | | | | | | | | | | | | | | | | | | Counted occurrences
// | | | | | | | | | | | | | | | | | | | | | Frame size |
// | | | | | | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | | | | | untrackedCnt | Header encoding
// | | | | | | | | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | | | | | | varPtrTable | |
// | | | | | | | | | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | | | | | | | gsCookieOffs | |
// | | | | | | | | | | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | | | | | | | | syncOffs | |
// | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
// v v v v v v v v v v v v v v v v v v v v v v v v v v v v v
Copy link
Member

Choose a reason for hiding this comment

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

Use named parameters in the new(...) expressions below instead of the ascii art?

Copy link
Member

Choose a reason for hiding this comment

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

(The ASCII art is from the pre-existing code that in turn mirrors the native code... which at some point was generated. The generation was done using a special build with debug prints that were then harvested to calculate the table of most common GC info encoding. The most common encodings then get the short codes which are expanded with this table.)

public uint NoGCRegionCount { get; set; } = 0;

public bool HasArgTabOffset { get; set; }
public uint ArgTabOffset { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Use private set throughout?


public bool HasArgTabOffset { get; set; }
public uint ArgTabOffset { get; set; }
public List<int> Epilogs { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Use ImmutableArray here so you don't need to do defensive copying to avoid writing to collections saved in other instances.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants