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
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ private bool VirtualUnwindFull(
}

uint functionLength = headerWord & 0x3ffffu;
uint offsetInFunction = (controlPcRva - functionEntry.BeginAddress) / 4;
uint offsetInFunction = (controlPcRva - functionEntry.BeginAddress) / 2;

//
// Determine the number of epilog scope records and the maximum number
Expand Down Expand Up @@ -167,7 +167,7 @@ private bool VirtualUnwindFull(

TargetPointer unwindCodePtr = unwindDataPtr + 4 * epilogScopeCount;
TargetPointer unwindCodesEndPtr = unwindCodePtr + 4 * unwindWords;
uint skipWords = 0;
uint skipHalfwords = 0;

//
// If we're near the start of the function, and this function has a prolog,
Expand All @@ -176,24 +176,25 @@ private bool VirtualUnwindFull(
// need to skip some to account for partial execution of the prolog.
//
// N.B. As an optimization here, note that each byte of unwind codes can
// describe at most one 32-bit instruction. Thus, the largest prologue
// that could possibly be described by UnwindWords (which is 4 * the
// number of unwind code bytes) is 4 * UnwindWords words. If
// OffsetInFunction is larger than this value, it is guaranteed to be
// in the body of the function.
// describe at most one instruction, and each instruction can cover at
// most two halfwords. Thus, the largest prolog that could possibly be
// described by UnwindWords (which is 4 * the number of unwind code
// bytes) is 2 * (4 * UnwindWords) halfwords. If OffsetInFunction is
// larger than this value, it is guaranteed to be in the body of the
// function.
//
uint scopeSize;
if (offsetInFunction < 4 * unwindWords)
if (offsetInFunction < 2 * (4 * unwindWords))
{
scopeSize = ComputeScopeSize(unwindCodePtr, unwindCodesEndPtr);
scopeSize = 2 * ComputeScopeSize(unwindCodePtr, unwindCodesEndPtr, isEpilog: false);
Comment thread
rcj1 marked this conversation as resolved.

if (offsetInFunction < scopeSize)
{
skipWords = scopeSize - offsetInFunction;
skipHalfwords = scopeSize - offsetInFunction;
}
}

if (skipWords > 0)
if (skipHalfwords > 0)
{
// Found that we are in the middle of a prolog, no need to check for epilog scopes
}
Expand All @@ -215,9 +216,9 @@ private bool VirtualUnwindFull(
//
else if ((headerWord & (1 << 21)) != 0)
{
if (offsetInFunction + (4 * unwindWords - unwindIndex) >= functionLength)
if (offsetInFunction + 2 * (4 * unwindWords - unwindIndex) >= functionLength)
{
scopeSize = ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr);
scopeSize = 2 * ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr, isEpilog: true);
uint scopeStart = functionLength - scopeSize;

//
Expand All @@ -227,7 +228,7 @@ private bool VirtualUnwindFull(
if (offsetInFunction >= scopeStart)
{
unwindCodePtr += unwindIndex;
skipWords = offsetInFunction - scopeStart;
skipHalfwords = offsetInFunction - scopeStart;
}
}
}
Expand All @@ -254,14 +255,14 @@ private bool VirtualUnwindFull(
break;

unwindIndex = headerWord >> 22;
if (offsetInFunction < scopeStart + (4 * unwindWords - unwindIndex))
if (offsetInFunction < scopeStart + 2 * (4 * unwindWords - unwindIndex))
{
scopeSize = ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr);
scopeSize = 2 * ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr, isEpilog: true);

if (offsetInFunction < scopeStart + scopeSize)
{
unwindCodePtr += unwindIndex;
skipWords = offsetInFunction - scopeStart;
skipHalfwords = offsetInFunction - scopeStart;
break;
}
}
Expand All @@ -273,15 +274,15 @@ private bool VirtualUnwindFull(
// to skip.
//

while (unwindCodePtr < unwindCodesEndPtr && skipWords > 0)
while (unwindCodePtr < unwindCodesEndPtr && skipHalfwords > 0)
{
byte curCode = _target.Read<byte>(unwindCodePtr);
if (OpcodeIsEnd(curCode))
{
break;
}
unwindCodePtr += UnwindCodeSizeTable[curCode];
skipWords--;
skipHalfwords -= 2;
Comment thread
rcj1 marked this conversation as resolved.
}

//
Expand Down Expand Up @@ -318,7 +319,7 @@ private bool VirtualUnwindFull(
return true;
}

private uint ComputeScopeSize(TargetPointer unwindCodePtr, TargetPointer unwindCodesEndPtr)
private uint ComputeScopeSize(TargetPointer unwindCodePtr, TargetPointer unwindCodesEndPtr, bool isEpilog)
{
//
// Iterate through the unwind codes until we hit an end marker.
Expand All @@ -338,6 +339,9 @@ private uint ComputeScopeSize(TargetPointer unwindCodePtr, TargetPointer unwindC
scopeSize++;
}

if (isEpilog)
scopeSize++;

return scopeSize;
}

Expand Down
70 changes: 70 additions & 0 deletions src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,76 @@ public void ARMUnwind_CompactEpilog_RestoresCallerContext()
Assert.NotEqual(0u, context.ContextFlags & (uint)ARMContext.ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL);
}

[Fact]
public void RISCV64Unwind_SingleEpilogAtReturn_DoesNotReapplyStackAdjustment()
{
MockTarget.Architecture arch = new() { IsLittleEndian = true, Is64Bit = true };
TestPlaceholderTarget.Builder targetBuilder = new(arch);
TargetTestHelpers helpers = targetBuilder.MemoryBuilder.TargetTestHelpers;

const uint ImageBase = 0x1000_0000;
const uint FunctionStart = 0x1000;
const uint RuntimeFunctionAddress = 0x1800;
const uint UnwindDataRva = 0x2000;
const ulong CurrentSp = 0x3000;
const ulong ReturnAddress = 0x1234_5678_9abc_def0;

Layout<MockRuntimeFunction> runtimeFunctionLayout = MockRuntimeFunction.CreateLayout(arch, includeEndAddress: false);
byte[] runtimeFunctionData = new byte[runtimeFunctionLayout.Size];
MockRuntimeFunction runtimeFunction = runtimeFunctionLayout.Create(runtimeFunctionData, RuntimeFunctionAddress);
runtimeFunction.BeginAddress = FunctionStart;
runtimeFunction.UnwindData = UnwindDataRva;
targetBuilder.MemoryBuilder.AddHeapFragment(new MockMemorySpace.HeapFragment
{
Address = RuntimeFunctionAddress,
Data = runtimeFunctionData,
Name = "Runtime function",
});

byte[] unwindData = new byte[2 * sizeof(uint)];
uint unwindHeader =
8u | // FunctionLength = 8 halfwords
(1u << 21) | // E = 1: single epilog at the end of the function
(1u << 27); // CodeWords = 1
helpers.Write(unwindData.AsSpan(0, sizeof(uint)), unwindHeader);
unwindData[sizeof(uint)] = 0x01; // alloc_s 1: restore 16 bytes of stack
unwindData[sizeof(uint) + 1] = 0xe4; // end
targetBuilder.MemoryBuilder.AddHeapFragment(new MockMemorySpace.HeapFragment
{
Address = ImageBase + UnwindDataRva,
Data = unwindData,
Name = "Unwind data",
});

TargetCodePointer controlPc = new(ImageBase + FunctionStart + 12);
CodeBlockHandle codeBlock = new(new TargetPointer(controlPc.Value));
Mock<IExecutionManager> executionManager = new();
executionManager.Setup(e => e.GetCodeBlockHandle(controlPc)).Returns(codeBlock);
executionManager.Setup(e => e.GetUnwindInfoBaseAddress(codeBlock)).Returns(new TargetPointer(ImageBase));
executionManager.Setup(e => e.GetUnwindInfo(codeBlock)).Returns(new TargetPointer(RuntimeFunctionAddress));

TestPlaceholderTarget target = targetBuilder
.AddTypes(new Dictionary<DataType, Target.TypeInfo>
{
[DataType.RuntimeFunction] = TargetTestHelpers.CreateTypeInfo(runtimeFunctionLayout),
})
.AddMockContract(executionManager.Object)
.Build();

RISCV64Context context = new()
{
ContextFlags = (uint)RISCV64Context.ContextFlagsValues.CONTEXT_FULL,
Sp = CurrentSp,
Ra = ReturnAddress,
Pc = controlPc.Value,
};

Assert.True(new Contracts.StackWalkHelpers.RISCV64.RISCV64Unwinder(target).Unwind(ref context));
Assert.Equal(CurrentSp, context.Sp);
Assert.Equal(ReturnAddress, context.Pc);
Assert.NotEqual(0u, context.ContextFlags & (uint)RISCV64Context.ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL);
}

[Fact]
public void ARM64Unwind_SaveAnyPair_RestoresCallerContext()
{
Expand Down