diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 9075ee2548b643..1b5e360f6ec308 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -23,10 +23,20 @@ struct CodeBlockHandle TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle); // Get the instruction pointer address of the start of the code block TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle); + // Get the instruction pointer address of the start of the funclet containing the code block + TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle); // Gets the unwind info of the code block at the specified code pointer - TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip); - // Gets the base address the UnwindInfo of codeInfoHandle is relative to. + TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle); + // Gets the base address the UnwindInfo of codeInfoHandle is relative to TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle); + // Gets the GCInfo associated with the code block and its version + // **Currently GetGCInfo only supports X86** + void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion); + // Gets the offset of the codeInfoHandle inside of the code block + TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle); + + // Extension Methods (implemented in terms of other APIs) + bool IsFunclet(CodeBlockHandle codeInfoHandle); ``` ## Version 1 @@ -59,7 +69,9 @@ Data descriptors used: | `RealCodeHeader` | `MethodDesc` | Pointer to the corresponding `MethodDesc` | | `RealCodeHeader` | `NumUnwindInfos` | Number of Unwind Infos | | `RealCodeHeader` | `UnwindInfos` | Start address of Unwind Infos | +| `RealCodeHeader` | `GCInfo` | Pointer to the GCInfo encoding | | `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module | +| `ReadyToRunInfo` | `ReadyToRunHeader` | Pointer to the ReadyToRunHeader | | `ReadyToRunInfo` | `CompositeInfo` | Pointer to composite R2R info - or itself for non-composite | | `ReadyToRunInfo` | `NumRuntimeFunctions` | Number of `RuntimeFunctions` | | `ReadyToRunInfo` | `RuntimeFunctions` | Pointer to an array of `RuntimeFunctions` - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontyperuntimefunctions)| @@ -67,6 +79,8 @@ Data descriptors used: | `ReadyToRunInfo` | `HotColdMap` | Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) | | `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | Pointer to an `ImageDataDirectory` for the delay load method call thunks | | `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` of entry point addresses to `MethodDesc` pointers | +| `ReadyToRunHeader` | `MajorVersion` | ReadyToRun major version | +| `ReadyToRunHeader` | `MinorVersion` | ReadyToRun minor version | | `ImageDataDirectory` | `VirtualAddress` | Virtual address of the image data directory | | `ImageDataDirectory` | `Size` | Size of the data | | `RuntimeFunction` | `BeginAddress` | Begin address of the function | @@ -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 | +| `GCInfoVersion` | uint32 | JITted code GCInfo version | Contracts used: | Contract Name | @@ -220,7 +235,7 @@ class CodeBlock } ``` -The `GetMethodDesc` and `GetStartAddress` APIs extract fields of the `CodeBlock`: +The `GetMethodDesc`, `GetStartAddress`, and `GetRelativeOffset` APIs extract fields of the `CodeBlock`: ```csharp TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) @@ -234,9 +249,15 @@ The `GetMethodDesc` and `GetStartAddress` APIs extract fields of the `CodeBlock` /* find CodeBlock info for codeInfoHandle.Address*/ return info.StartAddress; } + + TargetNUInt IExecutionManager.GetRelativeOffset(CodeBlockHandle codeInfoHandle) + { + /* find CodeBlock info for codeInfoHandle.Address*/ + return info.RelativeOffset; + } ``` -`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. +`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. * 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. @@ -245,6 +266,19 @@ The `GetMethodDesc` and `GetStartAddress` APIs extract fields of the `CodeBlock` 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. `GetUnwindInfoBaseAddress` finds this base address for a given `CodeBlockHandle`. +`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. + +* 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`. + +* 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. + * 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: + * MajorVersion >= 11 and MajorVersion < 15 => 4 + + +`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. + +`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. + ### RangeSectionMap The range section map logically partitions the entire 32-bit or 64-bit addressable space into chunks. diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 143a2f89435f56..080c3a7795e04c 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -327,3 +327,16 @@ TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle); ```csharp string GetFrameName(TargetPointer frameIdentifier); ``` + +### x86 Specifics + +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. + +#### GCInfo Parsing +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). + +#### Unwinding Algorithm + +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`. + +Currently there isn't great documentation on the algorithm, beyond inspecting the implementations. diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index aab39b5678ad99..60193392bd9732 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -566,6 +566,7 @@ CDAC_TYPE_END(FixupPrecodeData) CDAC_TYPE_BEGIN(ReadyToRunInfo) CDAC_TYPE_INDETERMINATE(ReadyToRunInfo) +CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, ReadyToRunHeader, cdac_data::ReadyToRunHeader) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, CompositeInfo, cdac_data::CompositeInfo) CDAC_TYPE_FIELD(ReadyToRunInfo, /*uint32*/, NumRuntimeFunctions, cdac_data::NumRuntimeFunctions) CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, RuntimeFunctions, cdac_data::RuntimeFunctions) @@ -575,6 +576,12 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, /*pointer*/, DelayLoadMethodCallThunks, cdac_dat CDAC_TYPE_FIELD(ReadyToRunInfo, /*HashMap*/, EntryPointToMethodDescMap, cdac_data::EntryPointToMethodDescMap) CDAC_TYPE_END(ReadyToRunInfo) +CDAC_TYPE_BEGIN(ReadyToRunHeader) +CDAC_TYPE_INDETERMINATE(READYTORUN_HEADER) +CDAC_TYPE_FIELD(ReadyToRunHeader, /*uint16*/, MajorVersion, offsetof(READYTORUN_HEADER, MajorVersion)) +CDAC_TYPE_FIELD(ReadyToRunHeader, /*uint16*/, MinorVersion, offsetof(READYTORUN_HEADER, MinorVersion)) +CDAC_TYPE_END(ReadyToRunHeader) + CDAC_TYPE_BEGIN(ImageDataDirectory) CDAC_TYPE_SIZE(sizeof(IMAGE_DATA_DIRECTORY)) CDAC_TYPE_FIELD(ImageDataDirectory, /*uint32*/, VirtualAddress, offsetof(IMAGE_DATA_DIRECTORY, VirtualAddress)) @@ -635,6 +642,7 @@ CDAC_TYPE_END(RangeSection) CDAC_TYPE_BEGIN(RealCodeHeader) CDAC_TYPE_INDETERMINATE(RealCodeHeader) CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, MethodDesc, offsetof(RealCodeHeader, phdrMDesc)) +CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, GCInfo, offsetof(RealCodeHeader, phdrJitGCInfo)) #ifdef FEATURE_EH_FUNCLETS CDAC_TYPE_FIELD(RealCodeHeader, /*uint32*/, NumUnwindInfos, offsetof(RealCodeHeader, nUnwindInfos)) CDAC_TYPE_FIELD(RealCodeHeader, /* T_RUNTIME_FUNCTION */, UnwindInfos, offsetof(RealCodeHeader, unwindInfos)) @@ -783,7 +791,7 @@ CDAC_TYPE_END(FaultingExceptionFrame) // CalleeSavedRegisters struct is different on each platform CDAC_TYPE_BEGIN(CalleeSavedRegisters) CDAC_TYPE_SIZE(sizeof(CalleeSavedRegisters)) -#if defined(TARGET_AMD64) +#if defined(TARGET_AMD64) || defined(TARGET_X86) #define CALLEE_SAVED_REGISTER(regname) \ CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, regname, offsetof(CalleeSavedRegisters, regname)) @@ -838,6 +846,8 @@ CDAC_GLOBAL_STRING(Architecture, riscv64) CDAC_GLOBAL_STRING(RID, RID_STRING) +CDAC_GLOBAL(GCInfoVersion, uint32, GCINFO_VERSION) + CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain) CDAC_GLOBAL_POINTER(SystemDomain, cdac_data::SystemDomain) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) diff --git a/src/coreclr/inc/gcinfo.h b/src/coreclr/inc/gcinfo.h index cdcbb9eb14f04e..cf4a43334b281f 100644 --- a/src/coreclr/inc/gcinfo.h +++ b/src/coreclr/inc/gcinfo.h @@ -77,13 +77,15 @@ struct GCInfoToken } #endif + // Keep this in sync with GetR2RGCInfoVersion in cDac (ExecutionManagerCore.ReadyToRunJitManager.cs) static uint32_t ReadyToRunVersionToGcInfoVersion(uint32_t readyToRunMajorVersion, uint32_t readyToRunMinorVersion) { -#ifdef SOS_INCLUDE - return GCInfoVersion(); -#else - return GCINFO_VERSION; -#endif + if (readyToRunMajorVersion >= 11) + return 4; + + // Since v2 and v3 had the same file format and v1 is no longer supported, + // we can assume GCInfo v3. + return 3; } }; diff --git a/src/coreclr/vm/gc_unwind_x86.inl b/src/coreclr/vm/gc_unwind_x86.inl index 5aaa8fa7f805e6..852cd40a024af6 100644 --- a/src/coreclr/vm/gc_unwind_x86.inl +++ b/src/coreclr/vm/gc_unwind_x86.inl @@ -2759,14 +2759,12 @@ void UnwindEspFrameProlog( unsigned offset = 0; -#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 const DWORD curOffs = info->prologOffs; unsigned ESP = pContext->SP; @@ -2922,14 +2920,12 @@ void UnwindEbpDoubleAlignFrameProlog( DWORD offset = 0; -#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 /* Check for the case where EBP has not been updated yet. */ diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 7ff51806dbd088..a67a539f743a17 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -344,6 +344,7 @@ class ReadyToRunInfo template<> struct cdac_data { + static constexpr size_t ReadyToRunHeader = offsetof(ReadyToRunInfo, m_pHeader); static constexpr size_t CompositeInfo = offsetof(ReadyToRunInfo, m_pCompositeInfo); static constexpr size_t NumRuntimeFunctions = offsetof(ReadyToRunInfo, m_nRuntimeFunctions); static constexpr size_t RuntimeFunctions = offsetof(ReadyToRunInfo, m_pRuntimeFunctions); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/IExecutionManagerExtensions.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/IExecutionManagerExtensions.cs new file mode 100644 index 00000000000000..303ff64eb444b1 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/IExecutionManagerExtensions.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; + +public static class IExecutionManagerExtensions +{ + public static bool IsFunclet(this IExecutionManager eman, CodeBlockHandle codeBlockHandle) + { + return eman.GetStartAddress(codeBlockHandle) != eman.GetFuncletStartAddress(codeBlockHandle); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs index a977c79cab0c3a..408d8e8f91846f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs @@ -18,8 +18,12 @@ public interface IExecutionManager : IContract CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => throw new NotImplementedException(); TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); - TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => throw new NotImplementedException(); + TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + // **Currently GetGCInfo only supports X86** + void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => throw new NotImplementedException(); + TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); } public readonly struct ExecutionManager : IExecutionManager diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 5e67e0c569d552..cda5f7de597926 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -88,6 +88,7 @@ public enum DataType ProfControlBlock, ILCodeVersionNode, ReadyToRunInfo, + ReadyToRunHeader, ImageDataDirectory, RuntimeFunction, HashMap, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index a110102616bd8d..ea0381d93a1097 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -60,6 +60,8 @@ public static class Globals public const string Architecture = nameof(Architecture); public const string OperatingSystem = nameof(OperatingSystem); + + public const string GCInfoVersion = nameof(GCInfoVersion); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs index 42982babbd0dc6..128394b97d2234 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs @@ -85,6 +85,30 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod return _runtimeFunctions.GetRuntimeFunctionAddress(unwindInfos, index); } + public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) + { + gcInfo = TargetPointer.Null; + gcVersion = 0; + + // EEJitManager::GetGCInfoToken + if (rangeSection.IsRangeList) + return; + + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress); + if (codeStart == TargetPointer.Null) + return; + Debug.Assert(codeStart.Value <= jittedCodeAddress.Value); + + if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) + return; + + gcVersion = Target.ReadGlobal(Constants.Globals.GCInfoVersion); + gcInfo = realCodeHeader.GCInfo; + } + private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) { // EEJitManager::FindMethodCode diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index 7b35e56f542d15..bdbe99980c4107 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -12,14 +12,12 @@ internal partial class ExecutionManagerCore : IExecutionManager { private sealed class ReadyToRunJitManager : JitManager { - private readonly uint _runtimeFunctionSize; private readonly PtrHashMapLookup _hashMap; private readonly HotColdLookup _hotCold; private readonly RuntimeFunctionLookup _runtimeFunctions; public ReadyToRunJitManager(Target target) : base(target) { - _runtimeFunctionSize = Target.GetTypeInfo(DataType.RuntimeFunction).Size!.Value; _hashMap = PtrHashMapLookup.Create(target); _hotCold = HotColdLookup.Create(target); _runtimeFunctions = RuntimeFunctionLookup.Create(target); @@ -28,50 +26,18 @@ public ReadyToRunJitManager(Target target) : base(target) public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) { // ReadyToRunJitManager::JitCodeToMethodInfo - if (rangeSection.Data == null) - throw new ArgumentException(nameof(rangeSection)); - info = default; - Debug.Assert(rangeSection.Data.R2RModule != TargetPointer.Null); - - Data.Module r2rModule = Target.ProcessedData.GetOrAdd(rangeSection.Data.R2RModule); - Debug.Assert(r2rModule.ReadyToRunInfo != TargetPointer.Null); - Data.ReadyToRunInfo r2rInfo = Target.ProcessedData.GetOrAdd(r2rModule.ReadyToRunInfo); - // Check if address is in a thunk - if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress)) + Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) return false; - // Find the relative address that we are looking for - TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); - TargetPointer imageBase = rangeSection.Data.RangeBegin; - TargetPointer relativeAddr = addr - imageBase; - - uint index; - if (!_runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, relativeAddr, out index)) - return false; - - bool featureEHFunclets = Target.ReadGlobal(Constants.Globals.FeatureEHFunclets) != 0; - if (featureEHFunclets) - { - // Look up index in hot/cold map - if the function is in the cold part, get the index of the hot part. - index = _hotCold.GetHotFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index); - Debug.Assert(index < r2rInfo.NumRuntimeFunctions); - } - - TargetPointer methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); - while (featureEHFunclets && methodDesc == TargetPointer.Null) - { - // Funclets won't have a direct entry in the map of runtime function entry point to method desc. - // The funclet's address (and index) will be greater than that of the corresponding function, so - // we decrement the index to find the actual function / method desc for the funclet. - index--; - methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); - } + index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index); + index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out TargetPointer methodDesc); - Debug.Assert(methodDesc != TargetPointer.Null); Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index); + TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); TargetCodePointer startAddress = imageBase + function.BeginAddress; TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress); @@ -97,6 +63,64 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) { // ReadyToRunJitManager::JitCodeToMethodInfo + Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer _, out uint index)) + return TargetPointer.Null; + + return _runtimeFunctions.GetRuntimeFunctionAddress(r2rInfo.RuntimeFunctions, index); + } + + public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) + { + gcInfo = TargetPointer.Null; + gcVersion = 0; + + // ReadyToRunJitManager::GetGCInfoToken + Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) + return; + + index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index); + index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _); + + Data.RuntimeFunction runtimeFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index); + + TargetPointer unwindInfo = runtimeFunction.UnwindData + imageBase; + uint unwindDataSize = GetUnwindDataSize(); + gcInfo = unwindInfo + unwindDataSize; + gcVersion = GetR2RGCInfoVersion(r2rInfo); + } + + // This function must be kept up to date with R2R version changes. + // When the R2R version is bumped, it must be mapped to the correct GCInfo version. + // Adding "MINIMUM_READYTORUN_MAJOR_VERSION" to ensure this is updated according + // to instructions in readytorun.h + private uint GetR2RGCInfoVersion(Data.ReadyToRunInfo r2rInfo) + { + Data.ReadyToRunHeader header = Target.ProcessedData.GetOrAdd(r2rInfo.ReadyToRunHeader); + + // see readytorun.h for the versioning details + return header.MajorVersion switch + { + < 11 => 3, + >= 11 => 4, + }; + } + + private uint GetUnwindDataSize() + { + RuntimeInfoArchitecture arch = Target.Contracts.RuntimeInfo.GetTargetArchitecture(); + return arch switch + { + RuntimeInfoArchitecture.X86 => sizeof(uint), + _ => throw new NotSupportedException($"GetUnwindDataSize not supported for architecture: {arch}") + }; + } + + #region RuntimeFunction Helpers + + private Data.ReadyToRunInfo GetReadyToRunInfo(RangeSection rangeSection) + { if (rangeSection.Data == null) throw new ArgumentException(nameof(rangeSection)); @@ -104,22 +128,56 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod Data.Module r2rModule = Target.ProcessedData.GetOrAdd(rangeSection.Data.R2RModule); Debug.Assert(r2rModule.ReadyToRunInfo != TargetPointer.Null); - Data.ReadyToRunInfo r2rInfo = Target.ProcessedData.GetOrAdd(r2rModule.ReadyToRunInfo); + return Target.ProcessedData.GetOrAdd(r2rModule.ReadyToRunInfo); + } + + private bool GetRuntimeFunction( + RangeSection rangeSection, + Data.ReadyToRunInfo r2rInfo, + TargetCodePointer jittedCodeAddress, + out TargetPointer imageBase, + out uint runtimeFunctionIndex) + { + imageBase = TargetPointer.Null; + runtimeFunctionIndex = 0; + + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); // Check if address is in a thunk if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress)) - return TargetPointer.Null; + return false; // Find the relative address that we are looking for TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); - TargetPointer imageBase = rangeSection.Data.RangeBegin; + imageBase = rangeSection.Data.RangeBegin; TargetPointer relativeAddr = addr - imageBase; - uint index; - if (!_runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, relativeAddr, out index)) - return TargetPointer.Null; + return _runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, relativeAddr, out runtimeFunctionIndex); + } - return _runtimeFunctions.GetRuntimeFunctionAddress(r2rInfo.RuntimeFunctions, index); + private uint AdjustRuntimeFunctionIndexForHotCold(Data.ReadyToRunInfo r2rInfo, uint index) + { + // Look up index in hot/cold map - if the function is in the cold part, get the index of the hot part. + index = _hotCold.GetHotFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index); + Debug.Assert(index < r2rInfo.NumRuntimeFunctions); + return index; + } + + private uint AdjustRuntimeFunctionToMethodStart(Data.ReadyToRunInfo r2rInfo, TargetPointer imageBase, uint index, out TargetPointer methodDesc) + { + methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); + while (methodDesc == TargetPointer.Null) + { + // Funclets won't have a direct entry in the map of runtime function entry point to method desc. + // The funclet's address (and index) will be greater than that of the corresponding function, so + // we decrement the index to find the actual function / method desc for the funclet. + index--; + methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); + } + + Debug.Assert(methodDesc != TargetPointer.Null); + return index; } private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) @@ -147,5 +205,7 @@ private TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInf return methodDesc; } + + #endregion } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index d8e1e501641711..6d9b993dcfff6c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -66,6 +66,7 @@ protected JitManager(Target target) public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info); public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress); + public abstract void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion); } private sealed class RangeSection @@ -180,18 +181,41 @@ TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHand return info.StartAddress; } - TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) + TargetCodePointer IExecutionManager.GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) { if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, ip); + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + if (range.Data == null) + throw new InvalidOperationException("Unable to get runtime function address"); + + JitManager jitManager = GetJitManager(range.Data); + TargetPointer runtimeFunctionPtr = jitManager.GetUnwindInfo(range, codeInfoHandle.Address.Value); + + if (runtimeFunctionPtr == TargetPointer.Null) + throw new InvalidOperationException("Unable to get runtime function address"); + + Data.RuntimeFunction runtimeFunction = _target.ProcessedData.GetOrAdd(runtimeFunctionPtr); + + // TODO(cdac): EXCEPTION_DATA_SUPPORTS_FUNCTION_FRAGMENTS, implement iterating over fragments until finding + // non-fragment RuntimeFunction + + return range.Data.RangeBegin + runtimeFunction.BeginAddress; + } + + TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); if (range.Data == null) return TargetPointer.Null; JitManager jitManager = GetJitManager(range.Data); - return jitManager.GetUnwindInfo(range, ip); + return jitManager.GetUnwindInfo(range, codeInfoHandle.Address.Value); } TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) @@ -205,4 +229,29 @@ TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInf return range.Data.RangeBegin; } + + void IExecutionManager.GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) + { + gcInfo = TargetPointer.Null; + gcVersion = 0; + + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + if (range.Data == null) + return; + + JitManager jitManager = GetJitManager(range.Data); + jitManager.GetGCInfo(range, codeInfoHandle.Address.Value, out gcInfo, out gcVersion); + } + + + TargetNUInt IExecutionManager.GetRelativeOffset(CodeBlockHandle codeInfoHandle) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + return info.RelativeOffset; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index 670b75dbe27b13..2feda4180c2feb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -17,6 +17,9 @@ internal ExecutionManager_1(Target target, Data.RangeSectionMap topRangeSectionM public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); - public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => _executionManagerCore.GetUnwindInfo(codeInfoHandle, ip); + public TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle); + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfo(codeInfoHandle); public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); + public void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => _executionManagerCore.GetGCInfo(codeInfoHandle, out gcInfo, out gcVersion); + public TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetRelativeOffset(codeInfoHandle); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index 7378685ce67396..356081e289b492 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -17,6 +17,9 @@ internal ExecutionManager_2(Target target, Data.RangeSectionMap topRangeSectionM public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); - public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => _executionManagerCore.GetUnwindInfo(codeInfoHandle, ip); + public TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle); + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfo(codeInfoHandle); public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); + public void GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion) => _executionManagerCore.GetGCInfo(codeInfoHandle, out gcInfo, out gcVersion); + public TargetNUInt GetRelativeOffset(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetRelativeOffset(codeInfoHandle); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64/AMD64Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64/AMD64Unwinder.cs index 5f7d18a29ea53f..d5a67fb65e411e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64/AMD64Unwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64/AMD64Unwinder.cs @@ -51,7 +51,7 @@ public bool Unwind(ref AMD64Context context) TargetPointer controlPC = context.InstructionPointer; TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); - Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh, controlPC.Value)); + Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh)); if (functionEntry.EndAddress is null) return false; if (GetUnwindInfoHeader(imageBase + functionEntry.UnwindData) is not UnwindInfoHeader unwindInfo) @@ -861,11 +861,11 @@ private bool UnwindPrologue( // the register than was pushed. // case UnwindCode.OpCodes.UWOP_PUSH_NONVOL: - { - SetRegister(ref context, unwindOp.OpInfo, _target.ReadPointer(context.Rsp)); - context.Rsp += 8; - break; - } + { + SetRegister(ref context, unwindOp.OpInfo, _target.ReadPointer(context.Rsp)); + context.Rsp += 8; + break; + } // // Allocate a large sized area on the stack. @@ -874,26 +874,26 @@ private bool UnwindPrologue( // 16- or 32-bits. // case UnwindCode.OpCodes.UWOP_ALLOC_LARGE: - { - index++; - UnwindCode nextUnwindOp = GetUnwindCode(unwindInfo, index); - uint frameOffset = nextUnwindOp.FrameOffset; - if (unwindOp.OpInfo != 0) { index++; - nextUnwindOp = GetUnwindCode(unwindInfo, index); - frameOffset += (uint)(nextUnwindOp.FrameOffset << 16); - } - else - { - // The 16-bit form is scaled. - frameOffset *= 8; + UnwindCode nextUnwindOp = GetUnwindCode(unwindInfo, index); + uint frameOffset = nextUnwindOp.FrameOffset; + if (unwindOp.OpInfo != 0) + { + index++; + nextUnwindOp = GetUnwindCode(unwindInfo, index); + frameOffset += (uint)(nextUnwindOp.FrameOffset << 16); + } + else + { + // The 16-bit form is scaled. + frameOffset *= 8; + } + + context.Rsp += frameOffset; + break; } - context.Rsp += frameOffset; - break; - } - // // Allocate a small sized area on the stack. // @@ -901,10 +901,10 @@ private bool UnwindPrologue( // allocation size (8 is the scale factor) minus 8. // case UnwindCode.OpCodes.UWOP_ALLOC_SMALL: - { - context.Rsp += (unwindOp.OpInfo * 8u) + 8u; - break; - } + { + context.Rsp += (unwindOp.OpInfo * 8u) + 8u; + break; + } // // Establish the frame pointer register. @@ -912,11 +912,11 @@ private bool UnwindPrologue( // The operation information is not used. // case UnwindCode.OpCodes.UWOP_SET_FPREG: - { - context.Rsp = GetRegister(context, unwindInfo.FrameRegister); - context.Rsp -= unwindInfo.FrameOffset * 16u; - break; - } + { + context.Rsp = GetRegister(context, unwindInfo.FrameRegister); + context.Rsp -= unwindInfo.FrameOffset * 16u; + break; + } // // Establish the frame pointer register using a large size displacement. @@ -926,19 +926,19 @@ private bool UnwindPrologue( // Unix only. // case UnwindCode.OpCodes.UWOP_SET_FPREG_LARGE: - { - UnwinderAssert(_unix); - UnwinderAssert(unwindInfo.FrameOffset == 15); - uint frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset; - frameOffset += (uint)(GetUnwindCode(unwindInfo, index + 2).FrameOffset << 16); - UnwinderAssert((frameOffset & 0xF0000000) == 0); + { + UnwinderAssert(_unix); + UnwinderAssert(unwindInfo.FrameOffset == 15); + uint frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset; + frameOffset += (uint)(GetUnwindCode(unwindInfo, index + 2).FrameOffset << 16); + UnwinderAssert((frameOffset & 0xF0000000) == 0); - context.Rsp = GetRegister(context, unwindInfo.FrameRegister); - context.Rsp -= frameOffset * 16; + context.Rsp = GetRegister(context, unwindInfo.FrameRegister); + context.Rsp -= frameOffset * 16; - index += 2; - break; - } + index += 2; + break; + } // // Save nonvolatile integer register on the stack using a @@ -947,12 +947,12 @@ private bool UnwindPrologue( // The operation information is the register number. // case UnwindCode.OpCodes.UWOP_SAVE_NONVOL: - { - uint frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset * 8u; - SetRegister(ref context, unwindOp.OpInfo, _target.ReadPointer(frameBase + frameOffset)); - index += 1; - break; - } + { + uint frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset * 8u; + SetRegister(ref context, unwindOp.OpInfo, _target.ReadPointer(frameBase + frameOffset)); + index += 1; + break; + } // // Function epilog marker (ignored for prologue unwind). @@ -998,21 +998,21 @@ private bool UnwindPrologue( // machine frame contains an error code or not. // case UnwindCode.OpCodes.UWOP_PUSH_MACHFRAME: - { - machineFrame = false; - TargetPointer returnAddressPtr = context.Rsp; - TargetPointer stackAddressPtr = context.Rsp + (3 * 8); - if (unwindOp.OpInfo != 0) { - returnAddressPtr += (uint)_target.PointerSize; - stackAddressPtr += (uint)_target.PointerSize; + machineFrame = false; + TargetPointer returnAddressPtr = context.Rsp; + TargetPointer stackAddressPtr = context.Rsp + (3 * 8); + if (unwindOp.OpInfo != 0) + { + returnAddressPtr += (uint)_target.PointerSize; + stackAddressPtr += (uint)_target.PointerSize; + } + + context.Rip = _target.ReadPointer(returnAddressPtr); + context.Rsp = _target.ReadPointer(stackAddressPtr); + break; } - context.Rip = _target.ReadPointer(returnAddressPtr); - context.Rsp = _target.ReadPointer(stackAddressPtr); - break; - } - default: Debug.Fail("Unexpected unwind operation code."); break; @@ -1224,7 +1224,7 @@ private Data.RuntimeFunction LookupPrimaryFunctionEntry(Data.RuntimeFunction fun return null; TargetPointer targetImageBase = _eman.GetUnwindInfoBaseAddress(cbh); - Data.RuntimeFunction targetFunctionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh, controlPC.Value)); + Data.RuntimeFunction targetFunctionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh)); targetFunctionEntry = LookupPrimaryFunctionEntry(targetFunctionEntry, targetImageBase); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs index 3e9091533e6da7..9bb6eca934f1ff 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs @@ -99,4 +99,6 @@ public override bool Equals(object? obj) } public override int GetHashCode() => Context.GetHashCode(); + + public override string ToString() => Context.ToString() ?? string.Empty; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index cf9aac6ac54507..e687784927153f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -28,6 +28,7 @@ public static IPlatformAgnosticContext GetContextForPlatform(Target target) IRuntimeInfo runtimeInfo = target.Contracts.RuntimeInfo; return runtimeInfo.GetTargetArchitecture() switch { + RuntimeInfoArchitecture.X86 => new ContextHolder(), RuntimeInfoArchitecture.X64 => new ContextHolder(), RuntimeInfoArchitecture.Arm64 => new ContextHolder(), RuntimeInfoArchitecture.Unknown => throw new InvalidOperationException($"Processor architecture is required for creating a platform specific context and is not provided by the target"), diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs index 617c02af49b0e4..151614d1681182 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs @@ -153,7 +153,7 @@ private static unsafe void GetStackWalkInfo(ulong controlPC, void* pUnwindInfoBa } if ((nuint)pFuncEntry != 0) { - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh); *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/CallPattern.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/CallPattern.cs new file mode 100644 index 00000000000000..da2024c826262a --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/CallPattern.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; + +internal static class CallPattern +{ + + /// + /// based on src\inc\gcdecoder.cpp DecodeCallPattern + /// + public static void DecodeCallPattern(uint pattern, out uint argCnt, out uint regMask, out uint argMask, out uint codeDelta) + { + uint val = CallPatternTable[(int)pattern]; + byte[] fld = BitConverter.GetBytes(val); + argCnt = fld[0]; + regMask = fld[1]; // EBP,EBX,ESI,EDI + argMask = fld[2]; + codeDelta = fld[3]; + } + + /// + /// based on src\inc\gcdecoder.cpp CallCommonDelta + /// + public static ReadOnlySpan CallCommonDelta => [6, 8, 10, 12]; + + /// + /// based on src\inc\gcdecoder.cpp CallPatternTable + /// + private static ReadOnlySpan CallPatternTable => + [ + 0x0a000200, // 30109 + 0x0c000200, // 22970 + 0x0c000201, // 19005 + 0x0a000300, // 12193 + 0x0c000300, // 10614 + 0x0e000200, // 10253 + 0x10000200, // 9746 + 0x0b000200, // 9698 + 0x0d000200, // 9625 + 0x08000200, // 8909 + 0x0c000301, // 8522 + 0x11000200, // 7382 + 0x0e000300, // 7357 + 0x12000200, // 7139 + 0x10000300, // 7062 + 0x11000300, // 6970 + 0x0a000201, // 6842 + 0x0a000100, // 6803 + 0x0f000200, // 6795 + 0x13000200, // 6559 + 0x08000300, // 6079 + 0x15000200, // 5874 + 0x0d000201, // 5492 + 0x0c000100, // 5193 + 0x0d000300, // 5165 + 0x23000200, // 5143 + 0x1b000200, // 5035 + 0x14000200, // 4872 + 0x0f000300, // 4850 + 0x0a000700, // 4781 + 0x09000200, // 4560 + 0x12000300, // 4496 + 0x16000200, // 4180 + 0x07000200, // 4021 + 0x09000300, // 4012 + 0x0c000700, // 3988 + 0x0c000600, // 3946 + 0x0e000100, // 3823 + 0x1a000200, // 3764 + 0x18000200, // 3744 + 0x17000200, // 3736 + 0x1f000200, // 3671 + 0x13000300, // 3559 + 0x0a000600, // 3214 + 0x0e000600, // 3109 + 0x08000201, // 2984 + 0x0b000300, // 2928 + 0x0a000301, // 2859 + 0x07000100, // 2826 + 0x13000100, // 2782 + 0x09000301, // 2644 + 0x19000200, // 2638 + 0x11000700, // 2618 + 0x21000200, // 2518 + 0x0d000202, // 2484 + 0x10000100, // 2480 + 0x0f000600, // 2413 + 0x14000300, // 2363 + 0x0c000500, // 2362 + 0x08000301, // 2285 + 0x20000200, // 2245 + 0x10000700, // 2240 + 0x0f000100, // 2236 + 0x1e000200, // 2214 + 0x0c000400, // 2193 + 0x16000300, // 2171 + 0x12000600, // 2132 + 0x22000200, // 2011 + 0x1d000200, // 2011 + 0x0c000f00, // 1996 + 0x0e000700, // 1971 + 0x0a000400, // 1970 + 0x09000201, // 1932 + 0x10000600, // 1903 + 0x15000300, // 1847 + 0x0a000101, // 1814 + 0x0a000b00, // 1771 + 0x0c000601, // 1737 + 0x09000700, // 1737 + 0x07000300, // 1684 + ]; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCArgTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCArgTable.cs new file mode 100644 index 00000000000000..3d2ec4afdaf071 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCArgTable.cs @@ -0,0 +1,526 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; + +public class GCArgTable +{ + private const uint byref_OFFSET_FLAG = 0x1; + + private readonly Target _target; + private readonly InfoHdr _header; + + public Dictionary> Transitions { get; private set; } = []; + + public GCArgTable(Target target, InfoHdr header, TargetPointer argTablePtr) + { + _target = target; + _header = header; + + TargetPointer offset = argTablePtr; + if (header.Interruptible) + { + GetTransitionsFullyInterruptible(ref offset); + } + else if (_header.EbpFrame) + { + GetTransitionsEbpFrame(ref offset); + } + else + { + GetTransitionsNoEbp(ref offset); + } + } + + private void AddNewTransition(BaseGcTransition transition) + { + if (!Transitions.TryGetValue(transition.CodeOffset, out List? value)) + { + value = []; + Transitions[transition.CodeOffset] = value; + } + + value.Add(transition); + } + + private void ArgEncoding(ref uint isPop, ref uint argOffs, ref uint argCnt, ref uint curOffs, ref bool isThis, ref bool iptr) + { + if (isPop != 0) + { + // A Pop of 0, means little-delta + + if (argOffs != 0) + { + AddNewTransition(new GcTransitionPointer((int)curOffs, argOffs, argCnt - argOffs, Action.POP, _header.EbpFrame)); + } + } + else + { + AddNewTransition(new GcTransitionPointer((int)curOffs, argOffs, argOffs + 1, Action.PUSH, _header.EbpFrame, isThis, iptr)); + isThis = false; + iptr = false; + } + } + + private static RegMask ThreeBitEncodingToRegMask(byte val) => + (val & 0x7) switch + { + 0x0 => RegMask.EAX, + 0x1 => RegMask.ECX, + 0x2 => RegMask.EDX, + 0x3 => RegMask.EBX, + 0x4 => RegMask.ESP, + 0x5 => RegMask.EBP, + 0x6 => RegMask.ESI, + 0x7 => RegMask.EDI, + _ => throw new ArgumentOutOfRangeException(nameof(val), $"Not expected register value: {val}"), + }; + + private static RegMask TwoBitEncodingToRegMask(byte val) => + (val & 0x3) switch + { + 0x0 => RegMask.EDI, + 0x1 => RegMask.ESI, + 0x2 => RegMask.EBX, + 0x3 => RegMask.EBP, + _ => throw new ArgumentOutOfRangeException(nameof(val), $"Not expected register value: {val}"), + }; + + /// + /// based on GCDump::DumpGCTable + /// + private void GetTransitionsFullyInterruptible(ref TargetPointer offset) + { + uint argCnt = 0; + bool isThis = false; + bool iptr = false; + uint curOffs = 0; + + while (true) + { + uint isPop; + uint argOffs; + uint val = _target.Read(offset++); + + if ((val & 0x80) == 0) + { + /* A small 'regPtr' encoding */ + + curOffs += val & 0x7; + + Action isLive = Action.LIVE; + if ((val & 0x40) == 0) + isLive = Action.DEAD; + AddNewTransition(new GcTransitionRegister((int)curOffs, ThreeBitEncodingToRegMask((byte)((val >> 3) & 7)), isLive, isThis, iptr)); + + isThis = false; + iptr = false; + continue; + } + + /* This is probably an argument push/pop */ + + argOffs = (val & 0x38) >> 3; + + /* 6 [110] and 7 [111] are reserved for other encodings */ + + if (argOffs < 6) + { + /* A small argument encoding */ + + curOffs += val & 0x07; + isPop = val & 0x40; + + ArgEncoding(ref isPop, ref argOffs, ref argCnt, ref curOffs, ref isThis, ref iptr); + + continue; + } + else if (argOffs == 6) + { + if ((val & 0x40) != 0) + { + curOffs += (((val & 0x07) + 1) << 3); + } + else + { + // non-ptr arg push + + curOffs += (val & 0x07); + argCnt++; + AddNewTransition(new GcTransitionPointer((int)curOffs, argOffs, argCnt, Action.PUSH, _header.EbpFrame, false, false, false)); + } + + continue; + } + + // argOffs was 7 [111] which is reserved for the larger encodings + switch (val) + { + case 0xFF: + return; + case 0xBC: + isThis = true; + break; + case 0xBF: + iptr = true; + break; + case 0xB8: + val = _target.GCDecodeUnsigned(ref offset); + curOffs += val; + break; + case 0xF8: + case 0xFC: + isPop = val & 0x04; + argOffs = _target.GCDecodeUnsigned(ref offset); + ArgEncoding(ref isPop, ref argOffs, ref argCnt, ref curOffs, ref isThis, ref iptr); + break; + case 0xFD: + argOffs = _target.GCDecodeUnsigned(ref offset); + AddNewTransition(new GcTransitionPointer((int)curOffs, argOffs, argCnt, Action.KILL, _header.EbpFrame)); + break; + case 0xF9: + argOffs = _target.GCDecodeUnsigned(ref offset); + AddNewTransition(new StackDepthTransition((int)curOffs, (int)argOffs)); + argCnt += argOffs; + break; + default: + throw new BadImageFormatException($"Unexpected special code {val}"); + } + } + } + + /// + /// based on GCDump::DumpGCTable + /// + private void GetTransitionsEbpFrame(ref TargetPointer offset) + { + while (true) + { + uint argMask = 0, byrefArgMask = 0; + uint regMask, byrefRegMask = 0; + + uint argCnt = 0; + TargetPointer argOffset = offset; + uint argTabSize; + + uint val, nxt; + uint curOffs = 0; + + // Get the next byte and check for a 'special' entry + uint encType = _target.Read(offset++); + GcTransitionCall? transition; + + switch (encType) + { + default: + // A tiny or small call entry + val = encType; + + if ((val & 0x80) == 0x00) + { + if ((val & 0x0F) != 0) + { + // A tiny call entry + + curOffs += (val & 0x0F); + regMask = (val & 0x70) >> 4; + argMask = 0; + } + else + { + RegMask reg; + if ((val & 0x10) != 0) + reg = RegMask.EDI; + else if ((val & 0x20) != 0) + reg = RegMask.ESI; + else if ((val & 0x40) != 0) + reg = RegMask.EBX; + else + throw new BadImageFormatException("Invalid register"); + transition = new GcTransitionCall((int)curOffs); + transition.CallRegisters.Add(new GcTransitionCall.CallRegister(reg, false)); + AddNewTransition(transition); + + continue; + } + } + else + { + // A small call entry + curOffs += (val & 0x7F); + val = _target.Read(offset++); + regMask = val >> 5; + argMask = val & 0x1F; + } + break; + + case 0xFD: // medium encoding + argMask = _target.Read(offset++); + val = _target.Read(offset++); + argMask |= (val & 0xF0) << 4; + nxt = _target.Read(offset++); + curOffs += (val & 0x0F) + ((nxt & 0x1F) << 4); + regMask = nxt >> 5; // EBX,ESI,EDI + break; + + case 0xF9: // medium encoding with byrefs + curOffs += _target.Read(offset++); + val = _target.Read(offset++); + argMask = val & 0x1F; + regMask = val >> 5; + val = _target.Read(offset++); + byrefArgMask = val & 0x1F; + byrefRegMask = val >> 5; + break; + case 0xFE: // large encoding + case 0xFA: // large encoding with byrefs + val = _target.Read(offset++); + regMask = val & 0x7; + byrefRegMask = val >> 4; + + curOffs += _target.Read(offset); + offset += 4; + argMask = _target.Read(offset); + offset += 4; + + if (encType == 0xFA) // read byrefArgMask + { + byrefArgMask = _target.Read(offset); + offset += 4; + } + break; + case 0xFB: // huge encoding + val = _target.Read(offset++); + regMask = val & 0x7; + byrefRegMask = val >> 4; + curOffs = _target.Read(offset); + offset += 4; + argCnt = _target.Read(offset); + offset += 4; + argTabSize = _target.Read(offset); + offset += 4; + argOffset = offset; + offset += argTabSize; + break; + case 0xFF: + return; + } + + /* + Here we have the following values: + + curOffs ... the code offset of the call + regMask ... mask of live pointer register variables + argMask ... bitmask of pushed pointer arguments + byrefRegMask ... byref qualifier for regMask + byrefArgMask ... byrer qualifier for argMask + */ + transition = new GcTransitionCall((int)curOffs, _header.EbpFrame, regMask, byrefRegMask); + AddNewTransition(transition); + + if (argCnt != 0) + { + do + { + val = _target.GCDecodeUnsigned(ref argOffset); + + uint stkOffs = val & ~byref_OFFSET_FLAG; + uint lowBit = val & byref_OFFSET_FLAG; + transition.PtrArgs.Add(new GcTransitionCall.PtrArg(stkOffs, lowBit)); + } + while (--argCnt > 0); + } + else + { + transition.ArgMask = argMask; + if (byrefArgMask != 0) + transition.IArgs = byrefArgMask; + } + } + } + + /// + /// based on GCDump::DumpGCTable + /// + private void SaveCallTransition(ref TargetPointer offset, uint val, uint curOffs, uint callRegMask, bool callPndTab, uint callPndTabCnt, uint callPndMask, uint lastSkip, ref uint imask) + { + uint iregMask, iargMask; + iregMask = imask & 0xF; + iargMask = imask >> 4; + + GcTransitionCall transition = new GcTransitionCall((int)curOffs, _header.EbpFrame, callRegMask, iregMask); + AddNewTransition(transition); + + if (callPndTab) + { + for (int i = 0; i < callPndTabCnt; i++) + { + uint pndOffs = _target.GCDecodeUnsigned(ref offset); + + uint stkOffs = val & ~byref_OFFSET_FLAG; + uint lowBit = val & byref_OFFSET_FLAG; + Console.WriteLine($"stkOffs: {stkOffs}, lowBit: {lowBit}"); + + transition.PtrArgs.Add(new GcTransitionCall.PtrArg(pndOffs, 0)); + } + } + else + { + if (callPndMask != 0) + transition.ArgMask = callPndMask; + if (iargMask != 0) + transition.IArgs = iargMask; + } + + Console.WriteLine($"lastSkip: {lastSkip}"); + imask /* = lastSkip */ = 0; + } + + private void GetTransitionsNoEbp(ref TargetPointer offset) + { + uint curOffs = 0; + uint lastSkip = 0; + uint imask = 0; + + for (; ; ) + { + uint val = _target.Read(offset++); + + if ((val & 0x80) == 0) + { + if ((val & 0x40) == 0) + { + if ((val & 0x20) == 0) + { + // push 000DDDDD push one item, 5-bit delta + curOffs += val & 0x1F; + AddNewTransition(new GcTransitionRegister((int)curOffs, RegMask.ESP, Action.PUSH)); + } + else + { + // push 00100000 [pushCount] ESP push multiple items + uint pushCount = _target.GCDecodeUnsigned(ref offset); + AddNewTransition(new GcTransitionRegister((int)curOffs, RegMask.ESP, Action.PUSH, false, false, (int)pushCount)); + } + } + else + { + uint popSize; + uint skip; + + if ((val & 0x3f) == 0) + { + // + // skip 01000000 [Delta] Skip arbitrary sized delta + // + skip = _target.GCDecodeUnsigned(ref offset); + curOffs += skip; + lastSkip = skip; + } + else + { + // pop 01CCDDDD pop CC items, 4-bit delta + popSize = (val & 0x30) >> 4; + skip = val & 0x0f; + curOffs += skip; + + if (popSize > 0) + { + AddNewTransition(new GcTransitionRegister((int)curOffs, RegMask.ESP, Action.POP, false, false, (int)popSize)); + } + else + lastSkip = skip; + } + } + } + else + { + uint callArgCnt = 0; + uint callRegMask; + bool callPndTab = false; + uint callPndMask = 0; + uint callPndTabCnt = 0, callPndTabSize = 0; + + switch ((val & 0x70) >> 4) + { + default: + // + // call 1PPPPPPP Call Pattern, P=[0..79] + // + CallPattern.DecodeCallPattern((val & 0x7f), out callArgCnt, out callRegMask, out callPndMask, out lastSkip); + curOffs += lastSkip; + SaveCallTransition(ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); + AddNewTransition(new StackDepthTransition((int)curOffs, (int)callArgCnt)); + break; + + case 5: + // + // call 1101RRRR DDCCCMMM Call RegMask=RRRR,ArgCnt=CCC, + // ArgMask=MMM Delta=commonDelta[DD] + // + callRegMask = val & 0xf; // EBP,EBX,ESI,EDI + val = _target.Read(offset++); + callPndMask = val & 0x7; + callArgCnt = (val >> 3) & 0x7; + lastSkip = CallPattern.CallCommonDelta[(int)(val >> 6)]; + curOffs += lastSkip; + SaveCallTransition(ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); + AddNewTransition(new StackDepthTransition((int)curOffs, (int)callArgCnt)); + break; + case 6: + // + // call 1110RRRR [ArgCnt] [ArgMask] + // Call ArgCnt,RegMask=RRR,ArgMask + // + callRegMask = val & 0xf; // EBP,EBX,ESI,EDI + callArgCnt = _target.GCDecodeUnsigned(ref offset); + callPndMask = _target.GCDecodeUnsigned(ref offset); + SaveCallTransition(ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); + AddNewTransition(new StackDepthTransition((int)curOffs, (int)callArgCnt)); + break; + case 7: + switch (val & 0x0C) + { + case 0x00: + // iptr 11110000 [IPtrMask] Arbitrary Interior Pointer Mask + imask = _target.GCDecodeUnsigned(ref offset); + AddNewTransition(new IPtrMask((int)curOffs, imask)); + break; + + case 0x04: + AddNewTransition(new CalleeSavedRegister((int)curOffs, TwoBitEncodingToRegMask((byte)(val & 0x3)))); + break; + + case 0x08: + val = _target.Read(offset++); + callRegMask = val & 0xF; + imask = val >> 4; + lastSkip = _target.Read(offset); + offset += 4; + curOffs += lastSkip; + callArgCnt = _target.Read(offset); + offset += 4; + callPndTabCnt = _target.Read(offset); + offset += 4; + callPndTabSize = _target.Read(offset); + offset += 4; + callPndTab = true; + SaveCallTransition(ref offset, val, curOffs, callRegMask, callPndTab, callPndTabCnt, callPndMask, lastSkip, ref imask); + AddNewTransition(new StackDepthTransition((int)curOffs, (int)callArgCnt)); + break; + case 0x0C: + return; + default: + throw new BadImageFormatException("Invalid GC encoding"); + } + break; + } + Console.WriteLine($"CallArgCount: {callArgCnt}"); + Console.WriteLine($"CallPndTabCnt: {callPndTabSize}"); + } + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfo.cs new file mode 100644 index 00000000000000..f58635691ac74b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfo.cs @@ -0,0 +1,246 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; + +[Flags] +public enum RegMask +{ + EAX = 0x1, + ECX = 0x2, + EDX = 0x4, + EBX = 0x8, + ESP = 0x10, + EBP = 0x20, + ESI = 0x40, + EDI = 0x80, + + NONE = 0x00, + RM_ALL = EAX | ECX | EDX | EBX | ESP | EBP | ESI | EDI, + RM_CALLEE_SAVED = EBP | EBX | ESI | EDI, + RM_CALLEE_TRASHED = RM_ALL & ~RM_CALLEE_SAVED, +} + +public record GCInfo +{ + private const uint MINIMUM_SUPPORTED_GCINFO_VERSION = 4; + private const uint MAXIMUM_SUPPORTED_GCINFO_VERSION = 4; + + private readonly Target _target; + + private readonly TargetPointer _gcInfoAddress; + private readonly uint _infoHdrSize; + + public uint RelativeOffset { get; set; } + public uint MethodSize { get; set; } + public InfoHdr Header { get; set; } + + public bool IsInProlog => PrologOffset != unchecked((uint)-1); + public uint PrologOffset { get; set; } = unchecked((uint)-1); + + public bool IsInEpilog => EpilogOffset != unchecked((uint)-1); + public uint EpilogOffset { get; set; } = unchecked((uint)-1); + + public uint RawStackSize { get; set; } + + + /// + /// Count of the callee-saved registers, excluding the frame pointer. + /// This does not include EBP for EBP-frames and double-aligned-frames. + /// + public uint SavedRegsCountExclFP { get; set; } + public RegMask SavedRegsMask { get; set; } = RegMask.NONE; + + /// + /// GC Transitions indexed by their relative offset. + /// + public ImmutableDictionary> Transitions => _transitions.Value; + private readonly Lazy>> _transitions = new(); + + /// + /// Number of bytes of stack space that has been pushed for arguments at the current RelativeOffset. + /// + public uint PushedArgSize => _pushedArgSize.Value; + private readonly Lazy _pushedArgSize; + + public GCInfo(Target target, TargetPointer gcInfoAddress, uint gcInfoVersion, uint relativeOffset) + { + if (gcInfoVersion < MINIMUM_SUPPORTED_GCINFO_VERSION) + { + throw new NotSupportedException($"GCInfo version {gcInfoVersion} is not supported. Minimum supported version is {MINIMUM_SUPPORTED_GCINFO_VERSION}."); + } + if (gcInfoVersion > MAXIMUM_SUPPORTED_GCINFO_VERSION) + { + throw new NotSupportedException($"GCInfo version {gcInfoVersion} is not supported. Maximum supported version is {MAXIMUM_SUPPORTED_GCINFO_VERSION}."); + } + + _target = target; + + _gcInfoAddress = gcInfoAddress; + TargetPointer offset = gcInfoAddress; + MethodSize = target.GCDecodeUnsigned(ref offset); + RelativeOffset = relativeOffset; + + Debug.Assert(relativeOffset >= 0); + Debug.Assert(relativeOffset <= MethodSize); + + Header = InfoHdr.DecodeHeader(target, ref offset, MethodSize); + _infoHdrSize = (uint)(offset.Value - gcInfoAddress.Value); + + // Check if we are in the prolog + if (relativeOffset < Header.PrologSize) + { + PrologOffset = relativeOffset; + } + + // Check if we are in an epilog + foreach (uint epilogStart in Header.Epilogs) + { + if (relativeOffset > epilogStart && relativeOffset < epilogStart + Header.EpilogSize) + { + EpilogOffset = relativeOffset - epilogStart; + } + } + + // Calculate raw stack size + uint frameDwordCount = Header.FrameSize; + RawStackSize = frameDwordCount * (uint)target.PointerSize; + + // Calculate callee saved regs + uint savedRegsCount = 0; + RegMask savedRegs = RegMask.NONE; + + if (Header.EdiSaved) + { + savedRegsCount++; + savedRegs |= RegMask.EDI; + } + if (Header.EsiSaved) + { + savedRegsCount++; + savedRegs |= RegMask.ESI; + } + if (Header.EbxSaved) + { + savedRegsCount++; + savedRegs |= RegMask.EBX; + } + if (Header.EbpSaved) + { + savedRegsCount++; + savedRegs |= RegMask.EBP; + } + + SavedRegsCountExclFP = savedRegsCount; + SavedRegsMask = savedRegs; + if (Header.EbpFrame || Header.DoubleAlign) + { + Debug.Assert(Header.EbpSaved); + SavedRegsCountExclFP--; + } + + // Lazily decode GC transitions. These values are not present in all Heap dumps. Only when they are required for stack walking. + // Therefore, we can only read them when they are used by the stack walker. + _transitions = new(DecodeTransitions); + + // Lazily calculate the pushed argument size. This forces the transitions to be decoded. + _pushedArgSize = new(CalculatePushedArgSize); + } + + private ImmutableDictionary> DecodeTransitions() + { + TargetPointer argTabPtr; + if (Header.HasArgTabOffset) + { + // The GCInfo has an explicit argument table offset + argTabPtr = _gcInfoAddress + _infoHdrSize + Header.ArgTabOffset; + } + else + { + // The GCInfo does not have an explicit argument table offset, we need to calculate it + // from the end of the header. The argument table is located after + // the NoGCRegions table, the UntrackedVariable table, and the FrameVariableLifetime table. + argTabPtr = _gcInfoAddress + _infoHdrSize; + + /* Skip over the no GC regions table */ + for (int i = 0; i < Header.NoGCRegionCount; i++) + { + // The NoGCRegion table has a variable size, each entry is 2 unsigned integers. + _target.GCDecodeUnsigned(ref argTabPtr); + _target.GCDecodeUnsigned(ref argTabPtr); + } + + /* Skip over the untracked frame variable table */ + for (int i = 0; i < Header.UntrackedCount; i++) + { + // The UntrackedVariable table has a variable size, each entry is 1 signed integer. + _target.GCDecodeSigned(ref argTabPtr); + } + + /* Skip over the frame variable lifetime table */ + for (int i = 0; i < Header.VarPtrTableSize; i++) + { + // The FrameVariableLifetime table has a variable size, each entry is 3 unsigned integer. + _target.GCDecodeUnsigned(ref argTabPtr); + _target.GCDecodeUnsigned(ref argTabPtr); + _target.GCDecodeUnsigned(ref argTabPtr); + } + } + GCArgTable argTable = new(_target, Header, argTabPtr); + return argTable.Transitions.ToImmutableDictionary(); + } + + private uint CalculatePushedArgSize() + { + int depth = 0; + foreach (int offset in Transitions.Keys.OrderBy(i => i)) + { + if (offset > RelativeOffset) + break; // calculate only to current offset + foreach (BaseGcTransition gcTransition in Transitions[offset]) + { + switch (gcTransition) + { + case GcTransitionRegister gcTransitionRegister: + if (gcTransitionRegister.IsLive == Action.PUSH) + { + depth += gcTransitionRegister.PushCountOrPopSize; + } + else if (gcTransitionRegister.IsLive == Action.POP) + { + depth -= gcTransitionRegister.PushCountOrPopSize; + } + break; + case StackDepthTransition stackDepthTransition: + depth += stackDepthTransition.StackDepthChange; + break; + case GcTransitionPointer gcTransitionPointer: + if (gcTransitionPointer.Act == Action.PUSH) + { + // FEATURE_EH_FUNCLETS implies fullArgInfo + // when there is fullArgInfo, the current depth is incremented by the number of pushed arguments + depth++; + } + else if (gcTransitionPointer.Act == Action.POP) + { + depth -= (int)gcTransitionPointer.ArgOffset; + } + break; + case IPtrMask: + case GcTransitionCall: + break; + default: + throw new InvalidOperationException("Unsupported gc transition type"); + } + } + } + + return (uint)(depth * _target.PointerSize); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfoTargetExtensions.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfoTargetExtensions.cs new file mode 100644 index 00000000000000..8634e0cde7afbe --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfoTargetExtensions.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; + +public static class GCInfoTargetExtensions +{ + /// + /// Based on src\inc\gcdecoder.cpp decodeUnsigned + /// + public static uint GCDecodeUnsigned(this Target target, ref TargetPointer src) + { + TargetPointer begin = src; + byte b = target.Read(src++); + uint value = b & 0x7Fu; + while ((b & 0x80) != 0) + { + if ((src - begin) > 5) + { + throw new InvalidOperationException("Invalid variable-length integer encoding."); + } + + b = target.Read(src++); + value <<= 7; + value += b & 0x7Fu; + } + + return value; + } + + /// + /// Based on src\inc\gcdecoder.cpp decodeSigned + /// + public static int GCDecodeSigned(this Target target, ref TargetPointer src) + { + TargetPointer begin = src; + byte b = target.Read(src++); + byte firstByte = b; + int value = b & 0x3F; + while ((b & 0x80) != 0) + { + if ((src - begin) > 5) + { + throw new InvalidOperationException("Invalid variable-length integer encoding."); + } + + b = target.Read(src++); + value <<= 7; + value += b & 0x7F; + } + + if ((firstByte & 0x40u) != 0) + { + value = -value; + } + + return value; + } + + /// + /// Based on src\inc\gcdecoder.cpp decodeUDelta + /// + public static uint GCDecodeUDelta(this Target target, ref TargetPointer src, uint lastValue) + { + uint delta = target.GCDecodeUnsigned(ref src); + return lastValue + delta; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCTransition.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCTransition.cs new file mode 100644 index 00000000000000..71e9003e997e6a --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCTransition.cs @@ -0,0 +1,312 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; + +public enum Action +{ + POP = 0x00, + PUSH = 0x01, + KILL = 0x02, + LIVE = 0x03, + DEAD = 0x04 +} + +public abstract class BaseGcTransition +{ + public int CodeOffset { get; set; } + + public BaseGcTransition() { } + + public BaseGcTransition(int codeOffset) + { + CodeOffset = codeOffset; + } +} + +public class CalleeSavedRegister : BaseGcTransition +{ + public RegMask Register { get; set; } + + public CalleeSavedRegister() { } + + public CalleeSavedRegister(int codeOffset, RegMask reg) + : base(codeOffset) + { + Register = reg; + } + + public override string ToString() + { + return $"thisptr in {Register}"; + } +} + +public class IPtrMask : BaseGcTransition +{ + public uint IMask { get; set; } + + public IPtrMask() { } + + public IPtrMask(int codeOffset, uint imask) + : base(codeOffset) + { + IMask = imask; + } + + public override string ToString() + { + return $"iptrMask: {IMask}"; + } +} + +public class GcTransitionRegister : BaseGcTransition +{ + public RegMask Register { get; set; } + public Action IsLive { get; set; } + public int PushCountOrPopSize { get; set; } + public bool IsThis { get; set; } + public bool Iptr { get; set; } + + public GcTransitionRegister() { } + + public GcTransitionRegister(int codeOffset, RegMask reg, Action isLive, bool isThis = false, bool iptr = false, int pushCountOrPopSize = 1) + : base(codeOffset) + { + Register = reg; + IsLive = isLive; + PushCountOrPopSize = pushCountOrPopSize; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + if (IsLive == Action.LIVE) + { + sb.Append($"reg {Register} becoming live"); + } + else if (IsLive == Action.DEAD) + { + sb.Append($"reg {Register} becoming dead"); + } + else + { + sb.Append((IsLive == Action.PUSH ? "push" : "pop") + $" {Register}"); + if (PushCountOrPopSize != 1) + sb.Append($" {PushCountOrPopSize}"); + } + + if (IsThis) + sb.Append(" 'this'"); + if (Iptr) + sb.Append(" (iptr)"); + + return sb.ToString(); + } +} + +public class GcTransitionPointer : BaseGcTransition +{ + private bool _isEbpFrame; + public uint ArgOffset { get; set; } + public uint ArgCount { get; set; } + public Action Act { get; set; } + public bool IsPtr { get; set; } + public bool IsThis { get; set; } + public bool Iptr { get; set; } + + public GcTransitionPointer() { } + + public GcTransitionPointer(int codeOffset, uint argOffs, uint argCnt, Action act, bool isEbpFrame, bool isThis = false, bool iptr = false, bool isPtr = true) + : base(codeOffset) + { + _isEbpFrame = isEbpFrame; + CodeOffset = codeOffset; + ArgOffset = argOffs; + ArgCount = argCnt; + Act = act; + IsPtr = isPtr; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + if (Act == Action.KILL) + { + sb.Append($"kill args {ArgOffset}"); + } + else + { + if (Act == Action.POP) + { + sb.Append($"pop "); + } + else + { + sb.Append($"push "); + } + if (IsPtr) + { + sb.Append($"{ArgOffset}"); + if (!_isEbpFrame) + { + sb.Append($" args ({ArgCount})"); + } + else if (Act == Action.POP) + { + sb.Append(" ptrs"); + } + + if (IsThis) + sb.Append(" 'this'"); + if (Iptr) + sb.Append(" (iptr)"); + } + else + { + sb.Append("non-pointer"); + sb.Append($" ({ArgCount})"); + } + } + + return sb.ToString(); + } +} + +public class GcTransitionCall : BaseGcTransition +{ + public struct CallRegister + { + public RegMask Register { get; set; } + public bool IsByRef { get; set; } + + public CallRegister(RegMask reg, bool isByRef) + { + Register = reg; + IsByRef = isByRef; + } + } + + public struct PtrArg + { + public uint StackOffset { get; set; } + public uint LowBit { get; set; } + + public PtrArg(uint stackOffset, uint lowBit) + { + StackOffset = stackOffset; + LowBit = lowBit; + } + } + + public List CallRegisters { get; set; } + public List PtrArgs { get; set; } + public uint ArgMask { get; set; } + public uint IArgs { get; set; } + + public GcTransitionCall(int codeOffset) + : base(codeOffset) + { + CallRegisters = new List(); + PtrArgs = new List(); + ArgMask = 0; + IArgs = 0; + } + + public GcTransitionCall(int codeOffset, bool isEbpFrame, uint regMask, uint byRefRegMask) + : base(codeOffset) + { + CallRegisters = new List(); + PtrArgs = new List(); + if ((regMask & 1) != 0) + { + RegMask reg = RegMask.EDI; + bool isByRef = (byRefRegMask & 1) != 0; + CallRegisters.Add(new CallRegister(reg, isByRef)); + } + if ((regMask & 2) != 0) + { + RegMask reg = RegMask.ESI; + bool isByRef = (byRefRegMask & 2) != 0; + CallRegisters.Add(new CallRegister(reg, isByRef)); + } + if ((regMask & 4) != 0) + { + RegMask reg = RegMask.EBX; + bool isByRef = (byRefRegMask & 4) != 0; + CallRegisters.Add(new CallRegister(reg, isByRef)); + } + if (!isEbpFrame) + { + if ((regMask & 8) != 0) + { + RegMask reg = RegMask.EBP; + CallRegisters.Add(new CallRegister(reg, false)); + } + } + ArgMask = 0; + IArgs = 0; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + sb.Append("call [ "); + foreach (CallRegister reg in CallRegisters) + { + sb.Append($"{reg.Register}"); + if (reg.IsByRef) + sb.Append("(byref)"); + sb.Append(' '); + } + + if (PtrArgs.Count > 0) + { + sb.Append(" ] ptrArgs=[ "); + foreach (PtrArg ptrArg in PtrArgs) + { + sb.Append($"{ptrArg.StackOffset}"); + if (ptrArg.LowBit != 0) + sb.Append('i'); + sb.Append(' '); + } + sb.Append(" ]"); + } + else + { + sb.Append($" ] argMask={ArgMask}"); + if (IArgs != 0) + sb.Append($" (iargs={IArgs})"); + } + + return sb.ToString(); + } +} + +public class StackDepthTransition : BaseGcTransition +{ + public int StackDepthChange { get; set; } + + public StackDepthTransition(int codeOffset) + : base(codeOffset) + { + } + + public StackDepthTransition(int codeOffset, int stackDepthChange) + : base(codeOffset) + { + StackDepthChange = stackDepthChange; + } + + public override string ToString() + { + return $"stack depth delta: {StackDepthChange}"; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/InfoHdr.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/InfoHdr.cs new file mode 100644 index 00000000000000..fd60ed855ef190 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/InfoHdr.cs @@ -0,0 +1,648 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +using System; +using System.Collections.Immutable; +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; + +/// +/// based on src\inc\gcinfotypes.h InfoHdrSmall +/// +public struct InfoHdr +{ + /* Fields set by the initial table encoding */ + public byte PrologSize { get; private set; } + public byte EpilogSize { get; private set; } + + public byte EpilogCount { get; private set; } + public bool EpilogAtEnd { get; private set; } + public bool EdiSaved { get; private set; } + public bool EsiSaved { get; private set; } + public bool EbxSaved { get; private set; } + public bool EbpSaved { get; private set; } + + public bool EbpFrame { get; private set; } + public bool Interruptible { get; private set; } + public bool DoubleAlign { get; private set; } + public bool Security { get; private set; } + public bool Handlers { get; private set; } + public bool LocalAlloc { get; private set; } + public bool EditAndContinue { get; private set; } + public bool VarArgs { get; private set; } + + public bool ProfCallbacks { get; private set; } + public bool GenericsContext { get; private set; } + public bool GenericsContextIsMethodDesc { get; private set; } + public ReturnKinds ReturnKind { get; private set; } + + public ushort ArgCount { get; private set; } + public uint FrameSize { get; private set; } + public uint UntrackedCount { get; private set; } + public uint VarPtrTableSize { get; private set; } + + /* Fields not set by the initial table encoding */ + public uint GsCookieOffset { get; private set; } = 0; + public uint SyncStartOffset { get; private set; } = 0; + public uint SyndEndOffset { get; private set; } = 0; + public uint RevPInvokeOffset { get; private set; } = INVALID_REV_PINVOKE_OFFSET; + public uint NoGCRegionCount { get; private set; } = 0; + + public bool HasArgTabOffset { get; private set; } + public uint ArgTabOffset { get; private set; } + public ImmutableArray Epilogs { get; private set; } + + #region Adjustments + + private const byte SET_FRAMESIZE_MAX = 7; + private const byte SET_ARGCOUNT_MAX = 8; + private const byte SET_PROLOGSIZE_MAX = 16; + private const byte SET_EPILOGSIZE_MAX = 10; + private const byte SET_EPILOGCNT_MAX = 4; + private const byte SET_UNTRACKED_MAX = 3; + private const byte SET_RET_KIND_MAX = 3; // 2 bits for ReturnKind + private const byte SET_NOGCREGIONS_MAX = 4; + private const byte ADJ_ENCODING_MAX = 0x7f; // Maximum valid encoding in a byte + // Also used to mask off next bit from each encoding byte. + private const byte MORE_BYTES_TO_FOLLOW = 0x80; // If the High-bit of a header or adjustment byte + // is set, then there are more adjustments to follow. + + /// + // Enum to define codes that are used to incrementally adjust the InfoHdr structure. + // First set of opcodes + /// + private enum InfoHdrAdjust : byte + { + + SET_FRAMESIZE = 0, // 0x00 + SET_ARGCOUNT = SET_FRAMESIZE + SET_FRAMESIZE_MAX + 1, // 0x08 + SET_PROLOGSIZE = SET_ARGCOUNT + SET_ARGCOUNT_MAX + 1, // 0x11 + SET_EPILOGSIZE = SET_PROLOGSIZE + SET_PROLOGSIZE_MAX + 1, // 0x22 + SET_EPILOGCNT = SET_EPILOGSIZE + SET_EPILOGSIZE_MAX + 1, // 0x2d + SET_UNTRACKED = SET_EPILOGCNT + (SET_EPILOGCNT_MAX + 1) * 2, // 0x37 + + FIRST_FLIP = SET_UNTRACKED + SET_UNTRACKED_MAX + 1, + + FLIP_EDI_SAVED = FIRST_FLIP, // 0x3b + FLIP_ESI_SAVED, // 0x3c + FLIP_EBX_SAVED, // 0x3d + FLIP_EBP_SAVED, // 0x3e + FLIP_EBP_FRAME, // 0x3f + FLIP_INTERRUPTIBLE, // 0x40 + FLIP_DOUBLE_ALIGN, // 0x41 + FLIP_SECURITY, // 0x42 + FLIP_HANDLERS, // 0x43 + FLIP_LOCALLOC, // 0x44 + FLIP_EDITnCONTINUE, // 0x45 + FLIP_VAR_PTR_TABLE_SZ, // 0x46 Flip whether a table-size exits after the header encoding + FFFF_UNTRACKED_CNT, // 0x47 There is a count (>SET_UNTRACKED_MAX) after the header encoding + FLIP_VARARGS, // 0x48 + FLIP_PROF_CALLBACKS, // 0x49 + FLIP_HAS_GS_COOKIE, // 0x4A - The offset of the GuardStack cookie follows after the header encoding + FLIP_SYNC, // 0x4B + FLIP_HAS_GENERICS_CONTEXT, // 0x4C + FLIP_GENERICS_CONTEXT_IS_METHODDESC, // 0x4D + FLIP_REV_PINVOKE_FRAME, // 0x4E + NEXT_OPCODE, // 0x4F -- see next Adjustment enumeration + NEXT_FOUR_START = 0x50, + NEXT_FOUR_FRAMESIZE = 0x50, + NEXT_FOUR_ARGCOUNT = 0x60, + NEXT_THREE_PROLOGSIZE = 0x70, + NEXT_THREE_EPILOGSIZE = 0x78 + }; + + /// + // Enum to define codes that are used to incrementally adjust the InfoHdr structure. + // Second set of opcodes, when first code is 0x4F + /// + private enum InfoHdrAdjust2 : uint + { + SET_RETURNKIND = 0, // 0x00-SET_RET_KIND_MAX Set ReturnKind to value + SET_NOGCREGIONS_CNT = SET_RETURNKIND + SET_RET_KIND_MAX + 1, // 0x04 + FFFF_NOGCREGION_CNT = SET_NOGCREGIONS_CNT + SET_NOGCREGIONS_MAX + 1 // 0x09 There is a count (>SET_NOGCREGIONS_MAX) after the header encoding + }; + + public enum ReturnKinds + { + RT_Scalar = 0, + RT_Object = 1, + RT_ByRef = 2, + RT_Unset = 3, // Encoding 3 means RT_Float on X86 + RT_Scalar_Obj = RT_Object << 2 | RT_Scalar, + RT_Scalar_ByRef = RT_ByRef << 2 | RT_Scalar, + + RT_Obj_Obj = RT_Object << 2 | RT_Object, + RT_Obj_ByRef = RT_ByRef << 2 | RT_Object, + + RT_ByRef_Obj = RT_Object << 2 | RT_ByRef, + RT_ByRef_ByRef = RT_ByRef << 2 | RT_ByRef, + + RT_Illegal = 0xFF + }; + + public static InfoHdr DecodeHeader(Target target, ref TargetPointer offset, uint codeLength) + { + byte nextByte = target.Read(offset++); + byte encoding = (byte)(nextByte & 0x7Fu); + + if (encoding < 0 || encoding >= INFO_HDR_TABLE.Length) + { + throw new InvalidOperationException("Table encoding is invalid."); + } + + InfoHdr infoHdr = INFO_HDR_TABLE[encoding]; + + while ((nextByte & MORE_BYTES_TO_FOLLOW) != 0) + { + nextByte = target.Read(offset++); + encoding = (byte)(nextByte & ADJ_ENCODING_MAX); + + if (encoding < (uint)InfoHdrAdjust.NEXT_FOUR_START) + { + if (encoding < (uint)InfoHdrAdjust.SET_ARGCOUNT) + { + infoHdr.FrameSize = (byte)(encoding - (uint)InfoHdrAdjust.SET_FRAMESIZE); + } + else if (encoding < (uint)InfoHdrAdjust.SET_PROLOGSIZE) + { + infoHdr.ArgCount = (byte)(encoding - (uint)InfoHdrAdjust.SET_ARGCOUNT); + } + else if (encoding < (uint)InfoHdrAdjust.SET_EPILOGSIZE) + { + infoHdr.PrologSize = (byte)(encoding - (uint)InfoHdrAdjust.SET_PROLOGSIZE); + } + else if (encoding < (uint)InfoHdrAdjust.SET_EPILOGCNT) + { + infoHdr.EpilogSize = (byte)(encoding - (uint)InfoHdrAdjust.SET_EPILOGSIZE); + } + else if (encoding < (uint)InfoHdrAdjust.SET_UNTRACKED) + { + infoHdr.EpilogCount = (byte)((encoding - (uint)InfoHdrAdjust.SET_EPILOGCNT) / 2); + infoHdr.EpilogAtEnd = ((encoding - (uint)InfoHdrAdjust.SET_EPILOGCNT) & 1) == 1 ? true : false; + Debug.Assert(!infoHdr.EpilogAtEnd || infoHdr.EpilogCount == 1); + } + else if (encoding < (uint)InfoHdrAdjust.FIRST_FLIP) + { + infoHdr.UntrackedCount = (byte)(encoding - (uint)InfoHdrAdjust.SET_UNTRACKED); + } + else + { + switch (encoding) + { + case (byte)InfoHdrAdjust.FLIP_EDI_SAVED: + infoHdr.EdiSaved = !infoHdr.EdiSaved; + break; + case (byte)InfoHdrAdjust.FLIP_ESI_SAVED: + infoHdr.EsiSaved = !infoHdr.EsiSaved; + break; + case (byte)InfoHdrAdjust.FLIP_EBX_SAVED: + infoHdr.EbxSaved = !infoHdr.EbxSaved; + break; + case (byte)InfoHdrAdjust.FLIP_EBP_SAVED: + infoHdr.EbpSaved = !infoHdr.EbpSaved; + break; + case (byte)InfoHdrAdjust.FLIP_EBP_FRAME: + infoHdr.EbpFrame = !infoHdr.EbpFrame; + break; + case (byte)InfoHdrAdjust.FLIP_INTERRUPTIBLE: + infoHdr.Interruptible = !infoHdr.Interruptible; + break; + case (byte)InfoHdrAdjust.FLIP_DOUBLE_ALIGN: + infoHdr.DoubleAlign = !infoHdr.DoubleAlign; + break; + case (byte)InfoHdrAdjust.FLIP_SECURITY: + infoHdr.Security = !infoHdr.Security; + break; + case (byte)InfoHdrAdjust.FLIP_HANDLERS: + infoHdr.Handlers = !infoHdr.Handlers; + break; + case (byte)InfoHdrAdjust.FLIP_LOCALLOC: + infoHdr.LocalAlloc = !infoHdr.LocalAlloc; + break; + case (byte)InfoHdrAdjust.FLIP_EDITnCONTINUE: + infoHdr.EditAndContinue = !infoHdr.EditAndContinue; + break; + case (byte)InfoHdrAdjust.FLIP_VAR_PTR_TABLE_SZ: + infoHdr.VarPtrTableSize ^= HAS_VARPTR; + break; + case (byte)InfoHdrAdjust.FFFF_UNTRACKED_CNT: + infoHdr.UntrackedCount = HAS_UNTRACKED; + break; + case (byte)InfoHdrAdjust.FLIP_VARARGS: + infoHdr.VarArgs = !infoHdr.VarArgs; + break; + case (byte)InfoHdrAdjust.FLIP_PROF_CALLBACKS: + infoHdr.ProfCallbacks = !infoHdr.ProfCallbacks; + break; + case (byte)InfoHdrAdjust.FLIP_HAS_GENERICS_CONTEXT: + infoHdr.GenericsContext = !infoHdr.GenericsContext; + break; + case (byte)InfoHdrAdjust.FLIP_GENERICS_CONTEXT_IS_METHODDESC: + infoHdr.GenericsContextIsMethodDesc = !infoHdr.GenericsContextIsMethodDesc; + break; + case (byte)InfoHdrAdjust.FLIP_HAS_GS_COOKIE: + infoHdr.GsCookieOffset ^= HAS_GS_COOKIE_OFFSET; + break; + case (byte)InfoHdrAdjust.FLIP_SYNC: + infoHdr.SyncStartOffset ^= HAS_SYNC_OFFSET; + break; + case (byte)InfoHdrAdjust.FLIP_REV_PINVOKE_FRAME: + infoHdr.RevPInvokeOffset ^= INVALID_REV_PINVOKE_OFFSET ^ HAS_REV_PINVOKE_FRAME_OFFSET; + break; + + case (byte)InfoHdrAdjust.NEXT_OPCODE: + nextByte = target.Read(offset++); + encoding = (byte)(nextByte & ADJ_ENCODING_MAX); + + // encoding here always corresponds to codes in InfoHdrAdjust2 set + if (encoding <= SET_RET_KIND_MAX) + { + infoHdr.ReturnKind = (ReturnKinds)encoding; + } + else if (encoding < (int)InfoHdrAdjust2.FFFF_NOGCREGION_CNT) + { + infoHdr.NoGCRegionCount = (uint)encoding - (uint)InfoHdrAdjust2.SET_NOGCREGIONS_CNT; + } + else if (encoding == (int)InfoHdrAdjust2.FFFF_NOGCREGION_CNT) + { + infoHdr.NoGCRegionCount = HAS_NOGCREGIONS; + } + else + { + throw new BadImageFormatException("Unexpected gcinfo header encoding"); + } + break; + default: + throw new BadImageFormatException("Unexpected gcinfo header encoding"); + } + } + } + else + { + byte lowBits; + switch (encoding >> 4) + { + case 5: + lowBits = (byte)(encoding & 0xf); + infoHdr.FrameSize <<= 4; + infoHdr.FrameSize += lowBits; + break; + case 6: + lowBits = (byte)(encoding & 0xf); + infoHdr.ArgCount <<= 4; + infoHdr.ArgCount += lowBits; + break; + case 7: + if ((encoding & 0x8) == 0) + { + lowBits = (byte)(encoding & 0x7); + infoHdr.PrologSize <<= 3; + infoHdr.PrologSize += lowBits; + } + else + { + lowBits = (byte)(encoding & 0x7); + infoHdr.EpilogSize <<= 3; + infoHdr.EpilogSize += lowBits; + } + break; + default: + throw new BadImageFormatException("Unexpected gcinfo header encoding"); + } + } + } + + if (infoHdr.UntrackedCount == HAS_UNTRACKED) + { + infoHdr.HasArgTabOffset = true; + infoHdr.UntrackedCount = target.GCDecodeUnsigned(ref offset); + } + if (infoHdr.VarPtrTableSize == HAS_VARPTR) + { + infoHdr.HasArgTabOffset = true; + infoHdr.VarPtrTableSize = target.GCDecodeUnsigned(ref offset); + } + if (infoHdr.GsCookieOffset == HAS_GS_COOKIE_OFFSET) + { + infoHdr.GsCookieOffset = target.GCDecodeUnsigned(ref offset); + } + if (infoHdr.SyncStartOffset == HAS_SYNC_OFFSET) + { + infoHdr.SyncStartOffset = target.GCDecodeUnsigned(ref offset); + infoHdr.SyndEndOffset = target.GCDecodeUnsigned(ref offset); + } + if (infoHdr.RevPInvokeOffset == HAS_REV_PINVOKE_FRAME_OFFSET) + { + infoHdr.RevPInvokeOffset = target.GCDecodeUnsigned(ref offset); + } + if (infoHdr.NoGCRegionCount == HAS_NOGCREGIONS) + { + infoHdr.HasArgTabOffset = true; + infoHdr.NoGCRegionCount = target.GCDecodeUnsigned(ref offset); + } + else if (infoHdr.NoGCRegionCount > 0) + { + infoHdr.HasArgTabOffset = true; + } + + ImmutableArray.Builder epilogsBuilder = ImmutableArray.CreateBuilder(); + if (infoHdr.EpilogCount > 1 || (infoHdr.EpilogCount != 0 && !infoHdr.EpilogAtEnd)) + { + uint offs = 0; + + for (int i = 0; i < infoHdr.EpilogCount; i++) + { + offs = target.GCDecodeUDelta(ref offset, offs); + epilogsBuilder.Add((int)offs); + } + } + else + { + if (infoHdr.EpilogCount != 0) + epilogsBuilder.Add((int)(codeLength - infoHdr.EpilogSize)); + } + infoHdr.Epilogs = epilogsBuilder.ToImmutable(); + + if (infoHdr.HasArgTabOffset) + { + infoHdr.ArgTabOffset = target.GCDecodeUnsigned(ref offset); + } + + /* Sanity Checks */ + Debug.Assert(infoHdr.PrologSize + (infoHdr.EpilogCount * infoHdr.EpilogSize) <= codeLength); + Debug.Assert(infoHdr.EpilogCount == 1 || !infoHdr.EpilogAtEnd); + + Debug.Assert(infoHdr.UntrackedCount <= infoHdr.ArgCount + infoHdr.FrameSize); + + Debug.Assert(infoHdr.EbpSaved || !(infoHdr.EbpFrame || infoHdr.DoubleAlign)); + Debug.Assert(!infoHdr.EbpFrame || !infoHdr.DoubleAlign); + Debug.Assert(infoHdr.EbpFrame || !infoHdr.Security); + Debug.Assert(infoHdr.EbpFrame || !infoHdr.Handlers); + Debug.Assert(infoHdr.EbpFrame || !infoHdr.LocalAlloc); + Debug.Assert(infoHdr.EbpFrame || !infoHdr.EditAndContinue); + + return infoHdr; + } + + #endregion + #region EncodingTable + + public const uint HAS_VARPTR = 0xFFFFFFFF; + public const uint HAS_UNTRACKED = 0xFFFFFFFF; + public const uint HAS_GS_COOKIE_OFFSET = 0xFFFFFFFF; + public const uint HAS_SYNC_OFFSET = 0xFFFFFFFF; + public const uint INVALID_REV_PINVOKE_OFFSET = unchecked((uint)-1); + public const uint HAS_REV_PINVOKE_FRAME_OFFSET = unchecked((uint)-2); + public const uint HAS_NOGCREGIONS = 0xFFFFFFFF; + private const uint YES = HAS_VARPTR; + + private InfoHdr( + byte prologSize, + byte epilogSize, + byte epilogCount, + byte epilogAtEnd, + byte ediSaved, + byte esiSaved, + byte ebxSaved, + byte ebpSaved, + byte ebpFrame, + byte interruptible, + byte doubleAlign, + byte security, + byte handlers, + byte localAlloc, + byte editAndContinue, + byte varArgs, + byte profCallbacks, + byte genericsContext, + byte genericsContextIsMethodDesc, + byte returnKind, + ushort argCount, + uint frameSize, + uint untrackedCount, + uint varPtrTableSize) + { + PrologSize = prologSize; + EpilogSize = epilogSize; + EpilogCount = epilogCount; + EpilogAtEnd = epilogAtEnd == 1; + EdiSaved = ediSaved == 1; + EsiSaved = esiSaved == 1; + EbxSaved = ebxSaved == 1; + EbpSaved = ebpSaved == 1; + + EbpFrame = ebpFrame == 1; + Interruptible = interruptible == 1; + DoubleAlign = doubleAlign == 1; + Security = security == 1; + Handlers = handlers == 1; + LocalAlloc = localAlloc == 1; + EditAndContinue = editAndContinue == 1; + VarArgs = varArgs == 1; + + ProfCallbacks = profCallbacks == 1; + GenericsContext = genericsContext == 1; + GenericsContextIsMethodDesc = genericsContextIsMethodDesc == 1; + ReturnKind = (ReturnKinds)returnKind; + + ArgCount = argCount; + FrameSize = frameSize; + UntrackedCount = untrackedCount; + VarPtrTableSize = varPtrTableSize; + + HasArgTabOffset = false; + ArgTabOffset = 0; + Epilogs = []; + } + + private static InfoHdr[] INFO_HDR_TABLE = + { + // 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 + new( 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 1139 00 + new( 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 128738 01 + new( 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 3696 02 + new( 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 402 03 + new( 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 4259 04 + new( 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0 ), // 3379 05 + new( 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0 ), // 2058 06 + new( 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0 ), // 728 07 + new( 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0 ), // 984 08 + new( 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0 ), // 606 09 + new( 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0 ), // 1110 0a + new( 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 0 ), // 414 0b + new( 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 1553 0c + new( 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, YES ), // 584 0d + new( 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 2182 0e + new( 1, 2, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 3445 0f + new( 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 1369 10 + new( 1, 2, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 515 11 + new( 1, 2, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 21127 12 + new( 1, 2, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 3517 13 + new( 1, 2, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 750 14 + new( 1, 4, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 1876 15 + new( 1, 4, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0 ), // 1665 16 + new( 1, 4, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0 ), // 729 17 + new( 1, 4, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0 ), // 484 18 + new( 1, 4, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 331 19 + new( 2, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 361 1a + new( 2, 3, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 964 1b + new( 2, 3, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 3713 1c + new( 2, 3, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 466 1d + new( 2, 3, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 1325 1e + new( 2, 3, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 712 1f + new( 2, 3, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 588 20 + new( 2, 3, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 20542 21 + new( 2, 3, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 3802 22 + new( 2, 3, 3, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 798 23 + new( 2, 5, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 1900 24 + new( 2, 5, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0 ), // 385 25 + new( 2, 5, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 1617 26 + new( 2, 5, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0 ), // 1743 27 + new( 2, 5, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0 ), // 909 28 + new( 2, 5, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0 ), // 602 29 + new( 2, 5, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0 ), // 352 2a + new( 2, 6, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 657 2b + new( 2, 7, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, YES ), // 1283 2c + new( 2, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 1286 2d + new( 3, 4, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 1495 2e + new( 3, 4, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 1989 2f + new( 3, 4, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 1154 30 + new( 3, 4, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 9300 31 + new( 3, 4, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 392 32 + new( 3, 4, 2, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 1720 33 + new( 3, 6, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 1246 34 + new( 3, 6, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0 ), // 800 35 + new( 3, 6, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 1179 36 + new( 3, 6, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0 ), // 1368 37 + new( 3, 6, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0 ), // 349 38 + new( 3, 6, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0 ), // 505 39 + new( 3, 6, 2, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 629 3a + new( 3, 8, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 2, YES ), // 365 3b + new( 4, 5, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 487 3c + new( 4, 5, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 1752 3d + new( 4, 5, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 1959 3e + new( 4, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 2436 3f + new( 4, 5, 2, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 861 40 + new( 4, 7, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 1459 41 + new( 4, 7, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0 ), // 950 42 + new( 4, 7, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 1491 43 + new( 4, 7, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0 ), // 879 44 + new( 4, 7, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0 ), // 408 45 + new( 5, 4, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 4870 46 + new( 5, 6, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 359 47 + new( 5, 6, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 ), // 915 48 + new( 5, 6, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0 ), // 412 49 + new( 5, 6, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 1288 4a + new( 5, 6, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 1591 4b + new( 5, 6, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, YES ), // 361 4c + new( 5, 6, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0 ), // 623 4d + new( 5, 8, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0 ), // 1239 4e + new( 6, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 457 4f + new( 6, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 606 50 + new( 6, 4, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, YES ), // 1073 51 + new( 6, 4, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, YES ), // 508 52 + new( 6, 6, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 330 53 + new( 6, 6, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 1709 54 + new( 6, 7, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 ), // 1164 55 + new( 7, 4, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // 556 56 + new( 7, 5, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, YES ), // 529 57 + new( 7, 5, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, YES ), // 1423 58 + new( 7, 8, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, YES ), // 2455 59 + new( 7, 8, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 ), // 956 5a + new( 7, 8, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, YES ), // 1399 5b + new( 7, 8, 2, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, YES ), // 587 5c + new( 7, 10, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 6, 1, YES ), // 743 5d + new( 7, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0 ), // 1004 5e + new( 7, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, YES ), // 487 5f + new( 7, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0 ), // 337 60 + new( 7, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, YES ), // 361 61 + new( 8, 3, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 ), // 560 62 + new( 8, 6, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 ), // 1377 63 + new( 9, 4, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 ), // 877 64 + new( 9, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 ), // 3041 65 + new( 9, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, YES ), // 349 66 + new( 10, 5, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0 ), // 2061 67 + new( 10, 5, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 ), // 577 68 + new( 11, 6, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0 ), // 1195 69 + new( 12, 5, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ), // 491 6a + new( 13, 8, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, YES ), // 627 6b + new( 13, 8, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0 ), // 1099 6c + new( 13, 10, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 6, 1, YES ), // 488 6d + new( 14, 7, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 574 6e + new( 16, 7, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, YES ), // 1281 6f + new( 16, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, YES ), // 1881 70 + new( 16, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 339 71 + new( 16, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0 ), // 2594 72 + new( 16, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0 ), // 339 73 + new( 16, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, YES ), // 2107 74 + new( 16, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, YES ), // 2372 75 + new( 16, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, YES ), // 1078 76 + new( 16, 7, 2, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, YES ), // 384 77 + new( 16, 9, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, YES ), // 1541 78 + new( 16, 9, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 4, 1, YES ), // 975 79 + new( 19, 7, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, YES ), // 546 7a + new( 24, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, YES ), // 675 7b + new( 45, 9, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ), // 902 7c + new( 51, 7, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, YES ), // 432 7d + new( 51, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, YES ), // 361 7e + new( 51, 7, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0 ), // 703 7f + }; + + #endregion +}; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs new file mode 100644 index 00000000000000..0b90ec428403fd --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs @@ -0,0 +1,954 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Linq; +using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; +using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86Context; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; + +public class X86Unwinder(Target target) +{ + private const byte X86_INSTR_INT3 = 0xCC; // int3 + private const byte X86_INSTR_POP_ECX = 0x59; // pop ecx + private const byte X86_INSTR_RET = 0xC2; // ret imm16 + private const byte X86_INSTR_JMP_NEAR_REL32 = 0xE9; // near jmp rel32 + private const byte X86_INSTR_PUSH_EAX = 0x50; // push eax + private const byte X86_INSTR_XOR = 0x33; // xor + private const byte X86_INSTR_NOP = 0x90; // nop + private const byte X86_INSTR_RETN = 0xC3; // ret + private const byte X86_INSTR_PUSH_EBP = 0x55; // push ebp + private const ushort X86_INSTR_W_MOV_EBP_ESP = 0xEC8B; // mov ebp, esp + private const byte X86_INSTR_CALL_REL32 = 0xE8; // call rel32 + private const ushort X86_INSTR_W_CALL_IND_IMM = 0x15FF; // call [addr32] + private const ushort X86_INSTR_w_JMP_FAR_IND_IMM = 0x25FF; // far jmp [addr32] + private const ushort X86_INSTR_w_TEST_ESP_EAX = 0x0485; // test [esp], eax + private const ushort X86_INSTR_w_LEA_ESP_EBP_BYTE_OFFSET = 0x658d; // lea esp, [ebp-bOffset] + private const ushort X86_INSTR_w_LEA_ESP_EBP_DWORD_OFFSET = 0xa58d; // lea esp, [ebp-dwOffset] + private const ushort X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX = 0x8485; // test [esp-dwOffset], eax + private const ushort X86_INSTR_w_LEA_EAX_ESP_BYTE_OFFSET = 0x448d; // lea eax, [esp-bOffset] + private const ushort X86_INSTR_w_LEA_EAX_ESP_DWORD_OFFSET = 0x848d; // lea eax, [esp-dwOffset] + + private readonly Target _target = target; + private readonly uint _pointerSize = (uint)target.PointerSize; + private readonly bool _updateAllRegs = true; + private readonly bool _unixX86ABI = target.Contracts.RuntimeInfo.GetTargetOperatingSystem() == RuntimeInfoOperatingSystem.Unix; + + private static readonly RegMask[] registerOrder = + [ + RegMask.EBP, // last register to be pushed + RegMask.EBX, + RegMask.ESI, + RegMask.EDI, // first register to be pushed + ]; + + #region Entrypoint + + // UnwindStackFrameX86 in src/coreclr/vm/gc_unwind_x86.inl + public bool Unwind(ref X86Context context) + { + IExecutionManager eman = _target.Contracts.ExecutionManager; + + if (eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh) + { + throw new InvalidOperationException("Unwind failed, unable to find code block for the instruction pointer."); + } + + eman.GetGCInfo(cbh, out TargetPointer gcInfoAddress, out uint gcInfoVersion); + uint relOffset = (uint)eman.GetRelativeOffset(cbh).Value; + TargetPointer methodStart = eman.GetStartAddress(cbh).AsTargetPointer; + TargetPointer funcletStart = eman.GetFuncletStartAddress(cbh).AsTargetPointer; + bool isFunclet = eman.IsFunclet(cbh); + + GCInfo gcInfo = new(_target, gcInfoAddress, gcInfoVersion, relOffset); + + if (gcInfo.IsInEpilog) + { + /* First, handle the epilog */ + TargetPointer epilogBase = methodStart + (gcInfo.RelativeOffset - gcInfo.EpilogOffset); + UnwindEpilog(ref context, gcInfo, epilogBase); + } + else if (!gcInfo.Header.EbpFrame && !gcInfo.Header.DoubleAlign) + { + /* Handle ESP frames */ + UnwindEspFrame(ref context, gcInfo, methodStart); + } + else + { + /* Now we know that we have an EBP frame */ + if (!UnwindEbpDoubleAlignFrame( + ref context, + gcInfo, + methodStart, + funcletStart, + isFunclet)) + { + return false; + } + } + + context.ContextFlags |= (uint)ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL; + return true; + } + + #endregion + #region Unwind Logic + + private void UnwindEpilog(ref X86Context context, GCInfo gcInfo, TargetPointer epilogBase) + { + Debug.Assert(gcInfo.IsInEpilog); + Debug.Assert(gcInfo.EpilogOffset > 0); + + if (gcInfo.Header.EbpFrame || gcInfo.Header.DoubleAlign) + { + UnwindEbpDoubleAlignFrameEpilog(ref context, gcInfo, epilogBase); + } + else + { + UnwindEspFrameEpilog(ref context, gcInfo, epilogBase); + } + + /* Now adjust stack pointer */ + context.Esp += ESPIncrementOnReturn(gcInfo); + } + + private void UnwindEbpDoubleAlignFrameEpilog(ref X86Context context, GCInfo gcInfo, TargetPointer epilogBase) + { + /* See how many instructions we have executed in the + epilog to determine which callee-saved registers + have already been popped */ + uint offset = 0; + + uint esp = context.Esp; + + bool needMovEspEbp = false; + + if (gcInfo.Header.DoubleAlign) + { + // add esp, RawStackSize + + if (!InstructionAlreadyExecuted(offset, gcInfo.EpilogOffset)) + { + esp += gcInfo.RawStackSize; + } + Debug.Assert(gcInfo.RawStackSize != 0); + offset = SKIP_ARITH_REG((int)gcInfo.RawStackSize, epilogBase, offset); + + // We also need "mov esp, ebp" after popping the callee-saved registers + needMovEspEbp = true; + } + else + { + bool needLea = false; + + if (gcInfo.Header.LocalAlloc) + { + // ESP may be variable if a localloc was actually executed. We will reset it. + // lea esp, [ebp-calleeSavedRegs] + needLea = true; + } + else if (gcInfo.SavedRegsCountExclFP == 0) + { + // We will just generate "mov esp, ebp" and be done with it. + if (gcInfo.RawStackSize != 0) + { + needMovEspEbp = true; + } + } + else if (gcInfo.RawStackSize == 0) + { + // do nothing before popping the callee-saved registers + } + else if (gcInfo.RawStackSize == _target.PointerSize && ReadByteAt(epilogBase) == X86_INSTR_POP_ECX) + { + // We may use "POP ecx" for doing "ADD ESP, 4", + // or we may not (in the case of JMP epilogs) + + // "pop ecx" will make ESP point to the callee-saved registers + if (!InstructionAlreadyExecuted(offset, gcInfo.EpilogOffset)) + { + esp += _pointerSize; + } + offset = SKIP_POP_REG(epilogBase, offset); + } + else + { + // We need to make ESP point to the callee-saved registers + // lea esp, [ebp-calleeSavedRegs] + + needLea = true; + } + + if (needLea) + { + // lea esp, [ebp-calleeSavedRegs] + + uint calleeSavedRegsSize = gcInfo.SavedRegsCountExclFP * _pointerSize; + + if (!InstructionAlreadyExecuted(offset, gcInfo.EpilogOffset)) + { + esp = context.Ebp - calleeSavedRegsSize; + } + + offset = SKIP_LEA_ESP_EBP(-(int)calleeSavedRegsSize, epilogBase, offset); + } + } + + foreach (RegMask regMask in registerOrder) + { + if (regMask == RegMask.EBP) + { + continue; // EBP is handled separately + } + + if (!gcInfo.SavedRegsMask.HasFlag(regMask)) + { + continue; + } + + if (!InstructionAlreadyExecuted(offset, gcInfo.EpilogOffset)) + { + if (_updateAllRegs) + { + TargetPointer regValueFromStack = _target.ReadPointer(esp); + SetRegValue(ref context, regMask, regValueFromStack); + } + esp += _pointerSize; + } + + offset = SKIP_POP_REG(epilogBase, offset); + } + + if (needMovEspEbp) + { + if (!InstructionAlreadyExecuted(offset, gcInfo.EpilogOffset)) + esp = context.Ebp; + + offset = SKIP_MOV_REG_REG(epilogBase, offset); + } + + // Have we executed the pop EBP? + if (!InstructionAlreadyExecuted(offset, gcInfo.EpilogOffset)) + { + context.Ebp = _target.Read(esp); + esp += _pointerSize; + } + _ = SKIP_POP_REG(epilogBase, offset); + + context.Eip = _target.Read(esp); + context.Esp = esp; + } + + private void UnwindEspFrameEpilog(ref X86Context context, GCInfo gcInfo, TargetPointer epilogBase) + { + Debug.Assert(gcInfo.IsInEpilog); + Debug.Assert(!gcInfo.Header.EbpFrame && !gcInfo.Header.DoubleAlign); + Debug.Assert(gcInfo.EpilogOffset > 0); + + uint offset = 0; + uint esp = context.Esp; + + if (gcInfo.RawStackSize != 0) + { + if (!InstructionAlreadyExecuted(offset, gcInfo.EpilogOffset)) + { + /* We have NOT executed the "ADD ESP, FrameSize", + so manually adjust stack pointer */ + esp += gcInfo.RawStackSize; + } + + // We have already popped off the frame (excluding the callee-saved registers) + if (ReadByteAt(epilogBase) == X86_INSTR_POP_ECX) + { + // We may use "POP ecx" for doing "ADD ESP, 4", + // or we may not (in the case of JMP epilogs) + Debug.Assert(gcInfo.RawStackSize == _target.PointerSize); + offset = SKIP_POP_REG(epilogBase, offset); + } + else + { + // "add esp, rawStkSize" + offset = SKIP_ARITH_REG((int)gcInfo.RawStackSize, epilogBase, offset); + } + } + + /* Remaining callee-saved regs are at ESP. Need to update + regsMask as well to exclude registers which have already been popped. */ + foreach (RegMask regMask in registerOrder) + { + if (!gcInfo.SavedRegsMask.HasFlag(regMask)) + continue; + + if (!InstructionAlreadyExecuted(offset, gcInfo.EpilogOffset)) + { + /* We have NOT yet popped off the register. + Get the value from the stack if needed */ + if (_updateAllRegs || regMask == RegMask.EBP) + { + TargetPointer regValueFromStack = _target.ReadPointer(esp); + SetRegValue(ref context, regMask, regValueFromStack); + } + esp += _pointerSize; + } + + offset = SKIP_POP_REG(epilogBase, offset); + } + + //CEE_JMP generates an epilog similar to a normal CEE_RET epilog except for the last instruction + Debug.Assert( + CheckInstrBytePattern((byte)(ReadByteAt(epilogBase + offset) & X86_INSTR_RET), X86_INSTR_RET, ReadByteAt(epilogBase + offset)) //ret + || CheckInstrBytePattern(ReadByteAt(epilogBase + offset), X86_INSTR_JMP_NEAR_REL32, ReadByteAt(epilogBase + offset)) //jmp ret32 + || CheckInstrWord(ReadShortAt(epilogBase + offset), X86_INSTR_w_JMP_FAR_IND_IMM)); //jmp [addr32] + + /* Finally we can set pPC */ + context.Eip = _target.Read(esp); + context.Esp = esp; + } + + private void UnwindEspFrame(ref X86Context context, GCInfo gcInfo, TargetPointer methodStart) + { + Debug.Assert(!gcInfo.Header.EbpFrame && !gcInfo.Header.DoubleAlign); + Debug.Assert(!gcInfo.IsInEpilog); + + Console.WriteLine(methodStart); + + uint esp = context.Esp; + + if (gcInfo.IsInProlog) + { + if (gcInfo.PrologOffset != 0) // Do nothing for the very start of the method + { + UnwindEspFrameProlog(ref context, gcInfo, methodStart); + esp = context.Esp; + } + } + else + { + /* We are past the prolog, ESP has been set above */ + + esp += gcInfo.PushedArgSize; + esp += gcInfo.RawStackSize; + + foreach (RegMask regMask in registerOrder) + { + if (!gcInfo.SavedRegsMask.HasFlag(regMask)) + continue; + + TargetPointer regValueFromStack = _target.ReadPointer(esp); + SetRegValue(ref context, regMask, regValueFromStack); + + // Pop the callee-saved registers + esp += _pointerSize; + } + } + + /* we can now set the (address of the) return address */ + context.Eip = _target.Read(esp); + context.Esp = esp + ESPIncrementOnReturn(gcInfo); + } + + private void UnwindEspFrameProlog(ref X86Context context, GCInfo gcInfo, TargetPointer methodStart) + { + Debug.Assert(gcInfo.IsInProlog); + Debug.Assert(!gcInfo.Header.EbpFrame && !gcInfo.Header.DoubleAlign); + + uint offset = 0; + + // If the first two instructions are 'nop, int3', then we will + // assume that is from a JitHalt operation and skip past it + if (ReadByteAt(methodStart) == X86_INSTR_NOP && ReadByteAt(methodStart + 1) == X86_INSTR_INT3) + { + offset += 2; + } + + uint curOffs = gcInfo.PrologOffset; + uint esp = context.Esp; + + RegMask regsMask = RegMask.NONE; + TargetPointer savedRegPtr = esp; + + // Find out how many callee-saved regs have already been pushed + foreach (RegMask regMask in registerOrder) + { + if (!gcInfo.SavedRegsMask.HasFlag(regMask)) + continue; + + if (InstructionAlreadyExecuted(offset, curOffs)) + { + esp += _pointerSize; + regsMask |= regMask; + } + + offset = SKIP_PUSH_REG(methodStart.Value, offset); + } + + if (gcInfo.RawStackSize != 0) + { + offset = SKIP_ALLOC_FRAME((int)gcInfo.RawStackSize, methodStart.Value, offset); + + // Note that this assumes that only the last instruction in SKIP_ALLOC_FRAME + // actually updates ESP + if (InstructionAlreadyExecuted(offset, curOffs + 1)) + { + savedRegPtr += gcInfo.RawStackSize; + esp += gcInfo.RawStackSize; + } + } + + + // Always restore EBP + if (regsMask.HasFlag(RegMask.EBP)) + { + context.Ebp = _target.Read(savedRegPtr); + savedRegPtr += _pointerSize; + } + + if (_updateAllRegs) + { + if (regsMask.HasFlag(RegMask.EBX)) + { + context.Ebx = _target.Read(savedRegPtr); + savedRegPtr += _pointerSize; + } + if (regsMask.HasFlag(RegMask.ESI)) + { + context.Esi = _target.Read(savedRegPtr); + savedRegPtr += _pointerSize; + } + if (regsMask.HasFlag(RegMask.EDI)) + { + context.Edi = _target.Read(savedRegPtr); + } + } + + context.Esp = esp; + } + + private bool UnwindEbpDoubleAlignFrame( + ref X86Context context, + GCInfo gcInfo, + TargetPointer methodStart, + TargetPointer funcletStart, + bool isFunclet) + { + Debug.Assert(gcInfo.Header.EbpFrame || gcInfo.Header.DoubleAlign); + + uint curEsp = context.Esp; + uint curEbp = context.Ebp; + + /* First check if we are in a filter (which is obviously after the prolog) */ + if (gcInfo.Header.Handlers && !gcInfo.IsInProlog) + { + TargetPointer baseSP; + + if (isFunclet) + { + baseSP = curEsp; + // Set baseSP as initial SP + baseSP += gcInfo.PushedArgSize; + + if (_unixX86ABI) + { + // 16-byte stack alignment padding (allocated in genFuncletProlog) + // Current funclet frame layout (see CodeGen::genFuncletProlog() and genFuncletEpilog()): + // prolog: sub esp, 12 + // epilog: add esp, 12 + // ret + // SP alignment padding should be added for all instructions except the first one and the last one. + // Epilog may not exist (unreachable), so we need to check the instruction code. + if (funcletStart != methodStart + gcInfo.RelativeOffset && ReadByteAt(methodStart + gcInfo.RelativeOffset) != X86_INSTR_RETN) + baseSP += 12; + } + + context.Eip = (uint)_target.ReadPointer(baseSP); + context.Esp = (uint)baseSP + _pointerSize; + return true; + } + + /* The cDAC only supports FEATURE_EH_FUNCLETS and therefore does not + support unwinding filters without funclets. */ + } + + // + // Prolog of an EBP method + // + + if (gcInfo.IsInProlog) + { + UnwindEbpDoubleAlignFrameProlog(ref context, gcInfo, methodStart.Value); + + /* Now adjust stack pointer. */ + + context.Esp += ESPIncrementOnReturn(gcInfo); + return true; + } + + if (_updateAllRegs) + { + // Get to the first callee-saved register + TargetPointer pSavedRegs = curEbp; + if (gcInfo.Header.DoubleAlign && (curEbp & 0x04) != 0) + pSavedRegs -= _pointerSize; + + foreach (RegMask regMask in registerOrder.Reverse()) + { + if (regMask == RegMask.EBP) continue; + + if (!gcInfo.SavedRegsMask.HasFlag(regMask)) continue; + + pSavedRegs -= _pointerSize; + TargetPointer regValueFromStack = _target.ReadPointer(pSavedRegs); + SetRegValue(ref context, regMask, regValueFromStack); + } + } + + /* The caller's ESP will be equal to EBP + retAddrSize + argSize. */ + context.Esp = curEbp + _pointerSize + ESPIncrementOnReturn(gcInfo); + + /* The caller's saved EIP is right after our EBP */ + context.Eip = (uint)_target.ReadPointer(curEbp + _pointerSize); + + /* The caller's saved EBP is pointed to by our EBP */ + context.Ebp = (uint)_target.ReadPointer(curEbp); + return true; + } + + private void UnwindEbpDoubleAlignFrameProlog(ref X86Context context, GCInfo gcInfo, TargetPointer methodStart) + { + Debug.Assert(gcInfo.IsInProlog); + Debug.Assert(gcInfo.Header.EbpFrame || gcInfo.Header.DoubleAlign); + + uint offset = 0; + + // If the first two instructions are 'nop, int3', then we will + // assume that is from a JitHalt operation and skip past it + if (ReadByteAt(methodStart) == X86_INSTR_NOP && ReadByteAt(methodStart + 1) == X86_INSTR_INT3) + { + offset += 2; + } + + /* Check for the case where EBP has not been updated yet. */ + uint curOffs = gcInfo.PrologOffset; + + // If we have still not executed "push ebp; mov ebp, esp", then we need to + // report the frame relative to ESP + + if (!InstructionAlreadyExecuted(offset + 1, curOffs)) + { + Debug.Assert(CheckInstrByte(ReadByteAt(methodStart + offset), X86_INSTR_PUSH_EBP) || + CheckInstrWord(ReadShortAt(methodStart + offset), X86_INSTR_W_MOV_EBP_ESP) || + CheckInstrByte(ReadByteAt(methodStart + offset), X86_INSTR_JMP_NEAR_REL32)); // a rejit jmp-stamp + + /* If we're past the "push ebp", adjust ESP to pop EBP off */ + if (curOffs == (offset + 1)) + context.Esp += _pointerSize; + + /* Stack pointer points to return address */ + context.Eip = (uint)_target.ReadPointer(context.Esp); + + /* EBP and callee-saved registers still have the correct value */ + return; + } + + // We are atleast after the "push ebp; mov ebp, esp" + offset = SKIP_MOV_REG_REG(methodStart, SKIP_PUSH_REG(methodStart, offset)); + + /* At this point, EBP has been set up. The caller's ESP and the return value + can be determined using EBP. Since we are still in the prolog, + we need to know our exact location to determine the callee-saved registers */ + uint curEBP = context.Ebp; + + if (_updateAllRegs) + { + TargetPointer pSavedRegs = curEBP; + + /* make sure that we align ESP just like the method's prolog did */ + if (gcInfo.Header.DoubleAlign) + { + // "and esp,-8" + offset = SKIP_ARITH_REG(-8, methodStart, offset); + if ((curEBP & 0x04) != 0) pSavedRegs--; + } + + /* Increment "offset" in steps to see which callee-saved + registers have been pushed already */ + + foreach (RegMask regMask in registerOrder.Reverse()) + { + if (regMask == RegMask.EBP) continue; + + if (!gcInfo.SavedRegsMask.HasFlag(regMask)) continue; + + if (InstructionAlreadyExecuted(offset, curOffs)) + { + pSavedRegs -= _pointerSize; + TargetPointer regValueFromStack = _target.ReadPointer(pSavedRegs); + SetRegValue(ref context, regMask, regValueFromStack); + } + + offset = SKIP_PUSH_REG(methodStart, offset); + } + } + + /* The caller's saved EBP is pointed to by our EBP */ + context.Ebp = (uint)_target.ReadPointer(curEBP); + context.Esp = (uint)_target.ReadPointer(curEBP + _pointerSize); + + /* Stack pointer points to return address */ + context.Eip = (uint)_target.ReadPointer(context.Esp); + } + + #endregion + #region Helper Methods + + // Use this to check if the instruction at offset "walkOffset" has already + // been executed + // "actualHaltOffset" is the offset when the code was suspended + // It is assumed that there is linear control flow from offset 0 to "actualHaltOffset". + // + // This has been factored out just so that the intent of the comparison + // is clear (compared to the opposite intent) + private static bool InstructionAlreadyExecuted(uint walkOffset, uint actualHaltOffset) + { + return walkOffset < actualHaltOffset; + } + + private uint ESPIncrementOnReturn(GCInfo gcInfo) + { + + uint stackParameterSize = gcInfo.Header.VarArgs ? 0 // varargs are caller-popped + : gcInfo.Header.ArgCount * _pointerSize; + return _pointerSize /* return address size */ + stackParameterSize; + } + + // skips past a "arith REG, IMM" + private uint SKIP_ARITH_REG(int val, TargetPointer baseAddress, uint offset) + { + uint delta = 0; + if (val != 0) + { + // Confirm that arith instruction is at the correct place + Debug.Assert(CheckInstrBytePattern((byte)(ReadByteAt(baseAddress + offset) & 0xFD), 0x81, ReadByteAt(baseAddress + offset))); + Debug.Assert(CheckInstrBytePattern((byte)(ReadByteAt(baseAddress + offset + 1) & 0xC0), 0xC0, ReadByteAt(baseAddress + offset + 1))); + + // only use DWORD form if needed + Debug.Assert(((ReadByteAt(baseAddress + offset) & 2) != 0 == CAN_COMPRESS(val)) || IsMarkerInstr(ReadByteAt(baseAddress + offset))); + + delta = 2u + (CAN_COMPRESS(val) ? 1u : 4u); + } + return offset + delta; + } + + private uint SKIP_POP_REG(TargetPointer baseAddress, uint offset) + { + // Confirm it is a pop instruction + Debug.Assert(CheckInstrBytePattern((byte)(ReadByteAt(baseAddress + offset) & 0xF8), 0x58, ReadByteAt(baseAddress + offset))); + + return offset + 1; + } + + private uint SKIP_PUSH_REG(TargetPointer baseAddress, uint offset) + { + // Confirm it is a push instruction + Debug.Assert(CheckInstrBytePattern((byte)(ReadByteAt(baseAddress + offset) & 0xF8), 0x50, ReadByteAt(baseAddress + offset))); + return offset + 1; + } + + private uint SKIP_LEA_ESP_EBP(int val, TargetPointer baseAddress, uint offset) + { + // Confirm it is the right instruction + // Note that only the first byte may have been stomped on by IsMarkerInstr() + // So we can check the second byte directly + Debug.Assert( + (CheckInstrWord(ReadShortAt(baseAddress), X86_INSTR_w_LEA_ESP_EBP_BYTE_OFFSET) && + (val == ReadSByteAt(baseAddress + 2)) && + CAN_COMPRESS(val)) + || + (CheckInstrWord(ReadShortAt(baseAddress), X86_INSTR_w_LEA_ESP_EBP_DWORD_OFFSET) && + (val == ReadIntAt(baseAddress + 2)) && + !CAN_COMPRESS(val)) + ); + + uint delta = 2u + (CAN_COMPRESS(val) ? 1u : 4u); + return offset + delta; + } + + private uint SKIP_MOV_REG_REG(TargetPointer baseAddress, uint offset) + { + // Confirm it is a move instruction + // Note that only the first byte may have been stomped on by IsMarkerInstr() + // So we can check the second byte directly + Debug.Assert( + CheckInstrBytePattern((byte)(ReadByteAt(baseAddress + offset) & 0xFD), 0x89, ReadByteAt(baseAddress + offset)) + && + (ReadByteAt(baseAddress + offset) & 0xC0) == 0xC0 + ); + return offset + 2; + } + + private uint SKIP_ALLOC_FRAME(int size, TargetPointer baseAddress, uint offset) + { + Debug.Assert(size != 0); + + if (size == _target.PointerSize) + { + // JIT emits "push eax" instead of "sub esp,4" + return SKIP_PUSH_REG(baseAddress, offset); + } + + const int STACK_PROBE_PAGE_SIZE_BYTES = 4096; + const int STACK_PROBE_BOUNDARY_THRESHOLD_BYTES = 1024; + + int lastProbedLocToFinalSp = size; + + if (size < STACK_PROBE_PAGE_SIZE_BYTES) + { + // sub esp, size + offset = SKIP_ARITH_REG(size, baseAddress, offset); + } + else + { + ushort wOpcode = ReadShortAt(baseAddress + offset); + + if (CheckInstrWord(wOpcode, X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX)) + { + // In .NET 5.0 and earlier for frames that have size smaller than 0x3000 bytes + // JIT emits one or two 'test eax, [esp-dwOffset]' instructions before adjusting the stack pointer. + Debug.Assert(size < 0x3000); + + // test eax, [esp-0x1000] + offset += 7; + lastProbedLocToFinalSp -= 0x1000; + + if (size >= 0x2000) + { + Debug.Assert(CheckInstrWord(ReadShortAt(baseAddress + offset), X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX)); + + //test eax, [esp-0x2000] + offset += 7; + lastProbedLocToFinalSp -= 0x1000; + } + + // sub esp, size + offset = SKIP_ARITH_REG(size, baseAddress, offset); + } + else + { + bool pushedStubParam = false; + + if (CheckInstrByte(ReadByteAt(baseAddress + offset), X86_INSTR_PUSH_EAX)) + { + // push eax + offset = SKIP_PUSH_REG(baseAddress, offset); + pushedStubParam = true; + } + + if (CheckInstrByte(ReadByteAt(baseAddress + offset), X86_INSTR_XOR)) + { + // In .NET Core 3.1 and earlier for frames that have size greater than or equal to 0x3000 bytes + // JIT emits the following loop. + Debug.Assert(size >= 0x3000); + + offset += 2; + // xor eax, eax 2 + // [nop] 0-3 + // loop: + // test [esp + eax], eax 3 + // sub eax, 0x1000 5 + // cmp eax, -size 5 + // jge loop 2 + + // R2R images that support ReJIT may have extra nops we need to skip over. + while (offset < 5) + { + if (CheckInstrByte(ReadByteAt(baseAddress + offset), X86_INSTR_NOP)) + { + offset++; + } + else + { + break; + } + } + + offset += 15; + + if (pushedStubParam) + { + // pop eax + offset = SKIP_POP_REG(baseAddress, offset); + } + + // sub esp, size + return SKIP_ARITH_REG(size, baseAddress, offset); + } + else + { + // In .NET 5.0 and later JIT emits a call to JIT_StackProbe helper. + + if (pushedStubParam) + { + // lea eax, [esp-size+4] + offset = SKIP_LEA_EAX_ESP(-size + 4, baseAddress, offset); + // call JIT_StackProbe + offset = SKIP_HELPER_CALL(baseAddress, offset); + // pop eax + offset = SKIP_POP_REG(baseAddress, offset); + // sub esp, size + return SKIP_ARITH_REG(size, baseAddress, offset); + } + else + { + // lea eax, [esp-size] + offset = SKIP_LEA_EAX_ESP(-size, baseAddress, offset); + // call JIT_StackProbe + offset = SKIP_HELPER_CALL(baseAddress, offset); + // mov esp, eax + return SKIP_MOV_REG_REG(baseAddress, offset); + } + } + } + } + + if (lastProbedLocToFinalSp + STACK_PROBE_BOUNDARY_THRESHOLD_BYTES > STACK_PROBE_PAGE_SIZE_BYTES) + { + Debug.Assert(CheckInstrWord(_target.Read(baseAddress + offset), X86_INSTR_w_TEST_ESP_EAX)); + + // test [esp], eax + offset += 3; + } + + return offset; + } + + private uint SKIP_LEA_EAX_ESP(int val, TargetPointer baseAddress, uint offset) + { + ushort wOpcode = ReadShortAt(baseAddress + offset); + if (CheckInstrWord(wOpcode, X86_INSTR_w_LEA_EAX_ESP_BYTE_OFFSET)) + { + Debug.Assert(val == _target.Read(baseAddress + offset + 3)); + Debug.Assert(CAN_COMPRESS(val)); + } + else + { + Debug.Assert(CheckInstrWord(wOpcode, X86_INSTR_w_LEA_EAX_ESP_DWORD_OFFSET)); + Debug.Assert(val == _target.Read(baseAddress + offset + 3)); + Debug.Assert(!CAN_COMPRESS(val)); + } + + uint delta = 3u + (CAN_COMPRESS(-val) ? 1u : 4u); + return offset + delta; + } + + private uint SKIP_HELPER_CALL(TargetPointer baseAddress, uint offset) + { + uint delta; + + if (CheckInstrByte(ReadByteAt(baseAddress + offset), X86_INSTR_CALL_REL32)) + { + delta = 5; + } + else + { + Debug.Assert(CheckInstrWord(_target.Read(baseAddress + offset), X86_INSTR_W_CALL_IND_IMM)); + delta = 6; + } + + return offset + delta; + } + + private static bool CAN_COMPRESS(int val) + { + return ((byte)val) == val; + } + + private static void SetRegValue(ref X86Context context, RegMask regMask, TargetPointer value) + { + uint regValue = (uint)value; + switch (regMask) + { + case RegMask.EAX: + context.Eax = regValue; + break; + case RegMask.EBX: + context.Ebx = regValue; + break; + case RegMask.ECX: + context.Ecx = regValue; + break; + case RegMask.EDX: + context.Edx = regValue; + break; + case RegMask.EBP: + context.Ebp = regValue; + break; + case RegMask.ESI: + context.Esi = regValue; + break; + case RegMask.EDI: + context.Edi = regValue; + break; + default: + throw new ArgumentException($"Unsupported register mask: {regMask}"); + } + } + + #endregion + + #region Verification Helpers + + /* Check if the given instruction opcode is the one we expect. + This is a "necessary" but not "sufficient" check as it ignores the check + if the instruction is one of our special markers (for debugging and GcStress) */ + private static bool CheckInstrByte(byte val, byte expectedValue) + { + return (val == expectedValue) || IsMarkerInstr(val); + } + + /* Similar to CheckInstrByte(). Use this to check a masked opcode (ignoring + optional bits in the opcode encoding). + valPattern is the masked out value. + expectedPattern is the mask value we expect. + val is the actual instruction opcode */ + private static bool CheckInstrBytePattern(byte valPattern, byte expectedPattern, byte val) + { + Debug.Assert((valPattern & val) == valPattern); + + return (valPattern == expectedPattern) || IsMarkerInstr(val); + } + + /* Similar to CheckInstrByte() */ + private static bool CheckInstrWord(ushort val, ushort expectedValue) + { + return (val == expectedValue) || IsMarkerInstr((byte)(val & 0xFF)); + } + + private static bool IsMarkerInstr(byte val) + { + return val == X86_INSTR_INT3; + } + + private sbyte ReadSByteAt(TargetPointer address) + { + return _target.Read(address); + } + + private byte ReadByteAt(TargetPointer address) + { + return _target.Read(address); + } + + private ushort ReadShortAt(TargetPointer address) + { + return _target.Read(address); + } + + private int ReadIntAt(TargetPointer address) + { + return _target.Read(address); + } + + #endregion +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs new file mode 100644 index 00000000000000..37d3e92a4f19ee --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs @@ -0,0 +1,256 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// X86-specific windows thread context. +/// +[StructLayout(LayoutKind.Explicit, Pack = 1)] +public struct X86Context : IPlatformContext +{ + [Flags] + public enum ContextFlagsValues : uint + { + CONTEXT_i386 = 0x00100000, + CONTEXT_CONTROL = CONTEXT_i386 | 0x1, + CONTEXT_INTEGER = CONTEXT_i386 | 0x2, + CONTEXT_SEGMENTS = CONTEXT_i386 | 0x4, + CONTEXT_FLOATING_POINT = CONTEXT_i386 | 0x8, + CONTEXT_DEBUG_REGISTERS = CONTEXT_i386 | 0x10, + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, + CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, + CONTEXT_XSTATE = CONTEXT_i386 | 0x40, + + // + // This flag is set by the unwinder if it has unwound to a call + // site, and cleared whenever it unwinds through a trap frame. + // It is used by language-specific exception handlers to help + // differentiate exception scopes during dispatching. + // + CONTEXT_UNWOUND_TO_CALL = 0x20000000, + CONTEXT_AREA_MASK = 0xFFFF, + } + + public readonly uint Size => 0x2cc; + public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public TargetPointer StackPointer + { + readonly get => new(Esp); + set => Esp = (uint)value.Value; + } + public TargetPointer InstructionPointer + { + readonly get => new(Eip); + set => Eip = (uint)value.Value; + } + public TargetPointer FramePointer + { + readonly get => new(Ebp); + set => Ebp = (uint)value.Value; + } + + public void Unwind(Target target) + { + X86Unwinder unwinder = new(target); + unwinder.Unwind(ref this); + } + + // Control flags + + [FieldOffset(0x0)] + public uint ContextFlags; + + #region Debug registers + + [Register(RegisterType.Debug)] + [FieldOffset(0x4)] + public uint Dr0; + + [Register(RegisterType.Debug)] + [FieldOffset(0x8)] + public uint Dr1; + + [Register(RegisterType.Debug)] + [FieldOffset(0xc)] + public uint Dr2; + + [Register(RegisterType.Debug)] + [FieldOffset(0x10)] + public uint Dr3; + + [Register(RegisterType.Debug)] + [FieldOffset(0x14)] + public uint Dr6; + + [Register(RegisterType.Debug)] + [FieldOffset(0x18)] + public uint Dr7; + + #endregion + + #region Floating point registers + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x1c)] + public uint ControlWord; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x20)] + public uint StatusWord; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x24)] + public uint TagWord; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x28)] + public uint ErrorOffset; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x2c)] + public uint ErrorSelector; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x30)] + public uint DataOffset; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x34)] + public uint DataSelector; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x38)] + public Float80 ST0; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x42)] + public Float80 ST1; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x4c)] + public Float80 ST2; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x56)] + public Float80 ST3; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x60)] + public Float80 ST4; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x6a)] + public Float80 ST5; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x74)] + public Float80 ST6; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x7e)] + public Float80 ST7; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x88)] + public uint Cr0NpxState; + + #endregion + + #region Segment Registers + + [Register(RegisterType.Segments)] + [FieldOffset(0x8c)] + public uint Gs; + + [Register(RegisterType.Segments)] + [FieldOffset(0x90)] + public uint Fs; + + [Register(RegisterType.Segments)] + [FieldOffset(0x94)] + public uint Es; + + [Register(RegisterType.Segments)] + [FieldOffset(0x98)] + public uint Ds; + + #endregion + + #region Integer registers + + [Register(RegisterType.General)] + [FieldOffset(0x9c)] + public uint Edi; + + [Register(RegisterType.General)] + [FieldOffset(0xa0)] + public uint Esi; + + [Register(RegisterType.General)] + [FieldOffset(0xa4)] + public uint Ebx; + + [Register(RegisterType.General)] + [FieldOffset(0xa8)] + public uint Edx; + + [Register(RegisterType.General)] + [FieldOffset(0xac)] + public uint Ecx; + + [Register(RegisterType.General)] + [FieldOffset(0xb0)] + public uint Eax; + + #endregion + + #region Control registers + + [Register(RegisterType.Control | RegisterType.FramePointer)] + [FieldOffset(0xb4)] + public uint Ebp; + + [Register(RegisterType.Control | RegisterType.ProgramCounter)] + [FieldOffset(0xb8)] + public uint Eip; + + [Register(RegisterType.Segments)] + [FieldOffset(0xbc)] + public uint Cs; + + [Register(RegisterType.General)] + [FieldOffset(0xc0)] + public uint EFlags; + + [Register(RegisterType.Control | RegisterType.StackPointer)] + [FieldOffset(0xc4)] + public uint Esp; + + [Register(RegisterType.Segments)] + [FieldOffset(0xc8)] + public uint Ss; + + #endregion + + [FieldOffset(0xcc)] + public unsafe fixed byte ExtendedRegisters[512]; +} + +/// +/// Float in X86-specific windows thread context. +/// +[StructLayout(LayoutKind.Explicit, Pack = 1)] +public readonly struct Float80 +{ + [FieldOffset(0x0)] + public readonly ulong Mantissa; + + [FieldOffset(0x8)] + public readonly ushort Exponent; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs index 53356b3bbf5a41..20b1faba88c531 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs @@ -7,7 +7,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; internal sealed class FrameIterator { - private enum FrameType + internal enum FrameType { Unknown, @@ -151,6 +151,8 @@ public static string GetFrameName(Target target, TargetPointer frameIdentifier) return frameType.ToString(); } + public FrameType GetCurrentFrameType() => GetFrameType(target, CurrentFrame.Identifier); + private static FrameType GetFrameType(Target target, TargetPointer frameIdentifier) { foreach (FrameType frameType in Enum.GetValues()) @@ -171,6 +173,7 @@ private IPlatformFrameHandler GetFrameHandler(IPlatformAgnosticContext context) { return context switch { + ContextHolder contextHolder => new X86FrameHandler(target, contextHolder), ContextHolder contextHolder => new AMD64FrameHandler(target, contextHolder), ContextHolder contextHolder => new ARM64FrameHandler(target, contextHolder), _ => throw new InvalidOperationException("Unsupported context type"), diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs new file mode 100644 index 00000000000000..337dcff688f60e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Data; +using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86Context; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class X86FrameHandler(Target target, ContextHolder contextHolder) : BaseFrameHandler(target, contextHolder), IPlatformFrameHandler +{ + private readonly ContextHolder _context = contextHolder; + + void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + if (frame.TargetContext is not TargetPointer targetContext) + { + throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); + } + _context.ReadFromAddress(_target, targetContext); + + // Clear the CONTEXT_XSTATE, since the X86Context contains just plain CONTEXT structure + // that does not support holding any extended state. + _context.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + } + + void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + { + // TODO(cdacX86): Implement handling for HijackFrame + throw new NotImplementedException(); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index 633283e91de529..2b1c42c1eae825 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -2,10 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; using System.Diagnostics.CodeAnalysis; using System.Diagnostics; using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -131,6 +132,11 @@ private void UpdateState(StackWalkData handle) } } + /// + /// If an explicit frame is allocated in a managed stack frame (e.g. an inlined pinvoke call), + /// we may have skipped an explicit frame. This function checks for them. + /// + /// true if there are skipped frames. private bool CheckForSkippedFrames(StackWalkData handle) { // ensure we can find the caller context diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunHeader.cs new file mode 100644 index 00000000000000..acc35d21d80859 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunHeader.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ReadyToRunHeader : IData +{ + static ReadyToRunHeader IData.Create(Target target, TargetPointer address) + => new ReadyToRunHeader(target, address); + + public ReadyToRunHeader(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ReadyToRunHeader); + + MajorVersion = target.Read(address + (ulong)type.Fields[nameof(MajorVersion)].Offset); + MinorVersion = target.Read(address + (ulong)type.Fields[nameof(MinorVersion)].Offset); + } + + public ushort MajorVersion { get; } + public ushort MinorVersion { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs index 01db9d76bd978b..ed932946d195de 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -10,15 +10,14 @@ internal sealed class ReadyToRunInfo : IData static ReadyToRunInfo IData.Create(Target target, TargetPointer address) => new ReadyToRunInfo(target, address); - private readonly Target _target; - public ReadyToRunInfo(Target target, TargetPointer address) { - _target = target; Target.TypeInfo type = target.GetTypeInfo(DataType.ReadyToRunInfo); CompositeInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(CompositeInfo)].Offset); + ReadyToRunHeader = target.ReadPointer(address + (ulong)type.Fields[nameof(ReadyToRunHeader)].Offset); + NumRuntimeFunctions = target.Read(address + (ulong)type.Fields[nameof(NumRuntimeFunctions)].Offset); RuntimeFunctions = NumRuntimeFunctions > 0 ? target.ReadPointer(address + (ulong)type.Fields[nameof(RuntimeFunctions)].Offset) @@ -34,10 +33,12 @@ public ReadyToRunInfo(Target target, TargetPointer address) // Map is from the composite info pointer (set to itself for non-multi-assembly composite images) EntryPointToMethodDescMap = CompositeInfo + (ulong)type.Fields[nameof(EntryPointToMethodDescMap)].Offset; - } + } internal TargetPointer CompositeInfo { get; } + public TargetPointer ReadyToRunHeader { get; } + public uint NumRuntimeFunctions { get; } public TargetPointer RuntimeFunctions { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs index 84ffab173b5f6c..35a90171ee6ba2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs @@ -12,6 +12,7 @@ public RealCodeHeader(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.RealCodeHeader); MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + GCInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCInfo)].Offset); // Only available if FEATURE_EH_FUNCLETS is enabled. if (type.Fields.ContainsKey(nameof(NumUnwindInfos))) @@ -23,6 +24,7 @@ public RealCodeHeader(Target target, TargetPointer address) } public TargetPointer MethodDesc { get; init; } + public TargetPointer GCInfo { get; init; } public uint? NumUnwindInfos { get; init; } public TargetPointer? UnwindInfos { get; init; } } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index 5a078fd5a8bca4..54d1e2c87949eb 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -1646,6 +1646,15 @@ int ISOSDacInterface.GetUsefulGlobals(DacpUsefulGlobalsData* data) catch (System.Exception ex) { hr = ex.HResult; + + // There are some scenarios where SOS can call GetUsefulGlobals before the globals are initialized, + // in these cases set the method table pointers to 0 and assert that the legacy DAC returns the same + // uninitialized values. + data->ArrayMethodTable = 0; + data->StringMethodTable = 0; + data->ObjectMethodTable = 0; + data->ExceptionMethodTable = 0; + data->FreeMethodTable = 0; } #if DEBUG @@ -1653,8 +1662,13 @@ int ISOSDacInterface.GetUsefulGlobals(DacpUsefulGlobalsData* data) { DacpUsefulGlobalsData dataLocal; int hrLocal = _legacyImpl.GetUsefulGlobals(&dataLocal); - Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); - if (hr == HResults.S_OK) + // SOS can call GetUsefulGlobals before the global pointers are initialized. + // In the DAC, this behavior depends on the compiler. + // MSVC builds: the DAC global table is a compile time constant and the DAC will return successfully. + // Clang builds: the DAC global table is constructed at runtime and the DAC will fail. + // Because of this variation, we cannot match the DAC behavior exactly. + // As long as the returned data matches, it should be fine. + if (hr == HResults.S_OK || hrLocal == HResults.S_OK) { Debug.Assert(data->ArrayMethodTable == dataLocal.ArrayMethodTable); Debug.Assert(data->StringMethodTable == dataLocal.StringMethodTable); diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs index e061b65972a482..d7017cfceab2d9 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -17,7 +17,7 @@ internal class ExecutionManager { public const ulong ExecutionManagerCodeRangeMapAddress = 0x000a_fff0; - const int RealCodeHeaderSize = 0x08; // must be big enough for the offsets of RealCodeHeader size in ExecutionManagerTestTarget, below + const int RealCodeHeaderSize = 0x10; // must be big enough for the offsets of RealCodeHeader size in ExecutionManagerTestTarget, below public struct AllocationRange { @@ -232,6 +232,7 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect Fields = [ new(nameof(Data.RealCodeHeader.MethodDesc), DataType.pointer), + new(nameof(Data.RealCodeHeader.GCInfo), DataType.pointer), ] }; @@ -240,6 +241,7 @@ public static RangeSectionMapTestBuilder CreateRangeSection(MockTarget.Architect DataType = DataType.ReadyToRunInfo, Fields = [ + new(nameof(Data.ReadyToRunInfo.ReadyToRunHeader), DataType.pointer), new(nameof(Data.ReadyToRunInfo.CompositeInfo), DataType.pointer), new(nameof(Data.ReadyToRunInfo.NumRuntimeFunctions), DataType.uint32), new(nameof(Data.ReadyToRunInfo.RuntimeFunctions), DataType.pointer), diff --git a/src/native/managed/compile-native.proj b/src/native/managed/compile-native.proj index 6f0f88a25b449d..ccdcbd57f31a3d 100644 --- a/src/native/managed/compile-native.proj +++ b/src/native/managed/compile-native.proj @@ -23,7 +23,7 @@ false false - false + false true false