diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 2bb03503096cc7..fa614820343781 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -32,11 +32,6 @@ public struct CodeBlockHandle // GetCodeBlockHandle. Other addresses are returned unchanged. TargetCodePointer GetDiagnosticCodeStartFromEntryPoint(TargetCodePointer entryPoint); - // Finds the unwind info for the function containing controlPC without resolving its method. - bool TryGetUnwindInfo( - TargetCodePointer controlPC, - out TargetPointer runtimeFunction, - out TargetPointer unwindInfoBaseAddress); // Gets the unwind info of the code block at the specified code pointer TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle); // Gets the base address the UnwindInfo of codeInfoHandle is relative to @@ -615,9 +610,7 @@ The `GetMethodDesc`, `GetStartAddress`, and `GetRelativeOffset` APIs extract fie } ``` -`IExecutionManager.TryGetUnwindInfo` maps a control PC directly to its Windows-style unwind data in the form of a `RUNTIME_FUNCTION` pointer and the base address to which that entry's relative fields apply. It finds the owning range and delegates to the same per-JIT-manager unwind lookup used by `GetUnwindInfo`, but does not resolve a method or create a `CodeBlockHandle`. It returns false, with both outputs null, when the address has no owning managed range or its code kind has no native unwind information. - -`IExecutionManager.GetUnwindInfo` gets the same platform-dependent `RUNTIME_FUNCTION` for an existing `CodeBlockHandle`. The ExecutionManager delegates to the JitManager implementations as the unwind infos are stored differently on jitted and R2R code. +`IExecutionManager.GetUnwindInfo` gets the platform-dependent `RUNTIME_FUNCTION` for an existing `CodeBlockHandle`. The ExecutionManager delegates to the JitManager implementations as the unwind infos 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. diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 1923e4ae9db961..638ea381c60921 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -486,7 +486,7 @@ Root-method frame identities have three forms: A funclet instead stores its establishing-frame pointer plus two, giving the identity low bits `10`. These tags are followed until the root-method frame is reached. The physical funclet frame supplies the function-local virtual IP, while the root frame supplies the identity used to find the controlling method's base virtual IP. cDAC requires each establishing-frame link to move toward the caller and bounds the traversal so corrupt target memory cannot create an unbounded walk. -**WASM R2R unwinding.** The virtual IP is the identity-derived method base plus twice the physical frame's stored function-local value. `WasmContext.Unwind` passes its current virtual IP as the control PC to `IExecutionManager.TryGetUnwindInfo`, reads the fixed frame size as a uint32 ULEB128 value from `unwindInfoBaseAddress + RuntimeFunction.UnwindData`, and adds that size to the physical frame base. A zero frame size terminates the R2R walk because it would make no progress; malformed uint32 ULEB128 data is invalid. +**WASM R2R unwinding.** The virtual IP is the identity-derived method base plus twice the physical frame's stored function-local value. `WasmContext.Unwind` resolves its current virtual IP through `IExecutionManager.GetCodeBlockHandle`, then uses `GetUnwindInfo` and `GetUnwindInfoBaseAddress` to locate the unwind data. It reads the fixed frame size as a uint32 ULEB128 value from `unwindInfoBaseAddress + RuntimeFunction.UnwindData` and adds that size to the physical frame base. A zero frame size terminates the R2R walk because it would make no progress; malformed uint32 ULEB128 data is invalid. For a managed caller, the resulting stack pointer identifies the caller's R2R frame record; its virtual IP and logical frame pointer are resolved as above. When the current frame has a positive odd reverse-P/Invoke identity, unwinding still preserves the advanced caller SP for the explicit `Frame` chain, but leaves IP and FP null because the caller is native. At a terminator or another non-R2R caller, the R2R context is cleared and walking continues through the explicit `Frame` or interpreter frame chain. 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 8053322ef84070..e138bf9ab5dc4c 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 @@ -117,7 +117,6 @@ public interface IExecutionManager : IContract TargetCodePointer GetDiagnosticCodeStartFromEntryPoint(TargetCodePointer entryPoint) => throw new NotImplementedException(); bool IsFunclet(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); bool IsFilterFunclet(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); - bool TryGetUnwindInfo(TargetCodePointer controlPC, out TargetPointer runtimeFunction, out TargetPointer unwindInfoBaseAddress) => throw new NotImplementedException(); TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => throw new NotImplementedException(); 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 4166213ddb1332..513728c9fc0776 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 @@ -544,50 +544,16 @@ TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle) return jitManager?.GetUnwindInfo(range, codeInfoHandle.Address.Value) ?? TargetPointer.Null; } - bool IExecutionManager.TryGetUnwindInfo( - TargetCodePointer controlPC, - out TargetPointer runtimeFunction, - out TargetPointer unwindInfoBaseAddress) - { - runtimeFunction = TargetPointer.Null; - unwindInfoBaseAddress = TargetPointer.Null; - - RangeSection range = RangeSection.Find( - _target, - _topRangeSectionMapAddress, - _rangeSectionMapLookup, - _virtualIPRangeListAddress, - controlPC); - if (range.Data is null) - return false; - - JitManager? jitManager = GetJitManager(range); - if (jitManager is null) - return false; - - runtimeFunction = jitManager.GetUnwindInfo(range, controlPC); - if (runtimeFunction == TargetPointer.Null) - return false; - - unwindInfoBaseAddress = GetUnwindInfoBaseAddress(range); - return true; - } - TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) { RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle); if (range.Data == null) throw new InvalidOperationException($"{nameof(RangeSection)} not found for {codeInfoHandle.Address}"); - return GetUnwindInfoBaseAddress(range); - } - - private TargetPointer GetUnwindInfoBaseAddress(RangeSection range) - { if (!range.IsVirtualIP) - return range.Data!.RangeBegin; + return range.Data.RangeBegin; - Data.Module module = _target.ProcessedData.GetOrAdd(range.Data!.R2RModule); + Data.Module module = _target.ProcessedData.GetOrAdd(range.Data.R2RModule); Data.ReadyToRunInfo r2rInfo = _target.ProcessedData.GetOrAdd(module.ReadyToRunInfo); return r2rInfo.LoadedImageBase; } 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 5854835e369be9..e1ba31d35be73f 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 @@ -25,7 +25,6 @@ internal ExecutionManager_1(Target target) public TargetCodePointer GetDiagnosticCodeStartFromEntryPoint(TargetCodePointer entryPoint) => _executionManagerCore.GetDiagnosticCodeStartFromEntryPoint(entryPoint); public bool IsFunclet(CodeBlockHandle codeInfoHandle) => _executionManagerCore.IsFunclet(codeInfoHandle); public bool IsFilterFunclet(CodeBlockHandle codeInfoHandle) => _executionManagerCore.IsFilterFunclet(codeInfoHandle); - public bool TryGetUnwindInfo(TargetCodePointer controlPC, out TargetPointer runtimeFunction, out TargetPointer unwindInfoBaseAddress) => _executionManagerCore.TryGetUnwindInfo(controlPC, out runtimeFunction, out unwindInfoBaseAddress); public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfo(codeInfoHandle); public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); public TargetPointer GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) => _executionManagerCore.GetDebugInfo(codeInfoHandle, out hasFlagByte); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs index 3dff927ed7cc07..731318099d568f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs @@ -49,14 +49,14 @@ public bool TryGetVirtualIPBase(uint frameIdentity, out ulong baseVirtualIP) public bool TryGetUnwindData(TargetCodePointer controlPC, out TargetPointer unwindDataAddress) { unwindDataAddress = TargetPointer.Null; - if (!_executionManager.TryGetUnwindInfo( - controlPC, - out TargetPointer runtimeFunctionAddress, - out TargetPointer unwindInfoBaseAddress)) - { + if (_executionManager.GetCodeBlockHandle(controlPC) is not CodeBlockHandle codeBlock) + return false; + + TargetPointer runtimeFunctionAddress = _executionManager.GetUnwindInfo(codeBlock); + if (runtimeFunctionAddress == TargetPointer.Null) return false; - } + TargetPointer unwindInfoBaseAddress = _executionManager.GetUnwindInfoBaseAddress(codeBlock); RuntimeFunction runtimeFunction = _target.ProcessedData.GetOrAdd(runtimeFunctionAddress); unwindDataAddress = unwindInfoBaseAddress + runtimeFunction.UnwindData; return true; diff --git a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs index 09559c004dd659..884d823223d8ba 100644 --- a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs @@ -493,18 +493,6 @@ public void GetMethodDesc_R2R_WasmVirtualIPRangeList_ResolvesCapturedShape() Assert.Equal(new TargetPointer(CapturedVirtualIP), em.GetStartAddress(handle.Value)); Assert.Equal(new TargetPointer(LoadedImageBase), em.GetUnwindInfoBaseAddress(handle.Value)); Assert.Equal(new TargetPointer(runtimeFunctionsAddress + (ulong)runtimeFunctionSize), em.GetUnwindInfo(handle.Value)); - Assert.True(em.TryGetUnwindInfo( - virtualIP, - out TargetPointer runtimeFunction, - out TargetPointer unwindInfoBaseAddress)); - Assert.Equal(new TargetPointer(runtimeFunctionsAddress + (ulong)runtimeFunctionSize), runtimeFunction); - Assert.Equal(new TargetPointer(LoadedImageBase), unwindInfoBaseAddress); - Assert.False(em.TryGetUnwindInfo( - new TargetCodePointer(0x7000_0000), - out runtimeFunction, - out unwindInfoBaseAddress)); - Assert.Equal(TargetPointer.Null, runtimeFunction); - Assert.Equal(TargetPointer.Null, unwindInfoBaseAddress); Assert.Equal(new TargetPointer(moduleAddress), em.FindReadyToRunModule(new TargetPointer(CapturedVirtualIP))); Assert.Equal(CodeKind.ReadyToRun, em.GetCodeKind(virtualIP)); } diff --git a/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs b/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs index db514222517802..2e91d0b2537d5f 100644 --- a/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs @@ -1570,21 +1570,13 @@ public InterpreterExecutionManager( public CodeKind GetCodeKind(TargetCodePointer codeAddress) => _interpreterIps.Contains(codeAddress.Value) ? CodeKind.Interpreter : default; - public bool TryGetUnwindInfo( - TargetCodePointer controlPC, - out TargetPointer runtimeFunction, - out TargetPointer unwindInfoBaseAddress) - { - if ((_managedIps?.Contains(controlPC.Value) ?? false) && _unwindInfo is not null) - { - (runtimeFunction, unwindInfoBaseAddress) = _unwindInfo(); - return runtimeFunction != TargetPointer.Null; - } + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) + => (_managedIps?.Contains(codeInfoHandle.Address.Value) ?? false) && _unwindInfo is not null + ? _unwindInfo().RuntimeFunction + : TargetPointer.Null; - runtimeFunction = TargetPointer.Null; - unwindInfoBaseAddress = TargetPointer.Null; - return false; - } + public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) + => _unwindInfo?.Invoke().BaseAddress ?? TargetPointer.Null; } private static IExecutionManager CreateInterpreterExecutionManager( diff --git a/src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs b/src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs index 95399213abf502..0c258341f46235 100644 --- a/src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs @@ -36,22 +36,12 @@ private sealed class FakeExecutionManager : IExecutionManager public TargetPointer RuntimeFunction { get; set; } public TargetPointer UnwindInfoBaseAddress { get; set; } - public bool TryGetUnwindInfo( - TargetCodePointer controlPC, - out TargetPointer runtimeFunction, - out TargetPointer unwindInfoBaseAddress) - { - if (controlPC == ControlPC && RuntimeFunction != TargetPointer.Null) - { - runtimeFunction = RuntimeFunction; - unwindInfoBaseAddress = UnwindInfoBaseAddress; - return true; - } - - runtimeFunction = TargetPointer.Null; - unwindInfoBaseAddress = TargetPointer.Null; - return false; - } + public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) + => ip == ControlPC ? new CodeBlockHandle(ip.AsTargetPointer) : null; + + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) => RuntimeFunction; + + public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => UnwindInfoBaseAddress; } // Builds a target whose FunctionTableIndexRangeList global points at a *slot* (pointer-to-pointer), @@ -183,6 +173,35 @@ public void TryGetUnwindData_ReturnsImageBasePlusUnwindData() Assert.Equal(LoadedImageBase + FunctionUnwindData, unwindData.Value); } + [Theory] + [InlineData(0x102u, 0x40u)] + [InlineData(0x122u, 0x80u)] + public void TryGetUnwindData_ExecutionManager_ResolvesRootAndFunclet(uint offset, uint expectedUnwindData) + { + const ulong MethodDescAddress = 0x0101_aaa0; + const ulong JitManagerAddress = 0x000b_ff00; + MockExecutionManagerBuilder emBuilder = new( + "c1", WasmArch, MockExecutionManagerBuilder.DefaultAllocationRange, isWasm: true); + var virtualIPRange = emBuilder.AllocateJittedCodeRange(SyntheticMinVirtualIP, 0x400); + MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([0x100, 0x8000_0120, 0x180], []); + r2rInfo.MinVirtualIP = SyntheticMinVirtualIP; + r2rInfo.LoadedImageBase = LoadedImageBase; + emBuilder.SetRuntimeFunctionUnwindData(r2rInfo, 0, 0x40); + emBuilder.SetRuntimeFunctionUnwindData(r2rInfo, 1, 0x80); + new MockHashMapBuilder(emBuilder.Builder).PopulatePtrMap( + r2rInfo.EntryPointToMethodDescMapAddress, + [(SyntheticMinVirtualIP + FunctionBeginAddress, MethodDescAddress)]); + MockLoaderModule module = emBuilder.AddReadyToRunModule(r2rInfo.Address); + emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, module.Address); + Target target = ExecutionManager.ExecutionManagerTests.CreateTarget( + emBuilder, targetArchitecture: RuntimeInfoArchitecture.Wasm); + WasmR2RInfo info = new(target); + + Assert.True(info.TryGetUnwindData( + new TargetCodePointer(SyntheticMinVirtualIP + offset), out TargetPointer unwindData)); + Assert.Equal(new TargetPointer(LoadedImageBase + expectedUnwindData), unwindData); + } + [Fact] public void TryGetVirtualIPBase_IndexNotInAnySection_ReturnsFalse() { diff --git a/src/native/managed/cdac/tests/UnitTests/WasmUnwinderTests.cs b/src/native/managed/cdac/tests/UnitTests/WasmUnwinderTests.cs index 532f67ee0e6130..6fb99cb71f4ce0 100644 --- a/src/native/managed/cdac/tests/UnitTests/WasmUnwinderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/WasmUnwinderTests.cs @@ -49,24 +49,14 @@ private sealed class FakeExecutionManager : IExecutionManager { public Dictionary UnwindInfo { get; } = new(); - public bool TryGetUnwindInfo( - TargetCodePointer controlPC, - out TargetPointer runtimeFunction, - out TargetPointer unwindInfoBaseAddress) - { - if (UnwindInfo.TryGetValue( - controlPC.Value, - out (TargetPointer RuntimeFunction, TargetPointer BaseAddress) info)) - { - runtimeFunction = info.RuntimeFunction; - unwindInfoBaseAddress = info.BaseAddress; - return true; - } + public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) + => UnwindInfo.ContainsKey(ip.Value) ? new CodeBlockHandle(ip.AsTargetPointer) : null; - runtimeFunction = TargetPointer.Null; - unwindInfoBaseAddress = TargetPointer.Null; - return false; - } + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle) + => UnwindInfo[codeInfoHandle.Address.Value].RuntimeFunction; + + public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) + => UnwindInfo[codeInfoHandle.Address.Value].BaseAddress; } private static TestPlaceholderTarget CreateTarget(MockMemorySpace.HeapFragment[] fragments)