Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions docs/design/datacontracts/ExecutionManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion docs/design/datacontracts/StackWalk.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Data.Module>(range.Data!.R2RModule);
Data.Module module = _target.ProcessedData.GetOrAdd<Data.Module>(range.Data.R2RModule);
Data.ReadyToRunInfo r2rInfo = _target.ProcessedData.GetOrAdd<Data.ReadyToRunInfo>(module.ReadyToRunInfo);
return r2rInfo.LoadedImageBase;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
rcj1 marked this conversation as resolved.
Resolved
}

TargetPointer unwindInfoBaseAddress = _executionManager.GetUnwindInfoBaseAddress(codeBlock);
RuntimeFunction runtimeFunction = _target.ProcessedData.GetOrAdd<RuntimeFunction>(runtimeFunctionAddress);
unwindDataAddress = unwindInfoBaseAddress + runtimeFunction.UnwindData;
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
20 changes: 6 additions & 14 deletions src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
51 changes: 35 additions & 16 deletions src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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()
{
Expand Down
24 changes: 7 additions & 17 deletions src/native/managed/cdac/tests/UnitTests/WasmUnwinderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,14 @@ private sealed class FakeExecutionManager : IExecutionManager
{
public Dictionary<ulong, (TargetPointer RuntimeFunction, TargetPointer BaseAddress)> 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)
Expand Down
Loading