-
Notifications
You must be signed in to change notification settings - Fork 5k
[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
base: main
Are you sure you want to change the base?
[cDAC] Support x86 stackwalking #116072
Conversation
Tagging subscribers to this area: @tommcdon |
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?? |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
/azp run runtime-diagnostics |
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?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
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. ;-)
/azp run runtime-diagnostics |
Azure Pipelines successfully started running 1 pipeline(s). |
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using System.IO; |
/// <summary> | ||
/// X86-specific windows thread context. | ||
/// </summary> | ||
[StructLayout(LayoutKind.Explicit, Pack = 1)] |
There was a problem hiding this comment.
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?
// 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
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.
Changes
IExecutionManager
GetFuncletStartAddress
GetGCInfo
RealCodeHeader::GCInfo
GetRelativeOffset
GetMethodDesc
andGetStartAddress
Extension IsFunclet
IExecutionManager.StartAddress(cbh) != IExecutionManager.GetFuncletStartAddress(cbh)
IStackWalk
Added support for X86 Platform
IPlatformAgnosticContext.GetContextForPlatform
support x86Managed Implementation of X86 GCInfo decoding
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/
Managed Implementation of X86 Unwinder
gc_unwind_x86.inl
GCInfo
usesInfoHdr
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
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