Skip to content

Commit 2dbdff1

Browse files
authored
[cDAC] Support x86 stackwalking (#116072)
* Implement managed x86 Unwinder * Add cDAC X86Context * Add cDAC X86FrameHandler * Implement IExecutionManager.GetFuncletStartAddress, IExecutionManager.GetGCInfo, and IExecutionManager.GetRelativeOffset * Allow NativeAOT cDAC compilation on x86
1 parent 663d39f commit 2dbdff1

36 files changed

+3563
-138
lines changed

docs/design/datacontracts/ExecutionManager.md

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,20 @@ struct CodeBlockHandle
2323
TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle);
2424
// Get the instruction pointer address of the start of the code block
2525
TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle);
26+
// Get the instruction pointer address of the start of the funclet containing the code block
27+
TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle);
2628
// Gets the unwind info of the code block at the specified code pointer
27-
TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip);
28-
// Gets the base address the UnwindInfo of codeInfoHandle is relative to.
29+
TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle);
30+
// Gets the base address the UnwindInfo of codeInfoHandle is relative to
2931
TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle);
32+
// Gets the GCInfo associated with the code block and its version
33+
// **Currently GetGCInfo only supports X86**
34+
void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion);
35+
// Gets the offset of the codeInfoHandle inside of the code block
36+
TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle);
37+
38+
// Extension Methods (implemented in terms of other APIs)
39+
bool IsFunclet(CodeBlockHandle codeInfoHandle);
3040
```
3141

3242
## Version 1
@@ -59,14 +69,18 @@ Data descriptors used:
5969
| `RealCodeHeader` | `MethodDesc` | Pointer to the corresponding `MethodDesc` |
6070
| `RealCodeHeader` | `NumUnwindInfos` | Number of Unwind Infos |
6171
| `RealCodeHeader` | `UnwindInfos` | Start address of Unwind Infos |
72+
| `RealCodeHeader` | `GCInfo` | Pointer to the GCInfo encoding |
6273
| `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module |
74+
| `ReadyToRunInfo` | `ReadyToRunHeader` | Pointer to the ReadyToRunHeader |
6375
| `ReadyToRunInfo` | `CompositeInfo` | Pointer to composite R2R info - or itself for non-composite |
6476
| `ReadyToRunInfo` | `NumRuntimeFunctions` | Number of `RuntimeFunctions` |
6577
| `ReadyToRunInfo` | `RuntimeFunctions` | Pointer to an array of `RuntimeFunctions` - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontyperuntimefunctions)|
6678
| `ReadyToRunInfo` | `NumHotColdMap` | Number of entries in the `HotColdMap` |
6779
| `ReadyToRunInfo` | `HotColdMap` | Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) |
6880
| `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | Pointer to an `ImageDataDirectory` for the delay load method call thunks |
6981
| `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` of entry point addresses to `MethodDesc` pointers |
82+
| `ReadyToRunHeader` | `MajorVersion` | ReadyToRun major version |
83+
| `ReadyToRunHeader` | `MinorVersion` | ReadyToRun minor version |
7084
| `ImageDataDirectory` | `VirtualAddress` | Virtual address of the image data directory |
7185
| `ImageDataDirectory` | `Size` | Size of the data |
7286
| `RuntimeFunction` | `BeginAddress` | Begin address of the function |
@@ -85,6 +99,7 @@ Global variables used:
8599
| `HashMapSlotsPerBucket` | uint32 | Number of slots in each bucket of a `HashMap` |
86100
| `HashMapValueMask` | uint64 | Bitmask used when storing values in a `HashMap` |
87101
| `FeatureEHFunclets` | uint8 | 1 if EH funclets are enabled, 0 otherwise |
102+
| `GCInfoVersion` | uint32 | JITted code GCInfo version |
88103

89104
Contracts used:
90105
| Contract Name |
@@ -220,7 +235,7 @@ class CodeBlock
220235
}
221236
```
222237

223-
The `GetMethodDesc` and `GetStartAddress` APIs extract fields of the `CodeBlock`:
238+
The `GetMethodDesc`, `GetStartAddress`, and `GetRelativeOffset` APIs extract fields of the `CodeBlock`:
224239

225240
```csharp
226241
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle)
@@ -234,9 +249,15 @@ The `GetMethodDesc` and `GetStartAddress` APIs extract fields of the `CodeBlock`
234249
/* find CodeBlock info for codeInfoHandle.Address*/
235250
return info.StartAddress;
236251
}
252+
253+
TargetNUInt IExecutionManager.GetRelativeOffset(CodeBlockHandle codeInfoHandle)
254+
{
255+
/* find CodeBlock info for codeInfoHandle.Address*/
256+
return info.RelativeOffset;
257+
}
237258
```
238259

239-
`GetUnwindInfo` gets the Windows style unwind data in the form of `RUNTIME_FUNCTION` which has a platform dependent implementation. The ExecutionManager delegates to the JitManager implementations as the unwind infos (`RUNTIME_FUNCTION`) are stored differently on jitted and R2R code.
260+
`IExecutionManager.GetUnwindInfo` gets the Windows style unwind data in the form of `RUNTIME_FUNCTION` which has a platform dependent implementation. The ExecutionManager delegates to the JitManager implementations as the unwind infos (`RUNTIME_FUNCTION`) are stored differently on jitted and R2R code.
240261

241262
* For jitted code (`EEJitManager`) a list of sorted `RUNTIME_FUNCTION` are stored on the `RealCodeHeader` which is accessed in the same was as `GetMethodInfo` described above. The correct `RUNTIME_FUNCTION` is found by binary searching the list based on IP.
242263

@@ -245,6 +266,19 @@ The `GetMethodDesc` and `GetStartAddress` APIs extract fields of the `CodeBlock`
245266
Unwind info (`RUNTIME_FUNCTION`) use relative addressing. For managed code, these values are relative to the start of the code's containing range in the RangeSectionMap (described below). This could be the beginning of a `CodeHeap` for jitted code or the base address of the loaded image for ReadyToRun code.
246267
`GetUnwindInfoBaseAddress` finds this base address for a given `CodeBlockHandle`.
247268

269+
`IExecutionManager.GetGCInfo` gets a pointer to the relevant GCInfo for a `CodeBlockHandle`. The ExecutionManager delegates to the JitManager implementations as the GCInfo is stored differently on jitted and R2R code.
270+
271+
* For jitted code (`EEJitManager`) a pointer to the `GCInfo` is stored on the `RealCodeHeader` which is accessed in the same was as `GetMethodInfo` described above. This can simply be returned as is. The `GCInfoVersion` is defined by the runtime global `GCInfoVersion`.
272+
273+
* For R2R code (`ReadyToRunJitManager`), the `GCInfo` is stored directly after the `UnwindData`. This in turn is found by looking up the `UnwindInfo` (`RUNTIME_FUNCTION`) and reading the `UnwindData` offset. We find the `UnwindInfo` as described above in `IExecutionManager.GetUnwindInfo`. Once we have the relevant unwind data, we calculate the size of the unwind data and return a pointer to the following byte (first byte of the GCInfo). The size of the unwind data is a platform specific. Currently only X86 is supported with a constant unwind data size of 32-bits.
274+
* The `GCInfoVersion` of R2R code is mapped from the R2R MajorVersion and MinorVersion which is read from the ReadyToRunHeader which itself is read from the ReadyToRunInfo (can be found as in GetMethodInfo). The current GCInfoVersion mapping is:
275+
* MajorVersion >= 11 and MajorVersion < 15 => 4
276+
277+
278+
`IExecutionManager.GetFuncletStartAddress` finds the start of the code blocks funclet. This will be different than the methods start address `GetStartAddress` if the current code block is inside of a funclet. To find the funclet start address, we get the unwind info corresponding to the code block using `IExecutionManager.GetUnwindInfo`. We then parse the unwind info to find the begin address (relative to the unwind info base address) and return the unwind info base address + unwind info begin address.
279+
280+
`IsFunclet` is implemented in terms of `IExecutionManager.GetStartAddress` and `IExecutionManager.GetFuncletStartAddress`. If the values are the same, the code block handle is not a funclet. If they are different, it is a funclet.
281+
248282
### RangeSectionMap
249283

250284
The range section map logically partitions the entire 32-bit or 64-bit addressable space into chunks.

docs/design/datacontracts/StackWalk.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,16 @@ TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle);
327327
```csharp
328328
string GetFrameName(TargetPointer frameIdentifier);
329329
```
330+
331+
### x86 Specifics
332+
333+
The x86 platform has some major differences to other platforms. In general this stems from the platform being older and not having a defined unwinding codes. Instead, to unwind managed frames, we rely on GCInfo associated with JITted code. For the unwind, we do not defer to a 'Windows like' native unwinder, instead the custom unwinder implementation was ported to managed code.
334+
335+
#### GCInfo Parsing
336+
The GCInfo structure is encoded using a variety of formats to optimize for speed of decoding and size on disk. For information on decoding and parsing refer to [GC Information Encoding for x86](../coreclr/jit/jit-gc-info-x86.md).
337+
338+
#### Unwinding Algorithm
339+
340+
The x86 architecture uses a custom unwinding algorithm defined in `gc_unwind_x86.inl`. The cDAC uses a copy of this algorithm ported to managed code in `X86Unwinder.cs`.
341+
342+
Currently there isn't great documentation on the algorithm, beyond inspecting the implementations.

src/coreclr/debug/runtimeinfo/datadescriptor.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ CDAC_TYPE_END(FixupPrecodeData)
566566

567567
CDAC_TYPE_BEGIN(ReadyToRunInfo)
568568
CDAC_TYPE_INDETERMINATE(ReadyToRunInfo)
569+
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, ReadyToRunHeader, cdac_data<ReadyToRunInfo>::ReadyToRunHeader)
569570
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, CompositeInfo, cdac_data<ReadyToRunInfo>::CompositeInfo)
570571
CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumRuntimeFunctions, cdac_data<ReadyToRunInfo>::NumRuntimeFunctions)
571572
CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data<ReadyToRunInfo>::RuntimeFunctions)
@@ -575,6 +576,12 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_dat
575576
CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data<ReadyToRunInfo>::EntryPointToMethodDescMap)
576577
CDAC_TYPE_END(ReadyToRunInfo)
577578

579+
CDAC_TYPE_BEGIN(ReadyToRunHeader)
580+
CDAC_TYPE_INDETERMINATE(READYTORUN_HEADER)
581+
CDAC_TYPE_FIELD(ReadyToRunHeader, /*uint16*/, MajorVersion, offsetof(READYTORUN_HEADER, MajorVersion))
582+
CDAC_TYPE_FIELD(ReadyToRunHeader, /*uint16*/, MinorVersion, offsetof(READYTORUN_HEADER, MinorVersion))
583+
CDAC_TYPE_END(ReadyToRunHeader)
584+
578585
CDAC_TYPE_BEGIN(ImageDataDirectory)
579586
CDAC_TYPE_SIZE(sizeof(IMAGE_DATA_DIRECTORY))
580587
CDAC_TYPE_FIELD(ImageDataDirectory, /*uint32*/, VirtualAddress, offsetof(IMAGE_DATA_DIRECTORY, VirtualAddress))
@@ -635,6 +642,7 @@ CDAC_TYPE_END(RangeSection)
635642
CDAC_TYPE_BEGIN(RealCodeHeader)
636643
CDAC_TYPE_INDETERMINATE(RealCodeHeader)
637644
CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, MethodDesc, offsetof(RealCodeHeader, phdrMDesc))
645+
CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, GCInfo, offsetof(RealCodeHeader, phdrJitGCInfo))
638646
#ifdef FEATURE_EH_FUNCLETS
639647
CDAC_TYPE_FIELD(RealCodeHeader, /*uint32*/, NumUnwindInfos, offsetof(RealCodeHeader, nUnwindInfos))
640648
CDAC_TYPE_FIELD(RealCodeHeader, /* T_RUNTIME_FUNCTION */, UnwindInfos, offsetof(RealCodeHeader, unwindInfos))
@@ -783,7 +791,7 @@ CDAC_TYPE_END(FaultingExceptionFrame)
783791
// CalleeSavedRegisters struct is different on each platform
784792
CDAC_TYPE_BEGIN(CalleeSavedRegisters)
785793
CDAC_TYPE_SIZE(sizeof(CalleeSavedRegisters))
786-
#if defined(TARGET_AMD64)
794+
#if defined(TARGET_AMD64) || defined(TARGET_X86)
787795

788796
#define CALLEE_SAVED_REGISTER(regname) \
789797
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, regname, offsetof(CalleeSavedRegisters, regname))
@@ -838,6 +846,8 @@ CDAC_GLOBAL_STRING(Architecture, riscv64)
838846

839847
CDAC_GLOBAL_STRING(RID, RID_STRING)
840848

849+
CDAC_GLOBAL(GCInfoVersion, uint32, GCINFO_VERSION)
850+
841851
CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain)
842852
CDAC_GLOBAL_POINTER(SystemDomain, cdac_data<SystemDomain>::SystemDomain)
843853
CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore)

src/coreclr/inc/gcinfo.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,15 @@ struct GCInfoToken
7777
}
7878
#endif
7979

80+
// Keep this in sync with GetR2RGCInfoVersion in cDac (ExecutionManagerCore.ReadyToRunJitManager.cs)
8081
static uint32_t ReadyToRunVersionToGcInfoVersion(uint32_t readyToRunMajorVersion, uint32_t readyToRunMinorVersion)
8182
{
82-
#ifdef SOS_INCLUDE
83-
return GCInfoVersion();
84-
#else
85-
return GCINFO_VERSION;
86-
#endif
83+
if (readyToRunMajorVersion >= 11)
84+
return 4;
85+
86+
// Since v2 and v3 had the same file format and v1 is no longer supported,
87+
// we can assume GCInfo v3.
88+
return 3;
8789
}
8890
};
8991

src/coreclr/vm/gc_unwind_x86.inl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2759,14 +2759,12 @@ void UnwindEspFrameProlog(
27592759

27602760
unsigned offset = 0;
27612761

2762-
#ifdef _DEBUG
27632762
// If the first two instructions are 'nop, int3', then we will
27642763
// assume that is from a JitHalt operation and skip past it
27652764
if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
27662765
{
27672766
offset += 2;
27682767
}
2769-
#endif
27702768

27712769
const DWORD curOffs = info->prologOffs;
27722770
unsigned ESP = pContext->SP;
@@ -2922,14 +2920,12 @@ void UnwindEbpDoubleAlignFrameProlog(
29222920

29232921
DWORD offset = 0;
29242922

2925-
#ifdef _DEBUG
29262923
// If the first two instructions are 'nop, int3', then we will
29272924
// assume that is from a JitHalt operation and skip past it
29282925
if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
29292926
{
29302927
offset += 2;
29312928
}
2932-
#endif
29332929

29342930
/* Check for the case where EBP has not been updated yet. */
29352931

src/coreclr/vm/readytoruninfo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ class ReadyToRunInfo
344344
template<>
345345
struct cdac_data<ReadyToRunInfo>
346346
{
347+
static constexpr size_t ReadyToRunHeader = offsetof(ReadyToRunInfo, m_pHeader);
347348
static constexpr size_t CompositeInfo = offsetof(ReadyToRunInfo, m_pCompositeInfo);
348349
static constexpr size_t NumRuntimeFunctions = offsetof(ReadyToRunInfo, m_nRuntimeFunctions);
349350
static constexpr size_t RuntimeFunctions = offsetof(ReadyToRunInfo, m_pRuntimeFunctions);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.Diagnostics.DataContractReader.Contracts.Extensions;
5+
6+
public static class IExecutionManagerExtensions
7+
{
8+
public static bool IsFunclet(this IExecutionManager eman, CodeBlockHandle codeBlockHandle)
9+
{
10+
return eman.GetStartAddress(codeBlockHandle) != eman.GetFuncletStartAddress(codeBlockHandle);
11+
}
12+
}

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ public interface IExecutionManager : IContract
1818
CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => throw new NotImplementedException();
1919
TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
2020
TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
21-
TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => throw new NotImplementedException();
21+
TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
22+
TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
2223
TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
24+
// **Currently GetGCInfo only supports X86**
25+
void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => throw new NotImplementedException();
26+
TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
2327
}
2428

2529
public readonly struct ExecutionManager : IExecutionManager

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public enum DataType
8888
ProfControlBlock,
8989
ILCodeVersionNode,
9090
ReadyToRunInfo,
91+
ReadyToRunHeader,
9192
ImageDataDirectory,
9293
RuntimeFunction,
9394
HashMap,

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public static class Globals
6060

6161
public const string Architecture = nameof(Architecture);
6262
public const string OperatingSystem = nameof(OperatingSystem);
63+
64+
public const string GCInfoVersion = nameof(GCInfoVersion);
6365
}
6466
public static class FieldNames
6567
{

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,30 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod
8585
return _runtimeFunctions.GetRuntimeFunctionAddress(unwindInfos, index);
8686
}
8787

88+
public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion)
89+
{
90+
gcInfo = TargetPointer.Null;
91+
gcVersion = 0;
92+
93+
// EEJitManager::GetGCInfoToken
94+
if (rangeSection.IsRangeList)
95+
return;
96+
97+
if (rangeSection.Data == null)
98+
throw new ArgumentException(nameof(rangeSection));
99+
100+
TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress);
101+
if (codeStart == TargetPointer.Null)
102+
return;
103+
Debug.Assert(codeStart.Value <= jittedCodeAddress.Value);
104+
105+
if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader))
106+
return;
107+
108+
gcVersion = Target.ReadGlobal<uint>(Constants.Globals.GCInfoVersion);
109+
gcInfo = realCodeHeader.GCInfo;
110+
}
111+
88112
private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointer jittedCodeAddress)
89113
{
90114
// EEJitManager::FindMethodCode

0 commit comments

Comments
 (0)