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

Merged
merged 61 commits into from
Jun 17, 2025

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.
    • Refactored ReadyToRunJitManager as GetGCInfo, GetUnwindInfo, and GetMethodInfo had very similar implementations.
  • 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.
      • GCArgTable to decode the argument/stack depth table.
      • GCTransition used as part of the GCArgTable decoding.
      • CallPattern to decode common call pattern encodings in the GCArgTable. 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

Issues

  • SOS can call GetUsefulGlobals API before global pointer data is initialized.

    • Occurs in the SOS.NestedExceptionTest.
    • When building on MSVC, the DAC global table is statically constructed and is available. When building on clang, the table is constructed in EEStartup and probably wouldn't be available when SOS calls.
    • For now, the cDAC assertion around GetUsefulGlobals has been loosened. If the cDAC and the DAC have different HResults, but the same data (usually all 0s), that is acceptable.
  • GCInfo Version

    • For JITted code, this is straight forwards, we read from the runtime the GCInfo version.
    • For R2R code, we can read the R2R header to determine the version.
    • Eventually the GCInfo Decoder will need to support future GCInfo versions. Since a Target can have multiple GCInfo versions, I think this should not be a standard cDAC contract. We can use the existing versioning mechanism.
  • ILStubType

    • No longer required due to remove x86 ICF special handling #116388
    • This is an enum where the later values can be different depending on FEATURE_INSTANTIATINGSTUB_AS_IL. Currently this PR only needs values before they have different numbering.
    • enum ILStubType : DWORD
      {
      StubNotSet = 0,
      StubCLRToNativeInterop,
      StubCLRToCOMInterop,
      StubNativeToCLRInterop,
      StubCOMToCLRInterop,
      StubStructMarshalInterop,
      StubArrayOp,
      StubMulticastDelegate,
      StubWrapperDelegate,
      #ifdef FEATURE_INSTANTIATINGSTUB_AS_IL
      StubUnboxingIL,
      StubInstantiating,
      #endif
      StubTailCallStoreArgs,
      StubTailCallCallTarget,
      StubVirtualStaticMethodDispatch,
      StubDelegateShuffleThunk,
      StubDelegateInvokeMethod,
      StubAsyncResume,
      StubLast
      };
    • For now, we are encoding the enum as a contract constant like other enums defined in method.hpp. Currently we are only using static values, so the runtime did not need to be changed. If we need values later, we can modify the runtime enum to remove the ifdef or add an else block to fill the same number of enum slots.
  • JitHalt Debug

    • In the runtime x86 unwinder, behavior in debug builds is different. In some cases, it will skip over a "JIT halt" at the start of a method, only in debug mode. Since the DAC and target runtime will always have the same configuration, the ifdef is really for the target runtime as well. This is not true for the cDAC. Is this check acceptable in release builds? Alternatively, I can add a datadescriptor to check if the target runtime is built in Debug or Release and match the DAC behavior exactly.
    • #ifdef _DEBUG
      // If the first two instructions are 'nop, int3', then we will
      // assume that is from a JitHalt operation and skip past it
      if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
      {
      offset += 2;
      }
      #endif
    • Based on discussion, this check is okay to happen in release builds. Modifying the runtime x86 unwinder to remove the _DEBUG check for consistency.
  • [cDAC] Implement sign extension for ClrDataAddresses #116222

  • X86 GCDecoder RevPInvoke nits #116264

  • IExecutionManager.GetGCInfo needs the function entry unwind info, not the unwind info directly correlating to the offset.

  • Handle parsing ArgTab correctly when GCInfo header does not have an argTabOffset.

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.

@max-charlamb

This comment was marked as duplicate.

This comment was marked as duplicate.

@max-charlamb

This comment was marked as duplicate.

This comment was marked as duplicate.

}

#if DEBUG
if (_legacyImpl is not null)
{
DacpUsefulGlobalsData dataLocal;
int hrLocal = _legacyImpl.GetUsefulGlobals(&dataLocal);
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
if (hr == HResults.S_OK)
// Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
Copy link
Member

Choose a reason for hiding this comment

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

Is this commented out assert intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. This was something I discovered while running the SOS tests on x86.
SOS can call GetUsefulGlobals API before global pointer data is initialized. When built with MSVC, the DAC global table is statically constructed and is available leading to a successful call. When building on clang, the table is constructed in EEStartup and probably wouldn't be available when SOS calls. Since it would be very difficult (and unnecessary) to match this behavior exactly, for now, I loosened the cDAC assertion around GetUsefulGlobals.

If the cDAC and the DAC have different HResults, but the same data (usually all 0s), that seems acceptable.

I noted it in the PR description but will add a comment explaining why the assertion is loosened.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added comment

return _runtimeFunctions.GetRuntimeFunctionAddress(r2rInfo.RuntimeFunctions, index);
private uint AdjustRuntimeFunctionIndexForHotCold(Data.ReadyToRunInfo r2rInfo, uint index)
{
bool featureEHFunclets = Target.ReadGlobal<byte>(Constants.Globals.FeatureEHFunclets) != 0;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I do not think we need to bother maintaining FeatureEHFunclets checks. It should be fine to assume funclets for cdac.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

Thanks

@@ -85,6 +99,7 @@ Global variables used:
| `HashMapSlotsPerBucket` | uint32 | Number of slots in each bucket of a `HashMap` |
| `HashMapValueMask` | uint64 | Bitmask used when storing values in a `HashMap` |
| `FeatureEHFunclets` | uint8 | 1 if EH funclets are enabled, 0 otherwise |
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 you can delete this constant too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are a couple other uses of the FeatureEHFunclets flag, I was going to open a different PR to totally remove the datadescriptor if that is okay.

Copy link
Member

Choose a reason for hiding this comment

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

Ok

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#116743 created, to avoid merge conflicts it will fail to build until this PR is merged

@max-charlamb
Copy link
Contributor Author

max-charlamb commented Jun 17, 2025

/azp run runtime-diagnostics

@max-charlamb

This comment was marked as duplicate.

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@max-charlamb
Copy link
Contributor Author

/ba-g Failures unrelated to changes. dotnet/dnceng#5904

@max-charlamb max-charlamb merged commit 2dbdff1 into dotnet:main Jun 17, 2025
150 of 156 checks passed
hoyosjs pushed a commit to dotnet/diagnostics that referenced this pull request Jun 19, 2025
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.

6 participants